From 62c249015d3f39a0ca67c026cdf091b6c4040153 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 26 May 2024 10:50:35 +0400 Subject: [PATCH 001/225] 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 c68aa80f1..76a03ceef 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit c68aa80f10b2953f2d32572e9ae1a5c29cfbfe78 +Subproject commit 76a03ceef98586ba148260f2f542ef67228f6e0e From 0033ad749f5fe9b2e8d716bf593496696167eb6f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 27 May 2024 07:48:16 +0400 Subject: [PATCH 002/225] 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 76a03ceef..bd2d2fe2f 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 76a03ceef98586ba148260f2f542ef67228f6e0e +Subproject commit bd2d2fe2fb86d3c54d8e2151764be340f8c5809e From daf30dcab876830af7c9a8601004ec4c54405ee5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 27 May 2024 13:33:25 +0400 Subject: [PATCH 003/225] Fix jump to last topic in case of unread topic click. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 542d41118..cdf2fef0d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1330,8 +1330,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { const auto selectedTopicJump = selected && selected->lookupIsInTopicJump( local.x(), - mouseY - offset - selected->top()) - && _controller->adaptive().isOneColumn(); + mouseY - offset - selected->top()); if (_collapsedSelected != collapsedSelected || _selected != selected || _selectedTopicJump != selectedTopicJump) { @@ -1373,8 +1372,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { const auto selectedTopicJump = (filteredSelected >= 0) && _filterResults[filteredSelected].row->lookupIsInTopicJump( local.x(), - mouseY - skip - _filterResults[filteredSelected].top) - && _controller->adaptive().isOneColumn(); + mouseY - skip - _filterResults[filteredSelected].top); if (_filteredSelected != filteredSelected || _selectedTopicJump != selectedTopicJump) { updateSelectedRow(); From ad6e34f3a43664a00e3e6829185b5b97e357a6fd Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 27 May 2024 13:59:05 +0400 Subject: [PATCH 004/225] 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 bd2d2fe2f..064ff7caf 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit bd2d2fe2fb86d3c54d8e2151764be340f8c5809e +Subproject commit 064ff7cafea2629c3a7162fb954fc597e0eec652 From 7357b40ba17d84725601462b2952fe3c683aac06 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 27 May 2024 17:54:03 +0400 Subject: [PATCH 005/225] Fix build with updated lib_ui. --- .../view/history_view_context_menu.cpp | 38 +------------------ .../history/view/history_view_context_menu.h | 2 - .../settings/business/settings_chat_intro.cpp | 2 +- .../business/settings_quick_replies.cpp | 3 +- 4 files changed, 3 insertions(+), 42 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index bb74d7311..dcebb2628 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1068,7 +1068,7 @@ void EditTagBox( } }, field->lifetime()); - AddLengthLimitLabel(field, kTagNameLimit); + Ui::AddLengthLimitLabel(field, kTagNameLimit); const auto save = [=] { const auto text = field->getLastText(); @@ -1828,40 +1828,4 @@ bool ItemHasTtl(HistoryItem *item) { : false; } -void AddLengthLimitLabel(not_null field, int limit) { - struct State { - rpl::variable length; - }; - const auto state = field->lifetime().make_state(); - state->length = rpl::single( - rpl::empty - ) | rpl::then(field->changes()) | rpl::map([=] { - return int(field->getLastText().size()); - }); - auto warningText = state->length.value() | rpl::map([=](int count) { - const auto threshold = std::min(limit / 2, 9); - const auto left = limit - count; - return (left < threshold) ? QString::number(left) : QString(); - }); - const auto warning = Ui::CreateChild( - field.get(), - std::move(warningText), - st::editTagLimit); - state->length.value() | rpl::map( - rpl::mappers::_1 > limit - ) | rpl::start_with_next([=](bool exceeded) { - warning->setTextColorOverride(exceeded - ? st::attentionButtonFg->c - : std::optional()); - }, warning->lifetime()); - rpl::combine( - field->sizeValue(), - warning->sizeValue() - ) | rpl::start_with_next([=] { - warning->moveToRight(0, 0); - }, warning->lifetime()); - warning->setAttribute(Qt::WA_TransparentForMouseEvents); - -} - } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index 8582a57fb..8f00f4da8 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -123,6 +123,4 @@ void AddEmojiPacksAction( [[nodiscard]] bool ItemHasTtl(HistoryItem *item); -void AddLengthLimitLabel(not_null field, int limit); - } // namespace HistoryView diff --git a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp index b8a7607f6..b9fa7c68c 100644 --- a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp +++ b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp @@ -164,7 +164,7 @@ private: current), st::settingsChatIntroFieldMargins); field->setMaxLength(limit); - AddLengthLimitLabel(field, limit); + Ui::AddLengthLimitLabel(field, limit); return field; } diff --git a/Telegram/SourceFiles/settings/business/settings_quick_replies.cpp b/Telegram/SourceFiles/settings/business/settings_quick_replies.cpp index 57e74cf25..941409f98 100644 --- a/Telegram/SourceFiles/settings/business/settings_quick_replies.cpp +++ b/Telegram/SourceFiles/settings/business/settings_quick_replies.cpp @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "data/business/data_shortcut_messages.h" #include "data/data_session.h" -#include "history/view/history_view_context_menu.h" // AddLengthLimitLabel. #include "lang/lang_keys.h" #include "main/main_account.h" #include "main/main_session.h" @@ -214,7 +213,7 @@ void EditShortcutNameBox( field->selectAll(); field->setMaxLength(kShortcutLimit * 2); - HistoryView::AddLengthLimitLabel(field, kShortcutLimit); + Ui::AddLengthLimitLabel(field, kShortcutLimit); const auto callback = [=] { const auto name = field->getLastText().trimmed(); From a43143d01cbda44b2a76582b72c1629992b6a213 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 27 May 2024 17:53:54 +0400 Subject: [PATCH 006/225] Separate ForceDisable from LastCheckCrashed. --- Telegram/SourceFiles/core/application.cpp | 5 ++--- Telegram/SourceFiles/core/core_settings.cpp | 5 +---- Telegram/lib_ui | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 5557eb503..d0470dfc7 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -383,7 +383,7 @@ void Application::run() { } SetCrashAnnotationsGL(); - if (!Platform::IsMac() && Ui::GL::LastCrashCheckFailed()) { + if (Ui::GL::LastCrashCheckFailed()) { showOpenGLCrashNotification(); } @@ -427,14 +427,12 @@ void Application::checkWindowAccount(not_null window) { void Application::showOpenGLCrashNotification() { const auto enable = [=] { - Ui::GL::ForceDisable(false); Ui::GL::CrashCheckFinish(); settings().setDisableOpenGL(false); Local::writeSettings(); Restart(); }; const auto keepDisabled = [=](Fn close) { - Ui::GL::ForceDisable(true); Ui::GL::CrashCheckFinish(); settings().setDisableOpenGL(true); Local::writeSettings(); @@ -792,6 +790,7 @@ void Application::badMtprotoConfigurationError() { } void Application::startLocalStorage() { + Ui::GL::DetectLastCheckCrash(); Local::start(); _saveSettingsTimer.emplace([=] { saveSettings(); }); settings().saveDelayedRequests() | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index a54f5ef33..daaff5acf 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -910,10 +910,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _recentEmojiPreload = std::move(recentEmojiPreload); _emojiVariants = std::move(emojiVariants); _disableOpenGL = (disableOpenGL == 1); - if (!Platform::IsMac()) { - Ui::GL::ForceDisable(_disableOpenGL - || Ui::GL::LastCrashCheckFailed()); - } + Ui::GL::ForceDisable(_disableOpenGL); _groupCallNoiseSuppression = (groupCallNoiseSuppression == 1); const auto uncheckedWorkMode = static_cast(workMode); switch (uncheckedWorkMode) { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 037ceb272..b21a93f3b 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 037ceb272c4dd2eb456e18b61e374c1fa759d109 +Subproject commit b21a93f3ba47d6233a0feb764be37850ae1edcb2 From f1c1c900bf84e652a4c391c1cfcc6effc16462d1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 27 May 2024 22:08:20 +0400 Subject: [PATCH 007/225] Fix newline regression on Linux. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index b21a93f3b..b2653606b 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit b21a93f3ba47d6233a0feb764be37850ae1edcb2 +Subproject commit b2653606be9db432e850ba1a17da00c4e9cf2e17 From f09a939a7c3c12e50910e84378b488c6ee57cda0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 27 May 2024 22:24:19 +0400 Subject: [PATCH 008/225] Improve fix for markup. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index b2653606b..34fcd4981 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit b2653606be9db432e850ba1a17da00c4e9cf2e17 +Subproject commit 34fcd4981d764f0869793893f37822b9c09393ab From 7db9abf72558a4a6009213284d2abf1a9f11d7c8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 10:04:57 +0400 Subject: [PATCH 009/225] Fix forum search open by Ctrl+F. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index f74956bd3..a08c16119 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -999,6 +999,7 @@ void Widget::setupShortcuts() { ) | rpl::filter([=] { return isActiveWindow() && Ui::InFocusChain(this) + && !_childList && !controller()->isLayerShown() && !controller()->window().locked(); }) | rpl::start_with_next([=](not_null request) { @@ -2723,6 +2724,7 @@ void Widget::openChildList( *opacity = value; update(); _inner->update(); + _search->setVisible(value < 1.); if (!value && _childListShadow.get() != shadow) { delete shadow; } From 32cd2120acc6ff55842f9243d7116d7b8dd8f6ee Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 10:54:20 +0400 Subject: [PATCH 010/225] Allow zero width space in the middle of text. Fixes #6959. --- Telegram/SourceFiles/chat_helpers/message_field.cpp | 5 +---- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 648c5c98c..557d0c337 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -431,10 +431,7 @@ bool HasSendText(not_null field) { const auto &text = field->getTextWithTags().text; for (const auto &ch : text) { const auto code = ch.unicode(); - if (code != ' ' - && code != '\n' - && code != '\r' - && !IsReplacedBySpace(code)) { + if (!IsTrimmed(ch) && !IsReplacedBySpace(code)) { return true; } } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 34fcd4981..a48b3d375 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 34fcd4981d764f0869793893f37822b9c09393ab +Subproject commit a48b3d3750ac1a1e3b2f4da3494fa5fb23b66106 From b9f63f80f14d826908089618dee05b2d1a00b6b1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 10:59:46 +0400 Subject: [PATCH 011/225] Reply-in-another-chat by Ctrl+Click on Reply. --- .../SourceFiles/history/history_inner_widget.cpp | 2 +- .../history/view/history_view_context_menu.cpp | 14 +++++--------- .../history/view/history_view_replies_section.cpp | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index ae28620d5..1b5e78f3e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2399,7 +2399,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto quoteOffset = selected.offset; text.replace('&', u"&&"_q); _menu->addAction(text, [=] { - if (canSendReply) { + if (canSendReply && !base::IsCtrlPressed()) { _widget->replyToMessage({ .messageId = itemId, .quote = quote, diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index dcebb2628..390578393 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -632,15 +632,11 @@ bool AddReplyToMessageAction( text.replace('&', u"&&"_q); const auto itemId = item->fullId(); menu->addAction(text, [=] { - if (!item) { - return; - } else { - list->replyToMessageRequestNotify({ - .messageId = itemId, - .quote = quote.text, - .quoteOffset = quote.offset, - }); - } + list->replyToMessageRequestNotify({ + .messageId = itemId, + .quote = quote.text, + .quoteOffset = quote.offset, + }); }, &st::menuIconReply); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 1974cce2b..7db2a2141 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -327,7 +327,7 @@ RepliesWidget::RepliesWidget( const auto canSendReply = _topic ? Data::CanSendAnything(_topic) : Data::CanSendAnything(_history->peer); - if (_joinGroup || !canSendReply) { + if (_joinGroup || !canSendReply || base::IsCtrlPressed()) { Controls::ShowReplyToChatBox(controller->uiShow(), { fullId }); } else { replyToMessage(fullId); From 72471c74d0d21754e6e70bb5f6250e010c682173 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 11:45:21 +0400 Subject: [PATCH 012/225] Limit symbols that allow diacritics after them. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index a48b3d375..e7c598aff 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit a48b3d3750ac1a1e3b2f4da3494fa5fb23b66106 +Subproject commit e7c598affe724322577ef46d9a07d1dcac3c617b From 275327c789f06064fafbd68d63f31a76bbb07b9c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 11:53:51 +0400 Subject: [PATCH 013/225] Version 5.0.3. - Ctrl+Click on Reply in menu to Reply in another chat. - Allow Zero-Width-Space character in text rendering. - Fix creating custom links in the message field. - Fix jump to the topic with last unread message. - Fix newline entering via Shift+Enter on Linux. - Fix forum search open by Ctrl+F. --- 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 ++++---- changelog.txt | 9 +++++++++ 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index e22af137a..804884178 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.0.3.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 99096c4ee..dde750548 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,2,0 - PRODUCTVERSION 5,0,2,0 + FILEVERSION 5,0,3,0 + PRODUCTVERSION 5,0,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.0.2.0" + VALUE "FileVersion", "5.0.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.2.0" + VALUE "ProductVersion", "5.0.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index ebdc4e7bf..9fce7f364 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,2,0 - PRODUCTVERSION 5,0,2,0 + FILEVERSION 5,0,3,0 + PRODUCTVERSION 5,0,3,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", "5.0.2.0" + VALUE "FileVersion", "5.0.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.2.0" + VALUE "ProductVersion", "5.0.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index e75931b50..73f9f2837 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 = 5000002; -constexpr auto AppVersionStr = "5.0.2"; +constexpr auto AppVersion = 5000003; +constexpr auto AppVersionStr = "5.0.3"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 8975fea05..f0887ef68 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5000002 +AppVersion 5000003 AppVersionStrMajor 5.0 -AppVersionStrSmall 5.0.2 -AppVersionStr 5.0.2 +AppVersionStrSmall 5.0.3 +AppVersionStr 5.0.3 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.0.2 +AppVersionOriginal 5.0.3 diff --git a/changelog.txt b/changelog.txt index e5eb46a9f..5a24a32e1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,12 @@ +5.0.3 (28.05.24) + +- Ctrl+Click on Reply in menu to Reply in another chat. +- Allow Zero-Width-Space character in text rendering. +- Fix creating custom links in the message field. +- Fix jump to the topic with last unread message. +- Fix newline entering via Shift+Enter on Linux. +- Fix forum search open by Ctrl+F. + 5.0.2 (24.05.24) - Toggle chats search focus by Tab key. From adc536b81de8901d89ea26c7297e0aba2a396a87 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 15:00:45 +0400 Subject: [PATCH 014/225] Fix Ctrl+Up reply in topics / comments. --- .../history/view/history_view_context_menu.cpp | 3 ++- .../history/view/history_view_list_widget.cpp | 9 ++++++--- .../history/view/history_view_list_widget.h | 13 ++++++++++--- .../history/view/history_view_replies_section.cpp | 9 +++++---- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 390578393..559a98ccd 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_transcribes.h" #include "api/api_who_reacted.h" #include "api/api_toggling_media.h" // Api::ToggleFavedSticker +#include "base/qt/qt_key_modifiers.h" #include "base/unixtime.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_cursor_state.h" @@ -636,7 +637,7 @@ bool AddReplyToMessageAction( .messageId = itemId, .quote = quote.text, .quoteOffset = quote.offset, - }); + }, base::IsCtrlPressed()); }, &st::menuIconReply); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index e849bd943..e31188605 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -3904,12 +3904,15 @@ bool ListWidget::lastMessageEditRequestNotify() const { } } -rpl::producer ListWidget::replyToMessageRequested() const { +auto ListWidget::replyToMessageRequested() const +-> rpl::producer { return _requestedToReplyToMessage.events(); } -void ListWidget::replyToMessageRequestNotify(FullReplyTo id) { - _requestedToReplyToMessage.fire(std::move(id)); +void ListWidget::replyToMessageRequestNotify( + FullReplyTo to, + bool forceAnotherChat) { + _requestedToReplyToMessage.fire({ std::move(to), forceAnotherChat }); } rpl::producer ListWidget::readMessageRequested() const { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index b68e689e9..7770341d9 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -286,11 +286,18 @@ public: QPoint tooltipPos() const override; bool tooltipWindowActive() const override; + struct ReplyToMessageRequest { + FullReplyTo to; + bool forceAnotherChat = false; + }; [[nodiscard]] rpl::producer editMessageRequested() const; void editMessageRequestNotify(FullMsgId item) const; [[nodiscard]] bool lastMessageEditRequestNotify() const; - [[nodiscard]] rpl::producer replyToMessageRequested() const; - void replyToMessageRequestNotify(FullReplyTo id); + [[nodiscard]] auto replyToMessageRequested() const + -> rpl::producer; + void replyToMessageRequestNotify( + FullReplyTo to, + bool forceAnotherChat = false); [[nodiscard]] rpl::producer readMessageRequested() const; [[nodiscard]] rpl::producer showMessageRequested() const; void replyNextMessage(FullMsgId fullId, bool next = true); @@ -760,7 +767,7 @@ private: base::Timer _touchScrollTimer; rpl::event_stream _requestedToEditMessage; - rpl::event_stream _requestedToReplyToMessage; + rpl::event_stream _requestedToReplyToMessage; rpl::event_stream _requestedToReadMessage; rpl::event_stream _requestedToShowMessage; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 7db2a2141..02d9c8412 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -323,14 +323,15 @@ RepliesWidget::RepliesWidget( }, _inner->lifetime()); _inner->replyToMessageRequested( - ) | rpl::start_with_next([=](auto fullId) { + ) | rpl::start_with_next([=](ListWidget::ReplyToMessageRequest request) { const auto canSendReply = _topic ? Data::CanSendAnything(_topic) : Data::CanSendAnything(_history->peer); - if (_joinGroup || !canSendReply || base::IsCtrlPressed()) { - Controls::ShowReplyToChatBox(controller->uiShow(), { fullId }); + const auto &to = request.to; + if (_joinGroup || !canSendReply || request.forceAnotherChat) { + Controls::ShowReplyToChatBox(controller->uiShow(), { to }); } else { - replyToMessage(fullId); + replyToMessage(to); _composeControls->focus(); } }, _inner->lifetime()); From 88cd886ec814fd465a7c543b7bcfaf669870e7a5 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 28 May 2024 17:34:18 +0400 Subject: [PATCH 015/225] 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 064ff7caf..115530c7a 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 064ff7cafea2629c3a7162fb954fc597e0eec652 +Subproject commit 115530c7aa8694f461d04f95d6a3561a18562e7e From 9e85b1aa23301b4189e924ed0cf1710828380bb2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 14:37:19 +0300 Subject: [PATCH 016/225] Fixed fake ability to hide webpage media with spoiler. --- .../view/controls/history_view_compose_media_edit_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp index 940ff7ff2..d6c58bb6a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp @@ -27,7 +27,7 @@ void MediaEditSpoilerManager::showMenu( const auto media = item->media(); const auto hasPreview = media && media->hasReplyPreview(); const auto preview = hasPreview ? media->replyPreview() : nullptr; - if (!preview) { + if (!preview || (media && media->webpage())) { return; } const auto spoilered = _spoilerOverride From 2605e754ff63ea632e586ade7720fdb42766cd87 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 15:03:20 +0300 Subject: [PATCH 017/225] Added back button to cloud password step in intro. --- Telegram/SourceFiles/intro/intro_password_check.h | 4 ++++ Telegram/SourceFiles/intro/intro_widget.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/intro/intro_password_check.h b/Telegram/SourceFiles/intro/intro_password_check.h index ddd117551..9bfcbad3d 100644 --- a/Telegram/SourceFiles/intro/intro_password_check.h +++ b/Telegram/SourceFiles/intro/intro_password_check.h @@ -35,6 +35,10 @@ public: void submit() override; rpl::producer nextButtonText() const override; + bool hasBack() const override { + return true; + } + protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/intro/intro_widget.cpp b/Telegram/SourceFiles/intro/intro_widget.cpp index 13ba4837b..9592929e9 100644 --- a/Telegram/SourceFiles/intro/intro_widget.cpp +++ b/Telegram/SourceFiles/intro/intro_widget.cpp @@ -849,7 +849,7 @@ void Widget::backRequested() { Core::App().domain().activate(parent); } else { moveToStep( - new StartWidget(this, _account, getData()), + Ui::CreateChild(this, _account, getData()), StackAction::Replace, Animate::Back); } From 26ba7e57ce79480c095219d069c4db072ef60679 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 15:27:43 +0300 Subject: [PATCH 018/225] Fixed color of radial animation in audio files from shared media. --- Telegram/SourceFiles/overview/overview_layout.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index c56582bc4..d8c70f1df 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -1398,7 +1398,9 @@ void Document::drawCornerDownload(QPainter &p, bool selected, const PaintContext icon->paintInCenter(p, inner); if (_radial && _radial->animating()) { const auto rinner = inner.marginsRemoved(QMargins(st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine)); - auto fg = selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg; + const auto &fg = selected + ? st::historyFileInIconFgSelected + : st::historyFileInIconFg; _radial->draw(p, rinner, st::historyAudioRadialLine, fg); } } From 48e3802565755be6ad2b1903823525f11590a933 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 16:42:26 +0300 Subject: [PATCH 019/225] Improved labels for channel and group types with restricted content. --- Telegram/Resources/langs/lang.strings | 2 ++ .../boxes/peers/edit_peer_info_box.cpp | 25 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d0a709c6d..848496650 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1522,8 +1522,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_peer_link_invite" = "Invite link"; "lng_manage_peer_link_expired" = "Expired link"; "lng_manage_private_group_title" = "Private"; +"lng_manage_private_group_noforwards_title" = "Private restricted"; "lng_manage_public_group_title" = "Public"; "lng_manage_private_peer_title" = "Private"; +"lng_manage_private_peer_noforwards_title" = "Private restricted"; "lng_manage_public_peer_title" = "Public"; "lng_manage_peer_send_title" = "Who can send new messages?"; "lng_manage_peer_send_only_members" = "Only members"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index c79ef325d..99427aca0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -415,7 +415,12 @@ private: std::deque> _saveStagesQueue; Saving _savingData; - const rpl::event_stream _privacyTypeUpdates; + struct PrivacyAndForwards { + Privacy privacy; + bool noForwards = false; + }; + + const rpl::event_stream _privacyTypeUpdates; const rpl::event_stream _linkedChatUpdates; mtpRequestId _linkedChatsRequestId = 0; @@ -761,7 +766,7 @@ void Controller::refreshHistoryVisibility() { void Controller::showEditPeerTypeBox( std::optional> error) { const auto boxCallback = crl::guard(this, [=](EditPeerTypeData data) { - _privacyTypeUpdates.fire_copy(data.privacy); + _privacyTypeUpdates.fire({ data.privacy, data.noForwards }); _typeDataSavedValue = data; refreshHistoryVisibility(); }); @@ -882,7 +887,8 @@ void Controller::fillPrivacyTypeButton() { ? tr::lng_manage_peer_group_type : tr::lng_manage_peer_channel_type)(), _privacyTypeUpdates.events( - ) | rpl::map([=](Privacy flag) { + ) | rpl::map([=](PrivacyAndForwards data) { + const auto flag = data.privacy; if (flag == Privacy::HasUsername) { _peer->session().api().usernames().requestToCache(_peer); } @@ -894,14 +900,21 @@ void Controller::fillPrivacyTypeButton() { : tr::lng_manage_public_peer_title)() : (hasLocation ? tr::lng_manage_peer_link_invite - : isGroup + : ((!data.noForwards) && isGroup) ? tr::lng_manage_private_group_title - : tr::lng_manage_private_peer_title)(); + : ((!data.noForwards) && !isGroup) + ? tr::lng_manage_private_peer_title + : isGroup + ? tr::lng_manage_private_group_noforwards_title + : tr::lng_manage_private_peer_noforwards_title)(); }) | rpl::flatten_latest(), [=] { showEditPeerTypeBox(); }, { &st::menuIconCustomize }); - _privacyTypeUpdates.fire_copy(_typeDataSavedValue->privacy); + _privacyTypeUpdates.fire_copy({ + _typeDataSavedValue->privacy, + _typeDataSavedValue->noForwards, + }); } void Controller::fillLinkedChatButton() { From f6b849e4f76fcfa27cd0ba8ef722a76c1f93a698 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 18:46:04 +0300 Subject: [PATCH 020/225] Added ability to add proxy from clipboard. --- Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/boxes/connection_box.cpp | 143 ++++++++++++++---- Telegram/SourceFiles/boxes/connection_box.h | 4 +- 3 files changed, 123 insertions(+), 28 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 848496650..87df781f8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1053,6 +1053,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_proxy_sponsor" = "Proxy sponsor"; "lng_proxy_sponsor_about" = "This channel is shown by your proxy server.\nTo remove this channel from your chats list,\ndisable the proxy in Telegram Settings."; "lng_proxy_sponsor_warning" = "This proxy may display a sponsored channel in your chat list. This doesn't reveal any of your Telegram traffic."; +"lng_proxy_add_from_clipboard" = "Add proxy from clipboard"; +"lng_proxy_add_from_clipboard_good_toast" = "Proxy was added from clipboard."; +"lng_proxy_add_from_clipboard_failed_toast" = "This is not a proxy link."; +"lng_proxy_add_from_clipboard_existing_toast" = "This proxy is already in the list."; "lng_badge_psa_default" = "PSA"; "lng_about_psa_default" = "This message provides you with a public service announcement. To remove it from your chats list, right click it and select **Hide**."; "lng_tooltip_psa_default" = "This message provides you with a public service announcement."; diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index cf9d91f8c..f50ef4b3a 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -7,31 +7,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/connection_box.h" -#include "ui/boxes/confirm_box.h" -#include "lang/lang_keys.h" -#include "storage/localstorage.h" -#include "base/qthelp_url.h" #include "base/call_delayed.h" +#include "base/qthelp_regex.h" +#include "base/qthelp_url.h" #include "core/application.h" +#include "core/click_handler_types.h" #include "core/core_settings.h" +#include "core/local_url_handlers.h" +#include "lang/lang_keys.h" #include "main/main_account.h" #include "mtproto/facade.h" -#include "ui/widgets/checkbox.h" +#include "storage/localstorage.h" +#include "ui/basic_click_handlers.h" +#include "ui/boxes/confirm_box.h" +#include "ui/effects/animations.h" +#include "ui/effects/radial_animation.h" +#include "ui/painter.h" +#include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/dropdown_menu.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/number_input.h" #include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" -#include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" -#include "ui/toast/toast.h" -#include "ui/effects/animations.h" -#include "ui/effects/radial_animation.h" -#include "ui/text/text_options.h" -#include "ui/text/text_utilities.h" -#include "ui/basic_click_handlers.h" -#include "ui/painter.h" #include "boxes/abstract_box.h" // Ui::show(). #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -48,6 +52,22 @@ constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000); using ProxyData = MTP::ProxyData; +[[nodiscard]] ProxyData ProxyDataFromFields( + ProxyData::Type type, + const QMap &fields) { + auto proxy = ProxyData(); + proxy.type = type; + proxy.host = fields.value(u"server"_q); + proxy.port = fields.value(u"port"_q).toUInt(); + if (type == ProxyData::Type::Socks5) { + proxy.user = fields.value(u"user"_q); + proxy.password = fields.value(u"pass"_q); + } else if (type == ProxyData::Type::Mtproto) { + proxy.password = fields.value(u"secret"_q); + } + return proxy; +}; + class HostInput : public Ui::MaskedInputField { public: HostInput( @@ -203,6 +223,7 @@ protected: private: void setupContent(); + void setupTopButton(); void createNoRowsLabel(); void addNewProxy(); void applyView(View &&view); @@ -600,9 +621,80 @@ void ProxiesBox::prepare() { addButton(tr::lng_proxy_add(), [=] { addNewProxy(); }); addButton(tr::lng_close(), [=] { closeBox(); }); + setupTopButton(); setupContent(); } +void ProxiesBox::setupTopButton() { + const auto top = addTopButton(st::infoTopBarMenu); + const auto menu + = top->lifetime().make_state>(); + const auto callback = [=] { + const auto maybeUrl = QGuiApplication::clipboard()->text(); + const auto local = Core::TryConvertUrlToLocal(maybeUrl); + + const auto proxyString = u"proxy"_q; + const auto socksString = u"socks"_q; + const auto protocol = u"tg://"_q; + const auto command = base::StringViewMid( + local, + protocol.size(), + 8192); + + if (local.startsWith(protocol + proxyString) + || local.startsWith(protocol + socksString)) { + + using namespace qthelp; + const auto options = RegExOption::CaseInsensitive; + for (const auto &[expression, _] : Core::LocalUrlHandlers()) { + const auto midExpression = base::StringViewMid( + expression, + 1); + const auto isSocks = midExpression.startsWith( + socksString); + if (!midExpression.startsWith(proxyString) + && !isSocks) { + continue; + } + const auto match = regex_match( + expression, + command, + options); + if (!match) { + continue; + } + const auto type = isSocks + ? ProxyData::Type::Socks5 + : ProxyData::Type::Mtproto; + const auto fields = url_parse_params( + match->captured(1), + qthelp::UrlParamNameTransform::ToLower); + const auto proxy = ProxyDataFromFields(type, fields); + const auto contains = _controller->contains(proxy); + const auto toast = (contains + ? tr::lng_proxy_add_from_clipboard_existing_toast + : tr::lng_proxy_add_from_clipboard_good_toast)(tr::now); + uiShow()->showToast(toast); + if (!contains) { + _controller->addNewItem(proxy); + } + break; + } + } else { + uiShow()->showToast( + tr::lng_proxy_add_from_clipboard_failed_toast(tr::now)); + } + }; + top->setClickedCallback([=] { + *menu = base::make_unique_q(top, st::defaultPopupMenu); + (*menu)->addAction( + tr::lng_proxy_add_from_clipboard(tr::now), + callback); + (*menu)->popup(QCursor::pos()); + return true; + }); +} + void ProxiesBox::setupContent() { const auto inner = setInnerWidget(object_ptr(this)); @@ -1096,24 +1188,13 @@ ProxiesBoxController::ProxiesBoxController(not_null account) void ProxiesBoxController::ShowApplyConfirmation( Type type, const QMap &fields) { - const auto server = fields.value(u"server"_q); - const auto port = fields.value(u"port"_q).toUInt(); - auto proxy = ProxyData(); - proxy.type = type; - proxy.host = server; - proxy.port = port; - if (type == Type::Socks5) { - proxy.user = fields.value(u"user"_q); - proxy.password = fields.value(u"pass"_q); - } else if (type == Type::Mtproto) { - proxy.password = fields.value(u"secret"_q); - } + const auto proxy = ProxyDataFromFields(type, fields); if (proxy) { static const auto UrlStartRegExp = QRegularExpression( "^https://", QRegularExpression::CaseInsensitiveOption); static const auto UrlEndRegExp = QRegularExpression("/$"); - const auto displayed = "https://" + server + "/"; + const auto displayed = "https://" + proxy.host + "/"; const auto parsed = QUrl::fromUserInput(displayed); const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed) ? displayed @@ -1131,7 +1212,7 @@ void ProxiesBoxController::ShowApplyConfirmation( lt_server, displayServer, lt_port, - QString::number(port)) + QString::number(proxy.port)) + (proxy.type == Type::Mtproto ? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now) : QString()); @@ -1448,6 +1529,14 @@ object_ptr ProxiesBoxController::addNewItemBox() { }); } +bool ProxiesBoxController::contains(const ProxyData &proxy) const { + const auto j = ranges::find( + _list, + proxy, + [](const Item &item) { return item.data; }); + return (j != end(_list)); +} + void ProxiesBoxController::addNewItem(const ProxyData &proxy) { auto &proxies = _settings.list(); proxies.push_back(proxy); diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h index 8c78ebf34..e3bbfd006 100644 --- a/Telegram/SourceFiles/boxes/connection_box.h +++ b/Telegram/SourceFiles/boxes/connection_box.h @@ -77,6 +77,9 @@ public: void setTryIPv6(bool enabled); rpl::producer proxySettingsValue() const; + [[nodiscard]] bool contains(const ProxyData &proxy) const; + void addNewItem(const ProxyData &proxy); + rpl::producer views() const; ~ProxiesBoxController(); @@ -109,7 +112,6 @@ private: void replaceItemValue( std::vector::iterator which, const ProxyData &proxy); - void addNewItem(const ProxyData &proxy); const not_null _account; Core::SettingsProxy &_settings; From 3b6870396c915ab4af3c6f92b8d3cfd59fa54653 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 19:06:48 +0300 Subject: [PATCH 021/225] Added ability to hide every sponsored message to premium users. --- Telegram/SourceFiles/history/history_inner_widget.cpp | 8 +++++++- Telegram/SourceFiles/history/history_item_helpers.cpp | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 1b5e78f3e..9809f9c89 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -161,7 +161,13 @@ void FillSponsoredMessagesMenu( menu->addSeparator(&st::expandedMenuSeparator); } menu->addAction(tr::lng_sponsored_hide_ads(tr::now), [=] { - ShowPremiumPreviewBox(controller, PremiumFeature::NoAds); + if (controller->session().premium()) { + using Result = Data::SponsoredReportResult; + controller->session().sponsoredMessages().createReportCallback( + itemId)(Result::Id("-1"), [](const auto &) {}); + } else { + ShowPremiumPreviewBox(controller, PremiumFeature::NoAds); + } }, &st::menuIconCancel); } diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 7b51f751c..d3d9569e6 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/components/sponsored_messages.h" #include "data/stickers/data_custom_emoji.h" #include "data/notify/data_notify_settings.h" #include "data/data_channel.h" @@ -363,7 +364,14 @@ ClickHandlerPtr HideSponsoredClickHandler() { return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { - ShowPremiumPreviewBox(controller, PremiumFeature::NoAds); + const auto &session = controller->session(); + if (session.premium()) { + using Result = Data::SponsoredReportResult; + session.sponsoredMessages().createReportCallback( + my.itemId)(Result::Id("-1"), [](const auto &) {}); + } else { + ShowPremiumPreviewBox(controller, PremiumFeature::NoAds); + } } }); } From e5132e3fe88dc5f6504b349d05c006ba15fcb135 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 20:32:48 +0400 Subject: [PATCH 022/225] Version 5.0.4 - Fix reply to last message by Ctrl+Up in topics. - Some other bug fixes. --- 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 ++++---- changelog.txt | 5 +++++ 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 804884178..519db7170 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.0.4.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index dde750548..5bec7f3bc 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,3,0 - PRODUCTVERSION 5,0,3,0 + FILEVERSION 5,0,4,0 + PRODUCTVERSION 5,0,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.0.3.0" + VALUE "FileVersion", "5.0.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.3.0" + VALUE "ProductVersion", "5.0.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 9fce7f364..d44287d24 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,3,0 - PRODUCTVERSION 5,0,3,0 + FILEVERSION 5,0,4,0 + PRODUCTVERSION 5,0,4,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", "5.0.3.0" + VALUE "FileVersion", "5.0.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.3.0" + VALUE "ProductVersion", "5.0.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 73f9f2837..1d8cecd6e 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 = 5000003; -constexpr auto AppVersionStr = "5.0.3"; +constexpr auto AppVersion = 5000004; +constexpr auto AppVersionStr = "5.0.4"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index f0887ef68..68f363453 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5000003 +AppVersion 5000004 AppVersionStrMajor 5.0 -AppVersionStrSmall 5.0.3 -AppVersionStr 5.0.3 +AppVersionStrSmall 5.0.4 +AppVersionStr 5.0.4 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.0.3 +AppVersionOriginal 5.0.4 diff --git a/changelog.txt b/changelog.txt index 5a24a32e1..19f721c85 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +5.0.4 (28.05.24) + +- Fix reply to last message by Ctrl+Up in topics. +- Some other bug fixes. + 5.0.3 (28.05.24) - Ctrl+Click on Reply in menu to Reply in another chat. From ef2aa051978bf7d17d5a15fb3a70cad3c96ccc8d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 May 2024 19:39:19 +0400 Subject: [PATCH 023/225] Allow HistoryView::ListWidget without SessionController. --- .../history/view/history_view_list_widget.cpp | 314 ++++++++++++------ .../history/view/history_view_list_widget.h | 73 +++- .../view/history_view_pinned_section.cpp | 3 +- .../view/history_view_pinned_section.h | 2 +- .../view/history_view_replies_section.cpp | 3 +- .../view/history_view_replies_section.h | 2 +- .../view/history_view_scheduled_section.cpp | 3 +- .../view/history_view_scheduled_section.h | 2 +- .../view/history_view_sublist_section.cpp | 3 +- .../view/history_view_sublist_section.h | 2 +- .../business/settings_shortcut_messages.cpp | 10 +- Telegram/SourceFiles/ui/chat/chat_style.h | 8 + .../window/window_session_controller.cpp | 2 +- .../window/window_session_controller.h | 10 +- 14 files changed, 312 insertions(+), 125 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index e31188605..538606339 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -90,6 +90,95 @@ constexpr auto kClearUserpicsAfter = 50; } // namespace +const crl::time ListWidget::kItemRevealDuration = crl::time(150); + +WindowListDelegate::WindowListDelegate( + not_null window) +: _window(window) { +} + +not_null WindowListDelegate::listWindow() { + return _window; +} + +not_null WindowListDelegate::listChatStyle() { + return _window->chatStyle(); +} + +rpl::producer WindowListDelegate::listChatWideValue() { + return _window->adaptive().chatWideValue(); +} + +auto WindowListDelegate::listMakeReactionsManager( + QWidget *wheelEventsTarget, + Fn update) +-> std::unique_ptr { + return std::make_unique( + wheelEventsTarget, + std::move(update), + _window->cachedReactionIconFactory().createMethod()); +} + +void WindowListDelegate::listVisibleAreaUpdated() { + _window->floatPlayerAreaUpdated(); +} + +std::shared_ptr WindowListDelegate::listUiShow() { + return _window->uiShow(); +} + +void WindowListDelegate::listShowPollResults( + not_null poll, + FullMsgId context) { + _window->showPollResults(poll, context); +} + +void WindowListDelegate::listCancelUploadLayer(not_null item) { + _window->cancelUploadLayer(item); +} + +bool WindowListDelegate::listAnimationsPaused() { + return _window->isGifPausedAtLeastFor(Window::GifPauseReason::Any); +} + +auto WindowListDelegate::listSendingAnimation() +-> Ui::MessageSendingAnimationController * { + return &_window->sendingAnimation(); +} + +Ui::ChatPaintContext WindowListDelegate::listPreparePaintContext( + Ui::ChatPaintContextArgs &&args) { + return _window->preparePaintContext(std::move(args)); +} + +bool WindowListDelegate::listMarkingContentRead() { + return _window->widget()->markingAsRead(); +} + +bool WindowListDelegate::listIgnorePaintEvent(QWidget *w, QPaintEvent *e) { + return _window->contentOverlapped(w, e); +} + +bool WindowListDelegate::listShowReactPremiumError( + not_null item, + const Data::ReactionId &id) { + return Window::ShowReactPremiumError(_window, item, id); +} + +void WindowListDelegate::listWindowSetInnerFocus() { + _window->widget()->setInnerFocus(); +} + +bool WindowListDelegate::listAllowsDragForward() { + return _window->adaptive().isOneColumn(); +} + +void WindowListDelegate::listLaunchDrag( + std::unique_ptr data, + Fn finished) { + _window->widget()->launchDrag(std::move(data), std::move(finished)); +} + ListWidget::MouseState::MouseState() : pointState(PointState::Outside) { } @@ -104,8 +193,6 @@ ListWidget::MouseState::MouseState( , pointState(pointState) { } -const crl::time ListWidget::kItemRevealDuration = crl::time(150); - template void ListWidget::enumerateItems(Method method) { constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom); @@ -283,31 +370,29 @@ void ListWidget::enumerateDates(Method method) { ListWidget::ListWidget( QWidget *parent, - not_null controller, + not_null session, not_null delegate) : RpWidget(parent) , _delegate(delegate) -, _controller(controller) +, _session(session) , _emojiInteractions(std::make_unique( - &controller->session(), + session, [=](not_null view) { return itemTop(view); })) , _context(_delegate->listContext()) , _itemAverageHeight(itemMinimalHeight()) , _pathGradient( MakePathShiftGradient( - controller->chatStyle(), + _delegate->listChatStyle(), [=] { update(); })) -, _reactionsManager( - std::make_unique( - this, - [=](QRect updated) { update(updated); }, - controller->cachedReactionIconFactory().createMethod())) +, _reactionsManager(_delegate->listMakeReactionsManager( + this, + [=](QRect updated) { update(updated); })) , _translateTracker(MaybeTranslateTracker(_delegate->listTranslateHistory())) , _scrollDateCheck([this] { scrollDateCheck(); }) , _applyUpdatedScrollState([this] { applyUpdatedScrollState(); }) , _selectEnabled(_delegate->listAllowsMultiSelect()) , _highlighter( - &session().data(), + &_session->data(), [=](const HistoryItem *item) { return viewForItem(item); }, [=](const Element *view) { repaintItem(view); }) , _touchSelectTimer([=] { onTouchSelect(); }) @@ -315,25 +400,25 @@ ListWidget::ListWidget( setAttribute(Qt::WA_AcceptTouchEvents); setMouseTracking(true); _scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); }); - session().data().viewRepaintRequest( + _session->data().viewRepaintRequest( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { repaintItem(view); } }, lifetime()); - session().data().viewResizeRequest( + _session->data().viewResizeRequest( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { resizeItem(view); } }, lifetime()); - session().data().itemViewRefreshRequest( + _session->data().itemViewRefreshRequest( ) | rpl::start_with_next([this](auto item) { if (const auto view = viewForItem(item)) { refreshItem(view); } }, lifetime()); - session().data().viewLayoutChanged( + _session->data().viewLayoutChanged( ) | rpl::start_with_next([this](auto view) { if (view->delegate() == this) { if (view->isUnderCursor()) { @@ -341,37 +426,37 @@ ListWidget::ListWidget( } } }, lifetime()); - session().data().itemDataChanges( + _session->data().itemDataChanges( ) | rpl::start_with_next([=](not_null item) { if (const auto view = viewForItem(item)) { view->itemDataChanged(); } }, lifetime()); - session().downloaderTaskFinished( + _session->downloaderTaskFinished( ) | rpl::start_with_next([=] { update(); }, lifetime()); - session().data().peerDecorationsUpdated( + _session->data().peerDecorationsUpdated( ) | rpl::start_with_next([=] { update(); }, lifetime()); - session().data().itemRemoved( + _session->data().itemRemoved( ) | rpl::start_with_next([=](not_null item) { itemRemoved(item); }, lifetime()); using MessageUpdateFlag = Data::MessageUpdate::Flag; - session().changes().realtimeMessageUpdates( + _session->changes().realtimeMessageUpdates( MessageUpdateFlag::NewUnreadReaction ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { maybeMarkReactionsRead(update.item); }, lifetime()); if (const auto history = _delegate->listTranslateHistory()) { - session().changes().historyUpdates( + _session->changes().historyUpdates( history, Data::HistoryUpdate::Flag::TranslatedTo ) | rpl::start_with_next([=] { @@ -379,7 +464,7 @@ ListWidget::ListWidget( }, lifetime()); } - session().data().itemVisibilityQueries( + _session->data().itemVisibilityQueries( ) | rpl::start_with_next([=]( const Data::Session::ItemVisibilityQuery &query) { if (const auto view = viewForItem(query.item)) { @@ -392,25 +477,27 @@ ListWidget::ListWidget( } }, lifetime()); - _reactionsManager->chosen( - ) | rpl::start_with_next([=](ChosenReaction reaction) { - _reactionsManager->updateButton({}); - reactionChosen(reaction); - }, lifetime()); - - Reactions::SetupManagerList( - _reactionsManager.get(), - _reactionsItem.value()); - - Core::App().settings().cornerReactionValue( - ) | rpl::start_with_next([=](bool value) { - _useCornerReaction = value; - if (!value) { + if (_reactionsManager) { + _reactionsManager->chosen( + ) | rpl::start_with_next([=](ChosenReaction reaction) { _reactionsManager->updateButton({}); - } - }, lifetime()); + reactionChosen(reaction); + }, lifetime()); - controller->adaptive().chatWideValue( + Reactions::SetupManagerList( + _reactionsManager.get(), + _reactionsItem.value()); + + Core::App().settings().cornerReactionValue( + ) | rpl::start_with_next([=](bool value) { + _useCornerReaction = value; + if (!value) { + _reactionsManager->updateButton({}); + } + }, lifetime()); + } + + _delegate->listChatWideValue( ) | rpl::start_with_next([=](bool wide) { _isChatWide = wide; }, lifetime()); @@ -427,11 +514,11 @@ ListWidget::ListWidget( } Main::Session &ListWidget::session() const { - return _controller->session(); + return *_session; } not_null ListWidget::controller() const { - return _controller; + return _delegate->listWindow(); } not_null ListWidget::delegate() const { @@ -1006,7 +1093,7 @@ void ListWidget::visibleTopBottomUpdated( } else { scrollDateHideByTimer(); } - _controller->floatPlayerAreaUpdated(); + _delegate->listVisibleAreaUpdated(); session().data().itemVisibilitiesUpdated(); _applyUpdatedScrollState.call(); @@ -1439,7 +1526,7 @@ bool ListWidget::showCopyRestriction(HistoryItem *item) { if (type == CopyRestrictionType::None) { return false; } - _controller->showToast((type == CopyRestrictionType::Channel) + _delegate->listUiShow()->showToast((type == CopyRestrictionType::Channel) ? tr::lng_error_nocopy_channel(tr::now) : tr::lng_error_nocopy_group(tr::now)); return true; @@ -1450,7 +1537,7 @@ bool ListWidget::showCopyMediaRestriction(not_null item) { if (type == CopyRestrictionType::None) { return false; } - _controller->showToast((type == CopyRestrictionType::Channel) + _delegate->listUiShow()->showToast((type == CopyRestrictionType::Channel) ? tr::lng_error_nocopy_channel(tr::now) : tr::lng_error_nocopy_group(tr::now)); return true; @@ -1676,7 +1763,7 @@ void ListWidget::elementStartStickerLoop(not_null view) { void ListWidget::elementShowPollResults( not_null poll, FullMsgId context) { - _controller->showPollResults(poll, context); + _delegate->listShowPollResults(poll, context); } void ListWidget::elementOpenPhoto( @@ -1694,7 +1781,7 @@ void ListWidget::elementOpenDocument( void ListWidget::elementCancelUpload(const FullMsgId &context) { if (const auto item = session().data().message(context)) { - _controller->cancelUploadLayer(item); + _delegate->listCancelUploadLayer(item); } } @@ -1706,7 +1793,7 @@ void ListWidget::elementShowTooltip( } bool ListWidget::elementAnimationsPaused() { - return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any); + return _delegate->listAnimationsPaused(); } bool ListWidget::elementHideReply(not_null view) { @@ -1838,8 +1925,8 @@ void ListWidget::startItemRevealAnimations() { void ListWidget::startMessageSendingAnimation( not_null item) { - auto &sendingAnimation = controller()->sendingAnimation(); - if (!sendingAnimation.checkExpectedType(item)) { + const auto sendingAnimation = _delegate->listSendingAnimation(); + if (!sendingAnimation || !sendingAnimation->checkExpectedType(item)) { return; } @@ -1855,7 +1942,7 @@ void ListWidget::startMessageSendingAnimation( return mapToGlobal(QPoint(0, itemTop(view) - additional)); }); - sendingAnimation.startAnimation({ + sendingAnimation->startAnimation({ .globalEndTopLeft = std::move(globalEndTopLeft), .view = [=] { return viewForItem(item); }, .paintContext = [=] { return preparePaintContext({}); }, @@ -2010,7 +2097,7 @@ TextSelection ListWidget::itemRenderSelection( Ui::ChatPaintContext ListWidget::preparePaintContext( const QRect &clip) const { - return controller()->preparePaintContext({ + return _delegate->listPreparePaintContext({ .theme = _delegate->listChatTheme(), .clip = clip, .visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)), @@ -2022,7 +2109,7 @@ Ui::ChatPaintContext ListWidget::preparePaintContext( bool ListWidget::markingContentsRead() const { return _showFinished && !_refreshingViewer - && controller()->widget()->markingAsRead(); + && _delegate->listMarkingContentRead(); } bool ListWidget::markingMessagesRead() const { @@ -2053,12 +2140,9 @@ void ListWidget::checkActivation() { } void ListWidget::paintEvent(QPaintEvent *e) { - if ((_context != Context::ShortcutMessages) - && _controller->contentOverlapped(this, e)) { + if (_delegate->listIgnorePaintEvent(this, e)) { return; - } - - if (_translateTracker) { + } else if (_translateTracker) { _translateTracker->startBunch(); } auto readTill = (HistoryItem*)nullptr; @@ -2100,20 +2184,25 @@ void ListWidget::paintEvent(QPaintEvent *e) { _delegate->listPaintEmpty(p, context); return; } - _reactionsManager->startEffectsCollection(); + if (_reactionsManager) { + _reactionsManager->startEffectsCollection(); + } - const auto session = &controller()->session(); + const auto session = &this->session(); auto top = itemTop(from->get()); context = context.translated(0, -top); p.translate(0, top); - const auto &sendingAnimation = _controller->sendingAnimation(); + const auto sendingAnimation = _delegate->listSendingAnimation(); for (auto i = from; i != to; ++i) { const auto view = *i; const auto item = view->data(); const auto height = view->height(); - if (!sendingAnimation.hasAnimatedMessage(item)) { - context.reactionInfo - = _reactionsManager->currentReactionPaintInfo(); + if (!sendingAnimation + || !sendingAnimation->hasAnimatedMessage(item)) { + if (_reactionsManager) { + context.reactionInfo + = _reactionsManager->currentReactionPaintInfo(); + } context.outbg = view->hasOutLayout(); context.selection = itemRenderSelection(view); context.highlight = _highlighter.state(item); @@ -2125,17 +2214,19 @@ void ListWidget::paintEvent(QPaintEvent *e) { const auto isSponsored = item->isSponsored(); const auto isUnread = _delegate->listElementShownUnread(view) && item->isRegular(); - const auto withReaction = item->hasUnreadReaction(); + const auto withReaction = context.reactionInfo + && item->hasUnreadReaction(); const auto yShown = [&](int y) { return (_visibleBottom >= y && _visibleTop <= y); }; - const auto markShown = isSponsored - ? view->markSponsoredViewed(_visibleBottom - top) - : withReaction - ? yShown(top + context.reactionInfo->position.y()) - : isUnread - ? yShown(top + height) - : yShown(top + height / 2); + const auto markShown = (_context != Context::ChatPreview) + && (isSponsored + ? view->markSponsoredViewed(_visibleBottom - top) + : withReaction + ? yShown(top + context.reactionInfo->position.y()) + : isUnread + ? yShown(top + height) + : yShown(top + height / 2)); if (markShown) { if (isSponsored) { session->sponsoredMessages().view(item->fullId()); @@ -2157,9 +2248,11 @@ void ListWidget::paintEvent(QPaintEvent *e) { if (item->hasExtendedMediaPreview()) { session->api().views().pollExtendedMedia(item); } - _reactionsManager->recordCurrentReactionEffect( - item->fullId(), - QPoint(0, top)); + if (_reactionsManager) { + _reactionsManager->recordCurrentReactionEffect( + item->fullId(), + QPoint(0, top)); + } top += height; context.translate(0, -height); p.translate(0, height); @@ -2170,7 +2263,9 @@ void ListWidget::paintEvent(QPaintEvent *e) { paintUserpics(p, context, clip); paintDates(p, context, clip); - _reactionsManager->paint(p, context); + if (_reactionsManager) { + _reactionsManager->paint(p, context); + } _emojiInteractions->paint(p); } @@ -2181,7 +2276,7 @@ void ListWidget::paintUserpics( if (_context == Context::ShortcutMessages) { return; } - const auto session = &controller()->session(); + const auto session = &this->session(); enumerateUserpics([&](not_null view, int userpicTop) { // stop the enumeration if the userpic is below the painted rect if (userpicTop >= clip.top() + clip.height()) { @@ -2530,7 +2625,7 @@ void ListWidget::toggleFavoriteReaction(not_null view) const { Data::LookupPossibleReactions(item).recent, favorite, &Data::Reaction::id) - || Window::ShowReactPremiumError(_controller, item, favorite)) { + || _delegate->listShowReactPremiumError(item, favorite)) { return; } else if (!ranges::contains(item->chosenReactions(), favorite)) { if (const auto top = itemTop(view); top >= 0) { @@ -2599,6 +2694,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (link && !link->property( kSendReactionEmojiProperty).value().empty() + && _reactionsManager && _reactionsManager->showContextMenu( this, e, @@ -2624,13 +2720,13 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { this, overItem, clickedReaction, - _controller, + controller(), _whoReactedMenuLifetime); e->accept(); return; } - auto request = ContextMenuRequest(_controller); + auto request = ContextMenuRequest(controller()); request.link = link; request.view = _overElement; @@ -2666,12 +2762,12 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto attached = reactItem ? AttachSelectorToMenu( _menu.get(), - _controller, + controller(), desiredPosition, reactItem, [=](ChosenReaction reaction) { reactionChosen(reaction); }, ItemReactionsAbout(reactItem), - _controller->cachedReactionIconFactory().createMethod()) + controller()->cachedReactionIconFactory().createMethod()) : AttachSelectorResult::Skipped; if (attached == AttachSelectorResult::Failed) { _menu = nullptr; @@ -2688,10 +2784,7 @@ void ListWidget::reactionChosen(ChosenReaction reaction) { const auto item = session().data().message(reaction.context); if (!item) { return; - } else if (Window::ShowReactPremiumError( - _controller, - item, - reaction.id)) { + } else if (_delegate->listShowReactPremiumError(item, reaction.id)) { if (_menu) { _menu->hideMenu(); } @@ -2945,7 +3038,9 @@ void ListWidget::enterEventHook(QEnterEvent *e) { } void ListWidget::leaveEventHook(QEvent *e) { - _reactionsManager->updateButton({ .cursorLeft = true }); + if (_reactionsManager) { + _reactionsManager->updateButton({ .cursorLeft = true }); + } if (const auto view = _overElement) { if (_overState.pointState != PointState::Outside) { repaintItem(view); @@ -3160,9 +3255,9 @@ void ListWidget::mouseActionStart( const auto pressElement = _overElement; _mouseAction = MouseAction::None; - _pressWasInactive = Ui::WasInactivePress(_controller->widget()); + _pressWasInactive = Ui::WasInactivePress(window()); if (_pressWasInactive) { - Ui::MarkInactivePress(_controller->widget(), false); + Ui::MarkInactivePress(window(), false); } if (ClickHandler::getPressed()) { @@ -3334,7 +3429,7 @@ void ListWidget::mouseActionFinish( } else if (_selectedTextItem && !_pressWasInactive) { if (_selectedTextRange.from == _selectedTextRange.to) { clearTextSelection(); - _controller->widget()->setInnerFocus(); + _delegate->listWindowSetInnerFocus(); } } } @@ -3362,7 +3457,7 @@ ClickHandlerContext ListWidget::prepareClickHandlerContext(FullMsgId id) { ? (ElementDelegate*)weak : nullptr; }, - .sessionWindow = base::make_weak(_controller), + .sessionWindow = base::make_weak(controller()), }; } @@ -3372,7 +3467,9 @@ void ListWidget::mouseActionUpdate() { std::clamp(mousePosition.x(), 0, width()), std::clamp(mousePosition.y(), _visibleTop, _visibleBottom)); - const auto reactionState = _reactionsManager->buttonTextState(point); + const auto reactionState = _reactionsManager + ? _reactionsManager->buttonTextState(point) + : TextState(); const auto reactionItem = session().data().message(reactionState.itemId); const auto reactionView = viewForItem(reactionItem); const auto view = reactionView @@ -3392,12 +3489,14 @@ void ListWidget::mouseActionUpdate() { _overElement = view; repaintItem(_overElement); } - _reactionsManager->updateButton(view - ? reactionButtonParameters( - view, - itemPoint, - reactionState) - : Reactions::ButtonParameters()); + if (_reactionsManager) { + _reactionsManager->updateButton(view + ? reactionButtonParameters( + view, + itemPoint, + reactionState) + : Reactions::ButtonParameters()); + } if (viewChanged && view) { _reactionsItem = item; } @@ -3624,7 +3723,7 @@ std::unique_ptr ListWidget::prepareDrag() { if (!urls.isEmpty()) { mimeData->setUrls(urls); } - if (uponSelected && !_controller->adaptive().isOneColumn()) { + if (uponSelected && !_delegate->listAllowsDragForward()) { const auto canForwardAll = [&] { for (const auto &[itemId, data] : _selected) { if (!data.canForward) { @@ -3689,8 +3788,10 @@ std::unique_ptr ListWidget::prepareDrag() { void ListWidget::performDrag() { if (auto mimeData = prepareDrag()) { // This call enters event loop and can destroy any QObject. - _reactionsManager->updateButton({}); - _controller->widget()->launchDrag( + if (_reactionsManager) { + _reactionsManager->updateButton({}); + } + _delegate->listLaunchDrag( std::move(mimeData), crl::guard(this, [=] { mouseActionUpdate(QCursor::pos()); })); } @@ -3708,7 +3809,10 @@ void ListWidget::repaintItem(const Element *view) { const auto range = view->verticalRepaintRange(); update(0, top + range.top, width(), range.height); const auto id = view->data()->fullId(); - if (const auto area = _reactionsManager->lookupEffectArea(id)) { + const auto area = _reactionsManager + ? _reactionsManager->lookupEffectArea(id) + : std::nullopt; + if (area) { update(*area); } } @@ -3866,7 +3970,9 @@ void ListWidget::itemRemoved(not_null item) { viewReplaced(view, nullptr); _views.erase(i); - _reactionsManager->remove(item->fullId()); + if (_reactionsManager) { + _reactionsManager->remove(item->fullId()); + } updateItemsGeometry(); } @@ -3992,7 +4098,7 @@ void ConfirmDeleteSelectedItems(not_null widget) { } } auto box = Box( - &widget->controller()->session(), + &widget->session(), widget->getSelectedIds()); box->setDeleteConfirmedCallback(crl::guard(widget, [=] { widget->cancelSelection(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 7770341d9..f88582722 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -25,11 +25,14 @@ class Session; } // namespace Main namespace Ui { +class Show; class PopupMenu; class ChatTheme; struct ChatPaintContext; +struct ChatPaintContextArgs; enum class TouchScrollState; struct PeerUserpicView; +class MessageSendingAnimationController; } // namespace Ui namespace Window { @@ -40,6 +43,7 @@ namespace Data { struct Group; struct Reaction; struct AllowedReactions; +struct ReactionId; } // namespace Data namespace HistoryView::Reactions { @@ -154,6 +158,71 @@ public: virtual History *listTranslateHistory() = 0; virtual void listAddTranslatedItems( not_null tracker) = 0; + + // Methods that use Window::SessionController by default. + virtual not_null listWindow() = 0; + virtual not_null listChatStyle() = 0; + virtual rpl::producer listChatWideValue() = 0; + virtual std::unique_ptr listMakeReactionsManager( + QWidget *wheelEventsTarget, + Fn update) = 0; + virtual void listVisibleAreaUpdated() = 0; + virtual std::shared_ptr listUiShow() = 0; + virtual void listShowPollResults( + not_null poll, + FullMsgId context) = 0; + virtual void listCancelUploadLayer(not_null item) = 0; + virtual bool listAnimationsPaused() = 0; + virtual auto listSendingAnimation() + -> Ui::MessageSendingAnimationController* = 0; + virtual Ui::ChatPaintContext listPreparePaintContext( + Ui::ChatPaintContextArgs &&args) = 0; + virtual bool listMarkingContentRead() = 0; + virtual bool listIgnorePaintEvent(QWidget *w, QPaintEvent *e) = 0; + virtual bool listShowReactPremiumError( + not_null item, + const Data::ReactionId &id) = 0; + virtual void listWindowSetInnerFocus() = 0; + virtual bool listAllowsDragForward() = 0; + virtual void listLaunchDrag( + std::unique_ptr data, + Fn finished) = 0; +}; + +class WindowListDelegate : public ListDelegate { +public: + explicit WindowListDelegate(not_null window); + + not_null listWindow() override; + not_null listChatStyle() override; + rpl::producer listChatWideValue() override; + std::unique_ptr listMakeReactionsManager( + QWidget *wheelEventsTarget, + Fn update) override; + void listVisibleAreaUpdated() override; + std::shared_ptr listUiShow() override; + void listShowPollResults( + not_null poll, + FullMsgId context) override; + void listCancelUploadLayer(not_null item) override; + bool listAnimationsPaused() override; + Ui::MessageSendingAnimationController *listSendingAnimation() override; + Ui::ChatPaintContext listPreparePaintContext( + Ui::ChatPaintContextArgs &&args) override; + bool listMarkingContentRead() override; + bool listIgnorePaintEvent(QWidget *w, QPaintEvent *e) override; + bool listShowReactPremiumError( + not_null item, + const Data::ReactionId &id) override; + void listWindowSetInnerFocus() override; + bool listAllowsDragForward() override; + void listLaunchDrag( + std::unique_ptr data, + Fn finished) override; + +private: + const not_null _window; + }; struct SelectionData { @@ -211,7 +280,7 @@ class ListWidget final public: ListWidget( QWidget *parent, - not_null controller, + not_null session, not_null delegate); static const crl::time kItemRevealDuration; @@ -648,7 +717,7 @@ private: static constexpr auto kMinimalIdsLimit = 24; const not_null _delegate; - const not_null _controller; + const not_null _session; const std::unique_ptr _emojiInteractions; const Context _context; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 0051aecea..51a3101eb 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -94,6 +94,7 @@ PinnedWidget::PinnedWidget( not_null controller, not_null thread) : Window::SectionWidget(parent, controller, thread->peer()) +, WindowListDelegate(controller) , _thread(thread->migrateToOrMe()) , _history(thread->owningHistory()) , _migratedPeer(thread->asHistory() @@ -161,7 +162,7 @@ PinnedWidget::PinnedWidget( _inner = _scroll->setOwnedWidget(object_ptr( this, - controller, + &controller->session(), static_cast(this))); _scroll->move(0, _topBar->height()); _scroll->show(); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index 88a592a1b..2c54e0684 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -36,7 +36,7 @@ class TranslateBar; class PinnedWidget final : public Window::SectionWidget - , private ListDelegate + , private WindowListDelegate , private CornerButtonsDelegate { public: PinnedWidget( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 02d9c8412..b1dcb13db 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -205,6 +205,7 @@ RepliesWidget::RepliesWidget( not_null history, MsgId rootId) : Window::SectionWidget(parent, controller, history->peer) +, WindowListDelegate(controller) , _history(history) , _rootId(rootId) , _root(lookupRoot()) @@ -299,7 +300,7 @@ RepliesWidget::RepliesWidget( _inner = _scroll->setOwnedWidget(object_ptr( this, - controller, + &controller->session(), static_cast(this))); _scroll->move(0, _topBar->height()); _scroll->show(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 33b7859b4..e3d05920e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -71,7 +71,7 @@ class TranslateBar; class RepliesWidget final : public Window::SectionWidget - , private ListDelegate + , private WindowListDelegate , private CornerButtonsDelegate { public: RepliesWidget( diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 887d16e09..3fe205b3c 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -97,6 +97,7 @@ ScheduledWidget::ScheduledWidget( not_null history, const Data::ForumTopic *forumTopic) : Window::SectionWidget(parent, controller, history->peer) +, WindowListDelegate(controller) , _history(history) , _forumTopic(forumTopic) , _scroll( @@ -167,7 +168,7 @@ ScheduledWidget::ScheduledWidget( _inner = _scroll->setOwnedWidget(object_ptr( this, - controller, + &controller->session(), static_cast(this))); _scroll->move(0, _topBar->height()); _scroll->show(); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index 6685c113a..9f181d231 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -52,7 +52,7 @@ class StickerToast; class ScheduledWidget final : public Window::SectionWidget - , private ListDelegate + , private WindowListDelegate , private CornerButtonsDelegate { public: ScheduledWidget( diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp index f4116fd15..df4f4ea04 100644 --- a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp @@ -67,6 +67,7 @@ SublistWidget::SublistWidget( not_null controller, not_null sublist) : Window::SectionWidget(parent, controller, sublist->peer()) +, WindowListDelegate(controller) , _sublist(sublist) , _history(sublist->owner().history(sublist->session().user())) , _topBar(this, controller) @@ -133,7 +134,7 @@ SublistWidget::SublistWidget( _inner = _scroll->setOwnedWidget(object_ptr( this, - controller, + &controller->session(), static_cast(this))); _scroll->move(0, _topBar->height()); _scroll->show(); diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.h b/Telegram/SourceFiles/history/view/history_view_sublist_section.h index 7ce880439..eff71d50c 100644 --- a/Telegram/SourceFiles/history/view/history_view_sublist_section.h +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.h @@ -37,7 +37,7 @@ class ComposeSearch; class SublistWidget final : public Window::SectionWidget - , private ListDelegate + , private WindowListDelegate , private CornerButtonsDelegate { public: SublistWidget( diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp index ca82da329..22e419efa 100644 --- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp +++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp @@ -72,7 +72,7 @@ using namespace HistoryView; class ShortcutMessages : public AbstractSection - , private ListDelegate + , private WindowListDelegate , private CornerButtonsDelegate { public: ShortcutMessages( @@ -164,6 +164,7 @@ private: History *listTranslateHistory() override; void listAddTranslatedItems( not_null tracker) override; + bool listIgnorePaintEvent(QWidget *w, QPaintEvent *e) override; // CornerButtonsDelegate delegate. void cornerButtonsShowAtPosition( @@ -330,6 +331,7 @@ ShortcutMessages::ShortcutMessages( rpl::producer containerValue, BusinessShortcutId shortcutId) : AbstractSection(parent) +, WindowListDelegate(controller) , _controller(controller) , _session(&controller->session()) , _scroll(scroll) @@ -370,7 +372,7 @@ ShortcutMessages::ShortcutMessages( _inner = Ui::CreateChild( this, - controller, + &controller->session(), static_cast(this)); _inner->overrideIsChatWide(false); @@ -1053,6 +1055,10 @@ void ShortcutMessages::listAddTranslatedItems( not_null tracker) { } +bool ShortcutMessages::listIgnorePaintEvent(QWidget *w, QPaintEvent *e) { + return false; +} + void ShortcutMessages::cornerButtonsShowAtPosition( Data::MessagePosition position) { showAtPosition(position); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 6b8cc459b..f7499a2d0 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -223,6 +223,14 @@ struct ChatPaintContext { }; +struct ChatPaintContextArgs { + not_null theme; + QRect clip; + QPoint visibleAreaPositionGlobal; + int visibleAreaTop = 0; + int visibleAreaWidth = 0; +}; + [[nodiscard]] int HistoryServiceMsgRadius(); [[nodiscard]] int HistoryServiceMsgInvertedRadius(); [[nodiscard]] int HistoryServiceMsgInvertedShrink(); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 4580991e7..0162d7169 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -2951,7 +2951,7 @@ void SessionController::openPeerStories( } HistoryView::PaintContext SessionController::preparePaintContext( - PaintContextArgs &&args) { + Ui::ChatPaintContextArgs &&args) { const auto visibleAreaTopLocal = content()->mapFromGlobal( args.visibleAreaPositionGlobal).y(); const auto viewport = QRect( diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 4ff92675f..9888dc1c4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -64,6 +64,7 @@ struct ChatThemeBackground; struct ChatThemeBackgroundData; class MessageSendingAnimationController; struct BoostCounters; +struct ChatPaintContextArgs; } // namespace Ui namespace Data { @@ -586,15 +587,8 @@ public: PeerId peerId, std::optional list = std::nullopt); - struct PaintContextArgs { - not_null theme; - QRect clip; - QPoint visibleAreaPositionGlobal; - int visibleAreaTop = 0; - int visibleAreaWidth = 0; - }; [[nodiscard]] Ui::ChatPaintContext preparePaintContext( - PaintContextArgs &&args); + Ui::ChatPaintContextArgs &&args); [[nodiscard]] not_null chatStyle() const { return _chatStyle.get(); } From 4427ae43065ba18d8caeefcda9bd5ebb37893714 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 May 2024 19:39:36 +0400 Subject: [PATCH 024/225] Empty preview widget on long userpic press. --- Telegram/CMakeLists.txt | 2 + .../dialogs/dialogs_inner_widget.cpp | 78 +++++++++++- .../dialogs/dialogs_inner_widget.h | 13 +- .../view/history_view_chat_preview.cpp | 113 ++++++++++++++++++ .../history/view/history_view_chat_preview.h | 26 ++++ Telegram/SourceFiles/ui/chat/chat.style | 9 ++ 6 files changed, 234 insertions(+), 7 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_chat_preview.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_chat_preview.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 88d42a190..88778fc72 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -781,6 +781,8 @@ PRIVATE history/view/history_view_about_view.h history/view/history_view_bottom_info.cpp history/view/history_view_bottom_info.h + history/view/history_view_chat_preview.cpp + history/view/history_view_chat_preview.h history/view/history_view_contact_status.cpp history/view/history_view_contact_status.h history/view/history_view_context_menu.cpp diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index cdf2fef0d..fbf6eb513 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_search_from_controllers.h" #include "dialogs/dialogs_search_tags.h" +#include "history/view/history_view_chat_preview.h" #include "history/view/history_view_context_menu.h" #include "history/history.h" #include "history/history_item.h" @@ -80,6 +81,7 @@ namespace { constexpr auto kHashtagResultsLimit = 5; constexpr auto kStartReorderThreshold = 30; +constexpr auto kChatPreviewDelay = crl::time(1000); int FixedOnTopDialogsCount(not_null list) { auto result = 0; @@ -157,6 +159,7 @@ InnerWidget::InnerWidget( + st::defaultDialogRow.padding.left()) , _cancelSearchInChat(this, st::dialogsCancelSearchInPeer) , _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) +, _chatPreviewTimer([=] { showChatPreview(); }) , _childListShown(std::move(childListShown)) { setAttribute(Qt::WA_OpaquePaintEvent, true); @@ -651,6 +654,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) { context.active = active; context.selected = _menuRow.key ? (row->key() == _menuRow.key) + : _chatPreviewKey + ? (row->key() == _chatPreviewKey) : selected; context.topicJumpSelected = selected && _selectedTopicJump @@ -1268,6 +1273,14 @@ void InnerWidget::mouseMoveEvent(QMouseEvent *e) { return; } selectByMouse(globalPosition); + if (!isUserpicPress()) { + cancelChatPreview(); + } +} + +void InnerWidget::cancelChatPreview() { + _chatPreviewTimer.cancel(); + _chatPreviewWillBeFor = {}; } void InnerWidget::clearIrrelevantState() { @@ -1490,11 +1503,15 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { row->repaint()); } ClickHandler::pressed(); - if (anim::Disabled() + if (pressShowsPreview()) { + _chatPreviewWillBeFor = computeChosenRow().key; + _chatPreviewTimer.callOnce(kChatPreviewDelay); + } else if (anim::Disabled() && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { mousePressReleased(e->globalPos(), e->button(), e->modifiers()); } } + const std::vector &InnerWidget::pinnedChatsOrder() const { const auto owner = &session().data(); return _savedSublists @@ -1513,6 +1530,7 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) { < style::ConvertScale(kStartReorderThreshold)) { return; } + cancelChatPreview(); _dragging = _pressed; if (updateReorderIndexGetCount() < 2) { _dragging = nullptr; @@ -2288,6 +2306,33 @@ void InnerWidget::fillArchiveSearchMenu(not_null menu) { }); } +void InnerWidget::showChatPreview() { + const auto key = base::take(_chatPreviewWillBeFor); + cancelChatPreview(); + if (!pressShowsPreview() || key != computeChosenRow().key) { + return; + } + ClickHandler::unpressed(); + mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier); + + _chatPreviewKey = key; + _menu = HistoryView::MakeChatPreview(this, key.entry()); + if (!_menu) { + return; + } + QObject::connect(_menu.get(), &QObject::destroyed, [=] { + if (_chatPreviewKey) { + updateDialogRow(RowDescriptor(base::take(_chatPreviewKey), {})); + } + const auto globalPosition = QCursor::pos(); + if (rect().contains(mapFromGlobal(globalPosition))) { + setMouseTracking(true); + selectByMouse(globalPosition); + } + }); + _menu->popup(_lastMousePosition.value_or(QCursor::pos())); +} + void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { _menu = nullptr; @@ -2316,7 +2361,9 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { } return RowDescriptor(); }(); - if (!row.key) return; + if (!row.key) { + return; + } _menuRow = row; if (_pressButton != Qt::LeftButton) { @@ -2555,6 +2602,12 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { refresh(); clearMouseSelection(true); } + if (_chatPreviewWillBeFor.topic() == topic) { + _chatPreviewWillBeFor = {}; + } + if (_chatPreviewKey.topic() == topic) { + _chatPreviewKey = {}; + } }, _searchResultsLifetime); } } @@ -3499,6 +3552,23 @@ ChosenRow InnerWidget::computeChosenRow() const { return ChosenRow(); } +bool InnerWidget::isUserpicPress() const { + return (_lastRowLocalMouseX >= 0) + && (_lastRowLocalMouseX < _st->nameLeft) + && (width() > _narrowWidth); +} + +bool InnerWidget::pressShowsPreview() const { + if (!isUserpicPress()) { + return false; + } + const auto key = computeChosenRow().key; + if (const auto history = key.history()) { + return !history->peer->isForum(); + } + return key.topic() != nullptr;; +} + bool InnerWidget::chooseRow( Qt::KeyboardModifiers modifiers, MsgId pressedTopicRootId) { @@ -3511,9 +3581,7 @@ bool InnerWidget::chooseRow( ChosenRow row, Qt::KeyboardModifiers modifiers) { row.newWindow = (modifiers & Qt::ControlModifier); - row.userpicClick = (_lastRowLocalMouseX >= 0) - && (_lastRowLocalMouseX < _st->nameLeft) - && (width() > _narrowWidth); + row.userpicClick = isUserpicPress(); return row; }; auto chosen = modifyChosenRow(computeChosenRow(), modifiers); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 7699576c8..c8186e2ea 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -7,14 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/flags.h" +#include "base/object_ptr.h" +#include "base/timer.h" #include "dialogs/dialogs_key.h" #include "data/data_messages.h" #include "ui/dragging_scroll_manager.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" #include "ui/userpic_view.h" -#include "base/flags.h" -#include "base/object_ptr.h" namespace style { struct DialogRow; @@ -121,6 +122,10 @@ public: void refreshEmptyLabel(); void resizeEmptyLabel(); + [[nodiscard]] bool isUserpicPress() const; + [[nodiscard]] bool pressShowsPreview() const; + void cancelChatPreview(); + void showChatPreview(); bool chooseRow( Qt::KeyboardModifiers modifiers = {}, MsgId pressedTopicRootId = {}); @@ -514,6 +519,10 @@ private: rpl::event_stream _completeHashtagRequests; rpl::event_stream<> _refreshHashtagsRequests; + base::Timer _chatPreviewTimer; + Key _chatPreviewWillBeFor; + Key _chatPreviewKey; + rpl::variable _childListShown; float64 _narrowRatio = 0.; bool _geometryInited = false; diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp new file mode 100644 index 000000000..7779b8b28 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -0,0 +1,113 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_chat_preview.h" + +#include "data/data_peer.h" +#include "history/history.h" +#include "ui/chat/chat_theme.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/menu/menu_item_base.h" +#include "window/themes/window_theme.h" +#include "window/section_widget.h" +#include "styles/style_chat.h" + +namespace HistoryView { +namespace { + +class Item final : public Ui::Menu::ItemBase { +public: + Item(not_null parent, not_null history); + + not_null action() const override; + bool isEnabled() const override; + +private: + void setupBackground(); + + int contentHeight() const override; + + void paintEvent(QPaintEvent *e) override; + + const not_null _dummyAction; + const std::shared_ptr _theme; + + QImage _bg; + +}; + +Item::Item(not_null parent, not_null history) +: Ui::Menu::ItemBase(parent, st::previewMenu.menu) +, _dummyAction(new QAction(parent)) +, _theme(Window::Theme::DefaultChatThemeOn(lifetime())) { + setPointerCursor(false); + setMinWidth(st::previewMenu.menu.widthMin); + resize(minWidth(), contentHeight()); + setupBackground(); +} + +not_null Item::action() const { + return _dummyAction; +} + +bool Item::isEnabled() const { + return false; +} + +int Item::contentHeight() const { + return st::previewMenu.maxHeight; +} + +void Item::setupBackground() { + const auto ratio = style::DevicePixelRatio(); + _bg = QImage( + size() * ratio, + QImage::Format_ARGB32_Premultiplied); + + const auto paint = [=] { + auto p = QPainter(&_bg); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(width(), height() * 2), + QRect(QPoint(), size())); + }; + paint(); + _theme->repaintBackgroundRequests() | rpl::start_with_next([=] { + paint(); + update(); + }, lifetime()); +} + +void Item::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + p.drawImage(0, 0, _bg); +} + +} // namespace + +base::unique_qptr MakeChatPreview( + QWidget *parent, + not_null entry) { + if (const auto topic = entry->asTopic()) { + return nullptr; + } + const auto history = entry->asHistory(); + if (!history || history->peer->isForum()) { + return nullptr; + } + + auto result = base::make_unique_q( + parent, + st::previewMenu); + + result->addAction(base::make_unique_q(result.get(), history)); + + return result; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.h b/Telegram/SourceFiles/history/view/history_view_chat_preview.h new file mode 100644 index 000000000..12a35bbba --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.h @@ -0,0 +1,26 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/unique_qptr.h" + +namespace Dialogs { +class Entry; +} // namespace Dialogs + +namespace Ui { +class PopupMenu; +} // namespace Ui + +namespace HistoryView { + +[[nodiscard]] base::unique_qptr MakeChatPreview( + QWidget *parent, + not_null entry); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 5dd77998a..c72097f25 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1076,3 +1076,12 @@ liveLocationLongInIconSelected: icon {{ "chat/live_location_long", msgInServiceF liveLocationLongOutIcon: icon {{ "chat/live_location_long", msgOutServiceFg }}; liveLocationLongOutIconSelected: icon {{ "chat/live_location_long", msgOutServiceFgSelected }}; liveLocationRemainingSize: 28px; + +previewMenu: PopupMenu(defaultPopupMenu) { + scrollPadding: margins(0px, 0px, 0px, 0px); + menu: Menu(defaultMenu) { + widthMin: 380px; + widthMax: 380px; + } + maxHeight: 420px; +} From da31fef1ae7e512dd1591476c3a24b81ae8353d7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 May 2024 11:26:39 +0400 Subject: [PATCH 025/225] Show long-press preview of topics. --- .../view/history_view_chat_preview.cpp | 463 +++++++++++++++++- .../history/view/history_view_element.h | 1 + .../history/view/history_view_message.cpp | 2 + Telegram/SourceFiles/ui/chat/chat.style | 2 + 4 files changed, 455 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 7779b8b28..c394af68b 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -7,10 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_chat_preview.h" +#include "data/data_forum_topic.h" #include "data/data_peer.h" +#include "data/data_replies_list.h" +#include "data/data_thread.h" +#include "history/view/reactions/history_view_reactions_button.h" +#include "history/view/history_view_list_widget.h" #include "history/history.h" +#include "history/history_item.h" +#include "main/main_session.h" +#include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/elastic_scroll.h" #include "ui/widgets/menu/menu_item_base.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" @@ -19,35 +28,159 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { -class Item final : public Ui::Menu::ItemBase { +class Item final + : public Ui::Menu::ItemBase + , private HistoryView::ListDelegate { public: - Item(not_null parent, not_null history); + Item(not_null parent, not_null thread); not_null action() const override; bool isEnabled() const override; private: - void setupBackground(); - int contentHeight() const override; - void paintEvent(QPaintEvent *e) override; + void setupBackground(); + void updateInnerVisibleArea(); + + Context listContext() override; + bool listScrollTo(int top, bool syntetic = true) override; + void listCancelRequest() override; + void listDeleteRequest() override; + void listTryProcessKeyInput(not_null e) override; + rpl::producer listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) override; + bool listAllowsMultiSelect() override; + bool listIsItemGoodForSelection(not_null item) override; + bool listIsLessInOrder( + not_null first, + not_null second) override; + void listSelectionChanged(SelectedItems &&items) override; + void listMarkReadTill(not_null item) override; + void listMarkContentsRead( + const base::flat_set> &items) override; + MessagesBarData listMessagesBar( + const std::vector> &elements) override; + void listContentRefreshed() override; + void listUpdateDateLink( + ClickHandlerPtr &link, + not_null view) override; + bool listElementHideReply(not_null view) override; + bool listElementShownUnread(not_null view) override; + bool listIsGoodForAroundPosition( + not_null view) override; + void listSendBotCommand( + const QString &command, + const FullMsgId &context) override; + void listSearch( + const QString &query, + const FullMsgId &context) override; + void listHandleViaClick(not_null bot) override; + not_null listChatTheme() override; + CopyRestrictionType listCopyRestrictionType( + HistoryItem *item) override; + CopyRestrictionType listCopyRestrictionType() { + return listCopyRestrictionType(nullptr); + } + CopyRestrictionType listCopyMediaRestrictionType( + not_null item) override; + CopyRestrictionType listSelectRestrictionType() override; + auto listAllowedReactionsValue() + -> rpl::producer override; + void listShowPremiumToast(not_null document) override; + void listOpenPhoto( + not_null photo, + FullMsgId context) override; + void listOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) override; + void listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) override; + QString listElementAuthorRank(not_null view) override; + History *listTranslateHistory() override; + void listAddTranslatedItems( + not_null tracker) override; + not_null listWindow() override; + not_null listChatStyle() override; + rpl::producer listChatWideValue() override; + std::unique_ptr listMakeReactionsManager( + QWidget *wheelEventsTarget, + Fn update) override; + void listVisibleAreaUpdated() override; + std::shared_ptr listUiShow() override; + void listShowPollResults( + not_null poll, + FullMsgId context) override; + void listCancelUploadLayer(not_null item) override; + bool listAnimationsPaused() override; + auto listSendingAnimation() + -> Ui::MessageSendingAnimationController* override; + Ui::ChatPaintContext listPreparePaintContext( + Ui::ChatPaintContextArgs &&args) override; + bool listMarkingContentRead() override; + bool listIgnorePaintEvent(QWidget *w, QPaintEvent *e) override; + bool listShowReactPremiumError( + not_null item, + const Data::ReactionId &id) override; + void listWindowSetInnerFocus() override; + bool listAllowsDragForward() override; + void listLaunchDrag( + std::unique_ptr data, + Fn finished) override; + const not_null _dummyAction; + const not_null _session; + const not_null _thread; + const not_null _history; + const not_null _peer; const std::shared_ptr _theme; + const std::unique_ptr _chatStyle; + const std::unique_ptr _scroll; + + QPointer _inner; QImage _bg; }; -Item::Item(not_null parent, not_null history) +Item::Item(not_null parent, not_null thread) : Ui::Menu::ItemBase(parent, st::previewMenu.menu) , _dummyAction(new QAction(parent)) -, _theme(Window::Theme::DefaultChatThemeOn(lifetime())) { +, _session(&thread->session()) +, _thread(thread) +, _history(thread->owningHistory()) +, _peer(thread->peer()) +, _theme(Window::Theme::DefaultChatThemeOn(lifetime())) +, _chatStyle(std::make_unique(_session->colorIndicesValue())) +, _scroll(std::make_unique(this)) { setPointerCursor(false); setMinWidth(st::previewMenu.menu.widthMin); resize(minWidth(), contentHeight()); setupBackground(); + + _inner = _scroll->setOwnedWidget(object_ptr( + this, + _session, + static_cast(this))); + _scroll->setGeometry(rect()); + _scroll->scrolls( + ) | rpl::start_with_next([=] { + updateInnerVisibleArea(); + }, lifetime()); + _scroll->setOverscrollBg(QColor(0, 0, 0, 0)); + using Type = Ui::ElasticScroll::OverscrollType; + _scroll->setOverscrollTypes(Type::Real, Type::Real); + + _inner->resizeToWidth(_scroll->width(), _scroll->height()); + + _inner->refreshViewer(); + + _inner->setAttribute(Qt::WA_TransparentForMouseEvents); } not_null Item::action() const { @@ -88,24 +221,328 @@ void Item::paintEvent(QPaintEvent *e) { p.drawImage(0, 0, _bg); } +void Item::updateInnerVisibleArea() { + const auto scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); +} + +Context Item::listContext() { + return Context::ChatPreview; +} + +bool Item::listScrollTo(int top, bool syntetic) { + top = std::clamp(top, 0, _scroll->scrollTopMax()); + if (_scroll->scrollTop() == top) { + updateInnerVisibleArea(); + return false; + } + _scroll->scrollToY(top); + return true; +} + +void Item::listCancelRequest() { +} + +void Item::listDeleteRequest() { +} + +void Item::listTryProcessKeyInput(not_null e) { +} + +rpl::producer Item::listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) { + if (const auto topic = _thread->asTopic()) { + return topic->replies()->source( + aroundId, + limitBefore, + limitAfter + ) | rpl::before_next([=] { // after_next makes a copy of value. + //if (!_loaded) { + // _loaded = true; + // crl::on_main(this, [=] { + // updatePinnedVisibility(); + // }); + //} + }); + } + // #TODO + //const auto messageId = aroundId.fullId.msg + // ? aroundId.fullId.msg + // : (ServerMaxMsgId - 1); + + //return SharedMediaMergedViewer( + // &_thread->session(), + // SharedMediaMergedKey( + // SparseIdsMergedSlice::Key( + // _history->peer->id, + // _thread->topicRootId(), + // _migratedPeer ? _migratedPeer->id : 0, + // messageId), + // Storage::SharedMediaType::Pinned), + // limitBefore, + // limitAfter + //) | rpl::map([=](SparseIdsMergedSlice &&slice) { + // auto result = Data::MessagesSlice(); + // result.fullCount = slice.fullCount(); + // result.skippedAfter = slice.skippedAfter(); + // result.skippedBefore = slice.skippedBefore(); + // const auto count = slice.size(); + // result.ids.reserve(count); + // if (const auto msgId = slice.nearest(messageId)) { + // result.nearestToAround = *msgId; + // } + // for (auto i = 0; i != count; ++i) { + // result.ids.push_back(slice[i]); + // } + // return result; + //}); + return rpl::single(Data::MessagesSlice()); +} + +bool Item::listAllowsMultiSelect() { + return false; +} + +bool Item::listIsItemGoodForSelection(not_null item) { + return false; +} + +bool Item::listIsLessInOrder( + not_null first, + not_null second) { + if (first->isRegular() && second->isRegular()) { + const auto firstPeer = first->history()->peer; + const auto secondPeer = second->history()->peer; + if (firstPeer == secondPeer) { + return first->id < second->id; + } else if (firstPeer->isChat()) { + return true; + } + return false; + } else if (first->isRegular()) { + return true; + } else if (second->isRegular()) { + return false; + } + return first->id < second->id; +} + +void Item::listSelectionChanged(SelectedItems &&items) { +} + +void Item::listMarkReadTill(not_null item) { +} + +void Item::listMarkContentsRead( + const base::flat_set> &items) { +} + +MessagesBarData Item::listMessagesBar( + const std::vector> &elements) { + return {};// #TODO +} + +void Item::listContentRefreshed() { +} + +void Item::listUpdateDateLink( + ClickHandlerPtr &link, + not_null view) { +} + +bool Item::listElementHideReply(not_null view) { + return false; +} + +bool Item::listElementShownUnread(not_null view) { + return view->data()->unread(view->data()->history()); +} + +bool Item::listIsGoodForAroundPosition(not_null view) { + return view->data()->isRegular(); +} + +void Item::listSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + +void Item::listSearch( + const QString &query, + const FullMsgId &context) { +} + +void Item::listHandleViaClick(not_null bot) { +} + +not_null Item::listChatTheme() { + return _theme.get(); +} + +CopyRestrictionType Item::listCopyRestrictionType(HistoryItem *item) { + return CopyRestrictionType::None; +} + +CopyRestrictionType Item::listCopyMediaRestrictionType( + not_null item) { + return CopyRestrictionType::None; +} + +CopyRestrictionType Item::listSelectRestrictionType() { + return CopyRestrictionType::None; +} + +auto Item::listAllowedReactionsValue() +-> rpl::producer { + return rpl::single(Data::AllowedReactions()); +} + +void Item::listShowPremiumToast(not_null document) { +} + +void Item::listOpenPhoto( + not_null photo, + FullMsgId context) { +} + +void Item::listOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) { +} + +void Item::listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) { + // #TODO +} + +QString Item::listElementAuthorRank(not_null view) { + return {}; +} + +History *Item::listTranslateHistory() { + return nullptr; +} + +void Item::listAddTranslatedItems( + not_null tracker) { +} + +not_null Item::listWindow() { + Unexpected("Item::listWindow."); +} + +not_null Item::listChatStyle() { + return _chatStyle.get(); +} + +rpl::producer Item::listChatWideValue() { + return rpl::single(false); +} + +std::unique_ptr Item::listMakeReactionsManager( + QWidget *wheelEventsTarget, + Fn update) { + return nullptr; +} + +void Item::listVisibleAreaUpdated() { +} + +std::shared_ptr Item::listUiShow() { + Unexpected("Item::listUiShow."); +} + +void Item::listShowPollResults( + not_null poll, + FullMsgId context) { +} + +void Item::listCancelUploadLayer(not_null item) { +} + +bool Item::listAnimationsPaused() { + return false; +} + +auto Item::listSendingAnimation() +-> Ui::MessageSendingAnimationController* { + return nullptr; +} + +Ui::ChatPaintContext Item::listPreparePaintContext( + Ui::ChatPaintContextArgs &&args) { + const auto visibleAreaTopLocal = mapFromGlobal( + args.visibleAreaPositionGlobal).y(); + const auto viewport = QRect( + 0, + args.visibleAreaTop - visibleAreaTopLocal, + args.visibleAreaWidth, + height()); + return args.theme->preparePaintContext( + _chatStyle.get(), + viewport, + args.clip, + false); +} + +bool Item::listMarkingContentRead() { + return false; +} + +bool Item::listIgnorePaintEvent(QWidget *w, QPaintEvent *e) { + return false; +} + +bool Item::listShowReactPremiumError( + not_null item, + const Data::ReactionId &id) { + return false; +} + +void Item::listWindowSetInnerFocus() { +} + +bool Item::listAllowsDragForward() { + return false; +} + +void Item::listLaunchDrag( + std::unique_ptr data, + Fn finished) { +} + } // namespace base::unique_qptr MakeChatPreview( QWidget *parent, not_null entry) { - if (const auto topic = entry->asTopic()) { - return nullptr; - } - const auto history = entry->asHistory(); - if (!history || history->peer->isForum()) { + const auto thread = entry->asThread(); + if (!thread) { return nullptr; + } else if (const auto history = entry->asHistory()) { + if (history->peer->isForum()) { + return nullptr; + } } auto result = base::make_unique_q( parent, st::previewMenu); - result->addAction(base::make_unique_q(result.get(), history)); + result->addAction(base::make_unique_q(result.get(), thread)); + if (const auto topic = thread->asTopic()) { + const auto weak = Ui::MakeWeak(result.get()); + topic->destroyed() | rpl::start_with_next([weak] { + if (const auto strong = weak.data()) { + LOG(("Preview hidden for a destroyed topic.")); + strong->hideMenu(true); + } + }, result->lifetime()); + } return result; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 6db33ff4b..a11d0831d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -61,6 +61,7 @@ enum class Context : char { TTLViewer, ShortcutMessages, ScheduledTopic, + ChatPreview, }; enum class OnlyEmojiAndSpaces : char { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 4eddd3b49..cc0b4aec7 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2081,6 +2081,7 @@ bool Message::hasFromPhoto() const { case Context::AdminLog: return true; case Context::History: + case Context::ChatPreview: case Context::TTLViewer: case Context::Pinned: case Context::Replies: @@ -3281,6 +3282,7 @@ bool Message::hasFromName() const { case Context::AdminLog: return true; case Context::History: + case Context::ChatPreview: case Context::TTLViewer: case Context::Pinned: case Context::Replies: diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index c72097f25..5c2cbf99d 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1084,4 +1084,6 @@ previewMenu: PopupMenu(defaultPopupMenu) { widthMax: 380px; } maxHeight: 420px; + radius: boxRadius; + shadow: boxRoundShadow; } From 68df8448a2fe9a915f7e268190921428065eeb5d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 May 2024 11:50:44 +0400 Subject: [PATCH 026/225] Use arc_angles.h from lib_ui. --- .../history/view/media/history_view_location.cpp | 5 ++++- Telegram/SourceFiles/ui/arc_angles.h | 16 ---------------- Telegram/cmake/td_ui.cmake | 1 - 3 files changed, 4 insertions(+), 18 deletions(-) delete mode 100644 Telegram/SourceFiles/ui/arc_angles.h diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index f16d65141..5b8b18e61 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -509,7 +509,10 @@ void Location::paintLiveRemaining( auto pen = QPen(color); pen.setWidthF(stroke); p.setPen(pen); - p.drawArc(rect, 90 * 16, int(base::SafeRound(360 * 16 * progress))); + p.drawArc( + rect, + arc::kQuarterLength, + int(base::SafeRound(arc::kFullLength * progress))); } p.setPen(stm->msgServiceFg); diff --git a/Telegram/SourceFiles/ui/arc_angles.h b/Telegram/SourceFiles/ui/arc_angles.h deleted file mode 100644 index cfaa59b94..000000000 --- a/Telegram/SourceFiles/ui/arc_angles.h +++ /dev/null @@ -1,16 +0,0 @@ -/* -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 - -namespace arc { - -constexpr auto kFullLength = 360 * 16; -constexpr auto kQuarterLength = (kFullLength / 4); -constexpr auto kHalfLength = (kFullLength / 2); - -} // namespace arc diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 61c9012c1..da2ea224d 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -396,7 +396,6 @@ PRIVATE ui/widgets/vertical_drum_picker.cpp ui/widgets/vertical_drum_picker.h - ui/arc_angles.h ui/cached_round_corners.cpp ui/cached_round_corners.h ui/color_contrast.cpp From f223ae7eeeb83ecd097ae528ec64f756db383ba5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 May 2024 14:47:08 +0400 Subject: [PATCH 027/225] Implement chats preview, show from unread. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/apiwrap.cpp | 41 ++++ Telegram/SourceFiles/apiwrap.h | 18 +- .../data/data_history_messages.cpp | 212 ++++++++++++++++++ .../SourceFiles/data/data_history_messages.h | 67 ++++++ Telegram/SourceFiles/data/data_replies_list.h | 4 - .../data/data_search_controller.cpp | 55 +++++ .../SourceFiles/data/data_search_controller.h | 19 +- Telegram/SourceFiles/history/history.cpp | 86 +++++-- Telegram/SourceFiles/history/history.h | 34 +-- Telegram/SourceFiles/history/history_item.cpp | 11 + Telegram/SourceFiles/history/history_item.h | 1 + .../view/history_view_chat_preview.cpp | 92 ++++---- .../history/view/history_view_list_widget.cpp | 4 +- .../storage/storage_sparse_ids_list.cpp | 17 +- Telegram/SourceFiles/ui/chat/chat.style | 3 + 16 files changed, 570 insertions(+), 96 deletions(-) create mode 100644 Telegram/SourceFiles/data/data_history_messages.cpp create mode 100644 Telegram/SourceFiles/data/data_history_messages.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 88778fc72..6426c6446 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -539,6 +539,8 @@ PRIVATE data/data_groups.h data/data_histories.cpp data/data_histories.h + data/data_history_messages.cpp + data/data_history_messages.h data/data_lastseen_status.h data/data_location.cpp data/data_location.h diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index aeed9e1b3..32e97c77f 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_chat_filters.h" #include "data/data_histories.h" +#include "data/data_history_messages.h" #include "core/core_cloud_password.h" #include "core/application.h" #include "base/unixtime.h" @@ -3078,6 +3079,46 @@ void ApiWrap::resolveJumpToHistoryDate( } } +void ApiWrap::requestHistory( + not_null history, + MsgId messageId, + SliceType slice) { + const auto peer = history->peer; + const auto key = HistoryRequest{ + peer, + messageId, + slice, + }; + if (_historyRequests.contains(key)) { + return; + } + + const auto prepared = Api::PrepareHistoryRequest(peer, messageId, slice); + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::History; + histories.sendRequest(history, requestType, [=](Fn finish) { + return request( + std::move(prepared) + ).done([=](const Api::HistoryRequestResult &result) { + _historyRequests.remove(key); + auto parsed = Api::ParseHistoryResult( + peer, + messageId, + slice, + result); + history->messages().addSlice( + std::move(parsed.messageIds), + parsed.noSkipRange, + parsed.fullCount); + finish(); + }).fail([=] { + _historyRequests.remove(key); + finish(); + }).send(); + }); + _historyRequests.emplace(key); +} + void ApiWrap::requestSharedMedia( not_null peer, MsgId topicRootId, diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index b1c844702..9710ee1bd 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -274,6 +274,10 @@ public: Fn, MsgId)> callback); using SliceType = Data::LoadDirection; + void requestHistory( + not_null history, + MsgId messageId, + SliceType slice); void requestSharedMedia( not_null peer, MsgId topicRootId, @@ -511,7 +515,8 @@ private: not_null peer, bool justClear, bool revoke); - void applyAffectedMessages(const MTPmessages_AffectedMessages &result) const; + void applyAffectedMessages( + const MTPmessages_AffectedMessages &result) const; void deleteAllFromParticipantSend( not_null channel, @@ -645,6 +650,17 @@ private: }; base::flat_set _sharedMediaRequests; + struct HistoryRequest { + not_null peer; + MsgId aroundId = 0; + SliceType sliceType = {}; + + friend inline auto operator<=>( + const HistoryRequest&, + const HistoryRequest&) = default; + }; + base::flat_set _historyRequests; + std::unique_ptr _dialogsLoadState; TimeId _dialogsLoadTill = 0; rpl::variable _dialogsLoadMayBlockByDate = false; diff --git a/Telegram/SourceFiles/data/data_history_messages.cpp b/Telegram/SourceFiles/data/data_history_messages.cpp new file mode 100644 index 000000000..7c29daf9e --- /dev/null +++ b/Telegram/SourceFiles/data/data_history_messages.cpp @@ -0,0 +1,212 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_history_messages.h" + +#include "apiwrap.h" +#include "data/data_chat.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_sparse_ids.h" +#include "history/history.h" +#include "main/main_session.h" + +namespace Data { + +void HistoryMessages::addNew(MsgId messageId) { + _chat.addNew(messageId); +} + +void HistoryMessages::addExisting(MsgId messageId, MsgRange noSkipRange) { + _chat.addExisting(messageId, noSkipRange); +} + +void HistoryMessages::addSlice( + std::vector &&messageIds, + MsgRange noSkipRange, + std::optional count) { + _chat.addSlice(std::move(messageIds), noSkipRange, count); +} + +void HistoryMessages::removeOne(MsgId messageId) { + _chat.removeOne(messageId); + _oneRemoved.fire_copy(messageId); +} + +void HistoryMessages::removeAll() { + _chat.removeAll(); + _allRemoved.fire({}); +} + +void HistoryMessages::invalidateBottom() { + _chat.invalidateBottom(); + _bottomInvalidated.fire({}); +} + +Storage::SparseIdsListResult HistoryMessages::snapshot( + const Storage::SparseIdsListQuery &query) const { + return _chat.snapshot(query); +} + +auto HistoryMessages::sliceUpdated() const +-> rpl::producer { + return _chat.sliceUpdated(); +} + +rpl::producer HistoryMessages::oneRemoved() const { + return _oneRemoved.events(); +} + +rpl::producer<> HistoryMessages::allRemoved() const { + return _allRemoved.events(); +} + +rpl::producer<> HistoryMessages::bottomInvalidated() const { + return _bottomInvalidated.events(); +} + +rpl::producer HistoryViewer( + not_null history, + MsgId aroundId, + int limitBefore, + int limitAfter) { + Expects(IsServerMsgId(aroundId) || (aroundId == 0)); + Expects((aroundId != 0) || (limitBefore == 0 && limitAfter == 0)); + + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + const auto messages = &history->messages(); + + auto builder = lifetime.make_state( + aroundId, + limitBefore, + limitAfter); + using RequestAroundInfo = SparseIdsSliceBuilder::AroundData; + builder->insufficientAround( + ) | rpl::start_with_next([=](const RequestAroundInfo &info) { + history->session().api().requestHistory( + history, + info.aroundId, + info.direction); + }, lifetime); + + auto pushNextSnapshot = [=] { + consumer.put_next(builder->snapshot()); + }; + + using SliceUpdate = Storage::SparseIdsSliceUpdate; + messages->sliceUpdated( + ) | rpl::filter([=](const SliceUpdate &update) { + return builder->applyUpdate(update); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + messages->oneRemoved( + ) | rpl::filter([=](MsgId messageId) { + return builder->removeOne(messageId); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + messages->allRemoved( + ) | rpl::filter([=] { + return builder->removeAll(); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + messages->bottomInvalidated( + ) | rpl::filter([=] { + return builder->invalidateBottom(); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + const auto snapshot = messages->snapshot({ + aroundId, + limitBefore, + limitAfter, + }); + if (snapshot.count || !snapshot.messageIds.empty()) { + if (builder->applyInitial(snapshot)) { + pushNextSnapshot(); + } + } + builder->checkInsufficient(); + + return lifetime; + }; +} + +rpl::producer HistoryMergedViewer( + not_null history, + /*Universal*/MsgId universalAroundId, + int limitBefore, + int limitAfter) { + const auto migrateFrom = history->peer->migrateFrom(); + auto createSimpleViewer = [=]( + PeerId peerId, + MsgId topicRootId, + SparseIdsSlice::Key simpleKey, + int limitBefore, + int limitAfter) { + const auto chosen = (history->peer->id == peerId) + ? history + : history->owner().history(peerId); + return HistoryViewer(history, simpleKey, limitBefore, limitAfter); + }; + const auto peerId = history->peer->id; + const auto topicRootId = MsgId(); + const auto migratedPeerId = migrateFrom ? migrateFrom->id : peerId; + using Key = SparseIdsMergedSlice::Key; + return SparseIdsMergedSlice::CreateViewer( + Key(peerId, topicRootId, migratedPeerId, universalAroundId), + limitBefore, + limitAfter, + std::move(createSimpleViewer)); +} + +rpl::producer HistoryMessagesViewer( + not_null history, + MessagePosition aroundId, + int limitBefore, + int limitAfter) { + const auto computeUnreadAroundId = [&] { + if (const auto migrated = history->migrateFrom()) { + if (const auto around = migrated->loadAroundId()) { + return MsgId(around - ServerMaxMsgId); + } + } + if (const auto around = history->loadAroundId()) { + return around; + } + return MsgId(ServerMaxMsgId - 1); + }; + const auto messageId = (aroundId.fullId.msg == ShowAtUnreadMsgId) + ? computeUnreadAroundId() + : (aroundId.fullId.msg == ShowAtTheEndMsgId) + ? (ServerMaxMsgId - 1) + : (aroundId.fullId.peer == history->peer->id) + ? aroundId.fullId.msg + : (aroundId.fullId.msg - ServerMaxMsgId); + return HistoryMergedViewer( + history, + messageId, + limitBefore, + limitAfter + ) | rpl::map([=](SparseIdsMergedSlice &&slice) { + auto result = Data::MessagesSlice(); + result.fullCount = slice.fullCount(); + result.skippedAfter = slice.skippedAfter(); + result.skippedBefore = slice.skippedBefore(); + const auto count = slice.size(); + result.ids.reserve(count); + if (const auto msgId = slice.nearest(messageId)) { + result.nearestToAround = *msgId; + } + for (auto i = 0; i != count; ++i) { + result.ids.push_back(slice[i]); + } + return result; + }); +} + +} // namespace Data \ No newline at end of file diff --git a/Telegram/SourceFiles/data/data_history_messages.h b/Telegram/SourceFiles/data/data_history_messages.h new file mode 100644 index 000000000..2b68462fb --- /dev/null +++ b/Telegram/SourceFiles/data/data_history_messages.h @@ -0,0 +1,67 @@ +/* +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 "storage/storage_sparse_ids_list.h" + +class History; +class SparseIdsSlice; +class SparseIdsMergedSlice; + +namespace Data { + +struct MessagesSlice; +struct MessagePosition; + +class HistoryMessages final { +public: + void addNew(MsgId messageId); + void addExisting(MsgId messageId, MsgRange noSkipRange); + void addSlice( + std::vector &&messageIds, + MsgRange noSkipRange, + std::optional count); + void removeOne(MsgId messageId); + void removeAll(); + void invalidateBottom(); + + [[nodiscard]] Storage::SparseIdsListResult snapshot( + const Storage::SparseIdsListQuery &query) const; + [[nodiscard]] auto sliceUpdated() const + -> rpl::producer; + [[nodiscard]] rpl::producer oneRemoved() const; + [[nodiscard]] rpl::producer<> allRemoved() const; + [[nodiscard]] rpl::producer<> bottomInvalidated() const; + +private: + Storage::SparseIdsList _chat; + rpl::event_stream _oneRemoved; + rpl::event_stream<> _allRemoved; + rpl::event_stream<> _bottomInvalidated; + +}; + +[[nodiscard]] rpl::producer HistoryViewer( + not_null history, + MsgId aroundId, + int limitBefore, + int limitAfter); + +[[nodiscard]] rpl::producer HistoryMergedViewer( + not_null history, + /*Universal*/MsgId universalAroundId, + int limitBefore, + int limitAfter); + +[[nodiscard]] rpl::producer HistoryMessagesViewer( + not_null history, + MessagePosition aroundId, + int limitBefore, + int limitAfter); + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index a33dee56b..42f56c1ae 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -78,10 +78,6 @@ private: [[nodiscard]] Histories &histories(); void subscribeToUpdates(); - [[nodiscard]] rpl::producer sourceFromServer( - MessagePosition aroundId, - int limitBefore, - int limitAfter); void appendClientSideMessages(MessagesSlice &slice); [[nodiscard]] bool buildFromData(not_null viewer); diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 43c5efb66..4e33a9eaa 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -21,6 +21,7 @@ namespace { constexpr auto kSharedMediaLimit = 100; constexpr auto kFirstSharedMediaLimit = 0; +constexpr auto kHistoryLimit = 50; constexpr auto kDefaultSearchTimeoutMs = crl::time(200); } // namespace @@ -199,6 +200,60 @@ SearchResult ParseSearchResult( return result; } +HistoryRequest PrepareHistoryRequest( + not_null peer, + MsgId messageId, + Data::LoadDirection direction) { + const auto minId = 0; + const auto maxId = 0; + const auto limit = kHistoryLimit; + const auto offsetId = [&] { + switch (direction) { + case Data::LoadDirection::Before: + case Data::LoadDirection::Around: return messageId; + case Data::LoadDirection::After: return messageId + 1; + } + Unexpected("Direction in PrepareSearchRequest"); + }(); + const auto addOffset = [&] { + switch (direction) { + case Data::LoadDirection::Before: return 0; + case Data::LoadDirection::Around: return -limit / 2; + case Data::LoadDirection::After: return -limit; + } + Unexpected("Direction in PrepareSearchRequest"); + }(); + const auto hash = uint64(0); + const auto offsetDate = int32(0); + + const auto mtpOffsetId = int(std::clamp( + offsetId.bare, + int64(0), + int64(0x3FFFFFFF))); + return MTPmessages_GetHistory( + peer->input, + MTP_int(mtpOffsetId), + MTP_int(offsetDate), + MTP_int(addOffset), + MTP_int(limit), + MTP_int(maxId), + MTP_int(minId), + MTP_long(hash)); +} + +HistoryResult ParseHistoryResult( + not_null peer, + MsgId messageId, + Data::LoadDirection direction, + const HistoryRequestResult &data) { + return ParseSearchResult( + peer, + Storage::SharedMediaType::kCount, + messageId, + direction, + data); +} + SearchController::CacheEntry::CacheEntry( not_null session, const Query &query) diff --git a/Telegram/SourceFiles/data/data_search_controller.h b/Telegram/SourceFiles/data/data_search_controller.h index a938df6ef..26b930b9b 100644 --- a/Telegram/SourceFiles/data/data_search_controller.h +++ b/Telegram/SourceFiles/data/data_search_controller.h @@ -32,7 +32,11 @@ struct SearchResult { using SearchRequest = MTPmessages_Search; using SearchRequestResult = MTPmessages_Messages; -std::optional PrepareSearchRequest( +using HistoryResult = SearchResult; +using HistoryRequest = MTPmessages_GetHistory; +using HistoryRequestResult = MTPmessages_Messages; + +[[nodiscard]] std::optional PrepareSearchRequest( not_null peer, MsgId topicRootId, Storage::SharedMediaType type, @@ -40,13 +44,24 @@ std::optional PrepareSearchRequest( MsgId messageId, Data::LoadDirection direction); -SearchResult ParseSearchResult( +[[nodiscard]] SearchResult ParseSearchResult( not_null peer, Storage::SharedMediaType type, MsgId messageId, Data::LoadDirection direction, const SearchRequestResult &data); +[[nodiscard]] HistoryRequest PrepareHistoryRequest( + not_null peer, + MsgId messageId, + Data::LoadDirection direction); + +[[nodiscard]] HistoryResult ParseHistoryResult( + not_null peer, + MsgId messageId, + Data::LoadDirection direction, + const HistoryRequestResult &data); + class SearchController final { public: using IdsList = Storage::SparseIdsList; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index dca53737d..3643ed726 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_document.h" #include "data/data_histories.h" +#include "data/data_history_messages.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "api/api_chat_participants.h" @@ -483,7 +484,7 @@ not_null History::insertItem( std::unique_ptr item) { Expects(item != nullptr); - const auto &[i, ok] = _messages.insert(std::move(item)); + const auto &[i, ok] = _items.insert(std::move(item)); const auto result = i->get(); owner().registerMessage(result); @@ -500,6 +501,9 @@ void History::destroyMessage(not_null item) { // All this must be done for all items manually in History::clear()! item->destroyHistoryEntry(); if (item->isRegular()) { + if (const auto messages = _messages.get()) { + messages->removeOne(item->id); + } if (const auto types = item->sharedMediaTypes()) { session().storage().remove(Storage::SharedMediaRemoveOne( peerId, @@ -524,11 +528,11 @@ void History::destroyMessage(not_null item) { Core::App().notifications().clearFromItem(item); auto hack = std::unique_ptr(item.get()); - const auto i = _messages.find(hack); + const auto i = _items.find(hack); hack.release(); - Assert(i != end(_messages)); - _messages.erase(i); + Assert(i != end(_items)); + _items.erase(i); if (documentToCancel) { session().data().documentMessageRemoved(documentToCancel); @@ -537,8 +541,8 @@ void History::destroyMessage(not_null item) { void History::destroyMessagesByDates(TimeId minDate, TimeId maxDate) { auto toDestroy = std::vector>(); - toDestroy.reserve(_messages.size()); - for (const auto &message : _messages) { + toDestroy.reserve(_items.size()); + for (const auto &message : _items) { if (message->isRegular() && message->date() > minDate && message->date() < maxDate) { @@ -552,8 +556,8 @@ void History::destroyMessagesByDates(TimeId minDate, TimeId maxDate) { void History::destroyMessagesByTopic(MsgId topicRootId) { auto toDestroy = std::vector>(); - toDestroy.reserve(_messages.size()); - for (const auto &message : _messages) { + toDestroy.reserve(_items.size()); + for (const auto &message : _items) { if (message->topicRootId() == topicRootId) { toDestroy.push_back(message.get()); } @@ -575,7 +579,7 @@ void History::unpinMessagesFor(MsgId topicRootId) { topic->setHasPinnedMessages(false); }); } - for (const auto &item : _messages) { + for (const auto &item : _items) { if (item->isPinned()) { item->setIsPinned(false); } @@ -589,7 +593,7 @@ void History::unpinMessagesFor(MsgId topicRootId) { if (const auto topic = peer->forumTopicFor(topicRootId)) { topic->setHasPinnedMessages(false); } - for (const auto &item : _messages) { + for (const auto &item : _items) { if (item->isPinned() && item->topicRootId() == topicRootId) { item->setIsPinned(false); } @@ -781,9 +785,12 @@ not_null History::addNewToBack( addItemToBlock(item); if (!unread && item->isRegular()) { + const auto from = loadedAtTop() ? 0 : minMsgId(); + const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); + if (_messages) { + _messages->addExisting(item->id, { from, till }); + } if (const auto types = item->sharedMediaTypes()) { - auto from = loadedAtTop() ? 0 : minMsgId(); - auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); auto &storage = session().storage(); storage.add(Storage::SharedMediaAddExisting( peer->id, @@ -1190,6 +1197,7 @@ void History::mainViewRemoved( void History::newItemAdded(not_null item) { item->indexAsNewItem(); + item->addToMessagesIndex(); if (const auto from = item->from() ? item->from()->asUser() : nullptr) { if (from == item->author()) { _sendActionPainter.clear(from); @@ -2385,6 +2393,9 @@ void History::setNotLoadedAtBottom() { session().storage().invalidate( Storage::SharedMediaInvalidateBottom(peer->id)); + if (const auto messages = _messages.get()) { + messages->invalidateBottom(); + } } void History::clearSharedMedia() { @@ -3092,6 +3103,46 @@ MsgRange History::rangeForDifferenceRequest() const { return MsgRange(); } +Data::HistoryMessages &History::messages() { + if (!_messages) { + _messages = std::make_unique(); + + const auto from = loadedAtTop() ? 0 : minMsgId(); + const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); + auto list = std::vector(); + list.reserve(std::min( + int(_items.size()), + int(blocks.size()) * kNewBlockEachMessage)); + auto sort = false; + for (const auto &block : blocks) { + for (const auto &view : block->messages) { + const auto item = view->data(); + if (item->isRegular()) { + const auto id = item->id; + if (!list.empty() && list.back() >= id) { + sort = true; + } + } + } + } + if (sort) { + ranges::sort(list); + } + if (till) { + _messages->addSlice(std::move(list), { from, till }, {}); + } + } + return *_messages; +} + +const Data::HistoryMessages &History::messages() const { + return const_cast(this)->messages(); +} + +Data::HistoryMessages *History::maybeMessages() { + return _messages.get(); +} + HistoryItem *History::insertJoinedMessage() { const auto channel = peer->asChannel(); if (!channel @@ -3194,11 +3245,11 @@ void History::removeJoinedMessage() { void History::reactionsEnabledChanged(bool enabled) { if (!enabled) { - for (const auto &item : _messages) { + for (const auto &item : _items) { item->updateReactions(nullptr); } } else { - for (const auto &item : _messages) { + for (const auto &item : _items) { item->updateReactionsUnknown(); } } @@ -3372,6 +3423,9 @@ void History::clear(ClearType type) { } _loadedAtTop = _loadedAtBottom = _lastMessage.has_value(); clearSharedMedia(); + if (const auto messages = _messages.get()) { + messages->removeAll(); + } clearLastKeyboard(); } @@ -3388,8 +3442,8 @@ void History::clear(ClearType type) { void History::clearUpTill(MsgId availableMinId) { auto remove = std::vector>(); - remove.reserve(_messages.size()); - for (const auto &item : _messages) { + remove.reserve(_items.size()); + for (const auto &item : _items) { const auto itemId = item->id; if (!item->isRegular()) { continue; diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index e00c23b8d..729b66045 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -26,12 +26,14 @@ class HistoryMainElementDelegateMixin; struct LanguageId; namespace Data { + struct Draft; class Session; class Folder; class ChatFilter; struct SponsoredFrom; class SponsoredMessages; +class HistoryMessages; enum class ForwardOptions { PreserveInfo, @@ -79,7 +81,7 @@ public: History(not_null owner, PeerId peerId); ~History(); - not_null owningHistory() override { + [[nodiscard]] not_null owningHistory() override { return this; } [[nodiscard]] Data::Thread *threadFor(MsgId topicRootId); @@ -93,23 +95,27 @@ public: void forumChanged(Data::Forum *old); [[nodiscard]] bool isForum() const; - not_null migrateToOrMe() const; - History *migrateFrom() const; - MsgRange rangeForDifferenceRequest() const; + [[nodiscard]] not_null migrateToOrMe() const; + [[nodiscard]] History *migrateFrom() const; + [[nodiscard]] MsgRange rangeForDifferenceRequest() const; - HistoryItem *joinedMessageInstance() const; + [[nodiscard]] Data::HistoryMessages &messages(); + [[nodiscard]] const Data::HistoryMessages &messages() const; + [[nodiscard]] Data::HistoryMessages *maybeMessages(); + + [[nodiscard]] HistoryItem *joinedMessageInstance() const; void checkLocalMessages(); void removeJoinedMessage(); void reactionsEnabledChanged(bool enabled); - bool isEmpty() const; - bool isDisplayedEmpty() const; - Element *findFirstNonEmpty() const; - Element *findFirstDisplayed() const; - Element *findLastNonEmpty() const; - Element *findLastDisplayed() const; - bool hasOrphanMediaGroupPart() const; + [[nodiscard]] bool isEmpty() const; + [[nodiscard]] bool isDisplayedEmpty() const; + [[nodiscard]] Element *findFirstNonEmpty() const; + [[nodiscard]] Element *findFirstDisplayed() const; + [[nodiscard]] Element *findLastNonEmpty() const; + [[nodiscard]] Element *findLastDisplayed() const; + [[nodiscard]] bool hasOrphanMediaGroupPart() const; [[nodiscard]] std::vector collectMessagesFromParticipantToDelete( not_null participant) const; @@ -590,7 +596,9 @@ private: std::optional _lastMessage; std::optional _lastServerMessage; base::flat_set> _clientSideMessages; - std::unordered_set> _messages; + std::unordered_set> _items; + + std::unique_ptr _messages; // This almost always is equal to _lastMessage. The only difference is // for a group that migrated to a supergroup. Then _lastMessage can diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index cdc57d00d..53ba1c67c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_game.h" +#include "data/data_history_messages.h" #include "data/data_user.h" #include "data/data_group_call.h" // Data::GroupCall::id(). #include "data/data_poll.h" // PollData::publicVotes. @@ -1791,6 +1792,7 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { setIsPinned(data.is_pinned()); contributeToSlowmode(data.vdate().v); addToSharedMediaIndex(); + addToMessagesIndex(); invalidateChatListEntry(); if (const auto period = data.vttl_period(); period && period->v > 0) { applyTTL(data.vdate().v + period->v); @@ -1815,6 +1817,7 @@ void HistoryItem::applySentMessage( contributeToSlowmode(data.vdate().v); if (!wasAlready) { addToSharedMediaIndex(); + addToMessagesIndex(); } invalidateChatListEntry(); if (const auto period = data.vttl_period(); period && period->v > 0) { @@ -2011,6 +2014,14 @@ void HistoryItem::removeFromSharedMediaIndex() { } } +void HistoryItem::addToMessagesIndex() { + if (isRegular()) { + if (const auto messages = _history->maybeMessages()) { + messages->addNew(id); + } + } +} + void HistoryItem::incrementReplyToTopCounter() { if (isRegular() && _history->peer->isMegagroup()) { _history->session().changes().messageUpdated( diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index c8963b707..c7d3e59aa 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -359,6 +359,7 @@ public: void indexAsNewItem(); void addToSharedMediaIndex(); + void addToMessagesIndex(); void removeFromSharedMediaIndex(); struct NotificationTextOptions { diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index c394af68b..caca93da1 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_chat_preview.h" #include "data/data_forum_topic.h" +#include "data/data_history_messages.h" #include "data/data_peer.h" #include "data/data_replies_list.h" #include "data/data_thread.h" @@ -15,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_list_widget.h" #include "history/history.h" #include "history/history_item.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" @@ -136,6 +138,7 @@ private: const not_null _dummyAction; const not_null _session; const not_null _thread; + const std::shared_ptr _replies; const not_null _history; const not_null _peer; const std::shared_ptr _theme; @@ -153,6 +156,7 @@ Item::Item(not_null parent, not_null thread) , _dummyAction(new QAction(parent)) , _session(&thread->session()) , _thread(thread) +, _replies(thread->asTopic() ? thread->asTopic()->replies() : nullptr) , _history(thread->owningHistory()) , _peer(thread->peer()) , _theme(Window::Theme::DefaultChatThemeOn(lifetime())) @@ -253,52 +257,13 @@ rpl::producer Item::listSource( Data::MessagePosition aroundId, int limitBefore, int limitAfter) { - if (const auto topic = _thread->asTopic()) { - return topic->replies()->source( + return _replies + ? _replies->source(aroundId, limitBefore, limitAfter) + : Data::HistoryMessagesViewer( + _thread->asHistory(), aroundId, limitBefore, - limitAfter - ) | rpl::before_next([=] { // after_next makes a copy of value. - //if (!_loaded) { - // _loaded = true; - // crl::on_main(this, [=] { - // updatePinnedVisibility(); - // }); - //} - }); - } - // #TODO - //const auto messageId = aroundId.fullId.msg - // ? aroundId.fullId.msg - // : (ServerMaxMsgId - 1); - - //return SharedMediaMergedViewer( - // &_thread->session(), - // SharedMediaMergedKey( - // SparseIdsMergedSlice::Key( - // _history->peer->id, - // _thread->topicRootId(), - // _migratedPeer ? _migratedPeer->id : 0, - // messageId), - // Storage::SharedMediaType::Pinned), - // limitBefore, - // limitAfter - //) | rpl::map([=](SparseIdsMergedSlice &&slice) { - // auto result = Data::MessagesSlice(); - // result.fullCount = slice.fullCount(); - // result.skippedAfter = slice.skippedAfter(); - // result.skippedBefore = slice.skippedBefore(); - // const auto count = slice.size(); - // result.ids.reserve(count); - // if (const auto msgId = slice.nearest(messageId)) { - // result.nearestToAround = *msgId; - // } - // for (auto i = 0; i != count; ++i) { - // result.ids.push_back(slice[i]); - // } - // return result; - //}); - return rpl::single(Data::MessagesSlice()); + limitAfter); } bool Item::listAllowsMultiSelect() { @@ -341,7 +306,44 @@ void Item::listMarkContentsRead( MessagesBarData Item::listMessagesBar( const std::vector> &elements) { - return {};// #TODO + if (elements.empty()) { + return {}; + } else if (!_replies && !_history->unreadCount()) { + return {}; + } + const auto repliesTill = _replies + ? _replies->computeInboxReadTillFull() + : MsgId(); + const auto migrated = _replies ? nullptr : _history->migrateFrom(); + const auto migratedTill = migrated ? migrated->inboxReadTillId() : 0; + const auto historyTill = _replies ? 0 : _history->inboxReadTillId(); + if (!_replies && !migratedTill && !historyTill) { + return {}; + } + + const auto hidden = _replies && (repliesTill < 2); + for (auto i = 0, count = int(elements.size()); i != count; ++i) { + const auto item = elements[i]->data(); + if (!item->isRegular() + || item->out() + || (_replies && !item->replyToId())) { + continue; + } + const auto inHistory = (item->history() == _history); + if ((_replies && item->id > repliesTill) + || (migratedTill && (inHistory || item->id > migratedTill)) + || (historyTill && inHistory && item->id > historyTill)) { + return { + .bar = { + .element = elements[i], + .hidden = hidden, + .focus = true, + }, + .text = tr::lng_unread_bar_some(), + }; + } + } + return {}; } void Item::listContentRefreshed() { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 538606339..bdcf93a03 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -939,7 +939,9 @@ void ListWidget::restoreScrollState() { } _scrollInited = true; _scrollTopState.item = _bar.element->data()->position(); - _scrollTopState.shift = st::lineWidth + st::historyUnreadBarMargin; + _scrollTopState.shift = st::lineWidth + + st::historyUnreadBarMargin + + _bar.element->displayedDateHeight(); } const auto index = findNearestItem(_scrollTopState.item); if (index >= 0) { diff --git a/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp b/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp index c3845ddca..33ea5ec5c 100644 --- a/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp +++ b/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp @@ -187,20 +187,9 @@ void SparseIdsList::invalidateBottom() { rpl::producer SparseIdsList::query( SparseIdsListQuery &&query) const { return [this, query = std::move(query)](auto consumer) { - auto slice = query.aroundId - ? ranges::lower_bound( - _slices, - query.aroundId, - std::less<>(), - [](const Slice &slice) { return slice.range.till; }) - : _slices.end(); - if (slice != _slices.end() - && slice->range.from <= query.aroundId) { - consumer.put_next(queryFromSlice(query, *slice)); - } else if (_count) { - auto result = SparseIdsListResult {}; - result.count = _count; - consumer.put_next(std::move(result)); + auto now = snapshot(query); + if (!now.messageIds.empty() || now.count) { + consumer.put_next(std::move(now)); } consumer.put_done(); return rpl::lifetime(); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 5c2cbf99d..36e3b5947 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1086,4 +1086,7 @@ previewMenu: PopupMenu(defaultPopupMenu) { maxHeight: 420px; radius: boxRadius; shadow: boxRoundShadow; + animation: PanelAnimation(defaultPanelAnimation) { + shadow: boxRoundShadow; + } } From 0f524ac67da173c038be12a76af957b94fec599d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 May 2024 16:27:43 +0400 Subject: [PATCH 028/225] Don't stick to bottom while loading down. --- .../SourceFiles/history/view/history_view_list_widget.cpp | 6 ++++-- .../SourceFiles/history/view/history_view_list_widget.h | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index bdcf93a03..e951cd802 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -589,6 +589,7 @@ void ListWidget::refreshRows(const Data::MessagesSlice &old) { auto destroyingBarElement = _bar.element; auto clearingOverElement = _overElement; + _itemsKnownTillEnd = (_slice.skippedAfter == 0); _resizePending = true; _items.clear(); _items.reserve(_slice.ids.size()); @@ -1107,7 +1108,7 @@ void ListWidget::applyUpdatedScrollState() { } void ListWidget::updateVisibleTopItem() { - if (_visibleBottom == height()) { + if (_itemsKnownTillEnd && _visibleBottom == height()) { _visibleTopItem = nullptr; } else if (_items.empty()) { _visibleTopItem = nullptr; @@ -2558,7 +2559,8 @@ Element *ListWidget::strictFindItemByY(int y) const { } auto ListWidget::countScrollState() const -> ScrollTopState { - if (_items.empty() || _visibleBottom == height()) { + if (_items.empty() + || (_itemsKnownTillEnd && _visibleBottom == height())) { return { Data::MessagePosition(), 0 }; } const auto topItem = findItemByY(_visibleTop); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index f88582722..bd8ad7827 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -727,6 +727,8 @@ private: int _aroundIndex = -1; int _idsLimit = kMinimalIdsLimit; Data::MessagesSlice _slice; + bool _itemsKnownTillEnd = false; + std::vector> _items; ViewsMap _views, _viewsCapacity; int _itemsTop = 0; From aee62c7591e530c69b386b41b738a63bddbdc5cd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 May 2024 18:28:52 +0400 Subject: [PATCH 029/225] Fix migrated history in chat preview. --- Telegram/SourceFiles/data/data_history_messages.cpp | 2 +- Telegram/SourceFiles/history/history.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/data/data_history_messages.cpp b/Telegram/SourceFiles/data/data_history_messages.cpp index 7c29daf9e..86b46503d 100644 --- a/Telegram/SourceFiles/data/data_history_messages.cpp +++ b/Telegram/SourceFiles/data/data_history_messages.cpp @@ -151,7 +151,7 @@ rpl::producer HistoryMergedViewer( const auto chosen = (history->peer->id == peerId) ? history : history->owner().history(peerId); - return HistoryViewer(history, simpleKey, limitBefore, limitAfter); + return HistoryViewer(chosen, simpleKey, limitBefore, limitAfter); }; const auto peerId = history->peer->id; const auto topicRootId = MsgId(); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 3643ed726..02a3324b9 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -3107,8 +3107,9 @@ Data::HistoryMessages &History::messages() { if (!_messages) { _messages = std::make_unique(); + const auto max = maxMsgId(); const auto from = loadedAtTop() ? 0 : minMsgId(); - const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); + const auto till = loadedAtBottom() ? ServerMaxMsgId : max; auto list = std::vector(); list.reserve(std::min( int(_items.size()), @@ -3122,13 +3123,14 @@ Data::HistoryMessages &History::messages() { if (!list.empty() && list.back() >= id) { sort = true; } + list.push_back(id); } } } if (sort) { ranges::sort(list); } - if (till) { + if (max || (loadedAtTop() && loadedAtBottom())) { _messages->addSlice(std::move(list), { from, till }, {}); } } From cd7cfcdf2fa1e566abe5c6e79379a2a07a384df4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 May 2024 18:30:14 +0400 Subject: [PATCH 030/225] Show chat preview on Alt+Click. --- .../dialogs/dialogs_inner_widget.cpp | 30 ++++++++++++------- .../dialogs/dialogs_inner_widget.h | 4 +-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index fbf6eb513..6aef6c8a7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -159,7 +159,7 @@ InnerWidget::InnerWidget( + st::defaultDialogRow.padding.left()) , _cancelSearchInChat(this, st::dialogsCancelSearchInPeer) , _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) -, _chatPreviewTimer([=] { showChatPreview(); }) +, _chatPreviewTimer([=] { showChatPreview(true); }) , _childListShown(std::move(childListShown)) { setAttribute(Qt::WA_OpaquePaintEvent, true); @@ -1435,6 +1435,18 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { setFilteredPressed(_filteredSelected, _selectedTopicJump); setPeerSearchPressed(_peerSearchSelected); setSearchedPressed(_searchedSelected); + + const auto alt = (e->modifiers() & Qt::AltModifier); + const auto onlyUserpic = !alt; + if (pressShowsPreview(onlyUserpic)) { + _chatPreviewWillBeFor = computeChosenRow().key; + if (alt) { + showChatPreview(onlyUserpic); + return; + } + _chatPreviewTimer.callOnce(kChatPreviewDelay); + } + if (base::in_range(_collapsedSelected, 0, _collapsedRows.size())) { auto row = &_collapsedRows[_collapsedSelected]->row; row->addRipple(e->pos(), QSize(width(), st::dialogsImportantBarHeight), [this, index = _collapsedSelected] { @@ -1503,10 +1515,8 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { row->repaint()); } ClickHandler::pressed(); - if (pressShowsPreview()) { - _chatPreviewWillBeFor = computeChosenRow().key; - _chatPreviewTimer.callOnce(kChatPreviewDelay); - } else if (anim::Disabled() + if (anim::Disabled() + && !_chatPreviewTimer.isActive() && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { mousePressReleased(e->globalPos(), e->button(), e->modifiers()); } @@ -2306,10 +2316,10 @@ void InnerWidget::fillArchiveSearchMenu(not_null menu) { }); } -void InnerWidget::showChatPreview() { +void InnerWidget::showChatPreview(bool onlyUserpic) { const auto key = base::take(_chatPreviewWillBeFor); cancelChatPreview(); - if (!pressShowsPreview() || key != computeChosenRow().key) { + if (!pressShowsPreview(onlyUserpic) || key != computeChosenRow().key) { return; } ClickHandler::unpressed(); @@ -3558,15 +3568,15 @@ bool InnerWidget::isUserpicPress() const { && (width() > _narrowWidth); } -bool InnerWidget::pressShowsPreview() const { - if (!isUserpicPress()) { +bool InnerWidget::pressShowsPreview(bool onlyUserpic) const { + if (onlyUserpic && !isUserpicPress()) { return false; } const auto key = computeChosenRow().key; if (const auto history = key.history()) { return !history->peer->isForum(); } - return key.topic() != nullptr;; + return key.topic() != nullptr; } bool InnerWidget::chooseRow( diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index c8186e2ea..fe9a3d926 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -123,9 +123,9 @@ public: void resizeEmptyLabel(); [[nodiscard]] bool isUserpicPress() const; - [[nodiscard]] bool pressShowsPreview() const; + [[nodiscard]] bool pressShowsPreview(bool onlyUserpic) const; void cancelChatPreview(); - void showChatPreview(); + void showChatPreview(bool onlyUserpic); bool chooseRow( Qt::KeyboardModifiers modifiers = {}, MsgId pressedTopicRootId = {}); From de73d8766c764d2cf5e64d269a53705749649708 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 May 2024 21:10:14 +0400 Subject: [PATCH 031/225] Open chat on exact clicked message from preview. --- .../dialogs/dialogs_inner_widget.cpp | 28 ++++++++++- .../view/history_view_chat_preview.cpp | 49 ++++++++++++++----- .../history/view/history_view_chat_preview.h | 12 ++++- .../history/view/history_view_list_widget.cpp | 4 ++ .../history/view/history_view_list_widget.h | 1 + 5 files changed, 80 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 6aef6c8a7..d3c772c31 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2326,10 +2326,34 @@ void InnerWidget::showChatPreview(bool onlyUserpic) { mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier); _chatPreviewKey = key; - _menu = HistoryView::MakeChatPreview(this, key.entry()); - if (!_menu) { + auto preview = HistoryView::MakeChatPreview(this, key.entry()); + if (!preview.menu) { return; } + _menu = std::move(preview.menu); + const auto weakMenu = Ui::MakeWeak(_menu.get()); + const auto weakThread = base::make_weak(key.entry()->asThread()); + const auto weakController = base::make_weak(_controller); + std::move( + preview.actions + ) | rpl::start_with_next([=](HistoryView::ChatPreviewAction action) { + if (const auto controller = weakController.get()) { + if (const auto thread = weakThread.get()) { + const auto itemId = action.openItemId; + const auto owner = &thread->owner(); + if (action.openInfo) { + controller->showPeerInfo(thread); + } else if (const auto item = owner->message(itemId)) { + controller->showMessage(item); + } else { + controller->showThread(thread); + } + } + } + if (const auto strong = weakMenu.data()) { + strong->hideMenu(); + } + }, _menu->lifetime()); QObject::connect(_menu.get(), &QObject::destroyed, [=] { if (_chatPreviewKey) { updateDialogRow(RowDescriptor(base::take(_chatPreviewKey), {})); diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index caca93da1..48b15494a 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/menu/menu_item_base.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" +#include "window/window_session_controller.h" #include "styles/style_chat.h" namespace HistoryView { @@ -36,8 +37,12 @@ class Item final public: Item(not_null parent, not_null thread); - not_null action() const override; - bool isEnabled() const override; + [[nodiscard]] not_null action() const override; + [[nodiscard]] bool isEnabled() const override; + + [[nodiscard]] rpl::producer actions() { + return _actions.events(); + } private: int contentHeight() const override; @@ -146,6 +151,7 @@ private: const std::unique_ptr _scroll; QPointer _inner; + rpl::event_stream _actions; QImage _bg; @@ -180,6 +186,22 @@ Item::Item(not_null parent, not_null thread) using Type = Ui::ElasticScroll::OverscrollType; _scroll->setOverscrollTypes(Type::Real, Type::Real); + _scroll->events() | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::MouseButtonPress) { + const auto relative = Ui::MapFrom( + _inner.data(), + _scroll.get(), + static_cast(e.get())->pos()); + if (const auto view = _inner->lookupItemByY(relative.y())) { + _actions.fire(ChatPreviewAction{ + .openItemId = view->data()->fullId(), + }); + } else { + _actions.fire(ChatPreviewAction{}); + } + } + }, lifetime()); + _inner->resizeToWidth(_scroll->width(), _scroll->height()); _inner->refreshViewer(); @@ -519,31 +541,36 @@ void Item::listLaunchDrag( } // namespace -base::unique_qptr MakeChatPreview( +ChatPreview MakeChatPreview( QWidget *parent, not_null entry) { const auto thread = entry->asThread(); if (!thread) { - return nullptr; + return {}; } else if (const auto history = entry->asHistory()) { if (history->peer->isForum()) { - return nullptr; + return {}; } } - auto result = base::make_unique_q( - parent, - st::previewMenu); + auto result = ChatPreview{ + .menu = base::make_unique_q( + parent, + st::previewMenu), + }; + const auto menu = result.menu.get(); - result->addAction(base::make_unique_q(result.get(), thread)); + auto action = base::make_unique_q(menu, thread); + result.actions = action->actions(); + menu->addAction(std::move(action)); if (const auto topic = thread->asTopic()) { - const auto weak = Ui::MakeWeak(result.get()); + const auto weak = Ui::MakeWeak(menu); topic->destroyed() | rpl::start_with_next([weak] { if (const auto strong = weak.data()) { LOG(("Preview hidden for a destroyed topic.")); strong->hideMenu(true); } - }, result->lifetime()); + }, menu->lifetime()); } return result; diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.h b/Telegram/SourceFiles/history/view/history_view_chat_preview.h index 12a35bbba..9445b6171 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.h +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.h @@ -19,7 +19,17 @@ class PopupMenu; namespace HistoryView { -[[nodiscard]] base::unique_qptr MakeChatPreview( +struct ChatPreviewAction { + FullMsgId openItemId; + bool openInfo = false; +}; + +struct ChatPreview { + base::unique_qptr menu; + rpl::producer actions; +}; + +[[nodiscard]] ChatPreview MakeChatPreview( QWidget *parent, not_null entry); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index e951cd802..a320a67f7 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1584,6 +1584,10 @@ bool ListWidget::hasSelectRestriction() const { != CopyRestrictionType::None; } +Element *ListWidget::lookupItemByY(int y) const { + return strictFindItemByY(y); +} + auto ListWidget::findViewForPinnedTracking(int top) const -> std::pair { const auto findScrollTopItem = [&](int top) diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index bd8ad7827..e5304bb44 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -344,6 +344,7 @@ public: [[nodiscard]] bool hasCopyRestrictionForSelected() const; [[nodiscard]] bool showCopyRestrictionForSelected(); [[nodiscard]] bool hasSelectRestriction() const; + [[nodiscard]] Element *lookupItemByY(int y) const; [[nodiscard]] std::pair findViewForPinnedTracking( int top) const; From a60783eae3016ea105d242a0a136e9c2eca1cb87 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 4 May 2024 11:37:07 +0400 Subject: [PATCH 032/225] Implement preview top and bottom. --- .../dialogs/dialogs_inner_widget.cpp | 10 +- .../view/history_view_chat_preview.cpp | 279 +++++++++++++++--- .../history/view/history_view_chat_preview.h | 2 + .../info/profile/info_profile_cover.cpp | 15 +- .../info/profile/info_profile_cover.h | 4 + Telegram/SourceFiles/ui/chat/chat.style | 22 ++ 6 files changed, 289 insertions(+), 43 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index d3c772c31..e76b137ec 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2341,7 +2341,15 @@ void InnerWidget::showChatPreview(bool onlyUserpic) { if (const auto thread = weakThread.get()) { const auto itemId = action.openItemId; const auto owner = &thread->owner(); - if (action.openInfo) { + if (action.markRead) { + Window::MarkAsReadThread(thread); + } else if (action.markUnread) { + if (const auto history = thread->asHistory()) { + history->owner().histories().changeDialogUnreadMark( + history, + true); + } + } else if (action.openInfo) { controller->showPeerInfo(thread); } else if (const auto item = owner->message(itemId)) { controller->showMessage(item); diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 48b15494a..e1316a9eb 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -7,26 +7,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_chat_preview.h" +#include "base/unixtime.h" +#include "data/data_changes.h" +#include "data/data_channel.h" +#include "data/data_chat.h" #include "data/data_forum_topic.h" #include "data/data_history_messages.h" #include "data/data_peer.h" +#include "data/data_peer_values.h" #include "data/data_replies_list.h" +#include "data/data_session.h" #include "data/data_thread.h" #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_list_widget.h" #include "history/history.h" #include "history/history_item.h" +#include "info/profile/info_profile_cover.h" +#include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" -#include "ui/widgets/popup_menu.h" -#include "ui/widgets/elastic_scroll.h" +#include "ui/controls/userpic_button.h" #include "ui/widgets/menu/menu_item_base.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/elastic_scroll.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/shadow.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace HistoryView { namespace { @@ -48,7 +61,10 @@ private: int contentHeight() const override; void paintEvent(QPaintEvent *e) override; + void setupTop(); + void setupMarkRead(); void setupBackground(); + void setupHistory(); void updateInnerVisibleArea(); Context listContext() override; @@ -148,7 +164,9 @@ private: const not_null _peer; const std::shared_ptr _theme; const std::unique_ptr _chatStyle; + const std::unique_ptr _top; const std::unique_ptr _scroll; + const std::unique_ptr _markRead; QPointer _inner; rpl::event_stream _actions; @@ -157,6 +175,56 @@ private: }; +struct StatusFields { + QString text; + bool active = false; +}; + +[[nodiscard]] rpl::producer StatusValue( + not_null peer) { + peer->updateFull(); + + using UpdateFlag = Data::PeerUpdate::Flag; + return peer->session().changes().peerFlagsValue( + peer, + UpdateFlag::OnlineStatus | UpdateFlag::Members + ) | rpl::map([=](const Data::PeerUpdate &update) + -> StatusFields { + const auto wrap = [](QString text) { + return StatusFields{ .text = text }; + }; + if (const auto user = peer->asUser()) { + const auto now = base::unixtime::now(); + return { + .text = Data::OnlineText(user, now), + .active = Data::OnlineTextActive(user, now), + }; + } else if (const auto chat = peer->asChat()) { + return wrap(!chat->amIn() + ? tr::lng_chat_status_unaccessible(tr::now) + : (chat->count <= 0) + ? tr::lng_group_status(tr::now) + : tr::lng_chat_status_members( + tr::now, + lt_count_decimal, + chat->count)); + } else if (const auto channel = peer->asChannel()) { + return wrap((channel->membersCount() > 0) + ? ((channel->isMegagroup() + ? tr::lng_chat_status_members + : tr::lng_chat_status_subscribers)( + tr::now, + lt_count_decimal, + channel->membersCount())) + : (channel->isMegagroup() + ? tr::lng_group_status(tr::now) + : tr::lng_channel_status(tr::now))); + } + Unexpected("Peer type in ChatPreview Item."); + }); + +} + Item::Item(not_null parent, not_null thread) : Ui::Menu::ItemBase(parent, st::previewMenu.menu) , _dummyAction(new QAction(parent)) @@ -167,17 +235,187 @@ Item::Item(not_null parent, not_null thread) , _peer(thread->peer()) , _theme(Window::Theme::DefaultChatThemeOn(lifetime())) , _chatStyle(std::make_unique(_session->colorIndicesValue())) -, _scroll(std::make_unique(this)) { +, _top(std::make_unique(this)) +, _scroll(std::make_unique(this)) +, _markRead( + std::make_unique( + this, + tr::lng_context_mark_read(tr::now), + st::previewMarkRead)) { setPointerCursor(false); setMinWidth(st::previewMenu.menu.widthMin); resize(minWidth(), contentHeight()); + setupTop(); + setupMarkRead(); setupBackground(); + setupHistory(); +} +not_null Item::action() const { + return _dummyAction; +} + +bool Item::isEnabled() const { + return false; +} + +int Item::contentHeight() const { + return st::previewMenu.maxHeight; +} + +void Item::setupTop() { + _top->setGeometry(0, 0, width(), st::previewTop.height); + _top->setClickedCallback([=] { + _actions.fire({ .openInfo = true }); + }); + _top->paintRequest() | rpl::start_with_next([=](QRect clip) { + const auto &st = st::previewTop; + auto p = QPainter(_top.get()); + p.fillRect(clip, st::topBarBg); + }, _top->lifetime()); + + const auto topic = _thread->asTopic(); + const auto name = Ui::CreateChild( + _top.get(), + (topic + ? Info::Profile::TitleValue(topic) + : Info::Profile::NameValue(_thread->peer())), + st::previewName); + name->setAttribute(Qt::WA_TransparentForMouseEvents); + auto statusFields = StatusValue( + _thread->peer() + ) | rpl::start_spawning(lifetime()); + auto statusText = rpl::duplicate( + statusFields + ) | rpl::map([](StatusFields &&fields) { + return fields.text; + }); + const auto status = Ui::CreateChild( + _top.get(), + (topic + ? Info::Profile::NameValue(topic->channel()) + : std::move(statusText)), + st::previewStatus); + std::move(statusFields) | rpl::start_with_next([=](const StatusFields &fields) { + status->setTextColorOverride(fields.active + ? st::windowActiveTextFg->c + : std::optional()); + }, status->lifetime()); + status->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto userpic = topic + ? nullptr + : Ui::CreateChild( + _top.get(), + _thread->peer(), + st::previewUserpic); + if (userpic) { + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + } + const auto icon = topic + ? Ui::CreateChild( + this, + topic, + [=] { return false; }) + : nullptr; + if (icon) { + icon->setAttribute(Qt::WA_TransparentForMouseEvents); + } + + const auto shadow = Ui::CreateChild(this); + _top->geometryValue() | rpl::start_with_next([=](QRect geometry) { + const auto &st = st::previewTop; + name->resizeToWidth(geometry.width() + - st.namePosition.x() + - st.photoPosition.x()); + name->move(st::previewTop.namePosition); + status->resizeToWidth(geometry.width() + - st.statusPosition.x() + - st.photoPosition.x()); + status->move(st.statusPosition); + shadow->setGeometry( + geometry.x(), + geometry.y() + geometry.height(), + geometry.width(), + st::lineWidth); + if (userpic) { + userpic->move(st.photoPosition); + } else { + icon->move( + st.photoPosition.x() + (st.photoSize - icon->width()) / 2, + st.photoPosition.y() + (st.photoSize - icon->height()) / 2); + } + }, shadow->lifetime()); +} + +void Item::setupMarkRead() { + _markRead->resizeToWidth(width()); + _markRead->move(0, height() - _markRead->height()); + + rpl::single( + rpl::empty + ) | rpl::then( + _thread->owner().chatsListFor(_thread)->unreadStateChanges( + ) | rpl::to_empty + ) | rpl::start_with_next([=] { + const auto state = _thread->chatListBadgesState(); + const auto unread = (state.unreadCounter || state.unread); + if (_thread->asTopic() && !unread) { + _markRead->hide(); + return; + } + _markRead->setText(unread + ? tr::lng_context_mark_read(tr::now) + : tr::lng_context_mark_unread(tr::now)); + _markRead->setClickedCallback([=] { + _actions.fire({ .markRead = unread, .markUnread = !unread }); + }); + _markRead->show(); + }, _markRead->lifetime()); + + const auto shadow = Ui::CreateChild(this); + _markRead->geometryValue() | rpl::start_with_next([=](QRect geometry) { + shadow->setGeometry( + geometry.x(), + geometry.y() - st::lineWidth, + geometry.width(), + st::lineWidth); + }, shadow->lifetime()); + shadow->showOn(_markRead->shownValue()); +} + +void Item::setupBackground() { + const auto ratio = style::DevicePixelRatio(); + _bg = QImage( + size() * ratio, + QImage::Format_ARGB32_Premultiplied); + + const auto paint = [=] { + auto p = QPainter(&_bg); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(width(), height() * 2), + QRect(QPoint(), size())); + }; + paint(); + _theme->repaintBackgroundRequests() | rpl::start_with_next([=] { + paint(); + update(); + }, lifetime()); +} + +void Item::setupHistory() { _inner = _scroll->setOwnedWidget(object_ptr( this, _session, static_cast(this))); - _scroll->setGeometry(rect()); + + _markRead->shownValue() | rpl::start_with_next([=](bool shown) { + const auto top = _top->height(); + const auto bottom = shown ? _markRead->height() : 0; + _scroll->setGeometry(rect().marginsRemoved({ 0, top, 0, bottom })); + }, _markRead->lifetime()); + _scroll->scrolls( ) | rpl::start_with_next([=] { updateInnerVisibleArea(); @@ -209,39 +447,6 @@ Item::Item(not_null parent, not_null thread) _inner->setAttribute(Qt::WA_TransparentForMouseEvents); } -not_null Item::action() const { - return _dummyAction; -} - -bool Item::isEnabled() const { - return false; -} - -int Item::contentHeight() const { - return st::previewMenu.maxHeight; -} - -void Item::setupBackground() { - const auto ratio = style::DevicePixelRatio(); - _bg = QImage( - size() * ratio, - QImage::Format_ARGB32_Premultiplied); - - const auto paint = [=] { - auto p = QPainter(&_bg); - Window::SectionWidget::PaintBackground( - p, - _theme.get(), - QSize(width(), height() * 2), - QRect(QPoint(), size())); - }; - paint(); - _theme->repaintBackgroundRequests() | rpl::start_with_next([=] { - paint(); - update(); - }, lifetime()); -} - void Item::paintEvent(QPaintEvent *e) { auto p = QPainter(this); p.drawImage(0, 0, _bg); diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.h b/Telegram/SourceFiles/history/view/history_view_chat_preview.h index 9445b6171..0598725c2 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.h +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.h @@ -22,6 +22,8 @@ namespace HistoryView { struct ChatPreviewAction { FullMsgId openItemId; bool openInfo = false; + bool markRead = false; + bool markUnread = false; }; struct ChatPreview { diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 6cc611fdb..e460b4dc8 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -231,12 +231,17 @@ TopicIconButton::TopicIconButton( QWidget *parent, not_null controller, not_null topic) +: TopicIconButton(parent, topic, [=] { + return controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer); +}) { +} + +TopicIconButton::TopicIconButton( + QWidget *parent, + not_null topic, + Fn paused) : AbstractButton(parent) -, _view( - topic, - [=] { return controller->isGifPausedAtLeastFor( - Window::GifPauseReason::Layer); }, - [=] { update(); }) { +, _view(topic, paused, [=] { update(); }) { resize(st::infoTopicCover.photo.size); paintRequest( ) | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index 20389e7c5..59a857eb6 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -82,6 +82,10 @@ public: QWidget *parent, not_null controller, not_null topic); + TopicIconButton( + QWidget *parent, + not_null topic, + Fn paused); private: TopicIconView _view; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 36e3b5947..8bba17e87 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1090,3 +1090,25 @@ previewMenu: PopupMenu(defaultPopupMenu) { shadow: boxRoundShadow; } } +previewTop: PeerListItem(defaultPeerListItem) { + height: 52px; + photoPosition: point(10px, 6px); + namePosition: point(60px, 9px); + statusPosition: point(60px, 27px); + photoSize: 40px; +} +previewMarkRead: FlatButton(historyComposeButton) { + height: 40px; + textTop: 10px; +} +previewName: FlatLabel(defaultFlatLabel) { + style: semiboldTextStyle; + textFg: windowFg; +} +previewStatus: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} +previewUserpic: UserpicButton(defaultUserpicButton) { + size: size(40px, 40px); + photoSize: 40px; +} From 470b3a2cbd0ca980ad0376c74367a65010ffd8e3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 8 May 2024 17:14:30 +0400 Subject: [PATCH 033/225] Fix preview showing on fast userpic click. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index e76b137ec..e21dcbcf4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1760,6 +1760,8 @@ void InnerWidget::mousePressReleased( QPoint globalPosition, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) { + _chatPreviewTimer.cancel(); + auto wasDragging = (_dragging != nullptr); if (wasDragging) { updateReorderIndexGetCount(); From 8a6b1677f4ce2b6cda2796a365ae87ebaa36f20e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 27 May 2024 16:28:03 +0300 Subject: [PATCH 034/225] Added userpic views to headers of forwarded messages. --- .../history/history_item_components.cpp | 41 +++++++++++++------ .../history/history_item_components.h | 4 +- .../history/view/history_view_message.cpp | 2 +- .../history/view/media/history_view_gif.cpp | 2 +- .../media/history_view_media_unwrapped.cpp | 2 +- Telegram/SourceFiles/ui/chat/chat.style | 1 + 6 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index b8a64263b..dc4a38c7a 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -183,8 +183,11 @@ bool HiddenSenderInfo::paintCustomUserpic( return valid; } -void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { +void HistoryMessageForwarded::create( + const HistoryMessageVia *via, + not_null item) const { auto phrase = TextWithEntities(); + auto context = Core::MarkedTextContext{}; const auto fromChannel = originalSender && originalSender->isChannel() && !originalSender->isMegagroup(); @@ -193,16 +196,27 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { ? originalSender->name() : originalHiddenSenderInfo->name) }; + if (originalSender) { + context.session = &originalSender->owner().session(); + context.customEmojiRepaint = [=] { + originalSender->owner().requestItemRepaint(item); + }; + phrase = Ui::Text::SingleCustomEmoji( + context.session->data().customEmojiManager().peerUserpicEmojiData( + originalSender, + st::fwdTextUserpicPadding)); + } if (!originalPostAuthor.isEmpty()) { - phrase = tr::lng_forwarded_signed( - tr::now, - lt_channel, - name, - lt_user, - { .text = originalPostAuthor }, - Ui::Text::WithEntities); + phrase.append( + tr::lng_forwarded_signed( + tr::now, + lt_channel, + name, + lt_user, + { .text = originalPostAuthor }, + Ui::Text::WithEntities)); } else { - phrase = name; + phrase.append(name); } if (story) { phrase = tr::lng_forwarded_story( @@ -248,18 +262,21 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { : tr::lng_forwarded_psa_default)( tr::now, lt_channel, - Ui::Text::Link(phrase.text, QString()), // Link 1. + Ui::Text::Wrapped( + phrase, + EntityType::CustomUrl, + QString()), // Link 1. Ui::Text::WithEntities); } } else { phrase = tr::lng_forwarded( tr::now, lt_user, - Ui::Text::Link(phrase.text, QString()), // Link 1. + Ui::Text::Wrapped(phrase, EntityType::CustomUrl, QString()), // Link 1. Ui::Text::WithEntities); } } - text.setMarkedText(st::fwdTextStyle, phrase); + text.setMarkedText(st::fwdTextStyle, phrase, kMarkupTextOptions, context); text.setLink(1, fromChannel ? JumpToMessageClickHandler(originalSender, originalId) diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index e35a804a2..44f31f3cd 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -125,7 +125,9 @@ private: }; struct HistoryMessageForwarded : public RuntimeComponent { - void create(const HistoryMessageVia *via) const; + void create( + const HistoryMessageVia *via, + not_null item) const; [[nodiscard]] bool forwardOfForward() const { return savedFromSender || savedFromHiddenSenderInfo; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index cc0b4aec7..96cbc4648 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -714,7 +714,7 @@ QSize Message::performCountOptimalSize() { const auto via = item->Get(); const auto entry = logEntryOriginal(); if (forwarded) { - forwarded->create(via); + forwarded->create(via, item); } auto mediaDisplayed = false; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 2097ca1b5..7dbee0416 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -293,7 +293,7 @@ QSize Gif::countOptimalSize() { auto reply = _parent->Get(); auto forwarded = item->Get(); if (forwarded) { - forwarded->create(via); + forwarded->create(via, item); } maxWidth += additionalWidth(reply, via, forwarded); accumulate_max(maxWidth, _parent->reactionsOptimalWidth()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 4c9e3277c..beee98df1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -59,7 +59,7 @@ QSize UnwrappedMedia::countOptimalSize() { const auto topic = _parent->displayedTopicButton(); const auto forwarded = getDisplayedForwardedInfo(); if (forwarded) { - forwarded->create(via); + forwarded->create(via, item); } maxWidth += additionalWidth(topic, reply, via, forwarded); accumulate_max(maxWidth, _parent->reactionsOptimalWidth()); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 8bba17e87..91ba60a37 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -125,6 +125,7 @@ outTextPaletteSelected: TextPalette(outTextPalette) { monoFg: msgOutMonoFgSelected; spoilerFg: msgOutDateFgSelected; } +fwdTextUserpicPadding: margins(0px, 1px, 3px, 0px); fwdTextStyle: TextStyle(semiboldTextStyle) { linkUnderline: kLinkUnderlineNever; } From 0dd6ff9d9be23af24da28c9c9ceb60d48a797844 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 21:43:05 +0400 Subject: [PATCH 035/225] Beta version 5.0.5. - Long press on chat userpic to show quick preview. - Alt+Click on chat to show quick preview. - Show author userpics in forwarded messages. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 6 +++--- .../history/view/history_view_chat_preview.cpp | 1 - Telegram/build/version | 10 +++++----- changelog.txt | 6 ++++++ 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 519db7170..fe3b473ac 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.0.5.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 5bec7f3bc..1f0a7a045 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,4,0 - PRODUCTVERSION 5,0,4,0 + FILEVERSION 5,0,5,0 + PRODUCTVERSION 5,0,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.0.4.0" + VALUE "FileVersion", "5.0.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.4.0" + VALUE "ProductVersion", "5.0.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index d44287d24..c334ca569 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,4,0 - PRODUCTVERSION 5,0,4,0 + FILEVERSION 5,0,5,0 + PRODUCTVERSION 5,0,5,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", "5.0.4.0" + VALUE "FileVersion", "5.0.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.4.0" + VALUE "ProductVersion", "5.0.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 1d8cecd6e..8de17b03c 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 = 5000004; -constexpr auto AppVersionStr = "5.0.4"; -constexpr auto AppBetaVersion = false; +constexpr auto AppVersion = 5000005; +constexpr auto AppVersionStr = "5.0.5"; +constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index e1316a9eb..0a7e9a8b9 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -269,7 +269,6 @@ void Item::setupTop() { _actions.fire({ .openInfo = true }); }); _top->paintRequest() | rpl::start_with_next([=](QRect clip) { - const auto &st = st::previewTop; auto p = QPainter(_top.get()); p.fillRect(clip, st::topBarBg); }, _top->lifetime()); diff --git a/Telegram/build/version b/Telegram/build/version index 68f363453..5d551a173 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5000004 +AppVersion 5000005 AppVersionStrMajor 5.0 -AppVersionStrSmall 5.0.4 -AppVersionStr 5.0.4 -BetaChannel 0 +AppVersionStrSmall 5.0.5 +AppVersionStr 5.0.5 +BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.0.4 +AppVersionOriginal 5.0.5.beta diff --git a/changelog.txt b/changelog.txt index 19f721c85..ddfebe110 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +5.0.5 beta (28.05.24) + +- Long press on chat userpic to show quick preview. +- Alt+Click on chat to show quick preview. +- Show author userpics in forwarded messages. + 5.0.4 (28.05.24) - Fix reply to last message by Ctrl+Up in topics. From 465fc42718be3bc8d6e1a9cc0be1b61b6eccdc32 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 09:43:40 +0400 Subject: [PATCH 036/225] Fix chat preview in non-default themes. --- Telegram/SourceFiles/history/view/history_view_chat_preview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 0a7e9a8b9..b804f7382 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -242,6 +242,7 @@ Item::Item(not_null parent, not_null thread) this, tr::lng_context_mark_read(tr::now), st::previewMarkRead)) { + _chatStyle->apply(_theme.get()); setPointerCursor(false); setMinWidth(st::previewMenu.menu.widthMin); resize(minWidth(), contentHeight()); From d2e600352160420314721b76817988b80d1f3433 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 10:12:56 +0400 Subject: [PATCH 037/225] Ignore right click on preview. --- .../view/history_view_chat_preview.cpp | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index b804f7382..8ad2a2a6a 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -426,16 +426,19 @@ void Item::setupHistory() { _scroll->events() | rpl::start_with_next([=](not_null e) { if (e->type() == QEvent::MouseButtonPress) { - const auto relative = Ui::MapFrom( - _inner.data(), - _scroll.get(), - static_cast(e.get())->pos()); - if (const auto view = _inner->lookupItemByY(relative.y())) { - _actions.fire(ChatPreviewAction{ - .openItemId = view->data()->fullId(), - }); - } else { - _actions.fire(ChatPreviewAction{}); + const auto button = static_cast(e.get())->button(); + if (button == Qt::LeftButton) { + const auto relative = Ui::MapFrom( + _inner.data(), + _scroll.get(), + static_cast(e.get())->pos()); + if (const auto view = _inner->lookupItemByY(relative.y())) { + _actions.fire(ChatPreviewAction{ + .openItemId = view->data()->fullId(), + }); + } else { + _actions.fire(ChatPreviewAction{}); + } } } }, lifetime()); From d6e827e9827dcaba54477fe866b3282025e27ed0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 10:48:41 +0400 Subject: [PATCH 038/225] Fix loading of chat preview messages. --- Telegram/SourceFiles/data/data_history_messages.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_history_messages.cpp b/Telegram/SourceFiles/data/data_history_messages.cpp index 86b46503d..66e888e28 100644 --- a/Telegram/SourceFiles/data/data_history_messages.cpp +++ b/Telegram/SourceFiles/data/data_history_messages.cpp @@ -89,6 +89,13 @@ rpl::producer HistoryViewer( using RequestAroundInfo = SparseIdsSliceBuilder::AroundData; builder->insufficientAround( ) | rpl::start_with_next([=](const RequestAroundInfo &info) { + if (!info.aroundId) { + // Ignore messages-count-only requests, because we perform + // them with non-zero limit of messages and end up adding + // a broken slice with several last messages from the chat + // with a non-skip range starting at zero. + return; + } history->session().api().requestHistory( history, info.aroundId, @@ -155,7 +162,7 @@ rpl::producer HistoryMergedViewer( }; const auto peerId = history->peer->id; const auto topicRootId = MsgId(); - const auto migratedPeerId = migrateFrom ? migrateFrom->id : peerId; + const auto migratedPeerId = migrateFrom ? migrateFrom->id : PeerId(0); using Key = SparseIdsMergedSlice::Key; return SparseIdsMergedSlice::CreateViewer( Key(peerId, topicRootId, migratedPeerId, universalAroundId), From 2b9e7a6b2568c2265be17289d29f246c406f0c4c Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 12:06:30 +0400 Subject: [PATCH 039/225] Show preview on Force-Click on macOS. --- .../dialogs/dialogs_inner_widget.cpp | 25 ++++++++++++++++--- .../dialogs/dialogs_inner_widget.h | 2 ++ .../platform/mac/main_window_mac.h | 6 +++++ .../platform/mac/main_window_mac.mm | 10 ++++++++ Telegram/SourceFiles/window/main_window.h | 4 +++ 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index e21dcbcf4..3b996f385 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -332,6 +332,11 @@ InnerWidget::InnerWidget( switchToFilter(filterId); }, lifetime()); + _controller->window().widget()->globalForceClicks( + ) | rpl::start_with_next([=](QPoint globalPosition) { + processGlobalForceClick(globalPosition); + }, lifetime()); + session().data().stories().incrementPreloadingMainSources(); handleChatListEntryRefreshes(); @@ -1424,6 +1429,16 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { } } +void InnerWidget::processGlobalForceClick(QPoint globalPosition) { + const auto parent = parentWidget(); + if (_pressButton == Qt::LeftButton + && parent->rect().contains(parent->mapFromGlobal(globalPosition)) + && pressShowsPreview(false)) { + _chatPreviewWillBeFor = computeChosenRow().key; + showChatPreview(false); + } +} + void InnerWidget::mousePressEvent(QMouseEvent *e) { selectByMouse(e->globalPos()); @@ -1761,6 +1776,7 @@ void InnerWidget::mousePressReleased( Qt::MouseButton button, Qt::KeyboardModifiers modifiers) { _chatPreviewTimer.cancel(); + _pressButton = Qt::NoButton; auto wasDragging = (_dragging != nullptr); if (wasDragging) { @@ -3598,8 +3614,11 @@ ChosenRow InnerWidget::computeChosenRow() const { bool InnerWidget::isUserpicPress() const { return (_lastRowLocalMouseX >= 0) - && (_lastRowLocalMouseX < _st->nameLeft) - && (width() > _narrowWidth); + && (_lastRowLocalMouseX < _st->nameLeft); +} + +bool InnerWidget::isUserpicPressOnWide() const { + return isUserpicPress() && (width() > _narrowWidth); } bool InnerWidget::pressShowsPreview(bool onlyUserpic) const { @@ -3625,7 +3644,7 @@ bool InnerWidget::chooseRow( ChosenRow row, Qt::KeyboardModifiers modifiers) { row.newWindow = (modifiers & Qt::ControlModifier); - row.userpicClick = isUserpicPress(); + row.userpicClick = isUserpicPressOnWide(); return row; }; auto chosen = modifyChosenRow(computeChosenRow(), modifiers); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index fe9a3d926..9194df36e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -123,6 +123,7 @@ public: void resizeEmptyLabel(); [[nodiscard]] bool isUserpicPress() const; + [[nodiscard]] bool isUserpicPressOnWide() const; [[nodiscard]] bool pressShowsPreview(bool onlyUserpic) const; void cancelChatPreview(); void showChatPreview(bool onlyUserpic); @@ -258,6 +259,7 @@ private: QPoint globalPosition, Qt::MouseButton button, Qt::KeyboardModifiers modifiers); + void processGlobalForceClick(QPoint globalPosition); void clearIrrelevantState(); void selectByMouse(QPoint globalPosition); void preloadRowsData(); diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.h b/Telegram/SourceFiles/platform/mac/main_window_mac.h index 6c2d15d07..2a3a59b9c 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.h +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.h @@ -28,6 +28,10 @@ public: void updateWindowIcon() override; + rpl::producer globalForceClicks() override { + return _forceClicks.events(); + } + class Private; protected: @@ -85,7 +89,9 @@ private: QAction *psMonospace = nullptr; QAction *psClearFormat = nullptr; + rpl::event_stream _forceClicks; int _customTitleHeight = 0; + int _lastPressureStage = 0; }; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 0b2adb9aa..9b0dd14bb 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -310,6 +310,16 @@ bool MainWindow::nativeEvent( Core::Sandbox::Instance().customEnterFromEventLoop([&] { imeCompositionStartReceived(); }); + } else if ([event type] == NSEventTypePressure) { + const auto stage = [event stage]; + if (_lastPressureStage != stage) { + _lastPressureStage = stage; + if (stage == 2) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + _forceClicks.fire(QCursor::pos()); + }); + } + } } } return false; diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h index 79dc3c6fe..67f38567c 100644 --- a/Telegram/SourceFiles/window/main_window.h +++ b/Telegram/SourceFiles/window/main_window.h @@ -144,6 +144,10 @@ public: Core::WindowPosition initial, QSize minSize) const; + [[nodiscard]] virtual rpl::producer globalForceClicks() { + return rpl::never(); + } + protected: void leaveEventHook(QEvent *e) override; From a9a0fe7cf5430549a542b3bb1c69d879fa0df060 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 12:07:56 +0400 Subject: [PATCH 040/225] Fix RTL chat names in preview. --- .../view/history_view_chat_preview.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 8ad2a2a6a..4baed10bb 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -275,11 +275,13 @@ void Item::setupTop() { }, _top->lifetime()); const auto topic = _thread->asTopic(); + auto nameValue = (topic + ? Info::Profile::TitleValue(topic) + : Info::Profile::NameValue(_thread->peer()) + ) | rpl::start_spawning(_top->lifetime()); const auto name = Ui::CreateChild( _top.get(), - (topic - ? Info::Profile::TitleValue(topic) - : Info::Profile::NameValue(_thread->peer())), + rpl::duplicate(nameValue), st::previewName); name->setAttribute(Qt::WA_TransparentForMouseEvents); auto statusFields = StatusValue( @@ -322,12 +324,19 @@ void Item::setupTop() { } const auto shadow = Ui::CreateChild(this); - _top->geometryValue() | rpl::start_with_next([=](QRect geometry) { + rpl::combine( + _top->widthValue(), + std::move(nameValue) + ) | rpl::start_with_next([=](int width, const auto &) { const auto &st = st::previewTop; - name->resizeToWidth(geometry.width() + name->resizeToNaturalWidth(width - st.namePosition.x() - st.photoPosition.x()); name->move(st::previewTop.namePosition); + }, name->lifetime()); + + _top->geometryValue() | rpl::start_with_next([=](QRect geometry) { + const auto &st = st::previewTop; status->resizeToWidth(geometry.width() - st.statusPosition.x() - st.photoPosition.x()); From e2b78b673be0132afffc2a967f62464de94514fd Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 12:08:22 +0400 Subject: [PATCH 041/225] Fix unintentional search focus. --- .../SourceFiles/chat_helpers/tabbed_section.cpp | 3 +++ Telegram/SourceFiles/mainwidget.cpp | 17 +++++++++++++---- Telegram/SourceFiles/mainwidget.h | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp index 3759697e3..92f6aaa00 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp @@ -28,6 +28,9 @@ TabbedSection::TabbedSection( not_null controller) : Window::SectionWidget(parent, controller) , _selector(controller->tabbedSelector()) { + if (Ui::InFocusChain(_selector)) { + parent->window()->setFocus(); + } _selector->setParent(this); _selector->setRoundRadius(0); _selector->setGeometry(rect()); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index c9acfef92..4dd47970d 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1772,7 +1772,7 @@ void MainWidget::showNewSection( _thirdSection = std::move(newThirdSection); _thirdSection->removeRequests( ) | rpl::start_with_next([=] { - _thirdSection.destroy(); + destroyThirdSection(); _thirdShadow.destroy(); updateControlsGeometry(); }, _thirdSection->lifetime()); @@ -2289,7 +2289,7 @@ void MainWidget::updateControlsGeometry() { } } } else { - _thirdSection.destroy(); + destroyThirdSection(); _thirdShadow.destroy(); } const auto mainSectionTop = getMainSectionTop(); @@ -2408,6 +2408,15 @@ void MainWidget::updateControlsGeometry() { floatPlayerUpdatePositions(); } +void MainWidget::destroyThirdSection() { + if (const auto strong = _thirdSection.data()) { + if (Ui::InFocusChain(strong)) { + setFocus(); + } + } + _thirdSection.destroy(); +} + void MainWidget::refreshResizeAreas() { if (!isOneColumn() && _dialogs) { ensureFirstColumnResizeAreaCreated(); @@ -2555,7 +2564,7 @@ void MainWidget::updateThirdColumnToCurrentChat( if (saveThirdSectionToStackBack()) { _stack.back()->setThirdSectionMemento( _thirdSection->createMemento()); - _thirdSection.destroy(); + destroyThirdSection(); } }; auto &settings = Core::App().settings(); @@ -2601,7 +2610,7 @@ void MainWidget::updateThirdColumnToCurrentChat( settings.setTabbedReplacedWithInfo(false); if (!key) { if (_thirdSection) { - _thirdSection.destroy(); + destroyThirdSection(); _thirdShadow.destroy(); updateControlsGeometry(); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 0f769a53f..14e1ef2ab 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -279,6 +279,7 @@ private: void showNewSection( std::shared_ptr memento, const SectionShow ¶ms); + void destroyThirdSection(); Window::SectionSlideParams prepareThirdSectionAnimation(Window::SectionWidget *section); From 7d636820ac1509e99916e93d911bc0c99ac1c7a4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 10:26:48 +0400 Subject: [PATCH 042/225] Show last updated topic preview on Alt+Click. --- .../dialogs/dialogs_inner_widget.cpp | 22 +++++++++++++++---- .../dialogs/dialogs_inner_widget.h | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 3b996f385..4a7dc475c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1429,12 +1429,25 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { } } +Key InnerWidget::computeChatPreviewRow() const { + auto result = computeChosenRow().key; + if (const auto peer = result.peer()) { + const auto topicId = _pressedTopicJump + ? _pressedTopicJumpRootId + : 0; + if (const auto topic = peer->forumTopicFor(topicId)) { + return topic; + } + } + return result; +} + void InnerWidget::processGlobalForceClick(QPoint globalPosition) { const auto parent = parentWidget(); if (_pressButton == Qt::LeftButton && parent->rect().contains(parent->mapFromGlobal(globalPosition)) && pressShowsPreview(false)) { - _chatPreviewWillBeFor = computeChosenRow().key; + _chatPreviewWillBeFor = computeChatPreviewRow(); showChatPreview(false); } } @@ -1454,7 +1467,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { const auto alt = (e->modifiers() & Qt::AltModifier); const auto onlyUserpic = !alt; if (pressShowsPreview(onlyUserpic)) { - _chatPreviewWillBeFor = computeChosenRow().key; + _chatPreviewWillBeFor = computeChatPreviewRow(); if (alt) { showChatPreview(onlyUserpic); return; @@ -2337,7 +2350,7 @@ void InnerWidget::fillArchiveSearchMenu(not_null menu) { void InnerWidget::showChatPreview(bool onlyUserpic) { const auto key = base::take(_chatPreviewWillBeFor); cancelChatPreview(); - if (!pressShowsPreview(onlyUserpic) || key != computeChosenRow().key) { + if (!pressShowsPreview(onlyUserpic) || key != computeChatPreviewRow()) { return; } ClickHandler::unpressed(); @@ -3627,7 +3640,8 @@ bool InnerWidget::pressShowsPreview(bool onlyUserpic) const { } const auto key = computeChosenRow().key; if (const auto history = key.history()) { - return !history->peer->isForum(); + return !history->peer->isForum() + || (_pressedTopicJump && _pressedTopicJumpRootId); } return key.topic() != nullptr; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 9194df36e..f78c220ad 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -398,6 +398,7 @@ private: void trackSearchResultsForum(Data::Forum *forum); [[nodiscard]] QBrush currentBg() const; + [[nodiscard]] Key computeChatPreviewRow() const; [[nodiscard]] const std::vector &pinnedChatsOrder() const; void checkReorderPinnedStart(QPoint localPosition); From 28cbb02b2099a4e43c1bccfc7f43b8db0a825cfe Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 10:40:30 +0400 Subject: [PATCH 043/225] Jump to chat only by Double-Click on preview. --- Telegram/SourceFiles/history/view/history_view_chat_preview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 4baed10bb..56d7f648c 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -434,7 +434,7 @@ void Item::setupHistory() { _scroll->setOverscrollTypes(Type::Real, Type::Real); _scroll->events() | rpl::start_with_next([=](not_null e) { - if (e->type() == QEvent::MouseButtonPress) { + if (e->type() == QEvent::MouseButtonDblClick) { const auto button = static_cast(e.get())->button(); if (button == Qt::LeftButton) { const auto relative = Ui::MapFrom( From 0527e9a0f728c66138136b58f7164ab2dee07af4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 16:41:51 +0400 Subject: [PATCH 044/225] Fix adding bot as admin to channel. --- .../SourceFiles/boxes/add_contact_box.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 45d4b1e99..6b9a0852c 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -192,20 +192,27 @@ void ShowAddParticipantsError( && channel && !channel->isMegagroup() && channel->canAddAdmins()) { - const auto makeAdmin = [=] { + const auto makeAdmin = [=](Fn close) { const auto user = forbidden.users.front(); const auto weak = std::make_shared>(); - const auto close = [=](auto&&...) { - if (*weak) { - (*weak)->closeBox(); + const auto done = [=](auto&&...) { + if (const auto strong = weak->data()) { + strong->uiShow()->showToast( + tr::lng_box_done(tr::now)); + strong->closeBox(); + } + }; + const auto fail = [=] { + if (const auto strong = weak->data()) { + strong->closeBox(); } }; const auto saveCallback = SaveAdminCallback( show, channel, user, - close, - close); + done, + fail); auto box = Box( channel, user, @@ -214,6 +221,7 @@ void ShowAddParticipantsError( box->setSaveCallback(saveCallback); *weak = box.data(); show->showBox(std::move(box)); + close(); }; show->showBox( Ui::MakeConfirmBox({ From 4f5594c8ccbfafb6d47aa418aab6b320360fcd0b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 17:10:12 +0400 Subject: [PATCH 045/225] ChatNext jumps to first chat if no chat opened. Fixes #27963. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 4a7dc475c..6527140b3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -4030,6 +4030,12 @@ void InnerWidget::setupShortcuts() { request->check(Command::ChatNext) && request->handle([=] { return jumpToDialogRow(next); }); + } else if (_state == WidgetState::Default + ? !_shownList->empty() + : !_filterResults.empty()) { + request->check(Command::ChatNext) && request->handle([=] { + return jumpToDialogRow(first); + }); } request->check(Command::ChatFirst) && request->handle([=] { return jumpToDialogRow(first); From aa4156d1e740ba3ee3c680ff17d74fb2f62b593d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 17:12:28 +0400 Subject: [PATCH 046/225] Ignore shortcuts when preview is shown. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 6527140b3..3c3ab74f6 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -3997,7 +3997,8 @@ void InnerWidget::setupShortcuts() { return isActiveWindow() && !_controller->isLayerShown() && !_controller->window().locked() - && !_childListShown.current().shown; + && !_childListShown.current().shown + && !_chatPreviewKey; }) | rpl::start_with_next([=](not_null request) { using Command = Shortcuts::Command; From a9dd9aeb90184bc81e4cc18445a4638434fab9f0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 17:38:50 +0400 Subject: [PATCH 047/225] Use InnoSetup SignTool for setup signing. Fixes #27583. --- Telegram/build/build.bat | 6 ------ Telegram/build/setup.iss | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Telegram/build/build.bat b/Telegram/build/build.bat index 43f145c55..3f3b65caa 100644 --- a/Telegram/build/build.bat +++ b/Telegram/build/build.bat @@ -208,12 +208,6 @@ if %BuildUWP% equ 0 ( iscc /dMyAppVersion=%AppVersionStrSmall% /dMyAppVersionZero=%AppVersionStr% /dMyAppVersionFull=%AppVersionStrFull% "/dReleasePath=%ReleasePath%" "/dMyBuildTarget=%BuildTarget%" "%FullScriptPath%setup.iss" if %errorlevel% neq 0 goto error if not exist "%SetupFile%" goto error -:sign3 - call "%SignPath%" "%SetupFile%" - if %errorlevel% neq 0 ( - timeout /t 3 - goto sign3 - ) ) call Packer.exe -version %VersionForPacker% -path %BinaryName%.exe -path Updater.exe -path "modules\%Platform%\d3d\d3dcompiler_47.dll" -target %BuildTarget% %AlphaBetaParam% diff --git a/Telegram/build/setup.iss b/Telegram/build/setup.iss index 8b0715581..5c5ee7635 100644 --- a/Telegram/build/setup.iss +++ b/Telegram/build/setup.iss @@ -34,6 +34,7 @@ CloseApplications=force DisableDirPage=no DisableProgramGroupPage=no WizardStyle=modern +SignTool=sha256 #if MyBuildTarget == "win64" ArchitecturesAllowed="x64 arm64" From 2bbc7406da448659aa2ef7de69a8f95115af6350 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 19:06:51 +0400 Subject: [PATCH 048/225] Beta version 5.0.6. - Fix chat preview with non-default themes. - Fix chat preview crash when scrolling up. - Jump to chat from preview only by Double-Click. - Show chat preview with Force Touch on macOS. --- 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 ++++---- changelog.txt | 7 +++++++ 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index fe3b473ac..cc2453f6b 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.0.6.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 1f0a7a045..0a0a61d84 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,5,0 - PRODUCTVERSION 5,0,5,0 + FILEVERSION 5,0,6,0 + PRODUCTVERSION 5,0,6,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.0.5.0" + VALUE "FileVersion", "5.0.6.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.5.0" + VALUE "ProductVersion", "5.0.6.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index c334ca569..1b7249bf0 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,5,0 - PRODUCTVERSION 5,0,5,0 + FILEVERSION 5,0,6,0 + PRODUCTVERSION 5,0,6,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", "5.0.5.0" + VALUE "FileVersion", "5.0.6.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.5.0" + VALUE "ProductVersion", "5.0.6.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 8de17b03c..911f4794f 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 = 5000005; -constexpr auto AppVersionStr = "5.0.5"; +constexpr auto AppVersion = 5000006; +constexpr auto AppVersionStr = "5.0.6"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 5d551a173..2a02b657b 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5000005 +AppVersion 5000006 AppVersionStrMajor 5.0 -AppVersionStrSmall 5.0.5 -AppVersionStr 5.0.5 +AppVersionStrSmall 5.0.6 +AppVersionStr 5.0.6 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 5.0.5.beta +AppVersionOriginal 5.0.6.beta diff --git a/changelog.txt b/changelog.txt index ddfebe110..70728871a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +5.0.6 beta (30.05.24) + +- Fix chat preview with non-default themes. +- Fix chat preview crash when scrolling up. +- Jump to chat from preview only by Double-Click. +- Show chat preview with Force Touch on macOS. + 5.0.5 beta (28.05.24) - Long press on chat userpic to show quick preview. From f8188f360a81db7eb73ce542575ce600f90fffb9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 May 2024 17:49:49 +0400 Subject: [PATCH 049/225] Update API scheme to layer 180. --- Telegram/SourceFiles/api/api_common.h | 1 + Telegram/SourceFiles/api/api_polls.cpp | 3 ++- Telegram/SourceFiles/api/api_sending.cpp | 6 ++++-- Telegram/SourceFiles/api/api_updates.cpp | 6 ++++-- Telegram/SourceFiles/apiwrap.cpp | 12 +++++++---- .../data/business/data_shortcut_messages.cpp | 3 ++- .../data/components/scheduled_messages.cpp | 6 ++++-- Telegram/SourceFiles/data/data_session.cpp | 3 ++- Telegram/SourceFiles/data/data_types.h | 1 + .../admin_log/history_admin_log_item.cpp | 3 ++- Telegram/SourceFiles/history/history_item.cpp | 6 +++++- Telegram/SourceFiles/history/history_item.h | 6 ++++-- .../media/stories/media_stories_share.cpp | 9 ++++++-- Telegram/SourceFiles/mtproto/scheme/api.tl | 21 +++++++++++++------ .../settings/settings_privacy_controllers.cpp | 3 ++- .../SourceFiles/window/window_peer_menu.cpp | 3 ++- 16 files changed, 65 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index fe0d489b0..9c3939bf5 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -23,6 +23,7 @@ struct SendOptions { PeerData *sendAs = nullptr; TimeId scheduled = 0; BusinessShortcutId shortcutId = 0; + EffectId effectId = 0; bool silent = false; bool handleSupportSwitch = false; bool hideViaBot = false; diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 3532e2c79..b2422af25 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -89,7 +89,8 @@ void Polls::create( MTPVector(), MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(_session, action.options.shortcutId) + Data::ShortcutIdToMTP(_session, action.options.shortcutId), + MTP_long(action.options.effectId) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index d91943fe0..9528ae446 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -165,7 +165,8 @@ void SendExistingMedia( sentEntities, MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(session, action.options.shortcutId) + Data::ShortcutIdToMTP(session, action.options.shortcutId), + MTP_long(action.options.effectId) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { if (error.code() == 400 @@ -335,7 +336,8 @@ bool SendDice(MessageToSend &message) { MTP_vector(), MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(session, action.options.shortcutId) + Data::ShortcutIdToMTP(session, action.options.shortcutId), + MTP_long(action.options.effectId) ), [=](const MTPUpdates &result, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) { api->sendMessageFail(error, peer, randomId, newId); diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 314e6c862..6613a1df3 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1137,7 +1137,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTPMessageReactions(), MTPVector(), MTP_int(d.vttl_period().value_or_empty()), - MTPint()), // quick_reply_shortcut_id + MTPint(), // quick_reply_shortcut_id + MTPlong()), // effect MessageFlags(), NewMessageType::Unread); } break; @@ -1172,7 +1173,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTPMessageReactions(), MTPVector(), MTP_int(d.vttl_period().value_or_empty()), - MTPint()), // quick_reply_shortcut_id + MTPint(), // quick_reply_shortcut_id + MTPlong()), // effect MessageFlags(), NewMessageType::Unread); } break; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 32e97c77f..c16926c73 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3869,7 +3869,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sentEntities, MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - mtpShortcut + mtpShortcut, + MTP_long(action.options.effectId) ), done, fail); } else { histories.sendPreparedMessage( @@ -3886,7 +3887,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sentEntities, MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - mtpShortcut + mtpShortcut, + MTP_long(action.options.effectId) ), done, fail); } isFirst = false; @@ -4180,7 +4182,8 @@ void ApiWrap::sendMediaWithRandomId( sentEntities, MTP_int(options.scheduled), (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(_session, options.shortcutId) + Data::ShortcutIdToMTP(_session, options.shortcutId), + MTP_long(options.effectId) ), [=](const MTPUpdates &result, const MTP::Response &response) { if (done) done(true); if (updateRecentStickers) { @@ -4282,7 +4285,8 @@ void ApiWrap::sendAlbumIfReady(not_null album) { MTP_vector(medias), MTP_int(album->options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(_session, album->options.shortcutId) + Data::ShortcutIdToMTP(_session, album->options.shortcutId), + MTP_long(album->options.effectId) ), [=](const MTPUpdates &result, const MTP::Response &response) { _sendingAlbums.remove(groupId); }, [=](const MTP::Error &error, const MTP::Response &response) { diff --git a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp index fbe30449b..57f8753a2 100644 --- a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp +++ b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp @@ -87,7 +87,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); MTPMessageReactions(), MTPVector(), MTP_int(data.vttl_period().value_or_empty()), - MTP_int(shortcutId)); + MTP_int(shortcutId), + MTP_long(data.veffect().value_or_empty())); }); } diff --git a/Telegram/SourceFiles/data/components/scheduled_messages.cpp b/Telegram/SourceFiles/data/components/scheduled_messages.cpp index 92e0da880..4313a8857 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/components/scheduled_messages.cpp @@ -91,7 +91,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); MTPMessageReactions(), MTPVector(), MTP_int(data.vttl_period().value_or_empty()), - MTPint()); // quick_reply_shortcut_id + MTPint(), // quick_reply_shortcut_id + MTP_long(data.veffect().value_or_empty())); // effect }); } @@ -259,7 +260,8 @@ void ScheduledMessages::sendNowSimpleMessage( MTPMessageReactions(), MTPVector(), MTP_int(update.vttl_period().value_or_empty()), - MTPint()), // quick_reply_shortcut_id + MTPint(), // quick_reply_shortcut_id + MTP_long(local->effectId())), // effect localFlags, NewMessageType::Unread); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 892830ede..f46fb1dc0 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4571,7 +4571,8 @@ void Session::insertCheckedServiceNotification( MTPMessageReactions(), MTPVector(), MTPint(), // ttl_period - MTPint()), // quick_reply_shortcut_id + MTPint(), // quick_reply_shortcut_id + MTPlong()), // effect localFlags, NewMessageType::Unread); } diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index ca11c8969..acc5c8915 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -138,6 +138,7 @@ using PollId = uint64; using WallPaperId = uint64; using CallId = uint64; using BotAppId = uint64; +using EffectId = uint64; constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL); 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 72d797460..7270696cd 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -139,7 +139,8 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { MTPMessageReactions(), MTPVector(), MTPint(), // ttl_period - MTPint()); // quick_reply_shortcut_id + MTPint(), // quick_reply_shortcut_id + MTP_long(data.veffect().value_or_empty())); }); } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 53ba1c67c..4326bed57 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -3116,6 +3116,10 @@ MessageGroupId HistoryItem::groupId() const { return _groupId; } +EffectId HistoryItem::effectId() const { + return _effectId; +} + bool HistoryItem::isEmpty() const { return _text.empty() && !_media @@ -4972,7 +4976,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { } void HistoryItem::setSelfDestruct( - HistoryServiceSelfDestruct::Type type, + HistorySelfDestructType type, MTPint mtpTTLvalue) { UpdateComponents(HistoryServiceSelfDestruct::Bit()); const auto selfdestruct = Get(); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index c7d3e59aa..de437cfe4 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -101,6 +101,7 @@ struct HistoryItemCommonFields { UserId viaBotId = 0; QString postAuthor; uint64 groupedId = 0; + EffectId effectId = 0; HistoryMessageMarkupData markup; }; @@ -493,8 +494,8 @@ public: not_null forwarded) const; [[nodiscard]] bool isEmpty() const; - [[nodiscard]] MessageGroupId groupId() const; + [[nodiscard]] EffectId effectId() const; [[nodiscard]] const HistoryMessageReplyMarkup *inlineReplyMarkup() const { return const_cast(this)->inlineReplyMarkup(); @@ -647,8 +648,9 @@ private: int _boostsApplied = 0; BusinessShortcutId _shortcutId = 0; - HistoryView::Element *_mainView = nullptr; MessageGroupId _groupId = MessageGroupId(); + EffectId _effectId = 0; + HistoryView::Element *_mainView = nullptr; friend class HistoryView::Element; friend class HistoryView::Message; diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index 76e353c00..bfd0e5c00 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -163,12 +163,17 @@ namespace Media::Stories { MTPVector(), MTP_int(action.options.scheduled), MTP_inputPeerEmpty(), - Data::ShortcutIdToMTP(session, action.options.shortcutId) + Data::ShortcutIdToMTP( + session, + action.options.shortcutId), + MTP_long(action.options.effectId) ), [=]( const MTPUpdates &result, const MTP::Response &response) { done(); - }, [=](const MTP::Error &error, const MTP::Response &response) { + }, [=]( + const MTP::Error &error, + const MTP::Response &response) { api->sendMessageFail(error, threadPeer, randomId); done(); }); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 963785e0b..d83726b0e 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -114,7 +114,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#2357bf25 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 flags2:# offline:flags2.1?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 via_business_bot_id:flags2.0?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 quick_reply_shortcut_id:flags.30?int = Message; +message#bde09c2e 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 flags2:# offline:flags2.1?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 via_business_bot_id:flags2.0?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 quick_reply_shortcut_id:flags.30?int effect:flags2.2?long = 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; @@ -413,6 +413,7 @@ updateBotNewBusinessMessage#9ddb347c flags:# connection_id:string message:Messag updateBotEditBusinessMessage#7df587c flags:# connection_id:string message:Message reply_to_message:flags.0?Message qts:int = Update; updateBotDeleteBusinessMessage#a02a982e connection_id:string peer:Peer messages:Vector qts:int = Update; updateNewStoryReaction#1824e40b story_id:int peer:Peer reaction:Reaction = Update; +updateBroadcastRevenueTransactions#dfd961f5 peer:Peer balances:BroadcastRevenueBalances = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1781,6 +1782,11 @@ reactionsNotifySettings#56e34970 flags:# messages_notify_from:flags.0?ReactionNo broadcastRevenueBalances#8438f1c6 current_balance:long available_balance:long overall_revenue:long = BroadcastRevenueBalances; +availableEffect#93c3e27e flags:# premium_required:flags.2?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:long effect_animation_id:flags.1?long = AvailableEffect; + +messages.availableEffectsNotModified#d1ed9a5b = messages.AvailableEffects; +messages.availableEffects#bddb616e hash:int effects:Vector documents:Vector = messages.AvailableEffects; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1791,6 +1797,8 @@ invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X; invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; invokeWithBusinessConnection#dd289f8e {X:Type} connection_id:string query:!X = X; +invokeWithGooglePlayIntegrity#1df92984 {X:Type} nonce:string token:string query:!X = X; +invokeWithApnsSecret#dae54f8#8252da54 {X:Type} nonce:string secret:string query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#aac7b717 flags:# no_joined_notifications:flags.0?true phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; @@ -1812,7 +1820,7 @@ auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken; auth.acceptLoginToken#e894ad4d token:bytes = Authorization; auth.checkRecoveryPassword#d36bf79 code:string = Bool; auth.importWebTokenAuthorization#2db873a9 api_id:int api_hash:string web_auth_token:string = auth.Authorization; -auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool; +auth.requestFirebaseSms#73ab08c0 flags:# phone_number:string phone_code_hash:string play_integrity_token:flags.0?string ios_push_secret:flags.1?string = Bool; auth.resetLoginEmail#7e960193 phone_number:string phone_code_hash:string = auth.SentCode; auth.reportMissingCode#cb9deff6 phone_number:string phone_code_hash:string mnc:string = Bool; @@ -1970,8 +1978,8 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#dff8042c flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; -messages.sendMedia#7bd66041 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; +messages.sendMessage#983f9745 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; +messages.sendMedia#7852834e flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; messages.forwardMessages#d5039208 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; @@ -2050,7 +2058,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#c964709 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; +messages.sendMultiMedia#37b74355 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -2170,6 +2178,7 @@ messages.deleteQuickReplyMessages#e105e910 shortcut_id:int id:Vector = Upda messages.toggleDialogFilterTags#fd2dda49 enabled:Bool = Bool; messages.getMyStickers#d0b5e1fc offset_id:long limit:int = messages.MyStickers; messages.getEmojiStickerGroups#1dd840f5 hash:int = messages.EmojiGroups; +messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; @@ -2432,4 +2441,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 179 +// LAYER 180 diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index b3829efeb..2bbe649f0 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -198,7 +198,8 @@ AdminLog::OwnedItem GenerateForwardedItem( MTPMessageReactions(), MTPVector(), MTPint(), // ttl_period - MTPint() // quick_reply_shortcut_id + MTPint(), // quick_reply_shortcut_id + MTPlong() // effect ).match([&](const MTPDmessage &data) { return history->makeMessage( history->nextNonHistoryEntryId(), diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 7648e4543..a4d57f791 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -135,7 +135,8 @@ void ShareBotGame( MTPVector(), MTP_int(0), // schedule_date MTPInputPeer(), // send_as - MTPInputQuickReplyShortcut() + MTPInputQuickReplyShortcut(), + MTPlong() ), [=](const MTPUpdates &, const MTP::Response &) { }, [=](const MTP::Error &error, const MTP::Response &) { history->session().api().sendMessageFail(error, history->peer); From ee4f83ffde3b68aabba4cbb69c5eca82afd899e8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 May 2024 20:47:05 +0400 Subject: [PATCH 050/225] Disable bottom-info reactions view. --- Telegram/SourceFiles/history/view/history_view_message.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 96cbc4648..beaae32f1 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -3013,6 +3013,8 @@ bool Message::isSignedAuthorElided() const { } bool Message::embedReactionsInBottomInfo() const { + return false; +#if 0 // legacy const auto item = data(); const auto user = item->history()->peer->asUser(); if (!user @@ -3045,6 +3047,7 @@ bool Message::embedReactionsInBottomInfo() const { } } return true; +#endif } bool Message::embedReactionsInBubble() const { From f7626340366a93337a3abddb27751216d0273ec3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 May 2024 23:34:28 +0400 Subject: [PATCH 051/225] Support effects API, show icon in info. --- .../data/data_message_reactions.cpp | 284 ++++++++++++++---- .../SourceFiles/data/data_message_reactions.h | 42 ++- .../dialogs/dialogs_search_tags.cpp | 6 +- Telegram/SourceFiles/history/history_item.cpp | 4 +- .../SourceFiles/history/history_widget.cpp | 5 +- .../history/view/history_view_bottom_info.cpp | 239 +++++---------- .../history/view/history_view_bottom_info.h | 43 ++- .../view/history_view_context_menu.cpp | 7 +- .../history/view/history_view_element.cpp | 11 + .../history/view/history_view_element.h | 5 + .../history/view/history_view_message.cpp | 65 +++- .../history/view/history_view_message.h | 4 + .../view/reactions/history_view_reactions.cpp | 6 +- Telegram/SourceFiles/ui/chat/chat.style | 2 + 14 files changed, 437 insertions(+), 286 deletions(-) diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 4dbe1dc87..539c55306 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -264,6 +264,7 @@ Reactions::Reactions(not_null owner) kRefreshFullListEach ) | rpl::start_with_next([=] { refreshDefault(); + requestEffects(); }, _lifetime); _owner->session().changes().messageUpdates( @@ -343,6 +344,12 @@ void Reactions::refreshTags() { requestTags(); } +void Reactions::refreshEffects() { + if (_effects.empty()) { + requestEffects(); + } +} + const std::vector &Reactions::list(Type type) const { switch (type) { case Type::Active: return _active; @@ -352,6 +359,7 @@ const std::vector &Reactions::list(Type type) const { case Type::MyTags: return _myTags.find((SavedSublist*)nullptr)->second.tags; case Type::Tags: return _tags; + case Type::Effects: return _effects; } Unexpected("Type in Reactions::list."); } @@ -552,21 +560,45 @@ rpl::producer Reactions::myTagRenamed() const { return _myTagRenamed.events(); } +rpl::producer<> Reactions::effectsUpdates() const { + return _effectsUpdated.events(); +} + +void Reactions::preloadReactionImageFor(const ReactionId &emoji) { + if (!emoji.emoji().isEmpty()) { + preloadImageFor(emoji); + } +} + +void Reactions::preloadEffectImageFor(EffectId id) { + preloadImageFor({ DocumentId(id) }); +} + void Reactions::preloadImageFor(const ReactionId &id) { - if (_images.contains(id) || id.emoji().isEmpty()) { + if (_images.contains(id)) { return; } auto &set = _images.emplace(id).first->second; - const auto i = ranges::find(_available, id, &Reaction::id); - const auto document = (i == end(_available)) + set.effect = (id.custom() != 0); + const auto i = set.effect + ? ranges::find(_effects, id, &Reaction::id) + : ranges::find(_available, id, &Reaction::id); + const auto document = (i == end(set.effect ? _effects : _available)) ? nullptr : i->centerIcon ? i->centerIcon : i->selectAnimation.get(); - if (document) { - loadImage(set, document, !i->centerIcon); - } else if (!_waitingForList) { - _waitingForList = true; + if (document || (set.effect && i != end(_effects))) { + if (!set.effect || i->centerIcon) { + loadImage(set, document, !i->centerIcon); + } else { + generateImage(set, i->title); + } + } else if (set.effect && !_waitingForEffects) { + _waitingForEffects = true; + refreshEffects(); + } else if (!set.effect && !_waitingForReactions) { + _waitingForReactions = true; refreshDefault(); } } @@ -597,14 +629,24 @@ void Reactions::preloadAnimationsFor(const ReactionId &id) { preload(i->aroundAnimation); } -QImage Reactions::resolveImageFor( - const ReactionId &emoji, - ImageSize size) { - const auto i = _images.find(emoji); +QImage Reactions::resolveReactionImageFor(const ReactionId &emoji) { + Expects(!emoji.custom()); + + return resolveImageFor(emoji); +} + +QImage Reactions::resolveEffectImageFor(EffectId id) { + return resolveImageFor({ DocumentId(id) }); +} + +QImage Reactions::resolveImageFor(const ReactionId &id) { + const auto i = _images.find(id); if (i == end(_images)) { - preloadImageFor(emoji); + preloadImageFor(id); } - auto &set = (i != end(_images)) ? i->second : _images[emoji]; + auto &set = (i != end(_images)) ? i->second : _images[id]; + set.effect = (id.custom() != 0); + const auto resolve = [&](QImage &image, int size) { const auto factor = style::DevicePixelRatio(); const auto frameSize = set.fromSelectAnimation @@ -634,21 +676,18 @@ QImage Reactions::resolveImageFor( } image.setDevicePixelRatio(factor); }; - if (set.bottomInfo.isNull() && set.icon) { - resolve(set.bottomInfo, st::reactionInfoImage); - resolve(set.inlineList, st::reactionInlineImage); + if (set.image.isNull() && set.icon) { + resolve( + set.image, + set.effect ? st::effectInfoImage : st::reactionInlineImage); crl::async([icon = std::move(set.icon)]{}); } - switch (size) { - case ImageSize::BottomInfo: return set.bottomInfo; - case ImageSize::InlineList: return set.inlineList; - } - Unexpected("ImageSize in Reactions::resolveImageFor."); + return set.image; } -void Reactions::resolveImages() { +void Reactions::resolveReactionImages() { for (auto &[id, set] : _images) { - if (!set.bottomInfo.isNull() || set.icon || set.media) { + if (set.effect || !set.image.isNull() || set.icon || set.media) { continue; } const auto i = ranges::find(_available, id, &Reaction::id); @@ -666,14 +705,38 @@ void Reactions::resolveImages() { } } +void Reactions::resolveEffectImages() { + for (auto &[id, set] : _images) { + if (!set.effect || !set.image.isNull() || set.icon || set.media) { + continue; + } + const auto i = ranges::find(_effects, id, &Reaction::id); + const auto document = (i == end(_effects)) + ? nullptr + : i->centerIcon + ? i->centerIcon + : nullptr; + if (document) { + loadImage(set, document, false); + } else if (i != end(_effects)) { + generateImage(set, i->title); + } else { + LOG(("API Error: Effect '%1' not found!" + ).arg(ReactionIdToLog(id))); + } + } +} + void Reactions::loadImage( ImageSet &set, not_null document, bool fromSelectAnimation) { - if (!set.bottomInfo.isNull() || set.icon) { + if (!set.image.isNull() || set.icon) { return; } else if (!set.media) { - set.fromSelectAnimation = fromSelectAnimation; + if (!set.effect) { + set.fromSelectAnimation = fromSelectAnimation; + } set.media = document->createMediaView(); set.media->checkStickerLarge(); } @@ -687,6 +750,26 @@ void Reactions::loadImage( } } +void Reactions::generateImage(ImageSet &set, const QString &emoji) { + Expects(set.effect); + + const auto e = Ui::Emoji::Find(emoji); + Assert(e != nullptr); + + const auto large = Ui::Emoji::GetSizeLarge(); + const auto factor = style::DevicePixelRatio(); + auto image = QImage(large, large, QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(factor); + image.fill(Qt::transparent); + { + QPainter p(&image); + Ui::Emoji::Draw(p, e, large, 0, 0); + } + const auto size = st::effectInfoImage; + set.image = image.scaled(size * factor, size * factor); + set.image.setDevicePixelRatio(factor); +} + void Reactions::setAnimatedIcon(ImageSet &set) { const auto size = style::ConvertScale(kSizeForDownscale); set.icon = Ui::MakeAnimatedIcon({ @@ -840,6 +923,25 @@ void Reactions::requestTags() { } +void Reactions::requestEffects() { + if (_effectsRequestId) { + return; + } + auto &api = _owner->session().api(); + _effectsRequestId = api.request(MTPmessages_GetAvailableEffects( + MTP_int(_effectsHash) + )).done([=](const MTPmessages_AvailableEffects &result) { + _effectsRequestId = 0; + result.match([&](const MTPDmessages_availableEffects &data) { + updateEffects(data); + }, [&](const MTPDmessages_availableEffectsNotModified &) { + }); + }).fail([=] { + _effectsRequestId = 0; + _effectsHash = 0; + }).send(); +} + void Reactions::updateTop(const MTPDmessages_reactions &data) { _topHash = data.vhash().v; _topIds = ListFromMTP(data); @@ -881,9 +983,9 @@ void Reactions::updateDefault(const MTPDmessages_availableReactions &data) { } } } - if (_waitingForList) { - _waitingForList = false; - resolveImages(); + if (_waitingForReactions) { + _waitingForReactions = false; + resolveReactionImages(); } defaultUpdated(); } @@ -939,6 +1041,32 @@ void Reactions::updateTags(const MTPDmessages_reactions &data) { _tagsUpdated.fire({}); } +void Reactions::updateEffects(const MTPDmessages_availableEffects &data) { + _effectsHash = data.vhash().v; + + const auto &list = data.veffects().v; + const auto toCache = [&](DocumentData *document) { + if (document) { + _iconsCache.emplace(document, document->createMediaView()); + } + }; + for (const auto &document : data.vdocuments().v) { + toCache(_owner->processDocument(document)); + } + _effects.clear(); + _effects.reserve(list.size()); + for (const auto &effect : list) { + if (const auto parsed = parse(effect)) { + _effects.push_back(*parsed); + } + } + if (_waitingForEffects) { + _waitingForEffects = false; + resolveEffectImages(); + } + effectsUpdated(); +} + void Reactions::recentUpdated() { _topRefreshTimer.callOnce(kTopRequestDelay); _recentUpdated.fire({}); @@ -969,6 +1097,10 @@ void Reactions::tagsUpdated() { _tagsUpdated.fire({}); } +void Reactions::effectsUpdated() { + _effectsUpdated.fire({}); +} + not_null Reactions::resolveListener() { return static_cast(this); } @@ -1111,35 +1243,73 @@ void Reactions::resolve(const ReactionId &id) { } std::optional Reactions::parse(const MTPAvailableReaction &entry) { - return entry.match([&](const MTPDavailableReaction &data) { - const auto emoji = qs(data.vreaction()); - const auto known = (Ui::Emoji::Find(emoji) != nullptr); - if (!known) { - LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji)); - } - return known - ? std::make_optional(Reaction{ - .id = ReactionId{ emoji }, - .title = qs(data.vtitle()), - //.staticIcon = _owner->processDocument(data.vstatic_icon()), - .appearAnimation = _owner->processDocument( - data.vappear_animation()), - .selectAnimation = _owner->processDocument( - data.vselect_animation()), - //.activateAnimation = _owner->processDocument( - // data.vactivate_animation()), - //.activateEffects = _owner->processDocument( - // data.veffect_animation()), - .centerIcon = (data.vcenter_icon() - ? _owner->processDocument(*data.vcenter_icon()).get() - : nullptr), - .aroundAnimation = (data.varound_animation() - ? _owner->processDocument( - *data.varound_animation()).get() - : nullptr), - .active = !data.is_inactive(), - }) - : std::nullopt; + const auto &data = entry.data(); + const auto emoji = qs(data.vreaction()); + const auto known = (Ui::Emoji::Find(emoji) != nullptr); + if (!known) { + LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji)); + return std::nullopt; + } + return std::make_optional(Reaction{ + .id = ReactionId{ emoji }, + .title = qs(data.vtitle()), + //.staticIcon = _owner->processDocument(data.vstatic_icon()), + .appearAnimation = _owner->processDocument( + data.vappear_animation()), + .selectAnimation = _owner->processDocument( + data.vselect_animation()), + //.activateAnimation = _owner->processDocument( + // data.vactivate_animation()), + //.activateEffects = _owner->processDocument( + // data.veffect_animation()), + .centerIcon = (data.vcenter_icon() + ? _owner->processDocument(*data.vcenter_icon()).get() + : nullptr), + .aroundAnimation = (data.varound_animation() + ? _owner->processDocument(*data.varound_animation()).get() + : nullptr), + .active = !data.is_inactive(), + }); +} + +std::optional Reactions::parse(const MTPAvailableEffect &entry) { + const auto &data = entry.data(); + const auto emoji = qs(data.vemoticon()); + const auto known = (Ui::Emoji::Find(emoji) != nullptr); + if (!known) { + LOG(("API Error: Unknown emoji in effects: %1").arg(emoji)); + return std::nullopt; + } + const auto id = DocumentId(data.vid().v); + const auto document = _owner->document(id); + if (!document->sticker()) { + LOG(("API Error: Bad sticker in effects: %1").arg(id)); + return std::nullopt; + } + const auto aroundId = data.veffect_animation_id().value_or_empty(); + const auto around = aroundId + ? _owner->document(aroundId).get() + : nullptr; + if (around && !around->sticker()) { + LOG(("API Error: Bad sticker in effects around: %1").arg(aroundId)); + return std::nullopt; + } + const auto iconId = data.vstatic_icon_id().value_or_empty(); + const auto icon = iconId ? _owner->document(iconId).get() : nullptr; + if (icon && !icon->sticker()) { + LOG(("API Error: Bad sticker in effects icon: %1").arg(iconId)); + return std::nullopt; + } + return std::make_optional(Reaction{ + .id = ReactionId{ id }, + .title = emoji, + .appearAnimation = document, + .selectAnimation = document, + .centerIcon = icon, + .aroundAnimation = around, + .active = true, + .effect = true, + .premium = data.is_premium_required(), }); } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 9d67e2e2c..755b1c4d2 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -37,6 +37,8 @@ struct Reaction { DocumentData *aroundAnimation = nullptr; int count = 0; bool active = false; + bool effect = false; + bool premium = false; }; struct PossibleItemReactionsRef { @@ -80,6 +82,7 @@ public: void refreshMyTags(SavedSublist *sublist = nullptr); void refreshMyTagsDelayed(); void refreshTags(); + void refreshEffects(); enum class Type { Active, @@ -88,6 +91,7 @@ public: All, MyTags, Tags, + Effects, }; [[nodiscard]] const std::vector &list(Type type) const; [[nodiscard]] const std::vector &myTagsInfo() const; @@ -108,16 +112,15 @@ public: [[nodiscard]] rpl::producer<> myTagsUpdates() const; [[nodiscard]] rpl::producer<> tagsUpdates() const; [[nodiscard]] rpl::producer myTagRenamed() const; + [[nodiscard]] rpl::producer<> effectsUpdates() const; + + void preloadReactionImageFor(const ReactionId &emoji); + [[nodiscard]] QImage resolveReactionImageFor(const ReactionId &emoji); + + void preloadEffectImageFor(EffectId id); + [[nodiscard]] QImage resolveEffectImageFor(EffectId id); - enum class ImageSize { - BottomInfo, - InlineList, - }; - void preloadImageFor(const ReactionId &emoji); void preloadAnimationsFor(const ReactionId &emoji); - [[nodiscard]] QImage resolveImageFor( - const ReactionId &emoji, - ImageSize size); void send(not_null item, bool addToRecent); [[nodiscard]] bool sending(not_null item) const; @@ -139,11 +142,11 @@ public: private: struct ImageSet { - QImage bottomInfo; - QImage inlineList; + QImage image; std::shared_ptr media; std::unique_ptr icon; bool fromSelectAnimation = false; + bool effect = false; }; struct TagsBySublist { TagsBySublist() = default; @@ -169,6 +172,7 @@ private: void requestGeneric(); void requestMyTags(SavedSublist *sublist = nullptr); void requestTags(); + void requestEffects(); void updateTop(const MTPDmessages_reactions &data); void updateRecent(const MTPDmessages_reactions &data); @@ -178,11 +182,13 @@ private: SavedSublist *sublist, const MTPDmessages_savedReactionTags &data); void updateTags(const MTPDmessages_reactions &data); + void updateEffects(const MTPDmessages_availableEffects &data); void recentUpdated(); void defaultUpdated(); void myTagsUpdated(); void tagsUpdated(); + void effectsUpdated(); [[nodiscard]] std::optional resolveById(const ReactionId &id); [[nodiscard]] std::vector resolveByIds( @@ -203,13 +209,19 @@ private: [[nodiscard]] std::optional parse( const MTPAvailableReaction &entry); + [[nodiscard]] std::optional parse( + const MTPAvailableEffect &entry); + void preloadImageFor(const ReactionId &id); + [[nodiscard]] QImage resolveImageFor(const ReactionId &id); void loadImage( ImageSet &set, not_null document, bool fromSelectAnimation); + void generateImage(ImageSet &set, const QString &emoji); void setAnimatedIcon(ImageSet &set); - void resolveImages(); + void resolveReactionImages(); + void resolveEffectImages(); void downloadTaskFinished(); void repaintCollected(); @@ -233,6 +245,7 @@ private: std::vector _topIds; base::flat_set _unresolvedTop; std::vector> _genericAnimations; + std::vector _effects; ReactionId _favoriteId; ReactionId _unresolvedFavoriteId; std::optional _favorite; @@ -249,6 +262,7 @@ private: rpl::event_stream _myTagsUpdated; rpl::event_stream<> _tagsUpdated; rpl::event_stream _myTagRenamed; + rpl::event_stream<> _effectsUpdated; // We need &i->second stay valid while inserting new items. // So we use std::map instead of base::flat_map here. @@ -271,9 +285,13 @@ private: mtpRequestId _tagsRequestId = 0; uint64 _tagsHash = 0; + mtpRequestId _effectsRequestId = 0; + int32 _effectsHash = 0; + base::flat_map _images; rpl::lifetime _imagesLoadLifetime; - bool _waitingForList = false; + bool _waitingForReactions = false; + bool _waitingForEffects = false; base::flat_map _sentRequests; diff --git a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp index 276d4e5b6..c7e85c94e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp @@ -168,7 +168,7 @@ void SearchTags::fill( .selected = ranges::contains(selected, id), }); if (!customId) { - _owner->reactions().preloadImageFor(id); + _owner->reactions().preloadReactionImageFor(id); } }; if (!premium) { @@ -335,9 +335,7 @@ void SearchTags::paint( paintBackground(p, geometry, tag); paintText(p, geometry, tag); if (!tag.custom && !tag.promo && tag.image.isNull()) { - tag.image = _owner->reactions().resolveImageFor( - tag.id, - ::Data::Reactions::ImageSize::InlineList); + tag.image = _owner->reactions().resolveReactionImageFor(tag.id); } const auto inner = geometry.marginsRemoved(padding); const auto image = QRect( diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 4326bed57..b65173f00 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -365,6 +365,7 @@ HistoryItem::HistoryItem( .from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0), .date = data.vdate().v, .shortcutId = data.vquick_reply_shortcut_id().value_or_empty(), + .effectId = data.veffect().value_or_empty(), }) { _boostsApplied = data.vfrom_boosts_applied().value_or_empty(); @@ -681,7 +682,8 @@ HistoryItem::HistoryItem( : history->peer) , _flags(FinalizeMessageFlags(history, fields.flags)) , _date(fields.date) -, _shortcutId(fields.shortcutId) { +, _shortcutId(fields.shortcutId) +, _effectId(fields.effectId) { if (isHistoryEntry() && IsClientMsgId(id)) { _history->registerClientSideMessage(this); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 029da2623..ce14ce620 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -74,6 +74,7 @@ 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_message_reactions.h" #include "data/data_peer_values.h" // Data::AmPremiumValue. #include "data/data_premium_limits.h" // Data::PremiumLimits. #include "data/stickers/data_stickers.h" @@ -1398,7 +1399,7 @@ int HistoryWidget::itemTopForHighlight( if (heightLeft >= 0) { return std::max(itemTop - (heightLeft / 2), 0); } else if (reactionCenter >= 0) { - const auto maxSize = st::reactionInfoImage; + const auto maxSize = st::reactionInlineImage; // Show message right till the bottom. const auto forBottom = itemTop + viewHeight - visibleAreaHeight; @@ -2375,6 +2376,8 @@ void HistoryWidget::showHistory( } } + session().data().reactions().refreshEffects(); + _scroll->hide(); _list = _scroll->setOwnedWidget( object_ptr(this, _scroll, controller(), _history)); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index a4000efe1..db8f49a27 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -30,14 +30,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { -struct BottomInfo::Reaction { +struct BottomInfo::Effect { mutable std::unique_ptr animation; mutable QImage image; - ReactionId id; - QString countText; - int count = 0; - int countTextWidth = 0; - bool chosen = false; + EffectId id = 0; }; BottomInfo::BottomInfo( @@ -58,17 +54,11 @@ void BottomInfo::update(Data &&data, int availableWidth) { } } -int BottomInfo::countReactionsMaxWidth() const { +int BottomInfo::countEffectMaxWidth() const { auto result = 0; - for (const auto &reaction : _reactions) { + if (_effect) { result += st::reactionInfoSize; - if (reaction.countTextWidth > 0) { - result += st::reactionInfoSkip - + reaction.countTextWidth - + st::reactionInfoDigitSkip; - } else { - result += st::reactionInfoBetween; - } + result += st::reactionInfoBetween; } if (result) { result += (st::reactionInfoSkip - st::reactionInfoBetween); @@ -76,19 +66,14 @@ int BottomInfo::countReactionsMaxWidth() const { return result; } -int BottomInfo::countReactionsHeight(int newWidth) const { +int BottomInfo::countEffectHeight(int newWidth) const { const auto left = 0; auto x = 0; auto y = 0; auto widthLeft = newWidth; - for (const auto &reaction : _reactions) { - const auto add = (reaction.countTextWidth > 0) - ? st::reactionInfoDigitSkip - : st::reactionInfoBetween; - const auto width = st::reactionInfoSize - + (reaction.countTextWidth > 0 - ? (st::reactionInfoSkip + reaction.countTextWidth) - : 0); + if (_effect) { + const auto add = st::reactionInfoBetween; + const auto width = st::reactionInfoSize; if (x > left && widthLeft < width) { x = left; y += st::msgDateFont->height; @@ -107,7 +92,7 @@ int BottomInfo::firstLineWidth() const { if (height() == minHeight()) { return width(); } - return maxWidth() - _reactionsMaxWidth; + return maxWidth() - _effectMaxWidth; } bool BottomInfo::isWide() const { @@ -115,14 +100,14 @@ bool BottomInfo::isWide() const { || !_data.author.isEmpty() || !_views.isEmpty() || !_replies.isEmpty() - || !_reactions.empty(); + || _effect; } TextState BottomInfo::textState( not_null item, QPoint position) const { auto result = TextState(item); - if (const auto link = revokeReactionLink(item, position)) { + if (const auto link = replayEffectLink(item, position)) { result.link = link; return result; } @@ -172,32 +157,26 @@ TextState BottomInfo::textState( return result; } -ClickHandlerPtr BottomInfo::revokeReactionLink( +ClickHandlerPtr BottomInfo::replayEffectLink( not_null item, QPoint position) const { - if (_reactions.empty()) { + if (!_effect) { return nullptr; } auto left = 0; auto top = 0; auto available = width(); if (height() != minHeight()) { - available = std::min(available, _reactionsMaxWidth); + available = std::min(available, _effectMaxWidth); left += width() - available; top += st::msgDateFont->height; } auto x = left; auto y = top; auto widthLeft = available; - for (const auto &reaction : _reactions) { - const auto chosen = reaction.chosen; - const auto add = (reaction.countTextWidth > 0) - ? st::reactionInfoDigitSkip - : st::reactionInfoBetween; - const auto width = st::reactionInfoSize - + (reaction.countTextWidth > 0 - ? (st::reactionInfoSkip + reaction.countTextWidth) - : 0); + if (_effect) { + const auto add = st::reactionInfoBetween; + const auto width = st::reactionInfoSize; if (x > left && widthLeft < width) { x = left; y += st::msgDateFont->height; @@ -208,11 +187,11 @@ ClickHandlerPtr BottomInfo::revokeReactionLink( y, st::reactionInfoSize, st::msgDateFont->height); - if (chosen && image.contains(position)) { - if (!_revokeLink) { - _revokeLink = revokeReactionLink(item); + if (image.contains(position)) { + if (!_replayLink) { + _replayLink = replayEffectLink(item); } - return _revokeLink; + return _replayLink; } x += width + add; widthLeft -= width + add; @@ -220,25 +199,16 @@ ClickHandlerPtr BottomInfo::revokeReactionLink( return nullptr; } -ClickHandlerPtr BottomInfo::revokeReactionLink( +ClickHandlerPtr BottomInfo::replayEffectLink( not_null item) const { const auto itemId = item->fullId(); const auto sessionId = item->history()->session().uniqueId(); return std::make_shared([=]( - ClickContext context) { + ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { - if (controller->session().uniqueId() == sessionId) { - auto &owner = controller->session().data(); - if (const auto item = owner.message(itemId)) { - const auto chosen = item->chosenReactions(); - if (!chosen.empty()) { - item->toggleReaction( - chosen.front(), - HistoryItem::ReactionSource::Existing); - } - } - } + controller->showToast("playing nice effect.."); + AssertIsDebug(); } }); } @@ -340,20 +310,20 @@ void BottomInfo::paint( firstLineBottom + st::historyViewsTop, outerWidth); } - if (!_reactions.empty()) { + if (_effect) { auto left = position.x(); auto top = position.y(); auto available = width(); if (height() != minHeight()) { - available = std::min(available, _reactionsMaxWidth); + available = std::min(available, _effectMaxWidth); left += width() - available; top += st::msgDateFont->height; } - paintReactions(p, position, left, top, available, context); + paintEffect(p, position, left, top, available, context); } } -void BottomInfo::paintReactions( +void BottomInfo::paintEffect( Painter &p, QPoint origin, int left, @@ -369,52 +339,33 @@ void BottomInfo::paintReactions( auto x = left; auto y = top; auto widthLeft = availableWidth; - for (const auto &reaction : _reactions) { - if (context.reactionInfo - && reaction.animation - && reaction.animation->finished()) { - reaction.animation = nullptr; - } - const auto animating = (reaction.animation != nullptr); - const auto add = (reaction.countTextWidth > 0) - ? st::reactionInfoDigitSkip - : st::reactionInfoBetween; - const auto width = st::reactionInfoSize - + (reaction.countTextWidth > 0 - ? (st::reactionInfoSkip + reaction.countTextWidth) - : 0); + if (_effect) { + const auto animating = (_effect->animation != nullptr); + const auto add = st::reactionInfoBetween; + const auto width = st::reactionInfoSize; if (x > left && widthLeft < width) { x = left; y += st::msgDateFont->height; widthLeft = availableWidth; } - if (reaction.image.isNull()) { - reaction.image = _reactionsOwner->resolveImageFor( - reaction.id, - ::Data::Reactions::ImageSize::BottomInfo); + if (_effect->image.isNull()) { + _effect->image = _reactionsOwner->resolveEffectImageFor( + _effect->id); } const auto image = QRect( - x + (st::reactionInfoSize - st::reactionInfoImage) / 2, - y + (st::msgDateFont->height - st::reactionInfoImage) / 2, - st::reactionInfoImage, - st::reactionInfoImage); - const auto skipImage = animating - && (reaction.count < 2 || !reaction.animation->flying()); - if (!reaction.image.isNull() && !skipImage) { - p.drawImage(image.topLeft(), reaction.image); + x + (st::reactionInfoSize - st::effectInfoImage) / 2, + y + (st::msgDateFont->height - st::effectInfoImage) / 2, + st::effectInfoImage, + st::effectInfoImage); + if (!_effect->image.isNull()) { + p.drawImage(image.topLeft(), _effect->image); } if (animating) { animations.push_back({ - .animation = reaction.animation.get(), + .animation = _effect->animation.get(), .target = image, }); } - if (reaction.countTextWidth > 0) { - p.drawText( - x + st::reactionInfoSize + st::reactionInfoSkip, - y + st::msgDateFont->ascent, - reaction.countText); - } x += width + add; widthLeft -= width + add; } @@ -448,18 +399,18 @@ QSize BottomInfo::countCurrentSize(int newWidth) { const auto dateHeight = (_data.flags & Data::Flag::Sponsored) ? 0 : st::msgDateFont->height; - const auto noReactionsWidth = maxWidth() - _reactionsMaxWidth; - accumulate_min(newWidth, std::max(noReactionsWidth, _reactionsMaxWidth)); + const auto noReactionsWidth = maxWidth() - _effectMaxWidth; + accumulate_min(newWidth, std::max(noReactionsWidth, _effectMaxWidth)); return QSize( newWidth, - dateHeight + countReactionsHeight(newWidth)); + dateHeight + countEffectHeight(newWidth)); } void BottomInfo::layout() { layoutDateText(); layoutViewsText(); layoutRepliesText(); - layoutReactionsText(); + layoutEffectText(); initDimensions(); } @@ -520,33 +471,12 @@ void BottomInfo::layoutRepliesText() { Ui::NameTextOptions()); } -void BottomInfo::layoutReactionsText() { - if (_data.reactions.empty()) { - _reactions.clear(); +void BottomInfo::layoutEffectText() { + if (!_data.effectId) { + _effect = nullptr; return; } - auto sorted = ranges::views::all( - _data.reactions - ) | ranges::views::transform([](const MessageReaction &reaction) { - return not_null{ &reaction }; - }) | ranges::to_vector; - ranges::sort( - sorted, - std::greater<>(), - &MessageReaction::count); - - auto reactions = std::vector(); - reactions.reserve(sorted.size()); - for (const auto &reaction : sorted) { - const auto &id = reaction->id; - const auto i = ranges::find(_reactions, id, &Reaction::id); - reactions.push_back((i != end(_reactions)) - ? std::move(*i) - : prepareReactionWithId(id)); - reactions.back().chosen = reaction->my; - setReactionCount(reactions.back(), reaction->count); - } - _reactions = std::move(reactions); + _effect = std::make_unique(prepareEffectWithId(_data.effectId)); } QSize BottomInfo::countOptimalSize() { @@ -571,69 +501,42 @@ QSize BottomInfo::countOptimalSize() { if (_data.flags & Data::Flag::Pinned) { width += st::historyPinWidth; } - _reactionsMaxWidth = countReactionsMaxWidth(); - width += _reactionsMaxWidth; + _effectMaxWidth = countEffectMaxWidth(); + width += _effectMaxWidth; const auto dateHeight = (_data.flags & Data::Flag::Sponsored) ? 0 : st::msgDateFont->height; return QSize(width, dateHeight); } -BottomInfo::Reaction BottomInfo::prepareReactionWithId( - const ReactionId &id) { - auto result = Reaction{ .id = id }; - _reactionsOwner->preloadImageFor(id); +BottomInfo::Effect BottomInfo::prepareEffectWithId(EffectId id) { + auto result = Effect{ .id = id }; + _reactionsOwner->preloadEffectImageFor(id); return result; } -void BottomInfo::setReactionCount(Reaction &reaction, int count) { - if (reaction.count == count) { - return; - } - reaction.count = count; - reaction.countText = (count > 1) - ? Lang::FormatCountToShort(count).string - : QString(); - reaction.countTextWidth = (count > 1) - ? st::msgDateFont->width(reaction.countText) - : 0; -} - -void BottomInfo::animateReaction( - Ui::ReactionFlyAnimationArgs &&args, +void BottomInfo::animateEffect( + Ui::ReactionFlyAnimationArgs &&args, Fn repaint) { - const auto i = ranges::find(_reactions, args.id, &Reaction::id); - if (i == end(_reactions)) { + if (!_effect || args.id.custom() != _effect->id) { return; } - i->animation = std::make_unique( + _effect->animation = std::make_unique( _reactionsOwner, args.translated(QPoint(width(), height())), std::move(repaint), - st::reactionInfoImage); + st::effectInfoImage); } -auto BottomInfo::takeReactionAnimations() --> base::flat_map> { - auto result = base::flat_map< - ReactionId, - std::unique_ptr>(); - for (auto &reaction : _reactions) { - if (reaction.animation) { - result.emplace(reaction.id, std::move(reaction.animation)); - } - } - return result; +auto BottomInfo::takeEffectAnimation() +-> std::unique_ptr { + return _effect ? std::move(_effect->animation) : nullptr; } -void BottomInfo::continueReactionAnimations(base::flat_map< - ReactionId, - std::unique_ptr> animations) { - for (auto &[id, animation] : animations) { - const auto i = ranges::find(_reactions, id, &Reaction::id); - if (i != end(_reactions)) { - i->animation = std::move(animation); - } +void BottomInfo::continueEffectAnimation( + std::unique_ptr animation) { + if (_effect) { + _effect->animation = std::move(animation); } } @@ -643,9 +546,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null message) { auto result = BottomInfo::Data(); result.date = message->dateTime(); - if (message->embedReactionsInBottomInfo()) { - result.reactions = item->reactions(); - } + result.effectId = item->effectId(); if (message->hasOutLayout()) { result.flags |= Flag::OutLayout; } diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index efdab3334..b4cc26f51 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -20,8 +20,6 @@ class ReactionFlyAnimation; namespace Data { class Reactions; -struct ReactionId; -struct MessageReaction; } // namespace Data namespace HistoryView { @@ -33,8 +31,6 @@ struct TextState; class BottomInfo final : public Object { public: - using ReactionId = ::Data::ReactionId; - using MessageReaction = ::Data::MessageReaction; struct Data { enum class Flag : uchar { Edited = 0x01, @@ -52,7 +48,7 @@ public: QDateTime date; QString author; - std::vector reactions; + EffectId effectId = 0; std::optional views; std::optional replies; std::optional forwardsCount; @@ -78,29 +74,26 @@ public: bool inverted, const PaintContext &context) const; - void animateReaction( + void animateEffect( Ui::ReactionFlyAnimationArgs &&args, Fn repaint); - [[nodiscard]] auto takeReactionAnimations() - -> base::flat_map< - ReactionId, - std::unique_ptr>; - void continueReactionAnimations(base::flat_map< - ReactionId, - std::unique_ptr> animations); + [[nodiscard]] auto takeEffectAnimation() + -> std::unique_ptr; + void continueEffectAnimation( + std::unique_ptr animation); private: - struct Reaction; + struct Effect; void layout(); void layoutDateText(); void layoutViewsText(); void layoutRepliesText(); - void layoutReactionsText(); + void layoutEffectText(); - [[nodiscard]] int countReactionsMaxWidth() const; - [[nodiscard]] int countReactionsHeight(int newWidth) const; - void paintReactions( + [[nodiscard]] int countEffectMaxWidth() const; + [[nodiscard]] int countEffectHeight(int newWidth) const; + void paintEffect( Painter &p, QPoint origin, int left, @@ -111,13 +104,11 @@ private: QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; - void setReactionCount(Reaction &reaction, int count); - [[nodiscard]] Reaction prepareReactionWithId( - const ReactionId &id); - [[nodiscard]] ClickHandlerPtr revokeReactionLink( + [[nodiscard]] Effect prepareEffectWithId(EffectId id); + [[nodiscard]] ClickHandlerPtr replayEffectLink( not_null item, QPoint position) const; - [[nodiscard]] ClickHandlerPtr revokeReactionLink( + [[nodiscard]] ClickHandlerPtr replayEffectLink( not_null item) const; const not_null<::Data::Reactions*> _reactionsOwner; @@ -125,9 +116,9 @@ private: Ui::Text::String _authorEditedDate; Ui::Text::String _views; Ui::Text::String _replies; - std::vector _reactions; - mutable ClickHandlerPtr _revokeLink; - int _reactionsMaxWidth = 0; + std::unique_ptr _effect; + mutable ClickHandlerPtr _replayLink; + int _effectMaxWidth = 0; bool _authorElided = false; }; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 559a98ccd..88d9ad183 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1038,7 +1038,7 @@ void EditTagBox( customId, [=] { field->update(); }); } else { - owner->reactions().preloadImageFor(id); + owner->reactions().preloadReactionImageFor(id); } field->paintRequest() | rpl::start_with_next([=](QRect clip) { auto p = QPainter(field); @@ -1053,9 +1053,8 @@ void EditTagBox( }); } else { if (state->image.isNull()) { - state->image = owner->reactions().resolveImageFor( - id, - ::Data::Reactions::ImageSize::InlineList); + state->image = owner->reactions().resolveReactionImageFor( + id); } if (!state->image.isNull()) { const auto size = st::reactionInlineSize; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index e687ec921..79fc506c1 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1770,6 +1770,17 @@ auto Element::takeReactionAnimations() return {}; } +void Element::animateEffect(Ui::ReactionFlyAnimationArgs &&args) { +} + +void Element::animateUnreadEffect() { +} + +auto Element::takeEffectAnimation() +-> std::unique_ptr { + return nullptr; +} + Element::~Element() { // Delete media while owner still exists. clearSpecialOnlyEmoji(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index a11d0831d..4316806ce 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -545,6 +545,11 @@ public: Data::ReactionId, std::unique_ptr>; + virtual void animateEffect(Ui::ReactionFlyAnimationArgs &&args); + void animateUnreadEffect(); + [[nodiscard]] virtual auto takeEffectAnimation() + -> std::unique_ptr; + void overrideMedia(std::unique_ptr media); virtual bool consumeHorizontalScroll(QPoint position, int delta) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index beaae32f1..ea17702ee 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -423,6 +423,7 @@ Message::Message( : base::flat_map< Data::ReactionId, std::unique_ptr>(); + auto animation = replacing ? replacing->takeEffectAnimation() : nullptr; if (!animations.empty()) { const auto repainter = [=] { repaint(); }; for (const auto &[id, animation] : animations) { @@ -430,10 +431,11 @@ Message::Message( } if (_reactions) { _reactions->continueAnimations(std::move(animations)); - } else { - _bottomInfo.continueReactionAnimations(std::move(animations)); } } + if (animation) { + _bottomInfo.continueEffectAnimation(std::move(animation)); + } if (data->isSponsored()) { const auto &session = data->history()->session(); const auto details = session.sponsoredMessages().lookupDetails( @@ -582,9 +584,6 @@ void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) { return; } - const auto animateInBottomInfo = [&](QPoint bottomRight) { - _bottomInfo.animateReaction(args.translated(-bottomRight), repainter); - }; if (bubble) { auto entry = logEntryOriginal(); @@ -609,6 +608,50 @@ void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) { _reactions->animate(args.translated(-reactionsPosition), repainter); return; } + } +} + +void Message::animateEffect(Ui::ReactionFlyAnimationArgs &&args) { + const auto item = data(); + const auto media = this->media(); + + auto g = countGeometry(); + if (g.width() < 1 || isHidden()) { + return; + } + const auto repainter = [=] { repaint(); }; + + const auto bubble = drawBubble(); + const auto reactionsInBubble = _reactions && embedReactionsInBubble(); + const auto mediaDisplayed = media && media->isDisplayed(); + const auto keyboard = item->inlineReplyKeyboard(); + auto keyboardHeight = 0; + if (keyboard) { + keyboardHeight = keyboard->naturalHeight(); + g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight); + } + + const auto animateInBottomInfo = [&](QPoint bottomRight) { + _bottomInfo.animateEffect(args.translated(-bottomRight), repainter); + }; + if (bubble) { + auto entry = logEntryOriginal(); + + // Entry page is always a bubble bottom. + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); + + auto inner = g; + if (_comments) { + inner.setHeight(inner.height() - st::historyCommentsButtonHeight); + } + auto trect = inner.marginsRemoved(st::msgPadding); + const auto reactionsTop = (reactionsInBubble && !_viewButton) + ? st::mediaInBubbleSkip + : 0; + const auto reactionsHeight = reactionsInBubble + ? (reactionsTop + _reactions->height()) + : 0; if (_viewButton) { const auto belowInfo = _viewButton->belowMessageInfo(); const auto infoHeight = reactionsInBubble @@ -653,9 +696,15 @@ auto Message::takeReactionAnimations() -> base::flat_map< Data::ReactionId, std::unique_ptr> { - return _reactions - ? _reactions->takeAnimations() - : _bottomInfo.takeReactionAnimations(); + if (_reactions) { + return _reactions->takeAnimations(); + } + return {}; +} + +auto Message::takeEffectAnimation() +-> std::unique_ptr { + return _bottomInfo.takeEffectAnimation(); } QSize Message::performCountOptimalSize() { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 62ab6c02b..77c7c5fad 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -158,6 +158,10 @@ public: Data::ReactionId, std::unique_ptr> override; + void animateEffect(Ui::ReactionFlyAnimationArgs &&args) override; + auto takeEffectAnimation() + -> std::unique_ptr override; + QRect innerGeometry() const override; [[nodiscard]] BottomRippleMask bottomRippleMask(int buttonHeight) const; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index 328a8749a..3a79d50b7 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -182,7 +182,7 @@ InlineList::Button InlineList::prepareButtonWithId(const ReactionId &id) { customId, _customEmojiRepaint); } else { - _owner->preloadImageFor(id); + _owner->preloadReactionImageFor(id); } return result; } @@ -439,9 +439,7 @@ void InlineList::paint( } } if (!button.custom && button.image.isNull()) { - button.image = _owner->resolveImageFor( - button.id, - ::Data::Reactions::ImageSize::InlineList); + button.image = _owner->resolveReactionImageFor(button.id); } const auto textFg = !inbubble diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 91ba60a37..302c5b593 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -904,6 +904,8 @@ reactionMainAppearShift: 20px; reactionCollapseFadeThreshold: 40px; reactionFlyUp: 50px; +effectInfoImage: 12px; + searchInChatMultiSelectItem: MultiSelectItem(defaultMultiSelectItem) { maxWidth: 200px; } From a19e71324bdac60b0c74a08486887ca9721cea17 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 7 May 2024 14:58:03 +0400 Subject: [PATCH 052/225] Implement basic effect animation. --- .../data/data_message_reactions.cpp | 13 ++ .../SourceFiles/data/data_message_reactions.h | 1 + Telegram/SourceFiles/data/data_types.h | 2 + .../admin_log/history_admin_log_inner.cpp | 5 + .../admin_log/history_admin_log_inner.h | 3 + .../history/history_inner_widget.cpp | 23 +++- .../history/history_inner_widget.h | 3 + Telegram/SourceFiles/history/history_item.cpp | 18 +++ Telegram/SourceFiles/history/history_item.h | 2 + .../SourceFiles/history/history_widget.cpp | 7 +- .../history/view/history_view_bottom_info.cpp | 22 +-- .../history/view/history_view_bottom_info.h | 6 +- .../history/view/history_view_element.cpp | 5 + .../history/view/history_view_element.h | 6 + .../view/history_view_emoji_interactions.cpp | 126 +++++++++++++++++- .../view/history_view_emoji_interactions.h | 24 ++++ .../history/view/history_view_list_widget.cpp | 6 + .../history/view/history_view_list_widget.h | 3 + .../history/view/history_view_message.cpp | 2 +- 19 files changed, 258 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 539c55306..8682ba655 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #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_values.h" #include "data/data_saved_sublist.h" #include "data/stickers/data_custom_emoji.h" @@ -594,6 +595,9 @@ void Reactions::preloadImageFor(const ReactionId &id) { } else { generateImage(set, i->title); } + if (set.effect) { + preloadEffect(*i); + } } else if (set.effect && !_waitingForEffects) { _waitingForEffects = true; refreshEffects(); @@ -603,6 +607,15 @@ void Reactions::preloadImageFor(const ReactionId &id) { } } +void Reactions::preloadEffect(const Reaction &effect) { + if (effect.aroundAnimation) { + effect.aroundAnimation->createMediaView()->checkStickerLarge(); + } else { + const auto premium = effect.selectAnimation; + premium->loadVideoThumbnail(premium->stickerSetOrigin()); + } +} + void Reactions::preloadAnimationsFor(const ReactionId &id) { const auto custom = id.custom(); const auto document = custom ? _owner->document(custom).get() : nullptr; diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 755b1c4d2..a1254a6d7 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -212,6 +212,7 @@ private: [[nodiscard]] std::optional parse( const MTPAvailableEffect &entry); + void preloadEffect(const Reaction &effect); void preloadImageFor(const ReactionId &id); [[nodiscard]] QImage resolveImageFor(const ReactionId &id); void loadImage( diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index acc5c8915..4d0e5b3d7 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -321,6 +321,8 @@ enum class MessageFlag : uint64 { ReactionsAreTags = (1ULL << 43), ShortcutMessage = (1ULL << 44), + + EffectWatchedLocal = (1ULL << 45), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; 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 b7bb4b9e0..61cf9a889 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -674,6 +674,11 @@ void InnerWidget::elementStartPremium( void InnerWidget::elementCancelPremium(not_null view) { } +void InnerWidget::elementStartEffect( + not_null view, + Element *replacing) { +} + QString InnerWidget::elementAuthorRank(not_null view) { return {}; } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index f47afddfa..bbe3c0381 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -136,6 +136,9 @@ public: HistoryView::Element *replacing) override; void elementCancelPremium( not_null view) override; + void elementStartEffect( + not_null view, + HistoryView::Element *replacing) override; QString elementAuthorRank( not_null view) override; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 9809f9c89..aed2480cc 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -304,12 +304,18 @@ public: _widget->elementStartPremium(view, replacing); } } - void elementCancelPremium(not_null view) override { if (_widget) { _widget->elementCancelPremium(view); } } + void elementStartEffect( + not_null view, + Element *replacing) override { + if (_widget) { + _widget->elementStartEffect(view, replacing); + } + } QString elementAuthorRank(not_null view) override { return {}; @@ -950,6 +956,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { _translateTracker->startBunch(); auto readTill = (HistoryItem*)nullptr; auto readContents = base::flat_set>(); + auto startEffects = base::flat_set>(); const auto markingAsViewed = _widget->markingContentsRead(); const auto guard = gsl::finally([&] { if (_pinnedItem) { @@ -958,6 +965,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) { _translateTracker->finishBunch(); if (readTill && _widget->markingMessagesRead()) { session().data().histories().readInboxTill(readTill); + if (!startEffects.empty()) { + for (const auto &view : startEffects) { + _emojiInteractions->playEffectOnRead(view); + } + } } if (markingAsViewed && !readContents.empty()) { session().api().markContentsRead(readContents); @@ -991,6 +1003,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) { session().sponsoredMessages().view(item->fullId()); } else if (isUnread) { readTill = item; + if (item->hasUnwatchedEffect()) { + startEffects.emplace(view); + } } if (markingAsViewed && item->hasViews()) { session().api().views().scheduleIncrement(item); @@ -3568,6 +3583,12 @@ void HistoryInner::elementCancelPremium(not_null view) { _emojiInteractions->cancelPremiumEffect(view); } +void HistoryInner::elementStartEffect( + not_null view, + Element *replacing) { + _emojiInteractions->playEffect(view); +} + auto HistoryInner::getSelectionState() const -> HistoryView::TopBarWidget::SelectedState { auto result = HistoryView::TopBarWidget::SelectedState {}; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 11eb81a5a..2ab1207a1 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -170,6 +170,9 @@ public: not_null view, Element *replacing); void elementCancelPremium(not_null view); + void elementStartEffect( + not_null view, + Element *replacing); void updateBotInfo(bool recount = true); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index b65173f00..7e929a816 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -687,6 +687,9 @@ HistoryItem::HistoryItem( if (isHistoryEntry() && IsClientMsgId(id)) { _history->registerClientSideMessage(this); } + if (_effectId) { + _history->owner().reactions().preloadEffectImageFor(_effectId); + } } HistoryItem::HistoryItem( @@ -1283,6 +1286,21 @@ bool HistoryItem::hasUnreadReaction() const { return (_flags & MessageFlag::HasUnreadReaction); } +bool HistoryItem::hasUnwatchedEffect() const { + return !out() + && effectId() + && !(_flags & MessageFlag::EffectWatchedLocal) + && unread(history()); +} + +bool HistoryItem::markEffectWatched() { + if (!hasUnwatchedEffect()) { + return false; + } + _flags |= MessageFlag::EffectWatchedLocal; + return true; +} + bool HistoryItem::mentionsMe() const { if (Has() && !Core::App().settings().notifyAboutPinned()) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index de437cfe4..1a276a447 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -241,6 +241,8 @@ public: [[nodiscard]] bool mentionsMe() const; [[nodiscard]] bool isUnreadMention() const; [[nodiscard]] bool hasUnreadReaction() const; + [[nodiscard]] bool hasUnwatchedEffect() const; + bool markEffectWatched(); [[nodiscard]] bool isUnreadMedia() const; [[nodiscard]] bool isIncomingUnreadMedia() const; [[nodiscard]] bool hasUnreadMediaFlag() const; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ce14ce620..0f919d652 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1389,7 +1389,10 @@ int HistoryWidget::itemTopForHighlight( const auto itemTop = _list->itemTop(view); Assert(itemTop >= 0); - const auto reactionCenter = view->data()->hasUnreadReaction() + const auto item = view->data(); + const auto unwatchedEffect = item->hasUnwatchedEffect(); + const auto showReactions = item->hasUnreadReaction() || unwatchedEffect; + const auto reactionCenter = showReactions ? view->reactionButtonParameters({}, {}).center.y() : -1; @@ -2376,8 +2379,6 @@ void HistoryWidget::showHistory( } } - session().data().reactions().refreshEffects(); - _scroll->hide(); _list = _scroll->setOwnedWidget( object_ptr(this, _scroll, controller(), _history)); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index db8f49a27..c7461f3fc 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/view/history_view_message.h" #include "history/view/history_view_cursor_state.h" +#include "chat_helpers/emoji_interactions.h" #include "core/click_handler_types.h" #include "main/main_session.h" #include "lottie/lottie_icon.h" @@ -104,10 +105,11 @@ bool BottomInfo::isWide() const { } TextState BottomInfo::textState( - not_null item, + not_null view, QPoint position) const { + const auto item = view->data(); auto result = TextState(item); - if (const auto link = replayEffectLink(item, position)) { + if (const auto link = replayEffectLink(view, position)) { result.link = link; return result; } @@ -158,7 +160,7 @@ TextState BottomInfo::textState( } ClickHandlerPtr BottomInfo::replayEffectLink( - not_null item, + not_null view, QPoint position) const { if (!_effect) { return nullptr; @@ -189,7 +191,7 @@ ClickHandlerPtr BottomInfo::replayEffectLink( st::msgDateFont->height); if (image.contains(position)) { if (!_replayLink) { - _replayLink = replayEffectLink(item); + _replayLink = replayEffectLink(view); } return _replayLink; } @@ -200,15 +202,17 @@ ClickHandlerPtr BottomInfo::replayEffectLink( } ClickHandlerPtr BottomInfo::replayEffectLink( - not_null item) const { + not_null view) const { + const auto item = view->data(); const auto itemId = item->fullId(); const auto sessionId = item->history()->session().uniqueId(); - return std::make_shared([=]( - ClickContext context) { + const auto weak = base::make_weak(view); + return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { - controller->showToast("playing nice effect.."); - AssertIsDebug(); + if (const auto strong = weak.get()) { + strong->delegate()->elementStartEffect(strong, nullptr); + } } }); } diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index b4cc26f51..c585281be 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -62,7 +62,7 @@ public: [[nodiscard]] int firstLineWidth() const; [[nodiscard]] bool isWide() const; [[nodiscard]] TextState textState( - not_null item, + not_null view, QPoint position) const; [[nodiscard]] bool isSignedAuthorElided() const; @@ -106,10 +106,10 @@ private: [[nodiscard]] Effect prepareEffectWithId(EffectId id); [[nodiscard]] ClickHandlerPtr replayEffectLink( - not_null item, + not_null view, QPoint position) const; [[nodiscard]] ClickHandlerPtr replayEffectLink( - not_null item) const; + not_null view) const; const not_null<::Data::Reactions*> _reactionsOwner; Data _data; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 79fc506c1..ef5b0c830 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -192,6 +192,11 @@ void DefaultElementDelegate::elementCancelPremium( not_null view) { } +void DefaultElementDelegate::elementStartEffect( + not_null view, + Element *replacing) { +} + QString DefaultElementDelegate::elementAuthorRank( not_null view) { return {}; diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 4316806ce..324640b6d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -113,6 +113,9 @@ public: not_null view, Element *replacing) = 0; virtual void elementCancelPremium(not_null view) = 0; + virtual void elementStartEffect( + not_null view, + Element *replacing) = 0; virtual QString elementAuthorRank(not_null view) = 0; virtual ~ElementDelegate() { @@ -163,6 +166,9 @@ public: not_null view, Element *replacing) override; void elementCancelPremium(not_null view) override; + void elementStartEffect( + not_null view, + Element *replacing) override; QString elementAuthorRank(not_null view) override; }; diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index 91e18f9d8..c9b299a72 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/media/history_view_sticker.h" #include "history/history.h" +#include "history/history_item.h" #include "chat_helpers/stickers_emoji_pack.h" #include "chat_helpers/emoji_interactions.h" #include "chat_helpers/stickers_lottie.h" @@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_message_reactions.h" #include "lottie/lottie_common.h" #include "lottie/lottie_single_player.h" #include "base/random.h" @@ -45,8 +47,8 @@ constexpr auto kDropDelayedAfterDelay = crl::time(2000); EmojiInteractions::EmojiInteractions( not_null session, Fn)> itemTop) - : _session(session) - , _itemTop(std::move(itemTop)) { +: _session(session) +, _itemTop(std::move(itemTop)) { _session->data().viewRemoved( ) | rpl::filter([=] { return !_plays.empty() || !_delayed.empty(); @@ -56,6 +58,11 @@ EmojiInteractions::EmojiInteractions( ranges::remove(_delayed, view, &Delayed::view), end(_delayed)); }, _lifetime); + + _session->data().reactions().effectsUpdates( + ) | rpl::start_with_next([=] { + checkPendingEffects(); + }, _lifetime); } EmojiInteractions::~EmojiInteractions() = default; @@ -143,6 +150,121 @@ void EmojiInteractions::play( false); } +void EmojiInteractions::playEffectOnRead(not_null view) { + if (view->data()->markEffectWatched()) { + playEffect(view); + } +} + +void EmojiInteractions::playEffect(not_null view) { + if (const auto resolved = resolveEffect(view)) { + playEffect(view, resolved); + } else if (view->data()->effectId()) { + if (resolved.document && !_downloadLifetime) { + _downloadLifetime = _session->downloaderTaskFinished( + ) | rpl::start_with_next([=] { + checkPendingEffects(); + }); + } + addPendingEffect(view); + } +} + +EmojiInteractions::ResolvedEffect EmojiInteractions::resolveEffect( + not_null view) { + const auto item = view->data(); + const auto effectId = item->effectId(); + if (!effectId) { + return {}; + } + using Type = Data::Reactions::Type; + const auto &effects = _session->data().reactions().list(Type::Effects); + const auto i = ranges::find( + effects, + Data::ReactionId{ effectId }, + &Data::Reaction::id); + if (i == end(effects)) { + return {}; + } + auto document = (DocumentData*)nullptr; + auto content = QByteArray(); + auto filepath = QString(); + if ((document = i->aroundAnimation)) { + content = document->createMediaView()->bytes(); + filepath = document->filepath(); + } else { + document = i->selectAnimation; + content = document->createMediaView()->videoThumbnailContent(); + } + return { + .emoticon = i->title, + .document = document, + .content = content, + .filepath = filepath, + }; +} + +void EmojiInteractions::playEffect( + not_null view, + const ResolvedEffect &resolved) { + play( + resolved.emoticon, + view, + resolved.document, + resolved.content, + resolved.filepath, + false, + false); +} + +void EmojiInteractions::addPendingEffect(not_null view) { + auto found = false; + const auto predicate = [&](base::weak_ptr weak) { + const auto strong = weak.get(); + if (strong == view) { + found = true; + } + return !strong; + }; + _pendingEffects.erase( + ranges::remove_if(_pendingEffects, predicate), + end(_pendingEffects)); + if (!found) { + _pendingEffects.push_back(view); + } +} + +void EmojiInteractions::checkPendingEffects() { + auto waitingDownload = false; + const auto predicate = [&](base::weak_ptr weak) { + const auto strong = weak.get(); + if (!strong) { + return true; + } + const auto resolved = resolveEffect(strong); + if (resolved) { + playEffect(strong, resolved); + return true; + } else if (!strong->data()->effectId()) { + return true; + } else if (resolved.document) { + waitingDownload = true; + } + return false; + }; + _pendingEffects.erase( + ranges::remove_if(_pendingEffects, predicate), + end(_pendingEffects)); + if (!waitingDownload) { + _downloadLifetime.destroy(); + } else if (!_downloadLifetime) { + _downloadLifetime = _session->downloaderTaskFinished( + ) | rpl::start_with_next([=] { + checkPendingEffects(); + }); + } +} + void EmojiInteractions::play( QString emoticon, not_null view, diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h index 4d38f38eb..b7af25997 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h @@ -43,6 +43,9 @@ public: void cancelPremiumEffect(not_null view); void visibleAreaUpdated(int visibleTop, int visibleBottom); + void playEffectOnRead(not_null view); + void playEffect(not_null view); + void paint(QPainter &p); [[nodiscard]] rpl::producer updateRequests() const; [[nodiscard]] rpl::producer playStarted() const; @@ -68,6 +71,16 @@ private: crl::time shouldHaveStartedAt = 0; bool incoming = false; }; + struct ResolvedEffect { + QString emoticon; + DocumentData *document = nullptr; + QByteArray content; + QString filepath; + + explicit operator bool() const { + return document && (!content.isEmpty() || !filepath.isEmpty()); + } + }; [[nodiscard]] QRect computeRect(const Play &play) const; @@ -85,6 +98,14 @@ private: bool incoming, bool premium); void checkDelayed(); + void addPendingEffect(not_null view); + + [[nodiscard]] ResolvedEffect resolveEffect( + not_null view); + void playEffect( + not_null view, + const ResolvedEffect &resolved); + void checkPendingEffects(); const not_null _session; const Fn)> _itemTop; @@ -97,6 +118,9 @@ private: rpl::event_stream _updateRequests; rpl::event_stream _playStarted; + std::vector> _pendingEffects; + rpl::lifetime _downloadLifetime; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index a320a67f7..59fb59534 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1857,6 +1857,12 @@ void ListWidget::elementCancelPremium(not_null view) { _emojiInteractions->cancelPremiumEffect(view); } +void ListWidget::elementStartEffect( + not_null view, + Element *replacing) { + _emojiInteractions->playEffect(view); +} + QString ListWidget::elementAuthorRank(not_null view) { return _delegate->listElementAuthorRank(view); } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index e5304bb44..b85bb7bed 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -419,6 +419,9 @@ public: not_null view, Element *replacing) override; void elementCancelPremium(not_null view) override; + void elementStartEffect( + not_null view, + Element *replacing) override; QString elementAuthorRank(not_null view) override; void setEmptyInfoWidget(base::unique_qptr &&w); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index ea17702ee..32cf1f5ad 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -3038,7 +3038,7 @@ TextState Message::bottomInfoTextState( const auto infoLeft = infoRight - size.width(); const auto infoTop = infoBottom - size.height(); return _bottomInfo.textState( - data(), + this, point - QPoint{ infoLeft, infoTop }); } From 92133e7f504f00eac200f4bc25eddb1503b9c627 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 7 May 2024 22:15:34 +0400 Subject: [PATCH 053/225] Show effect animation with correct geometry. --- .../SourceFiles/boxes/premium_preview_box.cpp | 2 +- .../chat_helpers/stickers_emoji_pack.cpp | 17 ++-- .../chat_helpers/stickers_emoji_pack.h | 22 ++++- .../data/data_message_reactions.cpp | 3 + .../history/view/history_view_bottom_info.cpp | 35 ++++---- .../history/view/history_view_bottom_info.h | 2 + .../history/view/history_view_element.cpp | 4 + .../history/view/history_view_element.h | 1 + .../view/history_view_emoji_interactions.cpp | 55 +++++++++---- .../view/history_view_emoji_interactions.h | 8 +- .../history/view/history_view_message.cpp | 82 +++++++++++++++++++ .../history/view/history_view_message.h | 1 + .../view/media/history_view_sticker.cpp | 5 ++ .../history/view/media/history_view_sticker.h | 1 + .../window/window_media_preview.cpp | 2 +- 15 files changed, 197 insertions(+), 43 deletions(-) diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 30f312745..76a2a18b7 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -286,7 +286,7 @@ void PreloadSticker(const std::shared_ptr &media) { document, media->videoThumbnailContent(), QString(), - true); + Stickers::EffectType::PremiumSticker); const auto update = [=] { if (!state->readyInvoked diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index f4cc13c4c..af07736a4 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -269,10 +269,10 @@ std::unique_ptr EmojiPack::effectPlayer( not_null document, QByteArray data, QString filepath, - bool premium) { + EffectType type) { // Shortened copy from stickers_lottie module. const auto baseKey = document->bigFileBaseCacheKey(); - const auto tag = uint8(0); + const auto tag = uint8(type); const auto keyShift = ((tag << 4) & 0xF0) | (uint8(ChatHelpers::StickerLottieSize::EmojiInteraction) & 0x0F); const auto key = Storage::Cache::Key{ @@ -292,19 +292,24 @@ std::unique_ptr EmojiPack::effectPlayer( std::move(data)); }); }; - const auto size = premium + const auto size = (type == EffectType::PremiumSticker) ? HistoryView::Sticker::PremiumEffectSize(document) - : HistoryView::Sticker::EmojiEffectSize(); + : (type == EffectType::EmojiInteraction) + ? HistoryView::Sticker::EmojiEffectSize() + : HistoryView::Sticker::MessageEffectSize(); const auto request = Lottie::FrameRequest{ size * style::DevicePixelRatio(), }; - auto &weakProvider = _sharedProviders[document]; + auto &weakProvider = _sharedProviders[{ document, type }]; auto shared = [&] { if (const auto result = weakProvider.lock()) { return result; } + const auto count = (type == EffectType::PremiumSticker) + ? kPremiumCachesCount + : kEmojiCachesCount; const auto result = Lottie::SinglePlayer::SharedProvider( - premium ? kPremiumCachesCount : kEmojiCachesCount, + count, get, put, Lottie::ReadContent(data, filepath), diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h index 0226b04ef..133880e1a 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h @@ -50,6 +50,12 @@ struct LargeEmojiImage { [[nodiscard]] static QSize Size(); }; +enum class EffectType : uint8 { + EmojiInteraction, + PremiumSticker, + MessageEffect, +}; + class EmojiPack final { public: using ViewElement = HistoryView::Element; @@ -95,11 +101,23 @@ public: not_null document, QByteArray data, QString filepath, - bool premium); + EffectType type); private: class ImageLoader; + struct ProviderKey { + not_null document; + Stickers::EffectType type = {}; + + friend inline auto operator<=>( + const ProviderKey &, + const ProviderKey &) = default; + friend inline bool operator==( + const ProviderKey &, + const ProviderKey &) = default; + }; + void refresh(); void refreshDelayed(); void refreshAnimations(); @@ -135,7 +153,7 @@ private: mtpRequestId _animationsRequestId = 0; base::flat_map< - not_null, + ProviderKey, std::weak_ptr> _sharedProviders; rpl::event_stream<> _refreshed; diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 8682ba655..a04cbcbfb 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -737,6 +737,9 @@ void Reactions::resolveEffectImages() { LOG(("API Error: Effect '%1' not found!" ).arg(ReactionIdToLog(id))); } + if (i != end(_effects)) { + preloadEffect(*i); + } } } diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index c7461f3fc..8af94c73b 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -173,20 +173,10 @@ ClickHandlerPtr BottomInfo::replayEffectLink( left += width() - available; top += st::msgDateFont->height; } - auto x = left; - auto y = top; - auto widthLeft = available; if (_effect) { - const auto add = st::reactionInfoBetween; - const auto width = st::reactionInfoSize; - if (x > left && widthLeft < width) { - x = left; - y += st::msgDateFont->height; - widthLeft = available; - } const auto image = QRect( - x, - y, + left, + top, st::reactionInfoSize, st::msgDateFont->height); if (image.contains(position)) { @@ -195,8 +185,6 @@ ClickHandlerPtr BottomInfo::replayEffectLink( } return _replayLink; } - x += width + add; - widthLeft -= width + add; } return nullptr; } @@ -544,6 +532,25 @@ void BottomInfo::continueEffectAnimation( } } +QRect BottomInfo::effectIconGeometry() const { + if (!_effect) { + return {}; + } + auto left = 0; + auto top = 0; + auto available = width(); + if (height() != minHeight()) { + available = std::min(available, _effectMaxWidth); + left += width() - available; + top += st::msgDateFont->height; + } + return QRect( + left + (st::reactionInfoSize - st::effectInfoImage) / 2, + top + (st::msgDateFont->height - st::effectInfoImage) / 2, + st::effectInfoImage, + st::effectInfoImage); +} + BottomInfo::Data BottomInfoDataFromMessage(not_null message) { using Flag = BottomInfo::Data::Flag; const auto item = message->data(); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index c585281be..594a488f1 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -82,6 +82,8 @@ public: void continueEffectAnimation( std::unique_ptr animation); + QRect effectIconGeometry() const; + private: struct Effect; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index ef5b0c830..82688196a 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1786,6 +1786,10 @@ auto Element::takeEffectAnimation() return nullptr; } +QRect Element::effectIconGeometry() const { + return QRect(); +} + Element::~Element() { // Delete media while owner still exists. clearSpecialOnlyEmoji(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 324640b6d..ab458b1a5 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -523,6 +523,7 @@ public: void previousInBlocksChanged(); void nextInBlocksRemoved(); + [[nodiscard]] virtual QRect effectIconGeometry() const; [[nodiscard]] virtual QRect innerGeometry() const = 0; void customEmojiRepaint(); diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index c9b299a72..efda8ead1 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -119,7 +119,7 @@ bool EmojiInteractions::playPremiumEffect( document->createMediaView()->videoThumbnailContent(), QString(), false, - true); + Stickers::EffectType::PremiumSticker); } } } @@ -147,7 +147,7 @@ void EmojiInteractions::play( media->bytes(), media->owner()->filepath(), incoming, - false); + Stickers::EffectType::EmojiInteraction); } void EmojiInteractions::playEffectOnRead(not_null view) { @@ -214,7 +214,7 @@ void EmojiInteractions::playEffect( resolved.content, resolved.filepath, false, - false); + Stickers::EffectType::MessageEffect); } void EmojiInteractions::addPendingEffect(not_null view) { @@ -272,7 +272,7 @@ void EmojiInteractions::play( QByteArray data, QString filepath, bool incoming, - bool premium) { + Stickers::EffectType type) { const auto top = _itemTop(view); const auto bottom = top + view->height(); if (_visibleTop >= bottom @@ -286,12 +286,14 @@ void EmojiInteractions::play( document, data, filepath, - premium); + type); - const auto inner = premium + const auto inner = (type == Stickers::EffectType::PremiumSticker) ? HistoryView::Sticker::Size(document) : HistoryView::Sticker::EmojiSize(); - const auto shift = premium ? QPoint() : GenerateRandomShift(inner); + const auto shift = (type == Stickers::EffectType::EmojiInteraction) + ? GenerateRandomShift(inner) + : QPoint(); const auto raw = lottie.get(); lottie->updates( ) | rpl::start_with_next([=](Lottie::Update update) { @@ -312,16 +314,18 @@ void EmojiInteractions::play( .lottie = std::move(lottie), .shift = shift, .inner = inner, - .outer = (premium + .outer = ((type == Stickers::EffectType::PremiumSticker) ? HistoryView::Sticker::PremiumEffectSize(document) - : HistoryView::Sticker::EmojiEffectSize()), - .premium = premium, + : (type == Stickers::EffectType::EmojiInteraction) + ? HistoryView::Sticker::EmojiEffectSize() + : HistoryView::Sticker::MessageEffectSize()), + .type = type, }); if (incoming) { _playStarted.fire(std::move(emoticon)); } if (const auto media = view->media()) { - if (!premium) { + if (type == Stickers::EffectType::EmojiInteraction) { media->stickerClearLoopPlayed(); } } @@ -336,9 +340,28 @@ void EmojiInteractions::visibleAreaUpdated( QRect EmojiInteractions::computeRect(const Play &play) const { const auto view = play.view; + const auto viewTop = _itemTop(view); + if (viewTop < 0) { + return QRect(); + } + if (play.type == Stickers::EffectType::MessageEffect) { + const auto icon = view->effectIconGeometry(); + if (icon.isEmpty()) { + return QRect(); + } + const auto size = play.outer; + const auto shift = view->hasRightLayout() + ? (-size.width() / 3) + : (size.width() / 3); + return QRect( + shift + icon.x() + (icon.width() - size.width()) / 2, + viewTop + icon.y() + (icon.height() - size.height()) / 2, + size.width(), + size.height()); + } const auto sticker = play.inner; const auto size = play.outer; - const auto shift = play.premium + const auto shift = (play.type == Stickers::EffectType::PremiumSticker) ? int(sticker.width() * kPremiumShift) : (size.width() / 40); const auto inner = view->innerGeometry(); @@ -346,11 +369,9 @@ QRect EmojiInteractions::computeRect(const Play &play) const { const auto left = rightAligned ? (inner.x() + inner.width() + shift - size.width()) : (inner.x() - shift); - const auto viewTop = _itemTop(view) + inner.y(); - if (viewTop < 0) { - return QRect(); - } - const auto top = viewTop + (sticker.height() - size.height()) / 2; + const auto top = viewTop + + inner.y() + + (sticker.height() - size.height()) / 2; return QRect(QPoint(left, top), size).translated(play.shift); } diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h index b7af25997..679b102a5 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h @@ -23,6 +23,10 @@ namespace Main { class Session; } // namespace Main +namespace Stickers { +enum class EffectType : uint8; +} // namespace Stickers + namespace HistoryView { class Element; @@ -60,7 +64,7 @@ private: int frame = 0; int framesCount = 0; int frameRate = 0; - bool premium = false; + Stickers::EffectType type = {}; bool started = false; bool finished = false; }; @@ -96,7 +100,7 @@ private: QByteArray data, QString filepath, bool incoming, - bool premium); + Stickers::EffectType type); void checkDelayed(); void addPendingEffect(not_null view); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 32cf1f5ad..cf19c8f9a 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -707,6 +707,88 @@ auto Message::takeEffectAnimation() return _bottomInfo.takeEffectAnimation(); } +QRect Message::effectIconGeometry() const { + const auto item = data(); + const auto media = this->media(); + + auto g = countGeometry(); + if (g.width() < 1 || isHidden()) { + return {}; + } + const auto bubble = drawBubble(); + const auto reactionsInBubble = _reactions && embedReactionsInBubble(); + const auto mediaDisplayed = media && media->isDisplayed(); + const auto keyboard = item->inlineReplyKeyboard(); + auto keyboardHeight = 0; + if (keyboard) { + keyboardHeight = keyboard->naturalHeight(); + g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight); + } + + const auto fromBottomInfo = [&](QPoint bottomRight) { + const auto size = _bottomInfo.currentSize(); + return _bottomInfo.effectIconGeometry().translated( + bottomRight - QPoint(size.width(), size.height())); + }; + if (bubble) { + auto entry = logEntryOriginal(); + + // Entry page is always a bubble bottom. + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); + + auto inner = g; + if (_comments) { + inner.setHeight(inner.height() - st::historyCommentsButtonHeight); + } + auto trect = inner.marginsRemoved(st::msgPadding); + const auto reactionsTop = (reactionsInBubble && !_viewButton) + ? st::mediaInBubbleSkip + : 0; + const auto reactionsHeight = reactionsInBubble + ? (reactionsTop + _reactions->height()) + : 0; + if (_viewButton) { + const auto belowInfo = _viewButton->belowMessageInfo(); + const auto infoHeight = reactionsInBubble + ? (reactionsHeight + 2 * st::mediaInBubbleSkip) + : _bottomInfo.height(); + const auto heightMargins = QMargins(0, 0, 0, infoHeight); + if (belowInfo) { + inner -= heightMargins; + } + trect.setHeight(trect.height() - _viewButton->height()); + if (reactionsInBubble) { + trect.setHeight(trect.height() - st::mediaInBubbleSkip + st::msgPadding.bottom()); + } else if (mediaDisplayed) { + trect.setHeight(trect.height() - st::mediaInBubbleSkip); + } + } + if (mediaOnBottom) { + trect.setHeight(trect.height() + + st::msgPadding.bottom() + - viewButtonHeight()); + } + if (mediaOnTop) { + trect.setY(trect.y() - st::msgPadding.top()); + } + if (mediaDisplayed && mediaOnBottom && media->customInfoLayout()) { + auto mediaHeight = media->height(); + auto mediaLeft = trect.x() - st::msgPadding.left(); + auto mediaTop = (trect.y() + trect.height() - mediaHeight); + return fromBottomInfo(QPoint(mediaLeft, mediaTop) + media->resolveCustomInfoRightBottom()); + } else { + return fromBottomInfo({ + inner.left() + inner.width() - (st::msgPadding.right() - st::msgDateDelta.x()), + inner.top() + inner.height() - (st::msgPadding.bottom() - st::msgDateDelta.y()), + }); + } + } else if (mediaDisplayed) { + return fromBottomInfo(g.topLeft() + media->resolveCustomInfoRightBottom()); + } + return {}; +} + QSize Message::performCountOptimalSize() { const auto item = data(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 77c7c5fad..a8c172c03 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -162,6 +162,7 @@ public: auto takeEffectAnimation() -> std::unique_ptr override; + QRect effectIconGeometry() const override; QRect innerGeometry() const override; [[nodiscard]] BottomRippleMask bottomRippleMask(int buttonHeight) const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index c8527d999..69e8bc0af 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -41,6 +41,7 @@ constexpr auto kMaxSizeFixed = 512; constexpr auto kMaxEmojiSizeFixed = 256; constexpr auto kPremiumMultiplier = (1 + 0.245 * 2); constexpr auto kEmojiMultiplier = 3; +constexpr auto kMessageEffectMultiplier = 2; [[nodiscard]] QImage CacheDiceImage( const QString &emoji, @@ -208,6 +209,10 @@ QSize Sticker::EmojiEffectSize() { return EmojiSize() * kEmojiMultiplier; } +QSize Sticker::MessageEffectSize() { + return EmojiSize() * kMessageEffectMultiplier; +} + QSize Sticker::EmojiSize() { const auto side = std::min(st::maxAnimatedEmojiSize, kMaxEmojiSizeFixed); return { side, side }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index c6bbbd0da..91a69f7e2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -91,6 +91,7 @@ public: not_null document); [[nodiscard]] static QSize UsualPremiumEffectSize(); [[nodiscard]] static QSize EmojiEffectSize(); + [[nodiscard]] static QSize MessageEffectSize(); [[nodiscard]] static QSize EmojiSize(); [[nodiscard]] static ClickHandlerPtr ShowSetHandler( not_null document); diff --git a/Telegram/SourceFiles/window/window_media_preview.cpp b/Telegram/SourceFiles/window/window_media_preview.cpp index 3aa79501d..87595711c 100644 --- a/Telegram/SourceFiles/window/window_media_preview.cpp +++ b/Telegram/SourceFiles/window/window_media_preview.cpp @@ -339,7 +339,7 @@ void MediaPreviewWidget::setupLottie() { _document, _documentMedia->videoThumbnailContent(), QString(), - true); + Stickers::EffectType::PremiumSticker); } else { const auto size = currentDimensions(); _lottie = std::make_unique( From 396ba9a984d6aeb5a67d5b88741a3fd0abb15c2b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 May 2024 21:01:01 +0400 Subject: [PATCH 054/225] Initial code of attaching effect selector. --- Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/boxes/create_poll_box.cpp | 2 + Telegram/SourceFiles/boxes/send_files_box.cpp | 3 +- Telegram/SourceFiles/boxes/share_box.cpp | 1 + .../history/history_inner_widget.cpp | 6 +- .../SourceFiles/history/history_widget.cpp | 7 +- .../history_view_compose_controls.cpp | 1 + .../view/history_view_context_menu.cpp | 1 + .../history/view/history_view_list_widget.cpp | 6 +- .../view/history_view_schedule_box.cpp | 2 + .../history/view/history_view_schedule_box.h | 7 ++ .../view/history_view_scheduled_section.cpp | 22 ++++-- .../view/history_view_scheduled_section.h | 5 ++ .../reactions/history_view_reactions_button.h | 2 +- .../history_view_reactions_selector.cpp | 18 +++-- .../history_view_reactions_selector.h | 6 +- .../history_view_reactions_strip.cpp | 11 ++- .../reactions/history_view_reactions_strip.h | 6 +- Telegram/SourceFiles/main/main_session.cpp | 2 + Telegram/SourceFiles/main/main_session.h | 13 +++- .../stories/media_stories_controller.cpp | 5 -- .../media/stories/media_stories_controller.h | 3 - .../media/stories/media_stories_delegate.h | 6 -- .../media/stories/media_stories_reactions.cpp | 4 +- .../media/view/media_view_overlay_widget.cpp | 6 -- .../media/view/media_view_overlay_widget.h | 8 --- Telegram/SourceFiles/menu/menu_send.cpp | 71 +++++++++++++++++-- Telegram/SourceFiles/menu/menu_send.h | 7 +- .../window/window_session_controller.cpp | 4 +- .../window/window_session_controller.h | 8 --- 30 files changed, 163 insertions(+), 83 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 87df781f8..3029db104 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -561,6 +561,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reaction_invoice" = "{reaction} to your invoice"; "lng_reaction_gif" = "{reaction} to your GIF"; +"lng_effect_add_title" = "Add an animated effect"; +"lng_effect_stickers_title" = "Message Effects"; + "lng_languages" = "Languages"; "lng_languages_none" = "No languages found."; "lng_languages_count#one" = "{count} language"; diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 1d02b5e52..c7de2a065 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -1295,6 +1295,7 @@ object_ptr CreatePollBox::setupContent() { _controller->show( HistoryView::PrepareScheduleBox( this, + _controller->uiShow(), SendMenu::Type::Scheduled, send)); }; @@ -1327,6 +1328,7 @@ object_ptr CreatePollBox::setupContent() { }; SendMenu::SetupMenuAndShortcuts( submit.data(), + _controller->uiShow(), sendMenuType, sendSilent, sendScheduled, diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index ea208d42f..8e1280f89 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -529,6 +529,7 @@ void SendFilesBox::refreshButtons() { if (_sendType == Api::SendType::Normal) { SendMenu::SetupMenuAndShortcuts( _send, + _show, [=] { return _sendMenuType; }, [=] { sendSilent(); }, [=] { sendScheduled(); }, @@ -1472,7 +1473,7 @@ void SendFilesBox::sendScheduled() { ? SendMenu::Type::ScheduledToUser : _sendMenuType; const auto callback = [=](Api::SendOptions options) { send(options); }; - auto box = HistoryView::PrepareScheduleBox(this, type, callback); + auto box = HistoryView::PrepareScheduleBox(this, _show, type, callback); const auto weak = Ui::MakeWeak(box.data()); _show->showBox(std::move(box)); if (const auto strong = weak.data()) { diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 32ec893d6..2d9ba4d3a 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -616,6 +616,7 @@ void ShareBox::submitScheduled() { uiShow()->showBox( HistoryView::PrepareScheduleBox( this, + nullptr, // ChatHelpers::Show for effect attachment. sendMenuType(), callback, HistoryView::DefaultScheduleTime(), diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index aed2480cc..3b51df0d7 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -351,8 +351,7 @@ HistoryInner::HistoryInner( , _reactionsManager( std::make_unique( this, - [=](QRect updated) { update(updated); }, - controller->cachedReactionIconFactory().createMethod())) + [=](QRect updated) { update(updated); })) , _touchSelectTimer([=] { onTouchSelect(); }) , _touchScrollTimer([=] { onTouchScrollTimer(); }) , _scrollDateCheck([this] { scrollDateCheck(); }) @@ -2778,8 +2777,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { desiredPosition, reactItem, [=](ChosenReaction reaction) { reactionChosen(reaction); }, - ItemReactionsAbout(reactItem), - _controller->cachedReactionIconFactory().createMethod()) + ItemReactionsAbout(reactItem)) : AttachSelectorResult::Skipped; if (attached == AttachSelectorResult::Failed) { _menu = nullptr; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 0f919d652..c89d3b0c7 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -323,6 +323,7 @@ HistoryWidget::HistoryWidget( SendMenu::SetupMenuAndShortcuts( _send.get(), + controller->uiShow(), [=] { return sendButtonMenuType(); }, [=] { sendSilent(); }, [=] { sendScheduled(); }, @@ -4192,7 +4193,11 @@ void HistoryWidget::sendScheduled() { } const auto callback = [=](Api::SendOptions options) { send(options); }; controller()->show( - HistoryView::PrepareScheduleBox(_list, sendMenuType(), callback)); + HistoryView::PrepareScheduleBox( + _list, + controller()->uiShow(), + sendMenuType(), + callback)); } void HistoryWidget::sendWhenOnline() { 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 9154d846e..7dcf2373b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -2197,6 +2197,7 @@ void ComposeControls::initSendButton() { SendMenu::SetupMenuAndShortcuts( _send.get(), + _show, [=] { return sendButtonMenuType(); }, SendMenu::DefaultSilentCallback(send), SendMenu::DefaultScheduleCallback(_show, sendMenuType(), send), diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 88d9ad183..12e0b06c6 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -589,6 +589,7 @@ bool AddRescheduleAction( const auto box = request.navigation->parentController()->show( HistoryView::PrepareScheduleBox( &request.navigation->session(), + request.navigation->uiShow(), sendMenuType, callback, date)); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 59fb59534..c8eacd8e3 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -115,8 +115,7 @@ auto WindowListDelegate::listMakeReactionsManager( -> std::unique_ptr { return std::make_unique( wheelEventsTarget, - std::move(update), - _window->cachedReactionIconFactory().createMethod()); + std::move(update)); } void WindowListDelegate::listVisibleAreaUpdated() { @@ -2780,8 +2779,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { desiredPosition, reactItem, [=](ChosenReaction reaction) { reactionChosen(reaction); }, - ItemReactionsAbout(reactItem), - controller()->cachedReactionIconFactory().createMethod()) + ItemReactionsAbout(reactItem)) : AttachSelectorResult::Skipped; if (attached == AttachSelectorResult::Failed) { _menu = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index 136a3f73c..83891717c 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -69,6 +69,7 @@ bool CanScheduleUntilOnline(not_null peer) { void ScheduleBox( not_null box, + std::shared_ptr show, SendMenu::Type type, Fn done, TimeId time, @@ -98,6 +99,7 @@ void ScheduleBox( using T = SendMenu::Type; SendMenu::SetupMenuAndShortcuts( descriptor.submit.data(), + show, [t = type == T::Disabled ? T::Disabled : T::SilentOnly] { return t; }, [=] { save(true, descriptor.collect()); }, nullptr, diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.h b/Telegram/SourceFiles/history/view/history_view_schedule_box.h index 5d72ed05f..72c2b4b89 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.h +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.h @@ -18,6 +18,10 @@ namespace Api { struct SendOptions; } // namespace Api +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace SendMenu { enum class Type; } // namespace SendMenu @@ -36,6 +40,7 @@ struct ScheduleBoxStyleArgs { void ScheduleBox( not_null box, + std::shared_ptr show, SendMenu::Type type, Fn done, TimeId time, @@ -44,12 +49,14 @@ void ScheduleBox( template [[nodiscard]] object_ptr PrepareScheduleBox( Guard &&guard, + std::shared_ptr show, SendMenu::Type type, Submit &&submit, TimeId scheduleTime = DefaultScheduleTime(), ScheduleBoxStyleArgs style = ScheduleBoxStyleArgs()) { return Box( ScheduleBox, + std::move(show), type, crl::guard(std::forward(guard), std::forward(submit)), scheduleTime, diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 3fe205b3c..e0522acb0 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -98,6 +98,7 @@ ScheduledWidget::ScheduledWidget( const Data::ForumTopic *forumTopic) : Window::SectionWidget(parent, controller, history->peer) , WindowListDelegate(controller) +, _show(controller->uiShow()) , _history(history) , _forumTopic(forumTopic) , _scroll( @@ -600,7 +601,8 @@ void ScheduledWidget::uploadFile( type, prepareSendAction(options)); }; - controller()->show(PrepareScheduleBox(this, sendMenuType(), callback)); + controller()->show( + PrepareScheduleBox(this, _show, sendMenuType(), callback)); } bool ScheduledWidget::showSendingFilesError( @@ -678,7 +680,8 @@ void ScheduledWidget::send() { return; } const auto callback = [=](Api::SendOptions options) { send(options); }; - controller()->show(PrepareScheduleBox(this, sendMenuType(), callback)); + controller()->show( + PrepareScheduleBox(this, _show, sendMenuType(), callback)); } void ScheduledWidget::send(Api::SendOptions options) { @@ -709,7 +712,8 @@ void ScheduledWidget::sendVoice( const auto callback = [=](Api::SendOptions options) { sendVoice(bytes, waveform, duration, options); }; - controller()->show(PrepareScheduleBox(this, sendMenuType(), callback)); + controller()->show( + PrepareScheduleBox(this, _show, sendMenuType(), callback)); } void ScheduledWidget::sendVoice( @@ -809,7 +813,8 @@ void ScheduledWidget::sendExistingDocument( const auto callback = [=](Api::SendOptions options) { sendExistingDocument(document, options); }; - controller()->show(PrepareScheduleBox(this, sendMenuType(), callback)); + controller()->show( + PrepareScheduleBox(this, _show, sendMenuType(), callback)); } bool ScheduledWidget::sendExistingDocument( @@ -838,7 +843,8 @@ void ScheduledWidget::sendExistingPhoto(not_null photo) { const auto callback = [=](Api::SendOptions options) { sendExistingPhoto(photo, options); }; - controller()->show(PrepareScheduleBox(this, sendMenuType(), callback)); + controller()->show( + PrepareScheduleBox(this, _show, sendMenuType(), callback)); } bool ScheduledWidget::sendExistingPhoto( @@ -872,7 +878,8 @@ void ScheduledWidget::sendInlineResult( const auto callback = [=](Api::SendOptions options) { sendInlineResult(result, bot, options); }; - controller()->show(PrepareScheduleBox(this, sendMenuType(), callback)); + controller()->show( + PrepareScheduleBox(this, _show, sendMenuType(), callback)); } void ScheduledWidget::sendInlineResult( @@ -1360,7 +1367,8 @@ void ScheduledWidget::listSendBotCommand( message.textWithTags = { text }; session().api().sendMessage(std::move(message)); }; - controller()->show(PrepareScheduleBox(this, sendMenuType(), callback)); + controller()->show( + PrepareScheduleBox(this, _show, sendMenuType(), callback)); } void ScheduledWidget::listSearch( diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index 9f181d231..cb5ead98f 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -17,6 +17,10 @@ class History; enum class SendMediaType; struct SendingAlbum; +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace SendMenu { enum class Type; } // namespace SendMenu @@ -262,6 +266,7 @@ private: not_null bot, Api::SendOptions options); + const std::shared_ptr _show; const not_null _history; const Data::ForumTopic *_forumTopic; std::shared_ptr _theme; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h index 495f33847..4a1842d99 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h @@ -138,7 +138,7 @@ public: Manager( QWidget *wheelEventsTarget, Fn buttonUpdate, - IconFactory iconFactory); + IconFactory iconFactory = nullptr); ~Manager(); using ReactionId = ::Data::ReactionId; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index cce557ffc..61c46a157 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -200,8 +200,8 @@ Selector::Selector( std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, TextWithEntities about, - IconFactory iconFactory, Fn close, + IconFactory iconFactory, bool child) : Selector( parent, @@ -261,14 +261,12 @@ Selector::Selector( QSize(2 * st::reactStripSkip + st::reactStripSize, st::reactStripHeight), st::reactionCornerShadow, st::reactStripHeight) -, _strip(iconFactory - ? std::make_unique( - _st, - QRect(0, 0, st::reactStripSize, st::reactStripSize), - st::reactStripImage, - crl::guard(this, [=] { update(_inner); }), - std::move(iconFactory)) - : nullptr) +, _strip(std::make_unique( + _st, + QRect(0, 0, st::reactStripSize, st::reactStripSize), + st::reactStripImage, + crl::guard(this, [=] { update(_inner); }), + std::move(iconFactory))) , _about(about.empty() ? nullptr : std::make_unique( @@ -1221,8 +1219,8 @@ auto AttachSelectorToMenu( std::move(show), std::move(reactions), std::move(about), - std::move(iconFactory), [=](bool fast) { menu->hideMenu(fast); }, + std::move(iconFactory), false); // child if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) { return base::make_unexpected(AttachSelectorResult::Failed); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 1d136c15b..cb9b8a68f 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -82,8 +82,8 @@ public: std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, TextWithEntities about, - IconFactory iconFactory, Fn close, + IconFactory iconFactory = nullptr, bool child = false); Selector( not_null parent, @@ -253,7 +253,7 @@ AttachSelectorResult AttachSelectorToMenu( not_null item, Fn chosen, TextWithEntities about, - IconFactory iconFactory); + IconFactory iconFactory = nullptr); [[nodiscard]] auto AttachSelectorToMenu( not_null menu, @@ -262,7 +262,7 @@ AttachSelectorResult AttachSelectorToMenu( std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, TextWithEntities about, - IconFactory iconFactory + IconFactory iconFactory = nullptr ) -> base::expected, AttachSelectorResult>; [[nodiscard]] TextWithEntities ItemReactionsAbout( diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.cpp index 7f36f6630..08524c180 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.cpp @@ -51,7 +51,9 @@ Strip::Strip( Fn update, IconFactory iconFactory) : _st(st) -, _iconFactory(std::move(iconFactory)) +, _iconFactory(iconFactory + ? std::move(iconFactory) + : DefaultCachingIconFactory) , _inner(inner) , _finalSize(size) , _update(std::move(update)) { @@ -558,4 +560,11 @@ std::shared_ptr DefaultIconFactory( return CreateIcon(media, size); } +std::shared_ptr DefaultCachingIconFactory( + not_null media, + int size) { + auto &factory = media->owner()->session().cachedReactionIconFactory(); + return factory.createMethod()(media, size); +} + } // namespace HistoryView::Reactions diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.h index f11ea7253..7ae2d0dd2 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_strip.h @@ -53,7 +53,7 @@ public: QRect inner, int size, Fn update, - IconFactory iconFactory); + IconFactory iconFactory = nullptr); enum class AddedButton : uchar { None, @@ -173,4 +173,8 @@ private: not_null media, int size); +[[nodiscard]] std::shared_ptr DefaultCachingIconFactory( + not_null media, + int size); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 1449d9497..aa70700ec 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers_emoji_pack.h" #include "chat_helpers/stickers_dice_pack.h" #include "chat_helpers/stickers_gift_box_pack.h" +#include "history/view/reactions/history_view_reactions_strip.h" #include "history/history.h" #include "history/history_item.h" #include "inline_bots/bot_attach_web_view.h" @@ -104,6 +105,7 @@ Session::Session( , _scheduledMessages(std::make_unique(this)) , _sponsoredMessages(std::make_unique(this)) , _topPeers(std::make_unique(this)) +, _cachedReactionIconFactory(std::make_unique()) , _supportHelper(Support::Helper::Create(this)) , _saveSettingsTimer([=] { saveSettings(); }) { Expects(_settings != nullptr); diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index daab53632..8d1b276ec 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -37,6 +37,10 @@ class SponsoredMessages; class TopPeers; } // namespace Data +namespace HistoryView::Reactions { +class CachedIconFactory; +} // namespace HistoryView::Reactions + namespace Storage { class DownloadManagerMtproto; class Uploader; @@ -156,6 +160,10 @@ public: [[nodiscard]] InlineBots::AttachWebView &attachWebView() const { return *_attachWebView; } + [[nodiscard]] auto cachedReactionIconFactory() const + -> HistoryView::Reactions::CachedIconFactory & { + return *_cachedReactionIconFactory; + } void saveSettings(); void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay); @@ -217,8 +225,6 @@ public: private: static constexpr auto kDefaultSaveDelay = crl::time(1000); - void parseColorIndices(const MTPDhelp_peerColors &data); - const UserId _userId; const not_null _account; @@ -246,6 +252,9 @@ private: const std::unique_ptr _sponsoredMessages; const std::unique_ptr _topPeers; + using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory; + const std::unique_ptr _cachedReactionIconFactory; + const std::unique_ptr _supportHelper; std::shared_ptr _selfUserpicView; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 39b3f45ff..04c06e197 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -679,11 +679,6 @@ auto Controller::stickerOrEmojiChosen() const return _delegate->storiesStickerOrEmojiChosen(); } -auto Controller::cachedReactionIconFactory() const --> HistoryView::Reactions::CachedIconFactory & { - return _delegate->storiesCachedReactionIconFactory(); -} - void Controller::rebuildFromContext( not_null peer, FullStoryId storyId) { diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index b3d796882..776667f73 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -30,7 +30,6 @@ class DocumentMedia; } // namespace Data namespace HistoryView::Reactions { -class CachedIconFactory; struct ChosenReaction; enum class AttachSelectorResult; } // namespace HistoryView::Reactions @@ -137,8 +136,6 @@ public: [[nodiscard]] std::shared_ptr uiShow() const; [[nodiscard]] auto stickerOrEmojiChosen() const -> rpl::producer; - [[nodiscard]] auto cachedReactionIconFactory() const - -> HistoryView::Reactions::CachedIconFactory &; void show(not_null story, Data::StoriesContext context); void jumpTo(not_null story, Data::StoriesContext context); diff --git a/Telegram/SourceFiles/media/stories/media_stories_delegate.h b/Telegram/SourceFiles/media/stories/media_stories_delegate.h index d1a06a52e..c453177cb 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_delegate.h +++ b/Telegram/SourceFiles/media/stories/media_stories_delegate.h @@ -17,10 +17,6 @@ class Story; struct StoriesContext; } // namespace Data -namespace HistoryView::Reactions { -class CachedIconFactory; -} // namespace HistoryView::Reactions - namespace Main { class Session; } // namespace Main @@ -48,8 +44,6 @@ public: -> std::shared_ptr = 0; [[nodiscard]] virtual auto storiesStickerOrEmojiChosen() -> rpl::producer = 0; - [[nodiscard]] virtual auto storiesCachedReactionIconFactory() - -> HistoryView::Reactions::CachedIconFactory & = 0; virtual void storiesRedisplay(not_null story) = 0; virtual void storiesJumpTo( not_null session, diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index da07a1516..724533e58 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -667,7 +667,6 @@ void Reactions::Panel::create() { TextWithEntities{ (mode == Mode::Message ? tr::lng_stories_reaction_as_message(tr::now) : QString()) }, - _controller->cachedReactionIconFactory().createMethod(), [=](bool fast) { hide(mode); }); _selector->chosen( @@ -867,8 +866,7 @@ auto Reactions::attachToMenu( st::storiesReactionsPan, show, LookupPossibleReactions(&show->session()), - TextWithEntities(), - _controller->cachedReactionIconFactory().createMethod()); + TextWithEntities()); if (!result) { return result.error(); } diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index cfa414afe..94dede7e9 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -415,7 +415,6 @@ OverlayWidget::OverlayWidget() , _widget(_surface->rpWidget()) , _fullscreen(Core::App().settings().mediaViewPosition().maximized == 2) , _windowed(Core::App().settings().mediaViewPosition().maximized == 0) -, _cachedReactionIconFactory(std::make_unique()) , _layerBg(std::make_unique(_body)) , _docDownload(_body, tr::lng_media_download(tr::now), st::mediaviewFileLink) , _docSaveAs(_body, tr::lng_mediaview_save_as(tr::now), st::mediaviewFileLink) @@ -4295,11 +4294,6 @@ auto OverlayWidget::storiesStickerOrEmojiChosen() return _storiesStickerOrEmojiChosen.events(); } -auto OverlayWidget::storiesCachedReactionIconFactory() --> HistoryView::Reactions::CachedIconFactory & { - return *_cachedReactionIconFactory; -} - void OverlayWidget::storiesJumpTo( not_null session, FullStoryId id, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 94a7e7b2f..02bdca5fb 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -55,10 +55,6 @@ namespace Window::Theme { struct Preview; } // namespace Window::Theme -namespace HistoryView::Reactions { -class CachedIconFactory; -} // namespace HistoryView::Reactions - namespace Media::Player { struct TrackState; } // namespace Media::Player @@ -251,8 +247,6 @@ private: std::shared_ptr storiesShow() override; auto storiesStickerOrEmojiChosen() -> rpl::producer override; - auto storiesCachedReactionIconFactory() - -> HistoryView::Reactions::CachedIconFactory & override; void storiesRedisplay(not_null story) override; void storiesJumpTo( not_null session, @@ -629,8 +623,6 @@ private: bool _showAsPip = false; std::unique_ptr _stories; - using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory; - std::unique_ptr _cachedReactionIconFactory; std::shared_ptr _cachedShow; rpl::event_stream<> _storiesChanged; Main::Session *_storiesSession = nullptr; diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index a31fcf103..1a1b09757 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -10,13 +10,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_common.h" #include "base/event_filter.h" #include "boxes/abstract_box.h" +#include "chat_helpers/compose/compose_show.h" #include "core/shortcuts.h" +#include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/history_view_schedule_box.h" #include "lang/lang_keys.h" #include "ui/widgets/popup_menu.h" #include "data/data_peer.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" +#include "data/data_message_reactions.h" #include "data/data_session.h" #include "main/main_session.h" #include "history/history.h" @@ -28,19 +31,51 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace SendMenu { +namespace { + +[[nodiscard]] Data::PossibleItemReactionsRef LookupPossibleEffects( + not_null session) { + auto result = Data::PossibleItemReactionsRef(); + const auto reactions = &session->data().reactions(); + const auto &full = reactions->list(Data::Reactions::Type::Active); + const auto &top = reactions->list(Data::Reactions::Type::Top); + const auto &recent = reactions->list(Data::Reactions::Type::Recent); + const auto premiumPossible = session->premiumPossible(); + auto added = base::flat_set(); + result.recent.reserve(full.size()); + for (const auto &reaction : ranges::views::concat(top, recent, full)) { + if (premiumPossible || !reaction.id.custom()) { + if (added.emplace(reaction.id).second) { + result.recent.push_back(&reaction); + } + } + } + result.customAllowed = premiumPossible; + const auto i = ranges::find( + result.recent, + reactions->favoriteId(), + &Data::Reaction::id); + if (i != end(result.recent) && i != begin(result.recent)) { + std::rotate(begin(result.recent), i, i + 1); + } + return result; +} + +} // namespace Fn DefaultSilentCallback(Fn send) { return [=] { send({ .silent = true }); }; } Fn DefaultScheduleCallback( - std::shared_ptr show, + std::shared_ptr show, Type type, Fn send) { return [=, weak = Ui::MakeWeak(show->toastParent())] { show->showBox( HistoryView::PrepareScheduleBox( weak, + show, type, [=](Api::SendOptions options) { send(options); }), Ui::LayerOption::KeepOther); @@ -95,6 +130,7 @@ FillMenuResult FillSendMenu( void SetupMenuAndShortcuts( not_null button, + std::shared_ptr show, Fn type, Fn silent, Fn schedule, @@ -107,12 +143,35 @@ void SetupMenuAndShortcuts( *menu = base::make_unique_q( button, st::popupMenuWithIcons); - const auto result = FillSendMenu(*menu, type(), silent, schedule, whenOnline); - const auto success = (result == FillMenuResult::Success); - if (success) { - (*menu)->popup(QCursor::pos()); + const auto result = FillSendMenu( + *menu, + type(), + silent, + schedule, + whenOnline); + if (result != FillMenuResult::Success) { + return false; } - return success; + const auto desiredPosition = QCursor::pos(); + using namespace HistoryView::Reactions; + const auto selector = show + ? AttachSelectorToMenu( + menu->get(), + desiredPosition, + st::reactPanelEmojiPan, + show, + LookupPossibleEffects(&show->session()), + { tr::lng_effect_add_title(tr::now) }) + : base::make_unexpected(AttachSelectorResult::Skipped); + if (selector) { + //(*selector)->chosen(); + (*menu)->popupPrepared(); + } else if (selector.error() == AttachSelectorResult::Failed) { + return false; + } else { + (*menu)->popup(desiredPosition); + } + return true; }; base::install_event_filter(button, [=](not_null e) { if (e->type() == QEvent::ContextMenu && showMenu()) { diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index 03b7f7ec5..250ac1e77 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -15,6 +15,10 @@ namespace Api { struct SendOptions; } // namespace Api +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Ui { class PopupMenu; class RpWidget; @@ -42,7 +46,7 @@ enum class FillMenuResult { Fn DefaultSilentCallback(Fn send); Fn DefaultScheduleCallback( - std::shared_ptr show, + std::shared_ptr show, Type type, Fn send); Fn DefaultWhenOnlineCallback(Fn send); @@ -57,6 +61,7 @@ FillMenuResult FillSendMenu( void SetupMenuAndShortcuts( not_null button, + std::shared_ptr show, Fn type, Fn silent, Fn schedule, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 0162d7169..d611652f7 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/reactions/history_view_reactions.h" -#include "history/view/reactions/history_view_reactions_button.h" +//#include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_replies_section.h" #include "history/view/history_view_scheduled_section.h" #include "media/player/media_player_instance.h" @@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" // Ui::FormatPhone. #include "ui/delayed_activation.h" #include "ui/boxes/boost_box.h" +#include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/effects/message_sending_animation_controller.h" #include "ui/style/style_palette_colorizer.h" @@ -1190,7 +1191,6 @@ SessionController::SessionController( , _activeChatsFilter(session->data().chatsFilters().defaultId()) , _defaultChatTheme(std::make_shared()) , _chatStyle(std::make_unique(session->colorIndicesValue())) -, _cachedReactionIconFactory(std::make_unique()) , _giftPremiumValidator(this) { init(); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 9888dc1c4..38085b945 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -593,11 +593,6 @@ public: return _chatStyle.get(); } - [[nodiscard]] auto cachedReactionIconFactory() const - -> HistoryView::Reactions::CachedIconFactory & { - return *_cachedReactionIconFactory; - } - [[nodiscard]] QString authedName() const { return _authedName; } @@ -713,9 +708,6 @@ private: std::deque> _lastUsedCustomChatThemes; rpl::variable _peerThemeOverride; - using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory; - std::unique_ptr _cachedReactionIconFactory; - base::has_weak_ptr _storyOpenGuard; GiftPremiumValidator _giftPremiumValidator; From d1106e5ae66a30baf21004755ea123f0d5b01d39 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 May 2024 14:10:53 +0400 Subject: [PATCH 055/225] Check effects availability in all SendMenu-s. --- .../SourceFiles/boxes/create_poll_box.cpp | 43 ++--- Telegram/SourceFiles/boxes/create_poll_box.h | 6 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 53 +++--- Telegram/SourceFiles/boxes/send_files_box.h | 12 +- Telegram/SourceFiles/boxes/share_box.cpp | 43 +++-- Telegram/SourceFiles/boxes/share_box.h | 6 +- .../SourceFiles/boxes/sticker_set_box.cpp | 15 +- Telegram/SourceFiles/boxes/sticker_set_box.h | 4 - .../chat_helpers/compose/compose_show.h | 4 +- .../chat_helpers/emoji_list_widget.cpp | 2 +- .../chat_helpers/emoji_list_widget.h | 2 +- .../chat_helpers/field_autocomplete.cpp | 33 ++-- .../chat_helpers/field_autocomplete.h | 4 +- .../chat_helpers/gifs_list_widget.cpp | 14 +- .../chat_helpers/gifs_list_widget.h | 4 +- .../chat_helpers/stickers_list_widget.cpp | 13 +- .../chat_helpers/stickers_list_widget.h | 2 +- .../chat_helpers/tabbed_selector.cpp | 4 +- .../chat_helpers/tabbed_selector.h | 6 +- .../data/data_message_reactions.cpp | 5 +- .../SourceFiles/history/history_widget.cpp | 61 ++++--- Telegram/SourceFiles/history/history_widget.h | 8 +- .../history_view_compose_controls.cpp | 30 ++-- .../controls/history_view_compose_controls.h | 10 +- .../view/history_view_context_menu.cpp | 2 +- .../view/history_view_replies_section.cpp | 15 +- .../view/history_view_replies_section.h | 4 +- .../view/history_view_schedule_box.cpp | 28 ++- .../history/view/history_view_schedule_box.h | 8 +- .../view/history_view_scheduled_section.cpp | 24 +-- .../view/history_view_scheduled_section.h | 4 +- .../history_view_reactions_selector.cpp | 23 ++- .../history_view_reactions_selector.h | 2 +- .../info_profile_emoji_status_panel.cpp | 2 +- .../inline_bots/bot_attach_web_view.cpp | 2 +- .../inline_bots/inline_results_inner.cpp | 22 +-- .../inline_bots/inline_results_inner.h | 6 +- .../inline_bots/inline_results_widget.cpp | 4 +- .../inline_bots/inline_results_widget.h | 4 +- Telegram/SourceFiles/mainwidget.cpp | 4 +- Telegram/SourceFiles/mainwidget.h | 4 +- .../media/stories/media_stories_reply.cpp | 13 +- .../media/stories/media_stories_reply.h | 6 + .../media/view/media_view_overlay_widget.cpp | 4 +- Telegram/SourceFiles/menu/menu_send.cpp | 166 ++++++++---------- Telegram/SourceFiles/menu/menu_send.h | 38 ++-- .../business/settings_shortcut_messages.cpp | 11 +- .../SourceFiles/window/window_peer_menu.cpp | 21 +-- .../SourceFiles/window/window_peer_menu.h | 2 +- .../window/window_session_controller.cpp | 8 +- 50 files changed, 394 insertions(+), 417 deletions(-) diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index c7de2a065..8b032b81a 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -910,12 +910,12 @@ CreatePollBox::CreatePollBox( PollData::Flags chosen, PollData::Flags disabled, Api::SendType sendType, - SendMenu::Type sendMenuType) + SendMenu::Details sendMenuDetails) : _controller(controller) , _chosen(chosen) , _disabled(disabled) , _sendType(sendType) -, _sendMenuType(sendMenuType) { +, _sendMenuDetails([result = sendMenuDetails] { return result; }) { } rpl::producer CreatePollBox::submitRequests() const { @@ -1288,20 +1288,9 @@ object_ptr CreatePollBox::setupContent() { _submitRequests.fire({ collectResult(), sendOptions }); } }; - const auto sendSilent = [=] { - send({ .silent = true }); - }; - const auto sendScheduled = [=] { - _controller->show( - HistoryView::PrepareScheduleBox( - this, - _controller->uiShow(), - SendMenu::Type::Scheduled, - send)); - }; - const auto sendWhenOnline = [=] { - send(Api::DefaultSendWhenOnlineOptions()); - }; + const auto sendAction = SendMenu::DefaultCallback( + _controller->uiShow(), + crl::guard(this, send)); options->scrollToWidget( ) | rpl::start_with_next([=](not_null widget) { @@ -1314,25 +1303,23 @@ object_ptr CreatePollBox::setupContent() { }, lifetime()); const auto isNormal = (_sendType == Api::SendType::Normal); - + const auto schedule = [=] { + sendAction(SendMenu::ActionType::Schedule, _sendMenuDetails()); + }; const auto submit = addButton( - isNormal + (isNormal ? tr::lng_polls_create_button() - : tr::lng_schedule_button(), - [=] { isNormal ? send({}) : sendScheduled(); }); - const auto sendMenuType = [=] { + : tr::lng_schedule_button()), + [=] { isNormal ? send({}) : schedule(); }); + const auto sendMenuDetails = [=] { collectError(); - return (*error) - ? SendMenu::Type::Disabled - : _sendMenuType; + return (*error) ? SendMenu::Details() : _sendMenuDetails(); }; SendMenu::SetupMenuAndShortcuts( submit.data(), _controller->uiShow(), - sendMenuType, - sendSilent, - sendScheduled, - sendWhenOnline); + sendMenuDetails, + sendAction); addButton(tr::lng_cancel(), [=] { closeBox(); }); return result; diff --git a/Telegram/SourceFiles/boxes/create_poll_box.h b/Telegram/SourceFiles/boxes/create_poll_box.h index 91805f0d8..91fc290ca 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.h +++ b/Telegram/SourceFiles/boxes/create_poll_box.h @@ -27,7 +27,7 @@ class SessionController; } // namespace Window namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu class CreatePollBox : public Ui::BoxContent { @@ -43,7 +43,7 @@ public: PollData::Flags chosen, PollData::Flags disabled, Api::SendType sendType, - SendMenu::Type sendMenuType); + SendMenu::Details sendMenuDetails); [[nodiscard]] rpl::producer submitRequests() const; void submitFailed(const QString &error); @@ -75,7 +75,7 @@ private: const PollData::Flags _chosen = PollData::Flags(); const PollData::Flags _disabled = PollData::Flags(); const Api::SendType _sendType = Api::SendType(); - const SendMenu::Type _sendMenuType; + const Fn _sendMenuDetails; base::unique_qptr _emojiPanel; Fn _setInnerFocus; Fn()> _dataIsValidValue; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 8e1280f89..6ba4771f8 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -328,7 +328,7 @@ SendFilesBox::SendFilesBox( const TextWithTags &caption, not_null toPeer, Api::SendType sendType, - SendMenu::Type sendMenuType) + SendMenu::Details sendMenuDetails) : SendFilesBox(nullptr, { .show = controller->uiShow(), .list = std::move(list), @@ -337,7 +337,7 @@ SendFilesBox::SendFilesBox( .limits = DefaultLimitsForPeer(toPeer), .check = DefaultCheckForPeer(controller, toPeer), .sendType = sendType, - .sendMenuType = sendMenuType, + .sendMenuDetails = [=] { return sendMenuDetails; }, }) { } @@ -350,7 +350,9 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor) , _titleHeight(st::boxTitleHeight) , _list(std::move(descriptor.list)) , _limits(descriptor.limits) -, _sendMenuType(descriptor.sendMenuType) +, _sendMenuDetails(descriptor.sendMenuDetails + ? descriptor.sendMenuDetails + : [] { return SendMenu::Details(); }) , _captionToPeer(descriptor.captionToPeer) , _check(std::move(descriptor.check)) , _confirmedCallback(std::move(descriptor.confirmed)) @@ -530,10 +532,8 @@ void SendFilesBox::refreshButtons() { SendMenu::SetupMenuAndShortcuts( _send, _show, - [=] { return _sendMenuType; }, - [=] { sendSilent(); }, - [=] { sendScheduled(); }, - [=] { sendWhenOnline(); }); + _sendMenuDetails, + SendMenu::DefaultCallback(_show, sendCallback())); } addButton(tr::lng_cancel(), [=] { closeBox(); }); _addFile = addLeftButton( @@ -546,7 +546,7 @@ void SendFilesBox::refreshButtons() { } bool SendFilesBox::hasSendMenu() const { - return (_sendMenuType != SendMenu::Type::Disabled); + return (_sendMenuDetails().type != SendMenu::Type::Disabled); } bool SendFilesBox::hasSpoilerMenu() const { @@ -607,11 +607,11 @@ void SendFilesBox::addMenuButton() { if (hasSendMenu()) { SendMenu::FillSendMenu( _menu.get(), - _sendMenuType, - [=] { sendSilent(); }, - [=] { sendScheduled(); }, - [=] { sendWhenOnline(); }, - &_st.tabbed.icons); + _show, + _sendMenuDetails(), + SendMenu::DefaultCallback(_show, sendCallback()), + &_st.tabbed.icons, + QCursor::pos()); } _menu->popup(QCursor::pos()); return true; @@ -1426,7 +1426,9 @@ void SendFilesBox::send( if ((_sendType == Api::SendType::Scheduled || _sendType == Api::SendType::ScheduledToUser) && !options.scheduled) { - return sendScheduled(); + return SendMenu::DefaultCallback(_show, sendCallback())( + SendMenu::ActionType::Schedule, + _sendMenuDetails()); } if (_preparing) { _whenReadySend = [=] { @@ -1464,25 +1466,10 @@ void SendFilesBox::send( closeBox(); } -void SendFilesBox::sendSilent() { - send({ .silent = true }); -} - -void SendFilesBox::sendScheduled() { - const auto type = (_sendType == Api::SendType::ScheduledToUser) - ? SendMenu::Type::ScheduledToUser - : _sendMenuType; - const auto callback = [=](Api::SendOptions options) { send(options); }; - auto box = HistoryView::PrepareScheduleBox(this, _show, type, callback); - const auto weak = Ui::MakeWeak(box.data()); - _show->showBox(std::move(box)); - if (const auto strong = weak.data()) { - strong->setCloseByOutsideClick(false); - } -} - -void SendFilesBox::sendWhenOnline() { - send(Api::DefaultSendWhenOnlineOptions()); +Fn SendFilesBox::sendCallback() { + return crl::guard(this, [=](Api::SendOptions options) { + send(options, false); + }); } SendFilesBox::~SendFilesBox() = default; diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index af712eda1..f6de4a4f8 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -47,7 +47,7 @@ class SessionController; } // namespace Window namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace HistoryView::Controls { @@ -96,7 +96,7 @@ struct SendFilesBoxDescriptor { SendFilesLimits limits = {}; SendFilesCheck check; Api::SendType sendType = {}; - SendMenu::Type sendMenuType = {}; + Fn sendMenuDetails = nullptr; const style::ComposeControls *stOverride = nullptr; SendFilesConfirmed confirmed; Fn cancelled; @@ -115,7 +115,7 @@ public: const TextWithTags &caption, not_null toPeer, Api::SendType sendType, - SendMenu::Type sendMenuType); + SendMenu::Details sendMenuDetails); SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor); void setConfirmedCallback(SendFilesConfirmed callback) { @@ -202,9 +202,7 @@ private: void generatePreviewFrom(int fromBlock); void send(Api::SendOptions options, bool ctrlShiftEnter = false); - void sendSilent(); - void sendScheduled(); - void sendWhenOnline(); + [[nodiscard]] Fn sendCallback(); void captionResized(); void saveSendWaySettings(); @@ -238,7 +236,7 @@ private: std::optional _removingIndex; SendFilesLimits _limits = {}; - SendMenu::Type _sendMenuType = {}; + Fn _sendMenuDetails = nullptr; PeerData *_captionToPeer = nullptr; SendFilesCheck _check; SendFilesConfirmed _confirmedCallback; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 2d9ba4d3a..3d42c5afe 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -473,15 +473,18 @@ void ShareBox::keyPressEvent(QKeyEvent *e) { } } -SendMenu::Type ShareBox::sendMenuType() const { +SendMenu::Details ShareBox::sendMenuDetails() const { const auto selected = _inner->selected(); - return ranges::all_of( + const auto type = ranges::all_of( selected | ranges::views::transform(&Data::Thread::peer), HistoryView::CanScheduleUntilOnline) ? SendMenu::Type::ScheduledToUser : (selected.size() == 1 && selected.front()->peer()->isSelf()) ? SendMenu::Type::Reminder : SendMenu::Type::Scheduled; + + // We can't support effect here because we don't have ChatHelpers::Show. + return { .type = type, .effectAllowed = false }; } void ShareBox::showMenu(not_null parent) { @@ -518,15 +521,25 @@ void ShareBox::showMenu(not_null parent) { _menu->addSeparator(); } - const auto result = SendMenu::FillSendMenu( + using namespace SendMenu; + const auto sendAction = crl::guard(this, [=](Action action, Details) { + const auto options = std::get_if(&action); + if (options || v::get(action) == ActionType::Send) { + submit(options ? *options : Api::SendOptions()); + } else { + submitScheduled(); + } + }); + _menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom); + const auto result = FillSendMenu( _menu.get(), - sendMenuType(), - [=] { submitSilent(); }, - [=] { submitScheduled(); }, - [=] { submitWhenOnline(); }); - const auto success = (result == SendMenu::FillMenuResult::Success); - if (_descriptor.forwardOptions.show || success) { - _menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom); + nullptr, // showForEffect. + sendMenuDetails(), + sendAction); + if (result == SendMenu::FillMenuResult::Prepared) { + _menu->popupPrepared(); + } else if (_descriptor.forwardOptions.show + && result != SendMenu::FillMenuResult::Failed) { _menu->popup(QCursor::pos()); } } @@ -607,26 +620,18 @@ void ShareBox::submit(Api::SendOptions options) { } } -void ShareBox::submitSilent() { - submit({ .silent = true }); -} - void ShareBox::submitScheduled() { const auto callback = [=](Api::SendOptions options) { submit(options); }; uiShow()->showBox( HistoryView::PrepareScheduleBox( this, nullptr, // ChatHelpers::Show for effect attachment. - sendMenuType(), + sendMenuDetails(), callback, HistoryView::DefaultScheduleTime(), _descriptor.scheduleBoxStyle)); } -void ShareBox::submitWhenOnline() { - submit(Api::DefaultSendWhenOnlineOptions()); -} - void ShareBox::copyLink() const { if (const auto onstack = _descriptor.copyCallback) { onstack(); diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index 253a8228e..5e48430cd 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -24,7 +24,7 @@ struct PeerList; } // namespace style namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace Window { @@ -130,13 +130,11 @@ private: void scrollAnimationCallback(); void submit(Api::SendOptions options); - void submitSilent(); void submitScheduled(); - void submitWhenOnline(); void copyLink() const; bool searchByUsername(bool useCache = false); - SendMenu::Type sendMenuType() const; + [[nodiscard]] SendMenu::Details sendMenuDetails() const; void scrollTo(Ui::ScrollToRequest request); void needSearchByUsername(); diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index ac4555120..efd034843 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -1014,7 +1014,7 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) { _menu = base::make_unique_q( this, st::popupMenuWithIcons); - const auto type = _show->sendMenuType(); + const auto details = _show->sendMenuDetails(); if (setType() == Data::StickersType::Emoji) { if (const auto t = PrepareTextFromEmoji(_pack[index]); !t.empty()) { _menu->addAction(tr::lng_mediaview_copy(tr::now), [=] { @@ -1023,17 +1023,16 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) { } }, &st::menuIconCopy); } - } else if (type != SendMenu::Type::Disabled) { + } else if (details.type != SendMenu::Type::Disabled) { const auto document = _pack[index]; - const auto sendSelected = [=](Api::SendOptions options) { + const auto send = crl::guard(this, [=](Api::SendOptions options) { chosen(index, document, options); - }; + }); SendMenu::FillSendMenu( _menu.get(), - type, - SendMenu::DefaultSilentCallback(sendSelected), - SendMenu::DefaultScheduleCallback(_show, type, sendSelected), - SendMenu::DefaultWhenOnlineCallback(sendSelected)); + _show, + details, + SendMenu::DefaultCallback(_show, send)); const auto show = _show; const auto toggleFavedSticker = [=] { diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.h b/Telegram/SourceFiles/boxes/sticker_set_box.h index c57a1f484..72dd38320 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.h +++ b/Telegram/SourceFiles/boxes/sticker_set_box.h @@ -23,10 +23,6 @@ namespace Data { class StickersSet; } // namespace Data -namespace SendMenu { -enum class Type; -} // namespace SendMenu - namespace ChatHelpers { struct FileChosen; class Show; diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_show.h b/Telegram/SourceFiles/chat_helpers/compose/compose_show.h index 7dcbab513..28fc7ff47 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_show.h +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_show.h @@ -22,7 +22,7 @@ class SessionController; } // namespace Window namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace ChatHelpers { @@ -57,7 +57,7 @@ public: [[nodiscard]] virtual rpl::producer<> pauseChanged() const = 0; [[nodiscard]] virtual rpl::producer adjustShadowLeft() const; - [[nodiscard]] virtual SendMenu::Type sendMenuType() const = 0; + [[nodiscard]] virtual SendMenu::Details sendMenuDetails() const = 0; virtual bool showMediaPreview( Data::FileOrigin origin, diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index a5b72ca7b..8482d8b81 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -1104,7 +1104,7 @@ void EmojiListWidget::fillRecentFrom(const std::vector &list) { } base::unique_qptr EmojiListWidget::fillContextMenu( - SendMenu::Type type) { + const SendMenu::Details &details) { if (v::is_null(_selected)) { return nullptr; } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 3159d340e..219add05b 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -144,7 +144,7 @@ public: RectPart origin); base::unique_qptr fillContextMenu( - SendMenu::Type type) override; + const SendMenu::Details &details) override; protected: void visibleTopBottomUpdated( diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 20f32f6e8..90f11e978 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -87,7 +87,7 @@ public: Api::SendOptions options = {}) const; void setRecentInlineBotsInRows(int32 bots); - void setSendMenuType(Fn &&callback); + void setSendMenuDetails(Fn &&callback); void rowsUpdated(); rpl::producer mentionChosen() const; @@ -155,7 +155,7 @@ private: const std::unique_ptr _pathGradient; StickerPremiumMark _premiumMark; - Fn _sendMenuType; + Fn _sendMenuDetails; rpl::event_stream _mentionChosen; rpl::event_stream _hashtagChosen; @@ -835,8 +835,9 @@ bool FieldAutocomplete::chooseSelected(ChooseMethod method) const { return _inner->chooseSelected(method); } -void FieldAutocomplete::setSendMenuType(Fn &&callback) { - _inner->setSendMenuType(std::move(callback)); +void FieldAutocomplete::setSendMenuDetails( + Fn &&callback) { + _inner->setSendMenuDetails(std::move(callback)); } bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) { @@ -1364,24 +1365,22 @@ void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) { return; } const auto index = _sel; - const auto type = _sendMenuType - ? _sendMenuType() - : SendMenu::Type::Disabled; + const auto details = _sendMenuDetails + ? _sendMenuDetails() + : SendMenu::Details(); const auto method = FieldAutocomplete::ChooseMethod::ByClick; _menu = base::make_unique_q( this, st::popupMenuWithIcons); - const auto send = [=](Api::SendOptions options) { + const auto send = crl::guard(this, [=](Api::SendOptions options) { chooseAtIndex(method, index, options); - }; + }); SendMenu::FillSendMenu( _menu, - type, - SendMenu::DefaultSilentCallback(send), - SendMenu::DefaultScheduleCallback(_show, type, send), - SendMenu::DefaultWhenOnlineCallback(send)); - + _show, + details, + SendMenu::DefaultCallback(_show, send)); if (!_menu->empty()) { _menu->popup(QCursor::pos()); } @@ -1604,9 +1603,9 @@ void FieldAutocomplete::Inner::showPreview() { } } -void FieldAutocomplete::Inner::setSendMenuType( - Fn &&callback) { - _sendMenuType = std::move(callback); +void FieldAutocomplete::Inner::setSendMenuDetails( + Fn &&callback) { + _sendMenuDetails = std::move(callback); } auto FieldAutocomplete::Inner::mentionChosen() const diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h index 5c9e291f3..d95b7e394 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h @@ -42,7 +42,7 @@ class DocumentMedia; } // namespace Data namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace ChatHelpers { @@ -123,7 +123,7 @@ public: void setModerateKeyActivateCallback(Fn callback) { _moderateKeyActivateCallback = std::move(callback); } - void setSendMenuType(Fn &&callback); + void setSendMenuDetails(Fn &&callback); void hideFast(); void showAnimated(); diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 609f21989..ab2fe0b98 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -380,22 +380,22 @@ void GifsListWidget::mousePressEvent(QMouseEvent *e) { } base::unique_qptr GifsListWidget::fillContextMenu( - SendMenu::Type type) { + const SendMenu::Details &details) { if (_selected < 0 || _pressed >= 0) { return nullptr; } auto menu = base::make_unique_q(this, st().menu); - const auto send = [=, selected = _selected](Api::SendOptions options) { + const auto selected = _selected; + const auto send = crl::guard(this, [=](Api::SendOptions options) { selectInlineResult(selected, options, true); - }; + }); const auto icons = &st().icons; SendMenu::FillSendMenu( menu, - type, - SendMenu::DefaultSilentCallback(send), - SendMenu::DefaultScheduleCallback(_show, type, send), - SendMenu::DefaultWhenOnlineCallback(send), + _show, + details, + SendMenu::DefaultCallback(_show, send), icons); if (const auto item = _mosaic.maybeItemAt(_selected)) { diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h index 84e20c864..ad6c26ae6 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h @@ -40,7 +40,7 @@ class SessionController; } // namespace Window namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace Data { @@ -102,7 +102,7 @@ public: rpl::producer<> cancelRequests() const; base::unique_qptr fillContextMenu( - SendMenu::Type type) override; + const SendMenu::Details &details) override; ~GifsListWidget(); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index e44b8aaef..1425f6dd8 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -1634,7 +1634,7 @@ void StickersListWidget::showStickerSetBox(not_null document) { } base::unique_qptr StickersListWidget::fillContextMenu( - SendMenu::Type type) { + const SendMenu::Details &details) { auto selected = _selected; auto &sets = shownSets(); if (v::is_null(selected) || !v::is_null(_pressed)) { @@ -1653,7 +1653,7 @@ base::unique_qptr StickersListWidget::fillContextMenu( auto menu = base::make_unique_q(this, st().menu); const auto document = set.stickers[sticker->index].document; - const auto send = [=](Api::SendOptions options) { + const auto send = crl::guard(this, [=](Api::SendOptions options) { _chosen.fire({ .document = document, .options = options, @@ -1661,14 +1661,13 @@ base::unique_qptr StickersListWidget::fillContextMenu( ? Ui::MessageSendingAnimationFrom() : messageSentAnimationInfo(section, index, document), }); - }; + }); const auto icons = &st().icons; SendMenu::FillSendMenu( menu, - type, - SendMenu::DefaultSilentCallback(send), - SendMenu::DefaultScheduleCallback(_show, type, send), - SendMenu::DefaultWhenOnlineCallback(send), + _show, + details, + SendMenu::DefaultCallback(_show, send), icons); const auto show = _show; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index aff2cf287..34838b364 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -116,7 +116,7 @@ public: std::shared_ptr getLottieRenderer(); base::unique_qptr fillContextMenu( - SendMenu::Type type) override; + const SendMenu::Details &details) override; bool mySetsEmpty() const; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index b63ae6967..5e8967541 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -1297,8 +1297,8 @@ void TabbedSelector::scrollToY(int y) { } } -void TabbedSelector::showMenuWithType(SendMenu::Type type) { - _menu = currentTab()->widget()->fillContextMenu(type); +void TabbedSelector::showMenuWithDetails(SendMenu::Details details) { + _menu = currentTab()->widget()->fillContextMenu(details); if (_menu && !_menu->empty()) { _menu->popup(QCursor::pos()); } diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index b9b03b33b..bb780797c 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -36,7 +36,7 @@ class TabbedSearch; } // namespace Ui namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace style { @@ -178,7 +178,7 @@ public: _beforeHidingCallback = std::move(callback); } - void showMenuWithType(SendMenu::Type type); + void showMenuWithDetails(SendMenu::Details details); void setDropDown(bool dropDown); // Float player interface. @@ -380,7 +380,7 @@ public: virtual void beforeHiding() { } [[nodiscard]] virtual base::unique_qptr fillContextMenu( - SendMenu::Type type) { + const SendMenu::Details &details) { return nullptr; } diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index a04cbcbfb..cdab73fb6 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -1297,9 +1297,10 @@ std::optional Reactions::parse(const MTPAvailableEffect &entry) { return std::nullopt; } const auto id = DocumentId(data.vid().v); - const auto document = _owner->document(id); + const auto stickerId = data.veffect_sticker_id().v; + const auto document = _owner->document(stickerId); if (!document->sticker()) { - LOG(("API Error: Bad sticker in effects: %1").arg(id)); + LOG(("API Error: Bad sticker in effects: %1").arg(stickerId)); return std::nullopt; } const auto aroundId = data.veffect_animation_id().value_or_empty(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index c89d3b0c7..e76900180 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -321,14 +321,22 @@ HistoryWidget::HistoryWidget( _fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); }); _send->addClickHandler([=] { sendButtonClicked(); }); - SendMenu::SetupMenuAndShortcuts( - _send.get(), - controller->uiShow(), - [=] { return sendButtonMenuType(); }, - [=] { sendSilent(); }, - [=] { sendScheduled(); }, - [=] { sendWhenOnline(); }); - + { + using namespace SendMenu; + const auto sendAction = [=](Action action, Details) { + const auto options = std::get_if(&action); + if (options || v::get(action) == ActionType::Send) { + send(options ? *options : Api::SendOptions()); + } else { + sendScheduled(); + } + }; + SetupMenuAndShortcuts( + _send.get(), + controller->uiShow(), + [=] { return sendButtonMenuDetails(); }, + sendAction); + } _unblock->addClickHandler([=] { unblockUser(); }); _botStart->addClickHandler([=] { sendBotStartCommand(); }); _joinChannel->addClickHandler([=] { joinChannel(); }); @@ -514,7 +522,9 @@ HistoryWidget::HistoryWidget( } }, lifetime()); - _fieldAutocomplete->setSendMenuType([=] { return sendMenuType(); }); + _fieldAutocomplete->setSendMenuDetails([=] { + return sendMenuDetails(); + }); if (_supportAutocomplete) { supportInitAutocomplete(); @@ -1185,7 +1195,7 @@ void HistoryWidget::initTabbedSelector() { selector->contextMenuRequested( ) | filter | rpl::start_with_next([=] { - selector->showMenuWithType(sendMenuType()); + selector->showMenuWithDetails(sendMenuDetails()); }, lifetime()); selector->choosingStickerUpdated( @@ -1564,7 +1574,9 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { sendInlineResult(result); } }); - _inlineResults->setSendMenuType([=] { return sendMenuType(); }); + _inlineResults->setSendMenuDetails([=] { + return sendMenuDetails(); + }); _inlineResults->requesting( ) | rpl::start_with_next([=](bool requesting) { _tabbedSelectorToggle->setLoading(requesting); @@ -4177,10 +4189,6 @@ void HistoryWidget::sendWithModifiers(Qt::KeyboardModifiers modifiers) { send({ .handleSupportSwitch = Support::HandleSwitch(modifiers) }); } -void HistoryWidget::sendSilent() { - send({ .silent = true }); -} - void HistoryWidget::sendScheduled() { if (!_list) { return; @@ -4196,22 +4204,20 @@ void HistoryWidget::sendScheduled() { HistoryView::PrepareScheduleBox( _list, controller()->uiShow(), - sendMenuType(), + sendMenuDetails(), callback)); } -void HistoryWidget::sendWhenOnline() { - send(Api::DefaultSendWhenOnlineOptions()); -} - -SendMenu::Type HistoryWidget::sendMenuType() const { - return !_peer +SendMenu::Details HistoryWidget::sendMenuDetails() const { + const auto type = !_peer ? SendMenu::Type::Disabled : _peer->isSelf() ? SendMenu::Type::Reminder : HistoryView::CanScheduleUntilOnline(_peer) ? SendMenu::Type::ScheduledToUser : SendMenu::Type::Scheduled; + const auto effectAllowed = _peer && _peer->isUser(); + return { .type = type, .effectAllowed = effectAllowed }; } auto HistoryWidget::computeSendButtonType() const { @@ -4227,10 +4233,11 @@ auto HistoryWidget::computeSendButtonType() const { return Type::Send; } -SendMenu::Type HistoryWidget::sendButtonMenuType() const { - return (computeSendButtonType() == Ui::SendButton::Type::Send) - ? sendMenuType() - : SendMenu::Type::Disabled; +SendMenu::Details HistoryWidget::sendButtonMenuDetails() const { + if (computeSendButtonType() != Ui::SendButton::Type::Send) { + return {}; + } + return sendMenuDetails(); } void HistoryWidget::unblockUser() { @@ -5609,7 +5616,7 @@ bool HistoryWidget::confirmSendingFiles( text, _peer, Api::SendType::Normal, - sendMenuType()); + sendMenuDetails()); _field->setTextWithTags({}); box->setConfirmedCallback(crl::guard(this, [=]( Ui::PreparedList &&list, diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index b731507b2..ca2a9ed47 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -33,7 +33,7 @@ class PhotoMedia; } // namespace Data namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace Api { @@ -268,7 +268,7 @@ public: void confirmDeleteSelected(); void clearSelected(); - [[nodiscard]] SendMenu::Type sendMenuType() const; + [[nodiscard]] SendMenu::Details sendMenuDetails() const; bool sendExistingDocument( not_null document, Api::SendOptions options, @@ -395,10 +395,8 @@ private: Api::SendOptions options) const; void send(Api::SendOptions options); void sendWithModifiers(Qt::KeyboardModifiers modifiers); - void sendSilent(); void sendScheduled(); - void sendWhenOnline(); - [[nodiscard]] SendMenu::Type sendButtonMenuType() const; + [[nodiscard]] SendMenu::Details sendButtonMenuDetails() const; void handlePendingHistoryUpdate(); void fullInfoUpdated(); void toggleTabbedSelectorMode(); 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 7dcf2373b..c81c59b8c 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -854,7 +854,7 @@ ComposeControls::ComposeControls( .recorderHeight = st::historySendSize.height(), .lockFromBottom = descriptor.voiceLockFromBottom, })) -, _sendMenuType(descriptor.sendMenuType) +, _sendMenuDetails(descriptor.sendMenuDetails) , _unavailableEmojiPasted(std::move(descriptor.unavailableEmojiPasted)) , _saveDraftTimer([=] { saveDraft(); }) , _saveCloudDraftTimer([=] { saveCloudDraft(); }) { @@ -1719,7 +1719,7 @@ void ComposeControls::initAutocomplete() { } }, _autocomplete->lifetime()); - _autocomplete->setSendMenuType([=] { return sendMenuType(); }); + _autocomplete->setSendMenuDetails([=] { return sendMenuDetails(); }); //_autocomplete->setModerateKeyActivateCallback([=](int key) { // return _keyboard->isHidden() @@ -2162,7 +2162,7 @@ void ComposeControls::initTabbedSelector() { _selector->contextMenuRequested( ) | rpl::start_with_next([=] { - _selector->showMenuWithType(sendMenuType()); + _selector->showMenuWithDetails(sendMenuDetails()); }, wrap->lifetime()); _selector->choosingStickerUpdated( @@ -2191,17 +2191,15 @@ void ComposeControls::initSendButton() { cancelInlineBot(); }, _send->lifetime()); - const auto send = [=](Api::SendOptions options) { + const auto send = crl::guard(_send.get(), [=](Api::SendOptions options) { _sendCustomRequests.fire(std::move(options)); - }; + }); SendMenu::SetupMenuAndShortcuts( _send.get(), _show, - [=] { return sendButtonMenuType(); }, - SendMenu::DefaultSilentCallback(send), - SendMenu::DefaultScheduleCallback(_show, sendMenuType(), send), - SendMenu::DefaultWhenOnlineCallback(send)); + [=] { return sendButtonMenuDetails(); }, + SendMenu::DefaultCallback(_show, send)); } void ComposeControls::initSendAsButton(not_null peer) { @@ -2510,14 +2508,14 @@ auto ComposeControls::computeSendButtonType() const { return (_mode == Mode::Normal) ? Type::Send : Type::Schedule; } -SendMenu::Type ComposeControls::sendMenuType() const { - return !_history ? SendMenu::Type::Disabled : _sendMenuType; +SendMenu::Details ComposeControls::sendMenuDetails() const { + return !_history ? SendMenu::Details() : _sendMenuDetails(); } -SendMenu::Type ComposeControls::sendButtonMenuType() const { +SendMenu::Details ComposeControls::sendButtonMenuDetails() const { return (computeSendButtonType() == Ui::SendButton::Type::Send) - ? sendMenuType() - : SendMenu::Type::Disabled; + ? sendMenuDetails() + : SendMenu::Details(); } void ComposeControls::updateSendButtonType() { @@ -3325,7 +3323,9 @@ void ComposeControls::applyInlineBotQuery( _inlineResultChosen.fire_copy(result); } }); - _inlineResults->setSendMenuType([=] { return sendMenuType(); }); + _inlineResults->setSendMenuDetails([=] { + return sendMenuDetails(); + }); _inlineResults->requesting( ) | rpl::start_with_next([=](bool requesting) { _tabbedSelectorToggle->setLoading(requesting); 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 f6c915968..11e7429e2 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -29,7 +29,7 @@ struct ComposeControls; } // namespace style namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace ChatHelpers { @@ -104,7 +104,7 @@ struct ComposeControlsDescriptor { std::shared_ptr show; Fn)> unavailableEmojiPasted; ComposeControlsMode mode = ComposeControlsMode::Normal; - SendMenu::Type sendMenuType = {}; + Fn sendMenuDetails = nullptr; Window::SessionController *regularWindow = nullptr; rpl::producer stickerOrEmojiChosen; rpl::producer customPlaceholder; @@ -282,8 +282,8 @@ private: void paintBackground(QPainter &p, QRect full, QRect clip); [[nodiscard]] auto computeSendButtonType() const; - [[nodiscard]] SendMenu::Type sendMenuType() const; - [[nodiscard]] SendMenu::Type sendButtonMenuType() const; + [[nodiscard]] SendMenu::Details sendMenuDetails() const; + [[nodiscard]] SendMenu::Details sendButtonMenuDetails() const; [[nodiscard]] auto sendContentRequests( SendRequestType requestType = SendRequestType::Text) const; @@ -396,7 +396,7 @@ private: const std::unique_ptr _header; const std::unique_ptr _voiceRecordBar; - const SendMenu::Type _sendMenuType; + const Fn _sendMenuDetails; const Fn)> _unavailableEmojiPasted; rpl::event_stream _sendCustomRequests; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 12e0b06c6..781a4875d 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -590,7 +590,7 @@ bool AddRescheduleAction( HistoryView::PrepareScheduleBox( &request.navigation->session(), request.navigation->uiShow(), - sendMenuType, + { .type = sendMenuType, .effectAllowed = false }, callback, date)); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index b1dcb13db..7f7ded51a 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -224,9 +224,11 @@ RepliesWidget::RepliesWidget( listShowPremiumToast(emoji); }, .mode = ComposeControls::Mode::Normal, - .sendMenuType = _topic - ? SendMenu::Type::Scheduled - : SendMenu::Type::SilentOnly, + .sendMenuDetails = [=] { + using Type = SendMenu::Type; + const auto type = _topic ? Type::Scheduled : Type::SilentOnly; + return SendMenu::Details{ .type = type }; + }, .regularWindow = controller, .stickerOrEmojiChosen = controller->stickerOrEmojiChosen(), .scheduledToggleValue = _topic @@ -950,7 +952,7 @@ bool RepliesWidget::confirmSendingFiles( _composeControls->getTextWithAppliedMarkdown(), _history->peer, Api::SendType::Normal, - SendMenu::Type::SilentOnly); // #TODO replies schedule + SendMenu::Details{ SendMenu::Type::SilentOnly }); // #TODO replies schedule box->setConfirmedCallback(crl::guard(this, [=]( Ui::PreparedList &&list, @@ -1445,13 +1447,14 @@ void RepliesWidget::sendInlineResult( finishSending(); } -SendMenu::Type RepliesWidget::sendMenuType() const { +SendMenu::Details RepliesWidget::sendMenuDetails() const { // #TODO replies schedule - return _history->peer->isSelf() + const auto type = _history->peer->isSelf() ? SendMenu::Type::Reminder : HistoryView::CanScheduleUntilOnline(_history->peer) ? SendMenu::Type::ScheduledToUser : SendMenu::Type::Scheduled; + return { .type = type, .effectAllowed = _history->peer->isUser() }; } FullReplyTo RepliesWidget::replyTo() const { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index e3d05920e..54f02ef16 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -19,7 +19,7 @@ enum class SendMediaType; struct SendingAlbum; namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace Api { @@ -249,7 +249,7 @@ private: mtpRequestId *const saveEditMsgRequestId, std::optional spoilerMediaOverride); void chooseAttach(std::optional overrideSendImagesAsPhotos); - [[nodiscard]] SendMenu::Type sendMenuType() const; + [[nodiscard]] SendMenu::Details sendMenuDetails() const; [[nodiscard]] FullReplyTo replyTo() const; [[nodiscard]] HistoryItem *lookupRoot() const; [[nodiscard]] Data::ForumTopic *lookupTopic(); diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index 83891717c..09288fed7 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -70,7 +70,7 @@ bool CanScheduleUntilOnline(not_null peer) { void ScheduleBox( not_null box, std::shared_ptr show, - SendMenu::Type type, + const SendMenu::Details &details, Fn done, TimeId time, ScheduleBoxStyleArgs style) { @@ -87,7 +87,7 @@ void ScheduleBox( copy(result); }; auto descriptor = Ui::ChooseDateTimeBox(box, { - .title = (type == SendMenu::Type::Reminder + .title = (details.type == SendMenu::Type::Reminder ? tr::lng_remind_title() : tr::lng_schedule_title()), .submit = tr::lng_schedule_button(), @@ -96,16 +96,26 @@ void ScheduleBox( .style = style.chooseDateTimeArgs, }); - using T = SendMenu::Type; - SendMenu::SetupMenuAndShortcuts( + using namespace SendMenu; + const auto childType = (details.type == Type::Disabled) + ? Type::Disabled + : Type::SilentOnly; + const auto childDetails = Details{ + .type = childType, + .effectAllowed = details.effectAllowed, + }; + const auto sendAction = crl::guard(box, [=](Action action, Details) { + save( + v::get(action).silent, + descriptor.collect()); + }); + SetupMenuAndShortcuts( descriptor.submit.data(), show, - [t = type == T::Disabled ? T::Disabled : T::SilentOnly] { return t; }, - [=] { save(true, descriptor.collect()); }, - nullptr, - nullptr); + [=] { return childDetails; }, + sendAction); - if (type == SendMenu::Type::ScheduledToUser) { + if (details.type == Type::ScheduledToUser) { const auto sendUntilOnline = box->addTopButton(*style.topButtonStyle); const auto timestamp = Api::kScheduledUntilOnlineTimestamp; FillSendUntilOnlineMenu( diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.h b/Telegram/SourceFiles/history/view/history_view_schedule_box.h index 72c2b4b89..a69177102 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.h +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.h @@ -23,7 +23,7 @@ class Show; } // namespace ChatHelpers namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace HistoryView { @@ -41,7 +41,7 @@ struct ScheduleBoxStyleArgs { void ScheduleBox( not_null box, std::shared_ptr show, - SendMenu::Type type, + const SendMenu::Details &details, Fn done, TimeId time, ScheduleBoxStyleArgs style); @@ -50,14 +50,14 @@ template [[nodiscard]] object_ptr PrepareScheduleBox( Guard &&guard, std::shared_ptr show, - SendMenu::Type type, + const SendMenu::Details &details, Submit &&submit, TimeId scheduleTime = DefaultScheduleTime(), ScheduleBoxStyleArgs style = ScheduleBoxStyleArgs()) { return Box( ScheduleBox, std::move(show), - type, + details, crl::guard(std::forward(guard), std::forward(submit)), scheduleTime, std::move(style)); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index e0522acb0..6535958be 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -115,7 +115,7 @@ ScheduledWidget::ScheduledWidget( listShowPremiumToast(emoji); }, .mode = ComposeControls::Mode::Scheduled, - .sendMenuType = SendMenu::Type::Disabled, + .sendMenuDetails = [] { return SendMenu::Details(); }, .regularWindow = controller, .stickerOrEmojiChosen = controller->stickerOrEmojiChosen(), })) @@ -493,7 +493,7 @@ bool ScheduledWidget::confirmSendingFiles( (CanScheduleUntilOnline(_history->peer) ? Api::SendType::ScheduledToUser : Api::SendType::Scheduled), - SendMenu::Type::Disabled); + SendMenu::Details()); box->setConfirmedCallback(crl::guard(this, [=]( Ui::PreparedList &&list, @@ -602,7 +602,7 @@ void ScheduledWidget::uploadFile( prepareSendAction(options)); }; controller()->show( - PrepareScheduleBox(this, _show, sendMenuType(), callback)); + PrepareScheduleBox(this, _show, sendMenuDetails(), callback)); } bool ScheduledWidget::showSendingFilesError( @@ -681,7 +681,7 @@ void ScheduledWidget::send() { } const auto callback = [=](Api::SendOptions options) { send(options); }; controller()->show( - PrepareScheduleBox(this, _show, sendMenuType(), callback)); + PrepareScheduleBox(this, _show, sendMenuDetails(), callback)); } void ScheduledWidget::send(Api::SendOptions options) { @@ -713,7 +713,7 @@ void ScheduledWidget::sendVoice( sendVoice(bytes, waveform, duration, options); }; controller()->show( - PrepareScheduleBox(this, _show, sendMenuType(), callback)); + PrepareScheduleBox(this, _show, sendMenuDetails(), callback)); } void ScheduledWidget::sendVoice( @@ -814,7 +814,7 @@ void ScheduledWidget::sendExistingDocument( sendExistingDocument(document, options); }; controller()->show( - PrepareScheduleBox(this, _show, sendMenuType(), callback)); + PrepareScheduleBox(this, _show, sendMenuDetails(), callback)); } bool ScheduledWidget::sendExistingDocument( @@ -844,7 +844,7 @@ void ScheduledWidget::sendExistingPhoto(not_null photo) { sendExistingPhoto(photo, options); }; controller()->show( - PrepareScheduleBox(this, _show, sendMenuType(), callback)); + PrepareScheduleBox(this, _show, sendMenuDetails(), callback)); } bool ScheduledWidget::sendExistingPhoto( @@ -879,7 +879,7 @@ void ScheduledWidget::sendInlineResult( sendInlineResult(result, bot, options); }; controller()->show( - PrepareScheduleBox(this, _show, sendMenuType(), callback)); + PrepareScheduleBox(this, _show, sendMenuDetails(), callback)); } void ScheduledWidget::sendInlineResult( @@ -911,12 +911,14 @@ void ScheduledWidget::sendInlineResult( _composeControls->focus(); } -SendMenu::Type ScheduledWidget::sendMenuType() const { - return _history->peer->isSelf() +SendMenu::Details ScheduledWidget::sendMenuDetails() const { + const auto type = _history->peer->isSelf() ? SendMenu::Type::Reminder : HistoryView::CanScheduleUntilOnline(_history->peer) ? SendMenu::Type::ScheduledToUser : SendMenu::Type::Scheduled; + const auto effectAllowed = _history->peer->isUser(); + return { .type = type, .effectAllowed = effectAllowed }; } void ScheduledWidget::cornerButtonsShowAtPosition( @@ -1368,7 +1370,7 @@ void ScheduledWidget::listSendBotCommand( session().api().sendMessage(std::move(message)); }; controller()->show( - PrepareScheduleBox(this, _show, sendMenuType(), callback)); + PrepareScheduleBox(this, _show, sendMenuDetails(), callback)); } void ScheduledWidget::listSearch( diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index cb5ead98f..e79304691 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -22,7 +22,7 @@ class Show; } // namespace ChatHelpers namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace Api { @@ -221,7 +221,7 @@ private: std::optional spoilerMediaOverride); void highlightSingleNewMessage(const Data::MessagesSlice &slice); void chooseAttach(); - [[nodiscard]] SendMenu::Type sendMenuType() const; + [[nodiscard]] SendMenu::Details sendMenuDetails() const; void pushReplyReturn(not_null item); void checkReplyReturns(); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 61c46a157..9ab46ef14 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "menu/menu_send.h" #include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/stickers_list_footer.h" #include "window/window_session_controller.h" @@ -123,15 +124,11 @@ UnifiedFactoryOwner::UnifiedFactoryOwner( const auto inStrip = _strip ? _strip->count() : 0; _unifiedIdsList.reserve(reactions.size()); for (const auto &reaction : reactions) { - if (const auto id = reaction.id.custom()) { - _unifiedIdsList.push_back(id); - } else { - _unifiedIdsList.push_back(reaction.selectAnimation->id); - } + _unifiedIdsList.push_back(reaction.selectAnimation->id); const auto unifiedId = _unifiedIdsList.back(); - if (!reaction.id.custom()) { - _defaultReactionIds.emplace(unifiedId, reaction.id.emoji()); + if (unifiedId != reaction.id.custom()) { + _defaultReactionIds.emplace(unifiedId, reaction.id); } if (index + 1 < inStrip) { _defaultReactionInStripMap.emplace(unifiedId, index++); @@ -165,7 +162,7 @@ Data::ReactionId UnifiedFactoryOwner::lookupReactionId( DocumentId unifiedId) const { const auto i = _defaultReactionIds.find(unifiedId); return (i != end(_defaultReactionIds)) - ? Data::ReactionId{ i->second } + ? i->second : Data::ReactionId{ unifiedId }; } @@ -174,21 +171,23 @@ UnifiedFactoryOwner::RecentFactory UnifiedFactoryOwner::factory() { -> std::unique_ptr { const auto tag = Data::CustomEmojiManager::SizeTag::Large; const auto sizeOverride = st::reactStripImage; - const auto isDefaultReaction = _defaultReactionIds.contains(id); + const auto i = _defaultReactionIds.find(id); + const auto isDefaultReaction = (i != end(_defaultReactionIds)) + && !i->second.custom(); const auto manager = &_session->data().customEmojiManager(); auto result = isDefaultReaction ? std::make_unique( manager->create(id, std::move(repaint), tag, sizeOverride), _defaultReactionShift) : manager->create(id, std::move(repaint), tag); - const auto i = _defaultReactionInStripMap.find(id); - if (i != end(_defaultReactionInStripMap)) { + const auto j = _defaultReactionInStripMap.find(id); + if (j != end(_defaultReactionInStripMap)) { Assert(_strip != nullptr); return std::make_unique( std::move(result), _strip, -_stripPaintOneShift, - i->second); + j->second); } return result; }; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index cb9b8a68f..06de4e7cd 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -66,7 +66,7 @@ private: Strip *_strip = nullptr; std::vector _unifiedIdsList; - base::flat_map _defaultReactionIds; + base::flat_map _defaultReactionIds; base::flat_map _defaultReactionInStripMap; QPoint _defaultReactionShift; diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp index 3a3176908..02f6f986e 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp @@ -229,7 +229,7 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) { _panel->selector()->contextMenuRequested( ) | rpl::start_with_next([=] { - _panel->selector()->showMenuWithType(SendMenu::Type::Scheduled); + _panel->selector()->showMenuWithDetails({}); }, _panel->lifetime()); auto statusChosen = _panel->selector()->customEmojiChosen( diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 95e7630e4..1eb99362b 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -1703,7 +1703,7 @@ std::unique_ptr MakeAttachBotsMenu( flag, flag, source, - sendMenuType); + { sendMenuType }); }, &st::menuIconCreatePoll); } for (const auto &bot : bots->attachBots()) { diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 5e45b3d4a..74219011d 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -329,23 +329,23 @@ void Inner::contextMenuEvent(QContextMenuEvent *e) { if (_selected < 0 || _pressed >= 0) { return; } - const auto type = _sendMenuType - ? _sendMenuType() - : SendMenu::Type::Disabled; + const auto details = _sendMenuDetails + ? _sendMenuDetails() + : SendMenu::Details(); _menu = base::make_unique_q( this, st::popupMenuWithIcons); - const auto send = [=, selected = _selected](Api::SendOptions options) { + const auto selected = _selected; + const auto send = crl::guard(this, [=](Api::SendOptions options) { selectInlineResult(selected, options, false); - }; + }); SendMenu::FillSendMenu( _menu, - type, - SendMenu::DefaultSilentCallback(send), - SendMenu::DefaultScheduleCallback(_controller->uiShow(), type, send), - SendMenu::DefaultWhenOnlineCallback(send)); + _controller->uiShow(), + details, + SendMenu::DefaultCallback(_controller->uiShow(), send)); const auto item = _mosaic.itemAt(_selected); if (const auto previewDocument = item->getPreviewDocument()) { @@ -689,8 +689,8 @@ void Inner::switchPm() { } } -void Inner::setSendMenuType(Fn &&callback) { - _sendMenuType = std::move(callback); +void Inner::setSendMenuDetails(Fn &&callback) { + _sendMenuDetails = std::move(callback); } } // namespace Layout diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.h b/Telegram/SourceFiles/inline_bots/inline_results_inner.h index 5aa22578a..4e6195e19 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.h @@ -43,7 +43,7 @@ struct ResultSelected; } // namespace InlineBots namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace InlineBots { @@ -89,7 +89,7 @@ public: void setResultSelectedCallback(Fn callback) { _resultSelectedCallback = std::move(callback); } - void setSendMenuType(Fn &&callback); + void setSendMenuDetails(Fn &&callback); // Ui::AbstractTooltipShower interface. QString tooltipText() const override; @@ -179,7 +179,7 @@ private: bool _previewShown = false; Fn _resultSelectedCallback; - Fn _sendMenuType; + Fn _sendMenuDetails; }; diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index c6e4b3380..12dfbb5f9 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -265,8 +265,8 @@ void Widget::setResultSelectedCallback(Fn callback) { _inner->setResultSelectedCallback(std::move(callback)); } -void Widget::setSendMenuType(Fn &&callback) { - _inner->setSendMenuType(std::move(callback)); +void Widget::setSendMenuDetails(Fn &&callback) { + _inner->setSendMenuDetails(std::move(callback)); } void Widget::hideAnimated() { diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.h b/Telegram/SourceFiles/inline_bots/inline_results_widget.h index 00b39e0d3..166287c58 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.h @@ -45,7 +45,7 @@ struct ResultSelected; } // namespace InlineBots namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace InlineBots { @@ -75,7 +75,7 @@ public: void hideAnimated(); void setResultSelectedCallback(Fn callback); - void setSendMenuType(Fn &&callback); + void setSendMenuDetails(Fn &&callback); [[nodiscard]] rpl::producer requesting() const { return _requesting.events(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4dd47970d..5f94f8a22 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1047,8 +1047,8 @@ void MainWidget::exportTopBarHeightUpdated() { } } -SendMenu::Type MainWidget::sendMenuType() const { - return _history->sendMenuType(); +SendMenu::Details MainWidget::sendMenuDetails() const { + return _history->sendMenuDetails(); } bool MainWidget::sendExistingDocument(not_null document) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 14e1ef2ab..3653070ac 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -29,7 +29,7 @@ struct SendOptions; } // namespace Api namespace SendMenu { -enum class Type; +struct Details; } // namespace SendMenu namespace Main { @@ -157,7 +157,7 @@ public: QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms); void checkMainSectionToLayer(); - [[nodiscard]] SendMenu::Type sendMenuType() const; + [[nodiscard]] SendMenu::Details sendMenuDetails() const; bool sendExistingDocument(not_null document); bool sendExistingDocument( not_null document, diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index b1a33d9c1..d93f49bf1 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -123,7 +123,7 @@ ReplyArea::ReplyArea(not_null controller) showPremiumToast(emoji); }, .mode = HistoryView::ComposeControlsMode::Normal, - .sendMenuType = SendMenu::Type::SilentOnly, + .sendMenuDetails = sendMenuDetails(), .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), .customPlaceholder = PlaceholderText( _controller->uiShow(), @@ -473,6 +473,15 @@ void ReplyArea::chooseAttach( crl::guard(this, [=] { _choosingAttach = false; })); } +Fn ReplyArea::sendMenuDetails() const { + return crl::guard(this, [=] { + return SendMenu::Details{ + .type = SendMenu::Type::SilentOnly, + .effectAllowed = _data.peer && _data.peer->isUser(), + }; + }); +} + bool ReplyArea::confirmSendingFiles( not_null data, std::optional overrideSendImagesAsPhotos, @@ -528,7 +537,7 @@ bool ReplyArea::confirmSendingFiles( .limits = DefaultLimitsForPeer(_data.peer), .check = DefaultCheckForPeer(show, _data.peer), .sendType = Api::SendType::Normal, - .sendMenuType = SendMenu::Type::SilentOnly, + .sendMenuDetails = sendMenuDetails(), .stOverride = &st::storiesComposeControls, .confirmed = crl::guard(this, confirmed), .cancelled = _controls->restoreTextCallback(insertTextOnCancel), diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index 90214b421..3e4ff217b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -38,6 +38,10 @@ namespace Main { class Session; } // namespace Main +namespace SendMenu { +struct Details; +} // namespace SendMenu + namespace Ui { struct PreparedList; class SendFilesWay; @@ -141,6 +145,8 @@ private: void sendVoice(VoiceToSend &&data); void chooseAttach(std::optional overrideSendImagesAsPhotos); + [[nodiscard]] Fn sendMenuDetails() const; + void showPremiumToast(not_null emoji); [[nodiscard]] bool showSlowmodeError(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 94dede7e9..836887e8c 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -348,8 +348,8 @@ public: rpl::producer adjustShadowLeft() const override { return rpl::single(false); } - SendMenu::Type sendMenuType() const override { - return SendMenu::Type::SilentOnly; + SendMenu::Details sendMenuDetails() const override { + return { SendMenu::Type::SilentOnly }; } bool showMediaPreview( diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 1a1b09757..1f216122b 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -37,140 +37,121 @@ namespace { not_null session) { auto result = Data::PossibleItemReactionsRef(); const auto reactions = &session->data().reactions(); - const auto &full = reactions->list(Data::Reactions::Type::Active); - const auto &top = reactions->list(Data::Reactions::Type::Top); - const auto &recent = reactions->list(Data::Reactions::Type::Recent); + const auto &effects = reactions->list(Data::Reactions::Type::Effects); const auto premiumPossible = session->premiumPossible(); auto added = base::flat_set(); - result.recent.reserve(full.size()); - for (const auto &reaction : ranges::views::concat(top, recent, full)) { - if (premiumPossible || !reaction.id.custom()) { + result.recent.reserve(effects.size()); + for (const auto &reaction : effects) { + if (premiumPossible || !reaction.premium) { if (added.emplace(reaction.id).second) { result.recent.push_back(&reaction); } } } - result.customAllowed = premiumPossible; - const auto i = ranges::find( - result.recent, - reactions->favoriteId(), - &Data::Reaction::id); - if (i != end(result.recent) && i != begin(result.recent)) { - std::rotate(begin(result.recent), i, i + 1); - } return result; } } // namespace -Fn DefaultSilentCallback(Fn send) { - return [=] { send({ .silent = true }); }; -} - -Fn DefaultScheduleCallback( +Fn DefaultCallback( std::shared_ptr show, - Type type, Fn send) { - return [=, weak = Ui::MakeWeak(show->toastParent())] { - show->showBox( - HistoryView::PrepareScheduleBox( - weak, - show, - type, - [=](Api::SendOptions options) { send(options); }), - Ui::LayerOption::KeepOther); + const auto guard = Ui::MakeWeak(show->toastParent()); + return [=](Action action, Details details) { + if (const auto options = std::get_if(&action)) { + send(*options); + } else if (v::get(action) == ActionType::Send) { + send({}); + } else { + using namespace HistoryView; + auto box = PrepareScheduleBox(guard, show, details, send); + const auto weak = Ui::MakeWeak(box.data()); + show->showBox(std::move(box)); + if (const auto strong = weak.data()) { + strong->setCloseByOutsideClick(false); + } + } }; } -Fn DefaultWhenOnlineCallback(Fn send) { - return [=] { send(Api::DefaultSendWhenOnlineOptions()); }; -} - FillMenuResult FillSendMenu( not_null menu, - Type type, - Fn silent, - Fn schedule, - Fn whenOnline, - const style::ComposeIcons *iconsOverride) { - if (!silent && !schedule) { - return FillMenuResult::None; + std::shared_ptr showForEffect, + Details details, + Fn action, + const style::ComposeIcons *iconsOverride, + std::optional desiredPositionOverride) { + const auto type = details.type; + if (type == Type::Disabled || !action) { + return FillMenuResult::Skipped; } const auto &icons = iconsOverride ? *iconsOverride : st::defaultComposeIcons; - const auto now = type; - if (now == Type::Disabled - || (!silent && now == Type::SilentOnly)) { - return FillMenuResult::None; - } - if (silent && now != Type::Reminder) { + if (type != Type::Reminder) { menu->addAction( tr::lng_send_silent_message(tr::now), - silent, + [=] { action(Api::SendOptions{ .silent = true }, details); }, &icons.menuMute); } - if (schedule && now != Type::SilentOnly) { + if (type != Type::SilentOnly) { menu->addAction( - (now == Type::Reminder + (type == Type::Reminder ? tr::lng_reminder_message(tr::now) : tr::lng_schedule_message(tr::now)), - schedule, + [=] { action(ActionType::Schedule, details); }, &icons.menuSchedule); } - if (whenOnline && now == Type::ScheduledToUser) { + if (type == Type::ScheduledToUser) { menu->addAction( tr::lng_scheduled_send_until_online(tr::now), - whenOnline, + [=] { action(Api::DefaultSendWhenOnlineOptions(), details); }, &icons.menuWhenOnline); } - return FillMenuResult::Success; + + using namespace HistoryView::Reactions; + const auto position = desiredPositionOverride.value_or(QCursor::pos()); + const auto selector = (showForEffect && details.effectAllowed) + ? AttachSelectorToMenu( + menu, + position, + st::reactPanelEmojiPan, + showForEffect, + LookupPossibleEffects(&showForEffect->session()), + { tr::lng_effect_add_title(tr::now) }) + : base::make_unexpected(AttachSelectorResult::Skipped); + if (!selector) { + if (selector.error() == AttachSelectorResult::Failed) { + return FillMenuResult::Failed; + } + menu->prepareGeometryFor(position); + return FillMenuResult::Prepared; + } + + (*selector)->chosen( + ) | rpl::start_with_next([=](ChosenReaction chosen) { + + }, menu->lifetime()); + + return FillMenuResult::Prepared; } void SetupMenuAndShortcuts( not_null button, std::shared_ptr show, - Fn type, - Fn silent, - Fn schedule, - Fn whenOnline) { - if (!silent && !schedule && !whenOnline) { - return; - } + Fn details, + Fn action) { const auto menu = std::make_shared>(); const auto showMenu = [=] { *menu = base::make_unique_q( button, st::popupMenuWithIcons); - const auto result = FillSendMenu( - *menu, - type(), - silent, - schedule, - whenOnline); - if (result != FillMenuResult::Success) { + const auto result = FillSendMenu(*menu, show, details(), action); + if (result != FillMenuResult::Prepared) { return false; } - const auto desiredPosition = QCursor::pos(); - using namespace HistoryView::Reactions; - const auto selector = show - ? AttachSelectorToMenu( - menu->get(), - desiredPosition, - st::reactPanelEmojiPan, - show, - LookupPossibleEffects(&show->session()), - { tr::lng_effect_add_title(tr::now) }) - : base::make_unexpected(AttachSelectorResult::Skipped); - if (selector) { - //(*selector)->chosen(); - (*menu)->popupPrepared(); - } else if (selector.error() == AttachSelectorResult::Failed) { - return false; - } else { - (*menu)->popup(desiredPosition); - } + (*menu)->popupPrepared(); return true; }; base::install_event_filter(button, [=](not_null e) { @@ -186,24 +167,21 @@ void SetupMenuAndShortcuts( }) | rpl::start_with_next([=](not_null request) { using Command = Shortcuts::Command; - const auto now = type(); - if (now == Type::Disabled - || (!silent && now == Type::SilentOnly)) { + const auto now = details().type; + if (now == Type::Disabled) { return; } - (silent - && (now != Type::Reminder) + ((now != Type::Reminder) && request->check(Command::SendSilentMessage) && request->handle([=] { - silent(); + action(Api::SendOptions{ .silent = true }, details()); return true; })) || - (schedule - && (now != Type::SilentOnly) + ((now != Type::SilentOnly) && request->check(Command::ScheduleMessage) && request->handle([=] { - schedule(); + action(ActionType::Schedule, details()); return true; })) || diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index 250ac1e77..66004c179 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -39,33 +39,39 @@ enum class Type { Reminder, }; -enum class FillMenuResult { - Success, - None, +struct Details { + Type type = Type::Disabled; + bool effectAllowed = false; }; -Fn DefaultSilentCallback(Fn send); -Fn DefaultScheduleCallback( +enum class FillMenuResult { + Prepared, + Skipped, + Failed, +}; + +enum class ActionType { + Send, + Schedule, +}; +using Action = std::variant; +[[nodiscard]] Fn DefaultCallback( std::shared_ptr show, - Type type, Fn send); -Fn DefaultWhenOnlineCallback(Fn send); FillMenuResult FillSendMenu( not_null menu, - Type type, - Fn silent, - Fn schedule, - Fn whenOnline, - const style::ComposeIcons *iconsOverride = nullptr); + std::shared_ptr showForEffect, + Details details, + Fn action, + const style::ComposeIcons *iconsOverride = nullptr, + std::optional desiredPositionOverride = std::nullopt); void SetupMenuAndShortcuts( not_null button, std::shared_ptr show, - Fn type, - Fn silent, - Fn schedule, - Fn whenOnline); + Fn details, + Fn action); void SetupUnreadMentionsMenu( not_null button, diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp index 22e419efa..ccbb1aea2 100644 --- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp +++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp @@ -242,7 +242,6 @@ private: mtpRequestId *const saveEditMsgRequestId, std::optional spoilerMediaOverride); void chooseAttach(std::optional overrideSendImagesAsPhotos); - [[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] FullReplyTo replyTo() const; void doSetInnerFocus(); void showAtPosition( @@ -792,7 +791,7 @@ QPointer ShortcutMessages::createPinnedToBottom( listShowPremiumToast(emoji); }, .mode = HistoryView::ComposeControlsMode::Normal, - .sendMenuType = SendMenu::Type::Disabled, + .sendMenuDetails = [] { return SendMenu::Details(); }, .regularWindow = _controller, .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), .customPlaceholder = std::move(placeholder), @@ -1346,7 +1345,7 @@ bool ShortcutMessages::confirmSendingFiles( _composeControls->getTextWithAppliedMarkdown(), _history->peer, Api::SendType::Normal, - SendMenu::Type::Disabled); + SendMenu::Details()); box->setConfirmedCallback(crl::guard(this, [=]( Ui::PreparedList &&list, @@ -1543,12 +1542,6 @@ void ShortcutMessages::sendInlineResult( return; } sendInlineResult(result, bot, {}, std::nullopt); - //const auto callback = [=](Api::SendOptions options) { - // sendInlineResult(result, bot, options); - //}; - //Ui::show( - // PrepareScheduleBox(this, sendMenuType(), callback), - // Ui::LayerOption::KeepOther); } void ShortcutMessages::sendInlineResult( diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index a4d57f791..cea289668 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1141,7 +1141,7 @@ void Filler::addCreatePoll() { flag, flag, source, - sendMenuType); + { sendMenuType }); }; _addAction( tr::lng_polls_create(tr::now), @@ -1589,7 +1589,7 @@ void PeerMenuCreatePoll( PollData::Flags chosen, PollData::Flags disabled, Api::SendType sendType, - SendMenu::Type sendMenuType) { + SendMenu::Details sendMenuDetails) { if (peer->isChannel() && !peer->isMegagroup()) { chosen &= ~PollData::Flag::PublicVotes; disabled |= PollData::Flag::PublicVotes; @@ -1599,7 +1599,7 @@ void PeerMenuCreatePoll( chosen, disabled, sendType, - sendMenuType); + sendMenuDetails); const auto weak = Ui::MakeWeak(box.data()); const auto lock = box->lifetime().make_state(false); box->submitRequests( @@ -2071,17 +2071,14 @@ QPointer ShowForwardMessagesBox( state->menu->addSeparator(); } - const auto type = sendMenuType(); + state->menu->setForcedVerticalOrigin( + Ui::PopupMenu::VerticalOrigin::Bottom); const auto result = SendMenu::FillSendMenu( state->menu.get(), - type, - SendMenu::DefaultSilentCallback(submit), - SendMenu::DefaultScheduleCallback(show, type, submit), - SendMenu::DefaultWhenOnlineCallback(submit)); - const auto success = (result == SendMenu::FillMenuResult::Success); - if (showForwardOptions || success) { - state->menu->setForcedVerticalOrigin( - Ui::PopupMenu::VerticalOrigin::Bottom); + show, + SendMenu::Details{ sendMenuType() }, + SendMenu::DefaultCallback(show, crl::guard(parent, submit))); + if (showForwardOptions || !state->menu->empty()) { state->menu->popup(QCursor::pos()); } }; diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index cc4f56178..4bf6c4d13 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -91,7 +91,7 @@ void PeerMenuCreatePoll( PollData::Flags chosen = PollData::Flags(), PollData::Flags disabled = PollData::Flags(), Api::SendType sendType = Api::SendType::Normal, - SendMenu::Type sendMenuType = SendMenu::Type::Scheduled); + SendMenu::Details sendMenuDetails = SendMenu::Details()); void PeerMenuDeleteTopicWithConfirmation( not_null navigation, not_null topic); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index d611652f7..37acebe47 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -122,7 +122,7 @@ public: rpl::producer<> pauseChanged() const override; rpl::producer adjustShadowLeft() const override; - SendMenu::Type sendMenuType() const override; + SendMenu::Details sendMenuDetails() const override; bool showMediaPreview( Data::FileOrigin origin, @@ -272,12 +272,12 @@ rpl::producer MainWindowShow::adjustShadowLeft() const { }); } -SendMenu::Type MainWindowShow::sendMenuType() const { +SendMenu::Details MainWindowShow::sendMenuDetails() const { const auto window = _window.get(); if (!window) { - return SendMenu::Type::Disabled; + return SendMenu::Details(); } - return window->content()->sendMenuType(); + return window->content()->sendMenuDetails(); } bool MainWindowShow::showMediaPreview( From e120ae6ae6bf526e21125d8fd86f09589c49a65e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 May 2024 15:00:30 +0400 Subject: [PATCH 056/225] Pass effect to API for sending. --- Telegram/SourceFiles/api/api_polls.cpp | 3 + Telegram/SourceFiles/api/api_sending.cpp | 9 + Telegram/SourceFiles/apiwrap.cpp | 13 +- .../media/stories/media_stories_share.cpp | 15 +- Telegram/SourceFiles/menu/menu_send.cpp | 165 +++++++++++++++++- 5 files changed, 194 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index b2422af25..52a5f6d6f 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -68,6 +68,9 @@ void Polls::create( if (action.options.shortcutId) { sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; } + if (action.options.effectId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_effect; + } const auto sendAs = action.options.sendAs; if (sendAs) { sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 9528ae446..cbe617b75 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -133,6 +133,9 @@ void SendExistingMedia( flags |= MessageFlag::ShortcutMessage; sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; } + if (action.options.effectId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_effect; + } session->data().registerMessageRandomId(randomId, newId); @@ -144,6 +147,7 @@ void SendExistingMedia( .date = HistoryItem::NewMessageDate(action.options), .shortcutId = action.options.shortcutId, .postAuthor = messagePostAuthor, + .effectId = action.options.effectId, }, media, caption); const auto performRequest = [=](const auto &repeatRequest) -> void { @@ -307,6 +311,9 @@ bool SendDice(MessageToSend &message) { flags |= MessageFlag::ShortcutMessage; sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; } + if (action.options.effectId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_effect; + } session->data().registerMessageRandomId(randomId, newId); @@ -318,6 +325,7 @@ bool SendDice(MessageToSend &message) { .date = HistoryItem::NewMessageDate(action.options), .shortcutId = action.options.shortcutId, .postAuthor = messagePostAuthor, + .effectId = action.options.effectId, }, TextWithEntities(), MTP_messageMediaDice( MTP_int(0), MTP_string(emoji))); @@ -512,6 +520,7 @@ void SendConfirmedFile( .shortcutId = file->to.options.shortcutId, .postAuthor = messagePostAuthor, .groupedId = groupId, + .effectId = file->to.options.effectId, }, caption, media); } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index c16926c73..0e96bcec1 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3383,6 +3383,7 @@ void ApiWrap::forwardMessages( .date = HistoryItem::NewMessageDate(action.options), .shortcutId = action.options.shortcutId, .postAuthor = messagePostAuthor, + .effectId = action.options.effectId, }, item); _session->data().registerMessageRandomId(randomId, newId); if (!localIds) { @@ -3483,6 +3484,7 @@ void ApiWrap::sendSharedContact( .date = HistoryItem::NewMessageDate(action.options), .shortcutId = action.options.shortcutId, .postAuthor = messagePostAuthor, + .effectId = action.options.effectId, }, TextWithEntities(), MTP_messageMediaContact( MTP_string(phone), MTP_string(firstName), @@ -3816,6 +3818,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_quick_reply_shortcut; mediaFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; } + if (action.options.effectId) { + sendFlags |= MTPmessages_SendMessage::Flag::f_effect; + mediaFlags |= MTPmessages_SendMedia::Flag::f_effect; + } lastMessage = history->addNewLocalMessage({ .id = newId.msg, .flags = flags, @@ -3824,6 +3830,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { .date = HistoryItem::NewMessageDate(action.options), .shortcutId = action.options.shortcutId, .postAuthor = messagePostAuthor, + .effectId = action.options.effectId, }, sending, media); const auto done = [=]( const MTPUpdates &result, @@ -4162,7 +4169,8 @@ void ApiWrap::sendMediaWithRandomId( | (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0)) | (options.scheduled ? Flag::f_schedule_date : Flag(0)) | (options.sendAs ? Flag::f_send_as : Flag(0)) - | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)); + | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) + | (options.effectId ? Flag::f_effect : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; @@ -4271,7 +4279,8 @@ void ApiWrap::sendAlbumIfReady(not_null album) { | (sendAs ? Flag::f_send_as : Flag(0)) | (album->options.shortcutId ? Flag::f_quick_reply_shortcut - : Flag(0)); + : Flag(0)) + | (album->options.effectId ? Flag::f_effect : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; histories.sendPreparedMessage( diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index bfd0e5c00..9d32a126a 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -128,9 +128,7 @@ namespace Media::Stories { if (action.replyTo) { sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } - const auto silentPost = ShouldSendSilent( - threadPeer, - action.options); + const auto silentPost = ShouldSendSilent(threadPeer, options); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } @@ -140,6 +138,9 @@ namespace Media::Stories { if (options.shortcutId) { sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut; } + if (options.effectId) { + sendFlags |= MTPmessages_SendMedia::Flag::f_effect; + } const auto done = [=] { if (!--state->requests) { if (show->valid()) { @@ -161,12 +162,10 @@ namespace Media::Stories { MTP_long(randomId), MTPReplyMarkup(), MTPVector(), - MTP_int(action.options.scheduled), + MTP_int(options.scheduled), MTP_inputPeerEmpty(), - Data::ShortcutIdToMTP( - session, - action.options.shortcutId), - MTP_long(action.options.effectId) + Data::ShortcutIdToMTP(session, options.shortcutId), + MTP_long(options.effectId) ), [=]( const MTPUpdates &result, const MTP::Response &response) { diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 1f216122b..6b3955915 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -12,9 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "chat_helpers/compose/compose_show.h" #include "core/shortcuts.h" +#include "history/view/media/history_view_sticker.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/history_view_schedule_box.h" #include "lang/lang_keys.h" +#include "ui/chat/chat_style.h" +#include "ui/chat/chat_theme.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "data/data_peer.h" #include "data/data_forum.h" @@ -25,6 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_unread_things.h" #include "apiwrap.h" +#include "window/themes/window_theme.h" +#include "window/section_widget.h" +#include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" @@ -33,6 +40,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace SendMenu { namespace { +class EffectPreview final : public Ui::RpWidget { +public: + EffectPreview( + not_null parent, + std::shared_ptr show, + Details details, + QPoint position, + const Data::Reaction &effect, + Fn action); +private: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + + void setupGeometry(QPoint position); + void setupBackground(); + void setupSend(Details details); + + const std::shared_ptr _show; + const std::shared_ptr _theme; + const std::unique_ptr _chatStyle; + const std::unique_ptr _send; + const Fn _actionWithEffect; + QRect _inner; + QImage _bg; + +}; + [[nodiscard]] Data::PossibleItemReactionsRef LookupPossibleEffects( not_null session) { auto result = Data::PossibleItemReactionsRef(); @@ -51,6 +85,124 @@ namespace { return result; } +void ShowEffectPreview( + not_null parent, + std::shared_ptr show, + Details details, + QPoint position, + const Data::Reaction &effect, + Fn action) { + const auto widget = Ui::CreateChild( + parent, + show, + details, + position, + effect, + action); + widget->raise(); + widget->show(); +} + +[[nodiscard]] Fn ComposeActionWithEffect( + Fn sendAction, + EffectId id) { + if (!id) { + return sendAction; + } + return [=](Action action, Details details) { + if (const auto options = std::get_if(&action)) { + options->effectId = id; + } + sendAction(action, details); + }; +} + +EffectPreview::EffectPreview( + not_null parent, + std::shared_ptr show, + Details details, + QPoint position, + const Data::Reaction &effect, + Fn action) +: RpWidget(parent) +, _show(show) +, _theme(Window::Theme::DefaultChatThemeOn(lifetime())) +, _chatStyle( + std::make_unique( + _show->session().colorIndicesValue())) +, _send( + std::make_unique( + this, + u"Send with Effect"_q,AssertIsDebug() + st::previewMarkRead)) +, _actionWithEffect(ComposeActionWithEffect(action, effect.id.custom())) { + setupGeometry(position); + setupBackground(); + setupSend(details); +} + +void EffectPreview::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + p.drawImage(0, 0, _bg); +} + +void EffectPreview::mousePressEvent(QMouseEvent *e) { + delete this; +} + +void EffectPreview::setupGeometry(QPoint position) { + const auto parent = parentWidget(); + const auto innerSize = HistoryView::Sticker::MessageEffectSize(); + const auto shadow = st::previewMenu.shadow; + const auto extend = shadow.extend; + _inner = QRect(QPoint(extend.left(), extend.top()), innerSize); + const auto size = _inner.marginsAdded(extend).size(); + const auto left = std::max( + std::min( + position.x() - size.width() / 2, + parent->width() - size.width()), + 0); + const auto topMin = std::min((parent->height() - size.height()) / 2, 0); + const auto top = std::max( + std::min( + position.y() - size.height() / 2, + parent->height() - size.height()), + topMin); + setGeometry(left, top, size.width(), size.height() + _send->height()); + _send->setGeometry(0, size.height(), size.width(), _send->height()); +} + +void EffectPreview::setupBackground() { + const auto ratio = style::DevicePixelRatio(); + _bg = QImage( + _inner.size() * ratio, + QImage::Format_ARGB32_Premultiplied); + + const auto paint = [=] { + auto p = QPainter(&_bg); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(width(), height() * 5), + QRect(QPoint(), size())); + }; + paint(); + _theme->repaintBackgroundRequests() | rpl::start_with_next([=] { + paint(); + update(); + }, lifetime()); +} + +void EffectPreview::setupSend(Details details) { + _send->setClickedCallback([=] { + _actionWithEffect(Api::SendOptions(), details); + }); + const auto type = details.type; + SetupMenuAndShortcuts(_send.get(), _show, [=] { + return Details{ .type = type }; + }, _actionWithEffect); +} + } // namespace Fn DefaultCallback( @@ -131,7 +283,18 @@ FillMenuResult FillSendMenu( (*selector)->chosen( ) | rpl::start_with_next([=](ChosenReaction chosen) { - + const auto &reactions = showForEffect->session().data().reactions(); + const auto &effects = reactions.list(Data::Reactions::Type::Effects); + const auto i = ranges::find(effects, chosen.id, &Data::Reaction::id); + if (i != end(effects)) { + ShowEffectPreview( + menu, + showForEffect, + details, + menu->mapFromGlobal(chosen.globalGeometry.center()), + *i, + action); + } }, menu->lifetime()); return FillMenuResult::Prepared; From 144109db0589096f9432087b960f6a7ec8966496 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 May 2024 16:46:14 +0400 Subject: [PATCH 057/225] Show effect preview before sending. --- Telegram/Resources/langs/lang.strings | 1 + .../data/components/scheduled_messages.cpp | 3 + Telegram/SourceFiles/menu/menu_send.cpp | 266 +++++++++++++++--- Telegram/SourceFiles/ui/chat/chat.style | 7 +- 4 files changed, 232 insertions(+), 45 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3029db104..968b7f911 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -563,6 +563,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_effect_add_title" = "Add an animated effect"; "lng_effect_stickers_title" = "Message Effects"; +"lng_effect_send" = "Send with Effect"; "lng_languages" = "Languages"; "lng_languages_none" = "No languages found."; diff --git a/Telegram/SourceFiles/data/components/scheduled_messages.cpp b/Telegram/SourceFiles/data/components/scheduled_messages.cpp index 4313a8857..c8505c47f 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/components/scheduled_messages.cpp @@ -228,6 +228,9 @@ void ScheduledMessages::sendNowSimpleMessage( : MTPDmessage::Flag(0)) | ((localFlags & MessageFlag::Outgoing) ? MTPDmessage::Flag::f_out + : MTPDmessage::Flag(0)) + | (local->effectId() + ? MTPDmessage::Flag::f_effect : MTPDmessage::Flag(0)); const auto views = 1; const auto forwards = 0; diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 6b3955915..536d988e3 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -11,15 +11,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "boxes/abstract_box.h" #include "chat_helpers/compose/compose_show.h" +#include "chat_helpers/stickers_emoji_pack.h" #include "core/shortcuts.h" #include "history/view/media/history_view_sticker.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/history_view_schedule_box.h" #include "lang/lang_keys.h" +#include "lottie/lottie_single_player.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" +#include "ui/effects/ripple_animation.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/shadow.h" +#include "ui/painter.h" +#include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_peer.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" @@ -48,25 +55,77 @@ public: Details details, QPoint position, const Data::Reaction &effect, - Fn action); + Fn action, + Fn done); + private: void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void setupGeometry(QPoint position); void setupBackground(); + void repaintBackground(); + void setupLottie(); void setupSend(Details details); + void createLottie(); + [[nodiscard]] bool checkReady(); + + const Data::Reaction _effect; const std::shared_ptr _show; const std::shared_ptr _theme; const std::unique_ptr _chatStyle; const std::unique_ptr _send; const Fn _actionWithEffect; + + QImage _icon; + std::shared_ptr _media; + QByteArray _bytes; + QString _filepath; + std::unique_ptr _lottie; + QRect _inner; QImage _bg; + rpl::lifetime _readyCheckLifetime; + }; +class BottomRounded final : public Ui::FlatButton { +public: + using FlatButton::FlatButton; + +private: + QImage prepareRippleMask() const override; + void paintEvent(QPaintEvent *e) override; + +}; + +QImage BottomRounded::prepareRippleMask() const { + const auto fill = false; + return Ui::RippleAnimation::MaskByDrawer(size(), fill, [&](QPainter &p) { + const auto radius = st::previewMenu.radius; + const auto expanded = rect().marginsAdded({ 0, 2 * radius, 0, 0 }); + p.drawRoundedRect(expanded, radius, radius); + }); +} + +void BottomRounded::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + auto hq = PainterHighQualityEnabler(p); + const auto radius = st::previewMenu.radius; + const auto expanded = rect().marginsAdded({ 0, 2 * radius, 0, 0 }); + p.setPen(Qt::NoPen); + const auto &st = st::previewMarkRead; + if (isOver()) { + p.setBrush(st.overBgColor); + } + p.drawRoundedRect(expanded, radius, radius); + p.end(); + + Ui::FlatButton::paintEvent(e); +} + [[nodiscard]] Data::PossibleItemReactionsRef LookupPossibleEffects( not_null session) { auto result = Data::PossibleItemReactionsRef(); @@ -85,35 +144,19 @@ private: return result; } -void ShowEffectPreview( - not_null parent, - std::shared_ptr show, - Details details, - QPoint position, - const Data::Reaction &effect, - Fn action) { - const auto widget = Ui::CreateChild( - parent, - show, - details, - position, - effect, - action); - widget->raise(); - widget->show(); -} - [[nodiscard]] Fn ComposeActionWithEffect( Fn sendAction, - EffectId id) { - if (!id) { - return sendAction; - } + EffectId id, + Fn done) { return [=](Action action, Details details) { if (const auto options = std::get_if(&action)) { options->effectId = id; } + const auto onstack = done; sendAction(action, details); + if (onstack) { + onstack(); + } }; } @@ -123,27 +166,49 @@ EffectPreview::EffectPreview( Details details, QPoint position, const Data::Reaction &effect, - Fn action) + Fn action, + Fn done) : RpWidget(parent) +, _effect(effect) , _show(show) , _theme(Window::Theme::DefaultChatThemeOn(lifetime())) , _chatStyle( std::make_unique( _show->session().colorIndicesValue())) , _send( - std::make_unique( + std::make_unique( this, - u"Send with Effect"_q,AssertIsDebug() - st::previewMarkRead)) -, _actionWithEffect(ComposeActionWithEffect(action, effect.id.custom())) { + tr::lng_effect_send(tr::now), + st::effectPreviewSend)) +, _actionWithEffect( + ComposeActionWithEffect( + action, + effect.id.custom(), + done)) { setupGeometry(position); setupBackground(); + setupLottie(); setupSend(details); } void EffectPreview::paintEvent(QPaintEvent *e) { auto p = QPainter(this); p.drawImage(0, 0, _bg); + + if (_lottie && _lottie->ready()) { + const auto factor = style::DevicePixelRatio(); + auto request = Lottie::FrameRequest(); + request.box = _inner.size() * factor; + const auto rightAligned = false;// hasRightLayout(); + if (!rightAligned) { + request.mirrorHorizontal = true; + } + const auto frame = _lottie->frameInfo(request); + p.drawImage( + QRect(_inner.topLeft(), frame.image.size() / factor), + frame.image); + _lottie->markFrameShown(); + } } void EffectPreview::mousePressEvent(QMouseEvent *e) { @@ -156,7 +221,8 @@ void EffectPreview::setupGeometry(QPoint position) { const auto shadow = st::previewMenu.shadow; const auto extend = shadow.extend; _inner = QRect(QPoint(extend.left(), extend.top()), innerSize); - const auto size = _inner.marginsAdded(extend).size(); + const auto size = _inner.marginsAdded(extend).size() + + QSize(0, _send->height()); const auto left = std::max( std::min( position.x() - size.width() / 2, @@ -168,29 +234,112 @@ void EffectPreview::setupGeometry(QPoint position) { position.y() - size.height() / 2, parent->height() - size.height()), topMin); - setGeometry(left, top, size.width(), size.height() + _send->height()); - _send->setGeometry(0, size.height(), size.width(), _send->height()); + setGeometry(left, top, size.width(), size.height()); + _send->setGeometry( + _inner.x(), + _inner.y() + _inner.height(), + _inner.width(), + _send->height()); } void EffectPreview::setupBackground() { const auto ratio = style::DevicePixelRatio(); _bg = QImage( - _inner.size() * ratio, + size() * ratio, QImage::Format_ARGB32_Premultiplied); + _bg.setDevicePixelRatio(ratio); + repaintBackground(); + _theme->repaintBackgroundRequests() | rpl::start_with_next([=] { + repaintBackground(); + update(); + }, lifetime()); +} - const auto paint = [=] { - auto p = QPainter(&_bg); +void EffectPreview::repaintBackground() { + const auto ratio = style::DevicePixelRatio(); + const auto inner = _inner.size() + QSize(0, _send->height()); + auto bg = QImage( + inner * ratio, + QImage::Format_ARGB32_Premultiplied); + bg.setDevicePixelRatio(ratio); + + { + auto p = QPainter(&bg); Window::SectionWidget::PaintBackground( p, _theme.get(), - QSize(width(), height() * 5), - QRect(QPoint(), size())); - }; - paint(); - _theme->repaintBackgroundRequests() | rpl::start_with_next([=] { - paint(); - update(); - }, lifetime()); + QSize(inner.width(), inner.height() * 5), + QRect(QPoint(), inner)); + + { // bubble + const auto radius = st::bubbleRadiusLarge; + const auto out = 2 * radius; + p.setPen(Qt::NoPen); + p.setBrush(_chatStyle->msgInShadow()); + const auto skip = st::msgPadding.bottom() - st::msgDateDelta.y(); + p.drawRoundedRect(-out, -out, out + inner.width() / 3, out + inner.height() / 2 + st::normalFont->height / 2 + st::msgShadow, radius, radius); + p.setBrush(_chatStyle->msgInBg()); + p.drawRoundedRect(-out, -out, out + inner.width() / 3, out + inner.height() / 2 + st::normalFont->height / 2, radius, radius); + + if (!_icon.isNull()) { + p.drawImage( + inner.width() / 3 - _icon.width(), + inner.height() / 2 + st::normalFont->height / 2 - _icon.height(), + _icon); + } + } + + p.fillRect( + QRect(0, _inner.height(), _inner.width(), _send->height()), + st::previewMarkRead.bgColor); + auto hq = PainterHighQualityEnabler(p); + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + auto roundRect = Ui::RoundRect(st::previewMenu.radius, st::menuBg); + roundRect.paint(p, QRect(QPoint(), inner), RectPart::AllCorners); + } + + _bg.fill(Qt::transparent); + auto p = QPainter(&_bg); + const auto &shadow = st::previewMenu.animation.shadow; + const auto shadowed = QRect(_inner.topLeft(), inner); + Ui::Shadow::paint(p, shadowed, width(), shadow); + p.drawImage(_inner.topLeft(), bg); +} + +void EffectPreview::setupLottie() { + const auto id = _effect.id.custom(); + const auto reactions = &_show->session().data().reactions(); + reactions->preloadEffectImageFor(id); + + if (const auto document = _effect.aroundAnimation) { + _media = document->createMediaView(); + } else { + _media = _effect.selectAnimation->createMediaView(); + } + rpl::single(rpl::empty) | rpl::then( + _show->session().downloaderTaskFinished() + ) | rpl::start_with_next([=] { + if (checkReady()) { + _readyCheckLifetime.destroy(); + createLottie(); + } + }, _readyCheckLifetime); +} + +void EffectPreview::createLottie() { + _lottie = _show->session().emojiStickersPack().effectPlayer( + _media->owner(), + _bytes, + _filepath, + Stickers::EffectType::MessageEffect); + const auto raw = _lottie.get(); + raw->updates( + ) | rpl::start_with_next([=](Lottie::Update update) { + v::match(update.data, [&](const Lottie::Information &information) { + }, [&](const Lottie::DisplayFrameRequest &request) { + this->update(); + }); + }, raw->lifetime()); } void EffectPreview::setupSend(Details details) { @@ -203,6 +352,22 @@ void EffectPreview::setupSend(Details details) { }, _actionWithEffect); } +bool EffectPreview::checkReady() { + if (_icon.isNull()) { + const auto reactions = &_show->session().data().reactions(); + _icon = reactions->resolveEffectImageFor(_effect.id.custom()); + repaintBackground(); + update(); + } + if (_effect.aroundAnimation) { + _bytes = _media->bytes(); + _filepath = _media->owner()->filepath(); + } else { + _bytes = _media->videoThumbnailContent(); + } + return !_icon.isNull() && (!_bytes.isEmpty() || !_filepath.isEmpty()); +} + } // namespace Fn DefaultCallback( @@ -281,19 +446,32 @@ FillMenuResult FillSendMenu( return FillMenuResult::Prepared; } + const auto effect = std::make_shared>(); (*selector)->chosen( ) | rpl::start_with_next([=](ChosenReaction chosen) { const auto &reactions = showForEffect->session().data().reactions(); const auto &effects = reactions.list(Data::Reactions::Type::Effects); const auto i = ranges::find(effects, chosen.id, &Data::Reaction::id); if (i != end(effects)) { - ShowEffectPreview( + if (const auto strong = effect->data()) { + delete strong; + } + const auto weak = Ui::MakeWeak(menu); + const auto done = [=] { + delete effect->data(); + if (const auto strong = weak.data()) { + strong->hideMenu(true); + } + }; + *effect = Ui::CreateChild( menu, showForEffect, details, menu->mapFromGlobal(chosen.globalGeometry.center()), *i, - action); + action, + crl::guard(menu, done)); + (*effect)->show(); } }, menu->lifetime()); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 302c5b593..f194dc8aa 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1101,7 +1101,7 @@ previewTop: PeerListItem(defaultPeerListItem) { photoSize: 40px; } previewMarkRead: FlatButton(historyComposeButton) { - height: 40px; + height: 39px; textTop: 10px; } previewName: FlatLabel(defaultFlatLabel) { @@ -1115,3 +1115,8 @@ previewUserpic: UserpicButton(defaultUserpicButton) { size: size(40px, 40px); photoSize: 40px; } + +effectPreviewSend: FlatButton(previewMarkRead) { + bgColor: transparent; + overBgColor: transparent; +} From f371cd1af2c1be3c08fe75bb519047259b34a711 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 11 May 2024 17:46:40 +0400 Subject: [PATCH 058/225] Use nice fake message for effect preview. --- Telegram/CMakeLists.txt | 2 + .../boxes/reactions_settings_box.cpp | 56 +------- .../history/view/history_view_fake_items.cpp | 66 ++++++++++ .../history/view/history_view_fake_items.h | 26 ++++ Telegram/SourceFiles/menu/menu_send.cpp | 122 +++++++++++++----- 5 files changed, 191 insertions(+), 81 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_fake_items.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_fake_items.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6426c6446..21e22150d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -799,6 +799,8 @@ PRIVATE history/view/history_view_emoji_interactions.h history/view/history_view_empty_list_bubble.cpp history/view/history_view_empty_list_bubble.h + history/view/history_view_fake_items.cpp + history/view/history_view_fake_items.h history/view/history_view_group_call_bar.cpp history/view/history_view_group_call_bar.h history/view/history_view_item_preview.h diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index e33624403..8a71e395b 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -16,8 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/admin_log/history_admin_log_item.h" #include "history/history.h" #include "history/history_item.h" -#include "history/view/history_view_element.h" #include "history/view/reactions/history_view_reactions_strip.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_fake_items.h" #include "lang/lang_keys.h" #include "boxes/premium_preview_box.h" #include "main/main_session.h" @@ -43,53 +44,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -PeerId GenerateUser(not_null history, const QString &name) { - Expects(history->peer->isUser()); - - const auto peerId = Data::FakePeerIdForJustName(name); - history->owner().processUser(MTP_user( - MTP_flags(MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_min), - peerToBareMTPInt(peerId), - MTP_long(0), - MTP_string(tr::lng_settings_chat_message_reply_from(tr::now)), - MTPstring(), // last name - MTPstring(), // username - MTPstring(), // phone - MTPUserProfilePhoto(), // profile photo - MTPUserStatus(), // status - MTP_int(0), // bot info version - MTPVector(), // restrictions - MTPstring(), // bot placeholder - MTPstring(), // lang code - MTPEmojiStatus(), - MTPVector(), - MTPint(), // stories_max_id - MTPPeerColor(), // color - MTPPeerColor())); // profile_color - return peerId; -} - -AdminLog::OwnedItem GenerateItem( - not_null delegate, - not_null history, - PeerId from, - FullMsgId replyTo, - const QString &text) { - Expects(history->peer->isUser()); - - const auto item = history->addNewLocalMessage({ - .id = history->nextNonHistoryEntryId(), - .flags = (MessageFlag::FakeHistoryItem - | MessageFlag::HasFromId - | MessageFlag::HasReplyInfo), - .from = from, - .replyTo = FullReplyTo{ .messageId = replyTo }, - .date = base::unixtime::now(), - }, TextWithEntities{ .text = text }, MTP_messageMediaEmpty()); - - return AdminLog::OwnedItem(delegate, item); -} - void AddMessage( not_null container, not_null controller, @@ -135,15 +89,15 @@ void AddMessage( const auto history = controller->session().data().history( PeerData::kServiceNotificationsId); - state->reply = GenerateItem( + state->reply = HistoryView::GenerateItem( state->delegate.get(), history, - GenerateUser( + HistoryView::GenerateUser( history, tr::lng_settings_chat_message_reply_from(tr::now)), FullMsgId(), tr::lng_settings_chat_message_reply(tr::now)); - auto message = GenerateItem( + auto message = HistoryView::GenerateItem( state->delegate.get(), history, history->peer->id, diff --git a/Telegram/SourceFiles/history/view/history_view_fake_items.cpp b/Telegram/SourceFiles/history/view/history_view_fake_items.cpp new file mode 100644 index 000000000..5a6538036 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_fake_items.cpp @@ -0,0 +1,66 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_fake_items.h" + +#include "base/unixtime.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" + +namespace HistoryView { + +AdminLog::OwnedItem GenerateItem( + not_null delegate, + not_null history, + PeerId from, + FullMsgId replyTo, + const QString &text, + EffectId effectId) { + Expects(history->peer->isUser()); + + const auto item = history->addNewLocalMessage({ + .id = history->nextNonHistoryEntryId(), + .flags = (MessageFlag::FakeHistoryItem + | MessageFlag::HasFromId + | MessageFlag::HasReplyInfo), + .from = from, + .replyTo = FullReplyTo{.messageId = replyTo }, + .date = base::unixtime::now(), + .effectId = effectId, + }, TextWithEntities{ .text = text }, MTP_messageMediaEmpty()); + + return AdminLog::OwnedItem(delegate, item); +} + +PeerId GenerateUser(not_null history, const QString &name) { + Expects(history->peer->isUser()); + + const auto peerId = Data::FakePeerIdForJustName(name); + history->owner().processUser(MTP_user( + MTP_flags(MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_min), + peerToBareMTPInt(peerId), + MTP_long(0), + MTP_string(name), + MTPstring(), // last name + MTPstring(), // username + MTPstring(), // phone + MTPUserProfilePhoto(), // profile photo + MTPUserStatus(), // status + MTP_int(0), // bot info version + MTPVector(), // restrictions + MTPstring(), // bot placeholder + MTPstring(), // lang code + MTPEmojiStatus(), + MTPVector(), + MTPint(), // stories_max_id + MTPPeerColor(), // color + MTPPeerColor())); // profile_color + return peerId; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_fake_items.h b/Telegram/SourceFiles/history/view/history_view_fake_items.h new file mode 100644 index 000000000..266c28004 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_fake_items.h @@ -0,0 +1,26 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/admin_log/history_admin_log_item.h" + +namespace HistoryView { + +[[nodiscard]] AdminLog::OwnedItem GenerateItem( + not_null delegate, + not_null history, + PeerId from, + FullMsgId replyTo, + const QString &text, + EffectId effectId = 0); + +[[nodiscard]] PeerId GenerateUser( + not_null history, + const QString &name); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 536d988e3..f04662997 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -9,17 +9,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_common.h" #include "base/event_filter.h" +#include "base/unixtime.h" #include "boxes/abstract_box.h" #include "chat_helpers/compose/compose_show.h" #include "chat_helpers/stickers_emoji_pack.h" #include "core/shortcuts.h" +#include "history/admin_log/history_admin_log_item.h" #include "history/view/media/history_view_sticker.h" #include "history/view/reactions/history_view_reactions_selector.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_fake_items.h" #include "history/view/history_view_schedule_box.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_unread_things.h" #include "lang/lang_keys.h" #include "lottie/lottie_single_player.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" +#include "ui/effects/path_shift_gradient.h" #include "ui/effects/ripple_animation.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" @@ -33,20 +41,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_message_reactions.h" #include "data/data_session.h" #include "main/main_session.h" -#include "history/history.h" -#include "history/history_unread_things.h" #include "apiwrap.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" +#include "styles/style_window.h" #include namespace SendMenu { namespace { +class Delegate final : public HistoryView::DefaultElementDelegate { +public: + Delegate(not_null pathGradient) + : _pathGradient(pathGradient) { + } + +private: + bool elementAnimationsPaused() override { + return false; + } + not_null elementPathShiftGradient() override { + return _pathGradient; + } + HistoryView::Context elementContext() override { + return HistoryView::Context::ContactPreview; + } + + const not_null _pathGradient; +}; + class EffectPreview final : public Ui::RpWidget { public: EffectPreview( @@ -64,6 +91,7 @@ private: void setupGeometry(QPoint position); void setupBackground(); + void setupItem(); void repaintBackground(); void setupLottie(); void setupSend(Details details); @@ -71,10 +99,16 @@ private: [[nodiscard]] bool checkReady(); + const EffectId _effectId = 0; const Data::Reaction _effect; const std::shared_ptr _show; const std::shared_ptr _theme; const std::unique_ptr _chatStyle; + const std::unique_ptr _pathGradient; + const std::unique_ptr _delegate; + const not_null _history; + const AdminLog::OwnedItem _replyTo; + const AdminLog::OwnedItem _item; const std::unique_ptr _send; const Fn _actionWithEffect; @@ -86,6 +120,7 @@ private: QRect _inner; QImage _bg; + QPoint _itemShift; rpl::lifetime _readyCheckLifetime; @@ -169,37 +204,67 @@ EffectPreview::EffectPreview( Fn action, Fn done) : RpWidget(parent) +, _effectId(effect.id.custom()) , _effect(effect) , _show(show) , _theme(Window::Theme::DefaultChatThemeOn(lifetime())) , _chatStyle( std::make_unique( _show->session().colorIndicesValue())) +, _pathGradient( + HistoryView::MakePathShiftGradient(_chatStyle.get(), [=] { update(); })) +, _delegate(std::make_unique(_pathGradient.get())) +, _history(show->session().data().history( + PeerData::kServiceNotificationsId)) +, _replyTo(HistoryView::GenerateItem( + _delegate.get(), + _history, + HistoryView::GenerateUser( + _history, + tr::lng_settings_chat_message_reply_from(tr::now)), + FullMsgId(), + tr::lng_settings_chat_message(tr::now))) +, _item(HistoryView::GenerateItem( + _delegate.get(), + _history, + _history->peer->id, + _replyTo->data()->fullId(), + tr::lng_settings_chat_message_reply(tr::now), + _effectId)) , _send( std::make_unique( this, tr::lng_effect_send(tr::now), st::effectPreviewSend)) -, _actionWithEffect( - ComposeActionWithEffect( - action, - effect.id.custom(), - done)) { +, _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) { setupGeometry(position); setupBackground(); + setupItem(); setupLottie(); setupSend(details); } void EffectPreview::paintEvent(QPaintEvent *e) { - auto p = QPainter(this); + auto p = Painter(this); p.drawImage(0, 0, _bg); + p.setClipRect(_inner); + p.translate(_itemShift); + auto rect = QRect(0, 0, st::windowMinWidth, _inner.height()); + auto context = _theme->preparePaintContext( + _chatStyle.get(), + rect, + rect, + false); + context.outbg = _item->hasOutLayout(); + _item->draw(p, context); + p.translate(-_itemShift); + if (_lottie && _lottie->ready()) { const auto factor = style::DevicePixelRatio(); auto request = Lottie::FrameRequest(); request.box = _inner.size() * factor; - const auto rightAligned = false;// hasRightLayout(); + const auto rightAligned = _item->hasRightLayout(); if (!rightAligned) { request.mirrorHorizontal = true; } @@ -255,6 +320,22 @@ void EffectPreview::setupBackground() { }, lifetime()); } +void EffectPreview::setupItem() { + _item->resizeGetHeight(st::windowMinWidth); + + const auto icon = _item->effectIconGeometry(); + Assert(!icon.isEmpty()); + + const auto size = _inner.size(); + const auto shift = _item->hasRightLayout() + ? (-size.width() / 3) + : (size.width() / 3); + const auto position = QPoint( + shift + icon.x() + (icon.width() - size.width()) / 2, + icon.y() + (icon.height() - size.height()) / 2); + _itemShift = _inner.topLeft() - position; +} + void EffectPreview::repaintBackground() { const auto ratio = style::DevicePixelRatio(); const auto inner = _inner.size() + QSize(0, _send->height()); @@ -270,25 +351,6 @@ void EffectPreview::repaintBackground() { _theme.get(), QSize(inner.width(), inner.height() * 5), QRect(QPoint(), inner)); - - { // bubble - const auto radius = st::bubbleRadiusLarge; - const auto out = 2 * radius; - p.setPen(Qt::NoPen); - p.setBrush(_chatStyle->msgInShadow()); - const auto skip = st::msgPadding.bottom() - st::msgDateDelta.y(); - p.drawRoundedRect(-out, -out, out + inner.width() / 3, out + inner.height() / 2 + st::normalFont->height / 2 + st::msgShadow, radius, radius); - p.setBrush(_chatStyle->msgInBg()); - p.drawRoundedRect(-out, -out, out + inner.width() / 3, out + inner.height() / 2 + st::normalFont->height / 2, radius, radius); - - if (!_icon.isNull()) { - p.drawImage( - inner.width() / 3 - _icon.width(), - inner.height() / 2 + st::normalFont->height / 2 - _icon.height(), - _icon); - } - } - p.fillRect( QRect(0, _inner.height(), _inner.width(), _send->height()), st::previewMarkRead.bgColor); @@ -300,6 +362,7 @@ void EffectPreview::repaintBackground() { _bg.fill(Qt::transparent); auto p = QPainter(&_bg); + const auto &shadow = st::previewMenu.animation.shadow; const auto shadowed = QRect(_inner.topLeft(), inner); Ui::Shadow::paint(p, shadowed, width(), shadow); @@ -307,9 +370,8 @@ void EffectPreview::repaintBackground() { } void EffectPreview::setupLottie() { - const auto id = _effect.id.custom(); const auto reactions = &_show->session().data().reactions(); - reactions->preloadEffectImageFor(id); + reactions->preloadEffectImageFor(_effectId); if (const auto document = _effect.aroundAnimation) { _media = document->createMediaView(); From 5fb7992b04589ed8e66b8f3dfc7add1a45d0d56b Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 11 May 2024 19:08:22 +0400 Subject: [PATCH 059/225] Improve effect initial auto-play. --- Telegram/SourceFiles/data/data_types.h | 2 +- Telegram/SourceFiles/history/history.cpp | 7 +++++ Telegram/SourceFiles/history/history.h | 1 + .../history/history_inner_widget.cpp | 24 +++++++++++------ .../history/history_inner_widget.h | 1 + Telegram/SourceFiles/history/history_item.cpp | 27 +++++++++++++++---- Telegram/SourceFiles/history/history_item.h | 2 ++ .../SourceFiles/history/history_widget.cpp | 3 +++ 8 files changed, 53 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 4d0e5b3d7..66e041c0f 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -322,7 +322,7 @@ enum class MessageFlag : uint64 { ShortcutMessage = (1ULL << 44), - EffectWatchedLocal = (1ULL << 45), + EffectWatched = (1ULL << 45), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 02a3324b9..754791e80 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1753,6 +1753,10 @@ MsgId History::loadAroundId() const { return MsgId(0); } +bool History::inboxReadTillKnown() const { + return _inboxReadBefore.has_value(); +} + MsgId History::inboxReadTillId() const { return _inboxReadBefore.value_or(1) - 1; } @@ -2927,6 +2931,9 @@ void History::setInboxReadTill(MsgId upTo) { accumulate_max(*_inboxReadBefore, upTo + 1); } else { _inboxReadBefore = upTo + 1; + for (const auto &item : _items) { + item->applyEffectWatchedOnUnreadKnown(); + } } } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 729b66045..73695785f 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -218,6 +218,7 @@ public: void outboxRead(MsgId upTo); void outboxRead(not_null wasRead); [[nodiscard]] MsgId loadAroundId() const; + [[nodiscard]] bool inboxReadTillKnown() const; [[nodiscard]] MsgId inboxReadTillId() const; [[nodiscard]] MsgId outboxReadTillId() const; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 3b51df0d7..38483247d 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -908,6 +908,14 @@ Ui::ChatPaintContext HistoryInner::preparePaintContext( }); } +void HistoryInner::startEffectOnRead(not_null item) { + if (item->history() == _history) { + if (const auto view = item->mainView()) { + _emojiInteractions->playEffectOnRead(view); + } + } +} + void HistoryInner::paintEvent(QPaintEvent *e) { if (_controller->contentOverlapped(this, e) || hasPendingResizedItems()) { @@ -962,13 +970,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) { _translateTracker->add(_pinnedItem); } _translateTracker->finishBunch(); + if (!startEffects.empty()) { + for (const auto &view : startEffects) { + _emojiInteractions->playEffectOnRead(view); + } + } if (readTill && _widget->markingMessagesRead()) { session().data().histories().readInboxTill(readTill); - if (!startEffects.empty()) { - for (const auto &view : startEffects) { - _emojiInteractions->playEffectOnRead(view); - } - } } if (markingAsViewed && !readContents.empty()) { session().api().markContentsRead(readContents); @@ -1002,9 +1010,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) { session().sponsoredMessages().view(item->fullId()); } else if (isUnread) { readTill = item; - if (item->hasUnwatchedEffect()) { - startEffects.emplace(view); - } + } + if (markingAsViewed && item->hasUnwatchedEffect()) { + startEffects.emplace(view); } if (markingAsViewed && item->hasViews()) { session().api().views().scheduleIncrement(item); diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 2ab1207a1..5a11b2944 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -174,6 +174,7 @@ public: not_null view, Element *replacing); + void startEffectOnRead(not_null item); void updateBotInfo(bool recount = true); bool wasSelectedText() const; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 7e929a816..7cb74b189 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -369,6 +369,9 @@ HistoryItem::HistoryItem( }) { _boostsApplied = data.vfrom_boosts_applied().value_or_empty(); + // Called only for server-received messages, not locally created ones. + applyInitialEffectWatched(); + const auto media = data.vmedia(); const auto checked = media ? CheckMessageMedia(*media) @@ -1287,17 +1290,14 @@ bool HistoryItem::hasUnreadReaction() const { } bool HistoryItem::hasUnwatchedEffect() const { - return !out() - && effectId() - && !(_flags & MessageFlag::EffectWatchedLocal) - && unread(history()); + return effectId() && !(_flags & MessageFlag::EffectWatched); } bool HistoryItem::markEffectWatched() { if (!hasUnwatchedEffect()) { return false; } - _flags |= MessageFlag::EffectWatchedLocal; + _flags |= MessageFlag::EffectWatched; return true; } @@ -3480,6 +3480,23 @@ void HistoryItem::setupForwardedComponent(const CreateConfig &config) { forwarded->imported = config.imported; } +void HistoryItem::applyInitialEffectWatched() { + if (!effectId()) { + return; + } else if (out()) { + // If this message came from the server, not generated on send. + _flags |= MessageFlag::EffectWatched; + } else if (_history->inboxReadTillId() && !unread(_history)) { + _flags |= MessageFlag::EffectWatched; + } +} + +void HistoryItem::applyEffectWatchedOnUnreadKnown() { + if (effectId() && !out() && !unread(_history)) { + _flags |= MessageFlag::EffectWatched; + } +} + bool HistoryItem::generateLocalEntitiesByReply() const { using namespace HistoryView; if (!_media) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 1a276a447..1b006e35a 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -400,6 +400,7 @@ public: void setPostAuthor(const QString &author); void setRealId(MsgId newId); void incrementReplyToTopCounter(); + void applyEffectWatchedOnUnreadKnown(); [[nodiscard]] bool emptyText() const { return _text.empty(); @@ -547,6 +548,7 @@ private: void createComponentsHelper(HistoryItemCommonFields &&fields); void createComponents(CreateConfig &&config); void setupForwardedComponent(const CreateConfig &config); + void applyInitialEffectWatched(); [[nodiscard]] bool generateLocalEntitiesByReply() const; [[nodiscard]] TextWithEntities withLocalEntities( diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e76900180..ca3803cff 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -3255,6 +3255,9 @@ void HistoryWidget::newItemAdded(not_null item) { if (item->showNotification()) { destroyUnreadBar(); if (markingMessagesRead()) { + if (_list && item->hasUnwatchedEffect()) { + _list->startEffectOnRead(item); + } if (item->isUnreadMention() && !item->isUnreadMedia()) { session().api().markContentsRead(item); } From b92a05011f1f079731ad539aeafe65c75e21bf32 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 12 May 2024 12:49:28 +0400 Subject: [PATCH 060/225] Show sticker effects in a StickerListWidget. --- .../chat_helpers/chat_helpers.style | 1 + .../chat_helpers/stickers_list_widget.cpp | 104 ++++++++++++++---- .../chat_helpers/stickers_list_widget.h | 14 ++- .../data/data_message_reactions.cpp | 3 + .../SourceFiles/data/data_message_reactions.h | 2 + .../history_view_reactions_selector.cpp | 62 +++++++++-- .../history_view_reactions_selector.h | 4 + Telegram/SourceFiles/menu/menu_send.cpp | 7 +- 8 files changed, 163 insertions(+), 34 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 73dc78f23..538c247b9 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -489,6 +489,7 @@ hashtagClose: IconButton { stickerPanWidthMin: 64px; stickerPanSize: size(stickerPanWidthMin, stickerPanWidthMin); +stickerEffectWidthMin: 48px; stickerPanPadding: 11px; stickerPanDeleteIconBg: icon {{ "emoji/emoji_delete_bg", stickerPanDeleteBg }}; stickerPanDeleteIconFg: icon {{ "emoji/emoji_delete", stickerPanDeleteFg }}; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 1425f6dd8..cc718fae7 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -189,8 +189,10 @@ StickersListWidget::StickersListWidget( , _overBg(st::roundRadiusLarge, st().overBg) , _api(&session().mtp()) , _localSetsManager(std::make_unique(&session())) +, _customRecentIds(std::move(descriptor.customRecentList)) , _section(Section::Stickers) , _isMasks(_mode == Mode::Masks) +, _isEffects(_mode == Mode::MessageEffects) , _updateItemsTimer([=] { updateItems(); }) , _updateSetsTimer([=] { updateSets(); }) , _trendingAddBgOver( @@ -220,9 +222,11 @@ StickersListWidget::StickersListWidget( , _premiumMark(std::make_unique(&session())) , _searchRequestTimer([=] { sendSearchRequest(); }) { setMouseTracking(true); - setAttribute(Qt::WA_OpaquePaintEvent); + if (st().bg->c.alpha() > 0) { + setAttribute(Qt::WA_OpaquePaintEvent); + } - if (!_isMasks) { + if (!_isMasks && !_isEffects) { setupSearch(); } @@ -254,23 +258,30 @@ StickersListWidget::StickersListWidget( refreshStickers(); }, lifetime()); - session().data().stickers().recentUpdated( - _isMasks ? Data::StickersType::Masks : Data::StickersType::Stickers - ) | rpl::start_with_next([=] { - refreshRecent(); - }, lifetime()); + if (!_isEffects) { + session().data().stickers().recentUpdated(_isMasks + ? Data::StickersType::Masks + : Data::StickersType::Stickers + ) | rpl::start_with_next([=] { + refreshRecent(); + }, lifetime()); + } positionValue( ) | rpl::skip(1) | rpl::map_to( TabbedSelector::Action::Update ) | rpl::start_to_stream(_choosingUpdated, lifetime()); - rpl::merge( - Data::AmPremiumValue(&session()) | rpl::to_empty, - session().api().premium().cloudSetUpdated() - ) | rpl::start_with_next([=] { + if (_isEffects) { refreshStickers(); - }, lifetime()); + } else { + rpl::merge( + Data::AmPremiumValue(&session()) | rpl::to_empty, + session().api().premium().cloudSetUpdated() + ) | rpl::start_with_next([=] { + refreshStickers(); + }, lifetime()); + } } rpl::producer StickersListWidget::chosen() const { @@ -498,11 +509,14 @@ StickersListWidget::SectionInfo StickersListWidget::sectionInfoByOffset(int yOff } int StickersListWidget::countDesiredHeight(int newWidth) { - if (newWidth <= st::stickerPanWidthMin) { + const auto minSize = _isEffects + ? st::stickerEffectWidthMin + : st::stickerPanWidthMin; + if (newWidth < 2 * minSize) { return 0; } auto availableWidth = newWidth - (st::stickerPanPadding - st().margin.left()); - auto columnCount = availableWidth / st::stickerPanWidthMin; + auto columnCount = availableWidth / minSize; auto singleWidth = availableWidth / columnCount; auto fullWidth = (st().margin.left() + newWidth + st::emojiScroll.width); auto rowsRight = (fullWidth - columnCount * singleWidth) / 2; @@ -872,7 +886,9 @@ QRect StickersListWidget::stickerRect(int section, int sel) { void StickersListWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); - p.fillRect(clip, st().bg); + if (st().bg->c.alpha() > 0) { + p.fillRect(clip, st().bg); + } paintStickers(p, clip); } @@ -1459,7 +1475,21 @@ void StickersListWidget::paintSticker( p.setOpacity(1.); } - if (premium) { + auto cornerPainted = false; + if (set.id == Data::Stickers::RecentSetId && !_cornerEmoji.empty()) { + Assert(index < _cornerEmoji.size()); + if (const auto emoji = _cornerEmoji[index]) { + const auto size = Ui::Emoji::GetSizeNormal(); + const auto ratio = style::DevicePixelRatio(); + const auto radius = st::roundRadiusSmall; + const auto position = pos + + QPoint(_singleSize.width(), _singleSize.height()) + - QPoint(size / ratio + radius, size / ratio + radius); + Ui::Emoji::Draw(p, emoji, size, position.x(), position.y()); + cornerPainted = true; + } + } + if (!cornerPainted && premium) { _premiumMark->paint( p, lottieFrame, @@ -1928,10 +1958,13 @@ void StickersListWidget::clearHeavyData() { void StickersListWidget::refreshStickers() { clearSelection(); - refreshMySets(); - refreshFeaturedSets(); - refreshSearchSets(); - + if (_isEffects) { + refreshEffects(); + } else { + refreshMySets(); + refreshFeaturedSets(); + refreshSearchSets(); + } resizeToWidth(width()); if (_footer) { @@ -1946,6 +1979,13 @@ void StickersListWidget::refreshStickers() { visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom()); } +void StickersListWidget::refreshEffects() { + auto wasSets = base::take(_mySets); + _mySets.reserve(1); + refreshRecentStickers(false); + takeHeavyData(_mySets, wasSets); +} + void StickersListWidget::refreshMySets() { auto wasSets = base::take(_mySets); _favedStickersMap.clear(); @@ -2107,7 +2147,27 @@ void StickersListWidget::refreshRecent() { } } +auto StickersListWidget::collectCustomRecents() -> std::vector { + _custom.clear(); + _cornerEmoji.clear(); + auto result = std::vector(); + + result.reserve(_customRecentIds.size()); + const auto owner = &session().data(); + for (const auto &descriptor : _customRecentIds) { + if (const auto document = descriptor.document; document->sticker()) { + result.push_back(Sticker{ document }); + _custom.push_back(false); + _cornerEmoji.push_back(Ui::Emoji::Find(descriptor.cornerEmoji)); + } + } + return result; +} + auto StickersListWidget::collectRecentStickers() -> std::vector { + if (_isEffects) { + return collectCustomRecents(); + } _custom.clear(); auto result = std::vector(); @@ -2435,7 +2495,9 @@ bool StickersListWidget::setHasTitle(const Set &set) const { return false; } else if (set.id == Data::Stickers::RecentSetId) { return !_mySets.empty() - && (_isMasks || (_mySets[0].id == Data::Stickers::FavedSetId)); + && (_isMasks + || _isEffects + || (_mySets[0].id == Data::Stickers::FavedSetId)); } return true; } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index 34838b364..8dca5601e 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -66,12 +66,19 @@ enum class StickersListMode { Masks, UserpicBuilder, ChatIntro, + MessageEffects, +}; + +struct StickerCustomRecentDescriptor { + not_null document; + QString cornerEmoji; }; struct StickersListDescriptor { std::shared_ptr show; StickersListMode mode = StickersListMode::Full; Fn paused; + std::vector customRecentList; const style::EmojiPan *st = nullptr; ComposeFeatures features; }; @@ -239,8 +246,10 @@ private: bool setHasTitle(const Set &set) const; bool stickerHasDeleteButton(const Set &set, int index) const; - std::vector collectRecentStickers(); + [[nodiscard]] std::vector collectRecentStickers(); + [[nodiscard]] std::vector collectCustomRecents(); void refreshRecentStickers(bool resize = true); + void refreshEffects(); void refreshFavedStickers(); enum class GroupStickersPlace { Visible, @@ -364,11 +373,13 @@ private: std::unique_ptr _localSetsManager; ChannelData *_megagroupSet = nullptr; uint64 _megagroupSetIdRequested = 0; + std::vector _customRecentIds; std::vector _mySets; std::vector _officialSets; std::vector _searchSets; int _featuredSetsCount = 0; std::vector _custom; + std::vector _cornerEmoji; base::flat_set> _favedStickersMap; std::weak_ptr _lottieRenderer; @@ -381,6 +392,7 @@ private: Section _section = Section::Stickers; const bool _isMasks; + const bool _isEffects; base::Timer _updateItemsTimer; base::Timer _updateSetsTimer; diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index cdab73fb6..78baff83e 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -249,6 +249,9 @@ PossibleItemReactions::PossibleItemReactions( : recent(other.recent | ranges::views::transform([](const auto &value) { return *value; }) | ranges::to_vector) +, stickers(other.stickers | ranges::views::transform([](const auto &value) { + return *value; +}) | ranges::to_vector) , customAllowed(other.customAllowed) , tags(other.tags){ } diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index a1254a6d7..98aab8d85 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -43,6 +43,7 @@ struct Reaction { struct PossibleItemReactionsRef { std::vector> recent; + std::vector> stickers; bool customAllowed = false; bool tags = false; }; @@ -52,6 +53,7 @@ struct PossibleItemReactions { explicit PossibleItemReactions(const PossibleItemReactionsRef &other); std::vector recent; + std::vector stickers; bool customAllowed = false; bool tags = false; }; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 9ab46ef14..506e05d13 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" +#include "ui/wrap/vertical_layout.h" #include "ui/text/text_custom_emoji.h" #include "ui/text/text_utilities.h" #include "ui/platform/ui_platform_utility.h" @@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "menu/menu_send.h" #include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/stickers_list_footer.h" +#include "chat_helpers/stickers_list_widget.h" #include "window/window_session_controller.h" #include "boxes/premium_preview_box.h" #include "mainwidget.h" @@ -217,6 +219,7 @@ Selector::Selector( child) { } +#if 0 // not ready Selector::Selector( not_null parent, const style::EmojiPan &st, @@ -237,6 +240,7 @@ Selector::Selector( close, child) { } +#endif Selector::Selector( not_null parent, @@ -931,8 +935,10 @@ void Selector::createList() { if (!_reactions.customAllowed) { st->bg = st::transparent; } - _list = _scroll->setOwnedWidget( - object_ptr(_scroll, EmojiListDescriptor{ + auto lists = _scroll->setOwnedWidget( + object_ptr(_scroll)); + _list = lists->add( + object_ptr(lists, EmojiListDescriptor{ .show = _show, .mode = _listMode, .paused = [] { return false; }, @@ -941,12 +947,35 @@ void Selector::createList() { : _recent), .customRecentFactory = _unifiedFactoryOwner->factory(), .st = st, - }) - ).data(); + })); + if (!_reactions.stickers.empty()) { + auto descriptors = ranges::views::all( + _reactions.stickers + ) | ranges::view::transform([](const Data::Reaction &reaction) { + return ChatHelpers::StickerCustomRecentDescriptor{ + reaction.selectAnimation, + reaction.title + }; + }) | ranges::to_vector; + _stickers = lists->add( + object_ptr( + lists, + StickersListDescriptor{ + .show = _show, + .mode = StickersListMode::MessageEffects, + .paused = [] { return false; }, + .customRecentList = std::move(descriptors), + .st = st, + })); + } _list->escapes() | rpl::start_to_stream(_escapes, _list->lifetime()); - _list->customChosen( + rpl::merge( + _list->customChosen(), + (_stickers + ? _stickers->chosen() + : rpl::never()) ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) { _chosen.fire({ .id = _unifiedFactoryOwner->lookupReactionId(data.document->id), @@ -986,24 +1015,35 @@ void Selector::createList() { _shadow->show(); } const auto geometry = inner.marginsRemoved(_st.margin); - _list->move(0, 0); - _list->resizeToWidth(geometry.width()); + lists->move(0, 0); + lists->resizeToWidth(geometry.width()); _list->refreshEmoji(); - _list->show(); + lists->show(); const auto updateVisibleTopBottom = [=] { const auto scrollTop = _scroll->scrollTop(); const auto scrollBottom = scrollTop + _scroll->height(); - _list->setVisibleTopBottom(scrollTop, scrollBottom); + lists->setVisibleTopBottom(scrollTop, scrollBottom); }; _scroll->scrollTopChanges( - ) | rpl::start_with_next(updateVisibleTopBottom, _list->lifetime()); + ) | rpl::start_with_next(updateVisibleTopBottom, lists->lifetime()); _list->scrollToRequests( ) | rpl::start_with_next([=](int y) { _scroll->scrollToY(y); - _shadow->update(); + if (_shadow) { + _shadow->update(); + } }, _list->lifetime()); + if (_stickers) { + _stickers->scrollToRequests( + ) | rpl::start_with_next([=](int y) { + _scroll->scrollToY(_list->height() + y); + if (_shadow) { + _shadow->update(); + } + }, _stickers->lifetime()); + } _scroll->setGeometry(inner.marginsRemoved({ _st.margin.left(), diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 06de4e7cd..1dc49fb33 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -24,6 +24,7 @@ namespace ChatHelpers { class Show; class TabbedPanel; class EmojiListWidget; +class StickersListWidget; class StickersListFooter; enum class EmojiListMode; } // namespace ChatHelpers @@ -85,6 +86,7 @@ public: Fn close, IconFactory iconFactory = nullptr, bool child = false); +#if 0 // not ready Selector( not_null parent, const style::EmojiPan &st, @@ -93,6 +95,7 @@ public: std::vector recent, Fn close, bool child = false); +#endif ~Selector(); [[nodiscard]] bool useTransparency() const; @@ -193,6 +196,7 @@ private: Ui::ScrollArea *_scroll = nullptr; ChatHelpers::EmojiListWidget *_list = nullptr; + ChatHelpers::StickersListWidget *_stickers = nullptr; ChatHelpers::StickersListFooter *_footer = nullptr; std::unique_ptr _unifiedFactoryOwner; Ui::PlainShadow *_shadow = nullptr; diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index f04662997..3cfc57d0b 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -169,10 +169,15 @@ void BottomRounded::paintEvent(QPaintEvent *e) { const auto premiumPossible = session->premiumPossible(); auto added = base::flat_set(); result.recent.reserve(effects.size()); + result.stickers.reserve(effects.size()); for (const auto &reaction : effects) { if (premiumPossible || !reaction.premium) { if (added.emplace(reaction.id).second) { - result.recent.push_back(&reaction); + if (reaction.aroundAnimation) { + result.recent.push_back(&reaction); + } else { + result.stickers.push_back(&reaction); + } } } } From b01244fc42876a1118dc44ee5a25b0885ec58a26 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 13 May 2024 12:31:23 +0400 Subject: [PATCH 061/225] Show correct sticker effects title. --- Telegram/Resources/langs/lang.strings | 2 +- Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 968b7f911..108fb7d39 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -562,7 +562,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reaction_gif" = "{reaction} to your GIF"; "lng_effect_add_title" = "Add an animated effect"; -"lng_effect_stickers_title" = "Message Effects"; +"lng_effect_stickers_title" = "Effects from stickers"; "lng_effect_send" = "Send with Effect"; "lng_languages" = "Languages"; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index cc718fae7..c2ef3f7f2 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -2239,7 +2239,9 @@ void StickersListWidget::refreshRecentStickers(bool performResize) { Data::Stickers::RecentSetId, nullptr, (SetFlag::Official | SetFlag::Special), - tr::lng_recent_stickers(tr::now), + (_isEffects + ? tr::lng_effect_stickers_title(tr::now) + : tr::lng_recent_stickers(tr::now)), shortName, recentPack.size(), externalLayout, From d0d1ef9e66b9d60008732e8e2d82e079be1a0a86 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 13 May 2024 12:39:31 +0400 Subject: [PATCH 062/225] Fix effect panel jump on expand. --- .../view/reactions/history_view_reactions_selector.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 506e05d13..c8a1a8982 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -400,9 +400,7 @@ void Selector::initGeometry(int innerTop) { _collapsedTopSkip = _useTransparency ? (extendTopForCategoriesAndAbout(forAbout) + _specialExpandTopSkip) : 0; - _topAddOnExpand = extendTopForCategories() - - _aboutExtend - + _specialExpandTopSkip; + _topAddOnExpand = _collapsedTopSkip - _aboutExtend; const auto height = margins.top() + _aboutExtend + innerHeight From bbb3a51b74f1e7d1d5f9f28d57a1f01ca99d29d3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 13 May 2024 14:28:51 +0400 Subject: [PATCH 063/225] Fix effect selector for !_useTransparency case. --- .../chat_helpers/chat_helpers.style | 2 +- .../history_view_reactions_selector.cpp | 91 ++++++++++++++++--- .../history_view_reactions_selector.h | 7 ++ .../media/stories/media_stories_reactions.cpp | 4 +- 4 files changed, 88 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 538c247b9..953232da9 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -670,7 +670,7 @@ defaultEmojiPan: EmojiPan { boxLabel: boxLabel; icons: defaultComposeIcons; about: defaultEmojiPanAbout; - aboutPadding: margins(12px, 2px, 12px, 2px); + aboutPadding: margins(12px, 3px, 12px, 2px); autocompleteBottomSkip: 0px; } statusEmojiPan: EmojiPan(defaultEmojiPan) { diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index c8a1a8982..70b987eda 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -377,6 +377,32 @@ int Selector::extendTopForCategoriesAndAbout(int width) const { return std::max(extendTopForCategories(), _aboutExtend); } +int Selector::opaqueExtendTopAbout(int width) const { + if (_about) { + const auto padding = _st.aboutPadding; + const auto available = width - padding.left() - padding.right(); + const auto countAboutHeight = [&](int width) { + _about->resizeToWidth(width); + return _about->height(); + }; + const auto desired = Ui::FindNiceTooltipWidth( + std::min(available, _st.about.minWidth * 2), + available, + countAboutHeight); + + _about->resizeToWidth(desired); + _aboutExtend = padding.top() + _about->height() + padding.bottom(); + } else { + _aboutExtend = 0; + } + return _aboutExtend; +} + +void Selector::setOpaqueHeightExpand(int expand, Fn apply) { + _opaqueHeightExpand = expand; + _opaqueApplyHeightExpand = std::move(apply); +} + int Selector::minimalHeight() const { return _skipy + std::min(_recentRows * _size, st::emojiPanMinHeight) @@ -399,15 +425,19 @@ void Selector::initGeometry(int innerTop) { const auto forAbout = width - margins.left() - margins.right(); _collapsedTopSkip = _useTransparency ? (extendTopForCategoriesAndAbout(forAbout) + _specialExpandTopSkip) - : 0; + : opaqueExtendTopAbout(forAbout); _topAddOnExpand = _collapsedTopSkip - _aboutExtend; const auto height = margins.top() + _aboutExtend + innerHeight + margins.bottom(); const auto left = style::RightToLeft() ? 0 : (parent.width() - width); - const auto top = innerTop - margins.top() - _collapsedTopSkip; - const auto add = _st.icons.stripBubble.height() - margins.bottom(); + const auto top = innerTop + - margins.top() + - (_useTransparency ? _collapsedTopSkip : 0); + const auto add = _useTransparency + ? (_st.icons.stripBubble.height() - margins.bottom()) + : 0; _outer = QRect(0, _collapsedTopSkip - _aboutExtend, width, height); _outerWithBubble = _outer.marginsAdded({ 0, 0, 0, add }); setGeometry(_outerWithBubble.marginsAdded( @@ -568,7 +598,7 @@ void Selector::paintCollapsed(QPainter &p) { } p.drawImage(_outer.topLeft(), _paintBuffer); } else { - p.fillRect(_inner, _st.bg); + p.fillRect(_outer.marginsRemoved(marginsForShadow()), _st.bg); } _strip->paint( p, @@ -647,6 +677,13 @@ Selector::ExpandingRects Selector::updateExpandingRects(float64 progress) { ? int(base::SafeRound( radius - sqrt(categories * (2 * radius - categories)))) : 0; + + if (!_useTransparency && _opaqueApplyHeightExpand) { + Ui::PostponeCall(this, [=] { + _opaqueApplyHeightExpand(y() + outer.y() + outer.height()); + }); + } + return { .categories = QRect(inner.x(), inner.y(), inner.width(), categories), .list = list, @@ -877,8 +914,9 @@ void Selector::expand() { const auto heightLimit = _reactions.customAllowed ? st::emojiPanMaxHeight : minimalHeight(); + const auto opaqueAdded = _useTransparency ? 0 : _opaqueHeightExpand; const auto willBeHeight = std::min( - parent.height() - y(), + parent.height() - y() + opaqueAdded, margins.top() + heightLimit + margins.bottom()); const auto additionalBottom = willBeHeight - height(); const auto additional = _specialExpandTopSkip + additionalBottom; @@ -903,7 +941,9 @@ void Selector::expand() { } _expanded = true; _paintBuffer = _cachedRound.PrepareImage(size()); - _expanding.start([=] { update(); }, 0., full, full); + _expanding.start([=] { + update(); + }, 0., full, full); }); } @@ -923,7 +963,9 @@ void Selector::createList() { &_show->session(), _strip ? _reactions.recent : std::vector(), _strip.get()); - _scroll = Ui::CreateChild(this, _reactions.customAllowed + _scroll = Ui::CreateChild(this, !_useTransparency + ? st::emojiScroll + : _reactions.customAllowed ? st::reactPanelScroll : st::reactPanelScrollRounded); _scroll->hide(); @@ -1069,7 +1111,7 @@ bool AdjustMenuGeometryForSelector( const auto margins = selector->marginsForShadow(); const auto categoriesAboutTop = selector->useTransparency() ? selector->extendTopForCategoriesAndAbout(width) - : 0; + : selector->opaqueExtendTopAbout(width); menu->setForceWidth(width - added); const auto height = menu->height(); const auto fullTop = margins.top() + categoriesAboutTop + extend.top(); @@ -1082,9 +1124,7 @@ bool AdjustMenuGeometryForSelector( const auto additionalPaddingBottom = (willBeHeightWithoutBottomPadding >= minimalHeight ? 0 - : useTransparency - ? (minimalHeight - willBeHeightWithoutBottomPadding) - : 0); + : (minimalHeight - willBeHeightWithoutBottomPadding)); menu->setAdditionalMenuPadding(QMargins( margins.left() + extend.left(), fullTop, @@ -1100,9 +1140,32 @@ bool AdjustMenuGeometryForSelector( return false; } const auto origin = menu->preparedOrigin(); - if (!additionalPaddingBottom - || origin == Ui::PanelAnimation::Origin::TopLeft - || origin == Ui::PanelAnimation::Origin::TopRight) { + const auto expandDown = (origin == Ui::PanelAnimation::Origin::TopLeft) + || (origin == Ui::PanelAnimation::Origin::TopRight); + if (!useTransparency) { + const auto expandBy = additionalPaddingBottom; + selector->setOpaqueHeightExpand(expandBy, [=](int bottom) { + const auto add = bottom - menu->height(); + if (add > 0) { + const auto updated = menu->geometry().marginsAdded({ + 0, expandDown ? 0 : add, 0, expandDown ? add : 0 }); + menu->setFixedSize(updated.size()); + menu->setGeometry(updated); + } + }); + menu->setAdditionalMenuPadding(QMargins( + margins.left() + extend.left(), + fullTop, + margins.right() + extend.right(), + 0 + ), QMargins( + margins.left(), + margins.top(), + margins.right(), + 0 + )); + return menu->prepareGeometryFor(desiredPosition); + } else if (!additionalPaddingBottom || expandDown) { return true; } menu->setAdditionalMenuPadding(QMargins( diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 1dc49fb33..ad173385d 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -104,12 +104,15 @@ public: [[nodiscard]] QMargins marginsForShadow() const; [[nodiscard]] int extendTopForCategories() const; [[nodiscard]] int extendTopForCategoriesAndAbout(int width) const; + [[nodiscard]] int opaqueExtendTopAbout(int width) const; [[nodiscard]] int minimalHeight() const; [[nodiscard]] int countAppearedWidth(float64 progress) const; void setSpecialExpandTopSkip(int skip); void initGeometry(int innerTop); void beforeDestroy(); + void setOpaqueHeightExpand(int expand, Fn apply); + [[nodiscard]] rpl::producer chosen() const { return _chosen.events(); } @@ -216,6 +219,10 @@ private: int _specialExpandTopSkip = 0; int _collapsedTopSkip = 0; int _topAddOnExpand = 0; + + int _opaqueHeightExpand = 0; + Fn _opaqueApplyHeightExpand; + const int _size = 0; int _recentRows = 0; int _columns = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 724533e58..30055ad0b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -667,7 +667,9 @@ void Reactions::Panel::create() { TextWithEntities{ (mode == Mode::Message ? tr::lng_stories_reaction_as_message(tr::now) : QString()) }, - [=](bool fast) { hide(mode); }); + [=](bool fast) { hide(mode); }, + nullptr, + true); _selector->chosen( ) | rpl::start_with_next([=]( From d102d256a9ae9782ae1ae84a84359be585094c19 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 13 May 2024 15:00:46 +0400 Subject: [PATCH 064/225] Implement effects search. --- .../chat_helpers/emoji_list_widget.cpp | 10 +- .../chat_helpers/emoji_list_widget.h | 4 + .../chat_helpers/stickers_list_widget.cpp | 108 ++++++++++++------ .../chat_helpers/stickers_list_widget.h | 16 ++- .../chat_helpers/tabbed_selector.cpp | 4 +- .../chat_helpers/tabbed_selector.h | 2 +- .../history_view_reactions_selector.cpp | 25 ++-- 7 files changed, 115 insertions(+), 54 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 8482d8b81..4962a42a9 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -583,9 +583,14 @@ void EmojiListWidget::setupSearch() { InvokeQueued(this, [=] { applyNextSearchQuery(); }); + _searchQueries.fire_copy(_nextSearchQuery); }, session, type); } +rpl::producer> EmojiListWidget::searchQueries() const { + return _searchQueries.events(); +} + void EmojiListWidget::applyNextSearchQuery() { if (_searchQuery == _nextSearchQuery) { return; @@ -834,7 +839,8 @@ void EmojiListWidget::unloadCustomIn(const SectionInfo &info) { object_ptr EmojiListWidget::createFooter() { Expects(_footer == nullptr); - if (_mode == EmojiListMode::RecentReactions) { + if (_mode == EmojiListMode::RecentReactions + || _mode == EmojiListMode::MessageEffects) { return { nullptr }; } @@ -2131,7 +2137,7 @@ void EmojiListWidget::refreshRecent() { } void EmojiListWidget::refreshCustom() { - if (_mode == Mode::RecentReactions) { + if (_mode == Mode::RecentReactions || _mode == Mode::MessageEffects) { return; } auto old = base::take(_custom); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 219add05b..59bfbfce6 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -77,6 +77,7 @@ enum class EmojiListMode { UserpicBuilder, BackgroundEmoji, PeerTitle, + MessageEffects, }; struct EmojiListDescriptor { @@ -146,6 +147,8 @@ public: base::unique_qptr fillContextMenu( const SendMenu::Details &details) override; + [[nodiscard]] rpl::producer> searchQueries() const; + protected: void visibleTopBottomUpdated( int visibleTop, @@ -418,6 +421,7 @@ private: bool _colorAllRippleForced = false; rpl::lifetime _colorAllRippleForcedLifetime; + rpl::event_stream> _searchQueries; std::vector _nextSearchQuery; std::vector _searchQuery; base::flat_set _searchEmoji; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index c2ef3f7f2..0c46508e6 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -547,7 +547,7 @@ int StickersListWidget::countDesiredHeight(int newWidth) { } void StickersListWidget::sendSearchRequest() { - if (_searchRequestId || _searchNextQuery.isEmpty()) { + if (_searchRequestId || _searchNextQuery.isEmpty() || _isEffects) { return; } @@ -556,14 +556,12 @@ void StickersListWidget::sendSearchRequest() { auto it = _searchCache.find(_searchQuery); if (it != _searchCache.cend()) { - _search->setLoading(false); + toggleSearchLoading(false); return; } - - _search->setLoading(true); - + toggleSearchLoading(true); if (_searchQuery == Ui::PremiumGroupFakeEmoticon()) { - _search->setLoading(false); + toggleSearchLoading(false); _searchRequestId = 0; _searchCache.emplace(_searchQuery, std::vector()); showSearchResults(); @@ -579,7 +577,7 @@ void StickersListWidget::sendSearchRequest() { searchResultsDone(result); }).fail([=] { // show error? - _search->setLoading(false); + toggleSearchLoading(false); _searchRequestId = 0; }).handleAllErrors().send(); } @@ -593,7 +591,10 @@ void StickersListWidget::searchForSets( return; } - if (query == Ui::PremiumGroupFakeEmoticon()) { + _filterStickersCornerEmoji.clear(); + if (_isEffects) { + filterEffectsByEmoji(std::move(emoji)); + } else if (query == Ui::PremiumGroupFakeEmoticon()) { _filteredStickers = session().data().stickers().getPremiumList(0); } else { _filteredStickers = session().data().stickers().getListByEmoji( @@ -602,7 +603,7 @@ void StickersListWidget::searchForSets( true); } if (_searchQuery != cleaned) { - _search->setLoading(false); + toggleSearchLoading(false); if (const auto requestId = base::take(_searchRequestId)) { _api.request(requestId).cancel(); } @@ -618,13 +619,14 @@ void StickersListWidget::searchForSets( } void StickersListWidget::cancelSetsSearch() { - _search->setLoading(false); + toggleSearchLoading(false); if (const auto requestId = base::take(_searchRequestId)) { _api.request(requestId).cancel(); } _searchRequestTimer.cancel(); _searchQuery = _searchNextQuery = QString(); _filteredStickers.clear(); + _filterStickersCornerEmoji.clear(); _searchCache.clear(); refreshSearchRows(nullptr); } @@ -655,8 +657,9 @@ void StickersListWidget::refreshSearchRows( }); fillFilteredStickersRow(); - fillLocalSearchRows(_searchNextQuery); - + if (!_isEffects) { + fillLocalSearchRows(_searchNextQuery); + } if (!cloudSets && _searchNextQuery.isEmpty()) { showStickerSet(!_mySets.empty() ? _mySets[0].id @@ -665,11 +668,10 @@ void StickersListWidget::refreshSearchRows( } setSection(Section::Search); - if (cloudSets) { + if (!_isEffects && cloudSets) { fillCloudSearchRows(*cloudSets); } refreshIcons(ValidateIconAnimations::Scroll); - _lastMousePosition = QCursor::pos(); resizeToWidth(width()); @@ -735,7 +737,7 @@ void StickersListWidget::fillFilteredStickersRow() { SearchEmojiSectionSetId(), nullptr, Data::StickersSetFlag::Special, - QString(), // title + _isEffects ? tr::lng_effect_stickers_title(tr::now) : QString(), QString(), // shortName _filteredStickers.size(), false, // externalLayout @@ -758,6 +760,12 @@ void StickersListWidget::addSearchRow(not_null set) { std::move(elements)); } +void StickersListWidget::toggleSearchLoading(bool loading) { + if (_search) { + _search->setLoading(loading); + } +} + void StickersListWidget::takeHeavyData( std::vector &to, std::vector &from) { @@ -839,7 +847,7 @@ auto StickersListWidget::shownSets() -> std::vector & { void StickersListWidget::searchResultsDone( const MTPmessages_FoundStickerSets &result) { - _search->setLoading(false); + toggleSearchLoading(false); _searchRequestId = 0; if (result.type() == mtpc_messages_foundStickerSetsNotModified) { @@ -1476,9 +1484,14 @@ void StickersListWidget::paintSticker( } auto cornerPainted = false; - if (set.id == Data::Stickers::RecentSetId && !_cornerEmoji.empty()) { - Assert(index < _cornerEmoji.size()); - if (const auto emoji = _cornerEmoji[index]) { + const auto corner = (set.id == Data::Stickers::RecentSetId) + ? &_cornerEmoji + : (set.id == SearchEmojiSectionSetId()) + ? &_filterStickersCornerEmoji + : nullptr; + if (corner && !corner->empty()) { + Assert(index < corner->size()); + if (const auto emoji = (*corner)[index]) { const auto size = Ui::Emoji::GetSizeNormal(); const auto ratio = style::DevicePixelRatio(); const auto radius = st::roundRadiusSmall; @@ -2492,14 +2505,14 @@ void StickersListWidget::updateSelected() { } bool StickersListWidget::setHasTitle(const Set &set) const { - if (set.id == Data::Stickers::FavedSetId + if (_isEffects) { + return true; + } else if (set.id == Data::Stickers::FavedSetId || set.id == SearchEmojiSectionSetId()) { return false; } else if (set.id == Data::Stickers::RecentSetId) { return !_mySets.empty() - && (_isMasks - || _isEffects - || (_mySets[0].id == Data::Stickers::FavedSetId)); + && (_isMasks || (_mySets[0].id == Data::Stickers::FavedSetId)); } return true; } @@ -2581,9 +2594,10 @@ void StickersListWidget::showStickerSet(uint64 setId) { const auto guard = gsl::finally([&] { _showingSetById = false; }); clearSelection(); - if (_search - && (!_searchQuery.isEmpty() || !_searchNextQuery.isEmpty())) { - _search->cancel(); + if (!_searchQuery.isEmpty() || !_searchNextQuery.isEmpty()) { + if (_search) { + _search->cancel(); + } cancelSetsSearch(); } @@ -2686,14 +2700,18 @@ void StickersListWidget::setupSearch() { ? TabbedSearchType::Greeting : TabbedSearchType::Stickers; _search = MakeSearch(this, st(), [=](std::vector &&query) { - auto set = base::flat_set(); - auto text = ranges::accumulate(query, QString(), []( + applySearchQuery(std::move(query)); + }, session, type); +} + +void StickersListWidget::applySearchQuery(std::vector &&query) { + auto set = base::flat_set(); + auto text = ranges::accumulate(query, QString(), []( QString a, QString b) { - return a.isEmpty() ? b : (a + ' ' + b); - }); - searchForSets(std::move(text), SearchEmoji(query, set)); - }, session, type); + return a.isEmpty() ? b : (a + ' ' + b); + }); + searchForSets(std::move(text), SearchEmoji(query, set)); } void StickersListWidget::displaySet(uint64 setId) { @@ -2768,6 +2786,32 @@ bool StickersListWidget::mySetsEmpty() const { return _mySets.empty(); } +void StickersListWidget::filterEffectsByEmoji( + const std::vector &emoji) { + _filteredStickers.clear(); + _filterStickersCornerEmoji.clear(); + if (_mySets.empty() + || _mySets.front().id != Data::Stickers::RecentSetId + || _mySets.front().stickers.empty()) { + return; + } + const auto &list = _mySets.front().stickers; + auto all = base::flat_set(); + for (const auto &one : emoji) { + all.emplace(one->original()); + } + const auto count = int(list.size()); + _filteredStickers.reserve(count); + _filterStickersCornerEmoji.reserve(count); + for (auto i = 0; i != count; ++i) { + Assert(i < _cornerEmoji.size()); + if (all.contains(_cornerEmoji[i])) { + _filteredStickers.push_back(list[i].document); + _filterStickersCornerEmoji.push_back(_cornerEmoji[i]); + } + } +} + StickersListWidget::~StickersListWidget() = default; object_ptr MakeConfirmRemoveSetBox( diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index 8dca5601e..1f1de231b 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -127,6 +127,8 @@ public: bool mySetsEmpty() const; + void applySearchQuery(std::vector &&query); + ~StickersListWidget(); protected: @@ -261,12 +263,13 @@ private: void updateSelected(); void setSelected(OverState newSelected); void setPressed(OverState newPressed); - std::unique_ptr createButtonRipple(int section); - QPoint buttonRippleTopLeft(int section) const; + [[nodiscard]] std::unique_ptr createButtonRipple( + int section); + [[nodiscard]] QPoint buttonRippleTopLeft(int section) const; - std::vector &shownSets(); - const std::vector &shownSets() const; - int featuredRowHeight() const; + [[nodiscard]] std::vector &shownSets(); + [[nodiscard]] const std::vector &shownSets() const; + [[nodiscard]] int featuredRowHeight() const; void checkVisibleFeatured(int visibleTop, int visibleBottom); void readVisibleFeatured(int visibleTop, int visibleBottom); @@ -324,6 +327,7 @@ private: [[nodiscard]] const Data::StickersSetsOrder &defaultSetsOrder() const; [[nodiscard]] Data::StickersSetsOrder &defaultSetsOrderRef(); + void filterEffectsByEmoji(const std::vector &emoji); enum class AppendSkip { None, @@ -356,6 +360,7 @@ private: void fillLocalSearchRows(const QString &query); void fillCloudSearchRows(const std::vector &cloudSets); void addSearchRow(not_null set); + void toggleSearchLoading(bool loading); void showPreview(); @@ -431,6 +436,7 @@ private: std::unique_ptr _premiumMark; std::vector> _filteredStickers; + std::vector _filterStickersCornerEmoji; std::map> _searchCache; std::vector> _searchIndex; base::Timer _searchRequestTimer; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 5e8967541..7ac136421 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -1460,9 +1460,7 @@ int TabbedSelector::Inner::resizeGetHeight(int newWidth) { } int TabbedSelector::Inner::minimalHeight() const { - return (_minimalHeight > 0) - ? _minimalHeight - : defaultMinimalHeight(); + return _minimalHeight.value_or(defaultMinimalHeight()); } int TabbedSelector::Inner::defaultMinimalHeight() const { diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index bb780797c..b5355dafc 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -422,7 +422,7 @@ private: int _visibleTop = 0; int _visibleBottom = 0; - int _minimalHeight = 0; + std::optional _minimalHeight; rpl::event_stream _scrollToRequests; rpl::event_stream _disableScrollRequests; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 70b987eda..2f55b6048 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -211,7 +211,9 @@ Selector::Selector( reactions, (reactions.customAllowed ? ChatHelpers::EmojiListMode::FullReactions - : ChatHelpers::EmojiListMode::RecentReactions), + : reactions.stickers.empty() + ? ChatHelpers::EmojiListMode::RecentReactions + : ChatHelpers::EmojiListMode::MessageEffects), {}, std::move(about), iconFactory, @@ -1075,15 +1077,6 @@ void Selector::createList() { _shadow->update(); } }, _list->lifetime()); - if (_stickers) { - _stickers->scrollToRequests( - ) | rpl::start_with_next([=](int y) { - _scroll->scrollToY(_list->height() + y); - if (_shadow) { - _shadow->update(); - } - }, _stickers->lifetime()); - } _scroll->setGeometry(inner.marginsRemoved({ _st.margin.left(), @@ -1091,7 +1084,17 @@ void Selector::createList() { 0, 0, })); - _list->setMinimalHeight(geometry.width(), _scroll->height()); + if (_stickers) { + _list->setMinimalHeight(geometry.width(), 0); + _stickers->setMinimalHeight(geometry.width(), 0); + + _list->searchQueries( + ) | rpl::start_with_next([=](std::vector &&query) { + _stickers->applySearchQuery(std::move(query)); + }, _stickers->lifetime()); + } else { + _list->setMinimalHeight(geometry.width(), _scroll->height()); + } updateVisibleTopBottom(); } From 732b67ca049557c3b04ad909f46d5f269aeaada3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 May 2024 12:16:39 +0400 Subject: [PATCH 065/225] Implement effects paywalls. --- Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/boxes/sticker_set_box.cpp | 45 +++++++----- Telegram/SourceFiles/boxes/sticker_set_box.h | 5 +- .../chat_helpers/chat_helpers.style | 1 + .../chat_helpers/emoji_list_widget.cpp | 65 +++++++++++++++-- .../chat_helpers/emoji_list_widget.h | 9 +++ .../chat_helpers/field_autocomplete.cpp | 2 +- .../chat_helpers/stickers_list_widget.cpp | 24 +++++- .../chat_helpers/stickers_list_widget.h | 3 + .../history_view_reactions_selector.cpp | 48 +++++++++++- .../history_view_reactions_selector.h | 1 + Telegram/SourceFiles/menu/menu_send.cpp | 73 +++++++++++++++---- Telegram/SourceFiles/ui/chat/chat.style | 9 +++ 13 files changed, 241 insertions(+), 47 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 108fb7d39..c0ef61a95 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -564,6 +564,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_effect_add_title" = "Add an animated effect"; "lng_effect_stickers_title" = "Effects from stickers"; "lng_effect_send" = "Send with Effect"; +"lng_effect_none" = "No effects found."; +"lng_effect_premium" = "Subscribe to {link} to add this animated effect."; +"lng_effect_premium_link" = "Telegram Premium"; "lng_languages" = "Languages"; "lng_languages_none" = "No languages found."; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index efd034843..b3c589be7 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -73,7 +73,9 @@ using Data::StickersSet; using Data::StickersPack; using SetFlag = Data::StickersSetFlag; -[[nodiscard]] std::optional ComputeImageColor(const QImage &frame) { +[[nodiscard]] std::optional ComputeImageColor( + const style::icon &lockIcon, + const QImage &frame) { if (frame.isNull() || frame.format() != QImage::Format_ARGB32_Premultiplied) { return {}; @@ -83,7 +85,7 @@ using SetFlag = Data::StickersSetFlag; auto sb = int64(); auto sa = int64(); const auto factor = frame.devicePixelRatio(); - const auto size = st::stickersPremiumLock.size() * factor; + const auto size = lockIcon.size() * factor; const auto width = std::min(frame.width(), size.width()); const auto height = std::min(frame.height(), size.height()); const auto skipx = (frame.width() - width) / 2; @@ -110,22 +112,30 @@ using SetFlag = Data::StickersSetFlag; } -[[nodiscard]] QColor ComputeLockColor(const QImage &frame) { - return ComputeImageColor(frame).value_or(st::windowSubTextFg->c); +[[nodiscard]] QColor ComputeLockColor( + const style::icon &lockIcon, + const QImage &frame) { + return ComputeImageColor( + lockIcon, + frame + ).value_or(st::windowSubTextFg->c); } -void ValidatePremiumLockBg(QImage &image, const QImage &frame) { +void ValidatePremiumLockBg( + const style::icon &lockIcon, + QImage &image, + const QImage &frame) { if (!image.isNull()) { return; } const auto factor = style::DevicePixelRatio(); - const auto size = st::stickersPremiumLock.size(); + const auto size = lockIcon.size(); image = QImage( size * factor, QImage::Format_ARGB32_Premultiplied); image.setDevicePixelRatio(factor); auto p = QPainter(&image); - const auto color = ComputeLockColor(frame); + const auto color = ComputeLockColor(lockIcon, frame); p.fillRect( QRect(QPoint(), size), anim::color(color, st::windowSubTextFg, kGrayLockOpacity)); @@ -134,12 +144,12 @@ void ValidatePremiumLockBg(QImage &image, const QImage &frame) { image = Images::Circle(std::move(image)); } -void ValidatePremiumStarFg(QImage &image) { +void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) { if (!image.isNull()) { return; } const auto factor = style::DevicePixelRatio(); - const auto size = st::stickersPremiumLock.size(); + const auto size = lockIcon.size(); image = QImage( size * factor, QImage::Format_ARGB32_Premultiplied); @@ -176,7 +186,10 @@ void ValidatePremiumStarFg(QImage &image) { } // namespace -StickerPremiumMark::StickerPremiumMark(not_null session) { +StickerPremiumMark::StickerPremiumMark( + not_null session, + const style::icon &lockIcon) +: _lockIcon(lockIcon) { style::PaletteChanged( ) | rpl::start_with_next([=] { _lockGray = QImage(); @@ -202,16 +215,14 @@ void StickerPremiumMark::paint( const auto factor = style::DevicePixelRatio(); const auto radius = st::roundRadiusSmall; const auto point = position + QPoint( - (_premium - ? (singleSize.width() - (bg.width() / factor) - radius) - : (singleSize.width() - (bg.width() / factor)) / 2), + (singleSize.width() - (bg.width() / factor) - radius), singleSize.height() - (bg.height() / factor) - radius); p.drawImage(point, bg); if (_premium) { validateStar(); p.drawImage(point, _star); } else { - st::stickersPremiumLock.paint(p, point, outerWidth); + _lockIcon.paint(p, point, outerWidth); } } @@ -219,11 +230,11 @@ void StickerPremiumMark::validateLock( const QImage &frame, QImage &backCache) { auto &image = frame.isNull() ? _lockGray : backCache; - ValidatePremiumLockBg(image, frame); + ValidatePremiumLockBg(_lockIcon, image, frame); } void StickerPremiumMark::validateStar() { - ValidatePremiumStarFg(_star); + ValidatePremiumStarFg(_lockIcon, _star); } class StickerSetBox::Inner final : public Ui::RpWidget { @@ -664,7 +675,7 @@ StickerSetBox::Inner::Inner( st::windowBgRipple, st::windowBgOver, [=] { repaintItems(); })) -, _premiumMark(_session) +, _premiumMark(_session, st::stickersPremiumLock) , _updateItemsTimer([=] { updateItems(); }) , _input(set) , _padding((type == Data::StickersType::Emoji) diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.h b/Telegram/SourceFiles/boxes/sticker_set_box.h index 72dd38320..e718cc45e 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.h +++ b/Telegram/SourceFiles/boxes/sticker_set_box.h @@ -30,7 +30,9 @@ class Show; class StickerPremiumMark final { public: - explicit StickerPremiumMark(not_null session); + StickerPremiumMark( + not_null session, + const style::icon &lockIcon); void paint( QPainter &p, @@ -44,6 +46,7 @@ private: void validateLock(const QImage &frame, QImage &backCache); void validateStar(); + const style::icon &_lockIcon; QImage _lockGray; QImage _star; bool _premium = false; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 953232da9..d69d4e6bb 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -754,6 +754,7 @@ inlineResultsMinWidth: 48px; inlineDurationMargin: 3px; stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }}; +emojiPremiumLock: icon{{ "chat/mini_lock", premiumButtonFg }}; reactStripExtend: margins(21px, 49px, 39px, 0px); reactStripHeight: 40px; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 4962a42a9..c91ccf1d1 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -136,6 +136,7 @@ struct EmojiListWidget::CustomEmojiInstance { struct EmojiListWidget::RecentOne { Ui::Text::CustomEmoji *custom = nullptr; RecentEmojiId id; + mutable QImage premiumLock; }; EmojiColorPicker::EmojiColorPicker( @@ -478,8 +479,12 @@ EmojiListWidget::EmojiListWidget( , _localSetsManager( std::make_unique(&session())) , _customRecentFactory(std::move(descriptor.customRecentFactory)) +, _freeEffects(std::move(descriptor.freeEffects)) , _customTextColor(std::move(descriptor.customTextColor)) , _overBg(st::emojiPanRadius, st().overBg) +, _premiumMark(std::make_unique( + &session(), + st::emojiPremiumLock)) , _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg) , _picker(this, st()) , _showPickerTimer([=] { showPicker(); }) @@ -591,6 +596,10 @@ rpl::producer> EmojiListWidget::searchQueries() const { return _searchQueries.events(); } +rpl::producer EmojiListWidget::recentShownCount() const { + return _recentShownCount.value(); +} + void EmojiListWidget::applyNextSearchQuery() { if (_searchQuery == _nextSearchQuery) { return; @@ -612,6 +621,9 @@ void EmojiListWidget::applyNextSearchQuery() { _searchCustomIds.clear(); } resizeToWidth(width()); + _recentShownCount = searching + ? _searchResults.size() + : _recent.size(); update(); if (modeChanged) { visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom()); @@ -1024,9 +1036,10 @@ int EmojiListWidget::countDesiredHeight(int newWidth) { const auto minimalLastHeight = std::max( minimalHeight - padding.bottom(), 0); - return qMax( - minimalHeight, - countResult(minimalLastHeight) + padding.bottom()); + const auto result = countResult(minimalLastHeight); + return result + ? qMax(minimalHeight, result + padding.bottom()) + : 0; } int EmojiListWidget::defaultMinimalHeight() const { @@ -1291,6 +1304,8 @@ void EmojiListWidget::paint( QRect clip) { validateEmojiPaintContext(context); + _paintAsPremium = session().premium(); + auto fromColumn = floorclamp( clip.x() - _rowsLeft, _singleSize.width(), @@ -1455,16 +1470,44 @@ void EmojiListWidget::drawRecent( QPoint position, const RecentOne &recent) { _recentPainted = true; + const auto locked = (_mode == Mode::MessageEffects) + && !_paintAsPremium + && v::is(recent.id.data) + && !_freeEffects.contains( + v::get(recent.id.data).id); + auto lockedPainted = false; + if (locked) { + if (_premiumMarkFrameCache.isNull()) { + const auto ratio = style::DevicePixelRatio(); + _premiumMarkFrameCache = QImage( + QSize(_customSingleSize, _customSingleSize) * ratio, + QImage::Format_ARGB32_Premultiplied); + _premiumMarkFrameCache.setDevicePixelRatio(ratio); + } + _premiumMarkFrameCache.fill(Qt::transparent); + } if (const auto custom = recent.custom) { - _emojiPaintContext->scale = context.progress; - _emojiPaintContext->position = position + const auto exactPosition = position + _innerPosition + _customPosition; + _emojiPaintContext->scale = context.progress; if (_mode == Mode::ChannelStatus) { _emojiPaintContext->internal.forceFirstFrame = (recent.id == _recent.front().id); } - custom->paint(p, *_emojiPaintContext); + if (locked) { + lockedPainted = custom->ready(); + + auto q = Painter(&_premiumMarkFrameCache); + _emojiPaintContext->position = QPoint(); + custom->paint(q, *_emojiPaintContext); + q.end(); + + p.drawImage(exactPosition, _premiumMarkFrameCache); + } else { + _emojiPaintContext->position = exactPosition; + custom->paint(p, *_emojiPaintContext); + } } else if (const auto emoji = std::get_if(&recent.id.data)) { if (_mode == Mode::EmojiStatus) { position += QPoint( @@ -1478,6 +1521,16 @@ void EmojiListWidget::drawRecent( } else { Unexpected("Empty custom emoji in EmojiListWidget::drawRecent."); } + + if (locked) { + _premiumMark->paint( + p, + lockedPainted ? _premiumMarkFrameCache : QImage(), + recent.premiumLock, + position, + _singleSize, + width()); + } } void EmojiListWidget::drawEmoji( diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 59bfbfce6..3aef95ac0 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/round_rect.h" #include "base/timer.h" +class StickerPremiumMark; + namespace style { struct EmojiPan; } // namespace style @@ -89,6 +91,7 @@ struct EmojiListDescriptor { Fn( DocumentId, Fn)> customRecentFactory; + base::flat_set freeEffects; const style::EmojiPan *st = nullptr; ComposeFeatures features; }; @@ -148,6 +151,7 @@ public: const SendMenu::Details &details) override; [[nodiscard]] rpl::producer> searchQueries() const; + [[nodiscard]] rpl::producer recentShownCount() const; protected: void visibleTopBottomUpdated( @@ -400,10 +404,13 @@ private: int _counts[kEmojiSectionCount]; std::vector _recent; base::flat_set _recentCustomIds; + base::flat_set _freeEffects; base::flat_set _repaintsScheduled; + rpl::variable _recentShownCount; std::unique_ptr _emojiPaintContext; bool _recentPainted = false; bool _grabbingChosen = false; + bool _paintAsPremium = false; QVector _emoji[kEmojiSectionCount]; std::vector _custom; base::flat_set _restrictedCustomList; @@ -417,6 +424,8 @@ private: Ui::RoundRect _overBg; QImage _searchExpandCache; + std::unique_ptr _premiumMark; + QImage _premiumMarkFrameCache; mutable std::unique_ptr _colorAllRipple; bool _colorAllRippleForced = false; rpl::lifetime _colorAllRippleForcedLifetime; diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 90f11e978..bece81b64 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -891,7 +891,7 @@ FieldAutocomplete::Inner::Inner( _st.pathBg, _st.pathFg, [=] { update(); })) -, _premiumMark(_session) +, _premiumMark(_session, st::stickersPremiumLock) , _previewTimer([=] { showPreview(); }) { _session->downloaderTaskFinished( ) | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 0c46508e6..bb1278fd8 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -219,7 +219,9 @@ StickersListWidget::StickersListWidget( , _installedWidth(st::stickersTrendingInstalled.font->width(_installedText)) , _settings(this, tr::lng_stickers_you_have(tr::now)) , _previewTimer([=] { showPreview(); }) -, _premiumMark(std::make_unique(&session())) +, _premiumMark(std::make_unique( + &session(), + st::stickersPremiumLock)) , _searchRequestTimer([=] { sendSearchRequest(); }) { setMouseTracking(true); if (st().bg->c.alpha() > 0) { @@ -542,8 +544,8 @@ int StickersListWidget::countDesiredHeight(int newWidth) { const auto minimalLastHeight = (_section == Section::Stickers) ? minimalHeight : 0; - return qMax(minimalHeight, countResult(minimalLastHeight)) - + st::stickerPanPadding; + const auto result = qMax(minimalHeight, countResult(minimalLastHeight)); + return result ? (result + st::stickerPanPadding) : 0; } void StickersListWidget::sendSearchRequest() { @@ -675,9 +677,14 @@ void StickersListWidget::refreshSearchRows( _lastMousePosition = QCursor::pos(); resizeToWidth(width()); + _recentShownCount = _filteredStickers.size(); updateSelected(); } +rpl::producer StickersListWidget::recentShownCount() const { + return _recentShownCount.value(); +} + void StickersListWidget::fillLocalSearchRows(const QString &query) { const auto searchWordsList = TextUtilities::PrepareSearchWords(query); if (searchWordsList.isEmpty()) { @@ -910,6 +917,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { toColumn = _columnCount - toColumn; } + _paintAsPremium = session().premium(); _pathGradient->startFrame(0, width(), width() / 2); auto &sets = shownSets(); @@ -1489,7 +1497,7 @@ void StickersListWidget::paintSticker( : (set.id == SearchEmojiSectionSetId()) ? &_filterStickersCornerEmoji : nullptr; - if (corner && !corner->empty()) { + if (corner && !corner->empty() && _paintAsPremium) { Assert(index < corner->size()); if (const auto emoji = (*corner)[index]) { const auto size = Ui::Emoji::GetSizeNormal(); @@ -1960,6 +1968,11 @@ void StickersListWidget::setSection(Section section) { } clearHeavyData(); _section = section; + _recentShownCount = (section == Section::Search) + ? _filteredStickers.size() + : _mySets.empty() + ? 0 + : _mySets.front().stickers.size(); } void StickersListWidget::clearHeavyData() { @@ -2242,6 +2255,9 @@ void StickersListWidget::refreshRecentStickers(bool performResize) { clearSelection(); auto recentPack = collectRecentStickers(); + if (_section == Section::Stickers) { + _recentShownCount = recentPack.size(); + } auto recentIt = std::find_if(_mySets.begin(), _mySets.end(), [](auto &set) { return set.id == Data::Stickers::RecentSetId; }); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index 1f1de231b..f1a89b210 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -128,6 +128,7 @@ public: bool mySetsEmpty() const; void applySearchQuery(std::vector &&query); + [[nodiscard]] rpl::producer recentShownCount() const; ~StickersListWidget(); @@ -388,6 +389,7 @@ private: base::flat_set> _favedStickersMap; std::weak_ptr _lottieRenderer; + bool _paintAsPremium = false; bool _showingSetById = false; crl::time _lastScrolledAt = 0; crl::time _lastFullUpdatedAt = 0; @@ -437,6 +439,7 @@ private: std::vector> _filteredStickers; std::vector _filterStickersCornerEmoji; + rpl::variable _recentShownCount; std::map> _searchCache; std::vector> _searchIndex; base::Timer _searchRequestTimer; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 2f55b6048..7173d3aed 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -972,6 +972,7 @@ void Selector::createList() { : st::reactPanelScrollRounded); _scroll->hide(); + const auto effects = !_reactions.stickers.empty(); const auto st = lifetime().make_state(_st); st->padding.setTop(_skipy); if (!_reactions.customAllowed) { @@ -979,15 +980,35 @@ void Selector::createList() { } auto lists = _scroll->setOwnedWidget( object_ptr(_scroll)); + auto recentList = _strip + ? _unifiedFactoryOwner->unifiedIdsList() + : _recent; + auto freeEffects = base::flat_set(); + if (effects) { + auto free = base::flat_set(); + free.reserve(_reactions.recent.size()); + for (const auto &reaction : _reactions.recent) { + if (!reaction.premium) { + free.emplace(reaction.id); + } + } + for (const auto &id : recentList) { + const auto reactionId = _strip + ? _unifiedFactoryOwner->lookupReactionId(id) + : Data::ReactionId{ id }; + if (free.contains(reactionId)) { + freeEffects.insert(id); + } + } + } _list = lists->add( object_ptr(lists, EmojiListDescriptor{ .show = _show, .mode = _listMode, .paused = [] { return false; }, - .customRecentList = (_strip - ? _unifiedFactoryOwner->unifiedIdsList() - : _recent), + .customRecentList = std::move(recentList), .customRecentFactory = _unifiedFactoryOwner->factory(), + .freeEffects = std::move(freeEffects), .st = st, })); if (!_reactions.stickers.empty()) { @@ -1092,6 +1113,27 @@ void Selector::createList() { ) | rpl::start_with_next([=](std::vector &&query) { _stickers->applySearchQuery(std::move(query)); }, _stickers->lifetime()); + + + rpl::combine( + _list->recentShownCount(), + _stickers->recentShownCount() + ) | rpl::start_with_next([=](int emoji, int stickers) { + _showEmptySearch = !emoji && !stickers; + _scroll->update(); + }, _scroll->lifetime()); + + _scroll->paintRequest() | rpl::filter([=] { + return _showEmptySearch; + }) | rpl::start_with_next([=] { + auto p = QPainter(_scroll); + p.setPen(st::windowSubTextFg); + p.setFont(st::normalFont); + p.drawText( + _scroll->rect(), + tr::lng_effect_none(tr::now), + style::al_center); + }, _scroll->lifetime()); } else { _list->setMinimalHeight(geometry.width(), _scroll->height()); } diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index ad173385d..d5b1e05d5 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -205,6 +205,7 @@ private: Ui::PlainShadow *_shadow = nullptr; rpl::variable _shadowTop = 0; rpl::variable _shadowSkip = 0; + bool _showEmptySearch = false; QImage _paintBuffer; Ui::Animations::Simple _expanding; diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 3cfc57d0b..b56491bcb 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -29,9 +29,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/effects/path_shift_gradient.h" #include "ui/effects/ripple_animation.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" +#include "ui/wrap/padding_wrap.h" #include "ui/painter.h" #include "data/data_document.h" #include "data/data_document_media.h" @@ -42,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "main/main_session.h" #include "apiwrap.h" +#include "settings/settings_premium.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" #include "styles/style_chat.h" @@ -89,6 +93,8 @@ private: void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; + [[nodiscard]] bool canSend() const; + void setupGeometry(QPoint position); void setupBackground(); void setupItem(); @@ -110,6 +116,9 @@ private: const AdminLog::OwnedItem _replyTo; const AdminLog::OwnedItem _item; const std::unique_ptr _send; + const std::unique_ptr> _premiumPromoLabel; + const not_null _bottom; + const Fn _close; const Fn _actionWithEffect; QImage _icon; @@ -236,11 +245,26 @@ EffectPreview::EffectPreview( _replyTo->data()->fullId(), tr::lng_settings_chat_message_reply(tr::now), _effectId)) -, _send( - std::make_unique( +, _send(canSend() + ? std::make_unique( this, tr::lng_effect_send(tr::now), - st::effectPreviewSend)) + st::effectPreviewSend) + : nullptr) +, _premiumPromoLabel(canSend() + ? nullptr + : std::make_unique>( + this, + object_ptr( + this, + tr::lng_effect_premium( + lt_link, + tr::lng_effect_premium_link() | Ui::Text::ToLink(), + Ui::Text::WithEntities), + st::effectPreviewPromoLabel), + st::effectPreviewPromoPadding)) +, _bottom(_send ? ((Ui::RpWidget*)_send.get()) : _premiumPromoLabel.get()) +, _close(done) , _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) { setupGeometry(position); setupBackground(); @@ -291,8 +315,9 @@ void EffectPreview::setupGeometry(QPoint position) { const auto shadow = st::previewMenu.shadow; const auto extend = shadow.extend; _inner = QRect(QPoint(extend.left(), extend.top()), innerSize); + _bottom->resizeToWidth(_inner.width()); const auto size = _inner.marginsAdded(extend).size() - + QSize(0, _send->height()); + + QSize(0, _bottom->height()); const auto left = std::max( std::min( position.x() - size.width() / 2, @@ -305,11 +330,11 @@ void EffectPreview::setupGeometry(QPoint position) { parent->height() - size.height()), topMin); setGeometry(left, top, size.width(), size.height()); - _send->setGeometry( + _bottom->setGeometry( _inner.x(), _inner.y() + _inner.height(), _inner.width(), - _send->height()); + _bottom->height()); } void EffectPreview::setupBackground() { @@ -343,7 +368,7 @@ void EffectPreview::setupItem() { void EffectPreview::repaintBackground() { const auto ratio = style::DevicePixelRatio(); - const auto inner = _inner.size() + QSize(0, _send->height()); + const auto inner = _inner.size() + QSize(0, _bottom->height()); auto bg = QImage( inner * ratio, QImage::Format_ARGB32_Premultiplied); @@ -357,7 +382,7 @@ void EffectPreview::repaintBackground() { QSize(inner.width(), inner.height() * 5), QRect(QPoint(), inner)); p.fillRect( - QRect(0, _inner.height(), _inner.width(), _send->height()), + QRect(0, _inner.height(), _inner.width(), _bottom->height()), st::previewMarkRead.bgColor); auto hq = PainterHighQualityEnabler(p); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); @@ -409,14 +434,32 @@ void EffectPreview::createLottie() { }, raw->lifetime()); } +bool EffectPreview::canSend() const { + return !_effect.premium || _show->session().premium(); +} + void EffectPreview::setupSend(Details details) { - _send->setClickedCallback([=] { - _actionWithEffect(Api::SendOptions(), details); - }); - const auto type = details.type; - SetupMenuAndShortcuts(_send.get(), _show, [=] { - return Details{ .type = type }; - }, _actionWithEffect); + if (_send) { + _send->setClickedCallback([=] { + _actionWithEffect(Api::SendOptions(), details); + }); + const auto type = details.type; + SetupMenuAndShortcuts(_send.get(), _show, [=] { + return Details{ .type = type }; + }, _actionWithEffect); + } else { + _premiumPromoLabel->entity()->setClickHandlerFilter([=](auto&&...) { + const auto window = _show->resolveWindow( + ChatHelpers::WindowUsage::PremiumPromo); + if (window) { + if (const auto onstack = _close) { + onstack(); + } + Settings::ShowPremium(window, "message_effect"); + } + return false; + }); + } } bool EffectPreview::checkReady() { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index f194dc8aa..182861622 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1120,3 +1120,12 @@ effectPreviewSend: FlatButton(previewMarkRead) { bgColor: transparent; overBgColor: transparent; } +effectPreviewPromoLabel: FlatLabel(defaultFlatLabel) { + minWidth: 64px; + align: align(top); + textFg: windowSubTextFg; + style: TextStyle(defaultTextStyle) { + font: font(11px); + } +} +effectPreviewPromoPadding: margins(4px, 6px, 4px, 6px); From ec5d8b73736f7700339f0aeda5a7ce7631858818 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 May 2024 13:07:37 +0400 Subject: [PATCH 066/225] More robust effect icon loading. --- .../SourceFiles/data/data_message_reactions.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 78baff83e..7a834dcc1 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -584,15 +584,14 @@ void Reactions::preloadImageFor(const ReactionId &id) { } auto &set = _images.emplace(id).first->second; set.effect = (id.custom() != 0); - const auto i = set.effect - ? ranges::find(_effects, id, &Reaction::id) - : ranges::find(_available, id, &Reaction::id); - const auto document = (i == end(set.effect ? _effects : _available)) + auto &list = set.effect ? _effects : _available; + const auto i = ranges::find(list, id, &Reaction::id); + const auto document = (i == end(list)) ? nullptr : i->centerIcon ? i->centerIcon : i->selectAnimation.get(); - if (document || (set.effect && i != end(_effects))) { + if (document || (set.effect && i != end(list))) { if (!set.effect || i->centerIcon) { loadImage(set, document, !i->centerIcon); } else { @@ -656,11 +655,13 @@ QImage Reactions::resolveEffectImageFor(EffectId id) { } QImage Reactions::resolveImageFor(const ReactionId &id) { - const auto i = _images.find(id); + auto i = _images.find(id); if (i == end(_images)) { preloadImageFor(id); + i = _images.find(id); + Assert(i != end(_images)); } - auto &set = (i != end(_images)) ? i->second : _images[id]; + auto &set = i->second; set.effect = (id.custom() != 0); const auto resolve = [&](QImage &image, int size) { From 8a58ded58268b1f597678004c44afbe34d8028d8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 May 2024 13:31:30 +0400 Subject: [PATCH 067/225] Show effect loading animation. --- Telegram/SourceFiles/menu/menu_send.cpp | 41 ++++++++++++++++++++++--- Telegram/SourceFiles/ui/chat/chat.style | 3 ++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index b56491bcb..450c50d55 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/effects/path_shift_gradient.h" +#include "ui/effects/radial_animation.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" @@ -103,6 +104,7 @@ private: void setupSend(Details details); void createLottie(); + bool checkIconBecameLoaded(); [[nodiscard]] bool checkReady(); const EffectId _effectId = 0; @@ -130,6 +132,8 @@ private: QRect _inner; QImage _bg; QPoint _itemShift; + QRect _iconRect; + std::unique_ptr _loading; rpl::lifetime _readyCheckLifetime; @@ -289,6 +293,28 @@ void EffectPreview::paintEvent(QPaintEvent *e) { _item->draw(p, context); p.translate(-_itemShift); + checkIconBecameLoaded(); + if (_icon.isNull()) { + if (!_loading) { + _loading = std::make_unique([=] { + update(); + }, st::effectPreviewLoading); + _loading->start(st::defaultInfiniteRadialAnimation.linearPeriod); + } + const auto loading = _iconRect.marginsRemoved( + { st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth }); + auto hq = PainterHighQualityEnabler(p); + Ui::InfiniteRadialAnimation::Draw( + p, + _loading->computeState(), + loading.topLeft(), + loading.size(), + width(), + _chatStyle->msgInDateFg(), + st::effectPreviewLoading.thickness); + } else { + _loading = nullptr; + } if (_lottie && _lottie->ready()) { const auto factor = style::DevicePixelRatio(); auto request = Lottie::FrameRequest(); @@ -364,6 +390,7 @@ void EffectPreview::setupItem() { shift + icon.x() + (icon.width() - size.width()) / 2, icon.y() + (icon.height() - size.height()) / 2); _itemShift = _inner.topLeft() - position; + _iconRect = icon.translated(_itemShift); } void EffectPreview::repaintBackground() { @@ -462,11 +489,17 @@ void EffectPreview::setupSend(Details details) { } } +bool EffectPreview::checkIconBecameLoaded() { + if (!_icon.isNull()) { + return false; + } + const auto reactions = &_show->session().data().reactions(); + _icon = reactions->resolveEffectImageFor(_effect.id.custom()); + return !_icon.isNull(); +} + bool EffectPreview::checkReady() { - if (_icon.isNull()) { - const auto reactions = &_show->session().data().reactions(); - _icon = reactions->resolveEffectImageFor(_effect.id.custom()); - repaintBackground(); + if (checkIconBecameLoaded()) { update(); } if (_effect.aroundAnimation) { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 182861622..1d7361bdd 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1129,3 +1129,6 @@ effectPreviewPromoLabel: FlatLabel(defaultFlatLabel) { } } effectPreviewPromoPadding: margins(4px, 6px, 4px, 6px); +effectPreviewLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + thickness: 2px; +} From 487fa9728a8943c298e8b94183c96312e9e67197 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 May 2024 14:07:38 +0400 Subject: [PATCH 068/225] Fade in/out effect preview. --- .../data/data_message_reactions.cpp | 8 +- .../SourceFiles/data/data_message_reactions.h | 4 + .../history_view_reactions_selector.cpp | 16 +- .../history_view_reactions_selector.h | 6 +- .../media/stories/media_stories_reactions.cpp | 3 +- Telegram/SourceFiles/menu/menu_send.cpp | 182 ++++++++++++------ 6 files changed, 155 insertions(+), 64 deletions(-) diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 7a834dcc1..f1175a473 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -575,7 +575,9 @@ void Reactions::preloadReactionImageFor(const ReactionId &emoji) { } void Reactions::preloadEffectImageFor(EffectId id) { - preloadImageFor({ DocumentId(id) }); + if (id != kFakeEffectId) { + preloadImageFor({ DocumentId(id) }); + } } void Reactions::preloadImageFor(const ReactionId &id) { @@ -651,7 +653,9 @@ QImage Reactions::resolveReactionImageFor(const ReactionId &emoji) { } QImage Reactions::resolveEffectImageFor(EffectId id) { - return resolveImageFor({ DocumentId(id) }); + return (id == kFakeEffectId) + ? QImage() + : resolveImageFor({ DocumentId(id) }); } QImage Reactions::resolveImageFor(const ReactionId &id) { diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 98aab8d85..793fcc198 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -119,6 +119,10 @@ public: void preloadReactionImageFor(const ReactionId &emoji); [[nodiscard]] QImage resolveReactionImageFor(const ReactionId &emoji); + // This is used to reserve space for the effect in BottomInfo but not + // actually paint anything, used in case we want to paint icon ourselves. + static constexpr auto kFakeEffectId = EffectId(1); + void preloadEffectImageFor(EffectId id); [[nodiscard]] QImage resolveEffectImageFor(EffectId id); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 7173d3aed..dcac6a69e 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -203,6 +203,7 @@ Selector::Selector( TextWithEntities about, Fn close, IconFactory iconFactory, + Fn paused, bool child) : Selector( parent, @@ -216,8 +217,9 @@ Selector::Selector( : ChatHelpers::EmojiListMode::MessageEffects), {}, std::move(about), - iconFactory, - close, + std::move(iconFactory), + std::move(paused), + std::move(close), child) { } @@ -253,6 +255,7 @@ Selector::Selector( std::vector recent, TextWithEntities about, IconFactory iconFactory, + Fn paused, Fn close, bool child) : RpWidget(parent) @@ -261,6 +264,7 @@ Selector::Selector( , _reactions(reactions) , _recent(std::move(recent)) , _listMode(mode) +, _paused(std::move(paused)) , _jumpedToPremium([=] { close(false); }) , _cachedRound( QSize(2 * st::reactStripSkip + st::reactStripSize, st::reactStripHeight), @@ -1005,7 +1009,7 @@ void Selector::createList() { object_ptr(lists, EmojiListDescriptor{ .show = _show, .mode = _listMode, - .paused = [] { return false; }, + .paused = _paused ? _paused : [] { return false; }, .customRecentList = std::move(recentList), .customRecentFactory = _unifiedFactoryOwner->factory(), .freeEffects = std::move(freeEffects), @@ -1026,7 +1030,7 @@ void Selector::createList() { StickersListDescriptor{ .show = _show, .mode = StickersListMode::MessageEffects, - .paused = [] { return false; }, + .paused = _paused ? _paused : [] { return false; }, .customRecentList = std::move(descriptors), .st = st, })); @@ -1352,7 +1356,8 @@ auto AttachSelectorToMenu( std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, TextWithEntities about, - IconFactory iconFactory) + IconFactory iconFactory, + Fn paused) -> base::expected, AttachSelectorResult> { if (reactions.recent.empty()) { return base::make_unexpected(AttachSelectorResult::Skipped); @@ -1366,6 +1371,7 @@ auto AttachSelectorToMenu( std::move(about), [=](bool fast) { menu->hideMenu(fast); }, std::move(iconFactory), + std::move(paused), false); // child if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) { return base::make_unexpected(AttachSelectorResult::Failed); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index d5b1e05d5..56f5210b7 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -85,6 +85,7 @@ public: TextWithEntities about, Fn close, IconFactory iconFactory = nullptr, + Fn paused = nullptr, bool child = false); #if 0 // not ready Selector( @@ -149,6 +150,7 @@ private: std::vector recent, TextWithEntities about, IconFactory iconFactory, + Fn paused, Fn close, bool child); @@ -187,6 +189,7 @@ private: const Data::PossibleItemReactions _reactions; const std::vector _recent; const ChatHelpers::EmojiListMode _listMode; + const Fn _paused; Fn _jumpedToPremium; Ui::RoundAreaWithShadow _cachedRound; std::unique_ptr _strip; @@ -274,7 +277,8 @@ AttachSelectorResult AttachSelectorToMenu( std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, TextWithEntities about, - IconFactory iconFactory = nullptr + IconFactory iconFactory = nullptr, + Fn paused = nullptr ) -> base::expected, AttachSelectorResult>; [[nodiscard]] TextWithEntities ItemReactionsAbout( diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 30055ad0b..af45d9150 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -668,7 +668,8 @@ void Reactions::Panel::create() { ? tr::lng_stories_reaction_as_message(tr::now) : QString()) }, [=](bool fast) { hide(mode); }, - nullptr, + nullptr, // iconFactory + nullptr, // paused true); _selector->chosen( diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 450c50d55..98645e79f 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -59,6 +59,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace SendMenu { namespace { +constexpr auto kToggleDuration = crl::time(400); + class Delegate final : public HistoryView::DefaultElementDelegate { public: Delegate(not_null pathGradient) @@ -90,6 +92,8 @@ public: Fn action, Fn done); + void hideAnimated(); + private: void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; @@ -104,8 +108,12 @@ private: void setupSend(Details details); void createLottie(); + [[nodiscard]] bool ready() const; + void paintLoading(QPainter &p); + void paintLottie(QPainter &p); bool checkIconBecameLoaded(); - [[nodiscard]] bool checkReady(); + [[nodiscard]] bool checkLoaded(); + void toggle(bool shown); const EffectId _effectId = 0; const Data::Reaction _effect; @@ -135,6 +143,10 @@ private: QRect _iconRect; std::unique_ptr _loading; + Ui::Animations::Simple _shownAnimation; + QPixmap _bottomCache; + bool _hiding = false; + rpl::lifetime _readyCheckLifetime; }; @@ -248,7 +260,7 @@ EffectPreview::EffectPreview( _history->peer->id, _replyTo->data()->fullId(), tr::lng_settings_chat_message_reply(tr::now), - _effectId)) + Data::Reactions::kFakeEffectId)) , _send(canSend() ? std::make_unique( this, @@ -271,68 +283,87 @@ EffectPreview::EffectPreview( , _close(done) , _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) { setupGeometry(position); - setupBackground(); setupItem(); + setupBackground(); setupLottie(); setupSend(details); + + toggle(true); } void EffectPreview::paintEvent(QPaintEvent *e) { - auto p = Painter(this); + checkIconBecameLoaded(); + + const auto progress = _shownAnimation.value(_hiding ? 0. : 1.); + if (!progress) { + return; + } + + auto p = QPainter(this); + p.setOpacity(progress); p.drawImage(0, 0, _bg); - p.setClipRect(_inner); - p.translate(_itemShift); - auto rect = QRect(0, 0, st::windowMinWidth, _inner.height()); - auto context = _theme->preparePaintContext( - _chatStyle.get(), - rect, - rect, - false); - context.outbg = _item->hasOutLayout(); - _item->draw(p, context); - p.translate(-_itemShift); + if (!_bottomCache.isNull()) { + p.drawPixmap(_bottom->pos(), _bottomCache); + } - checkIconBecameLoaded(); - if (_icon.isNull()) { - if (!_loading) { - _loading = std::make_unique([=] { - update(); - }, st::effectPreviewLoading); - _loading->start(st::defaultInfiniteRadialAnimation.linearPeriod); - } - const auto loading = _iconRect.marginsRemoved( - { st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth }); - auto hq = PainterHighQualityEnabler(p); - Ui::InfiniteRadialAnimation::Draw( - p, - _loading->computeState(), - loading.topLeft(), - loading.size(), - width(), - _chatStyle->msgInDateFg(), - st::effectPreviewLoading.thickness); + if (!ready()) { + paintLoading(p); } else { _loading = nullptr; - } - if (_lottie && _lottie->ready()) { - const auto factor = style::DevicePixelRatio(); - auto request = Lottie::FrameRequest(); - request.box = _inner.size() * factor; - const auto rightAligned = _item->hasRightLayout(); - if (!rightAligned) { - request.mirrorHorizontal = true; + p.drawImage(_iconRect, _icon); + if (!_hiding) { + p.setOpacity(1.); } - const auto frame = _lottie->frameInfo(request); - p.drawImage( - QRect(_inner.topLeft(), frame.image.size() / factor), - frame.image); - _lottie->markFrameShown(); + paintLottie(p); } } +bool EffectPreview::ready() const { + return !_icon.isNull() && _lottie && _lottie->ready(); +} + +void EffectPreview::paintLoading(QPainter &p) { + if (!_loading) { + _loading = std::make_unique([=] { + update(); + }, st::effectPreviewLoading); + _loading->start(st::defaultInfiniteRadialAnimation.linearPeriod); + } + const auto loading = _iconRect.marginsRemoved( + { st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth }); + auto hq = PainterHighQualityEnabler(p); + Ui::InfiniteRadialAnimation::Draw( + p, + _loading->computeState(), + loading.topLeft(), + loading.size(), + width(), + _chatStyle->msgInDateFg(), + st::effectPreviewLoading.thickness); +} + +void EffectPreview::paintLottie(QPainter &p) { + const auto factor = style::DevicePixelRatio(); + auto request = Lottie::FrameRequest(); + request.box = _inner.size() * factor; + const auto rightAligned = _item->hasRightLayout(); + if (!rightAligned) { + request.mirrorHorizontal = true; + } + const auto frame = _lottie->frameInfo(request); + p.drawImage( + QRect(_inner.topLeft(), frame.image.size() / factor), + frame.image); + _lottie->markFrameShown(); +} + +void EffectPreview::hideAnimated() { + toggle(false); +} + void EffectPreview::mousePressEvent(QMouseEvent *e) { - delete this; + hideAnimated(); } void EffectPreview::setupGeometry(QPoint position) { @@ -402,7 +433,7 @@ void EffectPreview::repaintBackground() { bg.setDevicePixelRatio(ratio); { - auto p = QPainter(&bg); + auto p = Painter(&bg); Window::SectionWidget::PaintBackground( p, _theme.get(), @@ -411,6 +442,18 @@ void EffectPreview::repaintBackground() { p.fillRect( QRect(0, _inner.height(), _inner.width(), _bottom->height()), st::previewMarkRead.bgColor); + + p.translate(_itemShift - _inner.topLeft()); + auto rect = QRect(0, 0, st::windowMinWidth, _inner.height()); + auto context = _theme->preparePaintContext( + _chatStyle.get(), + rect, + rect, + false); + context.outbg = _item->hasOutLayout(); + _item->draw(p, context); + p.translate(_inner.topLeft() - _itemShift); + auto hq = PainterHighQualityEnabler(p); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); auto roundRect = Ui::RoundRect(st::previewMenu.radius, st::menuBg); @@ -438,7 +481,7 @@ void EffectPreview::setupLottie() { rpl::single(rpl::empty) | rpl::then( _show->session().downloaderTaskFinished() ) | rpl::start_with_next([=] { - if (checkReady()) { + if (checkLoaded()) { _readyCheckLifetime.destroy(); createLottie(); } @@ -495,10 +538,14 @@ bool EffectPreview::checkIconBecameLoaded() { } const auto reactions = &_show->session().data().reactions(); _icon = reactions->resolveEffectImageFor(_effect.id.custom()); - return !_icon.isNull(); + if (_icon.isNull()) { + return false; + } + repaintBackground(); + return true; } -bool EffectPreview::checkReady() { +bool EffectPreview::checkLoaded() { if (checkIconBecameLoaded()) { update(); } @@ -511,6 +558,29 @@ bool EffectPreview::checkReady() { return !_icon.isNull() && (!_bytes.isEmpty() || !_filepath.isEmpty()); } +void EffectPreview::toggle(bool shown) { + if (!shown && _hiding) { + return; + } + _hiding = !shown; + if (_bottomCache.isNull()) { + _bottomCache = Ui::GrabWidget(_bottom); + _bottom->hide(); + } + _shownAnimation.start([=] { + update(); + if (!_shownAnimation.animating()) { + if (_hiding) { + delete this; + } else { + _bottomCache = QPixmap(); + _bottom->show(); + } + } + }, shown ? 0. : 1., shown ? 1. : 0., kToggleDuration, anim::easeOutCirc); + show(); +} + } // namespace Fn DefaultCallback( @@ -571,6 +641,7 @@ FillMenuResult FillSendMenu( } using namespace HistoryView::Reactions; + const auto effect = std::make_shared>(); const auto position = desiredPositionOverride.value_or(QCursor::pos()); const auto selector = (showForEffect && details.effectAllowed) ? AttachSelectorToMenu( @@ -579,7 +650,9 @@ FillMenuResult FillSendMenu( st::reactPanelEmojiPan, showForEffect, LookupPossibleEffects(&showForEffect->session()), - { tr::lng_effect_add_title(tr::now) }) + { tr::lng_effect_add_title(tr::now) }, + nullptr, // iconFactory + [=] { return (*effect) != nullptr; }) // paused : base::make_unexpected(AttachSelectorResult::Skipped); if (!selector) { if (selector.error() == AttachSelectorResult::Failed) { @@ -589,7 +662,6 @@ FillMenuResult FillSendMenu( return FillMenuResult::Prepared; } - const auto effect = std::make_shared>(); (*selector)->chosen( ) | rpl::start_with_next([=](ChosenReaction chosen) { const auto &reactions = showForEffect->session().data().reactions(); @@ -597,7 +669,7 @@ FillMenuResult FillSendMenu( const auto i = ranges::find(effects, chosen.id, &Data::Reaction::id); if (i != end(effects)) { if (const auto strong = effect->data()) { - delete strong; + strong->hideAnimated(); } const auto weak = Ui::MakeWeak(menu); const auto done = [=] { From a011a7c3160c2d3ec3e46c01e58bcd94bb6545a0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 May 2024 14:47:34 +0400 Subject: [PATCH 069/225] Fix sending scheduled effects. --- .../SourceFiles/boxes/create_poll_box.cpp | 4 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 2 +- Telegram/SourceFiles/boxes/share_box.cpp | 29 +++++------- Telegram/SourceFiles/boxes/share_box.h | 1 - .../SourceFiles/history/history_widget.cpp | 13 +++-- Telegram/SourceFiles/history/history_widget.h | 2 +- .../view/history_view_context_menu.cpp | 1 + .../view/history_view_schedule_box.cpp | 35 +++++++++----- .../history/view/history_view_schedule_box.h | 8 ++-- Telegram/SourceFiles/menu/menu_send.cpp | 47 ++++++++++--------- Telegram/SourceFiles/menu/menu_send.h | 13 +++-- 11 files changed, 86 insertions(+), 69 deletions(-) diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 8b032b81a..151ab54b8 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -1304,7 +1304,9 @@ object_ptr CreatePollBox::setupContent() { const auto isNormal = (_sendType == Api::SendType::Normal); const auto schedule = [=] { - sendAction(SendMenu::ActionType::Schedule, _sendMenuDetails()); + sendAction( + { .type = SendMenu::ActionType::Schedule }, + _sendMenuDetails()); }; const auto submit = addButton( (isNormal diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 6ba4771f8..c28a1dc70 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -1427,7 +1427,7 @@ void SendFilesBox::send( || _sendType == Api::SendType::ScheduledToUser) && !options.scheduled) { return SendMenu::DefaultCallback(_show, sendCallback())( - SendMenu::ActionType::Schedule, + { .type = SendMenu::ActionType::Schedule }, _sendMenuDetails()); } if (_preparing) { diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 3d42c5afe..043a8ad18 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -523,12 +523,19 @@ void ShareBox::showMenu(not_null parent) { using namespace SendMenu; const auto sendAction = crl::guard(this, [=](Action action, Details) { - const auto options = std::get_if(&action); - if (options || v::get(action) == ActionType::Send) { - submit(options ? *options : Api::SendOptions()); - } else { - submitScheduled(); + if (action.type == ActionType::Send) { + submit(action.options); + return; } + uiShow()->showBox( + HistoryView::PrepareScheduleBox( + this, + nullptr, // ChatHelpers::Show for effect attachment. + sendMenuDetails(), + [=](Api::SendOptions options) { submit(options); }, + action.options, + HistoryView::DefaultScheduleTime(), + _descriptor.scheduleBoxStyle)); }); _menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom); const auto result = FillSendMenu( @@ -620,18 +627,6 @@ void ShareBox::submit(Api::SendOptions options) { } } -void ShareBox::submitScheduled() { - const auto callback = [=](Api::SendOptions options) { submit(options); }; - uiShow()->showBox( - HistoryView::PrepareScheduleBox( - this, - nullptr, // ChatHelpers::Show for effect attachment. - sendMenuDetails(), - callback, - HistoryView::DefaultScheduleTime(), - _descriptor.scheduleBoxStyle)); -} - void ShareBox::copyLink() const { if (const auto onstack = _descriptor.copyCallback) { onstack(); diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index 5e48430cd..32e824b15 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -130,7 +130,6 @@ private: void scrollAnimationCallback(); void submit(Api::SendOptions options); - void submitScheduled(); void copyLink() const; bool searchByUsername(bool useCache = false); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ca3803cff..e68bade1a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -324,11 +324,10 @@ HistoryWidget::HistoryWidget( { using namespace SendMenu; const auto sendAction = [=](Action action, Details) { - const auto options = std::get_if(&action); - if (options || v::get(action) == ActionType::Send) { - send(options ? *options : Api::SendOptions()); + if (action.type == ActionType::Send) { + send(action.options); } else { - sendScheduled(); + sendScheduled(action.options); } }; SetupMenuAndShortcuts( @@ -4192,7 +4191,7 @@ void HistoryWidget::sendWithModifiers(Qt::KeyboardModifiers modifiers) { send({ .handleSupportSwitch = Support::HandleSwitch(modifiers) }); } -void HistoryWidget::sendScheduled() { +void HistoryWidget::sendScheduled(Api::SendOptions initialOptions) { if (!_list) { return; } @@ -4202,13 +4201,13 @@ void HistoryWidget::sendScheduled() { ignoreSlowmodeCountdown)) { return; } - const auto callback = [=](Api::SendOptions options) { send(options); }; controller()->show( HistoryView::PrepareScheduleBox( _list, controller()->uiShow(), sendMenuDetails(), - callback)); + [=](Api::SendOptions options) { send(options); }, + initialOptions)); } SendMenu::Details HistoryWidget::sendMenuDetails() const { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index ca2a9ed47..bc3641e67 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -395,7 +395,7 @@ private: Api::SendOptions options) const; void send(Api::SendOptions options); void sendWithModifiers(Qt::KeyboardModifiers modifiers); - void sendScheduled(); + void sendScheduled(Api::SendOptions initialOptions); [[nodiscard]] SendMenu::Details sendButtonMenuDetails() const; void handlePendingHistoryUpdate(); void fullInfoUpdated(); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 781a4875d..a02f4c786 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -592,6 +592,7 @@ bool AddRescheduleAction( request.navigation->uiShow(), { .type = sendMenuType, .effectAllowed = false }, callback, + {}, // initial options date)); owner->itemRemoved( diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index 09288fed7..9ab0af794 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -70,28 +70,34 @@ bool CanScheduleUntilOnline(not_null peer) { void ScheduleBox( not_null box, std::shared_ptr show, + const Api::SendOptions &initialOptions, const SendMenu::Details &details, Fn done, TimeId time, ScheduleBoxStyleArgs style) { - const auto save = [=](bool silent, TimeId scheduleDate) { - if (!scheduleDate) { + const auto submit = [=](Api::SendOptions options) { + if (!options.scheduled) { return; } - auto result = Api::SendOptions(); // Pro tip: Hold Ctrl key to send a silent scheduled message! - result.silent = silent || base::IsCtrlPressed(); - result.scheduled = scheduleDate; + if (base::IsCtrlPressed()) { + options.silent = true; + } const auto copy = done; box->closeBox(); - copy(result); + copy(options); + }; + const auto with = [=](TimeId scheduled) { + auto result = initialOptions; + result.scheduled = scheduled; + return result; }; auto descriptor = Ui::ChooseDateTimeBox(box, { .title = (details.type == SendMenu::Type::Reminder ? tr::lng_remind_title() : tr::lng_schedule_title()), .submit = tr::lng_schedule_button(), - .done = [=](TimeId result) { save(false, result); }, + .done = [=](TimeId result) { submit(with(result)); }, .time = time, .style = style.chooseDateTimeArgs, }); @@ -105,9 +111,16 @@ void ScheduleBox( .effectAllowed = details.effectAllowed, }; const auto sendAction = crl::guard(box, [=](Action action, Details) { - save( - v::get(action).silent, - descriptor.collect()); + Expects(action.type == ActionType::Send); + + auto options = with(descriptor.collect()); + if (action.options.silent) { + options.silent = action.options.silent; + } + if (action.options.effectId) { + options.effectId = action.options.effectId; + } + submit(options); }); SetupMenuAndShortcuts( descriptor.submit.data(), @@ -120,7 +133,7 @@ void ScheduleBox( const auto timestamp = Api::kScheduledUntilOnlineTimestamp; FillSendUntilOnlineMenu( sendUntilOnline.data(), - [=] { save(false, timestamp); }, + [=] { submit(with(timestamp)); }, style); } } diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.h b/Telegram/SourceFiles/history/view/history_view_schedule_box.h index a69177102..5f5385c73 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.h +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "api/api_common.h" #include "ui/boxes/choose_date_time.h" namespace style { @@ -14,10 +15,6 @@ struct IconButton; struct PopupMenu; } // namespace style -namespace Api { -struct SendOptions; -} // namespace Api - namespace ChatHelpers { class Show; } // namespace ChatHelpers @@ -41,6 +38,7 @@ struct ScheduleBoxStyleArgs { void ScheduleBox( not_null box, std::shared_ptr show, + const Api::SendOptions &initialOptions, const SendMenu::Details &details, Fn done, TimeId time, @@ -52,11 +50,13 @@ template std::shared_ptr show, const SendMenu::Details &details, Submit &&submit, + const Api::SendOptions &initialOptions = {}, TimeId scheduleTime = DefaultScheduleTime(), ScheduleBoxStyleArgs style = ScheduleBoxStyleArgs()) { return Box( ScheduleBox, std::move(show), + initialOptions, details, crl::guard(std::forward(guard), std::forward(submit)), scheduleTime, diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 98645e79f..89be56356 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -214,9 +214,8 @@ void BottomRounded::paintEvent(QPaintEvent *e) { EffectId id, Fn done) { return [=](Action action, Details details) { - if (const auto options = std::get_if(&action)) { - options->effectId = id; - } + action.options.effectId = id; + const auto onstack = done; sendAction(action, details); if (onstack) { @@ -511,7 +510,7 @@ bool EffectPreview::canSend() const { void EffectPreview::setupSend(Details details) { if (_send) { _send->setClickedCallback([=] { - _actionWithEffect(Api::SendOptions(), details); + _actionWithEffect({}, details); }); const auto type = details.type; SetupMenuAndShortcuts(_send.get(), _show, [=] { @@ -588,18 +587,20 @@ Fn DefaultCallback( Fn send) { const auto guard = Ui::MakeWeak(show->toastParent()); return [=](Action action, Details details) { - if (const auto options = std::get_if(&action)) { - send(*options); - } else if (v::get(action) == ActionType::Send) { - send({}); - } else { - using namespace HistoryView; - auto box = PrepareScheduleBox(guard, show, details, send); - const auto weak = Ui::MakeWeak(box.data()); - show->showBox(std::move(box)); - if (const auto strong = weak.data()) { - strong->setCloseByOutsideClick(false); - } + if (action.type == ActionType::Send) { + send(action.options); + return; + } + auto box = HistoryView::PrepareScheduleBox( + guard, + show, + details, + send, + action.options); + const auto weak = Ui::MakeWeak(box.data()); + show->showBox(std::move(box)); + if (const auto strong = weak.data()) { + strong->setCloseByOutsideClick(false); } }; } @@ -622,7 +623,9 @@ FillMenuResult FillSendMenu( if (type != Type::Reminder) { menu->addAction( tr::lng_send_silent_message(tr::now), - [=] { action(Api::SendOptions{ .silent = true }, details); }, + [=] { action( + { Api::SendOptions{ .silent = true } }, + details); }, &icons.menuMute); } if (type != Type::SilentOnly) { @@ -630,13 +633,15 @@ FillMenuResult FillSendMenu( (type == Type::Reminder ? tr::lng_reminder_message(tr::now) : tr::lng_schedule_message(tr::now)), - [=] { action(ActionType::Schedule, details); }, + [=] { action({ .type = ActionType::Schedule }, details); }, &icons.menuSchedule); } if (type == Type::ScheduledToUser) { menu->addAction( tr::lng_scheduled_send_until_online(tr::now), - [=] { action(Api::DefaultSendWhenOnlineOptions(), details); }, + [=] { action( + { Api::DefaultSendWhenOnlineOptions() }, + details); }, &icons.menuWhenOnline); } @@ -730,14 +735,14 @@ void SetupMenuAndShortcuts( ((now != Type::Reminder) && request->check(Command::SendSilentMessage) && request->handle([=] { - action(Api::SendOptions{ .silent = true }, details()); + action({ Api::SendOptions{ .silent = true } }, details()); return true; })) || ((now != Type::SilentOnly) && request->check(Command::ScheduleMessage) && request->handle([=] { - action(ActionType::Schedule, details()); + action({ .type = ActionType::Schedule }, details()); return true; })) || diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index 66004c179..f503a7e56 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -7,14 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "api/api_common.h" + namespace style { struct ComposeIcons; } // namespace style -namespace Api { -struct SendOptions; -} // namespace Api - namespace ChatHelpers { class Show; } // namespace ChatHelpers @@ -54,7 +52,12 @@ enum class ActionType { Send, Schedule, }; -using Action = std::variant; +struct Action { + using Type = ActionType; + + Api::SendOptions options; + Type type = Type::Send; +}; [[nodiscard]] Fn DefaultCallback( std::shared_ptr show, Fn send); From f7ab8a21748f1041bc4aaa992106a1a6cc1971db Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 May 2024 14:54:46 +0400 Subject: [PATCH 070/225] Fix Chat / Effect previews in custom themes. --- Telegram/SourceFiles/menu/menu_send.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 89be56356..67e5deaf8 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -281,6 +281,8 @@ EffectPreview::EffectPreview( , _bottom(_send ? ((Ui::RpWidget*)_send.get()) : _premiumPromoLabel.get()) , _close(done) , _actionWithEffect(ComposeActionWithEffect(action, _effectId, done)) { + _chatStyle->apply(_theme.get()); + setupGeometry(position); setupItem(); setupBackground(); From cde70b98079e685b35ebf5b3213b5a4c239f881c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 May 2024 18:16:25 +0400 Subject: [PATCH 071/225] Play effects in a separate layer over MainWidget. --- .../history/history_inner_widget.cpp | 8 +- .../view/history_view_emoji_interactions.cpp | 83 ++++++++++++++++--- .../view/history_view_emoji_interactions.h | 20 ++++- .../history/view/history_view_list_widget.cpp | 8 +- 4 files changed, 92 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 38483247d..1687b5049 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/call_delayed.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "mainwidget.h" #include "menu/menu_item_download_files.h" #include "core/application.h" #include "apiwrap.h" @@ -340,6 +341,8 @@ HistoryInner::HistoryInner( , _history(history) , _elementDelegate(_history->delegateMixin()->delegate()) , _emojiInteractions(std::make_unique( + this, + controller->content(), &controller->session(), [=](not_null view) { return itemTop(view); })) , _migrated(history->migrateFrom()) @@ -393,10 +396,6 @@ HistoryInner::HistoryInner( _emojiInteractions->play(std::move(request), view); } }, lifetime()); - _emojiInteractions->updateRequests( - ) | rpl::start_with_next([=](QRect rect) { - update(rect); - }, lifetime()); _emojiInteractions->playStarted( ) | rpl::start_with_next([=](QString &&emoji) { _controller->emojiInteractions().playStarted(_peer, std::move(emoji)); @@ -1238,7 +1237,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) { p.setOpacity(1.); _reactionsManager->paint(p, context); - _emojiInteractions->paint(p); } bool HistoryInner::eventHook(QEvent *e) { diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index efda8ead1..de775e17d 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -45,9 +45,13 @@ constexpr auto kDropDelayedAfterDelay = crl::time(2000); } // namespace EmojiInteractions::EmojiInteractions( + not_null parent, + not_null layerParent, not_null session, Fn)> itemTop) -: _session(session) +: _parent(parent) +, _layerParent(layerParent) +, _session(session) , _itemTop(std::move(itemTop)) { _session->data().viewRemoved( ) | rpl::filter([=] { @@ -282,6 +286,18 @@ void EmojiInteractions::play( return; } + if (!_layer) { + _layer = base::make_unique_q(_layerParent); + const auto raw = _layer.get(); + raw->setAttribute(Qt::WA_TransparentForMouseEvents); + raw->show(); + raw->paintRequest() | rpl::start_with_next([=](QRect clip) { + paint(raw, clip); + }, raw->lifetime()); + } + refreshLayerShift(); + _layer->setGeometry(_layerParent->rect()); + auto lottie = document->session().emojiStickersPack().effectPlayer( document, data, @@ -302,11 +318,12 @@ void EmojiInteractions::play( const auto i = ranges::find(_plays, raw, [](const Play &p) { return p.lottie.get(); }); - const auto rect = computeRect(*i).translated(shift); - if (rect.y() + rect.height() >= _visibleTop - && rect.y() <= _visibleBottom) { - _updateRequests.fire_copy(rect); + auto update = computeRect(*i).translated(shift + _layerShift); + if (!i->lastTarget.isEmpty()) { + update = i->lastTarget.united(update); } + _layer->update(update); + i->lastTarget = QRect(); }); }, lottie->lifetime()); _plays.push_back({ @@ -331,6 +348,16 @@ void EmojiInteractions::play( } } +void EmojiInteractions::refreshLayerShift() { + _layerShift = Ui::MapFrom(_layerParent, _parent, QPoint(0, 0)); +} + +void EmojiInteractions::refreshLayerGeometryAndUpdate(QRect rect) { + if (!rect.isEmpty()) { + _layer->update(rect.translated(_layerShift)); + } +} + void EmojiInteractions::visibleAreaUpdated( int visibleTop, int visibleBottom) { @@ -375,12 +402,34 @@ QRect EmojiInteractions::computeRect(const Play &play) const { return QRect(QPoint(left, top), size).translated(play.shift); } -void EmojiInteractions::paint(QPainter &p) { +void EmojiInteractions::paint(not_null layer, QRect clip) { + refreshLayerShift(); + const auto factor = style::DevicePixelRatio(); + const auto whole = layer->rect(); + + auto p = QPainter(layer); + + auto updated = QRect(); + const auto addRect = [&](QRect rect) { + if (updated.isEmpty()) { + updated = rect; + } else { + updated = rect.united(updated); + } + }; for (auto &play : _plays) { if (!play.lottie->ready()) { continue; } + const auto target = computeRect(play).translated(_layerShift); + if (!target.intersects(whole)) { + play.finished = true; + addRect(target); + continue; + } else if (!target.intersects(clip)) { + continue; + } auto request = Lottie::FrameRequest(); request.box = play.outer * factor; const auto rightAligned = play.view->hasRightLayout(); @@ -394,18 +443,18 @@ void EmojiInteractions::paint(QPainter &p) { play.framesCount = information.framesCount; play.frameRate = information.frameRate; } - const auto rect = computeRect(play); if (play.started && !play.frame) { play.finished = true; - _updateRequests.fire_copy(rect); + addRect(target); continue; } else if (play.frame > 0) { play.started = true; } p.drawImage( - QRect(rect.topLeft(), frame.image.size() / factor), + QRect(target.topLeft(), frame.image.size() / factor), frame.image); play.lottie->markFrameShown(); + play.lastTarget = target.translated(_layerShift); } _plays.erase(ranges::remove_if(_plays, [](const Play &play) { if (!play.finished) { @@ -414,6 +463,18 @@ void EmojiInteractions::paint(QPainter &p) { return true; }), end(_plays)); checkDelayed(); + + if (_plays.empty()) { + layer->hide(); + if (_layer.get() == layer) { + crl::on_main([moved = std::move(_layer)] {}); + } + } else if (!updated.isEmpty()) { + const auto translated = updated.translated(_layerShift); + if (translated.intersects(whole)) { + _layer->update(translated); + } + } } void EmojiInteractions::checkDelayed() { @@ -456,10 +517,6 @@ void EmojiInteractions::checkDelayed() { good.incoming); } -rpl::producer EmojiInteractions::updateRequests() const { - return _updateRequests.events(); -} - rpl::producer EmojiInteractions::playStarted() const { return _playStarted.events(); } diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h index 679b102a5..6c1f25e8d 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/unique_qptr.h" + namespace Data { class DocumentMedia; } // namespace Data @@ -27,6 +29,10 @@ namespace Stickers { enum class EffectType : uint8; } // namespace Stickers +namespace Ui { +class RpWidget; +} // namespace Ui + namespace HistoryView { class Element; @@ -34,6 +40,8 @@ class Element; class EmojiInteractions final { public: EmojiInteractions( + not_null parent, + not_null layerParent, not_null session, Fn)> itemTop); ~EmojiInteractions(); @@ -50,14 +58,14 @@ public: void playEffectOnRead(not_null view); void playEffect(not_null view); - void paint(QPainter &p); - [[nodiscard]] rpl::producer updateRequests() const; + void paint(not_null layer, QRect clip); [[nodiscard]] rpl::producer playStarted() const; private: struct Play { not_null view; std::unique_ptr lottie; + mutable QRect lastTarget; QPoint shift; QSize inner; QSize outer; @@ -111,15 +119,21 @@ private: const ResolvedEffect &resolved); void checkPendingEffects(); + void refreshLayerShift(); + void refreshLayerGeometryAndUpdate(QRect rect); + + const not_null _parent; + const not_null _layerParent; const not_null _session; const Fn)> _itemTop; + base::unique_qptr _layer; + QPoint _layerShift; int _visibleTop = 0; int _visibleBottom = 0; std::vector _plays; std::vector _delayed; - rpl::event_stream _updateRequests; rpl::event_stream _playStarted; std::vector> _pendingEffects; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index c8eacd8e3..1b8905dfa 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -375,6 +375,8 @@ ListWidget::ListWidget( , _delegate(delegate) , _session(session) , _emojiInteractions(std::make_unique( + this, + _delegate->listWindow()->content(), session, [=](not_null view) { return itemTop(view); })) , _context(_delegate->listContext()) @@ -501,11 +503,6 @@ ListWidget::ListWidget( _isChatWide = wide; }, lifetime()); - _emojiInteractions->updateRequests( - ) | rpl::start_with_next([=](QRect rect) { - update(rect); - }, lifetime()); - _selectScroll.scrolls( ) | rpl::start_with_next([=](int d) { delegate->listScrollTo(_visibleTop + d, false); @@ -2278,7 +2275,6 @@ void ListWidget::paintEvent(QPaintEvent *e) { if (_reactionsManager) { _reactionsManager->paint(p, context); } - _emojiInteractions->paint(p); } void ListWidget::paintUserpics( From 363b700f1f805cbcc81786e7ef10426e160a2e7d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 May 2024 18:35:57 +0400 Subject: [PATCH 072/225] Fix chat preview and new emoji interactions. --- .../SourceFiles/history/view/history_view_chat_preview.cpp | 5 +++++ .../SourceFiles/history/view/history_view_list_widget.cpp | 6 +++++- .../SourceFiles/history/view/history_view_list_widget.h | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 56d7f648c..fdc7607f9 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -129,6 +129,7 @@ private: void listAddTranslatedItems( not_null tracker) override; not_null listWindow() override; + not_null listEmojiInteractionsParent() override; not_null listChatStyle() override; rpl::producer listChatWideValue() override; std::unique_ptr listMakeReactionsManager( @@ -676,6 +677,10 @@ not_null Item::listWindow() { Unexpected("Item::listWindow."); } +not_null Item::listEmojiInteractionsParent() { + return this; +} + not_null Item::listChatStyle() { return _chatStyle.get(); } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 1b8905dfa..a66ba415e 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -101,6 +101,10 @@ not_null WindowListDelegate::listWindow() { return _window; } +not_null WindowListDelegate::listEmojiInteractionsParent() { + return _window->content(); +} + not_null WindowListDelegate::listChatStyle() { return _window->chatStyle(); } @@ -376,7 +380,7 @@ ListWidget::ListWidget( , _session(session) , _emojiInteractions(std::make_unique( this, - _delegate->listWindow()->content(), + _delegate->listEmojiInteractionsParent(), session, [=](not_null view) { return itemTop(view); })) , _context(_delegate->listContext()) diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index b85bb7bed..5cfc57c08 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -161,6 +161,7 @@ public: // Methods that use Window::SessionController by default. virtual not_null listWindow() = 0; + virtual not_null listEmojiInteractionsParent() = 0; virtual not_null listChatStyle() = 0; virtual rpl::producer listChatWideValue() = 0; virtual std::unique_ptr listMakeReactionsManager( @@ -194,6 +195,7 @@ public: explicit WindowListDelegate(not_null window); not_null listWindow() override; + not_null listEmojiInteractionsParent() override; not_null listChatStyle() override; rpl::producer listChatWideValue() override; std::unique_ptr listMakeReactionsManager( From 5bfbae3afc9c666823d0589911e24aec35281247 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 16 May 2024 12:54:39 +0400 Subject: [PATCH 073/225] Update API scheme on layer 180. --- Telegram/SourceFiles/api/api_confirm_phone.cpp | 4 +++- Telegram/SourceFiles/intro/intro_code.cpp | 8 ++++++-- Telegram/SourceFiles/mtproto/scheme/api.tl | 9 +++++---- .../SourceFiles/passport/passport_form_controller.cpp | 4 +++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/api/api_confirm_phone.cpp b/Telegram/SourceFiles/api/api_confirm_phone.cpp index f4788e128..3a42a793d 100644 --- a/Telegram/SourceFiles/api/api_confirm_phone.cpp +++ b/Telegram/SourceFiles/api/api_confirm_phone.cpp @@ -97,8 +97,10 @@ void ConfirmPhone::resolve( box->resendRequests( ) | rpl::start_with_next([=] { _api.request(MTPauth_ResendCode( + MTP_flags(0), MTP_string(phone), - MTP_string(phoneHash) + MTP_string(phoneHash), + MTPstring() // reason )).done([=] { if (boxWeak) { boxWeak->callDone(); diff --git a/Telegram/SourceFiles/intro/intro_code.cpp b/Telegram/SourceFiles/intro/intro_code.cpp index e5d09140c..e48e8bb59 100644 --- a/Telegram/SourceFiles/intro/intro_code.cpp +++ b/Telegram/SourceFiles/intro/intro_code.cpp @@ -266,8 +266,10 @@ void CodeWidget::sendCall() { _callStatus = CallStatus::Calling; _callTimer.cancel(); _callRequestId = api().request(MTPauth_ResendCode( + MTP_flags(0), MTP_string(getData()->phone), - MTP_bytes(getData()->phoneHash) + MTP_bytes(getData()->phoneHash), + MTPstring() // reason )).done([=](const MTPauth_SentCode &result) { callDone(result); }).send(); @@ -376,8 +378,10 @@ void CodeWidget::noTelegramCode() { return; } _noTelegramCodeRequestId = api().request(MTPauth_ResendCode( + MTP_flags(0), MTP_string(getData()->phone), - MTP_bytes(getData()->phoneHash) + MTP_bytes(getData()->phoneHash), + MTPstring() // reason )).done([=](const MTPauth_SentCode &result) { noTelegramCodeDone(result); }).fail([=](const MTP::Error &error) { diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index d83726b0e..c0d2472f9 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -758,7 +758,7 @@ auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeTyp auth.sentCodeTypeEmailCode#f450f59b flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int reset_available_period:flags.3?int reset_pending_date:flags.4?int = auth.SentCodeType; auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType; auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType; -auth.sentCodeTypeFirebaseSms#e57b1432 flags:# nonce:flags.0?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; +auth.sentCodeTypeFirebaseSms#13c90f17 flags:# nonce:flags.0?bytes play_integrity_nonce:flags.2?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; auth.sentCodeTypeSmsWord#a416ac81 flags:# beginning:flags.0?string = auth.SentCodeType; auth.sentCodeTypeSmsPhrase#b37794af flags:# beginning:flags.0?string = auth.SentCodeType; @@ -1798,7 +1798,7 @@ invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; invokeWithBusinessConnection#dd289f8e {X:Type} connection_id:string query:!X = X; invokeWithGooglePlayIntegrity#1df92984 {X:Type} nonce:string token:string query:!X = X; -invokeWithApnsSecret#dae54f8#8252da54 {X:Type} nonce:string secret:string query:!X = X; +invokeWithApnsSecret#0dae54f8 {X:Type} nonce:string secret:string query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#aac7b717 flags:# no_joined_notifications:flags.0?true phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; @@ -1812,7 +1812,7 @@ auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_au auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.recoverPassword#37096c70 flags:# code:string new_settings:flags.0?account.PasswordInputSettings = auth.Authorization; -auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; +auth.resendCode#cae47523 flags:# phone_number:string phone_code_hash:string reason:flags.0?string = auth.SentCode; auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; auth.exportLoginToken#b7e085fe api_id:int api_hash:string except_ids:Vector = auth.LoginToken; @@ -1820,7 +1820,7 @@ auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken; auth.acceptLoginToken#e894ad4d token:bytes = Authorization; auth.checkRecoveryPassword#d36bf79 code:string = Bool; auth.importWebTokenAuthorization#2db873a9 api_id:int api_hash:string web_auth_token:string = auth.Authorization; -auth.requestFirebaseSms#73ab08c0 flags:# phone_number:string phone_code_hash:string play_integrity_token:flags.0?string ios_push_secret:flags.1?string = Bool; +auth.requestFirebaseSms#8e39261e flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string play_integrity_token:flags.2?string ios_push_secret:flags.1?string = Bool; auth.resetLoginEmail#7e960193 phone_number:string phone_code_hash:string = auth.SentCode; auth.reportMissingCode#cb9deff6 phone_number:string phone_code_hash:string mnc:string = Bool; @@ -2289,6 +2289,7 @@ channels.setBoostsToUnblockRestrictions#ad399cee channel:InputChannel boosts:int channels.setEmojiStickers#3cd930b7 channel:InputChannel stickerset:InputStickerSet = Bool; channels.reportSponsoredMessage#af8ff6b9 channel:InputChannel random_id:bytes option:bytes = channels.SponsoredMessageReportResult; channels.restrictSponsoredMessages#9ae91519 channel:InputChannel restricted:Bool = Updates; +channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index eafb62369..b7bc84207 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -2261,8 +2261,10 @@ void FormController::requestPhoneCall(not_null value) { value->verification.call->setStatus( { Ui::SentCodeCall::State::Calling, 0 }); _api.request(MTPauth_ResendCode( + MTP_flags(0), MTP_string(getPhoneFromValue(value)), - MTP_string(value->verification.phoneCodeHash) + MTP_string(value->verification.phoneCodeHash), + MTPstring() // reason )).done([=] { value->verification.call->callDone(); }).send(); From 787cf7853e7b4e9097944a8c505f6ab3a9b75e39 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 16 May 2024 22:54:22 +0400 Subject: [PATCH 074/225] Implement simple chats search bar. --- Telegram/Resources/langs/lang.strings | 11 ++ .../boxes/peers/edit_forum_topic_box.cpp | 29 ++- .../boxes/peers/edit_forum_topic_box.h | 8 + .../SourceFiles/data/data_forum_topic.cpp | 60 +++++- Telegram/SourceFiles/data/data_forum_topic.h | 23 ++- .../data/stickers/data_custom_emoji.cpp | 4 + .../SourceFiles/dialogs/dialogs_widget.cpp | 158 +++++++++++++++- Telegram/SourceFiles/dialogs/dialogs_widget.h | 13 ++ .../dialogs/ui/chat_search_tabs.cpp | 178 ++++++++++++++++++ .../SourceFiles/dialogs/ui/chat_search_tabs.h | 83 ++++++++ .../info/profile/info_profile_cover.cpp | 2 +- Telegram/cmake/td_ui.cmake | 2 + 12 files changed, 555 insertions(+), 16 deletions(-) create mode 100644 Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp create mode 100644 Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c0ef61a95..47ad8c12d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5174,6 +5174,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_font_system" = "System font"; "lng_font_not_found" = "Font not found."; +"lng_search_tab_my_messages" = "My Messages"; +"lng_search_tab_this_topic" = "This Topic"; +"lng_search_tab_this_chat" = "This Chat"; +"lng_search_tab_this_channel" = "This Channel"; +"lng_search_tab_this_group" = "This Group"; +"lng_search_tab_public_posts" = "Public Posts"; +"lng_search_tab_no_results" = "No Results"; +"lng_search_tab_no_results_text" = "There were no results for \"{query}\"."; +"lng_search_tab_no_results_retry" = "Try another hashtag."; +"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it."; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 2a4882588..e8a8b9d59 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -42,10 +42,7 @@ namespace { constexpr auto kDefaultIconId = DocumentId(0x7FFF'FFFF'FFFF'FFFFULL); -struct DefaultIcon { - QString title; - int32 colorId = 0; -}; +using DefaultIcon = Data::TopicIconDescriptor; class DefaultIconEmoji final : public Ui::Text::CustomEmoji { public: @@ -89,10 +86,14 @@ QString DefaultIconEmoji::entityData() { void DefaultIconEmoji::paint(QPainter &p, const Context &context) { if (_image.isNull()) { - _image = Data::ForumTopicIconFrame( - _icon.colorId, - _icon.title, - st::defaultForumTopicIcon); + _image = Data::IsForumGeneralIconTitle(_icon.title) + ? Data::ForumTopicGeneralIconFrame( + st::defaultForumTopicIcon.size, + Data::ParseForumGeneralIconColor(_icon.colorId)) + : Data::ForumTopicIconFrame( + _icon.colorId, + _icon.title, + st::defaultForumTopicIcon); } const auto esize = Ui::Emoji::GetSizeLarge() / style::DevicePixelRatio(); const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize); @@ -212,7 +213,7 @@ bool DefaultIconEmoji::readyInDefaultState() { ) | rpl::start_with_next([=] { state->frame = Data::ForumTopicGeneralIconFrame( st::largeForumTopicIcon.size, - st::windowSubTextFg); + st::windowSubTextFg->c); result->update(); }, result->lifetime()); @@ -261,7 +262,7 @@ struct IconSelector { if (id == kDefaultIconId) { return std::make_unique( rpl::duplicate(defaultIcon), - repaint); + std::move(repaint)); } return manager->create(id, std::move(repaint), tag); }; @@ -572,3 +573,11 @@ void EditForumTopicBox( box->closeBox(); }); } + +std::unique_ptr MakeTopicIconEmoji( + Data::TopicIconDescriptor descriptor, + Fn repaint) { + return std::make_unique( + rpl::single(descriptor), + std::move(repaint)); +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h index fddc3c052..620ecebaf 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; +namespace Data { +struct TopicIconDescriptor; +} // namespace Data + namespace Window { class SessionController; } // namespace Window @@ -25,3 +29,7 @@ void EditForumTopicBox( not_null controller, not_null forum, MsgId rootId); + +[[nodiscard]] std::unique_ptr MakeTopicIconEmoji( + Data::TopicIconDescriptor descriptor, + Fn repaint); diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 7b5391a19..7285c4c39 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -143,7 +143,7 @@ QImage ForumTopicIconFrame( return background; } -QImage ForumTopicGeneralIconFrame(int size, const style::color &color) { +QImage ForumTopicGeneralIconFrame(int size, const QColor &color) { const auto ratio = style::DevicePixelRatio(); auto svg = QSvgRenderer(ForumTopicIconPath(u"general"_q)); auto result = QImage( @@ -172,6 +172,62 @@ TextWithEntities ForumTopicIconWithTitle( : TextWithEntities{ title }; } +QString ForumGeneralIconTitle() { + return QChar(0) + u"general"_q; +} + +bool IsForumGeneralIconTitle(const QString &title) { + return !title.isEmpty() && !title[0].unicode(); +} + +int32 ForumGeneralIconColor(const QColor &color) { + return int32(uint32(color.red()) << 16 + | uint32(color.green()) << 8 + | uint32(color.blue()) + | (uint32(color.alpha() == 255 ? 0 : color.alpha()) << 24)); +} + +QColor ParseForumGeneralIconColor(int32 value) { + const auto alpha = uint32(value) >> 24; + return QColor( + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF, + alpha ? alpha : 255); +} + +QString TopicIconEmojiEntity(TopicIconDescriptor descriptor) { + return IsForumGeneralIconTitle(descriptor.title) + ? u"topic_general:"_q + QString::number(uint32(descriptor.colorId)) + : (u"topic_icon:"_q + + QString::number(uint32(descriptor.colorId)) + + ' ' + + ExtractNonEmojiLetter(descriptor.title)); +} + +TopicIconDescriptor ParseTopicIconEmojiEntity(QStringView entity) { + if (!entity.startsWith(u"topic_")) { + return {}; + } + const auto general = u"topic_general:"_q; + const auto normal = u"topic_icon:"_q; + if (entity.startsWith(general)) { + return { + .title = ForumGeneralIconTitle(), + .colorId = int32(entity.mid(general.size()).toUInt()), + }; + } else if (entity.startsWith(normal)) { + const auto parts = entity.mid(normal.size()).split(' '); + if (parts.size() == 2) { + return { + .title = parts[1].toString(), + .colorId = int32(parts[0].toUInt()), + }; + } + } + return {}; +} + ForumTopic::ForumTopic(not_null forum, MsgId rootId) : Thread(&forum->history()->owner(), Type::ForumTopic) , _forum(forum) @@ -640,7 +696,7 @@ void ForumTopic::validateGeneralIcon( : context.selected ? st::dialogsTextFgOver : st::dialogsTextFg; - _defaultIcon = ForumTopicGeneralIconFrame(size, color); + _defaultIcon = ForumTopicGeneralIconFrame(size, color->c); _flags = (_flags & ~mask) | flags; } diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 275f38fcb..06423e475 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -48,12 +48,33 @@ class Forum; const style::ForumTopicIcon &st); [[nodiscard]] QImage ForumTopicGeneralIconFrame( int size, - const style::color &color); + const QColor &color); [[nodiscard]] TextWithEntities ForumTopicIconWithTitle( MsgId rootId, DocumentId iconId, const QString &title); +[[nodiscard]] QString ForumGeneralIconTitle(); +[[nodiscard]] bool IsForumGeneralIconTitle(const QString &title); +[[nodiscard]] int32 ForumGeneralIconColor(const QColor &color); +[[nodiscard]] QColor ParseForumGeneralIconColor(int32 value); + +struct TopicIconDescriptor { + QString title; + int32 colorId = 0; + + [[nodiscard]] bool empty() const { + return !colorId && title.isEmpty(); + } + explicit operator bool() const { + return !empty(); + } +}; + +[[nodiscard]] QString TopicIconEmojiEntity(TopicIconDescriptor descriptor); +[[nodiscard]] TopicIconDescriptor ParseTopicIconEmojiEntity( + QStringView entity); + class ForumTopic final : public Thread { public: static constexpr auto kGeneralId = 1; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 22b877fb9..7f110b6b1 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/stickers/data_custom_emoji.h" +#include "boxes/peers/edit_forum_topic_box.h" // MakeTopicIconEmoji. #include "chat_helpers/stickers_emoji_pack.h" #include "main/main_app_config.h" #include "main/main_session.h" @@ -15,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_file_origin.h" +#include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity. #include "data/data_peer.h" #include "data/data_message_reactions.h" #include "data/stickers/data_stickers.h" @@ -539,6 +541,8 @@ std::unique_ptr CustomEmojiManager::create( const auto ratio = style::DevicePixelRatio(); const auto size = EmojiSizeFromTag(tag) / ratio; return userpic(data, std::move(update), size); + } else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) { + return MakeTopicIconEmoji(parsed, std::move(update)); } const auto parsed = ParseCustomEmojiData(data); return parsed diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index a08c16119..15624e29d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt/qt_key_modifiers.h" #include "base/options.h" +#include "dialogs/ui/chat_search_tabs.h" #include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/ui/dialogs_suggestions.h" @@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_requests_bar.h" #include "history/view/history_view_group_call_bar.h" #include "boxes/peers/edit_peer_requests_box.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/elastic_scroll.h" #include "ui/widgets/fields/input_field.h" @@ -62,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_channel.h" #include "data/data_chat.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_user.h" #include "data/data_folder.h" #include "data/data_forum.h" @@ -1075,6 +1078,9 @@ void Widget::updateControlsVisibility(bool fast) { updateJumpToDateVisibility(fast); updateSearchFromVisibility(fast); } + if (_searchTabs) { + _searchTabs->show(); + } if (_connecting) { _connecting->setForceHidden(false); } @@ -1218,6 +1224,114 @@ void Widget::updateSuggestions(anim::type animated) { } } +void Widget::updateSearchTabs() { + const auto has = _searchInChat + || _hiddenSearchInChat + || _searchingHashtag; + if (!has) { + _searchTab = ChatSearchTab::MyMessages; + if (_searchTabs) { + _searchTabs = nullptr; + updateControlsGeometry(); + } + return; + } else if (!_searchTabs) { + _searchTabs = std::make_unique(this, _searchTab); + _searchTabs->setVisible(!_showAnimation); + _searchTabs->tabChanges( + ) | rpl::start_with_next([=](ChatSearchTab tab) { + if (_searchTab != tab) { + _searchTab = tab; + applySearchTab(); + } + }, _searchTabs->lifetime()); + } + const auto topic = _searchInChat.topic() + ? _searchInChat.topic() + : _hiddenSearchInChat.topic(); + const auto peer = _searchInChat.owningHistory() + ? _searchInChat.owningHistory()->peer.get() + : _hiddenSearchInChat.owningHistory() + ? _hiddenSearchInChat.owningHistory()->peer.get() + : nullptr; + const auto topicShortLabel = topic + ? Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({ + .title = (topic->isGeneral() + ? Data::ForumGeneralIconTitle() + : topic->title()), + .colorId = (topic->isGeneral() + ? Data::ForumGeneralIconColor(st::windowSubTextFg->c) + : topic->colorId()), + })) + : TextWithEntities(); + const auto peerShortLabel = peer + ? Ui::Text::SingleCustomEmoji( + session().data().customEmojiManager().peerUserpicEmojiData( + peer)) + : TextWithEntities(); + const auto myShortLabel = DefaultShortLabel(ChatSearchTab::MyMessages); + const auto publicShortLabel = _searchingHashtag + ? DefaultShortLabel(ChatSearchTab::PublicPosts) + : TextWithEntities(); + _searchTab = _searchInChat.topic() + ? ChatSearchTab::ThisTopic + : _searchInChat.owningHistory() + ? ChatSearchTab::ThisPeer + : (_searchingHashtag && _searchTab == ChatSearchTab::PublicPosts) + ? ChatSearchTab::PublicPosts + : ChatSearchTab::MyMessages; + _searchTabs->setTabShortLabels({ + { ChatSearchTab::ThisTopic, topicShortLabel }, + { ChatSearchTab::ThisPeer, peerShortLabel }, + { ChatSearchTab::MyMessages, myShortLabel }, + { ChatSearchTab::PublicPosts, publicShortLabel }, + }, _searchTab); + + updateControlsGeometry(); +} + +void Widget::applySearchTab() { + switch (_searchTab) { + case ChatSearchTab::ThisTopic: + if (_searchInChat.topic()) { + } else if (_hiddenSearchInChat.topic()) { + setSearchInChat( + base::take(_hiddenSearchInChat), + base::take(_hiddenSearchFromAuthor), + base::take(_hiddenSearchTags)); + } else { + updateSearchTabs(); + } + case ChatSearchTab::ThisPeer: + if (_searchInChat.topic()) { + _hiddenSearchInChat = _searchInChat; + _hiddenSearchFromAuthor = _searchFromAuthor; + _hiddenSearchTags = _searchTags; + setSearchInChat( + _searchInChat.owningHistory(), + _searchFromAuthor, + _searchTags); + } else if (_searchInChat) { + return; + } else if (_hiddenSearchInChat) { + setSearchInChat( + _hiddenSearchInChat.owningHistory(), + _hiddenSearchFromAuthor, + _hiddenSearchTags); + } + case ChatSearchTab::MyMessages: + if (_searchInChat) { + _hiddenSearchInChat = (_hiddenSearchInChat.topic() + && _hiddenSearchInChat.owningHistory() == _searchInChat.history()) + ? _hiddenSearchInChat + : _searchInChat; + _hiddenSearchFromAuthor = _searchFromAuthor; + _hiddenSearchTags = _searchTags; + setSearchInChat({}); + } + } +} + void Widget::changeOpenedSubsection( FnMut change, bool fromRight, @@ -2619,8 +2733,31 @@ void Widget::updateCancelSearch() { _cancelSearch->toggle(shown, anim::type::normal); } +bool Widget::fixSearchQuery() { + if (_fixingSearchQuery) { + return false; + } + _fixingSearchQuery = true; + const auto query = _search->getLastText(); + if (_searchTab == ChatSearchTab::PublicPosts) { + const auto fixed = FixHashtagSearchQuery( + query, + _search->textCursor().position()); + if (fixed.text != query) { + _search->setText(fixed.text); + _search->setCursorPosition(fixed.cursorPosition); + } + _searchingHashtag = true; + } else if (_searchingHashtag != IsHashtagSearchQuery(query)) { + _searchingHashtag = !_searchingHashtag; + updateSearchTabs(); + } + _fixingSearchQuery = false; + return true; +} + void Widget::applySearchUpdate(bool force) { - if (_showAnimation && !force) { + if (!fixSearchQuery() || (_showAnimation && !force)) { return; } @@ -2852,8 +2989,17 @@ bool Widget::setSearchInChat( } if (searchInPeerUpdated) { _searchInChat = chat; + if (chat) { + _hiddenSearchFromAuthor = {}; + _hiddenSearchTags = {}; + const auto hiddenTopic = _hiddenSearchInChat.topic(); + if (!hiddenTopic || hiddenTopic->history() != chat.history()) { + _hiddenSearchInChat = {}; + } + } controller()->setSearchInChat(_searchInChat); updateSuggestions(anim::type::instant); + updateSearchTabs(); updateJumpToDateVisibility(); updateStoriesVisibility(); } @@ -3190,6 +3336,9 @@ void Widget::updateControlsGeometry() { if (_forumRequestsBar) { _forumRequestsBar->resizeToWidth(barw); } + if (_searchTabs) { + _searchTabs->resizeToWidth(barw); + } _updateScrollGeometryCached = [=] { const auto moreChatsBarTop = expandedStoriesTop + ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded); @@ -3211,8 +3360,13 @@ void Widget::updateControlsGeometry() { if (_forumReportBar) { _forumReportBar->bar().move(0, forumReportTop); } - const auto scrollTop = forumReportTop + const auto searchTabsTop = forumReportTop + (_forumReportBar ? _forumReportBar->bar().height() : 0); + if (_searchTabs) { + _searchTabs->move(0, searchTabsTop); + } + const auto scrollTop = searchTabsTop + + (_searchTabs ? _searchTabs->height() : 0); const auto scrollHeight = height() - scrollTop - bottomSkip; const auto wasScrollHeight = _scroll->height(); _scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index b183cb36f..a1bc0e4ea 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -76,6 +76,8 @@ struct ChosenRow; class InnerWidget; enum class SearchRequestType; class Suggestions; +class ChatSearchTabs; +enum class ChatSearchTab : uchar; class Widget final : public Window::AbstractSectionWidget { public: @@ -225,6 +227,7 @@ private: QPixmap newContentCache, Window::SlideDirection direction); + void applySearchTab(); void openChildList( not_null forum, const Window::SectionShow ¶ms); @@ -232,6 +235,7 @@ private: void fullSearchRefreshOn(rpl::producer<> events); void updateCancelSearch(); + bool fixSearchQuery(); void applySearchUpdate(bool force = false); void refreshLoadMoreButton(bool mayBlock, bool isBlocked); void loadMoreBlockedByDate(); @@ -251,6 +255,7 @@ private: void updateScrollUpPosition(); void updateLockUnlockPosition(); void updateSuggestions(anim::type animated); + void updateSearchTabs(); void processSearchFocusChange(); [[nodiscard]] bool redirectToSearchPossible() const; @@ -289,6 +294,7 @@ private: QPointer _inner; std::unique_ptr _suggestions; std::vector> _hidingSuggestions; + std::unique_ptr _searchTabs; class BottomButton; object_ptr _updateTelegram = { nullptr }; object_ptr _loadMoreChats = { nullptr }; @@ -304,6 +310,9 @@ private: object_ptr _scrollToTop; bool _scrollToTopIsShown = false; bool _forumSearchRequested = false; + bool _fixingSearchQuery = false; + bool _searchingHashtag = false; + ChatSearchTab _searchTab = ChatSearchTab(); Data::Folder *_openedFolder = nullptr; Data::Forum *_openedForum = nullptr; @@ -316,6 +325,10 @@ private: bool _searchSuggestionsLocked = false; bool _searchHasFocus = false; + Key _hiddenSearchInChat; + PeerData *_hiddenSearchFromAuthor = nullptr; + std::vector _hiddenSearchTags; + rpl::event_stream> _storiesContents; base::flat_map _storiesUserpicsViewsHidden; base::flat_map _storiesUserpicsViewsShown; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp new file mode 100644 index 000000000..456183d30 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp @@ -0,0 +1,178 @@ +/* +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 "dialogs/ui/chat_search_tabs.h" + +#include "lang/lang_keys.h" +#include "ui/widgets/discrete_sliders.h" +#include "ui/widgets/shadow.h" +#include "styles/style_dialogs.h" + +namespace Dialogs { +namespace { + +[[nodiscard]] QString TabLabel( + ChatSearchTab tab, + ChatSearchPeerTabType type = {}) { + switch (tab) { + case ChatSearchTab::MyMessages: + return tr::lng_search_tab_my_messages(tr::now); + case ChatSearchTab::ThisTopic: + return tr::lng_search_tab_this_topic(tr::now); + case ChatSearchTab::ThisPeer: + switch (type) { + case ChatSearchPeerTabType::Chat: + return tr::lng_search_tab_this_chat(tr::now); + case ChatSearchPeerTabType::Channel: + return tr::lng_search_tab_this_channel(tr::now); + case ChatSearchPeerTabType::Group: + return tr::lng_search_tab_this_group(tr::now); + } + Unexpected("Type in Dialogs::TabLabel."); + case ChatSearchTab::PublicPosts: + return tr::lng_search_tab_public_posts(tr::now); + } + Unexpected("Tab in Dialogs::TabLabel."); +} + +} // namespace + +TextWithEntities DefaultShortLabel(ChatSearchTab tab) { + // Return them in QString::fromUtf8 format. + switch (tab) { + case ChatSearchTab::MyMessages: + return { QString::fromUtf8("\xf0\x9f\x93\xa8") }; + case ChatSearchTab::PublicPosts: + return { QString::fromUtf8("\xf0\x9f\x8c\x8e") }; + } + Unexpected("Tab in Dialogs::DefaultShortLabel."); +} + +FixedHashtagSearchQuery FixHashtagSearchQuery( + const QString &query, + int cursorPosition) { + const auto trimmed = query.trimmed(); + const auto hash = trimmed.isEmpty() + ? query.size() + : query.indexOf(trimmed); + const auto start = std::min(cursorPosition, hash); + auto result = query.mid(0, start); + for (const auto &ch : query.mid(start)) { + if (ch.isSpace()) { + if (cursorPosition > result.size()) { + --cursorPosition; + } + continue; + } else if (result.size() == start) { + result += '#'; + if (ch != '#') { + ++cursorPosition; + } + } + if (ch != '#') { + result += ch; + } + } + return { result, cursorPosition }; +} + +bool IsHashtagSearchQuery(const QString &query) { + const auto trimmed = query.trimmed(); + if (trimmed.isEmpty() || trimmed[0] != '#') { + return false; + } + for (const auto &ch : trimmed) { + if (ch.isSpace()) { + return false; + } + } + return true; +} + +ChatSearchTabs::ChatSearchTabs(QWidget *parent, ChatSearchTab active) +: RpWidget(parent) +, _tabs(std::make_unique(this, st::dialogsSearchTabs)) +, _shadow(std::make_unique(this)) +, _active(active) { + for (const auto tab : { + ChatSearchTab::ThisTopic, + ChatSearchTab::ThisPeer, + ChatSearchTab::MyMessages, + ChatSearchTab::PublicPosts, + }) { + _list.push_back({ tab, TabLabel(tab) }); + } + _tabs->move(0, 0); + _tabs->sectionActivated( + ) | rpl::start_with_next([=](int index) { + for (const auto &tab : _list) { + if (!index) { + _active = tab.value; + return; + } + if (!tab.shortLabel.empty()) { + --index; + } + } + }, lifetime()); +} + +ChatSearchTabs::~ChatSearchTabs() = default; + +void ChatSearchTabs::setPeerTabType(ChatSearchPeerTabType type) { + _type = type; + const auto i = ranges::find(_list, ChatSearchTab::ThisPeer, &Tab::value); + Assert(i != end(_list)); + i->label = TabLabel(ChatSearchTab::ThisPeer, type); + if (!i->shortLabel.empty()) { + refreshTabs(_active.current()); + } +} + +void ChatSearchTabs::setTabShortLabels( + std::vector labels, + ChatSearchTab active) { + for (const auto &label : labels) { + const auto i = ranges::find(_list, label.tab, &Tab::value); + Assert(i != end(_list)); + i->shortLabel = std::move(label.label); + } + refreshTabs(active); +} + +rpl::producer ChatSearchTabs::tabChanges() const { + return _active.changes(); +} + +void ChatSearchTabs::refreshTabs(ChatSearchTab active) { + auto index = 0; + auto labels = std::vector(); + for (const auto &tab : _list) { + if (tab.value == active) { + index = int(labels.size()); + Assert(!tab.shortLabel.empty()); + labels.push_back(tab.label); + } else if (!tab.shortLabel.empty()) { + labels.push_back(tab.label); + } + } + _tabs->setSections(labels); + _tabs->setActiveSectionFast(index); + resizeToWidth(width()); +} + +int ChatSearchTabs::resizeGetHeight(int newWidth) { + _tabs->resizeToWidth(newWidth); + _shadow->setGeometry( + _tabs->x(), + _tabs->y() + _tabs->height() - st::lineWidth, + _tabs->width(), + st::lineWidth); + return _tabs->height(); +} + +} // namespace Dialogs \ No newline at end of file diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h new file mode 100644 index 000000000..0e2640f83 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h @@ -0,0 +1,83 @@ +/* +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" + +namespace Ui { +class SettingsSlider; +class PlainShadow; +} // namespace Ui + +namespace Dialogs { + +enum class ChatSearchTab : uchar { + MyMessages, + ThisTopic, + ThisPeer, + PublicPosts, +}; + +enum class ChatSearchPeerTabType : uchar { + Chat, + Channel, + Group, +}; + +// Available for MyMessages and PublicPosts. +[[nodiscard]] TextWithEntities DefaultShortLabel(ChatSearchTab tab); + +class ChatSearchTabs final : public Ui::RpWidget { +public: + ChatSearchTabs(QWidget *parent, ChatSearchTab active); + ~ChatSearchTabs(); + + void setPeerTabType(ChatSearchPeerTabType type); + + // A [custom] emoji to use when there is not enough space for text. + // Only tabs with available short labels are shown. + struct ShortLabel { + ChatSearchTab tab = {}; + TextWithEntities label; + }; + void setTabShortLabels( + std::vector labels, + ChatSearchTab active); + + [[nodiscard]] rpl::producer tabChanges() const; + +private: + struct Tab { + ChatSearchTab value = {}; + QString label; + TextWithEntities shortLabel; + }; + + void refreshTabs(ChatSearchTab active); + int resizeGetHeight(int newWidth) override; + + const std::unique_ptr _tabs; + const std::unique_ptr _shadow; + + std::vector _list; + rpl::variable _active; + ChatSearchPeerTabType _type = {}; + +}; + +struct FixedHashtagSearchQuery { + QString text; + int cursorPosition = 0; +}; +[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery( + const QString &query, + int cursorPosition); + +[[nodiscard]] bool IsHashtagSearchQuery(const QString &query); + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index e460b4dc8..9d69f6738 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -211,7 +211,7 @@ void TopicIconView::setupImage(not_null topic) { ) | rpl::start_with_next([=] { _image = ForumTopicGeneralIconFrame( st::infoForumTopicIcon.size, - _generalIconFg); + _generalIconFg->c); _update(); }, _lifetime); return; diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index da2ea224d..533fe85ef 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -85,6 +85,8 @@ PRIVATE data/data_subscription_option.h dialogs/dialogs_three_state_icon.h + dialogs/ui/chat_search_tabs.cpp + dialogs/ui/chat_search_tabs.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h dialogs/ui/top_peers_strip.cpp From dd5643ac67512e6609094482f7931539158e9167 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 17 May 2024 14:56:56 +0400 Subject: [PATCH 075/225] Start chats search rewrite. --- .../dialogs/dialogs_inner_widget.cpp | 134 +--- .../dialogs/dialogs_inner_widget.h | 10 +- Telegram/SourceFiles/dialogs/dialogs_key.cpp | 18 + Telegram/SourceFiles/dialogs/dialogs_key.h | 29 + .../SourceFiles/dialogs/dialogs_widget.cpp | 702 +++++++++--------- Telegram/SourceFiles/dialogs/dialogs_widget.h | 43 +- .../view/history_view_top_bar_widget.cpp | 12 +- .../view/history_view_top_bar_widget.h | 3 +- Telegram/SourceFiles/mainwidget.cpp | 14 +- 9 files changed, 474 insertions(+), 491 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 3c3ab74f6..25cf71a96 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -157,13 +157,11 @@ InnerWidget::InnerWidget( , _narrowWidth(st::defaultDialogRow.padding.left() + st::defaultDialogRow.photoSize + st::defaultDialogRow.padding.left()) -, _cancelSearchInChat(this, st::dialogsCancelSearchInPeer) , _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) , _chatPreviewTimer([=] { showChatPreview(true); }) , _childListShown(std::move(childListShown)) { setAttribute(Qt::WA_OpaquePaintEvent, true); - _cancelSearchInChat->hide(); _cancelSearchFromUser->hide(); style::PaletteChanged( @@ -509,13 +507,7 @@ int InnerWidget::searchInChatSkip() const { if (_searchTags) { result += _searchTags->height(); } - if (_searchInChat) { - result += st::searchedBarHeight + st::dialogsSearchInHeight; - } if (_searchFromShown) { - if (_searchInChat) { - result += st::lineWidth; - } result += st::dialogsSearchInHeight; } return result; @@ -856,7 +848,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } } - if (_searchInChat || _searchFromPeer) { + if (_searchFromShown) { paintSearchInChat(p, { .st = &st::forumTopicRow, .currentBg = currentBg(), @@ -1141,37 +1133,8 @@ void InnerWidget::paintSearchInChat( top += height; } p.setFont(st::searchedBarFont); - if (_searchInChat) { - const auto bar = st::searchedBarHeight; - p.fillRect(0, top, width(), top + bar, st::searchedBarBg); - p.setPen(st::searchedBarFg); - p.drawTextLeft(st::searchedBarPosition.x(), top + st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now)); - top += bar; - } auto fullRect = QRect(0, top, width(), height - top); p.fillRect(fullRect, currentBg()); - if (_searchInChat) { - if (_searchFromShown) { - p.fillRect(QRect(0, top + st::dialogsSearchInHeight, width(), st::lineWidth), st::shadowFg); - } - p.setPen(st::dialogsNameFg); - if (const auto topic = _searchInChat.topic()) { - paintSearchInTopic(p, context, topic, _searchInChatUserpic, top, _searchInChatText); - } else if (const auto peer = _searchInChat.peer()) { - if (peer->isSelf()) { - paintSearchInSaved(p, top, _searchInChatText); - } else if (peer->isRepliesChat()) { - paintSearchInReplies(p, top, _searchInChatText); - } else { - paintSearchInPeer(p, peer, _searchInChatUserpic, top, _searchInChatText); - } - } else if (const auto sublist = _searchInChat.sublist()) { - paintSearchInSaved(p, top, _searchInChatText); - } else { - Unexpected("Empty Key in paintSearchInChat."); - } - top += st::dialogsSearchInHeight + st::lineWidth; - } if (_searchFromShown) { p.setPen(st::dialogsTextFg); p.setTextPalette(st::dialogsSearchFromPalette); @@ -1930,12 +1893,10 @@ void InnerWidget::moveCancelSearchButtons() { const auto widthForCancelButton = qMax( width(), st::columnMinimalWidthLeft - _narrowWidth); - const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchInChat->width(); + const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchFromUser->width(); const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2; const auto skip = st::searchedBarHeight + (_searchTags ? _searchTags->height() : 0); - _cancelSearchInChat->moveToLeft(left, skip + top); - const auto next = _searchInChat ? (skip + st::dialogsSearchInHeight + st::lineWidth) : 0; - _cancelSearchFromUser->moveToLeft(left, next + top); + _cancelSearchFromUser->moveToLeft(left, skip + top); } void InnerWidget::dialogRowReplaced( @@ -2334,7 +2295,7 @@ void InnerWidget::fillArchiveSearchMenu(not_null menu) { const auto folder = session().data().folderLoaded(Data::Folder::kId); if (!folder || !folder->chatsList()->fullSize().current() - || _searchInChat) { + || _searchState.inChat) { return; } const auto skip = session().settings().skipArchiveInSearch(); @@ -2491,17 +2452,26 @@ void InnerWidget::parentGeometryChanged() { } } -void InnerWidget::applyFilterUpdate(QString newFilter, bool force) { +void InnerWidget::applySearchState(SearchState state) { + if (_searchState == state) { + return; + } + auto withSameQuery = state; + withSameQuery.query = _searchState.query; + const auto otherChanged = (_searchState != withSameQuery); + + _searchState = std::move(state); + auto newFilter = _searchState.query; const auto mentionsSearch = (newFilter == u"@"_q); const auto words = mentionsSearch ? QStringList(newFilter) : TextUtilities::PrepareSearchWords(newFilter); newFilter = words.isEmpty() ? QString() : words.join(' '); - if (newFilter != _filter || force) { + if (newFilter != _filter || otherChanged) { _filter = newFilter; if (_filter.isEmpty() - && !_searchFromPeer - && _searchTagsSelected.empty()) { + && !_searchState.fromPeer + && _searchState.tags.empty()) { clearFilter(); } else { setState(WidgetState::Filtered); @@ -2521,9 +2491,7 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) { top += i->row->height(); } }; - if (!_searchInChat - && !_searchFromPeer - && !words.isEmpty()) { + if (_searchState.filterChatsList() && !words.isEmpty()) { if (_savedSublists) { const auto owner = &session().data(); append(owner->savedMessages().chatsList()->indexed()); @@ -2549,7 +2517,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) { } void InnerWidget::onHashtagFilterUpdate(QStringView newFilter) { - if (newFilter.isEmpty() || newFilter.at(0) != '#' || _searchInChat) { + if (newFilter.isEmpty() + || newFilter.at(0) != '#' + || _searchState.inChat) { _hashtagFilter = QString(); if (!_hashtagResults.empty()) { _hashtagResults.clear(); @@ -2751,10 +2721,6 @@ rpl::producer<> InnerWidget::searchMessages() const { return _searchMessages.events(); } -rpl::producer<> InnerWidget::cancelSearchInChatRequests() const { - return _cancelSearchInChat->clicks() | rpl::to_empty; -} - rpl::producer InnerWidget::completeHashtagRequests() const { return _completeHashtagRequests.events(); } @@ -2847,12 +2813,12 @@ void InnerWidget::searchReceived( const auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart) || (type == SearchRequestType::MigratedFromOffset); - const auto key = (!_openedForum || _searchInChat.topic()) - ? _searchInChat + const auto key = (!_openedForum || _searchState.inChat.topic()) + ? _searchState.inChat : Key(_openedForum->history()); if (inject - && (!_searchInChat - || inject->history() == _searchInChat.history())) { + && (!_searchState.inChat + || inject->history() == _searchState.inChat.history())) { Assert(_searchResults.empty()); const auto index = int(_searchResults.size()); _searchResults.push_back( @@ -2984,7 +2950,7 @@ void InnerWidget::refresh(bool toTop) { } } else if (_state == WidgetState::Filtered) { if (_waitingForSearch) { - h = searchedOffset() + (_searchResults.size() * _st->height) + ((_searchResults.empty() && !_searchInChat) ? -st::searchedBarHeight : 0); + h = searchedOffset() + (_searchResults.size() * _st->height) + ((_searchResults.empty() && !_searchState.inChat) ? -st::searchedBarHeight : 0); } else { h = searchedOffset() + (_searchResults.size() * _st->height); } @@ -3124,7 +3090,7 @@ void InnerWidget::searchInChat( _searchTags->selectedChanges( ) | rpl::start_with_next([=](std::vector &&list) { - _searchTagsSelected = std::move(list); + _searchState.tags = std::move(list); }, _searchTags->lifetime()); _searchTags->repaintRequests() | rpl::start_with_next([=] { @@ -3150,20 +3116,17 @@ void InnerWidget::searchInChat( }, _searchTags->lifetime()); } else { _searchTags = nullptr; - _searchTagsSelected.clear(); + _searchState.tags.clear(); } } else { _searchTags = nullptr; - _searchTagsSelected.clear(); + _searchState.tags.clear(); } - _searchInChat = key; - _searchFromPeer = from; + _searchState.inChat = key; + _searchState.fromPeer = from; _searchFromShown = key.sublist() ? key.sublist()->peer().get() : from; - if (_searchInChat) { + if (_searchState.inChat) { onHashtagFilterUpdate(QStringView()); - _cancelSearchInChat->show(); - } else { - _cancelSearchInChat->hide(); } if (_searchFromShown) { _cancelSearchFromUser->show(); @@ -3172,15 +3135,9 @@ void InnerWidget::searchInChat( _cancelSearchFromUser->hide(); _searchFromUserUserpic = {}; } - if (_searchInChat || _searchFromPeer) { + if (_searchState.inChat || _searchState.fromPeer) { refreshSearchInChatLabel(); } - - if (peer) { - _searchInChatUserpic = peer->createUserpicView(); - } else { - _searchInChatUserpic = {}; - } moveCancelSearchButtons(); } @@ -3192,27 +3149,6 @@ auto InnerWidget::searchTagsChanges() const } void InnerWidget::refreshSearchInChatLabel() { - const auto dialog = [&] { - if (const auto topic = _searchInChat.topic()) { - return topic->title(); - } else if (const auto peer = _searchInChat.peer()) { - if (peer->isSelf()) { - return tr::lng_saved_messages(tr::now); - } else if (peer->isRepliesChat()) { - return tr::lng_replies_messages(tr::now); - } - return peer->name(); - } else if (_searchInChat.sublist()) { - return tr::lng_saved_messages(tr::now); - } - return QString(); - }(); - if (!dialog.isEmpty()) { - _searchInChatText.setText( - st::semiboldTextStyle, - dialog, - Ui::DialogTextOptions()); - } const auto from = _searchFromShown ? _searchFromShown->name() : u""_q; if (!from.isEmpty()) { const auto fromUserText = tr::lng_dlg_search_from( @@ -3236,8 +3172,8 @@ void InnerWidget::repaintSearchResult(int index) { } void InnerWidget::clearFilter() { - if (_state == WidgetState::Filtered || _searchInChat) { - if (_searchInChat) { + if (_state == WidgetState::Filtered || _searchState.inChat) { + if (_searchState.inChat) { setState(WidgetState::Filtered); _waitingForSearch = true; } else { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index f78c220ad..805a7ff2b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -145,6 +145,7 @@ public: } [[nodiscard]] bool hasFilteredResults() const; + void applySearchState(SearchState state); void searchInChat( Key key, PeerData *from, @@ -152,7 +153,6 @@ public: [[nodiscard]] auto searchTagsChanges() const -> rpl::producer>; - void applyFilterUpdate(QString newFilter, bool force = false); void onHashtagFilterUpdate(QStringView newFilter); void appendToFiltered(Key key); @@ -169,7 +169,6 @@ public: [[nodiscard]] rpl::producer mustScrollTo() const; [[nodiscard]] rpl::producer dialogMoved() const; [[nodiscard]] rpl::producer<> searchMessages() const; - [[nodiscard]] rpl::producer<> cancelSearchInChatRequests() const; [[nodiscard]] rpl::producer completeHashtagRequests() const; [[nodiscard]] rpl::producer<> refreshHashtagsRequests() const; @@ -486,21 +485,16 @@ private: WidgetState _state = WidgetState::Default; object_ptr _empty = { nullptr }; - object_ptr _cancelSearchInChat; object_ptr _cancelSearchFromUser; Ui::DraggingScrollManager _draggingScroll; - Key _searchInChat; + SearchState _searchState; History *_searchInMigrated = nullptr; - PeerData *_searchFromPeer = nullptr; PeerData *_searchFromShown = nullptr; - mutable Ui::PeerUserpicView _searchInChatUserpic; mutable Ui::PeerUserpicView _searchFromUserUserpic; - Ui::Text::String _searchInChatText; Ui::Text::String _searchFromUserText; std::unique_ptr _searchTags; - std::vector _searchTagsSelected; int _searchTagsLeft = 0; RowDescriptor _menuRow; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp index dfa728141..263a605cc 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_forum_topic.h" #include "data/data_saved_sublist.h" +#include "dialogs/ui/chat_search_tabs.h" #include "history/history.h" namespace Dialogs { @@ -84,4 +85,21 @@ PeerData *Key::peer() const { return nullptr; } +[[nodiscard]] bool SearchState::empty() const { + return !inChat + && QStringView(query).trimmed().isEmpty(); +} + +ChatSearchTab SearchState::defaultTabForMe() const { + return inChat.topic() + ? ChatSearchTab::ThisTopic + : inChat.history() + ? ChatSearchTab::ThisPeer + : ChatSearchTab::MyMessages; +} + +bool SearchState::filterChatsList() const { + return !inChat && (tab == ChatSearchTab::MyMessages); +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index c43dc9f15..a9278e08c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/qt/qt_compare.h" + class History; class PeerData; @@ -15,11 +17,13 @@ class Thread; class Folder; class ForumTopic; class SavedSublist; +struct ReactionId; } // namespace Data namespace Dialogs { class Entry; +enum class ChatSearchTab : uchar; class Key { public: @@ -120,4 +124,29 @@ struct EntryState { = default; }; +struct SearchState { + Key inChat; + PeerData *fromPeer = nullptr; + std::vector tags; + ChatSearchTab tab = {}; + QString query; + + [[nodiscard]] bool empty() const; + [[nodiscard]] ChatSearchTab defaultTabForMe() const; + [[nodiscard]] bool filterChatsList() const; + + explicit operator bool() const { + return !empty(); + } + + friend inline auto operator<=>( + const SearchState&, + const SearchState&) noexcept = default; + friend inline bool operator==( + const SearchState&, + const SearchState&) = default; +}; + +; + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 15624e29d..cc510a948 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -250,7 +250,7 @@ Widget::Widget( st::dialogsStoriesList, _storiesContents.events() | rpl::flatten_latest()) : nullptr) -, _searchTimer([=] { searchMessages(); }) +, _searchTimer([=] { search(); }) , _singleMessageSearch(&controller->session()) { const auto makeChildListShown = [](PeerId peerId, float64 shown) { return InnerWidget::ChildListShown{ peerId, shown }; @@ -325,11 +325,7 @@ Widget::Widget( }, lifetime()); _inner->searchMessages( ) | rpl::start_with_next([=] { - needSearchMessages(); - }, lifetime()); - _inner->cancelSearchInChatRequests( - ) | rpl::start_with_next([=] { - cancelSearchInChat(); + searchRequested(); }, lifetime()); _inner->completeHashtagRequests( ) | rpl::start_with_next([=](const QString &tag) { @@ -341,12 +337,12 @@ Widget::Widget( }, lifetime()); _inner->cancelSearchFromUserRequests( ) | rpl::start_with_next([=] { - setSearchInChat((_openedForum && !_searchInChat) - ? Key(_openedForum->history()) - : _searchInChat.sublist() - ? Key(session().data().history(session().user())) - : _searchInChat, nullptr); - applySearchUpdate(true); + auto copy = _searchState; + copy.fromPeer = nullptr; + if (copy.inChat.sublist()) { + copy.inChat = session().data().history(session().user()); + } + applySearchState(std::move(copy)); }, lifetime()); _inner->chosenRow( ) | rpl::start_with_next([=](const ChosenRow &row) { @@ -528,7 +524,7 @@ Widget::Widget( void Widget::chosenRow(const ChosenRow &row) { storiesToggleExplicitExpand(false); - if (!_search->getLastText().isEmpty()) { + if (!currentSearchQuery().isEmpty()) { if (const auto history = row.key.history()) { session().recentPeers().bump(history->peer); } @@ -660,7 +656,7 @@ void Widget::setupMoreChatsBar() { controller()->activeChatsFilter( ) | rpl::start_with_next([=](FilterId id) { storiesToggleExplicitExpand(false); - if (!_searchInChat) { + if (!_searchState.inChat) { cancelSearch(); } @@ -1041,7 +1037,7 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) { _searchQuery = QString(); _scroll->scrollToY(0); cancelSearchRequest(); - searchMessages(); + search(); }, lifetime()); } @@ -1134,7 +1130,7 @@ void Widget::updateHasFocus(not_null focused) { bool Widget::cancelSearchByMouseBack() { return _searchHasFocus && !_searchSuggestionsLocked - && !_searchInChat + && !_searchState.inChat && cancelSearch(); } @@ -1147,7 +1143,7 @@ void Widget::processSearchFocusChange() { void Widget::updateSuggestions(anim::type animated) { const auto suggest = (_searchHasFocus || _searchSuggestionsLocked) - && !_searchInChat + && !_searchState.inChat && (_inner->state() == WidgetState::Default); if (anim::Disabled() || !session().data().chatsListLoaded()) { animated = anim::type::instant; @@ -1225,34 +1221,28 @@ void Widget::updateSuggestions(anim::type animated) { } void Widget::updateSearchTabs() { - const auto has = _searchInChat - || _hiddenSearchInChat - || _searchingHashtag; + const auto has = _searchState.inChat || _searchingHashtag; if (!has) { - _searchTab = ChatSearchTab::MyMessages; if (_searchTabs) { _searchTabs = nullptr; updateControlsGeometry(); } return; } else if (!_searchTabs) { - _searchTabs = std::make_unique(this, _searchTab); + _searchTabs = std::make_unique( + this, + _searchState.tab); _searchTabs->setVisible(!_showAnimation); _searchTabs->tabChanges( ) | rpl::start_with_next([=](ChatSearchTab tab) { - if (_searchTab != tab) { - _searchTab = tab; - applySearchTab(); - } + auto copy = _searchState; + copy.tab = tab; + applySearchState(std::move(copy)); }, _searchTabs->lifetime()); } - const auto topic = _searchInChat.topic() - ? _searchInChat.topic() - : _hiddenSearchInChat.topic(); - const auto peer = _searchInChat.owningHistory() - ? _searchInChat.owningHistory()->peer.get() - : _hiddenSearchInChat.owningHistory() - ? _hiddenSearchInChat.owningHistory()->peer.get() + const auto topic = _searchState.inChat.topic(); + const auto peer = _searchState.inChat.owningHistory() + ? _searchState.inChat.owningHistory()->peer.get() : nullptr; const auto topicShortLabel = topic ? Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({ @@ -1273,65 +1263,28 @@ void Widget::updateSearchTabs() { const auto publicShortLabel = _searchingHashtag ? DefaultShortLabel(ChatSearchTab::PublicPosts) : TextWithEntities(); - _searchTab = _searchInChat.topic() - ? ChatSearchTab::ThisTopic - : _searchInChat.owningHistory() - ? ChatSearchTab::ThisPeer - : (_searchingHashtag && _searchTab == ChatSearchTab::PublicPosts) - ? ChatSearchTab::PublicPosts - : ChatSearchTab::MyMessages; + if ((_searchState.tab == ChatSearchTab::ThisTopic + && !_searchState.inChat.topic()) + || (_searchState.tab == ChatSearchTab::ThisPeer + && !_searchState.inChat) + || (_searchState.tab == ChatSearchTab::PublicPosts + && !_searchingHashtag)) { + _searchState.tab = _searchState.inChat.topic() + ? ChatSearchTab::ThisTopic + : _searchState.inChat.owningHistory() + ? ChatSearchTab::ThisPeer + : ChatSearchTab::MyMessages; + } _searchTabs->setTabShortLabels({ { ChatSearchTab::ThisTopic, topicShortLabel }, { ChatSearchTab::ThisPeer, peerShortLabel }, { ChatSearchTab::MyMessages, myShortLabel }, { ChatSearchTab::PublicPosts, publicShortLabel }, - }, _searchTab); + }, _searchState.tab); updateControlsGeometry(); } -void Widget::applySearchTab() { - switch (_searchTab) { - case ChatSearchTab::ThisTopic: - if (_searchInChat.topic()) { - } else if (_hiddenSearchInChat.topic()) { - setSearchInChat( - base::take(_hiddenSearchInChat), - base::take(_hiddenSearchFromAuthor), - base::take(_hiddenSearchTags)); - } else { - updateSearchTabs(); - } - case ChatSearchTab::ThisPeer: - if (_searchInChat.topic()) { - _hiddenSearchInChat = _searchInChat; - _hiddenSearchFromAuthor = _searchFromAuthor; - _hiddenSearchTags = _searchTags; - setSearchInChat( - _searchInChat.owningHistory(), - _searchFromAuthor, - _searchTags); - } else if (_searchInChat) { - return; - } else if (_hiddenSearchInChat) { - setSearchInChat( - _hiddenSearchInChat.owningHistory(), - _hiddenSearchFromAuthor, - _hiddenSearchTags); - } - case ChatSearchTab::MyMessages: - if (_searchInChat) { - _hiddenSearchInChat = (_hiddenSearchInChat.topic() - && _hiddenSearchInChat.owningHistory() == _searchInChat.history()) - ? _hiddenSearchInChat - : _searchInChat; - _hiddenSearchFromAuthor = _searchFromAuthor; - _hiddenSearchTags = _searchTags; - setSearchInChat({}); - } - } -} - void Widget::changeOpenedSubsection( FnMut change, bool fromRight, @@ -1575,7 +1528,8 @@ void Widget::showSearchInTopBar(anim::type animated) { _subsectionTopBar->toggleSearch(true, animated); _subsectionTopBar->searchEnableChooseFromUser( true, - !_searchFromAuthor); + !_searchState.fromPeer); + updateForceDisplayWide(); } QPixmap Widget::grabForFolderSlideAnimation() { @@ -1640,7 +1594,7 @@ void Widget::setInnerFocus(bool unfocusSearch) { return; } else if (!unfocusSearch && (!_search->getLastText().isEmpty() - || _searchInChat + || _searchState.inChat || _searchHasFocus || _searchSuggestionsLocked)) { _search->setFocus(); @@ -1657,7 +1611,7 @@ void Widget::jumpToTop(bool belowPinned) { if (session().supportMode()) { return; } - if ((currentSearchQuery().trimmed().isEmpty() && !_searchInChat)) { + if ((currentSearchQuery().trimmed().isEmpty() && !_searchState.inChat)) { auto to = 0; if (belowPinned) { const auto list = _openedForum @@ -1795,8 +1749,8 @@ void Widget::updateStoriesVisibility() { || _childList || _searchHasFocus || _searchSuggestionsLocked - || !_search->getLastText().isEmpty() - || _searchInChat + || !currentSearchQuery().isEmpty() + || _searchState.inChat || _stories->empty(); if (_stories->isHidden() != hidden) { _stories->setVisible(!hidden); @@ -1933,7 +1887,7 @@ void Widget::escape() { controller()->setActiveChatsFilter(first); } } - } else if (!_searchInChat + } else if (!_searchState.inChat && controller()->activeChatEntryCurrent().key) { controller()->content()->dialogsCancelled(); } @@ -1953,7 +1907,7 @@ void Widget::submit() { _inner->selectSkip(1); _inner->chooseRow(); } else { - searchMessages(); + search(); } } @@ -1996,55 +1950,63 @@ void Widget::loadMoreBlockedByDate() { session().api().requestMoreBlockedByDateDialogs(); } -bool Widget::searchMessages(bool searchCache) { +bool Widget::search(bool inCache) { auto result = false; - auto q = currentSearchQuery().trimmed(); - if (q.isEmpty() && !_searchFromAuthor && _searchTags.empty()) { + const auto query = currentSearchQuery().trimmed(); + const auto inPeer = searchInPeer(); + const auto fromPeer = searchFromPeer(); + const auto &inTags = searchInTags(); + const auto tab = _searchState.tab; + const auto skipRequest = (query.isEmpty() && !fromPeer && inTags.empty()) + || (tab == ChatSearchTab::PublicPosts && query.size() < 2); + if (skipRequest) { cancelSearchRequest(); _api.request(base::take(_peerSearchRequest)).cancel(); _api.request(base::take(_topicSearchRequest)).cancel(); return true; - } - if (searchCache) { - const auto success = _singleMessageSearch.lookup(q, [=] { - needSearchMessages(); + } else if (inCache) { + const auto success = _singleMessageSearch.lookup(query, [=] { + searchRequested(); }); if (!success) { return false; } - const auto i = _searchCache.find(q); + const auto i = _searchCache.find(query); if (i != _searchCache.end()) { - _searchQuery = q; - _searchQueryFrom = _searchFromAuthor; - _searchQueryTags = _searchTags; + _searchQuery = query; + _searchQueryFrom = fromPeer; + _searchQueryTags = inTags; + _searchQueryTab = tab; _searchNextRate = 0; _searchFull = _searchFullMigrated = false; cancelSearchRequest(); searchReceived( - ((_searchInChat || _openedForum) + (inPeer ? SearchRequestType::PeerFromStart : SearchRequestType::FromStart), i->second, 0); result = true; } - } else if (_searchQuery != q - || _searchQueryFrom != _searchFromAuthor - || _searchQueryTags != _searchTags) { - _searchQuery = q; - _searchQueryFrom = _searchFromAuthor; - _searchQueryTags = _searchTags; + } else if (_searchQuery != query + || _searchQueryFrom != fromPeer + || _searchQueryTags != inTags + || _searchQueryTab != tab) { + _searchQuery = query; + _searchQueryFrom = fromPeer; + _searchQueryTags = inTags; + _searchQueryTab = tab; _searchNextRate = 0; _searchFull = _searchFullMigrated = false; cancelSearchRequest(); - if (const auto peer = searchInPeer()) { + if (inPeer) { const auto topic = searchInTopic(); auto &histories = session().data().histories(); const auto type = Data::Histories::RequestType::History; - const auto history = session().data().history(peer); + const auto history = session().data().history(inPeer); const auto sublist = _openedForum ? nullptr - : _searchInChat.sublist(); + : _searchState.inChat.sublist(); const auto fromPeer = sublist ? nullptr : _searchQueryFrom; const auto savedPeer = sublist ? sublist->peer().get() @@ -2059,7 +2021,7 @@ bool Widget::searchMessages(bool searchCache) { | (_searchQueryTags.empty() ? Flag() : Flag::f_saved_reaction)), - peer->input, + inPeer->input, MTP_string(_searchQuery), (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()), (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()), @@ -2089,6 +2051,20 @@ bool Widget::searchMessages(bool searchCache) { _searchQueries.emplace(_searchRequest, _searchQuery); return _searchRequest; }); + } else if (_searchState.tab == ChatSearchTab::PublicPosts) { + const auto type = SearchRequestType::FromStart; + _searchRequest = session().api().request(MTPchannels_SearchPosts( + MTP_string(_searchState.query.trimmed().mid(1)), + MTP_int(0), // offset_rate + MTP_inputPeerEmpty(), // offset_peer + MTP_int(0), // offset_id + MTP_int(kSearchPerPage) + )).done([=](const MTPmessages_Messages &result) { + searchReceived(type, result, _searchRequest); + }).fail([=](const MTP::Error &error) { + searchFailed(type, error, _searchRequest); + }).send(); + _searchQueries.emplace(_searchRequest, _searchQuery); } else { const auto type = SearchRequestType::FromStart; const auto flags = session().settings().skipArchiveInSearch() @@ -2102,9 +2078,9 @@ bool Widget::searchMessages(bool searchCache) { MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date MTP_int(0), // max_date - MTP_int(0), - MTP_inputPeerEmpty(), - MTP_int(0), + MTP_int(0), // offset_rate + MTP_inputPeerEmpty(), // offset_peer + MTP_int(0), // offset_id MTP_int(kSearchPerPage) )).done([=](const MTPmessages_Messages &result) { searchReceived(type, result, _searchRequest); @@ -2114,18 +2090,18 @@ bool Widget::searchMessages(bool searchCache) { _searchQueries.emplace(_searchRequest, _searchQuery); } } - const auto query = Api::ConvertPeerSearchQuery(q); - if (searchForPeersRequired(query)) { - if (searchCache) { - auto i = _peerSearchCache.find(query); + const auto peerQuery = Api::ConvertPeerSearchQuery(query); + if (searchForPeersRequired(peerQuery)) { + if (inCache) { + auto i = _peerSearchCache.find(peerQuery); if (i != _peerSearchCache.end()) { - _peerSearchQuery = query; + _peerSearchQuery = peerQuery; _peerSearchRequest = 0; peerSearchReceived(i->second, 0); result = true; } - } else if (_peerSearchQuery != query) { - _peerSearchQuery = query; + } else if (_peerSearchQuery != peerQuery) { + _peerSearchQuery = peerQuery; _peerSearchFull = false; _peerSearchRequest = _api.request(MTPcontacts_Search( MTP_string(_peerSearchQuery), @@ -2139,7 +2115,7 @@ bool Widget::searchMessages(bool searchCache) { } } else { _api.request(base::take(_peerSearchRequest)).cancel(); - _peerSearchQuery = query; + _peerSearchQuery = peerQuery; _peerSearchFull = true; peerSearchReceived( MTP_contacts_found( @@ -2149,45 +2125,41 @@ bool Widget::searchMessages(bool searchCache) { MTP_vector(0)), 0); } - if (searchForTopicsRequired(query)) { - if (searchCache) { - if (_topicSearchQuery != query) { + if (searchForTopicsRequired(peerQuery)) { + if (inCache) { + if (_topicSearchQuery != peerQuery) { result = false; } - } else if (_topicSearchQuery != query) { - _topicSearchQuery = query; + } else if (_topicSearchQuery != peerQuery) { + _topicSearchQuery = peerQuery; _topicSearchFull = false; searchTopics(); } } else { _api.request(base::take(_topicSearchRequest)).cancel(); - _topicSearchQuery = query; + _topicSearchQuery = peerQuery; _topicSearchFull = true; } return result; } bool Widget::searchForPeersRequired(const QString &query) const { - return !_searchInChat - && !_searchFromAuthor - && _searchTags.empty() + return _searchState.filterChatsList() && !_openedForum && !query.isEmpty() - && (query[0] != '#'); + && !IsHashtagSearchQuery(query); } bool Widget::searchForTopicsRequired(const QString &query) const { - return !_searchInChat - && !_searchFromAuthor - && _searchTags.empty() + return _searchState.filterChatsList() && _openedForum && !query.isEmpty() - && (query[0] != '#') + && !IsHashtagSearchQuery(query) && !_openedForum->topicsList()->loaded(); } -void Widget::needSearchMessages() { - if (!searchMessages(true)) { +void Widget::searchRequested() { + if (!search(true)) { _searchTimer.callOnce(AutoSearchTimeout); } } @@ -2196,63 +2168,66 @@ void Widget::showMainMenu() { controller()->widget()->showMainMenu(); } -void Widget::searchMessages(QString query, Key inChat) { - if (_childList) { - const auto forum = controller()->shownForum().current(); - const auto topic = inChat.topic(); - if ((forum && forum->channel() == inChat.peer()) - || (topic && topic->forum() == forum)) { - _childList->searchMessages(query, inChat); - return; - } - hideChildList(); - } - if (_openedFolder) { - controller()->closeFolder(); - } +void Widget::searchMessages(SearchState state) { + applySearchState(std::move(state)); + session().local().saveRecentSearchHashtags(state.query); + //if (_childList) { + // const auto forum = controller()->shownForum().current(); + // const auto topic = inChat.topic(); + // if ((forum && forum->channel() == inChat.peer()) + // || (topic && topic->forum() == forum)) { + // _childList->searchMessages(query, inChat); + // return; + // } + // hideChildList(); + //} + //if (_openedFolder) { + // controller()->closeFolder(); + //} - auto tags = Data::SearchTagsFromQuery(query); - if (!tags.empty()) { - if (!inChat.sublist()) { - inChat = session().data().history(session().user()); - } - query = QString(); - } - const auto inChatChanged = [&] { - const auto inPeer = inChat.peer(); - const auto inTopic = inChat.topic(); - if (!inTopic - && _openedForum - && inPeer == _openedForum->channel() - && _subsectionTopBar - && _subsectionTopBar->searchMode()) { - return false; - } else if ((inTopic || (inPeer && !inPeer->isForum())) - && (inChat == _searchInChat)) { - return false; - } else if (inPeer) { - if (const auto to = inPeer->migrateTo()) { - if (to == _searchInChat.peer() && !_searchInChat.topic()) { - return false; - } - } - } - return true; - }(); - if ((currentSearchQuery() != query) - || inChatChanged - || _searchTags != tags) { - if (inChat) { - cancelSearch(); - setSearchInChat(inChat, nullptr, tags); - } - setSearchQuery(query); - applySearchUpdate(true); - _searchTimer.cancel(); - searchMessages(); + //auto tags = Data::SearchTagsFromQuery(query); + //if (!tags.empty()) { + // if (!inChat.sublist()) { + // inChat = session().data().history(session().user()); + // } + // query = QString(); + //} + //const auto inChatChanged = [&] { + // const auto inPeer = inChat.peer(); + // const auto inTopic = inChat.topic(); + // if (!inTopic + // && _openedForum + // && inPeer == _openedForum->channel() + // && _subsectionTopBar + // && _subsectionTopBar->searchMode()) { + // return false; + // } else if ((inTopic || (inPeer && !inPeer->isForum())) + // && (inChat == _searchState.inChat)) { + // return false; + // } else if (inPeer) { + // if (const auto to = inPeer->migrateTo()) { + // if (to == _searchState.inChat.peer() + // && !_searchState.inChat.topic()) { + // return false; + // } + // } + // } + // return true; + //}(); + //if ((currentSearchQuery() != query) + // || inChatChanged + // || _searchState.tags != tags) { + // if (inChat) { + // cancelSearch(); + // setSearchInChat(inChat, nullptr, tags); + // } + // setSearchQuery(query); + // applySearchUpdate(true); + // _searchTimer.cancel(); + // searchMessages(); - session().local().saveRecentSearchHashtags(query); - } + // session().local().saveRecentSearchHashtags(query); + //} } void Widget::searchTopics() { @@ -2308,7 +2283,7 @@ void Widget::searchMore() { const auto history = session().data().history(peer); const auto sublist = _openedForum ? nullptr - : _searchInChat.sublist(); + : _searchState.inChat.sublist(); const auto fromPeer = sublist ? nullptr : _searchQueryFrom; const auto savedPeer = sublist ? sublist->peer().get() @@ -2728,7 +2703,7 @@ void Widget::listScrollUpdated() { void Widget::updateCancelSearch() { const auto shown = !_search->getLastText().isEmpty() - || (!_searchInChat + || (!_searchState.inChat && (_searchHasFocus || _searchSuggestionsLocked)); _cancelSearch->toggle(shown, anim::type::normal); } @@ -2738,14 +2713,13 @@ bool Widget::fixSearchQuery() { return false; } _fixingSearchQuery = true; - const auto query = _search->getLastText(); - if (_searchTab == ChatSearchTab::PublicPosts) { + const auto query = currentSearchQuery(); + if (_searchState.tab == ChatSearchTab::PublicPosts) { const auto fixed = FixHashtagSearchQuery( query, - _search->textCursor().position()); + currentSearchQueryCursorPosition()); if (fixed.text != query) { - _search->setText(fixed.text); - _search->setCursorPosition(fixed.cursorPosition); + setSearchQuery(fixed.text, fixed.cursorPosition); } _searchingHashtag = true; } else if (_searchingHashtag != IsHashtagSearchQuery(query)) { @@ -2756,53 +2730,33 @@ bool Widget::fixSearchQuery() { return true; } -void Widget::applySearchUpdate(bool force) { - if (!fixSearchQuery() || (_showAnimation && !force)) { +void Widget::applySearchUpdate() { + if (!fixSearchQuery()) { return; } - - updateLockUnlockVisibility(anim::type::normal); - updateStoriesVisibility(); - const auto filterText = currentSearchQuery(); - _inner->applyFilterUpdate(filterText, force); - if (filterText.isEmpty() && !_searchFromAuthor && _searchTags.empty()) { - clearSearchCache(); - } - updateCancelSearch(); - if (!_postponeProcessSearchFocusChange) { - updateSuggestions(anim::type::instant); - } - updateLoadMoreChatsVisibility(); - updateJumpToDateVisibility(); - updateLockUnlockPosition(); - - if (filterText.isEmpty()) { - _peerSearchCache.clear(); - for (const auto &[requestId, query] : base::take(_peerSearchQueries)) { - _api.request(requestId).cancel(); - } - _peerSearchQuery = QString(); - } + auto copy = _searchState; + copy.query = currentSearchQuery(); + applySearchState(std::move(copy)); if (_chooseFromUser->toggled() - || _searchFromAuthor - || !_searchTags.empty()) { + || _searchState.fromPeer + || !_searchState.tags.empty()) { auto switchToChooseFrom = HistoryView::SwitchToChooseFromQuery(); if (_lastSearchText != switchToChooseFrom && switchToChooseFrom.startsWith(_lastSearchText) - && filterText == switchToChooseFrom) { + && _searchState.query == switchToChooseFrom) { showSearchFrom(); } } - _lastSearchText = filterText; - updateForceDisplayWide(); + _lastSearchText = _searchState.query; } void Widget::updateForceDisplayWide() { controller()->setChatsForceDisplayWide(_searchHasFocus + || (_subsectionTopBar && _subsectionTopBar->searchHasFocus()) || _searchSuggestionsLocked - || !_search->getLastText().isEmpty() - || _searchInChat); + || !currentSearchQuery().isEmpty() + || _searchState.inChat); } void Widget::showForum( @@ -2936,118 +2890,135 @@ void Widget::closeChildList(anim::type animated) { updateStoriesVisibility(); } -void Widget::searchInChat(Key chat) { - searchMessages(QString(), chat); -} - -bool Widget::setSearchInChat( - Key chat, - PeerData *from, - std::vector tags) { - if (_childList) { - if (_childList->setSearchInChat(chat, from, tags)) { +bool Widget::applySearchState(SearchState state) { + if (_searchState == state) { + return true; + } else if (_childList) { + if (_childList->applySearchState(state)) { return true; } hideChildList(); } - const auto peer = chat.peer(); - const auto topic = chat.topic(); + + // Adjust state to be consistent. + if (const auto peer = state.inChat.peer()) { + if (const auto to = peer->migrateTo()) { + state.inChat = peer->owner().history(to); + } + } + const auto peer = state.inChat.peer(); + const auto topic = state.inChat.topic(); const auto forum = peer ? peer->forum() : nullptr; - if (chat.folder() || (forum && !topic)) { - chat = Key(); + if (state.inChat.folder() || (forum && !topic)) { + state.inChat = {}; } - const auto searchInPeerUpdated = (_searchInChat != chat); - if (searchInPeerUpdated) { - from = nullptr; - } else if (!chat && !forum) { - from = nullptr; + if (!state.inChat && !forum) { + state.fromPeer = nullptr; } - const auto searchFromUpdated = searchInPeerUpdated - || (_searchFromAuthor != from); - _searchFromAuthor = from; + if (state.tab == ChatSearchTab::PublicPosts + && !IsHashtagSearchQuery(state.query)) { + state.tab = ChatSearchTab::MyMessages; + } + if (!state.tags.empty()) { + state.inChat = session().data().history(session().user()); + } + + const auto inChatChanged = (_searchState.inChat != state.inChat); + const auto fromPeerChanged = (_searchState.fromPeer != state.fromPeer); + const auto tagsChanged = (_searchState.tags != state.tags); + const auto queryChanged = (_searchState.query != state.query); + const auto tabChanged = (_searchState.tab != state.tab); if (forum) { if (_openedForum == forum) { + _searchState.fromPeer = state.fromPeer; // showSearchInTopBar showSearchInTopBar(anim::type::normal); } else if (_layout == Layout::Main) { _forumSearchRequested = true; + _searchState.fromPeer = state.fromPeer; // showSearchInTopBar controller()->showForum(forum); } else { return false; } + } else if (peer && (_layout != Layout::Main)) { + return false; } - _searchInMigrated = nullptr; - if (peer && !forum) { - if (_layout != Layout::Main) { - return false; - } else if (const auto migrateTo = peer->migrateTo()) { - const auto to = peer->owner().history(migrateTo); - return setSearchInChat(to, from, tags); - } else if (const auto migrateFrom = peer->migrateFrom()) { - _searchInMigrated = peer->owner().history(migrateFrom); - } + + const auto migrateFrom = (peer && !topic) + ? peer->migrateFrom() + : nullptr; + _searchInMigrated = migrateFrom + ? peer->owner().history(migrateFrom).get() + : nullptr; + _searchState = state; + if (queryChanged) { + updateLockUnlockVisibility(anim::type::normal); + updateLoadMoreChatsVisibility(); + updateCancelSearch(); } - if (searchInPeerUpdated) { - _searchInChat = chat; - if (chat) { - _hiddenSearchFromAuthor = {}; - _hiddenSearchTags = {}; - const auto hiddenTopic = _hiddenSearchInChat.topic(); - if (!hiddenTopic || hiddenTopic->history() != chat.history()) { - _hiddenSearchInChat = {}; - } - } - controller()->setSearchInChat(_searchInChat); - updateSuggestions(anim::type::instant); + if (inChatChanged) { + controller()->setSearchInChat(_searchState.inChat); updateSearchTabs(); + } + if (queryChanged || inChatChanged) { updateJumpToDateVisibility(); updateStoriesVisibility(); } - if (searchFromUpdated) { + if (fromPeerChanged) { updateSearchFromVisibility(); - clearSearchCache(); } updateLockUnlockPosition(); - if (_searchInChat && _layout == Layout::Main) { + + if ((state.query.isEmpty() && !state.fromPeer && state.tags.empty()) + || inChatChanged + || fromPeerChanged + || tagsChanged + || tabChanged) { + clearSearchCache(); + } + if (state.query.isEmpty()) { + _peerSearchCache.clear(); + for (const auto &[requestId, query] : base::take(_peerSearchQueries)) { + _api.request(requestId).cancel(); + } + _peerSearchQuery = QString(); + } + + if (_searchState.inChat && _layout == Layout::Main) { controller()->closeFolder(); } - _searchTags = std::move(tags); - _inner->searchInChat(_searchInChat, _searchFromAuthor, _searchTags); + + _inner->applySearchState(_searchState); + + if (!_postponeProcessSearchFocusChange) { + // Suggestions depend on _inner->state(), not on _searchState. + updateSuggestions(anim::type::instant); + } + _searchTagsLifetime = _inner->searchTagsChanges( ) | rpl::start_with_next([=](std::vector &&list) { - if (_searchTags != list) { - clearSearchCache(); - _searchTags = std::move(list); - if (_searchTags.empty()) { - applySearchUpdate(true); - } else { - searchMessages(); - } - } + auto copy = _searchState; + copy.tags = std::move(list); + applySearchState(std::move(copy)); }); if (_subsectionTopBar) { _subsectionTopBar->searchEnableJumpToDate( - _openedForum && _searchInChat); + _openedForum && _searchState.inChat); } - if (_searchFromAuthor + if (_searchState.fromPeer && _lastSearchText == HistoryView::SwitchToChooseFromQuery()) { cancelSearch(); } - if (_searchInChat || !_search->getLastText().isEmpty()) { + if (_searchState.inChat || !_search->getLastText().isEmpty()) { _search->setFocus(); } else { - setInnerFocus(true); + setInnerFocus(); } updateForceDisplayWide(); + applySearchUpdate(); return true; } -bool Widget::setSearchInChat( - Key chat, - PeerData *from) { - return setSearchInChat(chat, from, {}); -} - void Widget::clearSearchCache() { _searchCache.clear(); _singleMessageSearch.clear(); @@ -3066,27 +3037,31 @@ void Widget::clearSearchCache() { } void Widget::showCalendar() { - if (_searchInChat) { - controller()->showCalendar(_searchInChat, QDate()); + if (_searchState.inChat) { + controller()->showCalendar(_searchState.inChat, QDate()); } } void Widget::showSearchFrom() { if (const auto peer = searchInPeer()) { - const auto weak = base::make_weak(_searchInChat.topic()); - const auto chat = (!_searchInChat && _openedForum) + const auto weak = base::make_weak(_searchState.inChat.topic()); + const auto chat = (!_searchState.inChat && _openedForum) ? Key(_openedForum->history()) - : _searchInChat; + : _searchState.inChat; auto box = SearchFromBox( peer, crl::guard(this, [=](not_null from) { controller()->hideLayer(); + auto copy = _searchState; if (!chat.topic()) { - setSearchInChat(chat, from); + copy.inChat = chat; + copy.fromPeer = from; + applySearchState(std::move(copy)); } else if (const auto strong = weak.get()) { - setSearchInChat(strong, from); + copy.inChat = strong; + copy.fromPeer = from; + applySearchState(std::move(copy)); } - applySearchUpdate(true); }), crl::guard(this, [=] { _search->setFocus(); })); if (box) { @@ -3132,9 +3107,8 @@ void Widget::completeHashtag(QString tag) { } if (cur - start - 1 == tag.size() && cur < t.size() && t.at(cur) == ' ') ++cur; hashtag = t.mid(0, start + 1) + tag + ' ' + t.mid(cur); - _search->setText(hashtag); - _search->setCursorPosition(start + 1 + tag.size() + 1); - applySearchUpdate(true); + setSearchQuery(hashtag, start + 1 + tag.size() + 1); + applySearchUpdate(); return; } break; @@ -3142,9 +3116,10 @@ void Widget::completeHashtag(QString tag) { break; } } - _search->setText(t.mid(0, cur) + '#' + tag + ' ' + t.mid(cur)); - _search->setCursorPosition(cur + 1 + tag.size() + 1); - applySearchUpdate(true); + setSearchQuery( + t.mid(0, cur) + '#' + tag + ' ' + t.mid(cur), + cur + 1 + tag.size() + 1); + applySearchUpdate(); } void Widget::resizeEvent(QResizeEvent *e) { @@ -3162,7 +3137,7 @@ void Widget::updateLockUnlockVisibility(anim::type animated) { || _childList || _searchHasFocus || _searchSuggestionsLocked - || _searchInChat + || _searchState.inChat || !_search->getLastText().isEmpty(); if (_lockUnlock->toggled() == hidden) { const auto stories = _stories && !_stories->empty(); @@ -3195,7 +3170,7 @@ void Widget::updateJumpToDateVisibility(bool fast) { } _jumpToDate->toggle( - (_searchInChat && _search->getLastText().isEmpty()), + (_searchState.inChat && _search->getLastText().isEmpty()), fast ? anim::type::instant : anim::type::normal); } @@ -3203,7 +3178,7 @@ void Widget::updateSearchFromVisibility(bool fast) { auto visible = [&] { if (const auto peer = searchInPeer()) { if (peer->isChat() || peer->isMegagroup()) { - return !_searchFromAuthor; + return !_searchState.fromPeer; } } return false; @@ -3427,7 +3402,7 @@ void Widget::keyPressEvent(QKeyEvent *e) { //} } else if ((e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Tab) && _searchHasFocus - && !_searchInChat) { + && !_searchState.inChat) { escape(); } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { submit(); @@ -3586,15 +3561,39 @@ void Widget::cancelSearchRequest() { } PeerData *Widget::searchInPeer() const { - return _openedForum + return (_searchState.tab == ChatSearchTab::MyMessages + || _searchState.tab == ChatSearchTab::PublicPosts) + ? nullptr + : _openedForum ? _openedForum->channel().get() - : _searchInChat.sublist() + : _searchState.inChat.sublist() ? session().user().get() - : _searchInChat.peer(); + : _searchState.inChat.peer(); } Data::ForumTopic *Widget::searchInTopic() const { - return _searchInChat.topic(); + return (_searchState.tab != ChatSearchTab::ThisTopic) + ? nullptr + : _searchState.inChat.topic(); +} + +PeerData *Widget::searchFromPeer() const { + if (const auto peer = searchInPeer()) { + if (peer->isChat() || peer->isMegagroup()) { + return _searchState.fromPeer; + } + } + return nullptr; +} + +const std::vector &Widget::searchInTags() const { + if (const auto peer = searchInPeer()) { + if (peer->isSelf() && _searchState.tab == ChatSearchTab::ThisPeer) { + return _searchState.tags; + } + } + static const auto kEmpty = std::vector(); + return kEmpty; } QString Widget::currentSearchQuery() const { @@ -3603,6 +3602,12 @@ QString Widget::currentSearchQuery() const { : _search->getLastText(); } +int Widget::currentSearchQueryCursorPosition() const { + return _subsectionTopBar + ? _subsectionTopBar->searchQueryCursorPosition() + : _search->textCursor().position(); +} + void Widget::clearSearchField() { if (_subsectionTopBar) { _subsectionTopBar->searchClear(); @@ -3611,28 +3616,36 @@ void Widget::clearSearchField() { } } -void Widget::setSearchQuery(const QString &query) { +void Widget::setSearchQuery(const QString &query, int cursorPosition) { + if (cursorPosition < 0) { + cursorPosition = query.size(); + } if (_subsectionTopBar) { - _subsectionTopBar->searchSetText(query); + _subsectionTopBar->searchSetText(query, cursorPosition); } else { _search->setText(query); + _search->setCursorPosition(cursorPosition); } } bool Widget::cancelSearch() { - auto clearingQuery = !currentSearchQuery().isEmpty(); - auto clearingInChat = false; cancelSearchRequest(); - if (!clearingQuery && (_searchInChat || _searchFromAuthor)) { - if (_searchInChat && controller()->adaptive().isOneColumn()) { - if (const auto thread = _searchInChat.thread()) { + auto updatedState = _searchState; + const auto clearingQuery = !updatedState.query.isEmpty(); + auto clearingInChat = !clearingQuery + && (updatedState.inChat || updatedState.fromPeer); + if (clearingQuery) { + updatedState.query = QString(); + } else if (clearingInChat) { + if (updatedState.inChat && controller()->adaptive().isOneColumn()) { + if (const auto thread = updatedState.inChat.thread()) { controller()->showThread(thread); } else { Unexpected("Empty key in cancelSearch()."); } } - setSearchInChat(Key()); - clearingInChat = true; + updatedState.inChat = {}; + updatedState.fromPeer = nullptr; } if (!clearingQuery && _subsectionTopBar @@ -3640,9 +3653,9 @@ bool Widget::cancelSearch() { setInnerFocus(true); clearingInChat = true; } - const auto clearSearchFocus = !_searchInChat + const auto clearSearchFocus = !updatedState.inChat && (_searchHasFocus || _searchSuggestionsLocked); - if (!_searchInChat && _suggestions) { + if (!updatedState.inChat && _suggestions) { _suggestions->clearPersistance(); _searchSuggestionsLocked = false; } @@ -3654,32 +3667,13 @@ bool Widget::cancelSearch() { _lastSearchId = _lastSearchMigratedId = 0; _inner->clearFilter(); clearSearchField(); - applySearchUpdate(); + applySearchState(std::move(updatedState)); if (_suggestions && clearSearchFocus) { setInnerFocus(true); } return clearingQuery || clearingInChat || clearSearchFocus; } -void Widget::cancelSearchInChat() { - cancelSearchRequest(); - const auto isOneColumn = controller()->adaptive().isOneColumn(); - if (_searchInChat) { - if (isOneColumn && currentSearchQuery().trimmed().isEmpty()) { - if (const auto thread = _searchInChat.thread()) { - controller()->showThread(thread); - } else { - Unexpected("Empty key in cancelSearchInPeer()."); - } - } - setSearchInChat(Key()); - } - applySearchUpdate(true); - if (!isOneColumn && _search->getLastText().isEmpty()) { - controller()->content()->dialogsCancelled(); - } -} - Widget::~Widget() { cancelSearchRequest(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index a1bc0e4ea..f265ebf87 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -100,7 +100,6 @@ public: void showForum( not_null forum, const Window::SectionShow ¶ms); - void searchInChat(Key chat); void setInnerFocus(bool unfocusSearch = false); [[nodiscard]] bool searchHasFocus() const; @@ -122,9 +121,7 @@ public: void scrollToEntry(const RowDescriptor &entry); - void searchMessages(QString query, Key inChat = {}); - void searchTopics(); - void searchMore(); + void searchMessages(SearchState state); [[nodiscard]] RowDescriptor resolveChatNext(RowDescriptor from = {}) const; [[nodiscard]] RowDescriptor resolveChatPrevious(RowDescriptor from = {}) const; @@ -154,14 +151,16 @@ protected: private: void chosenRow(const ChosenRow &row); void listScrollUpdated(); - void cancelSearchInChat(); void searchCursorMoved(); void completeHashtag(QString tag); [[nodiscard]] QString currentSearchQuery() const; + [[nodiscard]] int currentSearchQueryCursorPosition() const; void clearSearchField(); - bool searchMessages(bool searchCache = false); - void needSearchMessages(); + void searchRequested(); + bool search(bool inCache = false); + void searchTopics(); + void searchMore(); void slideFinished(); void searchReceived( @@ -176,6 +175,8 @@ private: void cancelSearchRequest(); [[nodiscard]] PeerData *searchInPeer() const; [[nodiscard]] Data::ForumTopic *searchInTopic() const; + [[nodiscard]] PeerData *searchFromPeer() const; + [[nodiscard]] const std::vector &searchInTags() const; void setupSupportMode(); void setupConnectingWidget(); @@ -190,18 +191,15 @@ private: void trackScroll(not_null widget); [[nodiscard]] bool searchForPeersRequired(const QString &query) const; [[nodiscard]] bool searchForTopicsRequired(const QString &query) const; - bool setSearchInChat( - Key chat, - PeerData *from, - std::vector tags); - bool setSearchInChat( - Key chat, - PeerData *from = nullptr); + + // Child list may be unable to set specific search state. + bool applySearchState(SearchState state); + void showCalendar(); void showSearchFrom(); void showMainMenu(); void clearSearchCache(); - void setSearchQuery(const QString &query); + void setSearchQuery(const QString &query, int cursorPosition = -1); void updateControlsVisibility(bool fast = false); void updateLockUnlockVisibility( anim::type animated = anim::type::instant); @@ -227,7 +225,6 @@ private: QPixmap newContentCache, Window::SlideDirection direction); - void applySearchTab(); void openChildList( not_null forum, const Window::SectionShow ¶ms); @@ -236,7 +233,7 @@ private: void fullSearchRefreshOn(rpl::producer<> events); void updateCancelSearch(); bool fixSearchQuery(); - void applySearchUpdate(bool force = false); + void applySearchUpdate(); void refreshLoadMoreButton(bool mayBlock, bool isBlocked); void loadMoreBlockedByDate(); @@ -271,7 +268,7 @@ private: Layout _layout = Layout::Main; int _narrowWidth = 0; object_ptr _searchControls; - object_ptr _subsectionTopBar = { nullptr } ; + object_ptr _subsectionTopBar = { nullptr }; struct { object_ptr toggle; object_ptr under; @@ -312,23 +309,16 @@ private: bool _forumSearchRequested = false; bool _fixingSearchQuery = false; bool _searchingHashtag = false; - ChatSearchTab _searchTab = ChatSearchTab(); Data::Folder *_openedFolder = nullptr; Data::Forum *_openedForum = nullptr; - Key _searchInChat; + SearchState _searchState; History *_searchInMigrated = nullptr; - PeerData *_searchFromAuthor = nullptr; - std::vector _searchTags; rpl::lifetime _searchTagsLifetime; QString _lastSearchText; bool _searchSuggestionsLocked = false; bool _searchHasFocus = false; - Key _hiddenSearchInChat; - PeerData *_hiddenSearchFromAuthor = nullptr; - std::vector _hiddenSearchTags; - rpl::event_stream> _storiesContents; base::flat_map _storiesUserpicsViewsHidden; base::flat_map _storiesUserpicsViewsShown; @@ -357,6 +347,7 @@ private: QString _searchQuery; PeerData *_searchQueryFrom = nullptr; std::vector _searchQueryTags; + ChatSearchTab _searchQueryTab = {}; int32 _searchNextRate = 0; bool _searchFull = false; bool _searchFullMigrated = false; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 31764b970..59a94d585 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -1412,15 +1412,25 @@ QString TopBarWidget::searchQueryCurrent() const { return _searchQuery.current(); } +int TopBarWidget::searchQueryCursorPosition() const { + return _searchMode + ? _searchField->textCursor().position() + : _searchQuery.current().size(); +} + void TopBarWidget::searchClear() { if (_searchMode) { _searchField->clear(); } } -void TopBarWidget::searchSetText(const QString &query) { +void TopBarWidget::searchSetText(const QString &query, int cursorPosition) { if (_searchMode) { + if (cursorPosition < 0) { + cursorPosition = query.size(); + } _searchField->setText(query); + _searchField->setCursorPosition(cursorPosition); } } diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index 36d18a5f6..5c94220b1 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -88,8 +88,9 @@ public: [[nodiscard]] rpl::producer<> searchSubmitted() const; [[nodiscard]] rpl::producer searchQuery() const; [[nodiscard]] QString searchQueryCurrent() const; + [[nodiscard]] int searchQueryCursorPosition() const; void searchClear(); - void searchSetText(const QString &query); + void searchSetText(const QString &query, int cursorPosition = -1); [[nodiscard]] rpl::producer<> forwardSelectionRequest() const { return _forwardSelection.events(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 5f94f8a22..d3873d4f4 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -731,8 +731,18 @@ void MainWidget::hideSingleUseKeyboard(FullMsgId replyToId) { } void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { + auto tags = Data::SearchTagsFromQuery(query); if (controller()->isPrimary()) { - _dialogs->searchMessages(query, inChat); + using Tab = Dialogs::ChatSearchTab; + auto state = Dialogs::SearchState{ + .inChat = ((tags.empty() || inChat.sublist()) + ? inChat + : session().data().history(session().user())), + .tags = tags, + .query = tags.empty() ? query : QString(), + }; + state.tab = state.defaultTabForMe(); + _dialogs->searchMessages(std::move(state)); if (isOneColumn()) { _controller->clearSectionStack(); } else { @@ -742,7 +752,7 @@ void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { if (const auto sublist = inChat.sublist()) { controller()->showSection( std::make_shared(sublist)); - } else if (!Data::SearchTagsFromQuery(query).empty()) { + } else if (!tags.empty()) { inChat = controller()->session().data().history( controller()->session().user()); } From 6a8edefc87c6e47e7c4e708782b4f794156fcbac Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 19 May 2024 12:05:32 +0400 Subject: [PATCH 076/225] Fix some bugs in new chats search. --- .../dialogs/dialogs_inner_widget.cpp | 199 +++++++++--------- .../dialogs/dialogs_inner_widget.h | 5 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 177 ++++++---------- Telegram/SourceFiles/dialogs/dialogs_widget.h | 7 +- .../dialogs/ui/chat_search_tabs.cpp | 24 ++- .../SourceFiles/dialogs/ui/chat_search_tabs.h | 7 +- 6 files changed, 188 insertions(+), 231 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 25cf71a96..ee71e46e7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_three_state_icon.h" +#include "dialogs/ui/chat_search_tabs.h" #include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_video_userpic.h" @@ -747,6 +748,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.fillRect(dialogsClip, currentBg()); } } else if (_state == WidgetState::Filtered) { + auto top = 0; if (!_hashtagResults.empty()) { auto from = floorclamp(r.y(), st::mentionHeight, 0, _hashtagResults.size()); auto to = ceilclamp(r.y() + r.height(), st::mentionHeight, 0, _hashtagResults.size()); @@ -791,6 +793,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.drawText(htagleft + firstwidth, st::mentionTop + st::mentionFont->ascent, second); } p.translate(0, st::mentionHeight); + top += st::mentionHeight; } } } @@ -800,7 +803,9 @@ void InnerWidget::paintEvent(QPaintEvent *e) { auto to = std::min( filteredIndex(r.y() + r.height() - skip) + 1, int(_filterResults.size())); - p.translate(0, filteredHeight(from)); + const auto height = filteredHeight(from); + p.translate(0, height); + top += height; for (; from < to; ++from) { const auto selected = isPressed() ? (from == _filteredPressed) @@ -808,6 +813,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto row = _filterResults[from].row; paintRow(row, selected, !activeEntry.fullId); p.translate(0, row->height()); + top += row->height(); } } @@ -817,11 +823,13 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.setPen(st::searchedBarFg); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_search_global_results(tr::now)); p.translate(0, st::searchedBarHeight); + top += st::searchedBarHeight; auto skip = peerSearchOffset(); auto from = floorclamp(r.y() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size()); auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _peerSearchResults.size()); p.translate(0, from * st::dialogsRowHeight); + top += from * st::dialogsRowHeight; if (from < _peerSearchResults.size()) { const auto activePeer = activeEntry.key.peer(); for (; from < to; ++from) { @@ -844,6 +852,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { .paused = videoPaused, }); p.translate(0, st::dialogsRowHeight); + top += st::dialogsRowHeight; } } } @@ -857,29 +866,15 @@ void InnerWidget::paintEvent(QPaintEvent *e) { .paused = videoPaused, }); p.translate(0, searchInChatSkip()); - if (_waitingForSearch && _searchResults.empty()) { - p.fillRect( - 0, - 0, - fullWidth, - st::searchedBarHeight, - st::searchedBarBg); - p.setFont(st::searchedBarFont); - p.setPen(st::searchedBarFg); - p.drawTextLeft( - st::searchedBarPosition.x(), - st::searchedBarPosition.y(), - width(), - tr::lng_dlg_search_for_messages(tr::now)); - p.translate(0, st::searchedBarHeight); + top += searchInChatSkip(); + if (_searchResults.empty()) { + p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg); } } const auto showUnreadInSearchResults = uniqueSearchResults(); - if (!_waitingForSearch || !_searchResults.empty()) { - const auto text = _searchResults.empty() - ? tr::lng_search_no_results(tr::now) - : showUnreadInSearchResults + if (!_searchResults.empty()) { + const auto text = showUnreadInSearchResults ? u"Search results"_q : tr::lng_search_found_results( tr::now, @@ -890,11 +885,13 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.setPen(st::searchedBarFg); p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text); p.translate(0, st::searchedBarHeight); + top += st::searchedBarHeight; auto skip = searchedOffset(); auto from = floorclamp(r.y() - skip, _st->height, 0, _searchResults.size()); auto to = ceilclamp(r.y() + r.height() - skip, _st->height, 0, _searchResults.size()); p.translate(0, from * _st->height); + top += from * _st->height; if (from < _searchResults.size()) { for (; from < to; ++from) { const auto &result = _searchResults[from]; @@ -920,6 +917,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { .displayUnreadInfo = showUnreadInSearchResults, }); p.translate(0, _st->height); + top += _st->height; } } } @@ -1127,10 +1125,11 @@ void InnerWidget::paintSearchInChat( auto top = 0; if (_searchTags) { const auto height = _searchTags->height(); + top += st::dialogsSearchTagBottom; p.fillRect(0, top, width(), height, currentBg()); - const auto position = QPoint(_searchTagsLeft, 0); + const auto position = QPoint(_searchTagsLeft, top); _searchTags->paint(p, position, context.now, context.paused); - top += height; + top += height - st::dialogsSearchTagBottom; } p.setFont(st::searchedBarFont); auto fullRect = QRect(0, top, width(), height - top); @@ -1279,7 +1278,9 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { _lastMousePosition = globalPosition; _lastRowLocalMouseX = local.x(); - const auto tagBase = QPoint(_searchTagsLeft, searchInChatOffset()); + const auto tagBase = QPoint( + _searchTagsLeft, + searchInChatOffset() + st::dialogsSearchTagBottom); const auto tagPoint = local - tagBase; const auto inTags = _searchTags && QRect( @@ -1895,7 +1896,7 @@ void InnerWidget::moveCancelSearchButtons() { st::columnMinimalWidthLeft - _narrowWidth); const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchFromUser->width(); const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2; - const auto skip = st::searchedBarHeight + (_searchTags ? _searchTags->height() : 0); + const auto skip = (_searchTags ? _searchTags->height() : 0); _cancelSearchFromUser->moveToLeft(left, skip + top); } @@ -2460,6 +2461,74 @@ void InnerWidget::applySearchState(SearchState state) { withSameQuery.query = _searchState.query; const auto otherChanged = (_searchState != withSameQuery); + const auto ignoreInChat = (state.tab == ChatSearchTab::MyMessages) + || (state.tab == ChatSearchTab::PublicPosts); + const auto sublist = ignoreInChat ? nullptr : state.inChat.sublist(); + const auto peer = ignoreInChat + ? nullptr + : sublist + ? session().user().get() + : state.inChat.peer(); + if (const auto migrateFrom = peer ? peer->migrateFrom() : nullptr) { + _searchInMigrated = peer->owner().history(migrateFrom); + } else { + _searchInMigrated = nullptr; + } + if (peer && peer->isSelf()) { + const auto reactions = &peer->owner().reactions(); + _searchTags = std::make_unique( + &peer->owner(), + reactions->myTagsValue(sublist), + state.tags); + + _searchTags->selectedChanges( + ) | rpl::start_with_next([=](std::vector &&list) { + _searchState.tags = std::move(list); + }, _searchTags->lifetime()); + + _searchTags->repaintRequests() | rpl::start_with_next([=] { + const auto height = _searchTags->height(); + update(0, searchInChatOffset(), width(), height); + }, _searchTags->lifetime()); + + _searchTags->menuRequests( + ) | rpl::start_with_next([=](Data::ReactionId id) { + HistoryView::ShowTagInListMenu( + &_menu, + _lastMousePosition.value_or(QCursor::pos()), + this, + id, + _controller); + }, _searchTags->lifetime()); + + _searchTags->heightValue() | rpl::skip( + 1 + ) | rpl::start_with_next([=] { + refresh(); + moveCancelSearchButtons(); + }, _searchTags->lifetime()); + } else { + _searchTags = nullptr; + state.tags.clear(); + } + _searchFromShown = ignoreInChat + ? nullptr + : sublist + ? sublist->peer().get() + : state.fromPeer; + if (state.inChat) { + onHashtagFilterUpdate(QStringView()); + } + if (_searchFromShown) { + _cancelSearchFromUser->show(); + _searchFromUserUserpic = _searchFromShown->createUserpicView(); + } else { + _cancelSearchFromUser->hide(); + _searchFromUserUserpic = {}; + } + refreshSearchInChatLabel(); + moveCancelSearchButtons(); + _searchState = std::move(state); auto newFilter = _searchState.query; const auto mentionsSearch = (newFilter == u"@"_q); @@ -2569,7 +2638,9 @@ InnerWidget::~InnerWidget() { } void InnerWidget::clearSearchResults(bool clearPeerSearchResults) { - if (clearPeerSearchResults) _peerSearchResults.clear(); + if (clearPeerSearchResults) { + _peerSearchResults.clear(); + } _searchResults.clear(); _searchResultsLifetime.destroy(); _searchResultsHistories.clear(); @@ -2807,7 +2878,8 @@ void InnerWidget::searchReceived( SearchRequestType type, int fullCount) { const auto uniquePeers = uniqueSearchResults(); - if (type == SearchRequestType::FromStart || type == SearchRequestType::PeerFromStart) { + if (type == SearchRequestType::FromStart + || type == SearchRequestType::PeerFromStart) { clearSearchResults(false); } const auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart) @@ -3066,81 +3138,6 @@ bool InnerWidget::hasFilteredResults() const { return !_filterResults.empty() && _hashtagResults.empty(); } -void InnerWidget::searchInChat( - Key key, - PeerData *from, - std::vector tags) { - _searchInMigrated = nullptr; - const auto sublist = key.sublist(); - const auto peer = sublist ? session().user().get() : key.peer(); - if (peer) { - if (const auto migrateTo = peer->migrateTo()) { - const auto to = peer->owner().history(migrateTo); - return searchInChat(to, from, tags); - } else if (const auto migrateFrom = peer->migrateFrom()) { - _searchInMigrated = peer->owner().history(migrateFrom); - } - - if (peer->isSelf()) { - const auto reactions = &peer->owner().reactions(); - _searchTags = std::make_unique( - &peer->owner(), - reactions->myTagsValue(sublist), - tags); - - _searchTags->selectedChanges( - ) | rpl::start_with_next([=](std::vector &&list) { - _searchState.tags = std::move(list); - }, _searchTags->lifetime()); - - _searchTags->repaintRequests() | rpl::start_with_next([=] { - const auto height = _searchTags->height(); - update(0, searchInChatOffset(), width(), height); - }, _searchTags->lifetime()); - - _searchTags->menuRequests( - ) | rpl::start_with_next([=](Data::ReactionId id) { - HistoryView::ShowTagInListMenu( - &_menu, - _lastMousePosition.value_or(QCursor::pos()), - this, - id, - _controller); - }, _searchTags->lifetime()); - - _searchTags->heightValue() | rpl::skip( - 1 - ) | rpl::start_with_next([=] { - refresh(); - moveCancelSearchButtons(); - }, _searchTags->lifetime()); - } else { - _searchTags = nullptr; - _searchState.tags.clear(); - } - } else { - _searchTags = nullptr; - _searchState.tags.clear(); - } - _searchState.inChat = key; - _searchState.fromPeer = from; - _searchFromShown = key.sublist() ? key.sublist()->peer().get() : from; - if (_searchState.inChat) { - onHashtagFilterUpdate(QStringView()); - } - if (_searchFromShown) { - _cancelSearchFromUser->show(); - _searchFromUserUserpic = _searchFromShown->createUserpicView(); - } else { - _cancelSearchFromUser->hide(); - _searchFromUserUserpic = {}; - } - if (_searchState.inChat || _searchState.fromPeer) { - refreshSearchInChatLabel(); - } - moveCancelSearchButtons(); -} - auto InnerWidget::searchTagsChanges() const -> rpl::producer> { return _searchTags diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 805a7ff2b..57f649c3c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -146,10 +146,6 @@ public: [[nodiscard]] bool hasFilteredResults() const; void applySearchState(SearchState state); - void searchInChat( - Key key, - PeerData *from, - std::vector tags); [[nodiscard]] auto searchTagsChanges() const -> rpl::producer>; @@ -386,6 +382,7 @@ private: const Ui::Text::String &text) const; void refreshSearchInChatLabel(); void repaintSearchResult(int index); + void paintEmpty(QPainter &p, int top); Ui::VideoUserpic *validateVideoUserpic(not_null row); Ui::VideoUserpic *validateVideoUserpic(not_null history); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index cc510a948..bc9f2426a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -375,7 +375,7 @@ Widget::Widget( _search->changes( ) | rpl::start_with_next([=] { - applySearchUpdate(); + crl::on_main(this, [=] { applySearchUpdate(); }); }, _search->lifetime()); _search->submits( @@ -524,7 +524,7 @@ Widget::Widget( void Widget::chosenRow(const ChosenRow &row) { storiesToggleExplicitExpand(false); - if (!currentSearchQuery().isEmpty()) { + if (!_searchState.query.isEmpty()) { if (const auto history = row.key.history()) { session().recentPeers().bump(history->peer); } @@ -1240,6 +1240,7 @@ void Widget::updateSearchTabs() { applySearchState(std::move(copy)); }, _searchTabs->lifetime()); } + const auto sublist = _searchState.inChat.sublist(); const auto topic = _searchState.inChat.topic(); const auto peer = _searchState.inChat.owningHistory() ? _searchState.inChat.owningHistory()->peer.get() @@ -1258,6 +1259,10 @@ void Widget::updateSearchTabs() { ? Ui::Text::SingleCustomEmoji( session().data().customEmojiManager().peerUserpicEmojiData( peer)) + : sublist + ? Ui::Text::SingleCustomEmoji( + session().data().customEmojiManager().peerUserpicEmojiData( + sublist->peer())) : TextWithEntities(); const auto myShortLabel = DefaultShortLabel(ChatSearchTab::MyMessages); const auto publicShortLabel = _searchingHashtag @@ -1275,12 +1280,17 @@ void Widget::updateSearchTabs() { ? ChatSearchTab::ThisPeer : ChatSearchTab::MyMessages; } + const auto peerTabType = (peer && peer->isBroadcast()) + ? ChatSearchPeerTabType::Channel + : (peer && (peer->isChat() || peer->isMegagroup())) + ? ChatSearchPeerTabType::Group + : ChatSearchPeerTabType::Chat; _searchTabs->setTabShortLabels({ { ChatSearchTab::ThisTopic, topicShortLabel }, { ChatSearchTab::ThisPeer, peerShortLabel }, { ChatSearchTab::MyMessages, myShortLabel }, { ChatSearchTab::PublicPosts, publicShortLabel }, - }, _searchState.tab); + }, _searchState.tab, peerTabType); updateControlsGeometry(); } @@ -1611,7 +1621,7 @@ void Widget::jumpToTop(bool belowPinned) { if (session().supportMode()) { return; } - if ((currentSearchQuery().trimmed().isEmpty() && !_searchState.inChat)) { + if ((_searchState.query.trimmed().isEmpty() && !_searchState.inChat)) { auto to = 0; if (belowPinned) { const auto list = _openedForum @@ -1749,7 +1759,7 @@ void Widget::updateStoriesVisibility() { || _childList || _searchHasFocus || _searchSuggestionsLocked - || !currentSearchQuery().isEmpty() + || !_searchState.query.isEmpty() || _searchState.inChat || _stories->empty(); if (_stories->isHidden() != hidden) { @@ -1952,16 +1962,22 @@ void Widget::loadMoreBlockedByDate() { bool Widget::search(bool inCache) { auto result = false; - const auto query = currentSearchQuery().trimmed(); + const auto query = _searchState.query.trimmed(); const auto inPeer = searchInPeer(); const auto fromPeer = searchFromPeer(); const auto &inTags = searchInTags(); const auto tab = _searchState.tab; + const auto fromStartType = inPeer + ? SearchRequestType::PeerFromStart + : SearchRequestType::FromStart; const auto skipRequest = (query.isEmpty() && !fromPeer && inTags.empty()) || (tab == ChatSearchTab::PublicPosts && query.size() < 2); if (skipRequest) { cancelSearchRequest(); + searchApplyEmpty(fromStartType, 0); _api.request(base::take(_peerSearchRequest)).cancel(); + _peerSearchQuery = QString(); + peerSearchApplyEmpty(0); _api.request(base::take(_topicSearchRequest)).cancel(); return true; } else if (inCache) { @@ -1981,9 +1997,7 @@ bool Widget::search(bool inCache) { _searchFull = _searchFullMigrated = false; cancelSearchRequest(); searchReceived( - (inPeer - ? SearchRequestType::PeerFromStart - : SearchRequestType::FromStart), + fromStartType, i->second, 0); result = true; @@ -2109,21 +2123,14 @@ bool Widget::search(bool inCache) { )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { peerSearchReceived(result, requestId); }).fail([=](const MTP::Error &error, mtpRequestId requestId) { - peopleFailed(error, requestId); + peerSearchFailed(error, requestId); }).send(); _peerSearchQueries.emplace(_peerSearchRequest, _peerSearchQuery); } } else { _api.request(base::take(_peerSearchRequest)).cancel(); _peerSearchQuery = peerQuery; - _peerSearchFull = true; - peerSearchReceived( - MTP_contacts_found( - MTP_vector(0), - MTP_vector(0), - MTP_vector(0), - MTP_vector(0)), - 0); + peerSearchApplyEmpty(0); } if (searchForTopicsRequired(peerQuery)) { if (inCache) { @@ -2170,64 +2177,7 @@ void Widget::showMainMenu() { void Widget::searchMessages(SearchState state) { applySearchState(std::move(state)); - session().local().saveRecentSearchHashtags(state.query); - //if (_childList) { - // const auto forum = controller()->shownForum().current(); - // const auto topic = inChat.topic(); - // if ((forum && forum->channel() == inChat.peer()) - // || (topic && topic->forum() == forum)) { - // _childList->searchMessages(query, inChat); - // return; - // } - // hideChildList(); - //} - //if (_openedFolder) { - // controller()->closeFolder(); - //} - - //auto tags = Data::SearchTagsFromQuery(query); - //if (!tags.empty()) { - // if (!inChat.sublist()) { - // inChat = session().data().history(session().user()); - // } - // query = QString(); - //} - //const auto inChatChanged = [&] { - // const auto inPeer = inChat.peer(); - // const auto inTopic = inChat.topic(); - // if (!inTopic - // && _openedForum - // && inPeer == _openedForum->channel() - // && _subsectionTopBar - // && _subsectionTopBar->searchMode()) { - // return false; - // } else if ((inTopic || (inPeer && !inPeer->isForum())) - // && (inChat == _searchState.inChat)) { - // return false; - // } else if (inPeer) { - // if (const auto to = inPeer->migrateTo()) { - // if (to == _searchState.inChat.peer() - // && !_searchState.inChat.topic()) { - // return false; - // } - // } - // } - // return true; - //}(); - //if ((currentSearchQuery() != query) - // || inChatChanged - // || _searchState.tags != tags) { - // if (inChat) { - // cancelSearch(); - // setSearchInChat(inChat, nullptr, tags); - // } - // setSearchQuery(query); - // applySearchUpdate(true); - // _searchTimer.cancel(); - // searchMessages(); - - // session().local().saveRecentSearchHashtags(query); - //} + session().local().saveRecentSearchHashtags(_searchState.query); } void Widget::searchTopics() { @@ -2574,18 +2524,34 @@ void Widget::peerSearchReceived( } } +void Widget::searchApplyEmpty(SearchRequestType type, mtpRequestId id) { + _searchFull = _searchFullMigrated = true; + searchReceived( + type, + MTP_messages_messages( + MTP_vector(), + MTP_vector(), + MTP_vector()), + id); +} + +void Widget::peerSearchApplyEmpty(mtpRequestId id) { + _peerSearchFull = true; + peerSearchReceived( + MTP_contacts_found( + MTP_vector(0), + MTP_vector(0), + MTP_vector(0), + MTP_vector(0)), + id); +} + void Widget::searchFailed( SearchRequestType type, const MTP::Error &error, mtpRequestId requestId) { if (error.type() == u"SEARCH_QUERY_EMPTY"_q) { - searchReceived( - type, - MTP_messages_messages( - MTP_vector(), - MTP_vector(), - MTP_vector()), - requestId); + searchApplyEmpty(type, requestId); } else if (_searchRequest == requestId) { _searchRequest = 0; if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { @@ -2596,8 +2562,8 @@ void Widget::searchFailed( } } -void Widget::peopleFailed(const MTP::Error &error, mtpRequestId requestId) { - if (_peerSearchRequest == requestId) { +void Widget::peerSearchFailed(const MTP::Error &error, mtpRequestId id) { + if (_peerSearchRequest == id) { _peerSearchRequest = 0; _peerSearchFull = true; } @@ -2702,40 +2668,33 @@ void Widget::listScrollUpdated() { } void Widget::updateCancelSearch() { - const auto shown = !_search->getLastText().isEmpty() + const auto shown = !_searchState.query.isEmpty() || (!_searchState.inChat && (_searchHasFocus || _searchSuggestionsLocked)); _cancelSearch->toggle(shown, anim::type::normal); } -bool Widget::fixSearchQuery() { - if (_fixingSearchQuery) { - return false; - } - _fixingSearchQuery = true; +QString Widget::validateSearchQuery() { const auto query = currentSearchQuery(); if (_searchState.tab == ChatSearchTab::PublicPosts) { + _searchingHashtag = true; const auto fixed = FixHashtagSearchQuery( query, currentSearchQueryCursorPosition()); if (fixed.text != query) { setSearchQuery(fixed.text, fixed.cursorPosition); } - _searchingHashtag = true; + return fixed.text; } else if (_searchingHashtag != IsHashtagSearchQuery(query)) { _searchingHashtag = !_searchingHashtag; updateSearchTabs(); } - _fixingSearchQuery = false; - return true; + return query; } void Widget::applySearchUpdate() { - if (!fixSearchQuery()) { - return; - } auto copy = _searchState; - copy.query = currentSearchQuery(); + copy.query = validateSearchQuery(); applySearchState(std::move(copy)); if (_chooseFromUser->toggled() @@ -2755,7 +2714,7 @@ void Widget::updateForceDisplayWide() { controller()->setChatsForceDisplayWide(_searchHasFocus || (_subsectionTopBar && _subsectionTopBar->searchHasFocus()) || _searchSuggestionsLocked - || !currentSearchQuery().isEmpty() + || !_searchState.query.isEmpty() || _searchState.inChat); } @@ -2961,12 +2920,10 @@ bool Widget::applySearchState(SearchState state) { updateSearchTabs(); } if (queryChanged || inChatChanged) { - updateJumpToDateVisibility(); updateStoriesVisibility(); } - if (fromPeerChanged) { - updateSearchFromVisibility(); - } + updateJumpToDateVisibility(); + updateSearchFromVisibility(); updateLockUnlockPosition(); if ((state.query.isEmpty() && !state.fromPeer && state.tags.empty()) @@ -2988,6 +2945,7 @@ bool Widget::applySearchState(SearchState state) { controller()->closeFolder(); } + setSearchQuery(_searchState.query); _inner->applySearchState(_searchState); if (!_postponeProcessSearchFocusChange) { @@ -3009,7 +2967,7 @@ bool Widget::applySearchState(SearchState state) { && _lastSearchText == HistoryView::SwitchToChooseFromQuery()) { cancelSearch(); } - if (_searchState.inChat || !_search->getLastText().isEmpty()) { + if (_searchState.inChat || !_searchState.query.isEmpty()) { _search->setFocus(); } else { setInnerFocus(); @@ -3138,7 +3096,7 @@ void Widget::updateLockUnlockVisibility(anim::type animated) { || _searchHasFocus || _searchSuggestionsLocked || _searchState.inChat - || !_search->getLastText().isEmpty(); + || !_searchState.query.isEmpty(); if (_lockUnlock->toggled() == hidden) { const auto stories = _stories && !_stories->empty(); _lockUnlock->toggle( @@ -3157,7 +3115,7 @@ void Widget::updateLoadMoreChatsVisibility() { } const auto hidden = (_openedFolder != nullptr) || (_openedForum != nullptr) - || !currentSearchQuery().isEmpty(); + || !_searchState.query.isEmpty(); if (_loadMoreChats->isHidden() != hidden) { _loadMoreChats->setVisible(!hidden); updateControlsGeometry(); @@ -3170,7 +3128,7 @@ void Widget::updateJumpToDateVisibility(bool fast) { } _jumpToDate->toggle( - (_searchState.inChat && _search->getLastText().isEmpty()), + (searchInPeer() && _searchState.query.isEmpty()), fast ? anim::type::instant : anim::type::normal); } @@ -3617,6 +3575,10 @@ void Widget::clearSearchField() { } void Widget::setSearchQuery(const QString &query, int cursorPosition) { + if (query.isEmpty()) { + clearSearchField(); + return; + } if (cursorPosition < 0) { cursorPosition = query.size(); } @@ -3666,7 +3628,6 @@ bool Widget::cancelSearch() { _lastSearchPeer = nullptr; _lastSearchId = _lastSearchMigratedId = 0; _inner->clearFilter(); - clearSearchField(); applySearchState(std::move(updatedState)); if (_suggestions && clearSearchFocus) { setInnerFocus(true); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index f265ebf87..47b407e07 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -232,7 +232,7 @@ private: void fullSearchRefreshOn(rpl::producer<> events); void updateCancelSearch(); - bool fixSearchQuery(); + [[nodiscard]] QString validateSearchQuery(); void applySearchUpdate(); void refreshLoadMoreButton(bool mayBlock, bool isBlocked); void loadMoreBlockedByDate(); @@ -241,7 +241,9 @@ private: SearchRequestType type, const MTP::Error &error, mtpRequestId requestId); - void peopleFailed(const MTP::Error &error, mtpRequestId requestId); + void peerSearchFailed(const MTP::Error &error, mtpRequestId requestId); + void searchApplyEmpty(SearchRequestType type, mtpRequestId id); + void peerSearchApplyEmpty(mtpRequestId id); void updateForceDisplayWide(); void scrollToDefault(bool verytop = false); @@ -307,7 +309,6 @@ private: object_ptr _scrollToTop; bool _scrollToTopIsShown = false; bool _forumSearchRequested = false; - bool _fixingSearchQuery = false; bool _searchingHashtag = false; Data::Folder *_openedFolder = nullptr; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp index 456183d30..d79eb0502 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp @@ -77,6 +77,10 @@ FixedHashtagSearchQuery FixHashtagSearchQuery( result += ch; } } + if (result.size() == start) { + result += '#'; + ++cursorPosition; + } return { result, cursorPosition }; } @@ -123,23 +127,17 @@ ChatSearchTabs::ChatSearchTabs(QWidget *parent, ChatSearchTab active) ChatSearchTabs::~ChatSearchTabs() = default; -void ChatSearchTabs::setPeerTabType(ChatSearchPeerTabType type) { - _type = type; - const auto i = ranges::find(_list, ChatSearchTab::ThisPeer, &Tab::value); - Assert(i != end(_list)); - i->label = TabLabel(ChatSearchTab::ThisPeer, type); - if (!i->shortLabel.empty()) { - refreshTabs(_active.current()); - } -} - void ChatSearchTabs::setTabShortLabels( std::vector labels, - ChatSearchTab active) { + ChatSearchTab active, + ChatSearchPeerTabType peerTabType) { for (const auto &label : labels) { const auto i = ranges::find(_list, label.tab, &Tab::value); Assert(i != end(_list)); i->shortLabel = std::move(label.label); + if (i->value == ChatSearchTab::ThisPeer) { + i->label = TabLabel(label.tab, peerTabType); + } } refreshTabs(active); } @@ -175,4 +173,8 @@ int ChatSearchTabs::resizeGetHeight(int newWidth) { return _tabs->height(); } +void ChatSearchTabs::paintEvent(QPaintEvent *e) { + QPainter(this).fillRect(e->rect(), st::dialogsBg); +} + } // namespace Dialogs \ No newline at end of file diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h index 0e2640f83..ba87f9f98 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h @@ -37,8 +37,6 @@ public: ChatSearchTabs(QWidget *parent, ChatSearchTab active); ~ChatSearchTabs(); - void setPeerTabType(ChatSearchPeerTabType type); - // A [custom] emoji to use when there is not enough space for text. // Only tabs with available short labels are shown. struct ShortLabel { @@ -47,7 +45,8 @@ public: }; void setTabShortLabels( std::vector labels, - ChatSearchTab active); + ChatSearchTab active, + ChatSearchPeerTabType peerTabType); [[nodiscard]] rpl::producer tabChanges() const; @@ -60,13 +59,13 @@ private: void refreshTabs(ChatSearchTab active); int resizeGetHeight(int newWidth) override; + void paintEvent(QPaintEvent *e) override; const std::unique_ptr _tabs; const std::unique_ptr _shadow; std::vector _list; rpl::variable _active; - ChatSearchPeerTabType _type = {}; }; From 583bcca6a903184af61bd64c49f4e0f36ac0205d Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 20 May 2024 10:51:14 +0400 Subject: [PATCH 077/225] Fix forum search open by Ctrl+F. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index bc9f2426a..da58fd030 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1599,8 +1599,7 @@ void Widget::checkUpdateStatus() { void Widget::setInnerFocus(bool unfocusSearch) { if (_childList) { _childList->setInnerFocus(); - } else if ((_openedFolder || _openedForum) - && _subsectionTopBar->searchSetFocus()) { + } else if (_subsectionTopBar && _subsectionTopBar->searchSetFocus()) { return; } else if (!unfocusSearch && (!_search->getLastText().isEmpty() @@ -2967,10 +2966,10 @@ bool Widget::applySearchState(SearchState state) { && _lastSearchText == HistoryView::SwitchToChooseFromQuery()) { cancelSearch(); } - if (_searchState.inChat || !_searchState.query.isEmpty()) { - _search->setFocus(); - } else { + if (!_searchState.inChat && _searchState.query.isEmpty()) { setInnerFocus(); + } else if (!_subsectionTopBar || !_subsectionTopBar->searchSetFocus()) { + _search->setFocus(); } updateForceDisplayWide(); applySearchUpdate(); From 701bf0d553869df470cad1de1864b2f00384f460 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 20 May 2024 10:58:18 +0400 Subject: [PATCH 078/225] Fix searching with "from:" prefix. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index da58fd030..ca9994f32 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -2881,6 +2881,12 @@ bool Widget::applySearchState(SearchState state) { state.inChat = session().data().history(session().user()); } + const auto clearQuery = state.fromPeer + && (_lastSearchText == HistoryView::SwitchToChooseFromQuery()); + if (clearQuery) { + state.query = _lastSearchText = QString(); + } + const auto inChatChanged = (_searchState.inChat != state.inChat); const auto fromPeerChanged = (_searchState.fromPeer != state.fromPeer); const auto tagsChanged = (_searchState.tags != state.tags); @@ -2962,10 +2968,6 @@ bool Widget::applySearchState(SearchState state) { _subsectionTopBar->searchEnableJumpToDate( _openedForum && _searchState.inChat); } - if (_searchState.fromPeer - && _lastSearchText == HistoryView::SwitchToChooseFromQuery()) { - cancelSearch(); - } if (!_searchState.inChat && _searchState.query.isEmpty()) { setInnerFocus(); } else if (!_subsectionTopBar || !_subsectionTopBar->searchSetFocus()) { From 7d61ab9412d2a74444d29bca1579002fc5ba44bb Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 20 May 2024 11:52:59 +0400 Subject: [PATCH 079/225] Improve forum search support. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 27 ++++++++++++++++--- Telegram/SourceFiles/dialogs/dialogs_widget.h | 1 + .../dialogs/ui/chat_search_tabs.cpp | 8 +++--- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index ca9994f32..7755bc84e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -615,6 +615,7 @@ void Widget::chosenRow(const ChosenRow &row) { escape(); } } + updateForceDisplayWide(); } void Widget::setGeometryWithTopMoved( @@ -1234,7 +1235,9 @@ void Widget::updateSearchTabs() { _searchState.tab); _searchTabs->setVisible(!_showAnimation); _searchTabs->tabChanges( - ) | rpl::start_with_next([=](ChatSearchTab tab) { + ) | rpl::filter([=](ChatSearchTab tab) { + return (_searchState.tab != tab); + }) | rpl::start_with_next([=](ChatSearchTab tab) { auto copy = _searchState; copy.tab = tab; applySearchState(std::move(copy)); @@ -1244,6 +1247,8 @@ void Widget::updateSearchTabs() { const auto topic = _searchState.inChat.topic(); const auto peer = _searchState.inChat.owningHistory() ? _searchState.inChat.owningHistory()->peer.get() + : _openedForum + ? _openedForum->channel().get() : nullptr; const auto topicShortLabel = topic ? Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({ @@ -1271,7 +1276,8 @@ void Widget::updateSearchTabs() { if ((_searchState.tab == ChatSearchTab::ThisTopic && !_searchState.inChat.topic()) || (_searchState.tab == ChatSearchTab::ThisPeer - && !_searchState.inChat) + && !_searchState.inChat + && !_openedForum) || (_searchState.tab == ChatSearchTab::PublicPosts && !_searchingHashtag)) { _searchState.tab = _searchState.inChat.topic() @@ -1960,6 +1966,12 @@ void Widget::loadMoreBlockedByDate() { } bool Widget::search(bool inCache) { + _processingSearch = true; + const auto guard = gsl::finally([&] { + _processingSearch = false; + listScrollUpdated(); + }); + auto result = false; const auto query = _searchState.query.trimmed(); const auto inPeer = searchInPeer(); @@ -2710,6 +2722,10 @@ void Widget::applySearchUpdate() { } void Widget::updateForceDisplayWide() { + if (_childList) { + _childList->updateForceDisplayWide(); + return; + } controller()->setChatsForceDisplayWide(_searchHasFocus || (_subsectionTopBar && _subsectionTopBar->searchHasFocus()) || _searchSuggestionsLocked @@ -2791,6 +2807,7 @@ void Widget::openChildList( if (hasFocus()) { setInnerFocus(); } + updateForceDisplayWide(); } void Widget::closeChildList(anim::type animated) { @@ -2846,6 +2863,7 @@ void Widget::closeChildList(anim::type animated) { _childListShadow = nullptr; } updateStoriesVisibility(); + updateForceDisplayWide(); } bool Widget::applySearchState(SearchState state) { @@ -2970,8 +2988,10 @@ bool Widget::applySearchState(SearchState state) { } if (!_searchState.inChat && _searchState.query.isEmpty()) { setInnerFocus(); - } else if (!_subsectionTopBar || !_subsectionTopBar->searchSetFocus()) { + } else if (!_subsectionTopBar) { _search->setFocus(); + } else if (_openedForum && !_subsectionTopBar->searchSetFocus()) { + _subsectionTopBar->toggleSearch(true, anim::type::normal); } updateForceDisplayWide(); applySearchUpdate(); @@ -3633,6 +3653,7 @@ bool Widget::cancelSearch() { if (_suggestions && clearSearchFocus) { setInnerFocus(true); } + updateForceDisplayWide(); return clearingQuery || clearingInChat || clearSearchFocus; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 47b407e07..ec604f61c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -319,6 +319,7 @@ private: QString _lastSearchText; bool _searchSuggestionsLocked = false; bool _searchHasFocus = false; + bool _processingSearch = false; rpl::event_stream> _storiesContents; base::flat_map _storiesUserpicsViewsHidden; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp index d79eb0502..f03503387 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp @@ -114,13 +114,13 @@ ChatSearchTabs::ChatSearchTabs(QWidget *parent, ChatSearchTab active) _tabs->sectionActivated( ) | rpl::start_with_next([=](int index) { for (const auto &tab : _list) { - if (!index) { + if (tab.shortLabel.empty()) { + continue; + } else if (!index) { _active = tab.value; return; } - if (!tab.shortLabel.empty()) { - --index; - } + --index; } }, lifetime()); } From b21bcb86cc87bb2a6f560edcdb38153b066752d4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 20 May 2024 14:05:32 +0400 Subject: [PATCH 080/225] Fix search from user in forums. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 7755bc84e..6b9c4fb25 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1408,6 +1408,9 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) { cancelSearch(); closeChildList(anim::type::instant); _openedForum = forum; + _searchState.tab = forum + ? ChatSearchTab::ThisPeer + : ChatSearchTab::MyMessages; _api.request(base::take(_topicSearchRequest)).cancel(); _inner->changeOpenedForum(forum); storiesToggleExplicitExpand(false); @@ -1468,6 +1471,7 @@ void Widget::refreshTopBars() { setFocus(); } _subsectionTopBar.destroy(); + updateSearchFromVisibility(true); } _forumSearchRequested = false; if (_openedForum) { @@ -1542,9 +1546,6 @@ void Widget::showSearchInTopBar(anim::type animated) { Expects(_subsectionTopBar != nullptr); _subsectionTopBar->toggleSearch(true, animated); - _subsectionTopBar->searchEnableChooseFromUser( - true, - !_searchState.fromPeer); updateForceDisplayWide(); } @@ -2888,12 +2889,18 @@ bool Widget::applySearchState(SearchState state) { if (state.inChat.folder() || (forum && !topic)) { state.inChat = {}; } - if (!state.inChat && !forum) { + if (!state.inChat && !forum && !_openedForum) { state.fromPeer = nullptr; } if (state.tab == ChatSearchTab::PublicPosts && !IsHashtagSearchQuery(state.query)) { - state.tab = ChatSearchTab::MyMessages; + state.tab = (_openedForum && !state.inChat) + ? ChatSearchTab::ThisPeer + : ChatSearchTab::MyMessages; + } else if (!state.inChat && !_searchTabs) { + state.tab = (forum || _openedForum) + ? ChatSearchTab::ThisPeer + : ChatSearchTab::MyMessages; } if (!state.tags.empty()) { state.inChat = session().data().history(session().user()); @@ -2913,11 +2920,9 @@ bool Widget::applySearchState(SearchState state) { if (forum) { if (_openedForum == forum) { - _searchState.fromPeer = state.fromPeer; // showSearchInTopBar showSearchInTopBar(anim::type::normal); } else if (_layout == Layout::Main) { _forumSearchRequested = true; - _searchState.fromPeer = state.fromPeer; // showSearchInTopBar controller()->showForum(forum); } else { return false; @@ -3162,11 +3167,13 @@ void Widget::updateSearchFromVisibility(bool fast) { } return false; }(); - auto changed = (visible == !_chooseFromUser->toggled()); + const auto changed = (visible == !_chooseFromUser->toggled()); _chooseFromUser->toggle( visible, fast ? anim::type::instant : anim::type::normal); - if (changed) { + if (_subsectionTopBar) { + _subsectionTopBar->searchEnableChooseFromUser(true, visible); + } else if (changed) { auto additional = QMargins(); if (visible) { additional.setRight(_chooseFromUser->width()); From fce520c9c02ad1ef6582ac02246e45eb257172c0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 20 May 2024 14:40:10 +0400 Subject: [PATCH 081/225] Clear saved messages tags on cancel search. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 6b9c4fb25..6024e7b04 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -3623,7 +3623,9 @@ bool Widget::cancelSearch() { auto updatedState = _searchState; const auto clearingQuery = !updatedState.query.isEmpty(); auto clearingInChat = !clearingQuery - && (updatedState.inChat || updatedState.fromPeer); + && (updatedState.inChat + || updatedState.fromPeer + || !updatedState.tags.empty()); if (clearingQuery) { updatedState.query = QString(); } else if (clearingInChat) { @@ -3636,6 +3638,7 @@ bool Widget::cancelSearch() { } updatedState.inChat = {}; updatedState.fromPeer = nullptr; + updatedState.tags = {}; } if (!clearingQuery && _subsectionTopBar From 42d53e5543d38371c705eca0c0618520007d1457 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 20 May 2024 17:38:18 +0400 Subject: [PATCH 082/225] Fix saved messages tags search. --- .../dialogs/dialogs_inner_widget.cpp | 59 +++++++++++-------- .../dialogs/dialogs_inner_widget.h | 4 ++ Telegram/SourceFiles/dialogs/dialogs_key.cpp | 2 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 3 +- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index ee71e46e7..274d889a9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -488,7 +488,7 @@ int InnerWidget::peerSearchOffset() const { + st::searchedBarHeight; } -int InnerWidget::searchInChatOffset() const { +int InnerWidget::searchTagsOffset() const { auto result = peerSearchOffset() - st::searchedBarHeight; if (!_peerSearchResults.empty()) { result += (_peerSearchResults.size() * st::dialogsRowHeight) @@ -497,6 +497,14 @@ int InnerWidget::searchInChatOffset() const { return result; } +int InnerWidget::searchInChatOffset() const { + auto result = searchTagsOffset(); + if (_searchTags) { + result += _searchTags->height(); + } + return result; +} + int InnerWidget::searchedOffset() const { return searchInChatOffset() + searchInChatSkip() @@ -505,9 +513,6 @@ int InnerWidget::searchedOffset() const { int InnerWidget::searchInChatSkip() const { auto result = 0; - if (_searchTags) { - result += _searchTags->height(); - } if (_searchFromShown) { result += st::dialogsSearchInHeight; } @@ -857,6 +862,17 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } } + if (_searchTags) { + paintSearchTags(p, { + .st = &st::forumTopicRow, + .currentBg = currentBg(), + .now = ms, + .width = fullWidth, + .paused = videoPaused, + }); + p.translate(0, _searchTags->height()); + top += _searchTags->height(); + } if (_searchFromShown) { paintSearchInChat(p, { .st = &st::forumTopicRow, @@ -1117,20 +1133,24 @@ QBrush InnerWidget::currentBg() const { _childListShown.current().shown); } +void InnerWidget::paintSearchTags( + Painter &p, + const Ui::PaintContext &context) const { + Expects(_searchTags != nullptr); + + const auto height = _searchTags->height(); + p.fillRect(0, 0, width(), height, currentBg()); + const auto top = st::dialogsSearchTagBottom / 2; + const auto position = QPoint(_searchTagsLeft, top); + _searchTags->paint(p, position, context.now, context.paused); +} + void InnerWidget::paintSearchInChat( Painter &p, const Ui::PaintContext &context) const { auto height = searchInChatSkip(); auto top = 0; - if (_searchTags) { - const auto height = _searchTags->height(); - top += st::dialogsSearchTagBottom; - p.fillRect(0, top, width(), height, currentBg()); - const auto position = QPoint(_searchTagsLeft, top); - _searchTags->paint(p, position, context.now, context.paused); - top += height - st::dialogsSearchTagBottom; - } p.setFont(st::searchedBarFont); auto fullRect = QRect(0, top, width(), height - top); p.fillRect(fullRect, currentBg()); @@ -1280,7 +1300,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { const auto tagBase = QPoint( _searchTagsLeft, - searchInChatOffset() + st::dialogsSearchTagBottom); + searchTagsOffset() + st::dialogsSearchTagBottom / 2); const auto tagPoint = local - tagBase; const auto inTags = _searchTags && QRect( @@ -2464,11 +2484,7 @@ void InnerWidget::applySearchState(SearchState state) { const auto ignoreInChat = (state.tab == ChatSearchTab::MyMessages) || (state.tab == ChatSearchTab::PublicPosts); const auto sublist = ignoreInChat ? nullptr : state.inChat.sublist(); - const auto peer = ignoreInChat - ? nullptr - : sublist - ? session().user().get() - : state.inChat.peer(); + const auto peer = ignoreInChat ? nullptr : state.inChat.peer(); if (const auto migrateFrom = peer ? peer->migrateFrom() : nullptr) { _searchInMigrated = peer->owner().history(migrateFrom); } else { @@ -2481,14 +2497,9 @@ void InnerWidget::applySearchState(SearchState state) { reactions->myTagsValue(sublist), state.tags); - _searchTags->selectedChanges( - ) | rpl::start_with_next([=](std::vector &&list) { - _searchState.tags = std::move(list); - }, _searchTags->lifetime()); - _searchTags->repaintRequests() | rpl::start_with_next([=] { const auto height = _searchTags->height(); - update(0, searchInChatOffset(), width(), height); + update(0, searchTagsOffset(), width(), height); }, _searchTags->lifetime()); _searchTags->menuRequests( diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 57f649c3c..8cde8de92 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -334,6 +334,7 @@ private: [[nodiscard]] int filteredIndex(int y) const; [[nodiscard]] int filteredHeight(int till = -1) const; [[nodiscard]] int peerSearchOffset() const; + [[nodiscard]] int searchTagsOffset() const; [[nodiscard]] int searchInChatOffset() const; [[nodiscard]] int searchedOffset() const; [[nodiscard]] int searchInChatSkip() const; @@ -349,6 +350,9 @@ private: Painter &p, not_null result, const Ui::PaintContext &context); + void paintSearchTags( + Painter &p, + const Ui::PaintContext &context) const; void paintSearchInChat( Painter &p, const Ui::PaintContext &context) const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp index 263a605cc..8a8cf398c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp @@ -93,7 +93,7 @@ PeerData *Key::peer() const { ChatSearchTab SearchState::defaultTabForMe() const { return inChat.topic() ? ChatSearchTab::ThisTopic - : inChat.history() + : (inChat.history() || inChat.sublist()) ? ChatSearchTab::ThisPeer : ChatSearchTab::MyMessages; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 6024e7b04..5d8cdc063 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1282,7 +1282,8 @@ void Widget::updateSearchTabs() { && !_searchingHashtag)) { _searchState.tab = _searchState.inChat.topic() ? ChatSearchTab::ThisTopic - : _searchState.inChat.owningHistory() + : (_searchState.inChat.owningHistory() + || _searchState.inChat.sublist()) ? ChatSearchTab::ThisPeer : ChatSearchTab::MyMessages; } From 7b7438cd7b6476b942642ee26cd363cc5b3a0cf6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 20 May 2024 18:00:34 +0400 Subject: [PATCH 083/225] Make chats list tabs closer to each other. --- Telegram/SourceFiles/dialogs/dialogs.style | 4 ++-- Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp | 6 +++--- Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp | 8 ++++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 3ee37b6f9..63c85b638 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -649,7 +649,7 @@ dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) { barRadius: 2px; barFg: transparent; barSnapToLabel: true; - strictSkip: 34px; + strictSkip: 18px; labelTop: 7px; labelStyle: semiboldTextStyle; labelFg: windowSubTextFg; @@ -659,7 +659,7 @@ dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) { rippleBgActive: lightButtonBgOver; ripple: defaultRippleAnimation; } - +dialogsSearchTabsPadding: 8px; dialogsStoriesList: DialogsStoriesList { small: dialogsStories; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp index f03503387..504321fab 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp @@ -110,7 +110,7 @@ ChatSearchTabs::ChatSearchTabs(QWidget *parent, ChatSearchTab active) }) { _list.push_back({ tab, TabLabel(tab) }); } - _tabs->move(0, 0); + _tabs->move(st::dialogsSearchTabsPadding, 0); _tabs->sectionActivated( ) | rpl::start_with_next([=](int index) { for (const auto &tab : _list) { @@ -166,9 +166,9 @@ void ChatSearchTabs::refreshTabs(ChatSearchTab active) { int ChatSearchTabs::resizeGetHeight(int newWidth) { _tabs->resizeToWidth(newWidth); _shadow->setGeometry( - _tabs->x(), + 0, _tabs->y() + _tabs->height() - st::lineWidth, - _tabs->width(), + newWidth, st::lineWidth); return _tabs->height(); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 37be201fa..868b3b74c 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -903,9 +903,13 @@ void Suggestions::setupTabs() { const auto shadow = Ui::CreateChild(this); shadow->lower(); - _tabs->sizeValue() | rpl::start_with_next([=](QSize size) { + _tabs->move(st::dialogsSearchTabsPadding, 0); + rpl::combine( + widthValue(), + _tabs->heightValue() + ) | rpl::start_with_next([=](int width, int height) { const auto line = st::lineWidth; - shadow->setGeometry(0, size.height() - line, size.width(), line); + shadow->setGeometry(0, height - line, width, line); }, shadow->lifetime()); shadow->showOn(_tabs->shownValue()); From 279db771cf86c833d2fe0f6087faefa73e241904 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 21 May 2024 09:36:56 +0400 Subject: [PATCH 084/225] Support dynamic search tabs with emoji. --- .../boxes/peers/edit_forum_topic_box.cpp | 36 ++++--- .../boxes/peers/edit_forum_topic_box.h | 4 +- .../data/stickers/data_custom_emoji.cpp | 2 +- Telegram/SourceFiles/dialogs/dialogs.style | 5 + .../SourceFiles/dialogs/dialogs_widget.cpp | 22 ++++- .../dialogs/ui/chat_search_tabs.cpp | 99 +++++++++++-------- .../SourceFiles/dialogs/ui/chat_search_tabs.h | 9 +- .../ui/widgets/discrete_sliders.cpp | 30 ++++++ .../SourceFiles/ui/widgets/discrete_sliders.h | 11 +++ 9 files changed, 158 insertions(+), 60 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index e8a8b9d59..ed8a77787 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -48,7 +48,8 @@ class DefaultIconEmoji final : public Ui::Text::CustomEmoji { public: DefaultIconEmoji( rpl::producer value, - Fn repaint); + Fn repaint, + Data::CustomEmojiSizeTag tag); int width() override; QString entityData() override; @@ -61,14 +62,17 @@ public: private: DefaultIcon _icon = {}; QImage _image; + Data::CustomEmojiSizeTag _tag = {}; rpl::lifetime _lifetime; }; DefaultIconEmoji::DefaultIconEmoji( - rpl::producer value, - Fn repaint) { + rpl::producer value, + Fn repaint, + Data::CustomEmojiSizeTag tag) +: _tag(tag) { std::move(value) | rpl::start_with_next([=](DefaultIcon value) { _icon = value; _image = QImage(); @@ -85,19 +89,22 @@ QString DefaultIconEmoji::entityData() { } void DefaultIconEmoji::paint(QPainter &p, const Context &context) { + const auto &st = (_tag == Data::CustomEmojiSizeTag::Normal) + ? st::normalForumTopicIcon + : st::defaultForumTopicIcon; if (_image.isNull()) { _image = Data::IsForumGeneralIconTitle(_icon.title) ? Data::ForumTopicGeneralIconFrame( - st::defaultForumTopicIcon.size, + st.size, Data::ParseForumGeneralIconColor(_icon.colorId)) - : Data::ForumTopicIconFrame( - _icon.colorId, - _icon.title, - st::defaultForumTopicIcon); + : Data::ForumTopicIconFrame(_icon.colorId, _icon.title, st); } - const auto esize = Ui::Emoji::GetSizeLarge() / style::DevicePixelRatio(); + const auto full = (_tag == Data::CustomEmojiSizeTag::Normal) + ? Ui::Emoji::GetSizeNormal() + : Ui::Emoji::GetSizeLarge(); + const auto esize = full / style::DevicePixelRatio(); const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize); - const auto skip = (customSize - st::defaultForumTopicIcon.size) / 2; + const auto skip = (customSize - st.size) / 2; p.drawImage(context.position + QPoint(skip, skip), _image); } @@ -262,7 +269,8 @@ struct IconSelector { if (id == kDefaultIconId) { return std::make_unique( rpl::duplicate(defaultIcon), - std::move(repaint)); + std::move(repaint), + tag); } return manager->create(id, std::move(repaint), tag); }; @@ -576,8 +584,10 @@ void EditForumTopicBox( std::unique_ptr MakeTopicIconEmoji( Data::TopicIconDescriptor descriptor, - Fn repaint) { + Fn repaint, + Data::CustomEmojiSizeTag tag) { return std::make_unique( rpl::single(descriptor), - std::move(repaint)); + std::move(repaint), + tag); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h index 620ecebaf..81758e84c 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h @@ -13,6 +13,7 @@ class History; namespace Data { struct TopicIconDescriptor; +enum class CustomEmojiSizeTag : uchar; } // namespace Data namespace Window { @@ -32,4 +33,5 @@ void EditForumTopicBox( [[nodiscard]] std::unique_ptr MakeTopicIconEmoji( Data::TopicIconDescriptor descriptor, - Fn repaint); + Fn repaint, + Data::CustomEmojiSizeTag tag); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 7f110b6b1..c7eea5d48 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -542,7 +542,7 @@ std::unique_ptr CustomEmojiManager::create( const auto size = EmojiSizeFromTag(tag) / ratio; return userpic(data, std::move(update), size); } else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) { - return MakeTopicIconEmoji(parsed, std::move(update)); + return MakeTopicIconEmoji(parsed, std::move(update), tag); } const auto parsed = ParseCustomEmojiData(data); return parsed diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 63c85b638..caa3526e2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -46,6 +46,11 @@ defaultForumTopicIcon: ForumTopicIcon { font: font(bold 11px); textTop: 2px; } +normalForumTopicIcon: ForumTopicIcon { + size: 19px; + font: font(bold 10px); + textTop: 2px; +} largeForumTopicIcon: ForumTopicIcon { size: 26px; font: font(bold 13px); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 5d8cdc063..db8724a61 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/event_filter.h" #include "core/application.h" +#include "core/ui_integration.h" #include "core/update_checker.h" #include "core/shortcuts.h" #include "boxes/peer_list_box.h" @@ -1230,9 +1231,17 @@ void Widget::updateSearchTabs() { } return; } else if (!_searchTabs) { + const auto savedSession = &session(); + const auto markedTextContext = [=](Fn repaint) { + return Core::MarkedTextContext{ + .session = savedSession, + .customEmojiRepaint = std::move(repaint), + }; + }; _searchTabs = std::make_unique( this, - _searchState.tab); + _searchState.tab, + std::move(markedTextContext)); _searchTabs->setVisible(!_showAnimation); _searchTabs->tabChanges( ) | rpl::filter([=](ChatSearchTab tab) { @@ -1250,16 +1259,19 @@ void Widget::updateSearchTabs() { : _openedForum ? _openedForum->channel().get() : nullptr; - const auto topicShortLabel = topic - ? Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({ + const auto topicShortLabel = !topic + ? TextWithEntities() + : topic->iconId() + ? Ui::Text::SingleCustomEmoji( + Data::SerializeCustomEmojiId(topic->iconId())) + : Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({ .title = (topic->isGeneral() ? Data::ForumGeneralIconTitle() : topic->title()), .colorId = (topic->isGeneral() ? Data::ForumGeneralIconColor(st::windowSubTextFg->c) : topic->colorId()), - })) - : TextWithEntities(); + })); const auto peerShortLabel = peer ? Ui::Text::SingleCustomEmoji( session().data().customEmojiManager().peerUserpicEmojiData( diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp index 504321fab..a8615ae11 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp @@ -97,31 +97,19 @@ bool IsHashtagSearchQuery(const QString &query) { return true; } -ChatSearchTabs::ChatSearchTabs(QWidget *parent, ChatSearchTab active) +ChatSearchTabs::ChatSearchTabs( + QWidget *parent, + ChatSearchTab active, + Fn)> markedTextContext) : RpWidget(parent) , _tabs(std::make_unique(this, st::dialogsSearchTabs)) , _shadow(std::make_unique(this)) +, _markedTextContext(std::move(markedTextContext)) , _active(active) { - for (const auto tab : { - ChatSearchTab::ThisTopic, - ChatSearchTab::ThisPeer, - ChatSearchTab::MyMessages, - ChatSearchTab::PublicPosts, - }) { - _list.push_back({ tab, TabLabel(tab) }); - } _tabs->move(st::dialogsSearchTabsPadding, 0); _tabs->sectionActivated( ) | rpl::start_with_next([=](int index) { - for (const auto &tab : _list) { - if (tab.shortLabel.empty()) { - continue; - } else if (!index) { - _active = tab.value; - return; - } - --index; - } + _active = _list[index].value; }, lifetime()); } @@ -131,40 +119,73 @@ void ChatSearchTabs::setTabShortLabels( std::vector labels, ChatSearchTab active, ChatSearchPeerTabType peerTabType) { - for (const auto &label : labels) { - const auto i = ranges::find(_list, label.tab, &Tab::value); - Assert(i != end(_list)); - i->shortLabel = std::move(label.label); - if (i->value == ChatSearchTab::ThisPeer) { - i->label = TabLabel(label.tab, peerTabType); + const auto &st = st::dialogsSearchTabs; + const auto &font = st.labelStyle.font; + _list.clear(); + _list.reserve(labels.size()); + + auto widthTotal = 0; + for (const auto tab : { + ChatSearchTab::ThisTopic, + ChatSearchTab::ThisPeer, + ChatSearchTab::MyMessages, + ChatSearchTab::PublicPosts, + }) { + const auto i = ranges::find(labels, tab, &ShortLabel::tab); + if (i != end(labels) && !i->label.empty()) { + const auto label = TabLabel(tab, peerTabType); + const auto widthFull = font->width(label) + st.strictSkip; + _list.push_back({ + .value = tab, + .label = label, + .shortLabel = i->label, + .widthFull = widthFull, + }); + widthTotal += widthFull; } } - refreshTabs(active); + const auto widthSingleEmoji = st::emojiSize + st.strictSkip; + for (const auto tab : { + ChatSearchTab::PublicPosts, + ChatSearchTab::ThisTopic, + ChatSearchTab::ThisPeer, + ChatSearchTab::MyMessages, + }) { + const auto i = ranges::find(_list, tab, &Tab::value); + if (i != end(_list)) { + i->widthThresholdForShort = widthTotal; + widthTotal -= i->widthFull; + widthTotal += widthSingleEmoji; + } + } + refillTabs(active, width()); } rpl::producer ChatSearchTabs::tabChanges() const { return _active.changes(); } -void ChatSearchTabs::refreshTabs(ChatSearchTab active) { - auto index = 0; - auto labels = std::vector(); +void ChatSearchTabs::refillTabs( + ChatSearchTab active, + int newWidth) { + auto labels = std::vector(); + const auto available = newWidth - 2 * st::dialogsSearchTabsPadding; for (const auto &tab : _list) { - if (tab.value == active) { - index = int(labels.size()); - Assert(!tab.shortLabel.empty()); - labels.push_back(tab.label); - } else if (!tab.shortLabel.empty()) { - labels.push_back(tab.label); - } + auto label = (available < tab.widthThresholdForShort) + ? tab.shortLabel + : TextWithEntities{ tab.label }; + labels.push_back(std::move(label)); } - _tabs->setSections(labels); - _tabs->setActiveSectionFast(index); - resizeToWidth(width()); + _tabs->setSections(labels, _markedTextContext([=] { update(); })); + + const auto i = ranges::find(_list, active, &Tab::value); + Assert(i != end(_list)); + _tabs->setActiveSectionFast(i - begin(_list)); + _tabs->resizeToWidth(newWidth); } int ChatSearchTabs::resizeGetHeight(int newWidth) { - _tabs->resizeToWidth(newWidth); + refillTabs(_active.current(), newWidth); _shadow->setGeometry( 0, _tabs->y() + _tabs->height() - st::lineWidth, diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h index ba87f9f98..720bf5734 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h @@ -34,7 +34,10 @@ enum class ChatSearchPeerTabType : uchar { class ChatSearchTabs final : public Ui::RpWidget { public: - ChatSearchTabs(QWidget *parent, ChatSearchTab active); + ChatSearchTabs( + QWidget *parent, + ChatSearchTab active, + Fn)> markedTextContext); ~ChatSearchTabs(); // A [custom] emoji to use when there is not enough space for text. @@ -55,14 +58,18 @@ private: ChatSearchTab value = {}; QString label; TextWithEntities shortLabel; + int widthFull = 0; + int widthThresholdForShort = 0; }; void refreshTabs(ChatSearchTab active); + void refillTabs(ChatSearchTab active, int newWidth); int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; const std::unique_ptr _tabs; const std::unique_ptr _shadow; + const Fn)> _markedTextContext; std::vector _list; rpl::variable _active; diff --git a/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp b/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp index e78de050a..53dc4e3dd 100644 --- a/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp +++ b/Telegram/SourceFiles/ui/widgets/discrete_sliders.cpp @@ -66,6 +66,13 @@ void DiscreteSlider::addSection(const QString &label) { resizeToWidth(width()); } +void DiscreteSlider::addSection( + const TextWithEntities &label, + const std::any &context) { + _sections.push_back(Section(label, getLabelStyle(), context)); + resizeToWidth(width()); +} + void DiscreteSlider::setSections(const std::vector &labels) { Assert(!labels.empty()); @@ -73,6 +80,22 @@ void DiscreteSlider::setSections(const std::vector &labels) { for (const auto &label : labels) { _sections.push_back(Section(label, getLabelStyle())); } + refresh(); +} + +void DiscreteSlider::setSections( + const std::vector &labels, + const std::any &context) { + Assert(!labels.empty()); + + _sections.clear(); + for (const auto &label : labels) { + _sections.push_back(Section(label, getLabelStyle(), context)); + } + refresh(); +} + +void DiscreteSlider::refresh() { stopAnimation(); if (_activeIndex >= _sections.size()) { _activeIndex = 0; @@ -182,6 +205,13 @@ DiscreteSlider::Section::Section( : label(st, label) { } +DiscreteSlider::Section::Section( + const TextWithEntities &label, + const style::TextStyle &st, + const std::any &context) { + this->label.setMarkedText(st, label, kMarkupTextOptions, context); +} + SettingsSlider::SettingsSlider( QWidget *parent, const style::SettingsSlider &st) diff --git a/Telegram/SourceFiles/ui/widgets/discrete_sliders.h b/Telegram/SourceFiles/ui/widgets/discrete_sliders.h index 38cdb473d..0cb930bf1 100644 --- a/Telegram/SourceFiles/ui/widgets/discrete_sliders.h +++ b/Telegram/SourceFiles/ui/widgets/discrete_sliders.h @@ -22,7 +22,13 @@ public: ~DiscreteSlider(); void addSection(const QString &label); + void addSection( + const TextWithEntities &label, + const std::any &context = {}); void setSections(const std::vector &labels); + void setSections( + const std::vector &labels, + const std::any &context = {}); int activeSection() const { return _activeIndex; } @@ -44,6 +50,10 @@ protected: struct Section { Section(const QString &label, const style::TextStyle &st); + Section( + const TextWithEntities &label, + const style::TextStyle &st, + const std::any &context); int left = 0; int width = 0; @@ -75,6 +85,7 @@ protected: _a_left.stop(); _a_width.stop(); } + void refresh(); void setSelectOnPress(bool selectOnPress); From e00c6ecfb8931358c050818f869e7e4f2e2f2a1c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 21 May 2024 13:16:08 +0400 Subject: [PATCH 085/225] Show empty / placeholder in chats search. --- .../dialogs/dialogs_inner_widget.cpp | 118 +++++++++++++++--- .../dialogs/dialogs_inner_widget.h | 12 +- Telegram/SourceFiles/dialogs/dialogs_key.cpp | 1 + Telegram/SourceFiles/dialogs/dialogs_key.h | 2 - .../SourceFiles/dialogs/dialogs_widget.cpp | 16 +-- .../dialogs/ui/chat_search_empty.cpp | 82 ++++++++++++ .../dialogs/ui/chat_search_empty.h | 43 +++++++ .../dialogs/ui/dialogs_suggestions.cpp | 50 +++----- .../dialogs/ui/dialogs_suggestions.h | 4 +- Telegram/cmake/td_ui.cmake | 2 + 10 files changed, 264 insertions(+), 66 deletions(-) create mode 100644 Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp create mode 100644 Telegram/SourceFiles/dialogs/ui/chat_search_empty.h diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 274d889a9..6767c52f9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_three_state_icon.h" +#include "dialogs/ui/chat_search_empty.h" #include "dialogs/ui/chat_search_tabs.h" #include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_stories_content.h" @@ -84,7 +85,7 @@ constexpr auto kHashtagResultsLimit = 5; constexpr auto kStartReorderThreshold = 30; constexpr auto kChatPreviewDelay = crl::time(1000); -int FixedOnTopDialogsCount(not_null list) { +[[nodiscard]] int FixedOnTopDialogsCount(not_null list) { auto result = 0; for (const auto &row : *list) { if (!row->entry()->fixedOnTopIndex()) { @@ -95,7 +96,7 @@ int FixedOnTopDialogsCount(not_null list) { return result; } -int PinnedDialogsCount( +[[nodiscard]] int PinnedDialogsCount( FilterId filterId, not_null list) { auto result = 0; @@ -110,6 +111,52 @@ int PinnedDialogsCount( return result; } +[[nodiscard]] object_ptr MakeSearchEmpty( + QWidget *parent, + SearchState state) { + const auto query = state.query.trimmed(); + const auto hashtag = !query.isEmpty() && (query[0] == '#'); + const auto trimmed = hashtag ? query.mid(1).trimmed() : query; + const auto waiting = trimmed.isEmpty() + && state.tags.empty() + && !state.fromPeer; + const auto icon = waiting + ? SearchEmptyIcon::Search + : SearchEmptyIcon::NoResults; + auto text = TextWithEntities(); + if (waiting) { + if (hashtag) { + text.append(tr::lng_search_tab_by_hashtag(tr::now)); + } else { + text.append( + tr::lng_dlg_search_for_messages(tr::now) + ).append('\n').append(Ui::Text::Link(tr::lng_cancel(tr::now))); + } + } else { + text.append(tr::lng_search_tab_no_results( + tr::now, + Ui::Text::Bold)); + if (!trimmed.isEmpty()) { + text.append("\n").append( + tr::lng_search_tab_no_results_text( + tr::now, + lt_query, + trimmed)); + if (hashtag) { + text.append("\n").append( + tr::lng_search_tab_no_results_retry(tr::now)); + } + } + } + auto result = object_ptr( + parent, + icon, + rpl::single(std::move(text))); + result->show(); + result->resizeToWidth(parent->width()); + return result; +} + } // namespace struct InnerWidget::CollapsedRow { @@ -186,7 +233,7 @@ InnerWidget::InnerWidget( session().data().contactsLoaded().changes( ) | rpl::start_with_next([=] { refresh(); - refreshEmptyLabel(); + refreshEmpty(); }, lifetime()); session().data().itemRemoved( @@ -1906,7 +1953,7 @@ void InnerWidget::resizeEvent(QResizeEvent *e) { if (_searchTags) { _searchTags->resizeToWidth(width() - 2 * _searchTagsLeft); } - resizeEmptyLabel(); + resizeEmpty(); moveCancelSearchButtons(); } @@ -2587,12 +2634,13 @@ void InnerWidget::applySearchState(SearchState state) { append(owner->contactsNoChatsList()); } } - refresh(true); } clearMouseSelection(true); } if (_state != WidgetState::Default) { + _searchLoading = true; _searchMessages.fire({}); + refresh(true); } } @@ -2791,6 +2839,10 @@ rpl::producer<> InnerWidget::cancelSearchFromUserRequests() const { return _cancelSearchFromUser->clicks() | rpl::to_empty; } +rpl::producer<> InnerWidget::cancelSearchRequests() const { + return _cancelSearch.events(); +} + rpl::producer InnerWidget::mustScrollTo() const { return _mustScrollTo.events(); } @@ -2888,6 +2940,8 @@ void InnerWidget::searchReceived( HistoryItem *inject, SearchRequestType type, int fullCount) { + _searchLoading = false; + const auto uniquePeers = uniqueSearchResults(); if (type == SearchRequestType::FromStart || type == SearchRequestType::PeerFromStart) { @@ -3018,7 +3072,7 @@ void InnerWidget::refresh(bool toTop) { } else if (needCollapsedRowsRefresh()) { return refreshWithCollapsedRows(toTop); } - refreshEmptyLabel(); + refreshEmpty(); if (_searchTags) { _searchTagsLeft = st::dialogsFilterSkip + st::dialogsFilterPadding.x(); @@ -3032,7 +3086,11 @@ void InnerWidget::refresh(bool toTop) { h = dialogsOffset() + _shownList->height(); } } else if (_state == WidgetState::Filtered) { - if (_waitingForSearch) { + if (_searchEmpty && !_searchEmpty->isHidden()) { + h = searchedOffset() + st::recentPeersEmptyHeightMin; + _searchEmpty->setMinimalHeight(st::recentPeersEmptyHeightMin); + _searchEmpty->move(0, h - st::recentPeersEmptyHeightMin); + } else if (_waitingForSearch) { h = searchedOffset() + (_searchResults.size() * _st->height) + ((_searchResults.empty() && !_searchState.inChat) ? -st::searchedBarHeight : 0); } else { h = searchedOffset() + (_searchResults.size() * _st->height); @@ -3047,7 +3105,32 @@ void InnerWidget::refresh(bool toTop) { update(); } -void InnerWidget::refreshEmptyLabel() { +void InnerWidget::refreshEmpty() { + if (_state == WidgetState::Filtered) { + const auto empty = _filterResults.empty() + && _searchResults.empty() + && _peerSearchResults.empty() + && _hashtagResults.empty(); + if (_searchLoading || !empty) { + if (_searchEmpty) { + _searchEmpty->hide(); + } + } else if (_searchEmptyState != _searchState) { + _searchEmptyState = _searchState; + _searchEmpty = MakeSearchEmpty(this, _searchState); + _searchEmpty->linkClicks() | rpl::start_with_next([=] { + _cancelSearch.fire({}); + }, _searchEmpty->lifetime()); + if (_controller->session().data().chatsListLoaded()) { + _searchEmpty->animate(); + } + } else if (_searchEmpty) { + _searchEmpty->show(); + } + } else { + _searchEmpty.destroy(); + } + const auto data = &session().data(); const auto state = !_shownList->empty() ? EmptyState::None @@ -3100,7 +3183,7 @@ void InnerWidget::refreshEmptyLabel() { return result; }); _empty.create(this, std::move(full), st::dialogsEmptyLabel); - resizeEmptyLabel(); + resizeEmpty(); _empty->overrideLinkClickHandler([=] { if (_emptyState == EmptyState::NoContacts) { _controller->showAddContact(); @@ -3114,13 +3197,16 @@ void InnerWidget::refreshEmptyLabel() { _empty->setVisible(_state == WidgetState::Default); } -void InnerWidget::resizeEmptyLabel() { - if (!_empty) { - return; +void InnerWidget::resizeEmpty() { + if (_empty) { + const auto skip = st::dialogsEmptySkip; + _empty->resizeToWidth(width() - 2 * skip); + _empty->move(skip, (st::dialogsEmptyHeight - _empty->height()) / 2); + } + if (_searchEmpty) { + _searchEmpty->resizeToWidth(width()); + _searchEmpty->move(0, searchedOffset()); } - const auto skip = st::dialogsEmptySkip; - _empty->resizeToWidth(width() - 2 * skip); - _empty->move(skip, (st::dialogsEmptyHeight - _empty->height()) / 2); } void InnerWidget::clearMouseSelection(bool clearSelection) { @@ -3481,7 +3567,7 @@ void InnerWidget::switchToFilter(FilterId filterId) { refreshShownList(); refreshWithCollapsedRows(true); } - refreshEmptyLabel(); + refreshEmpty(); { const auto skip = found // Don't save a scroll state for very flexible chat filters. diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 8cde8de92..a7e3ae619 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -60,6 +60,7 @@ class Row; class FakeRow; class IndexedList; class SearchTags; +class SearchEmpty; struct ChosenRow { Key key; @@ -119,8 +120,8 @@ public: void clearFilter(); void refresh(bool toTop = false); - void refreshEmptyLabel(); - void resizeEmptyLabel(); + void refreshEmpty(); + void resizeEmpty(); [[nodiscard]] bool isUserpicPress() const; [[nodiscard]] bool isUserpicPressOnWide() const; @@ -158,6 +159,7 @@ public: void setLoadMoreFilteredCallback(Fn callback); [[nodiscard]] rpl::producer<> listBottomReached() const; [[nodiscard]] rpl::producer<> cancelSearchFromUserRequests() const; + [[nodiscard]] rpl::producer<> cancelSearchRequests() const; [[nodiscard]] rpl::producer chosenRow() const; [[nodiscard]] rpl::producer<> updated() const; @@ -386,7 +388,6 @@ private: const Ui::Text::String &text) const; void refreshSearchInChatLabel(); void repaintSearchResult(int index); - void paintEmpty(QPainter &p, int top); Ui::VideoUserpic *validateVideoUserpic(not_null row); Ui::VideoUserpic *validateVideoUserpic(not_null history); @@ -395,7 +396,6 @@ private: void clearSearchResults(bool clearPeerSearchResults = true); void updateSelectedRow(Key key = Key()); void trackSearchResultsHistory(not_null history); - void trackSearchResultsForum(Data::Forum *forum); [[nodiscard]] QBrush currentBg() const; [[nodiscard]] Key computeChatPreviewRow() const; @@ -485,8 +485,11 @@ private: WidgetState _state = WidgetState::Default; + object_ptr _searchEmpty = { nullptr }; + SearchState _searchEmptyState; object_ptr _empty = { nullptr }; object_ptr _cancelSearchFromUser; + rpl::event_stream<> _cancelSearch; Ui::DraggingScrollManager _draggingScroll; @@ -526,6 +529,7 @@ private: bool _geometryInited = false; bool _savedSublists = false; + bool _searchLoading = false; base::unique_qptr _menu; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp index 8a8cf398c..7c1be62aa 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp @@ -87,6 +87,7 @@ PeerData *Key::peer() const { [[nodiscard]] bool SearchState::empty() const { return !inChat + && tags.empty() && QStringView(query).trimmed().isEmpty(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index a9278e08c..2f631288e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -147,6 +147,4 @@ struct SearchState { const SearchState&) = default; }; -; - } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index db8724a61..67155eb3d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -345,6 +345,10 @@ Widget::Widget( } applySearchState(std::move(copy)); }, lifetime()); + _inner->cancelSearchRequests( + ) | rpl::start_with_next([=] { + applySearchState({}); + }, lifetime()); _inner->chosenRow( ) | rpl::start_with_next([=](const ChosenRow &row) { chosenRow(row); @@ -1988,6 +1992,9 @@ bool Widget::search(bool inCache) { auto result = false; const auto query = _searchState.query.trimmed(); + const auto trimmed = (query.isEmpty() || query[0] != '#') + ? query + : query.mid(1).trimmed(); const auto inPeer = searchInPeer(); const auto fromPeer = searchFromPeer(); const auto &inTags = searchInTags(); @@ -1995,9 +2002,7 @@ bool Widget::search(bool inCache) { const auto fromStartType = inPeer ? SearchRequestType::PeerFromStart : SearchRequestType::FromStart; - const auto skipRequest = (query.isEmpty() && !fromPeer && inTags.empty()) - || (tab == ChatSearchTab::PublicPosts && query.size() < 2); - if (skipRequest) { + if (trimmed.isEmpty() && !fromPeer && inTags.empty()) { cancelSearchRequest(); searchApplyEmpty(fromStartType, 0); _api.request(base::take(_peerSearchRequest)).cancel(); @@ -2021,10 +2026,7 @@ bool Widget::search(bool inCache) { _searchNextRate = 0; _searchFull = _searchFullMigrated = false; cancelSearchRequest(); - searchReceived( - fromStartType, - i->second, - 0); + searchReceived(fromStartType, i->second, 0); result = true; } } else if (_searchQuery != query diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp new file mode 100644 index 000000000..855112f85 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.cpp @@ -0,0 +1,82 @@ +/* +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 "dialogs/ui/chat_search_empty.h" + +#include "base/object_ptr.h" +#include "lottie/lottie_icon.h" +#include "settings/settings_common.h" +#include "ui/widgets/labels.h" +#include "styles/style_dialogs.h" + +namespace Dialogs { + +SearchEmpty::SearchEmpty( + QWidget *parent, + Icon icon, + rpl::producer text) +: RpWidget(parent) { + setup(icon, std::move(text)); +} + +void SearchEmpty::setMinimalHeight(int minimalHeight) { + const auto minimal = st::recentPeersEmptyHeightMin; + resize(width(), std::max(minimalHeight, minimal)); +} + +void SearchEmpty::setup(Icon icon, rpl::producer text) { + const auto label = Ui::CreateChild( + this, + std::move(text), + st::defaultPeerListAbout); + label->setClickHandlerFilter([=](const auto &, Qt::MouseButton button) { + if (button == Qt::LeftButton) { + _linkClicks.fire({}); + } + return false; + }); + const auto size = st::recentPeersEmptySize; + const auto animation = [&] { + switch (icon) { + case Icon::Search: return u"search"_q; + case Icon::NoResults: return u"noresults"_q; + } + Unexpected("Icon in SearchEmpty::setup."); + }(); + const auto [widget, animate] = Settings::CreateLottieIcon( + this, + { + .name = animation, + .sizeOverride = { size, size }, + }, + st::recentPeersEmptyMargin); + const auto animated = widget.data(); + + sizeValue() | rpl::start_with_next([=](QSize size) { + const auto padding = st::recentPeersEmptyMargin; + const auto paddings = padding.left() + padding.right(); + label->resizeToWidth(size.width() - paddings); + const auto x = (size.width() - animated->width()) / 2; + const auto y = (size.height() - animated->height()) / 3; + const auto top = y + animated->height() + st::recentPeersEmptySkip; + const auto sub = std::max(top + label->height() - size.height(), 0); + animated->move(x, y - sub); + label->move((size.width() - label->width()) / 2, top - sub); + }, lifetime()); + + _animate = [animate] { + animate(anim::repeat::once); + }; +} + +void SearchEmpty::animate() { + if (const auto onstack = _animate) { + onstack(); + } +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h new file mode 100644 index 000000000..6ddf52676 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_empty.h @@ -0,0 +1,43 @@ +/* +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" + +namespace Dialogs { + +enum class SearchEmptyIcon { + Search, + NoResults, +}; + +class SearchEmpty final : public Ui::RpWidget { +public: + using Icon = SearchEmptyIcon; + + SearchEmpty( + QWidget *parent, + Icon icon, + rpl::producer text); + + void setMinimalHeight(int minimalHeight); + [[nodiscard]] rpl::producer<> linkClicks() const { + return _linkClicks.events(); + } + + void animate(); + +private: + void setup(Icon icon, rpl::producer text); + + Fn _animate; + rpl::event_stream<> _linkClicks; + +}; + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 868b3b74c..9a86cca75 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -20,12 +20,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_values.h" #include "data/data_session.h" #include "data/data_user.h" +#include "dialogs/ui/chat_search_empty.h" #include "history/history.h" #include "lang/lang_keys.h" -#include "lottie/lottie_icon.h" #include "main/main_session.h" #include "settings/settings_common.h" #include "ui/boxes/confirm_box.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/buttons.h" #include "ui/widgets/discrete_sliders.h" @@ -1360,53 +1361,30 @@ object_ptr> Suggestions::setupRecentPeers( } object_ptr> Suggestions::setupEmptyRecent() { - return setupEmpty(_chatsContent, "search", tr::lng_recent_none()); + const auto icon = SearchEmptyIcon::Search; + return setupEmpty(_chatsContent, icon, tr::lng_recent_none()); } object_ptr> Suggestions::setupEmptyChannels() { - return setupEmpty( - _channelsContent, - "noresults", - tr::lng_channels_none_about()); + const auto icon = SearchEmptyIcon::NoResults; + return setupEmpty(_channelsContent, icon, tr::lng_channels_none_about()); } object_ptr> Suggestions::setupEmpty( not_null parent, - const QString &animation, + SearchEmptyIcon icon, rpl::producer text) { - auto content = object_ptr(parent); + auto content = object_ptr( + parent, + icon, + std::move(text) | Ui::Text::ToWithEntities()); + const auto raw = content.data(); - - const auto label = Ui::CreateChild( - raw, - std::move(text), - st::defaultPeerListAbout); - const auto size = st::recentPeersEmptySize; - const auto [widget, animate] = Settings::CreateLottieIcon( - raw, - { - .name = animation, - .sizeOverride = { size, size }, - }, - st::recentPeersEmptyMargin); - const auto icon = widget.data(); - rpl::combine( _chatsScroll->heightValue(), _topPeersWrap->heightValue() ) | rpl::start_with_next([=](int height, int top) { - raw->resize( - raw->width(), - std::max(height - top, st::recentPeersEmptyHeightMin)); - }, raw->lifetime()); - - raw->sizeValue() | rpl::start_with_next([=](QSize size) { - const auto x = (size.width() - icon->width()) / 2; - const auto y = (size.height() - icon->height()) / 3; - icon->move(x, y); - label->move( - (size.width() - label->width()) / 2, - y + icon->height() + st::recentPeersEmptySkip); + raw->setMinimalHeight(height - top); }, raw->lifetime()); auto result = object_ptr>( @@ -1417,7 +1395,7 @@ object_ptr> Suggestions::setupEmpty( result->toggledValue() | rpl::filter([=](bool shown) { return shown && _controller->session().data().chatsListLoaded(); }) | rpl::start_with_next([=] { - animate(anim::repeat::once); + raw->animate(); }, raw->lifetime()); return result; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 947bf2fa9..27b9b527f 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -34,6 +34,8 @@ class SessionController; namespace Dialogs { +enum class SearchEmptyIcon; + struct RecentPeersList { std::vector> list; }; @@ -112,7 +114,7 @@ private: -> object_ptr>; [[nodiscard]] object_ptr> setupEmpty( not_null parent, - const QString &animation, + SearchEmptyIcon icon, rpl::producer text); void switchTab(Tab tab); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 533fe85ef..adf5b8f11 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -85,6 +85,8 @@ PRIVATE data/data_subscription_option.h dialogs/dialogs_three_state_icon.h + dialogs/ui/chat_search_empty.cpp + dialogs/ui/chat_search_empty.h dialogs/ui/chat_search_tabs.cpp dialogs/ui/chat_search_tabs.h dialogs/ui/dialogs_stories_list.cpp From 1865fd382c4e38c95cdfa3c07307c208f7f5757b Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 21 May 2024 13:58:37 +0400 Subject: [PATCH 086/225] Show loading placeholders in chats list. --- .../dialogs/dialogs_inner_widget.cpp | 45 +++++++++++++------ .../dialogs/dialogs_inner_widget.h | 5 +-- .../SourceFiles/dialogs/dialogs_widget.cpp | 4 +- .../ui/effects/loading_element.cpp | 31 +++++++++++++ .../SourceFiles/ui/effects/loading_element.h | 6 +++ 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 6767c52f9..bae1e94e7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -64,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" +#include "ui/effects/loading_element.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/empty_userpic.h" @@ -936,7 +937,17 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } const auto showUnreadInSearchResults = uniqueSearchResults(); - if (!_searchResults.empty()) { + if (_searchResults.empty()) { + if (_loadingAnimation) { + const auto text = tr::lng_contacts_loading(tr::now); + p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg); + p.setFont(st::searchedBarFont); + p.setPen(st::searchedBarFg); + p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text); + p.translate(0, st::searchedBarHeight); + top += st::searchedBarHeight; + } + } else { const auto text = showUnreadInSearchResults ? u"Search results"_q : tr::lng_search_found_results( @@ -1442,7 +1453,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { updateSelectedRow(); } } - if (!_waitingForSearch && !_searchResults.empty()) { + if (!_searchResults.empty()) { auto skip = searchedOffset(); auto searchedSelected = (mouseY >= skip) ? ((mouseY - skip) / _st->height) : -1; if (searchedSelected < 0 || searchedSelected >= _searchResults.size()) { @@ -2602,7 +2613,6 @@ void InnerWidget::applySearchState(SearchState state) { clearFilter(); } else { setState(WidgetState::Filtered); - _waitingForSearch = true; _filterResults.clear(); _filterResultsGlobal.clear(); const auto append = [&](not_null list) { @@ -2986,13 +2996,6 @@ void InnerWidget::searchReceived( } else { _searchedCount = fullCount; } - if (_waitingForSearch - && (!_searchResults.empty() - || !_searchInMigrated - || type == SearchRequestType::MigratedFromStart - || type == SearchRequestType::MigratedFromOffset)) { - _waitingForSearch = false; - } refresh(); } @@ -3090,8 +3093,8 @@ void InnerWidget::refresh(bool toTop) { h = searchedOffset() + st::recentPeersEmptyHeightMin; _searchEmpty->setMinimalHeight(st::recentPeersEmptyHeightMin); _searchEmpty->move(0, h - st::recentPeersEmptyHeightMin); - } else if (_waitingForSearch) { - h = searchedOffset() + (_searchResults.size() * _st->height) + ((_searchResults.empty() && !_searchState.inChat) ? -st::searchedBarHeight : 0); + } else if (_loadingAnimation) { + h = searchedOffset() + _loadingAnimation->height(); } else { h = searchedOffset() + (_searchResults.size() * _st->height); } @@ -3127,8 +3130,21 @@ void InnerWidget::refreshEmpty() { } else if (_searchEmpty) { _searchEmpty->show(); } + + if (!_searchLoading || !empty) { + _loadingAnimation.destroy(); + } else if (!_loadingAnimation) { + _loadingAnimation = Ui::CreateLoadingDialogRowWidget( + this, + *_st, + 2); + _loadingAnimation->resizeToWidth(width()); + _loadingAnimation->move(0, searchedOffset()); + _loadingAnimation->show(); + } } else { _searchEmpty.destroy(); + _loadingAnimation.destroy(); } const auto data = &session().data(); @@ -3207,6 +3223,10 @@ void InnerWidget::resizeEmpty() { _searchEmpty->resizeToWidth(width()); _searchEmpty->move(0, searchedOffset()); } + if (_loadingAnimation) { + _loadingAnimation->resizeToWidth(width()); + _loadingAnimation->move(0, searchedOffset()); + } } void InnerWidget::clearMouseSelection(bool clearSelection) { @@ -3269,7 +3289,6 @@ void InnerWidget::clearFilter() { if (_state == WidgetState::Filtered || _searchState.inChat) { if (_searchState.inChat) { setState(WidgetState::Filtered); - _waitingForSearch = true; } else { setState(WidgetState::Default); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index a7e3ae619..b41a4aeb3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -141,9 +141,6 @@ public: [[nodiscard]] not_null st() const { return _st; } - [[nodiscard]] bool waitingForSearch() const { - return _waitingForSearch; - } [[nodiscard]] bool hasFilteredResults() const; void applySearchState(SearchState state); @@ -467,7 +464,6 @@ private: int _filteredSelected = -1; int _filteredPressed = -1; - bool _waitingForSearch = false; EmptyState _emptyState = EmptyState::None; QString _peerSearchQuery; @@ -485,6 +481,7 @@ private: WidgetState _state = WidgetState::Default; + object_ptr _loadingAnimation = { nullptr }; object_ptr _searchEmpty = { nullptr }; SearchState _searchEmptyState; object_ptr _empty = { nullptr }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 67155eb3d..fa1c71a5f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -448,7 +448,7 @@ Widget::Widget( _inner->setLoadMoreCallback([=] { const auto state = _inner->state(); if (state == WidgetState::Filtered - && (!_inner->waitingForSearch() + && (!_searchFull || (_searchInMigrated && _searchFull && !_searchFullMigrated))) { @@ -1936,7 +1936,7 @@ void Widget::submit() { const auto state = _inner->state(); if (state == WidgetState::Default || (state == WidgetState::Filtered - && (!_inner->waitingForSearch() || _inner->hasFilteredResults()))) { + && _inner->hasFilteredResults())) { _inner->selectSkip(1); _inner->chooseRow(); } else { diff --git a/Telegram/SourceFiles/ui/effects/loading_element.cpp b/Telegram/SourceFiles/ui/effects/loading_element.cpp index 5e30157c2..5cc2e5e90 100644 --- a/Telegram/SourceFiles/ui/effects/loading_element.cpp +++ b/Telegram/SourceFiles/ui/effects/loading_element.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rect.h" #include "ui/rp_widget.h" #include "styles/style_basic.h" +#include "styles/style_dialogs.h" #include "styles/style_widgets.h" namespace Ui { @@ -63,10 +64,28 @@ void LoadingText::paint(QPainter &p, int width) { h / 2); } +[[nodiscard]] const style::PeerListItem &PeerListItemFromDialogRow( + rpl::lifetime &lifetime, + const style::DialogRow &st) { + using namespace style; + const auto item = lifetime.make_state(PeerListItem{ + .height = st.height, + .photoPosition = QPoint(st.padding.left(), st.padding.top()), + .namePosition = QPoint(st.nameLeft, st.nameTop), + .nameStyle = st::semiboldTextStyle, + .statusPosition = QPoint(st.textLeft, st.textTop), + .photoSize = st.photoSize, + }); + return *item; +} + class LoadingPeerListItem final : public LoadingElement { public: LoadingPeerListItem(const style::PeerListItem &st) : _st(st) { } + LoadingPeerListItem(const style::DialogRow &st) + : _st(PeerListItemFromDialogRow(_lifetime, st)) { + } [[nodiscard]] int height() const override { return _st.height; @@ -114,6 +133,7 @@ public: } private: + rpl::lifetime _lifetime; const style::PeerListItem &_st; }; @@ -220,4 +240,15 @@ object_ptr CreateLoadingPeerListItemWidget( st); } +object_ptr CreateLoadingDialogRowWidget( + not_null parent, + const style::DialogRow &st, + int lines) { + return CreateLoadingElementWidget( + parent, + lines, + rpl::single(false), + st); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/loading_element.h b/Telegram/SourceFiles/ui/effects/loading_element.h index ab3b0d17e..757250f0a 100644 --- a/Telegram/SourceFiles/ui/effects/loading_element.h +++ b/Telegram/SourceFiles/ui/effects/loading_element.h @@ -13,6 +13,7 @@ class object_ptr; namespace style { struct FlatLabel; struct PeerListItem; +struct DialogRow; } // namespace style namespace Ui { @@ -30,4 +31,9 @@ object_ptr CreateLoadingPeerListItemWidget( const style::PeerListItem &st, int lines); +object_ptr CreateLoadingDialogRowWidget( + not_null parent, + const style::DialogRow &st, + int lines); + } // namespace Ui From 1ce49df123a904a2a745047e7b440d0e07ec0030 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 21 May 2024 14:36:24 +0400 Subject: [PATCH 087/225] Fix saved / replies userpics in chats search. --- .../data/stickers/data_custom_emoji.cpp | 26 ++++++++++--- .../data/stickers/data_custom_emoji.h | 3 +- Telegram/SourceFiles/dialogs/dialogs.style | 2 +- .../dialogs/dialogs_inner_widget.cpp | 1 + .../SourceFiles/dialogs/dialogs_widget.cpp | 8 +++- .../SourceFiles/ui/dynamic_thumbnails.cpp | 39 +++++++++++++++++++ Telegram/SourceFiles/ui/dynamic_thumbnails.h | 1 + 7 files changed, 70 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index c7eea5d48..0e47aa390 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -598,13 +598,21 @@ std::unique_ptr CustomEmojiManager::userpic( if (v.size() != 5 && v.size() != 1) { return nullptr; } - const auto id = PeerId(v[0].toULongLong()); + auto image = std::shared_ptr(); + if (v[0] == u"self"_q) { + image = Ui::MakeSavedMessagesThumbnail(); + } else if (v[0] == u"replies"_q) { + image = Ui::MakeRepliesThumbnail(); + } else { + const auto id = PeerId(v[0].toULongLong()); + image = Ui::MakeUserpicThumbnail(_owner->peer(id)); + } 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(image), std::move(update), padding, size); @@ -992,10 +1000,16 @@ QString CustomEmojiManager::registerInternalEmoji( [[nodiscard]] QString CustomEmojiManager::peerUserpicEmojiData( not_null peer, - QMargins padding) { - return UserpicEmojiPrefix() - + QString::number(peer->id.value) - + InternalPadding(padding); + QMargins padding, + bool respectSavedRepliesEtc) { + const auto id = !respectSavedRepliesEtc + ? QString::number(peer->id.value) + : peer->isSelf() + ? u"self"_q + : peer->isRepliesChat() + ? u"replies"_q + : QString::number(peer->id.value); + return UserpicEmojiPrefix() + id + InternalPadding(padding); } int FrameSizeFromTag(SizeTag tag) { diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 1cbbe0de9..d7a6c46ca 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -94,7 +94,8 @@ public: [[nodiscard]] QString peerUserpicEmojiData( not_null peer, - QMargins padding = {}); + QMargins padding = {}, + bool respectSavedRepliesEtc = false); [[nodiscard]] uint64 coloredSetId() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index caa3526e2..4a910953e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -522,7 +522,7 @@ forumTopicRow: DialogRow(defaultDialogRow) { photoSize: 20px; nameLeft: 39px; nameTop: 7px; - textLeft: 68px; + textLeft: 39px; textTop: 29px; unreadMarkDiameter: 8px; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index bae1e94e7..9e79f3ac2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -3145,6 +3145,7 @@ void InnerWidget::refreshEmpty() { } else { _searchEmpty.destroy(); _loadingAnimation.destroy(); + _searchEmptyState = {}; } const auto data = &session().data(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index fa1c71a5f..b3b5a31f9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1279,11 +1279,15 @@ void Widget::updateSearchTabs() { const auto peerShortLabel = peer ? Ui::Text::SingleCustomEmoji( session().data().customEmojiManager().peerUserpicEmojiData( - peer)) + peer, + {}, + true)) : sublist ? Ui::Text::SingleCustomEmoji( session().data().customEmojiManager().peerUserpicEmojiData( - sublist->peer())) + sublist->peer(), + {}, + true)) : TextWithEntities(); const auto myShortLabel = DefaultShortLabel(ChatSearchTab::MyMessages); const auto publicShortLabel = _searchingHashtag diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp index fb977e6d7..b9ea1ad0e 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -130,6 +130,17 @@ private: }; +class RepliesUserpic final : public DynamicImage { +public: + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + QImage _frame; + int _paletteVersion = 0; + +}; + PeerUserpic::PeerUserpic(not_null peer, bool forceRound) : _peer(peer) , _forceRound(forceRound) { @@ -358,6 +369,30 @@ QImage SavedMessagesUserpic::image(int size) { void SavedMessagesUserpic::subscribeToUpdates(Fn callback) { } +QImage RepliesUserpic::image(int size) { + const auto good = (_frame.width() == size * _frame.devicePixelRatio()); + const auto paletteVersion = style::PaletteVersion(); + if (!good || _paletteVersion != paletteVersion) { + _paletteVersion = paletteVersion; + + const auto ratio = style::DevicePixelRatio(); + if (!good) { + _frame = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(ratio); + } + _frame.fill(Qt::transparent); + + auto p = Painter(&_frame); + Ui::EmptyUserpic::PaintRepliesMessages(p, 0, 0, size, size); + } + return _frame; +} + +void RepliesUserpic::subscribeToUpdates(Fn callback) { +} + } // namespace std::shared_ptr MakeUserpicThumbnail( @@ -370,6 +405,10 @@ std::shared_ptr MakeSavedMessagesThumbnail() { return std::make_shared(); } +std::shared_ptr MakeRepliesThumbnail() { + return std::make_shared(); +} + std::shared_ptr MakeStoryThumbnail( not_null story) { using Result = std::shared_ptr; diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.h b/Telegram/SourceFiles/ui/dynamic_thumbnails.h index ad8556d5d..4b4e0e553 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.h +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.h @@ -21,6 +21,7 @@ class DynamicImage; not_null peer, bool forceRound = false); [[nodiscard]] std::shared_ptr MakeSavedMessagesThumbnail(); +[[nodiscard]] std::shared_ptr MakeRepliesThumbnail(); [[nodiscard]] std::shared_ptr MakeStoryThumbnail( not_null story); From 39e03c3ca7265f722cf972add9249e4b8ca2385f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 16 May 2024 14:15:24 +0400 Subject: [PATCH 088/225] Update API scheme to layer181. --- Telegram/SourceFiles/api/api_updates.cpp | 6 ++-- .../data/business/data_shortcut_messages.cpp | 3 +- .../data/components/scheduled_messages.cpp | 6 ++-- Telegram/SourceFiles/data/data_session.cpp | 3 +- .../admin_log/history_admin_log_item.cpp | 3 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 30 +++++++++++++++++-- .../SourceFiles/payments/payments_form.cpp | 5 +++- .../settings/settings_privacy_controllers.cpp | 3 +- 8 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 6613a1df3..56f8a8da4 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1138,7 +1138,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTPVector(), MTP_int(d.vttl_period().value_or_empty()), MTPint(), // quick_reply_shortcut_id - MTPlong()), // effect + MTPlong(), // effect + MTPFactCheck()), MessageFlags(), NewMessageType::Unread); } break; @@ -1174,7 +1175,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTPVector(), MTP_int(d.vttl_period().value_or_empty()), MTPint(), // quick_reply_shortcut_id - MTPlong()), // effect + MTPlong(), // effect + MTPFactCheck()), MessageFlags(), NewMessageType::Unread); } break; diff --git a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp index 57f8753a2..39b211545 100644 --- a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp +++ b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp @@ -88,7 +88,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); MTPVector(), MTP_int(data.vttl_period().value_or_empty()), MTP_int(shortcutId), - MTP_long(data.veffect().value_or_empty())); + MTP_long(data.veffect().value_or_empty()), + (data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck())); }); } diff --git a/Telegram/SourceFiles/data/components/scheduled_messages.cpp b/Telegram/SourceFiles/data/components/scheduled_messages.cpp index c8505c47f..9e69dd35b 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/components/scheduled_messages.cpp @@ -92,7 +92,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); MTPVector(), MTP_int(data.vttl_period().value_or_empty()), MTPint(), // quick_reply_shortcut_id - MTP_long(data.veffect().value_or_empty())); // effect + MTP_long(data.veffect().value_or_empty()), // effect + data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()); }); } @@ -264,7 +265,8 @@ void ScheduledMessages::sendNowSimpleMessage( MTPVector(), MTP_int(update.vttl_period().value_or_empty()), MTPint(), // quick_reply_shortcut_id - MTP_long(local->effectId())), // effect + MTP_long(local->effectId()), // effect + MTPFactCheck()), localFlags, NewMessageType::Unread); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index f46fb1dc0..d101d5d88 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4572,7 +4572,8 @@ void Session::insertCheckedServiceNotification( MTPVector(), MTPint(), // ttl_period MTPint(), // quick_reply_shortcut_id - MTPlong()), // effect + MTPlong(), // effect + MTPFactCheck()), localFlags, NewMessageType::Unread); } 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 7270696cd..5e46d8d97 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -140,7 +140,8 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { MTPVector(), MTPint(), // ttl_period MTPint(), // quick_reply_shortcut_id - MTP_long(data.veffect().value_or_empty())); + MTP_long(data.veffect().value_or_empty()), + data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()); }); } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index c0d2472f9..330e20ea7 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -38,7 +38,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro inputMediaPhotoExternal#e5bbfe1a flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; -inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia; +inputMediaInvoice#405fef0d flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:flags.3?string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia; inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; @@ -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#bde09c2e 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 flags2:# offline:flags2.1?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 via_business_bot_id:flags2.0?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 quick_reply_shortcut_id:flags.30?int effect:flags2.2?long = Message; +message#94345242 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 flags2:# offline:flags2.1?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 via_business_bot_id:flags2.0?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 quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck = 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; @@ -414,6 +414,7 @@ updateBotEditBusinessMessage#7df587c flags:# connection_id:string message:Messag updateBotDeleteBusinessMessage#a02a982e connection_id:string peer:Peer messages:Vector qts:int = Update; updateNewStoryReaction#1824e40b story_id:int peer:Peer reaction:Reaction = Update; updateBroadcastRevenueTransactions#dfd961f5 peer:Peer balances:BroadcastRevenueBalances = Update; +updateStarsBalance#fb85198 balance:long = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -901,6 +902,7 @@ inputWebFileAudioAlbumThumbLocation#f46fe924 flags:# small:flags.2?true document upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; payments.paymentForm#a0058751 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON additional_methods:flags.6?Vector saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?Vector users:Vector = payments.PaymentForm; +payments.paymentFormStars#7bf6b15c flags:# form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; @@ -1447,6 +1449,7 @@ attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; inputInvoiceSlug#c326caef slug:string = InputInvoice; inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice; +inputInvoiceStars#1da33ad8 option:StarsTopupOption = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1458,6 +1461,7 @@ inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true upgra inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentPremiumGiftCode#a3805f3f flags:# users:Vector boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentPremiumGiveaway#160544ca flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector prize_description:flags.4?string random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentStars#4f0ee8df flags:# stars:long currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1787,6 +1791,19 @@ availableEffect#93c3e27e flags:# premium_required:flags.2?true id:long emoticon: messages.availableEffectsNotModified#d1ed9a5b = messages.AvailableEffects; messages.availableEffects#bddb616e hash:int effects:Vector documents:Vector = messages.AvailableEffects; +factCheck#b89bfccf flags:# need_check:flags.0?true country:flags.1?string text:flags.1?TextWithEntities hash:long = FactCheck; + +starsTransactionPeerAppStore#b457b375 = StarsTransactionPeer; +starsTransactionPeerPlayMarket#7b560a0b = StarsTransactionPeer; +starsTransactionPeerFragment#e92fd902 = StarsTransactionPeer; +starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; + +starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; + +starsTransaction#5f6b790c id:string stars:long date:int peer:StarsTransactionPeer = StarsTransaction; + +payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2179,6 +2196,9 @@ messages.toggleDialogFilterTags#fd2dda49 enabled:Bool = Bool; messages.getMyStickers#d0b5e1fc offset_id:long limit:int = messages.MyStickers; messages.getEmojiStickerGroups#1dd840f5 hash:int = messages.EmojiGroups; messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects; +messages.editFactCheck#589ee75 peer:InputPeer msg_id:int text:TextWithEntities = Updates; +messages.deleteFactCheck#d1da940c peer:InputPeer msg_id:int = Updates; +messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector = Vector; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; @@ -2324,6 +2344,10 @@ payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode; payments.applyGiftCode#f6e26854 slug:string = Updates; payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo; payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; +payments.getStarsTopupOptions#c00ec7d3 = Vector; +payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; +payments.getStarsTransactions#673ac2f9 flags:# inbound:flags.0?true outbound:flags.1?true peer:InputPeer offset:string = payments.StarsStatus; +payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payments.PaymentResult; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -2442,4 +2466,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 180 +// LAYER 181 diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index d39b17737..59c8e5d65 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -359,8 +359,11 @@ void Form::requestForm() { MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)) )).done([=](const MTPpayments_PaymentForm &result) { hideProgress(); - result.match([&](const auto &data) { + result.match([&](const MTPDpayments_paymentForm &data) { processForm(data); + }, [&](const MTPDpayments_paymentFormStars &data) { + // #TODO stars + _updates.fire(Error{ Error::Type::Form }); }); }).fail([=](const MTP::Error &error) { hideProgress(); diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 2bbe649f0..6feb9c32a 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -199,7 +199,8 @@ AdminLog::OwnedItem GenerateForwardedItem( MTPVector(), MTPint(), // ttl_period MTPint(), // quick_reply_shortcut_id - MTPlong() // effect + MTPlong(), // effect + MTPFactCheck() ).match([&](const MTPDmessage &data) { return history->makeMessage( history->nextNonHistoryEntryId(), From ac2f35f12bb6b49e1ca8bf8b24c678b13ee38163 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 17 May 2024 14:35:19 +0300 Subject: [PATCH 089/225] Processed payments form with API scheme on layer 181. --- .../SourceFiles/payments/payments_form.cpp | 121 ++++++++++-------- Telegram/SourceFiles/payments/payments_form.h | 4 +- 2 files changed, 72 insertions(+), 53 deletions(-) diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 59c8e5d65..16469cb1e 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -359,12 +359,7 @@ void Form::requestForm() { MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)) )).done([=](const MTPpayments_PaymentForm &result) { hideProgress(); - result.match([&](const MTPDpayments_paymentForm &data) { - processForm(data); - }, [&](const MTPDpayments_paymentFormStars &data) { - // #TODO stars - _updates.fire(Error{ Error::Type::Form }); - }); + processForm(result); }).fail([=](const MTP::Error &error) { hideProgress(); _updates.fire(Error{ Error::Type::Form, error.type() }); @@ -390,33 +385,44 @@ void Form::requestReceipt() { }).send(); } -void Form::processForm(const MTPDpayments_paymentForm &data) { - _session->data().processUsers(data.vusers()); +void Form::processForm(const MTPpayments_PaymentForm &result) { + using TLForm = MTPDpayments_paymentForm; + using TLCredits = MTPDpayments_paymentFormStars; + result.match([&](const auto &data) { + _session->data().processUsers(data.vusers()); - data.vinvoice().match([&](const auto &data) { - processInvoice(data); - }); - processDetails(data); - if (const auto info = data.vsaved_info()) { - info->match([&](const auto &data) { - processSavedInformation(data); + data.vinvoice().match([&](const auto &data) { + processInvoice(data); }); - } - _paymentMethod.savedCredentials.clear(); - _paymentMethod.savedCredentialsIndex = 0; - if (const auto credentials = data.vsaved_credentials()) { - _paymentMethod.savedCredentials.reserve(credentials->v.size()); - for (const auto &saved : credentials->v) { - _paymentMethod.savedCredentials.push_back({ - .id = qs(saved.data().vid()), - .title = qs(saved.data().vtitle()), + }); + + processDetails(result); + result.match([&](const TLForm &data) { + if (const auto info = data.vsaved_info()) { + info->match([&](const auto &data) { + processSavedInformation(data); }); } - refreshPaymentMethodDetails(); - } - if (const auto additional = data.vadditional_methods()) { - processAdditionalPaymentMethods(additional->v); - } + }, [](const TLCredits &) { + }); + _paymentMethod.savedCredentials.clear(); + _paymentMethod.savedCredentialsIndex = 0; + result.match([&](const TLForm &data) { + if (const auto credentials = data.vsaved_credentials()) { + _paymentMethod.savedCredentials.reserve(credentials->v.size()); + for (const auto &saved : credentials->v) { + _paymentMethod.savedCredentials.push_back({ + .id = qs(saved.data().vid()), + .title = qs(saved.data().vtitle()), + }); + } + refreshPaymentMethodDetails(); + } + if (const auto additional = data.vadditional_methods()) { + processAdditionalPaymentMethods(additional->v); + } + }, [](const TLCredits &) { + }); fillPaymentMethodInformation(); _updates.fire(FormReady{}); } @@ -479,31 +485,44 @@ void Form::processInvoice(const MTPDinvoice &data) { }; } -void Form::processDetails(const MTPDpayments_paymentForm &data) { - const auto nativeParams = data.vnative_params(); - auto nativeParamsJson = nativeParams - ? nativeParams->match( - [&](const MTPDdataJSON &data) { return data.vdata().v; }) - : QByteArray(); - _details = FormDetails{ - .formId = data.vform_id().v, - .url = qs(data.vurl()), - .nativeProvider = qs(data.vnative_provider().value_or_empty()), - .nativeParamsJson = std::move(nativeParamsJson), - .botId = data.vbot_id().v, - .providerId = data.vprovider_id().v, - .canSaveCredentials = data.is_can_save_credentials(), - .passwordMissing = data.is_password_missing(), - }; - _invoice.cover.title = qs(data.vtitle()); +void Form::processDetails(const MTPpayments_PaymentForm &result) { + using TLForm = MTPDpayments_paymentForm; + using TLCredits = MTPDpayments_paymentFormStars; + _details = result.match([&](const TLForm &data) { + const auto nativeParams = data.vnative_params(); + auto nativeParamsJson = nativeParams + ? nativeParams->match( + [&](const MTPDdataJSON &data) { return data.vdata().v; }) + : QByteArray(); + return FormDetails{ + .formId = data.vform_id().v, + .url = qs(data.vurl()), + .nativeProvider = qs(data.vnative_provider().value_or_empty()), + .nativeParamsJson = std::move(nativeParamsJson), + .botId = data.vbot_id().v, + .providerId = data.vprovider_id().v, + .canSaveCredentials = data.is_can_save_credentials(), + .passwordMissing = data.is_password_missing(), + }; + }, [](const TLCredits &data) { + return FormDetails{ + .formId = data.vform_id().v, + .botId = data.vbot_id().v, + }; + }); + _invoice.cover.title = result.match([](const auto &data) { + return qs(data.vtitle()); + }); _invoice.cover.description = TextUtilities::ParseEntities( - qs(data.vdescription()), + result.match([](const auto &d) { return qs(d.vdescription()); }), TextParseLinks | TextParseMultiline); if (_invoice.cover.thumbnail.isNull() && !_thumbnailLoadProcess) { - if (const auto photo = data.vphoto()) { - loadThumbnail( - _session->data().photoFromWeb(*photo, ImageLocation())); - } + result.match([&](const auto &data) { + if (const auto photo = data.vphoto()) { + loadThumbnail( + _session->data().photoFromWeb(*photo, ImageLocation())); + } + }); } if (const auto botId = _details.botId) { if (const auto bot = _session->data().userLoaded(botId)) { diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 414eb0b02..68cb75ceb 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -285,10 +285,10 @@ private: void requestForm(); void requestReceipt(); - void processForm(const MTPDpayments_paymentForm &data); + void processForm(const MTPpayments_PaymentForm &result); void processReceipt(const MTPDpayments_paymentReceipt &data); void processInvoice(const MTPDinvoice &data); - void processDetails(const MTPDpayments_paymentForm &data); + void processDetails(const MTPpayments_PaymentForm &result); void processDetails(const MTPDpayments_paymentReceipt &data); void processSavedInformation(const MTPDpaymentRequestedInfo &data); void processAdditionalPaymentMethods( From bc7aa91fbb9aea8aaa7a235dd28eb2a9d5acd3f5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 16 May 2024 16:49:59 +0300 Subject: [PATCH 090/225] Removed redundant constructor from Ui::Premium::TopBar. --- .../SourceFiles/boxes/gift_premium_box.cpp | 38 ++++++++++--------- .../boosts/create_giveaway_box.cpp | 16 ++++---- .../SourceFiles/settings/settings_premium.cpp | 8 ++-- .../ui/effects/premium_top_bar.cpp | 17 --------- .../SourceFiles/ui/effects/premium_top_bar.h | 8 ---- 5 files changed, 35 insertions(+), 52 deletions(-) diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 10ad9409d..339e7ee30 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -1147,16 +1147,18 @@ void GiftCodeBox( object_ptr( box, st::giveawayGiftCodeCover, - nullptr, - rpl::conditional( - state->used.value(), - tr::lng_gift_link_used_title(), - tr::lng_gift_link_title()), - rpl::conditional( - state->used.value(), - tr::lng_gift_link_used_about(Ui::Text::RichLangValue), - tr::lng_gift_link_about(Ui::Text::RichLangValue)), - true)); + Ui::Premium::TopBarDescriptor{ + .clickContextOther = nullptr, + .title = rpl::conditional( + state->used.value(), + tr::lng_gift_link_used_title(), + tr::lng_gift_link_title()), + .about = rpl::conditional( + state->used.value(), + tr::lng_gift_link_used_about(Ui::Text::RichLangValue), + tr::lng_gift_link_about(Ui::Text::RichLangValue)), + .light = true, + })); const auto max = st::giveawayGiftCodeTopHeight; bar->setMaximumHeight(max); @@ -1283,13 +1285,15 @@ void GiftCodePendingBox( object_ptr( box, st, - clickContext, - tr::lng_gift_link_title(), - tr::lng_gift_link_pending_about( - lt_user, - rpl::single(Ui::Text::Link(resultToName)), - Ui::Text::RichLangValue), - true)); + Ui::Premium::TopBarDescriptor{ + .clickContextOther = clickContext, + .title = tr::lng_gift_link_title(), + .about = tr::lng_gift_link_pending_about( + lt_user, + rpl::single(Ui::Text::Link(resultToName)), + Ui::Text::RichLangValue), + .light = true, + })); const auto max = st::giveawayGiftCodeTopHeight; bar->setMaximumHeight(max); diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp index 4734718b9..629d6949b 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp @@ -174,13 +174,15 @@ void AddPremiumTopBarWithDefaultTitleBar( const auto bar = Ui::CreateChild( box.get(), st::startGiveawayCover, - nullptr, - tr::lng_giveaway_new_title(), - (group - ? tr::lng_giveaway_new_about_group - : tr::lng_giveaway_new_about)(Ui::Text::RichLangValue), - true, - false); + Ui::Premium::TopBarDescriptor{ + .clickContextOther = nullptr, + .title = tr::lng_giveaway_new_title(), + .about = (group + ? tr::lng_giveaway_new_about_group + : tr::lng_giveaway_new_about)(Ui::Text::RichLangValue), + .light = true, + .optimizeMinistars = false, + }); bar->setAttribute(Qt::WA_TransparentForMouseEvents); box->addRow( diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 057f36d2d..9a003d3d2 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -1064,9 +1064,11 @@ QPointer Premium::createPinnedToTop( return Ui::CreateChild( parent.get(), st::defaultPremiumCover, - clickContextOther, - std::move(title), - std::move(about)); + Ui::Premium::TopBarDescriptor{ + .clickContextOther = clickContextOther, + .title = std::move(title), + .about = std::move(about), + }); }(); _setPaused = [=](bool paused) { content->setPaused(paused); diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp index 5ef11ba50..c5f12ad6f 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp @@ -164,23 +164,6 @@ void TopBarAbstract::computeIsDark() { _isDark = (contrast > kMinAcceptableContrast); } -TopBar::TopBar( - not_null parent, - const style::PremiumCover &st, - Fn clickContextOther, - rpl::producer title, - rpl::producer about, - bool light, - bool optimizeMinistars) -: TopBar(parent, st, { - .clickContextOther = std::move(clickContextOther), - .title = std::move(title), - .about = std::move(about), - .light = light, - .optimizeMinistars = optimizeMinistars, -}) { -} - TopBar::TopBar( not_null parent, const style::PremiumCover &st, diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.h b/Telegram/SourceFiles/ui/effects/premium_top_bar.h index 4ac3cc8be..13e8a38c7 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.h +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.h @@ -75,14 +75,6 @@ struct TopBarDescriptor { class TopBar final : public TopBarAbstract { public: - TopBar( - not_null parent, - const style::PremiumCover &st, - Fn clickContextOther, - rpl::producer title, - rpl::producer about, - bool light = false, - bool optimizeMinistars = true); TopBar( not_null parent, const style::PremiumCover &st, From 2a224c839ef6629b251035a88e0759386cd33278 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 16 May 2024 16:31:25 +0300 Subject: [PATCH 091/225] Added initial phrases for settings section for credits. --- Telegram/Resources/langs/lang.strings | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 47ad8c12d..d138d5f91 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2298,6 +2298,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_business_about_sponsored_link" = "Telegram Ad Platform {emoji}"; "lng_business_about_sponsored_url" = "https://ads.telegram.org"; +"lng_credits_summary_title" = "Telegram Stars"; +"lng_credits_summary_about" = "Buy Stars to unlock content and services in miniapps on Telegram."; +"lng_credits_summary_options_subtitle" = "Choose package"; +"lng_credits_summary_options_credits#one" = "{count} Star"; +"lng_credits_summary_options_credits#other" = "{count} Stars"; +"lng_credits_summary_options_more" = "More Options"; +"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}."; +"lng_credits_summary_options_about_link" = "Terms and Conditions"; +"lng_credits_summary_history_tab_full" = "All Transactions"; +"lng_credits_summary_history_tab_in" = "Incoming"; +"lng_credits_summary_history_tab_out" = "Outgoing"; +"lng_credits_summary_history_entry_inner_in" = "In-App Purchase"; +"lng_credits_summary_balance" = "Balance"; +"lng_credits_box_out_title" = "Confirm Your Purchase"; +"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?"; +"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?"; +"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star"; +"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars"; +"lng_credits_summary_in_toast_title" = "Stars Acquired"; +"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance."; +"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance."; + "lng_location_title" = "Location"; "lng_location_about" = "Display the location of your business on your account."; "lng_location_address" = "Enter Address"; From 53d97b4146dc0031cd2f67eabc28feb561ce01e0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 16 May 2024 21:28:13 +0300 Subject: [PATCH 092/225] Added ability to provide custom gradient to colored premium stars. --- .../ui/effects/premium_stars_colored.cpp | 13 ++++++++----- .../SourceFiles/ui/effects/premium_stars_colored.h | 4 ++-- Telegram/SourceFiles/ui/effects/premium_top_bar.cpp | 6 ++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp b/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp index 18c9c9c24..d5987a5e7 100644 --- a/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp @@ -35,11 +35,14 @@ void ColoredMiniStars::setSize(const QSize &size) { _mask.fill(Qt::transparent); { auto p = QPainter(&_mask); - if (_colorOverride) { - p.fillRect(0, 0, size.width(), size.height(), *_colorOverride); + if (_stopsOverride && _stopsOverride->size() == 1) { + const auto &color = _stopsOverride->front().second; + p.fillRect(0, 0, size.width(), size.height(), color); } else { auto gradient = QLinearGradient(0, 0, size.width(), 0); - gradient.setStops(Ui::Premium::GiftGradientStops()); + gradient.setStops((_stopsOverride && _stopsOverride->size() > 1) + ? (*_stopsOverride) + : Ui::Premium::GiftGradientStops()); p.setPen(Qt::NoPen); p.setBrush(gradient); p.drawRect(0, 0, size.width(), size.height()); @@ -63,8 +66,8 @@ void ColoredMiniStars::setPosition(QPoint position) { _position = std::move(position); } -void ColoredMiniStars::setColorOverride(std::optional color) { - _colorOverride = color; +void ColoredMiniStars::setColorOverride(std::optional stops) { + _stopsOverride = stops; } void ColoredMiniStars::paint(QPainter &p) { diff --git a/Telegram/SourceFiles/ui/effects/premium_stars_colored.h b/Telegram/SourceFiles/ui/effects/premium_stars_colored.h index 75660ab99..73001ada0 100644 --- a/Telegram/SourceFiles/ui/effects/premium_stars_colored.h +++ b/Telegram/SourceFiles/ui/effects/premium_stars_colored.h @@ -21,7 +21,7 @@ public: void setSize(const QSize &size); void setPosition(QPoint position); - void setColorOverride(std::optional color); + void setColorOverride(std::optional stops); void setCenter(const QRect &rect); void paint(QPainter &p); @@ -34,7 +34,7 @@ private: QImage _mask; QSize _size; QPoint _position; - std::optional _colorOverride; + std::optional _stopsOverride; }; diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp index c5f12ad6f..aaba431ca 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp @@ -202,10 +202,12 @@ TopBar::TopBar( if (_logo == u"dollar"_q) { _dollar = ScaleTo(QImage(u":/gui/art/business_logo.png"_q)); - _ministars.setColorOverride(st::premiumButtonFg->c); + _ministars.setColorOverride( + QGradientStops{{ 0, st::premiumButtonFg->c }}); } else if (!_light && !TopBarAbstract::isDark()) { _star.load(Svg()); - _ministars.setColorOverride(st::premiumButtonFg->c); + _ministars.setColorOverride( + QGradientStops{{ 0, st::premiumButtonFg->c }}); } else { _star.load(ColorizedSvg()); _ministars.setColorOverride(std::nullopt); From 4a0bffe61832a4731454f9fdbf24e6550964f6d8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 16 May 2024 21:34:07 +0300 Subject: [PATCH 093/225] Added ability to provide custom gradient to star in premium top bar. --- .../settings/settings_privacy_security.cpp | 4 +++- Telegram/SourceFiles/ui/effects/premium_top_bar.cpp | 12 +++++++----- Telegram/SourceFiles/ui/effects/premium_top_bar.h | 3 ++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 66a26632b..ab8f32217 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/core_settings.h" #include "ui/chat/chat_style.h" +#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" @@ -79,7 +80,8 @@ using Privacy = Api::UserPrivacy; image.fill(Qt::transparent); { auto p = QPainter(&image); - auto star = QSvgRenderer(Ui::Premium::ColorizedSvg()); + auto star = QSvgRenderer( + Ui::Premium::ColorizedSvg(Ui::Premium::ButtonGradientStops())); star.render(&p, Rect(size)); } return image; diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp index aaba431ca..fd3d11950 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp @@ -46,15 +46,15 @@ QString Svg() { return u":/gui/icons/settings/star.svg"_q; } -QByteArray ColorizedSvg() { +QByteArray ColorizedSvg(const QGradientStops &gradientStops) { auto f = QFile(Svg()); if (!f.open(QIODevice::ReadOnly)) { return QByteArray(); } auto content = QString::fromUtf8(f.readAll()); - auto stops = [] { + auto stops = [&] { auto s = QString(); - for (const auto &stop : Ui::Premium::ButtonGradientStops()) { + for (const auto &stop : gradientStops) { s += QString("") .arg(QString::number(stop.first), stop.second.name()); } @@ -209,8 +209,10 @@ TopBar::TopBar( _ministars.setColorOverride( QGradientStops{{ 0, st::premiumButtonFg->c }}); } else { - _star.load(ColorizedSvg()); - _ministars.setColorOverride(std::nullopt); + _star.load(ColorizedSvg(descriptor.gradientStops + ? (*descriptor.gradientStops) + : Ui::Premium::ButtonGradientStops())); + _ministars.setColorOverride(descriptor.gradientStops); } auto event = QResizeEvent(size(), size()); resizeEvent(&event); diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.h b/Telegram/SourceFiles/ui/effects/premium_top_bar.h index 13e8a38c7..86c8158ff 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.h +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.h @@ -26,7 +26,7 @@ class FlatLabel; namespace Ui::Premium { [[nodiscard]] QString Svg(); -[[nodiscard]] QByteArray ColorizedSvg(); +[[nodiscard]] QByteArray ColorizedSvg(const QGradientStops &gradientStops); [[nodiscard]] QImage GenerateStarForLightTopBar(QRectF rect); class TopBarAbstract : public RpWidget { @@ -71,6 +71,7 @@ struct TopBarDescriptor { rpl::producer about; bool light = false; bool optimizeMinistars = true; + std::optional gradientStops; }; class TopBar final : public TopBarAbstract { From b5eb195f43b4b5c7812689d257fed3ce4c2904a7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 16 May 2024 21:44:42 +0300 Subject: [PATCH 094/225] Added initial dummy settings section for credits. --- .../SourceFiles/settings/settings_credits.cpp | 231 ++++++++++++++++++ .../SourceFiles/settings/settings_credits.h | 44 ++++ .../SourceFiles/settings/settings_main.cpp | 20 +- Telegram/SourceFiles/ui/effects/premium.style | 6 + .../ui/effects/premium_graphics.cpp | 7 + .../SourceFiles/ui/effects/premium_graphics.h | 1 + 6 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 Telegram/SourceFiles/settings/settings_credits.cpp create mode 100644 Telegram/SourceFiles/settings/settings_credits.h diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp new file mode 100644 index 000000000..4656170f6 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -0,0 +1,231 @@ +/* +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 "settings/settings_credits.h" + +#include "api/api_credits.h" +#include "core/click_handler_types.h" +#include "data/data_user.h" +#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_common_session.h" +#include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_top_bar.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" +#include "styles/style_info.h" +#include "styles/style_premium.h" +#include "styles/style_settings.h" + +namespace Settings { +namespace { + +using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; + +class Credits : public Section { +public: + Credits( + QWidget *parent, + not_null controller); + + [[nodiscard]] rpl::producer title() override; + + [[nodiscard]] QPointer createPinnedToTop( + not_null parent) override; + + void showFinished() override; + + [[nodiscard]] bool hasFlexibleTopBar() const override; + + void setStepDataReference(std::any &data) override; + + [[nodiscard]] rpl::producer<> sectionShowBack() override final; + +private: + void setupContent(); + void setupOptions(not_null container); + + const not_null _controller; + + base::unique_qptr> _back; + base::unique_qptr _close; + rpl::variable _backToggles; + rpl::variable _wrap; + Fn _setPaused; + + rpl::event_stream<> _showBack; + rpl::event_stream<> _showFinished; + rpl::variable _buttonText; + +}; + +Credits::Credits( + QWidget *parent, + not_null controller) +: Section(parent) +, _controller(controller) { + setupContent(); +} + +rpl::producer Credits::title() { + return tr::lng_premium_summary_title(); +} + +bool Credits::hasFlexibleTopBar() const { + return true; +} + +rpl::producer<> Credits::sectionShowBack() { + return _showBack.events(); +} + +void Credits::setStepDataReference(std::any &data) { + const auto my = std::any_cast(&data); + if (my) { + _backToggles = std::move( + my->backButtonEnables + ) | rpl::map_to(true); + _wrap = std::move(my->wrapValue); + } +} + +void Credits::setupOptions(not_null container) { +} + +void Credits::setupContent() { + const auto content = Ui::CreateChild(this); + + Ui::ResizeFitChild(this, content); +} + +QPointer Credits::createPinnedToTop( + not_null parent) { + + const auto content = [&]() -> Ui::Premium::TopBarAbstract* { + const auto weak = base::make_weak(_controller); + const auto clickContextOther = [=] { + return QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = weak, + .botStartAutoSubmit = true, + }); + }; + return Ui::CreateChild( + parent.get(), + st::creditsPremiumCover, + Ui::Premium::TopBarDescriptor{ + .clickContextOther = clickContextOther, + .title = tr::lng_credits_summary_title(), + .about = tr::lng_credits_summary_about( + TextWithEntities::Simple), + .light = true, + .gradientStops = Ui::Premium::CreditsIconGradientStops(), + }); + }(); + _setPaused = [=](bool paused) { + content->setPaused(paused); + }; + + _wrap.value( + ) | rpl::start_with_next([=](Info::Wrap wrap) { + content->setRoundEdges(wrap == Info::Wrap::Layer); + }, content->lifetime()); + + content->setMaximumHeight(st::settingsPremiumTopHeight); + content->setMinimumHeight(st::infoLayerTopBarHeight); + + content->resize(content->width(), content->maximumHeight()); + content->additionalHeight( + ) | rpl::start_with_next([=](int additionalHeight) { + const auto wasMax = (content->height() == content->maximumHeight()); + content->setMaximumHeight(st::settingsPremiumTopHeight + + additionalHeight); + if (wasMax) { + content->resize(content->width(), content->maximumHeight()); + } + }, content->lifetime()); + + _wrap.value( + ) | rpl::start_with_next([=](Info::Wrap wrap) { + const auto isLayer = (wrap == Info::Wrap::Layer); + _back = base::make_unique_q>( + content, + object_ptr( + content, + (isLayer ? st::infoTopBarBack : st::infoLayerTopBarBack)), + st::infoTopBarScale); + _back->setDuration(0); + _back->toggleOn(isLayer + ? _backToggles.value() | rpl::type_erased() + : rpl::single(true)); + _back->entity()->addClickHandler([=] { + _showBack.fire({}); + }); + _back->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + const auto &st = isLayer ? st::infoLayerTopBar : st::infoTopBar; + content->setTextPosition( + toggled ? st.back.width : st.titlePosition.x(), + st.titlePosition.y()); + }, _back->lifetime()); + + if (!isLayer) { + _close = nullptr; + } else { + _close = base::make_unique_q( + content, + st::infoTopBarClose); + _close->addClickHandler([=] { + _controller->parentController()->hideLayer(); + _controller->parentController()->hideSpecialLayer(); + }); + content->widthValue( + ) | rpl::start_with_next([=] { + _close->moveToRight(0, 0); + }, _close->lifetime()); + } + }, content->lifetime()); + + return Ui::MakeWeak(not_null{ content }); +} + +void Credits::showFinished() { + _showFinished.fire({}); +} + +} // namespace + +template <> +struct SectionFactory : AbstractSectionFactory { + object_ptr create( + not_null parent, + not_null controller, + not_null scroll, + rpl::producer containerValue + ) const final override { + return object_ptr(parent, controller); + } + bool hasCustomTopBar() const final override { + return true; + } + + [[nodiscard]] static const std::shared_ptr &Instance() { + static const auto result = std::make_shared(); + return result; + } +}; + +Type CreditsId() { + return Credits::Id(); +} + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits.h b/Telegram/SourceFiles/settings/settings_credits.h new file mode 100644 index 000000000..7484fa637 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_credits.h @@ -0,0 +1,44 @@ +/* +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 "settings/settings_type.h" + +enum class PremiumFeature; + +namespace style { +struct RoundButton; +} // namespace style + +namespace ChatHelpers { +class Show; +enum class WindowUsage; +} // namespace ChatHelpers + +namespace Ui { +class RpWidget; +class RoundButton; +class GradientButton; +class VerticalLayout; +} // namespace Ui + +namespace Main { +class Session; +class SessionShow; +} // namespace Main + +namespace Window { +class SessionController; +} // namespace Window + +namespace Settings { + +[[nodiscard]] Type CreditsId(); + +} // namespace Settings + diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 551d4f33c..19a63e84f 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -9,17 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/click_handler_types.h" +#include "settings/settings_advanced.h" #include "settings/settings_business.h" -#include "settings/settings_codes.h" +#include "settings/settings_calls.h" #include "settings/settings_chat.h" +#include "settings/settings_codes.h" +#include "settings/settings_credits.h" +#include "settings/settings_folders.h" #include "settings/settings_information.h" #include "settings/settings_notifications.h" -#include "settings/settings_privacy_security.h" -#include "settings/settings_advanced.h" -#include "settings/settings_folders.h" -#include "settings/settings_calls.h" #include "settings/settings_power_saving.h" #include "settings/settings_premium.h" +#include "settings/settings_privacy_security.h" #include "settings/settings_scale_preview.h" #include "boxes/language_box.h" #include "boxes/username_box.h" @@ -414,6 +415,15 @@ void SetupPremium( controller->setPremiumRef("settings"); showOther(PremiumId()); }); + AddButtonWithIcon( + container, + tr::lng_credits_summary_title(), + st::settingsButton, + { .icon = &st::menuIconPremium } + )->addClickHandler([=] { + controller->setPremiumRef("settings"); + showOther(CreditsId()); + }); const auto button = AddButtonWithIcon( container, tr::lng_business_title(), diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index afdac7146..0fffd9c5e 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -364,3 +364,9 @@ boostFeatureLink: icon{{ "settings/premium/features/feature_links", windowBgActi boostFeatureName: icon{{ "settings/premium/features/feature_color_names", windowBgActive }}; boostFeatureStories: icon{{ "settings/premium/features/feature_stories", windowBgActive }}; boostFeatureTranscribe: icon{{ "settings/premium/features/feature_voice", windowBgActive }}; + +creditsPremiumCover: PremiumCover(defaultPremiumCover) { + about: FlatLabel(userPremiumCoverAbout) { + textFg: boxTitleFg; + } +} diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index b701f8500..d04dae62b 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -1177,6 +1177,13 @@ QGradientStops StoriesIconsGradientStops() { }; } +QGradientStops CreditsIconGradientStops() { + return { + { 0., st::creditsBg1->c }, + { 1., st::creditsBg2->c }, + }; +} + void ShowListBox( not_null box, const style::PremiumLimits &st, diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.h b/Telegram/SourceFiles/ui/effects/premium_graphics.h index 728dd9d40..339d49a3d 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.h +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.h @@ -124,6 +124,7 @@ void AddAccountsRow( [[nodiscard]] QGradientStops LockGradientStops(); [[nodiscard]] QGradientStops FullHeightGradientStops(); [[nodiscard]] QGradientStops GiftGradientStops(); +[[nodiscard]] QGradientStops CreditsIconGradientStops(); struct ListEntry final { rpl::producer title; From f1636de572e23b45768f46ee3aa7645f30ece5c8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 16 May 2024 19:22:55 +0300 Subject: [PATCH 095/225] Added initial api support for credits topup options. --- Telegram/CMakeLists.txt | 4 ++ Telegram/SourceFiles/api/api_credits.cpp | 52 ++++++++++++++++++++++++ Telegram/SourceFiles/api/api_credits.h | 35 ++++++++++++++++ Telegram/SourceFiles/data/data_credits.h | 21 ++++++++++ Telegram/cmake/td_ui.cmake | 1 + 5 files changed, 113 insertions(+) create mode 100644 Telegram/SourceFiles/api/api_credits.cpp create mode 100644 Telegram/SourceFiles/api/api_credits.h create mode 100644 Telegram/SourceFiles/data/data_credits.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 21e22150d..b1b39f59f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -120,6 +120,8 @@ PRIVATE api/api_common.h api/api_confirm_phone.cpp api/api_confirm_phone.h + api/api_credits.cpp + api/api_credits.h api/api_earn.cpp api/api_earn.h api/api_editing.cpp @@ -1360,6 +1362,8 @@ PRIVATE settings/settings_codes.h settings/settings_common_session.cpp settings/settings_common_session.h + settings/settings_credits.cpp + settings/settings_credits.h settings/settings_experimental.cpp settings/settings_experimental.h settings/settings_folders.cpp diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp new file mode 100644 index 000000000..96b03a8e8 --- /dev/null +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -0,0 +1,52 @@ +/* +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 "api/api_credits.h" + +#include "apiwrap.h" +#include "data/data_peer.h" +#include "main/main_session.h" + +namespace Api { + +CreditsTopupOptions::CreditsTopupOptions(not_null peer) +: _peer(peer) +, _api(&peer->session().api().instance()) { +} + +rpl::producer CreditsTopupOptions::request() { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + using TLOption = MTPStarsTopupOption; + _api.request(MTPpayments_GetStarsTopupOptions( + )).done([=](const MTPVector &result) { + _options = ranges::views::all( + result.v + ) | ranges::views::transform([](const TLOption &option) { + return Data::CreditTopupOption{ + .credits = option.data().vstars().v, + .product = qs( + option.data().vstore_product().value_or_empty()), + .currency = qs(option.data().vcurrency()), + .amount = option.data().vamount().v, + }; + }) | ranges::to_vector; + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + + return lifetime; + }; +} + +Data::CreditTopupOptions CreditsTopupOptions::options() const { + return _options; +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h new file mode 100644 index 000000000..61a568b62 --- /dev/null +++ b/Telegram/SourceFiles/api/api_credits.h @@ -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 +*/ +#pragma once + +#include "data/data_credits.h" +#include "mtproto/sender.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +class CreditsTopupOptions final { +public: + CreditsTopupOptions(not_null peer); + + [[nodiscard]] rpl::producer request(); + [[nodiscard]] Data::CreditTopupOptions options() const; + +private: + const not_null _peer; + + Data::CreditTopupOptions _options; + + MTP::Sender _api; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h new file mode 100644 index 000000000..db2ffec15 --- /dev/null +++ b/Telegram/SourceFiles/data/data_credits.h @@ -0,0 +1,21 @@ +/* +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 + +namespace Data { + +struct CreditTopupOption final { + uint64 credits = 0; + QString product; + QString currency; + uint64 amount = 0; +}; + +using CreditTopupOptions = std::vector; + +} // namespace Data diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index adf5b8f11..987cca721 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -80,6 +80,7 @@ PRIVATE data/data_birthday.cpp data/data_birthday.h data/data_channel_earn.h + data/data_credits.h data/data_statistics_chart.cpp data/data_statistics_chart.h data/data_subscription_option.h From d0bfee69635e68ec9333e4f86b0c58c61dd47eb4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 17 May 2024 00:04:44 +0300 Subject: [PATCH 096/225] Added initial list for topup options in settings section for credits. --- .../SourceFiles/settings/settings_credits.cpp | 113 +++++++++++++++++- Telegram/SourceFiles/ui/effects/credits.style | 24 ++++ Telegram/SourceFiles/ui/effects/premium.style | 6 - Telegram/cmake/td_ui.cmake | 1 + 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 Telegram/SourceFiles/ui/effects/credits.style diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 4656170f6..10ac6e011 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -16,22 +16,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common_session.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" +#include "ui/image/image_prepare.h" #include "ui/painter.h" #include "ui/rect.h" +#include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" +#include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" +#include "styles/style_credits.h" #include "styles/style_info.h" -#include "styles/style_premium.h" +#include "styles/style_layers.h" #include "styles/style_settings.h" +#include + namespace Settings { namespace { using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; +[[nodiscard]] QImage GenerateStarForLightTopBar(QRectF rect) { + const auto strokeWidth = 3; + + auto colorized = qs(Ui::Premium::ColorizedSvg( + Ui::Premium::CreditsIconGradientStops())); + colorized.replace( + "stroke=\"none\"", + "stroke=\"" + st::creditsStroke->c.name() + "\""); + colorized.replace("stroke-width=\"1\"", "stroke-width=\"3\""); + auto svg = QSvgRenderer(colorized.toUtf8()); + svg.setViewBox(svg.viewBox() + Margins(strokeWidth)); + + const auto size = Size(st::settingsButton.height); + auto frame = QImage( + size * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + frame.setDevicePixelRatio(style::DevicePixelRatio()); + + frame.fill(Qt::transparent); + { + auto q = QPainter(&frame); + svg.render(&q, Rect(size)); + } + return frame; +} + class Credits : public Section { public: Credits( @@ -57,6 +91,8 @@ private: const not_null _controller; + QImage _star; + base::unique_qptr> _back; base::unique_qptr _close; rpl::variable _backToggles; @@ -73,7 +109,8 @@ Credits::Credits( QWidget *parent, not_null controller) : Section(parent) -, _controller(controller) { +, _controller(controller) +, _star(GenerateStarForLightTopBar({})) { setupContent(); } @@ -100,10 +137,82 @@ void Credits::setStepDataReference(std::any &data) { } void Credits::setupOptions(not_null container) { + const auto options = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto content = options->entity(); + + Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); + + const auto fill = [=](Data::CreditTopupOptions options) { + while (content->count()) { + delete content->widgetAt(0); + } + Ui::AddSubsectionTitle( + content, + tr::lng_credits_summary_options_subtitle()); + for (const auto &option : options) { + const auto button = content->add(object_ptr( + content, + tr::lng_credits_summary_options_credits( + lt_count_decimal, + rpl::single(option.credits) | tr::to_count()), + st::creditsTopupButton)); + const auto icon = Ui::CreateChild(button); + icon->resize(Size(button->st().height)); + icon->paintRequest( + ) | rpl::start_with_next([=](const QRect &rect) { + auto p = QPainter(icon); + p.drawImage(0, 0, _star); + }, icon->lifetime()); + const auto price = Ui::CreateChild( + button, + Ui::FillAmountAndCurrency(option.amount, option.currency), + st::creditsTopupPrice); + button->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + const auto &st = button->st(); + price->moveToRight(st.padding.right(), st.padding.top()); + icon->moveToLeft(st.iconLeft, st.padding.top()); + }, button->lifetime()); + button->setClickedCallback([=] { + }); + Ui::ToggleChildrenVisibility(button, true); + } + + // Footer. + { + auto text = tr::lng_credits_summary_options_about( + lt_link, + tr::lng_credits_summary_options_about_link( + ) | rpl::map([](const QString &t) { + using namespace Ui::Text; + return Link(t, u"https://telegram.org/tos"_q); + }), + Ui::Text::RichLangValue); + Ui::AddSkip(content); + Ui::AddDividerText(content, std::move(text)); + } + + content->resizeToWidth(container->width()); + }; + + using ApiOptions = Api::CreditsTopupOptions; + const auto apiCredits = content->lifetime().make_state( + _controller->session().user()); + + apiCredits->request( + ) | rpl::start_with_error_done([=](const QString &error) { + _controller->showToast(error); + }, [=] { + fill(apiCredits->options()); + }, content->lifetime()); } void Credits::setupContent() { const auto content = Ui::CreateChild(this); + setupOptions(content); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style new file mode 100644 index 000000000..4c27ea541 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -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 +*/ +using "ui/basic.style"; +using "boxes/boxes.style"; +using "ui/widgets/widgets.style"; +using "ui/effects/premium.style"; +using "settings/settings.style"; + +creditsPremiumCover: PremiumCover(defaultPremiumCover) { + about: FlatLabel(userPremiumCoverAbout) { + textFg: boxTitleFg; + } +} +creditsTopupButton: SettingsButton(settingsButton) { + style: semiboldTextStyle; +} +creditsTopupPrice: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 0fffd9c5e..afdac7146 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -364,9 +364,3 @@ boostFeatureLink: icon{{ "settings/premium/features/feature_links", windowBgActi boostFeatureName: icon{{ "settings/premium/features/feature_color_names", windowBgActive }}; boostFeatureStories: icon{{ "settings/premium/features/feature_stories", windowBgActive }}; boostFeatureTranscribe: icon{{ "settings/premium/features/feature_voice", windowBgActive }}; - -creditsPremiumCover: PremiumCover(defaultPremiumCover) { - about: FlatLabel(userPremiumCoverAbout) { - textFg: boxTitleFg; - } -} diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 987cca721..8c8cddac4 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -16,6 +16,7 @@ set(style_files ui/filter_icons.style ui/menu_icons.style ui/chat/chat.style + ui/effects/credits.style ui/effects/premium.style boxes/boxes.style dialogs/dialogs.style From 9b11b95c5b64d88e97f8a53b32c0b4b326b8afee Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 17 May 2024 03:37:56 +0300 Subject: [PATCH 097/225] Added api support of invoice payments for credits. --- .../payments/payments_checkout_process.cpp | 22 ++++++++++++ .../payments/payments_checkout_process.h | 4 +++ .../SourceFiles/payments/payments_form.cpp | 11 ++++++ Telegram/SourceFiles/payments/payments_form.h | 15 +++++++- .../SourceFiles/settings/settings_credits.cpp | 35 +++++++++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index e9fb12d26..108376084 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -139,6 +139,28 @@ void CheckoutProcess::Start( j->second->requestActivate(); } +void CheckoutProcess::Start( + InvoiceCredits creditsInvoice, + Fn reactivate) { + const auto randomId = creditsInvoice.randomId; + auto id = InvoiceId{ std::move(creditsInvoice) }; + auto &processes = LookupSessionProcesses(SessionFromId(id)); + const auto i = processes.byRandomId.find(randomId); + if (i != end(processes.byRandomId)) { + i->second->setReactivateCallback(std::move(reactivate)); + i->second->requestActivate(); + return; + } + const auto j = processes.byRandomId.emplace( + randomId, + std::make_unique( + std::move(id), + Mode::Payment, + std::move(reactivate), + PrivateTag{})).first; + j->second->requestActivate(); +} + std::optional CheckoutProcess::InvoicePaid( not_null item) { const auto session = &item->history()->session(); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index dd995324f..9a8703927 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -37,6 +37,7 @@ namespace Payments { class Form; struct FormUpdate; struct Error; +struct InvoiceCredits; struct InvoiceId; struct InvoicePremiumGiftCode; @@ -73,6 +74,9 @@ public: static void Start( InvoicePremiumGiftCode giftCodeInvoice, Fn reactivate); + static void Start( + InvoiceCredits creditsInvoice, + Fn reactivate); [[nodiscard]] static std::optional InvoicePaid( not_null item); [[nodiscard]] static std::optional InvoicePaid( diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 16469cb1e..c02ea408b 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -117,6 +117,8 @@ not_null SessionFromId(const InvoiceId &id) { return &message->peer->session(); } else if (const auto slug = std::get_if(&id.value)) { return slug->session; + } else if (const auto slug = std::get_if(&id.value)) { + return slug->session; } const auto &giftCode = v::get(id.value); const auto users = std::get_if( @@ -314,6 +316,15 @@ MTPInputInvoice Form::inputInvoice() const { MTP_int(message->itemId.bare)); } else if (const auto slug = std::get_if(&_id.value)) { return MTP_inputInvoiceSlug(MTP_string(slug->slug)); + } else if (const auto credits = std::get_if(&_id.value)) { + return MTP_inputInvoiceStars(MTP_starsTopupOption( + credits->product.isEmpty() + ? MTP_flags(0) + : MTP_flags(MTPDstarsTopupOption::Flag::f_store_product), + MTP_long(credits->credits), + MTP_string(credits->product), + MTP_string(credits->currency), + MTP_long(credits->amount))); } const auto &giftCode = v::get(_id.value); using Flag = MTPDpremiumGiftCodeOption::Flag; diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 68cb75ceb..92448feb6 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -216,8 +216,21 @@ struct InvoicePremiumGiftCode { int months = 0; }; +struct InvoiceCredits { + not_null session; + uint64 randomId = 0; + uint64 credits = 0; + QString product; + QString currency; + uint64 amount = 0; +}; + struct InvoiceId { - std::variant value; + std::variant< + InvoiceMessage, + InvoiceSlug, + InvoicePremiumGiftCode, + InvoiceCredits> value; }; [[nodiscard]] not_null SessionFromId(const InvoiceId &id); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 10ac6e011..6509fe0a6 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -13,7 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. #include "lang/lang_keys.h" #include "main/main_session.h" +#include "payments/payments_checkout_process.h" +#include "payments/payments_form.h" #include "settings/settings_common_session.h" +#include "ui/boxes/boost_box.h" // Ui::StartFireworks. #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" #include "ui/image/image_prepare.h" @@ -33,6 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_settings.h" +#include // XXH64. + #include namespace Settings { @@ -40,6 +45,16 @@ namespace { using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; +[[nodiscard]] uint64 UniqueIdFromOption( + const Data::CreditTopupOption &d) { + const auto string = QString::number(d.credits) + + d.product + + d.currency + + QString::number(d.amount); + + return XXH64(string.data(), string.size() * sizeof(ushort), 0); +} + [[nodiscard]] QImage GenerateStarForLightTopBar(QRectF rect) { const auto strokeWidth = 3; @@ -177,6 +192,26 @@ void Credits::setupOptions(not_null container) { icon->moveToLeft(st.iconLeft, st.padding.top()); }, button->lifetime()); button->setClickedCallback([=] { + const auto invoice = Payments::InvoiceCredits{ + .session = &_controller->session(), + .randomId = UniqueIdFromOption(option), + .credits = option.credits, + .product = option.product, + .currency = option.currency, + .amount = option.amount, + }; + + const auto weak = Ui::MakeWeak(button); + const auto done = [=](Payments::CheckoutResult result) { + if (const auto strong = weak.data()) { + strong->window()->setFocus(); + if (result == Payments::CheckoutResult::Paid) { + Ui::StartFireworks(this); + } + } + }; + + Payments::CheckoutProcess::Start(std::move(invoice), done); }); Ui::ToggleChildrenVisibility(button, true); } From 1a393ddebbf860c1adeaf0dbb80bbf9ca9f89dfc Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 17 May 2024 04:21:19 +0300 Subject: [PATCH 098/225] Added star icons with gradient to main settings for premium buttons. --- .../SourceFiles/settings/settings_main.cpp | 96 +++++++++++++++++-- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 19a63e84f..332677a93 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -28,12 +28,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/basic_click_handlers.h" #include "ui/boxes/confirm_box.h" #include "ui/controls/userpic_button.h" +#include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg. #include "ui/wrap/slide_wrap.h" #include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/widgets/continuous_sliders.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/new_badges.h" +#include "ui/rect.h" #include "ui/vertical_list.h" #include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_emoji_status_panel.h" @@ -254,6 +257,77 @@ void Cover::refreshUsernameGeometry(int newWidth) { _username->moveToLeft(usernameLeft, usernameTop, newWidth); } +[[nodiscard]] not_null AddPremiumStar( + not_null button, + bool credits) { + const auto stops = credits + ? Ui::Premium::CreditsIconGradientStops() + : Ui::Premium::ButtonGradientStops(); + + const auto ministarsContainer = Ui::CreateChild(button); + const auto &buttonSt = button->st(); + const auto fullHeight = buttonSt.height + + rect::m::sum::v(buttonSt.padding); + using MiniStars = Ui::Premium::ColoredMiniStars; + const auto ministars = button->lifetime().make_state( + ministarsContainer, + false); + ministars->setColorOverride(stops); + + ministarsContainer->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(ministarsContainer); + { + constexpr auto kScale = 0.35; + const auto r = ministarsContainer->rect(); + p.translate(r.center()); + p.scale(kScale, kScale); + p.translate(-r.center()); + } + ministars->paint(p); + }, ministarsContainer->lifetime()); + + const auto badge = Ui::CreateChild(button.get()); + + auto star = [&] { + const auto factor = style::DevicePixelRatio(); + const auto size = Size(st::settingsButtonNoIcon.style.font->ascent); + auto image = QImage( + size * factor, + QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(factor); + image.fill(Qt::transparent); + { + auto p = QPainter(&image); + auto star = QSvgRenderer(Ui::Premium::ColorizedSvg(stops)); + star.render(&p, Rect(size)); + } + return image; + }(); + badge->resize(star.size() / style::DevicePixelRatio()); + badge->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(badge); + p.drawImage(0, 0, star); + }, badge->lifetime()); + + button->sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + badge->moveToLeft( + button->st().iconLeft + + (st::menuIconShop.width() - badge->width()) / 2, + (s.height() - badge->height()) / 2); + ministarsContainer->moveToLeft( + badge->x() - (fullHeight - badge->height()) / 2, + 0); + }, badge->lifetime()); + + ministarsContainer->resize(fullHeight, fullHeight); + ministars->setCenter(ministarsContainer->rect()); + + return button; +} + } // namespace void SetupPowerSavingButton( @@ -406,20 +480,22 @@ void SetupPremium( Ui::AddDivider(container); Ui::AddSkip(container); - AddButtonWithIcon( - container, - tr::lng_premium_summary_title(), - st::settingsButton, - { .icon = &st::menuIconPremium } + AddPremiumStar( + AddButtonWithIcon( + container, + tr::lng_premium_summary_title(), + st::settingsButton), + false )->addClickHandler([=] { controller->setPremiumRef("settings"); showOther(PremiumId()); }); - AddButtonWithIcon( - container, - tr::lng_credits_summary_title(), - st::settingsButton, - { .icon = &st::menuIconPremium } + AddPremiumStar( + AddButtonWithIcon( + container, + tr::lng_credits_summary_title(), + st::settingsButton), + true )->addClickHandler([=] { controller->setPremiumRef("settings"); showOther(CreditsId()); From f0a82de7841a6b2b33b57c5828dc602045d670ae Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 18 May 2024 02:00:46 +0300 Subject: [PATCH 099/225] Fixed editing of last message with uploading media. --- Telegram/SourceFiles/history/view/history_view_list_widget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index a66ba415e..9c125252c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -4008,7 +4008,8 @@ void ListWidget::editMessageRequestNotify(FullMsgId item) const { bool ListWidget::lastMessageEditRequestNotify() const { const auto now = base::unixtime::now(); auto proj = [&](not_null view) { - return view->data()->allowsEdit(now); + return view->data()->allowsEdit(now) + && !view->data()->isUploading(); }; const auto &list = ranges::views::reverse(_items); const auto it = ranges::find_if(list, std::move(proj)); From 2bf8cb84d042714a5fdf70348f13ac1148675fc0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 20 May 2024 15:25:48 +0300 Subject: [PATCH 100/225] Added api support of credits status and credits history. --- Telegram/SourceFiles/api/api_credits.cpp | 96 ++++++++++++++++++++++++ Telegram/SourceFiles/api/api_credits.h | 36 +++++++++ Telegram/SourceFiles/data/data_credits.h | 22 ++++++ 3 files changed, 154 insertions(+) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 96b03a8e8..a6f3f3eca 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -8,10 +8,54 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "apiwrap.h" +#include "base/unixtime.h" #include "data/data_peer.h" +#include "data/data_session.h" #include "main/main_session.h" namespace Api { +namespace { + +[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL( + const MTPStarsTransaction &tl) { + using HistoryPeerTL = MTPDstarsTransactionPeer; + return Data::CreditsHistoryEntry{ + .id = qs(tl.data().vid()), + .credits = tl.data().vstars().v, + .date = base::unixtime::parse(tl.data().vdate().v), + .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { + return Data::CreditsHistoryEntry::PeerType::Peer; + }, [](const MTPDstarsTransactionPeerPlayMarket &) { + return Data::CreditsHistoryEntry::PeerType::PlayMarket; + }, [](const MTPDstarsTransactionPeerFragment &) { + return Data::CreditsHistoryEntry::PeerType::Fragment; + }, [](const MTPDstarsTransactionPeerAppStore &) { + return Data::CreditsHistoryEntry::PeerType::AppStore; + }), + .peerId = tl.data().vpeer().match([](const HistoryPeerTL &p) { + return peerFromMTP(p.vpeer()); + }, [](const auto &) { + return PeerId(0); + }), + }; +} + +[[nodiscard]] Data::CreditsStatusSlice StatusFromTL( + const MTPpayments_StarsStatus &status, + not_null peer) { + peer->owner().processUsers(status.data().vusers()); + peer->owner().processChats(status.data().vchats()); + return Data::CreditsStatusSlice{ + .list = ranges::views::all( + status.data().vhistory().v + ) | ranges::views::transform(HistoryFromTL) | ranges::to_vector, + .balance = status.data().vbalance().v, + .allLoaded = status.data().vnext_offset().has_value(), + .token = qs(status.data().vnext_offset().value_or_empty()), + }; +} + +} // namespace CreditsTopupOptions::CreditsTopupOptions(not_null peer) : _peer(peer) @@ -45,6 +89,58 @@ rpl::producer CreditsTopupOptions::request() { }; } +CreditsStatus::CreditsStatus(not_null peer) +: _peer(peer) +, _api(&peer->session().api().instance()) { +} + +void CreditsStatus::request( + const Data::CreditsStatusSlice::OffsetToken &token, + Fn done) { + if (_requestId) { + return; + } + + using TLResult = MTPpayments_StarsStatus; + + _requestId = _api.request(MTPpayments_GetStarsStatus( + _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input + )).done([=](const TLResult &result) { + _requestId = 0; + done(StatusFromTL(result, _peer)); + }).fail([=] { + _requestId = 0; + done({}); + }).send(); +} + +CreditsHistory::CreditsHistory(not_null peer, bool in, bool out) +: _peer(peer) +, _flags(HistoryTL::Flags(0) + | (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0)) + | (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0))) +, _api(&peer->session().api().instance()) { +} + +void CreditsHistory::request( + const Data::CreditsStatusSlice::OffsetToken &token, + Fn done) { + if (_requestId) { + return; + } + _requestId = _api.request(MTPpayments_GetStarsTransactions( + MTP_flags(_flags), + _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input, + MTP_string(token) + )).done([=](const MTPpayments_StarsStatus &result) { + _requestId = 0; + done(StatusFromTL(result, _peer)); + }).fail([=] { + _requestId = 0; + done({}); + }).send(); +} + Data::CreditTopupOptions CreditsTopupOptions::options() const { return _options; } diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index 61a568b62..5807f34c7 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -32,4 +32,40 @@ private: }; +class CreditsStatus final { +public: + CreditsStatus(not_null peer); + + void request( + const Data::CreditsStatusSlice::OffsetToken &token, + Fn done); + +private: + const not_null _peer; + + mtpRequestId _requestId = 0; + + MTP::Sender _api; + +}; + +class CreditsHistory final { +public: + CreditsHistory(not_null peer, bool in, bool out); + + void request( + const Data::CreditsStatusSlice::OffsetToken &token, + Fn done); + +private: + using HistoryTL = MTPpayments_GetStarsTransactions; + const not_null _peer; + const HistoryTL::Flags _flags; + + mtpRequestId _requestId = 0; + + MTP::Sender _api; + +}; + } // namespace Api diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index db2ffec15..86ccd022f 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -18,4 +18,26 @@ struct CreditTopupOption final { using CreditTopupOptions = std::vector; +struct CreditsHistoryEntry final { + enum class PeerType { + Peer, + AppStore, + PlayMarket, + Fragment, + }; + QString id; + uint64 credits = 0; + QDateTime date; + PeerType peerType; + PeerId peerId = PeerId(0); +}; + +struct CreditsStatusSlice final { + using OffsetToken = QString; + std::vector list; + uint64 balance = 0; + bool allLoaded = false; + OffsetToken token; +}; + } // namespace Data From 65384d54f1fa41e71dec8f278371a67179f095ff Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 20 May 2024 18:53:59 +0300 Subject: [PATCH 101/225] Added random debug data to credits history. --- Telegram/SourceFiles/api/api_credits.cpp | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index a6f3f3eca..8a609820e 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_session.h" #include "main/main_session.h" +#if _DEBUG +#include "base/random.h" +#endif namespace Api { namespace { @@ -107,6 +110,10 @@ void CreditsStatus::request( _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input )).done([=](const TLResult &result) { _requestId = 0; +#if _DEBUG + done({ .balance = uint64(base::RandomIndex(9999)) }); + return; +#endif done(StatusFromTL(result, _peer)); }).fail([=] { _requestId = 0; @@ -134,6 +141,46 @@ void CreditsHistory::request( MTP_string(token) )).done([=](const MTPpayments_StarsStatus &result) { _requestId = 0; +#if _DEBUG + done({ + .list = [&] { + auto a = std::vector(); + const auto isIn = _flags & HistoryTL::Flag::f_inbound; + const auto isOut = _flags & HistoryTL::Flag::f_outbound; + for (auto i = 0; i < base::RandomIndex(10) + 1; i++) { + const auto type = (isIn && isOut) + ? base::RandomIndex(4) + : isOut + ? 0 + : (base::RandomIndex(3) + 1); + a.push_back(Data::CreditsHistoryEntry{ + .id = QString::number(base::RandomValue()), + .credits = uint64( + std::max(base::RandomIndex(15000), 1)), + .date = base::unixtime::parse( + std::abs(base::RandomValue())), + .peerType = ((type == 0) + ? Data::CreditsHistoryEntry::PeerType::Peer + : (type == 1) + ? Data::CreditsHistoryEntry::PeerType::PlayMarket + : (type == 2) + ? Data::CreditsHistoryEntry::PeerType::Fragment + : Data::CreditsHistoryEntry::PeerType::AppStore), + .peerId = (type == 0) + ? peerFromUser(5000233800) + : PeerId(0), + }); + } + return a; + }(), + .balance = 47890, + .allLoaded = !token.isEmpty(), + .token = token.isEmpty() + ? QString::number(base::RandomValue()) + : QString(), + }); + return; +#endif done(StatusFromTL(result, _peer)); }).fail([=] { _requestId = 0; From 154fe63b4390dbb88b58c5e6ddecd48441bbe5d5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 20 May 2024 19:13:22 +0300 Subject: [PATCH 102/225] Implemented list of credit history entries. --- .../info_statistics_list_controllers.cpp | 306 ++++++++++++++++++ .../info_statistics_list_controllers.h | 11 + Telegram/SourceFiles/ui/effects/credits.style | 2 + 3 files changed, 319 insertions(+) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 1ed6d7884..6d6abdec4 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -7,9 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/statistics/info_statistics_list_controllers.h" +#include "api/api_credits.h" #include "api/api_statistics.h" #include "boxes/peer_list_controllers.h" #include "data/data_channel.h" +#include "data/data_credits.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_user.h" @@ -26,7 +28,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "styles/style_credits.h" #include "styles/style_dialogs.h" // dialogsStoriesFull. +#include "styles/style_intro.h" // introFragmentIcon. +#include "styles/style_layers.h" // boxRowPadding. #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "styles/style_statistics.h" @@ -113,6 +118,15 @@ struct BoostsDescriptor final { not_null peer; }; +struct CreditsDescriptor final { + Data::CreditsStatusSlice firstSlice; + Fn entryClickedCallback; + not_null peer; + not_null creditIcon; + bool in = false; + bool out = false; +}; + class PeerListRowWithFullId : public PeerListRow { public: PeerListRowWithFullId( @@ -692,6 +706,254 @@ rpl::producer BoostsController::totalBoostsValue() const { return _totalBoosts.value(); } +class CreditsRow final : public PeerListRow { +public: + struct Descriptor final { + Data::CreditsHistoryEntry entry; + not_null creditIcon; + int rowHeight = 0; + }; + + CreditsRow(not_null peer, const Descriptor &descriptor); + CreditsRow(const Descriptor &descriptor); + + [[nodiscard]] const Data::CreditsHistoryEntry &entry() const; + [[nodiscard]] QString generateName() override; + + [[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback( + bool forceRound) override; + + QSize rightActionSize() const override; + QMargins rightActionMargins() const override; + bool rightActionDisabled() const override; + void rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) override; + +private: + void init(); + + const Data::CreditsHistoryEntry _entry; + not_null const _creditIcon; + const int _rowHeight; + Ui::EmptyUserpic _userpic; + + Ui::Text::String _rightText; +}; + +CreditsRow::CreditsRow(not_null peer, const Descriptor &descriptor) +: PeerListRow(peer, UniqueRowIdFromString(descriptor.entry.id)) +, _entry(descriptor.entry) +, _creditIcon(descriptor.creditIcon) +, _rowHeight(descriptor.rowHeight) +, _userpic(Ui::EmptyUserpic::UserpicColor(0), QString()) { + init(); +} + +CreditsRow::CreditsRow(const Descriptor &descriptor) +: PeerListRow(UniqueRowIdFromString(descriptor.entry.id)) +, _entry(descriptor.entry) +, _creditIcon(descriptor.creditIcon) +, _rowHeight(descriptor.rowHeight) +, _userpic( + [&]() -> Ui::EmptyUserpic::BgColors { + switch (descriptor.entry.peerType) { + case Data::CreditsHistoryEntry::PeerType::Peer: + return Ui::EmptyUserpic::UserpicColor(0); + case Data::CreditsHistoryEntry::PeerType::AppStore: + return { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::PlayMarket: + return { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::Fragment: + return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; + } + Unexpected("Unknown peer type."); + }(), + QString()) { + init(); +} + +void CreditsRow::init() { + PeerListRow::setCustomStatus(langDateTimeFull(_entry.date)); + { + constexpr auto kMinus = QChar(0x2212); + _rightText.setText( + st::semiboldTextStyle, + (PeerListRow::special() ? QChar('+') : kMinus) + + Lang::FormatCountDecimal(_entry.credits)); + } +} + +const Data::CreditsHistoryEntry &CreditsRow::entry() const { + return _entry; +} + +QString CreditsRow::generateName() { + return !PeerListRow::special() + ? PeerListRow::generateName() + : (_entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) + ? tr::lng_bot_username_description1_link(tr::now) + : tr::lng_credits_summary_history_entry_inner_in(tr::now); +} + +PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { + if (!PeerListRow::special()) { + return PeerListRow::generatePaintUserpicCallback(force); + } + return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { + _userpic.paintCircle(p, x, y, outerWidth, size); + using PeerType = Data::CreditsHistoryEntry::PeerType; + ((_entry.peerType == PeerType::AppStore) + ? st::sessionIconiPhone + : (_entry.peerType == PeerType::PlayMarket) + ? st::sessionIconAndroid + : st::introFragmentIcon).paintInCenter(p, { x, y, size, size }); + }; +} + +QSize CreditsRow::rightActionSize() const { + return QSize( + _rightText.maxWidth() + + (_creditIcon->width() / style::DevicePixelRatio()) + + st::creditsHistoryRightSkip + + _rightText.style()->font->spacew * 2, + _rowHeight); +} + +QMargins CreditsRow::rightActionMargins() const { + return QMargins(0, 0, st::boxRowPadding.right(), 0); +} + +bool CreditsRow::rightActionDisabled() const { + return true; +} + +void CreditsRow::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + const auto &font = _rightText.style()->font; + y += _rowHeight / 2; + p.setPen(PeerListRow::special() + ? st::boxTextFgGood + : st::menuIconAttentionColor); + x += st::creditsHistoryRightSkip; + _rightText.draw(p, Ui::Text::PaintContext{ + .position = QPoint(x, y - font->height / 2), + .availableWidth = outerWidth, + .outerWidth = outerWidth, + }); + x += _rightText.maxWidth() + font->spacew * 2; + p.drawImage( + x, + y -(_creditIcon->height() / style::DevicePixelRatio()) / 2, + *_creditIcon); +} + +class CreditsController final : public PeerListController { +public: + explicit CreditsController(CreditsDescriptor d); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void loadMoreRows() override; + + [[nodiscard]] bool skipRequest() const; + void requestNext(); + + [[nodiscard]] rpl::producer allLoadedValue() const; + +private: + void applySlice(const Data::CreditsStatusSlice &slice); + + const not_null _session; + Fn _entryClickedCallback; + not_null const _creditIcon; + + Api::CreditsHistory _api; + Data::CreditsStatusSlice _firstSlice; + Data::CreditsStatusSlice::OffsetToken _apiToken; + + rpl::variable _allLoaded = false; + bool _requesting = false; + +}; + +CreditsController::CreditsController(CreditsDescriptor d) +: _session(&d.peer->session()) +, _entryClickedCallback(std::move(d.entryClickedCallback)) +, _creditIcon(d.creditIcon) +, _api(d.peer, d.in, d.out) +, _firstSlice(std::move(d.firstSlice)) { + PeerListController::setStyleOverrides(&st::boostsListBox); +} + +Main::Session &CreditsController::session() const { + return *_session; +} + +bool CreditsController::skipRequest() const { + return _requesting || _allLoaded.current(); +} + +void CreditsController::requestNext() { + _requesting = true; + _api.request(_apiToken, [=](const Data::CreditsStatusSlice &s) { + _requesting = false; + applySlice(s); + }); +} + +void CreditsController::prepare() { + applySlice(base::take(_firstSlice)); + delegate()->peerListRefreshRows(); +} + +void CreditsController::loadMoreRows() { +} + +void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { + _allLoaded = slice.allLoaded; + _apiToken = slice.token; + + for (const auto &item : slice.list) { + auto row = [&] { + const auto descriptor = CreditsRow::Descriptor{ + .entry = item, + .creditIcon = _creditIcon, + .rowHeight = computeListSt().item.height, + }; + if (item.peerId) { + const auto peer = session().data().peer(item.peerId); + return std::make_unique(peer, descriptor); + } else { + return std::make_unique(descriptor); + } + }(); + delegate()->peerListAppendRow(std::move(row)); + } + delegate()->peerListRefreshRows(); +} + +void CreditsController::rowClicked(not_null row) { + if (_entryClickedCallback) { + _entryClickedCallback( + static_cast(row.get())->entry()); + } +} + +rpl::producer CreditsController::allLoadedValue() const { + return _allLoaded.value(); +} + } // namespace void AddPublicForwards( @@ -843,4 +1105,48 @@ void AddBoostsList( button->setClickedCallback(showMore); } +void AddCreditsHistoryList( + const Data::CreditsStatusSlice &firstSlice, + not_null container, + Fn callback, + not_null self, + not_null icon, + bool in, + bool out) { + struct State final { + State(CreditsDescriptor d) : controller(std::move(d)) { + } + PeerListContentDelegateSimple delegate; + CreditsController controller; + }; + auto d = CreditsDescriptor{ firstSlice, callback, self, icon, in, out }; + const auto state = container->lifetime().make_state(std::move(d)); + + state->delegate.setContent(container->add( + object_ptr(container, &state->controller))); + state->controller.setDelegate(&state->delegate); + + const auto wrap = container->add( + object_ptr>( + container, + object_ptr( + container, + tr::lng_stories_show_more(), + st::statisticsShowMoreButton)), + { 0, -st::settingsButton.padding.top(), 0, 0 }); + const auto button = wrap->entity(); + AddArrow(button); + + const auto showMore = [=] { + if (!state->controller.skipRequest()) { + state->controller.requestNext(); + container->resizeToWidth(container->width()); + } + }; + wrap->toggleOn( + state->controller.allLoadedValue() | rpl::map(!rpl::mappers::_1), + anim::type::instant); + button->setClickedCallback(showMore); +} + } // namespace Info::Statistics diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h index 2a2dfe93a..0e3d00019 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h @@ -16,6 +16,8 @@ class VerticalLayout; namespace Data { struct Boost; struct BoostsListSlice; +struct CreditsHistoryEntry; +struct CreditsStatusSlice; struct PublicForwardsSlice; struct RecentPostId; struct SupergroupStatistics; @@ -44,4 +46,13 @@ void AddBoostsList( not_null peer, rpl::producer title); +void AddCreditsHistoryList( + const Data::CreditsStatusSlice &firstSlice, + not_null container, + Fn entryClickedCallback, + not_null self, + not_null creditIcon, + bool in, + bool out); + } // namespace Info::Statistics diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 4c27ea541..355d0fc5b 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -22,3 +22,5 @@ creditsTopupButton: SettingsButton(settingsButton) { creditsTopupPrice: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; } + +creditsHistoryRightSkip: 10px; From ca37ffa08691230c40a33e7fb7f83a46fb8d9fc3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 20 May 2024 19:13:31 +0300 Subject: [PATCH 103/225] Added list of credit history entries to credits settings. --- .../SourceFiles/settings/settings_credits.cpp | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 6509fe0a6..dc7afd1c1 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -11,11 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "data/data_user.h" #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. +#include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" #include "settings/settings_common_session.h" +#include "statistics/widgets/chart_header_widget.h" #include "ui/boxes/boost_box.h" // Ui::StartFireworks. #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" @@ -26,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" @@ -35,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_settings.h" +#include "styles/style_statistics.h" #include // XXH64. @@ -103,6 +107,7 @@ public: private: void setupContent(); void setupOptions(not_null container); + void setupHistory(not_null container); const not_null _controller; @@ -245,9 +250,169 @@ void Credits::setupOptions(not_null container) { }, content->lifetime()); } +void Credits::setupHistory(not_null container) { + const auto history = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto content = history->entity(); + + Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); + + const auto fill = [=]( + const Data::CreditsStatusSlice &fullSlice, + const Data::CreditsStatusSlice &inSlice, + const Data::CreditsStatusSlice &outSlice) { + const auto inner = content; + if (fullSlice.list.empty()) { + return; + } + const auto hasOneTab = inSlice.list.empty() && outSlice.list.empty(); + const auto hasIn = !inSlice.list.empty(); + const auto hasOut = !outSlice.list.empty(); + const auto fullTabText = tr::lng_credits_summary_history_tab_full( + tr::now); + const auto inTabText = tr::lng_credits_summary_history_tab_in( + tr::now); + const auto outTabText = tr::lng_credits_summary_history_tab_out( + tr::now); + if (hasOneTab) { + Ui::AddSkip(inner); + const auto header = inner->add( + object_ptr(inner), + st::statisticsLayerMargins + + st::boostsChartHeaderPadding); + header->resizeToWidth(header->width()); + header->setTitle(fullTabText); + header->setSubTitle({}); + } + + class Slider final : public Ui::SettingsSlider { + public: + using Ui::SettingsSlider::SettingsSlider; + void setNaturalWidth(int w) { + _naturalWidth = w; + } + int naturalWidth() const override { + return _naturalWidth; + } + + private: + int _naturalWidth = 0; + + }; + + const auto slider = inner->add( + object_ptr>( + inner, + object_ptr(inner, st::defaultTabsSlider)), + st::boxRowPadding); + slider->toggle(!hasOneTab, anim::type::instant); + + slider->entity()->addSection(fullTabText); + if (hasIn) { + slider->entity()->addSection(inTabText); + } + if (hasOut) { + slider->entity()->addSection(outTabText); + } + + { + const auto &st = st::defaultTabsSlider; + slider->entity()->setNaturalWidth(0 + + st.labelStyle.font->width(fullTabText) + + (hasIn ? st.labelStyle.font->width(inTabText) : 0) + + (hasOut ? st.labelStyle.font->width(outTabText) : 0) + + rect::m::sum::h(st::boxRowPadding)); + } + + const auto fullWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + const auto inWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + const auto outWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + + rpl::single(0) | rpl::then( + slider->entity()->sectionActivated() + ) | rpl::start_with_next([=](int index) { + if (index == 0) { + fullWrap->toggle(true, anim::type::instant); + inWrap->toggle(false, anim::type::instant); + outWrap->toggle(false, anim::type::instant); + } else if (index == 1) { + inWrap->toggle(true, anim::type::instant); + fullWrap->toggle(false, anim::type::instant); + outWrap->toggle(false, anim::type::instant); + } else { + outWrap->toggle(true, anim::type::instant); + fullWrap->toggle(false, anim::type::instant); + inWrap->toggle(false, anim::type::instant); + } + }, inner->lifetime()); + + const auto entryClicked = [=](const Data::CreditsHistoryEntry &e) { + }; + + Info::Statistics::AddCreditsHistoryList( + fullSlice, + fullWrap->entity(), + entryClicked, + _controller->session().user(), + &_star, + true, + true); + Info::Statistics::AddCreditsHistoryList( + inSlice, + inWrap->entity(), + entryClicked, + _controller->session().user(), + &_star, + true, + false); + Info::Statistics::AddCreditsHistoryList( + outSlice, + outWrap->entity(), + std::move(entryClicked), + _controller->session().user(), + &_star, + false, + true); + + Ui::AddSkip(inner); + Ui::AddSkip(inner); + + inner->resizeToWidth(container->width()); + }; + + const auto apiLifetime = content->lifetime().make_state(); + { + using Api = Api::CreditsHistory; + const auto self = _controller->session().user(); + const auto apiFull = apiLifetime->make_state(self, true, true); + const auto apiIn = apiLifetime->make_state(self, true, false); + const auto apiOut = apiLifetime->make_state(self, false, true); + apiFull->request({}, [=](Data::CreditsStatusSlice fullSlice) { + apiIn->request({}, [=](Data::CreditsStatusSlice inSlice) { + apiOut->request({}, [=](Data::CreditsStatusSlice outSlice) { + fill(fullSlice, inSlice, outSlice); + apiLifetime->destroy(); + }); + }); + }); + } +} + void Credits::setupContent() { const auto content = Ui::CreateChild(this); setupOptions(content); + setupHistory(content); Ui::ResizeFitChild(this, content); } From d81c3554cc080a9e63bf7cbbd5a462cf1a23a1b7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 21 May 2024 04:17:39 +0300 Subject: [PATCH 104/225] Added multiple icons to credits topup options. --- .../SourceFiles/settings/settings_credits.cpp | 97 ++++++++++++++----- 1 file changed, 74 insertions(+), 23 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index dc7afd1c1..2b6064abb 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -59,28 +59,55 @@ using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; return XXH64(string.data(), string.size() * sizeof(ushort), 0); } -[[nodiscard]] QImage GenerateStarForLightTopBar(QRectF rect) { - const auto strokeWidth = 3; +[[nodiscard]] QImage GenerateStars(int height, int count) { + constexpr auto kOutlineWidth = .6; + constexpr auto kStrokeWidth = 3; + constexpr auto kShift = 3; auto colorized = qs(Ui::Premium::ColorizedSvg( Ui::Premium::CreditsIconGradientStops())); colorized.replace( - "stroke=\"none\"", - "stroke=\"" + st::creditsStroke->c.name() + "\""); - colorized.replace("stroke-width=\"1\"", "stroke-width=\"3\""); + u"stroke=\"none\""_q, + u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name())); + colorized.replace( + u"stroke-width=\"1\""_q, + u"stroke-width=\"%1\""_q.arg(kStrokeWidth)); auto svg = QSvgRenderer(colorized.toUtf8()); - svg.setViewBox(svg.viewBox() + Margins(strokeWidth)); + svg.setViewBox(svg.viewBox() + Margins(kStrokeWidth)); + + const auto starSize = Size(height - kOutlineWidth * 2); - const auto size = Size(st::settingsButton.height); auto frame = QImage( - size * style::DevicePixelRatio(), + QSize( + (height + kShift * (count - 1)) * style::DevicePixelRatio(), + height * style::DevicePixelRatio()), QImage::Format_ARGB32_Premultiplied); frame.setDevicePixelRatio(style::DevicePixelRatio()); - frame.fill(Qt::transparent); + const auto drawSingle = [&](QPainter &q) { + const auto s = kOutlineWidth; + q.save(); + q.translate(s, s); + q.setCompositionMode(QPainter::CompositionMode_Clear); + svg.render(&q, QRectF(QPointF(s, 0), starSize)); + svg.render(&q, QRectF(QPointF(s, s), starSize)); + svg.render(&q, QRectF(QPointF(0, s), starSize)); + svg.render(&q, QRectF(QPointF(-s, s), starSize)); + svg.render(&q, QRectF(QPointF(-s, 0), starSize)); + svg.render(&q, QRectF(QPointF(-s, -s), starSize)); + svg.render(&q, QRectF(QPointF(0, -s), starSize)); + svg.render(&q, QRectF(QPointF(s, -s), starSize)); + q.setCompositionMode(QPainter::CompositionMode_SourceOver); + svg.render(&q, Rect(starSize)); + q.restore(); + }; { auto q = QPainter(&frame); - svg.render(&q, Rect(size)); + q.translate(frame.width() / style::DevicePixelRatio() - height, 0); + for (auto i = count; i > 0; --i) { + drawSingle(q); + q.translate(-kShift, 0); + } } return frame; } @@ -130,7 +157,7 @@ Credits::Credits( not_null controller) : Section(parent) , _controller(controller) -, _star(GenerateStarForLightTopBar({})) { +, _star(GenerateStars(st::creditsTopupButton.height, 1)) { setupContent(); } @@ -172,29 +199,53 @@ void Credits::setupOptions(not_null container) { Ui::AddSubsectionTitle( content, tr::lng_credits_summary_options_subtitle()); - for (const auto &option : options) { + const auto &st = st::creditsTopupButton; + const auto diffBetweenTextAndStar = st.padding.left() + - st.iconLeft + - (_star.width() / style::DevicePixelRatio()); + const auto buttonHeight = st.height + rect::m::sum::v(st.padding); + for (auto i = 0; i < options.size(); i++) { + const auto &option = options[i]; const auto button = content->add(object_ptr( content, + rpl::never(), + st)); + const auto text = button->lifetime().make_state( + st.style, tr::lng_credits_summary_options_credits( + tr::now, lt_count_decimal, - rpl::single(option.credits) | tr::to_count()), - st::creditsTopupButton)); - const auto icon = Ui::CreateChild(button); - icon->resize(Size(button->st().height)); - icon->paintRequest( - ) | rpl::start_with_next([=](const QRect &rect) { - auto p = QPainter(icon); - p.drawImage(0, 0, _star); - }, icon->lifetime()); + option.credits)); const auto price = Ui::CreateChild( button, Ui::FillAmountAndCurrency(option.amount, option.currency), st::creditsTopupPrice); + const auto inner = Ui::CreateChild(button); + const auto stars = GenerateStars(st.height, (i + 1)); + inner->paintRequest( + ) | rpl::start_with_next([=](const QRect &rect) { + auto p = QPainter(inner); + p.drawImage( + 0, + (buttonHeight - stars.height()) / 2, + stars); + const auto textLeft = diffBetweenTextAndStar + + stars.width() / style::DevicePixelRatio(); + p.setPen(st.textFg); + text->draw(p, { + .position = QPoint(textLeft, 0), + .availableWidth = inner->width() - textLeft, + }); + }, inner->lifetime()); button->sizeValue( ) | rpl::start_with_next([=](const QSize &size) { - const auto &st = button->st(); price->moveToRight(st.padding.right(), st.padding.top()); - icon->moveToLeft(st.iconLeft, st.padding.top()); + inner->moveToLeft(st.iconLeft, st.padding.top()); + inner->resize( + size.width() + - rect::m::sum::h(st.padding) + - price->width(), + buttonHeight); }, button->lifetime()); button->setClickedCallback([=] { const auto invoice = Payments::InvoiceCredits{ From 030d35ea7ea043216f82b7932a28745f2fd464df Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 21 May 2024 05:19:32 +0300 Subject: [PATCH 105/225] Added initial implementation of balance in credits settings. --- .../SourceFiles/settings/settings_credits.cpp | 69 ++++++++++++++++++- Telegram/SourceFiles/ui/effects/credits.style | 1 + .../ui/effects/premium_top_bar.cpp | 7 +- 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 2b6064abb..4ed6e8ac2 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -139,6 +139,7 @@ private: const not_null _controller; QImage _star; + QImage _balanceStar; base::unique_qptr> _back; base::unique_qptr _close; @@ -157,7 +158,8 @@ Credits::Credits( not_null controller) : Section(parent) , _controller(controller) -, _star(GenerateStars(st::creditsTopupButton.height, 1)) { +, _star(GenerateStars(st::creditsTopupButton.height, 1)) +, _balanceStar(GenerateStars(st::creditsBalanceStarHeight, 1)) { setupContent(); } @@ -514,6 +516,71 @@ QPointer Credits::createPinnedToTop( } }, content->lifetime()); + const auto balance = Ui::CreateChild(content); + { + const auto starSize = _balanceStar.size() / style::DevicePixelRatio(); + const auto label = balance->lifetime().make_state( + st::defaultTextStyle, + tr::lng_credits_summary_balance(tr::now)); + const auto count = balance->lifetime().make_state( + st::semiboldTextStyle, + tr::lng_contacts_loading(tr::now)); + const auto api = balance->lifetime().make_state( + _controller->session().user()); + const auto diffBetweenStarAndCount = count->style()->font->spacew; + const auto resize = [=] { + balance->resize( + std::max( + label->maxWidth(), + count->maxWidth() + + starSize.width() + + diffBetweenStarAndCount), + label->style()->font->height + starSize.height()); + }; + api->request({}, [=](Data::CreditsStatusSlice slice) { + count->setText( + st::semiboldTextStyle, + Lang::FormatCountToShort(slice.balance).string); + resize(); + }); + balance->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(balance); + + p.setPen(st::boxTextFg); + + label->draw(p, { + .position = QPoint(balance->width() - label->maxWidth(), 0), + .availableWidth = balance->width(), + }); + count->draw(p, { + .position = QPoint( + balance->width() - count->maxWidth(), + label->minHeight() + + (starSize.height() - count->minHeight()) / 2), + .availableWidth = balance->width(), + }); + p.drawImage( + balance->width() + - count->maxWidth() + - starSize.width() + - diffBetweenStarAndCount, + label->minHeight(), + _balanceStar); + }, balance->lifetime()); + rpl::combine( + balance->sizeValue(), + content->sizeValue() + ) | rpl::start_with_next([=](const QSize &, const QSize &) { + balance->moveToRight( + (_close + ? _close->width() + st::creditsHistoryRightSkip + : st::creditsHistoryRightSkip * 2), + st::creditsHistoryRightSkip); + balance->update(); + }, balance->lifetime()); + } + _wrap.value( ) | rpl::start_with_next([=](Info::Wrap wrap) { const auto isLayer = (wrap == Info::Wrap::Layer); diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 355d0fc5b..8a1afec19 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -24,3 +24,4 @@ creditsTopupPrice: FlatLabel(defaultFlatLabel) { } creditsHistoryRightSkip: 10px; +creditsBalanceStarHeight: 20px; diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp index fd3d11950..999a077d4 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp @@ -256,11 +256,8 @@ void TopBar::resizeEvent(QResizeEvent *e) { const auto progress = (max > min) ? ((e->size().height() - min) / float64(max - min)) : 1.; - _progress.top = 1. - - std::clamp( - (1. - progress) / kBodyAnimationPart, - 0., - 1.); + _progress.top = 1. + - std::clamp((1. - progress) / kBodyAnimationPart, 0., 1.); _progress.body = _progress.top; _progress.title = 1. - progress; _progress.scaleTitle = 1. + kTitleAdditionalScale * progress; From 6336df2bd6ec8c140cb891fa42b7114d9112645c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 21 May 2024 05:22:27 +0300 Subject: [PATCH 106/225] Slightly improved code style for decimal counts. --- .../view/reactions/history_view_reactions_tabs.cpp | 13 +++++++------ .../statistics/widgets/point_details_widget.cpp | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.cpp index c3b5848cc..f3b23804f 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_tabs.cpp @@ -7,13 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/reactions/history_view_reactions_tabs.h" -#include "ui/rp_widget.h" -#include "ui/abstract_button.h" -#include "ui/painter.h" -#include "ui/controls/who_reacted_context_action.h" #include "data/data_message_reaction_id.h" -#include "styles/style_widgets.h" +#include "lang/lang_tag.h" +#include "ui/abstract_button.h" +#include "ui/controls/who_reacted_context_action.h" +#include "ui/painter.h" +#include "ui/rp_widget.h" #include "styles/style_chat.h" +#include "styles/style_widgets.h" namespace HistoryView::Reactions { namespace { @@ -35,7 +36,7 @@ not_null CreateTab( bool selected = false; }; const auto stm = &st.item; - const auto text = QString("%L1").arg(count); + const auto text = Lang::FormatCountDecimal(count); const auto font = st::semiboldFont; const auto textWidth = font->width(text); const auto result = Ui::CreateChild(parent.get()); diff --git a/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp b/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp index 50d56fec5..305010866 100644 --- a/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp +++ b/Telegram/SourceFiles/statistics/widgets/point_details_widget.cpp @@ -81,7 +81,7 @@ void PaintDetails( line.name); auto value = Ui::Text::String( st::statisticsDetailsPopupStyle, - QString("%L1").arg(absoluteValue)); + Lang::FormatCountDecimal(absoluteValue)); const auto nameWidth = name.maxWidth(); const auto valueWidth = value.maxWidth(); @@ -177,7 +177,7 @@ PointDetailsWidget::PointDetailsWidget( const auto calculatedWidth = [&]{ const auto maxValueText = Ui::Text::String( _textStyle, - QString("%L1").arg(maxAbsoluteValue)); + Lang::FormatCountDecimal(maxAbsoluteValue)); const auto maxValueTextWidth = maxValueText.maxWidth(); auto maxNameTextWidth = 0; @@ -284,7 +284,7 @@ void PointDetailsWidget::setXIndex(int xIndex) { textLine.name.setText(_textStyle, dataLine.name); textLine.value.setText( _textStyle, - QString("%L1").arg(dataLine.y[xIndex])); + Lang::FormatCountDecimal(dataLine.y[xIndex])); hasPositiveValues |= (dataLine.y[xIndex] > 0); textLine.valueColor = QColor(dataLine.color); if (_chartData.currencyRate) { From 42d6d0d58af6d84c212a7d7bc219c53888229c3d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 21 May 2024 05:42:03 +0300 Subject: [PATCH 107/225] Added tooltip to balance label in credits settings for high values. --- .../SourceFiles/settings/settings_credits.cpp | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 4ed6e8ac2..a31c68e10 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/labels.h" +#include "ui/widgets/tooltip.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -49,6 +50,45 @@ namespace { using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; +class Balance final + : public Ui::RpWidget + , public Ui::AbstractTooltipShower { +public: + using Ui::RpWidget::RpWidget; + + void setBalance(uint64 balance) { + _balance = balance; + _tooltip = Lang::FormatCountDecimal(balance); + } + + void enterEventHook(QEnterEvent *e) override { + if (_balance >= 10'000) { + Ui::Tooltip::Show(1000, this); + } + } + + void leaveEventHook(QEvent *e) override { + Ui::Tooltip::Hide(); + } + + QString tooltipText() const override { + return _tooltip; + } + + QPoint tooltipPos() const override { + return QCursor::pos(); + } + + bool tooltipWindowActive() const override { + return Ui::AppInFocus() && Ui::InFocusChain(window()); + } + +private: + QString _tooltip; + uint64 _balance = 0; + +}; + [[nodiscard]] uint64 UniqueIdFromOption( const Data::CreditTopupOption &d) { const auto string = QString::number(d.credits) @@ -516,7 +556,7 @@ QPointer Credits::createPinnedToTop( } }, content->lifetime()); - const auto balance = Ui::CreateChild(content); + const auto balance = Ui::CreateChild(content); { const auto starSize = _balanceStar.size() / style::DevicePixelRatio(); const auto label = balance->lifetime().make_state( @@ -541,6 +581,7 @@ QPointer Credits::createPinnedToTop( count->setText( st::semiboldTextStyle, Lang::FormatCountToShort(slice.balance).string); + balance->setBalance(slice.balance); resize(); }); balance->paintRequest( From 5adde6c93afddae2bd1211fe7ba050021edde514 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 21 May 2024 15:02:34 +0400 Subject: [PATCH 108/225] Fix build on Windows. --- .../info/statistics/info_statistics_list_controllers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 6d6abdec4..285a0e1a8 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -847,8 +847,8 @@ void CreditsRow::rightActionPaint( x += st::creditsHistoryRightSkip; _rightText.draw(p, Ui::Text::PaintContext{ .position = QPoint(x, y - font->height / 2), - .availableWidth = outerWidth, .outerWidth = outerWidth, + .availableWidth = outerWidth, }); x += _rightText.maxWidth() + font->spacew * 2; p.drawImage( From 8eb24f620d4b6fb5571fc8a00e39b9aa546a0cdf Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 21 May 2024 16:22:45 +0400 Subject: [PATCH 109/225] Update API scheme on layer 181. --- Telegram/SourceFiles/api/api_credits.cpp | 4 ++++ Telegram/SourceFiles/api/api_text_entities.cpp | 3 ++- Telegram/SourceFiles/data/data_credits.h | 2 ++ Telegram/SourceFiles/mtproto/scheme/api.tl | 4 +++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 8a609820e..32497e249 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -34,6 +34,10 @@ namespace { return Data::CreditsHistoryEntry::PeerType::Fragment; }, [](const MTPDstarsTransactionPeerAppStore &) { return Data::CreditsHistoryEntry::PeerType::AppStore; + }, [](const MTPDstarsTransactionPeerUnsupported &) { + return Data::CreditsHistoryEntry::PeerType::Unsupported; + }, [](const MTPDstarsTransactionPeerPremiumBot &) { + return Data::CreditsHistoryEntry::PeerType::PremiumBot; }), .peerId = tl.data().vpeer().match([](const HistoryPeerTL &p) { return peerFromMTP(p.vpeer()); diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 44a689ff4..3c5098911 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -311,7 +311,8 @@ MTPVector EntitiesToMTP( MTP_string(entity.data()))); } break; case EntityType::Blockquote: { - v.push_back(MTP_messageEntityBlockquote(offset, length)); + v.push_back( + MTP_messageEntityBlockquote(MTP_flags(0), offset, length)); } break; case EntityType::Spoiler: { v.push_back(MTP_messageEntitySpoiler(offset, length)); diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 86ccd022f..cb0310c93 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -24,6 +24,8 @@ struct CreditsHistoryEntry final { AppStore, PlayMarket, Fragment, + Unsupported, + PremiumBot, }; QString id; uint64 credits = 0; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 330e20ea7..deb1d9e80 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -671,7 +671,7 @@ messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; messageEntityCustomEmoji#c8cf05f8 offset:int length:int document_id:long = MessageEntity; -messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; +messageEntityBlockquote#f1ccaaac flags:# collapsed:flags.0?true offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; @@ -1793,8 +1793,10 @@ messages.availableEffects#bddb616e hash:int effects:Vector docu factCheck#b89bfccf flags:# need_check:flags.0?true country:flags.1?string text:flags.1?TextWithEntities hash:long = FactCheck; +starsTransactionPeerUnsupported#95f2bfe4 = StarsTransactionPeer; starsTransactionPeerAppStore#b457b375 = StarsTransactionPeer; starsTransactionPeerPlayMarket#7b560a0b = StarsTransactionPeer; +starsTransactionPeerPremiumBot#250dbaf8 = StarsTransactionPeer; starsTransactionPeerFragment#e92fd902 = StarsTransactionPeer; starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; From 5e29f382cdd61f3a143dbe10969fb03ec1f8c130 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 21 May 2024 16:22:18 +0300 Subject: [PATCH 110/225] Fixed build with Xcode. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 2 +- Telegram/SourceFiles/dialogs/dialogs_key.h | 1 + Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp | 6 +++--- Telegram/SourceFiles/mainwidget.cpp | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 9e79f3ac2..a56e28b2f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -801,7 +801,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.fillRect(dialogsClip, currentBg()); } } else if (_state == WidgetState::Filtered) { - auto top = 0; + [[maybe_unused]] auto top = 0; if (!_hashtagResults.empty()) { auto from = floorclamp(r.y(), st::mentionHeight, 0, _hashtagResults.size()); auto to = ceilclamp(r.y() + r.height(), st::mentionHeight, 0, _hashtagResults.size()); diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 2f631288e..8b5d22ed7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/qt/qt_compare.h" +#include "data/data_message_reaction_id.h" class History; class PeerData; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp index a8615ae11..24fc21507 100644 --- a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp @@ -56,9 +56,9 @@ FixedHashtagSearchQuery FixHashtagSearchQuery( const QString &query, int cursorPosition) { const auto trimmed = query.trimmed(); - const auto hash = trimmed.isEmpty() + const auto hash = int(trimmed.isEmpty() ? query.size() - : query.indexOf(trimmed); + : query.indexOf(trimmed)); const auto start = std::min(cursorPosition, hash); auto result = query.mid(0, start); for (const auto &ch : query.mid(start)) { @@ -198,4 +198,4 @@ void ChatSearchTabs::paintEvent(QPaintEvent *e) { QPainter(this).fillRect(e->rect(), st::dialogsBg); } -} // namespace Dialogs \ No newline at end of file +} // namespace Dialogs diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d3873d4f4..025b66cf8 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -733,7 +733,6 @@ void MainWidget::hideSingleUseKeyboard(FullMsgId replyToId) { void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { auto tags = Data::SearchTagsFromQuery(query); if (controller()->isPrimary()) { - using Tab = Dialogs::ChatSearchTab; auto state = Dialogs::SearchState{ .inChat = ((tags.empty() || inChat.sublist()) ? inChat From 0e30e306ff9d4a0ae2cde7381e994c058ec4db34 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 21 May 2024 17:28:30 +0300 Subject: [PATCH 111/225] Added second type of stars to animation of mini stars. --- .../SourceFiles/ui/effects/premium_stars.cpp | 17 ++++++++++++++--- Telegram/SourceFiles/ui/effects/premium_stars.h | 13 ++++++++++++- .../ui/effects/premium_stars_colored.cpp | 6 ++++-- .../ui/effects/premium_stars_colored.h | 5 ++++- .../SourceFiles/ui/effects/premium_top_bar.cpp | 2 +- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/ui/effects/premium_stars.cpp b/Telegram/SourceFiles/ui/effects/premium_stars.cpp index f7b1b5257..c63f700d7 100644 --- a/Telegram/SourceFiles/ui/effects/premium_stars.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_stars.cpp @@ -17,7 +17,10 @@ namespace Premium { constexpr auto kDeformationMax = 0.1; -MiniStars::MiniStars(Fn updateCallback, bool opaque) +MiniStars::MiniStars( + Fn updateCallback, + bool opaque, + Type type) : _availableAngles({ Interval{ -10, 40 }, Interval{ 180 + 10 - 40, 40 }, @@ -29,6 +32,7 @@ MiniStars::MiniStars(Fn updateCallback, bool opaque) , _size({ 5, 10 }) , _alpha({ opaque ? 100 : 40, opaque ? 100 : 60 }) , _sinFactor({ 10, 190 }) +, _spritesCount({ 0, ((type == Type::MonoStars) ? 1 : 2) }) , _appearProgressTill(0.2) , _disappearProgressAfter(0.8) , _distanceProgressStart(0.5) @@ -41,6 +45,10 @@ MiniStars::MiniStars(Fn updateCallback, bool opaque) updateCallback(base::take(_rectToUpdate)); } }) { + if (type == Type::BiStars) { + _secondSprite = std::make_unique( + u":/gui/icons/settings/star.svg"_q); + } if (anim::Disabled()) { const auto from = _deathTime.from + _deathTime.length; auto r = bytes::vector(from + 1); @@ -117,7 +125,7 @@ void MiniStars::paint(QPainter &p, const QRectF &rect) { - starHeight / 2., starWidth, starHeight); - _sprite.render(&p, renderRect); + ministar.sprite->render(&p, renderRect); _rectToUpdate |= renderRect.toRect(); } p.setOpacity(opacity); @@ -128,7 +136,7 @@ void MiniStars::setPaused(bool paused) { } void MiniStars::createStar(crl::time now) { - constexpr auto kRandomSize = 8; + constexpr auto kRandomSize = 9; auto random = bytes::vector(kRandomSize); base::RandomFill(random.data(), random.size()); @@ -148,6 +156,9 @@ void MiniStars::createStar(crl::time now) { .alpha = float64(randomInterval(_alpha, next())) / 100., .sinFactor = randomInterval(_sinFactor, next()) / 100. * ((uchar(next()) % 2) == 1 ? 1. : -1.), + .sprite = ((randomInterval(_spritesCount, next()) && _secondSprite) + ? _secondSprite.get() + : &_sprite), }; for (auto i = 0; i < _ministars.size(); i++) { if (ministar.birthTime > _ministars[i].deathTime) { diff --git a/Telegram/SourceFiles/ui/effects/premium_stars.h b/Telegram/SourceFiles/ui/effects/premium_stars.h index 52feb589d..d224d8186 100644 --- a/Telegram/SourceFiles/ui/effects/premium_stars.h +++ b/Telegram/SourceFiles/ui/effects/premium_stars.h @@ -16,7 +16,15 @@ namespace Premium { class MiniStars final { public: - MiniStars(Fn updateCallback, bool opaque = false); + enum class Type { + MonoStars, + BiStars, + }; + + MiniStars( + Fn updateCallback, + bool opaque = false, + Type type = Type::MonoStars); void paint(QPainter &p, const QRectF &rect); void setPaused(bool paused); @@ -31,6 +39,7 @@ private: float64 size = 0.; float64 alpha = 0.; float64 sinFactor = 0.; + not_null sprite; }; struct Interval { @@ -50,12 +59,14 @@ private: const Interval _size; const Interval _alpha; const Interval _sinFactor; + const Interval _spritesCount; const float64 _appearProgressTill; const float64 _disappearProgressAfter; const float64 _distanceProgressStart; QSvgRenderer _sprite; + std::unique_ptr _secondSprite; Ui::Animations::Basic _animation; diff --git a/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp b/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp index d5987a5e7..5e20cfd22 100644 --- a/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_stars_colored.cpp @@ -15,14 +15,16 @@ namespace Premium { ColoredMiniStars::ColoredMiniStars( not_null parent, - bool optimizeUpdate) + bool optimizeUpdate, + MiniStars::Type type) : _ministars( optimizeUpdate ? Fn([=](const QRect &r) { parent->update(r.translated(_position)); }) : Fn([=](const QRect &) { parent->update(); }), - true) { + true, + type) { } void ColoredMiniStars::setSize(const QSize &size) { diff --git a/Telegram/SourceFiles/ui/effects/premium_stars_colored.h b/Telegram/SourceFiles/ui/effects/premium_stars_colored.h index 73001ada0..d0ae77b00 100644 --- a/Telegram/SourceFiles/ui/effects/premium_stars_colored.h +++ b/Telegram/SourceFiles/ui/effects/premium_stars_colored.h @@ -17,7 +17,10 @@ namespace Premium { class ColoredMiniStars final { public: // optimizeUpdate may cause paint glitch. - ColoredMiniStars(not_null parent, bool optimizeUpdate); + ColoredMiniStars( + not_null parent, + bool optimizeUpdate, + MiniStars::Type type = MiniStars::Type::MonoStars); void setSize(const QSize &size); void setPosition(QPoint position); diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp index 999a077d4..640a96b3e 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp @@ -174,7 +174,7 @@ TopBar::TopBar( , _titleFont(st.titleFont) , _titlePadding(st.titlePadding) , _about(this, std::move(descriptor.about), st.about) -, _ministars(this, descriptor.optimizeMinistars) { +, _ministars(this, descriptor.optimizeMinistars, MiniStars::Type::BiStars) { std::move( descriptor.title ) | rpl::start_with_next([=](QString text) { From 174fb62c325af9904e9f8a1463fe5eb2ee17a9ee Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 22 May 2024 00:49:35 +0300 Subject: [PATCH 112/225] Fixed purchases of credits. --- Telegram/SourceFiles/api/api_credits.cpp | 55 +++---------------- Telegram/SourceFiles/data/data_credits.h | 1 + .../SourceFiles/payments/payments_form.cpp | 12 +++- Telegram/SourceFiles/payments/payments_form.h | 1 + .../SourceFiles/settings/settings_credits.cpp | 1 + 5 files changed, 19 insertions(+), 51 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 32497e249..717adb64a 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -57,7 +57,7 @@ namespace { status.data().vhistory().v ) | ranges::views::transform(HistoryFromTL) | ranges::to_vector, .balance = status.data().vbalance().v, - .allLoaded = status.data().vnext_offset().has_value(), + .allLoaded = !status.data().vnext_offset().has_value(), .token = qs(status.data().vnext_offset().value_or_empty()), }; } @@ -85,6 +85,7 @@ rpl::producer CreditsTopupOptions::request() { option.data().vstore_product().value_or_empty()), .currency = qs(option.data().vcurrency()), .amount = option.data().vamount().v, + .extended = option.data().is_extended(), }; }) | ranges::to_vector; consumer.put_done(); @@ -114,10 +115,6 @@ void CreditsStatus::request( _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input )).done([=](const TLResult &result) { _requestId = 0; -#if _DEBUG - done({ .balance = uint64(base::RandomIndex(9999)) }); - return; -#endif done(StatusFromTL(result, _peer)); }).fail([=] { _requestId = 0; @@ -127,9 +124,11 @@ void CreditsStatus::request( CreditsHistory::CreditsHistory(not_null peer, bool in, bool out) : _peer(peer) -, _flags(HistoryTL::Flags(0) - | (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0)) - | (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0))) +, _flags((in == out) + ? HistoryTL::Flags(0) + : HistoryTL::Flags(0) + | (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0)) + | (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0))) , _api(&peer->session().api().instance()) { } @@ -145,46 +144,6 @@ void CreditsHistory::request( MTP_string(token) )).done([=](const MTPpayments_StarsStatus &result) { _requestId = 0; -#if _DEBUG - done({ - .list = [&] { - auto a = std::vector(); - const auto isIn = _flags & HistoryTL::Flag::f_inbound; - const auto isOut = _flags & HistoryTL::Flag::f_outbound; - for (auto i = 0; i < base::RandomIndex(10) + 1; i++) { - const auto type = (isIn && isOut) - ? base::RandomIndex(4) - : isOut - ? 0 - : (base::RandomIndex(3) + 1); - a.push_back(Data::CreditsHistoryEntry{ - .id = QString::number(base::RandomValue()), - .credits = uint64( - std::max(base::RandomIndex(15000), 1)), - .date = base::unixtime::parse( - std::abs(base::RandomValue())), - .peerType = ((type == 0) - ? Data::CreditsHistoryEntry::PeerType::Peer - : (type == 1) - ? Data::CreditsHistoryEntry::PeerType::PlayMarket - : (type == 2) - ? Data::CreditsHistoryEntry::PeerType::Fragment - : Data::CreditsHistoryEntry::PeerType::AppStore), - .peerId = (type == 0) - ? peerFromUser(5000233800) - : PeerId(0), - }); - } - return a; - }(), - .balance = 47890, - .allLoaded = !token.isEmpty(), - .token = token.isEmpty() - ? QString::number(base::RandomValue()) - : QString(), - }); - return; -#endif done(StatusFromTL(result, _peer)); }).fail([=] { _requestId = 0; diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index cb0310c93..0984cf8bf 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -14,6 +14,7 @@ struct CreditTopupOption final { QString product; QString currency; uint64 amount = 0; + bool extended = false; }; using CreditTopupOptions = std::vector; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index c02ea408b..07a6b2a67 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -317,10 +317,16 @@ MTPInputInvoice Form::inputInvoice() const { } else if (const auto slug = std::get_if(&_id.value)) { return MTP_inputInvoiceSlug(MTP_string(slug->slug)); } else if (const auto credits = std::get_if(&_id.value)) { + using Flag = MTPDstarsTopupOption::Flag; + const auto emptyFlag = MTPDstarsTopupOption::Flags(0); return MTP_inputInvoiceStars(MTP_starsTopupOption( - credits->product.isEmpty() - ? MTP_flags(0) - : MTP_flags(MTPDstarsTopupOption::Flag::f_store_product), + MTP_flags(emptyFlag + | (credits->product.isEmpty() + ? Flag::f_store_product + : emptyFlag) + | (credits->extended + ? Flag::f_extended + : emptyFlag)), MTP_long(credits->credits), MTP_string(credits->product), MTP_string(credits->currency), diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 92448feb6..834113115 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -223,6 +223,7 @@ struct InvoiceCredits { QString product; QString currency; uint64 amount = 0; + bool extended = false; }; struct InvoiceId { diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index a31c68e10..71ea3a9e1 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -297,6 +297,7 @@ void Credits::setupOptions(not_null container) { .product = option.product, .currency = option.currency, .amount = option.amount, + .extended = option.extended, }; const auto weak = Ui::MakeWeak(button); From e11755af46d12a53b344804be78d1fb5e0b39b02 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 22 May 2024 17:01:42 +0300 Subject: [PATCH 113/225] Added credits balance to main session. --- Telegram/SourceFiles/api/api_updates.cpp | 5 +++++ Telegram/SourceFiles/main/main_session.cpp | 8 ++++++++ Telegram/SourceFiles/main/main_session.h | 4 ++++ Telegram/SourceFiles/settings/settings_credits.cpp | 10 +++++++--- Telegram/SourceFiles/settings/settings_main.cpp | 13 ++++++++++++- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 56f8a8da4..0ebf17f33 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -2614,6 +2614,11 @@ void Updates::feedUpdate(const MTPUpdate &update) { _session->data().stories().apply(data.vstealth_mode()); } break; + case mtpc_updateStarsBalance: { + const auto &data = update.c_updateStarsBalance(); + _session->setCredits(data.vbalance().v); + } break; + } } diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index aa70700ec..fc8435d66 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -280,6 +280,14 @@ bool Session::premiumCanBuy() const { return _premiumPossible.current(); } +rpl::producer Session::creditsValue() const { + return _credits.value(); +} + +void Session::setCredits(uint64 credits) { + _credits = credits; +} + bool Session::isTestMode() const { return mtp().isTestMode(); } diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 8d1b276ec..85aa2fe08 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -100,6 +100,9 @@ public: [[nodiscard]] bool premiumBadgesShown() const; [[nodiscard]] bool premiumCanBuy() const; + [[nodiscard]] rpl::producer creditsValue() const; + void setCredits(uint64 credits); + [[nodiscard]] bool isTestMode() const; [[nodiscard]] uint64 uniqueId() const; // userId() with TestDC shift. [[nodiscard]] UserId userId() const; @@ -258,6 +261,7 @@ private: const std::unique_ptr _supportHelper; std::shared_ptr _selfUserpicView; + rpl::variable _credits = 0; rpl::variable _premiumPossible = false; rpl::event_stream _termsLockChanges; diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 71ea3a9e1..67f9e2a3e 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -578,12 +578,16 @@ QPointer Credits::createPinnedToTop( + diffBetweenStarAndCount), label->style()->font->height + starSize.height()); }; - api->request({}, [=](Data::CreditsStatusSlice slice) { + _controller->session().creditsValue( + ) | rpl::start_with_next([=](uint64 value) { count->setText( st::semiboldTextStyle, - Lang::FormatCountToShort(slice.balance).string); - balance->setBalance(slice.balance); + Lang::FormatCountToShort(value).string); + balance->setBalance(value); resize(); + }, balance->lifetime()); + api->request({}, [=](Data::CreditsStatusSlice slice) { + _controller->session().setCredits(slice.balance); }); balance->paintRequest( ) | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 332677a93..9f2e73072 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_main.h" +#include "api/api_credits.h" #include "core/application.h" #include "core/click_handler_types.h" #include "settings/settings_advanced.h" @@ -491,9 +492,13 @@ void SetupPremium( showOther(PremiumId()); }); AddPremiumStar( - AddButtonWithIcon( + AddButtonWithLabel( container, tr::lng_credits_summary_title(), + controller->session().creditsValue( + ) | rpl::map([=](uint64 c) { + return c ? Lang::FormatCountToShort(c).string : QString{}; + }), st::settingsButton), true )->addClickHandler([=] { @@ -510,6 +515,12 @@ void SetupPremium( }); Ui::NewBadge::AddToRight(button); + const auto api = button->lifetime().make_state( + controller->session().user()); + api->request({}, [=](Data::CreditsStatusSlice slice) { + controller->session().setCredits(slice.balance); + }); + if (controller->session().premiumCanBuy()) { const auto button = AddButtonWithIcon( container, From 5ca9b74142e2c5519b0c9458b0bdb3df6d9d535a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 22 May 2024 18:40:15 +0300 Subject: [PATCH 114/225] Fixed effect for successful payments in credits settings. --- Telegram/SourceFiles/settings/settings_credits.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 67f9e2a3e..1793d07ac 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -178,6 +178,8 @@ private: const not_null _controller; + QWidget *_parent = nullptr; + QImage _star; QImage _balanceStar; @@ -305,7 +307,9 @@ void Credits::setupOptions(not_null container) { if (const auto strong = weak.data()) { strong->window()->setFocus(); if (result == Payments::CheckoutResult::Paid) { - Ui::StartFireworks(this); + if (_parent) { + Ui::StartFireworks(_parent); + } } } }; @@ -513,6 +517,7 @@ void Credits::setupContent() { QPointer Credits::createPinnedToTop( not_null parent) { + _parent = parent; const auto content = [&]() -> Ui::Premium::TopBarAbstract* { const auto weak = base::make_weak(_controller); From 69c48e2b5b8ad096e238ad473c66306e752004ca Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 22 May 2024 21:47:36 +0300 Subject: [PATCH 115/225] Moved out credits balance widget to single place. --- .../SourceFiles/settings/settings_credits.cpp | 116 ++++++++++-------- .../SourceFiles/settings/settings_credits.h | 28 +---- 2 files changed, 70 insertions(+), 74 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 1793d07ac..a1fde01f4 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -562,63 +562,16 @@ QPointer Credits::createPinnedToTop( } }, content->lifetime()); - const auto balance = Ui::CreateChild(content); { - const auto starSize = _balanceStar.size() / style::DevicePixelRatio(); - const auto label = balance->lifetime().make_state( - st::defaultTextStyle, - tr::lng_credits_summary_balance(tr::now)); - const auto count = balance->lifetime().make_state( - st::semiboldTextStyle, - tr::lng_contacts_loading(tr::now)); + const auto balance = AddBalanceWidget( + content, + _controller->session().creditsValue(), + true); const auto api = balance->lifetime().make_state( _controller->session().user()); - const auto diffBetweenStarAndCount = count->style()->font->spacew; - const auto resize = [=] { - balance->resize( - std::max( - label->maxWidth(), - count->maxWidth() - + starSize.width() - + diffBetweenStarAndCount), - label->style()->font->height + starSize.height()); - }; - _controller->session().creditsValue( - ) | rpl::start_with_next([=](uint64 value) { - count->setText( - st::semiboldTextStyle, - Lang::FormatCountToShort(value).string); - balance->setBalance(value); - resize(); - }, balance->lifetime()); api->request({}, [=](Data::CreditsStatusSlice slice) { _controller->session().setCredits(slice.balance); }); - balance->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(balance); - - p.setPen(st::boxTextFg); - - label->draw(p, { - .position = QPoint(balance->width() - label->maxWidth(), 0), - .availableWidth = balance->width(), - }); - count->draw(p, { - .position = QPoint( - balance->width() - count->maxWidth(), - label->minHeight() - + (starSize.height() - count->minHeight()) / 2), - .availableWidth = balance->width(), - }); - p.drawImage( - balance->width() - - count->maxWidth() - - starSize.width() - - diffBetweenStarAndCount, - label->minHeight(), - _balanceStar); - }, balance->lifetime()); rpl::combine( balance->sizeValue(), content->sizeValue() @@ -706,4 +659,65 @@ Type CreditsId() { return Credits::Id(); } +not_null AddBalanceWidget( + not_null parent, + rpl::producer balanceValue, + bool rightAlign) { + const auto balance = Ui::CreateChild(parent); + const auto balanceStar = balance->lifetime().make_state( + GenerateStars(st::creditsBalanceStarHeight, 1)); + const auto starSize = balanceStar->size() / style::DevicePixelRatio(); + const auto label = balance->lifetime().make_state( + st::defaultTextStyle, + tr::lng_credits_summary_balance(tr::now)); + const auto count = balance->lifetime().make_state( + st::semiboldTextStyle, + tr::lng_contacts_loading(tr::now)); + const auto diffBetweenStarAndCount = count->style()->font->spacew; + const auto resize = [=] { + balance->resize( + std::max( + label->maxWidth(), + count->maxWidth() + + starSize.width() + + diffBetweenStarAndCount), + label->style()->font->height + starSize.height()); + }; + std::move(balanceValue) | rpl::start_with_next([=](uint64 value) { + count->setText( + st::semiboldTextStyle, + Lang::FormatCountToShort(value).string); + balance->setBalance(value); + resize(); + }, balance->lifetime()); + balance->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(balance); + + p.setPen(st::boxTextFg); + + label->draw(p, { + .position = QPoint( + rightAlign ? (balance->width() - label->maxWidth()) : 0, + 0), + .availableWidth = balance->width(), + }); + count->draw(p, { + .position = QPoint( + balance->width() - count->maxWidth(), + label->minHeight() + + (starSize.height() - count->minHeight()) / 2), + .availableWidth = balance->width(), + }); + p.drawImage( + balance->width() + - count->maxWidth() + - starSize.width() + - diffBetweenStarAndCount, + label->minHeight(), + *balanceStar); + }, balance->lifetime()); + return balance; +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits.h b/Telegram/SourceFiles/settings/settings_credits.h index 7484fa637..2267a0cd5 100644 --- a/Telegram/SourceFiles/settings/settings_credits.h +++ b/Telegram/SourceFiles/settings/settings_credits.h @@ -9,36 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_type.h" -enum class PremiumFeature; - -namespace style { -struct RoundButton; -} // namespace style - -namespace ChatHelpers { -class Show; -enum class WindowUsage; -} // namespace ChatHelpers - namespace Ui { class RpWidget; -class RoundButton; -class GradientButton; -class VerticalLayout; } // namespace Ui -namespace Main { -class Session; -class SessionShow; -} // namespace Main - -namespace Window { -class SessionController; -} // namespace Window - namespace Settings { [[nodiscard]] Type CreditsId(); +[[nodiscard]] not_null AddBalanceWidget( + not_null parent, + rpl::producer balanceValue, + bool rightAlign); + } // namespace Settings From 3d81414c71161ede9eefc44af81ec54282834899 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 22 May 2024 21:48:11 +0300 Subject: [PATCH 116/225] Added initial implementation of box for sending credits. --- Telegram/CMakeLists.txt | 2 + .../SourceFiles/boxes/send_credits_box.cpp | 263 ++++++++++++++++++ Telegram/SourceFiles/boxes/send_credits_box.h | 22 ++ Telegram/SourceFiles/ui/effects/credits.style | 5 + 4 files changed, 292 insertions(+) create mode 100644 Telegram/SourceFiles/boxes/send_credits_box.cpp create mode 100644 Telegram/SourceFiles/boxes/send_credits_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b1b39f59f..8d1d2369d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -301,6 +301,8 @@ PRIVATE boxes/ringtones_box.h boxes/self_destruction_box.cpp boxes/self_destruction_box.h + boxes/send_credits_box.cpp + boxes/send_credits_box.h boxes/send_files_box.cpp boxes/send_files_box.h boxes/sessions_box.cpp diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp new file mode 100644 index 000000000..ae45850bd --- /dev/null +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -0,0 +1,263 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/send_credits_box.h" + +#include "api/api_credits.h" +#include "core/ui_integration.h" // Core::MarkedTextContext. +#include "data/data_credits.h" +#include "data/data_file_origin.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "data/stickers/data_custom_emoji.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "payments/payments_form.h" +#include "settings/settings_credits.h" +#include "ui/controls/userpic_button.h" +#include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg. +#include "ui/image/image_prepare.h" +#include "ui/layers/generic_box.h" +#include "ui/rect.h" +#include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "styles/style_boxes.h" +#include "styles/style_credits.h" +#include "styles/style_giveaway.h" +#include "styles/style_layers.h" +#include "styles/style_premium.h" +#include "styles/style_settings.h" + +namespace Ui { +namespace { +} // namespace + +void SendCreditsBox( + not_null box, + not_null item) { + const auto media = item->media(); + const auto invoice = media ? media->invoice() : nullptr; + if (!invoice) { + return; + } + box->setStyle(st::giveawayGiftCodeBox); + box->setNoContentMargin(true); + + const auto session = &item->history()->owner().session(); + + const auto photoSize = st::defaultUserpicButton.photoSize; + + const auto content = box->verticalLayout(); + Ui::AddSkip(content, photoSize / 2); + + { + const auto ministarsContainer = Ui::CreateChild(box); + const auto fullHeight = photoSize * 2; + using MiniStars = Ui::Premium::ColoredMiniStars; + const auto ministars = box->lifetime().make_state( + ministarsContainer, + false, + Ui::Premium::MiniStars::Type::BiStars); + ministars->setColorOverride(Ui::Premium::CreditsIconGradientStops()); + + ministarsContainer->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(ministarsContainer); + ministars->paint(p); + }, ministarsContainer->lifetime()); + + box->widthValue( + ) | rpl::start_with_next([=](int width) { + ministarsContainer->resize(width, fullHeight); + const auto w = fullHeight / 3 * 2; + ministars->setCenter(QRect( + (width - w) / 2, + (fullHeight - w) / 2, + w, + w)); + }, ministarsContainer->lifetime()); + } + + if (false && invoice->photo) { + struct State { + std::shared_ptr view; + Image *image = nullptr; + rpl::lifetime downloadLifetime; + }; + const auto state = content->lifetime().make_state(); + const auto widget = box->addRow( + object_ptr>( + content, + object_ptr(content)))->entity(); + state->view = invoice->photo->createMediaView(); + state->view->wanted(Data::PhotoSize::Large, item->fullId()); + + widget->resize(Size(photoSize)); + + rpl::single(rpl::empty_value()) | rpl::then( + session->downloaderTaskFinished() + ) | rpl::start_with_next([=] { + using Size = Data::PhotoSize; + if (const auto large = state->view->image(Size::Large)) { + state->image = large; + } else if (const auto small = state->view->image(Size::Small)) { + state->image = small; + } else if (const auto t = state->view->image(Size::Thumbnail)) { + state->image = t; + } + widget->update(); + if (state->view->loaded()) { + state->downloadLifetime.destroy(); + } + }, state->downloadLifetime); + + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(widget); + if (state->image) { + p.drawPixmap(0, 0, state->image->pix(widget->width(), { + .options = Images::Option::RoundCircle, + })); + } + }, widget->lifetime()); + } else { + const auto widget = box->addRow( + object_ptr>( + content, + object_ptr( + content, + item->author(), + st::defaultUserpicButton))); + widget->setAttribute(Qt::WA_TransparentForMouseEvents); + } + + const auto asd = box->lifetime().make_state(); + + Ui::AddSkip(content); + box->addRow(object_ptr>( + box, + object_ptr( + box, + tr::lng_credits_box_out_title(), + st::settingsPremiumUserTitle))); + Ui::AddSkip(content); + box->addRow(object_ptr>( + box, + object_ptr( + box, + tr::lng_credits_box_out_sure( + lt_count, + rpl::single(invoice->amount) | tr::to_count(), + lt_text, + rpl::single(TextWithEntities{ invoice->title }), + lt_bot, + rpl::single(TextWithEntities{ item->author()->name() }), + Ui::Text::RichLangValue), + st::creditsBoxAbout))); + Ui::AddSkip(content); + Ui::AddSkip(content); + + const auto button = box->addButton(rpl::single(QString()), [=] { + }); + { + const auto emojiMargin = QMargins( + 0, + -st::moderateBoxExpandInnerSkip, + 0, + 0); + const auto buttonEmoji = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::settingsPremiumIconStar, + emojiMargin, + true)); + auto buttonText = tr::lng_credits_box_out_confirm( + lt_count, + rpl::single(invoice->amount) | tr::to_count(), + lt_emoji, + rpl::single(buttonEmoji), + Ui::Text::RichLangValue); + const auto buttonLabel = Ui::CreateChild( + button, + rpl::single(QString()), + st::defaultFlatLabel); + std::move( + buttonText + ) | rpl::start_with_next([=](const TextWithEntities &text) { + buttonLabel->setMarkedText( + text, + Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = [=] { buttonLabel->update(); }, + }); + }, buttonLabel->lifetime()); + buttonLabel->setTextColorOverride( + box->getDelegate()->style().button.textFg->c); + button->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + buttonLabel->moveToLeft( + (size.width() - buttonLabel->width()) / 2, + (size.height() - buttonLabel->height()) / 2); + }, buttonLabel->lifetime()); + buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + } + + const auto buttonWidth = st::boxWidth + - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); + + { + const auto close = Ui::CreateChild( + box.get(), + st::boxTitleClose); + close->setClickedCallback([=] { + box->closeBox(); + }); + box->widthValue( + ) | rpl::start_with_next([=](int width) { + close->moveToRight(0, 0); + close->raise(); + }, close->lifetime()); + } + + { + const auto balance = Settings::AddBalanceWidget( + content, + session->creditsValue(), + false); + const auto api = balance->lifetime().make_state( + session->user()); + api->request({}, [=](Data::CreditsStatusSlice slice) { + session->setCredits(slice.balance); + }); + rpl::combine( + balance->sizeValue(), + content->sizeValue() + ) | rpl::start_with_next([=](const QSize &, const QSize &) { + balance->moveToLeft( + st::creditsHistoryRightSkip * 2, + st::creditsHistoryRightSkip); + balance->update(); + }, balance->lifetime()); + } +} + +bool IsCreditsInvoice(not_null item) { + const auto media = item->media(); + const auto invoice = media ? media->invoice() : nullptr; + return invoice && (invoice->currency == "XTR"); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h new file mode 100644 index 000000000..fa877e305 --- /dev/null +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -0,0 +1,22 @@ +/* +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 HistoryItem; + +namespace Ui { + +class GenericBox; + +void SendCreditsBox( + not_null box, + not_null item); + +[[nodiscard]] bool IsCreditsInvoice(not_null item); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 8a1afec19..19c73c7ba 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -25,3 +25,8 @@ creditsTopupPrice: FlatLabel(defaultFlatLabel) { creditsHistoryRightSkip: 10px; creditsBalanceStarHeight: 20px; + +creditsBoxAbout: FlatLabel(defaultFlatLabel) { + minWidth: 256px; + align: align(top); +} From 84cde1354db20bed7e5a78dca2ef35e60091640c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 22 May 2024 21:48:57 +0300 Subject: [PATCH 117/225] Slightly improved invoice view in messages for credits. --- Telegram/SourceFiles/api/api_bot.cpp | 17 +++++++++++------ .../history/view/media/history_view_invoice.cpp | 7 ++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 0284ae719..befdc04e5 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_cloud_password.h" #include "api/api_send_progress.h" +#include "boxes/send_credits_box.h" #include "boxes/share_box.h" #include "boxes/passcode_box.h" #include "boxes/url_auth_box.h" @@ -330,12 +331,16 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { } break; case ButtonType::Buy: { - Payments::CheckoutProcess::Start( - item, - Payments::Mode::Payment, - crl::guard(controller, [=](auto) { - controller->widget()->activate(); - })); + if (Ui::IsCreditsInvoice(item)) { + controller->uiShow()->show(Box(Ui::SendCreditsBox, item)); + } else { + Payments::CheckoutProcess::Start( + item, + Payments::Mode::Payment, + crl::guard(controller, [=](auto) { + controller->widget()->activate(); + })); + } } break; case ButtonType::Url: { diff --git a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp index 95676cc89..65987e6bb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_invoice.h" +#include "boxes/send_credits_box.h" // IsCreditsInvoice. #include "lang/lang_keys.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" @@ -34,7 +35,8 @@ Invoice::Invoice( } void Invoice::fillFromData(not_null invoice) { - if (invoice->photo) { + const auto isCreditsCurrency = Ui::IsCreditsInvoice(_parent->data()); + if (invoice->photo && !isCreditsCurrency) { const auto spoiler = false; _attach = std::make_unique( _parent, @@ -64,6 +66,9 @@ void Invoice::fillFromData(not_null invoice) { 0, int(statusText.text.size()) }); statusText.text += ' ' + labelText().toUpper(); + if (isCreditsCurrency) { + statusText = {}; + } _status.setMarkedText( st::defaultTextStyle, statusText, From dcc52a7333cc4fcd0650983d43e642f2d477c57f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 23 May 2024 17:05:58 +0300 Subject: [PATCH 118/225] Moved out painting of userpic for credits entries to single place. --- .../info_statistics_list_controllers.cpp | 51 +++++------------ .../ui/effects/credits_graphics.cpp | 55 +++++++++++++++++++ .../SourceFiles/ui/effects/credits_graphics.h | 19 +++++++ Telegram/cmake/td_ui.cmake | 2 + 4 files changed, 90 insertions(+), 37 deletions(-) create mode 100644 Telegram/SourceFiles/ui/effects/credits_graphics.cpp create mode 100644 Telegram/SourceFiles/ui/effects/credits_graphics.h diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 285a0e1a8..24c37d4e1 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/channel_statistics/boosts/giveaway/boost_badge.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "ui/effects/credits_graphics.h" #include "ui/effects/outline_segments.h" // Ui::UnreadStoryOutlineGradient. #include "ui/effects/toggle_arrow.h" #include "ui/painter.h" @@ -30,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/vertical_layout.h" #include "styles/style_credits.h" #include "styles/style_dialogs.h" // dialogsStoriesFull. -#include "styles/style_intro.h" // introFragmentIcon. #include "styles/style_layers.h" // boxRowPadding. #include "styles/style_menu_icons.h" #include "styles/style_settings.h" @@ -740,7 +740,8 @@ private: const Data::CreditsHistoryEntry _entry; not_null const _creditIcon; const int _rowHeight; - Ui::EmptyUserpic _userpic; + + PaintRoundImageCallback _paintUserpicCallback; Ui::Text::String _rightText; }; @@ -749,8 +750,7 @@ CreditsRow::CreditsRow(not_null peer, const Descriptor &descriptor) : PeerListRow(peer, UniqueRowIdFromString(descriptor.entry.id)) , _entry(descriptor.entry) , _creditIcon(descriptor.creditIcon) -, _rowHeight(descriptor.rowHeight) -, _userpic(Ui::EmptyUserpic::UserpicColor(0), QString()) { +, _rowHeight(descriptor.rowHeight) { init(); } @@ -758,22 +758,7 @@ CreditsRow::CreditsRow(const Descriptor &descriptor) : PeerListRow(UniqueRowIdFromString(descriptor.entry.id)) , _entry(descriptor.entry) , _creditIcon(descriptor.creditIcon) -, _rowHeight(descriptor.rowHeight) -, _userpic( - [&]() -> Ui::EmptyUserpic::BgColors { - switch (descriptor.entry.peerType) { - case Data::CreditsHistoryEntry::PeerType::Peer: - return Ui::EmptyUserpic::UserpicColor(0); - case Data::CreditsHistoryEntry::PeerType::AppStore: - return { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 }; - case Data::CreditsHistoryEntry::PeerType::PlayMarket: - return { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 }; - case Data::CreditsHistoryEntry::PeerType::Fragment: - return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; - } - Unexpected("Unknown peer type."); - }(), - QString()) { +, _rowHeight(descriptor.rowHeight) { init(); } @@ -786,6 +771,9 @@ void CreditsRow::init() { (PeerListRow::special() ? QChar('+') : kMinus) + Lang::FormatCountDecimal(_entry.credits)); } + _paintUserpicCallback = !PeerListRow::special() + ? PeerListRow::generatePaintUserpicCallback(false) + : Ui::GenerateCreditsPaintUserpicCallback(_entry); } const Data::CreditsHistoryEntry &CreditsRow::entry() const { @@ -801,27 +789,16 @@ QString CreditsRow::generateName() { } PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { - if (!PeerListRow::special()) { - return PeerListRow::generatePaintUserpicCallback(force); - } - return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { - _userpic.paintCircle(p, x, y, outerWidth, size); - using PeerType = Data::CreditsHistoryEntry::PeerType; - ((_entry.peerType == PeerType::AppStore) - ? st::sessionIconiPhone - : (_entry.peerType == PeerType::PlayMarket) - ? st::sessionIconAndroid - : st::introFragmentIcon).paintInCenter(p, { x, y, size, size }); - }; + return _paintUserpicCallback; } QSize CreditsRow::rightActionSize() const { return QSize( - _rightText.maxWidth() - + (_creditIcon->width() / style::DevicePixelRatio()) - + st::creditsHistoryRightSkip - + _rightText.style()->font->spacew * 2, - _rowHeight); + _rightText.maxWidth() + + (_creditIcon->width() / style::DevicePixelRatio()) + + st::creditsHistoryRightSkip + + _rightText.style()->font->spacew * 2, + _rowHeight); } QMargins CreditsRow::rightActionMargins() const { diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp new file mode 100644 index 000000000..555ee44c0 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -0,0 +1,55 @@ +/* +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/effects/credits_graphics.h" + +#include + +#include "data/data_credits.h" +#include "ui/empty_userpic.h" +#include "ui/painter.h" +#include "styles/style_credits.h" +#include "styles/style_intro.h" // introFragmentIcon. +#include "styles/style_settings.h" + +namespace Ui { + +using PaintRoundImageCallback = Fn; + +PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( + const Data::CreditsHistoryEntry &entry) { + const auto bg = [&]() -> Ui::EmptyUserpic::BgColors { + switch (entry.peerType) { + case Data::CreditsHistoryEntry::PeerType::Peer: + return Ui::EmptyUserpic::UserpicColor(0); + case Data::CreditsHistoryEntry::PeerType::AppStore: + return { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::PlayMarket: + return { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::Fragment: + return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; + } + Unexpected("Unknown peer type."); + }(); + const auto userpic = std::make_shared(bg, QString()); + return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { + userpic->paintCircle(p, x, y, outerWidth, size); + using PeerType = Data::CreditsHistoryEntry::PeerType; + ((entry.peerType == PeerType::AppStore) + ? st::sessionIconiPhone + : (entry.peerType == PeerType::PlayMarket) + ? st::sessionIconAndroid + : st::introFragmentIcon).paintInCenter(p, { x, y, size, size }); + }; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h new file mode 100644 index 000000000..c7d8584df --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -0,0 +1,19 @@ +/* +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 + +namespace Data { +struct CreditsHistoryEntry; +} // namespace Data + +namespace Ui { + +Fn GenerateCreditsPaintUserpicCallback( + const Data::CreditsHistoryEntry &entry); + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 8c8cddac4..0a97c6b6d 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -356,6 +356,8 @@ PRIVATE ui/effects/fireworks_animation.h ui/effects/glare.cpp ui/effects/glare.h + ui/effects/credits_graphics.cpp + ui/effects/credits_graphics.h ui/effects/loading_element.cpp ui/effects/loading_element.h ui/effects/outline_segments.cpp From 43cb315f477d0f265e3fa10305c615c57565d01e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 23 May 2024 17:55:33 +0300 Subject: [PATCH 119/225] Replaced PeerId with BareId in credits history entries. --- Telegram/SourceFiles/api/api_credits.cpp | 4 ++-- Telegram/SourceFiles/data/data_credits.h | 2 +- .../info/statistics/info_statistics_list_controllers.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 717adb64a..61ce79870 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -39,11 +39,11 @@ namespace { }, [](const MTPDstarsTransactionPeerPremiumBot &) { return Data::CreditsHistoryEntry::PeerType::PremiumBot; }), - .peerId = tl.data().vpeer().match([](const HistoryPeerTL &p) { + .bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) { return peerFromMTP(p.vpeer()); }, [](const auto &) { return PeerId(0); - }), + }).value, }; } diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 0984cf8bf..7a241c87a 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -32,7 +32,7 @@ struct CreditsHistoryEntry final { uint64 credits = 0; QDateTime date; PeerType peerType; - PeerId peerId = PeerId(0); + uint64 bareId = 0; }; struct CreditsStatusSlice final { diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 24c37d4e1..5a7c7e1bc 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -908,8 +908,8 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { .creditIcon = _creditIcon, .rowHeight = computeListSt().item.height, }; - if (item.peerId) { - const auto peer = session().data().peer(item.peerId); + if (item.bareId) { + const auto peer = session().data().peer(PeerId(item.bareId)); return std::make_unique(peer, descriptor); } else { return std::make_unique(descriptor); From d1e914fb309f23299e8f404de18b83d82292ee6b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 23 May 2024 21:44:50 +0300 Subject: [PATCH 120/225] Added initial implementation of box for credits history entries. --- Telegram/Resources/langs/lang.strings | 5 + .../SourceFiles/boxes/gift_premium_box.cpp | 48 +++++++ Telegram/SourceFiles/boxes/gift_premium_box.h | 7 ++ .../boosts/giveaway/giveaway.style | 4 + .../info_statistics_list_controllers.cpp | 4 +- .../SourceFiles/settings/settings_credits.cpp | 118 ++++++++++++++++++ Telegram/SourceFiles/ui/effects/credits.style | 4 + .../ui/effects/credits_graphics.cpp | 9 ++ .../SourceFiles/ui/effects/credits_graphics.h | 3 + 9 files changed, 199 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d138d5f91..8cf16783d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2319,6 +2319,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_summary_in_toast_title" = "Stars Acquired"; "lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance."; "lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance."; +"lng_credits_box_history_entry_peer" = "Recipient"; +"lng_credits_box_history_entry_id" = "Transaction ID"; +"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; +"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}."; +"lng_credits_box_history_entry_about_link" = "here"; "lng_location_title" = "Location"; "lng_location_about" = "Display the location of your business on your account."; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 339e7ee30..ef87c6423 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_boosts.h" #include "data/data_changes.h" #include "data/data_channel.h" +#include "data/data_credits.h" #include "data/data_media_types.h" // Data::GiveawayStart. #include "data/data_peer_values.h" // Data::PeerPremiumValue. #include "data/data_session.h" @@ -1633,3 +1634,50 @@ void ResolveGiveawayInfo( messageId, crl::guard(controller, show)); } + +void AddCreditsHistoryEntryTable( + not_null controller, + not_null container, + const Data::CreditsHistoryEntry &entry) { + auto table = container->add( + object_ptr( + container, + st::giveawayGiftCodeTable), + st::giveawayGiftCodeTableMargin); + if (entry.bareId) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_peer(), + controller, + PeerId(entry.bareId)); + } + if (!entry.id.isEmpty()) { + constexpr auto kOneLineCount = 18; + const auto oneLine = entry.id.length() <= kOneLineCount; + auto label = object_ptr( + table, + rpl::single( + Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})), + oneLine + ? st::giveawayGiftCodeValue + : st::giveawayGiftCodeValueMultiline); + label->setClickHandlerFilter([=](const auto &...) { + TextUtilities::SetClipboardText( + TextForMimeData::Simple(entry.id)); + controller->showToast( + tr::lng_credits_box_history_entry_id_copied(tr::now)); + return false; + }); + AddTableRow( + table, + tr::lng_credits_box_history_entry_id(), + std::move(label), + st::giveawayGiftCodeValueMargin); + } + if (!entry.date.isNull()) { + AddTableRow( + table, + tr::lng_gift_link_label_date(), + rpl::single(Ui::Text::WithEntities(langDateTime(entry.date)))); + } +} diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index b054bc3a2..e3194b273 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -16,12 +16,14 @@ struct GiftCode; } // namespace Api namespace Data { +struct CreditsHistoryEntry; struct GiveawayStart; struct GiveawayResults; } // namespace Data namespace Ui { class GenericBox; +class VerticalLayout; } // namespace Ui namespace Window { @@ -71,3 +73,8 @@ void ResolveGiveawayInfo( MsgId messageId, std::optional start, std::optional results); + +void AddCreditsHistoryEntryTable( + not_null controller, + not_null container, + const Data::CreditsHistoryEntry &entry); diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style index 2368a616f..ef705ab2a 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style @@ -95,6 +95,10 @@ giveawayGiftCodeValue: FlatLabel(defaultFlatLabel) { linkUnderline: kLinkUnderlineNever; } } +giveawayGiftCodeValueMultiline: FlatLabel(giveawayGiftCodeValue) { + minWidth: 100px; + maxHeight: 0px; +} giveawayGiftCodeValueMargin: margins(13px, 9px, 13px, 9px); giveawayGiftCodePeerMargin: margins(11px, 6px, 11px, 4px); giveawayGiftCodeUserpic: UserpicButton(defaultUserpicButton) { diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 5a7c7e1bc..5154a3044 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -783,9 +783,7 @@ const Data::CreditsHistoryEntry &CreditsRow::entry() const { QString CreditsRow::generateName() { return !PeerListRow::special() ? PeerListRow::generateName() - : (_entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) - ? tr::lng_bot_username_description1_link(tr::now) - : tr::lng_credits_summary_history_entry_inner_in(tr::now); + : Ui::GenerateEntryName(_entry).text; } PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index a1fde01f4..44326a9af 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -8,7 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits.h" #include "api/api_credits.h" +#include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" +#include "data/data_session.h" #include "data/data_user.h" #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. #include "info/statistics/info_statistics_list_controllers.h" @@ -19,9 +21,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common_session.h" #include "statistics/widgets/chart_header_widget.h" #include "ui/boxes/boost_box.h" // Ui::StartFireworks. +#include "ui/controls/userpic_button.h" +#include "ui/effects/credits_graphics.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" #include "ui/image/image_prepare.h" +#include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/text/format_values.h" @@ -32,12 +37,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/tooltip.h" #include "ui/wrap/fade_wrap.h" +#include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include "styles/style_credits.h" +#include "styles/style_giveaway.h" #include "styles/style_info.h" #include "styles/style_layers.h" +#include "styles/style_premium.h" #include "styles/style_settings.h" #include "styles/style_statistics.h" @@ -455,7 +463,117 @@ void Credits::setupHistory(not_null container) { } }, inner->lifetime()); + const auto controller = _controller->parentController(); + const auto entryBox = [=]( + not_null box, + const Data::CreditsHistoryEntry &e) { + box->setStyle(st::giveawayGiftCodeBox); + box->setNoContentMargin(true); + + const auto content = box->verticalLayout(); + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + + const auto &stUser = st::boostReplaceUserpic; + const auto peer = e.bareId + ? _controller->session().data().peer(PeerId(e.bareId)) + : nullptr; + if (peer) { + content->add(object_ptr>( + content, + object_ptr(content, peer, stUser))); + } else { + const auto widget = content->add( + object_ptr>( + content, + object_ptr(content)))->entity(); + using Draw = Fn; + const auto draw = widget->lifetime().make_state( + Ui::GenerateCreditsPaintUserpicCallback(e)); + widget->resize(Size(stUser.photoSize)); + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(widget); + (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); + }, widget->lifetime()); + } + + Ui::AddSkip(content); + Ui::AddSkip(content); + + + box->addRow(object_ptr>( + box, + object_ptr( + box, + rpl::single(peer + ? peer->name() + : Ui::GenerateEntryName(e).text), + st::creditsBoxAboutTitle))); + + Ui::AddSkip(content); + + { + constexpr auto kMinus = QChar(0x2212); + auto &lifetime = content->lifetime(); + const auto text = lifetime.make_state( + st::semiboldTextStyle, + (!e.bareId ? QChar('+') : kMinus) + + Lang::FormatCountDecimal(e.credits)); + + const auto amount = content->add( + object_ptr( + content, + _star.height() / style::DevicePixelRatio())); + const auto font = text->style()->font; + amount->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(amount); + const auto starWidth = _star.width() + / style::DevicePixelRatio(); + const auto fullWidth = text->maxWidth() + + font->spacew * 2 + + starWidth; + p.setPen(!e.bareId + ? st::boxTextFgGood + : st::menuIconAttentionColor); + const auto x = (amount->width() - fullWidth) / 2; + text->draw(p, Ui::Text::PaintContext{ + .position = QPoint( + x, + (amount->height() - font->height) / 2), + .outerWidth = amount->width(), + .availableWidth = amount->width(), + });; + p.drawImage( + x + fullWidth - starWidth, + 0, + _star); + }, amount->lifetime()); + } + + Ui::AddSkip(content); + Ui::AddSkip(content); + + AddCreditsHistoryEntryTable( + controller, + box->verticalLayout(), + e); + + const auto button = box->addButton(tr::lng_box_ok(), [=] { + box->closeBox(); + }); + const auto buttonWidth = st::boxWidth + - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); + }; const auto entryClicked = [=](const Data::CreditsHistoryEntry &e) { + controller->uiShow()->show(Box(entryBox, e)); }; Info::Statistics::AddCreditsHistoryList( diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 19c73c7ba..9f6866f96 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -30,3 +30,7 @@ creditsBoxAbout: FlatLabel(defaultFlatLabel) { minWidth: 256px; align: align(top); } + +creditsBoxAboutTitle: FlatLabel(settingsPremiumUserTitle) { + minWidth: 256px; +} diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 555ee44c0..fd465f6b3 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "data/data_credits.h" +#include "lang/lang_keys.h" #include "ui/empty_userpic.h" #include "ui/painter.h" #include "styles/style_credits.h" @@ -52,4 +53,12 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( }; } +TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { + return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) + ? tr::lng_bot_username_description1_link + : tr::lng_credits_summary_history_entry_inner_in)( + tr::now, + TextWithEntities::Simple); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index c7d8584df..44d848748 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -16,4 +16,7 @@ namespace Ui { Fn GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry); +[[nodiscard]] TextWithEntities GenerateEntryName( + const Data::CreditsHistoryEntry &entry); + } // namespace Ui From 5ee2bca616f2d24830b15fb34f8ed54a4c9aa35b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 24 May 2024 09:39:52 +0400 Subject: [PATCH 121/225] Update API scheme on layer 181. --- Telegram/SourceFiles/mtproto/scheme/api.tl | 4 +- .../SourceFiles/payments/payments_form.cpp | 42 +++++++++++++++++++ Telegram/SourceFiles/payments/payments_form.h | 2 + 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index deb1d9e80..fb82b7ff9 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -910,6 +910,7 @@ payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult; payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult; payments.paymentReceipt#70c4fe03 flags:# date:int bot_id:long provider_id:long title:string description:string photo:flags.2?WebDocument invoice:Invoice info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption tip_amount:flags.3?long currency:string total_amount:long credentials_title:string users:Vector = payments.PaymentReceipt; +payments.paymentReceiptStars#dabbf83a flags:# date:int bot_id:long title:string description:string photo:flags.2?WebDocument invoice:Invoice currency:string total_amount:long transaction_id:string users:Vector = payments.PaymentReceipt; payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo; @@ -1802,7 +1803,7 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#5f6b790c id:string stars:long date:int peer:StarsTransactionPeer = StarsTransaction; +starsTransaction#cc7079b2 flags:# id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument = StarsTransaction; payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; @@ -2350,6 +2351,7 @@ payments.getStarsTopupOptions#c00ec7d3 = Vector; payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; payments.getStarsTransactions#673ac2f9 flags:# inbound:flags.0?true outbound:flags.1?true peer:InputPeer offset:string = payments.StarsStatus; payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payments.PaymentResult; +payments.refundStarsCharge#f090bbec user_id:InputUser msg_id:int charge_id:string = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 07a6b2a67..b6512d532 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -471,6 +471,17 @@ void Form::processReceipt(const MTPDpayments_paymentReceipt &data) { _updates.fire(FormReady{}); } +void Form::processReceipt(const MTPDpayments_paymentReceiptStars &data) { + _session->data().processUsers(data.vusers()); + + data.vinvoice().match([&](const auto &data) { + processInvoice(data); + }); + processDetails(data); + fillPaymentMethodInformation(); + _updates.fire(FormReady{}); +} + void Form::processInvoice(const MTPDinvoice &data) { const auto suggested = data.vsuggested_tip_amounts().value_or_empty(); _invoice = Ui::Invoice{ @@ -586,6 +597,37 @@ void Form::processDetails(const MTPDpayments_paymentReceipt &data) { } } +void Form::processDetails(const MTPDpayments_paymentReceiptStars &data) { + _invoice.receipt = Ui::Receipt{ + .date = data.vdate().v, + .totalAmount = ParsePriceAmount(data.vtotal_amount().v), + .currency = qs(data.vcurrency()), + .paid = true, + }; + _details = FormDetails{ + .botId = data.vbot_id().v, + }; + if (_invoice.cover.title.isEmpty() + && _invoice.cover.description.empty() + && _invoice.cover.thumbnail.isNull() + && !_thumbnailLoadProcess) { + _invoice.cover = Ui::Cover{ + .title = qs(data.vtitle()), + .description = { qs(data.vdescription()) }, + }; + if (const auto web = data.vphoto()) { + if (const auto photo = _session->data().photoFromWeb(*web, {})) { + loadThumbnail(photo); + } + } + } + if (_details.botId) { + if (const auto bot = _session->data().userLoaded(_details.botId)) { + _invoice.cover.seller = bot->name(); + } + } +} + void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) { const auto address = data.vshipping_address(); _savedInformation = _information = Ui::RequestedInformation{ diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 834113115..ffa6b17d7 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -301,9 +301,11 @@ private: void requestReceipt(); void processForm(const MTPpayments_PaymentForm &result); void processReceipt(const MTPDpayments_paymentReceipt &data); + void processReceipt(const MTPDpayments_paymentReceiptStars &data); void processInvoice(const MTPDinvoice &data); void processDetails(const MTPpayments_PaymentForm &result); void processDetails(const MTPDpayments_paymentReceipt &data); + void processDetails(const MTPDpayments_paymentReceiptStars &data); void processSavedInformation(const MTPDpaymentRequestedInfo &data); void processAdditionalPaymentMethods( const QVector &list); From b299881bf86ada72f246af54eed51c2d87634561 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 21 May 2024 18:15:56 +0400 Subject: [PATCH 122/225] Track factcheck text and create media. --- .../data/components/factchecks.cpp | 136 ++++++++++++++++++ .../SourceFiles/data/components/factchecks.h | 51 +++++++ Telegram/SourceFiles/data/data_session.cpp | 20 ++- .../admin_log/history_admin_log_inner.cpp | 2 + Telegram/SourceFiles/history/history_item.cpp | 34 +++++ Telegram/SourceFiles/history/history_item.h | 4 + .../history/history_item_components.cpp | 25 ++++ .../history/history_item_components.h | 25 ++++ .../SourceFiles/history/history_item_text.cpp | 11 ++ .../history/view/history_view_message.cpp | 23 +++ .../history/view/history_view_message.h | 5 + Telegram/SourceFiles/main/main_session.cpp | 2 + Telegram/SourceFiles/main/main_session.h | 5 + 13 files changed, 332 insertions(+), 11 deletions(-) create mode 100644 Telegram/SourceFiles/data/components/factchecks.cpp create mode 100644 Telegram/SourceFiles/data/components/factchecks.h diff --git a/Telegram/SourceFiles/data/components/factchecks.cpp b/Telegram/SourceFiles/data/components/factchecks.cpp new file mode 100644 index 000000000..4e5fcd090 --- /dev/null +++ b/Telegram/SourceFiles/data/components/factchecks.cpp @@ -0,0 +1,136 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/components/factchecks.h" + +#include "apiwrap.h" +#include "base/random.h" +#include "data/data_session.h" +#include "history/view/media/history_view_web_page.h" +#include "history/view/history_view_message.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" + +namespace Data { +namespace { + +constexpr auto kRequestDelay = crl::time(1000); + +} // namespace + +Factchecks::Factchecks(not_null session) +: _session(session) +, _requestTimer([=] { request(); }) { +} + +void Factchecks::requestFor(not_null item) { + subscribeIfNotYet(); + + if (const auto factcheck = item->Get()) { + factcheck->requested = true; + } + if (!_requestTimer.isActive()) { + _requestTimer.callOnce(kRequestDelay); + } + const auto changed = !_pending.empty() + && (_pending.front()->history() != item->history()); + const auto added = _pending.emplace(item).second; + if (changed) { + request(); + } else if (added && _pending.size() == 1) { + _requestTimer.callOnce(kRequestDelay); + } +} + +void Factchecks::subscribeIfNotYet() { + if (_subscribed) { + return; + } + _subscribed = true; + + _session->data().itemRemoved( + ) | rpl::start_with_next([=](not_null item) { + _pending.remove(item); + const auto i = ranges::find(_requested, item.get()); + if (i != end(_requested)) { + *i = nullptr; + } + }, _lifetime); +} + +void Factchecks::request() { + _requestTimer.cancel(); + + if (!_requested.empty() || _pending.empty()) { + return; + } + _session->api().request(base::take(_requestId)).cancel(); + + auto ids = QVector(); + ids.reserve(_pending.size()); + const auto history = _pending.front()->history(); + for (auto i = begin(_pending); i != end(_pending);) { + const auto &item = *i; + if (item->history() == history) { + _requested.push_back(item); + ids.push_back(MTP_int(item->id.bare)); + i = _pending.erase(i); + } else { + ++i; + } + } + _requestId = _session->api().request(MTPmessages_GetFactCheck( + history->peer->input, + MTP_vector(std::move(ids)) + )).done([=](const MTPVector &result) { + _requestId = 0; + const auto &list = result.v; + auto index = 0; + for (const auto &item : base::take(_requested)) { + if (!item) { + } else if (index >= list.size()) { + item->setFactcheck({}); + } else { + item->setFactcheck(FromMTP(item, &list[index])); + } + ++index; + } + if (!_pending.empty()) { + request(); + } + }).fail([=] { + _requestId = 0; + for (const auto &item : base::take(_requested)) { + if (item) { + item->setFactcheck({}); + } + } + if (!_pending.empty()) { + request(); + } + }).send(); +} + +std::unique_ptr Factchecks::makeMedia( + not_null view, + not_null factcheck) { + if (!factcheck->page) { + factcheck->page = view->history()->owner().webpage( + base::RandomValue(), + tr::lng_factcheck_title(tr::now), + factcheck->data.text); + } + return std::make_unique( + view, + factcheck->page, + MediaWebPageFlags()); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/components/factchecks.h b/Telegram/SourceFiles/data/components/factchecks.h new file mode 100644 index 000000000..452706f9b --- /dev/null +++ b/Telegram/SourceFiles/data/components/factchecks.h @@ -0,0 +1,51 @@ +/* +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/timer.h" + +class HistoryItem; +struct HistoryMessageFactcheck; + +namespace HistoryView { +class Message; +class WebPage; +} // namespace HistoryView + +namespace Main { +class Session; +} // namespace Main + +namespace Data { + +class Factchecks final { +public: + explicit Factchecks(not_null session); + + void requestFor(not_null item); + [[nodiscard]] std::unique_ptr makeMedia( + not_null view, + not_null factcheck); + +private: + void subscribeIfNotYet(); + void request(); + + const not_null _session; + + base::Timer _requestTimer; + base::flat_set> _pending; + std::vector _requested; + mtpRequestId _requestId = 0; + bool _subscribed = false; + + rpl::lifetime _lifetime; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index d101d5d88..529b933d4 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4250,29 +4250,27 @@ void Session::notifyPollUpdateDelayed(not_null poll) { } void Session::sendWebPageGamePollNotifications() { + auto resize = std::vector>(); for (const auto &page : base::take(_webpagesUpdated)) { _webpageUpdates.fire_copy(page); - const auto i = _webpageViews.find(page); - if (i != _webpageViews.end()) { - for (const auto &view : i->second) { - requestViewResize(view); - } + if (const auto i = _webpageViews.find(page) + ; i != _webpageViews.end()) { + resize.insert(end(resize), begin(i->second), end(i->second)); } } for (const auto &game : base::take(_gamesUpdated)) { if (const auto i = _gameViews.find(game); i != _gameViews.end()) { - for (const auto &view : i->second) { - requestViewResize(view); - } + resize.insert(end(resize), begin(i->second), end(i->second)); } } for (const auto &poll : base::take(_pollsUpdated)) { if (const auto i = _pollViews.find(poll); i != _pollViews.end()) { - for (const auto &view : i->second) { - requestViewResize(view); - } + resize.insert(end(resize), begin(i->second), end(i->second)); } } + for (const auto &view : resize) { + requestViewResize(view); + } } rpl::producer> Session::webPageUpdates() const { 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 61cf9a889..8fa440bf6 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -1306,6 +1306,8 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { && !link && (view->hasVisibleText() || mediaHasTextForCopy + || (item->Has() + && !item->Get()->data.text.empty()) || item->Has())) { _menu->addAction(tr::lng_context_copy_text(tr::now), [=] { copyContextText(itemId); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 7cb74b189..3a2ed484a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -415,6 +415,11 @@ HistoryItem::HistoryItem( } setReactions(data.vreactions()); applyTTL(data); + + if (const auto check = FromMTP(this, data.vfactcheck())) { + AddComponents(HistoryMessageFactcheck::Bit()); + Get()->data = check; + } } } @@ -1494,6 +1499,33 @@ void HistoryItem::addLogEntryOriginal( content); } +void HistoryItem::setFactcheck(MessageFactcheck info) { + if (!info) { + if (Has()) { + RemoveComponents(HistoryMessageFactcheck::Bit()); + history()->owner().requestItemResize(this); + } + } else { + AddComponents(HistoryMessageFactcheck::Bit()); + const auto factcheck = Get(); + if (factcheck->data.hash == info.hash + && (info.needCheck || !factcheck->data.needCheck)) { + return; + } else if (factcheck->data.text != info.text + || factcheck->data.country != info.country + || factcheck->data.hash != info.hash) { + factcheck->data = std::move(info); + factcheck->requested = false; + history()->owner().requestItemResize(this); + } + } +} + +bool HistoryItem::hasUnrequestedFactcheck() const { + const auto factcheck = Get(); + return factcheck && factcheck->data.needCheck && !factcheck->requested; +} + PeerData *HistoryItem::specialNotificationPeer() const { return (mentionsMe() && !_history->peer->isUser()) ? from().get() @@ -3143,6 +3175,8 @@ EffectId HistoryItem::effectId() const { bool HistoryItem::isEmpty() const { return _text.empty() && !_media + && (!Has() + || Get()->data.text.empty()) && !Has(); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 1b006e35a..5a864d076 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -23,9 +23,11 @@ struct HistoryMessageReplyMarkup; struct HistoryMessageTranslation; struct HistoryMessageForwarded; struct HistoryMessageSavedMediaData; +struct HistoryMessageFactcheck; struct HistoryServiceDependentData; enum class HistorySelfDestructType; struct PreparedServiceText; +struct MessageFactcheck; class ReplyKeyboard; struct LanguageId; @@ -204,6 +206,8 @@ public: WebPageId localId, const QString &label, const TextWithEntities &content); + void setFactcheck(MessageFactcheck info); + [[nodiscard]] bool hasUnrequestedFactcheck() const; [[nodiscard]] not_null notificationThread() const; [[nodiscard]] not_null history() const { diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index dc4a38c7a..54e42a16e 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -1062,6 +1062,31 @@ HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=( HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default; +MessageFactcheck FromMTP( + not_null item, + const tl::conditional &factcheck) { + auto result = MessageFactcheck(); + if (!factcheck) { + return result; + } + const auto &data = factcheck->data(); + if (const auto text = data.vtext()) { + const auto &data = text->data(); + result.text = { + qs(data.vtext()), + Api::EntitiesFromMTP( + &item->history()->session(), + data.ventities().v), + }; + } + if (const auto country = data.vcountry()) { + result.country = qs(country->v); + } + result.hash = data.vhash().v; + result.needCheck = data.is_need_check(); + return result; +} + HistoryDocumentCaptioned::HistoryDocumentCaptioned() : caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) { } diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 44f31f3cd..fa7ffbf3a 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -562,6 +562,31 @@ struct HistoryMessageLogEntryOriginal }; +struct MessageFactcheck { + TextWithEntities text; + QString country; + uint64 hash = 0; + bool needCheck = false; + + [[nodiscard]] bool empty() const { + return text.empty() && country.isEmpty() && !hash; + } + explicit operator bool() const { + return !empty(); + } +}; + +[[nodiscard]] MessageFactcheck FromMTP( + not_null item, + const tl::conditional &factcheck); + +struct HistoryMessageFactcheck +: public RuntimeComponent { + MessageFactcheck data; + WebPageData *page = nullptr; + bool requested = false; +}; + struct HistoryServiceData : public RuntimeComponent { std::vector textLinks; diff --git a/Telegram/SourceFiles/history/history_item_text.cpp b/Telegram/SourceFiles/history/history_item_text.cpp index 1c66c59a4..ba35168cf 100644 --- a/Telegram/SourceFiles/history/history_item_text.cpp +++ b/Telegram/SourceFiles/history/history_item_text.cpp @@ -46,6 +46,12 @@ TextForMimeData HistoryItemText(not_null item) { titleResult.append('\n').append(std::move(descriptionResult)); return titleResult; }(); + auto factcheckResult = [&] { + const auto factcheck = item->Get(); + return factcheck + ? TextForMimeData::Rich(base::duplicate(factcheck->data.text)) + : TextForMimeData(); + }(); auto result = textResult; if (result.empty()) { result = std::move(mediaResult); @@ -57,6 +63,11 @@ TextForMimeData HistoryItemText(not_null item) { } else if (!logEntryOriginalResult.empty()) { result.append(u"\n\n"_q).append(std::move(logEntryOriginalResult)); } + if (result.empty()) { + result = std::move(factcheckResult); + } else if (!factcheckResult.empty()) { + result.append(u"\n\n"_q).append(std::move(factcheckResult)); + } return result; } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index cf19c8f9a..861c96ee7 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/round_rect.h" #include "ui/text/text_utilities.h" #include "ui/power_saving.h" +#include "data/components/factchecks.h" #include "data/components/sponsored_messages.h" #include "data/data_session.h" #include "data/data_user.h" @@ -799,6 +800,24 @@ QSize Message::performCountOptimalSize() { RemoveComponents(Reply::Bit()); } + const auto factcheck = item->Get(); + if (factcheck && !factcheck->data.text.empty()) { + AddComponents(Factcheck::Bit()); + Get()->page = history()->session().factchecks().makeMedia( + this, + factcheck); + + auto copy = data()->originalText(); + if (!copy.text.contains("FACT CHECK")) { + copy.append("\n\nFACT CHECK!!\n\n").append(factcheck->data.text); + crl::on_main(this, [=] { + data()->setText(std::move(copy)); + }); + } + } else { + RemoveComponents(Factcheck::Bit()); + } + const auto markup = item->inlineReplyMarkup(); const auto reactionsKey = [&] { return embedReactionsInBottomInfo() @@ -1069,6 +1088,10 @@ void Message::draw(Painter &p, const PaintContext &context) const { const auto item = data(); const auto media = this->media(); + if (item->hasUnrequestedFactcheck()) { + item->history()->session().factchecks().requestFor(item); + } + const auto stm = context.messageStyle(); const auto bubble = drawBubble(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index a8c172c03..5f64193d7 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -44,6 +44,11 @@ struct LogEntryOriginal std::unique_ptr page; }; +struct Factcheck +: public RuntimeComponent { + std::unique_ptr page; +}; + struct PsaTooltipState : public RuntimeComponent { QString type; mutable ClickHandlerPtr link; diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index fc8435d66..951ec735a 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/file_upload.h" #include "storage/storage_account.h" #include "storage/storage_facade.h" +#include "data/components/factchecks.h" #include "data/components/recent_peers.h" #include "data/components/scheduled_messages.h" #include "data/components/sponsored_messages.h" @@ -105,6 +106,7 @@ Session::Session( , _scheduledMessages(std::make_unique(this)) , _sponsoredMessages(std::make_unique(this)) , _topPeers(std::make_unique(this)) +, _factchecks(std::make_unique(this)) , _cachedReactionIconFactory(std::make_unique()) , _supportHelper(Support::Helper::Create(this)) , _saveSettingsTimer([=] { saveSettings(); }) { diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 85aa2fe08..9581e7cd4 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -35,6 +35,7 @@ class RecentPeers; class ScheduledMessages; class SponsoredMessages; class TopPeers; +class Factchecks; } // namespace Data namespace HistoryView::Reactions { @@ -127,6 +128,9 @@ public: [[nodiscard]] Data::TopPeers &topPeers() const { return *_topPeers; } + [[nodiscard]] Data::Factchecks &factchecks() const { + return *_factchecks; + } [[nodiscard]] Api::Updates &updates() const { return *_updates; } @@ -254,6 +258,7 @@ private: const std::unique_ptr _scheduledMessages; const std::unique_ptr _sponsoredMessages; const std::unique_ptr _topPeers; + const std::unique_ptr _factchecks; using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory; const std::unique_ptr _cachedReactionIconFactory; From 923a9ec6a8122a887a839570107a6114c40ae2de Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 23 May 2024 13:07:03 +0400 Subject: [PATCH 123/225] Show toggle-able factcheck footer. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 6 + .../data/components/factchecks.cpp | 2 + Telegram/SourceFiles/data/data_web_page.h | 2 + .../admin_log/history_admin_log_item.cpp | 5 +- .../history/view/history_view_message.cpp | 196 ++++++++-- .../history/view/history_view_message.h | 2 + .../view/media/history_view_web_page.cpp | 361 +++++++++++------- .../view/media/history_view_web_page.h | 70 ++-- 9 files changed, 456 insertions(+), 190 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 8d1d2369d..a562048ec 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -466,6 +466,8 @@ PRIVATE data/business/data_business_info.h data/business/data_shortcut_messages.cpp data/business/data_shortcut_messages.h + data/components/factchecks.cpp + data/components/factchecks.h data/components/recent_peers.cpp data/components/recent_peers.h data/components/scheduled_messages.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8cf16783d..6b28b450e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3287,6 +3287,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_spoiler_effect" = "Hide with Spoiler"; "lng_context_disable_spoiler" = "Remove Spoiler"; +"lng_context_add_factcheck" = "Add Fact Check"; + +"lng_factcheck_title" = "Fact Check"; +"lng_factcheck_placeholder" = "Add Facts or Context"; +"lng_factcheck_whats_this" = "what's this?"; +"lng_factcheck_about" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation."; "lng_translate_show_original" = "Show Original"; "lng_translate_bar_to" = "Translate to {name}"; diff --git a/Telegram/SourceFiles/data/components/factchecks.cpp b/Telegram/SourceFiles/data/components/factchecks.cpp index 4e5fcd090..0423d1d43 100644 --- a/Telegram/SourceFiles/data/components/factchecks.cpp +++ b/Telegram/SourceFiles/data/components/factchecks.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/random.h" #include "data/data_session.h" +#include "data/data_web_page.h" #include "history/view/media/history_view_web_page.h" #include "history/view/history_view_message.h" #include "history/history.h" @@ -126,6 +127,7 @@ std::unique_ptr Factchecks::makeMedia( base::RandomValue(), tr::lng_factcheck_title(tr::now), factcheck->data.text); + factcheck->page->type = WebPageType::Factcheck; } return std::make_unique( view, diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index 8b82ed859..6497a0394 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -54,6 +54,8 @@ enum class WebPageType : uint8 { VoiceChat, Livestream, + + Factcheck, }; [[nodiscard]] WebPageType ParseWebPageType(const MTPDwebPage &type); [[nodiscard]] bool IgnoreIv(WebPageType type); 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 5e46d8d97..19f76be07 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -111,7 +111,8 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { | MTPDmessage::Flag::f_forwards //| MTPDmessage::Flag::f_reactions | MTPDmessage::Flag::f_restriction_reason - | MTPDmessage::Flag::f_ttl_period; + | MTPDmessage::Flag::f_ttl_period + | MTPDmessage::Flag::f_factcheck; return MTP_message( MTP_flags(data.vflags().v & ~removeFlags), data.vid(), @@ -141,7 +142,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { MTPint(), // ttl_period MTPint(), // quick_reply_shortcut_id MTP_long(data.veffect().value_or_empty()), - data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()); + MTPFactCheck()); }); } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 861c96ee7..564ea3b7b 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -586,10 +586,11 @@ void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) { } if (bubble) { - auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); + const auto entry = logEntryOriginal(); // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto inner = g; @@ -636,10 +637,11 @@ void Message::animateEffect(Ui::ReactionFlyAnimationArgs &&args) { _bottomInfo.animateEffect(args.translated(-bottomRight), repainter); }; if (bubble) { - auto entry = logEntryOriginal(); + const auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto inner = g; @@ -732,10 +734,11 @@ QRect Message::effectIconGeometry() const { bottomRight - QPoint(size.width(), size.height())); }; if (bubble) { - auto entry = logEntryOriginal(); + const auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto inner = g; @@ -806,14 +809,6 @@ QSize Message::performCountOptimalSize() { Get()->page = history()->session().factchecks().makeMedia( this, factcheck); - - auto copy = data()->originalText(); - if (!copy.text.contains("FACT CHECK")) { - copy.append("\n\nFACT CHECK!!\n\n").append(factcheck->data.text); - crl::on_main(this, [=] { - data()->setText(std::move(copy)); - }); - } } else { RemoveComponents(Factcheck::Bit()); } @@ -863,6 +858,7 @@ QSize Message::performCountOptimalSize() { const auto forwarded = item->Get(); const auto via = item->Get(); const auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); if (forwarded) { forwarded->create(via, item); } @@ -872,13 +868,16 @@ QSize Message::performCountOptimalSize() { mediaDisplayed = media->isDisplayed(); media->initDimensions(); } + if (check) { + check->initDimensions(); + } if (entry) { entry->initDimensions(); } // Entry page is always a bubble bottom. const auto withVisibleText = hasVisibleText(); - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); maxWidth = plainMaxWidth(); if (context() == Context::Replies && item->isDiscussionPost()) { @@ -918,6 +917,7 @@ QSize Message::performCountOptimalSize() { minHeight += st::msgPadding.top(); if (mediaDisplayed) minHeight += st::mediaInBubbleSkip; if (entry) minHeight += st::mediaInBubbleSkip; + if (check) minHeight += st::mediaInBubbleSkip; } if (mediaDisplayed) { // Parts don't participate in maxWidth() in case of media message. @@ -1002,6 +1002,10 @@ QSize Message::performCountOptimalSize() { + st::msgPadding.right(); accumulate_max(maxWidth, replyw); } + if (check) { + accumulate_max(maxWidth, check->maxWidth()); + minHeight += check->minHeight(); + } if (entry) { accumulate_max(maxWidth, entry->maxWidth()); minHeight += entry->minHeight(); @@ -1121,11 +1125,12 @@ void Message::draw(Painter &p, const PaintContext &context) const { return; } - auto entry = logEntryOriginal(); + const auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); auto mediaDisplayed = media && media->isDisplayed(); // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); const auto displayInfo = needInfoDisplay(); @@ -1150,6 +1155,9 @@ void Message::draw(Painter &p, const PaintContext &context) const { if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) { localMediaBottom -= st::msgPadding.bottom(); } + if (check) { + localMediaBottom -= check->height(); + } if (entry) { localMediaBottom -= entry->height(); } @@ -1299,6 +1307,9 @@ void Message::draw(Painter &p, const PaintContext &context) const { if (entry) { trect.setHeight(trect.height() - entry->height()); } + if (check) { + trect.setHeight(trect.height() - check->height()); + } if (displayInfo) { trect.setHeight(trect.height() - (_bottomInfo.height() - st::msgDateFont->height)); @@ -1358,6 +1369,19 @@ void Message::draw(Painter &p, const PaintContext &context) const { } } } + if (check) { + auto checkLeft = inner.left(); + auto checkTop = trect.y() + trect.height(); + p.translate(checkLeft, checkTop); + auto checkContext = context.translated(checkLeft, -checkTop); + checkContext.selection = skipTextSelection(context.selection); + if (mediaDisplayed) { + checkContext.selection = media->skipSelection( + checkContext.selection); + } + check->draw(p, checkContext); + p.translate(-checkLeft, -checkTop); + } if (entry) { auto entryLeft = inner.left(); auto entryTop = trect.y() + trect.height(); @@ -1924,10 +1948,11 @@ PointState Message::pointState(QPoint point) const { } if (const auto mediaDisplayed = media && media->isDisplayed()) { // Hack for grouped media point state. - auto entry = logEntryOriginal(); + const auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); if (item->repliesAreComments() || item->externalReply()) { g.setHeight(g.height() - st::historyCommentsButtonHeight); @@ -1959,6 +1984,10 @@ PointState Message::pointState(QPoint point) const { // if (getStateReplyInfo(point, trect, &result)) return result; // if (getStateViaBotIdInfo(point, trect, &result)) return result; //} + if (check) { + auto checkHeight = check->height(); + trect.setHeight(trect.height() - checkHeight); + } if (entry) { auto entryHeight = entry->height(); trect.setHeight(trect.height() - entryHeight); @@ -1995,6 +2024,9 @@ void Message::clickHandlerPressedChanged( } } Element::clickHandlerPressedChanged(handler, pressed); + if (const auto check = factcheckBlock()) { + check->clickHandlerPressedChanged(handler, pressed); + } if (!handler) { return; } else if (_rightAction && (handler == _rightAction->link)) { @@ -2306,10 +2338,11 @@ TextState Message::textState( if (bubble) { const auto inBubble = g.contains(point); - auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); + const auto entry = logEntryOriginal(); // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); auto inner = g; @@ -2393,9 +2426,23 @@ TextState Message::textState( + visibleMediaTextLength(); } } + if (check) { + auto checkHeight = check->height(); + trect.setHeight(trect.height() - checkHeight); + auto checkLeft = inner.left(); + auto checkTop = trect.y() + trect.height(); + if (point.y() >= checkTop && point.y() < checkTop + checkHeight) { + result = check->textState( + point - QPoint(checkLeft, checkTop), + request); + result.symbol += visibleTextLength() + + visibleMediaTextLength(); + } + } auto checkBottomInfoState = [&] { - if (mediaOnBottom && (entry || media->customInfoLayout())) { + if (mediaOnBottom + && (check || entry || media->customInfoLayout())) { return; } const auto bottomInfoResult = bottomInfoTextState( @@ -2862,6 +2909,7 @@ void Message::updatePressed(QPoint point) { TextForMimeData Message::selectedText(TextSelection selection) const { const auto media = this->media(); auto logEntryOriginalResult = TextForMimeData(); + auto factcheckResult = TextForMimeData(); const auto mediaDisplayed = (media && media->isDisplayed()); const auto mediaBefore = mediaDisplayed && invertMedia(); const auto textSelection = mediaBefore @@ -2876,7 +2924,15 @@ TextForMimeData Message::selectedText(TextSelection selection) const { auto mediaResult = (mediaDisplayed || isHiddenByGroup()) ? media->selectedText(mediaSelection) : TextForMimeData(); - if (auto entry = logEntryOriginal()) { + if (const auto check = factcheckBlock()) { + const auto checkSelection = mediaBefore + ? skipTextSelection(textSelection) + : mediaDisplayed + ? media->skipSelection(mediaSelection) + : skipTextSelection(selection); + factcheckResult = check->selectedText(checkSelection); + } + if (const auto entry = logEntryOriginal()) { const auto originalSelection = mediaBefore ? skipTextSelection(textSelection) : mediaDisplayed @@ -2892,6 +2948,11 @@ TextForMimeData Message::selectedText(TextSelection selection) const { } else if (!second.empty()) { result.append(u"\n\n"_q).append(std::move(second)); } + if (result.empty()) { + result = std::move(factcheckResult); + } else if (!factcheckResult.empty()) { + result.append(u"\n\n"_q).append(std::move(factcheckResult)); + } if (result.empty()) { result = std::move(logEntryOriginalResult); } else if (!logEntryOriginalResult.empty()) { @@ -2981,6 +3042,21 @@ TextSelection Message::adjustSelection( ? mediaAdjusted : unskipTextSelection(mediaAdjusted); } + auto checkResult = TextSelection(); + if (const auto check = factcheckBlock()) { + auto checkSelection = !mediaDisplayed + ? skipTextSelection(selection) + : mediaBefore + ? skipTextSelection(textSelection) + : media->skipSelection(mediaSelection); + auto checkAdjusted = useSelection(checkSelection, true) + ? check->adjustSelection(checkSelection, type) + : checkSelection; + checkResult = unskipTextSelection(checkAdjusted); + if (mediaDisplayed) { + checkResult = media->unskipSelection(checkResult); + } + } auto entryResult = TextSelection(); if (const auto entry = logEntryOriginal()) { auto entrySelection = !mediaDisplayed @@ -3003,6 +3079,12 @@ TextSelection Message::adjustSelection( std::max(result.to, mediaResult.to), }; } + if (!checkResult.empty()) { + result = result.empty() ? checkResult : TextSelection{ + std::min(result.from, checkResult.from), + std::max(result.to, checkResult.to), + }; + } if (!entryResult.empty()) { result = result.empty() ? entryResult : TextSelection{ std::min(result.from, entryResult.from), @@ -3410,6 +3492,13 @@ WebPage *Message::logEntryOriginal() const { return nullptr; } +WebPage *Message::factcheckBlock() const { + if (const auto entry = Get()) { + return entry->page.get(); + } + return nullptr; +} + bool Message::toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const { if (_comments && _comments->link == handler) { @@ -3539,7 +3628,9 @@ bool Message::drawBubble() const { const auto item = data(); if (isHidden()) { return false; - } else if (logEntryOriginal() || item->isFakeAboutView()) { + } else if (logEntryOriginal() + || factcheckBlock() + || item->isFakeAboutView()) { return true; } const auto media = this->media(); @@ -3560,7 +3651,7 @@ bool Message::unwrapped() const { const auto item = data(); if (isHidden()) { return true; - } else if (logEntryOriginal()) { + } else if (logEntryOriginal() || factcheckBlock()) { return false; } const auto media = this->media(); @@ -3921,8 +4012,22 @@ void Message::updateMediaInBubbleState() { || Has() || item->Has(); }; - auto entry = logEntryOriginal(); - if (entry) { + const auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); + if (check) { + mediaHasSomethingBelow = true; + mediaHasSomethingAbove = getMediaHasSomethingAbove(); + auto checkState = (mediaHasSomethingAbove + || hasVisibleText() + || (media && media->isDisplayed())) + ? MediaInBubbleState::Bottom + : MediaInBubbleState::None; + check->setInBubbleState(checkState); + if (!media) { + check->setBubbleRounding(countBubbleRounding()); + return; + } + } else if (entry) { mediaHasSomethingBelow = true; mediaHasSomethingAbove = getMediaHasSomethingAbove(); auto entryState = (mediaHasSomethingAbove @@ -3947,7 +4052,7 @@ void Message::updateMediaInBubbleState() { return; } - if (!entry) { + if (!check && !entry) { mediaHasSomethingAbove = getMediaHasSomethingAbove(); } if (!invertMedia() && hasVisibleText()) { @@ -4214,10 +4319,11 @@ int Message::resizeContentGetHeight(int newWidth) { if (bubble) { auto reply = Get(); auto via = item->Get(); - auto entry = logEntryOriginal(); + const auto check = factcheckBlock(); + const auto entry = logEntryOriginal(); // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); if (reactionsInBubble) { @@ -4226,12 +4332,20 @@ int Message::resizeContentGetHeight(int newWidth) { if (contentWidth == maxWidth()) { if (mediaDisplayed) { + if (check) { + newHeight += check->resizeGetHeight(contentWidth); + } if (entry) { newHeight += entry->resizeGetHeight(contentWidth); } - } else if (entry) { - // In case of text-only message it is counted in minHeight already. - entry->resizeGetHeight(contentWidth); + } else { + if (check) { + check->resizeGetHeight(contentWidth); + } + if (entry) { + // In case of text-only message it is counted in minHeight already. + entry->resizeGetHeight(contentWidth); + } } } else { const auto withVisibleText = hasVisibleText(); @@ -4251,15 +4365,24 @@ int Message::resizeContentGetHeight(int newWidth) { if (!mediaOnTop) { newHeight += st::msgPadding.top(); if (mediaDisplayed) newHeight += st::mediaInBubbleSkip; + if (check) newHeight += st::mediaInBubbleSkip; if (entry) newHeight += st::mediaInBubbleSkip; } if (mediaDisplayed) { newHeight += media->height(); + if (check) { + newHeight += check->resizeGetHeight(contentWidth); + } + if (entry) { + newHeight += entry->resizeGetHeight(contentWidth); + } + } else { + if (check) { + newHeight += check->resizeGetHeight(contentWidth); + } if (entry) { newHeight += entry->resizeGetHeight(contentWidth); } - } else if (entry) { - newHeight += entry->resizeGetHeight(contentWidth); } if (reactionsInBubble) { if (!mediaDisplayed || _viewButton) { @@ -4343,9 +4466,12 @@ int Message::resizeContentGetHeight(int newWidth) { bool Message::needInfoDisplay() const { const auto media = this->media(); const auto mediaDisplayed = media ? media->isDisplayed() : false; + const auto check = factcheckBlock(); const auto entry = logEntryOriginal(); return entry ? !entry->customInfoLayout() + : check + ? !check->customInfoLayout() : ((mediaDisplayed && media->isBubbleBottom()) ? !media->customInfoLayout() : true); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 5f64193d7..240e1a47a 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -47,6 +47,7 @@ struct LogEntryOriginal struct Factcheck : public RuntimeComponent { std::unique_ptr page; + bool expanded = false; }; struct PsaTooltipState : public RuntimeComponent { @@ -294,6 +295,7 @@ private: [[nodiscard]] int viewButtonHeight() const; [[nodiscard]] WebPage *logEntryOriginal() const; + [[nodiscard]] WebPage *factcheckBlock() const; [[nodiscard]] ClickHandlerPtr createGoToCommentsLink() const; [[nodiscard]] ClickHandlerPtr psaTooltipLink() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 0181d7da3..77d6fa5ea 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_web_page.h" #include "core/application.h" +#include "countries/countries_instance.h" #include "base/qt/qt_key_modifiers.h" #include "window/window_session_controller.h" #include "iv/iv_instance.h" @@ -19,13 +20,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_web_page.h" -#include "history/history.h" -#include "history/history_item_components.h" -#include "history/view/history_view_cursor_state.h" -#include "history/view/history_view_reply.h" -#include "history/view/history_view_sponsored_click_handler.h" #include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_sticker.h" +#include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_message.h" +#include "history/view/history_view_reply.h" +#include "history/view/history_view_sponsored_click_handler.h" +#include "history/history.h" +#include "history/history_item_components.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "menu/menu_sponsored.h" @@ -36,13 +38,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "styles/style_chat.h" namespace HistoryView { namespace { constexpr auto kMaxOriginalEntryLines = 8192; +constexpr auto kFactcheckCollapsedLines = 3; constexpr auto kStickerSetLines = 3; +constexpr auto kFactcheckAboutDuration = 5 * crl::time(1000); [[nodiscard]] int ArticleThumbWidth(not_null thumb, int height) { const auto size = thumb->location(Data::PhotoSize::Thumbnail); @@ -148,6 +153,39 @@ constexpr auto kStickerSetLines = 3; }); } +[[nodiscard]] ClickHandlerPtr AboutFactcheckClickHandler(QString iso2) { + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + const auto show = my.show + ? my.show + : controller + ? controller->uiShow() + : nullptr; + if (show) { + const auto name = Countries::Instance().countryNameByISO2(iso2); + const auto use = name.isEmpty() ? iso2 : name; + show->showToast({ + .text = { tr::lng_factcheck_about(tr::now, lt_country, use) }, + .duration = kFactcheckAboutDuration, + }); + } + }); +} + +[[nodiscard]] ClickHandlerPtr ToggleFactcheckClickHandler( + not_null view) { + const auto weak = base::make_weak(view); + return std::make_shared([=](ClickContext context) { + if (const auto strong = weak.get()) { + if (const auto factcheck = strong->Get()) { + factcheck->expanded = !factcheck->expanded; + strong->history()->owner().requestViewResize(strong); + } + } + }); +} + [[nodiscard]] TextWithEntities PageToPhrase(not_null page) { const auto type = page->type; const auto text = Ui::Text::Upper(page->iv @@ -234,32 +272,55 @@ WebPage::WebPage( : Media(parent) , _st(st::historyPagePreview) , _data(data) -, _sponsoredData([&]() -> std::optional { - if (!(flags & MediaWebPageFlag::Sponsored)) { - return std::nullopt; - } - const auto &session = _parent->data()->history()->session(); - const auto details = session.sponsoredMessages().lookupDetails( - _parent->data()->fullId()); - auto result = std::make_optional(); - result->buttonText = details.buttonText; - result->isLinkInternal = details.isLinkInternal; - result->backgroundEmojiId = details.backgroundEmojiId; - result->colorIndex = details.colorIndex; - result->canReport = details.canReport; - return result; -}()) +, _flags(flags) , _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) , _title(st::msgMinWidth - _st.padding.left() - _st.padding.right()) -, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) -, _flags(flags) { +, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) { history()->owner().registerWebPageView(_data, _parent); } +void WebPage::setupAdditionalData() { + if (_flags & MediaWebPageFlag::Sponsored) { + _additionalData = std::make_unique(SponsoredData()); + const auto raw = sponsoredData(); + const auto &session = _parent->data()->history()->session(); + const auto details = session.sponsoredMessages().lookupDetails( + _parent->data()->fullId()); + raw->buttonText = details.buttonText; + raw->isLinkInternal = details.isLinkInternal ? 1 : 0; + raw->backgroundEmojiId = details.backgroundEmojiId; + raw->colorIndex = details.colorIndex; + raw->canReport = details.canReport ? 1 : 0; + } else if (_data->stickerSet) { + _additionalData = std::make_unique(StickerSetData()); + const auto raw = stickerSetData(); + for (const auto &sticker : _data->stickerSet->items) { + if (!sticker->sticker()) { + continue; + } + raw->views.push_back( + std::make_unique(_parent, sticker, true)); + } + const auto side = std::ceil(std::sqrt(raw->views.size())); + const auto box = UnitedLineHeight() * kStickerSetLines; + const auto single = box / side; + for (const auto &view : raw->views) { + view->setWebpagePart(); + view->initSize(single); + } + } else if (_data->type == WebPageType::Factcheck) { + _additionalData = std::make_unique(FactcheckData()); + } +} + QSize WebPage::countOptimalSize() { if (_data->pendingTill || _data->failed) { return { 0, 0 }; } + setupAdditionalData(); + + const auto sponsored = sponsoredData(); + const auto factcheck = factcheckData(); // Detect _openButtonWidth before counting paddings. _openButton = Ui::Text::String(); @@ -274,12 +335,10 @@ QSize WebPage::countOptimalSize() { PageToPhrase(_data), kMarkupTextOptions, context); - } else if (_sponsoredData) { - if (!_sponsoredData->buttonText.isEmpty()) { - _openButton.setText( - st::semiboldTextStyle, - Ui::Text::Upper(_sponsoredData->buttonText)); - } + } else if (sponsored && !sponsored->buttonText.isEmpty()) { + _openButton.setText( + st::semiboldTextStyle, + Ui::Text::Upper(sponsored->buttonText)); } const auto padding = inBubblePadding() + innerMargin(); @@ -296,25 +355,7 @@ QSize WebPage::countOptimalSize() { } const auto lineHeight = UnitedLineHeight(); - if (_data->stickerSet && !_stickerSet) { - _stickerSet = std::make_unique(); - for (const auto &sticker : _data->stickerSet->items) { - if (!sticker->sticker()) { - continue; - } - _stickerSet->views.push_back( - std::make_unique(_parent, sticker, true)); - } - const auto side = std::ceil(std::sqrt(_stickerSet->views.size())); - const auto box = lineHeight * kStickerSetLines; - const auto single = box / side; - for (const auto &view : _stickerSet->views) { - view->setWebpagePart(); - view->initSize(single); - } - } - - if (!_openl && (!_data->url.isEmpty() || _sponsoredData)) { + if (!_openl && (!_data->url.isEmpty() || sponsored || factcheck)) { const auto original = _parent->data()->originalText(); const auto previewOfHiddenUrl = [&] { if (_data->type == WebPageType::BotApp) { @@ -352,28 +393,31 @@ QSize WebPage::countOptimalSize() { } return true; }(); - _openl = _data->iv - ? IvClickHandler(_data, original) - : (previewOfHiddenUrl || UrlClickHandler::IsSuspicious( - _data->url)) - ? std::make_shared(_data->url) - : std::make_shared(_data->url, true); - if (_data->document + if (sponsored) { + _openl = SponsoredLink(_data->url, sponsored->isLinkInternal); + if (sponsored->canReport) { + sponsored->hint.link = AboutSponsoredClickHandler(); + } + } else if (factcheck) { + const auto item = _parent->data(); + if (const auto info = item->Get()) { + const auto country = info->data.country; + factcheck->hint.link = AboutFactcheckClickHandler(country); + } + } else if (_data->document && (_data->document->isWallPaper() || _data->document->isTheme())) { _openl = std::make_shared( std::move(_openl), _data->document, _parent->data()->fullId()); - } - if (_sponsoredData) { - _openl = SponsoredLink( - _data->url, - _sponsoredData->isLinkInternal); - - if (_sponsoredData->canReport) { - _sponsoredData->hintLink = AboutSponsoredClickHandler(); - } + } else { + _openl = _data->iv + ? IvClickHandler(_data, original) + : (previewOfHiddenUrl || UrlClickHandler::IsSuspicious( + _data->url)) + ? std::make_shared(_data->url) + : std::make_shared(_data->url, true); } } @@ -456,7 +500,12 @@ QSize WebPage::countOptimalSize() { const auto siteNameHeight = _siteName.isEmpty() ? 0 : lineHeight; const auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; - const auto descMaxLines = isLogEntryOriginal() + const auto factcheckMetrics = factcheck + ? computeFactcheckMetrics(_description.minHeight()) + : FactcheckMetrics(); + const auto descMaxLines = factcheck + ? factcheckMetrics.lines + : isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1)); const auto descriptionMinHeight = _description.isEmpty() @@ -517,15 +566,16 @@ QSize WebPage::countOptimalSize() { if (_asArticle) { minHeight = resizeGetHeight(maxWidth); } - if (_sponsoredData && _sponsoredData->canReport) { - _sponsoredData->widthBeforeHint - = st::webPageTitleStyle.font->width(siteName); + if (const auto hint = hintData()) { + hint->widthBefore = st::webPageTitleStyle.font->width(siteName); const auto &font = st::webPageSponsoredHintFont; - _sponsoredData->hintSize = QSize( - font->width(tr::lng_sponsored_message_revenue_button(tr::now)) - + font->height, + hint->text = sponsored + ? tr::lng_sponsored_message_revenue_button(tr::now) + : tr::lng_factcheck_whats_this(tr::now); + hint->size = QSize( + font->width(hint->text) + font->height, font->height); - maxWidth += _sponsoredData->hintSize.width(); + maxWidth += hint->size.width(); } return { maxWidth, minHeight }; } @@ -539,9 +589,22 @@ QSize WebPage::countCurrentSize(int newWidth) { const auto innerWidth = newWidth - rect::m::sum::h(padding); auto newHeight = 0; - const auto specialRightPix = (_sponsoredData || _stickerSet); + const auto stickerSet = stickerSetData(); + const auto factcheck = factcheckData(); + const auto specialRightPix = (sponsoredData() || stickerSet); const auto lineHeight = UnitedLineHeight(); - const auto linesMax = (specialRightPix || isLogEntryOriginal()) + const auto factcheckMetrics = factcheck + ? computeFactcheckMetrics(_description.countHeight(innerWidth)) + : FactcheckMetrics(); + if (factcheck) { + factcheck->expandable = factcheckMetrics.expandable; + _openl = factcheck->expandable + ? ToggleFactcheckClickHandler(_parent) + : nullptr; + } + const auto linesMax = factcheck + ? (factcheckMetrics.lines + 1) + : (specialRightPix || isLogEntryOriginal()) ? kMaxOriginalEntryLines : 5; const auto siteNameHeight = _siteNameLines ? lineHeight : 0; @@ -550,7 +613,7 @@ QSize WebPage::countCurrentSize(int newWidth) { if (asArticle() || specialRightPix) { constexpr auto kSponsoredUserpicLines = 2; _pixh = lineHeight - * (_stickerSet + * (stickerSet ? kStickerSetLines : specialRightPix ? kSponsoredUserpicLines @@ -673,8 +736,14 @@ void WebPage::ensurePhotoMediaCreated() const { } bool WebPage::hasHeavyPart() const { + if (const auto stickerSet = stickerSetData()) { + for (const auto &part : stickerSet->views) { + if (part->hasHeavyPart()) { + return true; + } + } + } return _photoMedia - || (_stickerSet) || (_attach ? _attach->hasHeavyPart() : false); } @@ -684,6 +753,11 @@ void WebPage::unloadHeavyPart() { } _description.unloadPersistentAnimation(); _photoMedia = nullptr; + if (const auto stickerSet = stickerSetData()) { + for (const auto &part : stickerSet->views) { + part->unloadHeavyPart(); + } + } } void WebPage::draw(Painter &p, const PaintContext &context) const { @@ -704,22 +778,22 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { auto tshift = inner.top(); auto paintw = inner.width(); - const auto asSponsored = (!!_sponsoredData); + const auto sponsored = sponsoredData(); const auto selected = context.selected(); const auto view = parent(); const auto from = view->data()->contentColorsFrom(); - const auto colorIndex = (asSponsored && _sponsoredData->colorIndex) - ? _sponsoredData->colorIndex + const auto colorIndex = (sponsored && sponsored->colorIndex) + ? sponsored->colorIndex : from ? from->colorIndex() : view->colorIndex(); const auto cache = context.outbg ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() : st->coloredReplyCache(selected, colorIndex).get(); - const auto backgroundEmojiId = (asSponsored - && _sponsoredData->backgroundEmojiId) - ? _sponsoredData->backgroundEmojiId + const auto backgroundEmojiId = (sponsored + && sponsored->backgroundEmojiId) + ? sponsored->backgroundEmojiId : from ? from->backgroundEmojiId() : DocumentId(); @@ -755,8 +829,8 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { } auto lineHeight = UnitedLineHeight(); - if (_stickerSet) { - const auto viewsCount = _stickerSet->views.size(); + if (const auto stickerSet = stickerSetData()) { + const auto viewsCount = stickerSet->views.size(); const auto box = _pixh; const auto topLeft = QPoint(inner.left() + paintw - box, tshift); const auto side = std::ceil(std::sqrt(viewsCount)); @@ -767,7 +841,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { if (viewsCount <= index) { break; } - const auto &view = _stickerSet->views[index]; + const auto &view = stickerSet->views[index]; const auto size = view->countOptimalSize(); const auto offsetX = (single - size.width()) / 2.; const auto offsetY = (single - size.height()) / 2.; @@ -822,7 +896,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { st->msgSelectOverlay(), st->msgSelectOverlayCorners(Ui::CachedCornerRadius::Small)); } - if (!asSponsored) { + if (!sponsored) { // Ignore photo width in sponsored messages, // as its width only affects the title. paintw -= pw + st::webPagePhotoDelta; @@ -850,35 +924,31 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { endskip, false, context.selection); - if (asSponsored - && _sponsoredData->canReport - && (paintw > - _sponsoredData->widthBeforeHint - + _sponsoredData->hintSize.width())) { - if (_sponsoredData->hintRipple) { - _sponsoredData->hintRipple->paint( - p, - _sponsoredData->lastHintPos.x(), - _sponsoredData->lastHintPos.y(), - width(), - &cache->bg); - if (_sponsoredData->hintRipple->empty()) { - _sponsoredData->hintRipple = nullptr; - } - } - + const auto hint = hintData(); + if (hint && (paintw > hint->widthBefore + hint->size.width())) { auto color = cache->icon; color.setAlphaF(color.alphaF() * 0.15); const auto height = st::webPageSponsoredHintFont->height; const auto radius = height / 2; - _sponsoredData->lastHintPos = QPointF( - radius + inner.left() + _sponsoredData->widthBeforeHint, + hint->lastPosition = QPointF( + radius + inner.left() + hint->widthBefore, tshift + (_siteName.style()->font->height - height) / 2.); - const auto rect = QRectF( - _sponsoredData->lastHintPos, - _sponsoredData->hintSize); + + if (hint->ripple) { + hint->ripple->paint( + p, + hint->lastPosition.x(), + hint->lastPosition.y(), + width(), + &cache->bg); + if (hint->ripple->empty()) { + hint->ripple = nullptr; + } + } + + const auto rect = QRectF(hint->lastPosition, hint->size); auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); p.setBrush(color); @@ -887,10 +957,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { p.setPen(cache->icon); p.setBrush(Qt::NoBrush); p.setFont(st::webPageSponsoredHintFont); - p.drawText( - rect, - tr::lng_sponsored_message_revenue_button(tr::now), - style::al_center); + p.drawText(rect, hint->text, style::al_center); } tshift += lineHeight; @@ -901,7 +968,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { const auto endskip = _title.hasSkipBlock() ? _parent->skipBlockWidth() : 0; - const auto titleWidth = asSponsored + const auto titleWidth = sponsored ? (paintw - _pixh - st::webPagePhotoDelta) : paintw; _title.drawLeftElided( @@ -1051,16 +1118,38 @@ bool WebPage::asArticle() const { return _asArticle && (_data->photo != nullptr); } +WebPage::StickerSetData *WebPage::stickerSetData() const { + return std::get_if(_additionalData.get()); +} + +WebPage::SponsoredData *WebPage::sponsoredData() const { + return std::get_if(_additionalData.get()); +} + +WebPage::FactcheckData *WebPage::factcheckData() const { + return std::get_if(_additionalData.get()); +} + +WebPage::HintData *WebPage::hintData() const { + if (const auto sponsored = sponsoredData()) { + return sponsored->hint.link ? &sponsored->hint : nullptr; + } else if (const auto factcheck = factcheckData()) { + return factcheck->hint.link ? &factcheck->hint : nullptr; + } + return nullptr; +} + TextState WebPage::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < rect::m::sum::h(st::msgPadding) + 1) { return result; } + const auto sponsored = sponsoredData(); const auto bubble = _attach ? _attach->bubbleMargins() : QMargins(); const auto full = Rect(currentSize()); auto outer = full - inBubblePadding(); - if (_sponsoredData) { + if (sponsored) { outer.translate(0, st::msgDateFont->height); } const auto inner = outer - innerMargin(); @@ -1175,16 +1264,15 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { } } } - if ((!result.link || _sponsoredData) && outer.contains(point)) { + if ((!result.link || sponsored) && outer.contains(point)) { result.link = _openl; } - if (_sponsoredData && _sponsoredData->canReport) { - const auto contains = QRectF( - _sponsoredData->lastHintPos, - _sponsoredData->hintSize).contains(point - - QPoint(0, st::msgDateFont->height)); - if (contains) { - result.link = _sponsoredData->hintLink; + if (const auto hint = hintData()) { + const auto check = point + - QPoint(0, sponsored ? st::msgDateFont->height : 0); + const auto hintRect = QRectF(hint->lastPosition, hint->size); + if (hintRect.contains(check)) { + result.link = hint->link; } } _lastPoint = point - outer.topLeft(); @@ -1256,25 +1344,25 @@ void WebPage::clickHandlerActiveChanged( void WebPage::clickHandlerPressedChanged( const ClickHandlerPtr &p, bool pressed) { - if (_sponsoredData && _sponsoredData->hintLink == p) { + const auto hint = hintData(); + if (hint && hint->link == p) { if (pressed) { - if (!_sponsoredData->hintRipple) { + if (!hint->ripple) { const auto owner = &parent()->history()->owner(); - auto ripple = std::make_unique( + hint->ripple = std::make_unique( st::defaultRippleAnimation, Ui::RippleAnimation::RoundRectMask( - _sponsoredData->hintSize, + hint->size, _st.radius), [=] { owner->requestViewRepaint(parent()); }); - _sponsoredData->hintRipple = std::move(ripple); } const auto full = Rect(currentSize()); const auto outer = full - inBubblePadding(); - _sponsoredData->hintRipple->add(_lastPoint + hint->ripple->add(_lastPoint + outer.topLeft() - - _sponsoredData->lastHintPos.toPoint()); - } else if (_sponsoredData->hintRipple) { - _sponsoredData->hintRipple->lastStop(); + - hint->lastPosition.toPoint()); + } else if (hint->ripple) { + hint->ripple->lastStop(); } return; } @@ -1387,6 +1475,19 @@ bool WebPage::isLogEntryOriginal() const { return _parent->data()->isAdminLogEntry() && _parent->media() != this; } +WebPage::FactcheckMetrics WebPage::computeFactcheckMetrics( + int fullHeight) const { + const auto possible = fullHeight / st::normalFont->height; + const auto expandable = (possible > kFactcheckCollapsedLines + 1); + const auto check = _parent->Get(); + const auto expanded = check && check->expanded; + const auto allowExpanding = (expanded || !expandable); + return { + .lines = allowExpanding ? possible : kFactcheckCollapsedLines, + .expandable = expandable, + }; +} + int WebPage::bottomInfoPadding() const { if (!isBubbleBottom()) { return 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h index 86f7ec817..47cba266c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -101,6 +101,40 @@ public: ~WebPage(); private: + struct FactcheckMetrics { + int lines = 0; + bool expandable = false; + }; + struct HintData { + QSize size; + QPointF lastPosition; + QString text; + int widthBefore = 0; + std::unique_ptr ripple; + ClickHandlerPtr link; + }; + struct StickerSetData { + std::vector> views; + }; + struct SponsoredData { + QString buttonText; + + uint64 backgroundEmojiId = 0; + uint8 colorIndex : 6 = 0; + uint8 isLinkInternal : 1 = 0; + uint8 canReport : 1 = 0; + + HintData hint; + }; + struct FactcheckData { + HintData hint; + bool expandable = false; + }; + using AdditionalData = std::variant< + StickerSetData, + SponsoredData, + FactcheckData>; + void playAnimation(bool autoplay) override; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; @@ -124,36 +158,26 @@ private: const ClickHandlerPtr &link) const; [[nodiscard]] bool asArticle() const; + [[nodiscard]] StickerSetData *stickerSetData() const; + [[nodiscard]] SponsoredData *sponsoredData() const; + [[nodiscard]] FactcheckData *factcheckData() const; + [[nodiscard]] HintData *hintData() const; + + [[nodiscard]] FactcheckMetrics computeFactcheckMetrics( + int fullHeight) const; + + void setupAdditionalData(); + const style::QuoteStyle &_st; const not_null _data; + const MediaWebPageFlags _flags; + std::vector> _collage; ClickHandlerPtr _openl; std::unique_ptr _attach; mutable std::shared_ptr _photoMedia; mutable std::unique_ptr _ripple; - struct StickerSet final { - std::vector> views; - }; - - std::unique_ptr _stickerSet; - - struct SponsoredData final { - QString buttonText; - bool isLinkInternal = false; - - uint64 backgroundEmojiId = 0; - uint8 colorIndex : 6 = 0; - - bool canReport = false; - QSize hintSize; - QPointF lastHintPos; - int widthBeforeHint = 0; - std::unique_ptr hintRipple; - ClickHandlerPtr hintLink; - }; - mutable std::optional _sponsoredData; - int _dataVersion = -1; int _siteNameLines = 0; int _descriptionLines = 0; @@ -172,7 +196,7 @@ private: int _pixw = 0; int _pixh = 0; - const MediaWebPageFlags _flags; + std::unique_ptr _additionalData; }; From a87a221f26c89ec024291f09775724bb05dc1b11 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 23 May 2024 17:22:07 +0400 Subject: [PATCH 124/225] Force red for factcheck. --- .../SourceFiles/history/view/media/history_view_web_page.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 77d6fa5ea..a4cd1d66a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -783,7 +783,9 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { const auto selected = context.selected(); const auto view = parent(); const auto from = view->data()->contentColorsFrom(); - const auto colorIndex = (sponsored && sponsored->colorIndex) + const auto colorIndex = factcheckData() + ? 0 // red + : (sponsored && sponsored->colorIndex) ? sponsored->colorIndex : from ? from->colorIndex() From 74861a334dd2479eb26c76a78b1c37179d8fb3cc Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 23 May 2024 17:50:59 +0400 Subject: [PATCH 125/225] Show expand/collapse icon in factcheck. --- .../view/media/history_view_web_page.cpp | 19 ++++++++++++++++--- .../view/media/history_view_web_page.h | 2 ++ Telegram/SourceFiles/ui/chat/chat.style | 3 +++ Telegram/codegen | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index a4cd1d66a..9c4c1ef05 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -598,6 +598,7 @@ QSize WebPage::countCurrentSize(int newWidth) { : FactcheckMetrics(); if (factcheck) { factcheck->expandable = factcheckMetrics.expandable; + factcheck->expanded = factcheckMetrics.expanded; _openl = factcheck->expandable ? ToggleFactcheckClickHandler(_parent) : nullptr; @@ -779,11 +780,12 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { auto paintw = inner.width(); const auto sponsored = sponsoredData(); + const auto factcheck = factcheckData(); const auto selected = context.selected(); const auto view = parent(); const auto from = view->data()->contentColorsFrom(); - const auto colorIndex = factcheckData() + const auto colorIndex = factcheck ? 0 // red : (sponsored && sponsored->colorIndex) ? sponsored->colorIndex @@ -793,8 +795,9 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { const auto cache = context.outbg ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() : st->coloredReplyCache(selected, colorIndex).get(); - const auto backgroundEmojiId = (sponsored - && sponsored->backgroundEmojiId) + const auto backgroundEmojiId = factcheck + ? DocumentId() + : (sponsored && sponsored->backgroundEmojiId) ? sponsored->backgroundEmojiId : from ? from->backgroundEmojiId() @@ -821,6 +824,15 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { if (!backgroundEmojiCache->frames[0].isNull()) { FillBackgroundEmoji(p, outer, false, *backgroundEmojiCache); } + } else if (factcheck && factcheck->expandable) { + const auto &icon = factcheck->expanded + ? st::factcheckIconCollapse + : st::factcheckIconExpand; + icon.paint( + p, + outer.x() + outer.width() - icon.width() - _st.padding.right(), + outer.y() + _st.padding.top(), + width()); } if (_ripple) { @@ -1487,6 +1499,7 @@ WebPage::FactcheckMetrics WebPage::computeFactcheckMetrics( return { .lines = allowExpanding ? possible : kFactcheckCollapsedLines, .expandable = expandable, + .expanded = expanded, }; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h index 47cba266c..4637adb31 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -104,6 +104,7 @@ private: struct FactcheckMetrics { int lines = 0; bool expandable = false; + bool expanded = false; }; struct HintData { QSize size; @@ -129,6 +130,7 @@ private: struct FactcheckData { HintData hint; bool expandable = false; + bool expanded = false; }; using AdditionalData = std::variant< StickerSetData, diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 1d7361bdd..6c398f236 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1132,3 +1132,6 @@ effectPreviewPromoPadding: margins(4px, 6px, 4px, 6px); effectPreviewLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { thickness: 2px; } + +factcheckIconExpand: icon {{ "fast_to_original-rotate_cw", historyPeer1NameFg }}; +factcheckIconCollapse: icon {{ "fast_to_original-rotate_ccw", historyPeer1NameFg }}; diff --git a/Telegram/codegen b/Telegram/codegen index 6462cda46..0af136124 160000 --- a/Telegram/codegen +++ b/Telegram/codegen @@ -1 +1 @@ -Subproject commit 6462cda46ec5d48a9ae452ba1b6f7dfe1b6d882d +Subproject commit 0af136124083369073b8fdaf45f0816fd2b10bad From 493f0450b42a37ef947ce9d209603b44905596d1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 23 May 2024 22:58:09 +0400 Subject: [PATCH 126/225] Implement factcheck edition. --- Telegram/Resources/icons/menu/factcheck.png | Bin 0 -> 588 bytes .../Resources/icons/menu/factcheck@2x.png | Bin 0 -> 1359 bytes .../Resources/icons/menu/factcheck@3x.png | Bin 0 -> 2267 bytes Telegram/Resources/langs/lang.strings | 6 +- .../data/components/factchecks.cpp | 61 +++++++++++++ .../SourceFiles/data/components/factchecks.h | 10 +++ .../admin_log/history_admin_log_inner.cpp | 3 +- .../history/history_inner_widget.cpp | 26 ++++++ Telegram/SourceFiles/history/history_item.cpp | 14 ++- Telegram/SourceFiles/history/history_item.h | 1 + .../history/history_item_components.cpp | 10 ++- .../history/history_item_components.h | 9 ++ .../history/history_item_edition.cpp | 1 + .../history/history_item_edition.h | 1 + .../history/view/history_view_message.cpp | 37 ++++---- .../ui/boxes/edit_factcheck_box.cpp | 81 ++++++++++++++++++ .../SourceFiles/ui/boxes/edit_factcheck_box.h | 15 ++++ Telegram/SourceFiles/ui/chat/chat.style | 15 ++++ Telegram/SourceFiles/ui/menu_icons.style | 1 + Telegram/cmake/td_ui.cmake | 2 + 20 files changed, 264 insertions(+), 29 deletions(-) create mode 100644 Telegram/Resources/icons/menu/factcheck.png create mode 100644 Telegram/Resources/icons/menu/factcheck@2x.png create mode 100644 Telegram/Resources/icons/menu/factcheck@3x.png create mode 100644 Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp create mode 100644 Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h diff --git a/Telegram/Resources/icons/menu/factcheck.png b/Telegram/Resources/icons/menu/factcheck.png new file mode 100644 index 0000000000000000000000000000000000000000..e9115a4cf1bc6bf28f2a3011f43af86c5ef4190e GIT binary patch literal 588 zcmV-S0<-;zP)@kxQ#_VI0M`_a&E2$$enN$m9%&v8j|xij*%Pr5!0> z0Oc|pgxgE02qx%JkJ9Fa5|mF<1v6_GWkIffNHf$d>{}A z27|Rah*R)oOJ(99{wY{eHb(TdmfgS~8gofW=~AGMQd627_TbodU>av%-YK z;Q)|Mr{6g^!!Q6`E|2&UsX0yq0+#~bnG3fRB#bQBxKA#^92E=bRo8@xp zcDwH+$8j2sM))`$j|YHOt0mFtbOK;mw%hIUJTDT79$}3}1HkY1-zVa`)9G{+MeTMw z5?Pj|X?nZe%H?vkTK)7N2u416mQ*U8&*uQ5(dgq`p-?=L;cyrL$8j>5O!#(kxx8Ml z11J`Y!URPX3Iza-M&tVi&+_~I0PJ=B9RdP_4NfH6bk*P;;w$bPu{XrDqSv@ z>-AcxRD3=kfZ=fXZVN?G3WcKA>j5|(kC{v+olYN*M*#hPU#V1nP()-f7#^e1X#BY1 aSLh!JF#T`ZCc07p0000R%tW>cwDjYjjw_RJt9C1q}I z?&0C#`T1F)P;_*3L`FtZsZ=VJ%H?v!VzFE<*J`!*_xJPj^Xcj7-|MElyc~?7P$(1% zh0#=Xb@g|-2?z+lbDW%D z0|Ns9pw()Ff`W)3i^byccp{NVBoguYd^Vd+r2PE+?(gpbKq8S?-wm71etCHTfWE#y z^Q$^KIsoAH_0`+k+nR1XJUrId*8$-D{oTjM$NWN1Pfwjr2LM}JTMP!n3RY}wZG}Q1 z(G6u~Wn>jfN=k^%D=sd!v$M0@O@4m9TrMXXwz09%GM)PRdP3yr=%~23*b)`u%K%_$ zX(>55*)lhngoK2Jg#|*eqN0Lay_c64Ui-ttLn|I`si~>^`}^3FudgqO%F4=00MKYO zVPRp`a)Sv83DIaY0MO9TV06G=Mx#t76Ct#-v$MImY1zbfc6LcgNsf+=dhzY;ZK+g> z5aMt+Bn;tuk;CE8XfzAlP$-nKv9Xz%8Iee&mvVM?#%I#w<0A>h=;&y?(uIYE78sI9 zB)Gh)s!A`FmzRfAiHV8iMDeDWnwlc(rmd|FPdPX^XqX)x9R+}ul@+qGsHUa{0OWGH zv$M1LRb^#m*v;(hEQLbR%i7!9@9pgYfZXJJN>ebUH#vrBbQY>W^^;2ZzDI!QtUyEKLv}A3ruW zh7gj=4{&crKP146BAz=dU$v^J3Aw$Fc-XgpP!$jqM}R;i;0Oj zJv{}0y1F_umAPE5R;wjdf)GM177I7&>FH^1Zmx+2WH1<^p`m8V3Iu}V<6{7Ldwcur zF8h@1>}=xsvbnjLm6b)V{&zAmGVmi9BNPfPQAtlv$D1G1-rjDR8?jhSh^(!x@%enq zbp9nREp20C1IvB(h%rHJZ7r_d-Q5)k1b<|Oz|qlhad8pr`uX{pU&!HbaMkMSDycj1 zYv^?P)6)|G^!N80W}TdzjJ}+@y1D@1<>iINVp-D-rl+R|tAE^9At51MU0sKVhsVdq zy}iBR;o(FoI5_z6@$s{7umWCQUc`;j)YK#t3WtY>-`?KfcSc4=ghF9`ef`D71&&zx zXX{6Db92D}l}gpx+S=06qEspkv-$b?-{l4q8yh=0IeB$;rB8}WrPJ?WzVAA+ zw52njzV!EWJ3aS*&Y8LQ&bc!KfDIcqY}l}2!^Yo&M4%RpM%%xC|CTLV7z~C^r&B7G zB9TZUkt}H(5)#5@vpqdMX*8Njr5YR@Y;JCznVBIl9%6`(k8f*hGyaK2qiJYpNJ~re z^72}>Y89PM_wevYPEHmIg-WGzfpKSNXG%)Szs}=Nva_>wI-PNO2ha(gUsZ=Te;MJ>Fsi~ia1&&|#4+_@8v)Yh$A;h5~` z=pZUWYlw@BgX8Dr%a`~Cg+d{meu;^R_yw(D)22;BLqjmJ*w|S7n&IK$Q1Hu_FWa_l z!>_qAcJ12L-`@{|sIRXlO1ZYS7Rn9}4@X8u5~Xa0sHiADpa1LEFX(gwj_=dtc)XJ;>8zD%T>w6rvX!GQH4 z7K@J`KTf2Nr8sir$j6T#7o5$^%uHg`84Skg=qS`}ZEZzwqkoM=D&z~niR3H$ba^vIUetv!gC|ZS|pWpcSI2y^b zXV1*qlg(NP6NyC7m`pYkx%tFksY;(>t+JXpPY^>Q{;DivmqAc*O+U=eU|D-?==fB?L5i{Ns(@ZOk_k+Gba zzrX+3*cfW|^yyQxvgWVY`}gnBhL@KY;T1q~ax#y{Ll-0jK_(|Bm$TfrZ{NCg>(KUx z4<88d?ds|ZmwS;&0H2D7TKuQxHlf`WqJl2}<;Nk{+y01z1&38w*<%f&C~?(W{z)djQa z=;&avSSIWf6BE(WN3YjgxKA@b#{d8dg<@)I3T^xN_*l}#1T-40rluw=EDZhG*VlLW z@ZpJx2@_p>eSOjOeRg*C>({TAc(Euk0012QbUNL_F3V9~ULF+{g|-I=2NM$$&Blr? zcd$=NOhC27$kEZ!-rgQ5nCnpC%5Tntk)9Iq4qn9%)EiFao%;@MSo6TNqaP{g{IIm`AX5!-F zEDV6nW=~B`p&hSWxngA+3!qRa?d|QTs9Y{LG0RIzN?_b-wK^do0sC)ub~gN1uh*|= z_u=g9ER{;pzGAT$|8xKVfbj5e^l}-E#=Cd#ny~ct_7;gm*y!P&EhQzzh{1~eVt#%; z+805P`1pA1a`}V8!a`JWYHG^tlf(7v*EcjYzyuHkIdkR=hr^kioP-C9i;JxYgUx2c zE&Toa_pM6hPvEmYkw^q9eG{x%v*zK$huG_;)oS5Sp-^aDCCy|q;c#ebYBE~{n_uqX zfL16J)6>&t?NusOR#sL;MFskkN~OY*qOGkhBO^nv*P9htj7Fo8%jIaBMx&YCwwUM1 zlPBnTlF4KY2E+1>d3kx*dDYq3$z(FE4ui>Lc6WE9QjLv`R_pxc#*G^=xa{og74^-{ z%~dEAdcD5Az1?)tWR|R~EEq;nQ4t=g{}LJ+3JXP*N_FbgsTEzf?c28>Ja`b#6IEwt z=giDZxctn|&*Oai@f#kGhaFU1U0r9-p2c|uEP=^n=H}*hc6MUD*VNPyuzf~xadE*u z07L2N=}AvdC&s~2q@|_7Nd;+dZ+CNZBhrnNlT%Sq5xi4F?%cUUzyh@h?8HVv5TvxU z)WyYxa9;ob5EvL(Qc|+u?d!dJ_lWgUP*8x?S1Of!K0i1(m{>1M;N|6oEenlC=wiwHp5h6ae7i;h|EgU{;qdUBa(v4YzOKhCvh* z6yO)UdGjU|EGsL+FK8uXGI?-t5RF5jP6v}coEEa2IWCT?k z85#K}FA08wt(&f0ySCWC-rhbTA%Vx^b$545rBXhh&*5;E+)!P(Z~;o?=jZ?bV>gMX zrze~h8jXg_<-(v@ELLi2>WddI7Q9R$7^zfRR#tZX`t_!!rn$K}c$}D05Ig(&pVzJR^#2SZ&h9)H? zMMpgwmupU=xw)ZV pm|C(ow_(GE4I4IW*s$?;`40p^KZZY1ozegR002ovPDHLkV1f$zR09A2 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6b28b450e..efb548724 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3217,6 +3217,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_reply_msg" = "Reply"; "lng_context_quote_and_reply" = "Quote & Reply"; "lng_context_edit_msg" = "Edit"; +"lng_context_add_factcheck" = "Add Fact Check"; +"lng_context_edit_factcheck" = "Edit Fact Check"; "lng_context_forward_msg" = "Forward Message"; "lng_context_send_now_msg" = "Send now"; "lng_context_reschedule" = "Reschedule"; @@ -3287,12 +3289,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_spoiler_effect" = "Hide with Spoiler"; "lng_context_disable_spoiler" = "Remove Spoiler"; -"lng_context_add_factcheck" = "Add Fact Check"; "lng_factcheck_title" = "Fact Check"; "lng_factcheck_placeholder" = "Add Facts or Context"; "lng_factcheck_whats_this" = "what's this?"; "lng_factcheck_about" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation."; +"lng_factcheck_add_done" = "Fact check added."; +"lng_factcheck_edit_done" = "Fact check edited."; +"lng_factcheck_remove_done" = "Fact check removed."; "lng_translate_show_original" = "Show Original"; "lng_translate_bar_to" = "Translate to {name}"; diff --git a/Telegram/SourceFiles/data/components/factchecks.cpp b/Telegram/SourceFiles/data/components/factchecks.cpp index 0423d1d43..af72d8aff 100644 --- a/Telegram/SourceFiles/data/components/factchecks.cpp +++ b/Telegram/SourceFiles/data/components/factchecks.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/components/factchecks.h" +#include "api/api_text_entities.h" #include "apiwrap.h" #include "base/random.h" #include "data/data_session.h" @@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_components.h" #include "lang/lang_keys.h" +#include "main/main_app_config.h" #include "main/main_session.h" namespace Data { @@ -135,4 +137,63 @@ std::unique_ptr Factchecks::makeMedia( MediaWebPageFlags()); } +bool Factchecks::canEdit(not_null item) const { + if (!canEdit() + || !item->isRegular() + || !item->history()->peer->isBroadcast()) { + return false; + } + const auto media = item->media(); + if (!media || media->webpage() || media->photo()) { + return true; + } else if (const auto document = media->document()) { + return !document->isVideoMessage() && !document->sticker(); + } + return false; +} + +bool Factchecks::canEdit() const { + return _session->appConfig().get(u"can_edit_factcheck"_q, false); +} + +int Factchecks::lengthLimit() const { + return _session->appConfig().get(u"factcheck_length_limit"_q, 1024); +} + +void Factchecks::save( + FullMsgId itemId, + TextWithEntities text, + Fn done) { + const auto item = _session->data().message(itemId); + if (!item) { + return; + } else if (text.empty()) { + _session->api().request(MTPmessages_DeleteFactCheck( + item->history()->peer->input, + MTP_int(item->id.bare) + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + done(QString()); + }).fail([=](const MTP::Error &error) { + done(error.type()); + }).send(); + } else { + _session->api().request(MTPmessages_EditFactCheck( + item->history()->peer->input, + MTP_int(item->id.bare), + MTP_textWithEntities( + MTP_string(text.text), + Api::EntitiesToMTP( + _session, + text.entities, + Api::ConvertOption::SkipLocal)) + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + done(QString()); + }).fail([=](const MTP::Error &error) { + done(error.type()); + }).send(); + } +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/components/factchecks.h b/Telegram/SourceFiles/data/components/factchecks.h index 452706f9b..7054a3c57 100644 --- a/Telegram/SourceFiles/data/components/factchecks.h +++ b/Telegram/SourceFiles/data/components/factchecks.h @@ -32,7 +32,17 @@ public: not_null view, not_null factcheck); + [[nodiscard]] bool canEdit(not_null item) const; + [[nodiscard]] int lengthLimit() const; + + void save( + FullMsgId itemId, + TextWithEntities text, + Fn done); + private: + [[nodiscard]] bool canEdit() const; + void subscribeIfNotYet(); void request(); 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 8fa440bf6..cb099afe4 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -1306,8 +1306,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { && !link && (view->hasVisibleText() || mediaHasTextForCopy - || (item->Has() - && !item->Get()->data.text.empty()) + || !item->factcheckText().empty() || item->Has())) { _menu->addAction(tr::lng_context_copy_text(tr::now), [=] { copyContextText(itemId); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 1687b5049..e6950859d 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/reaction_fly_animation.h" #include "ui/text/text_options.h" #include "ui/text/text_isolated_emoji.h" +#include "ui/boxes/edit_factcheck_box.h" #include "ui/boxes/report_box.h" #include "ui/layers/generic_box.h" #include "ui/controls/delete_message_context_action.h" @@ -72,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_who_reacted.h" #include "api/api_views.h" #include "lang/lang_keys.h" +#include "data/components/factchecks.h" #include "data/components/sponsored_messages.h" #include "data/data_session.h" #include "data/data_document.h" @@ -2171,6 +2173,30 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } }, &st::menuIconEdit); } + if (session->factchecks().canEdit(item)) { + const auto text = item->factcheckText(); + const auto phrase = text.empty() + ? tr::lng_context_add_factcheck(tr::now) + : tr::lng_context_edit_factcheck(tr::now); + _menu->addAction(phrase, [=] { + controller->show(Box(EditFactcheckBox, text, [=]( + TextWithEntities result) { + const auto done = [=](QString error) { + controller->showToast(!error.isEmpty() + ? error + : result.empty() + ? tr::lng_factcheck_remove_done(tr::now) + : text.empty() + ? tr::lng_factcheck_add_done(tr::now) + : tr::lng_factcheck_edit_done(tr::now)); + }; + session->factchecks().save( + itemId, + result, + crl::guard(controller, done)); + })); + }, &st::menuIconFactcheck); + } const auto pinItem = (item->canPin() && item->isPinned()) ? item : groupLeaderOrSelf(item); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 3a2ed484a..30cc8a9c3 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1508,14 +1508,18 @@ void HistoryItem::setFactcheck(MessageFactcheck info) { } else { AddComponents(HistoryMessageFactcheck::Bit()); const auto factcheck = Get(); + const auto textChanged = (factcheck->data.text != info.text); if (factcheck->data.hash == info.hash && (info.needCheck || !factcheck->data.needCheck)) { return; - } else if (factcheck->data.text != info.text + } else if (textChanged || factcheck->data.country != info.country || factcheck->data.hash != info.hash) { factcheck->data = std::move(info); factcheck->requested = false; + if (textChanged) { + factcheck->page = nullptr; + } history()->owner().requestItemResize(this); } } @@ -1526,6 +1530,13 @@ bool HistoryItem::hasUnrequestedFactcheck() const { return factcheck && factcheck->data.needCheck && !factcheck->requested; } +TextWithEntities HistoryItem::factcheckText() const { + if (const auto factcheck = Get()) { + return factcheck->data.text; + } + return {}; +} + PeerData *HistoryItem::specialNotificationPeer() const { return (mentionsMe() && !_history->peer->isUser()) ? from().get() @@ -1725,6 +1736,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { } applyTTL(edition.ttl); + setFactcheck(FromMTP(this, edition.mtpFactcheck)); finishEdition(keyboardTop); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 5a864d076..3118bf3bc 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -208,6 +208,7 @@ public: const TextWithEntities &content); void setFactcheck(MessageFactcheck info); [[nodiscard]] bool hasUnrequestedFactcheck() const; + [[nodiscard]] TextWithEntities factcheckText() const; [[nodiscard]] not_null notificationThread() const; [[nodiscard]] not_null history() const { diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 54e42a16e..fd1a04942 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -1065,6 +1065,12 @@ HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default; MessageFactcheck FromMTP( not_null item, const tl::conditional &factcheck) { + return FromMTP(&item->history()->session(), factcheck); +} + +MessageFactcheck FromMTP( + not_null session, + const tl::conditional &factcheck) { auto result = MessageFactcheck(); if (!factcheck) { return result; @@ -1074,9 +1080,7 @@ MessageFactcheck FromMTP( const auto &data = text->data(); result.text = { qs(data.vtext()), - Api::EntitiesFromMTP( - &item->history()->session(), - data.ventities().v), + Api::EntitiesFromMTP(session, data.ventities().v), }; } if (const auto country = data.vcountry()) { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index fa7ffbf3a..906fe18ce 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL struct WebPageData; class VoiceSeekClickHandler; +class ReplyKeyboard; namespace Ui { struct ChatPaintContext; @@ -31,6 +32,7 @@ struct GeometryDescriptor; namespace Data { class Session; class Story; +class SavedSublist; } // namespace Data namespace Media::Player { @@ -47,6 +49,10 @@ class Document; class TranscribeButton; } // namespace HistoryView +namespace style { +struct BotKeyboardButton; +} // namespace style + struct HistoryMessageVia : public RuntimeComponent { void create(not_null owner, UserId userId); void resize(int32 availw) const; @@ -579,6 +585,9 @@ struct MessageFactcheck { [[nodiscard]] MessageFactcheck FromMTP( not_null item, const tl::conditional &factcheck); +[[nodiscard]] MessageFactcheck FromMTP( + not_null session, + const tl::conditional &factcheck); struct HistoryMessageFactcheck : public RuntimeComponent { diff --git a/Telegram/SourceFiles/history/history_item_edition.cpp b/Telegram/SourceFiles/history/history_item_edition.cpp index 4349a3c93..d66d0cf7b 100644 --- a/Telegram/SourceFiles/history/history_item_edition.cpp +++ b/Telegram/SourceFiles/history/history_item_edition.cpp @@ -24,6 +24,7 @@ HistoryMessageEdition::HistoryMessageEdition( replyMarkup = HistoryMessageMarkupData(message.vreply_markup()); mtpMedia = message.vmedia(); mtpReactions = message.vreactions(); + mtpFactcheck = message.vfactcheck(); views = message.vviews().value_or(-1); forwards = message.vforwards().value_or(-1); if (const auto mtpReplies = message.vreplies()) { diff --git a/Telegram/SourceFiles/history/history_item_edition.h b/Telegram/SourceFiles/history/history_item_edition.h index b8f577d80..a44109299 100644 --- a/Telegram/SourceFiles/history/history_item_edition.h +++ b/Telegram/SourceFiles/history/history_item_edition.h @@ -36,4 +36,5 @@ struct HistoryMessageEdition { HistoryMessageRepliesData replies; const MTPMessageMedia *mtpMedia = nullptr; const MTPMessageReactions *mtpReactions = nullptr; + const MTPFactCheck *mtpFactcheck = nullptr; }; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 564ea3b7b..b860c2c58 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -917,8 +917,8 @@ QSize Message::performCountOptimalSize() { minHeight += st::msgPadding.top(); if (mediaDisplayed) minHeight += st::mediaInBubbleSkip; if (entry) minHeight += st::mediaInBubbleSkip; - if (check) minHeight += st::mediaInBubbleSkip; } + if (check) minHeight += st::mediaInBubbleSkip; if (mediaDisplayed) { // Parts don't participate in maxWidth() in case of media message. if (media->enforceBubbleWidth()) { @@ -1308,7 +1308,7 @@ void Message::draw(Painter &p, const PaintContext &context) const { trect.setHeight(trect.height() - entry->height()); } if (check) { - trect.setHeight(trect.height() - check->height()); + trect.setHeight(trect.height() - check->height() - st::mediaInBubbleSkip); } if (displayInfo) { trect.setHeight(trect.height() @@ -1371,7 +1371,7 @@ void Message::draw(Painter &p, const PaintContext &context) const { } if (check) { auto checkLeft = inner.left(); - auto checkTop = trect.y() + trect.height(); + auto checkTop = trect.y() + trect.height() + st::mediaInBubbleSkip; p.translate(checkLeft, checkTop); auto checkContext = context.translated(checkLeft, -checkTop); checkContext.selection = skipTextSelection(context.selection); @@ -1986,7 +1986,7 @@ PointState Message::pointState(QPoint point) const { //} if (check) { auto checkHeight = check->height(); - trect.setHeight(trect.height() - checkHeight); + trect.setHeight(trect.height() - checkHeight - st::mediaInBubbleSkip); } if (entry) { auto entryHeight = entry->height(); @@ -2428,9 +2428,9 @@ TextState Message::textState( } if (check) { auto checkHeight = check->height(); - trect.setHeight(trect.height() - checkHeight); + trect.setHeight(trect.height() - checkHeight - st::mediaInBubbleSkip); auto checkLeft = inner.left(); - auto checkTop = trect.y() + trect.height(); + auto checkTop = trect.y() + trect.height() + st::mediaInBubbleSkip; if (point.y() >= checkTop && point.y() < checkTop + checkHeight) { result = check->textState( point - QPoint(checkLeft, checkTop), @@ -4333,7 +4333,7 @@ int Message::resizeContentGetHeight(int newWidth) { if (contentWidth == maxWidth()) { if (mediaDisplayed) { if (check) { - newHeight += check->resizeGetHeight(contentWidth); + newHeight += check->resizeGetHeight(contentWidth) + st::mediaInBubbleSkip; } if (entry) { newHeight += entry->resizeGetHeight(contentWidth); @@ -4365,24 +4365,16 @@ int Message::resizeContentGetHeight(int newWidth) { if (!mediaOnTop) { newHeight += st::msgPadding.top(); if (mediaDisplayed) newHeight += st::mediaInBubbleSkip; - if (check) newHeight += st::mediaInBubbleSkip; if (entry) newHeight += st::mediaInBubbleSkip; } if (mediaDisplayed) { newHeight += media->height(); - if (check) { - newHeight += check->resizeGetHeight(contentWidth); - } - if (entry) { - newHeight += entry->resizeGetHeight(contentWidth); - } - } else { - if (check) { - newHeight += check->resizeGetHeight(contentWidth); - } - if (entry) { - newHeight += entry->resizeGetHeight(contentWidth); - } + } + if (check) { + newHeight += check->resizeGetHeight(contentWidth) + st::mediaInBubbleSkip; + } + if (entry) { + newHeight += entry->resizeGetHeight(contentWidth); } if (reactionsInBubble) { if (!mediaDisplayed || _viewButton) { @@ -4518,7 +4510,8 @@ void Message::refreshInfoSkipBlock() { return media->storyExpired(); } return false; - } else if (item->Has()) { + } else if (item->Has() + || factcheckBlock()) { return false; } else if (media && media->isDisplayed() && !_invertMedia) { return false; diff --git a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp new file mode 100644 index 000000000..f13c3e976 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp @@ -0,0 +1,81 @@ +/* +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/boxes/edit_factcheck_box.h" + +#include "lang/lang_keys.h" +#include "ui/widgets/fields/input_field.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" + +void EditFactcheckBox( + not_null box, + TextWithEntities current, + Fn save) { + box->setTitle(tr::lng_factcheck_title()); + + const auto field = box->addRow(object_ptr( + box, + st::factcheckField, + Ui::InputField::Mode::NoNewlines, + tr::lng_factcheck_placeholder(), + TextWithTags{ + current.text, + TextUtilities::ConvertEntitiesToTextTags(current.entities) + })); + + enum class State { + Initial, + Changed, + Removed, + }; + const auto state = box->lifetime().make_state>( + State::Initial); + field->changes() | rpl::start_with_next([=] { + const auto now = field->getLastText().trimmed(); + *state = !now.isEmpty() + ? State::Changed + : current.empty() + ? State::Initial + : State::Removed; + }, field->lifetime()); + + state->value() | rpl::start_with_next([=](State state) { + box->clearButtons(); + if (state == State::Removed) { + box->addButton(tr::lng_box_remove(), [=] { + box->closeBox(); + save({}); + }, st::attentionBoxButton); + } else if (state == State::Initial) { + box->addButton(tr::lng_settings_save(), [=] { + if (current.empty()) { + field->showError(); + } else { + box->closeBox(); + } + }); + } else { + box->addButton(tr::lng_settings_save(), [=] { + auto result = field->getTextWithAppliedMarkdown(); + + box->closeBox(); + save({ + result.text, + TextUtilities::ConvertTextTagsToEntities(result.tags) + }); + }); + } + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + }, box->lifetime()); + + box->setFocusCallback([=] { + field->setFocusFast(); + }); +} diff --git a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h new file mode 100644 index 000000000..f09f52eb4 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h @@ -0,0 +1,15 @@ +/* +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/layers/generic_box.h" + +void EditFactcheckBox( + not_null box, + TextWithEntities current, + Fn save); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 6c398f236..9dbf43303 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1135,3 +1135,18 @@ effectPreviewLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { factcheckIconExpand: icon {{ "fast_to_original-rotate_cw", historyPeer1NameFg }}; factcheckIconCollapse: icon {{ "fast_to_original-rotate_ccw", historyPeer1NameFg }}; +factcheckField: InputField(defaultInputField) { + textBg: transparent; + textMargins: margins(0px, 0px, 0px, 4px); + + placeholderFg: placeholderFg; + placeholderFgActive: placeholderFgActive; + placeholderFgError: placeholderFgActive; + placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; + + heightMin: 24px; + + font: normalFont; +} diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index eee9919fd..8f2c0f861 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -153,6 +153,7 @@ menuIconTagFilter: icon{{ "menu/tag_filter", menuIconColor }}; menuIconTagRename: icon{{ "menu/tag_rename", menuIconColor }}; menuIconGroupsHide: icon {{ "menu/hide_members", menuIconColor }}; menuIconFont: icon {{ "menu/fonts", menuIconColor }}; +menuIconFactcheck: icon {{ "menu/factcheck", menuIconColor }}; menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }}; menuIconTTLAnyTextPosition: point(11px, 22px); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 0a97c6b6d..3c0c026a3 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -261,6 +261,8 @@ PRIVATE ui/boxes/country_select_box.h ui/boxes/edit_birthday_box.cpp ui/boxes/edit_birthday_box.h + ui/boxes/edit_factcheck_box.cpp + ui/boxes/edit_factcheck_box.h ui/boxes/edit_invite_link.cpp ui/boxes/edit_invite_link.h ui/boxes/rate_call_box.cpp From d13bf19b7953ff9fc0422713941e57b94dcbe455 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 24 May 2024 09:37:38 +0400 Subject: [PATCH 127/225] Show length limit when editing a factcheck. --- .../data/components/factchecks.cpp | 17 +++++++++++ .../SourceFiles/data/components/factchecks.h | 9 ++++++ .../history/history_inner_widget.cpp | 18 +++--------- .../view/history_view_context_menu.cpp | 28 +++++++++++++++++++ .../settings/business/settings_chat_intro.cpp | 2 -- .../ui/boxes/edit_factcheck_box.cpp | 6 ++++ .../SourceFiles/ui/boxes/edit_factcheck_box.h | 1 + 7 files changed, 65 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/data/components/factchecks.cpp b/Telegram/SourceFiles/data/components/factchecks.cpp index af72d8aff..07051dabd 100644 --- a/Telegram/SourceFiles/data/components/factchecks.cpp +++ b/Telegram/SourceFiles/data/components/factchecks.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "ui/layers/show.h" namespace Data { namespace { @@ -196,4 +197,20 @@ void Factchecks::save( } } +void Factchecks::save( + FullMsgId itemId, + TextWithEntities was, + TextWithEntities text, + std::shared_ptr show) { + const auto done = [=](QString error) { + show->showToast(!error.isEmpty() + ? error + : was.empty() + ? tr::lng_factcheck_remove_done(tr::now) + : text.empty() + ? tr::lng_factcheck_add_done(tr::now) + : tr::lng_factcheck_edit_done(tr::now)); + }; +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/components/factchecks.h b/Telegram/SourceFiles/data/components/factchecks.h index 7054a3c57..515e52286 100644 --- a/Telegram/SourceFiles/data/components/factchecks.h +++ b/Telegram/SourceFiles/data/components/factchecks.h @@ -21,6 +21,10 @@ namespace Main { class Session; } // namespace Main +namespace Ui { +class Show; +} // namespace Ui + namespace Data { class Factchecks final { @@ -39,6 +43,11 @@ public: FullMsgId itemId, TextWithEntities text, Fn done); + void save( + FullMsgId itemId, + TextWithEntities was, + TextWithEntities text, + std::shared_ptr show); private: [[nodiscard]] bool canEdit() const; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index e6950859d..76fa5c463 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2179,21 +2179,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { ? tr::lng_context_add_factcheck(tr::now) : tr::lng_context_edit_factcheck(tr::now); _menu->addAction(phrase, [=] { - controller->show(Box(EditFactcheckBox, text, [=]( + const auto limit = session->factchecks().lengthLimit(); + controller->show(Box(EditFactcheckBox, text, limit, [=]( TextWithEntities result) { - const auto done = [=](QString error) { - controller->showToast(!error.isEmpty() - ? error - : result.empty() - ? tr::lng_factcheck_remove_done(tr::now) - : text.empty() - ? tr::lng_factcheck_add_done(tr::now) - : tr::lng_factcheck_edit_done(tr::now)); - }; - session->factchecks().save( - itemId, - result, - crl::guard(controller, done)); + const auto show = controller->uiShow(); + session->factchecks().save(itemId, text, result, show); })); }, &st::menuIconFactcheck); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index a02f4c786..3d47b358e 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/controls/delete_message_context_action.h" #include "ui/controls/who_reacted_context_action.h" +#include "ui/boxes/edit_factcheck_box.h" #include "ui/boxes/report_box.h" #include "ui/ui_utility.h" #include "menu/menu_item_download_files.h" @@ -53,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/sticker_set_box.h" #include "boxes/stickers_box.h" #include "boxes/translate_box.h" +#include "data/components/factchecks.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_document.h" @@ -713,6 +715,31 @@ bool AddEditMessageAction( return true; } +void AddFactcheckAction( + not_null menu, + const ContextMenuRequest &request, + not_null list) { + const auto item = request.item; + if (!item || !item->history()->session().factchecks().canEdit(item)) { + return; + } + const auto itemId = item->fullId(); + const auto text = item->factcheckText(); + const auto session = &item->history()->session(); + const auto phrase = text.empty() + ? tr::lng_context_add_factcheck(tr::now) + : tr::lng_context_edit_factcheck(tr::now); + menu->addAction(phrase, [=] { + const auto limit = session->factchecks().lengthLimit(); + const auto controller = request.navigation->parentController(); + controller->show(Box(EditFactcheckBox, text, limit, [=]( + TextWithEntities result) { + const auto show = controller->uiShow(); + session->factchecks().save(itemId, text, result, show); + })); + }, &st::menuIconFactcheck); +} + bool AddPinMessageAction( not_null menu, const ContextMenuRequest &request, @@ -972,6 +999,7 @@ void AddTopMessageActions( AddGoToMessageAction(menu, request, list); AddViewRepliesAction(menu, request, list); AddEditMessageAction(menu, request, list); + AddFactcheckAction(menu, request, list); AddPinMessageAction(menu, request, list); } diff --git a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp index b9fa7c68c..e9bc2f73e 100644 --- a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp +++ b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp @@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_sticker_player.h" #include "history/view/history_view_about_view.h" -#include "history/view/history_view_context_menu.h" #include "history/view/history_view_element.h" #include "history/history.h" #include "lang/lang_keys.h" @@ -168,7 +167,6 @@ private: return field; } - rpl::producer> IconPlayerValue( not_null sticker, Fn update) { diff --git a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp index f13c3e976..16a5b14af 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL void EditFactcheckBox( not_null box, TextWithEntities current, + int limit, Fn save) { box->setTitle(tr::lng_factcheck_title()); @@ -27,6 +28,7 @@ void EditFactcheckBox( current.text, TextUtilities::ConvertEntitiesToTextTags(current.entities) })); + AddLengthLimitLabel(field, limit); enum class State { Initial, @@ -62,6 +64,10 @@ void EditFactcheckBox( } else { box->addButton(tr::lng_settings_save(), [=] { auto result = field->getTextWithAppliedMarkdown(); + if (result.text.size() > limit) { + field->showError(); + return; + } box->closeBox(); save({ diff --git a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h index f09f52eb4..7eb104af3 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h +++ b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h @@ -12,4 +12,5 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL void EditFactcheckBox( not_null box, TextWithEntities current, + int limit, Fn save); From a3ef36f9f71cf7519a44660f85e391b937e001c6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 24 May 2024 09:55:48 +0400 Subject: [PATCH 128/225] Fix build on Windows. --- Telegram/SourceFiles/settings/settings_credits.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 44326a9af..127c5fd91 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -477,7 +477,7 @@ void Credits::setupHistory(not_null container) { const auto &stUser = st::boostReplaceUserpic; const auto peer = e.bareId - ? _controller->session().data().peer(PeerId(e.bareId)) + ? _controller->session().data().peer(PeerId(e.bareId)).get() : nullptr; if (peer) { content->add(object_ptr>( From 97a5e0c6ea3a0b06005ee3bf1bdaeaf174ed4406 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 24 May 2024 11:23:27 +0400 Subject: [PATCH 129/225] Support limited formatting input in factcheck. --- .../SourceFiles/boxes/create_poll_box.cpp | 2 +- .../chat_helpers/message_field.cpp | 117 ++++++++++-------- .../SourceFiles/chat_helpers/message_field.h | 3 + .../data/components/factchecks.cpp | 12 +- .../SourceFiles/data/components/factchecks.h | 2 +- .../history/history_inner_widget.cpp | 2 +- .../view/history_view_context_menu.cpp | 3 +- .../platform/linux/main_window_linux.cpp | 24 ++-- .../platform/mac/main_window_mac.h | 2 - .../platform/mac/main_window_mac.mm | 48 ++++--- .../mac/touchbar/mac_touchbar_manager.h | 6 +- .../mac/touchbar/mac_touchbar_manager.mm | 21 ++-- .../SourceFiles/support/support_helper.cpp | 2 +- .../ui/boxes/edit_factcheck_box.cpp | 4 +- .../SourceFiles/ui/boxes/edit_factcheck_box.h | 7 +- Telegram/lib_ui | 2 +- 16 files changed, 151 insertions(+), 106 deletions(-) diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 151ab54b8..46138e3fd 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -1044,7 +1044,7 @@ not_null CreatePollBox::setupSolution( solution->setInstantReplaces(Ui::InstantReplaces::Default()); solution->setInstantReplacesEnabled( Core::App().settings().replaceEmojiValue()); - solution->setMarkdownReplacesEnabled(rpl::single(true)); + solution->setMarkdownReplacesEnabled(true); solution->setEditLinkCallback( DefaultEditLinkCallback(_controller->uiShow(), solution)); solution->customTab(true); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 557d0c337..836459ad8 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -60,60 +60,43 @@ constexpr auto kTypesDuration = 4 * crl::time(1000); // For mention / custom emoji tags save and validate selfId, // ignore tags for different users. -class FieldTagMimeProcessor final { -public: - FieldTagMimeProcessor( - not_null _session, - Fn)> allowPremiumEmoji); - - QString operator()(QStringView mimeTag); - -private: - const not_null _session; - const Fn)> _allowPremiumEmoji; - -}; - -FieldTagMimeProcessor::FieldTagMimeProcessor( - not_null session, - Fn)> allowPremiumEmoji) -: _session(session) -, _allowPremiumEmoji(allowPremiumEmoji) { -} - -QString FieldTagMimeProcessor::operator()(QStringView mimeTag) { - const auto id = _session->userId().bare; - auto all = TextUtilities::SplitTags(mimeTag); - auto premiumSkipped = (DocumentData*)nullptr; - for (auto i = all.begin(); i != all.end();) { - const auto tag = *i; - if (TextUtilities::IsMentionLink(tag) - && TextUtilities::MentionNameDataToFields(tag).selfId != id) { - i = all.erase(i); - continue; - } else if (Ui::InputField::IsCustomEmojiLink(tag)) { - const auto data = Ui::InputField::CustomEmojiEntityData(tag); - const auto emoji = Data::ParseCustomEmojiData(data); - if (!emoji) { +[[nodiscard]] Fn FieldTagMimeProcessor( + not_null session, + Fn)> allowPremiumEmoji) { + return [=](QStringView mimeTag) { + const auto id = session->userId().bare; + auto all = TextUtilities::SplitTags(mimeTag); + auto premiumSkipped = (DocumentData*)nullptr; + for (auto i = all.begin(); i != all.end();) { + const auto tag = *i; + if (TextUtilities::IsMentionLink(tag) + && TextUtilities::MentionNameDataToFields(tag).selfId != id) { i = all.erase(i); continue; - } else if (!_session->premium()) { - const auto document = _session->data().document(emoji); - if (document->isPremiumEmoji()) { - if (!_allowPremiumEmoji - || premiumSkipped - || !_session->premiumPossible() - || !_allowPremiumEmoji(document)) { - premiumSkipped = document; - i = all.erase(i); - continue; + } else if (Ui::InputField::IsCustomEmojiLink(tag)) { + const auto data = Ui::InputField::CustomEmojiEntityData(tag); + const auto emoji = Data::ParseCustomEmojiData(data); + if (!emoji) { + i = all.erase(i); + continue; + } else if (!session->premium()) { + const auto document = session->data().document(emoji); + if (document->isPremiumEmoji()) { + if (!allowPremiumEmoji + || premiumSkipped + || !session->premiumPossible() + || !allowPremiumEmoji(document)) { + premiumSkipped = document; + i = all.erase(i); + continue; + } } } } + ++i; } - ++i; - } - return TextUtilities::JoinTag(all); + return TextUtilities::JoinTag(all); + }; } //bool ValidateUrl(const QString &value) { @@ -352,7 +335,7 @@ void InitMessageFieldHandlers( field->setInstantReplaces(Ui::InstantReplaces::Default()); field->setInstantReplacesEnabled( Core::App().settings().replaceEmojiValue()); - field->setMarkdownReplacesEnabled(rpl::single(true)); + field->setMarkdownReplacesEnabled(true); if (show) { field->setEditLinkCallback( DefaultEditLinkCallback(show, field, fieldStyle)); @@ -360,6 +343,42 @@ void InitMessageFieldHandlers( } } +Fn)> FactcheckFieldIniter( + std::shared_ptr show) { + Expects(show != nullptr); + + return [=](not_null field) { + field->setTagMimeProcessor([](QStringView mimeTag) { + using Field = Ui::InputField; + auto all = TextUtilities::SplitTags(mimeTag); + for (auto i = all.begin(); i != all.end();) { + const auto tag = *i; + if (tag != Field::kTagBold + && tag != Field::kTagItalic + && (!Field::IsValidMarkdownLink(mimeTag) + || TextUtilities::IsMentionLink(mimeTag))) { + i = all.erase(i); + continue; + } + ++i; + } + return TextUtilities::JoinTag(all); + }); + field->setInstantReplaces(Ui::InstantReplaces::Default()); + field->setInstantReplacesEnabled( + Core::App().settings().replaceEmojiValue()); + field->setMarkdownReplacesEnabled(rpl::single( + Ui::MarkdownEnabledState{ + Ui::MarkdownEnabled{ + { Ui::InputField::kTagBold, Ui::InputField::kTagItalic } + } + } + )); + field->setEditLinkCallback(DefaultEditLinkCallback(show, field)); + InitSpellchecker(show, field); + }; +} + void InitMessageFieldHandlers( not_null controller, not_null field, diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 0c9d8ff85..1e67642a7 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -77,6 +77,9 @@ void InitSpellchecker( not_null field, bool skipDictionariesManager = false); +[[nodiscard]] Fn)> FactcheckFieldIniter( + std::shared_ptr show); + bool HasSendText(not_null field); void InitMessageFieldFade( diff --git a/Telegram/SourceFiles/data/components/factchecks.cpp b/Telegram/SourceFiles/data/components/factchecks.cpp index 07051dabd..d068f8146 100644 --- a/Telegram/SourceFiles/data/components/factchecks.cpp +++ b/Telegram/SourceFiles/data/components/factchecks.cpp @@ -199,18 +199,20 @@ void Factchecks::save( void Factchecks::save( FullMsgId itemId, - TextWithEntities was, + const TextWithEntities &was, TextWithEntities text, std::shared_ptr show) { - const auto done = [=](QString error) { + const auto wasEmpty = was.empty(); + const auto textEmpty = text.empty(); + save(itemId, std::move(text), [=](QString error) { show->showToast(!error.isEmpty() ? error - : was.empty() + : wasEmpty ? tr::lng_factcheck_remove_done(tr::now) - : text.empty() + : textEmpty ? tr::lng_factcheck_add_done(tr::now) : tr::lng_factcheck_edit_done(tr::now)); - }; + }); } } // namespace Data diff --git a/Telegram/SourceFiles/data/components/factchecks.h b/Telegram/SourceFiles/data/components/factchecks.h index 515e52286..a7eaca3d4 100644 --- a/Telegram/SourceFiles/data/components/factchecks.h +++ b/Telegram/SourceFiles/data/components/factchecks.h @@ -45,7 +45,7 @@ public: Fn done); void save( FullMsgId itemId, - TextWithEntities was, + const TextWithEntities &was, TextWithEntities text, std::shared_ptr show); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 76fa5c463..57f11e678 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2184,7 +2184,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { TextWithEntities result) { const auto show = controller->uiShow(); session->factchecks().save(itemId, text, result, show); - })); + }, FactcheckFieldIniter(controller->uiShow()))); }, &st::menuIconFactcheck); } const auto pinItem = (item->canPin() && item->isPinned()) diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 3d47b358e..b217777a5 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -69,6 +69,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_message_reactions.h" #include "data/stickers/data_custom_emoji.h" +#include "chat_helpers/message_field.h" // FactcheckFieldIniter. #include "core/file_utilities.h" #include "core/click_handler_types.h" #include "base/platform/base_platform_info.h" @@ -736,7 +737,7 @@ void AddFactcheckAction( TextWithEntities result) { const auto show = controller->uiShow(); session->factchecks().save(itemId, text, result, show); - })); + }, FactcheckFieldIniter(controller->uiShow()))); }, &st::menuIconFactcheck); } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index ba73e3b3a..ff9fddc74 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -448,7 +448,7 @@ void MainWindow::updateGlobalMenuHook() { auto canSelectAll = false; const auto mimeData = QGuiApplication::clipboard()->mimeData(); const auto clipboardHasText = mimeData ? mimeData->hasText() : false; - auto markdownEnabled = false; + auto markdownState = Ui::MarkdownEnabledState(); if (const auto edit = qobject_cast(focused)) { canCut = canCopy = canDelete = edit->hasSelectedText(); canSelectAll = !edit->text().isEmpty(); @@ -464,7 +464,7 @@ void MainWindow::updateGlobalMenuHook() { if (canCopy) { if (const auto inputField = dynamic_cast( focused->parentWidget())) { - markdownEnabled = inputField->isMarkdownEnabled(); + markdownState = inputField->markdownEnabledState(); } } } else if (const auto list = dynamic_cast(focused)) { @@ -489,13 +489,19 @@ void MainWindow::updateGlobalMenuHook() { ForceDisabled(psNewGroup, inactive || support); ForceDisabled(psNewChannel, inactive || support); - ForceDisabled(psBold, !markdownEnabled); - ForceDisabled(psItalic, !markdownEnabled); - ForceDisabled(psUnderline, !markdownEnabled); - ForceDisabled(psStrikeOut, !markdownEnabled); - ForceDisabled(psBlockquote, !markdownEnabled); - ForceDisabled(psMonospace, !markdownEnabled); - ForceDisabled(psClearFormat, !markdownEnabled); + const auto diabled = [=](const QString &tag) { + return !markdownState.enabledForTag(tag); + }; + using Field = Ui::InputField; + ForceDisabled(psBold, diabled(Field::kTagBold)); + ForceDisabled(psItalic, diabled(Field::kTagItalic)); + ForceDisabled(psUnderline, diabled(Field::kTagUnderline)); + ForceDisabled(psStrikeOut, diabled(Field::kTagStrikeOut)); + ForceDisabled(psBlockquote, diabled(Field::kTagBlockquote)); + ForceDisabled( + psMonospace, + diabled(Field::kTagPre) || diabled(Field::kTagCode)); + ForceDisabled(psClearFormat, markdownState.disabled()); } bool MainWindow::eventFilter(QObject *obj, QEvent *evt) { diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.h b/Telegram/SourceFiles/platform/mac/main_window_mac.h index 2a3a59b9c..63ed4009b 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.h +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.h @@ -64,8 +64,6 @@ private: base::Timer _hideAfterFullScreenTimer; - rpl::variable _canApplyMarkdown; - QMenuBar psMainMenu; QAction *psLogout = nullptr; QAction *psUndo = nullptr; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 9b0dd14bb..43ba894b4 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -92,10 +92,11 @@ public: void setNativeWindow(NSWindow *window, NSView *view); void initTouchBar( NSWindow *window, - not_null controller, - rpl::producer canApplyMarkdown); + not_null controller); void setWindowBadge(const QString &str); + void setMarkdownEnabledState(Ui::MarkdownEnabledState state); + bool clipboardHasText(); ~Private(); @@ -103,6 +104,8 @@ private: not_null _public; friend class MainWindow; + rpl::variable _markdownState; + NSWindow * __weak _nativeWindow = nil; NSView * __weak _nativeView = nil; @@ -229,8 +232,7 @@ void MainWindow::Private::setNativeWindow(NSWindow *window, NSView *view) { void MainWindow::Private::initTouchBar( NSWindow *window, - not_null controller, - rpl::producer canApplyMarkdown) { + not_null controller) { if (!IsMac10_13OrGreater()) { return; } @@ -240,12 +242,17 @@ void MainWindow::Private::initTouchBar( [window performSelectorOnMainThread:@selector(setTouchBar:) withObject:[[[RootTouchBar alloc] - init:std::move(canApplyMarkdown) + init:_markdownState.value() controller:controller domain:(&Core::App().domain())] autorelease] waitUntilDone:true]; } +void MainWindow::Private::setMarkdownEnabledState( + Ui::MarkdownEnabledState state) { + _markdownState = state; +} + bool MainWindow::Private::clipboardHasText() { auto currentChangeCount = static_cast([_generalPasteboard changeCount]); if (_generalPasteboardChangeCount != currentChangeCount) { @@ -289,10 +296,7 @@ void MainWindow::initHook() { if (auto view = reinterpret_cast(winId())) { if (auto window = [view window]) { _private->setNativeWindow(window, view); - _private->initTouchBar( - window, - &controller(), - _canApplyMarkdown.changes()); + _private->initTouchBar(window, &controller()); } } } @@ -558,7 +562,7 @@ void MainWindow::updateGlobalMenuHook() { auto focused = QApplication::focusWidget(); bool canUndo = false, canRedo = false, canCut = false, canCopy = false, canPaste = false, canDelete = false, canSelectAll = false; auto clipboardHasText = _private->clipboardHasText(); - auto canApplyMarkdown = false; + auto markdownState = Ui::MarkdownEnabledState(); if (auto edit = qobject_cast(focused)) { canCut = canCopy = canDelete = edit->hasSelectedText(); canSelectAll = !edit->text().isEmpty(); @@ -574,7 +578,7 @@ void MainWindow::updateGlobalMenuHook() { if (canCopy) { if (const auto inputField = dynamic_cast( focused->parentWidget())) { - canApplyMarkdown = inputField->isMarkdownEnabled(); + markdownState = inputField->markdownEnabledState(); } } } else if (auto list = dynamic_cast(focused)) { @@ -582,7 +586,7 @@ void MainWindow::updateGlobalMenuHook() { canDelete = list->canDeleteSelected(); } - _canApplyMarkdown = canApplyMarkdown; + _private->setMarkdownEnabledState(markdownState); updateIsActive(); const auto logged = (sessionController() != nullptr); @@ -603,13 +607,19 @@ void MainWindow::updateGlobalMenuHook() { ForceDisabled(psNewChannel, inactive || support); ForceDisabled(psShowTelegram, isActive()); - ForceDisabled(psBold, !canApplyMarkdown); - ForceDisabled(psItalic, !canApplyMarkdown); - ForceDisabled(psUnderline, !canApplyMarkdown); - ForceDisabled(psStrikeOut, !canApplyMarkdown); - ForceDisabled(psBlockquote, !canApplyMarkdown); - ForceDisabled(psMonospace, !canApplyMarkdown); - ForceDisabled(psClearFormat, !canApplyMarkdown); + const auto diabled = [=](const QString &tag) { + return !markdownState.enabledForTag(tag); + }; + using Field = Ui::InputField; + ForceDisabled(psBold, diabled(Field::kTagBold)); + ForceDisabled(psItalic, diabled(Field::kTagItalic)); + ForceDisabled(psUnderline, diabled(Field::kTagUnderline)); + ForceDisabled(psStrikeOut, diabled(Field::kTagStrikeOut)); + ForceDisabled(psBlockquote, diabled(Field::kTagBlockquote)); + ForceDisabled( + psMonospace, + diabled(Field::kTagPre) || diabled(Field::kTagCode)); + ForceDisabled(psClearFormat, markdownState.disabled()); } bool MainWindow::eventFilter(QObject *obj, QEvent *evt) { diff --git a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.h b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.h index 464f87c9c..f34cf8190 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.h +++ b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.h @@ -17,9 +17,13 @@ namespace Window { class Controller; } // namespace Window +namespace Ui { +struct MarkdownEnabledState; +} // namespace Ui + API_AVAILABLE(macos(10.12.2)) @interface RootTouchBar : NSTouchBar -- (id)init:(rpl::producer)canApplyMarkdown +- (id)init:(rpl::producer)markdownState controller:(not_null)controller domain:(not_null)domain; @end diff --git a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.mm b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.mm index 5f314e4b5..ea273dab7 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.mm @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/mac/touchbar/mac_touchbar_audio.h" #include "platform/mac/touchbar/mac_touchbar_common.h" #include "platform/mac/touchbar/mac_touchbar_main.h" +#include "ui/widgets/fields/input_field.h" #include "window/window_controller.h" #include "window/window_session_controller.h" @@ -57,13 +58,12 @@ const auto kAudioItemIdentifier = @"touchbarAudio"; Main::Session *_session; Window::Controller *_controller; - bool _canApplyMarkdownLast; - rpl::event_stream _canApplyMarkdown; + rpl::variable _markdownState; rpl::event_stream<> _touchBarSwitches; rpl::lifetime _lifetime; } -- (id)init:(rpl::producer)canApplyMarkdown +- (id)init:(rpl::producer)markdownState controller:(not_null)controller domain:(not_null)domain { self = [super init]; @@ -75,10 +75,7 @@ const auto kAudioItemIdentifier = @"touchbarAudio"; self.defaultItemIdentifiers = @[]; }); _controller = controller; - _canApplyMarkdownLast = false; - std::move( - canApplyMarkdown - ) | rpl::start_to_stream(_canApplyMarkdown, _lifetime); + _markdownState = std::move(markdownState); auto sessionChanges = domain->activeSessionChanges( ) | rpl::map([=](Main::Session *session) { @@ -140,8 +137,7 @@ const auto kAudioItemIdentifier = @"touchbarAudio"; init:_controller touchBarSwitches:_touchBarSwitches.events()] autorelease]; rpl::combine( - _canApplyMarkdown.events_starting_with_copy( - _canApplyMarkdownLast), + _markdownState.value(), _controller->sessionController()->activeChatValue( ) | rpl::map([](Dialogs::Key k) { const auto topic = k.topic(); @@ -153,16 +149,15 @@ const auto kAudioItemIdentifier = @"touchbarAudio"; : (peer && Data::CanSendAnyOf(peer, rights)); }) | rpl::distinct_until_changed() ) | rpl::start_with_next([=]( - bool canApplyMarkdown, + Ui::MarkdownEnabledState state, bool hasActiveChat) { - _canApplyMarkdownLast = canApplyMarkdown; item.groupTouchBar.defaultItemIdentifiers = @[ kPinnedPanelItemIdentifier, - canApplyMarkdown + (!state.disabled() ? kPopoverInputItemIdentifier : hasActiveChat ? kPopoverPickerItemIdentifier - : @""]; + : @"")]; }, [item lifetime]); return [item autorelease]; diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index 08f27db07..286f1fc6c 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -88,7 +88,7 @@ EditInfoBox::EditInfoBox( _field->setInstantReplaces(Ui::InstantReplaces::Default()); _field->setInstantReplacesEnabled( Core::App().settings().replaceEmojiValue()); - _field->setMarkdownReplacesEnabled(rpl::single(true)); + _field->setMarkdownReplacesEnabled(true); _field->setEditLinkCallback( DefaultEditLinkCallback(controller->uiShow(), _field)); } diff --git a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp index 16a5b14af..2c371da40 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.cpp @@ -16,7 +16,8 @@ void EditFactcheckBox( not_null box, TextWithEntities current, int limit, - Fn save) { + Fn save, + Fn)> initField) { box->setTitle(tr::lng_factcheck_title()); const auto field = box->addRow(object_ptr( @@ -29,6 +30,7 @@ void EditFactcheckBox( TextUtilities::ConvertEntitiesToTextTags(current.entities) })); AddLengthLimitLabel(field, limit); + initField(field); enum class State { Initial, diff --git a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h index 7eb104af3..b2ca16479 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h +++ b/Telegram/SourceFiles/ui/boxes/edit_factcheck_box.h @@ -9,8 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" +namespace Ui { +class InputField; +} // namespace Ui + void EditFactcheckBox( not_null box, TextWithEntities current, int limit, - Fn save); + Fn save, + Fn)> initField); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index e7c598aff..0835adcc2 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit e7c598affe724322577ef46d9a07d1dcac3c617b +Subproject commit 0835adcc2d3cb1f2cf0d3630f6d95485864621f9 From 7194781bb836a34524de21c6246dbff51bc1daf3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 16:02:05 +0300 Subject: [PATCH 130/225] Added api support for premium bot peer type for credits history entries. --- Telegram/SourceFiles/api/api_credits.cpp | 34 +++++++++++++++++++ Telegram/SourceFiles/api/api_credits.h | 3 ++ .../info_statistics_list_controllers.cpp | 19 +++++++---- .../info_statistics_list_controllers.h | 2 +- .../SourceFiles/settings/settings_credits.cpp | 21 ++++++++---- .../ui/effects/credits_graphics.cpp | 16 ++++++++- 6 files changed, 80 insertions(+), 15 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 61ce79870..1604d6d5c 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "data/data_peer.h" #include "data/data_session.h" +#include "main/main_app_config.h" #include "main/main_session.h" #if _DEBUG #include "base/random.h" @@ -155,4 +156,37 @@ Data::CreditTopupOptions CreditsTopupOptions::options() const { return _options; } +rpl::producer> PremiumPeerBot( + not_null session) { + const auto username = session->appConfig().get( + u"premium_bot_username"_q, + QString()); + if (username.isEmpty()) { + return rpl::never>(); + } + if (const auto p = session->data().peerByUsername(username)) { + return rpl::single>(p); + } + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + const auto api = lifetime.make_state(&session->mtp()); + + api->request(MTPcontacts_ResolveUsername( + MTP_string(username) + )).done([=](const MTPcontacts_ResolvedPeer &result) { + session->data().processUsers(result.data().vusers()); + session->data().processChats(result.data().vchats()); + const auto botPeer = session->data().peerLoaded( + peerFromMTP(result.data().vpeer())); + if (!botPeer) { + return consumer.put_done(); + } + consumer.put_next(not_null{ botPeer }); + }).send(); + + return lifetime; + }; +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index 5807f34c7..265e7b387 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -68,4 +68,7 @@ private: }; +[[nodiscard]] rpl::producer> PremiumPeerBot( + not_null session); + } // namespace Api diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 5154a3044..ecbcff3ec 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -121,7 +121,7 @@ struct BoostsDescriptor final { struct CreditsDescriptor final { Data::CreditsStatusSlice firstSlice; Fn entryClickedCallback; - not_null peer; + not_null premiumBot; not_null creditIcon; bool in = false; bool out = false; @@ -768,7 +768,7 @@ void CreditsRow::init() { constexpr auto kMinus = QChar(0x2212); _rightText.setText( st::semiboldTextStyle, - (PeerListRow::special() ? QChar('+') : kMinus) + (!_entry.bareId ? QChar('+') : kMinus) + Lang::FormatCountDecimal(_entry.credits)); } _paintUserpicCallback = !PeerListRow::special() @@ -816,7 +816,7 @@ void CreditsRow::rightActionPaint( bool actionSelected) { const auto &font = _rightText.style()->font; y += _rowHeight / 2; - p.setPen(PeerListRow::special() + p.setPen(!_entry.bareId ? st::boxTextFgGood : st::menuIconAttentionColor); x += st::creditsHistoryRightSkip; @@ -850,6 +850,7 @@ private: void applySlice(const Data::CreditsStatusSlice &slice); const not_null _session; + const not_null _premiumBot; Fn _entryClickedCallback; not_null const _creditIcon; @@ -863,10 +864,11 @@ private: }; CreditsController::CreditsController(CreditsDescriptor d) -: _session(&d.peer->session()) +: _session(&d.premiumBot->session()) +, _premiumBot(d.premiumBot) , _entryClickedCallback(std::move(d.entryClickedCallback)) , _creditIcon(d.creditIcon) -, _api(d.peer, d.in, d.out) +, _api(d.premiumBot, d.in, d.out) , _firstSlice(std::move(d.firstSlice)) { PeerListController::setStyleOverrides(&st::boostsListBox); } @@ -906,9 +908,12 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { .creditIcon = _creditIcon, .rowHeight = computeListSt().item.height, }; + using Type = Data::CreditsHistoryEntry::PeerType; if (item.bareId) { const auto peer = session().data().peer(PeerId(item.bareId)); return std::make_unique(peer, descriptor); + } else if (item.peerType == Type::PremiumBot) { + return std::make_unique(_premiumBot, descriptor); } else { return std::make_unique(descriptor); } @@ -1084,7 +1089,7 @@ void AddCreditsHistoryList( const Data::CreditsStatusSlice &firstSlice, not_null container, Fn callback, - not_null self, + not_null bot, not_null icon, bool in, bool out) { @@ -1094,7 +1099,7 @@ void AddCreditsHistoryList( PeerListContentDelegateSimple delegate; CreditsController controller; }; - auto d = CreditsDescriptor{ firstSlice, callback, self, icon, in, out }; + auto d = CreditsDescriptor{ firstSlice, callback, bot, icon, in, out }; const auto state = container->lifetime().make_state(std::move(d)); state->delegate.setContent(container->add( diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h index 0e3d00019..09883090d 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h @@ -50,7 +50,7 @@ void AddCreditsHistoryList( const Data::CreditsStatusSlice &firstSlice, not_null container, Fn entryClickedCallback, - not_null self, + not_null premiumBot, not_null creditIcon, bool in, bool out); diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 127c5fd91..1b9efbe07 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -366,6 +366,7 @@ void Credits::setupHistory(not_null container) { Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); const auto fill = [=]( + not_null premiumBot, const Data::CreditsStatusSlice &fullSlice, const Data::CreditsStatusSlice &inSlice, const Data::CreditsStatusSlice &outSlice) { @@ -475,8 +476,12 @@ void Credits::setupHistory(not_null container) { Ui::AddSkip(content); Ui::AddSkip(content); + using Type = Data::CreditsHistoryEntry::PeerType; + const auto &stUser = st::boostReplaceUserpic; - const auto peer = e.bareId + const auto peer = (e.peerType == Type::PremiumBot) + ? premiumBot.get() + : e.bareId ? _controller->session().data().peer(PeerId(e.bareId)).get() : nullptr; if (peer) { @@ -580,7 +585,7 @@ void Credits::setupHistory(not_null container) { fullSlice, fullWrap->entity(), entryClicked, - _controller->session().user(), + premiumBot, &_star, true, true); @@ -588,7 +593,7 @@ void Credits::setupHistory(not_null container) { inSlice, inWrap->entity(), entryClicked, - _controller->session().user(), + premiumBot, &_star, true, false); @@ -596,7 +601,7 @@ void Credits::setupHistory(not_null container) { outSlice, outWrap->entity(), std::move(entryClicked), - _controller->session().user(), + premiumBot, &_star, false, true); @@ -617,8 +622,12 @@ void Credits::setupHistory(not_null container) { apiFull->request({}, [=](Data::CreditsStatusSlice fullSlice) { apiIn->request({}, [=](Data::CreditsStatusSlice inSlice) { apiOut->request({}, [=](Data::CreditsStatusSlice outSlice) { - fill(fullSlice, inSlice, outSlice); - apiLifetime->destroy(); + ::Api::PremiumPeerBot( + &_controller->session() + ) | rpl::start_with_next([=](not_null bot) { + fill(bot, fullSlice, inSlice, outSlice); + apiLifetime->destroy(); + }, *apiLifetime); }); }); }); diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index fd465f6b3..44e82195a 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_credits.h" #include "styles/style_intro.h" // introFragmentIcon. #include "styles/style_settings.h" +#include "styles/style_dialogs.h" namespace Ui { @@ -38,6 +39,13 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( return { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 }; case Data::CreditsHistoryEntry::PeerType::Fragment: return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::PremiumBot: + return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::Unsupported: + return { + st::historyPeerArchiveUserpicBg, + st::historyPeerArchiveUserpicBg, + }; } Unexpected("Unknown peer type."); }(); @@ -45,11 +53,17 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { userpic->paintCircle(p, x, y, outerWidth, size); using PeerType = Data::CreditsHistoryEntry::PeerType; + if (entry.peerType == PeerType::PremiumBot) { + return; + } + const auto rect = QRect(x, y, size, size); ((entry.peerType == PeerType::AppStore) ? st::sessionIconiPhone : (entry.peerType == PeerType::PlayMarket) ? st::sessionIconAndroid - : st::introFragmentIcon).paintInCenter(p, { x, y, size, size }); + : (entry.peerType == PeerType::Fragment) + ? st::introFragmentIcon + : st::dialogsInaccessibleUserpic).paintInCenter(p, rect); }; } From 1d3110228d6930f97f238f59567322d58c7abc46 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 24 May 2024 18:24:59 +0400 Subject: [PATCH 131/225] Fix phrase in factcheck toast. --- Telegram/SourceFiles/data/components/factchecks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/components/factchecks.cpp b/Telegram/SourceFiles/data/components/factchecks.cpp index d068f8146..77251925e 100644 --- a/Telegram/SourceFiles/data/components/factchecks.cpp +++ b/Telegram/SourceFiles/data/components/factchecks.cpp @@ -207,9 +207,9 @@ void Factchecks::save( save(itemId, std::move(text), [=](QString error) { show->showToast(!error.isEmpty() ? error - : wasEmpty - ? tr::lng_factcheck_remove_done(tr::now) : textEmpty + ? tr::lng_factcheck_remove_done(tr::now) + : wasEmpty ? tr::lng_factcheck_add_done(tr::now) : tr::lng_factcheck_edit_done(tr::now)); }); From 923aaec085cd10bd3dc87675307f3c811b489d44 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 20:46:23 +0300 Subject: [PATCH 132/225] Returned media to messages with credits invoice. --- .../SourceFiles/history/view/media/history_view_invoice.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp index 65987e6bb..ace70246a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_invoice.h" -#include "boxes/send_credits_box.h" // IsCreditsInvoice. #include "lang/lang_keys.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" @@ -35,7 +34,7 @@ Invoice::Invoice( } void Invoice::fillFromData(not_null invoice) { - const auto isCreditsCurrency = Ui::IsCreditsInvoice(_parent->data()); + const auto isCreditsCurrency = false; if (invoice->photo && !isCreditsCurrency) { const auto spoiler = false; _attach = std::make_unique( From f08ff9247083c72de12fbcf7a9c6039673054fb5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 20:54:13 +0300 Subject: [PATCH 133/225] Added initial ability to provide data for non-panel payment forms. --- .../payments/payments_checkout_process.cpp | 1 + .../payments/payments_checkout_process.h | 5 + .../SourceFiles/payments/payments_form.cpp | 37 ++++- Telegram/SourceFiles/payments/payments_form.h | 128 ++++++++++-------- 4 files changed, 113 insertions(+), 58 deletions(-) diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 108376084..14e7d695d 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -381,6 +381,7 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { if (weak) { closeAndReactivate(CheckoutResult::Paid); } + }, [&](const CreditsPaymentStarted &data) { }, [&](const Error &error) { handleError(error); }); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 9a8703927..e26fff3cd 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -40,6 +40,7 @@ struct Error; struct InvoiceCredits; struct InvoiceId; struct InvoicePremiumGiftCode; +struct CreditsFormData; enum class Mode { Payment, @@ -53,6 +54,10 @@ enum class CheckoutResult { Failed, }; +struct NonPanelPaymentForm : std::variant> { + using variant::variant; +}; + struct PaidInvoice { QString title; }; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index b6512d532..f3283b570 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -376,7 +376,42 @@ void Form::requestForm() { MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)) )).done([=](const MTPpayments_PaymentForm &result) { hideProgress(); - processForm(result); + result.match([&](const MTPDpayments_paymentForm &data) { + processForm(result); + }, [&](const MTPDpayments_paymentFormStars &data) { + _session->data().processUsers(data.vusers()); + const auto currency = qs(data.vinvoice().data().vcurrency()); + const auto &tlPrices = data.vinvoice().data().vprices().v; + const auto amount = tlPrices.empty() + ? 0 + : tlPrices.front().data().vamount().v; + if (currency != "XTR" || !amount) { + using Type = Error::Type; + _updates.fire(Error{ Type::Form, u"Bad Stars Form."_q }); + return; + } + const auto invoice = InvoiceCredits{ + .session = _session, + .randomId = 0, + .credits = amount, + .currency = currency, + .amount = amount, + }; + const auto formData = CreditsFormData{ + .formId = data.vform_id().v, + .botId = data.vbot_id().v, + .title = qs(data.vtitle()), + .description = qs(data.vdescription()), + .photo = data.vphoto() + ? _session->data().photoFromWeb( + *data.vphoto(), + ImageLocation()) + : nullptr, + .invoice = invoice, + .inputInvoice = inputInvoice(), + }; + _updates.fire(CreditsPaymentStarted{ .data = formData }); + }); }).fail([=](const MTP::Error &error) { hideProgress(); _updates.fire(Error{ Error::Type::Form, error.type() }); diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index ffa6b17d7..609af8213 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -120,63 +120,6 @@ struct PaymentMethod { Ui::PaymentMethodDetails ui; }; -struct ToggleProgress { - bool shown = true; -}; -struct FormReady {}; -struct ThumbnailUpdated { - QImage thumbnail; -}; -struct ValidateFinished {}; -struct PaymentMethodUpdate { - bool requestNewPassword = false; -}; -struct VerificationNeeded { - QString url; -}; -struct TmpPasswordRequired {}; -struct BotTrustRequired { - not_null bot; - not_null provider; -}; -struct PaymentFinished { - MTPUpdates updates; -}; -struct Error { - enum class Type { - None, - Form, - Validate, - Stripe, - SmartGlocal, - TmpPassword, - Send, - }; - Type type = Type::None; - QString id; - - [[nodiscard]] bool empty() const { - return (type == Type::None); - } - [[nodiscard]] explicit operator bool() const { - return !empty(); - } -}; - -struct FormUpdate : std::variant< - ToggleProgress, - FormReady, - ThumbnailUpdated, - ValidateFinished, - PaymentMethodUpdate, - VerificationNeeded, - TmpPasswordRequired, - BotTrustRequired, - PaymentFinished, - Error> { - using variant::variant; -}; - struct InvoiceMessage { not_null peer; MsgId itemId = 0; @@ -234,6 +177,77 @@ struct InvoiceId { InvoiceCredits> value; }; +struct CreditsFormData { + uint64 formId = 0; + uint64 botId = 0; + QString title; + QString description; + PhotoData *photo = nullptr; + InvoiceCredits invoice; + MTPInputInvoice inputInvoice; +}; + +struct ToggleProgress { + bool shown = true; +}; +struct FormReady {}; +struct ThumbnailUpdated { + QImage thumbnail; +}; +struct ValidateFinished {}; +struct PaymentMethodUpdate { + bool requestNewPassword = false; +}; +struct VerificationNeeded { + QString url; +}; +struct TmpPasswordRequired {}; +struct BotTrustRequired { + not_null bot; + not_null provider; +}; +struct PaymentFinished { + MTPUpdates updates; +}; +struct CreditsPaymentStarted { + CreditsFormData data; +}; +struct Error { + enum class Type { + None, + Form, + Validate, + Stripe, + SmartGlocal, + TmpPassword, + Send, + }; + Type type = Type::None; + QString id; + + [[nodiscard]] bool empty() const { + return (type == Type::None); + } + [[nodiscard]] explicit operator bool() const { + return !empty(); + } +}; + +struct FormUpdate : std::variant< + ToggleProgress, + FormReady, + ThumbnailUpdated, + ValidateFinished, + PaymentMethodUpdate, + VerificationNeeded, + TmpPasswordRequired, + BotTrustRequired, + PaymentFinished, + CreditsPaymentStarted, + Error> { + using variant::variant; +}; + [[nodiscard]] not_null SessionFromId(const InvoiceId &id); [[nodiscard]] MTPinputStorePaymentPurpose InvoicePremiumGiftCodeGiveawayToTL( From 3dd894ad30936f7a3deeb456715abe41316b5be2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 20:48:01 +0300 Subject: [PATCH 134/225] Improved SendCreditsBox for data from credits payment form. --- Telegram/SourceFiles/api/api_bot.cpp | 2 +- .../SourceFiles/boxes/send_credits_box.cpp | 64 ++++++++++++++----- Telegram/SourceFiles/boxes/send_credits_box.h | 6 +- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index befdc04e5..f4331cd1f 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -332,7 +332,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::Buy: { if (Ui::IsCreditsInvoice(item)) { - controller->uiShow()->show(Box(Ui::SendCreditsBox, item)); + // controller->uiShow()->show(Box(Ui::SendCreditsBox, item)); } else { Payments::CheckoutProcess::Start( item, diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index ae45850bd..743acd5d7 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/send_credits_box.h" #include "api/api_credits.h" +#include "apiwrap.h" #include "core/ui_integration.h" // Core::MarkedTextContext. #include "data/data_credits.h" #include "data/data_file_origin.h" @@ -17,8 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "history/history.h" #include "history/history_item.h" +#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. #include "lang/lang_keys.h" #include "main/main_session.h" +#include "payments/payments_checkout_process.h" +#include "payments/payments_form.h" #include "payments/payments_form.h" #include "settings/settings_credits.h" #include "ui/controls/userpic_button.h" @@ -38,21 +42,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" namespace Ui { -namespace { -} // namespace void SendCreditsBox( not_null box, - not_null item) { - const auto media = item->media(); - const auto invoice = media ? media->invoice() : nullptr; - if (!invoice) { + std::shared_ptr form) { + if (!form) { return; } + struct State { + rpl::variable confirmButtonBusy = false; + }; + const auto state = box->lifetime().make_state(); box->setStyle(st::giveawayGiftCodeBox); box->setNoContentMargin(true); - const auto session = &item->history()->owner().session(); + const auto session = form->invoice.session; const auto photoSize = st::defaultUserpicButton.photoSize; @@ -87,7 +91,9 @@ void SendCreditsBox( }, ministarsContainer->lifetime()); } - if (false && invoice->photo) { + const auto bot = session->data().user(form->botId); + + if (form->photo) { struct State { std::shared_ptr view; Image *image = nullptr; @@ -98,8 +104,8 @@ void SendCreditsBox( object_ptr>( content, object_ptr(content)))->entity(); - state->view = invoice->photo->createMediaView(); - state->view->wanted(Data::PhotoSize::Large, item->fullId()); + state->view = form->photo->createMediaView(); + form->photo->load(Data::PhotoSize::Thumbnail, {}); widget->resize(Size(photoSize)); @@ -135,7 +141,7 @@ void SendCreditsBox( content, object_ptr( content, - item->author(), + bot, st::defaultUserpicButton))); widget->setAttribute(Qt::WA_TransparentForMouseEvents); } @@ -156,18 +162,42 @@ void SendCreditsBox( box, tr::lng_credits_box_out_sure( lt_count, - rpl::single(invoice->amount) | tr::to_count(), + rpl::single(form->invoice.amount) | tr::to_count(), lt_text, - rpl::single(TextWithEntities{ invoice->title }), + rpl::single(TextWithEntities{ form->title }), lt_bot, - rpl::single(TextWithEntities{ item->author()->name() }), + rpl::single(TextWithEntities{ bot->name() }), Ui::Text::RichLangValue), st::creditsBoxAbout))); Ui::AddSkip(content); Ui::AddSkip(content); const auto button = box->addButton(rpl::single(QString()), [=] { + if (state->confirmButtonBusy.current()) { + return; + } + state->confirmButtonBusy = true; + session->api().request( + MTPpayments_SendStarsForm( + MTP_flags(0), + MTP_long(form->formId), + form->inputInvoice) + ).done([=](auto result) { + state->confirmButtonBusy = false; + box->closeBox(); + }).fail([=](const MTP::Error &error) { + state->confirmButtonBusy = false; + box->uiShow()->showToast(error.type()); + }).send(); }); + { + using namespace Info::Statistics; + const auto loadingAnimation = InfiniteRadialAnimationWidget( + button, + st::giveawayGiftCodeStartButton.height / 2); + AddChildToWidgetCenter(button.data(), loadingAnimation); + loadingAnimation->showOn(state->confirmButtonBusy.value()); + } { const auto emojiMargin = QMargins( 0, @@ -181,7 +211,7 @@ void SendCreditsBox( true)); auto buttonText = tr::lng_credits_box_out_confirm( lt_count, - rpl::single(invoice->amount) | tr::to_count(), + rpl::single(form->invoice.amount) | tr::to_count(), lt_emoji, rpl::single(buttonEmoji), Ui::Text::RichLangValue); @@ -208,6 +238,10 @@ void SendCreditsBox( (size.height() - buttonLabel->height()) / 2); }, buttonLabel->lifetime()); buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + state->confirmButtonBusy.value( + ) | rpl::start_with_next([=](bool busy) { + buttonLabel->setVisible(!busy); + }, buttonLabel->lifetime()); } const auto buttonWidth = st::boxWidth diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index fa877e305..37a093ca9 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -9,13 +9,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryItem; +namespace Payments { +struct CreditsFormData; +} // namespace Payments + namespace Ui { class GenericBox; void SendCreditsBox( not_null box, - not_null item); + std::shared_ptr data); [[nodiscard]] bool IsCreditsInvoice(not_null item); From dda6b92becc623d2b9a222c45735f5502169de90 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 20:47:15 +0300 Subject: [PATCH 135/225] Added initial ability to process non-panel payment forms. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/api/api_bot.cpp | 19 +++---- .../SourceFiles/core/local_url_handlers.cpp | 4 +- Telegram/SourceFiles/history/history_item.cpp | 6 ++- .../media/history_view_extended_preview.cpp | 8 ++- .../inline_bots/bot_attach_web_view.cpp | 10 +++- .../payments/payments_checkout_process.cpp | 49 ++++++++++++++++--- .../payments/payments_checkout_process.h | 9 +++- .../payments/payments_non_panel_process.cpp | 46 +++++++++++++++++ .../payments/payments_non_panel_process.h | 27 ++++++++++ 10 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 Telegram/SourceFiles/payments/payments_non_panel_process.cpp create mode 100644 Telegram/SourceFiles/payments/payments_non_panel_process.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index a562048ec..75ab2b48f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1221,6 +1221,8 @@ PRIVATE payments/payments_checkout_process.h payments/payments_form.cpp payments/payments_form.h + payments/payments_non_panel_process.cpp + payments/payments_non_panel_process.h platform/linux/file_utilities_linux.cpp platform/linux/file_utilities_linux.h platform/linux/launcher_linux.cpp diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index f4331cd1f..cf4611563 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_cloud_password.h" #include "api/api_send_progress.h" -#include "boxes/send_credits_box.h" #include "boxes/share_box.h" #include "boxes/passcode_box.h" #include "boxes/url_auth_box.h" @@ -28,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "inline_bots/bot_attach_web_view.h" #include "payments/payments_checkout_process.h" +#include "payments/payments_non_panel_process.h" #include "main/main_session.h" #include "mainwidget.h" #include "mainwindow.h" @@ -331,16 +331,13 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { } break; case ButtonType::Buy: { - if (Ui::IsCreditsInvoice(item)) { - // controller->uiShow()->show(Box(Ui::SendCreditsBox, item)); - } else { - Payments::CheckoutProcess::Start( - item, - Payments::Mode::Payment, - crl::guard(controller, [=](auto) { - controller->widget()->activate(); - })); - } + Payments::CheckoutProcess::Start( + item, + Payments::Mode::Payment, + crl::guard(controller, [=](auto) { + controller->widget()->activate(); + }), + Payments::ProcessNonPanelPaymentFormFactory(controller, item)); } break; case ButtonType::Url: { diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 6fa9d7bb6..544038640 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/background_preview_box.h" #include "ui/boxes/confirm_box.h" #include "ui/boxes/edit_birthday_box.h" +#include "payments/payments_non_panel_process.h" #include "boxes/share_box.h" #include "boxes/connection_box.h" #include "boxes/edit_privacy_box.h" @@ -1092,7 +1093,8 @@ bool ResolveInvoice( Payments::CheckoutProcess::Start( &controller->session(), slug, - crl::guard(window, [=](auto) { window->activate(); })); + crl::guard(window, [=](auto) { window->activate(); }), + Payments::ProcessNonPanelPaymentFormFactory(controller)); return true; } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 30cc8a9c3..c1af45c5f 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "payments/payments_checkout_process.h" // CheckoutProcess::Start. +#include "payments/payments_non_panel_process.h" // ProcessNonPanelPaymentFormFactory. #include "platform/platform_notifications_manager.h" #include "spellcheck/spellcheck_highlight_syntax.h" #include "styles/style_dialogs.h" @@ -3950,7 +3951,10 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { CheckoutProcess::Start( item, Mode::Receipt, - crl::guard(weak, [=](auto) { weak->window().activate(); })); + crl::guard(weak, [=](auto) { weak->window().activate(); }), + Payments::ProcessNonPanelPaymentFormFactory( + weak.get(), + item)); } }); } else if (type == mtpc_messageActionGroupCall diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index d127fc4fc..9db9acc07 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/power_saving.h" #include "data/data_session.h" #include "payments/payments_checkout_process.h" +#include "payments/payments_non_panel_process.h" #include "window/window_session_controller.h" #include "mainwindow.h" #include "core/click_handler_types.h" @@ -41,7 +42,12 @@ namespace { ? crl::guard( controller, [=](auto) { controller->widget()->activate(); }) - : Fn())); + : Fn()), + (controller + ? Payments::ProcessNonPanelPaymentFormFactory( + controller, + item) + : nullptr)); }); } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 1eb99362b..03561bdd6 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "payments/payments_checkout_process.h" +#include "payments/payments_non_panel_process.h" #include "storage/storage_account.h" #include "boxes/peer_list_controllers.h" #include "lang/lang_keys.h" @@ -603,7 +604,14 @@ void AttachWebView::botHandleInvoice(QString slug) { } }; _panel->hideForPayment(); - Payments::CheckoutProcess::Start(&_bot->session(), slug, reactivate); + Payments::CheckoutProcess::Start( + &_bot->session(), + slug, + reactivate, + _context + ? Payments::ProcessNonPanelPaymentFormFactory( + _context->controller.get()) + : nullptr); } void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) { diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 14e7d695d..597d7687b 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -64,7 +64,9 @@ base::flat_map, SessionProcesses> Processes; void CheckoutProcess::Start( not_null item, Mode mode, - Fn reactivate) { + Fn reactivate, + Fn nonPanelPaymentFormProcess) { + const auto hasNonPanelPaymentFormProcess = !!nonPanelPaymentFormProcess; auto &processes = LookupSessionProcesses(&item->history()->session()); const auto media = item->media(); const auto invoice = media ? media->invoice() : nullptr; @@ -83,7 +85,11 @@ void CheckoutProcess::Start( const auto i = processes.byItem.find(id); if (i != end(processes.byItem)) { i->second->setReactivateCallback(std::move(reactivate)); - i->second->requestActivate(); + i->second->setNonPanelPaymentFormProcess( + std::move(nonPanelPaymentFormProcess)); + if (!hasNonPanelPaymentFormProcess) { + i->second->requestActivate(); + } return; } const auto j = processes.byItem.emplace( @@ -92,19 +98,28 @@ void CheckoutProcess::Start( InvoiceId{ InvoiceMessage{ item->history()->peer, id.msg } }, mode, std::move(reactivate), + std::move(nonPanelPaymentFormProcess), PrivateTag{})).first; - j->second->requestActivate(); + if (!hasNonPanelPaymentFormProcess) { + j->second->requestActivate(); + } } void CheckoutProcess::Start( not_null session, const QString &slug, - Fn reactivate) { + Fn reactivate, + Fn nonPanelPaymentFormProcess) { + const auto hasNonPanelPaymentFormProcess = !!nonPanelPaymentFormProcess; auto &processes = LookupSessionProcesses(session); const auto i = processes.bySlug.find(slug); if (i != end(processes.bySlug)) { i->second->setReactivateCallback(std::move(reactivate)); - i->second->requestActivate(); + i->second->setNonPanelPaymentFormProcess( + std::move(nonPanelPaymentFormProcess)); + if (!hasNonPanelPaymentFormProcess) { + i->second->requestActivate(); + } return; } const auto j = processes.bySlug.emplace( @@ -113,8 +128,11 @@ void CheckoutProcess::Start( InvoiceId{ InvoiceSlug{ session, slug } }, Mode::Payment, std::move(reactivate), + std::move(nonPanelPaymentFormProcess), PrivateTag{})).first; - j->second->requestActivate(); + if (!hasNonPanelPaymentFormProcess) { + j->second->requestActivate(); + } } void CheckoutProcess::Start( @@ -135,6 +153,7 @@ void CheckoutProcess::Start( std::move(id), Mode::Payment, std::move(reactivate), + nullptr, PrivateTag{})).first; j->second->requestActivate(); } @@ -157,6 +176,7 @@ void CheckoutProcess::Start( std::move(id), Mode::Payment, std::move(reactivate), + nullptr, PrivateTag{})).first; j->second->requestActivate(); } @@ -280,11 +300,13 @@ CheckoutProcess::CheckoutProcess( InvoiceId id, Mode mode, Fn reactivate, + Fn nonPanelPaymentFormProcess, PrivateTag) : _session(SessionFromId(id)) , _form(std::make_unique
(id, (mode == Mode::Receipt))) , _panel(std::make_unique(panelDelegate())) -, _reactivate(std::move(reactivate)) { +, _reactivate(std::move(reactivate)) +, _nonPanelPaymentFormProcess(std::move(nonPanelPaymentFormProcess)) { _form->updates( ) | rpl::start_with_next([=](const FormUpdate &update) { handleFormUpdate(update); @@ -299,7 +321,9 @@ CheckoutProcess::CheckoutProcess( ) | rpl::start_with_next([=] { panelCancelEdit(); }, _panel->lifetime()); - showForm(); + if (!_nonPanelPaymentFormProcess) { + showForm(); + } _panel->toggleProgress(true); if (mode == Mode::Payment) { @@ -318,6 +342,11 @@ void CheckoutProcess::setReactivateCallback( _reactivate = std::move(reactivate); } +void CheckoutProcess::setNonPanelPaymentFormProcess( + Fn callback) { + _nonPanelPaymentFormProcess = std::move(callback); +} + void CheckoutProcess::requestActivate() { _panel->requestActivate(); } @@ -382,6 +411,10 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { closeAndReactivate(CheckoutResult::Paid); } }, [&](const CreditsPaymentStarted &data) { + if (_nonPanelPaymentFormProcess) { + _nonPanelPaymentFormProcess( + std::make_shared(data.data)); + } }, [&](const Error &error) { handleError(error); }); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index e26fff3cd..2f939d5a6 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -71,11 +71,13 @@ public: static void Start( not_null item, Mode mode, - Fn reactivate); + Fn reactivate, + Fn nonPanelPaymentFormProcess); static void Start( not_null session, const QString &slug, - Fn reactivate); + Fn reactivate, + Fn nonPanelPaymentFormProcess); static void Start( InvoicePremiumGiftCode giftCodeInvoice, Fn reactivate); @@ -93,6 +95,7 @@ public: InvoiceId id, Mode mode, Fn reactivate, + Fn nonPanelPaymentFormProcess, PrivateTag); ~CheckoutProcess(); @@ -111,6 +114,7 @@ private: static void UnregisterPaymentStart(not_null process); void setReactivateCallback(Fn reactivate); + void setNonPanelPaymentFormProcess(Fn); void requestActivate(); void closeAndReactivate(CheckoutResult result); void close(); @@ -173,6 +177,7 @@ private: const std::unique_ptr _panel; QPointer _enterPasswordBox; Fn _reactivate; + Fn _nonPanelPaymentFormProcess; SubmitState _submitState = SubmitState::None; bool _initialSilentValidation = false; bool _sendFormPending = false; diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp new file mode 100644 index 000000000..3669576d9 --- /dev/null +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -0,0 +1,46 @@ +/* +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 "payments/payments_non_panel_process.h" + +#include "payments/payments_checkout_process.h" // NonPanelPaymentForm. +#include "payments/payments_form.h" +#include "history/history_item.h" +#include "ui/layers/generic_box.h" +#include "window/window_session_controller.h" +#include "boxes/send_credits_box.h" + +namespace Payments { +namespace { + +bool IsCreditsInvoice(not_null item) { + const auto media = item->media(); + const auto invoice = media ? media->invoice() : nullptr; + return invoice && (invoice->currency == "XTR"); +} + +} // namespace + +Fn ProcessNonPanelPaymentFormFactory( + not_null controller) { + return [=](NonPanelPaymentForm form) { + using CreditsFormDataPtr = std::shared_ptr; + if (const auto creditsData = std::get_if(&form)) { + controller->uiShow()->show(Box(Ui::SendCreditsBox, *creditsData)); + } + }; +} + +Fn ProcessNonPanelPaymentFormFactory( + not_null controller, + not_null item) { + return IsCreditsInvoice(item) + ? ProcessNonPanelPaymentFormFactory(controller) + : nullptr; +} + +} // namespace Payments diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.h b/Telegram/SourceFiles/payments/payments_non_panel_process.h new file mode 100644 index 000000000..76939415d --- /dev/null +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.h @@ -0,0 +1,27 @@ +/* +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 HistoryItem; + +namespace Window { +class SessionController; +} // namespace Window + +namespace Payments { + +struct NonPanelPaymentForm; + +Fn ProcessNonPanelPaymentFormFactory( + not_null controller); + +Fn ProcessNonPanelPaymentFormFactory( + not_null controller, + not_null item); + +} // namespace Payments From e4e343b871155d3b80143cd46b0fb8ed8d2818b1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 20:47:36 +0300 Subject: [PATCH 136/225] Removed unused Ui::IsCreditsInvoice. --- Telegram/SourceFiles/boxes/send_credits_box.cpp | 6 ------ Telegram/SourceFiles/boxes/send_credits_box.h | 2 -- 2 files changed, 8 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 743acd5d7..445bc9029 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -288,10 +288,4 @@ void SendCreditsBox( } } -bool IsCreditsInvoice(not_null item) { - const auto media = item->media(); - const auto invoice = media ? media->invoice() : nullptr; - return invoice && (invoice->currency == "XTR"); -} - } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index 37a093ca9..bbc74107a 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -21,6 +21,4 @@ void SendCreditsBox( not_null box, std::shared_ptr data); -[[nodiscard]] bool IsCreditsInvoice(not_null item); - } // namespace Ui From d73313479b9e2d44cb99241e906f6a4c7ad836f4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 21:01:44 +0300 Subject: [PATCH 137/225] Partly reverted "Processed payments form with API scheme on layer 181.". --- .../SourceFiles/payments/payments_form.cpp | 114 +++++++----------- Telegram/SourceFiles/payments/payments_form.h | 4 +- 2 files changed, 47 insertions(+), 71 deletions(-) diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index f3283b570..8cdd0b9a2 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -377,7 +377,7 @@ void Form::requestForm() { )).done([=](const MTPpayments_PaymentForm &result) { hideProgress(); result.match([&](const MTPDpayments_paymentForm &data) { - processForm(result); + processForm(data); }, [&](const MTPDpayments_paymentFormStars &data) { _session->data().processUsers(data.vusers()); const auto currency = qs(data.vinvoice().data().vcurrency()); @@ -437,44 +437,33 @@ void Form::requestReceipt() { }).send(); } -void Form::processForm(const MTPpayments_PaymentForm &result) { - using TLForm = MTPDpayments_paymentForm; - using TLCredits = MTPDpayments_paymentFormStars; - result.match([&](const auto &data) { - _session->data().processUsers(data.vusers()); +void Form::processForm(const MTPDpayments_paymentForm &data) { + _session->data().processUsers(data.vusers()); - data.vinvoice().match([&](const auto &data) { - processInvoice(data); + data.vinvoice().match([&](const auto &data) { + processInvoice(data); + }); + processDetails(data); + if (const auto info = data.vsaved_info()) { + info->match([&](const auto &data) { + processSavedInformation(data); }); - }); - - processDetails(result); - result.match([&](const TLForm &data) { - if (const auto info = data.vsaved_info()) { - info->match([&](const auto &data) { - processSavedInformation(data); - }); - } - }, [](const TLCredits &) { - }); + } _paymentMethod.savedCredentials.clear(); _paymentMethod.savedCredentialsIndex = 0; - result.match([&](const TLForm &data) { - if (const auto credentials = data.vsaved_credentials()) { - _paymentMethod.savedCredentials.reserve(credentials->v.size()); - for (const auto &saved : credentials->v) { - _paymentMethod.savedCredentials.push_back({ - .id = qs(saved.data().vid()), - .title = qs(saved.data().vtitle()), - }); - } - refreshPaymentMethodDetails(); + if (const auto credentials = data.vsaved_credentials()) { + _paymentMethod.savedCredentials.reserve(credentials->v.size()); + for (const auto &saved : credentials->v) { + _paymentMethod.savedCredentials.push_back({ + .id = qs(saved.data().vid()), + .title = qs(saved.data().vtitle()), + }); } - if (const auto additional = data.vadditional_methods()) { - processAdditionalPaymentMethods(additional->v); - } - }, [](const TLCredits &) { - }); + refreshPaymentMethodDetails(); + } + if (const auto additional = data.vadditional_methods()) { + processAdditionalPaymentMethods(additional->v); + } fillPaymentMethodInformation(); _updates.fire(FormReady{}); } @@ -548,44 +537,31 @@ void Form::processInvoice(const MTPDinvoice &data) { }; } -void Form::processDetails(const MTPpayments_PaymentForm &result) { - using TLForm = MTPDpayments_paymentForm; - using TLCredits = MTPDpayments_paymentFormStars; - _details = result.match([&](const TLForm &data) { - const auto nativeParams = data.vnative_params(); - auto nativeParamsJson = nativeParams - ? nativeParams->match( - [&](const MTPDdataJSON &data) { return data.vdata().v; }) - : QByteArray(); - return FormDetails{ - .formId = data.vform_id().v, - .url = qs(data.vurl()), - .nativeProvider = qs(data.vnative_provider().value_or_empty()), - .nativeParamsJson = std::move(nativeParamsJson), - .botId = data.vbot_id().v, - .providerId = data.vprovider_id().v, - .canSaveCredentials = data.is_can_save_credentials(), - .passwordMissing = data.is_password_missing(), - }; - }, [](const TLCredits &data) { - return FormDetails{ - .formId = data.vform_id().v, - .botId = data.vbot_id().v, - }; - }); - _invoice.cover.title = result.match([](const auto &data) { - return qs(data.vtitle()); - }); +void Form::processDetails(const MTPDpayments_paymentForm &data) { + const auto nativeParams = data.vnative_params(); + auto nativeParamsJson = nativeParams + ? nativeParams->match( + [&](const MTPDdataJSON &data) { return data.vdata().v; }) + : QByteArray(); + _details = FormDetails{ + .formId = data.vform_id().v, + .url = qs(data.vurl()), + .nativeProvider = qs(data.vnative_provider().value_or_empty()), + .nativeParamsJson = std::move(nativeParamsJson), + .botId = data.vbot_id().v, + .providerId = data.vprovider_id().v, + .canSaveCredentials = data.is_can_save_credentials(), + .passwordMissing = data.is_password_missing(), + }; + _invoice.cover.title = qs(data.vtitle()); _invoice.cover.description = TextUtilities::ParseEntities( - result.match([](const auto &d) { return qs(d.vdescription()); }), + qs(data.vdescription()), TextParseLinks | TextParseMultiline); if (_invoice.cover.thumbnail.isNull() && !_thumbnailLoadProcess) { - result.match([&](const auto &data) { - if (const auto photo = data.vphoto()) { - loadThumbnail( - _session->data().photoFromWeb(*photo, ImageLocation())); - } - }); + if (const auto photo = data.vphoto()) { + loadThumbnail( + _session->data().photoFromWeb(*photo, ImageLocation())); + } } if (const auto botId = _details.botId) { if (const auto bot = _session->data().userLoaded(botId)) { diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 609af8213..6507ffa13 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -313,11 +313,11 @@ private: void requestForm(); void requestReceipt(); - void processForm(const MTPpayments_PaymentForm &result); + void processForm(const MTPDpayments_paymentForm &data); void processReceipt(const MTPDpayments_paymentReceipt &data); void processReceipt(const MTPDpayments_paymentReceiptStars &data); void processInvoice(const MTPDinvoice &data); - void processDetails(const MTPpayments_PaymentForm &result); + void processDetails(const MTPDpayments_paymentForm &data); void processDetails(const MTPDpayments_paymentReceipt &data); void processDetails(const MTPDpayments_paymentReceiptStars &data); void processSavedInformation(const MTPDpaymentRequestedInfo &data); From e9fb580ba42311d5ab0f75f75d77a5616139e5ec Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 21:13:14 +0300 Subject: [PATCH 138/225] Moved out credits currency to single place. --- Telegram/SourceFiles/payments/payments_form.cpp | 3 ++- Telegram/SourceFiles/payments/payments_non_panel_process.cpp | 3 ++- Telegram/SourceFiles/ui/text/format_values.h | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 8cdd0b9a2..c6ddd84a9 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "smartglocal/smartglocal_token.h" #include "storage/storage_account.h" #include "ui/image/image.h" +#include "ui/text/format_values.h" #include "ui/text/text_entity.h" #include "apiwrap.h" #include "core/core_cloud_password.h" @@ -385,7 +386,7 @@ void Form::requestForm() { const auto amount = tlPrices.empty() ? 0 : tlPrices.front().data().vamount().v; - if (currency != "XTR" || !amount) { + if (currency != ::Ui::kCreditsCurrency || !amount) { using Type = Error::Type; _updates.fire(Error{ Type::Form, u"Bad Stars Form."_q }); return; diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 3669576d9..47232ac8a 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/payments_form.h" #include "history/history_item.h" #include "ui/layers/generic_box.h" +#include "ui/text/format_values.h" #include "window/window_session_controller.h" #include "boxes/send_credits_box.h" @@ -20,7 +21,7 @@ namespace { bool IsCreditsInvoice(not_null item) { const auto media = item->media(); const auto invoice = media ? media->invoice() : nullptr; - return invoice && (invoice->currency == "XTR"); + return invoice && (invoice->currency == Ui::kCreditsCurrency); } } // namespace diff --git a/Telegram/SourceFiles/ui/text/format_values.h b/Telegram/SourceFiles/ui/text/format_values.h index b73775118..e4294eed1 100644 --- a/Telegram/SourceFiles/ui/text/format_values.h +++ b/Telegram/SourceFiles/ui/text/format_values.h @@ -13,6 +13,8 @@ inline constexpr auto FileStatusSizeReady = 0xFFFFFFF0LL; inline constexpr auto FileStatusSizeLoaded = 0xFFFFFFF1LL; inline constexpr auto FileStatusSizeFailed = 0xFFFFFFF2LL; +inline const QString kCreditsCurrency = u"XTR"_q; + [[nodiscard]] QString FormatSizeText(qint64 size); [[nodiscard]] QString FormatDownloadText(qint64 ready, qint64 total); [[nodiscard]] QString FormatProgressText(qint64 ready, qint64 total); From a2a27e115cf9b315dd1c93f5b42831349c9e5080 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 21:55:19 +0300 Subject: [PATCH 139/225] Moved out box for credits history entries to single place. --- .../SourceFiles/settings/settings_credits.cpp | 235 +++++++++--------- .../SourceFiles/settings/settings_credits.h | 17 ++ 2 files changed, 139 insertions(+), 113 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 1b9efbe07..c02860045 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -465,120 +465,12 @@ void Credits::setupHistory(not_null container) { }, inner->lifetime()); const auto controller = _controller->parentController(); - const auto entryBox = [=]( - not_null box, - const Data::CreditsHistoryEntry &e) { - box->setStyle(st::giveawayGiftCodeBox); - box->setNoContentMargin(true); - - const auto content = box->verticalLayout(); - Ui::AddSkip(content); - Ui::AddSkip(content); - Ui::AddSkip(content); - - using Type = Data::CreditsHistoryEntry::PeerType; - - const auto &stUser = st::boostReplaceUserpic; - const auto peer = (e.peerType == Type::PremiumBot) - ? premiumBot.get() - : e.bareId - ? _controller->session().data().peer(PeerId(e.bareId)).get() - : nullptr; - if (peer) { - content->add(object_ptr>( - content, - object_ptr(content, peer, stUser))); - } else { - const auto widget = content->add( - object_ptr>( - content, - object_ptr(content)))->entity(); - using Draw = Fn; - const auto draw = widget->lifetime().make_state( - Ui::GenerateCreditsPaintUserpicCallback(e)); - widget->resize(Size(stUser.photoSize)); - widget->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(widget); - (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); - }, widget->lifetime()); - } - - Ui::AddSkip(content); - Ui::AddSkip(content); - - - box->addRow(object_ptr>( - box, - object_ptr( - box, - rpl::single(peer - ? peer->name() - : Ui::GenerateEntryName(e).text), - st::creditsBoxAboutTitle))); - - Ui::AddSkip(content); - - { - constexpr auto kMinus = QChar(0x2212); - auto &lifetime = content->lifetime(); - const auto text = lifetime.make_state( - st::semiboldTextStyle, - (!e.bareId ? QChar('+') : kMinus) - + Lang::FormatCountDecimal(e.credits)); - - const auto amount = content->add( - object_ptr( - content, - _star.height() / style::DevicePixelRatio())); - const auto font = text->style()->font; - amount->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(amount); - const auto starWidth = _star.width() - / style::DevicePixelRatio(); - const auto fullWidth = text->maxWidth() - + font->spacew * 2 - + starWidth; - p.setPen(!e.bareId - ? st::boxTextFgGood - : st::menuIconAttentionColor); - const auto x = (amount->width() - fullWidth) / 2; - text->draw(p, Ui::Text::PaintContext{ - .position = QPoint( - x, - (amount->height() - font->height) / 2), - .outerWidth = amount->width(), - .availableWidth = amount->width(), - });; - p.drawImage( - x + fullWidth - starWidth, - 0, - _star); - }, amount->lifetime()); - } - - Ui::AddSkip(content); - Ui::AddSkip(content); - - AddCreditsHistoryEntryTable( - controller, - box->verticalLayout(), - e); - - const auto button = box->addButton(tr::lng_box_ok(), [=] { - box->closeBox(); - }); - const auto buttonWidth = st::boxWidth - - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); - button->widthValue() | rpl::filter([=] { - return (button->widthNoMargins() != buttonWidth); - }) | rpl::start_with_next([=] { - button->resizeToWidth(buttonWidth); - }, button->lifetime()); - }; const auto entryClicked = [=](const Data::CreditsHistoryEntry &e) { - controller->uiShow()->show(Box(entryBox, e)); + controller->uiShow()->show(Box( + ReceiptCreditsBox, + controller, + premiumBot.get(), + e)); }; Info::Statistics::AddCreditsHistoryList( @@ -847,4 +739,121 @@ not_null AddBalanceWidget( return balance; } +void ReceiptCreditsBox( + not_null box, + not_null controller, + PeerData *premiumBot, + const Data::CreditsHistoryEntry &e) { + box->setStyle(st::giveawayGiftCodeBox); + box->setNoContentMargin(true); + + const auto star = GenerateStars(st::creditsTopupButton.height, 1); + + const auto content = box->verticalLayout(); + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + + using Type = Data::CreditsHistoryEntry::PeerType; + + const auto &stUser = st::boostReplaceUserpic; + const auto peer = (e.peerType == Type::PremiumBot) + ? premiumBot + : e.bareId + ? controller->session().data().peer(PeerId(e.bareId)).get() + : nullptr; + if (peer) { + content->add(object_ptr>( + content, + object_ptr(content, peer, stUser))); + } else { + const auto widget = content->add( + object_ptr>( + content, + object_ptr(content)))->entity(); + using Draw = Fn; + const auto draw = widget->lifetime().make_state( + Ui::GenerateCreditsPaintUserpicCallback(e)); + widget->resize(Size(stUser.photoSize)); + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(widget); + (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); + }, widget->lifetime()); + } + + Ui::AddSkip(content); + Ui::AddSkip(content); + + + box->addRow(object_ptr>( + box, + object_ptr( + box, + rpl::single(peer + ? peer->name() + : Ui::GenerateEntryName(e).text), + st::creditsBoxAboutTitle))); + + Ui::AddSkip(content); + + { + constexpr auto kMinus = QChar(0x2212); + auto &lifetime = content->lifetime(); + const auto text = lifetime.make_state( + st::semiboldTextStyle, + (!e.bareId ? QChar('+') : kMinus) + + Lang::FormatCountDecimal(e.credits)); + + const auto amount = content->add( + object_ptr( + content, + star.height() / style::DevicePixelRatio())); + const auto font = text->style()->font; + amount->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(amount); + const auto starWidth = star.width() + / style::DevicePixelRatio(); + const auto fullWidth = text->maxWidth() + + font->spacew * 2 + + starWidth; + p.setPen(!e.bareId + ? st::boxTextFgGood + : st::menuIconAttentionColor); + const auto x = (amount->width() - fullWidth) / 2; + text->draw(p, Ui::Text::PaintContext{ + .position = QPoint( + x, + (amount->height() - font->height) / 2), + .outerWidth = amount->width(), + .availableWidth = amount->width(), + });; + p.drawImage( + x + fullWidth - starWidth, + 0, + star); + }, amount->lifetime()); + } + + Ui::AddSkip(content); + Ui::AddSkip(content); + + AddCreditsHistoryEntryTable( + controller, + box->verticalLayout(), + e); + + const auto button = box->addButton(tr::lng_box_ok(), [=] { + box->closeBox(); + }); + const auto buttonWidth = st::boxWidth + - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits.h b/Telegram/SourceFiles/settings/settings_credits.h index 2267a0cd5..9f0d97ee0 100644 --- a/Telegram/SourceFiles/settings/settings_credits.h +++ b/Telegram/SourceFiles/settings/settings_credits.h @@ -9,7 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_type.h" +class PeerData; + +namespace Data { +struct CreditsHistoryEntry; +} // namespace Data + +namespace Window { +class SessionController; +} // namespace Window + namespace Ui { +class GenericBox; class RpWidget; } // namespace Ui @@ -22,5 +33,11 @@ namespace Settings { rpl::producer balanceValue, bool rightAlign); +void ReceiptCreditsBox( + not_null box, + not_null controller, + PeerData *premiumBot, + const Data::CreditsHistoryEntry &e); + } // namespace Settings From 93eff78cd6527b097adcfa218787c1be6c12e86c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 21:55:27 +0300 Subject: [PATCH 140/225] Replaced all credits currency name at least with simple star. --- Telegram/SourceFiles/ui/text/format_values.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/ui/text/format_values.cpp b/Telegram/SourceFiles/ui/text/format_values.cpp index a5115acf2..d95062313 100644 --- a/Telegram/SourceFiles/ui/text/format_values.cpp +++ b/Telegram/SourceFiles/ui/text/format_values.cpp @@ -147,6 +147,9 @@ QString FillAmountAndCurrency( Expects(amount != std::numeric_limits::min()); const auto rule = LookupCurrencyRule(currency); + if (currency == kCreditsCurrency) { + return QChar(0x2B50) + Lang::FormatCountDecimal(std::abs(amount)); + } const auto prefix = (amount < 0) ? QString::fromUtf8("\xe2\x88\x92") From c27c567225affbb8bca12452192775e31bc463ed Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 22:20:41 +0300 Subject: [PATCH 141/225] Added initial support for non-panel credits receipts. --- Telegram/SourceFiles/history/history_item.cpp | 1 + .../history/history_item_components.h | 1 + .../payments/payments_checkout_process.cpp | 5 ++++ .../payments/payments_checkout_process.h | 5 +++- .../SourceFiles/payments/payments_form.cpp | 20 +++++++++----- Telegram/SourceFiles/payments/payments_form.h | 14 ++++++++++ .../payments/payments_non_panel_process.cpp | 27 +++++++++++++++++-- 7 files changed, 64 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index c1af45c5f..88983c580 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -3941,6 +3941,7 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { payment->slug = data.vinvoice_slug().value_or_empty(); payment->recurringInit = data.is_recurring_init(); payment->recurringUsed = data.is_recurring_used(); + payment->isCreditsCurrency = (currency == Ui::kCreditsCurrency); payment->amount = Ui::FillAmountAndCurrency(amount, currency); payment->invoiceLink = std::make_shared([=]( ClickContext context) { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 906fe18ce..8d262a52e 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -653,6 +653,7 @@ struct HistoryServicePayment ClickHandlerPtr invoiceLink; bool recurringInit = false; bool recurringUsed = false; + bool isCreditsCurrency = false; }; struct HistoryServiceSameBackground diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 597d7687b..3080c149b 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -415,6 +415,11 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { _nonPanelPaymentFormProcess( std::make_shared(data.data)); } + }, [&](const CreditsReceiptReady &data) { + if (_nonPanelPaymentFormProcess) { + _nonPanelPaymentFormProcess( + std::make_shared(data.data)); + } }, [&](const Error &error) { handleError(error); }); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 2f939d5a6..3c7f112ca 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -41,6 +41,7 @@ struct InvoiceCredits; struct InvoiceId; struct InvoicePremiumGiftCode; struct CreditsFormData; +struct CreditsReceiptData; enum class Mode { Payment, @@ -54,7 +55,9 @@ enum class CheckoutResult { Failed, }; -struct NonPanelPaymentForm : std::variant> { +struct NonPanelPaymentForm : std::variant< + std::shared_ptr, + std::shared_ptr> { using variant::variant; }; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index c6ddd84a9..8dac6d0c5 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -499,12 +499,20 @@ void Form::processReceipt(const MTPDpayments_paymentReceipt &data) { void Form::processReceipt(const MTPDpayments_paymentReceiptStars &data) { _session->data().processUsers(data.vusers()); - data.vinvoice().match([&](const auto &data) { - processInvoice(data); - }); - processDetails(data); - fillPaymentMethodInformation(); - _updates.fire(FormReady{}); + const auto receiptData = CreditsReceiptData{ + .id = qs(data.vtransaction_id()), + .title = qs(data.vtitle()), + .description = qs(data.vdescription()), + .photo = data.vphoto() + ? _session->data().photoFromWeb( + *data.vphoto(), + ImageLocation()) + : nullptr, + .peerId = peerFromUser(data.vbot_id().v), + .credits = data.vtotal_amount().v, + .date = data.vdate().v, + }; + _updates.fire(CreditsReceiptReady{ .data = receiptData }); } void Form::processInvoice(const MTPDinvoice &data) { diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 6507ffa13..cf4cef890 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -187,6 +187,16 @@ struct CreditsFormData { MTPInputInvoice inputInvoice; }; +struct CreditsReceiptData { + QString id; + QString title; + QString description; + PhotoData *photo = nullptr; + PeerId peerId = PeerId(0); + uint64 credits = 0; + TimeId date = 0; +}; + struct ToggleProgress { bool shown = true; }; @@ -212,6 +222,9 @@ struct PaymentFinished { struct CreditsPaymentStarted { CreditsFormData data; }; +struct CreditsReceiptReady { + CreditsReceiptData data; +}; struct Error { enum class Type { None, @@ -244,6 +257,7 @@ struct FormUpdate : std::variant< BotTrustRequired, PaymentFinished, CreditsPaymentStarted, + CreditsReceiptReady, Error> { using variant::variant; }; diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 47232ac8a..2369d3d5d 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -7,18 +7,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "payments/payments_non_panel_process.h" +#include "base/unixtime.h" +#include "boxes/send_credits_box.h" +#include "data/data_credits.h" +#include "history/history_item.h" +#include "history/history_item_components.h" #include "payments/payments_checkout_process.h" // NonPanelPaymentForm. #include "payments/payments_form.h" -#include "history/history_item.h" +#include "settings/settings_credits.h" #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "window/window_session_controller.h" -#include "boxes/send_credits_box.h" namespace Payments { namespace { bool IsCreditsInvoice(not_null item) { + if (const auto payment = item->Get()) { + return payment->isCreditsCurrency; + } const auto media = item->media(); const auto invoice = media ? media->invoice() : nullptr; return invoice && (invoice->currency == Ui::kCreditsCurrency); @@ -30,9 +37,25 @@ Fn ProcessNonPanelPaymentFormFactory( not_null controller) { return [=](NonPanelPaymentForm form) { using CreditsFormDataPtr = std::shared_ptr; + using CreditsReceiptPtr = std::shared_ptr; if (const auto creditsData = std::get_if(&form)) { controller->uiShow()->show(Box(Ui::SendCreditsBox, *creditsData)); } + if (const auto r = std::get_if(&form)) { + const auto receipt = *r; + const auto entry = Data::CreditsHistoryEntry{ + .id = receipt->id, + .credits = receipt->credits, + .date = base::unixtime::parse(receipt->date), + .peerType = Data::CreditsHistoryEntry::PeerType::Peer, + .bareId = receipt->peerId.value, + }; + controller->uiShow()->show(Box( + Settings::ReceiptCreditsBox, + controller, + nullptr, + entry)); + } }; } From 1edf0ed70b429e06aee2a0859f283be27ebd18ac Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 22:41:51 +0300 Subject: [PATCH 142/225] Moved out widget of photo for credits history entries to single place. --- .../SourceFiles/boxes/send_credits_box.cpp | 46 ++--------------- .../SourceFiles/settings/settings_credits.cpp | 49 +++++++++++++++++++ .../SourceFiles/settings/settings_credits.h | 8 +++ 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 445bc9029..595434d99 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -11,8 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "core/ui_integration.h" // Core::MarkedTextContext. #include "data/data_credits.h" -#include "data/data_file_origin.h" -#include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" @@ -94,47 +92,9 @@ void SendCreditsBox( const auto bot = session->data().user(form->botId); if (form->photo) { - struct State { - std::shared_ptr view; - Image *image = nullptr; - rpl::lifetime downloadLifetime; - }; - const auto state = content->lifetime().make_state(); - const auto widget = box->addRow( - object_ptr>( - content, - object_ptr(content)))->entity(); - state->view = form->photo->createMediaView(); - form->photo->load(Data::PhotoSize::Thumbnail, {}); - - widget->resize(Size(photoSize)); - - rpl::single(rpl::empty_value()) | rpl::then( - session->downloaderTaskFinished() - ) | rpl::start_with_next([=] { - using Size = Data::PhotoSize; - if (const auto large = state->view->image(Size::Large)) { - state->image = large; - } else if (const auto small = state->view->image(Size::Small)) { - state->image = small; - } else if (const auto t = state->view->image(Size::Thumbnail)) { - state->image = t; - } - widget->update(); - if (state->view->loaded()) { - state->downloadLifetime.destroy(); - } - }, state->downloadLifetime); - - widget->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(widget); - if (state->image) { - p.drawPixmap(0, 0, state->image->pix(widget->width(), { - .options = Images::Option::RoundCircle, - })); - } - }, widget->lifetime()); + box->addRow(object_ptr>( + content, + Settings::HistoryEntryPhoto(content, form->photo, photoSize))); } else { const auto widget = box->addRow( object_ptr>( diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index c02860045..e9a01aa0a 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" +#include "data/data_file_origin.h" +#include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_user.h" #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. @@ -856,4 +858,51 @@ void ReceiptCreditsBox( }, button->lifetime()); } +object_ptr HistoryEntryPhoto( + not_null parent, + not_null photo, + int photoSize) { + struct State { + std::shared_ptr view; + Image *image = nullptr; + rpl::lifetime downloadLifetime; + }; + const auto state = parent->lifetime().make_state(); + auto owned = object_ptr(parent); + const auto widget = owned.data(); + state->view = photo->createMediaView(); + photo->load(Data::PhotoSize::Thumbnail, {}); + + widget->resize(Size(photoSize)); + + rpl::single(rpl::empty_value()) | rpl::then( + photo->owner().session().downloaderTaskFinished() + ) | rpl::start_with_next([=] { + using Size = Data::PhotoSize; + if (const auto large = state->view->image(Size::Large)) { + state->image = large; + } else if (const auto small = state->view->image(Size::Small)) { + state->image = small; + } else if (const auto t = state->view->image(Size::Thumbnail)) { + state->image = t; + } + widget->update(); + if (state->view->loaded()) { + state->downloadLifetime.destroy(); + } + }, state->downloadLifetime); + + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(widget); + if (state->image) { + p.drawPixmap(0, 0, state->image->pix(widget->width(), { + .options = Images::Option::RoundCircle, + })); + } + }, widget->lifetime()); + + return owned; +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits.h b/Telegram/SourceFiles/settings/settings_credits.h index 9f0d97ee0..32eba05db 100644 --- a/Telegram/SourceFiles/settings/settings_credits.h +++ b/Telegram/SourceFiles/settings/settings_credits.h @@ -9,6 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_type.h" +template +class object_ptr; + class PeerData; namespace Data { @@ -39,5 +42,10 @@ void ReceiptCreditsBox( PeerData *premiumBot, const Data::CreditsHistoryEntry &e); +[[nodiscard]] object_ptr HistoryEntryPhoto( + not_null parent, + not_null photo, + int photoSize); + } // namespace Settings From 58da617e3f4f1a32fe7172a6eb7cefc87a42742b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 22:52:47 +0300 Subject: [PATCH 143/225] Added description and optional photo to credits receipts. --- Telegram/SourceFiles/api/api_credits.cpp | 26 +++++++++++++------ Telegram/SourceFiles/data/data_credits.h | 8 ++++-- .../boosts/giveaway/giveaway.style | 4 +-- .../payments/payments_non_panel_process.cpp | 8 ++++-- .../SourceFiles/settings/settings_credits.cpp | 24 +++++++++++++++-- 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 1604d6d5c..453d9536f 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/unixtime.h" #include "data/data_peer.h" +#include "data/data_photo.h" #include "data/data_session.h" #include "main/main_app_config.h" #include "main/main_session.h" @@ -21,12 +22,24 @@ namespace Api { namespace { [[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL( - const MTPStarsTransaction &tl) { + const MTPStarsTransaction &tl, + not_null peer) { using HistoryPeerTL = MTPDstarsTransactionPeer; + const auto photo = tl.data().vphoto() + ? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation()) + : nullptr; return Data::CreditsHistoryEntry{ .id = qs(tl.data().vid()), - .credits = tl.data().vstars().v, + .title = qs(tl.data().vtitle().value_or_empty()), + .description = qs(tl.data().vdescription().value_or_empty()), .date = base::unixtime::parse(tl.data().vdate().v), + .photoId = photo ? photo->id : 0, + .credits = tl.data().vstars().v, + .bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) { + return peerFromMTP(p.vpeer()); + }, [](const auto &) { + return PeerId(0); + }).value, .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { return Data::CreditsHistoryEntry::PeerType::Peer; }, [](const MTPDstarsTransactionPeerPlayMarket &) { @@ -40,11 +53,6 @@ namespace { }, [](const MTPDstarsTransactionPeerPremiumBot &) { return Data::CreditsHistoryEntry::PeerType::PremiumBot; }), - .bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) { - return peerFromMTP(p.vpeer()); - }, [](const auto &) { - return PeerId(0); - }).value, }; } @@ -56,7 +64,9 @@ namespace { return Data::CreditsStatusSlice{ .list = ranges::views::all( status.data().vhistory().v - ) | ranges::views::transform(HistoryFromTL) | ranges::to_vector, + ) | ranges::views::transform([&](const MTPStarsTransaction &tl) { + return HistoryFromTL(tl, peer); + }) | ranges::to_vector, .balance = status.data().vbalance().v, .allLoaded = !status.data().vnext_offset().has_value(), .token = qs(status.data().vnext_offset().value_or_empty()), diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 7a241c87a..af830e26c 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -20,6 +20,7 @@ struct CreditTopupOption final { using CreditTopupOptions = std::vector; struct CreditsHistoryEntry final { + using PhotoId = uint64; enum class PeerType { Peer, AppStore, @@ -29,10 +30,13 @@ struct CreditsHistoryEntry final { PremiumBot, }; QString id; - uint64 credits = 0; + QString title; + QString description; QDateTime date; - PeerType peerType; + PhotoId photoId = 0; + uint64 credits = 0; uint64 bareId = 0; + PeerType peerType; }; struct CreditsStatusSlice final { diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style index ef705ab2a..2715dfe95 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style @@ -96,8 +96,8 @@ giveawayGiftCodeValue: FlatLabel(defaultFlatLabel) { } } giveawayGiftCodeValueMultiline: FlatLabel(giveawayGiftCodeValue) { - minWidth: 100px; - maxHeight: 0px; + minWidth: 128px; + maxHeight: 100px; } giveawayGiftCodeValueMargin: margins(13px, 9px, 13px, 9px); giveawayGiftCodePeerMargin: margins(11px, 6px, 11px, 4px); diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 2369d3d5d..5ff5a07f3 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "boxes/send_credits_box.h" #include "data/data_credits.h" +#include "data/data_photo.h" #include "history/history_item.h" #include "history/history_item_components.h" #include "payments/payments_checkout_process.h" // NonPanelPaymentForm. @@ -45,10 +46,13 @@ Fn ProcessNonPanelPaymentFormFactory( const auto receipt = *r; const auto entry = Data::CreditsHistoryEntry{ .id = receipt->id, - .credits = receipt->credits, + .title = receipt->title, + .description = receipt->description, .date = base::unixtime::parse(receipt->date), - .peerType = Data::CreditsHistoryEntry::PeerType::Peer, + .photoId = receipt->photo ? receipt->photo->id : 0, + .credits = receipt->credits, .bareId = receipt->peerId.value, + .peerType = Data::CreditsHistoryEntry::PeerType::Peer, }; controller->uiShow()->show(Box( Settings::ReceiptCreditsBox, diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index e9a01aa0a..0eec25cf4 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -764,7 +764,14 @@ void ReceiptCreditsBox( : e.bareId ? controller->session().data().peer(PeerId(e.bareId)).get() : nullptr; - if (peer) { + const auto photo = e.photoId + ? controller->session().data().photo(e.photoId).get() + : nullptr; + if (photo) { + content->add(object_ptr>( + content, + HistoryEntryPhoto(content, photo, stUser.photoSize))); + } else if (peer) { content->add(object_ptr>( content, object_ptr(content, peer, stUser))); @@ -792,7 +799,10 @@ void ReceiptCreditsBox( box, object_ptr( box, - rpl::single(peer + rpl::single( + !e.title.isEmpty() + ? e.title + : peer ? peer->name() : Ui::GenerateEntryName(e).text), st::creditsBoxAboutTitle))); @@ -838,6 +848,16 @@ void ReceiptCreditsBox( }, amount->lifetime()); } + if (!e.description.isEmpty()) { + Ui::AddSkip(content); + box->addRow(object_ptr>( + box, + object_ptr( + box, + rpl::single(e.description), + st::defaultFlatLabel))); + } + Ui::AddSkip(content); Ui::AddSkip(content); From 3c246e1e92f08fabfe0efa68ea70467a23d9b322 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 23:09:14 +0300 Subject: [PATCH 144/225] Split functions for credits settings. --- Telegram/CMakeLists.txt | 2 + .../SourceFiles/boxes/send_credits_box.cpp | 2 +- .../payments/payments_non_panel_process.cpp | 2 +- .../SourceFiles/settings/settings_credits.cpp | 347 +--------------- .../SourceFiles/settings/settings_credits.h | 34 -- .../settings/settings_credits_graphics.cpp | 391 ++++++++++++++++++ .../settings/settings_credits_graphics.h | 49 +++ 7 files changed, 446 insertions(+), 381 deletions(-) create mode 100644 Telegram/SourceFiles/settings/settings_credits_graphics.cpp create mode 100644 Telegram/SourceFiles/settings/settings_credits_graphics.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 75ab2b48f..d1171a5ef 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1370,6 +1370,8 @@ PRIVATE settings/settings_common_session.h settings/settings_credits.cpp settings/settings_credits.h + settings/settings_credits_graphics.cpp + settings/settings_credits_graphics.h settings/settings_experimental.cpp settings/settings_experimental.h settings/settings_folders.cpp diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 595434d99..303560ab3 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" #include "payments/payments_form.h" -#include "settings/settings_credits.h" +#include "settings/settings_credits_graphics.h" #include "ui/controls/userpic_button.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg. diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 5ff5a07f3..f2874d62e 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "payments/payments_checkout_process.h" // NonPanelPaymentForm. #include "payments/payments_form.h" -#include "settings/settings_credits.h" +#include "settings/settings_credits_graphics.h" #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "window/window_session_controller.h" diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 0eec25cf4..e2c4bcdf9 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_credits.h" +#include "settings/settings_credits_graphics.h" #include "api/api_credits.h" #include "boxes/gift_premium_box.h" #include "core/click_handler_types.h" @@ -23,11 +24,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common_session.h" #include "statistics/widgets/chart_header_widget.h" #include "ui/boxes/boost_box.h" // Ui::StartFireworks. -#include "ui/controls/userpic_button.h" -#include "ui/effects/credits_graphics.h" #include "ui/effects/premium_graphics.h" #include "ui/effects/premium_top_bar.h" -#include "ui/image/image_prepare.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" @@ -37,14 +35,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/labels.h" -#include "ui/widgets/tooltip.h" #include "ui/wrap/fade_wrap.h" -#include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include "styles/style_credits.h" -#include "styles/style_giveaway.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_premium.h" @@ -58,47 +53,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Settings { namespace { -using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; - -class Balance final - : public Ui::RpWidget - , public Ui::AbstractTooltipShower { -public: - using Ui::RpWidget::RpWidget; - - void setBalance(uint64 balance) { - _balance = balance; - _tooltip = Lang::FormatCountDecimal(balance); - } - - void enterEventHook(QEnterEvent *e) override { - if (_balance >= 10'000) { - Ui::Tooltip::Show(1000, this); - } - } - - void leaveEventHook(QEvent *e) override { - Ui::Tooltip::Hide(); - } - - QString tooltipText() const override { - return _tooltip; - } - - QPoint tooltipPos() const override { - return QCursor::pos(); - } - - bool tooltipWindowActive() const override { - return Ui::AppInFocus() && Ui::InFocusChain(window()); - } - -private: - QString _tooltip; - uint64 _balance = 0; - -}; - [[nodiscard]] uint64 UniqueIdFromOption( const Data::CreditTopupOption &d) { const auto string = QString::number(d.credits) @@ -109,59 +63,6 @@ private: return XXH64(string.data(), string.size() * sizeof(ushort), 0); } -[[nodiscard]] QImage GenerateStars(int height, int count) { - constexpr auto kOutlineWidth = .6; - constexpr auto kStrokeWidth = 3; - constexpr auto kShift = 3; - - auto colorized = qs(Ui::Premium::ColorizedSvg( - Ui::Premium::CreditsIconGradientStops())); - colorized.replace( - u"stroke=\"none\""_q, - u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name())); - colorized.replace( - u"stroke-width=\"1\""_q, - u"stroke-width=\"%1\""_q.arg(kStrokeWidth)); - auto svg = QSvgRenderer(colorized.toUtf8()); - svg.setViewBox(svg.viewBox() + Margins(kStrokeWidth)); - - const auto starSize = Size(height - kOutlineWidth * 2); - - auto frame = QImage( - QSize( - (height + kShift * (count - 1)) * style::DevicePixelRatio(), - height * style::DevicePixelRatio()), - QImage::Format_ARGB32_Premultiplied); - frame.setDevicePixelRatio(style::DevicePixelRatio()); - frame.fill(Qt::transparent); - const auto drawSingle = [&](QPainter &q) { - const auto s = kOutlineWidth; - q.save(); - q.translate(s, s); - q.setCompositionMode(QPainter::CompositionMode_Clear); - svg.render(&q, QRectF(QPointF(s, 0), starSize)); - svg.render(&q, QRectF(QPointF(s, s), starSize)); - svg.render(&q, QRectF(QPointF(0, s), starSize)); - svg.render(&q, QRectF(QPointF(-s, s), starSize)); - svg.render(&q, QRectF(QPointF(-s, 0), starSize)); - svg.render(&q, QRectF(QPointF(-s, -s), starSize)); - svg.render(&q, QRectF(QPointF(0, -s), starSize)); - svg.render(&q, QRectF(QPointF(s, -s), starSize)); - q.setCompositionMode(QPainter::CompositionMode_SourceOver); - svg.render(&q, Rect(starSize)); - q.restore(); - }; - { - auto q = QPainter(&frame); - q.translate(frame.width() / style::DevicePixelRatio() - height, 0); - for (auto i = count; i > 0; --i) { - drawSingle(q); - q.translate(-kShift, 0); - } - } - return frame; -} - class Credits : public Section { public: Credits( @@ -228,6 +129,7 @@ rpl::producer<> Credits::sectionShowBack() { } void Credits::setStepDataReference(std::any &data) { + using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; const auto my = std::any_cast(&data); if (my) { _backToggles = std::move( @@ -680,249 +582,4 @@ Type CreditsId() { return Credits::Id(); } -not_null AddBalanceWidget( - not_null parent, - rpl::producer balanceValue, - bool rightAlign) { - const auto balance = Ui::CreateChild(parent); - const auto balanceStar = balance->lifetime().make_state( - GenerateStars(st::creditsBalanceStarHeight, 1)); - const auto starSize = balanceStar->size() / style::DevicePixelRatio(); - const auto label = balance->lifetime().make_state( - st::defaultTextStyle, - tr::lng_credits_summary_balance(tr::now)); - const auto count = balance->lifetime().make_state( - st::semiboldTextStyle, - tr::lng_contacts_loading(tr::now)); - const auto diffBetweenStarAndCount = count->style()->font->spacew; - const auto resize = [=] { - balance->resize( - std::max( - label->maxWidth(), - count->maxWidth() - + starSize.width() - + diffBetweenStarAndCount), - label->style()->font->height + starSize.height()); - }; - std::move(balanceValue) | rpl::start_with_next([=](uint64 value) { - count->setText( - st::semiboldTextStyle, - Lang::FormatCountToShort(value).string); - balance->setBalance(value); - resize(); - }, balance->lifetime()); - balance->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(balance); - - p.setPen(st::boxTextFg); - - label->draw(p, { - .position = QPoint( - rightAlign ? (balance->width() - label->maxWidth()) : 0, - 0), - .availableWidth = balance->width(), - }); - count->draw(p, { - .position = QPoint( - balance->width() - count->maxWidth(), - label->minHeight() - + (starSize.height() - count->minHeight()) / 2), - .availableWidth = balance->width(), - }); - p.drawImage( - balance->width() - - count->maxWidth() - - starSize.width() - - diffBetweenStarAndCount, - label->minHeight(), - *balanceStar); - }, balance->lifetime()); - return balance; -} - -void ReceiptCreditsBox( - not_null box, - not_null controller, - PeerData *premiumBot, - const Data::CreditsHistoryEntry &e) { - box->setStyle(st::giveawayGiftCodeBox); - box->setNoContentMargin(true); - - const auto star = GenerateStars(st::creditsTopupButton.height, 1); - - const auto content = box->verticalLayout(); - Ui::AddSkip(content); - Ui::AddSkip(content); - Ui::AddSkip(content); - - using Type = Data::CreditsHistoryEntry::PeerType; - - const auto &stUser = st::boostReplaceUserpic; - const auto peer = (e.peerType == Type::PremiumBot) - ? premiumBot - : e.bareId - ? controller->session().data().peer(PeerId(e.bareId)).get() - : nullptr; - const auto photo = e.photoId - ? controller->session().data().photo(e.photoId).get() - : nullptr; - if (photo) { - content->add(object_ptr>( - content, - HistoryEntryPhoto(content, photo, stUser.photoSize))); - } else if (peer) { - content->add(object_ptr>( - content, - object_ptr(content, peer, stUser))); - } else { - const auto widget = content->add( - object_ptr>( - content, - object_ptr(content)))->entity(); - using Draw = Fn; - const auto draw = widget->lifetime().make_state( - Ui::GenerateCreditsPaintUserpicCallback(e)); - widget->resize(Size(stUser.photoSize)); - widget->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(widget); - (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); - }, widget->lifetime()); - } - - Ui::AddSkip(content); - Ui::AddSkip(content); - - - box->addRow(object_ptr>( - box, - object_ptr( - box, - rpl::single( - !e.title.isEmpty() - ? e.title - : peer - ? peer->name() - : Ui::GenerateEntryName(e).text), - st::creditsBoxAboutTitle))); - - Ui::AddSkip(content); - - { - constexpr auto kMinus = QChar(0x2212); - auto &lifetime = content->lifetime(); - const auto text = lifetime.make_state( - st::semiboldTextStyle, - (!e.bareId ? QChar('+') : kMinus) - + Lang::FormatCountDecimal(e.credits)); - - const auto amount = content->add( - object_ptr( - content, - star.height() / style::DevicePixelRatio())); - const auto font = text->style()->font; - amount->paintRequest( - ) | rpl::start_with_next([=] { - auto p = Painter(amount); - const auto starWidth = star.width() - / style::DevicePixelRatio(); - const auto fullWidth = text->maxWidth() - + font->spacew * 2 - + starWidth; - p.setPen(!e.bareId - ? st::boxTextFgGood - : st::menuIconAttentionColor); - const auto x = (amount->width() - fullWidth) / 2; - text->draw(p, Ui::Text::PaintContext{ - .position = QPoint( - x, - (amount->height() - font->height) / 2), - .outerWidth = amount->width(), - .availableWidth = amount->width(), - });; - p.drawImage( - x + fullWidth - starWidth, - 0, - star); - }, amount->lifetime()); - } - - if (!e.description.isEmpty()) { - Ui::AddSkip(content); - box->addRow(object_ptr>( - box, - object_ptr( - box, - rpl::single(e.description), - st::defaultFlatLabel))); - } - - Ui::AddSkip(content); - Ui::AddSkip(content); - - AddCreditsHistoryEntryTable( - controller, - box->verticalLayout(), - e); - - const auto button = box->addButton(tr::lng_box_ok(), [=] { - box->closeBox(); - }); - const auto buttonWidth = st::boxWidth - - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); - button->widthValue() | rpl::filter([=] { - return (button->widthNoMargins() != buttonWidth); - }) | rpl::start_with_next([=] { - button->resizeToWidth(buttonWidth); - }, button->lifetime()); -} - -object_ptr HistoryEntryPhoto( - not_null parent, - not_null photo, - int photoSize) { - struct State { - std::shared_ptr view; - Image *image = nullptr; - rpl::lifetime downloadLifetime; - }; - const auto state = parent->lifetime().make_state(); - auto owned = object_ptr(parent); - const auto widget = owned.data(); - state->view = photo->createMediaView(); - photo->load(Data::PhotoSize::Thumbnail, {}); - - widget->resize(Size(photoSize)); - - rpl::single(rpl::empty_value()) | rpl::then( - photo->owner().session().downloaderTaskFinished() - ) | rpl::start_with_next([=] { - using Size = Data::PhotoSize; - if (const auto large = state->view->image(Size::Large)) { - state->image = large; - } else if (const auto small = state->view->image(Size::Small)) { - state->image = small; - } else if (const auto t = state->view->image(Size::Thumbnail)) { - state->image = t; - } - widget->update(); - if (state->view->loaded()) { - state->downloadLifetime.destroy(); - } - }, state->downloadLifetime); - - widget->paintRequest( - ) | rpl::start_with_next([=] { - auto p = QPainter(widget); - if (state->image) { - p.drawPixmap(0, 0, state->image->pix(widget->width(), { - .options = Images::Option::RoundCircle, - })); - } - }, widget->lifetime()); - - return owned; -} - } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits.h b/Telegram/SourceFiles/settings/settings_credits.h index 32eba05db..c0e32e1be 100644 --- a/Telegram/SourceFiles/settings/settings_credits.h +++ b/Telegram/SourceFiles/settings/settings_credits.h @@ -9,43 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_type.h" -template -class object_ptr; - -class PeerData; - -namespace Data { -struct CreditsHistoryEntry; -} // namespace Data - -namespace Window { -class SessionController; -} // namespace Window - -namespace Ui { -class GenericBox; -class RpWidget; -} // namespace Ui - namespace Settings { [[nodiscard]] Type CreditsId(); -[[nodiscard]] not_null AddBalanceWidget( - not_null parent, - rpl::producer balanceValue, - bool rightAlign); - -void ReceiptCreditsBox( - not_null box, - not_null controller, - PeerData *premiumBot, - const Data::CreditsHistoryEntry &e); - -[[nodiscard]] object_ptr HistoryEntryPhoto( - not_null parent, - not_null photo, - int photoSize); - } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp new file mode 100644 index 000000000..6b8cc7ecb --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -0,0 +1,391 @@ +/* +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 "settings/settings_credits_graphics.h" + +#include "api/api_credits.h" +#include "boxes/gift_premium_box.h" +#include "core/click_handler_types.h" +#include "data/data_file_origin.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_common_session.h" +#include "statistics/widgets/chart_header_widget.h" +#include "ui/boxes/boost_box.h" // Ui::StartFireworks. +#include "ui/controls/userpic_button.h" +#include "ui/effects/credits_graphics.h" +#include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_top_bar.h" +#include "ui/image/image_prepare.h" +#include "ui/layers/generic_box.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/tooltip.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/wrap/padding_wrap.h" +#include "window/window_session_controller.h" +#include "styles/style_credits.h" +#include "styles/style_giveaway.h" +#include "styles/style_info.h" +#include "styles/style_layers.h" +#include "styles/style_premium.h" +#include "styles/style_settings.h" + +#include // XXH64. + +#include + +namespace Settings { +namespace { + +class Balance final + : public Ui::RpWidget + , public Ui::AbstractTooltipShower { +public: + using Ui::RpWidget::RpWidget; + + void setBalance(uint64 balance) { + _balance = balance; + _tooltip = Lang::FormatCountDecimal(balance); + } + + void enterEventHook(QEnterEvent *e) override { + if (_balance >= 10'000) { + Ui::Tooltip::Show(1000, this); + } + } + + void leaveEventHook(QEvent *e) override { + Ui::Tooltip::Hide(); + } + + QString tooltipText() const override { + return _tooltip; + } + + QPoint tooltipPos() const override { + return QCursor::pos(); + } + + bool tooltipWindowActive() const override { + return Ui::AppInFocus() && Ui::InFocusChain(window()); + } + +private: + QString _tooltip; + uint64 _balance = 0; + +}; + +} // namespace + +QImage GenerateStars(int height, int count) { + constexpr auto kOutlineWidth = .6; + constexpr auto kStrokeWidth = 3; + constexpr auto kShift = 3; + + auto colorized = qs(Ui::Premium::ColorizedSvg( + Ui::Premium::CreditsIconGradientStops())); + colorized.replace( + u"stroke=\"none\""_q, + u"stroke=\"%1\""_q.arg(st::creditsStroke->c.name())); + colorized.replace( + u"stroke-width=\"1\""_q, + u"stroke-width=\"%1\""_q.arg(kStrokeWidth)); + auto svg = QSvgRenderer(colorized.toUtf8()); + svg.setViewBox(svg.viewBox() + Margins(kStrokeWidth)); + + const auto starSize = Size(height - kOutlineWidth * 2); + + auto frame = QImage( + QSize( + (height + kShift * (count - 1)) * style::DevicePixelRatio(), + height * style::DevicePixelRatio()), + QImage::Format_ARGB32_Premultiplied); + frame.setDevicePixelRatio(style::DevicePixelRatio()); + frame.fill(Qt::transparent); + const auto drawSingle = [&](QPainter &q) { + const auto s = kOutlineWidth; + q.save(); + q.translate(s, s); + q.setCompositionMode(QPainter::CompositionMode_Clear); + svg.render(&q, QRectF(QPointF(s, 0), starSize)); + svg.render(&q, QRectF(QPointF(s, s), starSize)); + svg.render(&q, QRectF(QPointF(0, s), starSize)); + svg.render(&q, QRectF(QPointF(-s, s), starSize)); + svg.render(&q, QRectF(QPointF(-s, 0), starSize)); + svg.render(&q, QRectF(QPointF(-s, -s), starSize)); + svg.render(&q, QRectF(QPointF(0, -s), starSize)); + svg.render(&q, QRectF(QPointF(s, -s), starSize)); + q.setCompositionMode(QPainter::CompositionMode_SourceOver); + svg.render(&q, Rect(starSize)); + q.restore(); + }; + { + auto q = QPainter(&frame); + q.translate(frame.width() / style::DevicePixelRatio() - height, 0); + for (auto i = count; i > 0; --i) { + drawSingle(q); + q.translate(-kShift, 0); + } + } + return frame; +} + +not_null AddBalanceWidget( + not_null parent, + rpl::producer balanceValue, + bool rightAlign) { + const auto balance = Ui::CreateChild(parent); + const auto balanceStar = balance->lifetime().make_state( + GenerateStars(st::creditsBalanceStarHeight, 1)); + const auto starSize = balanceStar->size() / style::DevicePixelRatio(); + const auto label = balance->lifetime().make_state( + st::defaultTextStyle, + tr::lng_credits_summary_balance(tr::now)); + const auto count = balance->lifetime().make_state( + st::semiboldTextStyle, + tr::lng_contacts_loading(tr::now)); + const auto diffBetweenStarAndCount = count->style()->font->spacew; + const auto resize = [=] { + balance->resize( + std::max( + label->maxWidth(), + count->maxWidth() + + starSize.width() + + diffBetweenStarAndCount), + label->style()->font->height + starSize.height()); + }; + std::move(balanceValue) | rpl::start_with_next([=](uint64 value) { + count->setText( + st::semiboldTextStyle, + Lang::FormatCountToShort(value).string); + balance->setBalance(value); + resize(); + }, balance->lifetime()); + balance->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(balance); + + p.setPen(st::boxTextFg); + + label->draw(p, { + .position = QPoint( + rightAlign ? (balance->width() - label->maxWidth()) : 0, + 0), + .availableWidth = balance->width(), + }); + count->draw(p, { + .position = QPoint( + balance->width() - count->maxWidth(), + label->minHeight() + + (starSize.height() - count->minHeight()) / 2), + .availableWidth = balance->width(), + }); + p.drawImage( + balance->width() + - count->maxWidth() + - starSize.width() + - diffBetweenStarAndCount, + label->minHeight(), + *balanceStar); + }, balance->lifetime()); + return balance; +} + +void ReceiptCreditsBox( + not_null box, + not_null controller, + PeerData *premiumBot, + const Data::CreditsHistoryEntry &e) { + box->setStyle(st::giveawayGiftCodeBox); + box->setNoContentMargin(true); + + const auto star = GenerateStars(st::creditsTopupButton.height, 1); + + const auto content = box->verticalLayout(); + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + + using Type = Data::CreditsHistoryEntry::PeerType; + + const auto &stUser = st::boostReplaceUserpic; + const auto peer = (e.peerType == Type::PremiumBot) + ? premiumBot + : e.bareId + ? controller->session().data().peer(PeerId(e.bareId)).get() + : nullptr; + const auto photo = e.photoId + ? controller->session().data().photo(e.photoId).get() + : nullptr; + if (photo) { + content->add(object_ptr>( + content, + HistoryEntryPhoto(content, photo, stUser.photoSize))); + } else if (peer) { + content->add(object_ptr>( + content, + object_ptr(content, peer, stUser))); + } else { + const auto widget = content->add( + object_ptr>( + content, + object_ptr(content)))->entity(); + using Draw = Fn; + const auto draw = widget->lifetime().make_state( + Ui::GenerateCreditsPaintUserpicCallback(e)); + widget->resize(Size(stUser.photoSize)); + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(widget); + (*draw)(p, 0, 0, stUser.photoSize, stUser.photoSize); + }, widget->lifetime()); + } + + Ui::AddSkip(content); + Ui::AddSkip(content); + + + box->addRow(object_ptr>( + box, + object_ptr( + box, + rpl::single( + !e.title.isEmpty() + ? e.title + : peer + ? peer->name() + : Ui::GenerateEntryName(e).text), + st::creditsBoxAboutTitle))); + + Ui::AddSkip(content); + + { + constexpr auto kMinus = QChar(0x2212); + auto &lifetime = content->lifetime(); + const auto text = lifetime.make_state( + st::semiboldTextStyle, + (!e.bareId ? QChar('+') : kMinus) + + Lang::FormatCountDecimal(e.credits)); + + const auto amount = content->add( + object_ptr( + content, + star.height() / style::DevicePixelRatio())); + const auto font = text->style()->font; + amount->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(amount); + const auto starWidth = star.width() + / style::DevicePixelRatio(); + const auto fullWidth = text->maxWidth() + + font->spacew * 2 + + starWidth; + p.setPen(!e.bareId + ? st::boxTextFgGood + : st::menuIconAttentionColor); + const auto x = (amount->width() - fullWidth) / 2; + text->draw(p, Ui::Text::PaintContext{ + .position = QPoint( + x, + (amount->height() - font->height) / 2), + .outerWidth = amount->width(), + .availableWidth = amount->width(), + });; + p.drawImage( + x + fullWidth - starWidth, + 0, + star); + }, amount->lifetime()); + } + + if (!e.description.isEmpty()) { + Ui::AddSkip(content); + box->addRow(object_ptr>( + box, + object_ptr( + box, + rpl::single(e.description), + st::defaultFlatLabel))); + } + + Ui::AddSkip(content); + Ui::AddSkip(content); + + AddCreditsHistoryEntryTable( + controller, + box->verticalLayout(), + e); + + const auto button = box->addButton(tr::lng_box_ok(), [=] { + box->closeBox(); + }); + const auto buttonWidth = st::boxWidth + - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); +} + +object_ptr HistoryEntryPhoto( + not_null parent, + not_null photo, + int photoSize) { + struct State { + std::shared_ptr view; + Image *image = nullptr; + rpl::lifetime downloadLifetime; + }; + const auto state = parent->lifetime().make_state(); + auto owned = object_ptr(parent); + const auto widget = owned.data(); + state->view = photo->createMediaView(); + photo->load(Data::PhotoSize::Thumbnail, {}); + + widget->resize(Size(photoSize)); + + rpl::single(rpl::empty_value()) | rpl::then( + photo->owner().session().downloaderTaskFinished() + ) | rpl::start_with_next([=] { + using Size = Data::PhotoSize; + if (const auto large = state->view->image(Size::Large)) { + state->image = large; + } else if (const auto small = state->view->image(Size::Small)) { + state->image = small; + } else if (const auto t = state->view->image(Size::Thumbnail)) { + state->image = t; + } + widget->update(); + if (state->view->loaded()) { + state->downloadLifetime.destroy(); + } + }, state->downloadLifetime); + + widget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(widget); + if (state->image) { + p.drawPixmap(0, 0, state->image->pix(widget->width(), { + .options = Images::Option::RoundCircle, + })); + } + }, widget->lifetime()); + + return owned; +} + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h new file mode 100644 index 000000000..ec078f492 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -0,0 +1,49 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +template +class object_ptr; + +class PeerData; + +namespace Data { +struct CreditsHistoryEntry; +} // namespace Data + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { +class GenericBox; +class RpWidget; +} // namespace Ui + +namespace Settings { + +[[nodiscard]] QImage GenerateStars(int height, int count); + +[[nodiscard]] not_null AddBalanceWidget( + not_null parent, + rpl::producer balanceValue, + bool rightAlign); + +void ReceiptCreditsBox( + not_null box, + not_null controller, + PeerData *premiumBot, + const Data::CreditsHistoryEntry &e); + +[[nodiscard]] object_ptr HistoryEntryPhoto( + not_null parent, + not_null photo, + int photoSize); + +} // namespace Settings + From 0549c8f0374ed8f4c617f757ecc14f532b587849 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 May 2024 23:19:51 +0300 Subject: [PATCH 145/225] Moved out list of top-up options to single place. --- .../SourceFiles/settings/settings_credits.cpp | 146 +---------------- .../settings/settings_credits_graphics.cpp | 148 ++++++++++++++++++ .../settings/settings_credits_graphics.h | 6 + 3 files changed, 160 insertions(+), 140 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index e2c4bcdf9..adffaba25 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -19,8 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" #include "main/main_session.h" -#include "payments/payments_checkout_process.h" -#include "payments/payments_form.h" #include "settings/settings_common_session.h" #include "statistics/widgets/chart_header_widget.h" #include "ui/boxes/boost_box.h" // Ui::StartFireworks. @@ -29,12 +27,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" -#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/discrete_sliders.h" -#include "ui/widgets/labels.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -46,23 +42,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" #include "styles/style_statistics.h" -#include // XXH64. - -#include - namespace Settings { namespace { -[[nodiscard]] uint64 UniqueIdFromOption( - const Data::CreditTopupOption &d) { - const auto string = QString::number(d.credits) - + d.product - + d.currency - + QString::number(d.amount); - - return XXH64(string.data(), string.size() * sizeof(ushort), 0); -} - class Credits : public Section { public: Credits( @@ -139,127 +121,6 @@ void Credits::setStepDataReference(std::any &data) { } } -void Credits::setupOptions(not_null container) { - const auto options = container->add( - object_ptr>( - container, - object_ptr(container))); - const auto content = options->entity(); - - Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); - - const auto fill = [=](Data::CreditTopupOptions options) { - while (content->count()) { - delete content->widgetAt(0); - } - Ui::AddSubsectionTitle( - content, - tr::lng_credits_summary_options_subtitle()); - const auto &st = st::creditsTopupButton; - const auto diffBetweenTextAndStar = st.padding.left() - - st.iconLeft - - (_star.width() / style::DevicePixelRatio()); - const auto buttonHeight = st.height + rect::m::sum::v(st.padding); - for (auto i = 0; i < options.size(); i++) { - const auto &option = options[i]; - const auto button = content->add(object_ptr( - content, - rpl::never(), - st)); - const auto text = button->lifetime().make_state( - st.style, - tr::lng_credits_summary_options_credits( - tr::now, - lt_count_decimal, - option.credits)); - const auto price = Ui::CreateChild( - button, - Ui::FillAmountAndCurrency(option.amount, option.currency), - st::creditsTopupPrice); - const auto inner = Ui::CreateChild(button); - const auto stars = GenerateStars(st.height, (i + 1)); - inner->paintRequest( - ) | rpl::start_with_next([=](const QRect &rect) { - auto p = QPainter(inner); - p.drawImage( - 0, - (buttonHeight - stars.height()) / 2, - stars); - const auto textLeft = diffBetweenTextAndStar - + stars.width() / style::DevicePixelRatio(); - p.setPen(st.textFg); - text->draw(p, { - .position = QPoint(textLeft, 0), - .availableWidth = inner->width() - textLeft, - }); - }, inner->lifetime()); - button->sizeValue( - ) | rpl::start_with_next([=](const QSize &size) { - price->moveToRight(st.padding.right(), st.padding.top()); - inner->moveToLeft(st.iconLeft, st.padding.top()); - inner->resize( - size.width() - - rect::m::sum::h(st.padding) - - price->width(), - buttonHeight); - }, button->lifetime()); - button->setClickedCallback([=] { - const auto invoice = Payments::InvoiceCredits{ - .session = &_controller->session(), - .randomId = UniqueIdFromOption(option), - .credits = option.credits, - .product = option.product, - .currency = option.currency, - .amount = option.amount, - .extended = option.extended, - }; - - const auto weak = Ui::MakeWeak(button); - const auto done = [=](Payments::CheckoutResult result) { - if (const auto strong = weak.data()) { - strong->window()->setFocus(); - if (result == Payments::CheckoutResult::Paid) { - if (_parent) { - Ui::StartFireworks(_parent); - } - } - } - }; - - Payments::CheckoutProcess::Start(std::move(invoice), done); - }); - Ui::ToggleChildrenVisibility(button, true); - } - - // Footer. - { - auto text = tr::lng_credits_summary_options_about( - lt_link, - tr::lng_credits_summary_options_about_link( - ) | rpl::map([](const QString &t) { - using namespace Ui::Text; - return Link(t, u"https://telegram.org/tos"_q); - }), - Ui::Text::RichLangValue); - Ui::AddSkip(content); - Ui::AddDividerText(content, std::move(text)); - } - - content->resizeToWidth(container->width()); - }; - - using ApiOptions = Api::CreditsTopupOptions; - const auto apiCredits = content->lifetime().make_state( - _controller->session().user()); - - apiCredits->request( - ) | rpl::start_with_error_done([=](const QString &error) { - _controller->showToast(error); - }, [=] { - fill(apiCredits->options()); - }, content->lifetime()); -} - void Credits::setupHistory(not_null container) { const auto history = container->add( object_ptr>( @@ -432,7 +293,12 @@ void Credits::setupHistory(not_null container) { void Credits::setupContent() { const auto content = Ui::CreateChild(this); - setupOptions(content); + const auto paid = [=] { + if (_parent) { + Ui::StartFireworks(_parent); + } + }; + FillCreditOptions(_controller, content, paid); setupHistory(content); Ui::ResizeFitChild(this, content); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 6b8cc7ecb..bb7688177 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -15,9 +15,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_user.h" #include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. +#include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "payments/payments_checkout_process.h" +#include "payments/payments_form.h" #include "settings/settings_common_session.h" +#include "settings/settings_credits_graphics.h" #include "statistics/widgets/chart_header_widget.h" #include "ui/boxes/boost_box.h" // Ui::StartFireworks. #include "ui/controls/userpic_button.h" @@ -28,12 +32,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" +#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/labels.h" #include "ui/widgets/tooltip.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/padding_wrap.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include "styles/style_credits.h" #include "styles/style_giveaway.h" @@ -41,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_premium.h" #include "styles/style_settings.h" +#include "styles/style_statistics.h" #include // XXH64. @@ -49,6 +59,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Settings { namespace { +[[nodiscard]] uint64 UniqueIdFromOption( + const Data::CreditTopupOption &d) { + const auto string = QString::number(d.credits) + + d.product + + d.currency + + QString::number(d.amount); + + return XXH64(string.data(), string.size() * sizeof(ushort), 0); +} + class Balance final : public Ui::RpWidget , public Ui::AbstractTooltipShower { @@ -143,6 +163,134 @@ QImage GenerateStars(int height, int count) { return frame; } +void FillCreditOptions( + not_null controller, + not_null container, + Fn paid) { + const auto options = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto content = options->entity(); + + Ui::AddSkip(content, st::settingsPremiumOptionsPadding.top()); + + const auto singleStarWidth = GenerateStars( + st::creditsTopupButton.height, + 1).width() / style::DevicePixelRatio(); + + const auto fill = [=](Data::CreditTopupOptions options) { + while (content->count()) { + delete content->widgetAt(0); + } + Ui::AddSubsectionTitle( + content, + tr::lng_credits_summary_options_subtitle()); + const auto &st = st::creditsTopupButton; + const auto diffBetweenTextAndStar = st.padding.left() + - st.iconLeft + - singleStarWidth; + const auto buttonHeight = st.height + rect::m::sum::v(st.padding); + for (auto i = 0; i < options.size(); i++) { + const auto &option = options[i]; + const auto button = content->add(object_ptr( + content, + rpl::never(), + st)); + const auto text = button->lifetime().make_state( + st.style, + tr::lng_credits_summary_options_credits( + tr::now, + lt_count_decimal, + option.credits)); + const auto price = Ui::CreateChild( + button, + Ui::FillAmountAndCurrency(option.amount, option.currency), + st::creditsTopupPrice); + const auto inner = Ui::CreateChild(button); + const auto stars = GenerateStars(st.height, (i + 1)); + inner->paintRequest( + ) | rpl::start_with_next([=](const QRect &rect) { + auto p = QPainter(inner); + p.drawImage( + 0, + (buttonHeight - stars.height()) / 2, + stars); + const auto textLeft = diffBetweenTextAndStar + + stars.width() / style::DevicePixelRatio(); + p.setPen(st.textFg); + text->draw(p, { + .position = QPoint(textLeft, 0), + .availableWidth = inner->width() - textLeft, + }); + }, inner->lifetime()); + button->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + price->moveToRight(st.padding.right(), st.padding.top()); + inner->moveToLeft(st.iconLeft, st.padding.top()); + inner->resize( + size.width() + - rect::m::sum::h(st.padding) + - price->width(), + buttonHeight); + }, button->lifetime()); + button->setClickedCallback([=] { + const auto invoice = Payments::InvoiceCredits{ + .session = &controller->session(), + .randomId = UniqueIdFromOption(option), + .credits = option.credits, + .product = option.product, + .currency = option.currency, + .amount = option.amount, + .extended = option.extended, + }; + + const auto weak = Ui::MakeWeak(button); + const auto done = [=](Payments::CheckoutResult result) { + if (const auto strong = weak.data()) { + strong->window()->setFocus(); + if (result == Payments::CheckoutResult::Paid) { + if (paid) { + paid(); + } + } + } + }; + + Payments::CheckoutProcess::Start(std::move(invoice), done); + }); + Ui::ToggleChildrenVisibility(button, true); + } + + // Footer. + { + auto text = tr::lng_credits_summary_options_about( + lt_link, + tr::lng_credits_summary_options_about_link( + ) | rpl::map([](const QString &t) { + using namespace Ui::Text; + return Link(t, u"https://telegram.org/tos"_q); + }), + Ui::Text::RichLangValue); + Ui::AddSkip(content); + Ui::AddDividerText(content, std::move(text)); + } + + content->resizeToWidth(container->width()); + }; + + using ApiOptions = Api::CreditsTopupOptions; + const auto apiCredits = content->lifetime().make_state( + controller->session().user()); + + apiCredits->request( + ) | rpl::start_with_error_done([=](const QString &error) { + controller->showToast(error); + }, [=] { + fill(apiCredits->options()); + }, content->lifetime()); +} + not_null AddBalanceWidget( not_null parent, rpl::producer balanceValue, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index ec078f492..3c09ff50f 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -23,12 +23,18 @@ class SessionController; namespace Ui { class GenericBox; class RpWidget; +class VerticalLayout; } // namespace Ui namespace Settings { [[nodiscard]] QImage GenerateStars(int height, int count); +void FillCreditOptions( + not_null controller, + not_null container, + Fn paid); + [[nodiscard]] not_null AddBalanceWidget( not_null parent, rpl::producer balanceValue, From 5defb9fb17816736ba9c7c66d55e4e0fde60f587 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 25 May 2024 01:36:25 +0300 Subject: [PATCH 146/225] Fixed display of credit spending. --- .../info/statistics/info_statistics_list_controllers.cpp | 2 +- Telegram/SourceFiles/settings/settings_credits_graphics.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index ecbcff3ec..0cdbe6233 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -769,7 +769,7 @@ void CreditsRow::init() { _rightText.setText( st::semiboldTextStyle, (!_entry.bareId ? QChar('+') : kMinus) - + Lang::FormatCountDecimal(_entry.credits)); + + Lang::FormatCountDecimal(std::abs(int64(_entry.credits)))); } _paintUserpicCallback = !PeerListRow::special() ? PeerListRow::generatePaintUserpicCallback(false) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index bb7688177..2511079c6 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -426,7 +426,7 @@ void ReceiptCreditsBox( const auto text = lifetime.make_state( st::semiboldTextStyle, (!e.bareId ? QChar('+') : kMinus) - + Lang::FormatCountDecimal(e.credits)); + + Lang::FormatCountDecimal(std::abs(int64(e.credits)))); const auto amount = content->add( object_ptr( From 7d75c2521457ae322970a59058b1dd5a11077c96 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 25 May 2024 01:12:56 +0300 Subject: [PATCH 147/225] Added box for small balance of credits. --- Telegram/Resources/langs/lang.strings | 3 + .../info_statistics_list_controllers.cpp | 2 +- .../payments/payments_checkout_process.cpp | 2 + .../payments/payments_non_panel_process.cpp | 32 +++++++- .../SourceFiles/settings/settings_credits.cpp | 2 +- .../settings/settings_credits_graphics.cpp | 82 +++++++++++++++++++ .../settings/settings_credits_graphics.h | 8 ++ Telegram/SourceFiles/ui/effects/credits.style | 5 ++ 8 files changed, 133 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index efb548724..9bae741a0 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2324,6 +2324,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; "lng_credits_box_history_entry_about" = "You can dispute this transaction {link}."; "lng_credits_box_history_entry_about_link" = "here"; +"lng_credits_small_balance_title#one" = "{count} Star Needed"; +"lng_credits_small_balance_title#other" = "{count} Stars Needed"; +"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps."; "lng_location_title" = "Location"; "lng_location_about" = "Display the location of your business on your account."; diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 0cdbe6233..b608b7218 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -868,7 +868,7 @@ CreditsController::CreditsController(CreditsDescriptor d) , _premiumBot(d.premiumBot) , _entryClickedCallback(std::move(d.entryClickedCallback)) , _creditIcon(d.creditIcon) -, _api(d.premiumBot, d.in, d.out) +, _api(d.premiumBot->session().user(), d.in, d.out) , _firstSlice(std::move(d.firstSlice)) { PeerListController::setStyleOverrides(&st::boostsListBox); } diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 3080c149b..cbb435389 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -414,11 +414,13 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { if (_nonPanelPaymentFormProcess) { _nonPanelPaymentFormProcess( std::make_shared(data.data)); + close(); } }, [&](const CreditsReceiptReady &data) { if (_nonPanelPaymentFormProcess) { _nonPanelPaymentFormProcess( std::make_shared(data.data)); + close(); } }, [&](const Error &error) { handleError(error); diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index f2874d62e..12fb6a13f 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -7,12 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "payments/payments_non_panel_process.h" +#include "api/api_credits.h" #include "base/unixtime.h" #include "boxes/send_credits_box.h" #include "data/data_credits.h" #include "data/data_photo.h" +#include "data/data_user.h" #include "history/history_item.h" #include "history/history_item_components.h" +#include "main/main_session.h" #include "payments/payments_checkout_process.h" // NonPanelPaymentForm. #include "payments/payments_form.h" #include "settings/settings_credits_graphics.h" @@ -40,7 +43,34 @@ Fn ProcessNonPanelPaymentFormFactory( using CreditsFormDataPtr = std::shared_ptr; using CreditsReceiptPtr = std::shared_ptr; if (const auto creditsData = std::get_if(&form)) { - controller->uiShow()->show(Box(Ui::SendCreditsBox, *creditsData)); + const auto form = *creditsData; + const auto lifetime = std::make_shared(); + const auto api = lifetime->make_state( + controller->session().user()); + const auto sendBox = [=, weak = base::make_weak(controller)] { + if (const auto strong = weak.get()) { + controller->uiShow()->show(Box(Ui::SendCreditsBox, form)); + } + }; + const auto weak = base::make_weak(controller); + api->request({}, [=](Data::CreditsStatusSlice slice) { + if (const auto strong = weak.get()) { + strong->session().setCredits(slice.balance); + const auto creditsNeeded = int64(form->invoice.credits) + - int64(slice.balance); + if (creditsNeeded <= 0) { + sendBox(); + } else { + strong->uiShow()->show(Box( + Settings::SmallBalanceBox, + strong, + creditsNeeded, + form->botId, + sendBox)); + } + } + lifetime->destroy(); + }); } if (const auto r = std::get_if(&form)) { const auto receipt = *r; diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index adffaba25..48a3eb9ba 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -298,7 +298,7 @@ void Credits::setupContent() { Ui::StartFireworks(_parent); } }; - FillCreditOptions(_controller, content, paid); + FillCreditOptions(_controller, content, 0, paid); setupHistory(content); Ui::ResizeFitChild(this, content); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 2511079c6..cab21f9ea 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -166,6 +166,7 @@ QImage GenerateStars(int height, int count) { void FillCreditOptions( not_null controller, not_null container, + int minCredits, Fn paid) { const auto options = container->add( object_ptr>( @@ -193,6 +194,9 @@ void FillCreditOptions( const auto buttonHeight = st.height + rect::m::sum::v(st.padding); for (auto i = 0; i < options.size(); i++) { const auto &option = options[i]; + if (option.credits < minCredits) { + continue; + } const auto button = content->add(object_ptr( content, rpl::never(), @@ -536,4 +540,82 @@ object_ptr HistoryEntryPhoto( return owned; } +void SmallBalanceBox( + not_null box, + not_null controller, + int creditsNeeded, + UserId botId, + Fn paid) { + box->setWidth(st::boxWideWidth); + box->addButton(tr::lng_close(), [=] { box->closeBox(); }); + const auto done = [=] { + box->closeBox(); + paid(); + }; + + const auto bot = controller->session().data().user(botId).get(); + + const auto content = [&]() -> Ui::Premium::TopBarAbstract* { + const auto weak = base::make_weak(controller); + const auto clickContextOther = [=] { + return QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = weak, + .botStartAutoSubmit = true, + }); + }; + return box->setPinnedToTopContent(object_ptr( + box, + st::creditsLowBalancePremiumCover, + Ui::Premium::TopBarDescriptor{ + .clickContextOther = clickContextOther, + .title = tr::lng_credits_small_balance_title( + lt_count, + rpl::single(creditsNeeded) | tr::to_count()), + .about = tr::lng_credits_small_balance_about( + lt_bot, + rpl::single(TextWithEntities{ bot->name() }), + Ui::Text::RichLangValue), + .light = true, + .gradientStops = Ui::Premium::CreditsIconGradientStops(), + })); + }(); + + FillCreditOptions(controller, box->verticalLayout(), creditsNeeded, done); + + content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight); + content->setMinimumHeight(st::infoLayerTopBarHeight); + + content->resize(content->width(), content->maximumHeight()); + content->additionalHeight( + ) | rpl::start_with_next([=](int additionalHeight) { + const auto wasMax = (content->height() == content->maximumHeight()); + content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight + + additionalHeight); + if (wasMax) { + content->resize(content->width(), content->maximumHeight()); + } + }, content->lifetime()); + + { + const auto balance = AddBalanceWidget( + content, + controller->session().creditsValue(), + true); + const auto api = balance->lifetime().make_state( + controller->session().user()); + api->request({}, [=](Data::CreditsStatusSlice slice) { + controller->session().setCredits(slice.balance); + }); + rpl::combine( + balance->sizeValue(), + content->sizeValue() + ) | rpl::start_with_next([=](const QSize &, const QSize &) { + balance->moveToRight( + st::creditsHistoryRightSkip * 2, + st::creditsHistoryRightSkip); + balance->update(); + }, balance->lifetime()); + } +} + } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 3c09ff50f..cb6c27e7e 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -33,6 +33,7 @@ namespace Settings { void FillCreditOptions( not_null controller, not_null container, + int minCredits, Fn paid); [[nodiscard]] not_null AddBalanceWidget( @@ -51,5 +52,12 @@ void ReceiptCreditsBox( not_null photo, int photoSize); +void SmallBalanceBox( + not_null box, + not_null controller, + int creditsNeeded, + UserId botId, + Fn paid); + } // namespace Settings diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 9f6866f96..bc05f58f8 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -16,6 +16,11 @@ creditsPremiumCover: PremiumCover(defaultPremiumCover) { textFg: boxTitleFg; } } +creditsLowBalancePremiumCover: PremiumCover(creditsPremiumCover) { + starSize: size(64px, 62px); + starTopSkip: 30px; +} +creditsLowBalancePremiumCoverHeight: 180px; creditsTopupButton: SettingsButton(settingsButton) { style: semiboldTextStyle; } From 0bd780b20f1ca6d2008859cd36e2b184d7aafcc8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 25 May 2024 01:36:17 +0300 Subject: [PATCH 148/225] Added fireworks effect on success credit sending. --- Telegram/SourceFiles/boxes/send_credits_box.cpp | 4 +++- Telegram/SourceFiles/boxes/send_credits_box.h | 3 ++- .../SourceFiles/payments/payments_non_panel_process.cpp | 9 ++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 303560ab3..309f4833c 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -43,7 +43,8 @@ namespace Ui { void SendCreditsBox( not_null box, - std::shared_ptr form) { + std::shared_ptr form, + Fn sent) { if (!form) { return; } @@ -145,6 +146,7 @@ void SendCreditsBox( ).done([=](auto result) { state->confirmButtonBusy = false; box->closeBox(); + sent(); }).fail([=](const MTP::Error &error) { state->confirmButtonBusy = false; box->uiShow()->showToast(error.type()); diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index bbc74107a..25ceb1d56 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -19,6 +19,7 @@ class GenericBox; void SendCreditsBox( not_null box, - std::shared_ptr data); + std::shared_ptr data, + Fn sent); } // namespace Ui diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 12fb6a13f..62a3b4681 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -16,9 +16,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_components.h" #include "main/main_session.h" +#include "mainwidget.h" #include "payments/payments_checkout_process.h" // NonPanelPaymentForm. #include "payments/payments_form.h" #include "settings/settings_credits_graphics.h" +#include "ui/boxes/boost_box.h" // Ui::StartFireworks. #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "window/window_session_controller.h" @@ -49,7 +51,12 @@ Fn ProcessNonPanelPaymentFormFactory( controller->session().user()); const auto sendBox = [=, weak = base::make_weak(controller)] { if (const auto strong = weak.get()) { - controller->uiShow()->show(Box(Ui::SendCreditsBox, form)); + controller->uiShow()->show(Box( + Ui::SendCreditsBox, + form, + crl::guard(strong, [=] { + Ui::StartFireworks(strong->content()); + }))); } }; const auto weak = base::make_weak(controller); From c942034ca45e6d5c937adceaa00d738946b1df50 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 25 May 2024 03:11:58 +0300 Subject: [PATCH 149/225] Added link to terms to box of credits history entries. --- Telegram/Resources/langs/lang.strings | 1 + .../settings/settings_credits_graphics.cpp | 18 ++++++++++++++++++ Telegram/SourceFiles/ui/effects/credits.style | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9bae741a0..54b5ee94a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2316,6 +2316,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?"; "lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star"; "lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars"; +"lng_credits_box_out_about" = "Review the {link} for Stars."; "lng_credits_summary_in_toast_title" = "Stars Acquired"; "lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance."; "lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance."; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index cab21f9ea..47dec10b9 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -481,6 +481,24 @@ void ReceiptCreditsBox( box->verticalLayout(), e); + Ui::AddSkip(content); + + box->addRow(object_ptr>( + box, + object_ptr( + box, + tr::lng_credits_box_out_about( + lt_link, + tr::lng_payments_terms_link( + ) | rpl::map([](const QString &t) { + using namespace Ui::Text; + return Link(t, u"https://telegram.org/tos"_q); + }), + Ui::Text::WithEntities), + st::creditsBoxAboutDivider))); + + Ui::AddSkip(content); + const auto button = box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index bc05f58f8..2da64404d 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -39,3 +39,7 @@ creditsBoxAbout: FlatLabel(defaultFlatLabel) { creditsBoxAboutTitle: FlatLabel(settingsPremiumUserTitle) { minWidth: 256px; } + +creditsBoxAboutDivider: FlatLabel(boxDividerLabel) { + align: align(top); +} From a0d97f03cb795ff3d51733164743da07834ec0ce Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 25 May 2024 11:31:00 +0400 Subject: [PATCH 150/225] Add factcheck footer support. --- Telegram/Resources/langs/lang.strings | 1 + .../view/media/history_view_web_page.cpp | 75 ++++++++++++++++--- .../view/media/history_view_web_page.h | 6 +- Telegram/SourceFiles/ui/chat/chat.style | 4 + 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 54b5ee94a..eb8eecb58 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3301,6 +3301,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_factcheck_add_done" = "Fact check added."; "lng_factcheck_edit_done" = "Fact check edited."; "lng_factcheck_remove_done" = "Fact check removed."; +"lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation."; "lng_translate_show_original" = "Show Original"; "lng_translate_bar_to" = "Translate to {name}"; diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 9c4c1ef05..0275ed2c2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -153,6 +153,17 @@ constexpr auto kFactcheckAboutDuration = 5 * crl::time(1000); }); } +[[nodiscard]] QString LookupFactcheckCountryIso2( + not_null item) { + const auto info = item->Get(); + return info ? info->data.country : QString(); +} + +[[nodiscard]] QString LookupFactcheckCountryName(const QString &iso2) { + const auto name = Countries::Instance().countryNameByISO2(iso2); + return name.isEmpty() ? iso2 : name; +} + [[nodiscard]] ClickHandlerPtr AboutFactcheckClickHandler(QString iso2) { return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); @@ -163,10 +174,11 @@ constexpr auto kFactcheckAboutDuration = 5 * crl::time(1000); ? controller->uiShow() : nullptr; if (show) { - const auto name = Countries::Instance().countryNameByISO2(iso2); - const auto use = name.isEmpty() ? iso2 : name; + const auto country = LookupFactcheckCountryName(iso2); show->showToast({ - .text = { tr::lng_factcheck_about(tr::now, lt_country, use) }, + .text = { + tr::lng_factcheck_about(tr::now, lt_country, country) + }, .duration = kFactcheckAboutDuration, }); } @@ -179,7 +191,7 @@ constexpr auto kFactcheckAboutDuration = 5 * crl::time(1000); return std::make_shared([=](ClickContext context) { if (const auto strong = weak.get()) { if (const auto factcheck = strong->Get()) { - factcheck->expanded = !factcheck->expanded; + factcheck->expanded = factcheck->expanded ? 0 : 1; strong->history()->owner().requestViewResize(strong); } } @@ -310,6 +322,7 @@ void WebPage::setupAdditionalData() { } } else if (_data->type == WebPageType::Factcheck) { _additionalData = std::make_unique(FactcheckData()); + const auto raw = factcheckData(); } } @@ -347,11 +360,23 @@ QSize WebPage::countOptimalSize() { _dataVersion = _data->version; _openl = nullptr; _attach = nullptr; - _collage = PrepareCollageMedia(_parent->data(), _data->collage); + const auto item = _parent->data(); + _collage = PrepareCollageMedia(item, _data->collage); const auto min = st::msgMinWidth - rect::m::sum::h(_st.padding); _siteName = Ui::Text::String(min); _title = Ui::Text::String(min); _description = Ui::Text::String(min); + if (factcheck) { + factcheck->footer = Ui::Text::String( + st::factcheckFooterStyle, + tr::lng_factcheck_bottom( + tr::now, + lt_country, + LookupFactcheckCountryName( + LookupFactcheckCountryIso2(item))), + kDefaultTextOptions, + min); + } } const auto lineHeight = UnitedLineHeight(); @@ -400,9 +425,9 @@ QSize WebPage::countOptimalSize() { } } else if (factcheck) { const auto item = _parent->data(); - if (const auto info = item->Get()) { - const auto country = info->data.country; - factcheck->hint.link = AboutFactcheckClickHandler(country); + const auto iso2 = LookupFactcheckCountryIso2(item); + if (!iso2.isEmpty()) { + factcheck->hint.link = AboutFactcheckClickHandler(iso2); } } else if (_data->document && (_data->document->isWallPaper() @@ -535,6 +560,10 @@ QSize WebPage::countOptimalSize() { _description.maxWidth() + articlePhotoMaxWidth); minHeight += descriptionMinHeight; } + if (factcheck && factcheck->expanded) { + accumulate_max(maxWidth, factcheck->footer.maxWidth()); + minHeight += st::factcheckFooterSkip + factcheck->footer.minHeight(); + } if (_attach) { const auto attachAtTop = _siteName.isEmpty() && _title.isEmpty() @@ -597,8 +626,8 @@ QSize WebPage::countCurrentSize(int newWidth) { ? computeFactcheckMetrics(_description.countHeight(innerWidth)) : FactcheckMetrics(); if (factcheck) { - factcheck->expandable = factcheckMetrics.expandable; - factcheck->expanded = factcheckMetrics.expanded; + factcheck->expandable = factcheckMetrics.expandable ? 1 : 0; + factcheck->expanded = factcheckMetrics.expanded ? 1 : 0; _openl = factcheck->expandable ? ToggleFactcheckClickHandler(_parent) : nullptr; @@ -682,6 +711,11 @@ QSize WebPage::countCurrentSize(int newWidth) { newHeight += _descriptionLines * lineHeight; } } + if (factcheck && factcheck->expanded) { + factcheck->footerHeight = st::factcheckFooterSkip + + factcheck->footer.countHeight(innerWidth); + newHeight += factcheck->footerHeight; + } if (_attach) { const auto attachAtTop = !_siteNameLines @@ -1023,6 +1057,23 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { ? (_descriptionLines * lineHeight) : _description.countHeight(paintw); } + if (factcheck && factcheck->expanded) { + const auto skip = st::factcheckFooterSkip; + const auto line = st::lineWidth; + const auto separatorTop = tshift + skip / 2; + + auto color = cache->icon; + color.setAlphaF(color.alphaF() * 0.3); + p.fillRect(inner.left(), separatorTop, paintw, line, color); + + p.setPen(cache->icon); + factcheck->footer.draw(p, { + .position = { inner.left(), tshift + skip }, + .outerWidth = width(), + .availableWidth = paintw, + }); + tshift += factcheck->footerHeight; + } if (_attach) { const auto attachAtTop = !_siteNameLines && !_titleLines @@ -1492,7 +1543,9 @@ bool WebPage::isLogEntryOriginal() const { WebPage::FactcheckMetrics WebPage::computeFactcheckMetrics( int fullHeight) const { const auto possible = fullHeight / st::normalFont->height; - const auto expandable = (possible > kFactcheckCollapsedLines + 1); + //const auto expandable = (possible > kFactcheckCollapsedLines + 1); + // Now always expandable because of the footer. + const auto expandable = true; const auto check = _parent->Get(); const auto expanded = check && check->expanded; const auto allowExpanding = (expanded || !expandable); diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h index 4637adb31..454af2bbf 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -129,8 +129,10 @@ private: }; struct FactcheckData { HintData hint; - bool expandable = false; - bool expanded = false; + Ui::Text::String footer; + uint32 footerHeight : 30 = 0; + uint32 expandable : 1 = 0; + uint32 expanded : 1 = 0; }; using AdditionalData = std::variant< StickerSetData, diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 9dbf43303..3069b6f90 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1133,6 +1133,10 @@ effectPreviewLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { thickness: 2px; } +factcheckFooterSkip: 16px; +factcheckFooterStyle: TextStyle(defaultTextStyle) { + font: font(11px); +} factcheckIconExpand: icon {{ "fast_to_original-rotate_cw", historyPeer1NameFg }}; factcheckIconCollapse: icon {{ "fast_to_original-rotate_ccw", historyPeer1NameFg }}; factcheckField: InputField(defaultInputField) { From 885dcf0b284d84971281922c8a51cd145f0b3aac Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 27 May 2024 23:24:49 +0400 Subject: [PATCH 151/225] Update API scheme on layer 181. --- Telegram/SourceFiles/mtproto/scheme/api.tl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index fb82b7ff9..f62e07bf6 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1803,7 +1803,7 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#cc7079b2 flags:# id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument = StarsTransaction; +starsTransaction#cc7079b2 flags:# refund:flags.3?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument = StarsTransaction; payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; @@ -2351,7 +2351,7 @@ payments.getStarsTopupOptions#c00ec7d3 = Vector; payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; payments.getStarsTransactions#673ac2f9 flags:# inbound:flags.0?true outbound:flags.1?true peer:InputPeer offset:string = payments.StarsStatus; payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payments.PaymentResult; -payments.refundStarsCharge#f090bbec user_id:InputUser msg_id:int charge_id:string = Updates; +payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; From be099880d8bbf928a266f4b2ea93de156c07cfe1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 02:22:32 +0300 Subject: [PATCH 152/225] Added initial ability to claim refund credits from history entries list. --- Telegram/SourceFiles/api/api_credits.cpp | 25 +++++++++-- Telegram/SourceFiles/api/api_credits.h | 6 +++ .../info_statistics_list_controllers.cpp | 42 +++++++++++++++++-- .../info_statistics_list_controllers.h | 5 +++ .../SourceFiles/settings/settings_credits.cpp | 3 ++ 5 files changed, 74 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 453d9536f..84c3e646f 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -8,15 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_credits.h" #include "apiwrap.h" +#include "api/api_updates.h" #include "base/unixtime.h" #include "data/data_peer.h" #include "data/data_photo.h" #include "data/data_session.h" +#include "data/data_user.h" #include "main/main_app_config.h" #include "main/main_session.h" -#if _DEBUG -#include "base/random.h" -#endif namespace Api { namespace { @@ -199,4 +198,24 @@ rpl::producer> PremiumPeerBot( }; } +void CreditsRefund( + not_null peer, + const QString &entryId, + Fn done, + Fn fail) { + const auto user = peer->asUser(); + if (!user) { + return; + } + peer->session().api().request(MTPpayments_RefundStarsCharge( + user->inputUser, + MTP_string(entryId) + )).done([=](const MTPUpdates &result) { + peer->session().api().updates().applyUpdates(result); + done(); + }).fail([=](const MTP::Error &error) { + fail(error.type()); + }).send(); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index 265e7b387..bd064a65b 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -68,6 +68,12 @@ private: }; +void CreditsRefund( + not_null peer, + const QString &entryId, + Fn done, + Fn fail); + [[nodiscard]] rpl::producer> PremiumPeerBot( not_null session); diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index b608b7218..0028e0fab 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/channel_statistics/boosts/giveaway/boost_badge.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "main/session/session_show.h" #include "ui/effects/credits_graphics.h" #include "ui/effects/outline_segments.h" // Ui::UnreadStoryOutlineGradient. #include "ui/effects/toggle_arrow.h" @@ -841,6 +842,10 @@ public: void rowClicked(not_null row) override; void loadMoreRows() override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + [[nodiscard]] bool skipRequest() const; void requestNext(); @@ -930,6 +935,29 @@ void CreditsController::rowClicked(not_null row) { } } +base::unique_qptr CreditsController::rowContextMenu( + QWidget *parent, + not_null row) { + const auto entry = static_cast(row.get())->entry(); + if (!entry.bareId) { + return nullptr; + } + auto menu = base::make_unique_q( + parent, + st::defaultPopupMenu); + const auto peer = row->peer(); + const auto callback = crl::guard(parent, [=, id = entry.id] { + const auto show = delegate()->peerListUiShow(); + Api::CreditsRefund( + peer, + id, + [=] { show->showToast(tr::lng_report_spam_done(tr::now)); }, + [=](const QString &error) { show->showToast(error); }); + }); + menu->addAction(tr::lng_channel_earn_history_return(tr::now), callback); + return menu; +} + rpl::producer CreditsController::allLoadedValue() const { return _allLoaded.value(); } @@ -1086,6 +1114,7 @@ void AddBoostsList( } void AddCreditsHistoryList( + std::shared_ptr show, const Data::CreditsStatusSlice &firstSlice, not_null container, Fn callback, @@ -1094,13 +1123,18 @@ void AddCreditsHistoryList( bool in, bool out) { struct State final { - State(CreditsDescriptor d) : controller(std::move(d)) { + State( + CreditsDescriptor d, + std::shared_ptr show) + : delegate(std::move(show)) + , controller(std::move(d)) { } - PeerListContentDelegateSimple delegate; + PeerListContentDelegateShow delegate; CreditsController controller; }; - auto d = CreditsDescriptor{ firstSlice, callback, bot, icon, in, out }; - const auto state = container->lifetime().make_state(std::move(d)); + const auto state = container->lifetime().make_state( + CreditsDescriptor{ firstSlice, callback, bot, icon, in, out }, + show); state->delegate.setContent(container->add( object_ptr(container, &state->controller))); diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h index 09883090d..2d381cb7c 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h @@ -23,6 +23,10 @@ struct RecentPostId; struct SupergroupStatistics; } // namespace Data +namespace Main { +class SessionShow; +} // namespace Main + namespace Info::Statistics { void AddPublicForwards( @@ -47,6 +51,7 @@ void AddBoostsList( rpl::producer title); void AddCreditsHistoryList( + std::shared_ptr show, const Data::CreditsStatusSlice &firstSlice, not_null container, Fn entryClickedCallback, diff --git a/Telegram/SourceFiles/settings/settings_credits.cpp b/Telegram/SourceFiles/settings/settings_credits.cpp index 48a3eb9ba..4cfeff843 100644 --- a/Telegram/SourceFiles/settings/settings_credits.cpp +++ b/Telegram/SourceFiles/settings/settings_credits.cpp @@ -239,6 +239,7 @@ void Credits::setupHistory(not_null container) { }; Info::Statistics::AddCreditsHistoryList( + controller->uiShow(), fullSlice, fullWrap->entity(), entryClicked, @@ -247,6 +248,7 @@ void Credits::setupHistory(not_null container) { true, true); Info::Statistics::AddCreditsHistoryList( + controller->uiShow(), inSlice, inWrap->entity(), entryClicked, @@ -255,6 +257,7 @@ void Credits::setupHistory(not_null container) { true, false); Info::Statistics::AddCreditsHistoryList( + controller->uiShow(), outSlice, outWrap->entity(), std::move(entryClicked), From ebaffc333e0a9758c09d823e57ec4f575ba79ce5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 02:28:13 +0300 Subject: [PATCH 153/225] Added initial api support of refund flag to credits history entries. --- Telegram/SourceFiles/api/api_credits.cpp | 1 + Telegram/SourceFiles/data/data_credits.h | 1 + .../SourceFiles/settings/settings_credits_graphics.cpp | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 84c3e646f..00f9e789f 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -52,6 +52,7 @@ namespace { }, [](const MTPDstarsTransactionPeerPremiumBot &) { return Data::CreditsHistoryEntry::PeerType::PremiumBot; }), + .refunded = tl.data().is_refund(), }; } diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index af830e26c..ddfd22a83 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -37,6 +37,7 @@ struct CreditsHistoryEntry final { uint64 credits = 0; uint64 bareId = 0; PeerType peerType; + bool refunded = false; }; struct CreditsStatusSlice final { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 47dec10b9..0cd0ae1a2 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -473,6 +473,16 @@ void ReceiptCreditsBox( st::defaultFlatLabel))); } + if (e.refunded) { + Ui::AddSkip(content); + box->addRow(object_ptr>( + box, + object_ptr( + box, + tr::lng_channel_earn_history_return_about(), + st::defaultFlatLabel))); + } + Ui::AddSkip(content); Ui::AddSkip(content); From 056ba644edc617c470cddee8b2a0c38cbac106a1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 13:58:31 +0400 Subject: [PATCH 154/225] Fix empty search placeholder. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index a56e28b2f..f90993a19 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -118,9 +118,16 @@ constexpr auto kChatPreviewDelay = crl::time(1000); const auto query = state.query.trimmed(); const auto hashtag = !query.isEmpty() && (query[0] == '#'); const auto trimmed = hashtag ? query.mid(1).trimmed() : query; + const auto fromPeer = (state.tab == ChatSearchTab::MyMessages + || state.tab == ChatSearchTab::PublicPosts + || !state.inChat.peer() + || !(state.inChat.peer()->isChat() + || state.inChat.peer()->isMegagroup())) + ? nullptr + : state.fromPeer; const auto waiting = trimmed.isEmpty() && state.tags.empty() - && !state.fromPeer; + && !fromPeer; const auto icon = waiting ? SearchEmptyIcon::Search : SearchEmptyIcon::NoResults; From c0f3d263a38b8a3650dec493d827aba09a40d157 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2024 13:58:47 +0400 Subject: [PATCH 155/225] Remove webpage length limit for factcheck. --- Telegram/SourceFiles/ui/text/text_options.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Telegram/SourceFiles/ui/text/text_options.cpp b/Telegram/SourceFiles/ui/text/text_options.cpp index 3c9eb97a6..404b53c8b 100644 --- a/Telegram/SourceFiles/ui/text/text_options.cpp +++ b/Telegram/SourceFiles/ui/text/text_options.cpp @@ -116,7 +116,6 @@ void InitTextOptions() { - st::messageQuoteStyle.padding.left() - st::messageQuoteStyle.padding.right() - st::msgPadding.right(); - WebpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; } const TextParseOptions &ItemTextDefaultOptions() { From 9a5923676ab207bc83bcb64d19af4275f59f5a78 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 16:00:35 +0300 Subject: [PATCH 156/225] Added posting of payment event to webview bot after credits sending. --- Telegram/SourceFiles/boxes/send_credits_box.cpp | 1 - .../inline_bots/bot_attach_web_view.cpp | 3 ++- .../payments/payments_non_panel_process.cpp | 17 +++++++++++++++-- .../payments/payments_non_panel_process.h | 4 +++- .../settings/settings_credits_graphics.cpp | 1 - 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 309f4833c..2873b4ffe 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" -#include "payments/payments_form.h" #include "settings/settings_credits_graphics.h" #include "ui/controls/userpic_button.h" #include "ui/effects/premium_graphics.h" diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 03561bdd6..68da29a3a 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -610,7 +610,8 @@ void AttachWebView::botHandleInvoice(QString slug) { reactivate, _context ? Payments::ProcessNonPanelPaymentFormFactory( - _context->controller.get()) + _context->controller.get(), + reactivate) : nullptr); } diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 62a3b4681..efab4dd89 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -40,7 +40,8 @@ bool IsCreditsInvoice(not_null item) { } // namespace Fn ProcessNonPanelPaymentFormFactory( - not_null controller) { + not_null controller, + Fn maybeReturnToBot) { return [=](NonPanelPaymentForm form) { using CreditsFormDataPtr = std::shared_ptr; using CreditsReceiptPtr = std::shared_ptr; @@ -51,12 +52,24 @@ Fn ProcessNonPanelPaymentFormFactory( controller->session().user()); const auto sendBox = [=, weak = base::make_weak(controller)] { if (const auto strong = weak.get()) { - controller->uiShow()->show(Box( + const auto unsuccessful = std::make_shared(true); + const auto box = controller->uiShow()->show(Box( Ui::SendCreditsBox, form, crl::guard(strong, [=] { + *unsuccessful = false; Ui::StartFireworks(strong->content()); + if (maybeReturnToBot) { + maybeReturnToBot(CheckoutResult::Paid); + } }))); + box->boxClosing() | rpl::start_with_next([=] { + crl::on_main([=] { + if ((*unsuccessful) && maybeReturnToBot) { + maybeReturnToBot(CheckoutResult::Cancelled); + } + }); + }, box->lifetime()); } }; const auto weak = base::make_weak(controller); diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.h b/Telegram/SourceFiles/payments/payments_non_panel_process.h index 76939415d..fb647a72a 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.h +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.h @@ -15,10 +15,12 @@ class SessionController; namespace Payments { +enum class CheckoutResult; struct NonPanelPaymentForm; Fn ProcessNonPanelPaymentFormFactory( - not_null controller); + not_null controller, + Fn maybeReturnToBot = nullptr); Fn ProcessNonPanelPaymentFormFactory( not_null controller, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 0cd0ae1a2..066adf6f0 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common_session.h" #include "settings/settings_credits_graphics.h" #include "statistics/widgets/chart_header_widget.h" -#include "ui/boxes/boost_box.h" // Ui::StartFireworks. #include "ui/controls/userpic_button.h" #include "ui/effects/credits_graphics.h" #include "ui/effects/premium_graphics.h" From cd7507fb23b1fd4eaa07ec9cdf59a81fc69cb569 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 May 2024 16:28:35 +0300 Subject: [PATCH 157/225] Respected accessibility of premium for credits purchasing. --- Telegram/Resources/langs/lang.strings | 1 + .../payments/payments_non_panel_process.cpp | 9 ++++++++- .../settings/settings_credits_graphics.cpp | 19 ++++++++++++++----- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index eb8eecb58..9d6823fb5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2328,6 +2328,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_small_balance_title#one" = "{count} Star Needed"; "lng_credits_small_balance_title#other" = "{count} Stars Needed"; "lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps."; +"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; "lng_location_title" = "Location"; "lng_location_about" = "Display the location of your business on your account."; diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index efab4dd89..112cdd576 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "history/history_item.h" #include "history/history_item_components.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "mainwidget.h" #include "payments/payments_checkout_process.h" // NonPanelPaymentForm. @@ -80,13 +81,19 @@ Fn ProcessNonPanelPaymentFormFactory( - int64(slice.balance); if (creditsNeeded <= 0) { sendBox(); - } else { + } else if (strong->session().premiumPossible()) { strong->uiShow()->show(Box( Settings::SmallBalanceBox, strong, creditsNeeded, form->botId, sendBox)); + } else { + strong->uiShow()->showToast( + tr::lng_credits_purchase_blocked(tr::now)); + if (maybeReturnToBot) { + maybeReturnToBot(CheckoutResult::Failed); + } } } lifetime->destroy(); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 066adf6f0..e9f825ead 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -286,11 +286,20 @@ void FillCreditOptions( const auto apiCredits = content->lifetime().make_state( controller->session().user()); - apiCredits->request( - ) | rpl::start_with_error_done([=](const QString &error) { - controller->showToast(error); - }, [=] { - fill(apiCredits->options()); + if (controller->session().premiumPossible()) { + apiCredits->request( + ) | rpl::start_with_error_done([=](const QString &error) { + controller->showToast(error); + }, [=] { + fill(apiCredits->options()); + }, content->lifetime()); + } + + controller->session().premiumPossibleValue( + ) | rpl::start_with_next([=](bool premiumPossible) { + if (!premiumPossible) { + fill({}); + } }, content->lifetime()); } From 58c060c59d2103dd1893d7f1ba47ed8799a9e0bd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 29 May 2024 02:37:56 +0300 Subject: [PATCH 158/225] Improved style of box for credits history entries for refunded entry. --- .../settings/settings_credits_graphics.cpp | 73 ++++++++++++++----- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index e9f825ead..16c15e44e 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -437,23 +437,38 @@ void ReceiptCreditsBox( auto &lifetime = content->lifetime(); const auto text = lifetime.make_state( st::semiboldTextStyle, - (!e.bareId ? QChar('+') : kMinus) + ((!e.bareId || e.refunded) ? QChar('+') : kMinus) + Lang::FormatCountDecimal(std::abs(int64(e.credits)))); + const auto refundedText = tr::lng_channel_earn_history_return( + tr::now); + const auto refunded = e.refunded + ? lifetime.make_state( + st::defaultTextStyle, + refundedText) + : (Ui::Text::String*)(nullptr); const auto amount = content->add( object_ptr( content, star.height() / style::DevicePixelRatio())); const auto font = text->style()->font; + const auto refundedFont = st::defaultTextStyle.font; + const auto starWidth = star.width() + / style::DevicePixelRatio(); + const auto refundedSkip = refundedFont->spacew * 2; + const auto refundedWidth = refunded + ? refundedFont->width(refundedText) + + refundedSkip + + refundedFont->height + : 0; + const auto fullWidth = text->maxWidth() + + font->spacew * 1 + + starWidth + + refundedWidth; amount->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(amount); - const auto starWidth = star.width() - / style::DevicePixelRatio(); - const auto fullWidth = text->maxWidth() - + font->spacew * 2 - + starWidth; - p.setPen(!e.bareId + p.setPen((!e.bareId || e.refunded) ? st::boxTextFgGood : st::menuIconAttentionColor); const auto x = (amount->width() - fullWidth) / 2; @@ -463,11 +478,41 @@ void ReceiptCreditsBox( (amount->height() - font->height) / 2), .outerWidth = amount->width(), .availableWidth = amount->width(), - });; + }); p.drawImage( - x + fullWidth - starWidth, + x + fullWidth - starWidth - refundedWidth, 0, star); + + if (refunded) { + const auto refundedLeft = fullWidth + + x + - refundedWidth + + refundedSkip; + const auto pen = p.pen(); + auto color = pen.color(); + color.setAlphaF(color.alphaF() * 0.15); + p.setPen(Qt::NoPen); + p.setBrush(color); + { + auto hq = PainterHighQualityEnabler(p); + p.drawRoundedRect( + refundedLeft, + (amount->height() - refundedFont->height) / 2, + refundedWidth - refundedSkip, + refundedFont->height, + refundedFont->height / 2, + refundedFont->height / 2); + } + p.setPen(pen); + refunded->draw(p, Ui::Text::PaintContext{ + .position = QPoint( + refundedLeft + refundedFont->height / 2, + (amount->height() - refundedFont->height) / 2), + .outerWidth = refundedWidth, + .availableWidth = refundedWidth, + }); + } }, amount->lifetime()); } @@ -481,16 +526,6 @@ void ReceiptCreditsBox( st::defaultFlatLabel))); } - if (e.refunded) { - Ui::AddSkip(content); - box->addRow(object_ptr>( - box, - object_ptr( - box, - tr::lng_channel_earn_history_return_about(), - st::defaultFlatLabel))); - } - Ui::AddSkip(content); Ui::AddSkip(content); From d3a01b6235160285319ebf83fa323d975ae801d8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 29 May 2024 03:50:40 +0300 Subject: [PATCH 159/225] Improved style of list of credits history entries for entry photo. --- Telegram/CMakeLists.txt | 2 + .../info_statistics_list_controllers.cpp | 20 ++++++- .../settings/settings_credits_graphics.cpp | 36 ++---------- .../ui/effects/credits_graphics.cpp | 55 +++++++++++++++++++ .../SourceFiles/ui/effects/credits_graphics.h | 6 ++ Telegram/cmake/td_ui.cmake | 2 - 6 files changed, 85 insertions(+), 36 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d1171a5ef..6e786a6cd 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1467,6 +1467,8 @@ PRIVATE ui/controls/silent_toggle.h ui/controls/userpic_button.cpp ui/controls/userpic_button.h + ui/effects/credits_graphics.cpp + ui/effects/credits_graphics.h ui/effects/emoji_fly_animation.cpp ui/effects/emoji_fly_animation.h ui/effects/message_sending_animation_common.h diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 0028e0fab..51dcc203b 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -713,6 +713,7 @@ public: Data::CreditsHistoryEntry entry; not_null creditIcon; int rowHeight = 0; + Fn)> updateCallback; }; CreditsRow(not_null peer, const Descriptor &descriptor); @@ -752,6 +753,14 @@ CreditsRow::CreditsRow(not_null peer, const Descriptor &descriptor) , _entry(descriptor.entry) , _creditIcon(descriptor.creditIcon) , _rowHeight(descriptor.rowHeight) { + const auto photo = _entry.photoId + ? peer->session().data().photo(_entry.photoId).get() + : nullptr; + if (photo) { + _paintUserpicCallback = Ui::GenerateCreditsPaintEntryCallback( + photo, + [this, update = descriptor.updateCallback] { update(this); }); + } init(); } @@ -772,9 +781,11 @@ void CreditsRow::init() { (!_entry.bareId ? QChar('+') : kMinus) + Lang::FormatCountDecimal(std::abs(int64(_entry.credits)))); } - _paintUserpicCallback = !PeerListRow::special() - ? PeerListRow::generatePaintUserpicCallback(false) - : Ui::GenerateCreditsPaintUserpicCallback(_entry); + if (!_paintUserpicCallback) { + _paintUserpicCallback = !PeerListRow::special() + ? PeerListRow::generatePaintUserpicCallback(false) + : Ui::GenerateCreditsPaintUserpicCallback(_entry); + } } const Data::CreditsHistoryEntry &CreditsRow::entry() const { @@ -912,6 +923,9 @@ void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { .entry = item, .creditIcon = _creditIcon, .rowHeight = computeListSt().item.height, + .updateCallback = [=](not_null row) { + delegate()->peerListUpdateRow(row); + }, }; using Type = Data::CreditsHistoryEntry::PeerType; if (item.bareId) { diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 16c15e44e..b855121a4 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -568,44 +568,18 @@ object_ptr HistoryEntryPhoto( not_null parent, not_null photo, int photoSize) { - struct State { - std::shared_ptr view; - Image *image = nullptr; - rpl::lifetime downloadLifetime; - }; - const auto state = parent->lifetime().make_state(); auto owned = object_ptr(parent); const auto widget = owned.data(); - state->view = photo->createMediaView(); - photo->load(Data::PhotoSize::Thumbnail, {}); - widget->resize(Size(photoSize)); - rpl::single(rpl::empty_value()) | rpl::then( - photo->owner().session().downloaderTaskFinished() - ) | rpl::start_with_next([=] { - using Size = Data::PhotoSize; - if (const auto large = state->view->image(Size::Large)) { - state->image = large; - } else if (const auto small = state->view->image(Size::Small)) { - state->image = small; - } else if (const auto t = state->view->image(Size::Thumbnail)) { - state->image = t; - } - widget->update(); - if (state->view->loaded()) { - state->downloadLifetime.destroy(); - } - }, state->downloadLifetime); + const auto draw = Ui::GenerateCreditsPaintEntryCallback( + photo, + [=] { widget->update(); }); widget->paintRequest( ) | rpl::start_with_next([=] { - auto p = QPainter(widget); - if (state->image) { - p.drawPixmap(0, 0, state->image->pix(widget->width(), { - .options = Images::Option::RoundCircle, - })); - } + auto p = Painter(widget); + draw(p, 0, 0, photoSize, photoSize); }, widget->lifetime()); return owned; diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 44e82195a..3c7a714f7 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -10,7 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "data/data_credits.h" +#include "data/data_file_origin.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" #include "lang/lang_keys.h" +#include "main/main_session.h" #include "ui/empty_userpic.h" #include "ui/painter.h" #include "styles/style_credits.h" @@ -67,6 +72,56 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback( }; } +Fn GenerateCreditsPaintEntryCallback( + not_null photo, + Fn update) { + struct State { + std::shared_ptr view; + Image *imagePtr = nullptr; + QImage image; + rpl::lifetime downloadLifetime; + bool entryImageLoaded = false; + }; + const auto state = std::make_shared(); + state->view = photo->createMediaView(); + photo->load(Data::PhotoSize::Thumbnail, {}); + + rpl::single(rpl::empty_value()) | rpl::then( + photo->owner().session().downloaderTaskFinished() + ) | rpl::start_with_next([=] { + using Size = Data::PhotoSize; + if (const auto large = state->view->image(Size::Large)) { + state->imagePtr = large; + } else if (const auto small = state->view->image(Size::Small)) { + state->imagePtr = small; + } else if (const auto t = state->view->image(Size::Thumbnail)) { + state->imagePtr = t; + } + update(); + if (state->view->loaded()) { + state->entryImageLoaded = true; + state->downloadLifetime.destroy(); + } + }, state->downloadLifetime); + + return [=](Painter &p, int x, int y, int outerWidth, int size) { + if (state->imagePtr + && (!state->entryImageLoaded || state->image.isNull())) { + const auto image = state->imagePtr->original(); + const auto minSize = std::min(image.width(), image.height()); + state->image = Images::Prepare( + image.copy( + (image.width() - minSize) / 2, + (image.height() - minSize) / 2, + minSize, + minSize), + size * style::DevicePixelRatio(), + { .options = Images::Option::RoundCircle }); + } + p.drawImage(x, y, state->image); + }; +} + TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) { return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) ? tr::lng_bot_username_description1_link diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index 44d848748..ddfbea07c 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +class PhotoData; + namespace Data { struct CreditsHistoryEntry; } // namespace Data @@ -16,6 +18,10 @@ namespace Ui { Fn GenerateCreditsPaintUserpicCallback( const Data::CreditsHistoryEntry &entry); +Fn GenerateCreditsPaintEntryCallback( + not_null photo, + Fn update); + [[nodiscard]] TextWithEntities GenerateEntryName( const Data::CreditsHistoryEntry &entry); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 3c0c026a3..29a2c10b0 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -358,8 +358,6 @@ PRIVATE ui/effects/fireworks_animation.h ui/effects/glare.cpp ui/effects/glare.h - ui/effects/credits_graphics.cpp - ui/effects/credits_graphics.h ui/effects/loading_element.cpp ui/effects/loading_element.h ui/effects/outline_segments.cpp From 57ecc2be1d3146271d9aad4f40416b9224b3c422 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 29 May 2024 03:50:52 +0300 Subject: [PATCH 160/225] Improved style of list of credits history entries for refunded entry. --- .../info_statistics_list_controllers.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 51dcc203b..2dd25c571 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -744,6 +744,7 @@ private: const int _rowHeight; PaintRoundImageCallback _paintUserpicCallback; + QString _name; Ui::Text::String _rightText; }; @@ -773,12 +774,21 @@ CreditsRow::CreditsRow(const Descriptor &descriptor) } void CreditsRow::init() { - PeerListRow::setCustomStatus(langDateTimeFull(_entry.date)); + _name = !PeerListRow::special() + ? PeerListRow::generateName() + : Ui::GenerateEntryName(_entry).text; + const auto joiner = QString(QChar(' ')) + QChar(8212) + QChar(' '); + PeerListRow::setCustomStatus( + langDateTimeFull(_entry.date) + + (_entry.refunded + ? (joiner + tr::lng_channel_earn_history_return(tr::now)) + : QString()) + + (_entry.title.isEmpty() ? QString() : (joiner + _name))); { constexpr auto kMinus = QChar(0x2212); _rightText.setText( st::semiboldTextStyle, - (!_entry.bareId ? QChar('+') : kMinus) + ((!_entry.bareId || _entry.refunded) ? QChar('+') : kMinus) + Lang::FormatCountDecimal(std::abs(int64(_entry.credits)))); } if (!_paintUserpicCallback) { @@ -793,9 +803,7 @@ const Data::CreditsHistoryEntry &CreditsRow::entry() const { } QString CreditsRow::generateName() { - return !PeerListRow::special() - ? PeerListRow::generateName() - : Ui::GenerateEntryName(_entry).text; + return _entry.title.isEmpty() ? _name : _entry.title; } PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { @@ -828,7 +836,7 @@ void CreditsRow::rightActionPaint( bool actionSelected) { const auto &font = _rightText.style()->font; y += _rowHeight / 2; - p.setPen(!_entry.bareId + p.setPen((!_entry.bareId || _entry.refunded) ? st::boxTextFgGood : st::menuIconAttentionColor); x += st::creditsHistoryRightSkip; From 02bd2bca64d004492aae8c4eaa2f258cc5b4ee0b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 29 May 2024 03:59:27 +0300 Subject: [PATCH 161/225] Removed display of credits button in settings when user has no credits. --- .../SourceFiles/settings/settings_main.cpp | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 9f2e73072..d08ce9a11 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -491,20 +491,30 @@ void SetupPremium( controller->setPremiumRef("settings"); showOther(PremiumId()); }); - AddPremiumStar( - AddButtonWithLabel( - container, - tr::lng_credits_summary_title(), + { + const auto wrap = container->add( + object_ptr>( + container, + object_ptr(container))); + wrap->toggleOn( controller->session().creditsValue( - ) | rpl::map([=](uint64 c) { - return c ? Lang::FormatCountToShort(c).string : QString{}; - }), - st::settingsButton), - true - )->addClickHandler([=] { - controller->setPremiumRef("settings"); - showOther(CreditsId()); - }); + ) | rpl::map(rpl::mappers::_1 > 0)); + wrap->finishAnimating(); + AddPremiumStar( + AddButtonWithLabel( + wrap->entity(), + tr::lng_credits_summary_title(), + controller->session().creditsValue( + ) | rpl::map([=](uint64 c) { + return c ? Lang::FormatCountToShort(c).string : QString{}; + }), + st::settingsButton), + true + )->addClickHandler([=] { + controller->setPremiumRef("settings"); + showOther(CreditsId()); + }); + } const auto button = AddButtonWithIcon( container, tr::lng_business_title(), From d219bccf2b74436b351fe5f4c672685fab43b25d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 29 May 2024 04:24:47 +0300 Subject: [PATCH 162/225] Fixed position of via bot header above reply in message view. --- .../history/view/history_view_message.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index b860c2c58..ddcd5e279 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1301,8 +1301,8 @@ void Message::draw(Painter &p, const PaintContext &context) const { paintFromName(p, trect, context); paintTopicButton(p, trect, context); paintForwardedInfo(p, trect, context); - paintReplyInfo(p, trect, context); paintViaBotIdInfo(p, trect, context); + paintReplyInfo(p, trect, context); } if (entry) { trect.setHeight(trect.height() - entry->height()); @@ -4147,17 +4147,17 @@ QRect Message::innerGeometry() const { + st::topicButtonPadding.bottom() + st::topicButtonSkip); } - // Skip displayForwardedFrom() until there are no animations for it. - if (const auto reply = Get()) { - // See paintReplyInfo(). - result.translate(0, reply->height()); - } if (!displayFromName() && !displayForwardedFrom()) { // See paintViaBotIdInfo(). if (data()->Has()) { result.translate(0, st::msgServiceNameFont->height); } } + // Skip displayForwardedFrom() until there are no animations for it. + if (const auto reply = Get()) { + // See paintReplyInfo(). + result.translate(0, reply->height()); + } } return result; } From 924d80ecba3285b75ba58da36887f55dd85918e8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 09:37:32 +0400 Subject: [PATCH 163/225] Use message text rendering for media captions. --- .../history/view/history_view_element.cpp | 26 ++- .../history/view/history_view_element.h | 3 + .../history/view/history_view_message.cpp | 33 ++-- .../history/view/history_view_message.h | 1 - .../view/media/history_view_document.cpp | 83 +++------ .../view/media/history_view_document.h | 12 +- .../media/history_view_extended_preview.cpp | 109 +----------- .../media/history_view_extended_preview.h | 29 +--- .../history/view/media/history_view_gif.cpp | 158 ++++------------- .../history/view/media/history_view_gif.h | 37 +--- .../history/view/media/history_view_invoice.h | 3 + .../history/view/media/history_view_media.cpp | 6 +- .../history/view/media/history_view_media.h | 13 +- .../view/media/history_view_media_grouped.cpp | 161 +++++------------- .../view/media/history_view_media_grouped.h | 15 +- .../history/view/media/history_view_photo.cpp | 128 ++------------ .../history/view/media/history_view_photo.h | 33 +--- .../view/media/history_view_web_page.h | 3 + 18 files changed, 225 insertions(+), 628 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 82688196a..3cdb44ed0 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -775,6 +775,10 @@ void Element::refreshMedia(Element *replacing) { } } +HistoryItem *Element::textItem() const { + return _textItem; +} + Ui::Text::IsolatedEmoji Element::isolatedEmoji() const { return _text.toIsolatedEmoji(); } @@ -952,11 +956,11 @@ auto Element::contextDependentServiceText() -> TextWithLinks { void Element::validateText() { const auto item = data(); - const auto &text = item->_text; const auto media = item->media(); const auto storyMention = media && media->storyMention(); if (media && media->storyExpired()) { _media = nullptr; + _textItem = item; if (!storyMention) { if (_text.isEmpty()) { setTextWithLinks(Ui::Text::Italic( @@ -965,6 +969,16 @@ void Element::validateText() { return; } } + + // Albums may show text of a different item than the parent one. + _textItem = _media ? _media->itemForText() : item.get(); + if (!_textItem) { + if (!_text.isEmpty()) { + setTextWithLinks({}); + } + return; + } + const auto &text = _textItem->_text; if (_text.isEmpty() == text.empty()) { } else if (_flags & Flag::ServiceMessage) { const auto contextDependentText = contextDependentServiceText(); @@ -972,11 +986,11 @@ void Element::validateText() { ? text : contextDependentText.text; const auto &customLinks = contextDependentText.text.empty() - ? item->customTextLinks() + ? _textItem->customTextLinks() : contextDependentText.links; setTextWithLinks(markedText, customLinks); } else { - setTextWithLinks(item->translatedTextWithLocalEntities()); + setTextWithLinks(_textItem->translatedTextWithLocalEntities()); } } @@ -1407,6 +1421,12 @@ bool Element::hasVisibleText() const { return false; } +int Element::textualMaxWidth() const { + return st::msgPadding.left() + + (hasVisibleText() ? text().maxWidth() : 0) + + st::msgPadding.right(); +} + auto Element::verticalRepaintRange() const -> VerticalRepaintRange { return { .top = 0, diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index ab458b1a5..fdac9a583 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -373,6 +373,7 @@ public: && _text.isOnlyCustomEmoji(); } + [[nodiscard]] HistoryItem *textItem() const; [[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const; [[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const; @@ -476,6 +477,7 @@ public: std::optional pressPoint) const; [[nodiscard]] virtual TimeId displayedEditDate() const; [[nodiscard]] virtual bool hasVisibleText() const; + [[nodiscard]] int textualMaxWidth() const; virtual void applyGroupAdminChanges( const base::flat_set &changes) { } @@ -633,6 +635,7 @@ private: mutable ClickHandlerPtr _fromLink; const QDateTime _dateTime; + HistoryItem *_textItem = nullptr; mutable Ui::Text::String _text; mutable int _textWidth = -1; mutable int _textHeight = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index ddcd5e279..133a19973 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -406,7 +406,6 @@ Message::Message( not_null data, Element *replacing) : Element(delegate, data, replacing, Flag(0)) -, _invertMedia(data->invertMedia() && !data->emptyText()) , _hideReply(delegate->elementHideReply(this)) , _bottomInfo( &data->history()->owner().reactions(), @@ -826,6 +825,15 @@ QSize Message::performCountOptimalSize() { validateInlineKeyboard(markup); updateViewButtonExistence(); refreshTopicButton(); + + const auto media = this->media(); + const auto textItem = this->textItem(); + const auto defaultInvert = media && media->aboveTextByDefault(); + const auto invertDefault = textItem + && textItem->invertMedia() + && !textItem->emptyText(); + _invertMedia = invertDefault ? !defaultInvert : defaultInvert; + updateMediaInBubbleState(); if (oldKey != reactionsKey()) { refreshReactions(); @@ -833,7 +841,6 @@ QSize Message::performCountOptimalSize() { refreshRightBadge(); refreshInfoSkipBlock(); - const auto media = this->media(); const auto botTop = item->isFakeAboutView() ? Get() : nullptr; @@ -877,9 +884,10 @@ QSize Message::performCountOptimalSize() { // Entry page is always a bubble bottom. const auto withVisibleText = hasVisibleText(); + const auto textualWidth = textualMaxWidth(); auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); - maxWidth = plainMaxWidth(); + maxWidth = textualWidth; if (context() == Context::Replies && item->isDiscussionPost()) { maxWidth = std::max(maxWidth, st::msgMaxWidth); } @@ -930,7 +938,7 @@ QSize Message::performCountOptimalSize() { if (botTop) { minHeight += botTop->height; } - if (maxWidth < plainMaxWidth()) { + if (maxWidth < textualWidth) { minHeight -= text().minHeight(); minHeight += text().countHeight(innerWidth); } @@ -3432,12 +3440,6 @@ void Message::refreshDataIdHook() { } } -int Message::plainMaxWidth() const { - return st::msgPadding.left() - + (hasVisibleText() ? text().maxWidth() : 0) - + st::msgPadding.right(); -} - int Message::monospaceMaxWidth() const { return st::msgPadding.left() + (hasVisibleText() ? text().countMaxMonospaceWidth() : 0) @@ -4189,7 +4191,7 @@ QRect Message::countGeometry() const { accumulate_min(contentWidth, maxWidth()); accumulate_min(contentWidth, int(_bubbleWidthLimit)); if (mediaWidth < contentWidth) { - const auto textualWidth = plainMaxWidth(); + const auto textualWidth = textualMaxWidth(); if (mediaWidth < textualWidth && (!media || !media->enforceBubbleWidth())) { accumulate_min(contentWidth, textualWidth); @@ -4300,7 +4302,7 @@ int Message::resizeContentGetHeight(int newWidth) { if (mediaDisplayed) { media->resizeGetHeight(contentWidth); if (media->width() < contentWidth) { - const auto textualWidth = plainMaxWidth(); + const auto textualWidth = textualMaxWidth(); if (media->width() < textualWidth && !media->enforceBubbleWidth()) { accumulate_min(contentWidth, textualWidth); @@ -4474,8 +4476,11 @@ bool Message::invertMedia() const { } bool Message::hasVisibleText() const { - if (data()->emptyText()) { - if (const auto media = data()->media()) { + const auto textItem = this->textItem(); + if (!textItem) { + return false; + } else if (textItem->emptyText()) { + if (const auto media = textItem->media()) { return media->storyExpired(); } return false; diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 240e1a47a..5c61fdaaa 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -287,7 +287,6 @@ private: void ensureRightAction() const; void refreshTopicButton(); void refreshInfoSkipBlock(); - [[nodiscard]] int plainMaxWidth() const; [[nodiscard]] int monospaceMaxWidth() const; void validateInlineKeyboard(HistoryMessageReplyMarkup *markup); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index bb64302c7..e415264b3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -300,9 +300,7 @@ Document::Document( _transcribedRound = entry.shown; } - auto caption = createCaption(); - - createComponents(!caption.isEmpty()); + createComponents(); if (const auto named = Get()) { fillNamedFromData(named); _tooltipFilename.setTooltipText(named->name); @@ -346,10 +344,6 @@ Document::Document( } setStatusSize(Ui::FileStatusSizeReady); - - if (const auto captioned = Get()) { - captioned->caption = std::move(caption); - } } Document::~Document() { @@ -374,7 +368,7 @@ bool Document::dataLoaded() const { return _dataMedia->loaded(); } -void Document::createComponents(bool caption) { +void Document::createComponents() { uint64 mask = 0; if (_data->isVoiceMessage() || _transcribedRound) { mask |= HistoryDocumentVoice::Bit(); @@ -385,9 +379,6 @@ void Document::createComponents(bool caption) { mask |= HistoryDocumentThumbed::Bit(); } } - if (caption) { - mask |= HistoryDocumentCaptioned::Bit(); - } UpdateComponents(mask); if (const auto thumbed = Get()) { thumbed->linksavel = std::make_shared( @@ -421,18 +412,6 @@ void Document::fillNamedFromData(not_null named) { } QSize Document::countOptimalSize() { - auto captioned = Get(); - if (_parent->media() != this && !_realParent->groupId()) { - if (captioned) { - RemoveComponents(HistoryDocumentCaptioned::Bit()); - captioned = nullptr; - } - } else if (captioned && captioned->caption.hasSkipBlock()) { - captioned->caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } - auto hasTranscribe = false; const auto voice = Get(); if (voice) { @@ -481,7 +460,7 @@ QSize Document::countOptimalSize() { st::messageTextStyle, text); hasTranscribe = true; - if (const auto skipBlockWidth = captioned + if (const auto skipBlockWidth = _parent->hasVisibleText() ? 0 : _parent->skipBlockWidth()) { voice->transcribeText.updateSkipBlock( @@ -528,7 +507,7 @@ QSize Document::countOptimalSize() { } auto minHeight = st.padding.top() + st.thumbSize + st.padding.bottom(); - if (!captioned && !hasTranscribe && _parent->bottomInfoIsWide()) { + if (isBubbleBottom() && !hasTranscribe && _parent->bottomInfoIsWide()) { minHeight += st::msgDateFont->height - st::msgDateDelta.y(); } if (!isBubbleTop()) { @@ -540,17 +519,6 @@ QSize Document::countOptimalSize() { - st::msgPadding.left() - st::msgPadding.right(); minHeight += voice->transcribeText.countHeight(captionw); - if (captioned) { - minHeight += st::mediaCaptionSkip; - } else if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } - } - if (captioned) { - auto captionw = maxWidth - - st::msgPadding.left() - - st::msgPadding.right(); - minHeight += captioned->caption.countHeight(captionw); if (isBubbleBottom()) { minHeight += st::msgPadding.bottom(); } @@ -1521,10 +1489,32 @@ QMargins Document::bubbleMargins() const { return QMargins(padding.left(), padding.top(), padding.right(), padding.bottom()); } -QSize Document::sizeForGroupingOptimal(int maxWidth) const { +void Document::refreshCaption(bool last) { + auto caption = createCaption(); + if (!caption.isEmpty()) { + AddComponents(HistoryDocumentCaptioned::Bit()); + auto captioned = Get(); + captioned->caption = std::move(caption); + const auto skip = last ? _parent->skipBlockWidth() : 0; + if (skip) { + captioned->caption.updateSkipBlock( + _parent->skipBlockWidth(), + _parent->skipBlockHeight()); + } else { + captioned->caption.removeSkipBlock(); + } + } else { + RemoveComponents(HistoryDocumentCaptioned::Bit()); + } +} + +QSize Document::sizeForGroupingOptimal(int maxWidth, bool last) const { const auto thumbed = Get(); const auto &st = (thumbed ? st::msgFileThumbLayoutGrouped : st::msgFileLayoutGrouped); auto height = st.padding.top() + st.thumbSize + st.padding.bottom(); + + const_cast(this)->refreshCaption(last); + if (const auto captioned = Get()) { auto captionw = maxWidth - st::msgPadding.left() @@ -1647,33 +1637,16 @@ void Document::refreshParentId(not_null realParent) { } void Document::parentTextUpdated() { - auto caption = (_parent->media() == this || _realParent->groupId()) - ? createCaption() - : Ui::Text::String(); - if (!caption.isEmpty()) { - AddComponents(HistoryDocumentCaptioned::Bit()); - auto captioned = Get(); - captioned->caption = std::move(caption); - } else { - RemoveComponents(HistoryDocumentCaptioned::Bit()); - } history()->owner().requestViewResize(_parent); } -TextWithEntities Document::getCaption() const { - if (const auto captioned = Get()) { - return captioned->caption.toTextWithEntities(); - } - return TextWithEntities(); -} - void Document::hideSpoilers() { if (const auto captioned = Get()) { captioned->caption.setSpoilerRevealed(false, anim::type::instant); } } -Ui::Text::String Document::createCaption() { +Ui::Text::String Document::createCaption() const { return File::createCaption(_realParent); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index 3d29651c4..2f8ec9ac2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -35,6 +35,10 @@ public: not_null document); ~Document(); + bool hideMessageText() const override { + return false; + } + void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; void updatePressed(QPoint point) override; @@ -56,7 +60,6 @@ public: return _data; } - TextWithEntities getCaption() const override; void hideSpoilers() override; bool needsBubble() const override { return true; @@ -66,7 +69,7 @@ public: } QMargins bubbleMargins() const override; - QSize sizeForGroupingOptimal(int maxWidth) const override; + QSize sizeForGroupingOptimal(int maxWidth, bool last) const override; QSize sizeForGrouping(int width) const override; void drawGrouped( Painter &p, @@ -117,12 +120,13 @@ private: LayoutMode mode) const; void ensureDataMediaCreated() const; - [[nodiscard]] Ui::Text::String createCaption(); + [[nodiscard]] Ui::Text::String createCaption() const; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; - void createComponents(bool caption); + void refreshCaption(bool last); + void createComponents(); void fillNamedFromData(not_null named); [[nodiscard]] Ui::BubbleRounding thumbRounding( diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index 9db9acc07..7d5b9f00a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -57,10 +57,8 @@ ExtendedPreview::ExtendedPreview( not_null parent, not_null invoice) : Media(parent) -, _invoice(invoice) -, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { +, _invoice(invoice) { const auto item = parent->data(); - _caption = createCaption(item); _spoiler.link = MakeInvoiceLink(item); resolveButtonText(); } @@ -113,17 +111,9 @@ void ExtendedPreview::unloadHeavyPart() { = _spoiler.cornerCache = _buttonBackground = QImage(); _spoiler.animation = nullptr; - _caption.unloadPersistentAnimation(); } QSize ExtendedPreview::countOptimalSize() { - if (_parent->media() != this) { - _caption = Ui::Text::String(); - } else if (_caption.hasSkipBlock()) { - _caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } const auto &preview = _invoice->extendedPreview; const auto dimensions = preview.dimensions; const auto minWidth = std::min( @@ -141,15 +131,6 @@ QSize ExtendedPreview::countOptimalSize() { if (preview.videoDuration < 0) { accumulate_max(maxWidth, scaled.height()); } - if (_parent->hasBubble() && !_caption.isEmpty()) { - maxWidth = qMax(maxWidth, st::msgPadding.left() - + _caption.maxWidth() - + st::msgPadding.right()); - minHeight += st::mediaCaptionSkip + _caption.minHeight(); - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } - } return { maxWidth, minHeight }; } @@ -157,7 +138,7 @@ QSize ExtendedPreview::countCurrentSize(int newWidth) { const auto &preview = _invoice->extendedPreview; const auto dimensions = preview.dimensions; const auto thumbMaxWidth = std::min(newWidth, st::maxMediaSize); - const auto minWidth = std::min( + const auto minWidth = std::min( std::max({ _parent->minWidthForMedia(), (_parent->hasBubble() @@ -176,20 +157,11 @@ QSize ExtendedPreview::countCurrentSize(int newWidth) { maxWidth()); newWidth = qMax(scaled.width(), minWidth); auto newHeight = qMax(scaled.height(), st::minPhotoSize); - if (_parent->hasBubble() && !_caption.isEmpty()) { + if (_parent->hasBubble()) { const auto maxWithCaption = qMin( st::msgMaxWidth, - (st::msgPadding.left() - + _caption.maxWidth() - + st::msgPadding.right())); + _parent->textualMaxWidth()); newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); - const auto captionw = newWidth - - st::msgPadding.left() - - st::msgPadding.right(); - newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - newHeight += st::msgPadding.bottom(); - } } return { newWidth, newHeight }; } @@ -210,16 +182,8 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { const auto inWebPage = (_parent->media() != this); const auto rounding = inWebPage ? std::optional() - : adjustedBubbleRoundingWithCaption(_caption); - if (bubble) { - if (!_caption.isEmpty()) { - painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); - } - } else { + : adjustedBubbleRounding(); + if (!bubble) { Assert(rounding.has_value()); fillImageShadow(p, rthumb, *rounding, context); } @@ -232,27 +196,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { } // date - if (!_caption.isEmpty()) { - p.setPen(stm->historyTextFg); - _parent->prepareCustomEmojiPaint(p, context, _caption); - auto highlightRequest = context.computeHighlightCache(); - _caption.draw(p, { - .position = QPoint( - st::msgPadding.left(), - painty + painth + st::mediaCaptionSkip), - .availableWidth = captionw, - .palette = &stm->textPalette, - .pre = stm->preCache.get(), - .blockquote = context.quoteCache(parent()->contentColorIndex()), - .colors = context.st->highlightColors(), - .spoiler = Ui::Text::DefaultSpoilerCache(), - .now = context.now, - .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), - .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .selection = context.selection, - .highlight = highlightRequest ? &*highlightRequest : nullptr, - }); - } else if (!inWebPage) { + if (!inWebPage) { auto fullRight = paintx + paintw; auto fullBottom = painty + painth; if (needInfoDisplay()) { @@ -349,28 +293,10 @@ TextState ExtendedPreview::textState(QPoint point, StateRequest request) const { } auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); - - if (bubble && !_caption.isEmpty()) { - const auto captionw = paintw - - st::msgPadding.left() - - st::msgPadding.right(); - painth -= _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { - result = TextState(_parent, _caption.getState( - point - QPoint(st::msgPadding.left(), painth), - captionw, - request.forText())); - return result; - } - painth -= st::mediaCaptionSkip; - } if (QRect(paintx, painty, paintw, painth).contains(point)) { result.link = _spoiler.link; } - if (_caption.isEmpty() && _parent->media() == this) { + if (!bubble && _parent->media() == this) { auto fullRight = paintx + paintw; auto fullBottom = painty + painth; const auto bottomInfoResult = _parent->bottomInfoTextState( @@ -412,23 +338,13 @@ bool ExtendedPreview::needInfoDisplay() const { || _parent->isLastAndSelfMessage(); } -TextForMimeData ExtendedPreview::selectedText(TextSelection selection) const { - return _caption.toTextForMimeData(selection); -} - -void ExtendedPreview::hideSpoilers() { - _caption.setSpoilerRevealed(false, anim::type::instant); -} - bool ExtendedPreview::needsBubble() const { - if (!_caption.isEmpty()) { - return true; - } const auto item = _parent->data(); return !item->isService() && (item->repliesAreComments() || item->externalReply() || item->viaBot() + || !item->emptyText() || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() @@ -441,11 +357,4 @@ QPoint ExtendedPreview::resolveCustomInfoRightBottom() const { return QPoint(width() - skipx, height() - skipy); } -void ExtendedPreview::parentTextUpdated() { - _caption = (_parent->media() == this) - ? createCaption(_parent->data()) - : Ui::Text::String(); - history()->owner().requestViewResize(_parent); -} - } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h index 8bec02154..0e645456e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h @@ -31,6 +31,10 @@ public: not_null invoice); ~ExtendedPreview(); + bool hideMessageText() const override { + return false; + } + void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; @@ -39,35 +43,15 @@ public: [[nodiscard]] bool dragItemByHandler( const ClickHandlerPtr &p) const override; - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override { - return _caption.adjustSelection(selection, type); - } - uint16 fullSelectionLength() const override { - return _caption.length(); - } - bool hasTextForCopy() const override { - return !_caption.isEmpty(); - } - - TextForMimeData selectedText(TextSelection selection) const override; - - TextWithEntities getCaption() const override { - return _caption.toTextWithEntities(); - } - void hideSpoilers() override; bool needsBubble() const override; bool customInfoLayout() const override { - return _caption.isEmpty(); + return true; } QPoint resolveCustomInfoRightBottom() const override; bool skipBubbleTail() const override { - return isRoundedInBubbleBottom() && _caption.isEmpty(); + return isRoundedInBubbleBottom(); } - void parentTextUpdated() override; - bool hasHeavyPart() const override; void unloadHeavyPart() override; @@ -90,7 +74,6 @@ private: const PaintContext &context) const; const not_null _invoice; - Ui::Text::String _caption; mutable MediaSpoiler _spoiler; mutable QImage _inlineThumbnail; mutable QImage _buttonBackground; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 7dbee0416..000cacbfa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -135,8 +135,6 @@ Gif::Gif( , _storyId(realParent->media() ? realParent->media()->storyId() : FullStoryId()) -, _caption( - st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _spoiler((spoiler || IsHiddenRoundMessage(_parent)) ? std::make_unique() : nullptr) @@ -184,7 +182,6 @@ Gif::Gif( createSpoilerLink(_spoiler.get()); } - refreshCaption(); if ((_dataMedia = _data->activeMediaView())) { dataMediaCreated(); } else { @@ -240,13 +237,6 @@ QSize Gif::countThumbSize(int &inOutWidthMax) const { } QSize Gif::countOptimalSize() { - if (_parent->media() != this) { - _caption = Ui::Text::String(); - } else if (_caption.hasSkipBlock()) { - _caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } if (_data->isVideoMessage() && _transcribe) { const auto &entry = _data->session().api().transcribes().entry( _realParent); @@ -271,21 +261,16 @@ QSize Gif::countOptimalSize() { accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); } if (_parent->hasBubble()) { - if (!_caption.isEmpty()) { - maxWidth = qMax(maxWidth, st::msgPadding.left() - + _caption.maxWidth() - + st::msgPadding.right()); - minHeight = adjustHeightForLessCrop( - scaled, - { maxWidth, minHeight }); - if (const auto botTop = _parent->Get()) { - accumulate_max(maxWidth, botTop->maxWidth); - minHeight += botTop->height; - } - minHeight += st::mediaCaptionSkip + _caption.minHeight(); - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } + maxWidth = qMax(maxWidth, _parent->textualMaxWidth()); + minHeight = adjustHeightForLessCrop( + scaled, + { maxWidth, minHeight }); + if (const auto botTop = _parent->Get()) { + accumulate_max(maxWidth, botTop->maxWidth); + minHeight += botTop->height; + } + if (isBubbleBottom()) { + minHeight += st::msgPadding.bottom(); } } else if (isUnwrapped()) { const auto item = _parent->data(); @@ -318,29 +303,18 @@ QSize Gif::countCurrentSize(int newWidth) { } if (_parent->hasBubble()) { accumulate_max(newWidth, _parent->minWidthForMedia()); - if (!_caption.isEmpty()) { - auto captionMaxWidth = st::msgPadding.left() - + _caption.maxWidth() - + st::msgPadding.right(); - const auto botTop = _parent->Get(); - if (botTop) { - accumulate_max(captionMaxWidth, botTop->maxWidth); - } - const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth); - newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); - newHeight = adjustHeightForLessCrop( - scaled, - { newWidth, newHeight }); - const auto captionw = newWidth - - st::msgPadding.left() - - st::msgPadding.right(); - if (botTop) { - newHeight += botTop->height; - } - newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - newHeight += st::msgPadding.bottom(); - } + auto captionMaxWidth = _parent->textualMaxWidth(); + const auto botTop = _parent->Get(); + if (botTop) { + accumulate_max(captionMaxWidth, botTop->maxWidth); + } + const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth); + newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); + newHeight = adjustHeightForLessCrop( + scaled, + { newWidth, newHeight }); + if (botTop) { + newHeight += botTop->height; } } else if (isUnwrapped()) { accumulate_max(newWidth, _parent->reactionsOptimalWidth()); @@ -433,7 +407,6 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto activeRoundPlaying = activeRoundStreamed(); auto paintx = 0, painty = 0, paintw = width(), painth = height(); - auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); const bool bubble = _parent->hasBubble(); const auto rightLayout = _parent->hasRightLayout(); const auto inWebPage = (_parent->media() != this); @@ -442,16 +415,10 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto rounding = inWebPage ? std::optional() - : adjustedBubbleRoundingWithCaption(_caption); + : adjustedBubbleRounding(); if (bubble) { - if (!_caption.isEmpty()) { - if (botTop) { - painth -= botTop->height; - } - painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } + if (botTop) { + painth -= botTop->height; } } @@ -792,11 +759,13 @@ void Gif::draw(Painter &p, const PaintContext &context) const { } } } - if (!unwrapped && !_caption.isEmpty()) { + if (!unwrapped && bubble) { p.setPen(stm->historyTextFg); - _parent->prepareCustomEmojiPaint(p, context, _caption); auto top = painty + painth + st::mediaCaptionSkip; if (botTop) { + auto captionw = paintw + - st::msgPadding.left() + - st::msgPadding.right(); botTop->text.drawLeftElided( p, st::msgPadding.left(), @@ -805,21 +774,6 @@ void Gif::draw(Painter &p, const PaintContext &context) const { _parent->width()); top += botTop->height; } - auto highlightRequest = context.computeHighlightCache(); - _caption.draw(p, { - .position = QPoint(st::msgPadding.left(), top), - .availableWidth = captionw, - .palette = &stm->textPalette, - .pre = stm->preCache.get(), - .blockquote = context.quoteCache(parent()->contentColorIndex()), - .colors = context.st->highlightColors(), - .spoiler = Ui::Text::DefaultSpoilerCache(), - .now = context.now, - .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), - .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .selection = context.selection, - .highlight = highlightRequest ? &*highlightRequest : nullptr, - }); } else if (!inWebPage && !skipDrawingSurrounding) { auto fullRight = paintx + usex + usew; auto fullBottom = painty + painth; @@ -1081,23 +1035,10 @@ TextState Gif::textState(QPoint point, StateRequest request) const { auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); - if (bubble && !_caption.isEmpty()) { - auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); - painth -= _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { - result = TextState(_parent, _caption.getState( - point - QPoint(st::msgPadding.left(), painth), - captionw, - request.forText())); - return result; - } + if (bubble) { if (const auto botTop = _parent->Get()) { painth -= botTop->height; } - painth -= st::mediaCaptionSkip; } const auto rightLayout = _parent->hasRightLayout(); const auto inWebPage = (_parent->media() != this); @@ -1212,7 +1153,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const { ? _cancell : _savel; } - if (unwrapped || _caption.isEmpty()) { + if (unwrapped || !bubble) { auto fullRight = usex + paintx + usew; auto fullBottom = painty + painth; auto maxRight = _parent->width() - st::msgMargin.left(); @@ -1289,23 +1230,11 @@ void Gif::clickHandlerPressedChanged( } } -TextForMimeData Gif::selectedText(TextSelection selection) const { - return _caption.toTextForMimeData(selection); -} - -SelectedQuote Gif::selectedQuote(TextSelection selection) const { - return Element::FindSelectedQuote(_caption, selection, _realParent); -} - -TextSelection Gif::selectionFromQuote(const SelectedQuote "e) const { - return Element::FindSelectionFromQuote(_caption, quote); -} - bool Gif::fullFeaturedGrouped(RectParts sides) const { return (sides & RectPart::Left) && (sides & RectPart::Right); } -QSize Gif::sizeForGroupingOptimal(int maxWidth) const { +QSize Gif::sizeForGroupingOptimal(int maxWidth, bool last) const { return sizeForAspectRatio(); } @@ -1588,7 +1517,6 @@ bool Gif::uploading() const { } void Gif::hideSpoilers() { - _caption.setSpoilerRevealed(false, anim::type::instant); if (_spoiler) { _spoiler->revealed = false; } @@ -1599,13 +1527,12 @@ bool Gif::needsBubble() const { return true; } else if (_data->isVideoMessage()) { return false; - } else if (!_caption.isEmpty()) { - return true; } const auto item = _parent->data(); return item->repliesAreComments() || item->externalReply() || item->viaBot() + || !item->emptyText() || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() @@ -1810,13 +1737,6 @@ bool Gif::isReadyForOpen() const { return true; } -void Gif::parentTextUpdated() { - if (_parent->media() == this) { - refreshCaption(); - history()->owner().requestViewResize(_parent); - } -} - bool Gif::hasHeavyPart() const { return (_spoiler && _spoiler->animation) || _streamed || _dataMedia; } @@ -1830,19 +1750,11 @@ void Gif::unloadHeavyPart() { } _thumbCache = QImage(); _videoThumbnailFrame = nullptr; - _caption.unloadPersistentAnimation(); togglePollingStory(false); } -void Gif::refreshParentId(not_null realParent) { - File::refreshParentId(realParent); - if (_parent->media() == this) { - refreshCaption(); - } -} - -void Gif::refreshCaption() { - _caption = createCaption(_parent->data()); +bool Gif::enforceBubbleWidth() const { + return true; } int Gif::additionalWidth( diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index dad49b351..840b12587 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -54,6 +54,10 @@ public: bool spoiler); ~Gif(); + bool hideMessageText() const override { + return false; + } + void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; @@ -61,23 +65,6 @@ public: const ClickHandlerPtr &p, bool pressed) override; - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override { - return _caption.adjustSelection(selection, type); - } - uint16 fullSelectionLength() const override { - return _caption.length(); - } - bool hasTextForCopy() const override { - return !_caption.isEmpty(); - } - - TextForMimeData selectedText(TextSelection selection) const override; - SelectedQuote selectedQuote(TextSelection selection) const override; - TextSelection selectionFromQuote( - const SelectedQuote "e) const override; - bool uploading() const override; DocumentData *getDocument() const override { @@ -85,7 +72,7 @@ public: } bool fullFeaturedGrouped(RectParts sides) const; - QSize sizeForGroupingOptimal(int maxWidth) const override; + QSize sizeForGroupingOptimal(int maxWidth, bool last) const override; QSize sizeForGrouping(int width) const override; void drawGrouped( Painter &p, @@ -105,14 +92,11 @@ public: void stopAnimation() override; void checkAnimation() override; - TextWithEntities getCaption() const override { - return _caption.toTextWithEntities(); - } void hideSpoilers() override; bool needsBubble() const override; bool unwrapped() const override; bool customInfoLayout() const override { - return _caption.isEmpty(); + return true; } QRect contentRectForReactions() const override; std::optional reactionButtonCenterOverride() const override; @@ -120,16 +104,13 @@ public: QString additionalInfoString() const override; bool skipBubbleTail() const override { - return isRoundedInBubbleBottom() && _caption.isEmpty(); + return isRoundedInBubbleBottom(); } bool isReadyForOpen() const override; - void parentTextUpdated() override; - bool hasHeavyPart() const override; void unloadHeavyPart() override; - - void refreshParentId(not_null realParent) override; + bool enforceBubbleWidth() const override; [[nodiscard]] static bool CanPlayInline(not_null document); @@ -148,7 +129,6 @@ private: void ensureDataMediaCreated() const; void dataMediaCreated() const; - void refreshCaption(); [[nodiscard]] bool autoplayEnabled() const; @@ -223,7 +203,6 @@ private: const not_null _data; const FullStoryId _storyId; - Ui::Text::String _caption; std::unique_ptr _streamed; const std::unique_ptr _spoiler; mutable std::unique_ptr _transcribe; diff --git a/Telegram/SourceFiles/history/view/media/history_view_invoice.h b/Telegram/SourceFiles/history/view/media/history_view_invoice.h index a95c7d5dc..b9f5203ab 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_invoice.h +++ b/Telegram/SourceFiles/history/view/media/history_view_invoice.h @@ -30,6 +30,9 @@ public: return _title.toString(); } + bool aboveTextByDefault() const override { + return false; + } bool hideMessageText() const override { return false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 56720e83f..65c209153 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -391,10 +391,8 @@ Ui::BubbleRounding Media::adjustedBubbleRounding(RectParts square) const { return result; } -Ui::BubbleRounding Media::adjustedBubbleRoundingWithCaption( - const Ui::Text::String &caption) const { - return adjustedBubbleRounding( - caption.isEmpty() ? RectParts() : RectPart::FullBottom); +HistoryItem *Media::itemForText() const { + return _parent->data(); } bool Media::isRoundedInBubbleBottom() const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index f9ad5c987..4926ca7c8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -104,6 +104,10 @@ public: [[nodiscard]] virtual bool hasTextForCopy() const { return false; } + [[nodiscard]] virtual bool aboveTextByDefault() const { + return true; + } + [[nodiscard]] virtual HistoryItem *itemForText() const; [[nodiscard]] virtual bool hideMessageText() const { return true; } @@ -194,7 +198,9 @@ public: virtual void checkAnimation() { } - [[nodiscard]] virtual QSize sizeForGroupingOptimal(int maxWidth) const { + [[nodiscard]] virtual QSize sizeForGroupingOptimal( + int maxWidth, + bool last) const { Unexpected("Grouping method call."); } [[nodiscard]] virtual QSize sizeForGrouping(int width) const { @@ -221,9 +227,6 @@ public: return false; } - [[nodiscard]] virtual TextWithEntities getCaption() const { - return TextWithEntities(); - } virtual void hideSpoilers() { } [[nodiscard]] virtual bool needsBubble() const = 0; @@ -273,8 +276,6 @@ public: } [[nodiscard]] Ui::BubbleRounding adjustedBubbleRounding( RectParts square = {}) const; - [[nodiscard]] Ui::BubbleRounding adjustedBubbleRoundingWithCaption( - const Ui::Text::String &caption) const; [[nodiscard]] bool isBubbleTop() const { return (_inBubbleState == MediaInBubbleState::Top) || (_inBubbleState == MediaInBubbleState::None); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 038192cf1..7d11579e9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -65,8 +65,7 @@ GroupedMedia::Part::Part( GroupedMedia::GroupedMedia( not_null parent, const std::vector> &medias) -: Media(parent) -, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { +: Media(parent) { const auto truncated = ranges::views::all( medias ) | ranges::views::transform([](const std::unique_ptr &v) { @@ -80,8 +79,7 @@ GroupedMedia::GroupedMedia( GroupedMedia::GroupedMedia( not_null parent, const std::vector> &items) -: Media(parent) -, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { +: Media(parent) { const auto medias = ranges::views::all( items ) | ranges::views::transform([](not_null item) { @@ -97,6 +95,31 @@ GroupedMedia::~GroupedMedia() { base::take(_parts); } +HistoryItem *GroupedMedia::itemForText() const { + if (_mode == Mode::Column) { + return Media::itemForText(); + } else if (!_captionItem) { + _captionItem = [&]() -> HistoryItem* { + auto result = (HistoryItem*)nullptr; + for (const auto &part : _parts) { + if (!part.item->emptyText()) { + if (result) { + return nullptr; + } else { + result = part.item; + } + } + } + return result; + }(); + } + return *_captionItem; +} + +bool GroupedMedia::hideMessageText() const { + return (_mode == Mode::Column); +} + GroupedMedia::Mode GroupedMedia::DetectMode(not_null media) { const auto document = media->document(); return (document && !document->isVideoFile()) @@ -105,12 +128,6 @@ GroupedMedia::Mode GroupedMedia::DetectMode(not_null media) { } QSize GroupedMedia::countOptimalSize() { - if (_caption.hasSkipBlock()) { - _caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } - std::vector sizes; const auto partsCount = _parts.size(); sizes.reserve(partsCount); @@ -123,8 +140,11 @@ QSize GroupedMedia::countOptimalSize() { accumulate_max(maxWidth, media->maxWidth()); } } + auto index = 0; for (const auto &part : _parts) { - sizes.push_back(part.content->sizeForGroupingOptimal(maxWidth)); + const auto last = (++index == _parts.size()); + sizes.push_back( + part.content->sizeForGroupingOptimal(maxWidth, last)); } const auto layout = (_mode == Mode::Grid) @@ -145,13 +165,7 @@ QSize GroupedMedia::countOptimalSize() { _parts[i].sides = item.sides; } - if (!_caption.isEmpty()) { - auto captionw = maxWidth - st::msgPadding.left() - st::msgPadding.right(); - minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } - } else if (_mode == Mode::Column && _parts.back().item->emptyText()) { + if (_mode == Mode::Column && _parts.back().item->emptyText()) { const auto item = _parent->data(); const auto msgsigned = item->Get(); const auto views = item->Get(); @@ -215,13 +229,7 @@ QSize GroupedMedia::countCurrentSize(int newWidth) { accumulate_max(newHeight, top + height); } } - if (!_caption.isEmpty()) { - const auto captionw = newWidth - st::msgPadding.left() - st::msgPadding.right(); - newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - newHeight += st::msgPadding.bottom(); - } - } else if (_mode == Mode::Column && _parts.back().item->emptyText()) { + if (_mode == Mode::Column && _parts.back().item->emptyText()) { const auto item = _parent->data(); const auto msgsigned = item->Get(); const auto views = item->Get(); @@ -341,7 +349,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { constexpr auto kSmall = Ui::BubbleCornerRounding::Small; const auto rounding = inWebPage ? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall } - : adjustedBubbleRoundingWithCaption(_caption); + : adjustedBubbleRounding(); auto highlight = context.highlight.range; const auto subpartHighlight = IsSubGroupSelection(highlight); for (auto i = 0, count = int(_parts.size()); i != count; ++i) { @@ -388,33 +396,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { } // date - if (!_caption.isEmpty()) { - const auto captionw = width() - st::msgPadding.left() - st::msgPadding.right(); - const auto captiony = height() - - groupPadding.bottom() - - (isBubbleBottom() ? st::msgPadding.bottom() : 0) - - _caption.countHeight(captionw); - const auto stm = context.messageStyle(); - p.setPen(stm->historyTextFg); - _parent->prepareCustomEmojiPaint(p, context, _caption); - auto highlightRequest = context.computeHighlightCache(); - _caption.draw(p, { - .position = QPoint( - st::msgPadding.left(), - captiony), - .availableWidth = captionw, - .palette = &stm->textPalette, - .pre = stm->preCache.get(), - .blockquote = context.quoteCache(parent()->contentColorIndex()), - .colors = context.st->highlightColors(), - .spoiler = Ui::Text::DefaultSpoilerCache(), - .now = context.now, - .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), - .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .selection = context.selection, - .highlight = highlightRequest ? &*highlightRequest : nullptr, - }); - } else if (_parent->media() == this) { + if (_parent->media() == this) { auto fullRight = width(); auto fullBottom = height(); if (needInfoDisplay()) { @@ -473,23 +455,7 @@ PointState GroupedMedia::pointState(QPoint point) const { TextState GroupedMedia::textState(QPoint point, StateRequest request) const { const auto groupPadding = groupedPadding(); auto result = getPartState(point - QPoint(0, groupPadding.top()), request); - if (!result.link && !_caption.isEmpty()) { - const auto captionw = width() - st::msgPadding.left() - st::msgPadding.right(); - const auto captiony = height() - - groupPadding.bottom() - - (isBubbleBottom() ? st::msgPadding.bottom() : 0) - - _caption.countHeight(captionw); - if (QRect(st::msgPadding.left(), captiony, captionw, height() - captiony).contains(point)) { - return TextState( - _captionItem - ? _captionItem - : _parent->data().get(), - _caption.getState( - point - QPoint(st::msgPadding.left(), captiony), - captionw, - request.forText())); - } - } else if (_parent->media() == this) { + if (_parent->media() == this) { auto fullRight = width(); auto fullBottom = height(); const auto bottomInfoResult = _parent->bottomInfoTextState( @@ -539,7 +505,7 @@ TextSelection GroupedMedia::adjustSelection( TextSelection selection, TextSelectType type) const { if (_mode != Mode::Column) { - return _caption.adjustSelection(selection, type); + return {}; } auto checked = 0; for (const auto &part : _parts) { @@ -563,7 +529,7 @@ TextSelection GroupedMedia::adjustSelection( uint16 GroupedMedia::fullSelectionLength() const { if (_mode != Mode::Column) { - return _caption.length(); + return {}; } auto result = 0; for (const auto &part : _parts) { @@ -574,7 +540,7 @@ uint16 GroupedMedia::fullSelectionLength() const { bool GroupedMedia::hasTextForCopy() const { if (_mode != Mode::Column) { - return !_caption.isEmpty(); + return {}; } for (const auto &part : _parts) { if (part.content->hasTextForCopy()) { @@ -587,7 +553,7 @@ bool GroupedMedia::hasTextForCopy() const { TextForMimeData GroupedMedia::selectedText( TextSelection selection) const { if (_mode != Mode::Column) { - return _caption.toTextForMimeData(selection); + return {}; } auto result = TextForMimeData(); for (const auto &part : _parts) { @@ -606,9 +572,7 @@ TextForMimeData GroupedMedia::selectedText( SelectedQuote GroupedMedia::selectedQuote(TextSelection selection) const { if (_mode != Mode::Column) { - return _captionItem - ? Element::FindSelectedQuote(_caption, selection, _captionItem) - : SelectedQuote(); + return {}; } for (const auto &part : _parts) { const auto next = part.content->skipSelection(selection); @@ -630,9 +594,7 @@ TextSelection GroupedMedia::selectionFromQuote( Expects(quote.item != nullptr); if (_mode != Mode::Column) { - return (_captionItem == quote.item) - ? Element::FindSelectionFromQuote(_caption, quote) - : TextSelection(); + return {}; } const auto i = ranges::find(_parts, not_null(quote.item), &Part::item); if (i == end(_parts)) { @@ -730,7 +692,6 @@ bool GroupedMedia::applyGroup(const DataMediaRange &medias) { if (_parts.empty()) { return false; } - refreshCaption(); Ensures(_parts.size() <= kMaxSize); return true; @@ -750,43 +711,13 @@ bool GroupedMedia::validateGroupParts( return (i == count); } -void GroupedMedia::refreshCaption() { - const auto part = [&]() -> const Part* { - if (_mode == Mode::Column) { - return nullptr; - } - auto result = (const Part*)nullptr; - for (const auto &part : _parts) { - if (!part.item->emptyText()) { - if (result) { - return nullptr; - } else { - result = ∂ - } - } - } - return result; - }(); - if (part) { - _caption = createCaption(part->item); - _captionItem = part->item; - } else { - _captionItem = nullptr; - } -} - not_null GroupedMedia::main() const { Expects(!_parts.empty()); return _parts.back().content.get(); } -TextWithEntities GroupedMedia::getCaption() const { - return main()->getCaption(); -} - void GroupedMedia::hideSpoilers() { - _caption.setSpoilerRevealed(false, anim::type::instant); for (const auto &part : _parts) { part.content->hideSpoilers(); } @@ -846,13 +777,11 @@ void GroupedMedia::unloadHeavyPart() { part.cacheKey = 0; part.cache = QPixmap(); } - _caption.unloadPersistentAnimation(); } void GroupedMedia::parentTextUpdated() { if (_parent->media() == this) { - refreshCaption(); - history()->owner().requestViewResize(_parent); + _captionItem = std::nullopt; } } @@ -867,7 +796,9 @@ QPoint GroupedMedia::resolveCustomInfoRightBottom() const { } bool GroupedMedia::computeNeedBubble() const { - if (!_caption.isEmpty() || _mode == Mode::Column) { + Expects(_mode == Mode::Column || _captionItem.has_value()); + + if (_mode == Mode::Column || *_captionItem) { return true; } if (const auto item = _parent->data()) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index f8baabe8a..5637f65ad 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -31,6 +31,9 @@ public: void refreshParentId(not_null realParent) override; + HistoryItem *itemForText() const override; + bool hideMessageText() const override; + void drawHighlight( Painter &p, const PaintContext &context, @@ -69,7 +72,6 @@ public: const ClickHandlerPtr &p, bool pressed) override; - TextWithEntities getCaption() const override; void hideSpoilers() override; Storage::SharedMediaTypesMask sharedMediaTypes() const override; @@ -79,14 +81,12 @@ public: HistoryMessageEdited *displayedEditBadge() const override; bool skipBubbleTail() const override { - return (_mode == Mode::Grid) - && isRoundedInBubbleBottom() - && _caption.isEmpty(); + return (_mode == Mode::Grid) && isRoundedInBubbleBottom(); } void updateNeedBubbleState() override; bool needsBubble() const override; bool customInfoLayout() const override { - return _caption.isEmpty() && (_mode != Mode::Column); + return (_mode != Mode::Column); } QPoint resolveCustomInfoRightBottom() const override; @@ -143,15 +143,12 @@ private: QPoint point, StateRequest request) const; - void refreshCaption(); - [[nodiscard]] Ui::BubbleRounding applyRoundingSides( Ui::BubbleRounding already, RectParts sides) const; [[nodiscard]] QMargins groupedPadding() const; - Ui::Text::String _caption; - HistoryItem *_captionItem = nullptr; + mutable std::optional _captionItem; std::vector _parts; Mode _mode = Mode::Grid; bool _needBubble = false; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 2532509a7..869527ece 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -73,9 +73,7 @@ Photo::Photo( , _storyId(realParent->media() ? realParent->media()->storyId() : FullStoryId()) -, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _spoiler(spoiler ? std::make_unique() : nullptr) { - _caption = createCaption(realParent); create(realParent->fullId()); } @@ -161,7 +159,6 @@ void Photo::unloadHeavyPart() { _spoiler->animation = nullptr; } _imageCache = QImage(); - _caption.unloadPersistentAnimation(); togglePollingStory(false); } @@ -184,15 +181,6 @@ QSize Photo::countOptimalSize() { if (_serviceWidth > 0) { return { int(_serviceWidth), int(_serviceWidth) }; } - - if (_parent->media() != this) { - _caption = Ui::Text::String(); - } else if (_caption.hasSkipBlock()) { - _caption.updateSkipBlock( - _parent->skipBlockWidth(), - _parent->skipBlockHeight()); - } - const auto dimensions = photoSize(); const auto scaled = CountDesiredMediaSize(dimensions); const auto minWidth = std::clamp( @@ -204,12 +192,7 @@ QSize Photo::countOptimalSize() { const auto maxActualWidth = qMax(scaled.width(), minWidth); auto maxWidth = qMax(maxActualWidth, scaled.height()); auto minHeight = qMax(scaled.height(), st::minPhotoSize); - if (_parent->hasBubble() && !_caption.isEmpty()) { - maxWidth = qMax( - maxWidth, - (st::msgPadding.left() - + _caption.maxWidth() - + st::msgPadding.right())); + if (_parent->hasBubble()) { minHeight = adjustHeightForLessCrop( dimensions, { maxWidth, minHeight }); @@ -217,10 +200,6 @@ QSize Photo::countOptimalSize() { accumulate_max(maxWidth, botTop->maxWidth); minHeight += botTop->height; } - minHeight += st::mediaCaptionSkip + _caption.minHeight(); - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } } return { maxWidth, minHeight }; } @@ -244,10 +223,8 @@ QSize Photo::countCurrentSize(int newWidth) { newWidth = qMax(pix.width(), minWidth); auto newHeight = qMax(pix.height(), st::minPhotoSize); auto imageHeight = newHeight; - if (_parent->hasBubble() && !_caption.isEmpty()) { - auto captionMaxWidth = st::msgPadding.left() - + _caption.maxWidth() - + st::msgPadding.right(); + if (_parent->hasBubble()) { + auto captionMaxWidth = _parent->textualMaxWidth(); const auto botTop = _parent->Get(); if (botTop) { accumulate_max(captionMaxWidth, botTop->maxWidth); @@ -257,16 +234,9 @@ QSize Photo::countCurrentSize(int newWidth) { imageHeight = newHeight = adjustHeightForLessCrop( dimensions, { newWidth, newHeight }); - const auto captionw = newWidth - - st::msgPadding.left() - - st::msgPadding.right(); if (botTop) { newHeight += botTop->height; } - newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); - if (isBubbleBottom()) { - newHeight += st::msgPadding.bottom(); - } } const auto enlargeInner = st::historyPageEnlargeSize; const auto enlargeOuter = 2 * st::historyPageEnlargeSkip + enlargeInner; @@ -309,8 +279,6 @@ void Photo::draw(Painter &p, const PaintContext &context) const { auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); - auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); - if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { @@ -326,19 +294,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const { } else { const auto rounding = inWebPage ? std::optional() - : adjustedBubbleRoundingWithCaption(_caption); - if (bubble) { - if (!_caption.isEmpty()) { - painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); - if (botTop) { - painth -= botTop->height; - } - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); - } - } else { + : adjustedBubbleRounding(); + if (!bubble) { Assert(rounding.has_value()); fillImageShadow(p, rthumb, *rounding, context); } @@ -414,35 +371,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { } // date - if (!_caption.isEmpty()) { - p.setPen(stm->historyTextFg); - _parent->prepareCustomEmojiPaint(p, context, _caption); - auto top = painty + painth + st::mediaCaptionSkip; - if (botTop) { - botTop->text.drawLeftElided( - p, - st::msgPadding.left(), - top, - captionw, - _parent->width()); - top += botTop->height; - } - auto highlightRequest = context.computeHighlightCache(); - _caption.draw(p, { - .position = QPoint(st::msgPadding.left(), top), - .availableWidth = captionw, - .palette = &stm->textPalette, - .pre = stm->preCache.get(), - .blockquote = context.quoteCache(parent()->contentColorIndex()), - .colors = context.st->highlightColors(), - .spoiler = Ui::Text::DefaultSpoilerCache(), - .now = context.now, - .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), - .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .selection = context.selection, - .highlight = highlightRequest ? &*highlightRequest : nullptr, - }); - } else if (!inWebPage) { + if (isBubbleBottom() && !inWebPage) { auto fullRight = paintx + paintw; auto fullBottom = painty + painth; if (needInfoDisplay()) { @@ -682,21 +611,7 @@ TextState Photo::textState(QPoint point, StateRequest request) const { auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); - if (bubble && !_caption.isEmpty()) { - const auto captionw = paintw - - st::msgPadding.left() - - st::msgPadding.right(); - painth -= _caption.countHeight(captionw); - if (isBubbleBottom()) { - painth -= st::msgPadding.bottom(); - } - if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { - result = TextState(_parent, _caption.getState( - point - QPoint(st::msgPadding.left(), painth), - captionw, - request.forText())); - return result; - } + if (bubble) { if (const auto botTop = _parent->Get()) { painth -= botTop->height; } @@ -719,7 +634,7 @@ TextState Photo::textState(QPoint point, StateRequest request) const { result.cursor = CursorState::Enlarge; } } - if (_caption.isEmpty() && _parent->media() == this) { + if (isBubbleBottom() && _parent->media() == this) { auto fullRight = paintx + paintw; auto fullBottom = painty + painth; const auto bottomInfoResult = _parent->bottomInfoTextState( @@ -746,13 +661,13 @@ TextState Photo::textState(QPoint point, StateRequest request) const { return result; } -QSize Photo::sizeForGroupingOptimal(int maxWidth) const { +QSize Photo::sizeForGroupingOptimal(int maxWidth, bool last) const { const auto size = photoSize(); return { std::max(size.width(), 1), std::max(size.height(), 1)}; } QSize Photo::sizeForGrouping(int width) const { - return sizeForGroupingOptimal(width); + return sizeForGroupingOptimal(width, false); } void Photo::drawGrouped( @@ -1094,27 +1009,14 @@ bool Photo::videoAutoplayEnabled() const { _data); } -TextForMimeData Photo::selectedText(TextSelection selection) const { - return _caption.toTextForMimeData(selection); -} - -SelectedQuote Photo::selectedQuote(TextSelection selection) const { - return Element::FindSelectedQuote(_caption, selection, _realParent); -} - -TextSelection Photo::selectionFromQuote(const SelectedQuote "e) const { - return Element::FindSelectionFromQuote(_caption, quote); -} - void Photo::hideSpoilers() { - _caption.setSpoilerRevealed(false, anim::type::instant); if (_spoiler) { _spoiler->revealed = false; } } bool Photo::needsBubble() const { - if (_storyId || !_caption.isEmpty()) { + if (_storyId) { return true; } const auto item = _parent->data(); @@ -1122,6 +1024,7 @@ bool Photo::needsBubble() const { && (item->repliesAreComments() || item->externalReply() || item->viaBot() + || !item->emptyText() || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() @@ -1139,13 +1042,6 @@ bool Photo::isReadyForOpen() const { return _dataMedia->loaded(); } -void Photo::parentTextUpdated() { - _caption = (_parent->media() == this) - ? createCaption(_parent->data()) - : Ui::Text::String(); - history()->owner().requestViewResize(_parent); -} - void Photo::showPhoto(FullMsgId id) { _parent->delegate()->elementOpenPhoto(_data, id); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 1c99839ce..8b50f52ac 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -41,26 +41,13 @@ public: int width); ~Photo(); + bool hideMessageText() const override { + return false; + } + void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; - [[nodiscard]] TextSelection adjustSelection( - TextSelection selection, - TextSelectType type) const override { - return _caption.adjustSelection(selection, type); - } - uint16 fullSelectionLength() const override { - return _caption.length(); - } - bool hasTextForCopy() const override { - return !_caption.isEmpty(); - } - - TextForMimeData selectedText(TextSelection selection) const override; - SelectedQuote selectedQuote(TextSelection selection) const override; - TextSelection selectionFromQuote( - const SelectedQuote "e) const override; - PhotoData *getPhoto() const override { return _data; } @@ -71,7 +58,7 @@ public: QPoint photoPosition, bool markFrameShown) const; - QSize sizeForGroupingOptimal(int maxWidth) const override; + QSize sizeForGroupingOptimal(int maxWidth, bool last) const override; QSize sizeForGrouping(int width) const override; void drawGrouped( Painter &p, @@ -88,22 +75,17 @@ public: QPoint point, StateRequest request) const override; - TextWithEntities getCaption() const override { - return _caption.toTextWithEntities(); - } void hideSpoilers() override; bool needsBubble() const override; bool customInfoLayout() const override { - return _caption.isEmpty(); + return true; } QPoint resolveCustomInfoRightBottom() const override; bool skipBubbleTail() const override { - return isRoundedInBubbleBottom() && _caption.isEmpty(); + return isRoundedInBubbleBottom(); } bool isReadyForOpen() const override; - void parentTextUpdated() override; - bool hasHeavyPart() const override; void unloadHeavyPart() override; @@ -168,7 +150,6 @@ private: const not_null _data; const FullStoryId _storyId; - Ui::Text::String _caption; mutable std::shared_ptr _dataMedia; mutable std::unique_ptr _streamed; const std::unique_ptr _spoiler; diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h index 454af2bbf..31bc076b0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -35,6 +35,9 @@ public: void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; + bool aboveTextByDefault() const override { + return false; + } bool hideMessageText() const override { return false; } From 67f781608870c894731e758765cf0a3b3d4776c7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 21:27:07 +0400 Subject: [PATCH 164/225] Allow sending photo/video captions above media. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/api/api_common.h | 1 + Telegram/SourceFiles/api/api_editing.cpp | 3 +- Telegram/SourceFiles/api/api_sending.cpp | 11 ++ Telegram/SourceFiles/apiwrap.cpp | 9 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 103 ++++++++++++------ Telegram/SourceFiles/boxes/send_files_box.h | 15 ++- .../chat_helpers/chat_helpers.style | 4 + .../data/data_message_reactions.cpp | 1 + .../media/stories/media_stories_share.cpp | 3 + .../SourceFiles/media/view/media_view.style | 2 + Telegram/SourceFiles/menu/menu_send.cpp | 36 +++++- Telegram/SourceFiles/menu/menu_send.h | 24 +++- .../ui/chat/attach/attach_prepare.cpp | 11 ++ .../ui/chat/attach/attach_prepare.h | 3 + 15 files changed, 182 insertions(+), 46 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9d6823fb5..4d1798501 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -251,6 +251,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_caption_limit2#other" = "Make the caption shorter or subscribe to **Telegram Premium** to double the limit to **{count}** characters."; "lng_caption_limit_reached#one" = "You've reached the media caption limit. Please make the caption shorter by {count} character."; "lng_caption_limit_reached#other" = "You've reached the media caption limit. Please make the caption shorter by {count} characters."; +"lng_caption_move_up" = "Move Caption Up"; +"lng_caption_move_down" = "Move Caption Down"; "lng_file_size_limit_title" = "File Too Large"; "lng_file_size_limit#one" = "{count} Gb"; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 9c3939bf5..efd92a9bc 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -26,6 +26,7 @@ struct SendOptions { EffectId effectId = 0; bool silent = false; bool handleSupportSwitch = false; + bool invertCaption = false; bool hideViaBot = false; crl::time ttlSeconds = 0; }; diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 7dcd768cb..60dfb4c69 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -81,7 +81,8 @@ mtpRequestId EditMessage( | ((!webpage.removed && !webpage.url.isEmpty()) ? MTPmessages_EditMessage::Flag::f_media : emptyFlag) - | ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert) + | (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert) + || options.invertCaption) ? MTPmessages_EditMessage::Flag::f_invert_media : emptyFlag) | (!sentEntities.v.isEmpty() diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index cbe617b75..8ab3d8bc7 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -136,6 +136,10 @@ void SendExistingMedia( if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.invertCaption) { + flags |= MessageFlag::InvertMedia; + sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; + } session->data().registerMessageRandomId(randomId, newId); @@ -314,6 +318,10 @@ bool SendDice(MessageToSend &message) { if (action.options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (action.options.invertCaption) { + flags |= MessageFlag::InvertMedia; + sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; + } session->data().registerMessageRandomId(randomId, newId); @@ -440,6 +448,9 @@ void SendConfirmedFile( flags |= MessageFlag::MediaIsUnread; } } + if (file->to.options.invertCaption) { + flags |= MessageFlag::InvertMedia; + } const auto messageFromId = file->to.options.sendAs ? file->to.options.sendAs->id diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 0e96bcec1..181cf26fb 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3772,7 +3772,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); FillMessagePostFlags(action, peer, flags); - if (exactWebPage && !ignoreWebPage && message.webPage.invert) { + if ((exactWebPage && !ignoreWebPage && message.webPage.invert) + || action.options.invertCaption) { flags |= MessageFlag::InvertMedia; sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media; mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media; @@ -4170,7 +4171,8 @@ void ApiWrap::sendMediaWithRandomId( | (options.scheduled ? Flag::f_schedule_date : Flag(0)) | (options.sendAs ? Flag::f_send_as : Flag(0)) | (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) - | (options.effectId ? Flag::f_effect : Flag(0)); + | (options.effectId ? Flag::f_effect : Flag(0)) + | (options.invertCaption ? Flag::f_invert_media : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; @@ -4280,7 +4282,8 @@ void ApiWrap::sendAlbumIfReady(not_null album) { | (album->options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0)) - | (album->options.effectId ? Flag::f_effect : Flag(0)); + | (album->options.effectId ? Flag::f_effect : Flag(0)) + | (album->options.invertCaption ? Flag::f_invert_media : Flag(0)); auto &histories = history->owner().histories(); const auto peer = history->peer; histories.sendPreparedMessage( diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index c28a1dc70..c38004f7b 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -350,9 +350,8 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor) , _titleHeight(st::boxTitleHeight) , _list(std::move(descriptor.list)) , _limits(descriptor.limits) -, _sendMenuDetails(descriptor.sendMenuDetails - ? descriptor.sendMenuDetails - : [] { return SendMenu::Details(); }) +, _sendMenuDetails(prepareSendMenuDetails(descriptor)) +, _sendMenuCallback(prepareSendMenuCallback()) , _captionToPeer(descriptor.captionToPeer) , _check(std::move(descriptor.check)) , _confirmedCallback(std::move(descriptor.confirmed)) @@ -366,6 +365,50 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor) enqueueNextPrepare(); } +Fn SendFilesBox::prepareSendMenuDetails( + const SendFilesBoxDescriptor &descriptor) { + auto initial = descriptor.sendMenuDetails; + return crl::guard(this, [=] { + auto result = initial ? initial() : SendMenu::Details(); + result.spoiler = !hasSpoilerMenu() + ? SendMenu::SpoilerState::None + : allWithSpoilers() + ? SendMenu::SpoilerState::Enabled + : SendMenu::SpoilerState::Possible; + const auto way = _sendWay.current(); + const auto canMoveCaption = _list.canMoveCaption( + way.groupFiles() && way.sendImagesAsPhotos(), + way.sendImagesAsPhotos() + ) && _caption && !_caption->getLastText().isEmpty(); + result.caption = !canMoveCaption + ? SendMenu::CaptionState::None + : _invertCaption + ? SendMenu::CaptionState::Above + : SendMenu::CaptionState::Below; + return result; + }); +} + +auto SendFilesBox::prepareSendMenuCallback() +-> Fn { + return crl::guard(this, [=](MenuAction action, MenuDetails details) { + using Type = SendMenu::ActionType; + switch (action.type) { + case Type::CaptionDown: _invertCaption = false; break; + case Type::CaptionUp: _invertCaption = true; break; + case Type::SpoilerOn: toggleSpoilers(true); break; + case Type::SpoilerOff: toggleSpoilers(false); break; + default: + SendMenu::DefaultCallback( + _show, + sendCallback())( + action, + details); + break; + } + }); +} + void SendFilesBox::initPreview() { using namespace rpl::mappers; @@ -533,7 +576,7 @@ void SendFilesBox::refreshButtons() { _send, _show, _sendMenuDetails, - SendMenu::DefaultCallback(_show, sendCallback())); + _sendMenuCallback); } addButton(tr::lng_cancel(), [=] { closeBox(); }); _addFile = addLeftButton( @@ -545,8 +588,10 @@ void SendFilesBox::refreshButtons() { addMenuButton(); } -bool SendFilesBox::hasSendMenu() const { - return (_sendMenuDetails().type != SendMenu::Type::Disabled); +bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const { + return (details.type != SendMenu::Type::Disabled) + || (details.spoiler != SendMenu::SpoilerState::None) + || (details.caption != SendMenu::CaptionState::None); } bool SendFilesBox::hasSpoilerMenu() const { @@ -583,7 +628,8 @@ void SendFilesBox::toggleSpoilers(bool enabled) { } void SendFilesBox::addMenuButton() { - if (!hasSendMenu() && !hasSpoilerMenu()) { + const auto details = _sendMenuDetails(); + if (!hasSendMenu(details)) { return; } @@ -592,31 +638,16 @@ void SendFilesBox::addMenuButton() { const auto &tabbed = _st.tabbed; const auto &icons = tabbed.icons; _menu = base::make_unique_q(top, tabbed.menu); - if (hasSpoilerMenu()) { - const auto spoilered = allWithSpoilers(); - _menu->addAction( - (spoilered - ? tr::lng_context_disable_spoiler(tr::now) - : tr::lng_context_spoiler_effect(tr::now)), - [=] { toggleSpoilers(!spoilered); }, - spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler); - if (hasSendMenu()) { - _menu->addSeparator(&tabbed.expandedSeparator); - } - } - if (hasSendMenu()) { - SendMenu::FillSendMenu( - _menu.get(), - _show, - _sendMenuDetails(), - SendMenu::DefaultCallback(_show, sendCallback()), - &_st.tabbed.icons, - QCursor::pos()); - } + SendMenu::FillSendMenu( + _menu.get(), + _show, + _sendMenuDetails(), + _sendMenuCallback, + &_st.tabbed.icons, + QCursor::pos()); _menu->popup(QCursor::pos()); return true; }); - } void SendFilesBox::initSendWay() { @@ -658,9 +689,7 @@ void SendFilesBox::initSendWay() { for (auto &block : _blocks) { block.setSendWay(value); } - if (!hasSendMenu()) { - refreshButtons(); - } + refreshButtons(); if (was != hidden()) { updateBoxSize(); updateControlsGeometry(); @@ -872,9 +901,7 @@ void SendFilesBox::pushBlock(int from, int till) { } void SendFilesBox::refreshControls(bool initial) { - if (initial || !hasSendMenu()) { - refreshButtons(); - } + refreshButtons(); refreshTitleText(); updateSendWayControls(); updateCaptionPlaceholder(); @@ -1426,9 +1453,12 @@ void SendFilesBox::send( if ((_sendType == Api::SendType::Scheduled || _sendType == Api::SendType::ScheduledToUser) && !options.scheduled) { + auto child = _sendMenuDetails(); + child.spoiler = SendMenu::SpoilerState::None; + child.caption = SendMenu::CaptionState::None; return SendMenu::DefaultCallback(_show, sendCallback())( { .type = SendMenu::ActionType::Schedule }, - _sendMenuDetails()); + child); } if (_preparing) { _whenReadySend = [=] { @@ -1453,6 +1483,7 @@ void SendFilesBox::send( auto caption = (_caption && !_caption->isHidden()) ? _caption->getTextWithAppliedMarkdown() : TextWithTags(); + options.invertCaption = _invertCaption; if (!validateLength(caption.text)) { return; } diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index f6de4a4f8..1e95a1aa8 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -48,6 +48,7 @@ class SessionController; namespace SendMenu { struct Details; +struct Action; } // namespace SendMenu namespace HistoryView::Controls { @@ -136,6 +137,9 @@ protected: void resizeEvent(QResizeEvent *e) override; private: + using MenuAction = SendMenu::Action; + using MenuDetails = SendMenu::Details; + class Block final { public: Block( @@ -173,7 +177,7 @@ private: void initSendWay(); void initPreview(); - [[nodiscard]] bool hasSendMenu() const; + [[nodiscard]] bool hasSendMenu(const MenuDetails &details) const; [[nodiscard]] bool hasSpoilerMenu() const; [[nodiscard]] bool allWithSpoilers(); [[nodiscard]] bool checkWithWay( @@ -225,6 +229,11 @@ private: void checkCharsLimitation(); + [[nodiscard]] Fn prepareSendMenuDetails( + const SendFilesBoxDescriptor &descriptor); + [[nodiscard]] auto prepareSendMenuCallback() + -> Fn; + const std::shared_ptr _show; const style::ComposeControls &_st; const Api::SendType _sendType = Api::SendType(); @@ -236,12 +245,14 @@ private: std::optional _removingIndex; SendFilesLimits _limits = {}; - Fn _sendMenuDetails = nullptr; + Fn _sendMenuDetails; + Fn _sendMenuCallback; PeerData *_captionToPeer = nullptr; SendFilesCheck _check; SendFilesConfirmed _confirmedCallback; Fn _cancelledCallback; bool _confirmed = false; + bool _invertCaption = false; object_ptr _caption = { nullptr }; TextWithTags _prefilledCaptionText; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index d69d4e6bb..8439c5eee 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -69,6 +69,8 @@ ComposeIcons { menuWhenOnline: icon; menuSpoiler: icon; menuSpoilerOff: icon; + menuBelow: icon; + menuAbove: icon; stripBubble: icon; stripExpandPanel: icon; @@ -606,6 +608,8 @@ defaultComposeIcons: ComposeIcons { menuWhenOnline: menuIconWhenOnline; menuSpoiler: menuIconSpoiler; menuSpoilerOff: menuIconSpoilerOff; + menuBelow: menuIconBelow; + menuAbove: menuIconAbove; stripBubble: icon{ { "chat/reactions_bubble_shadow", windowShadowFg }, diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index f1175a473..2b42a740d 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -1104,6 +1104,7 @@ void Reactions::defaultUpdated() { } refreshMyTags(); refreshTags(); + refreshEffects(); _defaultUpdated.fire({}); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index 9d32a126a..773239e0b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -141,6 +141,9 @@ namespace Media::Stories { if (options.effectId) { sendFlags |= MTPmessages_SendMedia::Flag::f_effect; } + if (options.invertCaption) { + sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media; + } const auto done = [=] { if (!--state->requests) { if (show->valid()) { diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 5da7f93a8..99d9b4288 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -636,6 +636,8 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) { menuWhenOnline: icon {{ "menu/send_when_online", storiesComposeWhiteText }}; menuSpoiler: icon {{ "menu/spoiler_on", storiesComposeWhiteText }}; menuSpoilerOff: icon {{ "menu/spoiler_off", storiesComposeWhiteText }}; + menuBelow: icon {{ "menu/link_below", storiesComposeWhiteText }}; + menuAbove: icon {{ "menu/link_above", storiesComposeWhiteText }}; stripBubble: icon{ { "chat/reactions_bubble_shadow", windowShadowFg }, diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 67e5deaf8..4bdd5709b 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -615,13 +615,47 @@ FillMenuResult FillSendMenu( const style::ComposeIcons *iconsOverride, std::optional desiredPositionOverride) { const auto type = details.type; - if (type == Type::Disabled || !action) { + const auto empty = (type == Type::Disabled) + && (details.spoiler == SpoilerState::None) + && (details.caption == CaptionState::None); + if (empty || !action) { return FillMenuResult::Skipped; } const auto &icons = iconsOverride ? *iconsOverride : st::defaultComposeIcons; + auto toggles = false; + if (details.spoiler != SpoilerState::None) { + const auto spoilered = (details.spoiler == SpoilerState::Enabled); + menu->addAction( + (spoilered + ? tr::lng_context_disable_spoiler(tr::now) + : tr::lng_context_spoiler_effect(tr::now)), + [=] { action({ .type = spoilered + ? ActionType::SpoilerOff + : ActionType::SpoilerOn + }, details); }, + spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler); + toggles = true; + } + if (details.caption != CaptionState::None) { + const auto above = (details.caption == CaptionState::Above); + menu->addAction( + (above + ? tr::lng_caption_move_down(tr::now) + : tr::lng_caption_move_up(tr::now)), + [=] { action({ .type = above + ? ActionType::CaptionDown + : ActionType::CaptionUp + }, details); }, + above ? &icons.menuBelow : &icons.menuAbove); + toggles = true; + } + if (toggles && type != Type::Disabled) { + menu->addSeparator(); + } + if (type != Type::Reminder) { menu->addAction( tr::lng_send_silent_message(tr::now), diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index f503a7e56..2c6dbb9b7 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -29,7 +29,7 @@ class Thread; namespace SendMenu { -enum class Type { +enum class Type : uchar { Disabled, SilentOnly, Scheduled, @@ -37,20 +37,38 @@ enum class Type { Reminder, }; +enum class SpoilerState : uchar { + None, + Enabled, + Possible, +}; + +enum class CaptionState : uchar { + None, + Below, + Above, +}; + struct Details { Type type = Type::Disabled; + SpoilerState spoiler = SpoilerState::None; + CaptionState caption = CaptionState::None; bool effectAllowed = false; }; -enum class FillMenuResult { +enum class FillMenuResult : uchar { Prepared, Skipped, Failed, }; -enum class ActionType { +enum class ActionType : uchar { Send, Schedule, + SpoilerOn, + SpoilerOff, + CaptionUp, + CaptionDown, }; struct Action { using Type = ActionType; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 6ca3bd241..ebdf9ac6f 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -184,6 +184,17 @@ bool PreparedList::canAddCaption(bool sendingAlbum, bool compress) const { return !hasFiles && !hasMusic && !hasNotGrouped; } +bool PreparedList::canMoveCaption(bool sendingAlbum, bool compress) const { + if (!canAddCaption(sendingAlbum, compress)) { + return false; + } else if (files.size() != 1) { + return true; + } + const auto &file = files.front(); + return (file.type == PreparedFile::Type::Video) + || (file.type == PreparedFile::Type::Photo && compress); +} + bool PreparedList::hasGroupOption(bool slowmode) const { if (slowmode || files.size() < 2) { return false; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index c6a74790a..e467cc611 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -112,6 +112,9 @@ struct PreparedList { void mergeToEnd(PreparedList &&other, bool cutToAlbumSize = false); [[nodiscard]] bool canAddCaption(bool sendingAlbum, bool compress) const; + [[nodiscard]] bool canMoveCaption( + bool sendingAlbum, + bool compress) const; [[nodiscard]] bool canBeSentInSlowmode() const; [[nodiscard]] bool canBeSentInSlowmodeWith( const PreparedList &other) const; From 8c0351be4ea099b77c5552adea7783e2c3727763 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 May 2024 23:09:51 +0400 Subject: [PATCH 165/225] Allow editing caption above/below media. --- .../SourceFiles/boxes/edit_caption_box.cpp | 23 +-- Telegram/SourceFiles/boxes/edit_caption_box.h | 4 - Telegram/SourceFiles/boxes/send_files_box.cpp | 7 +- .../SourceFiles/history/history_widget.cpp | 41 ++++-- Telegram/SourceFiles/history/history_widget.h | 1 + .../history_view_compose_controls.cpp | 31 ++-- ...istory_view_compose_media_edit_manager.cpp | 132 +++++++++++++----- .../history_view_compose_media_edit_manager.h | 35 ++++- .../view/media/history_view_media_grouped.cpp | 2 +- Telegram/SourceFiles/menu/menu_send.cpp | 15 +- 10 files changed, 200 insertions(+), 91 deletions(-) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index be4d4e7fa..bb9914f09 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_session_settings.h" #include "mainwidget.h" // controller->content() -> QWidget* +#include "menu/menu_send.h" #include "mtproto/mtproto_config.h" #include "platform/platform_specific.h" #include "storage/localimageloader.h" // SendMediaType @@ -225,13 +226,6 @@ void EditPhotoImage( } // namespace -EditCaptionBox::EditCaptionBox( - QWidget*, - not_null controller, - not_null item) -: EditCaptionBox({}, controller, item, PrepareEditText(item), {}, {}) { -} - EditCaptionBox::EditCaptionBox( QWidget*, not_null controller, @@ -365,9 +359,21 @@ void EditCaptionBox::StartPhotoEdit( } void EditCaptionBox::prepare() { - addButton(tr::lng_settings_save(), [=] { save(); }); + const auto button = addButton(tr::lng_settings_save(), [=] { save(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); + const auto details = crl::guard(this, [=] { + return SendMenu::Details(); + }); + const auto callback = [=](SendMenu::Action action, const auto &) { + + }; + SendMenu::SetupMenuAndShortcuts( + button, + nullptr, + details, + crl::guard(this, callback)); + updateBoxSize(); setupField(); @@ -899,6 +905,7 @@ void EditCaptionBox::save() { auto options = Api::SendOptions(); options.scheduled = item->isScheduled() ? item->date() : 0; options.shortcutId = item->shortcutId(); + //options.invertCaption = _invertCaption; if (!_preparedList.files.empty()) { if ((_albumType != Ui::AlbumType::None) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index 110c0e588..b3a10f43f 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -32,10 +32,6 @@ enum class AlbumType; class EditCaptionBox final : public Ui::BoxContent { public: - EditCaptionBox( - QWidget*, - not_null controller, - not_null item); EditCaptionBox( QWidget*, not_null controller, diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index c38004f7b..0656488ee 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -379,7 +379,7 @@ Fn SendFilesBox::prepareSendMenuDetails( const auto canMoveCaption = _list.canMoveCaption( way.groupFiles() && way.sendImagesAsPhotos(), way.sendImagesAsPhotos() - ) && _caption && !_caption->getLastText().isEmpty(); + ) && _caption && HasSendText(_caption); result.caption = !canMoveCaption ? SendMenu::CaptionState::None : _invertCaption @@ -638,14 +638,15 @@ void SendFilesBox::addMenuButton() { const auto &tabbed = _st.tabbed; const auto &icons = tabbed.icons; _menu = base::make_unique_q(top, tabbed.menu); + const auto position = QCursor::pos(); SendMenu::FillSendMenu( _menu.get(), _show, _sendMenuDetails(), _sendMenuCallback, &_st.tabbed.icons, - QCursor::pos()); - _menu->popup(QCursor::pos()); + position); + _menu->popup(position); return true; }); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e68bade1a..bcbada572 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -324,7 +324,12 @@ HistoryWidget::HistoryWidget( { using namespace SendMenu; const auto sendAction = [=](Action action, Details) { - if (action.type == ActionType::Send) { + if (action.type == ActionType::CaptionUp + || action.type == ActionType::CaptionDown + || action.type == ActionType::SpoilerOn + || action.type == ActionType::SpoilerOff) { + _mediaEditSpoiler.apply(action); + } else if (action.type == ActionType::Send) { send(action.options); } else { sendScheduled(action.options); @@ -2672,7 +2677,7 @@ void HistoryWidget::setEditMsgId(MsgId msgId) { unregisterDraftSources(); _editMsgId = msgId; if (!msgId) { - _mediaEditSpoiler.setSpoilerOverride(std::nullopt); + _mediaEditSpoiler.cancel(); _canReplaceMedia = false; if (_preview) { _preview->setDisabled(false); @@ -4056,15 +4061,14 @@ void HistoryWidget::saveEditMsg() { })(); }; - auto options = Api::SendOptions(); _saveEditMsgRequestId = Api::EditTextMessage( item, sending, webPageDraft, - options, + { .invertCaption = _mediaEditSpoiler.invertCaption() }, done, fail, - _mediaEditSpoiler.spoilerOverride()); + _mediaEditSpoiler.spoilered()); } void HistoryWidget::hideChildWidgets() { @@ -4222,6 +4226,12 @@ SendMenu::Details HistoryWidget::sendMenuDetails() const { return { .type = type, .effectAllowed = effectAllowed }; } +SendMenu::Details HistoryWidget::saveMenuDetails() const { + return (_editMsgId && _replyEditMsg) + ? _mediaEditSpoiler.sendMenuDetails(HasSendText(_field)) + : SendMenu::Details(); +} + auto HistoryWidget::computeSendButtonType() const { using Type = Ui::SendButton::Type; @@ -4236,7 +4246,11 @@ auto HistoryWidget::computeSendButtonType() const { } SendMenu::Details HistoryWidget::sendButtonMenuDetails() const { - if (computeSendButtonType() != Ui::SendButton::Type::Send) { + using Type = Ui::SendButton::Type; + const auto type = computeSendButtonType(); + if (type == Type::Save) { + return saveMenuDetails(); + } else if (type != Type::Send) { return {}; } return sendMenuDetails(); @@ -6587,8 +6601,8 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { && (e->button() == Qt::RightButton)) { _mediaEditSpoiler.showMenu( _list, - session().data().message(_history->peer, _editMsgId), - [=](bool) { mouseMoveEvent(nullptr); }); + [=] { mouseMoveEvent(nullptr); }, + HasSendText(_field)); } else if (_inPhotoEdit && _photoEditMedia) { EditCaptionBox::StartPhotoEdit( controller(), @@ -8171,6 +8185,9 @@ void HistoryWidget::updateReplyEditTexts(bool force) { const auto editMedia = _editMsgId ? _replyEditMsg->media() : nullptr; + if (_editMsgId && _replyEditMsg) { + _mediaEditSpoiler.start(_replyEditMsg); + } _canReplaceMedia = editMedia && editMedia->allowsEditMedia(); _photoEditMedia = (_canReplaceMedia && editMedia->photo() @@ -8264,14 +8281,12 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { ? drawMsgText->media() : nullptr; const auto hasPreview = media && media->hasReplyPreview(); - const auto preview = _mediaEditSpoiler.spoilerOverride() - ? _mediaEditSpoiler.mediaPreview(drawMsgText) + const auto preview = _mediaEditSpoiler + ? _mediaEditSpoiler.mediaPreview() : hasPreview ? media->replyPreview() : nullptr; - const auto spoilered = _mediaEditSpoiler.spoilerOverride() - ? (*_mediaEditSpoiler.spoilerOverride()) - : (preview && media->hasSpoiler()); + const auto spoilered = _mediaEditSpoiler.spoilered(); if (!spoilered) { _replySpoiler = nullptr; } else if (!_replySpoiler) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index bc3641e67..f829cf797 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -269,6 +269,7 @@ public: void clearSelected(); [[nodiscard]] SendMenu::Details sendMenuDetails() const; + [[nodiscard]] SendMenu::Details saveMenuDetails() const; bool sendExistingDocument( not_null document, Api::SendOptions options, 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 c81c59b8c..6c985bbf0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -124,7 +124,8 @@ class FieldHeader final : public Ui::RpWidget { public: FieldHeader( QWidget *parent, - std::shared_ptr show); + std::shared_ptr show, + Fn hasSendText); void setHistory(const SetHistoryArgs &args); void updateTopicRootId(MsgId topicRootId); @@ -187,6 +188,8 @@ private: }; const std::shared_ptr _show; + const Fn _hasSendText; + History *_history = nullptr; MsgId _topicRootId = 0; @@ -230,9 +233,11 @@ private: FieldHeader::FieldHeader( QWidget *parent, - std::shared_ptr show) + std::shared_ptr show, + Fn hasSendText) : RpWidget(parent) , _show(std::move(show)) +, _hasSendText(std::move(hasSendText)) , _forwardPanel( std::make_unique([=] { customEmojiRepaint(); })) , _data(&_show->session().data()) @@ -406,8 +411,8 @@ void FieldHeader::init() { if (inPreviewRect && isEditingMessage()) { _mediaEditSpoiler.showMenu( this, - _data->message(_editMsgId.current()), - [=](bool) { update(); }); + [=] { update(); }, + _hasSendText()); } else if (const auto reply = replyingToMessage()) { _jumpToItemRequests.fire_copy(reply); } @@ -582,14 +587,12 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { const auto media = _shownMessage->media(); _shownMessageHasPreview = media && media->hasReplyPreview(); - const auto preview = _mediaEditSpoiler.spoilerOverride() - ? _mediaEditSpoiler.mediaPreview(_shownMessage) + const auto preview = _mediaEditSpoiler + ? _mediaEditSpoiler.mediaPreview() : _shownMessageHasPreview ? media->replyPreview() : nullptr; - const auto spoilered = _mediaEditSpoiler.spoilerOverride() - ? (*_mediaEditSpoiler.spoilerOverride()) - : (preview && media->hasSpoiler()); + const auto spoilered = _mediaEditSpoiler.spoilered(); if (!spoilered) { _shownPreviewSpoiler = nullptr; } else if (!_shownPreviewSpoiler) { @@ -734,7 +737,7 @@ void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) { _photoEditAllowed = photoEditAllowed; _editMsgId = id; if (!photoEditAllowed) { - _mediaEditSpoiler.setSpoilerOverride(std::nullopt); + _mediaEditSpoiler.cancel(); _inPhotoEdit = false; _inPhotoEditOver.stop(); } @@ -781,8 +784,9 @@ MessageToEdit FieldHeader::queryToEdit() { .options = { .scheduled = item->isScheduled() ? item->date() : 0, .shortcutId = item->shortcutId(), + .invertCaption = _mediaEditSpoiler.invertCaption(), }, - .spoilerMediaOverride = _mediaEditSpoiler.spoilerOverride(), + .spoilerMediaOverride = _mediaEditSpoiler.spoilered(), }; } @@ -842,7 +846,10 @@ ComposeControls::ComposeControls( parent, _show, &_st.tabbed)) -, _header(std::make_unique(_wrap.get(), _show)) +, _header(std::make_unique( + _wrap.get(), + _show, + [=] { return HasSendText(_field); })) , _voiceRecordBar(std::make_unique( _wrap.get(), Controls::VoiceRecordBarDescriptor{ diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp index d6c58bb6a..f20681a0f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp @@ -10,74 +10,134 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_file_origin.h" #include "data/data_photo.h" +#include "data/data_session.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" +#include "menu/menu_send.h" #include "ui/widgets/popup_menu.h" +#include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" namespace HistoryView { MediaEditSpoilerManager::MediaEditSpoilerManager() = default; +void MediaEditSpoilerManager::start(not_null item) { + const auto media = item->media(); + if (!media) { + return; + } + _item = item; + _spoilered = media->hasSpoiler(); + _invertCaption = item->invertMedia(); + _lifetime = item->history()->owner().itemRemoved( + ) | rpl::start_with_next([=](not_null removed) { + if (removed == _item) { + cancel(); + } + }); +} + +void MediaEditSpoilerManager::apply(SendMenu::Action action) { + using Type = SendMenu::Action::Type; + if (action.type == Type::CaptionUp) { + _invertCaption = true; + } else if (action.type == Type::CaptionDown) { + _invertCaption = false; + } else if (action.type == Type::SpoilerOn) { + _spoilered = true; + } else if (action.type == Type::SpoilerOff) { + _spoilered = false; + } +} + +void MediaEditSpoilerManager::cancel() { + _menu = nullptr; + _item = nullptr; + _lifetime.destroy(); +} + void MediaEditSpoilerManager::showMenu( not_null parent, - not_null item, - Fn callback) { - const auto media = item->media(); + Fn finished, + bool hasCaptionText) { + if (!_item) { + return; + } + const auto media = _item->media(); const auto hasPreview = media && media->hasReplyPreview(); const auto preview = hasPreview ? media->replyPreview() : nullptr; if (!preview || (media && media->webpage())) { return; } - const auto spoilered = _spoilerOverride - ? (*_spoilerOverride) - : (preview && media->hasSpoiler()); - const auto menu = Ui::CreateChild( + _menu = base::make_unique_q( parent, st::popupMenuWithIcons); - menu->addAction( - spoilered - ? tr::lng_context_disable_spoiler(tr::now) - : tr::lng_context_spoiler_effect(tr::now), - [=] { - _spoilerOverride = (!spoilered); - if (callback) { - callback(!spoilered); - } - }, - spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler); - menu->popup(QCursor::pos()); + const auto callback = [=](SendMenu::Action action, const auto &) { + apply(action); + }; + const auto position = QCursor::pos(); + SendMenu::FillSendMenu( + _menu.get(), + nullptr, + sendMenuDetails(hasCaptionText), + callback, + &st::defaultComposeIcons, + position); + _menu->popup(position); } -[[nodiscard]] Image *MediaEditSpoilerManager::mediaPreview( - not_null item) { - if (!_spoilerOverride) { - return nullptr; - } - if (const auto media = item->media()) { +[[nodiscard]] Image *MediaEditSpoilerManager::mediaPreview() { + if (const auto media = _item ? _item->media() : nullptr) { if (const auto photo = media->photo()) { return photo->getReplyPreview( - item->fullId(), - item->history()->peer, - *_spoilerOverride); + _item->fullId(), + _item->history()->peer, + _spoilered); } else if (const auto document = media->document()) { return document->getReplyPreview( - item->fullId(), - item->history()->peer, - *_spoilerOverride); + _item->fullId(), + _item->history()->peer, + _spoilered); } } return nullptr; } -void MediaEditSpoilerManager::setSpoilerOverride( - std::optional spoilerOverride) { - _spoilerOverride = spoilerOverride; +bool MediaEditSpoilerManager::spoilered() const { + return _spoilered; } -std::optional MediaEditSpoilerManager::spoilerOverride() const { - return _spoilerOverride; +bool MediaEditSpoilerManager::invertCaption() const { + return _invertCaption; +} + +SendMenu::Details MediaEditSpoilerManager::sendMenuDetails( + bool hasCaptionText) const { + const auto media = _item ? _item->media() : nullptr; + if (!media) { + return {}; + } + const auto editingMedia = media->allowsEditMedia(); + const auto editPhoto = editingMedia ? media->photo() : nullptr; + const auto editDocument = editingMedia ? media->document() : nullptr; + const auto canSaveSpoiler = (editPhoto && !editPhoto->isNull()) + || (editDocument + && (editDocument->isVideoFile() || editDocument->isGifv())); + const auto canMoveCaption = media->allowsEditCaption() && hasCaptionText; + return { + .spoiler = (!canSaveSpoiler + ? SendMenu::SpoilerState::None + : _spoilered + ? SendMenu::SpoilerState::Enabled + : SendMenu::SpoilerState::Possible), + .caption = (!canMoveCaption + ? SendMenu::CaptionState::None + : _invertCaption + ? SendMenu::CaptionState::Above + : SendMenu::CaptionState::Below), + }; } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h index 8bc34b7c4..0f9f49e2b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h @@ -7,8 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/unique_qptr.h" + +namespace SendMenu { +struct Details; +struct Action; +} // namespace SendMenu + namespace Ui { class RpWidget; +class PopupMenu; } // namespace Ui class Image; @@ -20,19 +28,34 @@ class MediaEditSpoilerManager final { public: MediaEditSpoilerManager(); + void start(not_null item); + void apply(SendMenu::Action action); + void cancel(); + void showMenu( not_null parent, - not_null item, - Fn callback); + Fn finished, + bool hasCaptionText); - [[nodiscard]] Image *mediaPreview(not_null item); + [[nodiscard]] Image *mediaPreview(); - void setSpoilerOverride(std::optional spoilerOverride); + [[nodiscard]] bool spoilered() const; + [[nodiscard]] bool invertCaption() const; - std::optional spoilerOverride() const; + [[nodiscard]] SendMenu::Details sendMenuDetails( + bool hasCaptionText) const; + + [[nodiscard]] explicit operator bool() const { + return _item != nullptr; + } private: - std::optional _spoilerOverride; + base::unique_qptr _menu; + HistoryItem *_item = nullptr; + bool _spoilered = false; + bool _invertCaption = false; + + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 7d11579e9..c4e51f174 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -396,7 +396,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { } // date - if (_parent->media() == this) { + if (_parent->media() == this && isBubbleBottom()) { auto fullRight = width(); auto fullBottom = height(); if (needInfoDisplay()) { diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 4bdd5709b..a1c8909fb 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -615,7 +615,8 @@ FillMenuResult FillSendMenu( const style::ComposeIcons *iconsOverride, std::optional desiredPositionOverride) { const auto type = details.type; - const auto empty = (type == Type::Disabled) + const auto sending = (type != Type::Disabled); + const auto empty = !sending && (details.spoiler == SpoilerState::None) && (details.caption == CaptionState::None); if (empty || !action) { @@ -653,18 +654,16 @@ FillMenuResult FillSendMenu( toggles = true; } if (toggles && type != Type::Disabled) { - menu->addSeparator(); + menu->addSeparator(&st::expandedMenuSeparator); } - if (type != Type::Reminder) { + if (sending && type != Type::Reminder) { menu->addAction( tr::lng_send_silent_message(tr::now), - [=] { action( - { Api::SendOptions{ .silent = true } }, - details); }, + [=] { action({ Api::SendOptions{ .silent = true } }, details); }, &icons.menuMute); } - if (type != Type::SilentOnly) { + if (sending && type != Type::SilentOnly) { menu->addAction( (type == Type::Reminder ? tr::lng_reminder_message(tr::now) @@ -672,7 +671,7 @@ FillMenuResult FillSendMenu( [=] { action({ .type = ActionType::Schedule }, details); }, &icons.menuSchedule); } - if (type == Type::ScheduledToUser) { + if (sending && type == Type::ScheduledToUser) { menu->addAction( tr::lng_scheduled_send_until_online(tr::now), [=] { action( From 974bf999212d79d91a8c954da6506f3dafc3147b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 00:01:18 +0400 Subject: [PATCH 166/225] Allow editing spoiler/caption-above in EditCaptionBox. --- Telegram/SourceFiles/api/api_editing.cpp | 155 +++++++++--------- Telegram/SourceFiles/api/api_editing.h | 2 +- Telegram/SourceFiles/api/api_sending.cpp | 1 + .../SourceFiles/boxes/edit_caption_box.cpp | 70 ++++++-- Telegram/SourceFiles/boxes/edit_caption_box.h | 11 +- Telegram/SourceFiles/boxes/send_files_box.cpp | 11 +- .../SourceFiles/history/history_widget.cpp | 26 +-- Telegram/SourceFiles/history/history_widget.h | 2 +- .../view/controls/compose_controls_common.h | 2 +- .../history_view_compose_controls.cpp | 25 ++- ...istory_view_compose_media_edit_manager.cpp | 43 +++-- .../history_view_compose_media_edit_manager.h | 11 +- .../view/history_view_replies_section.cpp | 6 +- .../view/history_view_replies_section.h | 2 +- .../view/history_view_scheduled_section.cpp | 6 +- .../view/history_view_scheduled_section.h | 2 +- .../business/settings_shortcut_messages.cpp | 8 +- .../attach_abstract_single_media_preview.cpp | 5 + .../attach_abstract_single_media_preview.h | 2 + .../ui/chat/attach/attach_prepare.cpp | 12 ++ .../ui/chat/attach/attach_prepare.h | 1 + 21 files changed, 247 insertions(+), 156 deletions(-) diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 60dfb4c69..b48ead0b8 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_histories.h" #include "data/data_session.h" #include "data/data_web_page.h" +#include "history/view/controls/history_view_compose_media_edit_manager.h" #include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" @@ -255,85 +256,85 @@ mtpRequestId EditTextMessage( SendOptions options, Fn done, Fn fail, - std::optional spoilerMediaOverride) { - if (spoilerMediaOverride) { - const auto spoiler = *spoilerMediaOverride; - if (const auto media = item->media()) { - auto takeInputMedia = Fn()>(nullptr); - auto takeFileReference = Fn(nullptr); - if (const auto photo = media->photo()) { - using Flag = MTPDinputMediaPhoto::Flag; - const auto flags = Flag() - | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag()) - | (spoiler ? Flag::f_spoiler : Flag()); - takeInputMedia = [=] { - return MTP_inputMediaPhoto( - MTP_flags(flags), - photo->mtpInput(), - MTP_int(media->ttlSeconds())); - }; - takeFileReference = [=] { return photo->fileReference(); }; - } else if (const auto document = media->document()) { - using Flag = MTPDinputMediaDocument::Flag; - const auto flags = Flag() - | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag()) - | (spoiler ? Flag::f_spoiler : Flag()); - takeInputMedia = [=] { - return MTP_inputMediaDocument( - MTP_flags(flags), - document->mtpInput(), - MTP_int(media->ttlSeconds()), - MTPstring()); // query - }; - takeFileReference = [=] { return document->fileReference(); }; - } - - const auto usedFileReference = takeFileReference - ? takeFileReference() - : QByteArray(); - const auto origin = item->fullId(); - const auto api = &item->history()->session().api(); - const auto performRequest = [=]( - const auto &repeatRequest, - mtpRequestId originalRequestId) -> mtpRequestId { - const auto handleReference = [=]( - const QString &error, - mtpRequestId requestId) { - if (error.startsWith(u"FILE_REFERENCE_"_q)) { - api->refreshFileReference(origin, [=](const auto &) { - if (takeFileReference && - (takeFileReference() != usedFileReference)) { - repeatRequest( - repeatRequest, - originalRequestId - ? originalRequestId - : requestId); - } else { - fail(error, requestId); - } - }); - } else { - fail(error, requestId); - } - }; - const auto callback = [=]( - Fn applyUpdates, - mtpRequestId requestId) { - applyUpdates(); - done(originalRequestId ? originalRequestId : requestId); - }; - const auto requestId = EditMessage( - item, - caption, - webpage, - options, - callback, - handleReference, - takeInputMedia ? takeInputMedia() : std::nullopt); - return originalRequestId ? originalRequestId : requestId; + bool spoilered) { + const auto media = item->media(); + if (media + && HistoryView::MediaEditManager::CanBeSpoilered(item) + && spoilered != media->hasSpoiler()) { + auto takeInputMedia = Fn()>(nullptr); + auto takeFileReference = Fn(nullptr); + if (const auto photo = media->photo()) { + using Flag = MTPDinputMediaPhoto::Flag; + const auto flags = Flag() + | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag()) + | (spoilered ? Flag::f_spoiler : Flag()); + takeInputMedia = [=] { + return MTP_inputMediaPhoto( + MTP_flags(flags), + photo->mtpInput(), + MTP_int(media->ttlSeconds())); }; - return performRequest(performRequest, 0); + takeFileReference = [=] { return photo->fileReference(); }; + } else if (const auto document = media->document()) { + using Flag = MTPDinputMediaDocument::Flag; + const auto flags = Flag() + | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag()) + | (spoilered ? Flag::f_spoiler : Flag()); + takeInputMedia = [=] { + return MTP_inputMediaDocument( + MTP_flags(flags), + document->mtpInput(), + MTP_int(media->ttlSeconds()), + MTPstring()); // query + }; + takeFileReference = [=] { return document->fileReference(); }; } + + const auto usedFileReference = takeFileReference + ? takeFileReference() + : QByteArray(); + const auto origin = item->fullId(); + const auto api = &item->history()->session().api(); + const auto performRequest = [=]( + const auto &repeatRequest, + mtpRequestId originalRequestId) -> mtpRequestId { + const auto handleReference = [=]( + const QString &error, + mtpRequestId requestId) { + if (error.startsWith(u"FILE_REFERENCE_"_q)) { + api->refreshFileReference(origin, [=](const auto &) { + if (takeFileReference && + (takeFileReference() != usedFileReference)) { + repeatRequest( + repeatRequest, + originalRequestId + ? originalRequestId + : requestId); + } else { + fail(error, requestId); + } + }); + } else { + fail(error, requestId); + } + }; + const auto callback = [=]( + Fn applyUpdates, + mtpRequestId requestId) { + applyUpdates(); + done(originalRequestId ? originalRequestId : requestId); + }; + const auto requestId = EditMessage( + item, + caption, + webpage, + options, + callback, + handleReference, + takeInputMedia ? takeInputMedia() : std::nullopt); + return originalRequestId ? originalRequestId : requestId; + }; + return performRequest(performRequest, 0); } const auto callback = [=](Fn applyUpdates, mtpRequestId id) { diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h index c8d0f6c50..630e1cd8d 100644 --- a/Telegram/SourceFiles/api/api_editing.h +++ b/Telegram/SourceFiles/api/api_editing.h @@ -56,6 +56,6 @@ mtpRequestId EditTextMessage( SendOptions options, Fn done, Fn fail, - std::optional spoilerMediaOverride); + bool spoilered); } // namespace Api diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 8ab3d8bc7..ade419248 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -514,6 +514,7 @@ void SendConfirmedFile( edition.ttl = 0; edition.mtpMedia = &media; edition.textWithEntities = caption; + edition.invertMedia = file->to.options.invertCaption; edition.useSameViews = true; edition.useSameForwards = true; edition.useSameMarkup = true; diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index bb9914f09..d46121c2f 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -176,7 +176,7 @@ void ChooseReplacement( void EditPhotoImage( not_null controller, std::shared_ptr media, - bool wasSpoiler, + bool spoilered, Fn done) { const auto large = media ? media->image(Data::PhotoSize::Large) @@ -199,7 +199,7 @@ void EditPhotoImage( using ImageInfo = Ui::PreparedFileInformation::Image; auto &file = list.files.front(); - file.spoiler = wasSpoiler; + file.spoiler = spoilered; const auto image = std::get_if(&file.information->media); image->modifications = mods; @@ -231,13 +231,13 @@ EditCaptionBox::EditCaptionBox( not_null controller, not_null item, TextWithTags &&text, + bool spoilered, + bool invertCaption, Ui::PreparedList &&list, Fn saved) : _controller(controller) , _historyItem(item) -, _isAllowedEditMedia(item->media() - ? item->media()->allowsEditMedia() - : false) +, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia()) , _albumType(ComputeAlbumType(item)) , _controls(base::make_unique_q(this)) , _scroll(base::make_unique_q(this, st::boxScroll)) @@ -255,6 +255,8 @@ EditCaptionBox::EditCaptionBox( Expects(item->media() != nullptr); Expects(item->media()->allowsEditCaption()); + _mediaEditManager.start(item, spoilered, invertCaption); + _controller->session().data().itemRemoved( _historyItem->fullId() ) | rpl::start_with_next([=] { @@ -268,6 +270,8 @@ void EditCaptionBox::StartMediaReplace( not_null controller, FullMsgId itemId, TextWithTags text, + bool spoilered, + bool invertCaption, Fn saved) { const auto session = &controller->session(); const auto item = session->data().message(itemId); @@ -279,6 +283,8 @@ void EditCaptionBox::StartMediaReplace( controller, item, std::move(text), + spoilered, + invertCaption, std::move(list), std::move(saved))); }; @@ -293,6 +299,8 @@ void EditCaptionBox::StartMediaReplace( FullMsgId itemId, Ui::PreparedList &&list, TextWithTags text, + bool spoilered, + bool invertCaption, Fn saved) { const auto session = &controller->session(); const auto item = session->data().message(itemId); @@ -326,6 +334,8 @@ void EditCaptionBox::StartMediaReplace( controller, item, std::move(text), + spoilered, + invertCaption, std::move(list), std::move(saved))); } @@ -336,14 +346,15 @@ void EditCaptionBox::StartPhotoEdit( std::shared_ptr media, FullMsgId itemId, TextWithTags text, + bool spoilered, + bool invertCaption, Fn saved) { const auto session = &controller->session(); const auto item = session->data().message(itemId); if (!item) { return; } - const auto hasSpoiler = item->media() && item->media()->hasSpoiler(); - EditPhotoImage(controller, media, hasSpoiler, [=]( + EditPhotoImage(controller, media, spoilered, [=]( Ui::PreparedList &&list) mutable { const auto item = session->data().message(itemId); if (!item) { @@ -353,6 +364,8 @@ void EditCaptionBox::StartPhotoEdit( controller, item, std::move(text), + spoilered, + invertCaption, std::move(list), std::move(saved))); }); @@ -363,10 +376,29 @@ void EditCaptionBox::prepare() { addButton(tr::lng_cancel(), [=] { closeBox(); }); const auto details = crl::guard(this, [=] { - return SendMenu::Details(); + auto result = SendMenu::Details(); + const auto allWithSpoilers = ranges::all_of( + _preparedList.files, + &Ui::PreparedFile::spoiler); + result.spoiler = !_preparedList.hasSpoilerMenu(!_asFile) + ? SendMenu::SpoilerState::None + : allWithSpoilers + ? SendMenu::SpoilerState::Enabled + : SendMenu::SpoilerState::Possible; + const auto canMoveCaption = _preparedList.canMoveCaption( + false, + !_asFile + ) && _field && HasSendText(_field); + result.caption = !canMoveCaption + ? SendMenu::CaptionState::None + : _mediaEditManager.invertCaption() + ? SendMenu::CaptionState::Above + : SendMenu::CaptionState::Below; + return result; }); const auto callback = [=](SendMenu::Action action, const auto &) { - + _mediaEditManager.apply(action); + rebuildPreview(); }; SendMenu::SetupMenuAndShortcuts( button, @@ -402,7 +434,6 @@ void EditCaptionBox::rebuildPreview() { applyChanges(); - _previewHasSpoiler = nullptr; if (_preparedList.files.empty()) { const auto media = _historyItem->media(); const auto photo = media->photo(); @@ -436,7 +467,13 @@ void EditCaptionBox::rebuildPreview() { _isPhoto = (media && media->isPhoto()); const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType); if (media && (!withCheckbox || !_asFile)) { - _previewHasSpoiler = [media] { return media->hasSpoiler(); }; + media->spoileredChanges( + ) | rpl::start_with_next([=](bool spoilered) { + _mediaEditManager.apply({ .type = spoilered + ? SendMenu::ActionType::SpoilerOn + : SendMenu::ActionType::SpoilerOff + }); + }, media->lifetime()); _content.reset(media); } else { _content.reset(Ui::CreateChild( @@ -763,10 +800,7 @@ bool EditCaptionBox::setPreparedList(Ui::PreparedList &&list) { } bool EditCaptionBox::hasSpoiler() const { - return _preparedList.files.empty() - ? (_historyItem->media() - && _historyItem->media()->hasSpoiler()) - : _preparedList.files.front().spoiler; + return _mediaEditManager.spoilered(); } void EditCaptionBox::captionResized() { @@ -875,8 +909,8 @@ bool EditCaptionBox::validateLength(const QString &text) const { } void EditCaptionBox::applyChanges() { - if (!_preparedList.files.empty() && _previewHasSpoiler) { - _preparedList.files.front().spoiler = _previewHasSpoiler(); + if (!_preparedList.files.empty()) { + _preparedList.files.front().spoiler = _mediaEditManager.spoilered(); } } @@ -905,7 +939,7 @@ void EditCaptionBox::save() { auto options = Api::SendOptions(); options.scheduled = item->isScheduled() ? item->date() : 0; options.shortcutId = item->shortcutId(); - //options.invertCaption = _invertCaption; + options.invertCaption = _mediaEditManager.invertCaption(); if (!_preparedList.files.empty()) { if ((_albumType != Ui::AlbumType::None) diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index b3a10f43f..af72a250f 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "history/view/controls/history_view_compose_media_edit_manager.h" #include "ui/layers/box_content.h" #include "ui/chat/attach/attach_prepare.h" @@ -37,6 +38,8 @@ public: not_null controller, not_null item, TextWithTags &&text, + bool spoilered, + bool invertCaption, Ui::PreparedList &&list, Fn saved); ~EditCaptionBox(); @@ -45,18 +48,24 @@ public: not_null controller, FullMsgId itemId, TextWithTags text, + bool spoilered, + bool invertCaption, Fn saved); static void StartMediaReplace( not_null controller, FullMsgId itemId, Ui::PreparedList &&list, TextWithTags text, + bool spoilered, + bool invertCaption, Fn saved); static void StartPhotoEdit( not_null controller, std::shared_ptr media, FullMsgId itemId, TextWithTags text, + bool spoilered, + bool invertCaption, Fn saved); protected: @@ -107,7 +116,6 @@ private: const base::unique_qptr _emojiToggle; base::unique_qptr _content; - Fn _previewHasSpoiler; base::unique_qptr _emojiPanel; base::unique_qptr _emojiFilter; @@ -118,6 +126,7 @@ private: std::shared_ptr _photoMedia; Ui::PreparedList _preparedList; + HistoryView::MediaEditManager _mediaEditManager; mtpRequestId _saveRequestId = 0; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 0656488ee..112e7543a 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -595,16 +595,7 @@ bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const { } bool SendFilesBox::hasSpoilerMenu() const { - const auto allAreVideo = !ranges::any_of(_list.files, [](const auto &f) { - using Type = Ui::PreparedFile::Type; - return (f.type != Type::Video); - }); - const auto allAreMedia = !ranges::any_of(_list.files, [](const auto &f) { - using Type = Ui::PreparedFile::Type; - return (f.type != Type::Photo) && (f.type != Type::Video); - }); - return allAreVideo - || (allAreMedia && _sendWay.current().sendImagesAsPhotos()); + return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos()); } void SendFilesBox::applyBlockChanges() { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index bcbada572..44c65a048 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -328,7 +328,7 @@ HistoryWidget::HistoryWidget( || action.type == ActionType::CaptionDown || action.type == ActionType::SpoilerOn || action.type == ActionType::SpoilerOff) { - _mediaEditSpoiler.apply(action); + _mediaEditManager.apply(action); } else if (action.type == ActionType::Send) { send(action.options); } else { @@ -2677,7 +2677,7 @@ void HistoryWidget::setEditMsgId(MsgId msgId) { unregisterDraftSources(); _editMsgId = msgId; if (!msgId) { - _mediaEditSpoiler.cancel(); + _mediaEditManager.cancel(); _canReplaceMedia = false; if (_preview) { _preview->setDisabled(false); @@ -2749,6 +2749,8 @@ bool HistoryWidget::updateReplaceMediaButton() { controller(), { _history->peer->id, _editMsgId }, _field->getTextWithTags(), + _mediaEditManager.spoilered(), + _mediaEditManager.invertCaption(), crl::guard(_list, [=] { cancelEdit(); })); }); }); @@ -4065,10 +4067,10 @@ void HistoryWidget::saveEditMsg() { item, sending, webPageDraft, - { .invertCaption = _mediaEditSpoiler.invertCaption() }, + { .invertCaption = _mediaEditManager.invertCaption() }, done, fail, - _mediaEditSpoiler.spoilered()); + _mediaEditManager.spoilered()); } void HistoryWidget::hideChildWidgets() { @@ -4228,7 +4230,7 @@ SendMenu::Details HistoryWidget::sendMenuDetails() const { SendMenu::Details HistoryWidget::saveMenuDetails() const { return (_editMsgId && _replyEditMsg) - ? _mediaEditSpoiler.sendMenuDetails(HasSendText(_field)) + ? _mediaEditManager.sendMenuDetails(HasSendText(_field)) : SendMenu::Details(); } @@ -5613,6 +5615,8 @@ bool HistoryWidget::confirmSendingFiles( { _history->peer->id, _editMsgId }, std::move(list), _field->getTextWithTags(), + _mediaEditManager.spoilered(), + _mediaEditManager.invertCaption(), crl::guard(_list, [=] { cancelEdit(); })); return true; } @@ -6599,7 +6603,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { if (_editMsgId && (_inDetails || _inPhotoEdit) && (e->button() == Qt::RightButton)) { - _mediaEditSpoiler.showMenu( + _mediaEditManager.showMenu( _list, [=] { mouseMoveEvent(nullptr); }, HasSendText(_field)); @@ -6609,6 +6613,8 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { _photoEditMedia, { _history->peer->id, _editMsgId }, _field->getTextWithTags(), + _mediaEditManager.spoilered(), + _mediaEditManager.invertCaption(), crl::guard(_list, [=] { cancelEdit(); })); } else if (!_inDetails) { return; @@ -8186,7 +8192,7 @@ void HistoryWidget::updateReplyEditTexts(bool force) { ? _replyEditMsg->media() : nullptr; if (_editMsgId && _replyEditMsg) { - _mediaEditSpoiler.start(_replyEditMsg); + _mediaEditManager.start(_replyEditMsg); } _canReplaceMedia = editMedia && editMedia->allowsEditMedia(); _photoEditMedia = (_canReplaceMedia @@ -8281,12 +8287,12 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { ? drawMsgText->media() : nullptr; const auto hasPreview = media && media->hasReplyPreview(); - const auto preview = _mediaEditSpoiler - ? _mediaEditSpoiler.mediaPreview() + const auto preview = _mediaEditManager + ? _mediaEditManager.mediaPreview() : hasPreview ? media->replyPreview() : nullptr; - const auto spoilered = _mediaEditSpoiler.spoilered(); + const auto spoilered = _mediaEditManager.spoilered(); if (!spoilered) { _replySpoiler = nullptr; } else if (!_replySpoiler) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index f829cf797..e9453a621 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -661,7 +661,7 @@ private: MsgId _editMsgId = 0; std::shared_ptr _photoEditMedia; bool _canReplaceMedia = false; - HistoryView::MediaEditSpoilerManager _mediaEditSpoiler; + HistoryView::MediaEditManager _mediaEditManager; HistoryItem *_replyEditMsg = nullptr; Ui::Text::String _replyEditMsgText; diff --git a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h index 01237072b..e51bae134 100644 --- a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h +++ b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h @@ -21,7 +21,7 @@ struct MessageToEdit { FullMsgId fullId; Api::SendOptions options; TextWithTags textWithTags; - std::optional spoilerMediaOverride; + bool spoilered = false; }; struct VoiceToSend { QByteArray bytes; 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 6c985bbf0..ac381222c 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -216,7 +216,7 @@ private: bool _repaintScheduled : 1 = false; bool _inClickable : 1 = false; - HistoryView::MediaEditSpoilerManager _mediaEditSpoiler; + HistoryView::MediaEditManager _mediaEditManager; const not_null _data; const not_null _cancel; @@ -409,7 +409,7 @@ void FieldHeader::init() { } } else if (!isLeftButton) { if (inPreviewRect && isEditingMessage()) { - _mediaEditSpoiler.showMenu( + _mediaEditManager.showMenu( this, [=] { update(); }, _hasSendText()); @@ -587,12 +587,12 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { const auto media = _shownMessage->media(); _shownMessageHasPreview = media && media->hasReplyPreview(); - const auto preview = _mediaEditSpoiler - ? _mediaEditSpoiler.mediaPreview() + const auto preview = _mediaEditManager + ? _mediaEditManager.mediaPreview() : _shownMessageHasPreview ? media->replyPreview() : nullptr; - const auto spoilered = _mediaEditSpoiler.spoilered(); + const auto spoilered = _mediaEditManager.spoilered(); if (!spoilered) { _shownPreviewSpoiler = nullptr; } else if (!_shownPreviewSpoiler) { @@ -737,7 +737,7 @@ void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) { _photoEditAllowed = photoEditAllowed; _editMsgId = id; if (!photoEditAllowed) { - _mediaEditSpoiler.cancel(); + _mediaEditManager.cancel(); _inPhotoEdit = false; _inPhotoEditOver.stop(); } @@ -784,9 +784,9 @@ MessageToEdit FieldHeader::queryToEdit() { .options = { .scheduled = item->isScheduled() ? item->date() : 0, .shortcutId = item->shortcutId(), - .invertCaption = _mediaEditSpoiler.invertCaption(), + .invertCaption = _mediaEditManager.invertCaption(), }, - .spoilerMediaOverride = _mediaEditSpoiler.spoilered(), + .spoilered = _mediaEditManager.spoilered(), }; } @@ -1138,11 +1138,14 @@ bool ComposeControls::confirmMediaEdit(Ui::PreparedList &list) { if (!isEditingMessage() || !_regularWindow) { return false; } else if (_canReplaceMedia) { + const auto queryToEdit = _header->queryToEdit(); EditCaptionBox::StartMediaReplace( _regularWindow, _editingId, std::move(list), _field->getTextWithTags(), + queryToEdit.spoilered, + queryToEdit.options.invertCaption, crl::guard(_wrap.get(), [=] { cancelEditMessage(); })); } else { _show->showToast(tr::lng_edit_caption_attach(tr::now)); @@ -1401,11 +1404,14 @@ void ComposeControls::init() { _header->editPhotoRequests( ) | rpl::start_with_next([=] { + const auto queryToEdit = _header->queryToEdit(); EditCaptionBox::StartPhotoEdit( _regularWindow, _photoEditMedia, _editingId, _field->getTextWithTags(), + queryToEdit.spoilered, + queryToEdit.options.invertCaption, crl::guard(_wrap.get(), [=] { cancelEditMessage(); })); }, _wrap->lifetime()); @@ -2943,10 +2949,13 @@ bool ComposeControls::updateReplaceMediaButton() { const auto hideDuration = st::historyReplaceMedia.ripple.hideDuration; _replaceMedia->setClickedCallback([=] { base::call_delayed(hideDuration, _wrap.get(), [=] { + const auto queryToEdit = _header->queryToEdit(); EditCaptionBox::StartMediaReplace( _regularWindow, _editingId, _field->getTextWithTags(), + queryToEdit.spoilered, + queryToEdit.options.invertCaption, crl::guard(_wrap.get(), [=] { cancelEditMessage(); })); }); }); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp index f20681a0f..dd2fa9893 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp @@ -21,16 +21,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { -MediaEditSpoilerManager::MediaEditSpoilerManager() = default; +MediaEditManager::MediaEditManager() = default; -void MediaEditSpoilerManager::start(not_null item) { +void MediaEditManager::start( + not_null item, + std::optional spoilered, + std::optional invertCaption) { const auto media = item->media(); if (!media) { return; } _item = item; - _spoilered = media->hasSpoiler(); - _invertCaption = item->invertMedia(); + _spoilered = spoilered.value_or(media->hasSpoiler()); + _invertCaption = invertCaption.value_or(item->invertMedia()); _lifetime = item->history()->owner().itemRemoved( ) | rpl::start_with_next([=](not_null removed) { if (removed == _item) { @@ -39,7 +42,7 @@ void MediaEditSpoilerManager::start(not_null item) { }); } -void MediaEditSpoilerManager::apply(SendMenu::Action action) { +void MediaEditManager::apply(SendMenu::Action action) { using Type = SendMenu::Action::Type; if (action.type == Type::CaptionUp) { _invertCaption = true; @@ -52,13 +55,13 @@ void MediaEditSpoilerManager::apply(SendMenu::Action action) { } } -void MediaEditSpoilerManager::cancel() { +void MediaEditManager::cancel() { _menu = nullptr; _item = nullptr; _lifetime.destroy(); } -void MediaEditSpoilerManager::showMenu( +void MediaEditManager::showMenu( not_null parent, Fn finished, bool hasCaptionText) { @@ -88,7 +91,7 @@ void MediaEditSpoilerManager::showMenu( _menu->popup(position); } -[[nodiscard]] Image *MediaEditSpoilerManager::mediaPreview() { +Image *MediaEditManager::mediaPreview() { if (const auto media = _item ? _item->media() : nullptr) { if (const auto photo = media->photo()) { return photo->getReplyPreview( @@ -105,15 +108,15 @@ void MediaEditSpoilerManager::showMenu( return nullptr; } -bool MediaEditSpoilerManager::spoilered() const { +bool MediaEditManager::spoilered() const { return _spoilered; } -bool MediaEditSpoilerManager::invertCaption() const { +bool MediaEditManager::invertCaption() const { return _invertCaption; } -SendMenu::Details MediaEditSpoilerManager::sendMenuDetails( +SendMenu::Details MediaEditManager::sendMenuDetails( bool hasCaptionText) const { const auto media = _item ? _item->media() : nullptr; if (!media) { @@ -122,10 +125,12 @@ SendMenu::Details MediaEditSpoilerManager::sendMenuDetails( const auto editingMedia = media->allowsEditMedia(); const auto editPhoto = editingMedia ? media->photo() : nullptr; const auto editDocument = editingMedia ? media->document() : nullptr; - const auto canSaveSpoiler = (editPhoto && !editPhoto->isNull()) - || (editDocument + const auto canSaveSpoiler = CanBeSpoilered(_item); + const auto canMoveCaption = media->allowsEditCaption() + && hasCaptionText + && (editPhoto + || editDocument && (editDocument->isVideoFile() || editDocument->isGifv())); - const auto canMoveCaption = media->allowsEditCaption() && hasCaptionText; return { .spoiler = (!canSaveSpoiler ? SendMenu::SpoilerState::None @@ -140,4 +145,14 @@ SendMenu::Details MediaEditSpoilerManager::sendMenuDetails( }; } +bool MediaEditManager::CanBeSpoilered(not_null item) { + const auto media = item ? item->media() : nullptr; + const auto editingMedia = media && media->allowsEditMedia(); + const auto editPhoto = editingMedia ? media->photo() : nullptr; + const auto editDocument = editingMedia ? media->document() : nullptr; + return (editPhoto && !editPhoto->isNull()) + || (editDocument + && (editDocument->isVideoFile() || editDocument->isGifv())); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h index 0f9f49e2b..0ca33f854 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h @@ -24,11 +24,14 @@ class HistoryItem; namespace HistoryView { -class MediaEditSpoilerManager final { +class MediaEditManager final { public: - MediaEditSpoilerManager(); + MediaEditManager(); - void start(not_null item); + void start( + not_null item, + std::optional spoilered = {}, + std::optional invertCaption = {}); void apply(SendMenu::Action action); void cancel(); @@ -49,6 +52,8 @@ public: return _item != nullptr; } + [[nodiscard]] static bool CanBeSpoilered(not_null item); + private: base::unique_qptr _menu; HistoryItem *_item = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 7f7ded51a..27ff0e4ff 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -740,7 +740,7 @@ void RepliesWidget::setupComposeControls() { _composeControls->editRequests( ) | rpl::start_with_next([=](auto data) { if (const auto item = session().data().message(data.fullId)) { - const auto spoiler = data.spoilerMediaOverride; + const auto spoiler = data.spoilered; edit(item, data.options, saveEditMsgRequestId, spoiler); } }, lifetime()); @@ -1213,7 +1213,7 @@ void RepliesWidget::edit( not_null item, Api::SendOptions options, mtpRequestId *const saveEditMsgRequestId, - std::optional spoilerMediaOverride) { + bool spoilered) { if (*saveEditMsgRequestId) { return; } @@ -1282,7 +1282,7 @@ void RepliesWidget::edit( options, crl::guard(this, done), crl::guard(this, fail), - spoilerMediaOverride); + spoilered); _composeControls->hidePanelsAnimated(); doSetInnerFocus(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 54f02ef16..37c8540fd 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -247,7 +247,7 @@ private: not_null item, Api::SendOptions options, mtpRequestId *const saveEditMsgRequestId, - std::optional spoilerMediaOverride); + bool spoilered); void chooseAttach(std::optional overrideSendImagesAsPhotos); [[nodiscard]] SendMenu::Details sendMenuDetails() const; [[nodiscard]] FullReplyTo replyTo() const; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 6535958be..bd4a1123b 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -320,7 +320,7 @@ void ScheduledWidget::setupComposeControls() { ) | rpl::start_with_next([=](auto data) { if (const auto item = session().data().message(data.fullId)) { if (item->isScheduled()) { - const auto spoiler = data.spoilerMediaOverride; + const auto spoiler = data.spoilered; edit(item, data.options, saveEditMsgRequestId, spoiler); } } @@ -733,7 +733,7 @@ void ScheduledWidget::edit( not_null item, Api::SendOptions options, mtpRequestId *const saveEditMsgRequestId, - std::optional spoilerMediaOverride) { + bool spoilered) { if (*saveEditMsgRequestId) { return; } @@ -802,7 +802,7 @@ void ScheduledWidget::edit( options, crl::guard(this, done), crl::guard(this, fail), - spoilerMediaOverride); + spoilered); _composeControls->hidePanelsAnimated(); _composeControls->focus(); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index e79304691..b2b0efa41 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -218,7 +218,7 @@ private: not_null item, Api::SendOptions options, mtpRequestId *const saveEditMsgRequestId, - std::optional spoilerMediaOverride); + bool spoilered); void highlightSingleNewMessage(const Data::MessagesSlice &slice); void chooseAttach(); [[nodiscard]] SendMenu::Details sendMenuDetails() const; diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp index ccbb1aea2..96c6cb841 100644 --- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp +++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp @@ -240,7 +240,7 @@ private: not_null item, Api::SendOptions options, mtpRequestId *const saveEditMsgRequestId, - std::optional spoilerMediaOverride); + bool spoilered); void chooseAttach(std::optional overrideSendImagesAsPhotos); [[nodiscard]] FullReplyTo replyTo() const; void doSetInnerFocus(); @@ -675,7 +675,7 @@ void ShortcutMessages::setupComposeControls() { ) | rpl::start_with_next([=](auto data) { if (const auto item = _session->data().message(data.fullId)) { if (item->isBusinessShortcut()) { - const auto spoiler = data.spoilerMediaOverride; + const auto spoiler = data.spoilered; edit(item, data.options, saveEditMsgRequestId, spoiler); } } @@ -1221,7 +1221,7 @@ void ShortcutMessages::edit( not_null item, Api::SendOptions options, mtpRequestId *const saveEditMsgRequestId, - std::optional spoilerMediaOverride) { + bool spoilered) { if (*saveEditMsgRequestId) { return; } @@ -1290,7 +1290,7 @@ void ShortcutMessages::edit( options, crl::guard(this, done), crl::guard(this, fail), - spoilerMediaOverride); + spoilered); _composeControls->hidePanelsAnimated(); doSetInnerFocus(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp index 29f38cdde..01b94a9d1 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp @@ -78,6 +78,10 @@ bool AbstractSingleMediaPreview::canHaveSpoiler() const { return supportsSpoilers(); } +rpl::producer AbstractSingleMediaPreview::spoileredChanges() const { + return _spoileredChanges.events(); +} + void AbstractSingleMediaPreview::preparePreview(QImage preview) { auto maxW = 0; auto maxH = 0; @@ -275,6 +279,7 @@ void AbstractSingleMediaPreview::showContextMenu(QPoint position) { ? tr::lng_context_disable_spoiler(tr::now) : tr::lng_context_spoiler_effect(tr::now), [=] { setSpoiler(!spoilered); + _spoileredChanges.fire_copy(!spoilered); }, spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler); if (_menu->empty()) { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h index 901815136..0f5f8e784 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h @@ -41,6 +41,7 @@ public: void setSpoiler(bool spoiler); [[nodiscard]] bool hasSpoiler() const; [[nodiscard]] bool canHaveSpoiler() const; + [[nodiscard]] rpl::producer spoileredChanges() const; protected: virtual bool supportsSpoilers() const = 0; @@ -79,6 +80,7 @@ private: int _previewHeight = 0; std::unique_ptr _spoiler; + rpl::event_stream _spoileredChanges; const int _minThumbH; const base::unique_qptr _controls; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index ebdf9ac6f..1a70c69b6 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -237,6 +237,18 @@ bool PreparedList::hasSticker() const { return ranges::any_of(files, &PreparedFile::isSticker); } +bool PreparedList::hasSpoilerMenu(bool compress) const { + const auto allAreVideo = !ranges::any_of(files, [](const auto &f) { + using Type = Ui::PreparedFile::Type; + return (f.type != Type::Video); + }); + const auto allAreMedia = !ranges::any_of(files, [](const auto &f) { + using Type = Ui::PreparedFile::Type; + return (f.type != Type::Photo) && (f.type != Type::Video); + }); + return allAreVideo || (allAreMedia && compress); +} + int MaxAlbumItems() { return kMaxAlbumCount; } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index e467cc611..30210e3d6 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -123,6 +123,7 @@ struct PreparedList { [[nodiscard]] bool hasSendImagesAsPhotosOption(bool slowmode) const; [[nodiscard]] bool canHaveEditorHintLabel() const; [[nodiscard]] bool hasSticker() const; + [[nodiscard]] bool hasSpoilerMenu(bool compress) const; Error error = Error::None; QString errorData; From 40fbd415ef8a4d0c287a5fadf379bdb7699edd44 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 15:05:13 +0400 Subject: [PATCH 167/225] Support collapsible blockquotes in Ui::Text::String. --- Telegram/CMakeLists.txt | 4 ++-- Telegram/SourceFiles/api/api_text_entities.cpp | 8 +++++++- .../SourceFiles/chat_helpers/message_field.cpp | 3 ++- Telegram/SourceFiles/data/data_session.cpp | 15 +++++++++++---- .../SourceFiles/export/data/export_data_types.cpp | 2 ++ .../export/output/export_output_json.cpp | 4 ++++ .../history/view/history_view_element.cpp | 10 ++++++++-- .../history/view/history_view_element.h | 1 + ...k_handler.cpp => history_view_text_helper.cpp} | 14 ++++++++++---- ...click_handler.h => history_view_text_helper.h} | 4 +--- .../history/view/media/history_view_document.cpp | 8 ++++++-- .../history/view/media/history_view_media.cpp | 4 ++-- .../view/media/history_view_media_grouped.cpp | 8 +++++++- .../history/view/media/history_view_web_page.cpp | 15 +++++++++------ Telegram/SourceFiles/ui/chat/chat.style | 13 +++++++++++-- Telegram/lib_ui | 2 +- 16 files changed, 84 insertions(+), 31 deletions(-) rename Telegram/SourceFiles/history/view/{history_view_spoiler_click_handler.cpp => history_view_text_helper.cpp} (68%) rename Telegram/SourceFiles/history/view/{history_view_spoiler_click_handler.h => history_view_text_helper.h} (78%) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6e786a6cd..fa06b446a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -837,14 +837,14 @@ PRIVATE history/view/history_view_send_action.h history/view/history_view_service_message.cpp history/view/history_view_service_message.h - history/view/history_view_spoiler_click_handler.cpp - history/view/history_view_spoiler_click_handler.h history/view/history_view_sponsored_click_handler.cpp history/view/history_view_sponsored_click_handler.h history/view/history_view_sticker_toast.cpp history/view/history_view_sticker_toast.h history/view/history_view_sublist_section.cpp history/view/history_view_sublist_section.h + history/view/history_view_text_helper.cpp + history/view/history_view_text_helper.h history/view/history_view_transcribe_button.cpp history/view/history_view_transcribe_button.h history/view/history_view_translate_bar.cpp diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 3c5098911..93d5cc5f3 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -217,6 +217,7 @@ EntitiesInText EntitiesFromMTP( EntityType::Blockquote, d.voffset().v, d.vlength().v, + d.is_collapsed() ? u"1"_q : QString(), }); }); } @@ -311,8 +312,13 @@ MTPVector EntitiesToMTP( MTP_string(entity.data()))); } break; case EntityType::Blockquote: { + using Flag = MTPDmessageEntityBlockquote::Flag; + const auto collapsed = !entity.data().isEmpty(); v.push_back( - MTP_messageEntityBlockquote(MTP_flags(0), offset, length)); + MTP_messageEntityBlockquote( + MTP_flags(collapsed ? Flag::f_collapsed : Flag()), + offset, + length)); } break; case EntityType::Spoiler: { v.push_back(MTP_messageEntitySpoiler(offset, length)); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 836459ad8..8b5f3c373 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -748,7 +748,8 @@ void MessageLinksParser::parse() { || (tag == Ui::InputField::kTagUnderline) || (tag == Ui::InputField::kTagStrikeOut) || (tag == Ui::InputField::kTagSpoiler) - || (tag == Ui::InputField::kTagBlockquote); + || (tag == Ui::InputField::kTagBlockquote) + || (tag == Ui::InputField::kTagBlockquoteCollapsed); }; _ranges.clear(); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 529b933d4..dba1c3d69 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1820,12 +1820,19 @@ rpl::producer> Session::itemDataChanges() const { } void Session::requestItemTextRefresh(not_null item) { - if (const auto i = _views.find(item); i != _views.end()) { - for (const auto &view : i->second) { - view->itemTextUpdated(); + const auto call = [&](not_null item) { + if (const auto i = _views.find(item); i != _views.end()) { + for (const auto &view : i->second) { + view->itemTextUpdated(); + } } - requestItemResize(item); + }; + if (const auto group = groups().find(item)) { + call(group->items.front()); + } else { + call(item); } + requestItemResize(item); } void Session::registerHighlightProcess( diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index c4b55f346..71750a575 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -307,6 +307,8 @@ std::vector ParseText( return NumberToString(data.vuser_id().v); }, [](const MTPDmessageEntityCustomEmoji &data) { return NumberToString(data.vdocument_id().v); + }, [](const MTPDmessageEntityBlockquote &data) { + return data.is_collapsed() ? Utf8String("1") : Utf8String(); }, [](const auto &) { return Utf8String(); }); result.push_back(std::move(part)); diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 67814e5b2..b485e615b 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -195,6 +195,8 @@ QByteArray SerializeText( ? "language" : (part.type == Type::TextUrl) ? "href" + : (part.type == Type::Blockquote) + ? "collapsed" : "none"; const auto additionalValue = (part.type == Type::MentionName) ? part.additional @@ -202,6 +204,8 @@ QByteArray SerializeText( || part.type == Type::TextUrl || part.type == Type::CustomEmoji) ? SerializeString(part.additional) + : (part.type == Type::Blockquote) + ? (part.additional.isEmpty() ? "false" : "true") : QByteArray(); return SerializeObject(context, { { "type", SerializeString(typeString) }, diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 3cdb44ed0..df32ee082 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/reactions/history_view_reactions.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_reply.h" -#include "history/view/history_view_spoiler_click_handler.h" +#include "history/view/history_view_text_helper.h" #include "history/history.h" #include "history/history_item_components.h" #include "history/history_item_helpers.h" @@ -1027,7 +1027,7 @@ void Element::setTextWithLinks( refreshMedia(nullptr); } } - FillTextWithAnimatedSpoilers(this, _text); + InitElementTextPart(this, _text); _textWidth = -1; _textHeight = 0; } @@ -1464,6 +1464,12 @@ void Element::itemTextUpdated() { } } +void Element::blockquoteExpandChanged() { + _textWidth = -1; + _textHeight = 0; + history()->owner().requestViewResize(this); +} + void Element::unloadHeavyPart() { history()->owner().unregisterHeavyViewPart(this); if (_media) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index fdac9a583..0c01e9767 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -498,6 +498,7 @@ public: virtual void itemDataChanged(); void itemTextUpdated(); + void blockquoteExpandChanged(); [[nodiscard]] virtual bool hasHeavyPart() const; virtual void unloadHeavyPart(); diff --git a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp b/Telegram/SourceFiles/history/view/history_view_text_helper.cpp similarity index 68% rename from Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp rename to Telegram/SourceFiles/history/view/history_view_text_helper.cpp index eb72db0f2..fbe97ed56 100644 --- a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp +++ b/Telegram/SourceFiles/history/view/history_view_text_helper.cpp @@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/view/history_view_spoiler_click_handler.h" +#include "history/view/history_view_text_helper.h" #include "data/data_session.h" #include "history/view/history_view_element.h" @@ -15,9 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { -void FillTextWithAnimatedSpoilers( - not_null view, - Ui::Text::String &text) { +void InitElementTextPart(not_null view, Ui::Text::String &text) { if (text.hasSpoilers()) { text.setSpoilerLinkFilter([weak = base::make_weak(view)]( const ClickContext &context) { @@ -30,6 +28,14 @@ void FillTextWithAnimatedSpoilers( return true; }); } + if (text.hasCollapsedBlockquots()) { + const auto weak = base::make_weak(view); + text.setBlockquoteExpandCallback([=](int quoteIndex, bool expanded) { + if (const auto view = weak.get()) { + view->blockquoteExpandChanged(); + } + }); + } } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.h b/Telegram/SourceFiles/history/view/history_view_text_helper.h similarity index 78% rename from Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.h rename to Telegram/SourceFiles/history/view/history_view_text_helper.h index 3bef398b6..d7a604b53 100644 --- a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.h +++ b/Telegram/SourceFiles/history/view/history_view_text_helper.h @@ -11,8 +11,6 @@ namespace HistoryView { class Element; -void FillTextWithAnimatedSpoilers( - not_null view, - Ui::Text::String &text); +void InitElementTextPart(not_null view, Ui::Text::String &text); } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index e415264b3..b1c972053 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -1490,8 +1490,12 @@ QMargins Document::bubbleMargins() const { } void Document::refreshCaption(bool last) { + const auto now = Get(); auto caption = createCaption(); if (!caption.isEmpty()) { + if (now) { + return; + } AddComponents(HistoryDocumentCaptioned::Bit()); auto captioned = Get(); captioned->caption = std::move(caption); @@ -1503,7 +1507,7 @@ void Document::refreshCaption(bool last) { } else { captioned->caption.removeSkipBlock(); } - } else { + } else if (now) { RemoveComponents(HistoryDocumentCaptioned::Bit()); } } @@ -1637,7 +1641,7 @@ void Document::refreshParentId(not_null realParent) { } void Document::parentTextUpdated() { - history()->owner().requestViewResize(_parent); + RemoveComponents(HistoryDocumentCaptioned::Bit()); } void Document::hideSpoilers() { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 65c209153..4f8c8096b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" -#include "history/view/history_view_spoiler_click_handler.h" +#include "history/view/history_view_text_helper.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_media_spoiler.h" #include "storage/storage_shared_media.h" @@ -319,7 +319,7 @@ Ui::Text::String Media::createCaption(not_null item) const { item->translatedTextWithLocalEntities(), Ui::ItemTextOptions(item), context); - FillTextWithAnimatedSpoilers(_parent, result); + InitElementTextPart(_parent, result); if (const auto width = _parent->skipBlockWidth()) { result.updateSkipBlock(width, _parent->skipBlockHeight()); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index c4e51f174..b1c8ba33d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -781,7 +781,13 @@ void GroupedMedia::unloadHeavyPart() { void GroupedMedia::parentTextUpdated() { if (_parent->media() == this) { - _captionItem = std::nullopt; + if (_mode == Mode::Column) { + for (const auto &part : _parts) { + part.content->parentTextUpdated(); + } + } else { + _captionItem = std::nullopt; + } } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 0275ed2c2..44863dd41 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -282,7 +282,9 @@ WebPage::WebPage( not_null data, MediaWebPageFlags flags) : Media(parent) -, _st(st::historyPagePreview) +, _st(data->type == WebPageType::Factcheck + ? st::factcheckPage + : st::historyPagePreview) , _data(data) , _flags(flags) , _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right()) @@ -859,13 +861,14 @@ void WebPage::draw(Painter &p, const PaintContext &context) const { FillBackgroundEmoji(p, outer, false, *backgroundEmojiCache); } } else if (factcheck && factcheck->expandable) { - const auto &icon = factcheck->expanded - ? st::factcheckIconCollapse - : st::factcheckIconExpand; + const auto &icon = factcheck->expanded ? _st.collapse : _st.expand; + const auto &position = factcheck->expanded + ? _st.collapsePosition + : _st.expandPosition; icon.paint( p, - outer.x() + outer.width() - icon.width() - _st.padding.right(), - outer.y() + _st.padding.top(), + outer.x() + outer.width() - icon.width() - position.x(), + outer.y() + outer.height() - icon.height() - position.y(), width()); } diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 3069b6f90..622ba8357 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -66,6 +66,10 @@ messageTextStyle: TextStyle(defaultTextStyle) { padding: margins(10px, 2px, 20px, 2px); icon: icon{{ "chat/mini_quote", windowFg }}; iconPosition: point(4px, 4px); + expand: icon{{ "intro_country_dropdown", windowFg }}; + expandPosition: point(6px, 4px); + collapse: icon{{ "intro_country_dropdown-flip_vertical", windowFg }}; + collapsePosition: point(6px, 4px); } pre: QuoteStyle(messageQuoteStyle) { header: 20px; @@ -1137,8 +1141,13 @@ factcheckFooterSkip: 16px; factcheckFooterStyle: TextStyle(defaultTextStyle) { font: font(11px); } -factcheckIconExpand: icon {{ "fast_to_original-rotate_cw", historyPeer1NameFg }}; -factcheckIconCollapse: icon {{ "fast_to_original-rotate_ccw", historyPeer1NameFg }}; +factcheckPage: QuoteStyle(historyPagePreview) { + padding: margins(10px, 5px, 22px, 7px); + expand: icon {{ "intro_country_dropdown", historyPeer1NameFg }}; + expandPosition: point(6px, 4px); + collapse: icon {{ "intro_country_dropdown-flip_vertical", historyPeer1NameFg }}; + collapsePosition: point(6px, 4px); +} factcheckField: InputField(defaultInputField) { textBg: transparent; textMargins: margins(0px, 0px, 0px, 4px); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 0835adcc2..495ea0af5 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 0835adcc2d3cb1f2cf0d3630f6d95485864621f9 +Subproject commit 495ea0af50da469fb30769ac2d78251e4279a746 From 4df5372dab24d5d459c8a14f1059dd96e5b761e1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 22:33:24 +0400 Subject: [PATCH 168/225] Support chat preview on touchscreens. --- .../dialogs/dialogs_inner_widget.cpp | 44 +++++++++++++++++++ .../dialogs/dialogs_inner_widget.h | 8 ++++ .../SourceFiles/dialogs/dialogs_widget.cpp | 13 ++++++ Telegram/SourceFiles/dialogs/dialogs_widget.h | 1 + Telegram/lib_ui | 2 +- 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f90993a19..7633e4d2f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1316,6 +1316,9 @@ void InnerWidget::paintSearchInTopic( } void InnerWidget::mouseMoveEvent(QMouseEvent *e) { + if (_chatPreviewTouchGlobal) { + return; + } const auto globalPosition = e->globalPos(); if (!_lastMousePosition) { _lastMousePosition = globalPosition; @@ -1333,6 +1336,8 @@ void InnerWidget::mouseMoveEvent(QMouseEvent *e) { void InnerWidget::cancelChatPreview() { _chatPreviewTimer.cancel(); _chatPreviewWillBeFor = {}; + _chatPreviewTouchLocal = {}; + _chatPreviewTouchGlobal = {}; } void InnerWidget::clearIrrelevantState() { @@ -2396,10 +2401,14 @@ void InnerWidget::fillArchiveSearchMenu(not_null menu) { void InnerWidget::showChatPreview(bool onlyUserpic) { const auto key = base::take(_chatPreviewWillBeFor); + const auto touchGlobal = base::take(_chatPreviewTouchGlobal); cancelChatPreview(); if (!pressShowsPreview(onlyUserpic) || key != computeChatPreviewRow()) { return; } + if (onlyUserpic && touchGlobal) { + _touchCancelRequests.fire({}); + } ClickHandler::unpressed(); mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier); @@ -2538,6 +2547,41 @@ void InnerWidget::parentGeometryChanged() { } } +void InnerWidget::processTouchEvent(not_null e) { + const auto point = e->touchPoints().empty() + ? std::optional() + : e->touchPoints().front().screenPos().toPoint(); + switch (e->type()) { + case QEvent::TouchBegin: { + if (!point) { + return; + } + selectByMouse(*point); + const auto onlyUserpic = true; + if (pressShowsPreview(onlyUserpic)) { + _chatPreviewTouchGlobal = point; + _chatPreviewWillBeFor = computeChatPreviewRow(); + _chatPreviewTimer.callOnce(kChatPreviewDelay); + } + } break; + + case QEvent::TouchUpdate: { + if (!_chatPreviewTouchGlobal || !point) { + return; + } + const auto delta = (*_chatPreviewTouchGlobal - *point); + if (delta.manhattanLength() > _st->photoSize) { + cancelChatPreview(); + } + } break; + + case QEvent::TouchEnd: + case QEvent::TouchCancel: if (_chatPreviewTouchGlobal) { + cancelChatPreview(); + } break; + } +} + void InnerWidget::applySearchState(SearchState state) { if (_searchState == state) { return; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index b41a4aeb3..ed94e0b58 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -174,6 +174,11 @@ public: void parentGeometryChanged(); + void processTouchEvent(not_null e); + [[nodiscard]] rpl::producer<> touchCancelRequests() const { + return _touchCancelRequests.events(); + } + protected: void visibleTopBottomUpdated( int visibleTop, @@ -520,6 +525,9 @@ private: base::Timer _chatPreviewTimer; Key _chatPreviewWillBeFor; Key _chatPreviewKey; + std::optional _chatPreviewTouchLocal; + std::optional _chatPreviewTouchGlobal; + rpl::event_stream<> _touchCancelRequests; rpl::variable _childListShown; float64 _narrowRatio = 0.; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index b3b5a31f9..092f2f4cc 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -473,6 +473,7 @@ Widget::Widget( updateSearchFromVisibility(true); setupSupportMode(); setupScrollUpButton(); + setupTouchChatPreview(); const auto overscrollBg = [=] { return anim::color( @@ -655,6 +656,18 @@ void Widget::setupScrollUpButton() { updateScrollUpVisibility(); } +void Widget::setupTouchChatPreview() { + _scroll->setCustomTouchProcess([=](not_null e) { + _inner->processTouchEvent(e); + return false; + }); + _inner->touchCancelRequests() | rpl::start_with_next([=] { + QTouchEvent ev(QEvent::TouchCancel); + ev.setTimestamp(crl::now()); + QGuiApplication::sendEvent(_scroll, &ev); + }, _inner->lifetime()); +} + void Widget::setupMoreChatsBar() { if (_layout == Layout::Child) { return; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index ec604f61c..d7e8edaeb 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -179,6 +179,7 @@ private: [[nodiscard]] const std::vector &searchInTags() const; void setupSupportMode(); + void setupTouchChatPreview(); void setupConnectingWidget(); void setupMainMenuToggle(); void setupMoreChatsBar(); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 495ea0af5..33aac93b1 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 495ea0af50da469fb30769ac2d78251e4279a746 +Subproject commit 33aac93b160d4cd30119c8859de722e28512902b From 4953246c5dfd5a4179733db7ff75949866d3bd53 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 23:22:03 +0400 Subject: [PATCH 169/225] Support touchscreen pinned chats reordering. --- .../dialogs/dialogs_inner_widget.cpp | 106 ++++++++++++++---- .../dialogs/dialogs_inner_widget.h | 9 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 3 +- 3 files changed, 90 insertions(+), 28 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 7633e4d2f..dc2728d21 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -79,6 +79,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_window.h" #include "styles/style_menu_icons.h" +#include + namespace Dialogs { namespace { @@ -1316,7 +1318,7 @@ void InnerWidget::paintSearchInTopic( } void InnerWidget::mouseMoveEvent(QMouseEvent *e) { - if (_chatPreviewTouchGlobal) { + if (_chatPreviewTouchGlobal || _touchDragStartGlobal) { return; } const auto globalPosition = e->globalPos(); @@ -1336,7 +1338,6 @@ void InnerWidget::mouseMoveEvent(QMouseEvent *e) { void InnerWidget::cancelChatPreview() { _chatPreviewTimer.cancel(); _chatPreviewWillBeFor = {}; - _chatPreviewTouchLocal = {}; _chatPreviewTouchGlobal = {}; } @@ -1622,8 +1623,14 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) { < style::ConvertScale(kStartReorderThreshold)) { return; } - cancelChatPreview(); _dragging = _pressed; + startReorderPinned(localPosition); +} + +void InnerWidget::startReorderPinned(QPoint localPosition) { + Expects(_dragging != nullptr); + + cancelChatPreview(); if (updateReorderIndexGetCount() < 2) { _dragging = nullptr; } else { @@ -1682,6 +1689,7 @@ void InnerWidget::finishReorderPinned() { if (wasDragging) { savePinnedOrder(); _dragging = nullptr; + _touchDragStartGlobal = {}; } _draggingIndex = -1; @@ -1694,6 +1702,22 @@ void InnerWidget::finishReorderPinned() { } } +bool InnerWidget::finishReorderOnRelease() { + if (!_dragging) { + return false; + } + updateReorderIndexGetCount(); + if (_draggingIndex >= 0) { + _pinnedRows[_draggingIndex].yadd.start(0.); + _pinnedRows[_draggingIndex].animStartTime = crl::now(); + if (!_pinnedShiftAnimation.animating()) { + _pinnedShiftAnimation.start(); + } + } + finishReorderPinned(); + return true; +} + void InnerWidget::stopReorderPinned() { _pinnedShiftAnimation.stop(); finishReorderPinned(); @@ -1845,18 +1869,7 @@ void InnerWidget::mousePressReleased( _chatPreviewTimer.cancel(); _pressButton = Qt::NoButton; - auto wasDragging = (_dragging != nullptr); - if (wasDragging) { - updateReorderIndexGetCount(); - if (_draggingIndex >= 0) { - _pinnedRows[_draggingIndex].yadd.start(0.); - _pinnedRows[_draggingIndex].animStartTime = crl::now(); - if (!_pinnedShiftAnimation.animating()) { - _pinnedShiftAnimation.start(); - } - } - finishReorderPinned(); - } + const auto wasDragging = finishReorderOnRelease(); auto collapsedPressed = _collapsedPressed; setCollapsedPressed(-1); @@ -2547,14 +2560,14 @@ void InnerWidget::parentGeometryChanged() { } } -void InnerWidget::processTouchEvent(not_null e) { +bool InnerWidget::processTouchEvent(not_null e) { const auto point = e->touchPoints().empty() ? std::optional() : e->touchPoints().front().screenPos().toPoint(); switch (e->type()) { case QEvent::TouchBegin: { if (!point) { - return; + return false; } selectByMouse(*point); const auto onlyUserpic = true; @@ -2562,24 +2575,69 @@ void InnerWidget::processTouchEvent(not_null e) { _chatPreviewTouchGlobal = point; _chatPreviewWillBeFor = computeChatPreviewRow(); _chatPreviewTimer.callOnce(kChatPreviewDelay); + } else if (!_dragging) { + _touchDragStartGlobal = point; + _touchDragPinnedTimer.callOnce(QApplication::startDragTime()); } } break; case QEvent::TouchUpdate: { - if (!_chatPreviewTouchGlobal || !point) { - return; + if (!point) { + return false; } - const auto delta = (*_chatPreviewTouchGlobal - *point); - if (delta.manhattanLength() > _st->photoSize) { - cancelChatPreview(); + if (_chatPreviewTouchGlobal) { + const auto delta = (*_chatPreviewTouchGlobal - *point); + if (delta.manhattanLength() > _st->photoSize) { + cancelChatPreview(); + } + } + if (_touchDragStartGlobal && _dragging) { + updateReorderPinned(mapFromGlobal(*point)); + return _dragging != nullptr; + } else if (_touchDragStartGlobal) { + const auto delta = (*_touchDragStartGlobal - *point); + if (delta.manhattanLength() > QApplication::startDragDistance()) { + if (_touchDragPinnedTimer.isActive()) { + _touchDragPinnedTimer.cancel(); + _touchDragStartGlobal = {}; + _touchDragNowGlobal = {}; + } else { + dragPinnedFromTouch(); + } + } else { + _touchDragNowGlobal = point; + } } } break; case QEvent::TouchEnd: - case QEvent::TouchCancel: if (_chatPreviewTouchGlobal) { - cancelChatPreview(); + case QEvent::TouchCancel: { + if (_chatPreviewTouchGlobal) { + cancelChatPreview(); + } + if (_touchDragStartGlobal) { + _touchDragStartGlobal = {}; + return finishReorderOnRelease(); + } } break; } + return false; +} + +void InnerWidget::dragPinnedFromTouch() { + Expects(_touchDragStartGlobal.has_value()); + + const auto global = *_touchDragStartGlobal; + _touchDragPinnedTimer.cancel(); + selectByMouse(global); + if (!_selected || _dragging || _state != WidgetState::Default) { + return; + } + _dragStart = mapFromGlobal(global); + _dragging = _selected; + const auto now = mapFromGlobal(_touchDragNowGlobal.value_or(global)); + startReorderPinned(now); + updateReorderPinned(now); } void InnerWidget::applySearchState(SearchState state) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index ed94e0b58..286f84842 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -174,7 +174,7 @@ public: void parentGeometryChanged(); - void processTouchEvent(not_null e); + bool processTouchEvent(not_null e); [[nodiscard]] rpl::producer<> touchCancelRequests() const { return _touchCancelRequests.events(); } @@ -404,15 +404,18 @@ private: [[nodiscard]] const std::vector &pinnedChatsOrder() const; void checkReorderPinnedStart(QPoint localPosition); + void startReorderPinned(QPoint localPosition); int updateReorderIndexGetCount(); bool updateReorderPinned(QPoint localPosition); void finishReorderPinned(); + bool finishReorderOnRelease(); void stopReorderPinned(); int countPinnedIndex(Row *ofRow); void savePinnedOrder(); bool pinnedShiftAnimationCallback(crl::time now); void handleChatListEntryRefreshes(); void moveCancelSearchButtons(); + void dragPinnedFromTouch(); void saveChatsFilterScrollState(FilterId filterId); void restoreChatsFilterScrollState(FilterId filterId); @@ -525,8 +528,10 @@ private: base::Timer _chatPreviewTimer; Key _chatPreviewWillBeFor; Key _chatPreviewKey; - std::optional _chatPreviewTouchLocal; std::optional _chatPreviewTouchGlobal; + base::Timer _touchDragPinnedTimer; + std::optional _touchDragStartGlobal; + std::optional _touchDragNowGlobal; rpl::event_stream<> _touchCancelRequests; rpl::variable _childListShown; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 092f2f4cc..3d73978bf 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -658,8 +658,7 @@ void Widget::setupScrollUpButton() { void Widget::setupTouchChatPreview() { _scroll->setCustomTouchProcess([=](not_null e) { - _inner->processTouchEvent(e); - return false; + return _inner->processTouchEvent(e); }); _inner->touchCancelRequests() | rpl::start_with_next([=] { QTouchEvent ev(QEvent::TouchCancel); From 27eb3e45bed5916fcb018b9a427fc26b8cfd9787 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 May 2024 23:28:30 +0400 Subject: [PATCH 170/225] Allow only t.me links in factchecks. --- Telegram/Resources/langs/lang.strings | 1 + .../chat_helpers/message_field.cpp | 57 +++++++++++++++++-- .../view/history_view_context_menu.cpp | 2 +- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4d1798501..1b8491c85 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3305,6 +3305,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_factcheck_edit_done" = "Fact check edited."; "lng_factcheck_remove_done" = "Fact check removed."; "lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation."; +"lng_factcheck_links" = "Only **t.me/** links are allowed."; "lng_translate_show_original" = "Show Original"; "lng_translate_bar_to" = "Translate to {name}"; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 8b5f3c373..779bfb4d5 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/shortcuts.h" #include "core/application.h" #include "core/core_settings.h" +#include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/wrap/vertical_layout.h" #include "ui/widgets/buttons.h" @@ -115,7 +116,8 @@ void EditLinkBox( const QString &startText, const QString &startLink, Fn callback, - const style::InputField *fieldStyle) { + const style::InputField *fieldStyle, + Fn validate) { Expects(callback != nullptr); const auto &fieldSt = fieldStyle ? *fieldStyle : st::defaultInputField; @@ -160,7 +162,7 @@ void EditLinkBox( const auto submit = [=] { const auto linkText = text->getLastText(); - const auto linkUrl = qthelp::validate_url(url->getLastText()); + const auto linkUrl = validate(url->getLastText()); if (linkText.isEmpty()) { text->showError(); return; @@ -312,7 +314,8 @@ Fn FactcheckEditLinkCallback( + std::shared_ptr show, + not_null field) { + const auto weak = Ui::MakeWeak(field); + return [=]( + EditLinkSelection selection, + QString text, + QString link, + EditLinkAction action) { + const auto validate = [=](QString url) { + if (IsGoodFactcheckUrl(url)) { + const auto start = u"https://"_q; + return url.startsWith(start) ? url : (start + url); + } + show->showToast( + tr::lng_factcheck_links(tr::now, Ui::Text::RichLangValue)); + return QString(); + }; + if (action == EditLinkAction::Check) { + return IsGoodFactcheckUrl(link); + } + auto callback = [=](const QString &text, const QString &link) { + if (const auto strong = weak.data()) { + strong->commitMarkdownLinkEdit(selection, text, link); + } + }; + show->showBox(Box( + EditLinkBox, + show, + text, + link, + std::move(callback), + nullptr, + validate)); + return true; + }; +} + Fn)> FactcheckFieldIniter( std::shared_ptr show) { Expects(show != nullptr); @@ -374,7 +423,7 @@ Fn)> FactcheckFieldIniter( } } )); - field->setEditLinkCallback(DefaultEditLinkCallback(show, field)); + field->setEditLinkCallback(FactcheckEditLinkCallback(show, field)); InitSpellchecker(show, field); }; } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index b217777a5..a53ab2028 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -734,7 +734,7 @@ void AddFactcheckAction( const auto limit = session->factchecks().lengthLimit(); const auto controller = request.navigation->parentController(); controller->show(Box(EditFactcheckBox, text, limit, [=]( - TextWithEntities result) { + TextWithEntities result) { const auto show = controller->uiShow(); session->factchecks().save(itemId, text, result, show); }, FactcheckFieldIniter(controller->uiShow()))); From 5cfd86b8295d39e6ce153f7bd2b9eff4a05178e7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 09:08:30 +0400 Subject: [PATCH 171/225] Use Ui::Text::String to show topic names. Fixes #27956. --- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index f7cbff178..809a74eaf 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -259,9 +259,9 @@ void PaintRow( not_null entry, VideoUserpic *videoUserpic, PeerData *from, - PeerBadge &fromBadge, + PeerBadge &rowBadge, Fn customEmojiRepaint, - const Text::String &fromName, + const Text::String &rowName, const HiddenSenderInfo *hiddenSenderInfo, HistoryItem *item, const Data::Draft *draft, @@ -626,10 +626,10 @@ void PaintRow( p.drawTextLeft(rectForName.left(), rectForName.top(), context.width, text); } else if (from) { if ((history || sublist) && !context.search) { - const auto badgeWidth = fromBadge.drawGetWidth( + const auto badgeWidth = rowBadge.drawGetWidth( p, rectForName, - fromName.maxWidth(), + rowName.maxWidth(), context.width, { .peer = from, @@ -663,7 +663,7 @@ void PaintRow( : context.selected ? st::dialogsNameFgOver : st::dialogsNameFg); - fromName.drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); + rowName.drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } else if (hiddenSenderInfo) { p.setPen(context.active ? st::dialogsNameFgActive @@ -681,12 +681,7 @@ void PaintRow( : (context.selected ? st::dialogsNameFgOver : st::dialogsNameFg)); - auto text = entry->chatListName(); // TODO feed name with emoji - auto textWidth = st::semiboldFont->width(text); - if (textWidth > rectForName.width()) { - text = st::semiboldFont->elided(text, rectForName.width()); - } - p.drawTextLeft(rectForName.left(), rectForName.top(), context.width, text); + rowName.drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); } } From ad342a5324e31c5cbd7e7252af4aab9e0c8d82ad Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 13:15:51 +0400 Subject: [PATCH 172/225] Extract chat preview as a SessionController part. --- Telegram/CMakeLists.txt | 2 + .../dialogs/dialogs_inner_widget.cpp | 155 +++++++----------- .../dialogs/dialogs_inner_widget.h | 12 +- .../window/window_chat_preview.cpp | 126 ++++++++++++++ .../SourceFiles/window/window_chat_preview.h | 48 ++++++ .../window/window_session_controller.cpp | 20 +++ .../window/window_session_controller.h | 10 ++ 7 files changed, 267 insertions(+), 106 deletions(-) create mode 100644 Telegram/SourceFiles/window/window_chat_preview.cpp create mode 100644 Telegram/SourceFiles/window/window_chat_preview.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index fa06b446a..8b081b263 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1516,6 +1516,8 @@ PRIVATE window/section_widget.h window/window_adaptive.cpp window/window_adaptive.h + window/window_chat_preview.cpp + window/window_chat_preview.h window/window_connecting_widget.cpp window/window_connecting_widget.h window/window_controller.cpp diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index dc2728d21..112e4c9df 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -86,7 +86,6 @@ namespace { constexpr auto kHashtagResultsLimit = 5; constexpr auto kStartReorderThreshold = 30; -constexpr auto kChatPreviewDelay = crl::time(1000); [[nodiscard]] int FixedOnTopDialogsCount(not_null list) { auto result = 0; @@ -216,7 +215,6 @@ InnerWidget::InnerWidget( + st::defaultDialogRow.photoSize + st::defaultDialogRow.padding.left()) , _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) -, _chatPreviewTimer([=] { showChatPreview(true); }) , _childListShown(std::move(childListShown)) { setAttribute(Qt::WA_OpaquePaintEvent, true); @@ -714,8 +712,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) { context.active = active; context.selected = _menuRow.key ? (row->key() == _menuRow.key) - : _chatPreviewKey - ? (row->key() == _chatPreviewKey) + : _chatPreviewRow.key + ? (row->key() == _chatPreviewRow.key) : selected; context.topicJumpSelected = selected && _selectedTopicJump @@ -981,6 +979,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto active = isSearchResultActive(result.get(), activeEntry); const auto selected = _menuRow.key ? isSearchResultActive(result.get(), _menuRow) + : _chatPreviewRow.key + ? isSearchResultActive(result.get(), _chatPreviewRow) : (from == (isPressed() ? _searchedPressed : _searchedSelected)); @@ -1330,15 +1330,18 @@ void InnerWidget::mouseMoveEvent(QMouseEvent *e) { return; } selectByMouse(globalPosition); - if (!isUserpicPress()) { + if (_chatPreviewScheduled && !isUserpicPress()) { cancelChatPreview(); } } void InnerWidget::cancelChatPreview() { - _chatPreviewTimer.cancel(); - _chatPreviewWillBeFor = {}; _chatPreviewTouchGlobal = {}; + _chatPreviewScheduled = false; + if (_chatPreviewRow.key) { + updateDialogRow(base::take(_chatPreviewRow)); + } + _controller->cancelScheduledPreview(); } void InnerWidget::clearIrrelevantState() { @@ -1484,26 +1487,24 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { } } -Key InnerWidget::computeChatPreviewRow() const { - auto result = computeChosenRow().key; - if (const auto peer = result.peer()) { +RowDescriptor InnerWidget::computeChatPreviewRow() const { + auto result = computeChosenRow(); + if (const auto peer = result.key.peer()) { const auto topicId = _pressedTopicJump ? _pressedTopicJumpRootId : 0; if (const auto topic = peer->forumTopicFor(topicId)) { - return topic; + return { topic, FullMsgId() }; } } - return result; + return { result.key, result.message.fullId }; } void InnerWidget::processGlobalForceClick(QPoint globalPosition) { const auto parent = parentWidget(); if (_pressButton == Qt::LeftButton - && parent->rect().contains(parent->mapFromGlobal(globalPosition)) - && pressShowsPreview(false)) { - _chatPreviewWillBeFor = computeChatPreviewRow(); - showChatPreview(false); + && parent->rect().contains(parent->mapFromGlobal(globalPosition))) { + showChatPreview(); } } @@ -1520,14 +1521,10 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { setSearchedPressed(_searchedSelected); const auto alt = (e->modifiers() & Qt::AltModifier); - const auto onlyUserpic = !alt; - if (pressShowsPreview(onlyUserpic)) { - _chatPreviewWillBeFor = computeChatPreviewRow(); - if (alt) { - showChatPreview(onlyUserpic); - return; - } - _chatPreviewTimer.callOnce(kChatPreviewDelay); + if (alt && showChatPreview()) { + return; + } else if (!alt && isUserpicPress()) { + scheduleChatPreview(); } if (base::in_range(_collapsedSelected, 0, _collapsedRows.size())) { @@ -1599,7 +1596,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { } ClickHandler::pressed(); if (anim::Disabled() - && !_chatPreviewTimer.isActive() + && !_chatPreviewScheduled && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { mousePressReleased(e->globalPos(), e->button(), e->modifiers()); } @@ -1866,7 +1863,9 @@ void InnerWidget::mousePressReleased( QPoint globalPosition, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) { - _chatPreviewTimer.cancel(); + if (_chatPreviewScheduled) { + _controller->cancelScheduledPreview(); + } _pressButton = Qt::NoButton; const auto wasDragging = finishReorderOnRelease(); @@ -2412,67 +2411,40 @@ void InnerWidget::fillArchiveSearchMenu(not_null menu) { }); } -void InnerWidget::showChatPreview(bool onlyUserpic) { - const auto key = base::take(_chatPreviewWillBeFor); - const auto touchGlobal = base::take(_chatPreviewTouchGlobal); - cancelChatPreview(); - if (!pressShowsPreview(onlyUserpic) || key != computeChatPreviewRow()) { - return; - } - if (onlyUserpic && touchGlobal) { - _touchCancelRequests.fire({}); - } - ClickHandler::unpressed(); - mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier); +bool InnerWidget::showChatPreview() { + const auto row = computeChatPreviewRow(); + const auto callback = crl::guard(this, [=](bool shown) { + chatPreviewShown(shown, row); + }); + return _controller->showChatPreview(row, callback); +} - _chatPreviewKey = key; - auto preview = HistoryView::MakeChatPreview(this, key.entry()); - if (!preview.menu) { - return; - } - _menu = std::move(preview.menu); - const auto weakMenu = Ui::MakeWeak(_menu.get()); - const auto weakThread = base::make_weak(key.entry()->asThread()); - const auto weakController = base::make_weak(_controller); - std::move( - preview.actions - ) | rpl::start_with_next([=](HistoryView::ChatPreviewAction action) { - if (const auto controller = weakController.get()) { - if (const auto thread = weakThread.get()) { - const auto itemId = action.openItemId; - const auto owner = &thread->owner(); - if (action.markRead) { - Window::MarkAsReadThread(thread); - } else if (action.markUnread) { - if (const auto history = thread->asHistory()) { - history->owner().histories().changeDialogUnreadMark( - history, - true); - } - } else if (action.openInfo) { - controller->showPeerInfo(thread); - } else if (const auto item = owner->message(itemId)) { - controller->showMessage(item); - } else { - controller->showThread(thread); - } - } - } - if (const auto strong = weakMenu.data()) { - strong->hideMenu(); - } - }, _menu->lifetime()); - QObject::connect(_menu.get(), &QObject::destroyed, [=] { - if (_chatPreviewKey) { - updateDialogRow(RowDescriptor(base::take(_chatPreviewKey), {})); +void InnerWidget::chatPreviewShown(bool shown, RowDescriptor row) { + _chatPreviewScheduled = false; + if (shown) { + _chatPreviewRow = row; + if (base::take(_chatPreviewTouchGlobal)) { + _touchCancelRequests.fire({}); } + ClickHandler::unpressed(); + mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier); + } else { + cancelChatPreview(); const auto globalPosition = QCursor::pos(); if (rect().contains(mapFromGlobal(globalPosition))) { setMouseTracking(true); selectByMouse(globalPosition); } + } +} + +bool InnerWidget::scheduleChatPreview() { + const auto row = computeChatPreviewRow(); + const auto callback = crl::guard(this, [=](bool shown) { + chatPreviewShown(shown, row); }); - _menu->popup(_lastMousePosition.value_or(QCursor::pos())); + _chatPreviewScheduled = _controller->scheduleChatPreview(row, callback); + return _chatPreviewScheduled; } void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { @@ -2571,10 +2543,8 @@ bool InnerWidget::processTouchEvent(not_null e) { } selectByMouse(*point); const auto onlyUserpic = true; - if (pressShowsPreview(onlyUserpic)) { + if (isUserpicPress() && scheduleChatPreview()) { _chatPreviewTouchGlobal = point; - _chatPreviewWillBeFor = computeChatPreviewRow(); - _chatPreviewTimer.callOnce(kChatPreviewDelay); } else if (!_dragging) { _touchDragStartGlobal = point; _touchDragPinnedTimer.callOnce(QApplication::startDragTime()); @@ -2894,11 +2864,8 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { refresh(); clearMouseSelection(true); } - if (_chatPreviewWillBeFor.topic() == topic) { - _chatPreviewWillBeFor = {}; - } - if (_chatPreviewKey.topic() == topic) { - _chatPreviewKey = {}; + if (_chatPreviewRow.key.topic() == topic) { + _chatPreviewRow = {}; } }, _searchResultsLifetime); } @@ -3793,18 +3760,6 @@ bool InnerWidget::isUserpicPressOnWide() const { return isUserpicPress() && (width() > _narrowWidth); } -bool InnerWidget::pressShowsPreview(bool onlyUserpic) const { - if (onlyUserpic && !isUserpicPress()) { - return false; - } - const auto key = computeChosenRow().key; - if (const auto history = key.history()) { - return !history->peer->isForum() - || (_pressedTopicJump && _pressedTopicJumpRootId); - } - return key.topic() != nullptr; -} - bool InnerWidget::chooseRow( Qt::KeyboardModifiers modifiers, MsgId pressedTopicRootId) { @@ -4157,7 +4112,7 @@ void InnerWidget::setupShortcuts() { && !_controller->isLayerShown() && !_controller->window().locked() && !_childListShown.current().shown - && !_chatPreviewKey; + && !_chatPreviewRow.key; }) | rpl::start_with_next([=](not_null request) { using Command = Shortcuts::Command; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 286f84842..ccca8eb71 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -125,9 +125,10 @@ public: [[nodiscard]] bool isUserpicPress() const; [[nodiscard]] bool isUserpicPressOnWide() const; - [[nodiscard]] bool pressShowsPreview(bool onlyUserpic) const; void cancelChatPreview(); - void showChatPreview(bool onlyUserpic); + bool scheduleChatPreview(); + bool showChatPreview(); + void chatPreviewShown(bool shown, RowDescriptor row = {}); bool chooseRow( Qt::KeyboardModifiers modifiers = {}, MsgId pressedTopicRootId = {}); @@ -400,7 +401,7 @@ private: void trackSearchResultsHistory(not_null history); [[nodiscard]] QBrush currentBg() const; - [[nodiscard]] Key computeChatPreviewRow() const; + [[nodiscard]] RowDescriptor computeChatPreviewRow() const; [[nodiscard]] const std::vector &pinnedChatsOrder() const; void checkReorderPinnedStart(QPoint localPosition); @@ -525,9 +526,8 @@ private: rpl::event_stream _completeHashtagRequests; rpl::event_stream<> _refreshHashtagsRequests; - base::Timer _chatPreviewTimer; - Key _chatPreviewWillBeFor; - Key _chatPreviewKey; + RowDescriptor _chatPreviewRow; + bool _chatPreviewScheduled = false; std::optional _chatPreviewTouchGlobal; base::Timer _touchDragPinnedTimer; std::optional _touchDragStartGlobal; diff --git a/Telegram/SourceFiles/window/window_chat_preview.cpp b/Telegram/SourceFiles/window/window_chat_preview.cpp new file mode 100644 index 000000000..cee4671dd --- /dev/null +++ b/Telegram/SourceFiles/window/window_chat_preview.cpp @@ -0,0 +1,126 @@ +/* +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 "window/window_chat_preview.h" + +#include "data/data_forum_topic.h" +#include "data/data_histories.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/view/history_view_chat_preview.h" +#include "mainwidget.h" +#include "ui/widgets/popup_menu.h" +#include "window/window_peer_menu.h" +#include "window/window_session_controller.h" + +namespace Window { +namespace { + +constexpr auto kChatPreviewDelay = crl::time(1000); + +} // namespace + +ChatPreviewManager::ChatPreviewManager( + not_null controller) +: _controller(controller) +, _timer([=] { showScheduled(); }) { +} + +bool ChatPreviewManager::show( + Dialogs::RowDescriptor row, + Fn callback) { + cancelScheduled(); + _topicLifetime.destroy(); + if (const auto topic = row.key.topic()) { + _topicLifetime = topic->destroyed() | rpl::start_with_next([=] { + _menu = nullptr; + }); + } + + const auto parent = _controller->content(); + auto preview = HistoryView::MakeChatPreview(parent, row.key.entry()); + if (!preview.menu) { + return false; + } + _menu = std::move(preview.menu); + const auto weakMenu = Ui::MakeWeak(_menu.get()); + const auto weakThread = base::make_weak(row.key.entry()->asThread()); + const auto weakController = base::make_weak(_controller); + std::move( + preview.actions + ) | rpl::start_with_next([=](HistoryView::ChatPreviewAction action) { + if (const auto controller = weakController.get()) { + if (const auto thread = weakThread.get()) { + const auto itemId = action.openItemId; + const auto owner = &thread->owner(); + if (action.markRead) { + MarkAsReadThread(thread); + } else if (action.markUnread) { + if (const auto history = thread->asHistory()) { + history->owner().histories().changeDialogUnreadMark( + history, + true); + } + } else if (action.openInfo) { + controller->showPeerInfo(thread); + } else if (const auto item = owner->message(itemId)) { + controller->showMessage(item); + } else { + controller->showThread(thread); + } + } + } + if (const auto strong = weakMenu.data()) { + strong->hideMenu(); + } + }, _menu->lifetime()); + QObject::connect(_menu.get(), &QObject::destroyed, [=] { + _topicLifetime.destroy(); + callback(false); + }); + + callback(true); + _menu->popup(QCursor::pos()); + + return true; +} + +bool ChatPreviewManager::schedule( + Dialogs::RowDescriptor row, + Fn callback) { + cancelScheduled(); + _topicLifetime.destroy(); + if (const auto topic = row.key.topic()) { + _topicLifetime = topic->destroyed() | rpl::start_with_next([=] { + cancelScheduled(); + _menu = nullptr; + }); + } else if (const auto history = row.key.history()) { + if (history->peer->isForum()) { + return false; + } + } else { + return false; + } + _scheduled = row; + _scheduledCallback = std::move(callback); + _timer.callOnce(kChatPreviewDelay); + return true; +} + +void ChatPreviewManager::showScheduled() { + show(base::take(_scheduled), base::take(_scheduledCallback)); +} + +void ChatPreviewManager::cancelScheduled() { + _scheduled = {}; + _scheduledCallback = nullptr; + _timer.cancel(); +} + +} // namespace Window \ No newline at end of file diff --git a/Telegram/SourceFiles/window/window_chat_preview.h b/Telegram/SourceFiles/window/window_chat_preview.h new file mode 100644 index 000000000..c3744e697 --- /dev/null +++ b/Telegram/SourceFiles/window/window_chat_preview.h @@ -0,0 +1,48 @@ +/* +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/timer.h" +#include "base/unique_qptr.h" +#include "dialogs/dialogs_key.h" + +namespace Ui { +class PopupMenu; +} // namespace Ui + +namespace Window { + +class SessionController; + +class ChatPreviewManager final { +public: + ChatPreviewManager(not_null controller); + + bool show( + Dialogs::RowDescriptor row, + Fn callback = nullptr); + bool schedule( + Dialogs::RowDescriptor row, + Fn callback = nullptr); + void cancelScheduled(); + +private: + void showScheduled(); + + const not_null _controller; + Dialogs::RowDescriptor _scheduled; + Fn _scheduledCallback; + base::Timer _timer; + + rpl::lifetime _topicLifetime; + + base::unique_qptr _menu; + +}; + +} // namespace Window \ No newline at end of file diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 37acebe47..e673da20a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_info_box.h" #include "boxes/peers/replace_boost_box.h" #include "boxes/delete_messages_box.h" +#include "window/window_chat_preview.h" #include "window/window_controller.h" #include "window/window_filters_menu.h" #include "info/channel_statistics/earn/info_earn_inner_widget.h" @@ -1179,6 +1180,7 @@ SessionController::SessionController( , _window(window) , _emojiInteractions( std::make_unique(session)) +, _chatPreviewManager(std::make_unique(this)) , _isPrimary(window->isPrimary()) , _sendingAnimation( std::make_unique(this)) @@ -2974,6 +2976,24 @@ QString SessionController::premiumRef() const { return _premiumRef; } +bool SessionController::showChatPreview( + Dialogs::RowDescriptor row, + Fn callback) { + return _chatPreviewManager->show(std::move(row), std::move(callback)); +} + +bool SessionController::scheduleChatPreview( + Dialogs::RowDescriptor row, + Fn callback) { + return _chatPreviewManager->schedule( + std::move(row), + std::move(callback)); +} + +void SessionController::cancelScheduledPreview() { + _chatPreviewManager->cancelScheduled(); +} + bool SessionController::contentOverlapped(QWidget *w, QPaintEvent *e) const { return widget()->contentOverlapped(w, e); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 38085b945..0cbaf0b9a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -88,6 +88,7 @@ using GifPauseReasons = ChatHelpers::PauseReasons; class SectionMemento; class Controller; class FiltersMenu; +class ChatPreviewManager; struct PeerByLinkInfo; @@ -600,6 +601,14 @@ public: void setPremiumRef(const QString &ref); [[nodiscard]] QString premiumRef() const; + bool showChatPreview( + Dialogs::RowDescriptor row, + Fn callback = nullptr); + bool scheduleChatPreview( + Dialogs::RowDescriptor row, + Fn callback = nullptr); + void cancelScheduledPreview(); + [[nodiscard]] bool contentOverlapped(QWidget *w, QPaintEvent *e) const; [[nodiscard]] std::shared_ptr uiShow() override; @@ -656,6 +665,7 @@ private: const not_null _window; const std::unique_ptr _emojiInteractions; + const std::unique_ptr _chatPreviewManager; const bool _isPrimary = false; mutable std::shared_ptr _cachedShow; From 4e8895ddd93d74d32728f9276270d8fc0352c2f0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 14:39:35 +0400 Subject: [PATCH 173/225] Show "View as Messages" preview by Alt+Click. --- .../dialogs/dialogs_inner_widget.cpp | 2 ++ .../view/history_view_chat_preview.cpp | 16 ++++++---- .../history/view/history_view_message.cpp | 4 ++- .../window/window_chat_preview.cpp | 27 ++++++++++------ .../SourceFiles/window/window_chat_preview.h | 7 +++-- .../SourceFiles/window/window_peer_menu.cpp | 31 +++++++++++++++++-- .../window/window_session_controller.cpp | 14 ++++++--- .../window/window_session_controller.h | 6 ++-- Telegram/lib_ui | 2 +- 9 files changed, 80 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 112e4c9df..343730d18 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1495,6 +1495,8 @@ RowDescriptor InnerWidget::computeChatPreviewRow() const { : 0; if (const auto topic = peer->forumTopicFor(topicId)) { return { topic, FullMsgId() }; + } else if (peer->isForum() && !result.key.topic()) { + return {}; } } return { result.key, result.message.fullId }; diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index fdc7607f9..91540a729 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_list_widget.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_components.h" #include "info/profile/info_profile_cover.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" @@ -369,7 +370,10 @@ void Item::setupMarkRead() { ) | rpl::start_with_next([=] { const auto state = _thread->chatListBadgesState(); const auto unread = (state.unreadCounter || state.unread); - if (_thread->asTopic() && !unread) { + const auto hidden = _thread->asTopic() + ? (!unread) + : _thread->peer()->isForum(); + if (hidden) { _markRead->hide(); return; } @@ -595,7 +599,11 @@ void Item::listUpdateDateLink( } bool Item::listElementHideReply(not_null view) { - return false; + if (!view->isTopicRootReply()) { + return false; + } + const auto reply = view->data()->Get(); + return reply && !reply->fields().manualQuote; } bool Item::listElementShownUnread(not_null view) { @@ -769,10 +777,6 @@ ChatPreview MakeChatPreview( const auto thread = entry->asThread(); if (!thread) { return {}; - } else if (const auto history = entry->asHistory()) { - if (history->peer->isForum()) { - return {}; - } } auto result = ChatPreview{ diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 133a19973..38b89f061 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1042,7 +1042,9 @@ QSize Message::performCountOptimalSize() { void Message::refreshTopicButton() { const auto item = data(); - if (isAttachedToPrevious() || context() != Context::History) { + if (isAttachedToPrevious() + || (context() != Context::History + && context() != Context::ChatPreview)) { _topicButton = nullptr; } else if (const auto topic = item->topic()) { if (!_topicButton) { diff --git a/Telegram/SourceFiles/window/window_chat_preview.cpp b/Telegram/SourceFiles/window/window_chat_preview.cpp index cee4671dd..24a71c3bc 100644 --- a/Telegram/SourceFiles/window/window_chat_preview.cpp +++ b/Telegram/SourceFiles/window/window_chat_preview.cpp @@ -33,16 +33,21 @@ ChatPreviewManager::ChatPreviewManager( bool ChatPreviewManager::show( Dialogs::RowDescriptor row, - Fn callback) { + Fn callback, + QPointer parentOverride) { cancelScheduled(); _topicLifetime.destroy(); if (const auto topic = row.key.topic()) { _topicLifetime = topic->destroyed() | rpl::start_with_next([=] { _menu = nullptr; }); + } else if (!row.key) { + return false; } - const auto parent = _controller->content(); + const auto parent = parentOverride + ? parentOverride + : _controller->content(); auto preview = HistoryView::MakeChatPreview(parent, row.key.entry()); if (!preview.menu) { return false; @@ -81,10 +86,14 @@ bool ChatPreviewManager::show( }, _menu->lifetime()); QObject::connect(_menu.get(), &QObject::destroyed, [=] { _topicLifetime.destroy(); - callback(false); + if (callback) { + callback(false); + } }); - callback(true); + if (callback) { + callback(true); + } _menu->popup(QCursor::pos()); return true; @@ -92,7 +101,8 @@ bool ChatPreviewManager::show( bool ChatPreviewManager::schedule( Dialogs::RowDescriptor row, - Fn callback) { + Fn callback, + QPointer parentOverride) { cancelScheduled(); _topicLifetime.destroy(); if (const auto topic = row.key.topic()) { @@ -100,15 +110,12 @@ bool ChatPreviewManager::schedule( cancelScheduled(); _menu = nullptr; }); - } else if (const auto history = row.key.history()) { - if (history->peer->isForum()) { - return false; - } } else { return false; } - _scheduled = row; + _scheduled = std::move(row); _scheduledCallback = std::move(callback); + _scheduledParentOverride = std::move(parentOverride); _timer.callOnce(kChatPreviewDelay); return true; } diff --git a/Telegram/SourceFiles/window/window_chat_preview.h b/Telegram/SourceFiles/window/window_chat_preview.h index c3744e697..6101ac3e9 100644 --- a/Telegram/SourceFiles/window/window_chat_preview.h +++ b/Telegram/SourceFiles/window/window_chat_preview.h @@ -25,10 +25,12 @@ public: bool show( Dialogs::RowDescriptor row, - Fn callback = nullptr); + Fn callback = nullptr, + QPointer parentOverride = nullptr); bool schedule( Dialogs::RowDescriptor row, - Fn callback = nullptr); + Fn callback = nullptr, + QPointer parentOverride = nullptr); void cancelScheduled(); private: @@ -37,6 +39,7 @@ private: const not_null _controller; Dialogs::RowDescriptor _scheduled; Fn _scheduledCallback; + QPointer _scheduledParentOverride; base::Timer _timer; rpl::lifetime _topicLifetime; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index cea289668..b1a37190f 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/random.h" #include "base/options.h" #include "base/unixtime.h" +#include "base/qt/qt_key_modifiers.h" #include "boxes/delete_messages_box.h" #include "boxes/max_invite_box.h" #include "boxes/moderate_messages_box.h" @@ -94,7 +95,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_menu_icons.h" #include -#include +#include namespace Window { namespace { @@ -1239,12 +1240,36 @@ void Filler::addViewAsMessages() { } const auto peer = _peer; const auto controller = _controller; - _addAction(tr::lng_forum_view_as_messages(tr::now), [=] { + const auto parentHideRequests = std::make_shared>(); + const auto filterOutChatPreview = [=] { + if (base::IsAltPressed()) { + const auto callback = [=](bool shown) { + if (!shown) { + parentHideRequests->fire({}); + } + }; + controller->showChatPreview({ + peer->owner().history(peer), + FullMsgId(), + }, callback, QApplication::activePopupWidget()); + return true; + } + return false; + }; + const auto open = [=] { if (const auto forum = peer->forum()) { peer->owner().saveViewAsMessages(forum, true); } controller->showPeerHistory(peer->id); - }, &st::menuIconAsMessages); + }; + auto to_instant = rpl::map_to(anim::type::instant); + _addAction({ + .text = tr::lng_forum_view_as_messages(tr::now), + .handler = open, + .icon = &st::menuIconAsMessages, + .triggerFilter = filterOutChatPreview, + .hideRequests = parentHideRequests->events() | to_instant + }); } void Filler::addViewAsTopics() { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index e673da20a..4a004dce8 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -2978,16 +2978,22 @@ QString SessionController::premiumRef() const { bool SessionController::showChatPreview( Dialogs::RowDescriptor row, - Fn callback) { - return _chatPreviewManager->show(std::move(row), std::move(callback)); + Fn callback, + QPointer parentOverride) { + return _chatPreviewManager->show( + std::move(row), + std::move(callback), + std::move(parentOverride)); } bool SessionController::scheduleChatPreview( Dialogs::RowDescriptor row, - Fn callback) { + Fn callback, + QPointer parentOverride) { return _chatPreviewManager->schedule( std::move(row), - std::move(callback)); + std::move(callback), + std::move(parentOverride)); } void SessionController::cancelScheduledPreview() { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 0cbaf0b9a..37f45f86e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -603,10 +603,12 @@ public: bool showChatPreview( Dialogs::RowDescriptor row, - Fn callback = nullptr); + Fn callback = nullptr, + QPointer parentOverride = nullptr); bool scheduleChatPreview( Dialogs::RowDescriptor row, - Fn callback = nullptr); + Fn callback = nullptr, + QPointer parentOverride = nullptr); void cancelScheduledPreview(); [[nodiscard]] bool contentOverlapped(QWidget *w, QPaintEvent *e) const; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 33aac93b1..d0514b2b0 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 33aac93b160d4cd30119c8859de722e28512902b +Subproject commit d0514b2b022043b3777b06d6068232aa4cda7e80 From f9f51b4e419be73b672f598ec8639fd86c6c15aa Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 15:01:43 +0400 Subject: [PATCH 174/225] Show preview in Frequent Contacts. --- .../dialogs/ui/dialogs_suggestions.cpp | 29 +++++++++++++++++++ .../dialogs/ui/dialogs_suggestions.h | 2 ++ .../dialogs/ui/top_peers_strip.cpp | 28 ++++++++++++++++++ .../SourceFiles/dialogs/ui/top_peers_strip.h | 5 ++++ .../window/window_chat_preview.cpp | 2 +- 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 9a86cca75..28802c811 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_participants.h" #include "apiwrap.h" #include "base/unixtime.h" +#include "base/qt/qt_key_modifiers.h" #include "boxes/peer_list_box.h" #include "data/components/recent_peers.h" #include "data/components/top_peers.h" @@ -940,6 +941,16 @@ void Suggestions::setupChats() { _topPeerChosen.fire(_controller->session().data().peer(peerId)); }, _topPeers->lifetime()); + _topPeers->pressed() | rpl::start_with_next([=](uint64 peerIdRaw) { + handlePressForChatPreview(PeerId(peerIdRaw), [=] { + _topPeers->cancelPress(); + }); + }, _topPeers->lifetime()); + + _topPeers->pressCancelled() | rpl::start_with_next([=] { + _controller->cancelScheduledPreview(); + }, _topPeers->lifetime()); + _topPeers->showMenuRequests( ) | rpl::start_with_next([=](const ShowTopPeerMenuRequest &request) { const auto weak = Ui::MakeWeak(this); @@ -983,6 +994,24 @@ void Suggestions::setupChats() { _chatsScroll->setVisible(_tab.current() == Tab::Chats); } +void Suggestions::handlePressForChatPreview( + PeerId id, + Fn cancelPress) { + const auto callback = crl::guard(this, [=](bool shown) { + if (shown) { + cancelPress(); + } + }); + const auto row = RowDescriptor( + _controller->session().data().history(id), + FullMsgId()); + if (base::IsAltPressed()) { + _controller->showChatPreview(row, callback); + } else { + _controller->scheduleChatPreview(row, callback); + } +} + void Suggestions::setupChannels() { _myChannelsCount.value() | rpl::start_with_next([=](int count) { _myChannels->toggle(count > 0, anim::type::instant); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 27b9b527f..16e07d15b 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -122,6 +122,8 @@ private: void startSlideAnimation(); void finishShow(); + void handlePressForChatPreview(PeerId id, Fn cancelPress); + const not_null _controller; const std::unique_ptr _tabs; diff --git a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp index 40e758ad2..48ec56696 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp @@ -286,6 +286,8 @@ void TopPeersStrip::stripMousePressEvent(QMouseEvent *e) { entry.ripple->add(e->pos() - QPoint( x + st::topPeersMargin.left(), y + st::topPeersMargin.top())); + + _presses.fire_copy(entry.id); } } @@ -304,6 +306,7 @@ void TopPeersStrip::stripMouseMoveEvent(QMouseEvent *e) { if (!_dragging && _mouseDownPosition) { if ((*_lastMousePosition - *_mouseDownPosition).manhattanLength() >= QApplication::startDragDistance()) { + _pressCancelled.fire({}); if (!_expandAnimation.animating()) { _dragging = true; _startDraggingLeft = _scrollLeft; @@ -371,6 +374,8 @@ void TopPeersStrip::subscribeUserpic(Entry &entry) { } void TopPeersStrip::stripMouseReleaseEvent(QMouseEvent *e) { + _pressCancelled.fire({}); + _lastMousePosition = e->globalPos(); const auto guard = gsl::finally([&] { _mouseDownPosition = std::nullopt; @@ -428,6 +433,29 @@ rpl::producer TopPeersStrip::clicks() const { return _clicks.events(); } +rpl::producer TopPeersStrip::pressed() const { + return _presses.events(); +} + +rpl::producer<> TopPeersStrip::pressCancelled() const { + return _pressCancelled.events(); +} + +void TopPeersStrip::cancelPress() { + const auto pressed = std::exchange(_pressed, -1); + if (pressed >= 0) { + Assert(pressed < _entries.size()); + auto &entry = _entries[pressed]; + if (entry.ripple) { + entry.ripple->lastStop(); + } + } + if (finishDragging()) { + return; + } + _mouseDownPosition = std::nullopt; +} + auto TopPeersStrip::showMenuRequests() const -> rpl::producer { return _showMenuRequests.events(); diff --git a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h index 23db15d1f..4dddd2890 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h @@ -50,6 +50,8 @@ public: [[nodiscard]] bool empty() const; [[nodiscard]] rpl::producer emptyValue() const; [[nodiscard]] rpl::producer clicks() const; + [[nodiscard]] rpl::producer pressed() const; + [[nodiscard]] rpl::producer<> pressCancelled() const; [[nodiscard]] auto showMenuRequests() const -> rpl::producer; [[nodiscard]] auto scrollToRequests() const @@ -61,6 +63,7 @@ public: bool selectByKeyboard(Qt::Key direction); void deselectByKeyboard(); bool chooseRow(); + void cancelPress(); uint64 updateFromParentDrag(QPoint globalPosition); void dragLeft(); @@ -114,6 +117,8 @@ private: rpl::variable _toggleExpanded = nullptr; rpl::event_stream _clicks; + rpl::event_stream _presses; + rpl::event_stream<> _pressCancelled; rpl::event_stream _showMenuRequests; rpl::event_stream> _verticalScrollEvents; diff --git a/Telegram/SourceFiles/window/window_chat_preview.cpp b/Telegram/SourceFiles/window/window_chat_preview.cpp index 24a71c3bc..60ce98bf6 100644 --- a/Telegram/SourceFiles/window/window_chat_preview.cpp +++ b/Telegram/SourceFiles/window/window_chat_preview.cpp @@ -110,7 +110,7 @@ bool ChatPreviewManager::schedule( cancelScheduled(); _menu = nullptr; }); - } else { + } else if (!row.key.history()) { return false; } _scheduled = std::move(row); From 521c17b76c5139ce81efc822ef0778b8bc8cb8de Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 16:49:59 +0400 Subject: [PATCH 175/225] Show chat previews in suggestions. --- Telegram/SourceFiles/boxes/peer_list_box.cpp | 20 ++++- Telegram/SourceFiles/boxes/peer_list_box.h | 12 +++ .../dialogs/ui/dialogs_suggestions.cpp | 76 ++++++++++++++----- 3 files changed, 89 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index dca4a065b..18120e99f 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_widgets.h" #include // XXH64. +#include [[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d) { return XXH64(d.data(), d.size() * sizeof(ushort), 0); @@ -1552,13 +1553,24 @@ void PeerListContent::handleMouseMove(QPoint globalPosition) { && *_lastMousePosition == globalPosition) { return; } + if (_trackPressStart + && ((*_trackPressStart - globalPosition).manhattanLength() + > QApplication::startDragDistance())) { + _trackPressStart = std::nullopt; + _controller->rowTrackPressCancel(); + } selectByMouse(globalPosition); } +void PeerListContent::cancelPress() { + setPressed(Selected()); +} + void PeerListContent::mousePressEvent(QMouseEvent *e) { _pressButton = e->button(); selectByMouse(e->globalPos()); setPressed(_selected); + _trackPressStart = {}; if (auto row = getRow(_selected.index)) { auto updateCallback = [this, row, hint = _selected.index] { updateRow(row, hint); @@ -1586,8 +1598,11 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) { row->addRipple(_st.item, maskGenerator, point, std::move(updateCallback)); } } + if (_pressButton == Qt::LeftButton && _controller->rowTrackPress(row)) { + _trackPressStart = e->globalPos(); + } } - if (anim::Disabled() && !_selected.element) { + if (anim::Disabled() && !_trackPressStart && !_selected.element) { mousePressReleased(e->button()); } } @@ -1597,6 +1612,9 @@ void PeerListContent::mouseReleaseEvent(QMouseEvent *e) { } void PeerListContent::mousePressReleased(Qt::MouseButton button) { + _trackPressStart = {}; + _controller->rowTrackPressCancel(); + updateRow(_pressed.index); updateRow(_selected.index); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 4349fd8c6..eb764d36b 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -347,6 +347,7 @@ public: virtual void peerListSortRows(Fn compare) = 0; virtual int peerListPartitionRows(Fn border) = 0; virtual std::shared_ptr peerListUiShow() = 0; + virtual void peerListCancelPress() = 0; template void peerListAddSelectedPeers(PeerDataRange &&range) { @@ -478,6 +479,12 @@ public: } } + virtual bool rowTrackPress(not_null row) { + return false; + } + virtual void rowTrackPressCancel() { + } + virtual void loadMoreRows() { } virtual void itemDeselectedHook(not_null peer) { @@ -655,6 +662,7 @@ public: void refreshRows(); void mouseLeftGeometry(); + void cancelPress(); void setSearchMode(PeerListSearchMode mode); void changeCheckState( @@ -829,6 +837,7 @@ private: bool _mouseSelection = false; std::optional _lastMousePosition; Qt::MouseButton _pressButton = Qt::LeftButton; + std::optional _trackPressStart; rpl::event_stream _scrollToRequests; @@ -991,6 +1000,9 @@ public: not_null row, bool highlightRow, Fn)> destroyed = nullptr) override; + void peerListCancelPress() override { + _content->cancelPress(); + } protected: not_null content() const { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 28802c811..0b768661e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -86,8 +86,25 @@ private: }; +class ControllerWithPreviews : public PeerListController { +public: + explicit ControllerWithPreviews( + not_null window); + + [[nodiscard]] not_null window() const { + return _window; + } + + bool rowTrackPress(not_null row) override; + void rowTrackPressCancel() override; + +private: + const not_null _window; + +}; + class RecentsController final - : public PeerListController + : public ControllerWithPreviews , public base::has_weak_ptr { public: RecentsController( @@ -115,7 +132,6 @@ private: void subscribeToEvents(); [[nodiscard]] Fn removeAllCallback(); - const not_null _window; RecentPeersList _recent; rpl::variable _count; rpl::event_stream> _chosen; @@ -138,7 +154,7 @@ private: }; class MyChannelsController final - : public PeerListController + : public ControllerWithPreviews , public base::has_weak_ptr { public: explicit MyChannelsController( @@ -163,7 +179,6 @@ private: void appendRow(not_null channel); void fill(bool force = false); - const not_null _window; std::vector> _channels; rpl::variable _toggleExpanded = nullptr; rpl::variable _count = 0; @@ -174,7 +189,7 @@ private: }; class RecommendationsController final - : public PeerListController + : public ControllerWithPreviews , public base::has_weak_ptr { public: explicit RecommendationsController( @@ -201,7 +216,6 @@ private: void setupDivider(); void appendRow(not_null channel); - const not_null _window; rpl::variable _count; History *_activeHistory = nullptr; bool _requested = false; @@ -404,10 +418,36 @@ const style::PeerListItem &ChannelRow::computeSt( return _active ? st::recentPeersItemActive : st::recentPeersItem; } +ControllerWithPreviews::ControllerWithPreviews( + not_null window) +: _window(window) { +} + +bool ControllerWithPreviews::rowTrackPress(not_null row) { + const auto peer = row->peer(); + const auto history = peer->owner().history(peer); + if (base::IsAltPressed()) { + _window->showChatPreview({ history, FullMsgId() }); + delegate()->peerListCancelPress(); + return false; + } + const auto point = delegate()->peerListLastRowMousePosition(); + const auto &st = computeListSt().item; + if (point && point->x() < st.photoPosition.x() + st.photoSize) { + _window->scheduleChatPreview({ history, FullMsgId() }); + return true; + } + return false; +} + +void ControllerWithPreviews::rowTrackPressCancel() { + _window->cancelScheduledPreview(); +} + RecentsController::RecentsController( not_null window, RecentPeersList list) -: _window(window) +: ControllerWithPreviews(window) , _recent(std::move(list)) { } @@ -429,7 +469,7 @@ void RecentsController::rowClicked(not_null row) { Fn RecentsController::removeAllCallback() { const auto weak = base::make_weak(this); - const auto session = &_window->session(); + const auto session = &this->session(); return crl::guard(session, [=] { if (weak) { _count = 0; @@ -450,7 +490,7 @@ base::unique_qptr RecentsController::rowContextMenu( st::popupMenuWithIcons); const auto peer = row->peer(); const auto weak = base::make_weak(this); - const auto session = &_window->session(); + const auto session = &this->session(); const auto removeOne = crl::guard(session, [=] { if (weak) { const auto rowId = peer->id.value; @@ -463,7 +503,7 @@ base::unique_qptr RecentsController::rowContextMenu( session->recentPeers().remove(peer); }); FillEntryMenu(Ui::Menu::CreateAddActionCallback(result), { - .controller = _window, + .controller = window(), .peer = peer, .removeOneText = tr::lng_recent_remove(tr::now), .removeOne = removeOne, @@ -475,7 +515,7 @@ base::unique_qptr RecentsController::rowContextMenu( } Main::Session &RecentsController::session() const { - return _window->session(); + return window()->session(); } QString RecentsController::savedMessagesChatStatus() const { @@ -496,7 +536,7 @@ void RecentsController::setupDivider() { tr::lng_recent_clear(tr::now), st::searchedBarLink); clear->setClickedCallback(RemoveAllConfirm( - _window, + window(), tr::lng_recent_clear_sure(tr::now), removeAllCallback())); rpl::combine( @@ -555,7 +595,7 @@ void RecentsController::subscribeToEvents() { MyChannelsController::MyChannelsController( not_null window) -: _window(window) { +: ControllerWithPreviews(window) { } void MyChannelsController::prepare() { @@ -679,7 +719,7 @@ base::unique_qptr MyChannelsController::rowContextMenu( const auto peer = row->peer(); const auto addAction = Ui::Menu::CreateAddActionCallback(result); Window::FillDialogsEntryMenu( - _window, + window(), Dialogs::EntryState{ .key = peer->owner().history(peer), .section = Dialogs::EntryState::Section::ContextMenu, @@ -689,7 +729,7 @@ base::unique_qptr MyChannelsController::rowContextMenu( } Main::Session &MyChannelsController::session() const { - return _window->session(); + return window()->session(); } void MyChannelsController::setupDivider() { @@ -760,7 +800,7 @@ void MyChannelsController::setupDivider() { RecommendationsController::RecommendationsController( not_null window) -: _window(window) { +: ControllerWithPreviews(window) { } void RecommendationsController::prepare() { @@ -795,7 +835,7 @@ void RecommendationsController::fill() { delegate()->peerListRefreshRows(); _count = delegate()->peerListFullRowsCount(); - _window->activeChatValue() | rpl::start_with_next([=](const Key &key) { + window()->activeChatValue() | rpl::start_with_next([=](const Key &key) { const auto history = key.history(); if (_activeHistory == history) { return; @@ -841,7 +881,7 @@ base::unique_qptr RecommendationsController::rowContextMenu( } Main::Session &RecommendationsController::session() const { - return _window->session(); + return window()->session(); } void RecommendationsController::setupDivider() { From 1f0acae151a380a5b9f06e67f7471b07ab4e2f8a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 17:12:51 +0400 Subject: [PATCH 176/225] Show frequent/recent selected when preview. --- Telegram/SourceFiles/boxes/peer_list_box.cpp | 9 ++- Telegram/SourceFiles/boxes/peer_list_box.h | 8 +-- .../dialogs/ui/dialogs_suggestions.cpp | 36 ++++------ .../dialogs/ui/dialogs_suggestions.h | 2 +- .../dialogs/ui/top_peers_strip.cpp | 72 +++++++++++-------- .../SourceFiles/dialogs/ui/top_peers_strip.h | 4 +- 6 files changed, 71 insertions(+), 60 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 18120e99f..c0ea166e0 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -1562,8 +1562,13 @@ void PeerListContent::handleMouseMove(QPoint globalPosition) { selectByMouse(globalPosition); } -void PeerListContent::cancelPress() { - setPressed(Selected()); +void PeerListContent::pressLeftToContextMenu(bool shown) { + if (shown) { + setContexted(_pressed); + setPressed(Selected()); + } else { + setContexted(Selected()); + } } void PeerListContent::mousePressEvent(QMouseEvent *e) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index eb764d36b..47febc1ad 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -347,7 +347,7 @@ public: virtual void peerListSortRows(Fn compare) = 0; virtual int peerListPartitionRows(Fn border) = 0; virtual std::shared_ptr peerListUiShow() = 0; - virtual void peerListCancelPress() = 0; + virtual void peerListPressLeftToContextMenu(bool shown) = 0; template void peerListAddSelectedPeers(PeerDataRange &&range) { @@ -662,7 +662,7 @@ public: void refreshRows(); void mouseLeftGeometry(); - void cancelPress(); + void pressLeftToContextMenu(bool shown); void setSearchMode(PeerListSearchMode mode); void changeCheckState( @@ -1000,8 +1000,8 @@ public: not_null row, bool highlightRow, Fn)> destroyed = nullptr) override; - void peerListCancelPress() override { - _content->cancelPress(); + void peerListPressLeftToContextMenu(bool shown) override { + _content->pressLeftToContextMenu(shown); } protected: diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 0b768661e..179b08887 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -86,7 +86,9 @@ private: }; -class ControllerWithPreviews : public PeerListController { +class ControllerWithPreviews + : public PeerListController + , public base::has_weak_ptr { public: explicit ControllerWithPreviews( not_null window); @@ -103,9 +105,7 @@ private: }; -class RecentsController final - : public ControllerWithPreviews - , public base::has_weak_ptr { +class RecentsController final : public ControllerWithPreviews { public: RecentsController( not_null window, @@ -153,9 +153,7 @@ private: }; -class MyChannelsController final - : public ControllerWithPreviews - , public base::has_weak_ptr { +class MyChannelsController final : public ControllerWithPreviews { public: explicit MyChannelsController( not_null window); @@ -188,9 +186,7 @@ private: }; -class RecommendationsController final - : public ControllerWithPreviews - , public base::has_weak_ptr { +class RecommendationsController final : public ControllerWithPreviews { public: explicit RecommendationsController( not_null window); @@ -426,15 +422,17 @@ ControllerWithPreviews::ControllerWithPreviews( bool ControllerWithPreviews::rowTrackPress(not_null row) { const auto peer = row->peer(); const auto history = peer->owner().history(peer); + const auto callback = crl::guard(this, [=](bool shown) { + delegate()->peerListPressLeftToContextMenu(shown); + }); if (base::IsAltPressed()) { - _window->showChatPreview({ history, FullMsgId() }); - delegate()->peerListCancelPress(); + _window->showChatPreview({ history, FullMsgId() }, callback); return false; } const auto point = delegate()->peerListLastRowMousePosition(); const auto &st = computeListSt().item; if (point && point->x() < st.photoPosition.x() + st.photoSize) { - _window->scheduleChatPreview({ history, FullMsgId() }); + _window->scheduleChatPreview({ history, FullMsgId() }, callback); return true; } return false; @@ -982,8 +980,8 @@ void Suggestions::setupChats() { }, _topPeers->lifetime()); _topPeers->pressed() | rpl::start_with_next([=](uint64 peerIdRaw) { - handlePressForChatPreview(PeerId(peerIdRaw), [=] { - _topPeers->cancelPress(); + handlePressForChatPreview(PeerId(peerIdRaw), [=](bool shown) { + _topPeers->pressLeftToContextMenu(shown); }); }, _topPeers->lifetime()); @@ -1036,12 +1034,8 @@ void Suggestions::setupChats() { void Suggestions::handlePressForChatPreview( PeerId id, - Fn cancelPress) { - const auto callback = crl::guard(this, [=](bool shown) { - if (shown) { - cancelPress(); - } - }); + Fn callback) { + callback = crl::guard(this, callback); const auto row = RowDescriptor( _controller->session().data().history(id), FullMsgId()); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 16e07d15b..734ec554c 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -122,7 +122,7 @@ private: void startSlideAnimation(); void finishShow(); - void handlePressForChatPreview(PeerId id, Fn cancelPress); + void handlePressForChatPreview(PeerId id, Fn callback); const not_null _controller; diff --git a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp index 48ec56696..67cafb98c 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp @@ -381,14 +381,7 @@ void TopPeersStrip::stripMouseReleaseEvent(QMouseEvent *e) { _mouseDownPosition = std::nullopt; }); - const auto pressed = std::exchange(_pressed, -1); - if (pressed >= 0) { - Assert(pressed < _entries.size()); - auto &entry = _entries[pressed]; - if (entry.ripple) { - entry.ripple->lastStop(); - } - } + const auto pressed = clearPressed(); if (finishDragging()) { return; } @@ -400,6 +393,18 @@ void TopPeersStrip::stripMouseReleaseEvent(QMouseEvent *e) { } } +int TopPeersStrip::clearPressed() { + const auto pressed = std::exchange(_pressed, -1); + if (pressed >= 0) { + Assert(pressed < _entries.size()); + auto &entry = _entries[pressed]; + if (entry.ripple) { + entry.ripple->lastStop(); + } + } + return pressed; +} + void TopPeersStrip::updateScrollMax(int newWidth) { if (_expandAnimation.animating()) { return; @@ -441,15 +446,13 @@ rpl::producer<> TopPeersStrip::pressCancelled() const { return _pressCancelled.events(); } -void TopPeersStrip::cancelPress() { - const auto pressed = std::exchange(_pressed, -1); - if (pressed >= 0) { - Assert(pressed < _entries.size()); - auto &entry = _entries[pressed]; - if (entry.ripple) { - entry.ripple->lastStop(); - } +void TopPeersStrip::pressLeftToContextMenu(bool shown) { + if (!shown) { + _contexted = -1; + update(); + return; } + _contexted = clearPressed(); if (finishDragging()) { return; } @@ -491,6 +494,9 @@ void TopPeersStrip::removeLocally(uint64 id) { if (_pressed > index) { --_pressed; } + if (_contexted > index) { + --_contexted; + } updateScrollMax(); _count = int(_entries.size()); update(); @@ -603,8 +609,17 @@ void TopPeersStrip::apply(const TopPeersList &list) { } auto now = std::vector(); - auto selectedId = (_selected >= 0) ? _entries[_selected].id : 0; - auto pressedId = (_pressed >= 0) ? _entries[_pressed].id : 0; + const auto selectedId = (_selected >= 0) ? _entries[_selected].id : 0; + const auto pressedId = (_pressed >= 0) ? _entries[_pressed].id : 0; + const auto contextedId = (_contexted >= 0) ? _entries[_contexted].id : 0; + const auto restoreIndex = [&](uint64 id) { + if (!id) { + return -1; + } + const auto i = ranges::find(_entries, id, &Entry::id); + return (i != end(_entries)) ? int(i - begin(_entries)) : -1; + }; + for (const auto &entry : list.entries) { if (_removed.contains(entry.id)) { continue; @@ -627,18 +642,9 @@ void TopPeersStrip::apply(const TopPeersList &list) { } } _entries = std::move(now); - if (selectedId) { - const auto i = ranges::find(_entries, selectedId, &Entry::id); - if (i != end(_entries)) { - _selected = int(i - begin(_entries)); - } - } - if (pressedId) { - const auto i = ranges::find(_entries, pressedId, &Entry::id); - if (i != end(_entries)) { - _pressed = int(i - begin(_entries)); - } - } + _selected = restoreIndex(selectedId); + _pressed = restoreIndex(pressedId); + _contexted = restoreIndex(contextedId); updateScrollMax(); unsubscribeUserpics(); _count = int(_entries.size()); @@ -736,7 +742,11 @@ void TopPeersStrip::paintStrip(QRect clip) { auto x = int(base::SafeRound(-shift + from * fsingle + added)); auto y = row * st.height; - const auto highlighted = (_pressed >= 0) ? _pressed : _selected; + const auto highlighted = (_contexted >= 0) + ? _contexted + : (_pressed >= 0) + ? _pressed + : _selected; for (auto i = from; i != till; ++i) { auto &entry = _entries[i]; const auto selected = (i == highlighted); diff --git a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h index 4dddd2890..f33ec722b 100644 --- a/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h +++ b/Telegram/SourceFiles/dialogs/ui/top_peers_strip.h @@ -63,7 +63,7 @@ public: bool selectByKeyboard(Qt::Key direction); void deselectByKeyboard(); bool chooseRow(); - void cancelPress(); + void pressLeftToContextMenu(bool shown); uint64 updateFromParentDrag(QPoint globalPosition); void dragLeft(); @@ -105,6 +105,7 @@ private: [[nodiscard]] QRect innerRounded() const; [[nodiscard]] int scrollLeft() const; [[nodiscard]] Layout currentLayout() const; + int clearPressed(); void apply(const TopPeersList &list); void apply(Entry &entry, const TopPeersEntry &data); @@ -132,6 +133,7 @@ private: int _selected = -1; int _pressed = -1; + int _contexted = -1; bool _selectionByKeyboard = false; bool _hiddenLocally = false; From 0fffeac8da689d1259e982de9699cac1f0cf36c6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 19:08:04 +0400 Subject: [PATCH 177/225] Fix drag-n-drop forward of selected album. --- Telegram/SourceFiles/history/history_inner_widget.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 57f11e678..c35035854 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1738,7 +1738,16 @@ std::unique_ptr HistoryInner::prepareDrag() { return mimeData; } else if (pressedView) { auto forwardIds = MessageIdsList(); - if (_mouseCursorState == CursorState::Date) { + const auto tryForwardSelection = uponSelected + && !_controller->adaptive().isOneColumn(); + const auto forwardSelectionState = tryForwardSelection + ? getSelectionState() + : HistoryView::TopBarWidget::SelectedState(); + if (forwardSelectionState.count > 0 + && (forwardSelectionState.count + == forwardSelectionState.canForwardCount)) { + forwardIds = getSelectedItems(); + } else if (_mouseCursorState == CursorState::Date) { forwardIds = session().data().itemOrItsGroup(_mouseActionItem); } else if (pressedView->isHiddenByGroup() && pressedHandler) { forwardIds = MessageIdsList(1, _mouseActionItem->fullId()); From a1049ec7ce017ed62b3bba6b14c805b1de1357d4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 19:12:22 +0400 Subject: [PATCH 178/225] Add touchscreen preview to recent / channels. --- Telegram/SourceFiles/boxes/peer_list_box.cpp | 21 ++++- Telegram/SourceFiles/boxes/peer_list_box.h | 10 +++ .../dialogs/dialogs_inner_widget.cpp | 13 +-- .../dialogs/dialogs_inner_widget.h | 2 +- .../dialogs/ui/dialogs_suggestions.cpp | 88 ++++++++++++++++++- .../dialogs/ui/dialogs_suggestions.h | 3 + .../window/window_chat_preview.cpp | 16 +++- .../SourceFiles/window/window_chat_preview.h | 7 +- .../window/window_session_controller.cpp | 12 ++- .../window/window_session_controller.h | 6 +- 10 files changed, 154 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index c0ea166e0..22cc40411 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -1556,10 +1556,12 @@ void PeerListContent::handleMouseMove(QPoint globalPosition) { if (_trackPressStart && ((*_trackPressStart - globalPosition).manhattanLength() > QApplication::startDragDistance())) { - _trackPressStart = std::nullopt; + _trackPressStart = {}; _controller->rowTrackPressCancel(); } - selectByMouse(globalPosition); + if (!_controller->rowTrackPressSkipMouseSelection()) { + selectByMouse(globalPosition); + } } void PeerListContent::pressLeftToContextMenu(bool shown) { @@ -1571,13 +1573,24 @@ void PeerListContent::pressLeftToContextMenu(bool shown) { } } +bool PeerListContent::trackRowPressFromGlobal(QPoint globalPosition) { + selectByMouse(globalPosition); + if (const auto row = getRow(_selected.index)) { + if (_controller->rowTrackPress(row)) { + _trackPressStart = globalPosition; + return true; + } + } + return false; +} + void PeerListContent::mousePressEvent(QMouseEvent *e) { _pressButton = e->button(); selectByMouse(e->globalPos()); setPressed(_selected); _trackPressStart = {}; - if (auto row = getRow(_selected.index)) { - auto updateCallback = [this, row, hint = _selected.index] { + if (const auto row = getRow(_selected.index)) { + const auto updateCallback = [this, row, hint = _selected.index] { updateRow(row, hint); }; if (_selected.element) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 47febc1ad..aa24856e2 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -347,7 +347,9 @@ public: virtual void peerListSortRows(Fn compare) = 0; virtual int peerListPartitionRows(Fn border) = 0; virtual std::shared_ptr peerListUiShow() = 0; + virtual void peerListPressLeftToContextMenu(bool shown) = 0; + virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0; template void peerListAddSelectedPeers(PeerDataRange &&range) { @@ -484,6 +486,9 @@ public: } virtual void rowTrackPressCancel() { } + virtual bool rowTrackPressSkipMouseSelection() { + return false; + } virtual void loadMoreRows() { } @@ -663,6 +668,7 @@ public: void mouseLeftGeometry(); void pressLeftToContextMenu(bool shown); + bool trackRowPressFromGlobal(QPoint globalPosition); void setSearchMode(PeerListSearchMode mode); void changeCheckState( @@ -1000,9 +1006,13 @@ public: not_null row, bool highlightRow, Fn)> destroyed = nullptr) override; + void peerListPressLeftToContextMenu(bool shown) override { _content->pressLeftToContextMenu(shown); } + bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override { + return _content->trackRowPressFromGlobal(globalPosition); + } protected: not_null content() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 343730d18..d0d8b8fe7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1526,7 +1526,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { if (alt && showChatPreview()) { return; } else if (!alt && isUserpicPress()) { - scheduleChatPreview(); + scheduleChatPreview(e->globalPos()); } if (base::in_range(_collapsedSelected, 0, _collapsedRows.size())) { @@ -2440,12 +2440,16 @@ void InnerWidget::chatPreviewShown(bool shown, RowDescriptor row) { } } -bool InnerWidget::scheduleChatPreview() { +bool InnerWidget::scheduleChatPreview(QPoint positionOverride) { const auto row = computeChatPreviewRow(); const auto callback = crl::guard(this, [=](bool shown) { chatPreviewShown(shown, row); }); - _chatPreviewScheduled = _controller->scheduleChatPreview(row, callback); + _chatPreviewScheduled = _controller->scheduleChatPreview( + row, + callback, + nullptr, + positionOverride); return _chatPreviewScheduled; } @@ -2544,8 +2548,7 @@ bool InnerWidget::processTouchEvent(not_null e) { return false; } selectByMouse(*point); - const auto onlyUserpic = true; - if (isUserpicPress() && scheduleChatPreview()) { + if (isUserpicPressOnWide() && scheduleChatPreview(*point)) { _chatPreviewTouchGlobal = point; } else if (!_dragging) { _touchDragStartGlobal = point; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index ccca8eb71..9f069c243 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -126,7 +126,7 @@ public: [[nodiscard]] bool isUserpicPress() const; [[nodiscard]] bool isUserpicPressOnWide() const; void cancelChatPreview(); - bool scheduleChatPreview(); + bool scheduleChatPreview(QPoint positionOverride); bool showChatPreview(); void chatPreviewShown(bool shown, RowDescriptor row = {}); bool chooseRow( diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 179b08887..ad6fe1f69 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -99,10 +99,17 @@ public: bool rowTrackPress(not_null row) override; void rowTrackPressCancel() override; + bool rowTrackPressSkipMouseSelection() override; + + bool processTouchEvent(not_null e); + void setupTouchChatPreview(not_null scroll); private: const not_null _window; + std::optional _chatPreviewTouchGlobal; + rpl::event_stream<> _touchCancelRequests; + }; class RecentsController final : public ControllerWithPreviews { @@ -426,22 +433,81 @@ bool ControllerWithPreviews::rowTrackPress(not_null row) { delegate()->peerListPressLeftToContextMenu(shown); }); if (base::IsAltPressed()) { - _window->showChatPreview({ history, FullMsgId() }, callback); + _window->showChatPreview( + { history, FullMsgId() }, + callback, + nullptr, + _chatPreviewTouchGlobal); return false; } const auto point = delegate()->peerListLastRowMousePosition(); const auto &st = computeListSt().item; if (point && point->x() < st.photoPosition.x() + st.photoSize) { - _window->scheduleChatPreview({ history, FullMsgId() }, callback); + _window->scheduleChatPreview( + { history, FullMsgId() }, + callback, + nullptr, + _chatPreviewTouchGlobal); return true; } return false; } void ControllerWithPreviews::rowTrackPressCancel() { + _chatPreviewTouchGlobal = {}; _window->cancelScheduledPreview(); } +bool ControllerWithPreviews::rowTrackPressSkipMouseSelection() { + return _chatPreviewTouchGlobal.has_value(); +} + +bool ControllerWithPreviews::processTouchEvent(not_null e) { + const auto point = e->touchPoints().empty() + ? std::optional() + : e->touchPoints().front().screenPos().toPoint(); + switch (e->type()) { + case QEvent::TouchBegin: { + if (!point) { + return false; + } + _chatPreviewTouchGlobal = point; + if (!delegate()->peerListTrackRowPressFromGlobal(*point)) { + _chatPreviewTouchGlobal = {}; + } + } break; + + case QEvent::TouchUpdate: { + if (!point) { + return false; + } + if (_chatPreviewTouchGlobal) { + const auto delta = (*_chatPreviewTouchGlobal - *point); + if (delta.manhattanLength() > computeListSt().item.photoSize) { + rowTrackPressCancel(); + } + } + } break; + + case QEvent::TouchEnd: + case QEvent::TouchCancel: { + if (_chatPreviewTouchGlobal) { + rowTrackPressCancel(); + } + } break; + } + return false; +} + +void ControllerWithPreviews::setupTouchChatPreview( + not_null scroll) { + _touchCancelRequests.events() | rpl::start_with_next([=] { + QTouchEvent ev(QEvent::TouchCancel); + ev.setTimestamp(crl::now()); + QGuiApplication::sendEvent(scroll, &ev); + }, lifetime()); +} + RecentsController::RecentsController( not_null window, RecentPeersList list) @@ -1030,6 +1096,7 @@ void Suggestions::setupChats() { }, _topPeers->lifetime()); _chatsScroll->setVisible(_tab.current() == Tab::Chats); + _chatsScroll->setCustomTouchProcess(_recentProcessTouch); } void Suggestions::handlePressForChatPreview( @@ -1063,6 +1130,11 @@ void Suggestions::setupChannels() { anim::type::instant); _channelsScroll->setVisible(_tab.current() == Tab::Channels); + _channelsScroll->setCustomTouchProcess([=](not_null e) { + const auto myChannels = _myChannelsProcessTouch(e); + const auto recommendations = _recommendationsProcessTouch(e); + return myChannels || recommendations; + }); } void Suggestions::selectJump(Qt::Key direction, int pageSize) { @@ -1371,6 +1443,9 @@ object_ptr> Suggestions::setupRecentPeers( controller->setStyleOverrides(&st::recentPeersList); _recentCount = controller->count(); + _recentProcessTouch = [=](not_null e) { + return controller->processTouchEvent(e); + }; controller->chosen( ) | rpl::start_with_next([=](not_null peer) { @@ -1419,6 +1494,7 @@ object_ptr> Suggestions::setupRecentPeers( delegate->setContent(raw); controller->setDelegate(delegate); + controller->setupTouchChatPreview(_chatsScroll.get()); return object_ptr>(this, std::move(content)); } @@ -1474,6 +1550,9 @@ object_ptr> Suggestions::setupMyChannels() { controller->setStyleOverrides(&st::recentPeersList); _myChannelsCount = controller->count(); + _myChannelsProcessTouch = [=](not_null e) { + return controller->processTouchEvent(e); + }; controller->chosen( ) | rpl::start_with_next([=](not_null peer) { @@ -1535,6 +1614,7 @@ object_ptr> Suggestions::setupMyChannels() { delegate->setContent(raw); controller->setDelegate(delegate); + controller->setupTouchChatPreview(_channelsScroll.get()); return object_ptr>(this, std::move(content)); } @@ -1549,6 +1629,9 @@ object_ptr> Suggestions::setupRecommendations() { controller->setStyleOverrides(&st::recentPeersList); _recommendationsCount = controller->count(); + _recommendationsProcessTouch = [=](not_null e) { + return controller->processTouchEvent(e); + }; _tab.value() | rpl::filter( rpl::mappers::_1 == Tab::Channels @@ -1603,6 +1686,7 @@ object_ptr> Suggestions::setupRecommendations() { delegate->setContent(raw); controller->setDelegate(delegate); + controller->setupTouchChatPreview(_channelsScroll.get()); return object_ptr>(this, std::move(content)); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 734ec554c..8b33718e6 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -139,6 +139,7 @@ private: Fn _recentSelectJump; Fn _recentUpdateFromParentDrag; Fn _recentDragLeft; + Fn)> _recentProcessTouch; const not_null*> _recentPeers; const not_null*> _emptyRecent; @@ -150,6 +151,7 @@ private: Fn _myChannelsSelectJump; Fn _myChannelsUpdateFromParentDrag; Fn _myChannelsDragLeft; + Fn)> _myChannelsProcessTouch; const not_null*> _myChannels; rpl::variable _recommendationsCount; @@ -157,6 +159,7 @@ private: Fn _recommendationsSelectJump; Fn _recommendationsUpdateFromParentDrag; Fn _recommendationsDragLeft; + Fn)> _recommendationsProcessTouch; const not_null*> _recommendations; const not_null*> _emptyChannels; diff --git a/Telegram/SourceFiles/window/window_chat_preview.cpp b/Telegram/SourceFiles/window/window_chat_preview.cpp index 60ce98bf6..e95d58d4c 100644 --- a/Telegram/SourceFiles/window/window_chat_preview.cpp +++ b/Telegram/SourceFiles/window/window_chat_preview.cpp @@ -34,7 +34,8 @@ ChatPreviewManager::ChatPreviewManager( bool ChatPreviewManager::show( Dialogs::RowDescriptor row, Fn callback, - QPointer parentOverride) { + QPointer parentOverride, + std::optional positionOverride) { cancelScheduled(); _topicLifetime.destroy(); if (const auto topic = row.key.topic()) { @@ -94,7 +95,7 @@ bool ChatPreviewManager::show( if (callback) { callback(true); } - _menu->popup(QCursor::pos()); + _menu->popup(positionOverride.value_or(QCursor::pos())); return true; } @@ -102,7 +103,8 @@ bool ChatPreviewManager::show( bool ChatPreviewManager::schedule( Dialogs::RowDescriptor row, Fn callback, - QPointer parentOverride) { + QPointer parentOverride, + std::optional positionOverride) { cancelScheduled(); _topicLifetime.destroy(); if (const auto topic = row.key.topic()) { @@ -116,17 +118,23 @@ bool ChatPreviewManager::schedule( _scheduled = std::move(row); _scheduledCallback = std::move(callback); _scheduledParentOverride = std::move(parentOverride); + _scheduledPositionOverride = positionOverride; _timer.callOnce(kChatPreviewDelay); return true; } void ChatPreviewManager::showScheduled() { - show(base::take(_scheduled), base::take(_scheduledCallback)); + show( + base::take(_scheduled), + base::take(_scheduledCallback), + nullptr, + base::take(_scheduledPositionOverride)); } void ChatPreviewManager::cancelScheduled() { _scheduled = {}; _scheduledCallback = nullptr; + _scheduledPositionOverride = {}; _timer.cancel(); } diff --git a/Telegram/SourceFiles/window/window_chat_preview.h b/Telegram/SourceFiles/window/window_chat_preview.h index 6101ac3e9..3ad2b9de2 100644 --- a/Telegram/SourceFiles/window/window_chat_preview.h +++ b/Telegram/SourceFiles/window/window_chat_preview.h @@ -26,11 +26,13 @@ public: bool show( Dialogs::RowDescriptor row, Fn callback = nullptr, - QPointer parentOverride = nullptr); + QPointer parentOverride = nullptr, + std::optional positionOverride = {}); bool schedule( Dialogs::RowDescriptor row, Fn callback = nullptr, - QPointer parentOverride = nullptr); + QPointer parentOverride = nullptr, + std::optional positionOverride = {}); void cancelScheduled(); private: @@ -40,6 +42,7 @@ private: Dialogs::RowDescriptor _scheduled; Fn _scheduledCallback; QPointer _scheduledParentOverride; + std::optional _scheduledPositionOverride; base::Timer _timer; rpl::lifetime _topicLifetime; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 4a004dce8..59d12ab35 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -2979,21 +2979,25 @@ QString SessionController::premiumRef() const { bool SessionController::showChatPreview( Dialogs::RowDescriptor row, Fn callback, - QPointer parentOverride) { + QPointer parentOverride, + std::optional positionOverride) { return _chatPreviewManager->show( std::move(row), std::move(callback), - std::move(parentOverride)); + std::move(parentOverride), + positionOverride); } bool SessionController::scheduleChatPreview( Dialogs::RowDescriptor row, Fn callback, - QPointer parentOverride) { + QPointer parentOverride, + std::optional positionOverride) { return _chatPreviewManager->schedule( std::move(row), std::move(callback), - std::move(parentOverride)); + std::move(parentOverride), + positionOverride); } void SessionController::cancelScheduledPreview() { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 37f45f86e..35e690903 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -604,11 +604,13 @@ public: bool showChatPreview( Dialogs::RowDescriptor row, Fn callback = nullptr, - QPointer parentOverride = nullptr); + QPointer parentOverride = nullptr, + std::optional positionOverride = {}); bool scheduleChatPreview( Dialogs::RowDescriptor row, Fn callback = nullptr, - QPointer parentOverride = nullptr); + QPointer parentOverride = nullptr, + std::optional positionOverride = {}); void cancelScheduledPreview(); [[nodiscard]] bool contentOverlapped(QWidget *w, QPaintEvent *e) const; From dd0d88ccd3089c6840bceb884f2bc8b5d532702f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 31 May 2024 15:26:19 +0300 Subject: [PATCH 179/225] Fixed userpic views in headers of forwarded messages with via bots. --- Telegram/SourceFiles/history/history_item_components.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index fd1a04942..69b514f53 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -222,14 +222,17 @@ void HistoryMessageForwarded::create( phrase = tr::lng_forwarded_story( tr::now, lt_user, - Ui::Text::Link(phrase.text, QString()), // Link 1. + Ui::Text::Wrapped(phrase, EntityType::CustomUrl, QString()), // Link 1. Ui::Text::WithEntities); } else if (via && psaType.isEmpty()) { + const auto linkData = Ui::Text::Link( + QString(), + 1).entities.front().data(); // Link 1. if (fromChannel) { phrase = tr::lng_forwarded_channel_via( tr::now, lt_channel, - Ui::Text::Link(phrase.text, 1), // Link 1. + Ui::Text::Wrapped(phrase, EntityType::CustomUrl, linkData), // Link 1. lt_inline_bot, Ui::Text::Link('@' + via->bot->username(), 2), // Link 2. Ui::Text::WithEntities); @@ -237,7 +240,7 @@ void HistoryMessageForwarded::create( phrase = tr::lng_forwarded_via( tr::now, lt_user, - Ui::Text::Link(phrase.text, 1), // Link 1. + Ui::Text::Wrapped(phrase, EntityType::CustomUrl, linkData), // Link 1. lt_inline_bot, Ui::Text::Link('@' + via->bot->username(), 2), // Link 2. Ui::Text::WithEntities); From 50ce847b316c41adb5b48c7f86739094877593aa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 31 May 2024 15:12:50 +0300 Subject: [PATCH 180/225] Fixed display of info in media from chat preview. --- .../history/view/media/history_view_extended_preview.cpp | 1 + Telegram/SourceFiles/history/view/media/history_view_gif.cpp | 1 + .../history/view/media/history_view_media_grouped.cpp | 1 + .../history/view/media/history_view_media_unwrapped.cpp | 1 + Telegram/SourceFiles/history/view/media/history_view_photo.cpp | 1 + 5 files changed, 5 insertions(+) diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index 7d5b9f00a..60db55c8c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -335,6 +335,7 @@ bool ExtendedPreview::needInfoDisplay() const { return _parent->data()->isSending() || _parent->data()->hasFailed() || _parent->isUnderCursor() + || (_parent->delegate()->elementContext() == Context::ChatPreview) || _parent->isLastAndSelfMessage(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 000cacbfa..4ae4da892 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1963,6 +1963,7 @@ bool Gif::needInfoDisplay() const { return _parent->data()->isSending() || _data->uploading() || _parent->isUnderCursor() + || (_parent->delegate()->elementContext() == Context::ChatPreview) // Don't show the GIF badge if this message has text. || (!_parent->hasBubble() && _parent->isLastAndSelfMessage()); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index b1c8ba33d..3297ef8c7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -827,6 +827,7 @@ bool GroupedMedia::needInfoDisplay() const { && (_parent->data()->isSending() || _parent->data()->hasFailed() || _parent->isUnderCursor() + || (_parent->delegate()->elementContext() == Context::ChatPreview) || _parent->isLastAndSelfMessage()); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index beee98df1..237c30b87 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -647,6 +647,7 @@ bool UnwrappedMedia::needInfoDisplay() const { || _parent->isUnderCursor() || _parent->rightActionSize() || _parent->isLastAndSelfMessage() + || (_parent->delegate()->elementContext() == Context::ChatPreview) || (_parent->hasRightLayout() && _content->alwaysShowOutTimestamp()); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 869527ece..e5c7877fa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -823,6 +823,7 @@ bool Photo::needInfoDisplay() const { return _parent->data()->isSending() || _parent->data()->hasFailed() || _parent->isUnderCursor() + || (_parent->delegate()->elementContext() == Context::ChatPreview) || _parent->isLastAndSelfMessage(); } From ba611d0f2dd71d76f40db1e143ea6feac5264b20 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 31 May 2024 18:58:14 +0300 Subject: [PATCH 181/225] Added initial api support of text phone entity in messages. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 1 + .../SourceFiles/api/api_text_entities.cpp | 9 +- .../SourceFiles/core/click_handler_types.h | 2 + .../SourceFiles/core/phone_click_handler.cpp | 325 ++++++++++++++++++ .../SourceFiles/core/phone_click_handler.h | 30 ++ Telegram/SourceFiles/core/ui_integration.cpp | 3 + Telegram/SourceFiles/history/history_item.cpp | 1 + Telegram/lib_ui | 2 +- 9 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 Telegram/SourceFiles/core/phone_click_handler.cpp create mode 100644 Telegram/SourceFiles/core/phone_click_handler.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 8b081b263..1517a952e 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -445,6 +445,8 @@ PRIVATE core/launcher.h core/local_url_handlers.cpp core/local_url_handlers.h + core/phone_click_handler.cpp + core/phone_click_handler.h core/sandbox.cpp core/sandbox.h core/shortcuts.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 1b8491c85..bf8781ace 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3427,6 +3427,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_add_contact" = "Create"; "lng_add_contact_button" = "New contact"; "lng_contacts_header" = "Contacts"; +"lng_menu_not_contact" = "This number is not on Telegram"; "lng_contacts_hidden_stories" = "Hidden Stories"; "lng_contacts_stories_status#one" = "{count} story"; "lng_contacts_stories_status#other" = "{count} stories"; diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 93d5cc5f3..067cc6c0c 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -178,7 +178,11 @@ EntitiesInText EntitiesFromMTP( }); } }, [&](const MTPDmessageEntityPhone &d) { - // Skipping phones. + result.push_back({ + EntityType::Phone, + d.voffset().v, + d.vlength().v, + }); }, [&](const MTPDmessageEntityCashtag &d) { result.push_back({ EntityType::Cashtag, @@ -266,6 +270,9 @@ MTPVector EntitiesToMTP( case EntityType::Email: { v.push_back(MTP_messageEntityEmail(offset, length)); } break; + case EntityType::Phone: { + v.push_back(MTP_messageEntityPhone(offset, length)); + } break; case EntityType::Hashtag: { v.push_back(MTP_messageEntityHashtag(offset, length)); } break; diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index e064d1e2d..20879a0ab 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -52,6 +52,8 @@ struct ClickHandlerContext { }; Q_DECLARE_METATYPE(ClickHandlerContext); +class PhoneClickHandler; + class HiddenUrlClickHandler : public UrlClickHandler { public: HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) { diff --git a/Telegram/SourceFiles/core/phone_click_handler.cpp b/Telegram/SourceFiles/core/phone_click_handler.cpp new file mode 100644 index 000000000..e510cfa04 --- /dev/null +++ b/Telegram/SourceFiles/core/phone_click_handler.cpp @@ -0,0 +1,325 @@ +/* +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 "core/phone_click_handler.h" + +#include "core/click_handler_types.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "info/profile/info_profile_values.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "mainwidget.h" +#include "mtproto/sender.h" +#include "ui/effects/ripple_animation.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/widgets/menu/menu_item_base.h" +#include "ui/widgets/popup_menu.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" +#include "styles/style_calls.h" +#include "styles/style_chat.h" // popupMenuExpandedSeparator. +#include "styles/style_menu_icons.h" + +namespace { + +[[nodiscard]] QString Trim(QString text) { + return text + .replace('+', QString()) + .replace(' ', QString()) + .replace('-', QString()); +} + +class ResolvePhoneAction final : public Ui::Menu::ItemBase { +public: + ResolvePhoneAction( + not_null parent, + const style::Menu &st, + const QString &phone, + not_null controller); + + bool isEnabled() const override; + not_null action() const override; + + void handleKeyPress(not_null e) override; + +protected: + QPoint prepareRippleStartPosition() const override; + QImage prepareRippleMask() const override; + + int contentHeight() const override; + +private: + void prepare(); + void paint(Painter &p); + + const not_null _dummyAction; + const style::Menu &_st; + rpl::variable _peer; + rpl::variable _loaded; + Ui::PeerUserpicView _userpicView; + + MTP::Sender _api; + + Ui::Text::String _above; + Ui::Text::String _below; + int _aboveWidth = 0; + int _belowWidth = 0; + const int _height = 0; + +}; + +ResolvePhoneAction::ResolvePhoneAction( + not_null parent, + const style::Menu &st, + const QString &phone, + not_null controller) +: ItemBase(parent, st) +, _dummyAction(new QAction(parent)) +, _st(st) +, _api(&controller->session().mtp()) +, _height(rect::m::sum::v(st::groupCallJoinAsPadding) + + st::groupCallJoinAsPhotoSize) { + setAcceptBoth(true); + initResizeHook(parent->sizeValue()); + setClickedCallback([=] { + if (const auto peer = _peer.current()) { + controller->showPeerInfo(peer); + } + }); + + const auto formattedPhone = Trim(phone); + + const auto owner = &controller->session().data(); + + if (const auto peer = owner->userByPhone(formattedPhone)) { + _peer = peer; + _loaded.force_assign(true); + } else { + _api.request(MTPcontacts_ResolvePhone( + MTP_string(phone) + )).done([=](const MTPcontacts_ResolvedPeer &result) { + result.match([&](const MTPDcontacts_resolvedPeer &data) { + owner->processUsers(data.vusers()); + owner->processChats(data.vchats()); + if (const auto peerId = peerFromMTP(data.vpeer())) { + _peer = owner->peer(peerId); + } + _loaded.force_assign(true); + }); + }).fail([=](const MTP::Error &error) { + if (error.code() == 400) { + _peer.force_assign(nullptr); + _loaded.force_assign(true); + } + }).send(); + } + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + paint(p); + }, lifetime()); + + enableMouseSelecting(); + prepare(); +} + +void ResolvePhoneAction::paint(Painter &p) { + const auto selected = isSelected() && _peer.current(); + const auto height = contentHeight(); + if (selected && _st.itemBgOver->c.alpha() < 255) { + p.fillRect(0, 0, width(), height, _st.itemBg); + } + p.fillRect(0, 0, width(), height, selected ? _st.itemBgOver : _st.itemBg); + if (isEnabled()) { + paintRipple(p, 0, 0); + } + + const auto &padding = st::groupCallJoinAsPadding; + const auto textLeft = padding.left() + + st::groupCallJoinAsPhotoSize + + padding.left(); + if (const auto peer = _peer.current()) { + peer->paintUserpic( + p, + _userpicView, + padding.left(), + padding.top(), + st::groupCallJoinAsPhotoSize); + p.setPen(selected ? _st.itemFgOver : _st.itemFg); + _above.drawLeftElided( + p, + textLeft, + st::groupCallJoinAsTextTop, + width() - textLeft - padding.right(), + width()); + p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut); + _below.drawLeftElided( + p, + textLeft, + st::groupCallJoinAsNameTop, + _belowWidth, + width()); + } else { + p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut); + p.drawText(rect() - padding, _below.toString(), style::al_center); + } +} + +void ResolvePhoneAction::prepare() { + rpl::combine( + tr::lng_context_view_profile(), + _peer.value( + ) | rpl::map([](PeerData *peer) { + return peer + ? Info::Profile::NameValue(peer) + : rpl::single(QString()); + }) | rpl::flatten_latest(), + tr::lng_menu_not_contact(), + _loaded.value( + ) | rpl::map([](bool loaded) { + return loaded + ? rpl::single(QString()) + : tr::lng_contacts_loading(); + }) | rpl::flatten_latest() + ) | rpl::start_with_next([=]( + QString text, + QString name, + QString no, + QString loading) { + const auto &padding = st::groupCallJoinAsPadding; + QWidget::setAttribute( + Qt::WA_TransparentForMouseEvents, + !_peer.current()); + const auto above = name; + const auto below = !loading.isEmpty() + ? loading + : name.isEmpty() + ? no + : text; + const auto options = kDefaultTextOptions; + const auto tempWidth = [&] { + _below.setMarkedText(_st.itemStyle, { text }, options); + return _below.maxWidth(); + }(); + _above.setMarkedText(_st.itemStyle, { above }, options); + _below.setMarkedText(_st.itemStyle, { below }, options); + const auto textWidth = _above.maxWidth(); + const auto nameWidth = _below.maxWidth(); + const auto textLeft = padding.left() + + st::groupCallJoinAsPhotoSize + + padding.left(); + const auto w = std::clamp( + (textLeft + tempWidth + padding.right()), + _st.widthMin, + _st.widthMax); + setMinWidth(w); + _aboveWidth = w - textLeft - padding.right(); + _belowWidth = w + - ((loading.isEmpty() && name.isEmpty()) ? 0 : textLeft) + - padding.right(); + update(); + }, lifetime()); +} + +bool ResolvePhoneAction::isEnabled() const { + return true; +} + +not_null ResolvePhoneAction::action() const { + return _dummyAction; +} + +QPoint ResolvePhoneAction::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()); +} + +QImage ResolvePhoneAction::prepareRippleMask() const { + return Ui::RippleAnimation::RectMask(size()); +} + +int ResolvePhoneAction::contentHeight() const { + return _height; +} + +void ResolvePhoneAction::handleKeyPress(not_null e) { + if (!isSelected() || !_peer.current()) { + return; + } + const auto key = e->key(); + if (key == Qt::Key_Enter || key == Qt::Key_Return) { + setClicked(Ui::Menu::TriggeredSource::Keyboard); + } +} + +} // namespace + +PhoneClickHandler::PhoneClickHandler( + not_null session, + QString text) +: _session(session) +, _text(text) { +} + +void PhoneClickHandler::onClick(ClickContext context) const { + if (context.button != Qt::LeftButton) { + return; + } + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + const auto pos = QCursor::pos(); + if (!controller) { + return; + } + const auto menu = Ui::CreateChild( + controller->content(), + st::popupMenuWithIcons); + + const auto phone = _text; + +#if 0 + const auto maybeContact = [&]() -> PeerData* { + const auto &chats = controller->session().data().contactsList(); + for (const auto &row : chats->all()) { + if (const auto history = row->history()) { + if (const auto user = history->peer->asUser()) { + if (Trim(user->phone()) == Trim(phone)) { + return user; + } + } + } + } + return nullptr; + }(); +#endif + + menu->addAction(tr::lng_profile_copy_phone(tr::now), [=] { + TextUtilities::SetClipboardText( + TextForMimeData::Simple(phone.trimmed())); + }, &st::menuIconCopy); + + menu->addSeparator(&st::popupMenuExpandedSeparator.menu.separator); + + menu->addAction( + base::make_unique_q( + menu, + menu->st().menu, + phone, + controller)); + + menu->popup(pos); +} + +auto PhoneClickHandler::getTextEntity() const -> TextEntity { + return { EntityType::Phone }; +} + +QString PhoneClickHandler::tooltip() const { + return _text; +} diff --git a/Telegram/SourceFiles/core/phone_click_handler.h b/Telegram/SourceFiles/core/phone_click_handler.h new file mode 100644 index 000000000..bed2be2c5 --- /dev/null +++ b/Telegram/SourceFiles/core/phone_click_handler.h @@ -0,0 +1,30 @@ +/* +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/basic_click_handlers.h" + +namespace Main { +class Session; +} // namespace Main + +class PhoneClickHandler : public ClickHandler { +public: + PhoneClickHandler(not_null session, QString text); + + void onClick(ClickContext context) const override; + + TextEntity getTextEntity() const override; + + QString tooltip() const override; + +private: + const not_null _session; + QString _text; + +}; diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index c1ef63e1b..f3de162ce 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "platform/platform_specific.h" #include "boxes/url_auth_box.h" +#include "core/phone_click_handler.h" #include "main/main_account.h" #include "main/main_session.h" #include "main/main_app_config.h" @@ -217,6 +218,8 @@ std::shared_ptr UiIntegration::createLinkHandler( return std::make_shared(data.text, data.type); case EntityType::Pre: return std::make_shared(data.text, data.type); + case EntityType::Phone: + return std::make_shared(my->session, data.text); } return Integration::createLinkHandler(data, context); } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 88983c580..ba8b4fb39 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -3127,6 +3127,7 @@ void HistoryItem::setText(const TextWithEntities &textWithEntities) { auto type = entity.type(); if (type == EntityType::Url || type == EntityType::CustomUrl + || type == EntityType::Phone || type == EntityType::Email) { _flags |= MessageFlag::HasTextLinks; break; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index d0514b2b0..444003724 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit d0514b2b022043b3777b06d6068232aa4cda7e80 +Subproject commit 4440037244bd0175752b82ee1177c676a5340f5c From 0c1b487956372076daa7882469676f0cc1314bbd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 21:30:04 +0400 Subject: [PATCH 182/225] Fix dragging of non-leader media. --- .../SourceFiles/history/history_inner_widget.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c35035854..48db2a077 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1749,12 +1749,13 @@ std::unique_ptr HistoryInner::prepareDrag() { forwardIds = getSelectedItems(); } else if (_mouseCursorState == CursorState::Date) { forwardIds = session().data().itemOrItsGroup(_mouseActionItem); - } else if (pressedView->isHiddenByGroup() && pressedHandler) { - forwardIds = MessageIdsList(1, _mouseActionItem->fullId()); - } else if (const auto media = pressedView->media()) { - if (media->dragItemByHandler(pressedHandler)) { - forwardIds = MessageIdsList(1, _mouseActionItem->fullId()); - } + } else if ((pressedView->isHiddenByGroup() && pressedHandler) + || (pressedView->media() + && pressedView->media()->dragItemByHandler(pressedHandler))) { + const auto item = _dragStateItem + ? _dragStateItem + : _mouseActionItem; + forwardIds = MessageIdsList(1, item->fullId()); } if (forwardIds.empty()) { return nullptr; From 72d1b43453a4539d95e8b3506e712ce3a6dac4da Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 20:38:04 +0400 Subject: [PATCH 183/225] Version 5.1. - Send messages with effects. - Move photo or video captions above the media. - Chat preview on chat photo long press or Alt+Click. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/build/version | 12 ++++++------ changelog.txt | 6 ++++++ 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index cc2453f6b..48ca03886 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.1.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 0a0a61d84..9be23b243 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,6,0 - PRODUCTVERSION 5,0,6,0 + FILEVERSION 5,1,0,0 + PRODUCTVERSION 5,1,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.0.6.0" + VALUE "FileVersion", "5.1.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.6.0" + VALUE "ProductVersion", "5.1.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 1b7249bf0..d5cdd825e 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,0,6,0 - PRODUCTVERSION 5,0,6,0 + FILEVERSION 5,1,0,0 + PRODUCTVERSION 5,1,0,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", "5.0.6.0" + VALUE "FileVersion", "5.1.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.0.6.0" + VALUE "ProductVersion", "5.1.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 911f4794f..fd851cb13 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 = 5000006; -constexpr auto AppVersionStr = "5.0.6"; -constexpr auto AppBetaVersion = true; +constexpr auto AppVersion = 5001000; +constexpr auto AppVersionStr = "5.1"; +constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 2a02b657b..ce0d925cd 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5000006 -AppVersionStrMajor 5.0 -AppVersionStrSmall 5.0.6 -AppVersionStr 5.0.6 -BetaChannel 1 +AppVersion 5001000 +AppVersionStrMajor 5.1 +AppVersionStrSmall 5.1 +AppVersionStr 5.1.0 +BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.0.6.beta +AppVersionOriginal 5.1 diff --git a/changelog.txt b/changelog.txt index 70728871a..066269cd4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +5.1 (31.05.24) + +- Send messages with effects. +- Move photo or video captions above the media. +- Chat preview on chat photo long press or Alt+Click. + 5.0.6 beta (30.05.24) - Fix chat preview with non-default themes. From 3ba1941808924dc2af1fe7b031a9a3c26eeb2910 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 23:03:06 +0400 Subject: [PATCH 184/225] Version 5.1: Fix build on macOS. --- Telegram/SourceFiles/boxes/send_credits_box.cpp | 2 -- Telegram/SourceFiles/boxes/send_files_box.cpp | 1 - Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp | 1 - Telegram/SourceFiles/core/phone_click_handler.cpp | 2 -- .../controls/history_view_compose_media_edit_manager.cpp | 4 ++-- .../SourceFiles/history/view/history_view_bottom_info.cpp | 3 --- Telegram/SourceFiles/history/view/history_view_message.cpp | 6 ------ .../history/view/media/history_view_extended_preview.cpp | 2 -- .../SourceFiles/history/view/media/history_view_photo.cpp | 2 -- .../history/view/media/history_view_web_page.cpp | 1 - Telegram/SourceFiles/window/window_peer_menu.cpp | 2 +- 11 files changed, 3 insertions(+), 23 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 2873b4ffe..8820c0c65 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -106,8 +106,6 @@ void SendCreditsBox( widget->setAttribute(Qt::WA_TransparentForMouseEvents); } - const auto asd = box->lifetime().make_state(); - Ui::AddSkip(content); box->addRow(object_ptr>( box, diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 112e7543a..fbaaf561a 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -627,7 +627,6 @@ void SendFilesBox::addMenuButton() { const auto top = addTopButton(_st.files.menu); top->setClickedCallback([=] { const auto &tabbed = _st.tabbed; - const auto &icons = tabbed.icons; _menu = base::make_unique_q(top, tabbed.menu); const auto position = QCursor::pos(); SendMenu::FillSendMenu( diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index bb1278fd8..337658af1 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -2179,7 +2179,6 @@ auto StickersListWidget::collectCustomRecents() -> std::vector { auto result = std::vector(); result.reserve(_customRecentIds.size()); - const auto owner = &session().data(); for (const auto &descriptor : _customRecentIds) { if (const auto document = descriptor.document; document->sticker()) { result.push_back(Sticker{ document }); diff --git a/Telegram/SourceFiles/core/phone_click_handler.cpp b/Telegram/SourceFiles/core/phone_click_handler.cpp index e510cfa04..fc8d50d01 100644 --- a/Telegram/SourceFiles/core/phone_click_handler.cpp +++ b/Telegram/SourceFiles/core/phone_click_handler.cpp @@ -210,8 +210,6 @@ void ResolvePhoneAction::prepare() { }(); _above.setMarkedText(_st.itemStyle, { above }, options); _below.setMarkedText(_st.itemStyle, { below }, options); - const auto textWidth = _above.maxWidth(); - const auto nameWidth = _below.maxWidth(); const auto textLeft = padding.left() + st::groupCallJoinAsPhotoSize + padding.left(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp index dd2fa9893..0f765fe69 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp @@ -129,8 +129,8 @@ SendMenu::Details MediaEditManager::sendMenuDetails( const auto canMoveCaption = media->allowsEditCaption() && hasCaptionText && (editPhoto - || editDocument - && (editDocument->isVideoFile() || editDocument->isGifv())); + || (editDocument + && (editDocument->isVideoFile() || editDocument->isGifv()))); return { .spoiler = (!canSaveSpoiler ? SendMenu::SpoilerState::None diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index 8af94c73b..42ea80dce 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -191,9 +191,6 @@ ClickHandlerPtr BottomInfo::replayEffectLink( ClickHandlerPtr BottomInfo::replayEffectLink( not_null view) const { - const auto item = view->data(); - const auto itemId = item->fullId(); - const auto sessionId = item->history()->session().uniqueId(); const auto weak = base::make_weak(view); return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 38b89f061..e8876c6e8 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -585,13 +585,7 @@ void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) { } if (bubble) { - const auto check = factcheckBlock(); - const auto entry = logEntryOriginal(); - // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); - auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); - auto inner = g; if (_comments) { inner.setHeight(inner.height() - st::historyCommentsButtonHeight); diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index 60db55c8c..88c405d2e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -174,10 +174,8 @@ int ExtendedPreview::minWidthForButton() const { void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - const auto stm = context.messageStyle(); auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); - auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); const auto inWebPage = (_parent->media() != this); const auto rounding = inWebPage diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index e5c7877fa..c9721746a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -271,7 +271,6 @@ void Photo::draw(Painter &p, const PaintContext &context) const { _dataMedia->automaticLoad(_realParent->fullId(), _parent->data()); const auto st = context.st; const auto sti = context.imageStyle(); - const auto stm = context.messageStyle(); auto loaded = _dataMedia->loaded(); auto displayLoading = _data->displayLoading(); @@ -286,7 +285,6 @@ void Photo::draw(Painter &p, const PaintContext &context) const { } } const auto radial = isRadialAnimation(); - const auto botTop = _parent->Get(); auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); if (_serviceWidth > 0) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 44863dd41..31e79f46f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -324,7 +324,6 @@ void WebPage::setupAdditionalData() { } } else if (_data->type == WebPageType::Factcheck) { _additionalData = std::make_unique(FactcheckData()); - const auto raw = factcheckData(); } } diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index b1a37190f..d3aecb1c5 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -2098,7 +2098,7 @@ QPointer ShowForwardMessagesBox( } state->menu->setForcedVerticalOrigin( Ui::PopupMenu::VerticalOrigin::Bottom); - const auto result = SendMenu::FillSendMenu( + SendMenu::FillSendMenu( state->menu.get(), show, SendMenu::Details{ sendMenuType() }, From bb43afdd93202594a9dc22d5150928ec4b053e07 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 10:52:48 +0400 Subject: [PATCH 185/225] Fix search in chat close by "Cancel" link. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 3d73978bf..e8f32d70c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -347,6 +347,7 @@ Widget::Widget( }, lifetime()); _inner->cancelSearchRequests( ) | rpl::start_with_next([=] { + setInnerFocus(true); applySearchState({}); }, lifetime()); _inner->chosenRow( From c681569349011c0e2f9ccdb51e5ab42e0fec6d8c Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 10:53:00 +0400 Subject: [PATCH 186/225] Fix info display in video with caption above. --- Telegram/SourceFiles/history/view/media/history_view_gif.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 4ae4da892..4c8713685 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -759,7 +759,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { } } } - if (!unwrapped && bubble) { + if (!unwrapped && bubble && !isBubbleBottom()) { p.setPen(stm->historyTextFg); auto top = painty + painth + st::mediaCaptionSkip; if (botTop) { From 5c83858a503c3f4a39e5276f6f33a99c83bc27de Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 10:58:48 +0400 Subject: [PATCH 187/225] Remove empty space below video without caption. --- Telegram/SourceFiles/history/view/media/history_view_gif.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 4c8713685..a781a3ecc 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -269,9 +269,6 @@ QSize Gif::countOptimalSize() { accumulate_max(maxWidth, botTop->maxWidth); minHeight += botTop->height; } - if (isBubbleBottom()) { - minHeight += st::msgPadding.bottom(); - } } else if (isUnwrapped()) { const auto item = _parent->data(); auto via = item->Get(); From 233e80d22d2bf991fe8261bd80a7b1f131f29918 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 3 May 2024 20:17:43 +0300 Subject: [PATCH 188/225] Fixed display of confirmation box for proxy links in correspond window. --- Telegram/SourceFiles/boxes/connection_box.cpp | 106 ++++++++++-------- Telegram/SourceFiles/boxes/connection_box.h | 5 + .../SourceFiles/core/local_url_handlers.cpp | 2 + 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index f50ef4b3a..93aeba1a8 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "boxes/abstract_box.h" // Ui::show(). +#include "window/window_session_controller.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" @@ -1186,59 +1187,68 @@ ProxiesBoxController::ProxiesBoxController(not_null account) } void ProxiesBoxController::ShowApplyConfirmation( + Window::SessionController *controller, Type type, const QMap &fields) { const auto proxy = ProxyDataFromFields(type, fields); - if (proxy) { - static const auto UrlStartRegExp = QRegularExpression( - "^https://", - QRegularExpression::CaseInsensitiveOption); - static const auto UrlEndRegExp = QRegularExpression("/$"); - const auto displayed = "https://" + proxy.host + "/"; - const auto parsed = QUrl::fromUserInput(displayed); - const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed) - ? displayed - : parsed.isValid() - ? QString::fromUtf8(parsed.toEncoded()) - : UrlClickHandler::ShowEncoded(displayed); - const auto displayServer = QString( - displayUrl - ).replace( - UrlStartRegExp, - QString() - ).replace(UrlEndRegExp, QString()); - const auto text = tr::lng_sure_enable_socks( - tr::now, - lt_server, - displayServer, - lt_port, - QString::number(proxy.port)) - + (proxy.type == Type::Mtproto - ? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now) - : QString()); - auto callback = [=](Fn &&close) { - auto &proxies = Core::App().settings().proxy().list(); - if (!ranges::contains(proxies, proxy)) { - proxies.push_back(proxy); - } - Core::App().setCurrentProxy( - proxy, - ProxyData::Settings::Enabled); - Local::writeSettings(); - close(); - }; - Ui::show( - Ui::MakeConfirmBox({ - .text = text, - .confirmed = std::move(callback), - .confirmText = tr::lng_sure_enable(), - }), - Ui::LayerOption::KeepOther); - } else { - Ui::show(Ui::MakeInformBox( + if (!proxy) { + auto box = Ui::MakeInformBox( (proxy.status() == ProxyData::Status::Unsupported ? tr::lng_proxy_unsupported(tr::now) - : tr::lng_proxy_invalid(tr::now)))); + : tr::lng_proxy_invalid(tr::now))); + if (controller) { + controller->uiShow()->showBox(std::move(box)); + } else { + Ui::show(std::move(box)); + } + return; + } + static const auto UrlStartRegExp = QRegularExpression( + "^https://", + QRegularExpression::CaseInsensitiveOption); + static const auto UrlEndRegExp = QRegularExpression("/$"); + const auto displayed = "https://" + proxy.host + "/"; + const auto parsed = QUrl::fromUserInput(displayed); + const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed) + ? displayed + : parsed.isValid() + ? QString::fromUtf8(parsed.toEncoded()) + : UrlClickHandler::ShowEncoded(displayed); + const auto displayServer = QString( + displayUrl + ).replace( + UrlStartRegExp, + QString() + ).replace(UrlEndRegExp, QString()); + const auto text = tr::lng_sure_enable_socks( + tr::now, + lt_server, + displayServer, + lt_port, + QString::number(proxy.port)) + + (proxy.type == Type::Mtproto + ? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now) + : QString()); + auto callback = [=](Fn &&close) { + auto &proxies = Core::App().settings().proxy().list(); + if (!ranges::contains(proxies, proxy)) { + proxies.push_back(proxy); + } + Core::App().setCurrentProxy( + proxy, + ProxyData::Settings::Enabled); + Local::writeSettings(); + close(); + }; + auto box = Ui::MakeConfirmBox({ + .text = text, + .confirmed = std::move(callback), + .confirmText = tr::lng_sure_enable(), + }); + if (controller) { + controller->uiShow()->showBox(std::move(box)); + } else { + Ui::show(std::move(box)); } } diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h index e3bbfd006..25da43458 100644 --- a/Telegram/SourceFiles/boxes/connection_box.h +++ b/Telegram/SourceFiles/boxes/connection_box.h @@ -30,6 +30,10 @@ namespace Main { class Account; } // namespace Main +namespace Window { +class SessionController; +} // namespace Window + class ProxiesBoxController { public: using ProxyData = MTP::ProxyData; @@ -38,6 +42,7 @@ public: explicit ProxiesBoxController(not_null account); static void ShowApplyConfirmation( + Window::SessionController *controller, Type type, const QMap &fields); diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 544038640..03c3254ab 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -350,6 +350,7 @@ bool ApplySocksProxy( match->captured(1), qthelp::UrlParamNameTransform::ToLower); ProxiesBoxController::ShowApplyConfirmation( + controller, MTP::ProxyData::Type::Socks5, params); if (controller) { @@ -366,6 +367,7 @@ bool ApplyMtprotoProxy( match->captured(1), qthelp::UrlParamNameTransform::ToLower); ProxiesBoxController::ShowApplyConfirmation( + controller, MTP::ProxyData::Type::Mtproto, params); if (controller) { From d9572949f60960f2b825d4399df2921877ff98d1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 3 May 2024 21:01:36 +0300 Subject: [PATCH 189/225] Replaced confirmation box for proxy links with generic box. --- Telegram/Resources/langs/lang.strings | 7 ++ Telegram/SourceFiles/boxes/boxes.style | 4 ++ Telegram/SourceFiles/boxes/connection_box.cpp | 68 ++++++++++++------- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bf8781ace..1ce2489af 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -304,6 +304,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?"; "lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type)."; "lng_sure_enable" = "Enable"; +"lng_proxy_box_title" = "Enable proxy"; +"lng_proxy_box_server" = "Server"; +"lng_proxy_box_port" = "Port"; +"lng_proxy_box_secret" = "Secret"; +"lng_proxy_box_status" = "Status"; +"lng_proxy_box_username" = "Username"; +"lng_proxy_box_password" = "Password"; "lng_proxy_invalid" = "The proxy link is invalid."; "lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version."; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 6d3d639ba..cfd123c93 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -642,6 +642,10 @@ proxyDropdownUpPosition: point(-2px, 20px); proxyAboutPadding: margins(22px, 7px, 22px, 14px); proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px); +proxyApplyBoxLabel : FlatLabel(defaultFlatLabel) { + maxHeight: 30px; +} + markdownLinkFieldPadding: margins(22px, 0px, 22px, 10px); termsContent: FlatLabel(defaultFlatLabel) { diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 93aeba1a8..9775ae058 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "core/application.h" -#include "core/click_handler_types.h" #include "core/core_settings.h" #include "core/local_url_handlers.h" #include "lang/lang_keys.h" @@ -36,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "ui/vertical_list.h" #include "boxes/abstract_box.h" // Ui::show(). #include "window/window_session_controller.h" #include "styles/style_layers.h" @@ -1220,35 +1220,51 @@ void ProxiesBoxController::ShowApplyConfirmation( UrlStartRegExp, QString() ).replace(UrlEndRegExp, QString()); - const auto text = tr::lng_sure_enable_socks( - tr::now, - lt_server, - displayServer, - lt_port, - QString::number(proxy.port)) - + (proxy.type == Type::Mtproto - ? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now) - : QString()); - auto callback = [=](Fn &&close) { - auto &proxies = Core::App().settings().proxy().list(); - if (!ranges::contains(proxies, proxy)) { - proxies.push_back(proxy); + const auto box = [=](not_null box) { + box->setTitle(tr::lng_proxy_box_title()); + if (type == Type::Mtproto) { + box->addRow(object_ptr( + box, + tr::lng_proxy_sponsor_warning(), + st::boxDividerLabel)); + Ui::AddSkip(box->verticalLayout()); + Ui::AddSkip(box->verticalLayout()); } - Core::App().setCurrentProxy( - proxy, - ProxyData::Settings::Enabled); - Local::writeSettings(); - close(); + const auto &stL = st::proxyApplyBoxLabel; + const auto &stSubL = st::boxDividerLabel; + const auto add = [&](const QString &s, tr::phrase<> phrase) { + if (!s.isEmpty()) { + box->addRow(object_ptr(box, s, stL)); + box->addRow(object_ptr(box, phrase(), stSubL)); + Ui::AddSkip(box->verticalLayout()); + Ui::AddSkip(box->verticalLayout()); + } + }; + if (!displayServer.isEmpty()) { + add(displayServer, tr::lng_proxy_box_server); + } + add(QString::number(proxy.port), tr::lng_proxy_box_port); + if (type == Type::Socks5) { + add(proxy.user, tr::lng_proxy_box_username); + add(proxy.password, tr::lng_proxy_box_password); + } else if (type == Type::Mtproto) { + add(proxy.password, tr::lng_proxy_box_secret); + } + box->addButton(tr::lng_sure_enable(), [=] { + auto &proxies = Core::App().settings().proxy().list(); + if (!ranges::contains(proxies, proxy)) { + proxies.push_back(proxy); + } + Core::App().setCurrentProxy(proxy, ProxyData::Settings::Enabled); + Local::writeSettings(); + box->closeBox(); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); }; - auto box = Ui::MakeConfirmBox({ - .text = text, - .confirmed = std::move(callback), - .confirmText = tr::lng_sure_enable(), - }); if (controller) { - controller->uiShow()->showBox(std::move(box)); + controller->uiShow()->showBox(Box(box)); } else { - Ui::show(std::move(box)); + Ui::show(Box(box)); } } From 7aef0b0a83c201401df3b587d3d53ddf1fde0c3e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 1 Jun 2024 16:55:01 +0300 Subject: [PATCH 190/225] Fixed frame size of video userpic in short info boxes on Retina. --- Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp index 47afccd2f..45d30a7ed 100644 --- a/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp @@ -398,7 +398,7 @@ void PeerShortInfoCover::paintRadial(QPainter &p) { QImage PeerShortInfoCover::currentVideoFrame() const { const auto size = QSize(_st.size, _st.size); const auto request = Media::Streaming::FrameRequest{ - .resize = size * style::DevicePixelRatio(), + .resize = size, .outer = size, }; return (_videoInstance From e9e347fa6c3b3ea10043d9c7b0d208150be5617d Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 20:34:16 +0400 Subject: [PATCH 191/225] Fix crash in stories privacy handling. --- .../history/view/controls/history_view_compose_controls.cpp | 4 ++++ 1 file changed, 4 insertions(+) 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 ac381222c..aae110eba 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -2405,6 +2405,10 @@ void ComposeControls::initWriteRestriction() { updateWrappingVisibility(); return; } + if (_like && _like->parentWidget() == _writeRestricted.get()) { + // Fix a crash because of _like destruction with its parent. + _like->setParent(_wrap.get()); + } _writeRestricted = std::make_unique(_parent); _writeRestricted->move(_wrap->pos()); _writeRestricted->resizeToWidth(_wrap->widthNoMargins()); From 59c016e4ce9688c41c73d329022e0c64a4d7c39b Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 20:39:31 +0400 Subject: [PATCH 192/225] Fix search input not in the end of the query. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index e8f32d70c..73c5ad839 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -3005,7 +3005,9 @@ bool Widget::applySearchState(SearchState state) { controller()->closeFolder(); } - setSearchQuery(_searchState.query); + if (_searchState.query != currentSearchQuery()) { + setSearchQuery(_searchState.query); + } _inner->applySearchState(_searchState); if (!_postponeProcessSearchFocusChange) { From 36766e7546e974ca530ab09c278cc63f1e0b83b1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 21:16:31 +0400 Subject: [PATCH 193/225] Fix collapsed blockquotes in the end of the text. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 444003724..00cb1e258 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 4440037244bd0175752b82ee1177c676a5340f5c +Subproject commit 00cb1e258b3d2bbd42406f9eb3abcf3229e04046 From c1b95afd8897219b26cccd662afa39df6de60c5a Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 21:17:09 +0400 Subject: [PATCH 194/225] Fix media spoiler/caption-above edit in topics/scheduled. --- .../history_view_compose_controls.cpp | 40 ++++++++++++++++++- .../controls/history_view_compose_controls.h | 1 + 2 files changed, 39 insertions(+), 2 deletions(-) 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 aae110eba..cc4a91a1f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -139,6 +139,8 @@ public: void previewReady(rpl::producer parsed); void previewUnregister(); + void mediaEditManagerApply(SendMenu::Action action); + [[nodiscard]] bool isDisplayed() const; [[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool readyToForward() const; @@ -150,6 +152,7 @@ public: [[nodiscard]] rpl::producer<> editPhotoRequests() const; [[nodiscard]] rpl::producer<> editOptionsRequests() const; [[nodiscard]] MessageToEdit queryToEdit(); + [[nodiscard]] SendMenu::Details saveMenuDetails(bool hasSendText) const; [[nodiscard]] FullReplyTo getDraftReply() const; [[nodiscard]] rpl::producer<> editCancelled() const { @@ -529,6 +532,10 @@ void FieldHeader::previewUnregister() { _previewLifetime.destroy(); } +void FieldHeader::mediaEditManagerApply(SendMenu::Action action) { + _mediaEditManager.apply(action); +} + void FieldHeader::paintWebPage(Painter &p, not_null context) { Expects(!!_preview.parsed); @@ -736,8 +743,12 @@ void FieldHeader::updateControlsGeometry(QSize size) { void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) { _photoEditAllowed = photoEditAllowed; _editMsgId = id; - if (!photoEditAllowed) { + if (!id) { _mediaEditManager.cancel(); + } else if (const auto item = _show->session().data().message(id)) { + _mediaEditManager.start(item); + } + if (!photoEditAllowed) { _inPhotoEdit = false; _inPhotoEditOver.stop(); } @@ -790,6 +801,12 @@ MessageToEdit FieldHeader::queryToEdit() { }; } +SendMenu::Details FieldHeader::saveMenuDetails(bool hasSendText) const { + return isEditingMessage() + ? _mediaEditManager.sendMenuDetails(hasSendText) + : SendMenu::Details(); +} + ComposeControls::ComposeControls( not_null parent, ComposeControlsDescriptor descriptor) @@ -2208,6 +2225,19 @@ void ComposeControls::initSendButton() { _sendCustomRequests.fire(std::move(options)); }); + using namespace SendMenu; + const auto sendAction = [=](Action action, Details details) { + if (action.type == ActionType::CaptionUp + || action.type == ActionType::CaptionDown + || action.type == ActionType::SpoilerOn + || action.type == ActionType::SpoilerOff) { + _header->mediaEditManagerApply(action); + } else { + SendMenu::DefaultCallback(_show, send)(action, details); + } + }; + + SendMenu::SetupMenuAndShortcuts( _send.get(), _show, @@ -2529,8 +2559,14 @@ SendMenu::Details ComposeControls::sendMenuDetails() const { return !_history ? SendMenu::Details() : _sendMenuDetails(); } +SendMenu::Details ComposeControls::saveMenuDetails() const { + return _header->saveMenuDetails(HasSendText(_field)); +} + SendMenu::Details ComposeControls::sendButtonMenuDetails() const { - return (computeSendButtonType() == Ui::SendButton::Type::Send) + return (computeSendButtonType() == Ui::SendButton::Type::Save) + ? saveMenuDetails() + : (computeSendButtonType() == Ui::SendButton::Type::Send) ? sendMenuDetails() : SendMenu::Details(); } 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 11e7429e2..fb373a0f7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -283,6 +283,7 @@ private: [[nodiscard]] auto computeSendButtonType() const; [[nodiscard]] SendMenu::Details sendMenuDetails() const; + [[nodiscard]] SendMenu::Details saveMenuDetails() const; [[nodiscard]] SendMenu::Details sendButtonMenuDetails() const; [[nodiscard]] auto sendContentRequests( From b7f165a2593fcf2320a7336ab90dae19db43d741 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 21:53:26 +0400 Subject: [PATCH 195/225] Fix albums with wide captions. --- .../history/view/media/history_view_media_grouped.cpp | 4 ++++ .../history/view/media/history_view_media_grouped.h | 1 + 2 files changed, 5 insertions(+) diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 3297ef8c7..8d6583405 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -801,6 +801,10 @@ QPoint GroupedMedia::resolveCustomInfoRightBottom() const { return QPoint(width() - skipx, height() - skipy); } +bool GroupedMedia::enforceBubbleWidth() const { + return _mode == Mode::Grid; +} + bool GroupedMedia::computeNeedBubble() const { Expects(_mode == Mode::Column || _captionItem.has_value()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index 5637f65ad..84dca4884 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -96,6 +96,7 @@ public: bool customHighlight() const override { return true; } + bool enforceBubbleWidth() const override; void stopAnimation() override; void checkAnimation() override; From f4abe37dff62c9c3cd0fb75113c4fbac93b21a2c Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 21:53:41 +0400 Subject: [PATCH 196/225] Display peer IDs with delimeters. --- Telegram/SourceFiles/info/profile/info_profile_actions.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 4dbd1fd33..18e398a03 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -172,8 +172,9 @@ base::options::toggle ShowPeerIdBelowAbout({ } value.append(Italic(u"id: "_q)); const auto raw = peer->id.value & PeerId::kChatTypeMask; - const auto id = QString::number(raw); - value.append(Link(Italic(id), "internal:copy:" + id)); + value.append(Link( + Italic(Lang::FormatCountDecimal(raw)), + "internal:copy:" + QString::number(raw))); return std::move(value); }); } From eac7bf1c4895a0ab457e6ff594002f4c38d1ac57 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 22:02:17 +0400 Subject: [PATCH 197/225] Show "View as Messages" preview on forum preview. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index d0d8b8fe7..3216e512c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1495,8 +1495,6 @@ RowDescriptor InnerWidget::computeChatPreviewRow() const { : 0; if (const auto topic = peer->forumTopicFor(topicId)) { return { topic, FullMsgId() }; - } else if (peer->isForum() && !result.key.topic()) { - return {}; } } return { result.key, result.message.fullId }; From 400f0f8785334df7fcb7e4047e0aeb86e5031c93 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 22:09:01 +0400 Subject: [PATCH 198/225] Empty line between description and ID. --- Telegram/SourceFiles/info/profile/info_profile_actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 18e398a03..3d6fb7675 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -168,7 +168,7 @@ base::options::toggle ShowPeerIdBelowAbout({ } using namespace Ui::Text; if (!value.empty()) { - value.append("\n"); + value.append("\n\n"); } value.append(Italic(u"id: "_q)); const auto raw = peer->id.value & PeerId::kChatTypeMask; From 70fe6497439ecfbb272a6319ba542991717cd430 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 22:31:43 +0400 Subject: [PATCH 199/225] Hide media viewer on macOS when showing IV. --- Telegram/SourceFiles/iv/iv_instance.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp index ab2e21327..69df5f772 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "iv/iv_instance.h" #include "apiwrap.h" +#include "base/platform/base_platform_info.h" #include "boxes/share_box.h" #include "core/application.h" #include "core/file_utilities.h" @@ -741,6 +742,11 @@ void Instance::show( not_null session, not_null data, QString hash) { + if (Platform::IsMac()) { + // Otherwise IV is not visible under the media viewer. + Core::App().hideMediaView(); + } + const auto guard = gsl::finally([&] { requestFull(session, data->id()); }); From bb79a0726299d9cb8cc844c5cec979547977f162 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 22:43:19 +0400 Subject: [PATCH 200/225] Move SendMenu additional actions to bottom. --- Telegram/SourceFiles/menu/menu_send.cpp | 61 ++++++++++++------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index a1c8909fb..58328da12 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -626,37 +626,6 @@ FillMenuResult FillSendMenu( ? *iconsOverride : st::defaultComposeIcons; - auto toggles = false; - if (details.spoiler != SpoilerState::None) { - const auto spoilered = (details.spoiler == SpoilerState::Enabled); - menu->addAction( - (spoilered - ? tr::lng_context_disable_spoiler(tr::now) - : tr::lng_context_spoiler_effect(tr::now)), - [=] { action({ .type = spoilered - ? ActionType::SpoilerOff - : ActionType::SpoilerOn - }, details); }, - spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler); - toggles = true; - } - if (details.caption != CaptionState::None) { - const auto above = (details.caption == CaptionState::Above); - menu->addAction( - (above - ? tr::lng_caption_move_down(tr::now) - : tr::lng_caption_move_up(tr::now)), - [=] { action({ .type = above - ? ActionType::CaptionDown - : ActionType::CaptionUp - }, details); }, - above ? &icons.menuBelow : &icons.menuAbove); - toggles = true; - } - if (toggles && type != Type::Disabled) { - menu->addSeparator(&st::expandedMenuSeparator); - } - if (sending && type != Type::Reminder) { menu->addAction( tr::lng_send_silent_message(tr::now), @@ -680,6 +649,36 @@ FillMenuResult FillSendMenu( &icons.menuWhenOnline); } + if ((type != Type::Disabled) + && ((details.spoiler != SpoilerState::None) + || (details.caption != CaptionState::None))) { + menu->addSeparator(&st::expandedMenuSeparator); + } + if (details.spoiler != SpoilerState::None) { + const auto spoilered = (details.spoiler == SpoilerState::Enabled); + menu->addAction( + (spoilered + ? tr::lng_context_disable_spoiler(tr::now) + : tr::lng_context_spoiler_effect(tr::now)), + [=] { action({ .type = spoilered + ? ActionType::SpoilerOff + : ActionType::SpoilerOn + }, details); }, + spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler); + } + if (details.caption != CaptionState::None) { + const auto above = (details.caption == CaptionState::Above); + menu->addAction( + (above + ? tr::lng_caption_move_down(tr::now) + : tr::lng_caption_move_up(tr::now)), + [=] { action({ .type = above + ? ActionType::CaptionDown + : ActionType::CaptionUp + }, details); }, + above ? &icons.menuBelow : &icons.menuAbove); + } + using namespace HistoryView::Reactions; const auto effect = std::make_shared>(); const auto position = desiredPositionOverride.value_or(QCursor::pos()); From ee680ac1f16be8203a199e62eafc5aa698a068af Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 22:49:51 +0400 Subject: [PATCH 201/225] Don't try adding effects to forwarded messages. --- Telegram/SourceFiles/apiwrap.cpp | 4 +++- Telegram/SourceFiles/history/history_widget.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 181cf26fb..ba4400684 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3383,7 +3383,9 @@ void ApiWrap::forwardMessages( .date = HistoryItem::NewMessageDate(action.options), .shortcutId = action.options.shortcutId, .postAuthor = messagePostAuthor, - .effectId = action.options.effectId, + + // forwarded messages don't have effects + //.effectId = action.options.effectId, }, item); _session->data().registerMessageRandomId(randomId, newId); if (!localIds) { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 44c65a048..2077077af 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4224,7 +4224,9 @@ SendMenu::Details HistoryWidget::sendMenuDetails() const { : HistoryView::CanScheduleUntilOnline(_peer) ? SendMenu::Type::ScheduledToUser : SendMenu::Type::Scheduled; - const auto effectAllowed = _peer && _peer->isUser(); + const auto effectAllowed = _peer + && _peer->isUser() + && (HasSendText(_field) || _previewDrawPreview); return { .type = type, .effectAllowed = effectAllowed }; } From 26345208a9e53a1ac1ab0a78bcb61a80c6e9a820 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 23:02:29 +0400 Subject: [PATCH 202/225] Fix caption disappearance on album sending. --- Telegram/SourceFiles/data/data_session.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index dba1c3d69..c11c2ef8a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1821,18 +1821,16 @@ rpl::producer> Session::itemDataChanges() const { void Session::requestItemTextRefresh(not_null item) { const auto call = [&](not_null item) { - if (const auto i = _views.find(item); i != _views.end()) { - for (const auto &view : i->second) { - view->itemTextUpdated(); - } - } + enumerateItemViews(item, [&](not_null view) { + view->itemTextUpdated(); + }); + requestItemResize(item); }; if (const auto group = groups().find(item)) { call(group->items.front()); } else { call(item); } - requestItemResize(item); } void Session::registerHighlightProcess( From 12eecec501de97183e4870447d48e8b62e0da5e9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 23:21:48 +0400 Subject: [PATCH 203/225] Disable effects for inline results sending. --- .../chat_helpers/gifs_list_widget.cpp | 11 ++++++++++- Telegram/SourceFiles/history/history_widget.cpp | 16 +++++++++++----- Telegram/SourceFiles/history/history_widget.h | 1 + .../inline_bots/inline_results_inner.cpp | 5 ++++- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index ab2fe0b98..2021e2943 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -390,11 +390,20 @@ base::unique_qptr GifsListWidget::fillContextMenu( const auto send = crl::guard(this, [=](Api::SendOptions options) { selectInlineResult(selected, options, true); }); + const auto item = _mosaic.maybeItemAt(_selected); + const auto isInlineResult = !item->getPhoto() + && !item->getDocument() + && item->getResult(); const auto icons = &st().icons; + auto copyDetails = details; + if (isInlineResult) { + // inline results don't have effects + copyDetails.effectAllowed = false; + } SendMenu::FillSendMenu( menu, _show, - details, + copyDetails, SendMenu::DefaultCallback(_show, send), icons); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 2077077af..383a76109 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -4211,7 +4211,7 @@ void HistoryWidget::sendScheduled(Api::SendOptions initialOptions) { HistoryView::PrepareScheduleBox( _list, controller()->uiShow(), - sendMenuDetails(), + sendButtonDefaultDetails(), [=](Api::SendOptions options) { send(options); }, initialOptions)); } @@ -4224,9 +4224,7 @@ SendMenu::Details HistoryWidget::sendMenuDetails() const { : HistoryView::CanScheduleUntilOnline(_peer) ? SendMenu::Type::ScheduledToUser : SendMenu::Type::Scheduled; - const auto effectAllowed = _peer - && _peer->isUser() - && (HasSendText(_field) || _previewDrawPreview); + const auto effectAllowed = _peer && _peer->isUser(); return { .type = type, .effectAllowed = effectAllowed }; } @@ -4257,7 +4255,15 @@ SendMenu::Details HistoryWidget::sendButtonMenuDetails() const { } else if (type != Type::Send) { return {}; } - return sendMenuDetails(); + return sendButtonDefaultDetails(); +} + +SendMenu::Details HistoryWidget::sendButtonDefaultDetails() const { + auto result = sendMenuDetails(); + if (!HasSendText(_field) && !_previewDrawPreview) { + result.effectAllowed = false; + } + return result; } void HistoryWidget::unblockUser() { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index e9453a621..e99705f03 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -398,6 +398,7 @@ private: void sendWithModifiers(Qt::KeyboardModifiers modifiers); void sendScheduled(Api::SendOptions initialOptions); [[nodiscard]] SendMenu::Details sendButtonMenuDetails() const; + [[nodiscsard]] SendMenu::Details sendButtonDefaultDetails() const; void handlePendingHistoryUpdate(); void fullInfoUpdated(); void toggleTabbedSelectorMode(); diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 74219011d..10c6236bf 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -329,10 +329,13 @@ void Inner::contextMenuEvent(QContextMenuEvent *e) { if (_selected < 0 || _pressed >= 0) { return; } - const auto details = _sendMenuDetails + auto details = _sendMenuDetails ? _sendMenuDetails() : SendMenu::Details(); + // inline results don't have effects + details.effectAllowed = false; + _menu = base::make_unique_q( this, st::popupMenuWithIcons); From 86778aa4d90390d17b95410e9cb8efd690f97667 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 23:26:02 +0400 Subject: [PATCH 204/225] Fix cancel search glitch with the new search. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 73c5ad839..085aa802a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -2973,13 +2973,13 @@ bool Widget::applySearchState(SearchState state) { if (queryChanged) { updateLockUnlockVisibility(anim::type::normal); updateLoadMoreChatsVisibility(); - updateCancelSearch(); } if (inChatChanged) { controller()->setSearchInChat(_searchState.inChat); updateSearchTabs(); } if (queryChanged || inChatChanged) { + updateCancelSearch(); updateStoriesVisibility(); } updateJumpToDateVisibility(); From bd20a3cfe4b0267720c01e173e47faa084328c89 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 23:35:04 +0400 Subject: [PATCH 205/225] Fix filtered premium sticker effects selection. --- .../history/view/reactions/history_view_reactions_selector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index dcac6a69e..11ba82094 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -1116,9 +1116,9 @@ void Selector::createList() { _list->searchQueries( ) | rpl::start_with_next([=](std::vector &&query) { _stickers->applySearchQuery(std::move(query)); + updateVisibleTopBottom(); }, _stickers->lifetime()); - rpl::combine( _list->recentShownCount(), _stickers->recentShownCount() From 51b866293fb81b9eeec63b37b840cf1cb30c17cb Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 23:40:20 +0400 Subject: [PATCH 206/225] Version 5.1.1. - Fix caption display on some media. - Fix collapsed blockquotes rendering. - Fully close search in chat by "Cancel" click. - Allow editing caption placement and spoiler in topics. - Disable effects on forwarded messages and inline results. --- 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 ++++---- changelog.txt | 8 ++++++++ 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 48ca03886..da40284a8 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.1.1.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 9be23b243..e02d029eb 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,1,0,0 - PRODUCTVERSION 5,1,0,0 + FILEVERSION 5,1,1,0 + PRODUCTVERSION 5,1,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.1.0.0" + VALUE "FileVersion", "5.1.1.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.1.0.0" + VALUE "ProductVersion", "5.1.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index d5cdd825e..8591989f3 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,1,0,0 - PRODUCTVERSION 5,1,0,0 + FILEVERSION 5,1,1,0 + PRODUCTVERSION 5,1,1,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", "5.1.0.0" + VALUE "FileVersion", "5.1.1.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.1.0.0" + VALUE "ProductVersion", "5.1.1.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index fd851cb13..8ba762a1e 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 = 5001000; -constexpr auto AppVersionStr = "5.1"; +constexpr auto AppVersion = 5001001; +constexpr auto AppVersionStr = "5.1.1"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index ce0d925cd..505809f37 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 5001000 +AppVersion 5001001 AppVersionStrMajor 5.1 -AppVersionStrSmall 5.1 -AppVersionStr 5.1.0 +AppVersionStrSmall 5.1.1 +AppVersionStr 5.1.1 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 5.1 +AppVersionOriginal 5.1.1 diff --git a/changelog.txt b/changelog.txt index 066269cd4..b07e06e10 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,11 @@ +5.1.1 (01.06.24) + +- Fix caption display on some media. +- Fix collapsed blockquotes rendering. +- Fully close search in chat by "Cancel" click. +- Allow editing caption placement and spoiler in topics. +- Disable effects on forwarded messages and inline results. + 5.1 (31.05.24) - Send messages with effects. From a7bffe7abdfa10871f41ddfbd86c8f70dac1f4a2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 23:58:31 +0400 Subject: [PATCH 207/225] Version 5.1.1: Fix misspelled attribute. --- Telegram/SourceFiles/history/history_widget.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index e99705f03..977415fba 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -398,7 +398,7 @@ private: void sendWithModifiers(Qt::KeyboardModifiers modifiers); void sendScheduled(Api::SendOptions initialOptions); [[nodiscard]] SendMenu::Details sendButtonMenuDetails() const; - [[nodiscsard]] SendMenu::Details sendButtonDefaultDetails() const; + [[nodiscard]] SendMenu::Details sendButtonDefaultDetails() const; void handlePendingHistoryUpdate(); void fullInfoUpdated(); void toggleTabbedSelectorMode(); From f61f649a7ea03f1c2837cf94e23c7210c28dd3c6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 1 Jun 2024 11:19:00 +0400 Subject: [PATCH 208/225] Add a power saving setting for effects. --- Telegram/Resources/langs/lang.strings | 1 + .../history/view/history_view_emoji_interactions.cpp | 4 +++- Telegram/SourceFiles/settings/settings_power_saving.cpp | 1 + Telegram/SourceFiles/ui/power_saving.h | 3 ++- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 1ce2489af..e027f6da9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -785,6 +785,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_power_chat" = "Animations in Chats"; "lng_settings_power_chat_background" = "Background rotation"; "lng_settings_power_chat_spoiler" = "Animated spoiler effect"; +"lng_settings_power_chat_effects" = "Effects in messages"; "lng_settings_power_calls" = "Animations in Calls"; "lng_settings_power_ui" = "Interface animations"; "lng_settings_power_auto" = "Save Power on Low Battery"; diff --git a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp index de775e17d..456f97cd9 100644 --- a/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_emoji_interactions.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_common.h" #include "lottie/lottie_single_player.h" #include "base/random.h" +#include "ui/power_saving.h" #include "styles/style_chat.h" namespace HistoryView { @@ -155,7 +156,8 @@ void EmojiInteractions::play( } void EmojiInteractions::playEffectOnRead(not_null view) { - if (view->data()->markEffectWatched()) { + const auto flag = PowerSaving::Flag::kChatEffects; + if (view->data()->markEffectWatched() && !PowerSaving::On(flag)) { playEffect(view); } } diff --git a/Telegram/SourceFiles/settings/settings_power_saving.cpp b/Telegram/SourceFiles/settings/settings_power_saving.cpp index 4d523e07a..beaf46613 100644 --- a/Telegram/SourceFiles/settings/settings_power_saving.cpp +++ b/Telegram/SourceFiles/settings/settings_power_saving.cpp @@ -152,6 +152,7 @@ EditFlagsDescriptor PowerSavingLabels() { &st::menuIconChatBubble, }, { kChatSpoiler, tr::lng_settings_power_chat_spoiler(tr::now) }, + { kChatEffects, tr::lng_settings_power_chat_effects(tr::now) }, }; auto calls = std::vector