From 8ebf329cd98922a16adc4d0ab55230f877d75578 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 Nov 2023 16:44:20 +0400 Subject: [PATCH 01/70] Display group / channel id in profile. --- .../SourceFiles/info/profile/info_profile_actions.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 404b82e71b9f36..4be3f55742a38e 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -558,11 +558,11 @@ object_ptr DetailsFiller::setupInfo() { ).text->setLinksTrusted(); } - const auto about = addInfoLine( - tr::lng_info_about_label(), - _topic ? rpl::single(TextWithEntities()) : AboutValue(_peer)); + const auto about = addInfoLine(tr::lng_info_about_label(), _topic + ? rpl::single(TextWithEntities()) + : AboutWithIdValue(_peer)); if (!_topic) { - addTranslateToMenu(about.text, AboutValue(_peer)); + addTranslateToMenu(about.text, AboutWithIdValue(_peer)); } } if (!_peer->isSelf()) { From b41c94be290d3991a37fc6470abfde13c18ea7b9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 Nov 2023 16:44:41 +0400 Subject: [PATCH 02/70] Fix crash in link preview edit. --- .../history/view/controls/history_view_draft_options.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index 3438ab15de171f..65a1cd6de5dec3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -638,10 +638,11 @@ void DraftOptionsBox( const auto &highlight = args.highlight; const auto &clearOldDraft = args.clearOldDraft; const auto resolveReply = [=] { - const auto current = state->quote.current(); auto result = draft.reply; - result.messageId = current.item->fullId(); - result.quote = current.text; + if (const auto current = state->quote.current()) { + result.messageId = current.item->fullId(); + result.quote = current.text; + } return result; }; const auto finish = [=]( From 8927a1b9a20a52fa673943cf1552fe94673eb00f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 Nov 2023 19:58:07 +0400 Subject: [PATCH 03/70] Fix media caption adding link in replies/scheduled. --- .../history/view/controls/history_view_compose_controls.cpp | 5 +++++ 1 file changed, 5 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 d2da74625620c9..21ce63e1a07aac 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1940,6 +1940,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _header->replyToMessage({}); if (_preview) { _preview->apply({ .removed = true }); + _preview->setDisabled(false); } _canReplaceMedia = false; _photoEditMedia = nullptr; @@ -1978,6 +1979,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _preview->apply( Data::WebPageDraft::FromItem(item), false); + _preview->setDisabled(media && !media->webpage()); } return true; } @@ -2009,6 +2011,9 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { cancelForward(); } _header->editMessage({}); + if (_preview) { + _preview->setDisabled(false); + } } } From 01d986403690ec6d8d619355ae8ed98761b404e1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 Nov 2023 20:29:35 +0400 Subject: [PATCH 04/70] Workaround crash in statistics. --- Telegram/SourceFiles/statistics/chart_widget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/statistics/chart_widget.cpp b/Telegram/SourceFiles/statistics/chart_widget.cpp index a6a167185c167a..cead2c80fb4ac7 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.cpp +++ b/Telegram/SourceFiles/statistics/chart_widget.cpp @@ -1018,9 +1018,9 @@ void ChartWidget::updateBottomDates() { const auto k = _chartArea->width() / d; const auto stepRaw = int(k / 6); + const auto by = int(_chartArea->width() / float64(_chartData.x.size())); _bottomLine.captionIndicesOffset = 0 - + st::statisticsChartBottomCaptionMaxWidth - / int(_chartArea->width() / float64(_chartData.x.size())); + + st::statisticsChartBottomCaptionMaxWidth / std::max(by, 1); const auto isCurrentNull = (_bottomLine.current.stepMinFast == 0); if (!isCurrentNull From 9e10a80e00b3c08d998c62b0023cab30301d90ae Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 Nov 2023 20:31:53 +0400 Subject: [PATCH 05/70] Version 4.11.3. - Fix adding a link to media captions in scheduled / comments. - Fix crash in link preview options saving. - Fix possible crash in statistics. --- 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 | 6 ++++++ 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 69b1a79d1f89d1..cc75e489e34c6c 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.11.3.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 4bb6bc8bdd9592..6913bae357c7ac 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,11,2,0 - PRODUCTVERSION 4,11,2,0 + FILEVERSION 4,11,3,0 + PRODUCTVERSION 4,11,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", "4.11.2.0" + VALUE "FileVersion", "4.11.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.11.2.0" + VALUE "ProductVersion", "4.11.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 527dddaa07060e..c1d26ca214a87a 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,11,2,0 - PRODUCTVERSION 4,11,2,0 + FILEVERSION 4,11,3,0 + PRODUCTVERSION 4,11,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", "4.11.2.0" + VALUE "FileVersion", "4.11.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.11.2.0" + VALUE "ProductVersion", "4.11.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 0d5d36bb2340ff..4003c2647ec77e 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 = 4011002; -constexpr auto AppVersionStr = "4.11.2"; +constexpr auto AppVersion = 4011003; +constexpr auto AppVersionStr = "4.11.3"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 9137ea6e50f198..1c347fd926c1bb 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4011002 +AppVersion 4011003 AppVersionStrMajor 4.11 -AppVersionStrSmall 4.11.2 -AppVersionStr 4.11.2 +AppVersionStrSmall 4.11.3 +AppVersionStr 4.11.3 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.11.2 +AppVersionOriginal 4.11.3 diff --git a/changelog.txt b/changelog.txt index 85f37f8be62723..b053efcf027829 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +4.11.3 (02.11.23) + +- Fix adding a link to media captions in scheduled / comments. +- Fix crash in link preview options saving. +- Fix possible crash in statistics. + 4.11.2 (01.11.23) - Highlight quoted parts in jump-to-message from replies. From 0f45abd5344c203b22cf605e0ee620178ebb1e79 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 30 Oct 2023 23:06:35 +0400 Subject: [PATCH 06/70] Make WithSmallCounter adaptive --- .../SourceFiles/platform/linux/tray_linux.cpp | 51 ++------ .../SourceFiles/platform/win/tray_win.cpp | 123 +++++++----------- Telegram/SourceFiles/window/main_window.cpp | 31 +---- 3 files changed, 61 insertions(+), 144 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 2a96e1f1bcdb07..a9e1de383de212 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -58,7 +58,7 @@ class IconGraphic final { const QString _mutePanelTrayIconName; const QString _attentionPanelTrayIconName; - const int _iconSizes[5]; + const int _iconSizes[7]; bool _muted = true; int32 _count = 0; @@ -73,7 +73,7 @@ IconGraphic::IconGraphic() : _panelTrayIconName("telegram-panel") , _mutePanelTrayIconName("telegram-mute-panel") , _attentionPanelTrayIconName("telegram-attention-panel") -, _iconSizes{ 16, 22, 24, 32, 48 } { +, _iconSizes{ 16, 22, 32, 48, 64, 128, 256 } { } IconGraphic::~IconGraphic() = default; @@ -214,46 +214,13 @@ QIcon IconGraphic::trayIcon( } } - auto iconImage = currentImageBack; - - if (counter > 0) { - const auto &bg = muted - ? st::trayCounterBgMute - : st::trayCounterBg; - const auto &fg = st::trayCounterFg; - if (iconSize >= 22) { - const auto imageSize = dprSize(iconImage); - const auto layerSize = (iconSize >= 48) - ? 32 - : (iconSize >= 36) - ? 24 - : (iconSize >= 32) - ? 20 - : 16; - const auto layer = Window::GenerateCounterLayer({ - .size = layerSize, - .devicePixelRatio = iconImage.devicePixelRatio(), - .count = counter, - .bg = bg, - .fg = fg, - }); - - QPainter p(&iconImage); - p.drawImage( - imageSize.width() - layer.width() - 1, - imageSize.height() - layer.height() - 1, - layer); - } else { - iconImage = Window::WithSmallCounter(std::move(iconImage), { - .size = 16, - .count = counter, - .bg = bg, - .fg = fg, - }); - } - } - - result.addPixmap(Ui::PixmapFromImage(std::move(iconImage))); + result.addPixmap(Ui::PixmapFromImage(counter > 0 + ? Window::WithSmallCounter(std::move(currentImageBack), { + .size = iconSize, + .count = counter, + .bg = muted ? st::trayCounterBgMute : st::trayCounterBg, + .fg = st::trayCounterFg, + }) : std::move(currentImageBack))); } updateIconRegenerationNeeded( diff --git a/Telegram/SourceFiles/platform/win/tray_win.cpp b/Telegram/SourceFiles/platform/win/tray_win.cpp index 3e7d16b5c0ade6..9c72068e96effa 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.cpp +++ b/Telegram/SourceFiles/platform/win/tray_win.cpp @@ -66,36 +66,10 @@ constexpr auto kTooltipDelay = crl::time(10000); bool supportMode, bool smallIcon, bool monochrome) { - static constexpr auto kCount = 3; - static auto ScaledLogo = std::array(); - static auto ScaledLogoNoMargin = std::array(); - static auto ScaledLogoDark = std::array(); - static auto ScaledLogoLight = std::array(); - - struct Dimensions { - int index = 0; - int size = 0; - }; - const auto d = [&]() -> Dimensions { - switch (args.size) { - case 16: - return { - .index = 0, - .size = 16, - }; - case 32: - return { - .index = 1, - .size = 32, - }; - default: - return { - .index = 2, - .size = 64, - }; - } - }(); - Assert(d.index < kCount); + static auto ScaledLogo = base::flat_map(); + static auto ScaledLogoNoMargin = base::flat_map(); + static auto ScaledLogoDark = base::flat_map(); + static auto ScaledLogoLight = base::flat_map(); const auto darkMode = IsDarkTaskbar(); auto &scaled = (monochrome && darkMode) @@ -107,37 +81,44 @@ constexpr auto kTooltipDelay = crl::time(10000); : ScaledLogo; auto result = [&] { - auto &image = scaled[d.index]; - if (image.isNull()) { + if (const auto it = scaled.find(args.size); it != scaled.end()) { + return it->second; + } else { if (monochrome && darkMode) { - const auto withColor = [&](QColor color) { - switch (d.size) { - case 16: - return st::macTrayIcon.instance(color, 100 / cIntRetinaFactor()); - case 32: - return st::macTrayIcon.instance(color, 200 / cIntRetinaFactor()); - default: - return st::macTrayIcon.instance(color, 300 / cIntRetinaFactor()); + const auto withColor = [&](QColor color) -> std::pair { + if (args.size <= 16) { + return { st::macTrayIcon.instance(color, 100 / cIntRetinaFactor()), 3 }; + } else if (args.size <= 32) { + return { st::macTrayIcon.instance(color, 200 / cIntRetinaFactor()), 6 }; + } else { + return { st::macTrayIcon.instance(color, 300 / cIntRetinaFactor()), 9 }; } }; - const auto darkModeResult = withColor({ 255, 255, 255 }); - const auto lightModeResult = withColor({ 0, 0, 0, 228 }); - image = *darkMode ? darkModeResult : lightModeResult; - const auto monochromeMargin = QPoint( - (image.width() - d.size) / 2, - (image.height() - d.size) / 2); - image = image.copy( - QRect(monochromeMargin, QSize(d.size, d.size))); + const auto result = *darkMode + ? withColor({ 255, 255, 255 }) + : withColor({ 0, 0, 0, 228 }); + auto &image = scaled.emplace( + args.size, + result.first.copy( + QRect( + QPoint(result.second, result.second), + result.first.size() + - QSize(result.second * 2, result.second * 2) + ) + ).scaledToWidth(args.size, Qt::SmoothTransformation) + ).first->second; image.setDevicePixelRatio(1); + return image; } else { - image = (smallIcon - ? Window::LogoNoMargin() - : Window::Logo()).scaledToWidth( - d.size, - Qt::SmoothTransformation); + return scaled.emplace( + args.size, + (smallIcon + ? Window::LogoNoMargin() + : Window::Logo() + ).scaledToWidth(args.size, Qt::SmoothTransformation) + ).first->second; } } - return image; }(); if ((!monochrome || !darkMode) && supportMode) { Window::ConvertIconToBlack(result); @@ -148,7 +129,7 @@ constexpr auto kTooltipDelay = crl::time(10000); return Window::WithSmallCounter(std::move(result), std::move(args)); } QPainter p(&result); - const auto half = d.size / 2; + const auto half = args.size / 2; args.size = half; p.drawPixmap( half, @@ -213,36 +194,24 @@ void Tray::updateIcon() { if (!_icon) { return; } - const auto counter = Core::App().unreadBadge(); - const auto muted = Core::App().unreadBadgeMuted(); const auto controller = Core::App().activePrimaryWindow(); const auto session = !controller ? nullptr : !controller->sessionController() ? nullptr : &controller->sessionController()->session(); - const auto monochrome = Core::App().settings().trayIconMonochrome(); - const auto supportMode = session && session->supportMode(); - const auto iconSizeWidth = GetSystemMetrics(SM_CXSMICON); - - auto iconSmallPixmap16 = Tray::IconWithCounter( - CounterLayerArgs(16, counter, muted), - true, - monochrome, - supportMode); - auto iconSmallPixmap32 = Tray::IconWithCounter( - CounterLayerArgs(32, counter, muted), - true, - monochrome, - supportMode); - auto iconSmall = QIcon(); - iconSmall.addPixmap(iconSmallPixmap16); - iconSmall.addPixmap(iconSmallPixmap32); + // Force Qt to use right icon size, not the larger one. QIcon forTrayIcon; - forTrayIcon.addPixmap(iconSizeWidth >= 20 - ? iconSmallPixmap32 - : iconSmallPixmap16); + forTrayIcon.addPixmap( + Tray::IconWithCounter( + CounterLayerArgs( + GetSystemMetrics(SM_CXSMICON), + Core::App().unreadBadge(), + Core::App().unreadBadgeMuted()), + true, + Core::App().settings().trayIconMonochrome(), + session && session->supportMode())); _icon->updateIcon(forTrayIcon); } diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index db485a177821c2..7f4a78f817f514 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -292,31 +292,12 @@ QImage WithSmallCounter(QImage image, CounterLayerArgs &&args) { int delta = 0; int radius = 0; }; - const auto d = [&]() -> Dimensions { - switch (args.size.value()) { - case 16: - return { - .size = 16, - .font = 8, - .delta = ((textSize < 2) ? 2 : 1), - .radius = ((textSize < 2) ? 4 : 3), - }; - case 32: - return { - .size = 32, - .font = 12, - .delta = ((textSize < 2) ? 5 : 2), - .radius = ((textSize < 2) ? 8 : 7), - }; - default: - return { - .size = 64, - .font = 22, - .delta = ((textSize < 2) ? 9 : 4), - .radius = ((textSize < 2) ? 16 : 14), - }; - } - }(); + const auto d = Dimensions{ + .size = args.size.value(), + .font = args.size.value() / 2, + .delta = args.size.value() / ((textSize < 2) ? 8 : 16), + .radius = args.size.value() / ((textSize < 2) ? 4 : 5), + }; auto p = QPainter(&image); auto hq = PainterHighQualityEnabler(p); From 6db4b8821edafc0b963021abc74ffa1378300b27 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 2 Nov 2023 03:05:46 +0400 Subject: [PATCH 07/70] Add support for building iconset and plist without Xcode --- Telegram/CMakeLists.txt | 46 +++++++++++++++++++++++++++++++++++++++-- Telegram/Telegram.plist | 16 ++++++++------ 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 95ac565a69347f..3bfaa0b1554a9f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1540,7 +1540,39 @@ elseif (APPLE) endif() set(icons_path ${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Images.xcassets) - target_add_resource(Telegram ${icons_path}) + if (CMAKE_GENERATOR STREQUAL Xcode) + target_add_resource(Telegram ${icons_path}) + else() + set(icon_path ${icons_path}/Icon.iconset) + find_program(ICONUTIL iconutil) + find_program(PNG2ICNS png2icns) + if (ICONUTIL) + add_custom_command( + OUTPUT Icon.icns + COMMAND ${ICONUTIL} + ARGS + --convert icns + --output Icon.icns + ${icon_path} + ) + elseif (PNG2ICNS) + add_custom_command( + OUTPUT Icon.icns + COMMAND ${PNG2ICNS} + ARGS + Icon.icns + ${icon_path}/icon_16x16.png + ${icon_path}/icon_32x32.png + ${icon_path}/icon_128x128.png + ${icon_path}/icon_256x256.png + ${icon_path}/icon_512x512.png + ) + endif() + if (ICONUTIL OR PNG2ICNS) + set_source_files_properties(Icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + target_add_resource(Telegram Icon.icns) + endif() + endif() set(lang_packs en @@ -1633,7 +1665,11 @@ if (build_macstore) COMMAND rm -rf $/../Frameworks/Breakpad.framework/Resources/Inspector ) else() - set(bundle_identifier "com.tdesktop.Telegram$<$:Debug>") + if (CMAKE_GENERATOR STREQUAL Xcode) + set(bundle_identifier "com.tdesktop.Telegram$<$:Debug>") + else() + set(bundle_identifier "com.tdesktop.Telegram") + endif() set(bundle_entitlements "Telegram.entitlements") if (LINUX AND DESKTOP_APP_USE_PACKAGED) set(output_name "telegram-desktop") @@ -1642,6 +1678,12 @@ else() endif() endif() +if (CMAKE_GENERATOR STREQUAL Xcode) + set(bundle_identifier_plist "$(PRODUCT_BUNDLE_IDENTIFIER)") +else() + set(bundle_identifier_plist ${bundle_identifier}) +endif() + set_target_properties(Telegram PROPERTIES OUTPUT_NAME ${output_name} MACOSX_BUNDLE_GUI_IDENTIFIER ${bundle_identifier} diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist index ea63952288f37f..53b8daf8b998fa 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -3,15 +3,19 @@ CFBundleExecutable - $(PRODUCT_NAME) + @output_name@ CFBundleGetInfoString Telegram Desktop messaging app + CFBundleIconFile + Icon.icns + CFBundleIconName + Icon.icns CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) + @bundle_identifier_plist@ CFBundlePackageType APPL CFBundleShortVersionString - $(CURRENT_PROJECT_VERSION) + @desktop_app_version_string@ CFBundleSignature ???? CFBundleURLTypes @@ -22,7 +26,7 @@ CFBundleURLIconFile Icon.icns CFBundleURLName - $(PRODUCT_BUNDLE_IDENTIFIER) + @bundle_identifier_plist@ CFBundleURLSchemes tg @@ -30,13 +34,13 @@ CFBundleVersion - $(CURRENT_PROJECT_VERSION) + @desktop_app_version_string@ ITSAppUsesNonExemptEncryption LSApplicationCategoryType public.app-category.social-networking LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) + @CMAKE_OSX_DEPLOYMENT_TARGET@ LSFileQuarantineEnabled NOTE From 98f3fa76a2fc4c6e037486df62058367e98411c0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 2 Nov 2023 03:19:47 +0400 Subject: [PATCH 08/70] Add CI for macOS packaged build --- .github/workflows/mac_packaged.yml | 158 +++++++++++++++++++++++++++++ Telegram/CMakeLists.txt | 31 +++--- Telegram/lib_ui | 2 +- cmake | 2 +- 4 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/mac_packaged.yml diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml new file mode 100644 index 00000000000000..5147f6cbeacee4 --- /dev/null +++ b/.github/workflows/mac_packaged.yml @@ -0,0 +1,158 @@ +name: MacOS Packaged. + +on: + push: + paths-ignore: + - 'docs/**' + - '**.md' + - 'changelog.txt' + - 'LEGAL' + - 'LICENSE' + - '.github/**' + - '!.github/workflows/mac_packaged.yml' + - 'lib/xdg/**' + - 'snap/**' + - 'Telegram/build/**' + - 'Telegram/Resources/uwp/**' + - 'Telegram/Resources/winrc/**' + - 'Telegram/SourceFiles/platform/win/**' + - 'Telegram/SourceFiles/platform/linux/**' + - 'Telegram/configure.bat' + pull_request: + paths-ignore: + - 'docs/**' + - '**.md' + - 'changelog.txt' + - 'LEGAL' + - 'LICENSE' + - '.github/**' + - '!.github/workflows/mac_packaged.yml' + - 'lib/xdg/**' + - 'snap/**' + - 'Telegram/build/**' + - 'Telegram/Resources/uwp/**' + - 'Telegram/Resources/winrc/**' + - 'Telegram/SourceFiles/platform/win/**' + - 'Telegram/SourceFiles/platform/linux/**' + - 'Telegram/configure.bat' + +jobs: + + macos: + name: MacOS + runs-on: macos-latest + + strategy: + matrix: + defines: + - "" + + env: + GIT: "https://github.com" + OPENALDIR: "/usr/local/opt/openal-soft" + UPLOAD_ARTIFACT: "false" + ONLY_CACHE: "false" + MANUAL_CACHING: "1" + AUTO_CACHING: "1" + + steps: + - name: Get repository name. + run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV + + - name: Clone. + uses: actions/checkout@v3.1.0 + with: + submodules: recursive + path: ${{ env.REPO_NAME }} + + - name: First set up. + run: | + brew install autoconf automake boost cmake ffmpeg openal-soft openssl opus ninja pkg-config python qt yasm xz + sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + + xcodebuild -version > CACHE_KEY.txt + echo $MANUAL_CACHING >> CACHE_KEY.txt + echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt + if [ "$AUTO_CACHING" = "1" ]; then + thisFile=$REPO_NAME/.github/workflows/mac_packaged.yml + echo `md5 -q $thisFile` >> CACHE_KEY.txt + fi + echo "CACHE_KEY=`md5 -q CACHE_KEY.txt`" >> $GITHUB_ENV + + echo "LibrariesPath=`pwd`" >> $GITHUB_ENV + + curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master + + - name: RNNoise. + run: | + cd $LibrariesPath + + git clone --depth=1 https://gitlab.xiph.org/xiph/rnnoise.git + cd rnnoise + ./autogen.sh + ./configure --disable-examples --disable-doc + make -j$(sysctl -n hw.logicalcpu) + make install + + - name: WebRTC cache. + id: cache-webrtc + uses: actions/cache@v3.0.11 + with: + path: ${{ env.LibrariesPath }}/tg_owt + key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }} + - name: WebRTC. + if: steps.cache-webrtc.outputs.cache-hit != 'true' + run: | + cd $LibrariesPath + + git clone --recursive --depth=1 $GIT/desktop-app/tg_owt.git + cd tg_owt + + cmake -B build . -GNinja -DCMAKE_BUILD_TYPE=Debug + cmake --build build --parallel + + - name: Telegram Desktop build. + if: env.ONLY_CACHE == 'false' + env: + tg_owt_DIR: ${{ env.LibrariesPath }}/tg_owt/build + run: | + cd $REPO_NAME + + DEFINE="" + if [ -n "${{ matrix.defines }}" ]; then + DEFINE="-D ${{ matrix.defines }}=ON" + echo Define from matrix: $DEFINE + echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV + else + echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV + fi + + cmake -Bbuild -GNinja . \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_FIND_FRAMEWORK=LAST \ + -DTDESKTOP_API_TEST=ON \ + -DDESKTOP_APP_USE_PACKAGED_LAZY=ON \ + $DEFINE + + cmake --build build --parallel + + cd build + macdeployqt Telegram.app + codesign --remove-signature Telegram.app + + mkdir dmgsrc + mv Telegram.app dmgsrc + hdiutil create -volname Telegram -srcfolder dmgsrc -ov -format UDZO Telegram.dmg + + - name: Move artifact. + if: env.UPLOAD_ARTIFACT == 'true' + run: | + cd $REPO_NAME/build + mkdir artifact + mv Telegram.dmg artifact/ + - uses: actions/upload-artifact@master + if: env.UPLOAD_ARTIFACT == 'true' + name: Upload artifact. + with: + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.REPO_NAME }}/build/artifact/ diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 3bfaa0b1554a9f..f52ea30a21df35 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1599,24 +1599,17 @@ elseif (APPLE) COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $/../Resources COMMAND cp ${CMAKE_BINARY_DIR}/lib_spellcheck.rcc $/../Resources ) - if (NOT build_macstore) + if (NOT build_macstore AND NOT DESKTOP_APP_DISABLE_CRASH_REPORTS) + if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64") + set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}") + else() + set(crashpad_dir_part "") + endif() add_custom_command(TARGET Telegram PRE_LINK - COMMAND mkdir -p $/../Frameworks - COMMAND cp $ $/../Frameworks/ + COMMAND mkdir -p $/../Helpers + COMMAND cp ${libs_loc}/crashpad/out/$,Debug,Release>${crashpad_dir_part}/crashpad_handler $/../Helpers/ ) - if (NOT DESKTOP_APP_DISABLE_CRASH_REPORTS) - if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64") - set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}") - else() - set(crashpad_dir_part "") - endif() - add_custom_command(TARGET Telegram - PRE_LINK - COMMAND mkdir -p $/../Helpers - COMMAND cp ${libs_loc}/crashpad/out/$,Debug,Release>${crashpad_dir_part}/crashpad_handler $/../Helpers/ - ) - endif() endif() else() target_link_libraries(Telegram @@ -1757,7 +1750,7 @@ endif() target_prepare_qrc(Telegram) -if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore) +if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_winstore) add_executable(Updater WIN32) init_non_host_target(Updater) @@ -1795,6 +1788,12 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT else() target_link_options(Updater PRIVATE -municode) endif() + elseif (APPLE) + add_custom_command(TARGET Updater + PRE_LINK + COMMAND mkdir -p $/../Frameworks + COMMAND cp $ $/../Frameworks/ + ) endif() if (DESKTOP_APP_SPECIAL_TARGET) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index d7f12b083ee990..1ae65d2cba7e41 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit d7f12b083ee9901c5e5a95170559376d51b00f20 +Subproject commit 1ae65d2cba7e41f7bb43b490f2d0cc150a958163 diff --git a/cmake b/cmake index 60474a576690f6..78098ede77a09e 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 60474a576690f63510d87d18ca348d9875c89f08 +Subproject commit 78098ede77a09e41da6233d765d02b43a60e7138 From 1851b6ff30f30950e3741600d6dd63d62961b22b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 1 Nov 2023 00:22:51 +0000 Subject: [PATCH 09/70] Update User-Agent for DNS to Chrome 118.0.5993.117. --- .../SourceFiles/mtproto/details/mtproto_domain_resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index b07d2c5e5379e3..f20bb78e0a38eb 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/116.0.5845.96 Safari/537.36"); + "Chrome/118.0.5993.117 Safari/537.36"); return kResult; } From 1852161fbf48b6f15143f19e91210af7bc6f67b5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 3 Nov 2023 14:56:42 +0300 Subject: [PATCH 10/70] Moved out generating unique PeerListRowId from string to single place. --- .../SourceFiles/boxes/filters/edit_filter_links.cpp | 12 +----------- Telegram/SourceFiles/boxes/peer_list_box.cpp | 6 ++++++ Telegram/SourceFiles/boxes/peer_list_box.h | 2 ++ .../boxes/peers/edit_peer_invite_links.cpp | 11 +++-------- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp index 557eefa9a47830..d731ead51f0a3d 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -37,8 +37,6 @@ For license and copyright information please follow this link: #include "styles/style_menu_icons.h" #include "styles/style_settings.h" -#include - namespace { constexpr auto kMaxLinkTitleLength = 32; @@ -223,14 +221,6 @@ class ChatRow final : public PeerListRow { }; -[[nodiscard]] uint64 ComputeRowId(const QString &link) { - return XXH64(link.data(), link.size() * sizeof(ushort), 0); -} - -[[nodiscard]] uint64 ComputeRowId(const InviteLinkData &data) { - return ComputeRowId(data.url); -} - [[nodiscard]] Color ComputeColor(const InviteLinkData &link) { return Color::Permanent; } @@ -242,7 +232,7 @@ class ChatRow final : public PeerListRow { LinkRow::LinkRow( not_null delegate, const InviteLinkData &data) -: PeerListRow(ComputeRowId(data)) +: PeerListRow(UniqueRowIdFromString(data.url)) , _delegate(delegate) , _data(data) , _color(ComputeColor(data)) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 3395a58504d44b..abd4f4b3253a2a 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -35,6 +35,12 @@ For license and copyright information please follow this link: #include "styles/style_dialogs.h" #include "styles/style_widgets.h" +#include // XXH64. + +[[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d) { + return XXH64(d.data(), d.size() * sizeof(ushort), 0); +} + PaintRoundImageCallback PaintUserpicCallback( not_null peer, bool respectSavedMessagesChat) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 6dfce4630ea2d6..0f557da68b431d 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -54,6 +54,8 @@ using PaintRoundImageCallback = Fn - namespace { enum class Color { @@ -112,12 +110,8 @@ class Row final : public PeerListRow { }; -[[nodiscard]] uint64 ComputeRowId(const QString &link) { - return XXH64(link.data(), link.size() * sizeof(ushort), 0); -} - [[nodiscard]] uint64 ComputeRowId(const InviteLinkData &data) { - return ComputeRowId(data.link); + return UniqueRowIdFromString(data.link); } [[nodiscard]] float64 ComputeProgress( @@ -628,7 +622,8 @@ void LinksController::updateRow(const InviteLinkData &data, TimeId now) { } bool LinksController::removeRow(const QString &link) { - if (const auto row = delegate()->peerListFindRow(ComputeRowId(link))) { + const auto id = UniqueRowIdFromString(link); + if (const auto row = delegate()->peerListFindRow(id)) { delegate()->peerListRemoveRow(row); return true; } From 9df551a1458c875116caa5426a76af97edf7376f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 24 Oct 2023 13:07:06 +0300 Subject: [PATCH 11/70] Improved API support of boosts list. --- Telegram/SourceFiles/api/api_statistics.cpp | 25 +++++++++++++++++-- .../SourceFiles/boxes/gift_premium_box.cpp | 7 ++---- Telegram/SourceFiles/data/data_boosts.h | 17 ++++++++++++- .../info/boosts/info_boosts_inner_widget.cpp | 5 ++-- .../info_statistics_list_controllers.cpp | 2 +- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index c3286f667c777f..1231a93037e632 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -567,9 +567,30 @@ void Boosts::requestBoosts( auto list = std::vector(); list.reserve(data.vboosts().v.size()); for (const auto &boost : data.vboosts().v) { + const auto &data = boost.data(); + const auto path = data.vused_gift_slug() + ? (u"giftcode/"_q + qs(data.vused_gift_slug()->v)) + : QString(); + auto giftCodeLink = !path.isEmpty() + ? Data::GiftCodeLink{ + _peer->session().createInternalLink(path), + _peer->session().createInternalLinkFull(path), + qs(data.vused_gift_slug()->v), + } + : Data::GiftCodeLink(); list.push_back({ - boost.data().vuser_id().value_or_empty(), - QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v), + data.is_gift(), + data.is_giveaway(), + data.is_unclaimed(), + qs(data.vid()), + data.vuser_id().value_or_empty(), + data.vgiveaway_msg_id() + ? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v } + : FullMsgId(), + QDateTime::fromSecsSinceEpoch(data.vdate().v), + data.vexpires().v, + std::move(giftCodeLink), + data.vmultiplier().value_or_empty(), }); } done(Data::BoostsListSlice{ diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 84ea6a429a0890..7de1b91cc8ec74 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -13,6 +13,7 @@ For license and copyright information please follow this link: #include "base/unixtime.h" #include "base/weak_ptr.h" #include "boxes/peers/prepare_short_info_box.h" +#include "data/data_boosts.h" #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_media_types.h" // Data::Giveaway @@ -237,11 +238,7 @@ void GiftBox( }, box->lifetime()); } -struct GiftCodeLink { - QString text; - QString link; -}; -[[nodiscard]] GiftCodeLink MakeGiftCodeLink( +[[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink( not_null session, const QString &slug) { const auto path = u"giftcode/"_q + slug; diff --git a/Telegram/SourceFiles/data/data_boosts.h b/Telegram/SourceFiles/data/data_boosts.h index 6ad253169659d0..b17ca85f8cfc5f 100644 --- a/Telegram/SourceFiles/data/data_boosts.h +++ b/Telegram/SourceFiles/data/data_boosts.h @@ -19,9 +19,24 @@ struct BoostsOverview final { float64 premiumMemberPercentage = 0; }; +struct GiftCodeLink final { + QString text; + QString link; + QString slug; +}; + struct Boost final { + bool isGift = false; + bool isGiveaway = false; + bool isUnclaimed = false; + + QString id; UserId userId = UserId(0); - QDateTime expirationDate; + FullMsgId giveawayMessage; + QDateTime date; + crl::time expiresAt = 0; + GiftCodeLink giftCodeLink; + int multiplier = 0; }; struct BoostsListSlice final { diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 7528c093be75f9..89dc030811ebbe 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -100,8 +100,9 @@ void FillOverview( const auto topLeftLabel = addPrimary(stats.level); const auto topRightLabel = addPrimary(stats.premiumMemberCount); const auto bottomLeftLabel = addPrimary(stats.boostCount); - const auto bottomRightLabel = addPrimary( - stats.nextLevelBoostCount - stats.boostCount); + const auto bottomRightLabel = addPrimary(std::max( + stats.nextLevelBoostCount - stats.boostCount, + 0)); addSub( topLeftLabel, diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index db764dff6ba5d7..89aadcf6606849 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -397,7 +397,7 @@ void BoostsController::applySlice(const Data::BoostsListSlice &slice) { row->setCustomStatus(tr::lng_boosts_list_status( tr::now, lt_date, - QLocale().toString(item.expirationDate, formatter))); + QLocale().toString(item.date, formatter))); delegate()->peerListAppendRow(std::move(row)); } delegate()->peerListRefreshRows(); From e00016312ed538014c1603b458bea9518b72edaa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 24 Oct 2023 13:54:52 +0300 Subject: [PATCH 12/70] Added button for giveaway creating to boosts info. --- Telegram/Resources/langs/lang.strings | 2 ++ .../info/boosts/info_boosts_inner_widget.cpp | 29 +++++++++++++++++++ .../SourceFiles/statistics/statistics.style | 6 ++++ 3 files changed, 37 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 183b86acd789a7..b8e1b160be396b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4316,6 +4316,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boosts_list_status" = "boost expires on {date}"; "lng_boosts_link_title" = "Link for boosting"; "lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts."; +"lng_boosts_get_boosts" = "Get Boosts via Gifts"; +"lng_boosts_get_boosts_subtext" = "Get more boosts for your channel by gifting Telegram Premium to your subscribers."; // Wnd specific diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 89dc030811ebbe..da6e11c4839b49 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -11,6 +11,7 @@ For license and copyright information please follow this link: #include "boxes/peers/edit_peer_invite_link.h" #include "info/boosts/info_boosts_widget.h" #include "info/info_controller.h" +#include "info/profile/info_profile_icon.h" #include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" #include "settings/settings_common.h" @@ -19,6 +20,7 @@ For license and copyright information please follow this link: #include "ui/controls/invite_link_buttons.h" #include "ui/controls/invite_link_label.h" #include "ui/rect.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "styles/style_info.h" #include "styles/style_statistics.h" @@ -174,6 +176,28 @@ void FillShareLink( ::Settings::AddSkip(content, st::boostsLinkFieldPadding.bottom()); } +void FillGetBoostsButton( + not_null content, + std::shared_ptr show, + not_null peer) { + const auto &st = st::getBoostsButton; + const auto &icon = st::getBoostsButtonIcon; + const auto button = content->add( + ::Settings::CreateButton( + content.get(), + tr::lng_boosts_get_boosts(), + st)); + button->setClickedCallback([=] { + }); + Ui::CreateChild( + button, + icon, + QPoint{ + st::infoSharedMediaButtonIconPosition.x(), + (st.height + rect::m::sum::v(st.padding) - icon.height()) / 2, + })->show(); +} + } // namespace InnerWidget::InnerWidget( @@ -266,6 +290,11 @@ void InnerWidget::fill() { ::Settings::AddSkip(inner); ::Settings::AddDividerText(inner, tr::lng_boosts_link_subtext()); + ::Settings::AddSkip(inner); + FillGetBoostsButton(inner, _show, _peer); + ::Settings::AddSkip(inner); + ::Settings::AddDividerText(inner, tr::lng_boosts_get_boosts_subtext()); + resizeToWidth(width()); crl::on_main([=]{ fakeShowed->fire({}); }); } diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index f797dbc1a059d6..c631a4f30450f3 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -142,3 +142,9 @@ boostsButton: SettingsButton(defaultSettingsButton) { textFg: lightButtonFg; textFgOver: lightButtonFgOver; } + +getBoostsButton: SettingsButton(reportReasonButton) { + textFg: lightButtonFg; + textFgOver: lightButtonFg; +} +getBoostsButtonIcon: icon {{ "menu/gift_premium", lightButtonFg }}; From 5b0c48bb5232223fdcfde4c201480d19af121cca Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 24 Oct 2023 15:06:00 +0300 Subject: [PATCH 13/70] Added dummy box for creating giveaway. --- Telegram/CMakeLists.txt | 2 + .../info/boosts/create_giveaway_box.cpp | 81 +++++++++++++++++++ .../info/boosts/create_giveaway_box.h | 23 ++++++ .../info/boosts/info_boosts_inner_widget.cpp | 5 +- .../info/boosts/info_boosts_widget.cpp | 1 - 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp create mode 100644 Telegram/SourceFiles/info/boosts/create_giveaway_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f52ea30a21df35..1cb4309aacf94c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -824,6 +824,8 @@ PRIVATE history/history_view_highlight_manager.h history/history_widget.cpp history/history_widget.h + info/boosts/create_giveaway_box.cpp + info/boosts/create_giveaway_box.h info/boosts/info_boosts_inner_widget.cpp info/boosts/info_boosts_inner_widget.h info/boosts/info_boosts_widget.cpp diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp new file mode 100644 index 00000000000000..828d1283d562fe --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_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 "info/boosts/create_giveaway_box.h" + +#include "boxes/peers/edit_participants_box.h" // ParticipantsBoxController +#include "data/data_peer.h" +#include "data/data_user.h" +#include "info/info_controller.h" +#include "lang/lang_keys.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/labels.h" + +namespace { + +class MembersListController : public ParticipantsBoxController { +public: + using ParticipantsBoxController::ParticipantsBoxController; + + void rowClicked(not_null row) override; + std::unique_ptr createRow( + not_null participant) const override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + +private: + +}; + +void MembersListController::rowClicked(not_null row) { + delegate()->peerListSetRowChecked(row, !row->checked()); +} + +std::unique_ptr MembersListController::createRow( + not_null participant) const { + const auto user = participant->asUser(); + if (!user || user->isInaccessible() || user->isBot() || user->isSelf()) { + return nullptr; + } + return std::make_unique(participant); +} + +base::unique_qptr MembersListController::rowContextMenu( + QWidget *parent, + not_null row) { + return nullptr; +} + +} // namespace + +void CreateGiveawayBox( + not_null box, + not_null controller, + not_null peer) { + box->addButton(tr::lng_box_ok(), [=] { + auto initBox = [=](not_null peersBox) { + peersBox->setTitle(tr::lng_giveaway_award_option()); + peersBox->addButton(tr::lng_settings_save(), [=] { + const auto selected = peersBox->collectSelectedRows(); + peersBox->closeBox(); + }); + peersBox->addButton(tr::lng_cancel(), [=] { + peersBox->closeBox(); + }); + }; + + box->uiShow()->showBox( + Box( + std::make_unique( + controller, + peer, + ParticipantsRole::Members), + std::move(initBox)), + Ui::LayerOption::KeepOther); + }); +} diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.h b/Telegram/SourceFiles/info/boosts/create_giveaway_box.h new file mode 100644 index 00000000000000..7463732dcb5d12 --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.h @@ -0,0 +1,23 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class PeerData; + +namespace Info { +class Controller; +} // namespace Info + +namespace Ui { +class GenericBox; +} // namespace Ui + +void CreateGiveawayBox( + not_null box, + not_null controller, + not_null peer); diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index da6e11c4839b49..73edbd68d7fa56 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -9,6 +9,7 @@ For license and copyright information please follow this link: #include "api/api_statistics.h" #include "boxes/peers/edit_peer_invite_link.h" +#include "info/boosts/create_giveaway_box.h" #include "info/boosts/info_boosts_widget.h" #include "info/info_controller.h" #include "info/profile/info_profile_icon.h" @@ -178,6 +179,7 @@ void FillShareLink( void FillGetBoostsButton( not_null content, + not_null controller, std::shared_ptr show, not_null peer) { const auto &st = st::getBoostsButton; @@ -188,6 +190,7 @@ void FillGetBoostsButton( tr::lng_boosts_get_boosts(), st)); button->setClickedCallback([=] { + show->showBox(Box(CreateGiveawayBox, controller, peer)); }); Ui::CreateChild( button, @@ -291,7 +294,7 @@ void InnerWidget::fill() { ::Settings::AddDividerText(inner, tr::lng_boosts_link_subtext()); ::Settings::AddSkip(inner); - FillGetBoostsButton(inner, _show, _peer); + FillGetBoostsButton(inner, _controller, _show, _peer); ::Settings::AddSkip(inner); ::Settings::AddDividerText(inner, tr::lng_boosts_get_boosts_subtext()); diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_widget.cpp index 2679f67a010c41..fac44f16ad0d25 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_widget.cpp @@ -118,4 +118,3 @@ std::shared_ptr Make(not_null peer) { } } // namespace Info::Boosts - From 67bbb477c7e0deba3b1b2b987828db9048ca5ce1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Oct 2023 11:58:25 +0300 Subject: [PATCH 14/70] Added ability to select users for awarding in giveaway box. --- .../Resources/icons/boosts/filled_gift.png | Bin 0 -> 727 bytes .../Resources/icons/boosts/filled_gift@2x.png | Bin 0 -> 1374 bytes .../Resources/icons/boosts/filled_gift@3x.png | Bin 0 -> 2147 bytes .../info/boosts/create_giveaway_box.cpp | 96 +++++++++++---- .../boosts/giveaway/giveaway_type_row.cpp | 114 ++++++++++++++++++ .../info/boosts/giveaway/giveaway_type_row.h | 49 ++++++++ .../SourceFiles/statistics/statistics.style | 12 ++ Telegram/cmake/td_ui.cmake | 3 + 8 files changed, 253 insertions(+), 21 deletions(-) create mode 100644 Telegram/Resources/icons/boosts/filled_gift.png create mode 100644 Telegram/Resources/icons/boosts/filled_gift@2x.png create mode 100644 Telegram/Resources/icons/boosts/filled_gift@3x.png create mode 100644 Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp create mode 100644 Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h diff --git a/Telegram/Resources/icons/boosts/filled_gift.png b/Telegram/Resources/icons/boosts/filled_gift.png new file mode 100644 index 0000000000000000000000000000000000000000..123818f74b8d1b7defc9157ceb5241c71876a4cf GIT binary patch literal 727 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfyvR+#WBP} z@M?(n>6L*Z|L&wuTCyZXLD+DD=2DGhqlHtHn3bnDc3NttACY&^OjT1BcAY8NRpiMN z!koz3%{xA#8pJNNvx_2SL!ziY2;p11Yet?6IuYW}bvep|LZYVF@Td%6DLl_LGe zCwr*ujLF*=k+c1_+v3E{H}B;u%kbTQ|NZ!5!XebTS@P;0w!3-aR-147+*J`YEo@3S_0%YpVld|YDY8E~jHrMo6{ zo}F8GK;b@D%N-A~=I&%eeP6r8^4)j6%{}JW9=pHCtdL3c(KncX-u<$r44;XzR@c!aor9N`l=rYVm6=NQ`YksW>YU)~ zD8kjcK~}4JE~`#xK!bRWfrN*Hfy5FU#(3W;e#>VD%|HMBtcz5yo8I*QKWrI7w63am z&U@dn)97fY3)A*KGZ(jp6eG{re1=~WwyOF3k-K;%MlLCBm%Ub&*=!&AgFDn>l&05f x)}8ure8ttQHM=_cBL!I|OuWxs-dLYn$Mks87gc44gzKP0<>~6@vd$@?2>{JO91Z{g literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/boosts/filled_gift@2x.png b/Telegram/Resources/icons/boosts/filled_gift@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..13e5102ed47f9c0095ae86fb9103464086cacf96 GIT binary patch literal 1374 zcmV-k1)=(hP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NG$Vo&&R9Fe^SW76TQ5YT!7H*A8 z;7E+4b5?NWuLb=3BxfMe0L}TWe|BScy zJLmh3@4qygnX}+I@8x;VIp2HUbI$+i)BpB=1^&(oBqStsbabq*ub-cvKRi6_?Cgw< zjb&wJNzr|Le5$Id78Vxv_xGQlpO21?mY0_s8ykayf+S0~y3NhaPft%j&D!|*xWB)@ zV2_WF-`?Ifv-){>cvw_aFH^qMeMh?H(>nj?CkXO^CPqL^mL4m zNLpH2NaZxZ%qVrPudhQxLnR6X-rCxtU;w?ny^;;OB_$+4IHgN=<1-bH%xuE3}Tlt!FWwftB@ z+ZaUZClmf6d&B~mGM1N@(<=7&_nX?-6;@YQ6W8qQEas|u>Ia1HesXdmNN#U$u{~7= z=OiN|BQz!~7?LI;B7#$x4hWSA#l5Mi>GJZD+-quTxT7gH)S-}&5Nwmu($au{0B%hg zz=a4Sx+y6sB;VWH6IkyeY>);hE3K`qH#axLU;v||qcj`)fK%A_B1;7242BiLH+JC^ zK_e*4BbY$_Ce%J@Cm~ z3(URvNB7Jp`|fBwhG#JDb+jApgM)(uRB$u^`(9*;pnUX(73%5f;X0dmaB%R4qXF1- z;bu}$Lk__XH(#|<3;&#)oYd4*&IOD+WNd7#D77f`xQhh_21+)#a;&bdav_oiDxV^odo4JcoNgp2{i5>479N?Rqn{#t>xZ87fbRmQa z1~4NRT+qV8!nmc=7}bRuF6yAhbrugX>D<-x!Z;%%1D_S{Wo2bVhzFD2(L_s=BLH3( zh@zvTaYPac0h5q=5qWZOdU~3dm#25++RV&M{^a0P+>B#+myr(6ggL-<46jw(V~`ej zWPNosEJJQ??%CNHx2&(P9~v5>cP&=|^Xe?A%)5Lx8jP)rYch7e#M0N-r}46ytwdtz z>gk|;eSPij?hX$RH@G=E$mI{+Rj&^uA2Jpf7i%c!xnjkm@ikXj5efO!)Rei~yVR;e z679R8{NHev$x7?~b;RhL6 za^UoU^z-A?^oYU5gUdA-W+Oll;Am5(IHQ6(fe>?D;{S3_<&Ap9n0Md!ZU6>|Jk zV0uccAA*lh)v6NEf}DHV#g&zncE-f%)bNr7grMav&S6Ytcz77+fF{Q|qZFRHa0dyf gTwEI-<6pHq)$07*qoM6N<$f`;;SfB*mh literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/boosts/filled_gift@3x.png b/Telegram/Resources/icons/boosts/filled_gift@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..7e3ad4e24fb933f8ae864c17ce1a99d67eedba18 GIT binary patch literal 2147 zcmV-p2%PtcP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?%}GQ-RA>e5npembI}pb|yJA=D z*MbePfFgGBK@m~Jf)8TDHw8toV8imDuYy<*Q3M4=uz&?@SP(0MqS)J2>=m(h@BQ&D z#Bs8l&F-FaxLnS8_?Y}NE%~yO%p@fx#b0p-iZf80f#MAOo`EvI$y8L^wrwX&n9!?N zufIxvpFVy1yYzST=+VuaH-Gu^#i6xj%a)TSP3qXOBSec9E#AL>|M>Ca`}gnf-o1P0 z&Yd4Vd~nF-v8zj$E~ieN`X~DL{Q2`mixyR`T-kPl!EE2Y{l||VQPLkjeq6b7W!0)x zZP`2`Wy_Xbym;}qZ{MQalK6=eCu-HICA}FiV8FX~?<7wYedo>{)`FDHFTHHpvM5(V z@ynMlH*DB2@M+YjQD47)4P^@jS&%G^{EjEkK6&!wP!HVT)~#C+uUD_$+qZAsoOUlC zUgO4%la4J}@Tgg{=EH{%t5>hC%shJZXwRNKw{PEm{rYu>4jq_d)?2x93wlrc5y)#M7rwj~zR9>C&YqPo5A$I(6zaV#J8@<;zPR==JN@6J`{c%jVpQ%|ig8csdyFq^a`n7iLT6>P#wQEO2HEaPvbZyX}fijWH z=Jo5>l}VyVlz~c>D(&01Pw|`PxN+m61gll6cJt;-EwQEm(^(VT)cSE1|`Cbu)j-$g)N)0 zbb^5qxRg4=w?gbDq0$CfpEf8F0%PskwIyO(m%Ul(D_5?_rGNMC-7;n3Hw>1ml!A`+ z!~VvmO`8@gm7ZbY!iCC3pY4S$K}oqiAWHG|N)h&nMvWS|r84!_s8QqDvuD!e=g*(} z_wVmknrL7w!VMcXNE6V@moIml$U*P0VZ)T8th9|AH#$x|iXTU#{1`lV@U?5#)Tlt1 z&?u28Jn{=+r{@q9>TZu7J=(W#uQam7k%$o$_z}}&Z|~l{)pmFC5Ay2K$C z8#$f91A;-4p1pbVM&UFGC+rF;c-m}#ATwvqETdTO0?}3GyNN}Wzow@%M!~b?*ItkCytXHg9k$IFuyskRUUTd`sV zm5gT1nqm0Cg9p2I?UEkki7rI&$dl_*tsS2V_l+GmaDa6vy(u(J{ax*V*_#ySf?+^p z*QPY!u-6=&K7CqAW^Zy14s1hY*QPY!u-Cwb4k(i4om$mnmjy* z3v$euG13^#?1Kq3;AB+EAS5aVPLLyY@7~?c$IvLf?9->u`0?Y}rX(n7D(~EVabsja zYIyhV-D}dMi9r-WAz;gvEnL$WTn0S~2V|~vk&p4ur>Jh&az-pzupoBs=gpgE+XIXh zT)K3rEm#PI|5dDWTvFDlQ^)oOkgizqfddEHvYyEA3lUEN!?t9}lFpqwneMJq<-w&)N~*5x)sFJYIvm}E-Bxa-%i zfBg9ISaV^bsjyf}FuZv2V(i$ls(u()qhQ#tUq7CM*!P#x9lkD-8XsFyDvI{{#xNEo zukaf;ZV*(6jjX6PrN7p#TXRDR8}XgD4cy-#A4 z@sdTo_ArJC29)L<;&zvj%}Us+JrQ0L%$PC5{lI3LpO&z(JcHmfVP z!a>0BaWpSHW~w|1uA0=L#Nk$oEXkoQS(oQ{3>&X^{0(p=Yu_^&RiT8x@)*ug?NK1v zgnC>MqrIf${Q2`y+CuSIg)rp4m_xWHp|Wk04e+yZ5c}K|%9eXDblRdowrS-7w}gid z9SZm%>`TKTLxx0Y3&oZG^vzK1QJ{SEMBc@FO8JBeDVl&XPvb0kjNw*o* z8=N!PcBf9AO5Uz|O_MGdV8a%F*%eGzETP;>-;A{+8CYz*ha=IHAddk#H1UIwIBc_K z%_5tWU@V<}BuX;YlAD27N5Q`lwGi2BrpmqaO}F2%z0I39m$rEt5G&$lklLbXuhTsT zb2H^piX6Co7{_l1zq^Z9!tV4Z5ck^0NR!;g3fb%FjMu=32ZeOqZ7Y|~@P0YzaZY|D zwxhB)5se!-V=GLLtX{o3kq465wkmrQ(YRr2%Y;y!KXT+qA}0*9ZP;`rB5=TQNnl&? zMNrCwrC5`;fkfkh8Kaoa%czWVEOAN{)MR{$@4*0!tRXxHh6xN|F2~=M@uNr-XCVJG Z@Grf)GNZUEmwW&K002ovPDHLkV1n3{9n}B; literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 828d1283d562fe..3daff65434703c 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -10,10 +10,13 @@ For license and copyright information please follow this link: #include "boxes/peers/edit_participants_box.h" // ParticipantsBoxController #include "data/data_peer.h" #include "data/data_user.h" +#include "info/boosts/giveaway/giveaway_type_row.h" #include "info/info_controller.h" #include "lang/lang_keys.h" #include "ui/layers/generic_box.h" +#include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" +#include "styles/style_layers.h" namespace { @@ -28,8 +31,6 @@ class MembersListController : public ParticipantsBoxController { QWidget *parent, not_null row) override; -private: - }; void MembersListController::rowClicked(not_null row) { @@ -57,25 +58,78 @@ void CreateGiveawayBox( not_null box, not_null controller, not_null peer) { - box->addButton(tr::lng_box_ok(), [=] { - auto initBox = [=](not_null peersBox) { - peersBox->setTitle(tr::lng_giveaway_award_option()); - peersBox->addButton(tr::lng_settings_save(), [=] { - const auto selected = peersBox->collectSelectedRows(); - peersBox->closeBox(); - }); - peersBox->addButton(tr::lng_cancel(), [=] { - peersBox->closeBox(); - }); - }; + struct State final { + std::vector> selectedToAward; + rpl::event_stream<> toAwardAmountChanged; + }; + const auto state = box->lifetime().make_state(); + using GiveawayType = Giveaway::GiveawayTypeRow::Type; + using GiveawayGroup = Ui::RadioenumGroup; + const auto typeGroup = std::make_shared(); + + box->setWidth(st::boxWideWidth); + + { + const auto row = box->verticalLayout()->add( + object_ptr( + box, + GiveawayType::Random, + tr::lng_giveaway_create_subtitle())); + row->addRadio(typeGroup); + row->setClickedCallback([=] { + typeGroup->setValue(GiveawayType::Random); + }); + } + { + const auto row = box->verticalLayout()->add( + object_ptr( + box, + GiveawayType::SpecificUsers, + state->toAwardAmountChanged.events_starting_with( + rpl::empty_value() + ) | rpl::map([=] { + const auto &selected = state->selectedToAward; + return selected.empty() + ? tr::lng_giveaway_award_subtitle() + : (selected.size() == 1) + ? rpl::single(selected.front()->name()) + : tr::lng_giveaway_award_chosen( + lt_count, + rpl::single(selected.size()) | tr::to_count()); + }) | rpl::flatten_latest())); + row->addRadio(typeGroup); + row->setClickedCallback([=] { + auto initBox = [=](not_null peersBox) { + peersBox->setTitle(tr::lng_giveaway_award_option()); + peersBox->addButton(tr::lng_settings_save(), [=] { + state->selectedToAward = peersBox->collectSelectedRows(); + state->toAwardAmountChanged.fire({}); + peersBox->closeBox(); + }); + peersBox->addButton(tr::lng_cancel(), [=] { + peersBox->closeBox(); + }); + peersBox->boxClosing( + ) | rpl::start_with_next([=] { + typeGroup->setValue(state->selectedToAward.empty() + ? GiveawayType::Random + : GiveawayType::SpecificUsers); + }, peersBox->lifetime()); + }; + + box->uiShow()->showBox( + Box( + std::make_unique( + controller, + peer, + ParticipantsRole::Members), + std::move(initBox)), + Ui::LayerOption::KeepOther); + }); + } + typeGroup->setValue(GiveawayType::Random); - box->uiShow()->showBox( - Box( - std::make_unique( - controller, - peer, - ParticipantsRole::Members), - std::move(initBox)), - Ui::LayerOption::KeepOther); + box->addButton(tr::lng_box_ok(), [=] { + box->closeBox(); }); } diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp new file mode 100644 index 00000000000000..1779b91e112a8d --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp @@ -0,0 +1,114 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "info/boosts/giveaway/giveaway_type_row.h" + +#include "lang/lang_keys.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/text/text_options.h" +#include "ui/widgets/checkbox.h" +#include "styles/style_boxes.h" +#include "styles/style_statistics.h" + +namespace Giveaway { + +constexpr auto kColorIndexSpecific = int(4); +constexpr auto kColorIndexRandom = int(2); + +GiveawayTypeRow::GiveawayTypeRow( + not_null parent, + Type type, + rpl::producer subtitle) +: RippleButton(parent, st::defaultRippleAnimation) +, _st(st::giveawayTypeListItem) +, _type(type) +, _userpic( + Ui::EmptyUserpic::UserpicColor((_type == Type::SpecificUsers) + ? kColorIndexSpecific + : kColorIndexRandom), + QString()) +, _name( + _st.nameStyle, + (type == Type::SpecificUsers) + ? tr::lng_giveaway_award_option(tr::now) + : tr::lng_giveaway_create_option(tr::now), + Ui::NameTextOptions()) { + std::move( + subtitle + ) | rpl::start_with_next([=] (const QString &s) { + _status.setText(st::defaultTextStyle, s, Ui::NameTextOptions()); + }, lifetime()); +} + +int GiveawayTypeRow::resizeGetHeight(int) { + return _st.height; +} + +void GiveawayTypeRow::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + + const auto paintOver = (isOver() || isDown()) && !isDisabled(); + const auto skipRight = _st.photoPosition.x(); + const auto outerWidth = width(); + const auto isSpecific = (_type == Type::SpecificUsers); + + if (paintOver) { + p.fillRect(e->rect(), _st.button.textBgOver); + } + Ui::RippleButton::paintRipple(p, 0, 0); + _userpic.paintCircle( + p, + _st.photoPosition.x(), + _st.photoPosition.y(), + outerWidth, + _st.photoSize); + { + const auto &userpic = isSpecific + ? st::giveawayUserpicGroup + : st::giveawayUserpic; + const auto userpicRect = QRect( + _st.photoPosition + - QPoint( + isSpecific ? -st::giveawayUserpicSkip : 0, + isSpecific ? 0 : st::giveawayUserpicSkip), + Size(_st.photoSize)); + userpic.paintInCenter(p, userpicRect); + } + + const auto namex = _st.namePosition.x(); + const auto namey = _st.namePosition.y(); + const auto namew = outerWidth - namex - skipRight; + + p.setPen(_st.nameFg); + _name.drawLeftElided(p, namex, namey, namew, width()); + + const auto statusx = _st.statusPosition.x(); + const auto statusy = _st.statusPosition.y(); + const auto statusw = outerWidth - statusx - skipRight; + p.setFont(st::contactsStatusFont); + p.setPen(isSpecific ? st::lightButtonFg : _st.statusFg); + _status.drawLeftElided(p, statusx, statusy, statusw, outerWidth); +} + +void GiveawayTypeRow::addRadio( + std::shared_ptr> typeGroup) { + const auto &st = st::defaultCheckbox; + const auto radio = Ui::CreateChild>( + this, + std::move(typeGroup), + _type, + QString(), + st); + radio->moveToLeft( + st::giveawayRadioPosition.x(), + st::giveawayRadioPosition.y()); + radio->setAttribute(Qt::WA_TransparentForMouseEvents); + radio->show(); +} + +} // namespace Giveaway diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h new file mode 100644 index 00000000000000..440b037f66edfb --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.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 + +#include "ui/empty_userpic.h" +#include "ui/widgets/buttons.h" + +namespace Ui { +template +class RadioenumGroup; +} // namespace Ui + +namespace Giveaway { + +class GiveawayTypeRow final : public Ui::RippleButton { +public: + enum class Type { + Random, + SpecificUsers, + }; + + GiveawayTypeRow( + not_null parent, + Type type, + rpl::producer subtitle); + + void addRadio(std::shared_ptr> typeGroup); + +protected: + void paintEvent(QPaintEvent *e) override; + + int resizeGetHeight(int) override; + +private: + const style::PeerListItem _st; + const Type _type; + + Ui::EmptyUserpic _userpic; + Ui::Text::String _status; + Ui::Text::String _name; + +}; + +} // namespace Giveaway diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index c631a4f30450f3..9f7abbcddf0606 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -148,3 +148,15 @@ getBoostsButton: SettingsButton(reportReasonButton) { textFgOver: lightButtonFg; } getBoostsButtonIcon: icon {{ "menu/gift_premium", lightButtonFg }}; + +giveawayTypeListItem: PeerListItem(defaultPeerListItem) { + height: 52px; + photoPosition: point(58px, 6px); + namePosition: point(110px, 8px); + statusPosition: point(110px, 28px); + photoSize: 42px; +} +giveawayUserpic: icon {{ "boosts/filled_gift", windowFgActive }}; +giveawayUserpicSkip: 1px; +giveawayUserpicGroup: icon {{ "limits/groups", windowFgActive }}; +giveawayRadioPosition: point(21px, 16px); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 5887ac5a30b20e..8205e54576ac15 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -109,6 +109,9 @@ PRIVATE info/userpic/info_userpic_emoji_builder_layer.cpp info/userpic/info_userpic_emoji_builder_layer.h + info/boosts/giveaway/giveaway_type_row.cpp + info/boosts/giveaway/giveaway_type_row.h + layout/abstract_layout_item.cpp layout/abstract_layout_item.h layout/layout_mosaic.cpp From e67d2e5db2a4b95a83ab07b83389519a5cec2a63 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Oct 2023 14:41:16 +0300 Subject: [PATCH 15/70] Added API support for premium giftcode options. --- Telegram/SourceFiles/api/api_premium.cpp | 94 ++++++++++++++++++- Telegram/SourceFiles/api/api_premium.h | 21 +++++ Telegram/SourceFiles/api/api_premium_option.h | 5 +- 3 files changed, 115 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 320f3f94a16ed9..e03b15b52ee091 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -9,12 +9,13 @@ For license and copyright information please follow this link: #include "api/api_premium_option.h" #include "api/api_text_entities.h" -#include "main/main_session.h" -#include "data/data_peer_values.h" +#include "apiwrap.h" #include "data/data_document.h" -#include "data/data_session.h" #include "data/data_peer.h" -#include "apiwrap.h" +#include "data/data_peer_values.h" +#include "data/data_session.h" +#include "main/main_session.h" +#include "ui/text/format_values.h" namespace Api { namespace { @@ -31,6 +32,24 @@ namespace { }; } +[[nodiscard]] Data::SubscriptionOptions GiftCodesFromTL( + const QVector &tlOptions) { + auto options = SubscriptionOptionsFromTL(tlOptions); + for (auto i = 0; i < options.size(); i++) { + const auto &tlOption = tlOptions[i].data(); + const auto perUserText = Ui::FillAmountAndCurrency( + tlOption.vamount().v / float64(tlOption.vusers().v), + qs(tlOption.vcurrency()), + false); + options[i].costPerMonth = perUserText + + ' ' + + QChar(0x00D7) + + ' ' + + QString::number(tlOption.vusers().v); + } + return options; +} + } // namespace Premium::Premium(not_null api) @@ -311,4 +330,71 @@ const Data::SubscriptionOptions &Premium::subscriptionOptions() const { return _subscriptionOptions; } +PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null peer) +: _peer(peer) +, _api(&peer->session().api().instance()) { +} + +rpl::producer PremiumGiftCodeOptions::request() { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + const auto channel = _peer->asChannel(); + if (!channel) { + return lifetime; + } + + using TLOption = MTPPremiumGiftCodeOption; + _api.request(MTPpayments_GetPremiumGiftCodeOptions( + MTP_flags( + MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer), + _peer->input + )).done([=](const MTPVector &result) { + auto tlMapOptions = base::flat_map>(); + for (const auto &tlOption : result.v) { + const auto &data = tlOption.data(); + tlMapOptions[data.vusers().v].push_back(tlOption); + } + for (const auto &[amount, tlOptions] : tlMapOptions) { + if (amount == 1) { + _optionsForOnePerson.currency = qs( + tlOptions.front().data().vcurrency()); + for (const auto &option : tlOptions) { + _optionsForOnePerson.months.push_back( + option.data().vmonths().v); + _optionsForOnePerson.totalCosts.push_back( + option.data().vamount().v); + } + } + _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); + } + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + + return lifetime; + }; +} + +Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) { + const auto it = _subscriptionOptions.find(amount); + if (it != end(_subscriptionOptions)) { + return it->second; + } else { + auto tlOptions = QVector(); + for (auto i = 0; i < _optionsForOnePerson.months.size(); i++) { + tlOptions.push_back(MTP_premiumGiftCodeOption( + MTP_flags(MTPDpremiumGiftCodeOption::Flags(0)), + MTP_int(amount), + MTP_int(_optionsForOnePerson.months[i]), + MTPstring(), + MTPint(), + MTP_string(_optionsForOnePerson.currency), + MTP_long(_optionsForOnePerson.totalCosts[i] * amount))); + } + _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); + return _subscriptionOptions[amount]; + } +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 01ed83cc7b3a67..95c0dfad142529 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -141,4 +141,25 @@ class Premium final { }; +class PremiumGiftCodeOptions final { +public: + PremiumGiftCodeOptions(not_null peer); + + [[nodiscard]] rpl::producer request(); + [[nodiscard]] Data::SubscriptionOptions options(int amount); + +private: + const not_null _peer; + using Amount = int; + base::flat_map _subscriptionOptions; + struct { + std::vector months; + std::vector totalCosts; + QString currency; + } _optionsForOnePerson; + + MTP::Sender _api; + +}; + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium_option.h b/Telegram/SourceFiles/api/api_premium_option.h index c23eb787647cd2..5758a8cb8f9dbe 100644 --- a/Telegram/SourceFiles/api/api_premium_option.h +++ b/Telegram/SourceFiles/api/api_premium_option.h @@ -36,7 +36,10 @@ template result.reserve(tlOptions.size()); for (const auto &tlOption : tlOptions) { const auto &option = tlOption.data(); - const auto botUrl = qs(option.vbot_url()); + auto botUrl = QString(); + if constexpr (!std::is_same_v) { + botUrl = qs(option.vbot_url()); + } const auto months = option.vmonths().v; const auto amount = option.vamount().v; const auto currency = qs(option.vcurrency()); From aa6edea33a475a80e456157c00904eadd23273be Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Oct 2023 14:41:57 +0300 Subject: [PATCH 16/70] Added list of giftcode options to giveaway box. --- .../info/boosts/create_giveaway_box.cpp | 55 ++++++++++++++++++- Telegram/SourceFiles/ui/effects/premium.style | 4 ++ .../ui/effects/premium_graphics.cpp | 1 + 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 3daff65434703c..8d6987378ae8be 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -7,16 +7,21 @@ For license and copyright information please follow this link: */ #include "info/boosts/create_giveaway_box.h" +#include "api/api_premium.h" #include "boxes/peers/edit_participants_box.h" // ParticipantsBoxController #include "data/data_peer.h" +#include "data/data_subscription_option.h" #include "data/data_user.h" #include "info/boosts/giveaway/giveaway_type_row.h" #include "info/info_controller.h" #include "lang/lang_keys.h" +#include "settings/settings_common.h" +#include "ui/effects/premium_graphics.h" #include "ui/layers/generic_box.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "styles/style_layers.h" +#include "styles/style_premium.h" namespace { @@ -59,10 +64,16 @@ void CreateGiveawayBox( not_null controller, not_null peer) { struct State final { + State(not_null p) : apiOptions(p) { + } + + Api::PremiumGiftCodeOptions apiOptions; + rpl::lifetime lifetimeApi; + std::vector> selectedToAward; rpl::event_stream<> toAwardAmountChanged; }; - const auto state = box->lifetime().make_state(); + const auto state = box->lifetime().make_state(peer); using GiveawayType = Giveaway::GiveawayTypeRow::Type; using GiveawayGroup = Ui::RadioenumGroup; const auto typeGroup = std::make_shared(); @@ -129,6 +140,48 @@ void CreateGiveawayBox( } typeGroup->setValue(GiveawayType::Random); + Settings::AddSkip(box->verticalLayout()); + Settings::AddDivider(box->verticalLayout()); + Settings::AddSkip(box->verticalLayout()); + + { + const auto listOptions = box->verticalLayout()->add( + object_ptr(box)); + const auto durationGroup = std::make_shared(0); + const auto rebuildListOptions = [=](int amountUsers) { + while (listOptions->count()) { + delete listOptions->widgetAt(0); + } + Ui::Premium::AddGiftOptions( + listOptions, + durationGroup, + state->apiOptions.options(amountUsers), + st::giveawayGiftCodeGiftOption, + true); + listOptions->resizeToWidth(box->width()); + }; + + typeGroup->setChangedCallback([=](GiveawayType type) { + const auto rebuild = [=] { + rebuildListOptions((type == GiveawayType::SpecificUsers) + ? state->selectedToAward.size() + : 1); + }; + if (!listOptions->count()) { + state->lifetimeApi = state->apiOptions.request( + ) | rpl::start_with_error_done([=](const QString &error) { + }, rebuild); + } else { + rebuild(); + } + }); + state->lifetimeApi = state->apiOptions.request( + ) | rpl::start_with_error_done([=](const QString &error) { + }, [=] { + rebuildListOptions(1); + }); + } + box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 1891e26e00b3c2..ee1256feadb27b 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -288,6 +288,10 @@ giveawayGiftCodeLinkHeight: 42px; giveawayGiftCodeLinkCopyWidth: 40px; giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px); +giveawayGiftCodeGiftOption: PremiumOption(premiumGiftOption) { + badgeShift: point(5px, 0px); +} + boostLinkStatsButton: IconButton(defaultIconButton) { width: giveawayGiftCodeLinkCopyWidth; height: giveawayGiftCodeLinkHeight; diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index 310f2291dc239d..c7c8cadbe73c9c 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -1320,6 +1320,7 @@ void AddGiftOptions( stCheckbox, std::move(radioView)); radio->setAttribute(Qt::WA_TransparentForMouseEvents); + radio->show(); { // Paint the last frame instantly for the layer animation. group->setValue(0); radio->finishAnimating(); From 5a55e850d94b4b29a616d47e6ae945b6250332cf Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Oct 2023 15:06:12 +0300 Subject: [PATCH 17/70] Added subtext to list of giftcode options in giveaway box. --- Telegram/Resources/langs/lang.strings | 2 - .../info/boosts/create_giveaway_box.cpp | 53 +++++++++++++++---- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b8e1b160be396b..75e3d31cadc352 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2100,8 +2100,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_duration_title#one" = "Duration of Premium subscription"; "lng_giveaway_duration_title#other" = "Duration of Premium subscriptions"; "lng_giveaway_duration_price" = "{price} x {amount}"; -"lng_giveaway_duration_about" = "You can review the list of features and terms of use for Telegram Premium {link}."; -"lng_giveaway_duration_about_link" = "here"; "lng_giveaway_date_select" = "Select Date and Time"; "lng_giveaway_date_confirm" = "Confirm"; "lng_giveaway_channels_select#one" = "Select up to {count} channel"; diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 8d6987378ae8be..d85cd6201d1c10 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -16,12 +16,15 @@ For license and copyright information please follow this link: #include "info/info_controller.h" #include "lang/lang_keys.h" #include "settings/settings_common.h" +#include "settings/settings_premium.h" // Settings::ShowPremium #include "ui/effects/premium_graphics.h" #include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "styles/style_layers.h" #include "styles/style_premium.h" +#include "styles/style_settings.h" namespace { @@ -63,6 +66,8 @@ void CreateGiveawayBox( not_null box, not_null controller, not_null peer) { + using GiveawayType = Giveaway::GiveawayTypeRow::Type; + using GiveawayGroup = Ui::RadioenumGroup; struct State final { State(not_null p) : apiOptions(p) { } @@ -72,10 +77,10 @@ void CreateGiveawayBox( std::vector> selectedToAward; rpl::event_stream<> toAwardAmountChanged; + + rpl::variable typeValue; }; const auto state = box->lifetime().make_state(peer); - using GiveawayType = Giveaway::GiveawayTypeRow::Type; - using GiveawayGroup = Ui::RadioenumGroup; const auto typeGroup = std::make_shared(); box->setWidth(st::boxWideWidth); @@ -88,7 +93,7 @@ void CreateGiveawayBox( tr::lng_giveaway_create_subtitle())); row->addRadio(typeGroup); row->setClickedCallback([=] { - typeGroup->setValue(GiveawayType::Random); + state->typeValue.force_assign(GiveawayType::Random); }); } { @@ -122,9 +127,10 @@ void CreateGiveawayBox( }); peersBox->boxClosing( ) | rpl::start_with_next([=] { - typeGroup->setValue(state->selectedToAward.empty() - ? GiveawayType::Random - : GiveawayType::SpecificUsers); + state->typeValue.force_assign( + state->selectedToAward.empty() + ? GiveawayType::Random + : GiveawayType::SpecificUsers); }, peersBox->lifetime()); }; @@ -138,30 +144,56 @@ void CreateGiveawayBox( Ui::LayerOption::KeepOther); }); } - typeGroup->setValue(GiveawayType::Random); Settings::AddSkip(box->verticalLayout()); Settings::AddDivider(box->verticalLayout()); Settings::AddSkip(box->verticalLayout()); + const auto durationGroup = std::make_shared(0); { const auto listOptions = box->verticalLayout()->add( object_ptr(box)); - const auto durationGroup = std::make_shared(0); const auto rebuildListOptions = [=](int amountUsers) { while (listOptions->count()) { delete listOptions->widgetAt(0); } + Settings::AddSubsectionTitle( + listOptions, + tr::lng_giveaway_duration_title( + lt_count, + rpl::single(amountUsers) | tr::to_count())); Ui::Premium::AddGiftOptions( listOptions, durationGroup, state->apiOptions.options(amountUsers), st::giveawayGiftCodeGiftOption, true); + + auto terms = object_ptr( + listOptions, + tr::lng_premium_gift_terms( + lt_link, + tr::lng_premium_gift_terms_link( + ) | rpl::map([](const QString &t) { + return Ui::Text::Link(t, 1); + }), + Ui::Text::WithEntities), + st::boxDividerLabel); + terms->setLink(1, std::make_shared([=] { + box->closeBox(); + Settings::ShowPremium(&peer->session(), QString()); + })); + listOptions->add(object_ptr( + listOptions, + std::move(terms), + st::settingsDividerLabelPadding)); + listOptions->resizeToWidth(box->width()); }; - typeGroup->setChangedCallback([=](GiveawayType type) { + state->typeValue.value( + ) | rpl::start_with_next([=](GiveawayType type) { + typeGroup->setValue(type); const auto rebuild = [=] { rebuildListOptions((type == GiveawayType::SpecificUsers) ? state->selectedToAward.size() @@ -174,13 +206,14 @@ void CreateGiveawayBox( } else { rebuild(); } - }); + }, box->lifetime()); state->lifetimeApi = state->apiOptions.request( ) | rpl::start_with_error_done([=](const QString &error) { }, [=] { rebuildListOptions(1); }); } + state->typeValue.force_assign(GiveawayType::Random); box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); From 2dcd8a964039b824f4847fd81c0087443e8a0cb5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Oct 2023 16:43:05 +0300 Subject: [PATCH 18/70] Added ability to award specific users in giveaway box. --- Telegram/SourceFiles/api/api_premium.cpp | 31 +++++++++- Telegram/SourceFiles/api/api_premium.h | 23 +++++++- .../info/boosts/create_giveaway_box.cpp | 57 +++++++++++++++++-- Telegram/SourceFiles/ui/effects/premium.style | 5 ++ 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index e03b15b52ee091..4ca994a7dfb559 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -10,11 +10,13 @@ For license and copyright information please follow this link: #include "api/api_premium_option.h" #include "api/api_text_entities.h" #include "apiwrap.h" +#include "base/random.h" #include "data/data_document.h" #include "data/data_peer.h" #include "data/data_peer_values.h" #include "data/data_session.h" #include "main/main_session.h" +#include "payments/payments_form.h" #include "ui/text/format_values.h" namespace Api { @@ -353,9 +355,16 @@ rpl::producer PremiumGiftCodeOptions::request() { for (const auto &tlOption : result.v) { const auto &data = tlOption.data(); tlMapOptions[data.vusers().v].push_back(tlOption); + + const auto token = Token{ data.vusers().v, data.vmonths().v }; + _stores[token] = Store{ + .amount = data.vamount().v, + .product = qs(data.vstore_product().value_or_empty()), + .quantity = data.vstore_quantity().value_or_empty(), + }; } for (const auto &[amount, tlOptions] : tlMapOptions) { - if (amount == 1) { + if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) { _optionsForOnePerson.currency = qs( tlOptions.front().data().vcurrency()); for (const auto &option : tlOptions) { @@ -376,6 +385,26 @@ rpl::producer PremiumGiftCodeOptions::request() { }; } +Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( + int users, + int monthsIndex) { + const auto randomId = base::RandomValue(); + const auto token = Token{ + users, + _optionsForOnePerson.months[monthsIndex], + }; + const auto &store = _stores[token]; + return Payments::InvoicePremiumGiftCode{ + .randomId = randomId, + .currency = _optionsForOnePerson.currency, + .amount = store.amount, + .storeProduct = store.product, + .storeQuantity = store.quantity, + .users = token.users, + .months = token.months, + }; +} + Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) { const auto it = _subscriptionOptions.find(amount); if (it != end(_subscriptionOptions)) { diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 95c0dfad142529..5a2205725ea46f 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -16,6 +16,10 @@ namespace Main { class Session; } // namespace Main +namespace Payments { +struct InvoicePremiumGiftCode; +} // namespace Payments + namespace Api { struct GiftCode { @@ -147,10 +151,25 @@ class PremiumGiftCodeOptions final { [[nodiscard]] rpl::producer request(); [[nodiscard]] Data::SubscriptionOptions options(int amount); + [[nodiscard]] Payments::InvoicePremiumGiftCode invoice( + int users, + int monthsIndex); private: - const not_null _peer; + struct Token final { + int users = 0; + int months = 0; + + friend inline constexpr auto operator<=>(Token, Token) = default; + + }; + struct Store final { + uint64 amount = 0; + QString product; + int quantity = 0; + }; using Amount = int; + const not_null _peer; base::flat_map _subscriptionOptions; struct { std::vector months; @@ -158,6 +177,8 @@ class PremiumGiftCodeOptions final { QString currency; } _optionsForOnePerson; + base::flat_map _stores; + MTP::Sender _api; }; diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index d85cd6201d1c10..739bb5a2842884 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -15,6 +15,8 @@ For license and copyright information please follow this link: #include "info/boosts/giveaway/giveaway_type_row.h" #include "info/info_controller.h" #include "lang/lang_keys.h" +#include "payments/payments_checkout_process.h" // Payments::CheckoutProcess +#include "payments/payments_form.h" // Payments::InvoicePremiumGiftCode #include "settings/settings_common.h" #include "settings/settings_premium.h" // Settings::ShowPremium #include "ui/effects/premium_graphics.h" @@ -79,6 +81,8 @@ void CreateGiveawayBox( rpl::event_stream<> toAwardAmountChanged; rpl::variable typeValue; + + bool confirmButtonBusy = false; }; const auto state = box->lifetime().make_state(peer); const auto typeGroup = std::make_shared(); @@ -213,9 +217,54 @@ void CreateGiveawayBox( rebuildListOptions(1); }); } + { + // TODO mini-icon. + const auto &stButton = st::premiumGiftBox; + box->setStyle(stButton); + auto button = object_ptr( + box, + state->toAwardAmountChanged.events_starting_with( + rpl::empty_value() + ) | rpl::map([=] { + return (typeGroup->value() == GiveawayType::SpecificUsers) + ? tr::lng_giveaway_award() + : tr::lng_giveaway_start(); + }) | rpl::flatten_latest(), + st::giveawayGiftCodeStartButton); + button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + button->resizeToWidth(box->width() + - stButton.buttonPadding.left() + - stButton.buttonPadding.right()); + button->setClickedCallback([=] { + if (state->confirmButtonBusy) { + return; + } + if (typeGroup->value() == GiveawayType::SpecificUsers) { + if (state->selectedToAward.empty()) { + return; + } + auto invoice = state->apiOptions.invoice( + state->selectedToAward.size(), + durationGroup->value()); + invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ + ranges::views::all( + state->selectedToAward + ) | ranges::views::transform([]( + const not_null p) { + return not_null{ p->asUser() }; + }) | ranges::to_vector, + peer->asChannel(), + }; + state->confirmButtonBusy = true; + Payments::CheckoutProcess::Start( + std::move(invoice), + crl::guard(box, [=](auto) { + state->confirmButtonBusy = false; + box->window()->setFocus(); + })); + } + }); + box->addButton(std::move(button)); + } state->typeValue.force_assign(GiveawayType::Random); - - box->addButton(tr::lng_box_ok(), [=] { - box->closeBox(); - }); } diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index ee1256feadb27b..6d1ae377212a57 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -291,6 +291,11 @@ giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px); giveawayGiftCodeGiftOption: PremiumOption(premiumGiftOption) { badgeShift: point(5px, 0px); } +giveawayGiftCodeStartButton: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + radius: 6px; +} boostLinkStatsButton: IconButton(defaultIconButton) { width: giveawayGiftCodeLinkCopyWidth; From 986d347ea4033ae4e32b0cc74d999d99fb4ca5de Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Oct 2023 18:44:36 +0300 Subject: [PATCH 19/70] Added slider for prize quantity to giveaway box. --- Telegram/Resources/langs/lang.strings | 6 +- Telegram/SourceFiles/api/api_premium.cpp | 16 +++ Telegram/SourceFiles/api/api_premium.h | 5 + .../info/boosts/create_giveaway_box.cpp | 106 +++++++++++++++++- Telegram/SourceFiles/ui/effects/premium.style | 16 +++ 5 files changed, 142 insertions(+), 7 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 75e3d31cadc352..9a0a409a02b103 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2078,9 +2078,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_award_subtitle" = "Select recipients >"; "lng_giveaway_award_chosen#one" = "{count} recipient >"; "lng_giveaway_award_chosen#other" = "{count} recipients >"; -"lng_giveaway_quantity_title" = "Quantity of prizes / boosts"; -"lng_giveaway_quantity#one" = "{count} Subscription / Boost"; -"lng_giveaway_quantity#other" = "{count} Subscriptions / Boosts"; +"lng_giveaway_quantity_title" = "Quantity of prizes"; +"lng_giveaway_quantity#one" = "{count} boost"; +"lng_giveaway_quantity#other" = "{count} boosts"; "lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive."; "lng_giveaway_channels_title" = "Channels included in the giveaway"; "lng_giveaway_channels_this#one" = "this channel will receive {count} boost"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 4ca994a7dfb559..ba63898c6c72c8 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -15,6 +15,8 @@ For license and copyright information please follow this link: #include "data/data_peer.h" #include "data/data_peer_values.h" #include "data/data_session.h" +#include "main/main_account.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "payments/payments_form.h" #include "ui/text/format_values.h" @@ -362,6 +364,9 @@ rpl::producer PremiumGiftCodeOptions::request() { .product = qs(data.vstore_product().value_or_empty()), .quantity = data.vstore_quantity().value_or_empty(), }; + if (!ranges::contains(_availablePresets, data.vusers().v)) { + _availablePresets.push_back(data.vusers().v); + } } for (const auto &[amount, tlOptions] : tlMapOptions) { if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) { @@ -385,6 +390,10 @@ rpl::producer PremiumGiftCodeOptions::request() { }; } +const std::vector &PremiumGiftCodeOptions::availablePresets() const { + return _availablePresets; +} + Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( int users, int monthsIndex) { @@ -426,4 +435,11 @@ Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) { } } +[[nodiscard]] int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const { + constexpr auto kFallbackCount = 4; + return _peer->session().account().appConfig().get( + u"giveaway_boosts_per_premium"_q, + kFallbackCount); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 5a2205725ea46f..d6141cac08ec34 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -151,10 +151,13 @@ class PremiumGiftCodeOptions final { [[nodiscard]] rpl::producer request(); [[nodiscard]] Data::SubscriptionOptions options(int amount); + [[nodiscard]] const std::vector &availablePresets() const; [[nodiscard]] Payments::InvoicePremiumGiftCode invoice( int users, int monthsIndex); + [[nodiscard]] int giveawayBoostsPerPremium() const; + private: struct Token final { int users = 0; @@ -177,6 +180,8 @@ class PremiumGiftCodeOptions final { QString currency; } _optionsForOnePerson; + std::vector _availablePresets; + base::flat_map _stores; MTP::Sender _api; diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 739bb5a2842884..ca0b7f36c5ee6b 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -23,7 +23,9 @@ For license and copyright information please follow this link: #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/widgets/checkbox.h" +#include "ui/widgets/continuous_sliders.h" #include "ui/widgets/labels.h" +#include "ui/wrap/slide_wrap.h" #include "styles/style_layers.h" #include "styles/style_premium.h" #include "styles/style_settings.h" @@ -81,6 +83,7 @@ void CreateGiveawayBox( rpl::event_stream<> toAwardAmountChanged; rpl::variable typeValue; + rpl::variable sliderValue; bool confirmButtonBusy = false; }; @@ -153,11 +156,104 @@ void CreateGiveawayBox( Settings::AddDivider(box->verticalLayout()); Settings::AddSkip(box->verticalLayout()); + const auto randomWrap = box->verticalLayout()->add( + object_ptr>( + box, + object_ptr(box))); + state->typeValue.value( + ) | rpl::start_with_next([=](GiveawayType type) { + randomWrap->toggle(type == GiveawayType::Random, anim::type::instant); + }, randomWrap->lifetime()); + + const auto sliderContainer = randomWrap->entity()->add( + object_ptr(randomWrap)); + const auto fillSliderContainer = [=] { + if (sliderContainer->count()) { + return; + } + const auto availablePresets = state->apiOptions.availablePresets(); + if (availablePresets.empty()) { + return; + } + state->sliderValue = availablePresets.front(); + const auto title = Settings::AddSubsectionTitle( + sliderContainer, + tr::lng_giveaway_quantity_title()); + const auto rightLabel = Ui::CreateChild( + sliderContainer, + st::giveawayGiftCodeQuantitySubtitle); + rightLabel->show(); + + const auto floatLabel = Ui::CreateChild( + sliderContainer, + st::giveawayGiftCodeQuantityFloat); + floatLabel->show(); + + rpl::combine( + tr::lng_giveaway_quantity( + lt_count, + state->sliderValue.value( + ) | rpl::map([=](int v) -> float64 { + return state->apiOptions.giveawayBoostsPerPremium() * v; + })), + title->positionValue(), + sliderContainer->geometryValue() + ) | rpl::start_with_next([=](QString s, const QPoint &p, QRect) { + rightLabel->setText(std::move(s)); + rightLabel->moveToRight(st::boxRowPadding.right(), p.y()); + }, rightLabel->lifetime()); + + Settings::AddSkip(sliderContainer); + Settings::AddSkip(sliderContainer); + const auto slider = sliderContainer->add( + object_ptr(sliderContainer, st::settingsScale), + st::boxRowPadding); + Settings::AddSkip(sliderContainer); + slider->resize(slider->width(), st::settingsScale.seekSize.height()); + slider->setPseudoDiscrete( + availablePresets.size(), + [=](int index) { return availablePresets[index]; }, + availablePresets.front(), + [=](int boosts) { state->sliderValue = boosts; }, + [](int) {}); + + state->sliderValue.value( + ) | rpl::start_with_next([=](int boosts) { + floatLabel->setText(QString::number(boosts)); + + const auto count = availablePresets.size(); + const auto sliderWidth = slider->width() + - st::settingsScale.seekSize.width(); + for (auto i = 0; i < count; i++) { + if ((i + 1 == count || availablePresets[i + 1] > boosts) + && availablePresets[i] <= boosts) { + const auto x = (sliderWidth * i) / (count - 1); + floatLabel->moveToLeft( + slider->x() + + x + + st::settingsScale.seekSize.width() / 2 + - floatLabel->width() / 2, + slider->y() - floatLabel->height()); + break; + } + } + }, floatLabel->lifetime()); + + Settings::AddSkip(sliderContainer); + Settings::AddDividerText( + sliderContainer, + tr::lng_giveaway_quantity_about()); + Settings::AddSkip(sliderContainer); + + sliderContainer->resizeToWidth(box->width()); + }; + const auto durationGroup = std::make_shared(0); { const auto listOptions = box->verticalLayout()->add( object_ptr(box)); const auto rebuildListOptions = [=](int amountUsers) { + fillSliderContainer(); while (listOptions->count()) { delete listOptions->widgetAt(0); } @@ -192,16 +288,18 @@ void CreateGiveawayBox( std::move(terms), st::settingsDividerLabelPadding)); - listOptions->resizeToWidth(box->width()); + box->verticalLayout()->resizeToWidth(box->width()); }; - state->typeValue.value( - ) | rpl::start_with_next([=](GiveawayType type) { + rpl::combine( + state->sliderValue.value(), + state->typeValue.value() + ) | rpl::start_with_next([=](int users, GiveawayType type) { typeGroup->setValue(type); const auto rebuild = [=] { rebuildListOptions((type == GiveawayType::SpecificUsers) ? state->selectedToAward.size() - : 1); + : users); }; if (!listOptions->count()) { state->lifetimeApi = state->apiOptions.request( diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 6d1ae377212a57..5226615174ba2c 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -296,6 +296,22 @@ giveawayGiftCodeStartButton: RoundButton(defaultActiveButton) { textTop: 12px; radius: 6px; } +giveawayGiftCodeQuantitySubtitle: FlatLabel(defaultFlatLabel) { + style: TextStyle(semiboldTextStyle) { + font: font(boxFontSize semibold); + } + textFg: windowActiveTextFg; + minWidth: 240px; + align: align(right); +} +giveawayGiftCodeQuantityFloat: FlatLabel(defaultFlatLabel) { + style: TextStyle(semiboldTextStyle) { + font: font(13px); + } + textFg: windowActiveTextFg; + minWidth: 50px; + align: align(center); +} boostLinkStatsButton: IconButton(defaultIconButton) { width: giveawayGiftCodeLinkCopyWidth; From af03660cab318e0f35fca36a2df497bf491dada1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 31 Oct 2023 17:24:50 +0300 Subject: [PATCH 20/70] Added ability to choose date for giveaway to giveaway box. --- .../info/boosts/create_giveaway_box.cpp | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index ca0b7f36c5ee6b..d149cafe86f665 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #include "info/boosts/create_giveaway_box.h" #include "api/api_premium.h" +#include "base/unixtime.h" #include "boxes/peers/edit_participants_box.h" // ParticipantsBoxController #include "data/data_peer.h" #include "data/data_subscription_option.h" @@ -19,8 +20,10 @@ For license and copyright information please follow this link: #include "payments/payments_form.h" // Payments::InvoicePremiumGiftCode #include "settings/settings_common.h" #include "settings/settings_premium.h" // Settings::ShowPremium +#include "ui/boxes/choose_date_time.h" #include "ui/effects/premium_graphics.h" #include "ui/layers/generic_box.h" +#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/continuous_sliders.h" @@ -32,6 +35,17 @@ For license and copyright information please follow this link: namespace { +[[nodiscard]] QDateTime ThreeDaysAfterToday() { + auto dateNow = QDateTime::currentDateTime(); + dateNow = dateNow.addDays(3); + auto timeNow = dateNow.time(); + while (timeNow.minute() % 5) { + timeNow = timeNow.addSecs(60); + } + dateNow.setTime(timeNow); + return dateNow; +} + class MembersListController : public ParticipantsBoxController { public: using ParticipantsBoxController::ParticipantsBoxController; @@ -84,6 +98,7 @@ void CreateGiveawayBox( rpl::variable typeValue; rpl::variable sliderValue; + rpl::variable dateValue; bool confirmButtonBusy = false; }; @@ -248,6 +263,51 @@ void CreateGiveawayBox( sliderContainer->resizeToWidth(box->width()); }; + { + const auto dateContainer = randomWrap->entity()->add( + object_ptr(randomWrap)); + Settings::AddSubsectionTitle( + dateContainer, + tr::lng_giveaway_date_title()); + + state->dateValue = ThreeDaysAfterToday().toSecsSinceEpoch(); + const auto button = Settings::AddButtonWithLabel( + dateContainer, + tr::lng_giveaway_date(), + state->dateValue.value() | rpl::map( + base::unixtime::parse + ) | rpl::map(Ui::FormatDateTime), + st::defaultSettingsButton); + + button->setClickedCallback([=] { + constexpr auto kSevenDays = 3600 * 24 * 7; + box->uiShow()->showBox(Box([=](not_null b) { + Ui::ChooseDateTimeBox(b, { + .title = tr::lng_giveaway_date_select(), + .submit = tr::lng_settings_save(), + .done = [=](TimeId time) { + state->dateValue = time; + b->closeBox(); + }, + .min = QDateTime::currentSecsSinceEpoch, + .time = state->dateValue.current(), + .max = [=] { + return QDateTime::currentSecsSinceEpoch() + + kSevenDays; + }, + }); + })); + }); + + Settings::AddSkip(dateContainer); + Settings::AddDividerText( + dateContainer, + tr::lng_giveaway_date_about( + lt_count, + state->sliderValue.value() | tr::to_count())); + Settings::AddSkip(dateContainer); + } + const auto durationGroup = std::make_shared(0); { const auto listOptions = box->verticalLayout()->add( From 2d097ca9ae54c64a0c8569cf4636c997e5275093 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 31 Oct 2023 20:00:30 +0300 Subject: [PATCH 21/70] Added box for selecting countries. --- .../boosts/giveaway/select_countries_box.cpp | 227 ++++++++++++++++++ .../boosts/giveaway/select_countries_box.h | 20 ++ .../SourceFiles/statistics/statistics.style | 5 + Telegram/cmake/td_ui.cmake | 2 + 4 files changed, 254 insertions(+) create mode 100644 Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp create mode 100644 Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.h diff --git a/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp new file mode 100644 index 00000000000000..9c3968bb3b2c9b --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp @@ -0,0 +1,227 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "info/boosts/giveaway/select_countries_box.h" + +#include "countries/countries_instance.h" +#include "lang/lang_keys.h" +#include "ui/emoji_config.h" +#include "ui/layers/generic_box.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/multi_select.h" +#include "ui/wrap/slide_wrap.h" +#include "styles/style_boxes.h" +#include "styles/style_giveaway.h" +#include "styles/style_settings.h" + +namespace Ui { +namespace { + +void AddSkip(not_null container) { + container->add(object_ptr( + container, + st::settingsSectionSkip)); +} + +[[nodiscard]] QImage CacheFlagEmoji(const QString &flag) { + const auto &st = st::giveawayGiftCodeCountrySelect.item; + auto roundPaintCache = QImage( + Size(st.height) * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + roundPaintCache.setDevicePixelRatio(style::DevicePixelRatio()); + roundPaintCache.fill(Qt::transparent); + { + const auto size = st.height; + auto p = Painter(&roundPaintCache); + auto hq = PainterHighQualityEnabler(p); + const auto flagText = Ui::Text::String(st::defaultTextStyle, flag); + p.setPen(st.textBg); + p.setBrush(st.textBg); + p.drawEllipse(0, 0, size, size); + flagText.draw(p, { + .position = QPoint( + 0 + (size - flagText.maxWidth()) / 2, + 0 + (size - flagText.minHeight()) / 2), + .outerWidth = size, + .availableWidth = size, + }); + } + return roundPaintCache; +} + +} // namespace + +void SelectCountriesBox( + not_null box, + const std::vector &selected, + Fn)> doneCallback, + Fn checkErrorCallback) { + struct State final { + std::vector resultList; + }; + const auto state = box->lifetime().make_state(); + + const auto multiSelect = box->setPinnedToTopContent( + object_ptr( + box, + st::giveawayGiftCodeCountrySelect, + tr::lng_participant_filter())); + AddSkip(box->verticalLayout()); + const auto &buttonSt = st::giveawayGiftCodeCountryButton; + + struct Entry final { + Ui::SlideWrap *wrap = nullptr; + QStringList list; + QString iso2; + }; + + auto countries = Countries::Instance().list(); + ranges::sort(countries, []( + const Countries::Info &a, + const Countries::Info &b) { + return (a.name.compare(b.name, Qt::CaseInsensitive) < 0); + }); + auto buttons = std::vector(); + buttons.reserve(countries.size()); + for (const auto &country : countries) { + const auto flag = Countries::Instance().flagEmojiByISO2(country.iso2); + const auto asd = Ui::Emoji::Find(flag); + if (!Ui::Emoji::Find(flag)) { + continue; + } + const auto itemId = buttons.size(); + auto button = object_ptr( + box->verticalLayout(), + rpl::single(flag + ' ' + country.name), + buttonSt); + const auto radio = Ui::CreateChild(button.data()); + const auto radioView = std::make_shared( + st::defaultRadio, + false, + [=] { radio->update(); }); + + { + const auto radioSize = radioView->getSize(); + radio->resize(radioSize); + radio->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + auto p = QPainter(radio); + radioView->paint(p, 0, 0, radioSize.width()); + }, radio->lifetime()); + const auto buttonHeight = buttonSt.height + + rect::m::sum::v(buttonSt.padding); + radio->moveToLeft( + st::giveawayRadioPosition.x(), + (buttonHeight - radioSize.height()) / 2); + } + + const auto roundPaintCache = CacheFlagEmoji(flag); + const auto paintCallback = [=](Painter &p, int x, int y, int, int) { + p.drawImage(x, y, roundPaintCache); + }; + const auto choose = [=](bool clicked) { + const auto value = !radioView->checked(); + if (value && checkErrorCallback(state->resultList.size())) { + return; + } + radioView->setChecked(value, anim::type::normal); + + if (value) { + state->resultList.push_back(country.iso2); + multiSelect->addItem( + itemId, + country.name, + st::activeButtonBg, + paintCallback, + clicked + ? Ui::MultiSelect::AddItemWay::Default + : Ui::MultiSelect::AddItemWay::SkipAnimation); + } else { + auto &list = state->resultList; + list.erase(ranges::remove(list, country.iso2), end(list)); + multiSelect->removeItem(itemId); + } + }; + button->setClickedCallback([=] { + choose(true); + }); + if (ranges::contains(selected, country.iso2)) { + choose(false); + } + + const auto wrap = box->verticalLayout()->add( + object_ptr>( + box, + std::move(button))); + wrap->toggle(true, anim::type::instant); + + { + auto list = QStringList{ + flag, + country.name, + country.alternativeName, + }; + buttons.push_back({ wrap, std::move(list), country.iso2 }); + } + } + + const auto noResults = box->addRow( + object_ptr>( + box, + object_ptr(box))); + { + noResults->toggle(false, anim::type::instant); + const auto container = noResults->entity(); + AddSkip(container); + AddSkip(container); + container->add( + object_ptr>( + container, + object_ptr( + container, + tr::lng_search_messages_none(), + st::membersAbout))); + AddSkip(container); + AddSkip(container); + } + + multiSelect->setQueryChangedCallback([=](const QString &query) { + auto wasAnyFound = false; + for (const auto &entry : buttons) { + const auto found = ranges::any_of(entry.list, [&]( + const QString &s) { + return s.startsWith(query, Qt::CaseInsensitive); + }); + entry.wrap->toggle(found, anim::type::instant); + wasAnyFound |= found; + } + noResults->toggle(!wasAnyFound, anim::type::instant); + }); + multiSelect->setItemRemovedCallback([=](uint64 itemId) { + auto &list = state->resultList; + auto &button = buttons[itemId]; + const auto it = ranges::find(list, button.iso2); + if (it != end(list)) { + list.erase(it); + button.wrap->entity()->clicked({}, Qt::LeftButton); + } + }); + + box->addButton(tr::lng_settings_save(), [=] { + doneCallback(state->resultList); + box->closeBox(); + }); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.h b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.h new file mode 100644 index 00000000000000..0d0fb91a0315f2 --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.h @@ -0,0 +1,20 @@ +/* +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 Ui { + +class GenericBox; + +void SelectCountriesBox( + not_null box, + const std::vector &selected, + Fn)> doneCallback, + Fn checkErrorCallback); + +} // namespace Ui diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index 9f7abbcddf0606..449c22293ec8ca 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -160,3 +160,8 @@ giveawayUserpic: icon {{ "boosts/filled_gift", windowFgActive }}; giveawayUserpicSkip: 1px; giveawayUserpicGroup: icon {{ "limits/groups", windowFgActive }}; giveawayRadioPosition: point(21px, 16px); + +giveawayGiftCodeCountryButton: SettingsButton(reportReasonButton) { +} +giveawayGiftCodeCountrySelect: MultiSelect(defaultMultiSelect) { +} diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 8205e54576ac15..e0d6fa4f5ee67a 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -111,6 +111,8 @@ PRIVATE info/boosts/giveaway/giveaway_type_row.cpp info/boosts/giveaway/giveaway_type_row.h + info/boosts/giveaway/select_countries_box.cpp + info/boosts/giveaway/select_countries_box.h layout/abstract_layout_item.cpp layout/abstract_layout_item.h From fc6d4d66b7b828dc81d3a7702187d2b034795c3d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 31 Oct 2023 20:43:07 +0300 Subject: [PATCH 22/70] Added ability to filter users by countries for giveaway to giveaway box. --- Telegram/Resources/langs/lang.strings | 10 ++- Telegram/SourceFiles/api/api_premium.cpp | 7 ++ Telegram/SourceFiles/api/api_premium.h | 1 + .../info/boosts/create_giveaway_box.cpp | 88 +++++++++++++++++++ .../boosts/giveaway/giveaway_type_row.cpp | 28 +++--- .../info/boosts/giveaway/giveaway_type_row.h | 5 +- 6 files changed, 127 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9a0a409a02b103..f1a46c481590ff 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2089,8 +2089,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway."; "lng_giveaway_users_title" = "Users eligible for the giveaway"; "lng_giveaway_users_all" = "All subscribers"; +"lng_giveaway_users_from_all_countries" = "from all countries"; +"lng_giveaway_users_from_one_country" = "from {country}"; +"lng_giveaway_users_from_countries#one" = "from {count} country"; +"lng_giveaway_users_from_countries#other" = "from {count} countries"; "lng_giveaway_users_new" = "Only new subscribers"; -"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to the newly joined subscribers."; +"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started or to users from specific countries."; "lng_giveaway_start" = "Start Giveaway"; "lng_giveaway_award" = "Gift Premium"; "lng_giveaway_date_title" = "Date when giveaway ends"; @@ -2106,6 +2110,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_channels_select#other" = "Select up to {count} channels"; "lng_giveaway_recipients_save" = "Save Recipients"; "lng_giveaway_recipients_deselect" = "Deselect All"; +"lng_giveaway_maximum_countries_error#one" = "You can select maximum {count} country."; +"lng_giveaway_maximum_countries_error#other" = "You can select maximum {count} countries."; +"lng_giveaway_maximum_channels_error#one" = "You can select maximum {count} channel."; +"lng_giveaway_maximum_channels_error#other" = "You can select maximum {count} channels."; "lng_prize_title" = "Congratulations!"; "lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index ba63898c6c72c8..185e2728cce387 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -442,4 +442,11 @@ Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) { kFallbackCount); } +[[nodiscard]] int PremiumGiftCodeOptions::giveawayCountriesMax() const { + constexpr auto kFallbackCount = 10; + return _peer->session().account().appConfig().get( + u"giveaway_countries_max"_q, + kFallbackCount); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index d6141cac08ec34..cfc7c6ec18d5ee 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -157,6 +157,7 @@ class PremiumGiftCodeOptions final { int monthsIndex); [[nodiscard]] int giveawayBoostsPerPremium() const; + [[nodiscard]] int giveawayCountriesMax() const; private: struct Token final { diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index d149cafe86f665..0e8b26133d6be9 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -8,12 +8,14 @@ For license and copyright information please follow this link: #include "info/boosts/create_giveaway_box.h" #include "api/api_premium.h" +#include "base/call_delayed.h" #include "base/unixtime.h" #include "boxes/peers/edit_participants_box.h" // ParticipantsBoxController #include "data/data_peer.h" #include "data/data_subscription_option.h" #include "data/data_user.h" #include "info/boosts/giveaway/giveaway_type_row.h" +#include "info/boosts/giveaway/select_countries_box.h" #include "info/info_controller.h" #include "lang/lang_keys.h" #include "payments/payments_checkout_process.h" // Payments::CheckoutProcess @@ -25,6 +27,7 @@ For license and copyright information please follow this link: #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/labels.h" @@ -99,6 +102,7 @@ void CreateGiveawayBox( rpl::variable typeValue; rpl::variable sliderValue; rpl::variable dateValue; + rpl::variable> countriesValue; bool confirmButtonBusy = false; }; @@ -308,6 +312,90 @@ void CreateGiveawayBox( Settings::AddSkip(dateContainer); } + const auto membersGroup = std::make_shared(); + { + const auto countriesContainer = randomWrap->entity()->add( + object_ptr(randomWrap)); + Settings::AddSubsectionTitle( + countriesContainer, + tr::lng_giveaway_users_title()); + + membersGroup->setValue(GiveawayType::AllMembers); + auto subtitle = state->countriesValue.value( + ) | rpl::map([=](const std::vector &list) { + return list.empty() + ? tr::lng_giveaway_users_from_all_countries() + : (list.size() == 1) + ? tr::lng_giveaway_users_from_one_country( + lt_country, + rpl::single(Countries::Instance().countryNameByISO2( + list.front()))) + : tr::lng_giveaway_users_from_countries( + lt_count, + rpl::single(list.size()) | tr::to_count()); + }) | rpl::flatten_latest(); + + const auto showBox = [=] { + auto done = [=](std::vector list) { + state->countriesValue = std::move(list); + }; + auto error = [=](int count) { + const auto max = state->apiOptions.giveawayCountriesMax(); + const auto error = (count >= max); + if (error) { + Ui::Toast::Show(tr::lng_giveaway_maximum_countries_error( + tr::now, + lt_count, + max)); + } + return error; + }; + + box->uiShow()->showBox(Box( + Ui::SelectCountriesBox, + state->countriesValue.current(), + std::move(done), + std::move(error))); + }; + + const auto createCallback = [=](GiveawayType type) { + return [=] { + const auto was = membersGroup->value(); + membersGroup->setValue(type); + const auto now = membersGroup->value(); + if (was == now) { + base::call_delayed( + st::defaultRippleAnimation.hideDuration, + box, + showBox); + } + }; + }; + + { + const auto row = countriesContainer->add( + object_ptr( + box, + GiveawayType::AllMembers, + rpl::duplicate(subtitle))); + row->addRadio(membersGroup); + row->setClickedCallback(createCallback(GiveawayType::AllMembers)); + } + const auto row = countriesContainer->add( + object_ptr( + box, + GiveawayType::OnlyNewMembers, + std::move(subtitle))); + row->addRadio(membersGroup); + row->setClickedCallback(createCallback(GiveawayType::OnlyNewMembers)); + + Settings::AddSkip(countriesContainer); + Settings::AddDividerText( + countriesContainer, + tr::lng_giveaway_users_about()); + Settings::AddSkip(countriesContainer); + } + const auto durationGroup = std::make_shared(0); { const auto listOptions = box->verticalLayout()->add( diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp index 1779b91e112a8d..25a71a00a75e4a 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp @@ -25,8 +25,10 @@ GiveawayTypeRow::GiveawayTypeRow( Type type, rpl::producer subtitle) : RippleButton(parent, st::defaultRippleAnimation) -, _st(st::giveawayTypeListItem) , _type(type) +, _st((_type == Type::SpecificUsers || _type == Type::Random) + ? st::giveawayTypeListItem + : st::defaultPeerListItem) , _userpic( Ui::EmptyUserpic::UserpicColor((_type == Type::SpecificUsers) ? kColorIndexSpecific @@ -36,7 +38,11 @@ GiveawayTypeRow::GiveawayTypeRow( _st.nameStyle, (type == Type::SpecificUsers) ? tr::lng_giveaway_award_option(tr::now) - : tr::lng_giveaway_create_option(tr::now), + : (type == Type::Random) + ? tr::lng_giveaway_create_option(tr::now) + : (type == Type::AllMembers) + ? tr::lng_giveaway_users_all(tr::now) + : tr::lng_giveaway_users_new(tr::now), Ui::NameTextOptions()) { std::move( subtitle @@ -56,18 +62,20 @@ void GiveawayTypeRow::paintEvent(QPaintEvent *e) { const auto skipRight = _st.photoPosition.x(); const auto outerWidth = width(); const auto isSpecific = (_type == Type::SpecificUsers); + const auto hasUserpic = (_type == Type::Random) || isSpecific; if (paintOver) { p.fillRect(e->rect(), _st.button.textBgOver); } Ui::RippleButton::paintRipple(p, 0, 0); - _userpic.paintCircle( - p, - _st.photoPosition.x(), - _st.photoPosition.y(), - outerWidth, - _st.photoSize); - { + if (hasUserpic) { + _userpic.paintCircle( + p, + _st.photoPosition.x(), + _st.photoPosition.y(), + outerWidth, + _st.photoSize); + const auto &userpic = isSpecific ? st::giveawayUserpicGroup : st::giveawayUserpic; @@ -91,7 +99,7 @@ void GiveawayTypeRow::paintEvent(QPaintEvent *e) { const auto statusy = _st.statusPosition.y(); const auto statusw = outerWidth - statusx - skipRight; p.setFont(st::contactsStatusFont); - p.setPen(isSpecific ? st::lightButtonFg : _st.statusFg); + p.setPen((isSpecific || !hasUserpic) ? st::lightButtonFg : _st.statusFg); _status.drawLeftElided(p, statusx, statusy, statusw, outerWidth); } diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h index 440b037f66edfb..0167b338e89fe8 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h @@ -22,6 +22,9 @@ class GiveawayTypeRow final : public Ui::RippleButton { enum class Type { Random, SpecificUsers, + + AllMembers, + OnlyNewMembers, }; GiveawayTypeRow( @@ -37,8 +40,8 @@ class GiveawayTypeRow final : public Ui::RippleButton { int resizeGetHeight(int) override; private: - const style::PeerListItem _st; const Type _type; + const style::PeerListItem _st; Ui::EmptyUserpic _userpic; Ui::Text::String _status; From 3ecf3f7c5566656553b194a61cc4326316e0b04f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 31 Oct 2023 23:20:23 +0300 Subject: [PATCH 23/70] Moved out peer list controller for giveaway box to separated file. --- Telegram/CMakeLists.txt | 2 + .../info/boosts/create_giveaway_box.cpp | 41 ++----------------- .../giveaway/giveaway_list_controllers.cpp | 41 +++++++++++++++++++ .../giveaway/giveaway_list_controllers.h | 40 ++++++++++++++++++ 4 files changed, 86 insertions(+), 38 deletions(-) create mode 100644 Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp create mode 100644 Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 1cb4309aacf94c..00f59723146d23 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -824,6 +824,8 @@ PRIVATE history/history_view_highlight_manager.h history/history_widget.cpp history/history_widget.h + info/boosts/giveaway/giveaway_list_controllers.cpp + info/boosts/giveaway/giveaway_list_controllers.h info/boosts/create_giveaway_box.cpp info/boosts/create_giveaway_box.h info/boosts/info_boosts_inner_widget.cpp diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 0e8b26133d6be9..626a01b789198c 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -10,10 +10,8 @@ For license and copyright information please follow this link: #include "api/api_premium.h" #include "base/call_delayed.h" #include "base/unixtime.h" -#include "boxes/peers/edit_participants_box.h" // ParticipantsBoxController #include "data/data_peer.h" -#include "data/data_subscription_option.h" -#include "data/data_user.h" +#include "info/boosts/giveaway/giveaway_list_controllers.h" #include "info/boosts/giveaway/giveaway_type_row.h" #include "info/boosts/giveaway/select_countries_box.h" #include "info/info_controller.h" @@ -49,38 +47,6 @@ namespace { return dateNow; } -class MembersListController : public ParticipantsBoxController { -public: - using ParticipantsBoxController::ParticipantsBoxController; - - void rowClicked(not_null row) override; - std::unique_ptr createRow( - not_null participant) const override; - base::unique_qptr rowContextMenu( - QWidget *parent, - not_null row) override; - -}; - -void MembersListController::rowClicked(not_null row) { - delegate()->peerListSetRowChecked(row, !row->checked()); -} - -std::unique_ptr MembersListController::createRow( - not_null participant) const { - const auto user = participant->asUser(); - if (!user || user->isInaccessible() || user->isBot() || user->isSelf()) { - return nullptr; - } - return std::make_unique(participant); -} - -base::unique_qptr MembersListController::rowContextMenu( - QWidget *parent, - not_null row) { - return nullptr; -} - } // namespace void CreateGiveawayBox( @@ -162,10 +128,9 @@ void CreateGiveawayBox( box->uiShow()->showBox( Box( - std::make_unique( + std::make_unique( controller, - peer, - ParticipantsRole::Members), + peer), std::move(initBox)), Ui::LayerOption::KeepOther); }); diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp new file mode 100644 index 00000000000000..e9b7e50fea42a1 --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp @@ -0,0 +1,41 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "info/boosts/giveaway/giveaway_list_controllers.h" + +#include "data/data_peer.h" +#include "data/data_user.h" +#include "lang/lang_keys.h" + +namespace Giveaway { + +AwardMembersListController::AwardMembersListController( + not_null navigation, + not_null peer) +: ParticipantsBoxController(navigation, peer, ParticipantsRole::Members) { +} + +void AwardMembersListController::rowClicked(not_null row) { + delegate()->peerListSetRowChecked(row, !row->checked()); +} + +std::unique_ptr AwardMembersListController::createRow( + not_null participant) const { + const auto user = participant->asUser(); + if (!user || user->isInaccessible() || user->isBot() || user->isSelf()) { + return nullptr; + } + return std::make_unique(participant); +} + +base::unique_qptr AwardMembersListController::rowContextMenu( + QWidget *parent, + not_null row) { + return nullptr; +} + +} // namespace Giveaway diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h new file mode 100644 index 00000000000000..404b6543324b8f --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h @@ -0,0 +1,40 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "boxes/peers/edit_participants_box.h" + +class PeerData; +class PeerListRow; + +namespace Ui { +class PopupMenu; +} // namespace Ui + +namespace Window { +class SessionNavigation; +} // namespace Window + +namespace Giveaway { + +class AwardMembersListController : public ParticipantsBoxController { +public: + AwardMembersListController( + not_null navigation, + not_null peer); + + void rowClicked(not_null row) override; + std::unique_ptr createRow( + not_null participant) const override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + +}; + +} // namespace Giveaway From 3c8188e0b46b22a7a1f85157186c4f4dc0ac7ce0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 31 Oct 2023 23:37:06 +0300 Subject: [PATCH 24/70] Added channel list controller to giveaway box for channel selecting. --- Telegram/Resources/langs/lang.strings | 2 + .../giveaway/giveaway_list_controllers.cpp | 112 ++++++++++++++++++ .../giveaway/giveaway_list_controllers.h | 30 +++++ 3 files changed, 144 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f1a46c481590ff..0b9f4c33497b43 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2114,6 +2114,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_maximum_countries_error#other" = "You can select maximum {count} countries."; "lng_giveaway_maximum_channels_error#one" = "You can select maximum {count} channel."; "lng_giveaway_maximum_channels_error#other" = "You can select maximum {count} channels."; +"lng_giveaway_channels_confirm_title" = "Channel is Private"; +"lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link."; "lng_prize_title" = "Congratulations!"; "lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp index e9b7e50fea42a1..8161553c52b987 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp @@ -7,8 +7,13 @@ For license and copyright information please follow this link: */ #include "info/boosts/giveaway/giveaway_list_controllers.h" +#include "apiwrap.h" +#include "data/data_channel.h" #include "data/data_peer.h" +#include "data/data_session.h" #include "data/data_user.h" +#include "main/main_session.h" +#include "ui/boxes/confirm_box.h" #include "lang/lang_keys.h" namespace Giveaway { @@ -38,4 +43,111 @@ base::unique_qptr AwardMembersListController::rowContextMenu( return nullptr; } +MyChannelsListController::MyChannelsListController( + not_null peer, + std::shared_ptr show, + std::vector> selected) +: PeerListController( + std::make_unique(&peer->session())) +, _peer(peer) +, _show(show) +, _selected(std::move(selected)) { +} + +std::unique_ptr MyChannelsListController::createSearchRow( + not_null peer) { + if (const auto channel = peer->asChannel()) { + return createRow(channel); + } + return nullptr; +} + +std::unique_ptr MyChannelsListController::createRestoredRow( + not_null peer) { + if (const auto channel = peer->asChannel()) { + return createRow(channel); + } + return nullptr; +} + +void MyChannelsListController::rowClicked(not_null row) { + const auto channel = row->peer()->asChannel(); + const auto checked = !row->checked(); + if (checked && channel && channel->username().isEmpty()) { + _show->showBox(Box(Ui::ConfirmBox, Ui::ConfirmBoxArgs{ + .text = tr::lng_giveaway_channels_confirm_about(), + .confirmed = [=](Fn close) { + delegate()->peerListSetRowChecked(row, checked); + close(); + }, + .confirmText = tr::lng_filters_recommended_add(), + .title = tr::lng_giveaway_channels_confirm_title(), + })); + } else { + delegate()->peerListSetRowChecked(row, checked); + } +} + +Main::Session &MyChannelsListController::session() const { + return _peer->session(); +} + +void MyChannelsListController::prepare() { + delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); + const auto api = _apiLifetime.make_state( + &session().api().instance()); + api->request( + MTPstories_GetChatsToSend() + ).done([=](const MTPmessages_Chats &result) { + _apiLifetime.destroy(); + const auto &chats = result.match([](const auto &data) { + return data.vchats().v; + }); + auto &owner = session().data(); + for (const auto &chat : chats) { + if (const auto peer = owner.processChat(chat)) { + if (!peer->isChannel() || (peer == _peer)) { + continue; + } + if (!delegate()->peerListFindRow(peer->id.value)) { + if (const auto channel = peer->asChannel()) { + auto row = createRow(channel); + const auto raw = row.get(); + delegate()->peerListAppendRow(std::move(row)); + if (ranges::contains(_selected, peer)) { + delegate()->peerListSetRowChecked(raw, true); + _selected.erase( + ranges::remove(_selected, peer), + end(_selected)); + } + } + } + } + } + for (const auto &selected : _selected) { + if (const auto channel = selected->asChannel()) { + auto row = createRow(channel); + const auto raw = row.get(); + delegate()->peerListAppendRow(std::move(row)); + delegate()->peerListSetRowChecked(raw, true); + } + } + delegate()->peerListRefreshRows(); + _selected.clear(); + }).send(); +} + +std::unique_ptr MyChannelsListController::createRow( + not_null channel) const { + if (channel->isMegagroup()) { + return nullptr; + } + auto row = std::make_unique(channel); + row->setCustomStatus(tr::lng_chat_status_subscribers( + tr::now, + lt_count, + channel->membersCount())); + return row; +} + } // namespace Giveaway diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h index 404b6543324b8f..46c0a53a3edcc9 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h @@ -14,6 +14,7 @@ class PeerListRow; namespace Ui { class PopupMenu; +class Show; } // namespace Ui namespace Window { @@ -37,4 +38,33 @@ class AwardMembersListController : public ParticipantsBoxController { }; +class MyChannelsListController : public PeerListController { +public: + MyChannelsListController( + not_null peer, + std::shared_ptr show, + std::vector> selected); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + + std::unique_ptr createSearchRow( + not_null peer) override; + std::unique_ptr createRestoredRow( + not_null peer) override; + +private: + std::unique_ptr createRow( + not_null channel) const; + + const not_null _peer; + const std::shared_ptr _show; + + std::vector> _selected; + + rpl::lifetime _apiLifetime; + +}; + } // namespace Giveaway From 33f6fc7d8ca72f719b3fe5f54141b3b439208a44 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 00:28:26 +0300 Subject: [PATCH 25/70] Added list controller to giveaway box to display selected channels. --- .../giveaway/giveaway_list_controllers.cpp | 156 +++++++++++++++++- .../giveaway/giveaway_list_controllers.h | 25 +++ .../SourceFiles/statistics/statistics.style | 3 + 3 files changed, 183 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp index 8161553c52b987..57cfe1b33d5f12 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp @@ -12,11 +12,97 @@ For license and copyright information please follow this link: #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_user.h" +#include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/boxes/confirm_box.h" -#include "lang/lang_keys.h" +#include "ui/effects/ripple_animation.h" +#include "ui/painter.h" +#include "styles/style_statistics.h" namespace Giveaway { +namespace { + +class ChannelRow final : public PeerListRow { +public: + using PeerListRow::PeerListRow; + + QSize rightActionSize() const override; + QMargins rightActionMargins() const override; + void rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) override; + + void rightActionAddRipple( + QPoint point, + Fn updateCallback) override; + void rightActionStopLastRipple() override; + +private: + std::unique_ptr _actionRipple; + +}; + +QSize ChannelRow::rightActionSize() const { + return QSize( + st::giveawayGiftCodeChannelDeleteIcon.width(), + st::giveawayGiftCodeChannelDeleteIcon.height()) * 2; +} + +QMargins ChannelRow::rightActionMargins() const { + return QMargins( + 0, + (st::defaultPeerListItem.height - rightActionSize().height()) / 2, + st::giveawayRadioPosition.x() / 2, + 0); +} + +void ChannelRow::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + if (_actionRipple) { + _actionRipple->paint( + p, + x, + y, + outerWidth); + if (_actionRipple->empty()) { + _actionRipple.reset(); + } + } + const auto rect = QRect(QPoint(x, y), ChannelRow::rightActionSize()); + (actionSelected + ? st::giveawayGiftCodeChannelDeleteIconOver + : st::giveawayGiftCodeChannelDeleteIcon).paintInCenter(p, rect); +} + +void ChannelRow::rightActionAddRipple( + QPoint point, + Fn updateCallback) { + if (!_actionRipple) { + auto mask = Ui::RippleAnimation::EllipseMask(rightActionSize()); + _actionRipple = std::make_unique( + st::defaultRippleAnimation, + std::move(mask), + std::move(updateCallback)); + } + _actionRipple->add(point); +} + +void ChannelRow::rightActionStopLastRipple() { + if (_actionRipple) { + _actionRipple->lastStop(); + } +} + +} // namespace AwardMembersListController::AwardMembersListController( not_null navigation, @@ -150,4 +236,72 @@ std::unique_ptr MyChannelsListController::createRow( return row; } +SelectedChannelsListController::SelectedChannelsListController( + not_null peer) +: _peer(peer) { +} + +void SelectedChannelsListController::setTopStatus(rpl::producer s) { + _statusLifetime = std::move( + s + ) | rpl::start_with_next([=](const QString &t) { + if (delegate()->peerListFullRowsCount() > 0) { + delegate()->peerListRowAt(0)->setCustomStatus(t); + } + }); +} + +void SelectedChannelsListController::rebuild( + std::vector> selected) { + while (delegate()->peerListFullRowsCount() > 1) { + delegate()->peerListRemoveRow(delegate()->peerListRowAt(1)); + } + for (const auto &peer : selected) { + delegate()->peerListAppendRow(createRow(peer->asChannel())); + } + delegate()->peerListRefreshRows(); +} + +auto SelectedChannelsListController::channelRemoved() const +-> rpl::producer> { + return _channelRemoved.events(); +} + +void SelectedChannelsListController::rowClicked(not_null row) { +} + +void SelectedChannelsListController::rowRightActionClicked( + not_null row) { + const auto peer = row->peer(); + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); + _channelRemoved.fire_copy(peer); +} + +Main::Session &SelectedChannelsListController::session() const { + return _peer->session(); +} + +void SelectedChannelsListController::prepare() { + delegate()->peerListAppendRow(createRow(_peer->asChannel())); +} + +std::unique_ptr SelectedChannelsListController::createRow( + not_null channel) const { + if (channel->isMegagroup()) { + return nullptr; + } + const auto isYourChannel = (_peer->asChannel() == channel); + auto row = isYourChannel + ? std::make_unique(channel) + : std::make_unique(channel); + row->setCustomStatus(isYourChannel + ? QString() + : tr::lng_chat_status_subscribers( + tr::now, + lt_count, + channel->membersCount())); + return row; +} + } // namespace Giveaway diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h index 46c0a53a3edcc9..2c1e589c20a13e 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h @@ -67,4 +67,29 @@ class MyChannelsListController : public PeerListController { }; +class SelectedChannelsListController : public PeerListController { +public: + SelectedChannelsListController(not_null peer); + + void setTopStatus(rpl::producer status); + + void rebuild(std::vector> selected); + [[nodiscard]] rpl::producer> channelRemoved() const; + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void rowRightActionClicked(not_null row) override; + +private: + std::unique_ptr createRow( + not_null channel) const; + + const not_null _peer; + + rpl::event_stream> _channelRemoved; + rpl::lifetime _statusLifetime; + +}; + } // namespace Giveaway diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index 449c22293ec8ca..ba72e5973f65d9 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -165,3 +165,6 @@ giveawayGiftCodeCountryButton: SettingsButton(reportReasonButton) { } giveawayGiftCodeCountrySelect: MultiSelect(defaultMultiSelect) { } + +giveawayGiftCodeChannelDeleteIcon: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFg }}; +giveawayGiftCodeChannelDeleteIconOver: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFgOver }}; From 5e28b2d6012af28740a6fcf67ffeddca2a98636a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 00:46:58 +0300 Subject: [PATCH 26/70] Added ability to select channels to giveaway box. --- .../info/boosts/create_giveaway_box.cpp | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 626a01b789198c..52f5c7283cab7a 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -65,6 +65,8 @@ void CreateGiveawayBox( std::vector> selectedToAward; rpl::event_stream<> toAwardAmountChanged; + std::vector> selectedToSubscribe; + rpl::variable typeValue; rpl::variable sliderValue; rpl::variable dateValue; @@ -232,6 +234,75 @@ void CreateGiveawayBox( sliderContainer->resizeToWidth(box->width()); }; + { + const auto channelsContainer = randomWrap->entity()->add( + object_ptr(randomWrap)); + Settings::AddSubsectionTitle( + channelsContainer, + tr::lng_giveaway_channels_title()); + + struct ListState final { + ListState(not_null p) : controller(p) { + } + PeerListContentDelegateSimple delegate; + Giveaway::SelectedChannelsListController controller; + }; + const auto listState = box->lifetime().make_state(peer); + + listState->delegate.setContent(channelsContainer->add( + object_ptr( + channelsContainer, + &listState->controller))); + listState->controller.setDelegate(&listState->delegate); + listState->controller.channelRemoved( + ) | rpl::start_with_next([=](not_null peer) { + auto &list = state->selectedToSubscribe; + list.erase(ranges::remove(list, peer), end(list)); + }, box->lifetime()); + listState->controller.setTopStatus(tr::lng_giveaway_channels_this( + lt_count, + state->sliderValue.value( + ) | rpl::map([=](int v) -> float64 { + return state->apiOptions.giveawayBoostsPerPremium() * v; + }))); + + using IconType = Settings::IconType; + Settings::AddButton( + channelsContainer, + tr::lng_giveaway_channels_add(), + st::settingsButtonActive, + { &st::settingsIconAdd, IconType::Round, &st::windowBgActive } + )->setClickedCallback([=] { + auto initBox = [=](not_null peersBox) { + peersBox->setTitle(tr::lng_giveaway_channels_add()); + peersBox->addButton(tr::lng_settings_save(), [=] { + const auto selected = peersBox->collectSelectedRows(); + state->selectedToSubscribe = selected; + listState->controller.rebuild(selected); + peersBox->closeBox(); + }); + peersBox->addButton(tr::lng_cancel(), [=] { + peersBox->closeBox(); + }); + }; + + box->uiShow()->showBox( + Box( + std::make_unique( + peer, + box->uiShow(), + state->selectedToSubscribe), + std::move(initBox)), + Ui::LayerOption::KeepOther); + }); + + Settings::AddSkip(channelsContainer); + Settings::AddDividerText( + channelsContainer, + tr::lng_giveaway_channels_about()); + Settings::AddSkip(channelsContainer); + } + { const auto dateContainer = randomWrap->entity()->add( object_ptr(randomWrap)); From 85fdc04d48ba930bfc4b7ac325537eb1f7cc9d4d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 01:13:03 +0300 Subject: [PATCH 27/70] Added ability to create giveaways from giveaway box. --- .../info/boosts/create_giveaway_box.cpp | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 52f5c7283cab7a..abd739e9d3e899 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -10,6 +10,7 @@ For license and copyright information please follow this link: #include "api/api_premium.h" #include "base/call_delayed.h" #include "base/unixtime.h" +#include "countries/countries_instance.h" #include "data/data_peer.h" #include "info/boosts/giveaway/giveaway_list_controllers.h" #include "info/boosts/giveaway/giveaway_type_row.h" @@ -521,13 +522,21 @@ void CreateGiveawayBox( if (state->confirmButtonBusy) { return; } - if (typeGroup->value() == GiveawayType::SpecificUsers) { + const auto type = typeGroup->value(); + const auto isSpecific = (type == GiveawayType::SpecificUsers); + const auto isRandom = (type == GiveawayType::Random); + if (!isSpecific && !isRandom) { + return; + } + auto invoice = state->apiOptions.invoice( + isSpecific + ? state->selectedToAward.size() + : state->sliderValue.current(), + durationGroup->value()); + if (isSpecific) { if (state->selectedToAward.empty()) { return; } - auto invoice = state->apiOptions.invoice( - state->selectedToAward.size(), - durationGroup->value()); invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ ranges::views::all( state->selectedToAward @@ -537,14 +546,28 @@ void CreateGiveawayBox( }) | ranges::to_vector, peer->asChannel(), }; - state->confirmButtonBusy = true; - Payments::CheckoutProcess::Start( - std::move(invoice), - crl::guard(box, [=](auto) { - state->confirmButtonBusy = false; - box->window()->setFocus(); - })); + } else if (isRandom) { + invoice.purpose = Payments::InvoicePremiumGiftCodeGiveaway{ + .boostPeer = peer->asChannel(), + .additionalChannels = ranges::views::all( + state->selectedToSubscribe + ) | ranges::views::transform([]( + const not_null p) { + return not_null{ p->asChannel() }; + }) | ranges::to_vector, + .countries = state->countriesValue.current(), + .untilDate = state->dateValue.current(), + .onlyNewSubscribers = (membersGroup->value() + == GiveawayType::OnlyNewMembers), + }; } + state->confirmButtonBusy = true; + Payments::CheckoutProcess::Start( + std::move(invoice), + crl::guard(box, [=](auto) { + state->confirmButtonBusy = false; + box->window()->setFocus(); + })); }); box->addButton(std::move(button)); } From d2d69a7a361bcbdc719dcdbefc7dba1bc9fdf8da Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 02:04:24 +0300 Subject: [PATCH 28/70] Added premium star to top of giveaway box. --- .../info/boosts/create_giveaway_box.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index abd739e9d3e899..bce9e702b2d7e1 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -23,6 +23,7 @@ For license and copyright information please follow this link: #include "settings/settings_premium.h" // Settings::ShowPremium #include "ui/boxes/choose_date_time.h" #include "ui/effects/premium_graphics.h" +#include "ui/effects/premium_top_bar.h" #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" @@ -31,6 +32,7 @@ For license and copyright information please follow this link: #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/labels.h" #include "ui/wrap/slide_wrap.h" +#include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_premium.h" #include "styles/style_settings.h" @@ -54,6 +56,35 @@ void CreateGiveawayBox( not_null box, not_null controller, not_null peer) { + { + const auto bar = box->verticalLayout()->add( + object_ptr( + box, + st::giveawayGiftCodeCover, + nullptr, + tr::lng_giveaway_new_title(), + tr::lng_giveaway_new_about(Ui::Text::RichLangValue), + true)); + bar->setMaximumHeight(st::giveawayGiftCodeTopHeight); + bar->setMinimumHeight(st::infoLayerTopBarHeight); + bar->resize(bar->width(), bar->maximumHeight()); + + Settings::AddSkip(box->verticalLayout()); + Settings::AddDivider(box->verticalLayout()); + Settings::AddSkip(box->verticalLayout()); + + const auto close = Ui::CreateChild( + box->verticalLayout().get(), + st::boxTitleClose); + close->setClickedCallback([=] { + box->closeBox(); + }); + box->widthValue( + ) | rpl::start_with_next([=](int) { + close->moveToRight(0, 0); + }, box->lifetime()); + } + using GiveawayType = Giveaway::GiveawayTypeRow::Type; using GiveawayGroup = Ui::RadioenumGroup; struct State final { From d8e38b43d94e129b6aa0178a5d8dc3b388350cbc Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 02:32:35 +0300 Subject: [PATCH 29/70] Added simple label for loading state to giveaway box. --- .../info/boosts/create_giveaway_box.cpp | 177 ++++++++++-------- Telegram/SourceFiles/ui/effects/premium.style | 2 + 2 files changed, 103 insertions(+), 76 deletions(-) diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index bce9e702b2d7e1..6304cba740dd3a 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -56,15 +56,18 @@ void CreateGiveawayBox( not_null box, not_null controller, not_null peer) { + box->setWidth(st::boxWideWidth); + + const auto bar = box->verticalLayout()->add( + object_ptr( + box, + st::giveawayGiftCodeCover, + nullptr, + tr::lng_giveaway_new_title(), + tr::lng_giveaway_new_about(Ui::Text::RichLangValue), + true)); { - const auto bar = box->verticalLayout()->add( - object_ptr( - box, - st::giveawayGiftCodeCover, - nullptr, - tr::lng_giveaway_new_title(), - tr::lng_giveaway_new_about(Ui::Text::RichLangValue), - true)); + bar->setPaused(true); bar->setMaximumHeight(st::giveawayGiftCodeTopHeight); bar->setMinimumHeight(st::infoLayerTopBarHeight); bar->resize(bar->width(), bar->maximumHeight()); @@ -109,10 +112,33 @@ void CreateGiveawayBox( const auto state = box->lifetime().make_state(peer); const auto typeGroup = std::make_shared(); - box->setWidth(st::boxWideWidth); + const auto loading = box->addRow( + object_ptr>( + box, + object_ptr(box))); + { + loading->toggle(true, anim::type::instant); + const auto container = loading->entity(); + Settings::AddSkip(container); + Settings::AddSkip(container); + container->add( + object_ptr>( + box, + object_ptr( + box, + tr::lng_contacts_loading(), + st::giveawayLoadingLabel))); + Settings::AddSkip(container); + Settings::AddSkip(container); + } + const auto contentWrap = box->verticalLayout()->add( + object_ptr>( + box, + object_ptr(box))); + contentWrap->toggle(false, anim::type::instant); { - const auto row = box->verticalLayout()->add( + const auto row = contentWrap->entity()->add( object_ptr( box, GiveawayType::Random, @@ -123,7 +149,7 @@ void CreateGiveawayBox( }); } { - const auto row = box->verticalLayout()->add( + const auto row = contentWrap->entity()->add( object_ptr( box, GiveawayType::SpecificUsers, @@ -170,13 +196,13 @@ void CreateGiveawayBox( }); } - Settings::AddSkip(box->verticalLayout()); - Settings::AddDivider(box->verticalLayout()); - Settings::AddSkip(box->verticalLayout()); + Settings::AddSkip(contentWrap->entity()); + Settings::AddDivider(contentWrap->entity()); + Settings::AddSkip(contentWrap->entity()); - const auto randomWrap = box->verticalLayout()->add( + const auto randomWrap = contentWrap->entity()->add( object_ptr>( - box, + contentWrap, object_ptr(box))); state->typeValue.value( ) | rpl::start_with_next([=](GiveawayType type) { @@ -186,9 +212,6 @@ void CreateGiveawayBox( const auto sliderContainer = randomWrap->entity()->add( object_ptr(randomWrap)); const auto fillSliderContainer = [=] { - if (sliderContainer->count()) { - return; - } const auto availablePresets = state->apiOptions.availablePresets(); if (availablePresets.empty()) { return; @@ -465,71 +488,56 @@ void CreateGiveawayBox( } const auto durationGroup = std::make_shared(0); + const auto listOptions = contentWrap->entity()->add( + object_ptr(box)); + const auto rebuildListOptions = [=](int amountUsers) { + while (listOptions->count()) { + delete listOptions->widgetAt(0); + } + Settings::AddSubsectionTitle( + listOptions, + tr::lng_giveaway_duration_title( + lt_count, + rpl::single(amountUsers) | tr::to_count())); + Ui::Premium::AddGiftOptions( + listOptions, + durationGroup, + state->apiOptions.options(amountUsers), + st::giveawayGiftCodeGiftOption, + true); + + auto terms = object_ptr( + listOptions, + tr::lng_premium_gift_terms( + lt_link, + tr::lng_premium_gift_terms_link( + ) | rpl::map([](const QString &t) { + return Ui::Text::Link(t, 1); + }), + Ui::Text::WithEntities), + st::boxDividerLabel); + terms->setLink(1, std::make_shared([=] { + box->closeBox(); + Settings::ShowPremium(&peer->session(), QString()); + })); + listOptions->add(object_ptr( + listOptions, + std::move(terms), + st::settingsDividerLabelPadding)); + + box->verticalLayout()->resizeToWidth(box->width()); + }; { - const auto listOptions = box->verticalLayout()->add( - object_ptr(box)); - const auto rebuildListOptions = [=](int amountUsers) { - fillSliderContainer(); - while (listOptions->count()) { - delete listOptions->widgetAt(0); - } - Settings::AddSubsectionTitle( - listOptions, - tr::lng_giveaway_duration_title( - lt_count, - rpl::single(amountUsers) | tr::to_count())); - Ui::Premium::AddGiftOptions( - listOptions, - durationGroup, - state->apiOptions.options(amountUsers), - st::giveawayGiftCodeGiftOption, - true); - - auto terms = object_ptr( - listOptions, - tr::lng_premium_gift_terms( - lt_link, - tr::lng_premium_gift_terms_link( - ) | rpl::map([](const QString &t) { - return Ui::Text::Link(t, 1); - }), - Ui::Text::WithEntities), - st::boxDividerLabel); - terms->setLink(1, std::make_shared([=] { - box->closeBox(); - Settings::ShowPremium(&peer->session(), QString()); - })); - listOptions->add(object_ptr( - listOptions, - std::move(terms), - st::settingsDividerLabelPadding)); - - box->verticalLayout()->resizeToWidth(box->width()); - }; rpl::combine( state->sliderValue.value(), state->typeValue.value() ) | rpl::start_with_next([=](int users, GiveawayType type) { typeGroup->setValue(type); - const auto rebuild = [=] { - rebuildListOptions((type == GiveawayType::SpecificUsers) - ? state->selectedToAward.size() - : users); - }; - if (!listOptions->count()) { - state->lifetimeApi = state->apiOptions.request( - ) | rpl::start_with_error_done([=](const QString &error) { - }, rebuild); - } else { - rebuild(); - } + rebuildListOptions((type == GiveawayType::SpecificUsers) + ? state->selectedToAward.size() + : users); }, box->lifetime()); - state->lifetimeApi = state->apiOptions.request( - ) | rpl::start_with_error_done([=](const QString &error) { - }, [=] { - rebuildListOptions(1); - }); } { // TODO mini-icon. @@ -603,4 +611,21 @@ void CreateGiveawayBox( box->addButton(std::move(button)); } state->typeValue.force_assign(GiveawayType::Random); + + box->setShowFinishedCallback([=] { + if (!loading->toggled()) { + return; + } + bar->setPaused(false); + state->lifetimeApi = state->apiOptions.request( + ) | rpl::start_with_error_done([=](const QString &error) { + }, [=] { + state->lifetimeApi.destroy(); + loading->toggle(false, anim::type::instant); + fillSliderContainer(); + rebuildListOptions(1); + contentWrap->toggle(true, anim::type::instant); + contentWrap->resizeToWidth(box->width()); + }); + }); } diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 5226615174ba2c..9d8736fdea1dcd 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -277,6 +277,8 @@ boostReplaceIconOutline: 2px; boostReplaceIconAdd: point(4px, 2px); boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }}; +giveawayLoadingLabel: FlatLabel(membersAbout) { +} giveawayGiftCodeTopHeight: 195px; giveawayGiftCodeLink: FlatLabel(defaultFlatLabel) { margin: margins(10px, 12px, 10px, 8px); From 36a91dd32bcaeb8615752eac12a2d054e0941314 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 02:47:21 +0300 Subject: [PATCH 30/70] Moved out giveaway styles to separated file. --- .../SourceFiles/boxes/gift_premium_box.cpp | 3 +- .../info/boosts/create_giveaway_box.cpp | 1 + .../info/boosts/giveaway/giveaway.style | 117 ++++++++++++++++++ Telegram/SourceFiles/ui/boxes/boost_box.cpp | 1 + Telegram/SourceFiles/ui/effects/premium.style | 107 ---------------- Telegram/cmake/td_ui.cmake | 1 + 6 files changed, 121 insertions(+), 109 deletions(-) create mode 100644 Telegram/SourceFiles/info/boosts/giveaway/giveaway.style diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 7de1b91cc8ec74..0f8aa249c705d7 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -32,7 +32,6 @@ For license and copyright information please follow this link: #include "ui/effects/premium_stars_colored.h" #include "ui/effects/premium_top_bar.h" #include "ui/layers/generic_box.h" -#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/gradient_round_button.h" @@ -41,8 +40,8 @@ For license and copyright information please follow this link: #include "window/window_peer_menu.h" // ShowChooseRecipientBox. #include "window/window_session_controller.h" #include "styles/style_boxes.h" +#include "styles/style_giveaway.h" #include "styles/style_layers.h" -#include "styles/style_chat_helpers.h" #include "styles/style_info.h" #include "styles/style_premium.h" diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 6304cba740dd3a..0d0ebc1de24a6f 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -32,6 +32,7 @@ For license and copyright information please follow this link: #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/labels.h" #include "ui/wrap/slide_wrap.h" +#include "styles/style_giveaway.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_premium.h" diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style new file mode 100644 index 00000000000000..d04b3bf6c53ed1 --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style @@ -0,0 +1,117 @@ +/* +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/effects/premium.style"; + +giveawayLoadingLabel: FlatLabel(membersAbout) { +} +giveawayGiftCodeTopHeight: 195px; +giveawayGiftCodeLink: FlatLabel(defaultFlatLabel) { + margin: margins(10px, 12px, 10px, 8px); + textFg: menuIconColor; + maxHeight: 24px; +} +giveawayGiftCodeLinkCopy: icon{{ "menu/copy", menuIconColor }}; +giveawayGiftCodeLinkHeight: 42px; +giveawayGiftCodeLinkCopyWidth: 40px; +giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px); + +giveawayGiftCodeGiftOption: PremiumOption(premiumGiftOption) { + badgeShift: point(5px, 0px); +} +giveawayGiftCodeStartButton: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + radius: 6px; +} +giveawayGiftCodeQuantitySubtitle: FlatLabel(defaultFlatLabel) { + style: TextStyle(semiboldTextStyle) { + font: font(boxFontSize semibold); + } + textFg: windowActiveTextFg; + minWidth: 240px; + align: align(right); +} +giveawayGiftCodeQuantityFloat: FlatLabel(defaultFlatLabel) { + style: TextStyle(semiboldTextStyle) { + font: font(13px); + } + textFg: windowActiveTextFg; + minWidth: 50px; + align: align(center); +} + +boostLinkStatsButton: IconButton(defaultIconButton) { + width: giveawayGiftCodeLinkCopyWidth; + height: giveawayGiftCodeLinkHeight; + icon: icon{{ "menu/stats", menuIconColor }}; + iconOver: icon{{ "menu/stats", menuIconColor }}; + ripple: emptyRippleAnimation; +} + +giveawayGiftCodeTable: Table(defaultTable) { + labelMinWidth: 91px; +} +giveawayGiftCodeTableMargin: margins(24px, 4px, 24px, 4px); +giveawayGiftCodeLabel: FlatLabel(defaultFlatLabel) { + textFg: menuIconColor; + maxHeight: 24px; + style: TextStyle(semiboldTextStyle) { + font: font(12px semibold); + } +} +giveawayGiftCodeLabelMargin: margins(13px, 10px, 13px, 10px); +giveawayGiftCodeValue: FlatLabel(defaultFlatLabel) { + maxHeight: 24px; + style: TextStyle(defaultTextStyle) { + font: font(12px); + linkUnderline: kLinkUnderlineNever; + } +} +giveawayGiftCodeValueMargin: margins(13px, 9px, 13px, 9px); +giveawayGiftCodePeerMargin: margins(11px, 6px, 11px, 4px); +giveawayGiftCodeUserpic: UserpicButton(defaultUserpicButton) { + size: size(24px, 24px); + photoSize: 24px; + photoPosition: point(-1px, -1px); +} +giveawayGiftCodeNamePosition: point(32px, 4px); +giveawayGiftCodeCover: PremiumCover(userPremiumCover) { + starSize: size(92px, 90px); + starTopSkip: 20px; + titlePadding: margins(0px, 15px, 0px, 17px); + titleFont: font(15px semibold); + about: FlatLabel(userPremiumCoverAbout) { + textFg: windowBoldFg; + style: TextStyle(premiumAboutTextStyle) { + lineHeight: 17px; + } + } +} +giveawayGiftCodeFooter: FlatLabel(defaultFlatLabel) { + align: align(top); + textFg: windowBoldFg; +} +giveawayGiftCodeFooterMargin: margins(0px, 9px, 0px, 4px); +giveawayGiftCodeBox: Box(defaultBox) { + buttonPadding: margins(22px, 11px, 22px, 22px); + buttonHeight: 42px; + button: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + font: font(13px semibold); + } + shadowIgnoreTopSkip: true; +} +giveawayRefundedLabel: FlatLabel(boxLabel) { + align: align(top); + style: semiboldTextStyle; + textFg: attentionButtonFg; +} +giveawayRefundedPadding: margins(8px, 10px, 8px, 10px); diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index 408b54c4f9cdd7..fb77089078e73f 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -13,6 +13,7 @@ For license and copyright information please follow this link: #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" +#include "styles/style_giveaway.h" #include "styles/style_layers.h" #include "styles/style_premium.h" diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 9d8736fdea1dcd..46305a2c7907c2 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -276,110 +276,3 @@ boostReplaceIconSkip: 3px; boostReplaceIconOutline: 2px; boostReplaceIconAdd: point(4px, 2px); boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }}; - -giveawayLoadingLabel: FlatLabel(membersAbout) { -} -giveawayGiftCodeTopHeight: 195px; -giveawayGiftCodeLink: FlatLabel(defaultFlatLabel) { - margin: margins(10px, 12px, 10px, 8px); - textFg: menuIconColor; - maxHeight: 24px; -} -giveawayGiftCodeLinkCopy: icon{{ "menu/copy", menuIconColor }}; -giveawayGiftCodeLinkHeight: 42px; -giveawayGiftCodeLinkCopyWidth: 40px; -giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px); - -giveawayGiftCodeGiftOption: PremiumOption(premiumGiftOption) { - badgeShift: point(5px, 0px); -} -giveawayGiftCodeStartButton: RoundButton(defaultActiveButton) { - height: 42px; - textTop: 12px; - radius: 6px; -} -giveawayGiftCodeQuantitySubtitle: FlatLabel(defaultFlatLabel) { - style: TextStyle(semiboldTextStyle) { - font: font(boxFontSize semibold); - } - textFg: windowActiveTextFg; - minWidth: 240px; - align: align(right); -} -giveawayGiftCodeQuantityFloat: FlatLabel(defaultFlatLabel) { - style: TextStyle(semiboldTextStyle) { - font: font(13px); - } - textFg: windowActiveTextFg; - minWidth: 50px; - align: align(center); -} - -boostLinkStatsButton: IconButton(defaultIconButton) { - width: giveawayGiftCodeLinkCopyWidth; - height: giveawayGiftCodeLinkHeight; - icon: icon{{ "menu/stats", menuIconColor }}; - iconOver: icon{{ "menu/stats", menuIconColor }}; - ripple: emptyRippleAnimation; -} - -giveawayGiftCodeTable: Table(defaultTable) { - labelMinWidth: 91px; -} -giveawayGiftCodeTableMargin: margins(24px, 4px, 24px, 4px); -giveawayGiftCodeLabel: FlatLabel(defaultFlatLabel) { - textFg: menuIconColor; - maxHeight: 24px; - style: TextStyle(semiboldTextStyle) { - font: font(12px semibold); - } -} -giveawayGiftCodeLabelMargin: margins(13px, 10px, 13px, 10px); -giveawayGiftCodeValue: FlatLabel(defaultFlatLabel) { - maxHeight: 24px; - style: TextStyle(defaultTextStyle) { - font: font(12px); - linkUnderline: kLinkUnderlineNever; - } -} -giveawayGiftCodeValueMargin: margins(13px, 9px, 13px, 9px); -giveawayGiftCodePeerMargin: margins(11px, 6px, 11px, 4px); -giveawayGiftCodeUserpic: UserpicButton(defaultUserpicButton) { - size: size(24px, 24px); - photoSize: 24px; - photoPosition: point(-1px, -1px); -} -giveawayGiftCodeNamePosition: point(32px, 4px); -giveawayGiftCodeCover: PremiumCover(userPremiumCover) { - starSize: size(92px, 90px); - starTopSkip: 20px; - titlePadding: margins(0px, 15px, 0px, 17px); - titleFont: font(15px semibold); - about: FlatLabel(userPremiumCoverAbout) { - textFg: windowBoldFg; - style: TextStyle(premiumAboutTextStyle) { - lineHeight: 17px; - } - } -} -giveawayGiftCodeFooter: FlatLabel(defaultFlatLabel) { - align: align(top); - textFg: windowBoldFg; -} -giveawayGiftCodeFooterMargin: margins(0px, 9px, 0px, 4px); -giveawayGiftCodeBox: Box(defaultBox) { - buttonPadding: margins(22px, 11px, 22px, 22px); - buttonHeight: 42px; - button: RoundButton(defaultActiveButton) { - height: 42px; - textTop: 12px; - font: font(13px semibold); - } - shadowIgnoreTopSkip: true; -} -giveawayRefundedLabel: FlatLabel(boxLabel) { - align: align(top); - style: semiboldTextStyle; - textFg: attentionButtonFg; -} -giveawayRefundedPadding: margins(8px, 10px, 8px, 10px); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index e0d6fa4f5ee67a..b9eab33971a0b6 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -23,6 +23,7 @@ set(style_files calls/calls.style export/view/export.style info/info.style + info/boosts/giveaway/giveaway.style info/userpic/info_userpic_builder.style intro/intro.style media/player/media_player.style From 2b1a46356a94da35381e06012db2b3ccc6eb3bc9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 04:45:18 +0300 Subject: [PATCH 31/70] Slightly improved style of giveaway box. --- .../SourceFiles/boxes/gift_premium_box.cpp | 2 +- .../info/boosts/create_giveaway_box.cpp | 142 ++++++++++-------- .../info/boosts/giveaway/giveaway.style | 48 ++++++ .../giveaway/giveaway_list_controllers.cpp | 7 +- .../boosts/giveaway/giveaway_type_row.cpp | 11 +- .../SourceFiles/statistics/statistics.style | 20 --- 6 files changed, 137 insertions(+), 93 deletions(-) diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 0f8aa249c705d7..dbad50cac5931b 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -41,8 +41,8 @@ For license and copyright information please follow this link: #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_giveaway.h" -#include "styles/style_layers.h" #include "styles/style_info.h" +#include "styles/style_layers.h" #include "styles/style_premium.h" #include diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 0d0ebc1de24a6f..27b9a49f7f2840 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -73,19 +73,20 @@ void CreateGiveawayBox( bar->setMinimumHeight(st::infoLayerTopBarHeight); bar->resize(bar->width(), bar->maximumHeight()); - Settings::AddSkip(box->verticalLayout()); - Settings::AddDivider(box->verticalLayout()); - Settings::AddSkip(box->verticalLayout()); + const auto container = box->verticalLayout(); + const auto &padding = st::giveawayGiftCodeCoverDividerPadding; + Settings::AddSkip(container, padding.top()); + Settings::AddDivider(container); + Settings::AddSkip(container, padding.bottom()); const auto close = Ui::CreateChild( - box->verticalLayout().get(), + container.get(), st::boxTitleClose); - close->setClickedCallback([=] { - box->closeBox(); - }); + close->setClickedCallback([=] { box->closeBox(); }); box->widthValue( ) | rpl::start_with_next([=](int) { - close->moveToRight(0, 0); + const auto &pos = st::giveawayGiftCodeCoverClosePosition; + close->moveToRight(pos.x(), pos.y()); }, box->lifetime()); } @@ -197,9 +198,12 @@ void CreateGiveawayBox( }); } - Settings::AddSkip(contentWrap->entity()); - Settings::AddDivider(contentWrap->entity()); - Settings::AddSkip(contentWrap->entity()); + { + const auto &padding = st::giveawayGiftCodeTypeDividerPadding; + Settings::AddSkip(contentWrap->entity(), padding.top()); + Settings::AddDivider(contentWrap->entity()); + Settings::AddSkip(contentWrap->entity(), padding.bottom()); + } const auto randomWrap = contentWrap->entity()->add( object_ptr>( @@ -245,12 +249,12 @@ void CreateGiveawayBox( rightLabel->moveToRight(st::boxRowPadding.right(), p.y()); }, rightLabel->lifetime()); - Settings::AddSkip(sliderContainer); - Settings::AddSkip(sliderContainer); + const auto &padding = st::giveawayGiftCodeSliderPadding; + Settings::AddSkip(sliderContainer, padding.top()); const auto slider = sliderContainer->add( object_ptr(sliderContainer, st::settingsScale), st::boxRowPadding); - Settings::AddSkip(sliderContainer); + Settings::AddSkip(sliderContainer, padding.bottom()); slider->resize(slider->width(), st::settingsScale.seekSize.height()); slider->setPseudoDiscrete( availablePresets.size(), @@ -275,7 +279,9 @@ void CreateGiveawayBox( + x + st::settingsScale.seekSize.width() / 2 - floatLabel->width() / 2, - slider->y() - floatLabel->height()); + slider->y() + - floatLabel->height() + - st::giveawayGiftCodeSliderFloatSkip); break; } } @@ -295,7 +301,8 @@ void CreateGiveawayBox( object_ptr(randomWrap)); Settings::AddSubsectionTitle( channelsContainer, - tr::lng_giveaway_channels_title()); + tr::lng_giveaway_channels_title(), + st::giveawayGiftCodeChannelsSubsectionPadding); struct ListState final { ListState(not_null p) : controller(p) { @@ -326,7 +333,7 @@ void CreateGiveawayBox( Settings::AddButton( channelsContainer, tr::lng_giveaway_channels_add(), - st::settingsButtonActive, + st::giveawayGiftCodeChannelsAddButton, { &st::settingsIconAdd, IconType::Round, &st::windowBgActive } )->setClickedCallback([=] { auto initBox = [=](not_null peersBox) { @@ -352,56 +359,12 @@ void CreateGiveawayBox( Ui::LayerOption::KeepOther); }); - Settings::AddSkip(channelsContainer); + const auto &padding = st::giveawayGiftCodeChannelsDividerPadding; + Settings::AddSkip(channelsContainer, padding.top()); Settings::AddDividerText( channelsContainer, tr::lng_giveaway_channels_about()); - Settings::AddSkip(channelsContainer); - } - - { - const auto dateContainer = randomWrap->entity()->add( - object_ptr(randomWrap)); - Settings::AddSubsectionTitle( - dateContainer, - tr::lng_giveaway_date_title()); - - state->dateValue = ThreeDaysAfterToday().toSecsSinceEpoch(); - const auto button = Settings::AddButtonWithLabel( - dateContainer, - tr::lng_giveaway_date(), - state->dateValue.value() | rpl::map( - base::unixtime::parse - ) | rpl::map(Ui::FormatDateTime), - st::defaultSettingsButton); - - button->setClickedCallback([=] { - constexpr auto kSevenDays = 3600 * 24 * 7; - box->uiShow()->showBox(Box([=](not_null b) { - Ui::ChooseDateTimeBox(b, { - .title = tr::lng_giveaway_date_select(), - .submit = tr::lng_settings_save(), - .done = [=](TimeId time) { - state->dateValue = time; - b->closeBox(); - }, - .min = QDateTime::currentSecsSinceEpoch, - .time = state->dateValue.current(), - .max = [=] { - return QDateTime::currentSecsSinceEpoch() - + kSevenDays; - }, - }); - })); - }); - - Settings::AddSkip(dateContainer); - Settings::AddDividerText( - dateContainer, - tr::lng_giveaway_date_about( - lt_count, - state->sliderValue.value() | tr::to_count())); - Settings::AddSkip(dateContainer); + Settings::AddSkip(channelsContainer, padding.bottom()); } const auto membersGroup = std::make_shared(); @@ -488,6 +451,52 @@ void CreateGiveawayBox( Settings::AddSkip(countriesContainer); } + { + const auto dateContainer = randomWrap->entity()->add( + object_ptr(randomWrap)); + Settings::AddSubsectionTitle( + dateContainer, + tr::lng_giveaway_date_title(), + st::giveawayGiftCodeChannelsSubsectionPadding); + + state->dateValue = ThreeDaysAfterToday().toSecsSinceEpoch(); + const auto button = Settings::AddButtonWithLabel( + dateContainer, + tr::lng_giveaway_date(), + state->dateValue.value() | rpl::map( + base::unixtime::parse + ) | rpl::map(Ui::FormatDateTime), + st::defaultSettingsButton); + + button->setClickedCallback([=] { + constexpr auto kSevenDays = 3600 * 24 * 7; + box->uiShow()->showBox(Box([=](not_null b) { + Ui::ChooseDateTimeBox(b, { + .title = tr::lng_giveaway_date_select(), + .submit = tr::lng_settings_save(), + .done = [=](TimeId time) { + state->dateValue = time; + b->closeBox(); + }, + .min = QDateTime::currentSecsSinceEpoch, + .time = state->dateValue.current(), + .max = [=] { + return QDateTime::currentSecsSinceEpoch() + + kSevenDays; + }, + }); + })); + }); + + Settings::AddSkip(dateContainer); + Settings::AddDividerText( + dateContainer, + tr::lng_giveaway_date_about( + lt_count, + state->sliderValue.value() | tr::to_count())); + Settings::AddSkip(dateContainer); + } + const auto durationGroup = std::make_shared(0); const auto listOptions = contentWrap->entity()->add( object_ptr(box)); @@ -499,7 +508,8 @@ void CreateGiveawayBox( listOptions, tr::lng_giveaway_duration_title( lt_count, - rpl::single(amountUsers) | tr::to_count())); + rpl::single(amountUsers) | tr::to_count()), + st::giveawayGiftCodeChannelsSubsectionPadding); Ui::Premium::AddGiftOptions( listOptions, durationGroup, @@ -507,6 +517,8 @@ void CreateGiveawayBox( st::giveawayGiftCodeGiftOption, true); + Settings::AddSkip(listOptions); + auto terms = object_ptr( listOptions, tr::lng_premium_gift_terms( diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style index d04b3bf6c53ed1..db9ad6f042150f 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style @@ -8,6 +8,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL using "ui/basic.style"; using "boxes/boxes.style"; using "ui/effects/premium.style"; +using "statistics/statistics.style"; + +giveawayTypeListItem: PeerListItem(defaultPeerListItem) { + height: 52px; + photoPosition: point(58px, 6px); + namePosition: point(110px, 8px); + statusPosition: point(110px, 28px); + photoSize: 42px; +} +giveawayUserpic: icon {{ "boosts/filled_gift", windowFgActive }}; +giveawayUserpicSkip: 1px; +giveawayUserpicGroup: icon {{ "limits/groups", windowFgActive }}; +giveawayRadioPosition: point(21px, 16px); + +giveawayGiftCodeCountryButton: SettingsButton(reportReasonButton) { +} +giveawayGiftCodeCountrySelect: MultiSelect(defaultMultiSelect) { +} + +giveawayGiftCodeChannelDeleteIcon: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFg }}; +giveawayGiftCodeChannelDeleteIconOver: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFgOver }}; giveawayLoadingLabel: FlatLabel(membersAbout) { } @@ -94,6 +115,33 @@ giveawayGiftCodeCover: PremiumCover(userPremiumCover) { } } } +giveawayGiftCodeCoverClosePosition: point(5px, 0px); +giveawayGiftCodeCoverDividerPadding: margins(0px, 11px, 0px, 5px); +giveawayGiftCodeTypeDividerPadding: margins(0px, 7px, 0px, 5px); +giveawayGiftCodeSliderPadding: margins(0px, 24px, 0px, 10px); +giveawayGiftCodeSliderFloatSkip: 6px; +giveawayGiftCodeChannelsSubsectionPadding: margins(0px, -1px, 0px, -4px); + +giveawayGiftCodeChannelsPeerList: PeerList(boostsListBox) { + padding: margins(0px, 7px, 0px, 0px); +} +giveawayGiftCodeMembersPeerList: PeerList(defaultPeerList) { + item: PeerListItem(defaultPeerListItem) { + height: 50px; + namePosition: point(62px, 7px); + statusPosition: point(62px, 27px); + } +} +giveawayRadioMembersPosition: point(21px, 14px); + +giveawayGiftCodeChannelsAddButton: SettingsButton(defaultSettingsButton) { + textFg: lightButtonFg; + textFgOver: lightButtonFgOver; + padding: margins(70px, 10px, 22px, 8px); + iconLeft: 28px; +} +giveawayGiftCodeChannelsDividerPadding: margins(0px, 5px, 0px, 5px); + giveawayGiftCodeFooter: FlatLabel(defaultFlatLabel) { align: align(top); textFg: windowBoldFg; diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp index 57cfe1b33d5f12..b6307e32a8bcb7 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp @@ -17,7 +17,7 @@ For license and copyright information please follow this link: #include "ui/boxes/confirm_box.h" #include "ui/effects/ripple_animation.h" #include "ui/painter.h" -#include "styles/style_statistics.h" +#include "styles/style_giveaway.h" namespace Giveaway { namespace { @@ -53,9 +53,10 @@ QSize ChannelRow::rightActionSize() const { } QMargins ChannelRow::rightActionMargins() const { + const auto itemHeight = st::giveawayGiftCodeChannelsPeerList.item.height; return QMargins( 0, - (st::defaultPeerListItem.height - rightActionSize().height()) / 2, + (itemHeight - rightActionSize().height()) / 2, st::giveawayRadioPosition.x() / 2, 0); } @@ -239,6 +240,8 @@ std::unique_ptr MyChannelsListController::createRow( SelectedChannelsListController::SelectedChannelsListController( not_null peer) : _peer(peer) { + PeerListController::setStyleOverrides( + &st::giveawayGiftCodeChannelsPeerList); } void SelectedChannelsListController::setTopStatus(rpl::producer s) { diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp index 25a71a00a75e4a..db1fedb08799af 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp @@ -13,7 +13,7 @@ For license and copyright information please follow this link: #include "ui/text/text_options.h" #include "ui/widgets/checkbox.h" #include "styles/style_boxes.h" -#include "styles/style_statistics.h" +#include "styles/style_giveaway.h" namespace Giveaway { @@ -28,7 +28,7 @@ GiveawayTypeRow::GiveawayTypeRow( , _type(type) , _st((_type == Type::SpecificUsers || _type == Type::Random) ? st::giveawayTypeListItem - : st::defaultPeerListItem) + : st::giveawayGiftCodeMembersPeerList.item) , _userpic( Ui::EmptyUserpic::UserpicColor((_type == Type::SpecificUsers) ? kColorIndexSpecific @@ -112,9 +112,10 @@ void GiveawayTypeRow::addRadio( _type, QString(), st); - radio->moveToLeft( - st::giveawayRadioPosition.x(), - st::giveawayRadioPosition.y()); + const auto pos = (_type == Type::SpecificUsers || _type == Type::Random) + ? st::giveawayRadioPosition + : st::giveawayRadioMembersPosition; + radio->moveToLeft(pos.x(), pos.y()); radio->setAttribute(Qt::WA_TransparentForMouseEvents); radio->show(); } diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index ba72e5973f65d9..c631a4f30450f3 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -148,23 +148,3 @@ getBoostsButton: SettingsButton(reportReasonButton) { textFgOver: lightButtonFg; } getBoostsButtonIcon: icon {{ "menu/gift_premium", lightButtonFg }}; - -giveawayTypeListItem: PeerListItem(defaultPeerListItem) { - height: 52px; - photoPosition: point(58px, 6px); - namePosition: point(110px, 8px); - statusPosition: point(110px, 28px); - photoSize: 42px; -} -giveawayUserpic: icon {{ "boosts/filled_gift", windowFgActive }}; -giveawayUserpicSkip: 1px; -giveawayUserpicGroup: icon {{ "limits/groups", windowFgActive }}; -giveawayRadioPosition: point(21px, 16px); - -giveawayGiftCodeCountryButton: SettingsButton(reportReasonButton) { -} -giveawayGiftCodeCountrySelect: MultiSelect(defaultMultiSelect) { -} - -giveawayGiftCodeChannelDeleteIcon: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFg }}; -giveawayGiftCodeChannelDeleteIconOver: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFgOver }}; From 2b4047b20d9aa9bcfd91868b78820ac0d1c38eb1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 05:09:19 +0300 Subject: [PATCH 32/70] Added support of some backend limitations for giveaways. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/api/api_premium.cpp | 24 +++++++- Telegram/SourceFiles/api/api_premium.h | 3 + .../info/boosts/create_giveaway_box.cpp | 56 +++++++++++-------- .../giveaway/giveaway_list_controllers.cpp | 21 ++++++- .../giveaway/giveaway_list_controllers.h | 9 +++ .../info/boosts/info_boosts_inner_widget.cpp | 10 +++- 7 files changed, 96 insertions(+), 29 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0b9f4c33497b43..6b11445f1cc218 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2114,6 +2114,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_maximum_countries_error#other" = "You can select maximum {count} countries."; "lng_giveaway_maximum_channels_error#one" = "You can select maximum {count} channel."; "lng_giveaway_maximum_channels_error#other" = "You can select maximum {count} channels."; +"lng_giveaway_maximum_users_error#one" = "You can select maximum {count} user."; +"lng_giveaway_maximum_users_error#other" = "You can select maximum {count} users."; "lng_giveaway_channels_confirm_title" = "Channel is Private"; "lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 185e2728cce387..629abc0d03bac5 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -435,18 +435,38 @@ Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) { } } -[[nodiscard]] int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const { +int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const { constexpr auto kFallbackCount = 4; return _peer->session().account().appConfig().get( u"giveaway_boosts_per_premium"_q, kFallbackCount); } -[[nodiscard]] int PremiumGiftCodeOptions::giveawayCountriesMax() const { +int PremiumGiftCodeOptions::giveawayCountriesMax() const { constexpr auto kFallbackCount = 10; return _peer->session().account().appConfig().get( u"giveaway_countries_max"_q, kFallbackCount); } +int PremiumGiftCodeOptions::giveawayAddPeersMax() const { + constexpr auto kFallbackCount = 10; + return _peer->session().account().appConfig().get( + u"giveaway_add_peers_max"_q, + kFallbackCount); +} + +int PremiumGiftCodeOptions::giveawayPeriodMax() const { + constexpr auto kFallbackCount = 3600 * 24 * 7; + return _peer->session().account().appConfig().get( + u"giveaway_period_max"_q, + kFallbackCount); +} + +bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const { + return _peer->session().account().appConfig().get( + u"giveaway_gifts_purchase_available"_q, + false); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index cfc7c6ec18d5ee..deb66df8064e1f 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -158,6 +158,9 @@ class PremiumGiftCodeOptions final { [[nodiscard]] int giveawayBoostsPerPremium() const; [[nodiscard]] int giveawayCountriesMax() const; + [[nodiscard]] int giveawayAddPeersMax() const; + [[nodiscard]] int giveawayPeriodMax() const; + [[nodiscard]] bool giveawayGiftsPurchaseAvailable() const; private: struct Token final { diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 27b9a49f7f2840..a1f72950b0df6a 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -51,6 +51,18 @@ namespace { return dateNow; } +[[nodiscard]] Fn CreateErrorCallback( + int max, + tr::phrase phrase) { + return [=](int count) { + const auto error = (count >= max); + if (error) { + Ui::Toast::Show(phrase(tr::now, lt_count, max)); + } + return error; + }; +} + } // namespace void CreateGiveawayBox( @@ -188,11 +200,16 @@ void CreateGiveawayBox( }, peersBox->lifetime()); }; + using Controller = Giveaway::AwardMembersListController; + auto listController = std::make_unique( + controller, + peer); + listController->setCheckError(CreateErrorCallback( + state->apiOptions.giveawayAddPeersMax(), + tr::lng_giveaway_maximum_users_error)); box->uiShow()->showBox( Box( - std::make_unique( - controller, - peer), + std::move(listController), std::move(initBox)), Ui::LayerOption::KeepOther); }); @@ -349,13 +366,16 @@ void CreateGiveawayBox( }); }; + using Controller = Giveaway::MyChannelsListController; + auto controller = std::make_unique( + peer, + box->uiShow(), + state->selectedToSubscribe); + controller->setCheckError(CreateErrorCallback( + state->apiOptions.giveawayAddPeersMax(), + tr::lng_giveaway_maximum_channels_error)); box->uiShow()->showBox( - Box( - std::make_unique( - peer, - box->uiShow(), - state->selectedToSubscribe), - std::move(initBox)), + Box(std::move(controller), std::move(initBox)), Ui::LayerOption::KeepOther); }); @@ -394,18 +414,9 @@ void CreateGiveawayBox( auto done = [=](std::vector list) { state->countriesValue = std::move(list); }; - auto error = [=](int count) { - const auto max = state->apiOptions.giveawayCountriesMax(); - const auto error = (count >= max); - if (error) { - Ui::Toast::Show(tr::lng_giveaway_maximum_countries_error( - tr::now, - lt_count, - max)); - } - return error; - }; - + auto error = CreateErrorCallback( + state->apiOptions.giveawayCountriesMax(), + tr::lng_giveaway_maximum_countries_error); box->uiShow()->showBox(Box( Ui::SelectCountriesBox, state->countriesValue.current(), @@ -469,7 +480,6 @@ void CreateGiveawayBox( st::defaultSettingsButton); button->setClickedCallback([=] { - constexpr auto kSevenDays = 3600 * 24 * 7; box->uiShow()->showBox(Box([=](not_null b) { Ui::ChooseDateTimeBox(b, { .title = tr::lng_giveaway_date_select(), @@ -482,7 +492,7 @@ void CreateGiveawayBox( .time = state->dateValue.current(), .max = [=] { return QDateTime::currentSecsSinceEpoch() - + kSevenDays; + + state->apiOptions.giveawayPeriodMax();; }, }); })); diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp index b6307e32a8bcb7..dd6df700f711e1 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp @@ -112,7 +112,13 @@ AwardMembersListController::AwardMembersListController( } void AwardMembersListController::rowClicked(not_null row) { - delegate()->peerListSetRowChecked(row, !row->checked()); + const auto checked = !row->checked(); + if (checked + && _checkErrorCallback + && _checkErrorCallback(delegate()->peerListSelectedRowsCount())) { + return; + } + delegate()->peerListSetRowChecked(row, checked); } std::unique_ptr AwardMembersListController::createRow( @@ -130,6 +136,10 @@ base::unique_qptr AwardMembersListController::rowContextMenu( return nullptr; } +void AwardMembersListController::setCheckError(Fn callback) { + _checkErrorCallback = std::move(callback); +} + MyChannelsListController::MyChannelsListController( not_null peer, std::shared_ptr show, @@ -160,6 +170,11 @@ std::unique_ptr MyChannelsListController::createRestoredRow( void MyChannelsListController::rowClicked(not_null row) { const auto channel = row->peer()->asChannel(); const auto checked = !row->checked(); + if (checked + && _checkErrorCallback + && _checkErrorCallback(delegate()->peerListSelectedRowsCount())) { + return; + } if (checked && channel && channel->username().isEmpty()) { _show->showBox(Box(Ui::ConfirmBox, Ui::ConfirmBoxArgs{ .text = tr::lng_giveaway_channels_confirm_about(), @@ -224,6 +239,10 @@ void MyChannelsListController::prepare() { }).send(); } +void MyChannelsListController::setCheckError(Fn callback) { + _checkErrorCallback = std::move(callback); +} + std::unique_ptr MyChannelsListController::createRow( not_null channel) const { if (channel->isMegagroup()) { diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h index 2c1e589c20a13e..8d08a8a159e859 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.h @@ -29,6 +29,8 @@ class AwardMembersListController : public ParticipantsBoxController { not_null navigation, not_null peer); + void setCheckError(Fn callback); + void rowClicked(not_null row) override; std::unique_ptr createRow( not_null participant) const override; @@ -36,6 +38,9 @@ class AwardMembersListController : public ParticipantsBoxController { QWidget *parent, not_null row) override; +private: + Fn _checkErrorCallback; + }; class MyChannelsListController : public PeerListController { @@ -45,6 +50,8 @@ class MyChannelsListController : public PeerListController { std::shared_ptr show, std::vector> selected); + void setCheckError(Fn callback); + Main::Session &session() const override; void prepare() override; void rowClicked(not_null row) override; @@ -61,6 +68,8 @@ class MyChannelsListController : public PeerListController { const not_null _peer; const std::shared_ptr _show; + Fn _checkErrorCallback; + std::vector> _selected; rpl::lifetime _apiLifetime; diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 73edbd68d7fa56..66445a109a7568 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #include "info/boosts/info_boosts_inner_widget.h" +#include "api/api_premium.h" #include "api/api_statistics.h" #include "boxes/peers/edit_peer_invite_link.h" #include "info/boosts/create_giveaway_box.h" @@ -182,6 +183,10 @@ void FillGetBoostsButton( not_null controller, std::shared_ptr show, not_null peer) { + if (!Api::PremiumGiftCodeOptions(peer).giveawayGiftsPurchaseAvailable()) { + return; + } + ::Settings::AddSkip(content); const auto &st = st::getBoostsButton; const auto &icon = st::getBoostsButtonIcon; const auto button = content->add( @@ -199,6 +204,8 @@ void FillGetBoostsButton( st::infoSharedMediaButtonIconPosition.x(), (st.height + rect::m::sum::v(st.padding) - icon.height()) / 2, })->show(); + ::Settings::AddSkip(content); + ::Settings::AddDividerText(content, tr::lng_boosts_get_boosts_subtext()); } } // namespace @@ -293,10 +300,7 @@ void InnerWidget::fill() { ::Settings::AddSkip(inner); ::Settings::AddDividerText(inner, tr::lng_boosts_link_subtext()); - ::Settings::AddSkip(inner); FillGetBoostsButton(inner, _controller, _show, _peer); - ::Settings::AddSkip(inner); - ::Settings::AddDividerText(inner, tr::lng_boosts_get_boosts_subtext()); resizeToWidth(width()); crl::on_main([=]{ fakeShowed->fire({}); }); From 69b24c494ebdc3cfc3457936a53003056395a1fa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 13:55:00 +0300 Subject: [PATCH 33/70] Added multiplier badge to boosts list in boosts info. --- .../Resources/icons/boosts/boost_mini2.png | Bin 0 -> 324 bytes .../Resources/icons/boosts/boost_mini2@2x.png | Bin 0 -> 521 bytes .../Resources/icons/boosts/boost_mini2@3x.png | Bin 0 -> 692 bytes .../info_statistics_list_controllers.cpp | 96 +++++++++++++++++- .../SourceFiles/statistics/statistics.style | 6 ++ 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 Telegram/Resources/icons/boosts/boost_mini2.png create mode 100644 Telegram/Resources/icons/boosts/boost_mini2@2x.png create mode 100644 Telegram/Resources/icons/boosts/boost_mini2@3x.png diff --git a/Telegram/Resources/icons/boosts/boost_mini2.png b/Telegram/Resources/icons/boosts/boost_mini2.png new file mode 100644 index 0000000000000000000000000000000000000000..b4884ccc8219eeafd8f74d49f69a2f80234439f3 GIT binary patch literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$e)_J-(hG>W; zCrDU1#KrB~woUBvUY{9G4uXk^4`r>(I5;^IPbFPEaPs8N%Fk)MyuH%qc?nq#;)!W# z_jVLMt~c1QeY^YgV^7Y{GUZ+*#KGcZ*zbEgZO1vd)|4)=RrR3boFyt=akR{0I#rWq5uE@ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/boosts/boost_mini2@2x.png b/Telegram/Resources/icons/boosts/boost_mini2@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9fb6e01e999deabdc3a38546147dca9f8ff2bdb0 GIT binary patch literal 521 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSVF5FO4N|zKE$K7JY)==*5D(tB zQw&!(1&AE~yq8Pymei893l#!*#24z$VzPH+<*n_IIpTlmmAZh-9}&lH3$raIrWQ+P zzW>m4`sr2cSD9b=iuOGJ|6lg~&hNJ}t}K3cV7cs%WsE+T_uS2U{&{Aae!8jw`hCIGtexwu z=lWGz9f_RQxyWLsPfBFpB95|u{R^(FR^aG+ykXyd-UqvQ+Yblr2p7!BnYAo`XHL=H zxcySA&mOQ!IFn|~mfDyac{QS=YGZ`KJL4Pgzjy2_%G!SWBkO}JX0y+p5;=0<{kMk& z4V$|=G*>?U_#=k#kIJvVcC3vRx89abQVDoawO6aFNuyIUG_;Y=^x0=i!N%JUHaIt) tO=~{=;^xPSJyC04{JRxd{$MlT9|naVzczhHx@!fB22WQ%mvv4FO#t!t-Y5V7 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/boosts/boost_mini2@3x.png b/Telegram/Resources/icons/boosts/boost_mini2@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..16aff7dbe80ce241eca2519a90f0cff05ea59631 GIT binary patch literal 692 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H_cU-pZ zz5o9H+p_i7Pe1;6LN96C#ivENX3`6G-p#ApJ1;bDaqH)~H;doQlw*O>ygwZsqE(+N+nQtW&ge4wsD6>7t!~9GRz2 z^l*_~kYo0{#x77{_0?5O0VYy=$l&v zv(KvC{Bhj9r(&L!Oy9wT4^Phu@UY#@`~I{@u;AggdFRtTOSl%DOqu1kd{Q}=x}98q zg^k}r^Q?uJUsmmvTW9EJFV}B3pTBhRf+d$TW5Vau?Y}?yq)K4^+CO*GHt!74IvA;Q zGR5d&LBt*x@t-yOZsshq(pDAgez=KoZ`9gNSv{Xy844WUmPv;n4ahfiox-frV2()iGLl7t>*5tFLC*IWOne%E7k%% zG65a28e$<@Q_B}PEp)gT(;2$_vS4G@)>%T_yG%d-jA39h`f+Y~w%;4JWVU~7j2T8V zC3@W+RlQ>UFqzBb^wUdu+k?%+GdS*F@mn68XsG{~6B2(nIsY*4?^DgX8?jFmlwv$x L{an^LB{Ts55AGPK literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 89aadcf6606849..b7f2168c60d0cd 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -321,6 +321,99 @@ void PublicForwardsController::appendRow( return; } +class BoostRow final : public PeerListRow { +public: + using PeerListRow::PeerListRow; + + void setMultiplier(int multiplier); + + int paintNameIconGetWidth( + Painter &p, + Fn repaint, + crl::time now, + int nameLeft, + int nameTop, + int nameWidth, + int availableWidth, + int outerWidth, + bool selected) override; + +private: + QImage _badge; + +}; + +void BoostRow::setMultiplier(int multiplier) { + if (!multiplier) { + _badge = QImage(); + return; + } + auto badgeText = Ui::Text::String( + st::statisticsDetailsBottomCaptionStyle, + QString::number(multiplier)); + const auto badgeTextWidth = badgeText.maxWidth(); + const auto badgex = 0; + const auto badgey = 0; + const auto badgeh = 0 + st::boostsListBadgeHeight; + const auto badgew = badgeTextWidth + + rect::m::sum::h(st::boostsListBadgeTextPadding); + auto result = QImage( + QSize(badgew, badgeh) * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(style::DevicePixelRatio()); + { + auto p = Painter(&result); + + p.setPen(Qt::NoPen); + p.setBrush(st::premiumButtonBg2); + + const auto r = QRect(badgex, badgey, badgew, badgeh); + { + auto hq = PainterHighQualityEnabler(p); + p.drawRoundedRect(r, badgeh / 2, badgeh / 2); + } + + p.setPen(st::premiumButtonFg); + p.setBrush(Qt::NoBrush); + badgeText.drawLeftElided( + p, + r.x() + st::boostsListBadgeTextPadding.left(), + badgey + st::boostsListBadgeTextPadding.top(), + badgew, + badgew * 2); + + st::boostsListMiniIcon.paint( + p, + QPoint(r.x() + st::boostsListMiniIconSkip, r.y()), + badgew * 2); + } + _badge = std::move(result); +} + +int BoostRow::paintNameIconGetWidth( + Painter &p, + Fn repaint, + crl::time now, + int nameLeft, + int nameTop, + int nameWidth, + int availableWidth, + int outerWidth, + bool selected) { + if (_badge.isNull()) { + return 0; + } + const auto badgew = _badge.width() / style::DevicePixelRatio(); + const auto nameTooLarge = (nameWidth > availableWidth); + const auto &padding = st::boostsListBadgePadding; + const auto left = nameTooLarge + ? ((nameLeft + availableWidth) - badgew - padding.left()) + : (nameLeft + nameWidth + padding.right()); + p.drawImage(left, nameTop + padding.top(), _badge); + return badgew + (nameTooLarge ? padding.left() : 0); +} + class BoostsController final : public PeerListController { public: explicit BoostsController(BoostsDescriptor d); @@ -393,7 +486,8 @@ void BoostsController::applySlice(const Data::BoostsListSlice &slice) { if (delegate()->peerListFindRow(user->id.value)) { continue; } - auto row = std::make_unique(user); + auto row = std::make_unique(user); + row->setMultiplier(item.multiplier); row->setCustomStatus(tr::lng_boosts_list_status( tr::now, lt_date, diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index c631a4f30450f3..e0966bce48915f 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -148,3 +148,9 @@ getBoostsButton: SettingsButton(reportReasonButton) { textFgOver: lightButtonFg; } getBoostsButtonIcon: icon {{ "menu/gift_premium", lightButtonFg }}; + +boostsListMiniIcon: icon{{ "boosts/boost_mini2", premiumButtonFg }}; +boostsListMiniIconSkip: 1px; +boostsListBadgeTextPadding: margins(16px, 1px, 6px, 0px); +boostsListBadgePadding: margins(4px, 1px, 4px, 0px); +boostsListBadgeHeight: 16px; From 01573af0de427a76991db72eacef462f06919ee3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 15:24:38 +0300 Subject: [PATCH 34/70] Added initial support of complex boosts list in boosts info. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/api/api_statistics.cpp | 1 + .../info_statistics_list_controllers.cpp | 103 +++++++++++++++--- 3 files changed, 91 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6b11445f1cc218..4c42f199a08e93 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4328,6 +4328,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts."; "lng_boosts_get_boosts" = "Get Boosts via Gifts"; "lng_boosts_get_boosts_subtext" = "Get more boosts for your channel by gifting Telegram Premium to your subscribers."; +"lng_boosts_list_unclaimed" = "Unclaimed"; +"lng_boosts_list_pending" = "To be distributed"; +"lng_boosts_list_pending_about" = "The recipient will be selected when the giveaway ends."; // Wnd specific diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index 1231a93037e632..3d3bc184c3aae0 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -554,6 +554,7 @@ void Boosts::requestBoosts( constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); constexpr auto kTlLimit = tl::make_int(kLimit); _requestId = _api.request(MTPpremium_GetBoostsList( + // MTP_flags(MTPpremium_GetBoostsList::Flag::f_gifts), MTP_flags(0), _peer->input, MTP_string(token.next), diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index b7f2168c60d0cd..cd71cd420e7924 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -18,6 +18,7 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "settings/settings_common.h" #include "ui/effects/toggle_arrow.h" +#include "ui/empty_userpic.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/widgets/buttons.h" @@ -30,6 +31,9 @@ For license and copyright information please follow this link: namespace Info::Statistics { namespace { +constexpr auto kColorIndexUnclaimed = int(3); +constexpr auto kColorIndexPending = int(4); + void AddArrow(not_null parent) { const auto arrow = Ui::CreateChild(parent.get()); arrow->paintRequest( @@ -323,9 +327,14 @@ void PublicForwardsController::appendRow( class BoostRow final : public PeerListRow { public: - using PeerListRow::PeerListRow; + BoostRow(not_null peer, const Data::Boost &boost); + BoostRow(const Data::Boost &boost); - void setMultiplier(int multiplier); + [[nodiscard]] const Data::Boost &boost() const; + [[nodiscard]] QString generateName() override; + + [[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback( + bool forceRound) override; int paintNameIconGetWidth( Painter &p, @@ -339,10 +348,72 @@ class BoostRow final : public PeerListRow { bool selected) override; private: + void init(); + void setMultiplier(int multiplier); + + const Data::Boost _boost; + Ui::EmptyUserpic _userpic; QImage _badge; }; +BoostRow::BoostRow(not_null peer, const Data::Boost &boost) +: PeerListRow(peer, UniqueRowIdFromString(boost.id)) +, _boost(boost) +, _userpic(Ui::EmptyUserpic::UserpicColor(0), QString()) { + init(); +} + +BoostRow::BoostRow(const Data::Boost &boost) +: PeerListRow(UniqueRowIdFromString(boost.id)) +, _boost(boost) +, _userpic( + Ui::EmptyUserpic::UserpicColor(boost.isUnclaimed + ? kColorIndexUnclaimed + : kColorIndexPending), + QString()) { + init(); +} + +void BoostRow::init() { + setMultiplier(_boost.multiplier); + constexpr auto kMonthsDivider = int(30 * 86400); + const auto months = (_boost.expiresAt - _boost.date.toSecsSinceEpoch()) + / kMonthsDivider; + auto status = !PeerListRow::special() + ? tr::lng_boosts_list_status( + tr::now, + lt_date, + langDateTime(_boost.date)) + : tr::lng_months_tiny(tr::now, lt_count, months) + + ' ' + + QChar(0x2022) + + ' ' + + langDateTime(_boost.date); + PeerListRow::setCustomStatus(std::move(status)); +} + +const Data::Boost &BoostRow::boost() const { + return _boost; +} + +QString BoostRow::generateName() { + return !PeerListRow::special() + ? PeerListRow::generateName() + : _boost.isUnclaimed + ? tr::lng_boosts_list_unclaimed(tr::now) + : tr::lng_boosts_list_pending(tr::now); +} + +PaintRoundImageCallback BoostRow::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); + }; +} + void BoostRow::setMultiplier(int multiplier) { if (!multiplier) { _badge = QImage(); @@ -480,27 +551,27 @@ void BoostsController::applySlice(const Data::BoostsListSlice &slice) { _allLoaded = slice.allLoaded; _apiToken = slice.token; - const auto formatter = u"MMM d, yyyy"_q; for (const auto &item : slice.list) { - const auto user = session().data().user(item.userId); - if (delegate()->peerListFindRow(user->id.value)) { - continue; - } - auto row = std::make_unique(user); - row->setMultiplier(item.multiplier); - row->setCustomStatus(tr::lng_boosts_list_status( - tr::now, - lt_date, - QLocale().toString(item.date, formatter))); + auto row = [&] { + if (item.userId && !item.isUnclaimed) { + const auto user = session().data().user(item.userId); + return std::make_unique(user, item); + } else { + return std::make_unique(item); + } + }(); delegate()->peerListAppendRow(std::move(row)); } delegate()->peerListRefreshRows(); } void BoostsController::rowClicked(not_null row) { - crl::on_main([=, peer = row->peer()] { - _showPeerInfo(peer); - }); + if (!row->special()) { + crl::on_main([=, peer = row->peer()] { + _showPeerInfo(peer); + }); + return; + } } } // namespace From e9a8acdc54776d8e83978e5bea642a77ed5cfa9b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 1 Nov 2023 16:05:20 +0300 Subject: [PATCH 35/70] Added initial click handler to boosts list in boosts info. --- .../info/boosts/info_boosts_inner_widget.cpp | 20 +++++++++++++++++-- .../info_statistics_list_controllers.cpp | 19 +++++++++--------- .../info_statistics_list_controllers.h | 3 ++- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 66445a109a7568..8c9b8ab7fb0b88 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -9,7 +9,11 @@ For license and copyright information please follow this link: #include "api/api_premium.h" #include "api/api_statistics.h" +#include "boxes/gift_premium_box.h" #include "boxes/peers/edit_peer_invite_link.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_user.h" #include "info/boosts/create_giveaway_box.h" #include "info/boosts/info_boosts_widget.h" #include "info/info_controller.h" @@ -268,8 +272,20 @@ void InnerWidget::fill() { ::Settings::AddSkip(inner); if (status.firstSlice.total > 0) { + auto boostClicked = [=](const Data::Boost &boost) { + if (!boost.giftCodeLink.slug.isEmpty()) { + ResolveGiftCode(_controller, boost.giftCodeLink.slug); + } else if (boost.userId) { + const auto user = _peer->owner().user(boost.userId); + crl::on_main(this, [=] { + _controller->showPeerInfo(user); + }); + } else if (!boost.isUnclaimed) { + _show->showToast(tr::lng_boosts_list_pending_about(tr::now)); + } + }; + ::Settings::AddSkip(inner); - using PeerPtr = not_null; const auto header = inner->add( object_ptr(inner), st::statisticsLayerMargins @@ -283,7 +299,7 @@ void InnerWidget::fill() { Statistics::AddBoostsList( status.firstSlice, inner, - [=](PeerPtr p) { _controller->showPeerInfo(p); }, + std::move(boostClicked), _peer, tr::lng_boosts_title()); ::Settings::AddSkip(inner); diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index cd71cd420e7924..085ee5bff915b4 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -31,6 +31,7 @@ For license and copyright information please follow this link: namespace Info::Statistics { namespace { +using BoostCallback = Fn; constexpr auto kColorIndexUnclaimed = int(3); constexpr auto kColorIndexPending = int(4); @@ -104,7 +105,7 @@ struct MembersDescriptor final { struct BoostsDescriptor final { Data::BoostsListSlice firstSlice; - Fn)> showPeerInfo; + BoostCallback boostClickedCallback; not_null peer; }; @@ -501,7 +502,7 @@ class BoostsController final : public PeerListController { void applySlice(const Data::BoostsListSlice &slice); const not_null _session; - Fn)> _showPeerInfo; + BoostCallback _boostClickedCallback; Api::Boosts _api; Data::BoostsListSlice _firstSlice; @@ -516,7 +517,7 @@ class BoostsController final : public PeerListController { BoostsController::BoostsController(BoostsDescriptor d) : _session(&d.peer->session()) -, _showPeerInfo(std::move(d.showPeerInfo)) +, _boostClickedCallback(std::move(d.boostClickedCallback)) , _api(d.peer) , _firstSlice(std::move(d.firstSlice)) { PeerListController::setStyleOverrides(&st::boostsListBox); @@ -566,11 +567,9 @@ void BoostsController::applySlice(const Data::BoostsListSlice &slice) { } void BoostsController::rowClicked(not_null row) { - if (!row->special()) { - crl::on_main([=, peer = row->peer()] { - _showPeerInfo(peer); - }); - return; + if (_boostClickedCallback) { + _boostClickedCallback( + static_cast(row.get())->boost()); } } @@ -677,7 +676,7 @@ void AddMembersList( void AddBoostsList( const Data::BoostsListSlice &firstSlice, not_null container, - Fn)> showPeerInfo, + BoostCallback boostClickedCallback, not_null peer, rpl::producer title) { const auto max = firstSlice.total; @@ -688,7 +687,7 @@ void AddBoostsList( BoostsController controller; int limit = Api::Boosts::kFirstSlice; }; - auto d = BoostsDescriptor{ firstSlice, std::move(showPeerInfo), peer }; + auto d = BoostsDescriptor{ firstSlice, boostClickedCallback, peer }; 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 dc619362d17159..de4bc5f2f19bdc 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h @@ -14,6 +14,7 @@ class VerticalLayout; } // namespace Ui namespace Data { +struct Boost; struct BoostsListSlice; struct PublicForwardsSlice; struct SupergroupStatistics; @@ -38,7 +39,7 @@ void AddMembersList( void AddBoostsList( const Data::BoostsListSlice &firstSlice, not_null container, - Fn)> showPeerInfo, + Fn boostClickedCallback, not_null peer, rpl::producer title); From 6fcf80c6cf142126d6dff3a96cdf766b72c12907 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 2 Nov 2023 14:49:03 +0300 Subject: [PATCH 36/70] Added initial right badges for complex boost entries in boosts info. --- .../info_statistics_list_controllers.cpp | 172 +++++++++++++----- .../SourceFiles/statistics/statistics.style | 11 ++ 2 files changed, 136 insertions(+), 47 deletions(-) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 085ee5bff915b4..e8ec14d972555e 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -35,6 +35,58 @@ using BoostCallback = Fn; constexpr auto kColorIndexUnclaimed = int(3); constexpr auto kColorIndexPending = int(4); +[[nodiscard]] QImage Badge( + const style::TextStyle &textStyle, + const QString &text, + int badgeHeight, + const style::margins &textPadding, + const style::color &bg, + const style::color &fg, + float64 bgOpacity, + const style::margins &iconPadding, + const style::icon &icon) { + auto badgeText = Ui::Text::String(textStyle, text); + const auto badgeTextWidth = badgeText.maxWidth(); + const auto badgex = 0; + const auto badgey = 0; + const auto badgeh = 0 + badgeHeight; + const auto badgew = badgeTextWidth + + rect::m::sum::h(textPadding); + auto result = QImage( + QSize(badgew, badgeh) * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(style::DevicePixelRatio()); + { + auto p = Painter(&result); + + p.setPen(Qt::NoPen); + p.setBrush(bg); + + const auto r = QRect(badgex, badgey, badgew, badgeh); + { + auto hq = PainterHighQualityEnabler(p); + auto o = ScopedPainterOpacity(p, bgOpacity); + p.drawRoundedRect(r, badgeh / 2, badgeh / 2); + } + + p.setPen(fg); + p.setBrush(Qt::NoBrush); + badgeText.drawLeftElided( + p, + r.x() + textPadding.left(), + badgey + textPadding.top(), + badgew, + badgew * 2); + + icon.paint( + p, + QPoint(r.x() + iconPadding.left(), r.y() + iconPadding.top()), + badgew * 2); + } + return result; +} + void AddArrow(not_null parent) { const auto arrow = Ui::CreateChild(parent.get()); arrow->paintRequest( @@ -348,13 +400,25 @@ class BoostRow final : public PeerListRow { int outerWidth, bool selected) 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(); - void setMultiplier(int multiplier); + void invalidateBadges(); const Data::Boost _boost; Ui::EmptyUserpic _userpic; QImage _badge; + QImage _rightBadge; }; @@ -377,7 +441,7 @@ BoostRow::BoostRow(const Data::Boost &boost) } void BoostRow::init() { - setMultiplier(_boost.multiplier); + invalidateBadges(); constexpr auto kMonthsDivider = int(30 * 86400); const auto months = (_boost.expiresAt - _boost.date.toSecsSinceEpoch()) / kMonthsDivider; @@ -415,52 +479,66 @@ PaintRoundImageCallback BoostRow::generatePaintUserpicCallback(bool force) { }; } -void BoostRow::setMultiplier(int multiplier) { - if (!multiplier) { - _badge = QImage(); - return; - } - auto badgeText = Ui::Text::String( - st::statisticsDetailsBottomCaptionStyle, - QString::number(multiplier)); - const auto badgeTextWidth = badgeText.maxWidth(); - const auto badgex = 0; - const auto badgey = 0; - const auto badgeh = 0 + st::boostsListBadgeHeight; - const auto badgew = badgeTextWidth - + rect::m::sum::h(st::boostsListBadgeTextPadding); - auto result = QImage( - QSize(badgew, badgeh) * style::DevicePixelRatio(), - QImage::Format_ARGB32_Premultiplied); - result.fill(Qt::transparent); - result.setDevicePixelRatio(style::DevicePixelRatio()); - { - auto p = Painter(&result); - - p.setPen(Qt::NoPen); - p.setBrush(st::premiumButtonBg2); - - const auto r = QRect(badgex, badgey, badgew, badgeh); - { - auto hq = PainterHighQualityEnabler(p); - p.drawRoundedRect(r, badgeh / 2, badgeh / 2); - } - - p.setPen(st::premiumButtonFg); - p.setBrush(Qt::NoBrush); - badgeText.drawLeftElided( - p, - r.x() + st::boostsListBadgeTextPadding.left(), - badgey + st::boostsListBadgeTextPadding.top(), - badgew, - badgew * 2); - - st::boostsListMiniIcon.paint( - p, - QPoint(r.x() + st::boostsListMiniIconSkip, r.y()), - badgew * 2); +void BoostRow::invalidateBadges() { + _badge = _boost.multiplier + ? Badge( + st::statisticsDetailsBottomCaptionStyle, + QString::number(_boost.multiplier), + st::boostsListBadgeHeight, + st::boostsListBadgeTextPadding, + st::premiumButtonBg2, + st::premiumButtonFg, + 1., + st::boostsListMiniIconPadding, + st::boostsListMiniIcon) + : QImage(); + + constexpr auto kBadgeBgOpacity = 0.2; + const auto &rightColor = _boost.isGiveaway + ? st::historyPeer4UserpicBg2 + : st::historyPeer8UserpicBg2; + const auto &rightIcon = _boost.isGiveaway + ? st::boostsListGiveawayMiniIcon + : st::boostsListGiftMiniIcon; + _rightBadge = (_boost.isGift || _boost.isGiveaway) + ? Badge( + st::boostsListRightBadgeTextStyle, + _boost.isGiveaway + ? tr::lng_gift_link_reason_giveaway(tr::now) + : tr::lng_gift_link_label_gift(tr::now), + st::boostsListRightBadgeHeight, + st::boostsListRightBadgeTextPadding, + rightColor, + rightColor, + kBadgeBgOpacity, + st::boostsListGiftMiniIconPadding, + rightIcon) + : QImage(); +} + + +QSize BoostRow::rightActionSize() const { + return _rightBadge.size() / style::DevicePixelRatio(); +} + +QMargins BoostRow::rightActionMargins() const { + return st::boostsListRightBadgePadding; +} + +bool BoostRow::rightActionDisabled() const { + return true; +} + +void BoostRow::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + if (!_rightBadge.isNull()) { + p.drawImage(x, y, _rightBadge); } - _badge = std::move(result); } int BoostRow::paintNameIconGetWidth( diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index e0966bce48915f..036fb16e52c12c 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -150,7 +150,18 @@ getBoostsButton: SettingsButton(reportReasonButton) { getBoostsButtonIcon: icon {{ "menu/gift_premium", lightButtonFg }}; boostsListMiniIcon: icon{{ "boosts/boost_mini2", premiumButtonFg }}; +boostsListMiniIconPadding: margins(1px, 0px, 0px, 0px); boostsListMiniIconSkip: 1px; boostsListBadgeTextPadding: margins(16px, 1px, 6px, 0px); boostsListBadgePadding: margins(4px, 1px, 4px, 0px); boostsListBadgeHeight: 16px; + +boostsListRightBadgeTextStyle: TextStyle(defaultTextStyle) { + font: font(12px semibold); +} +boostsListRightBadgeTextPadding: margins(16px, 1px, 6px, 0px); +boostsListRightBadgePadding: margins(4px, 5px, 8px, 0px); +boostsListRightBadgeHeight: 20px; +boostsListGiftMiniIconPadding: margins(1px, 2px, 0px, 0px); +boostsListGiftMiniIcon: icon{{ "boosts/boost_mini2", historyPeer8UserpicBg2 }}; +boostsListGiveawayMiniIcon: icon{{ "boosts/boost_mini2", historyPeer4UserpicBg2 }}; From 79b5f7eda95fef4eff2d1382105d91d3a48f3f5d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 2 Nov 2023 15:28:17 +0300 Subject: [PATCH 37/70] Added initial API support for different boosts lists. --- Telegram/SourceFiles/api/api_statistics.cpp | 22 +++++++++++++------ Telegram/SourceFiles/data/data_boosts.h | 6 +++-- .../info/boosts/info_boosts_inner_widget.cpp | 6 ++--- .../info_statistics_list_controllers.cpp | 2 +- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index 3d3bc184c3aae0..3069843c179a1e 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -533,8 +533,13 @@ rpl::producer Boosts::request() { }; _boostStatus.link = qs(data.vboost_url()); - requestBoosts({}, [=](Data::BoostsListSlice &&slice) { - _boostStatus.firstSlice = std::move(slice); + using namespace Data; + requestBoosts({ .gifts = false }, [=](BoostsListSlice &&slice) { + _boostStatus.firstSliceBoosts = std::move(slice); + requestBoosts({ .gifts = true }, [=](BoostsListSlice &&s) { + _boostStatus.firstSliceGifts = std::move(s); + consumer.put_done(); + }); consumer.put_done(); }); }).fail([=](const MTP::Error &error) { @@ -553,9 +558,11 @@ void Boosts::requestBoosts( } constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); constexpr auto kTlLimit = tl::make_int(kLimit); + const auto gifts = token.gifts; _requestId = _api.request(MTPpremium_GetBoostsList( - // MTP_flags(MTPpremium_GetBoostsList::Flag::f_gifts), - MTP_flags(0), + gifts + ? MTP_flags(MTPpremium_GetBoostsList::Flag::f_gifts) + : MTP_flags(0), _peer->input, MTP_string(token.next), token.next.isEmpty() ? kTlFirstSlice : kTlLimit @@ -596,12 +603,13 @@ void Boosts::requestBoosts( } done(Data::BoostsListSlice{ .list = std::move(list), - .total = data.vcount().v, + .multipliedTotal = data.vcount().v, .allLoaded = (data.vcount().v == data.vboosts().v.size()), .token = Data::BoostsListSlice::OffsetToken{ - data.vnext_offset() + .next = data.vnext_offset() ? qs(*data.vnext_offset()) - : QString() + : QString(), + .gifts = gifts, }, }); }).fail([=] { diff --git a/Telegram/SourceFiles/data/data_boosts.h b/Telegram/SourceFiles/data/data_boosts.h index b17ca85f8cfc5f..a62c70c92ec318 100644 --- a/Telegram/SourceFiles/data/data_boosts.h +++ b/Telegram/SourceFiles/data/data_boosts.h @@ -42,16 +42,18 @@ struct Boost final { struct BoostsListSlice final { struct OffsetToken final { QString next; + bool gifts = false; }; std::vector list; - int total = 0; + int multipliedTotal = 0; bool allLoaded = false; OffsetToken token; }; struct BoostStatus final { BoostsOverview overview; - BoostsListSlice firstSlice; + BoostsListSlice firstSliceBoosts; + BoostsListSlice firstSliceGifts; QString link; }; diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 8c9b8ab7fb0b88..5a14a9916b0193 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -271,7 +271,7 @@ void InnerWidget::fill() { ::Settings::AddDivider(inner); ::Settings::AddSkip(inner); - if (status.firstSlice.total > 0) { + if (status.firstSliceBoosts.multipliedTotal > 0) { auto boostClicked = [=](const Data::Boost &boost) { if (!boost.giftCodeLink.slug.isEmpty()) { ResolveGiftCode(_controller, boost.giftCodeLink.slug); @@ -294,10 +294,10 @@ void InnerWidget::fill() { header->setTitle(tr::lng_boosts_list_title( tr::now, lt_count, - status.firstSlice.total)); + status.firstSliceBoosts.total)); header->setSubTitle({}); Statistics::AddBoostsList( - status.firstSlice, + status.firstSliceBoosts, inner, std::move(boostClicked), _peer, diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index e8ec14d972555e..93b05dc3881fb5 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -757,7 +757,7 @@ void AddBoostsList( BoostCallback boostClickedCallback, not_null peer, rpl::producer title) { - const auto max = firstSlice.total; + const auto max = firstSlice.multipliedTotal; struct State final { State(BoostsDescriptor d) : controller(std::move(d)) { } From 72c8999e500e5dff81afd7efaaa1d066ced189ed Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 2 Nov 2023 16:17:25 +0300 Subject: [PATCH 38/70] Added initial tabbed selector for different boosts lists in boosts info. --- Telegram/Resources/langs/lang.strings | 2 + .../info/boosts/info_boosts_inner_widget.cpp | 77 +++++++++++++++---- .../SourceFiles/ui/widgets/discrete_sliders.h | 2 +- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4c42f199a08e93..cf90704d1b605a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4331,6 +4331,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boosts_list_unclaimed" = "Unclaimed"; "lng_boosts_list_pending" = "To be distributed"; "lng_boosts_list_pending_about" = "The recipient will be selected when the giveaway ends."; +"lng_boosts_list_tab_gifts#one" = "{count} Gifts"; +"lng_boosts_list_tab_gifts#other" = "{count} Gifts"; // Wnd specific diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 5a14a9916b0193..05b57cdf824eb1 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -27,7 +27,9 @@ For license and copyright information please follow this link: #include "ui/controls/invite_link_label.h" #include "ui/rect.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/labels.h" +#include "ui/wrap/slide_wrap.h" #include "styles/style_info.h" #include "styles/style_statistics.h" @@ -271,7 +273,9 @@ void InnerWidget::fill() { ::Settings::AddDivider(inner); ::Settings::AddSkip(inner); - if (status.firstSliceBoosts.multipliedTotal > 0) { + const auto hasBoosts = (status.firstSliceBoosts.multipliedTotal > 0); + const auto hasGifts = (status.firstSliceGifts.multipliedTotal > 0); + if (hasBoosts || hasGifts) { auto boostClicked = [=](const Data::Boost &boost) { if (!boost.giftCodeLink.slug.isEmpty()) { ResolveGiftCode(_controller, boost.giftCodeLink.slug); @@ -285,28 +289,73 @@ void InnerWidget::fill() { } }; - ::Settings::AddSkip(inner); - const auto header = inner->add( - object_ptr(inner), - st::statisticsLayerMargins - + st::boostsChartHeaderPadding); - header->resizeToWidth(header->width()); - header->setTitle(tr::lng_boosts_list_title( +#ifdef _DEBUG + const auto hasOneTab = false; +#else + const auto hasOneTab = (hasBoosts != hasGifts); +#endif + const auto boostsTabText = tr::lng_boosts_list_title( + tr::now, + lt_count, + status.firstSliceBoosts.multipliedTotal); + const auto giftsTabText = tr::lng_boosts_list_tab_gifts( tr::now, lt_count, - status.firstSliceBoosts.total)); - header->setSubTitle({}); + status.firstSliceGifts.multipliedTotal); + if (hasOneTab) { + ::Settings::AddSkip(inner); + const auto header = inner->add( + object_ptr(inner), + st::statisticsLayerMargins + + st::boostsChartHeaderPadding); + header->resizeToWidth(header->width()); + header->setTitle(hasBoosts ? boostsTabText : giftsTabText); + header->setSubTitle({}); + } + + const auto slider = inner->add( + object_ptr>( + inner, + object_ptr( + inner, + st::defaultTabsSlider))); + slider->toggle(!hasOneTab, anim::type::instant); + slider->entity()->addSection(boostsTabText); + slider->entity()->addSection(giftsTabText); + + const auto boostsWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + const auto giftsWrap = inner->add( + object_ptr>( + inner, + object_ptr(inner))); + boostsWrap->toggle(hasOneTab ? true : hasBoosts, anim::type::instant); + giftsWrap->toggle(hasOneTab ? false : hasGifts, anim::type::instant); + + slider->entity()->sectionActivated( + ) | rpl::start_with_next([=](int index) { + boostsWrap->toggle(!index, anim::type::instant); + giftsWrap->toggle(index, anim::type::instant); + }, inner->lifetime()); + Statistics::AddBoostsList( status.firstSliceBoosts, - inner, + boostsWrap->entity(), + boostClicked, + _peer, + tr::lng_boosts_title()); + Statistics::AddBoostsList( + status.firstSliceGifts, + giftsWrap->entity(), std::move(boostClicked), _peer, tr::lng_boosts_title()); + ::Settings::AddSkip(inner); - ::Settings::AddDividerText( - inner, - tr::lng_boosts_list_subtext()); ::Settings::AddSkip(inner); + ::Settings::AddDividerText(inner, tr::lng_boosts_list_subtext()); } ::Settings::AddSkip(inner); diff --git a/Telegram/SourceFiles/ui/widgets/discrete_sliders.h b/Telegram/SourceFiles/ui/widgets/discrete_sliders.h index e1513e693030b5..e3ae5498698729 100644 --- a/Telegram/SourceFiles/ui/widgets/discrete_sliders.h +++ b/Telegram/SourceFiles/ui/widgets/discrete_sliders.h @@ -29,7 +29,7 @@ class DiscreteSlider : public RpWidget { void setActiveSectionFast(int index); void finishAnimating(); - auto sectionActivated() const { + [[nodiscard]] rpl::producer sectionActivated() const { return _sectionActivated.events(); } From 764b0d3dcdc8f7e455bd77a418388930d1d2220d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 3 Nov 2023 12:53:44 +0300 Subject: [PATCH 39/70] Added loading label to boosts info. --- .../info/boosts/info_boosts_inner_widget.cpp | 7 ++ .../info_statistics_inner_widget.cpp | 88 +++++++++---------- .../statistics/info_statistics_inner_widget.h | 5 ++ 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 05b57cdf824eb1..36c90fef0c0c23 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -18,6 +18,7 @@ For license and copyright information please follow this link: #include "info/boosts/info_boosts_widget.h" #include "info/info_controller.h" #include "info/profile/info_profile_icon.h" +#include "info/statistics/info_statistics_inner_widget.h" // FillLoading. #include "info/statistics/info_statistics_list_controllers.h" #include "lang/lang_keys.h" #include "settings/settings_common.h" @@ -229,12 +230,18 @@ InnerWidget::InnerWidget( void InnerWidget::load() { const auto api = lifetime().make_state(_peer); + Info::Statistics::FillLoading( + this, + _loaded.events_starting_with(false) | rpl::map(!rpl::mappers::_1), + _showFinished.events()); + _showFinished.events( ) | rpl::take(1) | rpl::start_with_next([=] { api->request( ) | rpl::start_with_error_done([](const QString &error) { }, [=] { _state = api->boostStatus(); + _loaded.fire(true); fill(); }, lifetime()); }, lifetime()); diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp index baa569ab35fc0f..b5b2ae8b094c40 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp @@ -247,50 +247,6 @@ void FillStatistic( } } -void FillLoading( - not_null container, - rpl::producer toggleOn, - rpl::producer<> showFinished) { - const auto emptyWrap = container->add( - object_ptr>( - container, - object_ptr(container))); - emptyWrap->toggleOn(std::move(toggleOn), anim::type::instant); - - const auto content = emptyWrap->entity(); - auto icon = ::Settings::CreateLottieIcon( - content, - { .name = u"stats"_q, .sizeOverride = Size(st::changePhoneIconSize) }, - st::settingsBlockedListIconPadding); - - ( - std::move(showFinished) | rpl::take(1) - ) | rpl::start_with_next([animate = std::move(icon.animate)] { - animate(anim::repeat::loop); - }, icon.widget->lifetime()); - content->add(std::move(icon.widget)); - - content->add( - object_ptr>( - content, - object_ptr( - content, - tr::lng_stats_loading(), - st::changePhoneTitle)), - st::changePhoneTitlePadding + st::boxRowPadding); - - content->add( - object_ptr>( - content, - object_ptr( - content, - tr::lng_stats_loading_subtext(), - st::statisticsLoadingSubtext)), - st::changePhoneDescriptionPadding + st::boxRowPadding); - - ::Settings::AddSkip(content, st::settingsBlockedListIconPadding.top()); -} - void AddHeader( not_null content, tr::phrase<> text, @@ -507,6 +463,50 @@ void FillOverview( } // namespace +void FillLoading( + not_null container, + rpl::producer toggleOn, + rpl::producer<> showFinished) { + const auto emptyWrap = container->add( + object_ptr>( + container, + object_ptr(container))); + emptyWrap->toggleOn(std::move(toggleOn), anim::type::instant); + + const auto content = emptyWrap->entity(); + auto icon = ::Settings::CreateLottieIcon( + content, + { .name = u"stats"_q, .sizeOverride = Size(st::changePhoneIconSize) }, + st::settingsBlockedListIconPadding); + + ( + std::move(showFinished) | rpl::take(1) + ) | rpl::start_with_next([animate = std::move(icon.animate)] { + animate(anim::repeat::loop); + }, icon.widget->lifetime()); + content->add(std::move(icon.widget)); + + content->add( + object_ptr>( + content, + object_ptr( + content, + tr::lng_stats_loading(), + st::changePhoneTitle)), + st::changePhoneTitlePadding + st::boxRowPadding); + + content->add( + object_ptr>( + content, + object_ptr( + content, + tr::lng_stats_loading_subtext(), + st::statisticsLoadingSubtext)), + st::changePhoneDescriptionPadding + st::boxRowPadding); + + ::Settings::AddSkip(content, st::settingsBlockedListIconPadding.top()); +} + InnerWidget::InnerWidget( QWidget *parent, not_null controller, diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.h b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.h index cd3aa93480723e..f2e21a6c1ef5e0 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.h +++ b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.h @@ -21,6 +21,11 @@ namespace Info::Statistics { class Memento; class MessagePreview; +void FillLoading( + not_null container, + rpl::producer toggleOn, + rpl::producer<> showFinished); + class InnerWidget final : public Ui::VerticalLayout { public: struct ShowRequest final { From 336705a503432a75ad921ee4fabdf9d7bab91521 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 3 Nov 2023 15:19:52 +0300 Subject: [PATCH 40/70] Fixed description in box for boosting. Regression was introduced in d82c422ea1. --- Telegram/SourceFiles/ui/boxes/boost_box.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index fb77089078e73f..e9ffe759d595be 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -63,6 +63,14 @@ void BoostBox( data.boost, st::boxRowPadding); + { + const auto &d = data.boost; + if (!d.nextLevelBoosts + || ((d.thisLevelBoosts == d.boosts) && d.mine)) { + --data.boost.level; + } + } + box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); const auto name = data.name; From f4cfbc5ed8a6f73bb7156afb580c1f5f1dfaf3b1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 3 Nov 2023 16:23:53 +0300 Subject: [PATCH 41/70] Improved behavior of show more button in boosts list from boosts info. --- Telegram/Resources/langs/lang.strings | 5 +- .../info/boosts/info_boosts_inner_widget.cpp | 7 +-- .../info_statistics_list_controllers.cpp | 49 +++++++++++-------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cf90704d1b605a..6ecca945959d96 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4322,7 +4322,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boosts_list_title#one" = "{count} booster"; "lng_boosts_list_title#other" = "{count} boosters"; "lng_boosts_list_subtext" = "Your channel is currently boosted by these users."; -"lng_boosts_show_more" = "Show More Boosts"; +"lng_boosts_show_more_boosts#one" = "Show {count} More Boosts"; +"lng_boosts_show_more_boosts#other" = "Show {count} More Boosts"; +"lng_boosts_show_more_gifts#one" = "Show {count} More Boosts"; +"lng_boosts_show_more_gifts#other" = "Show {count} More Boosts"; "lng_boosts_list_status" = "boost expires on {date}"; "lng_boosts_link_title" = "Link for boosting"; "lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts."; diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index 36c90fef0c0c23..494df7a99b0229 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -67,11 +67,12 @@ void FillOverview( object_ptr(content), st::statisticsLayerMargins); - const auto addPrimary = [&](float64 v) { + const auto addPrimary = [&](float64 v, bool approximately = false) { return Ui::CreateChild( container, (v >= 0) - ? Lang::FormatCountToShort(v).string + ? (approximately && v ? QChar(0x2248) : QChar()) + + Lang::FormatCountToShort(v).string : QString(), st::statisticsOverviewValue); }; @@ -109,7 +110,7 @@ void FillOverview( const auto topLeftLabel = addPrimary(stats.level); - const auto topRightLabel = addPrimary(stats.premiumMemberCount); + const auto topRightLabel = addPrimary(stats.premiumMemberCount, true); const auto bottomLeftLabel = addPrimary(stats.boostCount); const auto bottomRightLabel = addPrimary(std::max( stats.nextLevelBoostCount - stats.boostCount, diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 93b05dc3881fb5..6d8c4307edb5d4 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -574,7 +574,9 @@ class BoostsController final : public PeerListController { void loadMoreRows() override; [[nodiscard]] bool skipRequest() const; - void setLimit(int limit); + void requestNext(); + + [[nodiscard]] rpl::producer totalBoostsValue() const; private: void applySlice(const Data::BoostsListSlice &slice); @@ -586,11 +588,11 @@ class BoostsController final : public PeerListController { Data::BoostsListSlice _firstSlice; Data::BoostsListSlice::OffsetToken _apiToken; - int _limit = 0; - bool _allLoaded = false; bool _requesting = false; + rpl::variable _totalBoosts; + }; BoostsController::BoostsController(BoostsDescriptor d) @@ -609,8 +611,7 @@ bool BoostsController::skipRequest() const { return _requesting || _allLoaded; } -void BoostsController::setLimit(int limit) { - _limit = limit; +void BoostsController::requestNext() { _requesting = true; _api.requestBoosts(_apiToken, [=](const Data::BoostsListSlice &slice) { _requesting = false; @@ -630,7 +631,9 @@ void BoostsController::applySlice(const Data::BoostsListSlice &slice) { _allLoaded = slice.allLoaded; _apiToken = slice.token; + auto sumFromSlice = 0; for (const auto &item : slice.list) { + sumFromSlice += item.multiplier ? item.multiplier : 1; auto row = [&] { if (item.userId && !item.isUnclaimed) { const auto user = session().data().user(item.userId); @@ -642,6 +645,7 @@ void BoostsController::applySlice(const Data::BoostsListSlice &slice) { delegate()->peerListAppendRow(std::move(row)); } delegate()->peerListRefreshRows(); + _totalBoosts = _totalBoosts.current() + sumFromSlice; } void BoostsController::rowClicked(not_null row) { @@ -651,6 +655,10 @@ void BoostsController::rowClicked(not_null row) { } } +rpl::producer BoostsController::totalBoostsValue() const { + return _totalBoosts.value(); +} + } // namespace void AddPublicForwards( @@ -763,7 +771,6 @@ void AddBoostsList( } PeerListContentDelegateSimple delegate; BoostsController controller; - int limit = Api::Boosts::kFirstSlice; }; auto d = BoostsDescriptor{ firstSlice, boostClickedCallback, peer }; const auto state = container->lifetime().make_state(std::move(d)); @@ -772,35 +779,35 @@ void AddBoostsList( object_ptr(container, &state->controller))); state->controller.setDelegate(&state->delegate); - if (max <= state->limit) { - return; - } const auto wrap = container->add( object_ptr>( container, object_ptr( container, - tr::lng_boosts_show_more(), + (firstSlice.token.gifts + ? tr::lng_boosts_show_more_gifts + : tr::lng_boosts_show_more_boosts)( + lt_count, + state->controller.totalBoostsValue( + ) | rpl::map( + max - rpl::mappers::_1 + ) | tr::to_count()), st::statisticsShowMoreButton)), { 0, -st::settingsButton.padding.top(), 0, 0 }); const auto button = wrap->entity(); AddArrow(button); const auto showMore = [=] { - if (state->controller.skipRequest()) { - return; - } - state->limit = std::min(int(max), state->limit + Api::Boosts::kLimit); - state->controller.setLimit(state->limit); - if (state->limit == max) { - wrap->toggle(false, anim::type::instant); + if (!state->controller.skipRequest()) { + state->controller.requestNext(); + container->resizeToWidth(container->width()); } - container->resizeToWidth(container->width()); }; + wrap->toggleOn( + state->controller.totalBoostsValue( + ) | rpl::map(rpl::mappers::_1 > 0 && rpl::mappers::_1 < max), + anim::type::instant); button->setClickedCallback(showMore); - if (state->limit == max) { - wrap->toggle(false, anim::type::instant); - } } } // namespace Info::Statistics From a4e5ea01dc26db74951207b1fbae810d36db6677 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 4 Nov 2023 13:41:50 +0300 Subject: [PATCH 42/70] Added icon to language button in settings from intro. --- Telegram/SourceFiles/settings/settings_intro.cpp | 2 +- Telegram/SourceFiles/settings/settings_main.cpp | 7 +++---- Telegram/SourceFiles/settings/settings_main.h | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_intro.cpp b/Telegram/SourceFiles/settings/settings_intro.cpp index 83c930c1a1c921..6aecfc0b96d207 100644 --- a/Telegram/SourceFiles/settings/settings_intro.cpp +++ b/Telegram/SourceFiles/settings/settings_intro.cpp @@ -63,7 +63,7 @@ object_ptr CreateIntroSettings( AddDivider(result); AddSkip(result); - SetupLanguageButton(window, result, false); + SetupLanguageButton(window, result); SetupConnectionType(window, &window->account(), result); AddSkip(result); if (HasUpdate()) { diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index f00224827d5bf3..43194a4bd7e6ea 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -272,8 +272,7 @@ void SetupPowerSavingButton( void SetupLanguageButton( not_null window, - not_null container, - bool icon) { + not_null container) { const auto button = AddButtonWithLabel( container, tr::lng_settings_language(), @@ -282,8 +281,8 @@ void SetupLanguageButton( ) | rpl::then( Lang::GetInstance().idChanges() ) | rpl::map([] { return Lang::GetInstance().nativeName(); }), - icon ? st::settingsButton : st::settingsButtonNoIcon, - { icon ? &st::menuIconTranslate : nullptr }); + st::settingsButton, + { &st::menuIconTranslate }); const auto guard = Ui::CreateChild(button.get()); button->addClickHandler([=] { const auto m = button->clickModifiers(); diff --git a/Telegram/SourceFiles/settings/settings_main.h b/Telegram/SourceFiles/settings/settings_main.h index 4c1529d7d7cdce..10220623d72783 100644 --- a/Telegram/SourceFiles/settings/settings_main.h +++ b/Telegram/SourceFiles/settings/settings_main.h @@ -22,8 +22,7 @@ namespace Settings { void SetupLanguageButton( not_null window, - not_null container, - bool icon = true); + not_null container); bool HasInterfaceScale(); void SetupInterfaceScale( not_null window, From 6def067e984aeafef0487304d82446d97bf41678 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 4 Nov 2023 15:53:51 +0300 Subject: [PATCH 43/70] Updated Qt to 6.2.6 on macOS. --- CMakeLists.txt | 2 +- Telegram/build/prepare/prepare.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b0a772fdbca69..954b9476fa52ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ if (NOT DESKTOP_APP_USE_PACKAGED) if (WIN32) set(qt_version 5.15.10) elseif (APPLE) - set(qt_version 6.2.5) + set(qt_version 6.2.6) endif() endif() include(cmake/external/qt/package.cmake) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index c39d9b3c3456db..e14f26265044ac 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -418,7 +418,7 @@ def runStages(): stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 81a81ffb5a + git checkout f603f4f986 """) stage('msys64', """ @@ -1381,14 +1381,14 @@ def runStages(): """) if buildQt6: - stage('qt_6_2_5', """ + stage('qt_6_2_6', """ mac: - git clone -b v6.2.5-lts-lgpl https://code.qt.io/qt/qt5.git qt_6_2_5 - cd qt_6_2_5 + git clone -b v6.2.6-lts-lgpl https://code.qt.io/qt/qt5.git qt_6_2_6 + cd qt_6_2_6 perl init-repository --module-subset=qtbase,qtimageformats,qtsvg -depends:patches/qtbase_6.2.5/*.patch +depends:patches/qtbase_6.2.6/*.patch cd qtbase - find ../../patches/qtbase_6.2.5 -type f -print0 | sort -z | xargs -0 git apply + find ../../patches/qtbase_6.2.6 -type f -print0 | sort -z | xargs -0 git apply -v cd .. sed -i.bak 's/tqtc-//' {qtimageformats,qtsvg}/dependencies.yaml @@ -1396,7 +1396,7 @@ def runStages(): release: CONFIGURATIONS=-debug-and-release mac: - ./configure -prefix "$USED_PREFIX/Qt-6.2.5" \ + ./configure -prefix "$USED_PREFIX/Qt-6.2.6" \ $CONFIGURATIONS \ -force-debug-info \ -opensource \ From 0a38cf250150e30e58bdd36c73201786f9acb368 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 4 Nov 2023 15:06:00 +0300 Subject: [PATCH 44/70] Updated Qt to 5.15.11 on Windows. --- CMakeLists.txt | 2 +- Telegram/build/prepare/prepare.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 954b9476fa52ac..738d30544e3d62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ include(cmake/options.cmake) if (NOT DESKTOP_APP_USE_PACKAGED) if (WIN32) - set(qt_version 5.15.10) + set(qt_version 5.15.11) elseif (APPLE) set(qt_version 6.2.6) endif() diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index e14f26265044ac..762e87cc0b9612 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1294,23 +1294,23 @@ def runStages(): """) if buildQt5: - stage('qt_5_15_10', """ - git clone https://github.com/qt/qt5.git qt_5_15_10 - cd qt_5_15_10 + stage('qt_5_15_11', """ + git clone https://github.com/qt/qt5.git qt_5_15_11 + cd qt_5_15_11 perl init-repository --module-subset=qtbase,qtimageformats,qtsvg - git checkout v5.15.10-lts-lgpl + git checkout v5.15.11-lts-lgpl git submodule update qtbase qtimageformats qtsvg -depends:patches/qtbase_5.15.10/*.patch +depends:patches/qtbase_5.15.11/*.patch cd qtbase win: - for /r %%i in (..\\..\\patches\\qtbase_5.15.10\\*) do git apply %%i + for /r %%i in (..\\..\\patches\\qtbase_5.15.11\\*) do git apply %%i -v cd .. SET CONFIGURATIONS=-debug release: SET CONFIGURATIONS=-debug-and-release win: - """ + removeDir("\"%LIBS_DIR%\\Qt-5.15.10\"") + """ + """ + removeDir("\"%LIBS_DIR%\\Qt-5.15.11\"") + """ SET ANGLE_DIR=%LIBS_DIR%\\tg_angle SET ANGLE_LIBS_DIR=%ANGLE_DIR%\\out SET MOZJPEG_DIR=%LIBS_DIR%\\mozjpeg @@ -1318,7 +1318,7 @@ def runStages(): SET OPENSSL_LIBS_DIR=%OPENSSL_DIR%\\out SET ZLIB_LIBS_DIR=%LIBS_DIR%\\zlib SET WEBP_DIR=%LIBS_DIR%\\libwebp - configure -prefix "%LIBS_DIR%\\Qt-5.15.10" ^ + configure -prefix "%LIBS_DIR%\\Qt-5.15.11" ^ %CONFIGURATIONS% ^ -force-debug-info ^ -opensource ^ @@ -1353,14 +1353,14 @@ def runStages(): jom -j16 jom -j16 install mac: - find ../../patches/qtbase_5.15.10 -type f -print0 | sort -z | xargs -0 git apply + find ../../patches/qtbase_5.15.11 -type f -print0 | sort -z | xargs -0 git apply cd .. CONFIGURATIONS=-debug release: CONFIGURATIONS=-debug-and-release mac: - ./configure -prefix "$USED_PREFIX/Qt-5.15.10" \ + ./configure -prefix "$USED_PREFIX/Qt-5.15.11" \ $CONFIGURATIONS \ -force-debug-info \ -opensource \ From 855cc8e900fdb23f4ec98e30e4b62a04517c37ec Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 4 Nov 2023 18:44:15 +0300 Subject: [PATCH 45/70] Updated some dependencies. --- Telegram/build/prepare/prepare.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 762e87cc0b9612..24618f3fbaf10c 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -501,7 +501,7 @@ def runStages(): stage('xz', """ !win: - git clone -b v5.2.9 https://git.tukaani.org/xz.git + git clone -b v5.4.5 https://git.tukaani.org/xz.git cd xz sed -i '' '\\@check_symbol_exists(futimens "sys/types.h;sys/stat.h" HAVE_FUTIMENS)@d' CMakeLists.txt CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake -B build . \\ @@ -513,7 +513,7 @@ def runStages(): """) stage('zlib', """ - git clone -b v1.2.11 https://github.com/madler/zlib.git + git clone -b v1.3 https://github.com/madler/zlib.git cd zlib win: cmake . ^ @@ -534,7 +534,7 @@ def runStages(): """) stage('mozjpeg', """ - git clone -b v4.1.3 https://github.com/mozilla/mozjpeg.git + git clone -b v4.1.5 https://github.com/mozilla/mozjpeg.git cd mozjpeg win: cmake . ^ @@ -900,9 +900,8 @@ def runStages(): """) stage('libwebp', """ - git clone https://github.com/webmproject/libwebp.git + git clone -b v1.3.2 https://github.com/webmproject/libwebp.git cd libwebp - git checkout chrome-m116-5845 win: nmake /f Makefile.vc CFG=debug-static OBJDIR=out RTLIBCFG=static all nmake /f Makefile.vc CFG=release-static OBJDIR=out RTLIBCFG=static all From 12e164c4df9c4413a1e3d4f38c71b0108f18a5d2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Nov 2023 09:06:57 +0400 Subject: [PATCH 46/70] Fix multiline terms checkbox. --- Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp | 4 ++-- Telegram/lib_ui | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 5a4bc0a93c0927..9e671a890b5eeb 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -233,7 +233,7 @@ void FillDisclaimerBox(not_null box, Fn done) { tr::lng_mini_apps_disclaimer_link(tr::now), tr::lng_mini_apps_tos_url(tr::now))), Ui::Text::WithEntities), - st::defaultBoxCheckbox, + st::urlAuthCheckbox, std::move(checkView)), { st::boxRowPadding.left(), @@ -241,7 +241,7 @@ void FillDisclaimerBox(not_null box, Fn done) { st::boxRowPadding.right(), 0, }); - row->setAllowTextLines(5); + row->setAllowTextLines(); row->setClickHandlerFilter([=]( const ClickHandlerPtr &link, Qt::MouseButton button) { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 1ae65d2cba7e41..08235c5e06ac56 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 1ae65d2cba7e41f7bb43b490f2d0cc150a958163 +Subproject commit 08235c5e06ac56564157f2856702804a2149d45e From 3b40bc6297d9ba2104767007c777a1a41c839080 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 Nov 2023 13:33:55 +0400 Subject: [PATCH 47/70] Show full reply names with icons. --- .../icons/chat/reply_type_channel.png | Bin 0 -> 277 bytes .../icons/chat/reply_type_channel@2x.png | Bin 0 -> 474 bytes .../icons/chat/reply_type_channel@3x.png | Bin 0 -> 619 bytes .../Resources/icons/chat/reply_type_group.png | Bin 0 -> 324 bytes .../icons/chat/reply_type_group@2x.png | Bin 0 -> 610 bytes .../icons/chat/reply_type_group@3x.png | Bin 0 -> 890 bytes .../Resources/icons/chat/reply_type_user.png | Bin 0 -> 239 bytes .../icons/chat/reply_type_user@2x.png | Bin 0 -> 416 bytes .../icons/chat/reply_type_user@3x.png | Bin 0 -> 554 bytes .../data/stickers/data_custom_emoji.cpp | 47 ++++ .../data/stickers/data_custom_emoji.h | 16 ++ .../history/history_item_components.cpp | 230 +++++++++++------- .../history/history_item_components.h | 16 +- .../history/view/history_view_message.cpp | 6 +- Telegram/SourceFiles/ui/chat/chat.style | 6 + 15 files changed, 226 insertions(+), 95 deletions(-) create mode 100644 Telegram/Resources/icons/chat/reply_type_channel.png create mode 100644 Telegram/Resources/icons/chat/reply_type_channel@2x.png create mode 100644 Telegram/Resources/icons/chat/reply_type_channel@3x.png create mode 100644 Telegram/Resources/icons/chat/reply_type_group.png create mode 100644 Telegram/Resources/icons/chat/reply_type_group@2x.png create mode 100644 Telegram/Resources/icons/chat/reply_type_group@3x.png create mode 100644 Telegram/Resources/icons/chat/reply_type_user.png create mode 100644 Telegram/Resources/icons/chat/reply_type_user@2x.png create mode 100644 Telegram/Resources/icons/chat/reply_type_user@3x.png diff --git a/Telegram/Resources/icons/chat/reply_type_channel.png b/Telegram/Resources/icons/chat/reply_type_channel.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa9e90f8a9f511de6e5823d112727aae6d6b9f6 GIT binary patch literal 277 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1SD^YpWXnZI14-?iy0WWg+Z8+Vb&Z8px|*& z7sn8ZsjdCCe69{6Zu=$mwJv*vL~V_7X^LoYO6RUCTey*-N_t{MyNhvaTUOM}a1R@y zdBJyg{(N_*Sk&f##OhV23g;wByc3@H-1oA|(mUID85k#|MhdV@31V>S*ra3meAcqJ z%q;>t)@iT5v8}d_X~D8gyG+>&sgZHc^Y^`fz01zJ@Z9y%tEGQACNA!Jd}EUiL+o|Q zwu>gc?|(aS9J}%U{34CA%Qok~`%O{_6p0mEy~?U@@sv%ux9gh{CC+`Gv+U(-P4gPY YmG6^!4cndt1HHuH>FVdQ&MBb@0C*j43IG5A literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/reply_type_channel@2x.png b/Telegram/Resources/icons/chat/reply_type_channel@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cb8bb8c2bfca050e092ade54bf5e45490a6a1fa8 GIT binary patch literal 474 zcmV<00VV#4P)TlE1IPP!z|%+)9&fHIYWbfRv3O{sV)-#vt(@_!o3E z^bcqxMt_IF!XhaWi9}*Rtr9;H$~AbeuTN=S`}!I0Ip=fEz4rhfLEM#b9M@ zKG${KW%GPKzgR3NrIgZMujfX=Fifk}x?ZpJo73s!*bI31nHm-|yCLlu}*S1wlxqQg5TiL;z5!R6Zp=DHeIe`$;Y&Mt6<#xL*m&4ma*^YP!xb~OIU;i5mJ#F1&J_-O(YC97K12*$zo*@ z|AEm=VnkR(jFnWRvw=i9*d&sY22t%B`qGH1UwQQ9y>Dr9bI$$Fxoz*g0Q?7dyN4Zrb3~x-|wX+ zfsbj-W^=FClhfSEAZb1f!&mO+m1R0G+O17WF z;ox*S0RSk9zO-H4@5e5eD-;TuOr|G2;W#cXL%ZG1<#Ny4c)aVsfkvY#m&-Iwzth<* zFbt>DY2h@vg=N{nU~oE}#1V(X(P%U>nT&Lj%jKfe>3*)-&1M3l->sg#^7kx0m8*=)9aK3^)8gpXD! z5Cn-g&O55r>I)MoBo>Q3ly$q^&&vP+{eE96Td&t&k)bFmzH^byX7lPiiq12NqMFU7 zbnsP~Qmj_%YPGuU?7Md)1VK8T&UU-q?RNEg{fnl5k6+r|qW@c4yHfxF002ovPDHLk FV1g_XB@6%n literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/reply_type_group.png b/Telegram/Resources/icons/chat/reply_type_group.png new file mode 100644 index 0000000000000000000000000000000000000000..b40ba4b8140488abcc9a57be4ee43489d69ce6d3 GIT binary patch literal 324 zcmV-K0lWT*P)PbKDXUZIp@3c{ch*%0Q@Vy?=QKlFiF8Q5eQ&##26~DPv(_VZp}78f8J)C@KB{E7|(` z6C|ZjVrQk4GAm`pLdk+iv6=`|cE;x<<)e8Q^EM6UeHu>|&*Hr{=X9?7T)%U79{~8T zUn>ZL!r^eg-=`>QJRT>LNvTx&v2+jw8;u72sL5m^lgT~<2!%p4pE8*Yx6JK!7mLM8 zr4o%sVHkb`P_Nf{0XUAcHTis=Yt83#rBW#r(ChVh0sH-)DX?0tuWqSSdX0j30mI?& z`*VxM^6Hw+W}zKuG#ZMco*sC=-|cogQ{eGV0GG=}5CnU9x7!5*0oH{e zsM&0C(@&?9(P(_l0l#6uFl@8gbUNK~x$Ja0BuR2zu~;07#k^jxTrM9B28l#sI-S1J z!KcLEfm*G0I-PpGUZqk=Bod0E2!a@mM(uX{cs#OY5CkCza=Bd2=d&Q1R;$fsvpA0P z{!yByuh(n0+l@pb2!a3r0K+hbVKhx|Hk($f6_3Xa1_OVzNF++7Qn%afU(rumuh%}G zk7=S-t8uMPr(-gio&o7}T3|G*+wJCXH~;`ZQIsS}u64Owyk74EV7uME3!rJbTrM-$ ztJUh0HJi=;&WTx26cse3M=2BvX0uwYUdQPLgCUmt*#7wfZSN7!3a4#lKR20DKN8A~ilfw*UYD07*qoM6N<$g3cxgxBvhE literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/reply_type_group@3x.png b/Telegram/Resources/icons/chat/reply_type_group@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..11dd58c192309892ab77312c0e7a00e1f2f0db9f GIT binary patch literal 890 zcmV-=1BLvFP)wKYOvTkwIv6rA2s=bUikIjQc}VD#M1);C z1=%sWBxE86ftTnI)FF^UBIpn(Ds&5qZV?q)SYlH4N79OIIiC)}9J)<=x9UCkIXz?F z=l6Wr^X%C(iKIr28vhMet2Gb^q|<4FAQFj$&*$sx?ED)QjYhMxvjfT?NpfLfp{}m( zS8b$H>A}H4xeblm?fzAvxw$zW8vsB)pYQ4EDenkDP+wo4-EMceT;t>8EiEk~*vMqE zOeVuG1OV9G-7V>8G#ZbNj!NctX=w>T5J5r)gQ21g03e-CGkdkQwPmwe*5vl~wjd$1 z*<6hfNs`Q7{A*7bg~rna$?%yjrcky1HT&ayp$tZD=qIo0*yM`~AUSaARX* zVq!w6RC0yYYW2#>NbRT7EB=;&z0Q6SNL*-$E#9UUFb&CO?LXV=%)0DvDI?nkKV z2_Xp5-rla)>oE*NQ51rpTrQVNr7kZo&(F_aUtfPi2!>&k$uu}PXt7v&dwb<_Icx0W zn6}J@}$GKw6b8S)R{{CKcAyD17f*=SMA&0{uuFdW3Z7!FiFB=;hxssck zn{>PQkGHuo#*Q{ByVRb=q@o#=+^Za`*(~_Dtx3siu*c8aX&VGDj z^6`KF|NsB}{r#3LCUf$Vl9Dc6zC3x#l$HWXNl8T>F^;7R7Ya&AW##7TDk^?_d3m{Z z_`00CyGn0w%jIB}l9vAe=jUg6W*Pgs7zYQ2RWH83zdu7uLBYgCBgTe~DWM4faz$S! literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/reply_type_user@2x.png b/Telegram/Resources/icons/chat/reply_type_user@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8ef54da954090e36d0c4c93727f71a01ffd9b261 GIT binary patch literal 416 zcmV;R0bl-!P)F0pLHs353wHtmE;B zF%H9UwOVz%U1o*jxZQ3?zUOke2!g-{nWjnFve|6dAm8^}A%swCKcqo}!GNhwg-)jv zQ=OX8XoN9t+^a0h$z;L?0RU#RSrkRJa=+gfiv_Kj``HpjQPVV4Rd2W3e!owWgn1m+ zP$wD=hq|th$75NRJDm>3IEte4`5Xkn>jQ0pTigyeaCI2@MC zWv|x*0EnX4BzT^;ZM!H68qr7`$MgBTWtR1N{UTAPD2nNH+Jw?HWrYYKp67jitQzX~ z`}BW8jU-7;=z6`r0cBbCx%j9{hGDp_E6eihN|Geo?e>l1|0GXvJYv0%C5}G;0000< KMNUMnLSTYX9=Kfq literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/reply_type_user@3x.png b/Telegram/Resources/icons/chat/reply_type_user@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..67a947f36d87a651e119f5bd42f9f0dd0d3ececa GIT binary patch literal 554 zcmV+_0@eMAP)Kl|75XKoCF^V;aE^ECgAwvJosS6vWD3pp}KamG**& z7(uW~Z((O+Ay^24O+dllpr{~-1QW%85i~*ED}+1sh`S-%+~KQI4D)7ob~6<9;ltk~ zolXx116h_eOOdaYKo+wH_m48u$&li#Lq>h(G?S-D(( zV}cM~uh&E(LBPbatfDCIWe~z%uSY-@iA0bl2%#*?$W%6DgvaB-sR<_xEfYl%M}`xI zmd)q$)oO(d8xDs!HK9qRQtt zu~;ONN!%o+lmLKiHXDsb-EOxeNu$xI)9IYgXVbO+H*k;9;c$dPpc9f*r*J{^!t4)nnwgdU|H5sR;^Y^neOmBZz!A1 zW@KeAtKjMCLkO`3YX+CgrK;-v8@*yILeoF}Jjopg9^TaM$b=lSr$7yZvKCDqe*xc~qF07*qoM6N<$g1RR1%m4rY literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 54b16fec6ba4d2..88f6a0785b46e8 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -89,6 +89,10 @@ class CallbackListener final : public CustomEmojiManager::Listener { : FrameSizeFromTag(tag); } +[[nodiscard]] QString InternalPrefix() { + return u"internal:"_q; +} + } // namespace class CustomEmojiLoader final @@ -514,6 +518,9 @@ std::unique_ptr CustomEmojiManager::create( Fn update, SizeTag tag, int sizeOverride) { + if (data.startsWith(InternalPrefix())) { + return internal(data); + } const auto parsed = ParseCustomEmojiData(data); return parsed ? create(parsed, std::move(update), tag, sizeOverride) @@ -540,6 +547,18 @@ std::unique_ptr CustomEmojiManager::create( }); } +std::unique_ptr CustomEmojiManager::internal( + QStringView data) { + const auto index = data.mid(InternalPrefix().size()).toInt(); + Assert(index >= 0 && index < _internalEmoji.size()); + + auto &info = _internalEmoji[index]; + return std::make_unique( + data.toString(), + info.image, + info.textColor); +} + void CustomEmojiManager::resolve( QStringView data, not_null listener) { @@ -885,6 +904,34 @@ uint64 CustomEmojiManager::coloredSetId() const { return _coloredSetId; } +QString CustomEmojiManager::registerInternalEmoji( + QImage emoji, + bool textColor) { + _internalEmoji.push_back({ std::move(emoji), textColor }); + return InternalPrefix() + QString::number(_internalEmoji.size() - 1); +} + +QString CustomEmojiManager::registerInternalEmoji( + const style::icon &icon, + bool textColor) { + const auto i = _iconEmoji.find(&icon); + if (i != end(_iconEmoji)) { + return i->second; + } + auto image = QImage( + icon.size() * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + image.setDevicePixelRatio(style::DevicePixelRatio()); + auto p = QPainter(&image); + icon.paint(p, 0, 0, icon.width()); + p.end(); + + const auto result = registerInternalEmoji(std::move(image), textColor); + _iconEmoji.emplace(&icon, result); + return result; +} + int FrameSizeFromTag(SizeTag tag) { const auto emoji = EmojiSizeFromTag(tag); const auto factor = style::DevicePixelRatio(); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 4c79f8870b43c4..01eb1fd79bd054 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -83,11 +83,22 @@ class CustomEmojiManager final : public base::has_weak_ptr { [[nodiscard]] Main::Session &session() const; [[nodiscard]] Session &owner() const; + [[nodiscard]] QString registerInternalEmoji( + QImage emoji, + bool textColor = true); + [[nodiscard]] QString registerInternalEmoji( + const style::icon &icon, + bool textColor = true); + [[nodiscard]] uint64 coloredSetId() const; private: static constexpr auto kSizeCount = int(SizeTag::kCount); + struct InternalEmojiData { + QImage image; + bool textColor = true; + }; struct RepaintBunch { crl::time when = 0; std::vector> instances; @@ -131,6 +142,8 @@ class CustomEmojiManager final : public base::has_weak_ptr { SizeTag tag, int sizeOverride, LoaderFactory factory); + [[nodiscard]] std::unique_ptr internal( + QStringView data); [[nodiscard]] static int SizeIndex(SizeTag tag); const not_null _owner; @@ -163,6 +176,9 @@ class CustomEmojiManager final : public base::has_weak_ptr { bool _repaintTimerScheduled = false; bool _requestSetsScheduled = false; + std::vector _internalEmoji; + base::flat_map, QString> _iconEmoji; + #if 0 // inject-to-on_main crl::time _repaintsLastAdded = 0; rpl::lifetime _repaintsLifetime; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 5dfadd9f64dae7..e331703aeb601e 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -477,7 +477,6 @@ HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply::~HistoryMessageReply() { // clearData() should be called by holder. Expects(resolvedMessage.empty()); - Expects(originalVia == nullptr); } bool HistoryMessageReply::updateData( @@ -523,44 +522,42 @@ bool HistoryMessageReply::updateData( } } + const auto repaint = [=] { holder->customEmojiRepaint(); }; + const auto context = Core::MarkedTextContext{ + .session = &holder->history()->session(), + .customEmojiRepaint = repaint, + }; const auto external = this->external(); - if (resolvedMessage + _multiline = 0; + // #TODO !_fields.storyId && (external || !_fields.quote.empty()); + + const auto displaying = resolvedMessage || resolvedStory - || (external && (!_fields.messageId || force))) { - const auto repaint = [=] { holder->customEmojiRepaint(); }; - const auto context = Core::MarkedTextContext{ - .session = &holder->history()->session(), - .customEmojiRepaint = repaint, - }; - const auto text = !_fields.quote.empty() - ? _fields.quote - : resolvedMessage - ? resolvedMessage->inReplyText() - : resolvedStory - ? resolvedStory->inReplyText() - : TextWithEntities{ u"..."_q }; - _text.setMarkedText( - st::defaultTextStyle, - text, - Ui::DialogTextOptions(), - context); - - updateName(holder); - setLinkFrom(holder); - if (resolvedMessage - && !resolvedMessage->Has()) { - if (const auto bot = resolvedMessage->viaBot()) { - originalVia = std::make_unique(); - originalVia->create( - &holder->history()->owner(), - peerToUser(bot->id)); - } - } + || (external && (!_fields.messageId || force)); + _displaying = displaying ? 1 : 0; - if (!resolvedMessage && !resolvedStory) { - _unavailable = 1; - } + const auto unavailable = !resolvedMessage + && !resolvedStory + && ((!_fields.storyId && !_fields.messageId) || force); + _unavailable = unavailable ? 1 : 0; + + const auto text = !_fields.quote.empty() + ? _fields.quote + : resolvedMessage + ? resolvedMessage->inReplyText() + : resolvedStory + ? resolvedStory->inReplyText() + : TextWithEntities{ u"..."_q }; + _text.setMarkedText( + st::defaultTextStyle, + text, + Ui::DialogTextOptions(), + context); + updateName(holder); + + if (_displaying) { + setLinkFrom(holder); const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; @@ -642,7 +639,6 @@ void HistoryMessageReply::setTopMessageId(MsgId topMessageId) { } void HistoryMessageReply::clearData(not_null holder) { - originalVia = nullptr; if (resolvedMessage) { holder->history()->owner().unregisterDependentMessage( holder, @@ -658,6 +654,12 @@ void HistoryMessageReply::clearData(not_null holder) { _name.clear(); _text.clear(); _unavailable = 1; + _displaying = 0; + _expandable = 0; + if (_multiline) { + holder->history()->owner().requestItemResize(holder); + _multiline = 0; + } refreshReplyToMedia(); } @@ -691,9 +693,10 @@ PeerData *HistoryMessageReply::sender(not_null holder) const { } QString HistoryMessageReply::senderName( - not_null holder) const { + not_null holder, + bool shorten) const { if (const auto peer = sender(holder)) { - return senderName(peer); + return senderName(peer, shorten); } else if (!resolvedMessage) { return _fields.externalSenderName; } else if (holder->Has()) { @@ -708,11 +711,11 @@ QString HistoryMessageReply::senderName( return QString(); } -QString HistoryMessageReply::senderName(not_null peer) const { - if (const auto user = originalVia ? peer->asUser() : nullptr) { - return user->firstName; - } - return peer->name(); +QString HistoryMessageReply::senderName( + not_null peer, + bool shorten) const { + const auto user = shorten ? peer->asUser() : nullptr; + return user ? user->firstName : peer->name(); } bool HistoryMessageReply::isNameUpdated( @@ -729,47 +732,101 @@ bool HistoryMessageReply::isNameUpdated( void HistoryMessageReply::updateName( not_null holder, std::optional resolvedSender) const { - const auto peer = resolvedSender.value_or(sender(holder)); - const auto name = peer ? senderName(peer) : senderName(holder); + auto viaBotUsername = QString(); + if (resolvedMessage + && !resolvedMessage->Has()) { + if (const auto bot = resolvedMessage->viaBot()) { + viaBotUsername = bot->username(); + } + } + const auto sender = resolvedSender.value_or(this->sender(holder)); + const auto externalPeer = _fields.externalPeerId + ? holder->history()->owner().peer(_fields.externalPeerId).get() + : nullptr; + const auto groupNameAdded = (externalPeer && externalPeer != sender); + const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded; + const auto name = sender + ? senderName(sender, shorten) + : senderName(holder, shorten); const auto hasPreview = (resolvedStory && resolvedStory->hasReplyPreview()) || (resolvedMessage && resolvedMessage->media() && resolvedMessage->media()->hasReplyPreview()); - const auto textLeft = hasPreview - ? (st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right()) - : st::historyReplyPadding.left(); - if (!name.isEmpty()) { - _name.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); - if (peer) { - _nameVersion = peer->nameVersion(); - } - const auto w = _name.maxWidth() - + (originalVia - ? (st::msgServiceFont->spacew + originalVia->maxWidth) - : 0) - + (_fields.quote.empty() - ? 0 - : st::messageTextStyle.blockquote.icon.width()); - _maxWidth = std::max( - w, - std::min(_text.maxWidth(), st::maxSignatureSize)) - + (_fields.storyId - ? (st::dialogsMiniReplyStory.skipText - + st::dialogsMiniReplyStory.icon.icon.width()) - : 0); - } else { - _maxWidth = st::msgDateFont->width(statePhrase()); + const auto previewSkip = st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left(); + const auto peerIcon = [](PeerData *peer) { + return !peer + ? &st::historyReplyUser + : peer->isBroadcast() + ? &st::historyReplyChannel + : (peer->isChannel() || peer->isChat()) + ? &st::historyReplyGroup + : &st::historyReplyUser; + }; + const auto peerEmoji = [&](PeerData *peer) { + const auto owner = &holder->history()->owner(); + return Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().registerInternalEmoji( + *peerIcon(peer))); + }; + auto nameFull = TextWithEntities(); + if (!groupNameAdded && !_fields.storyId) { + nameFull.append(peerEmoji(sender)); } - _maxWidth = textLeft + nameFull.append(name); + if (groupNameAdded) { + nameFull.append(peerEmoji(externalPeer)); + nameFull.append(externalPeer->name()); + } + if (!viaBotUsername.isEmpty()) { + nameFull.append(u" @"_q).append(viaBotUsername); + } + const auto context = Core::MarkedTextContext{ + .session = &holder->history()->session(), + .customEmojiRepaint = [] {}, + .customEmojiLoopLimit = 1, + }; + _name.setMarkedText( + st::fwdTextStyle, + nameFull, + Ui::NameTextOptions(), + context); + if (sender) { + _nameVersion = sender->nameVersion(); + } + const auto nameMaxWidth = previewSkip + + _name.maxWidth() + + (hasQuoteIcon() + ? st::messageTextStyle.blockquote.icon.width() + : 0); + const auto storySkip = _fields.storyId + ? (st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width()) + : 0; + const auto optimalTextSize = _multiline + ? countMultilineOptimalSize(previewSkip) + : QSize( + (previewSkip + + storySkip + + std::min(_text.maxWidth(), st::maxSignatureSize)), + st::normalFont->height); + _maxWidth = std::max(nameMaxWidth, optimalTextSize.width()); + if (!_displaying) { + const auto phraseWidth = st::msgDateFont->width(statePhrase()); + _maxWidth = _unavailable + ? phraseWidth + : std::max(_maxWidth, phraseWidth); + } + _maxWidth = st::historyReplyPadding.left() + _maxWidth + st::historyReplyPadding.right(); _minHeight = st::historyReplyPadding.top() + st::msgServiceNameFont->height - + st::normalFont->height + + optimalTextSize.height() + st::historyReplyPadding.bottom(); } @@ -785,17 +842,11 @@ int HistoryMessageReply::resizeToWidth(int width) const { + st::historyReplyPreview + st::historyReplyPreviewMargin.right()) : st::historyReplyPadding.left(); - if (originalVia) { - originalVia->resize(width - - textLeft - - st::historyReplyPadding.right() - - _name.maxWidth() - - st::msgServiceFont->spacew); - } - if (width >= _maxWidth) { + if (width >= _maxWidth || !_multiline) { _height = _minHeight; return height(); } + // #TODO _height = _minHeight; return height(); } @@ -826,6 +877,15 @@ void HistoryMessageReply::storyRemoved( } } +bool HistoryMessageReply::hasQuoteIcon() const { + return _fields.manualQuote && !_fields.quote.empty(); +} + +QSize HistoryMessageReply::countMultilineOptimalSize( + int firstLineSkip) const { + return QSize(); // #TODO +} + void HistoryMessageReply::paint( Painter &p, not_null holder, @@ -839,7 +899,7 @@ void HistoryMessageReply::paint( y += st::historyReplyTop; const auto rect = QRect(x, y, w, _height); - const auto hasQuote = _fields.manualQuote && !_fields.quote.empty(); + const auto hasQuote = hasQuoteIcon(); const auto selected = context.selected(); const auto colorPeer = resolvedMessage ? resolvedMessage->displayFrom() @@ -969,10 +1029,6 @@ void HistoryMessageReply::paint( ? FromNameFg(context, colorIndexPlusOne - 1) : stm->msgServiceFg->c); _name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); - if (originalVia && w > _name.maxWidth() + st::msgServiceFont->spacew) { - p.setFont(st::msgServiceFont); - p.drawText(x + textLeft + _name.maxWidth() + st::msgServiceFont->spacew, y + st::historyReplyPadding.top() + st::msgServiceFont->ascent, originalVia->text); - } p.setPen(inBubble ? stm->historyTextFg diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index c250e91fbfc22e..55c4e149bb3a34 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -276,8 +276,12 @@ struct HistoryMessageReply [[nodiscard]] bool external() const; [[nodiscard]] PeerData *sender(not_null holder) const; - [[nodiscard]] QString senderName(not_null holder) const; - [[nodiscard]] QString senderName(not_null peer) const; + [[nodiscard]] QString senderName( + not_null holder, + bool shorten) const; + [[nodiscard]] QString senderName( + not_null peer, + bool shorten) const; [[nodiscard]] bool isNameUpdated(not_null holder) const; void updateName( not_null holder, @@ -340,7 +344,6 @@ struct HistoryMessageReply WebPageId replyToWebPageId = 0; ReplyToMessagePointer resolvedMessage; ReplyToStoryPointer resolvedStory; - std::unique_ptr originalVia; std::unique_ptr spoiler; struct { @@ -349,6 +352,10 @@ struct HistoryMessageReply } ripple; private: + [[nodiscard]] bool hasQuoteIcon() const; + [[nodiscard]] QSize countMultilineOptimalSize( + int firstLineSkip) const; + ReplyFields _fields; ClickHandlerPtr _link; mutable Ui::Text::String _name; @@ -359,6 +366,9 @@ struct HistoryMessageReply mutable int _height = 0; mutable int _nameVersion = 0; uint8 _unavailable : 1 = 0; + uint8 _displaying : 1 = 0; + uint8 _multiline : 1 = 0; + uint8 _expandable : 1 = 0; }; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index f5a6d655da34a8..8337021010fe1d 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -771,13 +771,9 @@ QSize Message::performCountOptimalSize() { accumulate_max(maxWidth, namew); } if (reply) { - auto replyw = st::msgPadding.left() + const auto replyw = st::msgPadding.left() + reply->maxWidth() + st::msgPadding.right(); - if (reply->originalVia) { - replyw += st::msgServiceFont->spacew - + reply->originalVia->maxWidth; - } accumulate_max(maxWidth, replyw); } if (entry) { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 70018cad183040..d4f189ac0075b4 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -32,6 +32,12 @@ historyReplyBottom: 2px; historyReplyPreview: 32px; historyReplyPreviewMargin: margins(7px, 4px, 4px, 4px); historyReplyPadding: margins(11px, 2px, 6px, 2px); +historyReplyUser: icon {{ "chat/reply_type_user", windowFg }}; +historyReplyUserPadding: margins(0px, 4px, 4px, 0px); +historyReplyGroup: icon {{ "chat/reply_type_group", windowFg }}; +historyReplyGroupPadding: margins(0px, 4px, 4px, 0px); +historyReplyChannel: icon {{ "chat/reply_type_channel", windowFg }}; +historyReplyChannelPadding: margins(0px, 5px, 4px, 0px); msgReplyPadding: margins(6px, 6px, 11px, 6px); msgReplyBarPos: point(1px, 0px); From 5c98406e1a88df5c00e621da90a302ce4259ff65 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 Nov 2023 20:20:04 +0400 Subject: [PATCH 48/70] Expandable quote snippets in replies. --- .../boxes/peers/edit_forum_topic_box.cpp | 5 + .../history/history_item_components.cpp | 220 +++++++++++++----- .../history/history_item_components.h | 15 +- .../history_view_reactions_selector.cpp | 5 + 4 files changed, 188 insertions(+), 57 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 5caa2ce5c8cc20..47674bd5ef6673 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -55,6 +55,7 @@ class DefaultIconEmoji final : public Ui::Text::CustomEmoji { rpl::producer value, Fn repaint); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; @@ -80,6 +81,10 @@ DefaultIconEmoji::DefaultIconEmoji( }, _lifetime); } +int DefaultIconEmoji::width() { + return st::emojiSize + 2 * st::emojiPadding; +} + QString DefaultIconEmoji::entityData() { return u"topic_icon:%1"_q.arg(_icon.colorId); } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index e331703aeb601e..758590db90cfcb 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -33,6 +33,7 @@ For license and copyright information please follow this link: #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" #include "data/stickers/data_custom_emoji.h" +#include "data/data_channel.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "data/data_user.h" @@ -54,6 +55,7 @@ For license and copyright information please follow this link: namespace { +constexpr auto kNonExpandedLinesLimit = 5; const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; void ValidateBackgroundEmoji( @@ -469,7 +471,10 @@ FullReplyTo ReplyToFromMTP( }); } -HistoryMessageReply::HistoryMessageReply() = default; +HistoryMessageReply::HistoryMessageReply() +: _name(st::maxSignatureSize / 2) +, _text(st::maxSignatureSize / 2) { +} HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply &&other) = default; @@ -528,8 +533,7 @@ bool HistoryMessageReply::updateData( .customEmojiRepaint = repaint, }; const auto external = this->external(); - _multiline = 0; - // #TODO !_fields.storyId && (external || !_fields.quote.empty()); + _multiline = !_fields.storyId && (external || !_fields.quote.empty()); const auto displaying = resolvedMessage || resolvedStory @@ -607,29 +611,70 @@ void HistoryMessageReply::updateFields( } } +bool HistoryMessageReply::expand() { + if (!_expandable || _expanded) { + return false; + } + _expanded = true; + return true; +} + void HistoryMessageReply::setLinkFrom( not_null holder) { - const auto externalPeerId = _fields.externalSenderId; - const auto external = externalPeerId - || !_fields.externalSenderName.isEmpty(); + const auto externalChannelId = peerToChannel(_fields.externalPeerId); + const auto messageId = _fields.messageId; + const auto quote = _fields.manualQuote + ? _fields.quote + : TextWithEntities(); + const auto returnToId = holder->fullId(); const auto externalLink = [=](ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { - if (externalPeerId) { - controller->showPeerInfo( - controller->session().data().peer(externalPeerId)); + auto error = QString(); + const auto owner = &controller->session().data(); + if (const auto item = owner->message(returnToId)) { + if (const auto reply = item->Get()) { + if (reply->expand()) { + owner->requestItemResize(item); + return; + } + } + } + if (externalChannelId) { + const auto channel = owner->channel(externalChannelId); + if (!channel->isForbidden()) { + if (messageId) { + JumpToMessageClickHandler( + channel, + messageId, + returnToId, + quote + )->onClick(context); + } else { + controller->showPeerInfo(channel); + } + } else if (channel->isBroadcast()) { + error = tr::lng_channel_not_accessible(tr::now); + } else { + error = tr::lng_group_not_accessible(tr::now); + } + } else { + error = tr::lng_reply_from_private_chat(tr::now); + } + if (!error.isEmpty()) { + controller->showToast(error); } - controller->showToast(tr::lng_reply_from_private_chat(tr::now)); } }; _link = resolvedMessage ? JumpToMessageClickHandler( resolvedMessage.get(), - holder->fullId(), - _fields.manualQuote ? _fields.quote : TextWithEntities()) + returnToId, + quote) : resolvedStory ? JumpToStoryClickHandler(resolvedStory.get()) - : (external && !_fields.messageId) + : (external() + && (!_fields.messageId || (_unavailable && externalChannelId))) ? std::make_shared(externalLink) : nullptr; } @@ -753,11 +798,13 @@ void HistoryMessageReply::updateName( || (resolvedMessage && resolvedMessage->media() && resolvedMessage->media()->hasReplyPreview()); - const auto previewSkip = st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right() - - st::historyReplyPadding.left(); + const auto previewSkip = hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; const auto peerIcon = [](PeerData *peer) { return !peer ? &st::historyReplyUser @@ -774,7 +821,7 @@ void HistoryMessageReply::updateName( *peerIcon(peer))); }; auto nameFull = TextWithEntities(); - if (!groupNameAdded && !_fields.storyId) { + if (!groupNameAdded && external() && !_fields.storyId) { nameFull.append(peerEmoji(sender)); } nameFull.append(name); @@ -836,23 +883,61 @@ int HistoryMessageReply::resizeToWidth(int width) const { || (resolvedMessage && resolvedMessage->media() && resolvedMessage->media()->hasReplyPreview()); - const auto textLeft = hasPreview + const auto previewSkip = hasPreview ? (st::messageQuoteStyle.outline + st::historyReplyPreviewMargin.left() + st::historyReplyPreview - + st::historyReplyPreviewMargin.right()) - : st::historyReplyPadding.left(); + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; if (width >= _maxWidth || !_multiline) { + _nameTwoLines = 0; + _expandable = 0; _height = _minHeight; return height(); } - // #TODO - _height = _minHeight; + const auto innerw = width + - st::historyReplyPadding.left() + - st::historyReplyPadding.right(); + const auto namew = innerw - previewSkip; + const auto desiredNameHeight = _name.countHeight(namew); + _nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0; + const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height; + const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; + auto lineCounter = 0; + auto elided = false; + const auto texth = _text.countDimensions( + textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height; + _expandable = (_multiline && elided) ? 1 : 0; + _height = st::historyReplyPadding.top() + + nameh + + (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth) + + st::historyReplyPadding.bottom(); return height(); } +Ui::Text::GeometryDescriptor HistoryMessageReply::textGeometry( + int available, + int firstLineSkip, + not_null line, + not_null outElided) const { + return { .layout = [=](Ui::Text::LineGeometry in) { + const auto skip = (*line ? 0 : firstLineSkip); + ++*line; + *outElided = *outElided + || !_multiline + || (!_expanded + && (*line == kNonExpandedLinesLimit) + && in.width > available - skip); + in.width = available - skip; + in.left += skip; + in.elided = *outElided; + return in; + } }; +} + int HistoryMessageReply::height() const { - return _height + st::historyReplyTop + st::historyReplyBottom; + return _height + st::historyReplyTop + st::historyReplyBottom; } QMargins HistoryMessageReply::margins() const { @@ -882,8 +967,13 @@ bool HistoryMessageReply::hasQuoteIcon() const { } QSize HistoryMessageReply::countMultilineOptimalSize( - int firstLineSkip) const { - return QSize(); // #TODO + int previewSkip) const { + auto elided = false; + auto lineCounter = 0; + const auto max = previewSkip + _text.maxWidth(); + const auto result = _text.countDimensions( + textGeometry(max, previewSkip, &lineCounter, &elided)); + return { result.width, result.height }; } void HistoryMessageReply::paint( @@ -972,23 +1062,33 @@ void HistoryMessageReply::paint( } } - const auto withPreviewLeft = st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right(); - auto textLeft = st::historyReplyPadding.left(); + auto hasPreview = (resolvedStory + && resolvedStory->hasReplyPreview()) + || (resolvedMessage + && resolvedMessage->media() + && resolvedMessage->media()->hasReplyPreview()); + auto previewSkip = hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; + if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) { + hasPreview = false; + previewSkip = 0; + } + const auto pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler); - if (w > textLeft) { + auto textLeft = x + st::historyReplyPadding.left(); + auto textTop = y + + st::historyReplyPadding.top() + + (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1)); + if (w > st::historyReplyPadding.left()) { if (resolvedMessage || resolvedStory || !_text.isEmpty()) { const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; - auto hasPreview = (media && media->hasReplyPreview()) - || (resolvedStory && resolvedStory->hasReplyPreview()); - if (hasPreview && w <= withPreviewLeft) { - hasPreview = false; - } if (hasPreview) { - textLeft = withPreviewLeft; const auto image = media ? media->replyPreview() : resolvedStory->replyPreview(); @@ -1021,22 +1121,29 @@ void HistoryMessageReply::paint( } } } - if (w > textLeft + st::historyReplyPadding.right()) { - w -= textLeft + st::historyReplyPadding.right(); + const auto textw = w + - st::historyReplyPadding.left() + - st::historyReplyPadding.right(); + const auto namew = textw - previewSkip; + auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; + if (namew > 0) { p.setPen(!inBubble ? st->msgImgReplyBarColor()->c : useColorIndex ? FromNameFg(context, colorIndexPlusOne - 1) : stm->msgServiceFg->c); - _name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); + _name.drawLeftElided( + p, + x + st::historyReplyPadding.left() + previewSkip, + y + st::historyReplyPadding.top(), + namew, + w + 2 * x, + _nameTwoLines ? 2 : 1); p.setPen(inBubble ? stm->historyTextFg : st->msgImgReplyBarColor()); holder->prepareCustomEmojiPaint(p, context, _text); - auto replyToTextPosition = QPoint( - x + textLeft, - y + st::historyReplyPadding.top() + st::msgServiceNameFont->height); auto replyToTextPalette = &(!inBubble ? st->imgReplyTextPalette() : useColorIndex @@ -1045,13 +1152,12 @@ void HistoryMessageReply::paint( if (_fields.storyId) { st::dialogsMiniReplyStory.icon.icon.paint( p, - replyToTextPosition, - w + 2 * x + 2 * textLeft, + textLeft + firstLineSkip, + textTop, + w + 2 * x, replyToTextPalette->linkFg->c); - replyToTextPosition += QPoint( - st::dialogsMiniReplyStory.skipText - + st::dialogsMiniReplyStory.icon.icon.width(), - 0); + firstLineSkip += st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width(); } auto owned = std::optional(); auto copy = std::optional(); @@ -1061,9 +1167,11 @@ void HistoryMessageReply::paint( copy->linkFg = owned->color(); replyToTextPalette = &*copy; } + auto l = 0; + auto e = false; _text.draw(p, { - .position = replyToTextPosition, - .availableWidth = w, + .position = { textLeft, textTop }, + .geometry = textGeometry(textw, firstLineSkip, &l, &e), .palette = replyToTextPalette, .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, @@ -1078,9 +1186,9 @@ void HistoryMessageReply::paint( p.setFont(st::msgDateFont); p.setPen(cache->icon); p.drawTextLeft( - x + textLeft, - (y + (_height - st::msgDateFont->height) / 2), - w + 2 * x + 2 * textLeft, + textLeft, + y + st::historyReplyPadding.top() + (st::msgDateFont->height / 2), + w + 2 * x, st::msgDateFont->elided( statePhrase(), w - textLeft - st::historyReplyPadding.right())); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 55c4e149bb3a34..e4159106fe93e2 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -25,6 +25,10 @@ struct PeerUserpicView; class SpoilerAnimation; } // namespace Ui +namespace Ui::Text { +struct GeometryDescriptor; +} // namespace Ui::Text + namespace Data { class Session; class Story; @@ -296,6 +300,8 @@ struct HistoryMessageReply not_null holder, not_null removed); + bool expand(); + void paint( Painter &p, not_null holder, @@ -353,6 +359,11 @@ struct HistoryMessageReply private: [[nodiscard]] bool hasQuoteIcon() const; + [[nodiscard]] Ui::Text::GeometryDescriptor textGeometry( + int available, + int firstLineSkip, + not_null line, + not_null outElided) const; [[nodiscard]] QSize countMultilineOptimalSize( int firstLineSkip) const; @@ -368,7 +379,9 @@ struct HistoryMessageReply uint8 _unavailable : 1 = 0; uint8 _displaying : 1 = 0; uint8 _multiline : 1 = 0; - uint8 _expandable : 1 = 0; + mutable uint8 _expandable : 1 = 0; + uint8 _expanded : 1 = 0; + mutable uint8 _nameTwoLines : 1 = 0; }; 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 13570306b7a590..3571a9e63c826c 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -47,6 +47,7 @@ class StripEmoji final : public Ui::Text::CustomEmoji { QPoint shift, int index); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; void unload() override; @@ -73,6 +74,10 @@ StripEmoji::StripEmoji( , _index(index) { } +int StripEmoji::width() { + return _wrapped->width(); +} + QString StripEmoji::entityData() { return _wrapped->entityData(); } From aab4ac85269c6b41dc0b1c9773e4d7c4a1325d02 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Nov 2023 09:38:43 +0400 Subject: [PATCH 49/70] Fix story replies. --- .../SourceFiles/history/history_item_components.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 758590db90cfcb..5ae8652af263f7 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -537,7 +537,7 @@ bool HistoryMessageReply::updateData( const auto displaying = resolvedMessage || resolvedStory - || (external && (!_fields.messageId || force)); + || (!_fields.quote.empty() && (!_fields.messageId || force)); _displaying = displaying ? 1 : 0; const auto unavailable = !resolvedMessage @@ -581,7 +581,7 @@ bool HistoryMessageReply::updateData( } return resolvedMessage || resolvedStory - || (external && !_fields.messageId) + || (external && !_fields.messageId && !_fields.storyId) || _unavailable; } @@ -1086,7 +1086,7 @@ void HistoryMessageReply::paint( + st::historyReplyPadding.top() + (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1)); if (w > st::historyReplyPadding.left()) { - if (resolvedMessage || resolvedStory || !_text.isEmpty()) { + if (_displaying) { const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; if (hasPreview) { const auto image = media @@ -1187,11 +1187,13 @@ void HistoryMessageReply::paint( p.setPen(cache->icon); p.drawTextLeft( textLeft, - y + st::historyReplyPadding.top() + (st::msgDateFont->height / 2), + (y + + st::historyReplyPadding.top() + + (st::msgDateFont->height / 2)), w + 2 * x, st::msgDateFont->elided( statePhrase(), - w - textLeft - st::historyReplyPadding.right())); + x + w - textLeft - st::historyReplyPadding.right())); } } } From 56ad8256932680da7e0cab8907fa7424828c857f Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Nov 2023 10:16:30 +0400 Subject: [PATCH 50/70] Attempt to fix build on Clang. --- Telegram/SourceFiles/mtproto/core_types.h | 13 +++++++++++++ Telegram/SourceFiles/payments/payments_form.cpp | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/mtproto/core_types.h b/Telegram/SourceFiles/mtproto/core_types.h index 05aafa69aedf3f..0437644be29b17 100644 --- a/Telegram/SourceFiles/mtproto/core_types.h +++ b/Telegram/SourceFiles/mtproto/core_types.h @@ -19,6 +19,7 @@ For license and copyright information please follow this link: #include #include #include +#include #include using mtpPrime = int32; @@ -243,6 +244,18 @@ inline MTPvector MTP_vector() { return tl::make_vector(); } +// ranges::to doesn't work with Qt 6 in Clang, +// because QVector is a type alias for QList there. +template +inline auto MTP_vector_from_range(Rng &&range) { + using T = std::remove_cvref_t; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0 ,0) + return MTP_vector(std::forward(range) | ranges::to()); +#else // QT_VERSION >= 6.0 + return MTP_vector(std::forward(range) | ranges::to()); +#endif // QT_VERSION < 6.0 +} + namespace tl { template diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 6099314bed16a7..9d58ad590701ac 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -295,11 +295,11 @@ MTPInputInvoice Form::inputInvoice() const { return MTP_inputInvoicePremiumGiftCode( MTP_inputStorePaymentPremiumGiftCode( MTP_flags(users->boostPeer ? Flag::f_boost_peer : Flag()), - MTP_vector(ranges::views::all( + MTP_vector_from_range(ranges::views::all( users->users ) | ranges::views::transform([](not_null user) { return MTPInputUser(user->inputUser); - }) | ranges::to), + })), users->boostPeer ? users->boostPeer->input : MTPInputPeer(), MTP_string(giftCode.currency), MTP_long(giftCode.amount)), @@ -321,16 +321,16 @@ MTPInputInvoice Form::inputInvoice() const { ? Flag() : Flag::f_countries_iso2)), giveaway.boostPeer->input, - MTP_vector(ranges::views::all( + MTP_vector_from_range(ranges::views::all( giveaway.additionalChannels ) | ranges::views::transform([](not_null c) { return MTPInputPeer(c->input); - }) | ranges::to()), - MTP_vector(ranges::views::all( + })), + MTP_vector_from_range(ranges::views::all( giveaway.countries ) | ranges::views::transform([](QString value) { return MTP_string(value); - }) | ranges::to()), + })), MTP_long(giftCode.randomId), MTP_int(giveaway.untilDate), MTP_string(giftCode.currency), From 4e0490494e90c508a2a7a52a0d9c8c89312459d6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Nov 2023 13:25:11 +0400 Subject: [PATCH 51/70] Extract reply view to a separate component. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/history/history_item.cpp | 6 +- .../history/history_item_components.cpp | 713 +--------------- .../history/history_item_components.h | 74 +- .../history/view/history_view_element.cpp | 11 +- .../history/view/history_view_element.h | 3 +- .../history/view/history_view_message.cpp | 80 +- .../history/view/history_view_message.h | 4 +- .../history/view/history_view_reply.cpp | 804 ++++++++++++++++++ .../history/view/history_view_reply.h | 116 +++ .../media/history_view_extended_preview.cpp | 2 +- .../history/view/media/history_view_gif.cpp | 41 +- .../history/view/media/history_view_gif.h | 3 +- .../view/media/history_view_location.cpp | 2 +- .../view/media/history_view_media_grouped.cpp | 2 +- .../media/history_view_media_unwrapped.cpp | 45 +- .../view/media/history_view_media_unwrapped.h | 7 +- .../history/view/media/history_view_photo.cpp | 2 +- 18 files changed, 1037 insertions(+), 880 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_reply.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_reply.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 00f59723146d23..3ad5943448b276 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -770,6 +770,8 @@ PRIVATE history/view/history_view_quick_action.h history/view/history_view_replies_section.cpp history/view/history_view_replies_section.h + history/view/history_view_reply.cpp + history/view/history_view_reply.h history/view/history_view_requests_bar.cpp history/view/history_view_requests_bar.h history/view/history_view_schedule_box.cpp diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index b4949c93f2d3be..f233b08d0d0f51 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -723,7 +723,7 @@ HistoryItem::HistoryItem( HistoryItem::~HistoryItem() { _media = nullptr; clearSavedMedia(); - if (auto reply = Get()) { + if (const auto reply = Get()) { reply->clearData(this); } clearDependencyMessage(); @@ -1674,7 +1674,6 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { setForwardsCount(data.vforwards().value_or(-1)); if (const auto reply = data.vreply_to()) { reply->match([&](const MTPDmessageReplyHeader &data) { - // #TODO replies const auto replyToPeer = data.vreply_to_peer_id() ? peerFromMTP(*data.vreply_to_peer_id()) : PeerId(); @@ -1980,9 +1979,6 @@ void HistoryItem::setRealId(MsgId newId) { _history->owner().requestItemResize(this); if (const auto reply = Get()) { - if (reply->link()) { - reply->setLinkFrom(this); - } incrementReplyToTopCounter(); } } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 5ae8652af263f7..c92f9f855b2d4b 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -55,130 +55,8 @@ For license and copyright information please follow this link: namespace { -constexpr auto kNonExpandedLinesLimit = 5; const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; -void ValidateBackgroundEmoji( - DocumentId backgroundEmojiId, - not_null data, - not_null cache, - not_null quote, - not_null holder) { - if (data->firstFrameMask.isNull()) { - if (!cache->frames[0].isNull()) { - for (auto &frame : cache->frames) { - frame = QImage(); - } - } - const auto tag = Data::CustomEmojiSizeTag::Isolated; - if (!data->emoji) { - const auto owner = &holder->history()->owner(); - const auto repaint = crl::guard(holder, [=] { - holder->history()->owner().requestViewRepaint(holder); - }); - data->emoji = owner->customEmojiManager().create( - backgroundEmojiId, - repaint, - tag); - } - if (!data->emoji->ready()) { - return; - } - const auto size = Data::FrameSizeFromTag(tag); - data->firstFrameMask = QImage( - QSize(size, size), - QImage::Format_ARGB32_Premultiplied); - data->firstFrameMask.fill(Qt::transparent); - data->firstFrameMask.setDevicePixelRatio(style::DevicePixelRatio()); - auto p = Painter(&data->firstFrameMask); - data->emoji->paint(p, { - .textColor = QColor(255, 255, 255), - .position = QPoint(0, 0), - .internal = { - .forceFirstFrame = true, - }, - }); - p.end(); - - data->emoji = nullptr; - } - if (!cache->frames[0].isNull() && cache->color == quote->icon) { - return; - } - cache->color = quote->icon; - const auto ratio = style::DevicePixelRatio(); - auto colorized = QImage( - data->firstFrameMask.size(), - QImage::Format_ARGB32_Premultiplied); - colorized.setDevicePixelRatio(ratio); - style::colorizeImage( - data->firstFrameMask, - cache->color, - &colorized, - QRect(), // src - QPoint(), // dst - true); // use alpha - const auto make = [&](int size) { - size = style::ConvertScale(size) * ratio; - auto result = colorized.scaled( - size, - size, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - result.setDevicePixelRatio(ratio); - return result; - }; - - constexpr auto kSize1 = 12; - constexpr auto kSize2 = 16; - constexpr auto kSize3 = 20; - cache->frames[0] = make(kSize1); - cache->frames[1] = make(kSize2); - cache->frames[2] = make(kSize3); -} - -void FillBackgroundEmoji( - Painter &p, - const QRect &rect, - bool quote, - const Ui::BackgroundEmojiCache &cache) { - p.setClipRect(rect); - - const auto &frames = cache.frames; - const auto right = rect.x() + rect.width(); - const auto paint = [&](int x, int y, int index, float64 opacity) { - y = style::ConvertScale(y); - if (y >= rect.height()) { - return; - } - p.setOpacity(opacity); - p.drawImage( - right - style::ConvertScale(x + (quote ? 12 : 0)), - rect.y() + y, - frames[index]); - }; - - paint(28, 4, 2, 0.32); - paint(51, 15, 1, 0.32); - paint(64, -2, 0, 0.28); - paint(87, 11, 1, 0.24); - paint(125, -2, 2, 0.16); - - paint(28, 31, 1, 0.24); - paint(72, 33, 2, 0.2); - - paint(46, 52, 1, 0.24); - paint(24, 55, 2, 0.18); - - if (quote) { - paint(4, 23, 1, 0.28); - paint(0, 48, 0, 0.24); - } - - p.setClipping(false); - p.setOpacity(1.); -} - } // namespace void HistoryMessageVia::create( @@ -471,10 +349,7 @@ FullReplyTo ReplyToFromMTP( }); } -HistoryMessageReply::HistoryMessageReply() -: _name(st::maxSignatureSize / 2) -, _text(st::maxSignatureSize / 2) { -} +HistoryMessageReply::HistoryMessageReply() = default; HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply &&other) = default; @@ -527,11 +402,6 @@ bool HistoryMessageReply::updateData( } } - const auto repaint = [=] { holder->customEmojiRepaint(); }; - const auto context = Core::MarkedTextContext{ - .session = &holder->history()->session(), - .customEmojiRepaint = repaint, - }; const auto external = this->external(); _multiline = !_fields.storyId && (external || !_fields.quote.empty()); @@ -545,38 +415,10 @@ bool HistoryMessageReply::updateData( && ((!_fields.storyId && !_fields.messageId) || force); _unavailable = unavailable ? 1 : 0; - const auto text = !_fields.quote.empty() - ? _fields.quote - : resolvedMessage - ? resolvedMessage->inReplyText() - : resolvedStory - ? resolvedStory->inReplyText() - : TextWithEntities{ u"..."_q }; - _text.setMarkedText( - st::defaultTextStyle, - text, - Ui::DialogTextOptions(), - context); - - updateName(holder); - - if (_displaying) { - setLinkFrom(holder); - const auto media = resolvedMessage - ? resolvedMessage->media() - : nullptr; - if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) { - spoiler = nullptr; - } else if (!spoiler) { - spoiler = std::make_unique(repaint); - } - } else if (force) { - if (_fields.messageId || _fields.storyId) { + if (force) { + if (!_displaying && (_fields.messageId || _fields.storyId)) { _unavailable = 1; } - spoiler = nullptr; - } - if (force) { holder->history()->owner().requestItemResize(holder); } return resolvedMessage @@ -611,74 +453,6 @@ void HistoryMessageReply::updateFields( } } -bool HistoryMessageReply::expand() { - if (!_expandable || _expanded) { - return false; - } - _expanded = true; - return true; -} - -void HistoryMessageReply::setLinkFrom( - not_null holder) { - const auto externalChannelId = peerToChannel(_fields.externalPeerId); - const auto messageId = _fields.messageId; - const auto quote = _fields.manualQuote - ? _fields.quote - : TextWithEntities(); - const auto returnToId = holder->fullId(); - const auto externalLink = [=](ClickContext context) { - const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { - auto error = QString(); - const auto owner = &controller->session().data(); - if (const auto item = owner->message(returnToId)) { - if (const auto reply = item->Get()) { - if (reply->expand()) { - owner->requestItemResize(item); - return; - } - } - } - if (externalChannelId) { - const auto channel = owner->channel(externalChannelId); - if (!channel->isForbidden()) { - if (messageId) { - JumpToMessageClickHandler( - channel, - messageId, - returnToId, - quote - )->onClick(context); - } else { - controller->showPeerInfo(channel); - } - } else if (channel->isBroadcast()) { - error = tr::lng_channel_not_accessible(tr::now); - } else { - error = tr::lng_group_not_accessible(tr::now); - } - } else { - error = tr::lng_reply_from_private_chat(tr::now); - } - if (!error.isEmpty()) { - controller->showToast(error); - } - } - }; - _link = resolvedMessage - ? JumpToMessageClickHandler( - resolvedMessage.get(), - returnToId, - quote) - : resolvedStory - ? JumpToStoryClickHandler(resolvedStory.get()) - : (external() - && (!_fields.messageId || (_unavailable && externalChannelId))) - ? std::make_shared(externalLink) - : nullptr; -} - void HistoryMessageReply::setTopMessageId(MsgId topMessageId) { _fields.topMessageId = topMessageId; } @@ -696,11 +470,8 @@ void HistoryMessageReply::clearData(not_null holder) { resolvedStory.get()); resolvedStory = nullptr; } - _name.clear(); - _text.clear(); _unavailable = 1; _displaying = 0; - _expandable = 0; if (_multiline) { holder->history()->owner().requestItemResize(holder); _multiline = 0; @@ -714,236 +485,6 @@ bool HistoryMessageReply::external() const { || !_fields.externalSenderName.isEmpty(); } -PeerData *HistoryMessageReply::sender(not_null holder) const { - if (resolvedStory) { - return resolvedStory->peer(); - } else if (!resolvedMessage) { - if (!_externalSender && _fields.externalSenderId) { - _externalSender = holder->history()->owner().peer( - _fields.externalSenderId); - } - return _externalSender; - } else if (holder->Has()) { - // Forward of a reply. Show reply-to original sender. - const auto forwarded - = resolvedMessage->Get(); - if (forwarded) { - return forwarded->originalSender; - } - } - if (const auto from = resolvedMessage->displayFrom()) { - return from; - } - return resolvedMessage->author().get(); -} - -QString HistoryMessageReply::senderName( - not_null holder, - bool shorten) const { - if (const auto peer = sender(holder)) { - return senderName(peer, shorten); - } else if (!resolvedMessage) { - return _fields.externalSenderName; - } else if (holder->Has()) { - // Forward of a reply. Show reply-to original sender. - const auto forwarded - = resolvedMessage->Get(); - if (forwarded) { - Assert(forwarded->hiddenSenderInfo != nullptr); - return forwarded->hiddenSenderInfo->name; - } - } - return QString(); -} - -QString HistoryMessageReply::senderName( - not_null peer, - bool shorten) const { - const auto user = shorten ? peer->asUser() : nullptr; - return user ? user->firstName : peer->name(); -} - -bool HistoryMessageReply::isNameUpdated( - not_null holder) const { - if (const auto from = sender(holder)) { - if (_nameVersion < from->nameVersion()) { - updateName(holder, from); - return true; - } - } - return false; -} - -void HistoryMessageReply::updateName( - not_null holder, - std::optional resolvedSender) const { - auto viaBotUsername = QString(); - if (resolvedMessage - && !resolvedMessage->Has()) { - if (const auto bot = resolvedMessage->viaBot()) { - viaBotUsername = bot->username(); - } - } - const auto sender = resolvedSender.value_or(this->sender(holder)); - const auto externalPeer = _fields.externalPeerId - ? holder->history()->owner().peer(_fields.externalPeerId).get() - : nullptr; - const auto groupNameAdded = (externalPeer && externalPeer != sender); - const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded; - const auto name = sender - ? senderName(sender, shorten) - : senderName(holder, shorten); - const auto hasPreview = (resolvedStory - && resolvedStory->hasReplyPreview()) - || (resolvedMessage - && resolvedMessage->media() - && resolvedMessage->media()->hasReplyPreview()); - const auto previewSkip = hasPreview - ? (st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right() - - st::historyReplyPadding.left()) - : 0; - const auto peerIcon = [](PeerData *peer) { - return !peer - ? &st::historyReplyUser - : peer->isBroadcast() - ? &st::historyReplyChannel - : (peer->isChannel() || peer->isChat()) - ? &st::historyReplyGroup - : &st::historyReplyUser; - }; - const auto peerEmoji = [&](PeerData *peer) { - const auto owner = &holder->history()->owner(); - return Ui::Text::SingleCustomEmoji( - owner->customEmojiManager().registerInternalEmoji( - *peerIcon(peer))); - }; - auto nameFull = TextWithEntities(); - if (!groupNameAdded && external() && !_fields.storyId) { - nameFull.append(peerEmoji(sender)); - } - nameFull.append(name); - if (groupNameAdded) { - nameFull.append(peerEmoji(externalPeer)); - nameFull.append(externalPeer->name()); - } - if (!viaBotUsername.isEmpty()) { - nameFull.append(u" @"_q).append(viaBotUsername); - } - const auto context = Core::MarkedTextContext{ - .session = &holder->history()->session(), - .customEmojiRepaint = [] {}, - .customEmojiLoopLimit = 1, - }; - _name.setMarkedText( - st::fwdTextStyle, - nameFull, - Ui::NameTextOptions(), - context); - if (sender) { - _nameVersion = sender->nameVersion(); - } - const auto nameMaxWidth = previewSkip - + _name.maxWidth() - + (hasQuoteIcon() - ? st::messageTextStyle.blockquote.icon.width() - : 0); - const auto storySkip = _fields.storyId - ? (st::dialogsMiniReplyStory.skipText - + st::dialogsMiniReplyStory.icon.icon.width()) - : 0; - const auto optimalTextSize = _multiline - ? countMultilineOptimalSize(previewSkip) - : QSize( - (previewSkip - + storySkip - + std::min(_text.maxWidth(), st::maxSignatureSize)), - st::normalFont->height); - _maxWidth = std::max(nameMaxWidth, optimalTextSize.width()); - if (!_displaying) { - const auto phraseWidth = st::msgDateFont->width(statePhrase()); - _maxWidth = _unavailable - ? phraseWidth - : std::max(_maxWidth, phraseWidth); - } - _maxWidth = st::historyReplyPadding.left() - + _maxWidth - + st::historyReplyPadding.right(); - _minHeight = st::historyReplyPadding.top() - + st::msgServiceNameFont->height - + optimalTextSize.height() - + st::historyReplyPadding.bottom(); -} - -int HistoryMessageReply::resizeToWidth(int width) const { - const auto hasPreview = (resolvedStory - && resolvedStory->hasReplyPreview()) - || (resolvedMessage - && resolvedMessage->media() - && resolvedMessage->media()->hasReplyPreview()); - const auto previewSkip = hasPreview - ? (st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right() - - st::historyReplyPadding.left()) - : 0; - if (width >= _maxWidth || !_multiline) { - _nameTwoLines = 0; - _expandable = 0; - _height = _minHeight; - return height(); - } - const auto innerw = width - - st::historyReplyPadding.left() - - st::historyReplyPadding.right(); - const auto namew = innerw - previewSkip; - const auto desiredNameHeight = _name.countHeight(namew); - _nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0; - const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height; - const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; - auto lineCounter = 0; - auto elided = false; - const auto texth = _text.countDimensions( - textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height; - _expandable = (_multiline && elided) ? 1 : 0; - _height = st::historyReplyPadding.top() - + nameh - + (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth) - + st::historyReplyPadding.bottom(); - return height(); -} - -Ui::Text::GeometryDescriptor HistoryMessageReply::textGeometry( - int available, - int firstLineSkip, - not_null line, - not_null outElided) const { - return { .layout = [=](Ui::Text::LineGeometry in) { - const auto skip = (*line ? 0 : firstLineSkip); - ++*line; - *outElided = *outElided - || !_multiline - || (!_expanded - && (*line == kNonExpandedLinesLimit) - && in.width > available - skip); - in.width = available - skip; - in.left += skip; - in.elided = *outElided; - return in; - } }; -} - -int HistoryMessageReply::height() const { - return _height + st::historyReplyTop + st::historyReplyBottom; -} - -QMargins HistoryMessageReply::margins() const { - return QMargins(0, st::historyReplyTop, 0, st::historyReplyBottom); -} - void HistoryMessageReply::itemRemoved( not_null holder, not_null removed) { @@ -962,254 +503,6 @@ void HistoryMessageReply::storyRemoved( } } -bool HistoryMessageReply::hasQuoteIcon() const { - return _fields.manualQuote && !_fields.quote.empty(); -} - -QSize HistoryMessageReply::countMultilineOptimalSize( - int previewSkip) const { - auto elided = false; - auto lineCounter = 0; - const auto max = previewSkip + _text.maxWidth(); - const auto result = _text.countDimensions( - textGeometry(max, previewSkip, &lineCounter, &elided)); - return { result.width, result.height }; -} - -void HistoryMessageReply::paint( - Painter &p, - not_null holder, - const Ui::ChatPaintContext &context, - int x, - int y, - int w, - bool inBubble) const { - const auto st = context.st; - const auto stm = context.messageStyle(); - - y += st::historyReplyTop; - const auto rect = QRect(x, y, w, _height); - const auto hasQuote = hasQuoteIcon(); - const auto selected = context.selected(); - const auto colorPeer = resolvedMessage - ? resolvedMessage->displayFrom() - : resolvedStory - ? resolvedStory->peer().get() - : _externalSender - ? _externalSender - : nullptr; - const auto backgroundEmojiId = colorPeer - ? colorPeer->backgroundEmojiId() - : DocumentId(); - const auto colorIndexPlusOne = colorPeer - ? (colorPeer->colorIndex() + 1) - : resolvedMessage - ? (resolvedMessage->hiddenSenderInfo()->colorIndex + 1) - : 0; - const auto useColorIndex = colorIndexPlusOne && !context.outbg; - const auto colorPattern = colorIndexPlusOne - ? st->colorPatternIndex(colorIndexPlusOne - 1) - : 0; - const auto cache = !inBubble - ? (hasQuote - ? st->serviceQuoteCache(colorPattern) - : st->serviceReplyCache(colorPattern)).get() - : useColorIndex - ? (hasQuote - ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) - : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() - : (hasQuote - ? stm->quoteCache[colorPattern] - : stm->replyCache[colorPattern]).get(); - const auto "eSt = hasQuote - ? st::messageTextStyle.blockquote - : st::messageQuoteStyle; - const auto backgroundEmoji = backgroundEmojiId - ? st->backgroundEmojiData(backgroundEmojiId).get() - : nullptr; - const auto backgroundEmojiCache = backgroundEmoji - ? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex( - selected, - context.outbg, - inBubble, - colorIndexPlusOne)] - : nullptr; - const auto rippleColor = cache->bg; - if (!inBubble) { - cache->bg = QColor(0, 0, 0, 0); - } - Ui::Text::ValidateQuotePaintCache(*cache, quoteSt); - Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt); - if (backgroundEmoji) { - ValidateBackgroundEmoji( - backgroundEmojiId, - backgroundEmoji, - backgroundEmojiCache, - cache, - holder); - if (!backgroundEmojiCache->frames[0].isNull()) { - FillBackgroundEmoji(p, rect, hasQuote, *backgroundEmojiCache); - } - } - if (!inBubble) { - cache->bg = rippleColor; - } - - if (ripple.animation) { - ripple.animation->paint(p, x, y, w, &rippleColor); - if (ripple.animation->empty()) { - ripple.animation.reset(); - } - } - - auto hasPreview = (resolvedStory - && resolvedStory->hasReplyPreview()) - || (resolvedMessage - && resolvedMessage->media() - && resolvedMessage->media()->hasReplyPreview()); - auto previewSkip = hasPreview - ? (st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right() - - st::historyReplyPadding.left()) - : 0; - if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) { - hasPreview = false; - previewSkip = 0; - } - - const auto pausedSpoiler = context.paused - || On(PowerSaving::kChatSpoiler); - auto textLeft = x + st::historyReplyPadding.left(); - auto textTop = y - + st::historyReplyPadding.top() - + (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1)); - if (w > st::historyReplyPadding.left()) { - if (_displaying) { - const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; - if (hasPreview) { - const auto image = media - ? media->replyPreview() - : resolvedStory->replyPreview(); - if (image) { - auto to = style::rtlrect( - x + st::historyReplyPreviewMargin.left(), - y + st::historyReplyPreviewMargin.top(), - st::historyReplyPreview, - st::historyReplyPreview, - w + 2 * x); - const auto preview = image->pixSingle( - image->size() / style::DevicePixelRatio(), - { - .colored = (context.selected() - ? &st->msgStickerOverlay() - : nullptr), - .options = Images::Option::RoundSmall, - .outer = to.size(), - }); - p.drawPixmap(to.x(), to.y(), preview); - if (spoiler) { - holder->clearCustomEmojiRepaint(); - Ui::FillSpoilerRect( - p, - to, - Ui::DefaultImageSpoiler().frame( - spoiler->index( - context.now, - pausedSpoiler))); - } - } - } - const auto textw = w - - st::historyReplyPadding.left() - - st::historyReplyPadding.right(); - const auto namew = textw - previewSkip; - auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; - if (namew > 0) { - p.setPen(!inBubble - ? st->msgImgReplyBarColor()->c - : useColorIndex - ? FromNameFg(context, colorIndexPlusOne - 1) - : stm->msgServiceFg->c); - _name.drawLeftElided( - p, - x + st::historyReplyPadding.left() + previewSkip, - y + st::historyReplyPadding.top(), - namew, - w + 2 * x, - _nameTwoLines ? 2 : 1); - - p.setPen(inBubble - ? stm->historyTextFg - : st->msgImgReplyBarColor()); - holder->prepareCustomEmojiPaint(p, context, _text); - auto replyToTextPalette = &(!inBubble - ? st->imgReplyTextPalette() - : useColorIndex - ? st->coloredTextPalette(selected, colorIndexPlusOne - 1) - : stm->replyTextPalette); - if (_fields.storyId) { - st::dialogsMiniReplyStory.icon.icon.paint( - p, - textLeft + firstLineSkip, - textTop, - w + 2 * x, - replyToTextPalette->linkFg->c); - firstLineSkip += st::dialogsMiniReplyStory.skipText - + st::dialogsMiniReplyStory.icon.icon.width(); - } - auto owned = std::optional(); - auto copy = std::optional(); - if (inBubble && colorIndexPlusOne) { - copy.emplace(*replyToTextPalette); - owned.emplace(cache->icon); - copy->linkFg = owned->color(); - replyToTextPalette = &*copy; - } - auto l = 0; - auto e = false; - _text.draw(p, { - .position = { textLeft, textTop }, - .geometry = textGeometry(textw, firstLineSkip, &l, &e), - .palette = replyToTextPalette, - .spoiler = Ui::Text::DefaultSpoilerCache(), - .now = context.now, - .pausedEmoji = (context.paused - || On(PowerSaving::kEmojiChat)), - .pausedSpoiler = pausedSpoiler, - .elisionOneLine = true, - }); - p.setTextPalette(stm->textPalette); - } - } else { - p.setFont(st::msgDateFont); - p.setPen(cache->icon); - p.drawTextLeft( - textLeft, - (y - + st::historyReplyPadding.top() - + (st::msgDateFont->height / 2)), - w + 2 * x, - st::msgDateFont->elided( - statePhrase(), - x + w - textLeft - st::historyReplyPadding.right())); - } - } -} - -void HistoryMessageReply::unloadPersistentAnimation() { - _text.unloadPersistentAnimation(); -} - -QString HistoryMessageReply::statePhrase() const { - return ((_fields.messageId || _fields.storyId) && !_unavailable) - ? tr::lng_profile_loading(tr::now) - : _fields.storyId - ? tr::lng_deleted_story(tr::now) - : tr::lng_deleted_message(tr::now); -} - void HistoryMessageReply::refreshReplyToMedia() { replyToDocumentId = 0; replyToWebPageId = 0; diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index e4159106fe93e2..b23b509b0fac7a 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -22,7 +22,6 @@ namespace Ui { struct ChatPaintContext; class ChatStyle; struct PeerUserpicView; -class SpoilerAnimation; } // namespace Ui namespace Ui::Text { @@ -264,8 +263,6 @@ struct HistoryMessageReply HistoryMessageReply &operator=(HistoryMessageReply &&other); ~HistoryMessageReply(); - static constexpr auto kBarAlpha = 230. / 255.; - void set(ReplyFields fields); void updateFields( @@ -279,20 +276,6 @@ struct HistoryMessageReply void clearData(not_null holder); [[nodiscard]] bool external() const; - [[nodiscard]] PeerData *sender(not_null holder) const; - [[nodiscard]] QString senderName( - not_null holder, - bool shorten) const; - [[nodiscard]] QString senderName( - not_null peer, - bool shorten) const; - [[nodiscard]] bool isNameUpdated(not_null holder) const; - void updateName( - not_null holder, - std::optional resolvedSender = std::nullopt) const; - [[nodiscard]] int resizeToWidth(int width) const; - [[nodiscard]] int height() const; - [[nodiscard]] QMargins margins() const; void itemRemoved( not_null holder, not_null removed); @@ -300,19 +283,7 @@ struct HistoryMessageReply not_null holder, not_null removed); - bool expand(); - - void paint( - Painter &p, - not_null holder, - const Ui::ChatPaintContext &context, - int x, - int y, - int w, - bool inBubble) const; - void unloadPersistentAnimation(); - - [[nodiscard]] ReplyFields fields() const { + [[nodiscard]] const ReplyFields &fields() const { return _fields; } [[nodiscard]] PeerId externalPeerId() const { @@ -327,21 +298,22 @@ struct HistoryMessageReply [[nodiscard]] MsgId topMessageId() const { return _fields.topMessageId; } - [[nodiscard]] int maxWidth() const { - return _maxWidth; - } - [[nodiscard]] ClickHandlerPtr link() const { - return _link; - } [[nodiscard]] bool topicPost() const { return _fields.topicPost; } [[nodiscard]] bool manualQuote() const { return _fields.manualQuote; } - [[nodiscard]] QString statePhrase() const; + [[nodiscard]] bool unavailable() const { + return _unavailable; + } + [[nodiscard]] bool displaying() const { + return _displaying; + } + [[nodiscard]] bool multiline() const { + return _multiline; + } - void setLinkFrom(not_null holder); void setTopMessageId(MsgId topMessageId); void refreshReplyToMedia(); @@ -350,38 +322,12 @@ struct HistoryMessageReply WebPageId replyToWebPageId = 0; ReplyToMessagePointer resolvedMessage; ReplyToStoryPointer resolvedStory; - std::unique_ptr spoiler; - - struct { - mutable std::unique_ptr animation; - QPoint lastPoint; - } ripple; private: - [[nodiscard]] bool hasQuoteIcon() const; - [[nodiscard]] Ui::Text::GeometryDescriptor textGeometry( - int available, - int firstLineSkip, - not_null line, - not_null outElided) const; - [[nodiscard]] QSize countMultilineOptimalSize( - int firstLineSkip) const; - ReplyFields _fields; - ClickHandlerPtr _link; - mutable Ui::Text::String _name; - mutable Ui::Text::String _text; - mutable PeerData *_externalSender = nullptr; - mutable int _maxWidth = 0; - mutable int _minHeight = 0; - mutable int _height = 0; - mutable int _nameVersion = 0; uint8 _unavailable : 1 = 0; uint8 _displaying : 1 = 0; uint8 _multiline : 1 = 0; - mutable uint8 _expandable : 1 = 0; - uint8 _expanded : 1 = 0; - mutable uint8 _nameTwoLines : 1 = 0; }; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index eeec9d86b378ce..329ed41f300c08 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -18,6 +18,7 @@ For license and copyright information please follow this link: #include "history/view/reactions/history_view_reactions_button.h" #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/history.h" #include "history/history_item.h" @@ -1362,6 +1363,10 @@ bool Element::hasFromName() const { return false; } +bool Element::displayReply() const { + return Has(); +} + bool Element::displayFromName() const { return false; } @@ -1419,10 +1424,6 @@ TimeId Element::displayedEditDate() const { return TimeId(0); } -HistoryMessageReply *Element::displayedReply() const { - return nullptr; -} - bool Element::toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const { return false; @@ -1482,7 +1483,7 @@ void Element::unloadHeavyPart() { if (_flags & Flag::HeavyCustomEmoji) { _flags &= ~Flag::HeavyCustomEmoji; _text.unloadPersistentAnimation(); - if (const auto reply = data()->Get()) { + if (const auto reply = Get()) { reply->unloadPersistentAnimation(); } } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index db3933b983ec64..4da9f4b9c64f73 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -49,6 +49,7 @@ enum class InfoDisplayType : char; struct StateRequest; struct TextState; class Media; +class Reply; enum class Context : char { History, @@ -433,6 +434,7 @@ class Element [[nodiscard]] virtual bool hasFromPhoto() const; [[nodiscard]] virtual bool displayFromPhoto() const; [[nodiscard]] virtual bool hasFromName() const; + [[nodiscard]] bool displayReply() const; [[nodiscard]] virtual bool displayFromName() const; [[nodiscard]] virtual TopicButton *displayedTopicButton() const; [[nodiscard]] virtual bool displayForwardedFrom() const; @@ -456,7 +458,6 @@ class Element std::optional pressPoint) const; [[nodiscard]] virtual TimeId displayedEditDate() const; [[nodiscard]] virtual bool hasVisibleText() const; - [[nodiscard]] virtual HistoryMessageReply *displayedReply() const; virtual void applyGroupAdminChanges( const base::flat_set &changes) { } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 8337021010fe1d..115b2e76c8e667 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -18,6 +18,7 @@ For license and copyright information please follow this link: #include "history/view/reactions/history_view_reactions.h" #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_group_call_bar.h" // UserpicInRow. +#include "history/view/history_view_reply.h" #include "history/view/history_view_view_button.h" // ViewButton. #include "history/history.h" #include "boxes/share_box.h" @@ -406,6 +407,7 @@ Message::Message( Element *replacing) : Element(delegate, data, replacing, Flag(0)) , _invertMedia(data->invertMedia() && !data->emptyText()) +, _hideReply(delegate->elementHideReply(this)) , _bottomInfo( &data->history()->owner().reactions(), BottomInfoDataFromMessage(this)) { @@ -597,6 +599,14 @@ auto Message::takeReactionAnimations() QSize Message::performCountOptimalSize() { const auto item = data(); + + const auto replyData = item->Get(); + if (replyData) { + AddComponents(Reply::Bit()); + } else { + RemoveComponents(Reply::Bit()); + } + const auto markup = item->inlineReplyMarkup(); const auto reactionsKey = [&] { return embedReactionsInBottomInfo() @@ -633,17 +643,19 @@ QSize Message::performCountOptimalSize() { if (_reactions) { _reactions->initDimensions(); } + + const auto reply = Get(); + if (reply) { + reply->update(this, replyData); + } + if (drawBubble()) { const auto forwarded = item->Get(); - const auto reply = displayedReply(); const auto via = item->Get(); const auto entry = logEntryOriginal(); if (forwarded) { forwarded->create(via); } - if (reply) { - reply->updateName(item); - } auto mediaDisplayed = false; if (media) { @@ -1217,9 +1229,11 @@ void Message::draw(Painter &p, const PaintContext &context) const { p.restore(); } - if (const auto reply = displayedReply()) { - if (reply->isNameUpdated(data())) { - const_cast(this)->setPendingResize(); + if (const auto reply = Get()) { + if (const auto replyData = item->Get()) { + if (reply->isNameUpdated(this, replyData)) { + const_cast(this)->setPendingResize(); + } } } } @@ -1598,8 +1612,15 @@ void Message::paintReplyInfo( Painter &p, QRect &trect, const PaintContext &context) const { - if (const auto reply = displayedReply()) { - reply->paint(p, this, context, trect.x(), trect.y(), trect.width(), true); + if (const auto reply = Get()) { + reply->paint( + p, + this, + context, + trect.x(), + trect.y(), + trect.width(), + true); trect.setY(trect.y() + reply->height()); } } @@ -1753,7 +1774,7 @@ void Message::clickHandlerPressedChanged( toggleTopicButtonRipple(pressed); } else if (_viewButton) { _viewButton->checkLink(handler, pressed); - } else if (const auto reply = displayedReply() + } else if (const auto reply = Get() ; reply && (handler == reply->link())) { toggleReplyRipple(pressed); } @@ -1796,13 +1817,13 @@ void Message::toggleRightActionRipple(bool pressed) { } void Message::toggleReplyRipple(bool pressed) { - const auto reply = displayedReply(); + const auto reply = Get(); if (!reply) { return; } if (pressed) { - if (!reply->ripple.animation && !unwrapped()) { + if (!unwrapped()) { const auto &padding = st::msgPadding; const auto geometry = countGeometry(); const auto item = data(); @@ -1810,18 +1831,11 @@ void Message::toggleReplyRipple(bool pressed) { const auto size = QSize( geometry.width() - padding.left() - padding.right(), reply->height() - margins.top() - margins.bottom()); - reply->ripple.animation = std::make_unique( - st::defaultRippleAnimation, - Ui::RippleAnimation::RoundRectMask( - size, - st::messageQuoteStyle.radius), - [=] { item->history()->owner().requestItemRepaint(item); }); - } - if (reply->ripple.animation) { - reply->ripple.animation->add(reply->ripple.lastPoint); + reply->createRippleAnimation(this, size); } - } else if (reply->ripple.animation) { - reply->ripple.animation->lastStop(); + reply->addRipple(); + } else { + reply->stopLastRipple(); } } @@ -2482,7 +2496,7 @@ bool Message::getStateReplyInfo( QPoint point, QRect &trect, not_null outResult) const { - if (const auto reply = displayedReply()) { + if (const auto reply = Get()) { const auto margins = reply->margins(); const auto height = reply->height(); if (point.y() >= trect.top() && point.y() < trect.top() + height) { @@ -2494,7 +2508,7 @@ bool Message::getStateReplyInfo( if (g.contains(point)) { if (const auto link = reply->link()) { outResult->link = reply->link(); - reply->ripple.lastPoint = point - g.topLeft(); + reply->saveRipplePoint(point - g.topLeft()); } } return true; @@ -2577,7 +2591,7 @@ void Message::updatePressed(QPoint point) { auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; trect.setTop(trect.top() + fwdheight); } - if (const auto reply = item->Get()) { + if (const auto reply = Get()) { trect.setTop(trect.top() + reply->height()); } if (const auto via = item->Get()) { @@ -3123,13 +3137,6 @@ WebPage *Message::logEntryOriginal() const { return nullptr; } -HistoryMessageReply *Message::displayedReply() const { - if (const auto reply = data()->Get()) { - return delegate()->elementHideReply(this) ? nullptr : reply; - } - return nullptr; -} - bool Message::toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const { if (_comments && _comments->link == handler) { @@ -3580,7 +3587,7 @@ void Message::updateMediaInBubbleState() { return displayFromName() || displayedTopicButton() || displayForwardedFrom() - || displayedReply() + || Has() || item->Has(); }; auto entry = logEntryOriginal(); @@ -3705,7 +3712,7 @@ QRect Message::innerGeometry() const { + st::topicButtonSkip); } // Skip displayForwardedFrom() until there are no animations for it. - if (const auto reply = displayedReply()) { + if (const auto reply = Get()) { // See paintReplyInfo(). result.translate(0, reply->height()); } @@ -3872,7 +3879,7 @@ int Message::resizeContentGetHeight(int newWidth) { textWidth - 2 * st::msgDateDelta.x())); if (bubble) { - auto reply = displayedReply(); + auto reply = Get(); auto via = item->Get(); auto entry = logEntryOriginal(); @@ -3962,7 +3969,6 @@ int Message::resizeContentGetHeight(int newWidth) { newHeight += reply->resizeToWidth(contentWidth - st::msgPadding.left() - st::msgPadding.right()); - reply->ripple.animation = nullptr; } if (needInfoDisplay()) { newHeight += (bottomInfoHeight - st::msgDateFont->height); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index aeba7c1aaee547..2b9112c1cc6b27 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -137,7 +137,6 @@ class Message final : public Element { [[nodiscard]] ClickHandlerPtr rightActionLink( std::optional pressPoint) const override; [[nodiscard]] TimeId displayedEditDate() const override; - [[nodiscard]] HistoryMessageReply *displayedReply() const override; [[nodiscard]] bool toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const override; [[nodiscard]] bool allowTextSelectionByHandler( @@ -308,8 +307,9 @@ class Message final : public Element { mutable std::unique_ptr _fromNameStatus; Ui::Text::String _rightBadge; mutable int _fromNameVersion = 0; - uint32 _bubbleWidthLimit : 31 = 0; + uint32 _bubbleWidthLimit : 30 = 0; uint32 _invertMedia : 1 = 0; + uint32 _hideReply : 1 = 0; BottomInfo _bottomInfo; diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp new file mode 100644 index 00000000000000..5d9722231b5510 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -0,0 +1,804 @@ +/* +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_reply.h" + +#include "core/click_handler_types.h" +#include "core/ui_integration.h" +#include "data/stickers/data_custom_emoji.h" +#include "data/data_channel.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_story.h" +#include "data/data_user.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/history_item_helpers.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/chat/chat_style.h" +#include "ui/effects/ripple_animation.h" +#include "ui/effects/spoiler_mess.h" +#include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "ui/painter.h" +#include "ui/power_saving.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_dialogs.h" + +namespace HistoryView { +namespace { + +constexpr auto kNonExpandedLinesLimit = 5; + +void ValidateBackgroundEmoji( + DocumentId backgroundEmojiId, + not_null data, + not_null cache, + not_null quote, + not_null view) { + if (data->firstFrameMask.isNull()) { + if (!cache->frames[0].isNull()) { + for (auto &frame : cache->frames) { + frame = QImage(); + } + } + const auto tag = Data::CustomEmojiSizeTag::Isolated; + if (!data->emoji) { + const auto owner = &view->history()->owner(); + const auto repaint = crl::guard(view, [=] { + view->history()->owner().requestViewRepaint(view); + }); + data->emoji = owner->customEmojiManager().create( + backgroundEmojiId, + repaint, + tag); + } + if (!data->emoji->ready()) { + return; + } + const auto size = Data::FrameSizeFromTag(tag); + data->firstFrameMask = QImage( + QSize(size, size), + QImage::Format_ARGB32_Premultiplied); + data->firstFrameMask.fill(Qt::transparent); + data->firstFrameMask.setDevicePixelRatio(style::DevicePixelRatio()); + auto p = Painter(&data->firstFrameMask); + data->emoji->paint(p, { + .textColor = QColor(255, 255, 255), + .position = QPoint(0, 0), + .internal = { + .forceFirstFrame = true, + }, + }); + p.end(); + + data->emoji = nullptr; + } + if (!cache->frames[0].isNull() && cache->color == quote->icon) { + return; + } + cache->color = quote->icon; + const auto ratio = style::DevicePixelRatio(); + auto colorized = QImage( + data->firstFrameMask.size(), + QImage::Format_ARGB32_Premultiplied); + colorized.setDevicePixelRatio(ratio); + style::colorizeImage( + data->firstFrameMask, + cache->color, + &colorized, + QRect(), // src + QPoint(), // dst + true); // use alpha + const auto make = [&](int size) { + size = style::ConvertScale(size) * ratio; + auto result = colorized.scaled( + size, + size, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + result.setDevicePixelRatio(ratio); + return result; + }; + + constexpr auto kSize1 = 12; + constexpr auto kSize2 = 16; + constexpr auto kSize3 = 20; + cache->frames[0] = make(kSize1); + cache->frames[1] = make(kSize2); + cache->frames[2] = make(kSize3); +} + +void FillBackgroundEmoji( + Painter &p, + const QRect &rect, + bool quote, + const Ui::BackgroundEmojiCache &cache) { + p.setClipRect(rect); + + const auto &frames = cache.frames; + const auto right = rect.x() + rect.width(); + const auto paint = [&](int x, int y, int index, float64 opacity) { + y = style::ConvertScale(y); + if (y >= rect.height()) { + return; + } + p.setOpacity(opacity); + p.drawImage( + right - style::ConvertScale(x + (quote ? 12 : 0)), + rect.y() + y, + frames[index]); + }; + + paint(28, 4, 2, 0.32); + paint(51, 15, 1, 0.32); + paint(64, -2, 0, 0.28); + paint(87, 11, 1, 0.24); + paint(125, -2, 2, 0.16); + + paint(28, 31, 1, 0.24); + paint(72, 33, 2, 0.2); + + paint(46, 52, 1, 0.24); + paint(24, 55, 2, 0.18); + + if (quote) { + paint(4, 23, 1, 0.28); + paint(0, 48, 0, 0.24); + } + + p.setClipping(false); + p.setOpacity(1.); +} + +} // namespace + +Reply::Reply() +: _name(st::maxSignatureSize / 2) +, _text(st::maxSignatureSize / 2) { +} + +Reply &Reply::operator=(Reply &&other) = default; + +Reply::~Reply() = default; + +void Reply::update( + not_null view, + not_null data) { + const auto item = view->data(); + const auto &fields = data->fields(); + const auto message = data->resolvedMessage.get(); + const auto story = data->resolvedStory.get(); + if (!_externalSender) { + if (const auto id = fields.externalSenderId) { + _externalSender = view->history()->owner().peer(id); + } + } + _colorPeer = message + ? message->displayFrom() + : story + ? story->peer().get() + : _externalSender + ? _externalSender + : nullptr; + _hiddenSenderColorIndexPlusOne = (!_colorPeer && message) + ? (message->hiddenSenderInfo()->colorIndex + 1) + : 0; + + const auto hasPreview = (story && story->hasReplyPreview()) + || (message + && message->media() + && message->media()->hasReplyPreview()); + _hasPreview = hasPreview ? 1 : 0; + _displaying = data->displaying() ? 1 : 0; + _multiline = data->multiline() ? 1 : 0; + _replyToStory = (fields.storyId != 0); + const auto hasQuoteIcon = _displaying + && fields.manualQuote + && !fields.quote.empty(); + _hasQuoteIcon = hasQuoteIcon ? 1 : 0; + + const auto text = (!_displaying && data->unavailable()) + ? TextWithEntities() + : !fields.quote.empty() + ? fields.quote + : message + ? message->inReplyText() + : story + ? story->inReplyText() + : TextWithEntities(); + const auto repaint = [=] { item->customEmojiRepaint(); }; + const auto context = Core::MarkedTextContext{ + .session = &view->history()->session(), + .customEmojiRepaint = repaint, + }; + _text.setMarkedText( + st::defaultTextStyle, + text, + Ui::DialogTextOptions(), + context); + + updateName(view, data); + + if (_displaying) { + setLinkFrom(view, data); + const auto media = message ? message->media() : nullptr; + if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) { + _spoiler = nullptr; + } else if (!_spoiler) { + _spoiler = std::make_unique(repaint); + } + } else { + _spoiler = nullptr; + } +} + +bool Reply::expand() { + if (!_expandable || _expanded) { + return false; + } + _expanded = true; + return true; +} + +void Reply::setLinkFrom( + not_null view, + not_null data) { + const auto weak = base::make_weak(view); + const auto &fields = data->fields(); + const auto externalChannelId = peerToChannel(fields.externalPeerId); + const auto messageId = fields.messageId; + const auto quote = fields.manualQuote + ? fields.quote + : TextWithEntities(); + const auto returnToId = view->data()->fullId(); + const auto externalLink = [=](ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + auto error = QString(); + const auto owner = &controller->session().data(); + if (const auto view = weak.get()) { + if (const auto reply = view->Get()) { + if (reply->expand()) { + owner->requestViewResize(view); + return; + } + } + } + if (externalChannelId) { + const auto channel = owner->channel(externalChannelId); + if (!channel->isForbidden()) { + if (messageId) { + JumpToMessageClickHandler( + channel, + messageId, + returnToId, + quote + )->onClick(context); + } else { + controller->showPeerInfo(channel); + } + } else if (channel->isBroadcast()) { + error = tr::lng_channel_not_accessible(tr::now); + } else { + error = tr::lng_group_not_accessible(tr::now); + } + } else { + error = tr::lng_reply_from_private_chat(tr::now); + } + if (!error.isEmpty()) { + controller->showToast(error); + } + } + }; + const auto message = data->resolvedMessage.get(); + const auto story = data->resolvedStory.get(); + _link = message + ? JumpToMessageClickHandler(message, returnToId, quote) + : story + ? JumpToStoryClickHandler(story) + : (data->external() + && (!fields.messageId + || (data->unavailable() && externalChannelId))) + ? std::make_shared(externalLink) + : nullptr; +} + +PeerData *Reply::sender( + not_null view, + not_null data) const { + const auto message = data->resolvedMessage.get(); + if (const auto story = data->resolvedStory.get()) { + return story->peer(); + } else if (!message) { + return _externalSender; + } else if (view->data()->Has()) { + // Forward of a reply. Show reply-to original sender. + const auto forwarded = message->Get(); + if (forwarded) { + return forwarded->originalSender; + } + } + if (const auto from = message->displayFrom()) { + return from; + } + return message->author().get(); +} + +QString Reply::senderName( + not_null view, + not_null data, + bool shorten) const { + if (const auto peer = sender(view, data)) { + return senderName(peer, shorten); + } else if (!data->resolvedMessage) { + return data->fields().externalSenderName; + } else if (view->data()->Has()) { + // Forward of a reply. Show reply-to original sender. + const auto forwarded + = data->resolvedMessage->Get(); + if (forwarded) { + Assert(forwarded->hiddenSenderInfo != nullptr); + return forwarded->hiddenSenderInfo->name; + } + } + return QString(); +} + +QString Reply::senderName( + not_null peer, + bool shorten) const { + const auto user = shorten ? peer->asUser() : nullptr; + return user ? user->firstName : peer->name(); +} + +bool Reply::isNameUpdated( + not_null view, + not_null data) const { + if (const auto from = sender(view, data)) { + if (_nameVersion < from->nameVersion()) { + updateName(view, data, from); + return true; + } + } + return false; +} + +void Reply::updateName( + not_null view, + not_null data, + std::optional resolvedSender) const { + auto viaBotUsername = QString(); + const auto story = data->resolvedStory.get(); + const auto message = data->resolvedMessage.get(); + if (message && !message->Has()) { + if (const auto bot = message->viaBot()) { + viaBotUsername = bot->username(); + } + } + const auto &fields = data->fields(); + const auto sender = resolvedSender.value_or(this->sender(view, data)); + const auto externalPeer = fields.externalPeerId + ? view->history()->owner().peer(fields.externalPeerId).get() + : nullptr; + const auto groupNameAdded = (externalPeer && externalPeer != sender); + const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded; + const auto name = sender + ? senderName(sender, shorten) + : senderName(view, data, shorten); + const auto previewSkip = _hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; + const auto peerIcon = [](PeerData *peer) { + return !peer + ? &st::historyReplyUser + : peer->isBroadcast() + ? &st::historyReplyChannel + : (peer->isChannel() || peer->isChat()) + ? &st::historyReplyGroup + : &st::historyReplyUser; + }; + const auto peerEmoji = [&](PeerData *peer) { + const auto owner = &view->history()->owner(); + return Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().registerInternalEmoji( + *peerIcon(peer))); + }; + auto nameFull = TextWithEntities(); + if (!groupNameAdded && data->external() && !fields.storyId) { + nameFull.append(peerEmoji(sender)); + } + nameFull.append(name); + if (groupNameAdded) { + nameFull.append(peerEmoji(externalPeer)); + nameFull.append(externalPeer->name()); + } + if (!viaBotUsername.isEmpty()) { + nameFull.append(u" @"_q).append(viaBotUsername); + } + const auto context = Core::MarkedTextContext{ + .session = &view->history()->session(), + .customEmojiRepaint = [] {}, + .customEmojiLoopLimit = 1, + }; + _name.setMarkedText( + st::fwdTextStyle, + nameFull, + Ui::NameTextOptions(), + context); + if (sender) { + _nameVersion = sender->nameVersion(); + } + const auto nameMaxWidth = previewSkip + + _name.maxWidth() + + (_hasQuoteIcon + ? st::messageTextStyle.blockquote.icon.width() + : 0); + const auto storySkip = fields.storyId + ? (st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width()) + : 0; + const auto optimalTextSize = _multiline + ? countMultilineOptimalSize(previewSkip) + : QSize( + (previewSkip + + storySkip + + std::min(_text.maxWidth(), st::maxSignatureSize)), + st::normalFont->height); + _maxWidth = std::max(nameMaxWidth, optimalTextSize.width()); + if (!data->displaying()) { + const auto unavailable = data->unavailable(); + _stateText = ((fields.messageId || fields.storyId) && !unavailable) + ? tr::lng_profile_loading(tr::now) + : fields.storyId + ? tr::lng_deleted_story(tr::now) + : tr::lng_deleted_message(tr::now); + const auto phraseWidth = st::msgDateFont->width(_stateText); + _maxWidth = unavailable + ? phraseWidth + : std::max(_maxWidth, phraseWidth); + } else { + _stateText = QString(); + } + _maxWidth = st::historyReplyPadding.left() + + _maxWidth + + st::historyReplyPadding.right(); + _minHeight = st::historyReplyPadding.top() + + st::msgServiceNameFont->height + + optimalTextSize.height() + + st::historyReplyPadding.bottom(); +} + +int Reply::resizeToWidth(int width) const { + _ripple.animation = nullptr; + + const auto previewSkip = _hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; + if (width >= _maxWidth || !_multiline) { + _nameTwoLines = 0; + _expandable = 0; + _height = _minHeight; + return height(); + } + const auto innerw = width + - st::historyReplyPadding.left() + - st::historyReplyPadding.right(); + const auto namew = innerw - previewSkip; + const auto desiredNameHeight = _name.countHeight(namew); + _nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0; + const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height; + const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; + auto lineCounter = 0; + auto elided = false; + const auto texth = _text.countDimensions( + textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height; + _expandable = (_multiline && elided) ? 1 : 0; + _height = st::historyReplyPadding.top() + + nameh + + (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth) + + st::historyReplyPadding.bottom(); + return height(); +} + +Ui::Text::GeometryDescriptor Reply::textGeometry( + int available, + int firstLineSkip, + not_null line, + not_null outElided) const { + return { .layout = [=](Ui::Text::LineGeometry in) { + const auto skip = (*line ? 0 : firstLineSkip); + ++*line; + *outElided = *outElided + || !_multiline + || (!_expanded + && (*line == kNonExpandedLinesLimit) + && in.width > available - skip); + in.width = available - skip; + in.left += skip; + in.elided = *outElided; + return in; + } }; +} + +int Reply::height() const { + return _height + st::historyReplyTop + st::historyReplyBottom; +} + +QMargins Reply::margins() const { + return QMargins(0, st::historyReplyTop, 0, st::historyReplyBottom); +} + +QSize Reply::countMultilineOptimalSize( + int previewSkip) const { + auto elided = false; + auto lineCounter = 0; + const auto max = previewSkip + _text.maxWidth(); + const auto result = _text.countDimensions( + textGeometry(max, previewSkip, &lineCounter, &elided)); + return { result.width, result.height }; +} + +void Reply::paint( + Painter &p, + not_null view, + const Ui::ChatPaintContext &context, + int x, + int y, + int w, + bool inBubble) const { + const auto st = context.st; + const auto stm = context.messageStyle(); + + y += st::historyReplyTop; + const auto rect = QRect(x, y, w, _height); + const auto selected = context.selected(); + const auto backgroundEmojiId = _colorPeer + ? _colorPeer->backgroundEmojiId() + : DocumentId(); + const auto colorIndexPlusOne = _colorPeer + ? (_colorPeer->colorIndex() + 1) + : _hiddenSenderColorIndexPlusOne; + const auto useColorIndex = colorIndexPlusOne && !context.outbg; + const auto colorPattern = colorIndexPlusOne + ? st->colorPatternIndex(colorIndexPlusOne - 1) + : 0; + const auto cache = !inBubble + ? (_hasQuoteIcon + ? st->serviceQuoteCache(colorPattern) + : st->serviceReplyCache(colorPattern)).get() + : useColorIndex + ? (_hasQuoteIcon + ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) + : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() + : (_hasQuoteIcon + ? stm->quoteCache[colorPattern] + : stm->replyCache[colorPattern]).get(); + const auto "eSt = _hasQuoteIcon + ? st::messageTextStyle.blockquote + : st::messageQuoteStyle; + const auto backgroundEmoji = backgroundEmojiId + ? st->backgroundEmojiData(backgroundEmojiId).get() + : nullptr; + const auto backgroundEmojiCache = backgroundEmoji + ? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex( + selected, + context.outbg, + inBubble, + colorIndexPlusOne)] + : nullptr; + const auto rippleColor = cache->bg; + if (!inBubble) { + cache->bg = QColor(0, 0, 0, 0); + } + Ui::Text::ValidateQuotePaintCache(*cache, quoteSt); + Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt); + if (backgroundEmoji) { + ValidateBackgroundEmoji( + backgroundEmojiId, + backgroundEmoji, + backgroundEmojiCache, + cache, + view); + if (!backgroundEmojiCache->frames[0].isNull()) { + FillBackgroundEmoji(p, rect, _hasQuoteIcon, *backgroundEmojiCache); + } + } + if (!inBubble) { + cache->bg = rippleColor; + } + + if (_ripple.animation) { + _ripple.animation->paint(p, x, y, w, &rippleColor); + if (_ripple.animation->empty()) { + _ripple.animation.reset(); + } + } + + auto hasPreview = (_hasPreview != 0); + auto previewSkip = hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; + if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) { + hasPreview = false; + previewSkip = 0; + } + + const auto pausedSpoiler = context.paused + || On(PowerSaving::kChatSpoiler); + auto textLeft = x + st::historyReplyPadding.left(); + auto textTop = y + + st::historyReplyPadding.top() + + (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1)); + if (w > st::historyReplyPadding.left()) { + if (_displaying) { + if (hasPreview) { + const auto data = view->data()->Get(); + const auto message = data + ? data->resolvedMessage.get() + : nullptr; + const auto media = message ? message->media() : nullptr; + const auto image = media + ? media->replyPreview() + : !data + ? nullptr + : data->resolvedStory + ? data->resolvedStory->replyPreview() + : nullptr; + if (image) { + auto to = style::rtlrect( + x + st::historyReplyPreviewMargin.left(), + y + st::historyReplyPreviewMargin.top(), + st::historyReplyPreview, + st::historyReplyPreview, + w + 2 * x); + const auto preview = image->pixSingle( + image->size() / style::DevicePixelRatio(), + { + .colored = (context.selected() + ? &st->msgStickerOverlay() + : nullptr), + .options = Images::Option::RoundSmall, + .outer = to.size(), + }); + p.drawPixmap(to.x(), to.y(), preview); + if (_spoiler) { + view->clearCustomEmojiRepaint(); + Ui::FillSpoilerRect( + p, + to, + Ui::DefaultImageSpoiler().frame( + _spoiler->index( + context.now, + pausedSpoiler))); + } + } + } + const auto textw = w + - st::historyReplyPadding.left() + - st::historyReplyPadding.right(); + const auto namew = textw - previewSkip; + auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; + if (namew > 0) { + p.setPen(!inBubble + ? st->msgImgReplyBarColor()->c + : useColorIndex + ? FromNameFg(context, colorIndexPlusOne - 1) + : stm->msgServiceFg->c); + _name.drawLeftElided( + p, + x + st::historyReplyPadding.left() + previewSkip, + y + st::historyReplyPadding.top(), + namew, + w + 2 * x, + _nameTwoLines ? 2 : 1); + + p.setPen(inBubble + ? stm->historyTextFg + : st->msgImgReplyBarColor()); + view->prepareCustomEmojiPaint(p, context, _text); + auto replyToTextPalette = &(!inBubble + ? st->imgReplyTextPalette() + : useColorIndex + ? st->coloredTextPalette(selected, colorIndexPlusOne - 1) + : stm->replyTextPalette); + if (_replyToStory) { + st::dialogsMiniReplyStory.icon.icon.paint( + p, + textLeft + firstLineSkip, + textTop, + w + 2 * x, + replyToTextPalette->linkFg->c); + firstLineSkip += st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width(); + } + auto owned = std::optional(); + auto copy = std::optional(); + if (inBubble && colorIndexPlusOne) { + copy.emplace(*replyToTextPalette); + owned.emplace(cache->icon); + copy->linkFg = owned->color(); + replyToTextPalette = &*copy; + } + auto l = 0; + auto e = false; + _text.draw(p, { + .position = { textLeft, textTop }, + .geometry = textGeometry(textw, firstLineSkip, &l, &e), + .palette = replyToTextPalette, + .spoiler = Ui::Text::DefaultSpoilerCache(), + .now = context.now, + .pausedEmoji = (context.paused + || On(PowerSaving::kEmojiChat)), + .pausedSpoiler = pausedSpoiler, + .elisionOneLine = true, + }); + p.setTextPalette(stm->textPalette); + } + } else { + p.setFont(st::msgDateFont); + p.setPen(cache->icon); + p.drawTextLeft( + textLeft, + (y + + st::historyReplyPadding.top() + + (st::msgDateFont->height / 2)), + w + 2 * x, + st::msgDateFont->elided( + _stateText, + x + w - textLeft - st::historyReplyPadding.right())); + } + } +} + +void Reply::createRippleAnimation( + not_null view, + QSize size) { + _ripple.animation = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask( + size, + st::messageQuoteStyle.radius), + [=] { view->history()->owner().requestViewRepaint(view); }); +} + +void Reply::saveRipplePoint(QPoint point) const { + _ripple.lastPoint = point; +} + +void Reply::addRipple() { + if (_ripple.animation) { + _ripple.animation->add(_ripple.lastPoint); + } +} + +void Reply::stopLastRipple() { + if (_ripple.animation) { + _ripple.animation->lastStop(); + } +} + +void Reply::unloadPersistentAnimation() { + _text.unloadPersistentAnimation(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_reply.h b/Telegram/SourceFiles/history/view/history_view_reply.h new file mode 100644 index 00000000000000..10b5ebb9bb7600 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_reply.h @@ -0,0 +1,116 @@ +/* +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/view/history_view_element.h" + +namespace Ui { +class SpoilerAnimation; +} // namespace Ui + +namespace HistoryView { + +class Reply final : public RuntimeComponent { +public: + Reply(); + Reply(const Reply &other) = delete; + Reply(Reply &&other) = delete; + Reply &operator=(const Reply &other) = delete; + Reply &operator=(Reply &&other); + ~Reply(); + + void update( + not_null view, + not_null data); + + [[nodiscard]] bool isNameUpdated( + not_null view, + not_null data) const; + void updateName( + not_null view, + not_null data, + std::optional resolvedSender = std::nullopt) const; + [[nodiscard]] int resizeToWidth(int width) const; + [[nodiscard]] int height() const; + [[nodiscard]] QMargins margins() const; + + bool expand(); + + void paint( + Painter &p, + not_null view, + const Ui::ChatPaintContext &context, + int x, + int y, + int w, + bool inBubble) const; + void unloadPersistentAnimation(); + + void createRippleAnimation(not_null view, QSize size); + void saveRipplePoint(QPoint point) const; + void addRipple(); + void stopLastRipple(); + + [[nodiscard]] int maxWidth() const { + return _maxWidth; + } + [[nodiscard]] ClickHandlerPtr link() const { + return _link; + } + +private: + [[nodiscard]] Ui::Text::GeometryDescriptor textGeometry( + int available, + int firstLineSkip, + not_null line, + not_null outElided) const; + [[nodiscard]] QSize countMultilineOptimalSize( + int firstLineSkip) const; + void setLinkFrom( + not_null view, + not_null data); + + [[nodiscard]] PeerData *sender( + not_null view, + not_null data) const; + [[nodiscard]] QString senderName( + not_null view, + not_null data, + bool shorten) const; + [[nodiscard]] QString senderName( + not_null peer, + bool shorten) const; + + ClickHandlerPtr _link; + std::unique_ptr _spoiler; + mutable PeerData *_externalSender = nullptr; + mutable PeerData *_colorPeer = nullptr; + mutable struct { + mutable std::unique_ptr animation; + QPoint lastPoint; + } _ripple; + mutable Ui::Text::String _name; + mutable Ui::Text::String _text; + mutable QString _stateText; + mutable int _maxWidth = 0; + mutable int _minHeight = 0; + mutable int _height = 0; + mutable int _nameVersion = 0; + uint8 _hiddenSenderColorIndexPlusOne = 0; + uint8 _hasQuoteIcon : 1 = 0; + uint8 _replyToStory : 1 = 0; + uint8 _expanded : 1 = 0; + mutable uint8 _expandable : 1 = 0; + mutable uint8 _nameTwoLines : 1 = 0; + mutable uint8 _hasPreview : 1 = 0; + mutable uint8 _displaying : 1 = 0; + mutable uint8 _multiline : 1 = 0; + +}; + +} // namespace HistoryView 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 a82fd13fd15c7d..140fdd86ad42c2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -419,7 +419,7 @@ bool ExtendedPreview::needsBubble() const { && (item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 340b1fa6689404..4f11171a82d4bb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -27,6 +27,7 @@ For license and copyright information please follow this link: #include "history/history.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_reply.h" #include "history/view/history_view_transcribe_button.h" #include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_media_spoiler.h" @@ -217,12 +218,12 @@ QSize Gif::countOptimalSize() { } else if (isUnwrapped()) { const auto item = _parent->data(); auto via = item->Get(); - auto reply = _parent->displayedReply(); + auto reply = _parent->Get(); auto forwarded = item->Get(); if (forwarded) { forwarded->create(via); } - maxWidth += additionalWidth(via, reply, forwarded); + maxWidth += additionalWidth(reply, via, forwarded); accumulate_max(maxWidth, _parent->reactionsOptimalWidth()); } return { maxWidth, minHeight }; @@ -274,10 +275,10 @@ QSize Gif::countCurrentSize(int newWidth) { const auto item = _parent->data(); auto via = item->Get(); - auto reply = _parent->displayedReply(); + auto reply = _parent->Get(); auto forwarded = item->Get(); if (via || reply || forwarded) { - auto additional = additionalWidth(via, reply, forwarded); + auto additional = additionalWidth(reply, via, forwarded); newWidth += additional; accumulate_min(newWidth, availableWidth); auto usew = maxWidth() - additional; @@ -385,13 +386,13 @@ void Gif::draw(Painter &p, const PaintContext &context) const { auto usex = 0, usew = paintw; const auto unwrapped = isUnwrapped(); const auto via = unwrapped ? item->Get() : nullptr; - const auto reply = unwrapped ? _parent->displayedReply() : nullptr; + const auto reply = unwrapped ? _parent->Get() : nullptr; const auto forwarded = unwrapped ? item->Get() : nullptr; const auto rightAligned = unwrapped && outbg && !_parent->delegate()->elementIsChatWide(); if (via || reply || forwarded) { - usew = maxWidth() - additionalWidth(via, reply, forwarded); + usew = maxWidth() - additionalWidth(reply, via, forwarded); if (rightAligned) { usex = width() - usew; } @@ -1013,13 +1014,13 @@ TextState Gif::textState(QPoint point, StateRequest request) const { const auto item = _parent->data(); auto usew = paintw, usex = 0; const auto via = unwrapped ? item->Get() : nullptr; - const auto reply = unwrapped ? _parent->displayedReply() : nullptr; + const auto reply = unwrapped ? _parent->Get() : nullptr; const auto forwarded = unwrapped ? item->Get() : nullptr; const auto rightAligned = unwrapped && outbg && !_parent->delegate()->elementIsChatWide(); if (via || reply || forwarded) { - usew = maxWidth() - additionalWidth(via, reply, forwarded); + usew = maxWidth() - additionalWidth(reply, via, forwarded); if (rightAligned) { usex = width() - usew; } @@ -1094,15 +1095,8 @@ TextState Gif::textState(QPoint point, StateRequest request) const { const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { result.link = reply->link(); - reply->ripple.lastPoint = point - replyRect.topLeft(); - if (!reply->ripple.animation) { - reply->ripple.animation = std::make_unique( - st::defaultRippleAnimation, - Ui::RippleAnimation::RoundRectMask( - replyRect.size(), - st::messageQuoteStyle.radius), - [=] { item->history()->owner().requestItemRepaint(item); }); - } + reply->saveRipplePoint(point - replyRect.topLeft()); + reply->createRippleAnimation(_parent, replyRect.size()); return result; } } @@ -1520,7 +1514,7 @@ bool Gif::needsBubble() const { return item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton(); @@ -1542,10 +1536,10 @@ QRect Gif::contentRectForReactions() const { && !_parent->delegate()->elementIsChatWide(); const auto item = _parent->data(); const auto via = item->Get(); - const auto reply = _parent->displayedReply(); + const auto reply = _parent->Get(); const auto forwarded = item->Get(); if (via || reply || forwarded) { - usew = maxWidth() - additionalWidth(via, reply, forwarded); + usew = maxWidth() - additionalWidth(reply, via, forwarded); } accumulate_max(usew, _parent->reactionsOptimalWidth()); if (rightAligned) { @@ -1602,8 +1596,8 @@ QPoint Gif::resolveCustomInfoRightBottom() const { int Gif::additionalWidth() const { const auto item = _parent->data(); return additionalWidth( + _parent->Get(), item->Get(), - item->Get(), item->Get()); } @@ -1763,7 +1757,10 @@ void Gif::refreshCaption() { _caption = createCaption(_parent->data()); } -int Gif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { +int Gif::additionalWidth( + const Reply *reply, + const HistoryMessageVia *via, + const HistoryMessageForwarded *forwarded) const { int result = 0; if (forwarded) { accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index eac33cc7a3dbd9..f2054977134772 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -37,6 +37,7 @@ enum class Error; namespace HistoryView { +class Reply; class TranscribeButton; class Gif final : public File { @@ -176,8 +177,8 @@ class Gif final : public File { [[nodiscard]] bool needInfoDisplay() const; [[nodiscard]] bool needCornerStatusDisplay() const; [[nodiscard]] int additionalWidth( + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const; [[nodiscard]] int additionalWidth() const; [[nodiscard]] bool isUnwrapped() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index ab75f2051b0cc6..293849ba3e142b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -375,7 +375,7 @@ bool Location::needsBubble() const { return item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton(); 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 bea617b2361ec7..d0133aa76afac8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -869,7 +869,7 @@ bool GroupedMedia::computeNeedBubble() const { if (item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton() 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 69f4875e1d70bd..e612022391a108 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -13,6 +13,7 @@ For license and copyright information please follow this link: #include "history/view/media/history_view_sticker.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_reply.h" #include "history/history_item.h" #include "history/history_item_components.h" #include "lottie/lottie_single_player.h" @@ -54,13 +55,13 @@ QSize UnwrappedMedia::countOptimalSize() { if (_parent->media() == this) { const auto item = _parent->data(); const auto via = item->Get(); - const auto reply = _parent->displayedReply(); + const auto reply = _parent->Get(); const auto topic = _parent->displayedTopicButton(); const auto forwarded = getDisplayedForwardedInfo(); if (forwarded) { forwarded->create(via); } - maxWidth += additionalWidth(topic, via, reply, forwarded); + maxWidth += additionalWidth(topic, reply, via, forwarded); accumulate_max(maxWidth, _parent->reactionsOptimalWidth()); if (const auto size = _parent->rightActionSize()) { minHeight = std::max( @@ -93,11 +94,11 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { accumulate_max(newWidth, _parent->reactionsOptimalWidth()); _topAdded = 0; const auto via = item->Get(); - const auto reply = _parent->displayedReply(); + const auto reply = _parent->Get(); const auto topic = _parent->displayedTopicButton(); const auto forwarded = getDisplayedForwardedInfo(); if (topic || via || reply || forwarded) { - const auto additional = additionalWidth(topic, via, reply, forwarded); + const auto additional = additionalWidth(topic, reply, via, forwarded); const auto optimalw = maxWidth() - additional; const auto additionalMinWidth = std::min(additional, st::msgReplyPadding.left() + st::msgMinWidth / 2); _additionalOnTop = (optimalw + additionalMinWidth) > newWidth; @@ -107,7 +108,7 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { if (reply) { [[maybe_unused]] auto h = reply->resizeToWidth(surroundingWidth); } - const auto surrounding = surroundingInfo(topic, via, reply, forwarded, surroundingWidth); + const auto surrounding = surroundingInfo(topic, reply, via, forwarded, surroundingWidth); if (_additionalOnTop) { _topAdded = surrounding.height + st::msgMargin.bottom(); newHeight += _topAdded; @@ -166,17 +167,17 @@ void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const { if (!inWebPage && (context.skipDrawingParts != PaintContext::SkipDrawingParts::Surrounding)) { const auto via = inWebPage ? nullptr : item->Get(); - const auto reply = inWebPage ? nullptr : _parent->displayedReply(); + const auto reply = inWebPage ? nullptr : _parent->Get(); const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton(); const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); - drawSurrounding(p, inner, context, topic, via, reply, forwarded); + drawSurrounding(p, inner, context, topic, reply, via, forwarded); } } UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo( const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded, int outerw) const { if (!topic && !via && !reply && !forwarded) { @@ -242,8 +243,8 @@ void UnwrappedMedia::drawSurrounding( const QRect &inner, const PaintContext &context, const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { const auto st = context.st; const auto sti = context.imageStyle(); @@ -263,9 +264,9 @@ void UnwrappedMedia::drawSurrounding( } auto replyRight = 0; auto rectw = _additionalOnTop - ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, via, reply, forwarded)) + ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, reply, via, forwarded)) : (width() - inner.width() - st::msgReplyPadding.left()); - if (const auto surrounding = surroundingInfo(topic, via, reply, forwarded, rectw)) { + if (const auto surrounding = surroundingInfo(topic, reply, via, forwarded, rectw)) { auto recth = surrounding.panelHeight; if (!surrounding.topicSize.isEmpty()) { auto rectw = surrounding.topicSize.width(); @@ -416,14 +417,14 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { if (_parent->media() == this) { const auto via = inWebPage ? nullptr : item->Get(); - const auto reply = inWebPage ? nullptr : _parent->displayedReply(); + const auto reply = inWebPage ? nullptr : _parent->Get(); const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton(); const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto replyRight = 0; auto rectw = _additionalOnTop - ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, via, reply, forwarded)) + ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, reply, via, forwarded)) : (width() - inner.width() - st::msgReplyPadding.left()); - if (const auto surrounding = surroundingInfo(topic, via, reply, forwarded, rectw)) { + if (const auto surrounding = surroundingInfo(topic, reply, via, forwarded, rectw)) { auto recth = surrounding.panelHeight; if (!surrounding.topicSize.isEmpty()) { auto rectw = surrounding.topicSize.width(); @@ -486,16 +487,8 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { result.link = reply->link(); - reply->ripple.lastPoint = point - replyRect.topLeft(); - if (!reply->ripple.animation) { - reply->ripple.animation = std::make_unique( - st::defaultRippleAnimation, - Ui::RippleAnimation::RoundRectMask( - replyRect.size(), - st::messageQuoteStyle.radius), - [=] { item->history()->owner().requestItemRepaint(item); }); - } - return result; + reply->saveRipplePoint(point - replyRect.topLeft()); + reply->createRippleAnimation(_parent, replyRect.size()); } } replyRight = rectx + rectw - st::msgReplyPadding.right(); @@ -542,7 +535,7 @@ bool UnwrappedMedia::hasTextForCopy() const { } bool UnwrappedMedia::dragItemByHandler(const ClickHandlerPtr &p) const { - const auto reply = _parent->displayedReply(); + const auto reply = _parent->Get(); return !reply || (reply->link() != p); } @@ -649,8 +642,8 @@ bool UnwrappedMedia::needInfoDisplay() const { int UnwrappedMedia::additionalWidth( const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { auto result = st::msgReplyPadding.left() + _parent->infoWidth() + 2 * st::msgDateImgPadding.x(); if (topic) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index 9932c0521cc311..1cd71c86acf5b9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -17,6 +17,7 @@ struct HistoryMessageForwarded; namespace HistoryView { +class Reply; struct TopicButton; class UnwrappedMedia final : public Media { @@ -120,8 +121,8 @@ class UnwrappedMedia final : public Media { }; [[nodiscard]] SurroundingInfo surroundingInfo( const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded, int outerw) const; void drawSurrounding( @@ -129,8 +130,8 @@ class UnwrappedMedia final : public Media { const QRect &inner, const PaintContext &context, const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const; QSize countOptimalSize() override; @@ -139,8 +140,8 @@ class UnwrappedMedia final : public Media { bool needInfoDisplay() const; int additionalWidth( const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const; int calculateFullRight(const QRect &inner) const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index de72e77864faed..b3d79c6fe2f027 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -1077,7 +1077,7 @@ bool Photo::needsBubble() const { && (item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton()); From 537c656ee14912961e1915fc5599b6105b19b53f Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Nov 2023 16:03:08 +0400 Subject: [PATCH 52/70] Show external reply media preview. --- .../SourceFiles/data/data_file_origin.cpp | 8 ++++ .../SourceFiles/data/data_media_types.cpp | 47 +++++++++---------- Telegram/SourceFiles/data/data_media_types.h | 1 + Telegram/SourceFiles/history/history_item.cpp | 6 +-- .../history/history_item_components.cpp | 29 ++++++++++-- .../history/history_item_components.h | 5 +- .../history/view/history_view_item_preview.h | 1 + .../history/view/history_view_reply.cpp | 35 ++++++++++++-- 8 files changed, 96 insertions(+), 36 deletions(-) diff --git a/Telegram/SourceFiles/data/data_file_origin.cpp b/Telegram/SourceFiles/data/data_file_origin.cpp index d37663208cc96d..9cac2ec4b5b65e 100644 --- a/Telegram/SourceFiles/data/data_file_origin.cpp +++ b/Telegram/SourceFiles/data/data_file_origin.cpp @@ -86,9 +86,16 @@ struct FileReferenceAccumulator { }, [](const auto &data) { }); } + void push(const MTPMessageReplyHeader &data) { + data.match([&](const MTPDmessageReplyHeader &data) { + push(data.vreply_media()); + }, [](const MTPDmessageReplyStoryHeader &data) { + }); + } void push(const MTPMessage &data) { data.match([&](const MTPDmessage &data) { push(data.vmedia()); + push(data.vreply_to()); }, [&](const MTPDmessageService &data) { data.vaction().match( [&](const MTPDmessageActionChatEditPhoto &data) { @@ -99,6 +106,7 @@ struct FileReferenceAccumulator { push(data.vwallpaper()); }, [](const auto &data) { }); + push(data.vreply_to()); }, [](const MTPDmessageEmpty &data) { }); } diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index cb0278a274f299..79de11506dea94 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -705,7 +705,7 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const { } } const auto type = tr::lng_in_dlg_photo(tr::now); - const auto caption = options.hideCaption + const auto caption = (options.hideCaption || options.ignoreMessageText) ? TextWithEntities() : options.translated ? parent()->translatedText() @@ -951,7 +951,7 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { } return tr::lng_in_dlg_file(tr::now); }(); - const auto caption = options.hideCaption + const auto caption = (options.hideCaption || options.ignoreMessageText) ? TextWithEntities() : options.translated ? parent()->translatedText() @@ -1500,7 +1500,9 @@ bool MediaWebPage::replyPreviewLoaded() const { } ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const { - auto text = options.translated + auto text = options.ignoreMessageText + ? TextWithEntities() + : options.translated ? parent()->translatedText() : parent()->originalText(); if (text.empty()) { @@ -2038,28 +2040,23 @@ MediaStory::MediaStory( owner->registerStoryItem(storyId, parent); const auto stories = &owner->stories(); - if (const auto maybeStory = stories->lookup(storyId)) { - if (!_mention) { - parent->setText((*maybeStory)->caption()); - } - } else { - if (maybeStory.error() == NoStory::Unknown) { - stories->resolve(storyId, crl::guard(this, [=] { - if (const auto maybeStory = stories->lookup(storyId)) { - if (!_mention) { - parent->setText((*maybeStory)->caption()); - } - } else { - _expired = true; - } - if (_mention) { - parent->updateStoryMentionText(); + const auto maybeStory = stories->lookup(storyId); + if (!maybeStory && maybeStory.error() == NoStory::Unknown) { + stories->resolve(storyId, crl::guard(this, [=] { + if (const auto maybeStory = stories->lookup(storyId)) { + if (!_mention && _viewMayExist) { + parent->setText((*maybeStory)->caption()); } - parent->history()->owner().requestItemViewRefresh(parent); - })); - } else { - _expired = true; - } + } else { + _expired = true; + } + if (_mention) { + parent->updateStoryMentionText(); + } + parent->history()->owner().requestItemViewRefresh(parent); + })); + } else if (!maybeStory) { + _expired = true; } } @@ -2154,6 +2151,7 @@ std::unique_ptr MediaStory::createView( if (_mention) { return nullptr; } + _viewMayExist = true; return std::make_unique( message, realParent, @@ -2161,6 +2159,7 @@ std::unique_ptr MediaStory::createView( spoiler); } _expired = false; + _viewMayExist = true; const auto story = *maybeStory; if (_mention) { return std::make_unique( diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 9af199b4dd52e5..b62cf98259d9bb 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -623,6 +623,7 @@ class MediaStory final : public Media, public base::has_weak_ptr { private: const FullStoryId _storyId; const bool _mention = false; + bool _viewMayExist = false; bool _expired = false; }; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f233b08d0d0f51..0a3d3563d234b8 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -451,7 +451,7 @@ HistoryItem::HistoryItem( config.reply.topicPost = (topicRootId != 0); if (const auto originalReply = original->Get()) { if (originalReply->external()) { - config.reply = originalReply->fields(); + config.reply = originalReply->fields().clone(this); if (!config.reply.externalPeerId) { config.reply.messageId = 0; } @@ -3133,7 +3133,7 @@ void HistoryItem::createComponents(CreateConfig &&config) { UpdateComponents(mask); if (const auto reply = Get()) { - reply->set(config.reply); + reply->set(std::move(config.reply)); if (!reply->updateData(this)) { if (const auto messageId = reply->messageId()) { RequestDependentMessageItem( @@ -3456,7 +3456,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) { }); } if (const auto reply = data.vreply_to()) { - config.reply = ReplyFieldsFromMTP(history(), *reply); + config.reply = ReplyFieldsFromMTP(this, *reply); } config.viaBotId = data.vvia_bot_id().value_or_empty(); config.viewsCount = data.vviews().value_or(-1); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index c92f9f855b2d4b..e4438b1c5fa776 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -270,15 +270,33 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { } } +ReplyFields ReplyFields::clone(not_null parent) const { + return { + .quote = quote, + .externalMedia = (externalMedia + ? externalMedia->clone(parent) + : nullptr), + .externalSenderId = externalSenderId, + .externalSenderName = externalSenderName, + .externalPostAuthor = externalPostAuthor, + .externalPeerId = externalPeerId, + .messageId = messageId, + .topMessageId = topMessageId, + .storyId = storyId, + .topicPost = topicPost, + .manualQuote = manualQuote, + }; +} + ReplyFields ReplyFieldsFromMTP( - not_null history, + not_null item, const MTPMessageReplyHeader &reply) { return reply.match([&](const MTPDmessageReplyHeader &data) { auto result = ReplyFields(); if (const auto peer = data.vreply_to_peer_id()) { result.externalPeerId = peerFromMTP(*peer); } - const auto owner = &history->owner(); + const auto owner = &item->history()->owner(); if (const auto id = data.vreply_to_msg_id().value_or_empty()) { result.messageId = data.is_reply_to_scheduled() ? owner->scheduledMessages().localMessageId(id) @@ -297,6 +315,9 @@ ReplyFields ReplyFieldsFromMTP( result.externalSenderName = qs(data.vfrom_name().value_or_empty()); } + if (const auto media = data.vreply_media()) { + result.externalMedia = HistoryItem::CreateMedia(item, *media); + } result.quote = TextWithEntities{ qs(data.vquote_text().value_or_empty()), Api::EntitiesFromMTP( @@ -357,6 +378,7 @@ HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply::~HistoryMessageReply() { // clearData() should be called by holder. Expects(resolvedMessage.empty()); + _fields.externalMedia = nullptr; } bool HistoryMessageReply::updateData( @@ -407,7 +429,8 @@ bool HistoryMessageReply::updateData( const auto displaying = resolvedMessage || resolvedStory - || (!_fields.quote.empty() && (!_fields.messageId || force)); + || ((!_fields.quote.empty() || _fields.externalMedia) + && (!_fields.messageId || force)); _displaying = displaying ? 1 : 0; const auto unavailable = !resolvedMessage diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index b23b509b0fac7a..f78671ba33ef5c 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -233,7 +233,10 @@ class ReplyToStoryPointer final { }; struct ReplyFields { + ReplyFields clone(not_null parent) const; + TextWithEntities quote; + std::unique_ptr externalMedia; PeerId externalSenderId = 0; QString externalSenderName; QString externalPostAuthor; @@ -246,7 +249,7 @@ struct ReplyFields { }; [[nodiscard]] ReplyFields ReplyFieldsFromMTP( - not_null history, + not_null item, const MTPMessageReplyHeader &reply); [[nodiscard]] FullReplyTo ReplyToFromMTP( diff --git a/Telegram/SourceFiles/history/view/history_view_item_preview.h b/Telegram/SourceFiles/history/view/history_view_item_preview.h index f2304c49e66c43..b29da9442d1f65 100644 --- a/Telegram/SourceFiles/history/view/history_view_item_preview.h +++ b/Telegram/SourceFiles/history/view/history_view_item_preview.h @@ -40,6 +40,7 @@ struct ToPreviewOptions { const std::vector *existing = nullptr; bool hideSender = false; bool hideCaption = false; + bool ignoreMessageText = false; bool generateImages = true; bool ignoreGroup = false; bool ignoreTopic = true; diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 5d9722231b5510..8ed051297088f5 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -15,6 +15,7 @@ For license and copyright information please follow this link: #include "data/data_session.h" #include "data/data_story.h" #include "data/data_user.h" +#include "history/view/history_view_item_preview.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" @@ -176,6 +177,7 @@ void Reply::update( const auto &fields = data->fields(); const auto message = data->resolvedMessage.get(); const auto story = data->resolvedStory.get(); + const auto externalMedia = fields.externalMedia.get(); if (!_externalSender) { if (const auto id = fields.externalSenderId) { _externalSender = view->history()->owner().peer(id); @@ -195,7 +197,8 @@ void Reply::update( const auto hasPreview = (story && story->hasReplyPreview()) || (message && message->media() - && message->media()->hasReplyPreview()); + && message->media()->hasReplyPreview()) + || (externalMedia && externalMedia->hasReplyPreview()); _hasPreview = hasPreview ? 1 : 0; _displaying = data->displaying() ? 1 : 0; _multiline = data->multiline() ? 1 : 0; @@ -213,6 +216,15 @@ void Reply::update( ? message->inReplyText() : story ? story->inReplyText() + : externalMedia + ? externalMedia->toPreview({ + .hideSender = true, + .hideCaption = true, + .ignoreMessageText = true, + .generateImages = false, + .ignoreGroup = true, + .ignoreTopic = true, + }).text : TextWithEntities(); const auto repaint = [=] { item->customEmojiRepaint(); }; const auto context = Core::MarkedTextContext{ @@ -222,7 +234,7 @@ void Reply::update( _text.setMarkedText( st::defaultTextStyle, text, - Ui::DialogTextOptions(), + _multiline ? Ui::ItemTextDefaultOptions() : Ui::DialogTextOptions(), context); updateName(view, data); @@ -388,7 +400,9 @@ void Reply::updateName( const auto externalPeer = fields.externalPeerId ? view->history()->owner().peer(fields.externalPeerId).get() : nullptr; - const auto groupNameAdded = (externalPeer && externalPeer != sender); + const auto groupNameAdded = externalPeer + && (externalPeer != sender) + && (externalPeer->isChat() || externalPeer->isMegagroup()); const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded; const auto name = sender ? senderName(sender, shorten) @@ -508,10 +522,16 @@ int Reply::resizeToWidth(int width) const { auto elided = false; const auto texth = _text.countDimensions( textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height; + const auto useh = elided + ? (kNonExpandedLinesLimit * st::normalFont->height) + : std::max(texth, st::normalFont->height); + if (!texth) { + int a = 0; + } _expandable = (_multiline && elided) ? 1 : 0; _height = st::historyReplyPadding.top() + nameh - + (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth) + + useh + st::historyReplyPadding.bottom(); return height(); } @@ -551,7 +571,10 @@ QSize Reply::countMultilineOptimalSize( const auto max = previewSkip + _text.maxWidth(); const auto result = _text.countDimensions( textGeometry(max, previewSkip, &lineCounter, &elided)); - return { result.width, result.height }; + return { + result.width, + std::max(result.height, st::normalFont->height), + }; } void Reply::paint( @@ -663,6 +686,8 @@ void Reply::paint( ? nullptr : data->resolvedStory ? data->resolvedStory->replyPreview() + : data->fields().externalMedia + ? data->fields().externalMedia->replyPreview() : nullptr; if (image) { auto to = style::rtlrect( From d6c37606467d98640b86cb8379c3f4b9bd343c15 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 4 Nov 2023 21:16:04 +0400 Subject: [PATCH 53/70] Improve external reply icons paddings. --- .../data/stickers/data_custom_emoji.cpp | 33 ++++++++++++++++--- .../data/stickers/data_custom_emoji.h | 2 ++ .../history/view/history_view_reply.cpp | 15 +++++---- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 88f6a0785b46e8..123621b37d4c23 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -93,6 +93,14 @@ class CallbackListener final : public CustomEmojiManager::Listener { return u"internal:"_q; } +[[nodiscard]] QString InternalPadding(QMargins value) { + return value.isNull() ? QString() : QString(",%1,%2,%3,%4" + ).arg(value.left() + ).arg(value.top() + ).arg(value.right() + ).arg(value.bottom()); +} + } // namespace class CustomEmojiLoader final @@ -549,13 +557,21 @@ std::unique_ptr CustomEmojiManager::create( std::unique_ptr CustomEmojiManager::internal( QStringView data) { - const auto index = data.mid(InternalPrefix().size()).toInt(); + const auto v = data.mid(InternalPrefix().size()).split(','); + if (v.size() != 5 && v.size() != 1) { + return nullptr; + } + const auto index = v[0].toInt(); Assert(index >= 0 && index < _internalEmoji.size()); auto &info = _internalEmoji[index]; + 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(), info.image, + padding, info.textColor); } @@ -906,17 +922,21 @@ uint64 CustomEmojiManager::coloredSetId() const { QString CustomEmojiManager::registerInternalEmoji( QImage emoji, + QMargins padding, bool textColor) { _internalEmoji.push_back({ std::move(emoji), textColor }); - return InternalPrefix() + QString::number(_internalEmoji.size() - 1); + return InternalPrefix() + + QString::number(_internalEmoji.size() - 1) + + InternalPadding(padding); } QString CustomEmojiManager::registerInternalEmoji( const style::icon &icon, + QMargins padding, bool textColor) { const auto i = _iconEmoji.find(&icon); if (i != end(_iconEmoji)) { - return i->second; + return i->second + InternalPadding(padding); } auto image = QImage( icon.size() * style::DevicePixelRatio(), @@ -927,9 +947,12 @@ QString CustomEmojiManager::registerInternalEmoji( icon.paint(p, 0, 0, icon.width()); p.end(); - const auto result = registerInternalEmoji(std::move(image), textColor); + const auto result = registerInternalEmoji( + std::move(image), + QMargins{}, + textColor); _iconEmoji.emplace(&icon, result); - return result; + return result + 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 01eb1fd79bd054..947f8bfdaae153 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -85,9 +85,11 @@ class CustomEmojiManager final : public base::has_weak_ptr { [[nodiscard]] QString registerInternalEmoji( QImage emoji, + QMargins padding = {}, bool textColor = true); [[nodiscard]] QString registerInternalEmoji( const style::icon &icon, + QMargins padding = {}, bool textColor = true); [[nodiscard]] uint64 coloredSetId() const; diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 8ed051297088f5..2873378230717e 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -415,19 +415,22 @@ void Reply::updateName( - st::historyReplyPadding.left()) : 0; const auto peerIcon = [](PeerData *peer) { + using namespace std; return !peer - ? &st::historyReplyUser + ? pair(&st::historyReplyUser, st::historyReplyUserPadding) : peer->isBroadcast() - ? &st::historyReplyChannel + ? pair(&st::historyReplyChannel, st::historyReplyChannelPadding) : (peer->isChannel() || peer->isChat()) - ? &st::historyReplyGroup - : &st::historyReplyUser; + ? pair(&st::historyReplyGroup, st::historyReplyGroupPadding) + : pair(&st::historyReplyUser, st::historyReplyUserPadding); }; const auto peerEmoji = [&](PeerData *peer) { const auto owner = &view->history()->owner(); + const auto icon = peerIcon(peer); return Ui::Text::SingleCustomEmoji( owner->customEmojiManager().registerInternalEmoji( - *peerIcon(peer))); + *icon.first, + icon.second)); }; auto nameFull = TextWithEntities(); if (!groupNameAdded && data->external() && !fields.storyId) { @@ -435,7 +438,7 @@ void Reply::updateName( } nameFull.append(name); if (groupNameAdded) { - nameFull.append(peerEmoji(externalPeer)); + nameFull.append(' ').append(peerEmoji(externalPeer)); nameFull.append(externalPeer->name()); } if (!viaBotUsername.isEmpty()) { From 150432e4d18e6c5f548c20046d0a431d0360990c Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 4 Nov 2023 22:57:42 +0400 Subject: [PATCH 54/70] Add explicit CMAKE_MSVC_RUNTIME_LIBRARY. --- Telegram/build/prepare/prepare.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 24618f3fbaf10c..bee5a987a9fe72 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -265,7 +265,7 @@ def winFailOnEach(command): startingCommand = True for command in commands: command = re.sub(r'\$([A-Za-z0-9_]+)', r'%\1%', command) - if re.search(r'\$', command): + if re.search(r'\$[^<]', command): error('Bad command: ' + command) appendCall = startingCommand and not re.match(r'(if|for) ', command) called = 'call ' + command if appendCall else command @@ -518,6 +518,7 @@ def runStages(): win: cmake . ^ -A %WIN32X64% ^ + -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ -DCMAKE_C_FLAGS="/DZLIB_WINAPI" @@ -621,6 +622,7 @@ def runStages(): cmake -B out . ^ -A %WIN32X64% ^ -DCMAKE_INSTALL_PREFIX=%LIBS_DIR%/local ^ + -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" cmake --build out --config Debug @@ -729,6 +731,7 @@ def runStages(): cmake . ^ -A %WIN32X64% ^ -DCMAKE_INSTALL_PREFIX=%LIBS_DIR%/local ^ + -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ -DBUILD_SHARED_LIBS=OFF ^ @@ -748,6 +751,7 @@ def runStages(): cmake . ^ -A %WIN32X64% ^ -DCMAKE_INSTALL_PREFIX=%LIBS_DIR%/local ^ + -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -DCMAKE_C_FLAGS="/DLIBDE265_STATIC_BUILD" ^ -DCMAKE_CXX_FLAGS="/DLIBDE265_STATIC_BUILD" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ @@ -774,6 +778,7 @@ def runStages(): cmake . ^ -A %WIN32X64% ^ -DCMAKE_INSTALL_PREFIX=%LIBS_DIR%/local ^ + -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_CXX_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG" ^ @@ -800,6 +805,7 @@ def runStages(): cmake . ^ -A %WIN32X64% ^ -DCMAKE_INSTALL_PREFIX=%LIBS_DIR%/local ^ + -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" ^ -DCMAKE_C_FLAGS="/DJXL_STATIC_DEFINE /DJXL_THREADS_STATIC_DEFINE" ^ -DCMAKE_CXX_FLAGS="/DJXL_STATIC_DEFINE /DJXL_THREADS_STATIC_DEFINE" ^ -DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^ From b8c76cac61271fb15c02f4993b6a84c42de3704d Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 5 Nov 2023 00:03:44 +0400 Subject: [PATCH 55/70] Use dot in monochrome tray icon on Windows. --- Telegram/Resources/icons/tray_monochrome.svg | 6 ++ Telegram/Resources/qrc/telegram/telegram.qrc | 1 + .../SourceFiles/platform/win/tray_win.cpp | 92 ++++++++++++------- 3 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 Telegram/Resources/icons/tray_monochrome.svg diff --git a/Telegram/Resources/icons/tray_monochrome.svg b/Telegram/Resources/icons/tray_monochrome.svg new file mode 100644 index 00000000000000..b960a4afd36b27 --- /dev/null +++ b/Telegram/Resources/icons/tray_monochrome.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index 8eac62c66a5d0f..0e43acb03d3432 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -28,6 +28,7 @@ ../../icons/settings/dino.svg ../../icons/settings/star.svg ../../icons/settings/starmini.svg + ../../icons/tray_monochrome.svg ../../art/topic_icons/blue.svg ../../art/topic_icons/yellow.svg ../../art/topic_icons/violet.svg diff --git a/Telegram/SourceFiles/platform/win/tray_win.cpp b/Telegram/SourceFiles/platform/win/tray_win.cpp index 9c72068e96effa..fda59791e822e3 100644 --- a/Telegram/SourceFiles/platform/win/tray_win.cpp +++ b/Telegram/SourceFiles/platform/win/tray_win.cpp @@ -12,6 +12,7 @@ For license and copyright information please follow this link: #include "core/application.h" #include "main/main_session.h" #include "storage/localstorage.h" +#include "ui/painter.h" #include "ui/ui_utility.h" #include "ui/widgets/popup_menu.h" #include "window/window_controller.h" @@ -23,6 +24,7 @@ For license and copyright information please follow this link: #include #include #include +#include namespace Platform { @@ -61,6 +63,49 @@ constexpr auto kTooltipDelay = crl::time(10000); return (value == 0); } +[[nodiscard]] QImage MonochromeIconFor(int size, bool darkMode) { + Expects(size > 0); + + static const auto Content = [&] { + auto f = QFile(u":/gui/icons/tray/monochrome.svg"_q); + f.open(QIODevice::ReadOnly); + return f.readAll(); + }(); + static auto Mask = QImage(); + static auto Size = 0; + if (Mask.isNull() || Size != size) { + Size = size; + Mask = QImage(size, size, QImage::Format_ARGB32_Premultiplied); + Mask.fill(Qt::transparent); + auto p = QPainter(&Mask); + QSvgRenderer(Content).render(&p, QRectF(0, 0, size, size)); + } + static auto Colored = QImage(); + static auto ColoredDark = QImage(); + auto &use = darkMode ? ColoredDark : Colored; + if (use.size() != Mask.size()) { + const auto color = darkMode ? 255 : 0; + const auto alpha = darkMode ? 255 : 228; + use = style::colorizeImage(Mask, { color, color, color, alpha }); + } + return use; +} + +[[nodiscard]] QImage MonochromeWithDot(QImage image, style::color color) { + auto p = QPainter(&image); + auto hq = PainterHighQualityEnabler(p); + const auto xm = image.width() / 16.; + const auto ym = image.height() / 16.; + p.setBrush(color); + p.setPen(Qt::NoPen); + p.drawEllipse(QRectF( // cx=3.9, cy=12.7, r=2.2 + 1.7 * xm, + 10.5 * ym, + 4.4 * xm, + 4.4 * ym)); + return image; +} + [[nodiscard]] QImage ImageIconWithCounter( Window::CounterLayerArgs &&args, bool supportMode, @@ -83,42 +128,16 @@ constexpr auto kTooltipDelay = crl::time(10000); auto result = [&] { if (const auto it = scaled.find(args.size); it != scaled.end()) { return it->second; - } else { - if (monochrome && darkMode) { - const auto withColor = [&](QColor color) -> std::pair { - if (args.size <= 16) { - return { st::macTrayIcon.instance(color, 100 / cIntRetinaFactor()), 3 }; - } else if (args.size <= 32) { - return { st::macTrayIcon.instance(color, 200 / cIntRetinaFactor()), 6 }; - } else { - return { st::macTrayIcon.instance(color, 300 / cIntRetinaFactor()), 9 }; - } - }; - const auto result = *darkMode - ? withColor({ 255, 255, 255 }) - : withColor({ 0, 0, 0, 228 }); - auto &image = scaled.emplace( - args.size, - result.first.copy( - QRect( - QPoint(result.second, result.second), - result.first.size() - - QSize(result.second * 2, result.second * 2) - ) - ).scaledToWidth(args.size, Qt::SmoothTransformation) - ).first->second; - image.setDevicePixelRatio(1); - return image; - } else { - return scaled.emplace( - args.size, - (smallIcon - ? Window::LogoNoMargin() - : Window::Logo() - ).scaledToWidth(args.size, Qt::SmoothTransformation) - ).first->second; - } + } else if (monochrome && darkMode) { + return MonochromeIconFor(args.size, *darkMode); } + return scaled.emplace( + args.size, + (smallIcon + ? Window::LogoNoMargin() + : Window::Logo() + ).scaledToWidth(args.size, Qt::SmoothTransformation) + ).first->second; }(); if ((!monochrome || !darkMode) && supportMode) { Window::ConvertIconToBlack(result); @@ -126,6 +145,9 @@ constexpr auto kTooltipDelay = crl::time(10000); if (!args.count) { return result; } else if (smallIcon) { + if (monochrome && darkMode) { + return MonochromeWithDot(std::move(result), args.bg); + } return Window::WithSmallCounter(std::move(result), std::move(args)); } QPainter p(&result); From 332ae22111ea6abf2fc54216a38ae137daee8292 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 5 Nov 2023 11:01:49 +0400 Subject: [PATCH 56/70] Fix build of crashpad with Xcode < 15. --- Telegram/build/prepare/prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index bee5a987a9fe72..8cd67af64e2d5a 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1216,7 +1216,7 @@ def runStages(): mac: git clone https://github.com/desktop-app/crashpad.git cd crashpad - git checkout f07f49e287 + git checkout 3279fae3f0 git submodule init git submodule update third_party/mini_chromium ZLIB_PATH=$USED_PREFIX/include From 20ba417dddc2788c4f84c31f031354d80079b298 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 12:06:21 +0400 Subject: [PATCH 57/70] Fix sending messages from "View as Messages". --- Telegram/SourceFiles/apiwrap.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 02e3bb9c1b0fcb..c3024e991ed471 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3580,7 +3580,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendAction(action); const auto clearCloudDraft = action.clearDraft; - const auto topicRootId = action.replyTo.topicRootId; + const auto replyTo = action.replyTo.messageId + ? peer->owner().message(action.replyTo.messageId) + : nullptr; + const auto topicRootId = action.replyTo.topicRootId + ? action.replyTo.topicRootId + : replyTo + ? replyTo->topicRootId() + : Data::ForumTopic::kGeneralId; const auto topic = peer->forumTopicFor(topicRootId); if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer)) || Api::SendDice(message)) { From 5350c97f8297b7b88b2adc681d378e34de512ef8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 12:31:59 +0400 Subject: [PATCH 58/70] Fix sending replies in forums. --- Telegram/SourceFiles/data/data_histories.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index e1fd60f6fdbbab..0f252aa5318985 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -48,14 +48,17 @@ MTPInputReplyTo ReplyToForMTP( } } else if (replyTo.messageId || replyTo.topicRootId) { const auto to = LookupReplyTo(history, replyTo.messageId); + const auto replyingToTopic = replyTo.topicRootId + ? history->peer->forumTopicFor(replyTo.topicRootId) + : nullptr; const auto replyingToTopicId = replyTo.topicRootId - ? replyTo.topicRootId - : Data::ForumTopic::kGeneralId; - const auto replyToTopicId = !to - ? replyingToTopicId - : to->topicRootId() + ? (replyingToTopic + ? replyingToTopic->rootId() + : Data::ForumTopic::kGeneralId) + : (to ? to->topicRootId() : Data::ForumTopic::kGeneralId); + const auto replyToTopicId = to ? to->topicRootId() - : Data::ForumTopic::kGeneralId; + : replyingToTopicId; const auto external = replyTo.messageId && (replyTo.messageId.peer != history->peer->id || replyingToTopicId != replyToTopicId); From d7539349c7eaf11ab931459cec9a2d699c45c626 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 12:35:08 +0400 Subject: [PATCH 59/70] Always show manual quote replies, hide redundant. --- .../history/history_inner_widget.cpp | 6 ++++- .../history/view/history_view_element.cpp | 12 ++-------- .../history/view/history_view_element.h | 1 - .../history/view/history_view_message.cpp | 3 +-- .../view/history_view_pinned_section.cpp | 6 ++++- .../view/history_view_replies_section.cpp | 22 +++++++++++++++++-- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index eee1ff08dbd0c5..bc8b0b09b697d0 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -260,7 +260,11 @@ class HistoryMainElementDelegate final return _widget ? _widget->elementAnimationsPaused() : false; } bool elementHideReply(not_null view) override { - return view->isTopicRootReply(); + if (!view->isTopicRootReply()) { + return false; + } + const auto reply = view->data()->Get(); + return reply && !reply->fields().manualQuote; } bool elementShownUnread(not_null view) override { return view->data()->unread(view->data()->history()); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 329ed41f300c08..1ba43758618b9f 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -498,7 +498,8 @@ Element::Element( | Flag::NeedsResize | (IsItemScheduledUntilOnline(data) ? Flag::ScheduledUntilOnline - : Flag())) + : Flag()) + | (countIsTopicRootReply() ? Flag::TopicRootReply : Flag())) , _context(delegate->elementContext()) { history()->owner().registerItemView(this); refreshMedia(replacing); @@ -1260,15 +1261,6 @@ QSize Element::countCurrentSize(int newWidth) { return performCountCurrentSize(newWidth); } -void Element::refreshIsTopicRootReply() { - const auto topicRootReply = countIsTopicRootReply(); - if (topicRootReply) { - _flags |= Flag::TopicRootReply; - } else { - _flags &= ~Flag::TopicRootReply; - } -} - bool Element::countIsTopicRootReply() const { const auto item = data(); if (!item->history()->isForum()) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 4da9f4b9c64f73..a5e7a86a47e655 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -565,7 +565,6 @@ class Element void clearSpecialOnlyEmoji(); void checkSpecialOnlyEmoji(); - void refreshIsTopicRootReply(); private: // This should be called only from previousInBlocksChanged() diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 115b2e76c8e667..d34e41722718b2 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -601,7 +601,7 @@ QSize Message::performCountOptimalSize() { const auto item = data(); const auto replyData = item->Get(); - if (replyData) { + if (replyData && !_hideReply) { AddComponents(Reply::Bit()); } else { RemoveComponents(Reply::Bit()); @@ -616,7 +616,6 @@ QSize Message::performCountOptimalSize() { : 2; }; const auto oldKey = reactionsKey(); - refreshIsTopicRootReply(); validateText(); validateInlineKeyboard(markup); updateViewButtonExistence(); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 0472e4eb1a094b..017289a0b23c63 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -597,7 +597,11 @@ void PinnedWidget::listUpdateDateLink( } bool PinnedWidget::listElementHideReply(not_null view) { - return (view->data()->replyToId() == _thread->topicRootId()); + if (const auto reply = view->data()->Get()) { + return !reply->fields().manualQuote + && (reply->messageId() == _thread->topicRootId()); + } + return false; } bool PinnedWidget::listElementShownUnread(not_null view) { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index c652ab2e803a7c..1c30b39cdac7d0 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -2020,7 +2020,7 @@ bool RepliesWidget::showMessage( } const auto id = FullMsgId(_history->peer->id, messageId); const auto message = _history->owner().message(id); - if (!message || !message->inThread(_rootId)) { + if (!message || (!message->inThread(_rootId) && id.msg != _rootId)) { return false; } const auto originMessage = [&]() -> HistoryItem* { @@ -2516,7 +2516,25 @@ void RepliesWidget::listUpdateDateLink( } bool RepliesWidget::listElementHideReply(not_null view) { - return (view->data()->replyToId() == _rootId); + if (const auto reply = view->data()->Get()) { + const auto replyToPeerId = reply->externalPeerId() + ? reply->externalPeerId() + : _history->peer->id; + if (reply->fields().manualQuote) { + return false; + } else if (replyToPeerId == _history->peer->id) { + return (reply->messageId() == _rootId); + } else if (_root) { + const auto forwarded = _root->Get(); + if (forwarded + && forwarded->savedFromPeer + && forwarded->savedFromPeer->id == replyToPeerId + && forwarded->savedFromMsgId == reply->messageId()) { + return true; + } + } + } + return false; } bool RepliesWidget::listElementShownUnread(not_null view) { From b2eeab53c5bf69e2be4eddc8b8f626415c7a41c2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 12:35:27 +0400 Subject: [PATCH 60/70] Improve 5-line reply text elision. --- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 4 +- .../dialogs/ui/dialogs_topics_view.cpp | 2 +- Telegram/SourceFiles/history/history_item.cpp | 10 +-- .../history/history_item_components.cpp | 19 ++++-- .../history/history_item_components.h | 2 + .../SourceFiles/history/history_widget.cpp | 2 +- .../history_view_compose_controls.cpp | 2 +- .../controls/history_view_draft_options.cpp | 4 ++ .../controls/history_view_forward_panel.cpp | 2 +- .../history/view/history_view_reply.cpp | 61 ++++++++----------- .../history/view/history_view_reply.h | 6 +- .../view/media/history_view_giveaway.cpp | 2 +- Telegram/SourceFiles/ui/chat/message_bar.cpp | 2 +- .../window/notifications_manager_default.cpp | 2 +- Telegram/lib_ui | 2 +- 15 files changed, 65 insertions(+), 57 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 4b9c3fe87c5ff6..e71c3cefd3c4d6 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -420,7 +420,7 @@ void PaintRow( .now = context.now, .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .elisionOneLine = true, + .elisionLines = 1, }); } else if (draft || (supportMode @@ -514,7 +514,7 @@ void PaintRow( .now = context.now, .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .elisionOneLine = true, + .elisionLines = 1, }); } } else if (!item) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp index f9fe160621540c..cd312fd85ea9be 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp @@ -141,7 +141,7 @@ void TopicsView::paint( .now = context.now, .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .elisionOneLine = true, + .elisionLines = 1, }); const auto skip = skipBig ? context.st->topicsSkipBig diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 0a3d3563d234b8..18235872cb6d70 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -3349,13 +3349,15 @@ void HistoryItem::createComponentsHelper( : (replyTo.messageId.peer == history()->peer->id) ? replyTo.messageId.msg : MsgId(); + const auto forum = _history->asForum(); + const auto topic = forum + ? forum->topicFor(replyTo.topicRootId) + : nullptr; if (!config.reply.externalPeerId - && to - && config.reply.topicPost - && replyTo.topicRootId != to->topicRootId()) { + && topic + && topic->rootId() != to->topicRootId()) { config.reply.externalPeerId = replyTo.messageId.peer; } - const auto forum = _history->asForum(); config.reply.topicPost = config.reply.externalPeerId ? (replyTo.topicRootId && (replyTo.topicRootId != Data::ForumTopic::kGeneralId)) diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index e4438b1c5fa776..66e30a58df5e87 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -424,12 +424,14 @@ bool HistoryMessageReply::updateData( } } - const auto external = this->external(); - _multiline = !_fields.storyId && (external || !_fields.quote.empty()); + const auto asExternal = displayAsExternal(holder); + const auto nonEmptyQuote = !_fields.quote.empty() + && (asExternal || _fields.manualQuote); + _multiline = !_fields.storyId && (asExternal || nonEmptyQuote); const auto displaying = resolvedMessage || resolvedStory - || ((!_fields.quote.empty() || _fields.externalMedia) + || ((nonEmptyQuote || _fields.externalMedia) && (!_fields.messageId || force)); _displaying = displaying ? 1 : 0; @@ -446,7 +448,7 @@ bool HistoryMessageReply::updateData( } return resolvedMessage || resolvedStory - || (external && !_fields.messageId && !_fields.storyId) + || (!_fields.messageId && !_fields.storyId && external()) || _unavailable; } @@ -508,6 +510,15 @@ bool HistoryMessageReply::external() const { || !_fields.externalSenderName.isEmpty(); } +bool HistoryMessageReply::displayAsExternal( + not_null holder) const { + // Don't display replies that could be local as external. + return external() + && (!resolvedMessage + || (holder->history() != resolvedMessage->history()) + || (holder->topicRootId() != resolvedMessage->topicRootId())); +} + void HistoryMessageReply::itemRemoved( not_null holder, not_null removed) { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index f78671ba33ef5c..3d363225f54c4c 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -279,6 +279,8 @@ struct HistoryMessageReply void clearData(not_null holder); [[nodiscard]] bool external() const; + [[nodiscard]] bool displayAsExternal( + not_null holder) const; void itemRemoved( not_null holder, not_null removed); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 60c32adedf6e59..7206e6a2bcec4b 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7951,7 +7951,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { .now = now, .pausedEmoji = paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = pausedSpoiler, - .elisionOneLine = true, + .elisionLines = 1, }); } else { p.setFont(st::msgDateFont); 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 21ce63e1a07aac..540d4ca5ffebc4 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -629,7 +629,7 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { .now = crl::now(), .pausedEmoji = p.inactive() || On(PowerSaving::kEmojiChat), .pausedSpoiler = p.inactive() || On(PowerSaving::kChatSpoiler), - .elisionOneLine = true, + .elisionLines = 1, }); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index 65a1cd6de5dec3..850ecfe6b604bd 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -669,6 +669,7 @@ void DraftOptionsBox( }); } + const auto weak = Ui::MakeWeak(box); Settings::AddButton( bottom, tr::lng_reply_show_in_chat(), @@ -676,6 +677,9 @@ void DraftOptionsBox( { &st::menuIconShowInChat } )->setClickedCallback([=] { highlight(resolveReply()); + if (const auto strong = weak.data()) { + strong->closeBox(); + } }); Settings::AddButton( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index 98799149f20bc9..85dadd5d866d9f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -396,7 +396,7 @@ void ForwardPanel::paint( .now = now, .pausedEmoji = paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = pausedSpoiler, - .elisionOneLine = true, + .elisionLines = 1, }); } diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 2873378230717e..f6c069db704bcd 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -210,10 +210,10 @@ void Reply::update( const auto text = (!_displaying && data->unavailable()) ? TextWithEntities() + : (message && (fields.quote.empty() || !fields.manualQuote)) + ? message->inReplyText() : !fields.quote.empty() ? fields.quote - : message - ? message->inReplyText() : story ? story->inReplyText() : externalMedia @@ -400,7 +400,9 @@ void Reply::updateName( const auto externalPeer = fields.externalPeerId ? view->history()->owner().peer(fields.externalPeerId).get() : nullptr; - const auto groupNameAdded = externalPeer + const auto displayAsExternal = data->displayAsExternal(view->data()); + const auto groupNameAdded = displayAsExternal + && externalPeer && (externalPeer != sender) && (externalPeer->isChat() || externalPeer->isMegagroup()); const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded; @@ -433,7 +435,7 @@ void Reply::updateName( icon.second)); }; auto nameFull = TextWithEntities(); - if (!groupNameAdded && data->external() && !fields.storyId) { + if (displayAsExternal && !groupNameAdded && !fields.storyId) { nameFull.append(peerEmoji(sender)); } nameFull.append(name); @@ -509,7 +511,7 @@ int Reply::resizeToWidth(int width) const { : 0; if (width >= _maxWidth || !_multiline) { _nameTwoLines = 0; - _expandable = 0; + _expandable = _minHeightExpandable; _height = _minHeight; return height(); } @@ -521,20 +523,13 @@ int Reply::resizeToWidth(int width) const { _nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0; const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height; const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; - auto lineCounter = 0; auto elided = false; const auto texth = _text.countDimensions( - textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height; - const auto useh = elided - ? (kNonExpandedLinesLimit * st::normalFont->height) - : std::max(texth, st::normalFont->height); - if (!texth) { - int a = 0; - } - _expandable = (_multiline && elided) ? 1 : 0; + textGeometry(innerw, firstLineSkip, &elided)).height; + _expandable = elided ? 1 : 0; _height = st::historyReplyPadding.top() + nameh - + useh + + std::max(texth, st::normalFont->height) + st::historyReplyPadding.bottom(); return height(); } @@ -542,21 +537,17 @@ int Reply::resizeToWidth(int width) const { Ui::Text::GeometryDescriptor Reply::textGeometry( int available, int firstLineSkip, - not_null line, - not_null outElided) const { - return { .layout = [=](Ui::Text::LineGeometry in) { - const auto skip = (*line ? 0 : firstLineSkip); - ++*line; - *outElided = *outElided - || !_multiline - || (!_expanded - && (*line == kNonExpandedLinesLimit) - && in.width > available - skip); - in.width = available - skip; - in.left += skip; - in.elided = *outElided; - return in; - } }; + bool *outElided) const { + return { .layout = [=](int line) { + const auto skip = (line ? 0 : firstLineSkip); + const auto elided = !_multiline + || (!_expanded && (line + 1 >= kNonExpandedLinesLimit)); + return Ui::Text::LineGeometry{ + .left = skip, + .width = available - skip, + .elided = elided, + }; + }, .outElided = outElided }; } int Reply::height() const { @@ -570,10 +561,10 @@ QMargins Reply::margins() const { QSize Reply::countMultilineOptimalSize( int previewSkip) const { auto elided = false; - auto lineCounter = 0; const auto max = previewSkip + _text.maxWidth(); const auto result = _text.countDimensions( - textGeometry(max, previewSkip, &lineCounter, &elided)); + textGeometry(max, previewSkip, &elided)); + _minHeightExpandable = elided ? 1 : 0; return { result.width, std::max(result.height, st::normalFont->height), @@ -767,18 +758,16 @@ void Reply::paint( copy->linkFg = owned->color(); replyToTextPalette = &*copy; } - auto l = 0; - auto e = false; _text.draw(p, { .position = { textLeft, textTop }, - .geometry = textGeometry(textw, firstLineSkip, &l, &e), + .geometry = textGeometry(textw, firstLineSkip), .palette = replyToTextPalette, .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, .pausedEmoji = (context.paused || On(PowerSaving::kEmojiChat)), .pausedSpoiler = pausedSpoiler, - .elisionOneLine = true, + .elisionLines = 1, }); p.setTextPalette(stm->textPalette); } diff --git a/Telegram/SourceFiles/history/view/history_view_reply.h b/Telegram/SourceFiles/history/view/history_view_reply.h index 10b5ebb9bb7600..5322d5c0929a95 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.h +++ b/Telegram/SourceFiles/history/view/history_view_reply.h @@ -67,8 +67,7 @@ class Reply final : public RuntimeComponent { [[nodiscard]] Ui::Text::GeometryDescriptor textGeometry( int available, int firstLineSkip, - not_null line, - not_null outElided) const; + bool *outElided = nullptr) const; [[nodiscard]] QSize countMultilineOptimalSize( int firstLineSkip) const; void setLinkFrom( @@ -101,11 +100,12 @@ class Reply final : public RuntimeComponent { mutable int _minHeight = 0; mutable int _height = 0; mutable int _nameVersion = 0; - uint8 _hiddenSenderColorIndexPlusOne = 0; + uint8 _hiddenSenderColorIndexPlusOne : 7 = 0; uint8 _hasQuoteIcon : 1 = 0; uint8 _replyToStory : 1 = 0; uint8 _expanded : 1 = 0; mutable uint8 _expandable : 1 = 0; + mutable uint8 _minHeightExpandable : 1 = 0; mutable uint8 _nameTwoLines : 1 = 0; mutable uint8 _hasPreview : 1 = 0; mutable uint8 _displaying : 1 = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index dceb0cd1d53823..350ba3668ffa6c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -388,7 +388,7 @@ void Giveaway::paintChannels( .align = style::al_left, .palette = &stm->textPalette, .now = context.now, - .elisionOneLine = true, + .elisionLines = 1, .elisionBreakEverywhere = true, }); } diff --git a/Telegram/SourceFiles/ui/chat/message_bar.cpp b/Telegram/SourceFiles/ui/chat/message_bar.cpp index d6b4417713e7c4..47f7056e48a404 100644 --- a/Telegram/SourceFiles/ui/chat/message_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/message_bar.cpp @@ -455,7 +455,7 @@ void MessageBar::paint(Painter &p) { .now = now, .pausedEmoji = paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = pausedSpoiler, - .elisionOneLine = true, + .elisionLines = 1, }); } } else if (_animation->bodyAnimation == BodyAnimation::Text) { diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 4454587d2538f2..ec2e8a1c0744c1 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -828,7 +828,7 @@ void Notification::paintTitle(Painter &p) { .spoiler = Ui::Text::DefaultSpoilerCache(), .pausedEmoji = On(PowerSaving::kEmojiChat), .pausedSpoiler = On(PowerSaving::kChatSpoiler), - .elisionOneLine = true, + .elisionLines = 1, }); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 08235c5e06ac56..65310f32dcc980 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 08235c5e06ac56564157f2856702804a2149d45e +Subproject commit 65310f32dcc980aeca0b13253b1278a6f3ce722e From e98f56b0b7d2a880e08f08bddb53d92789312c97 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 5 Nov 2023 18:10:27 +0300 Subject: [PATCH 61/70] Fixed aspect ratio of non-standard stickers in photo editor. --- Telegram/SourceFiles/editor/scene/scene_item_image.cpp | 8 +++++++- Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/editor/scene/scene_item_image.cpp b/Telegram/SourceFiles/editor/scene/scene_item_image.cpp index b932b11ad89d8a..b3cc544cfd905a 100644 --- a/Telegram/SourceFiles/editor/scene/scene_item_image.cpp +++ b/Telegram/SourceFiles/editor/scene/scene_item_image.cpp @@ -23,7 +23,13 @@ void ItemImage::paint( QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *w) { - p->drawPixmap(contentRect().toRect(), _pixmap); + const auto rect = contentRect(); + const auto pixmapSize = QSizeF(_pixmap.size() / style::DevicePixelRatio()) + .scaled(rect.size(), Qt::KeepAspectRatio); + const auto resultRect = QRectF(rect.topLeft(), pixmapSize).translated( + (rect.width() - pixmapSize.width()) / 2., + (rect.height() - pixmapSize.height()) / 2.); + p->drawPixmap(resultRect.toRect(), _pixmap); ItemBase::paint(p, option, w); } diff --git a/Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp b/Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp index a60f487e0aae2e..c678452e9eccaf 100644 --- a/Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp +++ b/Telegram/SourceFiles/editor/scene/scene_item_sticker.cpp @@ -110,7 +110,13 @@ void ItemSticker::paint( QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *w) { - p->drawImage(contentRect().toRect(), _image); + const auto rect = contentRect(); + const auto imageSize = QSizeF(_image.size() / style::DevicePixelRatio()) + .scaled(rect.size(), Qt::KeepAspectRatio); + const auto resultRect = QRectF(rect.topLeft(), imageSize).translated( + (rect.width() - imageSize.width()) / 2., + (rect.height() - imageSize.height()) / 2.); + p->drawImage(resultRect, _image); ItemBase::paint(p, option, w); } From 61a9d9c01d3fcaa7b37857edaf25810f6512d3cf Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 12:47:37 +0400 Subject: [PATCH 62/70] Don't close schedule media box by outside click. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 8 +++++++- Telegram/SourceFiles/history/history_widget.cpp | 3 +-- .../history/view/history_view_replies_section.cpp | 3 +-- .../history/view/history_view_scheduled_section.cpp | 3 +-- .../SourceFiles/media/stories/media_stories_reply.cpp | 7 ++----- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index efea9ad8eb0d46..613f4866b358f4 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -427,6 +427,7 @@ void SendFilesBox::prepare() { preparePreview(); initPreview(); SetupShadowsToScrollContent(this, _scroll, _inner->heightValue()); + setCloseByOutsideClick(false); boxClosing() | rpl::start_with_next([=] { if (!_confirmed && _cancelledCallback) { @@ -1437,7 +1438,12 @@ void SendFilesBox::sendScheduled() { ? SendMenu::Type::ScheduledToUser : _sendMenuType; const auto callback = [=](Api::SendOptions options) { send(options); }; - _show->showBox(HistoryView::PrepareScheduleBox(this, type, callback)); + auto box = HistoryView::PrepareScheduleBox(this, 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() { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 7206e6a2bcec4b..05a339a804c0c7 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5370,8 +5370,7 @@ bool HistoryWidget::confirmSendingFiles( })); Window::ActivateWindow(controller()); - const auto shown = controller()->show(std::move(box)); - shown->setCloseByOutsideClick(false); + controller()->show(std::move(box)); 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 1c30b39cdac7d0..ac23678b8ed5b2 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -972,8 +972,7 @@ bool RepliesWidget::confirmSendingFiles( insertTextOnCancel)); //ActivateWindow(controller()); - const auto shown = controller()->show(std::move(box)); - shown->setCloseByOutsideClick(false); + controller()->show(std::move(box)); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 3f9314c406e75a..02570767ed3d30 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -429,8 +429,7 @@ bool ScheduledWidget::confirmSendingFiles( insertTextOnCancel)); //ActivateWindow(controller()); - const auto shown = controller()->show(std::move(box)); - shown->setCloseByOutsideClick(false); + controller()->show(std::move(box)); return true; } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index bb3a834a608b88..8a208878516684 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -500,7 +500,7 @@ bool ReplyArea::confirmSendingFiles( auto confirmed = [=](auto &&...args) { sendingFilesConfirmed(std::forward(args)...); }; - auto box = Box(SendFilesBoxDescriptor{ + show->show(Box(SendFilesBoxDescriptor{ .show = show, .list = std::move(list), .caption = _controls->getTextWithAppliedMarkdown(), @@ -511,10 +511,7 @@ bool ReplyArea::confirmSendingFiles( .stOverride = &st::storiesComposeControls, .confirmed = crl::guard(this, confirmed), .cancelled = _controls->restoreTextCallback(insertTextOnCancel), - }); - if (const auto shown = show->show(std::move(box))) { - shown->setCloseByOutsideClick(false); - } + })); return true; } From f7ad91e80c4e76f543d5e900ca87ac888ab22720 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 13:39:05 +0400 Subject: [PATCH 63/70] Fix round video link preview with reactions. --- Telegram/SourceFiles/history/view/history_view_message.cpp | 4 ++++ .../SourceFiles/history/view/media/history_view_gif.cpp | 4 +++- .../history/view/media/history_view_theme_document.cpp | 7 ------- .../history/view/media/history_view_theme_document.h | 1 - .../history/view/media/history_view_web_page.cpp | 1 - Telegram/lib_ui | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index d34e41722718b2..dd061d81947344 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -683,6 +683,10 @@ QSize Message::performCountOptimalSize() { std::min(st::msgMaxWidth, reactionsMaxWidth)); if (!mediaDisplayed || _viewButton) { minHeight += st::mediaInBubbleSkip; + } else if (!media->additionalInfoString().isEmpty()) { + // In round videos in a web page status text is painted + // in the bottom left corner, reactions should be below. + minHeight += st::msgDateFont->height; } if (maxWidth >= reactionsMaxWidth) { minHeight += _reactions->minHeight(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 4f11171a82d4bb..128797f8f24750 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -624,7 +624,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const { == PaintContext::SkipDrawingParts::Surrounding; if (!unwrapped && !skipDrawingSurrounding) { - drawCornerStatus(p, context, QPoint()); + if (!isRound || !inWebPage) { + drawCornerStatus(p, context, QPoint()); + } } else if (!skipDrawingSurrounding) { if (isRound) { const auto mediaUnread = item->hasUnreadMediaFlag(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index ad5dfcac1b803b..0fbc095f8fff5d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -418,13 +418,6 @@ bool ThemeDocument::isReadyForOpen() const { return !_data || _dataMedia->loaded(); } -QString ThemeDocument::additionalInfoString() const { - // This will force message info (time) to be displayed below - // this attachment in WebPage media. - static auto result = QString(" "); - return result; -} - bool ThemeDocument::hasHeavyPart() const { return (_dataMedia != nullptr); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.h b/Telegram/SourceFiles/history/view/media/history_view_theme_document.h index 94549e9514cb7e..7ed9bcf91fc5e7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.h @@ -46,7 +46,6 @@ class ThemeDocument final : public File { return true; } bool isReadyForOpen() const override; - QString additionalInfoString() const override; bool hasHeavyPart() const override; void unloadHeavyPart() override; 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 a6bb07528484b6..56299bf451bced 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -710,7 +710,6 @@ TextState WebPage::textState(QPoint point, StateRequest request) const { auto inner = outer.marginsRemoved(innerMargin()); auto tshift = inner.top(); auto paintw = inner.width(); - auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString(); auto lineHeight = UnitedLineHeight(); auto inThumb = false; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 65310f32dcc980..762a611f2009e6 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 65310f32dcc980aeca0b13253b1278a6f3ce722e +Subproject commit 762a611f2009e623d6291ae5564bcd5fa4602e3d From 8a804fcfad8511f8dc1cca2d9f2e484892ce4d7a Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 14:41:42 +0400 Subject: [PATCH 64/70] Add toasts about giveaway/gift start. --- Telegram/Resources/langs/lang.strings | 6 +++ .../info/boosts/create_giveaway_box.cpp | 48 +++++++++++++++++-- Telegram/lib_ui | 2 +- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6ecca945959d96..001925cbe5f580 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2119,6 +2119,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_channels_confirm_title" = "Channel is Private"; "lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link."; +"lng_giveaway_created_title" = "Giveaway created"; +"lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel."; +"lng_giveaway_awarded_title" = "Premium subscriptions gifted"; +"lng_giveaway_awarded_body" = "Check your channels' {link} to see how gifts boosted your channel."; +"lng_giveaway_created_link" = "Statistics"; + "lng_prize_title" = "Congratulations!"; "lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; "lng_prize_duration" = "Your prize is a **Telegram Premium** subscription {duration}."; diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index a1f72950b0df6a..c1781ea031c483 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -15,7 +15,9 @@ For license and copyright information please follow this link: #include "info/boosts/giveaway/giveaway_list_controllers.h" #include "info/boosts/giveaway/giveaway_type_row.h" #include "info/boosts/giveaway/select_countries_box.h" +#include "info/boosts/info_boosts_widget.h" #include "info/info_controller.h" +#include "info/info_memento.h" #include "lang/lang_keys.h" #include "payments/payments_checkout_process.h" // Payments::CheckoutProcess #include "payments/payments_form.h" // Payments::InvoicePremiumGiftCode @@ -40,6 +42,8 @@ For license and copyright information please follow this link: namespace { +constexpr auto kDoneTooltipDuration = 5 * crl::time(1000); + [[nodiscard]] QDateTime ThreeDaysAfterToday() { auto dateNow = QDateTime::currentDateTime(); dateNow = dateNow.addDays(3); @@ -71,6 +75,8 @@ void CreateGiveawayBox( not_null peer) { box->setWidth(st::boxWideWidth); + const auto weakWindow = base::make_weak(controller->parentController()); + const auto bar = box->verticalLayout()->add( object_ptr( box, @@ -624,12 +630,44 @@ void CreateGiveawayBox( }; } state->confirmButtonBusy = true; - Payments::CheckoutProcess::Start( - std::move(invoice), - crl::guard(box, [=](auto) { + const auto show = box->uiShow(); + const auto weak = Ui::MakeWeak(box.get()); + const auto done = [=](Payments::CheckoutResult result) { + if (const auto strong = weak.data()) { state->confirmButtonBusy = false; - box->window()->setFocus(); - })); + strong->window()->setFocus(); + strong->closeBox(); + } + if (result == Payments::CheckoutResult::Paid) { + const auto filter = [=](const auto &...) { + if (const auto window = weakWindow.get()) { + window->showSection(Info::Boosts::Make(peer)); + } + return false; + }; + const auto title = isSpecific + ? tr::lng_giveaway_awarded_title + : tr::lng_giveaway_created_title; + const auto body = isSpecific + ? tr::lng_giveaway_awarded_body + : tr::lng_giveaway_created_body; + show->showToast({ + .text = Ui::Text::Bold( + title(tr::now)).append('\n').append( + body( + tr::now, + lt_link, + Ui::Text::Link( + tr::lng_giveaway_created_link( + tr::now)), + Ui::Text::WithEntities)), + .duration = kDoneTooltipDuration, + .adaptive = true, + .filter = filter, + }); + } + }; + Payments::CheckoutProcess::Start(std::move(invoice), done); }); box->addButton(std::move(button)); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 762a611f2009e6..b8998700c4a348 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 762a611f2009e623d6291ae5564bcd5fa4602e3d +Subproject commit b8998700c4a34890cf20b7cad0a9225877101d69 From b77e74de7f43e53f55a1d1be69b0eef37ace7215 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 14:50:21 +0400 Subject: [PATCH 65/70] Beta version 4.11.4. - Show quoted part in channel comments. - Show replies with icons and multiline preview. - Send correct replies in topics and channel comments. - In monochrome Windows tray icon use dot instead of counter. --- 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 | 10 +++++----- changelog.txt | 7 +++++++ 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index cc75e489e34c6c..596b3c26ac8c48 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.11.4.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 6913bae357c7ac..9d1794ab9e1be8 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,11,3,0 - PRODUCTVERSION 4,11,3,0 + FILEVERSION 4,11,4,0 + PRODUCTVERSION 4,11,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", "4.11.3.0" + VALUE "FileVersion", "4.11.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.11.3.0" + VALUE "ProductVersion", "4.11.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index c1d26ca214a87a..0765e116e09d6d 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,11,3,0 - PRODUCTVERSION 4,11,3,0 + FILEVERSION 4,11,4,0 + PRODUCTVERSION 4,11,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", "4.11.3.0" + VALUE "FileVersion", "4.11.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.11.3.0" + VALUE "ProductVersion", "4.11.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 4003c2647ec77e..34bc746ff5268c 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 = 4011003; -constexpr auto AppVersionStr = "4.11.3"; -constexpr auto AppBetaVersion = false; +constexpr auto AppVersion = 4011004; +constexpr auto AppVersionStr = "4.11.4"; +constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 1c347fd926c1bb..a826c7577b9224 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4011003 +AppVersion 4011004 AppVersionStrMajor 4.11 -AppVersionStrSmall 4.11.3 -AppVersionStr 4.11.3 -BetaChannel 0 +AppVersionStrSmall 4.11.4 +AppVersionStr 4.11.4 +BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 4.11.3 +AppVersionOriginal 4.11.4.beta diff --git a/changelog.txt b/changelog.txt index b053efcf027829..d76d6f71fe1c3b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +4.11.4 beta (06.11.23) + +- Show quoted part in channel comments. +- Show replies with icons and multiline preview. +- Send correct replies in topics and channel comments. +- In monochrome Windows tray icon use dot instead of counter. + 4.11.3 (02.11.23) - Fix adding a link to media captions in scheduled / comments. From eea7caeb947a428b8292e229785c2b12ae9fe7d2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 20:29:41 +0400 Subject: [PATCH 66/70] Fix a phrase in giveaway info. --- Telegram/SourceFiles/boxes/gift_premium_box.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index dbad50cac5931b..9a87a58fc1768d 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -689,7 +689,8 @@ void GiveawayInfoBox( text.append(' ').append(tr::lng_prizes_end_activated( tr::now, lt_count, - info.activatedCount)); + info.activatedCount, + Ui::Text::RichLangValue)); } if (!info.giftCode.isEmpty()) { text.append("\n\n"); From 86d628077681fad0a228d6b60f9497f5bd3cf383 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 20:30:41 +0400 Subject: [PATCH 67/70] Fix bidi data init. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index b8998700c4a348..f59c13455ce539 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit b8998700c4a34890cf20b7cad0a9225877101d69 +Subproject commit f59c13455ce53933b9024fa32fd3a5f4e7ed0405 From 4e915e9d1ae058054559e97e12987b9651d33904 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 20:31:11 +0400 Subject: [PATCH 68/70] Beta version 4.11.4: Fix build with GCC. --- Telegram/SourceFiles/history/view/history_view_message.cpp | 1 - Telegram/SourceFiles/history/view/history_view_reply.cpp | 1 - .../SourceFiles/info/boosts/giveaway/select_countries_box.cpp | 1 - Telegram/lib_ui | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index dd061d81947344..c92b057157bd7f 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1829,7 +1829,6 @@ void Message::toggleReplyRipple(bool pressed) { if (!unwrapped()) { const auto &padding = st::msgPadding; const auto geometry = countGeometry(); - const auto item = data(); const auto margins = reply->margins(); const auto size = QSize( geometry.width() - padding.left() - padding.right(), diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index f6c069db704bcd..6899a083245c03 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -388,7 +388,6 @@ void Reply::updateName( not_null data, std::optional resolvedSender) const { auto viaBotUsername = QString(); - const auto story = data->resolvedStory.get(); const auto message = data->resolvedMessage.get(); if (message && !message->Has()) { if (const auto bot = message->viaBot()) { diff --git a/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp index 9c3968bb3b2c9b..4194ed98c482d4 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp @@ -93,7 +93,6 @@ void SelectCountriesBox( buttons.reserve(countries.size()); for (const auto &country : countries) { const auto flag = Countries::Instance().flagEmojiByISO2(country.iso2); - const auto asd = Ui::Emoji::Find(flag); if (!Ui::Emoji::Find(flag)) { continue; } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index f59c13455ce539..0837a6364d85fc 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit f59c13455ce53933b9024fa32fd3a5f4e7ed0405 +Subproject commit 0837a6364d85fc2b057d14b3548d7d7b644adab3 From 36662c51dd8386b92ae3d9da00d7cdaffda3cfe5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 22:16:12 +0400 Subject: [PATCH 69/70] Fix giveaway sticker and phrases. --- Telegram/Resources/langs/lang.strings | 8 ++++---- Telegram/SourceFiles/data/data_media_types.cpp | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 001925cbe5f580..f3d56b3d0e6270 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2159,10 +2159,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}."; "lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link."; "lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links."; -"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}."; -"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}."; -"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels."; -"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels."; +"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}"; +"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}"; +"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels"; +"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels"; "lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}"; "lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}"; "lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}"; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 79de11506dea94..1132d75c49e735 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -47,6 +47,7 @@ For license and copyright information please follow this link: #include "storage/storage_shared_media.h" #include "storage/localstorage.h" #include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot. +#include "chat_helpers/stickers_gift_box_pack.h" #include "data/data_session.h" #include "data/data_auto_download.h" #include "data/data_photo.h" @@ -369,6 +370,7 @@ Giveaway ComputeGiveawayData( .untilDate = data.vuntil_date().v, .quantity = data.vquantity().v, .months = data.vmonths().v, + .all = !data.is_only_new_subscribers(), }; result.channels.reserve(data.vchannels().v.size()); const auto owner = &item->history()->owner(); @@ -2188,6 +2190,7 @@ MediaGiveaway::MediaGiveaway( const Giveaway &data) : Media(parent) , _giveaway(data) { + parent->history()->session().giftBoxStickersPacks().load(); } std::unique_ptr MediaGiveaway::clone(not_null parent) { From 6eb7d68d49da79e8a6dbbe334028f3ce9ad92235 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 6 Nov 2023 22:21:00 +0400 Subject: [PATCH 70/70] Version 4.11.5. - Giveaway phrases and sticker fixes. - Show quoted part in channel comments. - Show replies with icons and multiline preview. - Send correct replies in topics and channel comments. - In monochrome Windows tray icon use dot instead of counter. --- 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 | 10 +++++----- changelog.txt | 8 ++++++++ 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 596b3c26ac8c48..522be5d7bcc5dd 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.11.5.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 9d1794ab9e1be8..880e9bb0cb1f05 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,11,4,0 - PRODUCTVERSION 4,11,4,0 + FILEVERSION 4,11,5,0 + PRODUCTVERSION 4,11,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", "4.11.4.0" + VALUE "FileVersion", "4.11.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.11.4.0" + VALUE "ProductVersion", "4.11.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 0765e116e09d6d..00ab6e2f623df8 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,11,4,0 - PRODUCTVERSION 4,11,4,0 + FILEVERSION 4,11,5,0 + PRODUCTVERSION 4,11,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", "4.11.4.0" + VALUE "FileVersion", "4.11.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.11.4.0" + VALUE "ProductVersion", "4.11.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 34bc746ff5268c..c8977d179d33e9 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 = 4011004; -constexpr auto AppVersionStr = "4.11.4"; -constexpr auto AppBetaVersion = true; +constexpr auto AppVersion = 4011005; +constexpr auto AppVersionStr = "4.11.5"; +constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index a826c7577b9224..0ba84356e82a67 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4011004 +AppVersion 4011005 AppVersionStrMajor 4.11 -AppVersionStrSmall 4.11.4 -AppVersionStr 4.11.4 -BetaChannel 1 +AppVersionStrSmall 4.11.5 +AppVersionStr 4.11.5 +BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.11.4.beta +AppVersionOriginal 4.11.5 diff --git a/changelog.txt b/changelog.txt index d76d6f71fe1c3b..c68073a1326c3e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,11 @@ +4.11.5 (06.11.23) + +- Giveaway phrases and sticker fixes. +- Show quoted part in channel comments. +- Show replies with icons and multiline preview. +- Send correct replies in topics and channel comments. +- In monochrome Windows tray icon use dot instead of counter. + 4.11.4 beta (06.11.23) - Show quoted part in channel comments.