From 5c8820d5d8c29682a70c550a1ffa0b96ad231daa Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 09:12:18 +0400 Subject: [PATCH 001/370] Use QClipboard::supportsSelection instead of ifdefs --- .../history/admin_log/history_admin_log_inner.cpp | 6 +++--- Telegram/SourceFiles/history/history_inner_widget.cpp | 6 +++--- .../SourceFiles/history/view/history_view_list_widget.cpp | 7 +++---- Telegram/SourceFiles/info/media/info_media_list_widget.cpp | 4 +--- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index f2a3a7753..1bb0e1a8a 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -1503,13 +1503,13 @@ void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton but _mouseSelectType = TextSelectType::Letters; //_widget->noSelectingScroll(); // TODO -#if defined Q_OS_UNIX && !defined Q_OS_MAC - if (_selectedItem && _selectedText.from != _selectedText.to) { + if (QGuiApplication::clipboard()->supportsSelection() + && _selectedItem + && _selectedText.from != _selectedText.to) { TextUtilities::SetClipboardText( _selectedItem->selectedText(_selectedText), QClipboard::Selection); } -#endif // Q_OS_UNIX && !Q_OS_MAC } void InnerWidget::updateSelected() { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c5b93c611..b94c38b6a 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -1382,8 +1382,9 @@ void HistoryInner::mouseActionFinish( _widget->noSelectingScroll(); _widget->updateTopBarSelection(); -#if defined Q_OS_UNIX && !defined Q_OS_MAC - if (!_selected.empty() && _selected.cbegin()->second != FullSelection) { + if (QGuiApplication::clipboard()->supportsSelection() + && !_selected.empty() + && _selected.cbegin()->second != FullSelection) { const auto [item, selection] = *_selected.cbegin(); if (const auto view = item->mainView()) { TextUtilities::SetClipboardText( @@ -1391,7 +1392,6 @@ void HistoryInner::mouseActionFinish( QClipboard::Selection); } } -#endif // Q_OS_UNIX && !Q_OS_MAC } void HistoryInner::mouseReleaseEvent(QMouseEvent *e) { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 66b362033..65943d1b4 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2222,16 +2222,15 @@ void ListWidget::mouseActionFinish( _mouseSelectType = TextSelectType::Letters; //_widget->noSelectingScroll(); // #TODO select scroll -#if defined Q_OS_UNIX && !defined Q_OS_MAC - if (_selectedTextItem + if (QGuiApplication::clipboard()->supportsSelection() + && _selectedTextItem && _selectedTextRange.from != _selectedTextRange.to) { if (const auto view = viewForItem(_selectedTextItem)) { TextUtilities::SetClipboardText( view->selectedText(_selectedTextRange), QClipboard::Selection); -} + } } -#endif // Q_OS_UNIX && !Q_OS_MAC } void ListWidget::mouseActionUpdate() { diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 3074318c9..be74d7251 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -2118,11 +2118,9 @@ void ListWidget::mouseActionFinish( //_widget->noSelectingScroll(); // #TODO scroll by drag //_widget->updateTopBarSelection(); -#if defined Q_OS_UNIX && !defined Q_OS_MAC - //if (hasSelectedText()) { // #TODO linux clipboard + //if (QGuiApplication::clipboard()->supportsSelection() && hasSelectedText()) { // #TODO linux clipboard // TextUtilities::SetClipboardText(_selected.cbegin()->first->selectedText(_selected.cbegin()->second), QClipboard::Selection); //} -#endif // Q_OS_UNIX && !Q_OS_MAC } void ListWidget::applyDragSelection() { From aec2b8df7e6ad67cb6e6d4745f6417684c29d7ed Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 10:37:30 +0400 Subject: [PATCH 002/370] Fix choosing directories in snap and flatpak --- .../platform/linux/file_utilities_linux.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 253bf528c..552ed888a 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -113,15 +113,17 @@ bool UseNative(Type type = Type::ReadFile) { // or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3) // or if portals are used and operation is to open folder // and portal doesn't support folder choosing + const auto sandboxedOrCustomPortal = InFlatpak() + || InSnap() + || UseXDGDesktopPortal(); + const auto neededForPortal = (type == Type::ReadFolder) && !CanOpenDirectoryWithPortal(); const auto neededNonForced = DesktopEnvironment::IsGtkBased() - || (UseXDGDesktopPortal() && neededForPortal); + || (sandboxedOrCustomPortal && neededForPortal); - const auto excludeNonForced = InFlatpak() - || InSnap() - || (UseXDGDesktopPortal() && !neededForPortal); + const auto excludeNonForced = sandboxedOrCustomPortal && !neededForPortal; return IsGtkIntegrationForced() || (neededNonForced && !excludeNonForced); From f521275accacf6656f612acb8acf0a029a5337a0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 10:54:37 +0400 Subject: [PATCH 003/370] Fix AreQtPluginsBundled to include static binary --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 5fff0b83d..4db8744c0 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -635,11 +635,11 @@ bool IsGtkIntegrationForced() { } bool AreQtPluginsBundled() { -#ifdef DESKTOP_APP_USE_PACKAGED_LAZY +#if !defined DESKTOP_APP_USE_PACKAGED || defined DESKTOP_APP_USE_PACKAGED_LAZY return true; -#else // DESKTOP_APP_USE_PACKAGED_LAZY +#else // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY return false; -#endif // !DESKTOP_APP_USE_PACKAGED_LAZY +#endif // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY } bool IsXDGDesktopPortalPresent() { From 6c023084d91f8b19dde399e16f038b4a4532d3ed Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 11:44:21 +0400 Subject: [PATCH 004/370] Move the excluding portal checks to UseXDGDesktopPortal --- .../platform/linux/specific_linux.cpp | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 4db8744c0..240366d8a 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -657,12 +657,19 @@ bool IsXDGDesktopPortalPresent() { bool UseXDGDesktopPortal() { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION static const auto Result = [&] { + const auto onlyIn = AreQtPluginsBundled() + // it is handled by Qt for flatpak and snap + && !InFlatpak() + && !InSnap(); + const auto envVar = qEnvironmentVariableIsSet("TDESKTOP_USE_PORTAL"); const auto portalPresent = IsXDGDesktopPortalPresent(); const auto neededForKde = DesktopEnvironment::IsKDE() && IsXDGDesktopPortalKDEPresent(); - return (neededForKde || envVar) && portalPresent; + return onlyIn + && portalPresent + && (neededForKde || envVar); }(); return Result; @@ -1086,24 +1093,19 @@ void start() { qputenv("QT_WAYLAND_DECORATION", "material"); } - if (AreQtPluginsBundled() - // it is handled by Qt for flatpak and snap - && !InFlatpak() - && !InSnap()) { - LOG(("Checking for XDG Desktop Portal...")); - // this can give us a chance to use - // a proper file dialog for current session - if (IsXDGDesktopPortalPresent()) { - LOG(("XDG Desktop Portal is present!")); - if (UseXDGDesktopPortal()) { - LOG(("Using XDG Desktop Portal.")); - qputenv("QT_QPA_PLATFORMTHEME", "xdgdesktopportal"); - } else { - LOG(("Not using XDG Desktop Portal.")); - } + // this can give us a chance to use + // a proper file dialog for current session + DEBUG_LOG(("Checking for XDG Desktop Portal...")); + if (IsXDGDesktopPortalPresent()) { + DEBUG_LOG(("XDG Desktop Portal is present!")); + if (UseXDGDesktopPortal()) { + LOG(("Using XDG Desktop Portal.")); + qputenv("QT_QPA_PLATFORMTHEME", "xdgdesktopportal"); } else { - LOG(("XDG Desktop Portal is not present :(")); + DEBUG_LOG(("Not using XDG Desktop Portal.")); } + } else { + DEBUG_LOG(("XDG Desktop Portal is not present :(")); } #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION From f7dcf6ce814dd32471b04d97081aa1f6b49c7c86 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 11:57:34 +0400 Subject: [PATCH 005/370] Hide IsXDGDesktopPortalPresent in a private namespace --- .../platform/linux/specific_linux.cpp | 22 ++++++++----------- .../platform/linux/specific_linux.h | 10 +++------ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 240366d8a..c1daf15d0 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -124,6 +124,14 @@ void PortalAutostart(bool autostart, bool silent = false) { } } +bool IsXDGDesktopPortalPresent() { + static const auto Result = QDBusInterface( + kXDGDesktopPortalService.utf16(), + kXDGDesktopPortalObjectPath.utf16()).isValid(); + + return Result; +} + bool IsXDGDesktopPortalKDEPresent() { static const auto Result = QDBusInterface( qsl("org.freedesktop.impl.portal.desktop.kde"), @@ -642,18 +650,6 @@ bool AreQtPluginsBundled() { #endif // DESKTOP_APP_USE_PACKAGED && !DESKTOP_APP_USE_PACKAGED_LAZY } -bool IsXDGDesktopPortalPresent() { -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION - static const auto Result = QDBusInterface( - kXDGDesktopPortalService.utf16(), - kXDGDesktopPortalObjectPath.utf16()).isValid(); - - return Result; -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - - return false; -} - bool UseXDGDesktopPortal() { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION static const auto Result = [&] { @@ -1093,6 +1089,7 @@ void start() { qputenv("QT_WAYLAND_DECORATION", "material"); } +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION // this can give us a chance to use // a proper file dialog for current session DEBUG_LOG(("Checking for XDG Desktop Portal...")); @@ -1108,7 +1105,6 @@ void start() { DEBUG_LOG(("XDG Desktop Portal is not present :(")); } -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION // IBus has changed its socket path several times // and each change should be synchronized with Qt. // Moreover, the last time Qt changed the path, diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index e6b961df0..2dd706914 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -23,26 +23,22 @@ inline void SetWatchingMediaKeys(bool watching) { bool InFlatpak(); bool InSnap(); bool IsStaticBinary(); +bool AreQtPluginsBundled(); bool UseGtkIntegration(); bool IsGtkIntegrationForced(); -bool AreQtPluginsBundled(); - -bool IsXDGDesktopPortalPresent(); bool UseXDGDesktopPortal(); bool CanOpenDirectoryWithPortal(); QString AppRuntimeDirectory(); - QString GetLauncherBasename(); QString GetLauncherFilename(); - QString GetIconName(); +void InstallLauncher(bool force = false); + inline void IgnoreApplicationActivationRightNow() { } -void InstallLauncher(bool force = false); - } // namespace Platform inline void psCheckLocalSocket(const QString &serverName) { From 9210cf6d365f5f9122431901039e2310e01b84a3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 8 Oct 2020 09:55:23 +0300 Subject: [PATCH 006/370] Build Windows version with Qt 5.15.1. --- cmake | 2 +- docs/building-msvc.md | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/cmake b/cmake index a7e73ebc0..94025be39 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit a7e73ebc036fdf32cdca56b62405bf9dcd8f8f09 +Subproject commit 94025be392ab7e58115ac376cbf449cdca10de22 diff --git a/docs/building-msvc.md b/docs/building-msvc.md index 87a1d30da..57d5b856d 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -33,7 +33,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** cd ThirdParty git clone https://github.com/desktop-app/patches.git cd patches - git checkout ddd4084 + git checkout a77e4d5 cd ../ git clone https://chromium.googlesource.com/external/gyp cd gyp @@ -63,7 +63,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** git clone https://github.com/desktop-app/patches.git cd patches - git checkout ddd4084 + git checkout a77e4d5 cd .. git clone https://github.com/desktop-app/lzma.git @@ -125,7 +125,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** git clone https://github.com/google/breakpad cd breakpad git checkout a1dbcdcb43 - git apply ../../tdesktop/Telegram/Patches/breakpad.diff + git apply ../patches/breakpad.diff cd src git clone https://github.com/google/googletest testing cd client\windows @@ -156,23 +156,23 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** set CHERE_INVOKING=enabled_from_arguments set MSYS2_PATH_TYPE=inherit - bash --login ../../tdesktop/Telegram/Patches/build_ffmpeg_win.sh + bash --login ../patches/build_ffmpeg_win.sh SET PATH=%PATH_BACKUP_% cd .. SET LibrariesPath=%cd% - git clone git://code.qt.io/qt/qt5.git qt_5_12_8 - cd qt_5_12_8 + git clone git://code.qt.io/qt/qt5.git qt_5_15_1 + cd qt_5_15_1 perl init-repository --module-subset=qtbase,qtimageformats - git checkout v5.12.8 + git checkout v5.15.1 git submodule update qtbase qtimageformats cd qtbase - for /r %i in (..\..\patches\qtbase_5_12_8\*) do git apply %i + for /r %i in (..\..\patches\qtbase_5_15_1\*) do git apply %i cd .. configure ^ - -prefix "%LibrariesPath%\Qt-5.12.8" ^ + -prefix "%LibrariesPath%\Qt-Qt-5.15.1" ^ -debug-and-release ^ -force-debug-info ^ -opensource ^ @@ -181,7 +181,6 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** -static-runtime ^ -no-opengl ^ -openssl-linked ^ - -recheck ^ -I "%LibrariesPath%\openssl_1_1_1\include" ^ OPENSSL_LIBS_DEBUG="%LibrariesPath%\openssl_1_1_1\out32.dbg\libssl.lib %LibrariesPath%\openssl_1_1_1\out32.dbg\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^ OPENSSL_LIBS_RELEASE="%LibrariesPath%\openssl_1_1_1\out32\libssl.lib %LibrariesPath%\openssl_1_1_1\out32\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^ @@ -193,8 +192,8 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** -nomake tests ^ -platform win32-msvc - jom -j4 - jom -j4 install + jom -j8 + jom -j8 install cd .. git clone https://github.com/desktop-app/tg_owt.git From 117de5a1f9e845489a563b3e9947dc8bbb2eed24 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Oct 2020 13:32:09 +0300 Subject: [PATCH 007/370] Build macOS version with Qt 5.15.1. --- .../media/view/media_view_overlay_widget.cpp | 6 +++ .../platform/mac/notifications_manager_mac.mm | 4 +- .../platform/mac/specific_mac_p.mm | 5 +- .../mac/touchbar/items/mac_scrubber_item.mm | 50 ++++++++++--------- .../mac/touchbar/mac_touchbar_audio.mm | 1 - .../mac/touchbar/mac_touchbar_common.mm | 11 ++-- docs/building-xcode.md | 10 ++-- 7 files changed, 44 insertions(+), 43 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index eff607fcb..e3ce5df7b 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -350,6 +350,12 @@ OverlayWidget::OverlayWidget() if (Platform::IsLinux()) { setWindowFlags(Qt::FramelessWindowHint | Qt::MaximizeUsingFullscreenGeometryHint); + } else if (Platform::IsMac()) { + // Without Qt::Tool starting with Qt 5.15.1 this widget + // when being opened from a fullscreen main window was + // opening not as overlay over the main window, but as + // a separate fullscreen window with a separate space. + setWindowFlags(Qt::FramelessWindowHint | Qt::Tool); } else { setWindowFlags(Qt::FramelessWindowHint); } diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm index 1bda44df9..827954675 100644 --- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm +++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm @@ -47,8 +47,6 @@ using Manager = Platform::Notifications::Manager; } // namespace -NSImage *qt_mac_create_nsimage(const QPixmap &pm); - @interface NotificationDelegate : NSObject { } @@ -267,7 +265,7 @@ void Manager::Private::showNotification( : peer->isRepliesChat() ? Ui::EmptyUserpic::GenerateRepliesMessages(st::notifyMacPhotoSize) : peer->genUserpic(userpicView, st::notifyMacPhotoSize); - NSImage *img = [qt_mac_create_nsimage(userpic) autorelease]; + NSImage *img = Q2NSImage(userpic.toImage()); [notification setContentImage:img]; } diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm index b4208a042..e53afe424 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm @@ -39,8 +39,6 @@ constexpr auto kIgnoreActivationTimeoutMs = 500; } // namespace -NSImage *qt_mac_create_nsimage(const QPixmap &pm); - using Platform::Q2NSString; using Platform::NS2QString; @@ -214,10 +212,9 @@ void SetApplicationIcon(const QIcon &icon) { if (!icon.isNull()) { auto pixmap = icon.pixmap(1024, 1024); pixmap.setDevicePixelRatio(cRetinaFactor()); - image = static_cast(qt_mac_create_nsimage(pixmap)); + image = Q2NSImage(pixmap.toImage()); } [[NSApplication sharedApplication] setApplicationIconImage:image]; - [image release]; } } // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm index 0d0395972..95f6cdea0 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm @@ -43,7 +43,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #import #import -NSImage *qt_mac_create_nsimage(const QPixmap &pm); using TouchBar::kCircleDiameter; using TouchBar::CreateNSImageFromStyleIcon; @@ -96,32 +95,32 @@ struct PickerScrubberItem { } void updateThumbnail() { - if (!document || !qpixmap.isNull()) { + if (!document || !image.isNull()) { return; } - const auto image = mediaView->getStickerSmall(); - if (!image) { + const auto sticker = mediaView->getStickerSmall(); + if (!sticker) { return; } - const auto size = image->size() + const auto size = sticker->size() .scaled(kCircleDiameter, kCircleDiameter, Qt::KeepAspectRatio); - qpixmap = image->pixSingle( + image = sticker->pixSingle( size.width(), size.height(), kCircleDiameter, kCircleDiameter, - ImageRoundRadius::None); + ImageRoundRadius::None).toImage(); } bool isStickerLoaded() const { - return !qpixmap.isNull(); + return !image.isNull(); } QString title = QString(); DocumentData *document = nullptr; std::shared_ptr mediaView = nullptr; - QPixmap qpixmap; + QImage image; EmojiPtr emoji = nullptr; }; @@ -140,21 +139,25 @@ struct PickerScrubberItemsHolder { }; using Platform::Q2NSString; +using Platform::Q2NSImage; NSImage *CreateNSImageFromEmoji(EmojiPtr emoji) { - const auto s = kIdealIconSize * cIntRetinaFactor(); - auto pixmap = QPixmap(s, s); - pixmap.setDevicePixelRatio(cRetinaFactor()); - pixmap.fill(Qt::black); - Painter paint(&pixmap); - PainterHighQualityEnabler hq(paint); - Ui::Emoji::Draw( - paint, - std::move(emoji), - Ui::Emoji::GetSizeTouchbar(), - 0, - 0); - return [qt_mac_create_nsimage(pixmap) autorelease]; + auto image = QImage( + QSize(kIdealIconSize, kIdealIconSize) * cIntRetinaFactor(), + QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(cRetinaFactor()); + image.fill(Qt::black); + { + Painter paint(&image); + PainterHighQualityEnabler hq(paint); + Ui::Emoji::Draw( + paint, + emoji, + Ui::Emoji::GetSizeTouchbar(), + 0, + 0); + } + return Q2NSImage(image); } auto ActiveChat(not_null controller) { @@ -421,8 +424,7 @@ void AppendEmojiPacks( PickerScrubberItemView *itemView = [scrubber makeItemWithIdentifier:kStickerItemIdentifier owner:self]; - itemView.imageView.image = [qt_mac_create_nsimage(item.qpixmap) - autorelease]; + itemView.imageView.image = Q2NSImage(item.image); itemView->documentId = document->id; return itemView; } else if (const auto emoji = item.emoji) { diff --git a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.mm b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.mm index 3fde9b0c6..decdef0ed 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.mm @@ -20,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #import #import -NSImage *qt_mac_create_nsimage(const QPixmap &pm); using TouchBar::kCircleDiameter; using TouchBar::CreateNSImageFromStyleIcon; diff --git a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_common.mm b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_common.mm index a7954218c..7a30c4875 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_common.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_common.mm @@ -9,9 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #ifndef OS_OSX -#import +#include "base/platform/mac/base_utilities_mac.h" -NSImage *qt_mac_create_nsimage(const QPixmap &pm); +#import namespace TouchBar { @@ -21,10 +21,9 @@ int WidthFromString(NSString *s) { } NSImage *CreateNSImageFromStyleIcon(const style::icon &icon, int size) { - const auto instance = icon.instance(QColor(255, 255, 255, 255), 100); - auto pixmap = QPixmap::fromImage(instance); - pixmap.setDevicePixelRatio(cRetinaFactor()); - NSImage *image = [qt_mac_create_nsimage(pixmap) autorelease]; + auto instance = icon.instance(QColor(255, 255, 255, 255), 100); + instance.setDevicePixelRatio(cRetinaFactor()); + NSImage *image = Platform::Q2NSImage(instance); [image setSize:NSMakeSize(size, size)]; return image; } diff --git a/docs/building-xcode.md b/docs/building-xcode.md index f3a599b62..f56d70158 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -254,16 +254,16 @@ Go to ***BuildPath*** and run ninja -C out/Release cd .. - git clone git://code.qt.io/qt/qt5.git qt_5_12_8 - cd qt_5_12_8 + git clone git://code.qt.io/qt/qt5.git qt_5_15_1 + cd qt_5_15_1 perl init-repository --module-subset=qtbase,qtimageformats - git checkout v5.12.8 + git checkout v5.15.1 git submodule update qtbase qtimageformats cd qtbase - find ../../patches/qtbase_5_12_8 -type f -print0 | sort -z | xargs -0 git apply + find ../../patches/qtbase_5_15_1 -type f -print0 | sort -z | xargs -0 git apply cd .. - ./configure -prefix "/usr/local/desktop-app/Qt-5.12.8" \ + ./configure -prefix "/usr/local/desktop-app/Qt-5.15.1" \ -debug-and-release \ -force-debug-info \ -opensource \ From b1e2beba2cd5fc5530377c82176d12f9218972a6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 2 Nov 2020 17:13:25 +0300 Subject: [PATCH 008/370] Fix macOS tray icon on Big Sur & Qt 5.15.1. --- .../platform/mac/main_window_mac.h | 4 +- .../platform/mac/main_window_mac.mm | 64 +++++++++++-------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.h b/Telegram/SourceFiles/platform/mac/main_window_mac.h index f9179a3e0..1366222ae 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.h +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.h @@ -65,7 +65,6 @@ protected: void titleVisibilityChangedHook() override; void unreadCounterChangedHook() override; - QImage psTrayIcon(bool selected = false) const; bool hasTrayIcon() const override { return trayIcon; } @@ -77,8 +76,6 @@ protected: QSystemTrayIcon *trayIcon = nullptr; QMenu *trayIconMenu = nullptr; - QImage trayImg, trayImgSel; - void psTrayMenuUpdated(); void psSetupTrayIcon(); virtual void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) = 0; @@ -95,6 +92,7 @@ private: void hideAndDeactivate(); void updateTitleCounter(); void updateIconCounters(); + [[nodiscard]] QIcon generateIconForTray(int counter, bool muted) const; std::unique_ptr _private; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 6fbd9ec11..d99bb9630 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -124,6 +124,17 @@ private: }; +[[nodiscard]] QImage TrayIconBack(bool darkMode) { + static const auto WithColor = [](QColor color) { + return st::macTrayIcon.instance(color, 100); + }; + static const auto DarkModeResult = WithColor({ 255, 255, 255 }); + static const auto LightModeResult = WithColor({ 0, 0, 0, 180 }); + auto result = darkMode ? DarkModeResult : LightModeResult; + result.detach(); + return result; +} + } // namespace class MainWindow::Private { @@ -200,7 +211,6 @@ private: - (void) darkModeChanged:(NSNotification *)aNotification { Core::Sandbox::Instance().customEnterFromEventLoop([&] { Core::App().settings().setSystemDarkMode(Platform::IsDarkMode()); - Core::App().domain().notifyUnreadBadgeChanged(); }); } @@ -485,9 +495,6 @@ MainWindow::MainWindow(not_null controller) auto forceOpenGL = std::make_unique(this); #endif // !OS_MAC_OLD - trayImg = st::macTrayIcon.instance(QColor(0, 0, 0, 180), 100); - trayImgSel = st::macTrayIcon.instance(QColor(255, 255, 255), 100); - _hideAfterFullScreenTimer.setCallback([this] { hideAndDeactivate(); }); subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) { @@ -543,10 +550,6 @@ void MainWindow::hideAndDeactivate() { hide(); } -QImage MainWindow::psTrayIcon(bool selected) const { - return selected ? trayImgSel : trayImg; -} - void MainWindow::psShowTrayMenu() { } @@ -566,14 +569,13 @@ void MainWindow::psTrayMenuUpdated() { void MainWindow::psSetupTrayIcon() { if (!trayIcon) { trayIcon = new QSystemTrayIcon(this); - - QIcon icon(QPixmap::fromImage(psTrayIcon(), Qt::ColorOnly)); - icon.addPixmap(QPixmap::fromImage(psTrayIcon(true), Qt::ColorOnly), QIcon::Selected); - - trayIcon->setIcon(icon); + trayIcon->setIcon(generateIconForTray( + Core::App().unreadBadge(), + Core::App().unreadBadgeMuted())); attachToTrayIcon(trayIcon); + } else { + updateIconCounters(); } - updateIconCounters(); trayIcon->show(); } @@ -651,21 +653,31 @@ void MainWindow::updateIconCounters() { _private->setWindowBadge(string); if (trayIcon) { - bool dm = Platform::IsDarkMenuBar(); - auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg); - QIcon icon; - QImage img(psTrayIcon(dm)), imgsel(psTrayIcon(true)); - img.detach(); - imgsel.detach(); - int32 size = 22 * cIntRetinaFactor(); - _placeCounter(img, size, counter, bg, (dm && muted) ? st::trayCounterFgMacInvert : st::trayCounterFg); - _placeCounter(imgsel, size, counter, st::trayCounterBgMacInvert, st::trayCounterFgMacInvert); - icon.addPixmap(App::pixmapFromImageInPlace(std::move(img))); - icon.addPixmap(App::pixmapFromImageInPlace(std::move(imgsel)), QIcon::Selected); - trayIcon->setIcon(icon); + trayIcon->setIcon(generateIconForTray(counter, muted)); } } +QIcon MainWindow::generateIconForTray(int counter, bool muted) const { + auto result = QIcon(); + auto lightMode = TrayIconBack(false); + auto darkMode = TrayIconBack(true); + auto lightModeActive = darkMode; + auto darkModeActive = darkMode; + lightModeActive.detach(); + darkModeActive.detach(); + const auto size = 22 * cIntRetinaFactor(); + const auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg); + _placeCounter(lightMode, size, counter, bg, st::trayCounterFg); + _placeCounter(darkMode, size, counter, bg, muted ? st::trayCounterFgMacInvert : st::trayCounterFg); + _placeCounter(lightModeActive, size, counter, st::trayCounterBgMacInvert, st::trayCounterFgMacInvert); + _placeCounter(darkModeActive, size, counter, st::trayCounterBgMacInvert, st::trayCounterFgMacInvert); + result.addPixmap(App::pixmapFromImageInPlace(std::move(lightMode)), QIcon::Normal, QIcon::Off); + result.addPixmap(App::pixmapFromImageInPlace(std::move(darkMode)), QIcon::Normal, QIcon::On); + result.addPixmap(App::pixmapFromImageInPlace(std::move(lightModeActive)), QIcon::Active, QIcon::Off); + result.addPixmap(App::pixmapFromImageInPlace(std::move(darkModeActive)), QIcon::Active, QIcon::On); + return result; +} + void MainWindow::initShadows() { _private->enableShadow(winId()); } From 80c4ecb9bff71b972c61019039588cb9745f2505 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 4 Nov 2020 09:57:03 +0300 Subject: [PATCH 009/370] Migrate CentOS docker file to Qt 5.15.1. --- Telegram/build/docker/build.sh | 87 ++++++++ Telegram/build/docker/centos_env/Dockerfile | 220 +++++++++++++------- Telegram/build/docker/centos_env/prepare.sh | 7 + Telegram/build/docker/centos_env/run.sh | 26 +++ 4 files changed, 269 insertions(+), 71 deletions(-) create mode 100755 Telegram/build/docker/build.sh create mode 100755 Telegram/build/docker/centos_env/prepare.sh create mode 100755 Telegram/build/docker/centos_env/run.sh diff --git a/Telegram/build/docker/build.sh b/Telegram/build/docker/build.sh new file mode 100755 index 000000000..4745a07bd --- /dev/null +++ b/Telegram/build/docker/build.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +Run () { + scl enable devtoolset-8 -- "$@" +} + +HomePath=/usr/src/tdesktop/Telegram +cd $HomePath + +ProjectPath="$HomePath/../out" +ReleasePath="$ProjectPath/Release" +BinaryName="Telegram" + +if [ ! -f "/usr/bin/cmake" ]; then + ln -s cmake3 /usr/bin/cmake +fi + +Run ./configure.sh + +cd $ProjectPath +Run cmake --build . --config Release --target Telegram -- -j8 +cd $ReleasePath + +echo "$BinaryName build complete!" + +if [ ! -f "$ReleasePath/$BinaryName" ]; then +Error "$BinaryName not found!" +fi + +# BadCount=`objdump -T $ReleasePath/$BinaryName | grep GLIBC_2\.1[6-9] | wc -l` +# if [ "$BadCount" != "0" ]; then +# Error "Bad GLIBC usages found: $BadCount" +# fi + +# BadCount=`objdump -T $ReleasePath/$BinaryName | grep GLIBC_2\.2[0-9] | wc -l` +# if [ "$BadCount" != "0" ]; then +# Error "Bad GLIBC usages found: $BadCount" +# fi + +BadCount=`objdump -T $ReleasePath/$BinaryName | grep GCC_4\.[3-9] | wc -l` +if [ "$BadCount" != "0" ]; then +Error "Bad GCC usages found: $BadCount" +fi + +BadCount=`objdump -T $ReleasePath/$BinaryName | grep GCC_[5-9]\. | wc -l` +if [ "$BadCount" != "0" ]; then +Error "Bad GCC usages found: $BadCount" +fi + +if [ ! -f "$ReleasePath/Updater" ]; then +Error "Updater not found!" +fi + +BadCount=`objdump -T $ReleasePath/Updater | grep GLIBC_2\.1[6-9] | wc -l` +if [ "$BadCount" != "0" ]; then +Error "Bad GLIBC usages found: $BadCount" +fi + +BadCount=`objdump -T $ReleasePath/Updater | grep GLIBC_2\.2[0-9] | wc -l` +if [ "$BadCount" != "0" ]; then +Error "Bad GLIBC usages found: $BadCount" +fi + +BadCount=`objdump -T $ReleasePath/Updater | grep GCC_4\.[3-9] | wc -l` +if [ "$BadCount" != "0" ]; then +Error "Bad GCC usages found: $BadCount" +fi + +BadCount=`objdump -T $ReleasePath/Updater | grep GCC_[5-9]\. | wc -l` +if [ "$BadCount" != "0" ]; then +Error "Bad GCC usages found: $BadCount" +fi + +echo "Dumping debug symbols.." +/dump_syms "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym" +echo "Done!" + +echo "Stripping the executable.." +strip -s "$ReleasePath/$BinaryName" +echo "Done!" + +rm -rf "$ReleasePath/root" +mkdir "$ReleasePath/root" +mv "$ReleasePath/$BinaryName" "$ReleasePath/root/" +mv "$ReleasePath/$BinaryName.sym" "$ReleasePath/root/" +mv "$ReleasePath/Updater" "$ReleasePath/root/" +mv "$ReleasePath/Packer" "$ReleasePath/root/" diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 0e055dee2..dcf95e11b 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -2,8 +2,9 @@ FROM centos:7 AS builder ENV GIT https://github.com ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig -ENV QT 5_12_8 -ENV QT_PREFIX /usr/local/desktop-app/Qt-5.12.8 +ENV QT 5_15_1 +ENV QT_TAG v5.15.1 +ENV QT_PREFIX /usr/local/desktop-app/Qt-5.15.1 ENV OPENSSL_VER 1_1_1 ENV OPENSSL_PREFIX /usr/local/desktop-app/openssl-1.1.1 @@ -18,6 +19,7 @@ RUN yum -y install git cmake3 zlib-devel gtk2-devel libICE-devel \ devtoolset-8-make devtoolset-8-gcc devtoolset-8-gcc-c++ \ devtoolset-8-binutils +SHELL [ "scl", "enable", "devtoolset-8", "--", "bash", "-c" ] RUN ln -s cmake3 /usr/bin/cmake ENV LibrariesPath /usr/src/Libraries @@ -31,10 +33,10 @@ FROM builder AS libffi RUN git clone -b v3.3 --depth=1 $GIT/libffi/libffi.git WORKDIR libffi -RUN scl enable devtoolset-8 -- ./autogen.sh -RUN scl enable devtoolset-8 -- ./configure --enable-static --disable-docs -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libffi-cache" install +RUN ./autogen.sh +RUN ./configure --enable-static --disable-docs +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/libffi-cache" install WORKDIR .. RUN rm -rf libffi @@ -43,9 +45,9 @@ FROM builder AS xz RUN git clone -b v5.2.5 https://git.tukaani.org/xz.git WORKDIR xz -RUN scl enable devtoolset-8 -- cmake3 -B build . -DCMAKE_BUILD_TYPE=Release -RUN scl enable devtoolset-8 -- cmake3 --build build -j$(nproc) -RUN DESTDIR="$LibrariesPath/xz-cache" scl enable devtoolset-8 -- cmake3 --install build +RUN cmake3 -B build . -DCMAKE_BUILD_TYPE=Release +RUN cmake3 --build build -j$(nproc) +RUN DESTDIR="$LibrariesPath/xz-cache" cmake3 --install build WORKDIR .. RUN rm -rf xz @@ -54,14 +56,14 @@ FROM builder AS mozjpeg RUN git clone -b v4.0.1-rc2 --depth=1 $GIT/mozilla/mozjpeg.git WORKDIR mozjpeg -RUN scl enable devtoolset-8 -- cmake3 -B build . \ +RUN cmake3 -B build . \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DWITH_JPEG8=ON \ -DPNG_SUPPORTED=OFF -RUN scl enable devtoolset-8 -- cmake3 --build build -j$(nproc) -RUN DESTDIR="$LibrariesPath/mozjpeg-cache" scl enable devtoolset-8 -- cmake3 --install build +RUN cmake3 --build build -j$(nproc) +RUN DESTDIR="$LibrariesPath/mozjpeg-cache" cmake3 --install build WORKDIR .. RUN rm -rf mozjpeg @@ -70,10 +72,10 @@ FROM builder AS opus RUN git clone -b v1.3 --depth=1 $GIT/xiph/opus.git WORKDIR opus -RUN scl enable devtoolset-8 -- ./autogen.sh -RUN scl enable devtoolset-8 -- ./configure -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/opus-cache" install +RUN ./autogen.sh +RUN ./configure +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/opus-cache" install WORKDIR .. RUN rm -rf opus @@ -82,9 +84,9 @@ FROM builder AS xcb-proto RUN git clone -b xcb-proto-1.14 --depth=1 https://gitlab.freedesktop.org/xorg/proto/xcbproto.git WORKDIR xcbproto -RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/xcb-proto-cache" install +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/xcb-proto-cache" install WORKDIR .. RUN rm -rf xcbproto @@ -95,20 +97,71 @@ COPY --from=xcb-proto ${LibrariesPath}/xcb-proto-cache / RUN git clone -b libxcb-1.14 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxcb.git WORKDIR libxcb -RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/xcb-cache" install +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/xcb-cache" install WORKDIR .. RUN rm -rf libxcb +FROM builder AS xcb-wm + +RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-wm.git + +WORKDIR libxcb-wm +RUN git checkout 0.4.1 +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/xcb-wm-cache" install + +FROM builder AS xcb-util + +RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-util.git + +WORKDIR libxcb-util +RUN git checkout 0.4.0 +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/xcb-util-cache" install + +FROM builder AS xcb-image +COPY --from=xcb-util ${LibrariesPath}/xcb-util-cache / + +RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-image.git + +WORKDIR libxcb-image +RUN git checkout 0.4.0 +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/xcb-image-cache" install + +FROM builder AS xcb-keysyms + +RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-keysyms.git + +WORKDIR libxcb-keysyms +RUN git checkout 0.4.0 +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/xcb-keysyms-cache" install + +FROM builder AS xcb-render-util + +RUN git clone --recursive https://gitlab.freedesktop.org/xorg/lib/libxcb-render-util.git + +WORKDIR libxcb-render-util +RUN git checkout 0.3.9 +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/xcb-render-util-cache" install + FROM builder AS libXext RUN git clone -b libXext-1.3.4 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxext.git WORKDIR libxext -RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libXext-cache" install +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/libXext-cache" install WORKDIR .. RUN rm -rf libxext @@ -117,9 +170,9 @@ FROM builder AS libXfixes RUN git clone -b libXfixes-5.0.3 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxfixes.git WORKDIR libxfixes -RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libXfixes-cache" install +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/libXfixes-cache" install WORKDIR .. RUN rm -rf libxfixes @@ -128,9 +181,9 @@ FROM builder AS libXi RUN git clone -b libXi-1.7.10 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxi.git WORKDIR libxi -RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libXi-cache" install +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/libXi-cache" install WORKDIR .. RUN rm -rf libxi @@ -139,9 +192,9 @@ FROM builder AS libXrender RUN git clone -b libXrender-0.9.10 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxrender.git WORKDIR libxrender -RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libXrender-cache" install +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/libXrender-cache" install WORKDIR .. RUN rm -rf libxrender @@ -152,13 +205,13 @@ COPY --from=libffi ${LibrariesPath}/libffi-cache / RUN git clone -b 1.18.0 --depth=1 https://gitlab.freedesktop.org/wayland/wayland.git WORKDIR wayland -RUN scl enable devtoolset-8 -- ./autogen.sh \ +RUN ./autogen.sh \ --enable-static \ --disable-documentation \ --disable-dtd-validation -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/wayland-cache" install +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/wayland-cache" install WORKDIR .. RUN rm -rf wayland @@ -173,9 +226,9 @@ COPY --from=wayland ${LibrariesPath}/wayland-cache / RUN git clone -b 2.9.0 --depth=1 $GIT/intel/libva.git WORKDIR libva -RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libva-cache" install +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/libva-cache" install WORKDIR .. RUN rm -rf libva @@ -184,9 +237,9 @@ FROM builder AS libvdpau RUN git clone -b libvdpau-1.2 --depth=1 https://gitlab.freedesktop.org/vdpau/libvdpau.git WORKDIR libvdpau -RUN scl enable devtoolset-8 -- ./autogen.sh --enable-static -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/libvdpau-cache" install +RUN ./autogen.sh --enable-static +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/libvdpau-cache" install WORKDIR .. RUN rm -rf libvdpau @@ -200,7 +253,7 @@ COPY --from=libvdpau ${LibrariesPath}/libvdpau-cache / RUN git clone -b release/3.4 --depth=1 $GIT/FFmpeg/FFmpeg.git ffmpeg WORKDIR ffmpeg -RUN scl enable devtoolset-8 -- ./configure \ +RUN ./configure \ --disable-debug \ --disable-programs \ --disable-doc \ @@ -302,14 +355,14 @@ RUN scl enable devtoolset-8 -- ./configure \ --enable-muxer=ogg \ --enable-muxer=opus -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/ffmpeg-cache" install +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/ffmpeg-cache" install FROM builder AS openal RUN git clone -b openal-soft-1.20.1 --depth=1 $GIT/kcat/openal-soft.git WORKDIR openal-soft -RUN scl enable devtoolset-8 -- cmake3 -B build . \ +RUN cmake3 -B build . \ -DCMAKE_BUILD_TYPE=Release \ -DLIBTYPE:STRING=STATIC \ -DALSOFT_EXAMPLES=OFF \ @@ -317,8 +370,8 @@ RUN scl enable devtoolset-8 -- cmake3 -B build . \ -DALSOFT_UTILS=OFF \ -DALSOFT_CONFIG=OFF -RUN scl enable devtoolset-8 -- cmake3 --build build -j$(nproc) -RUN DESTDIR="$LibrariesPath/openal-cache" scl enable devtoolset-8 -- cmake3 --install build +RUN cmake3 --build build -j$(nproc) +RUN DESTDIR="$LibrariesPath/openal-cache" cmake3 --install build WORKDIR .. RUN rm -rf openal @@ -329,9 +382,9 @@ RUN git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \ $GIT/openssl/openssl.git $opensslDir WORKDIR ${opensslDir} -RUN scl enable devtoolset-8 -- ./config --prefix="$OPENSSL_PREFIX" no-tests -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/openssl-cache" install_sw +RUN ./config --prefix="$OPENSSL_PREFIX" no-tests +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/openssl-cache" install_sw WORKDIR .. RUN rm -rf $opensslDir @@ -340,14 +393,14 @@ FROM builder AS xkbcommon RUN git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git WORKDIR libxkbcommon -RUN scl enable devtoolset-8 -- ./autogen.sh \ +RUN ./autogen.sh \ --disable-docs \ --disable-wayland \ --with-xkb-config-root=/usr/share/X11/xkb \ --with-x-locale-root=/usr/share/X11/locale -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$LibrariesPath/xkbcommon-cache" install +RUN make -j$(nproc) +RUN make DESTDIR="$LibrariesPath/xkbcommon-cache" install WORKDIR .. RUN rm -rf libxkbcommon @@ -357,6 +410,11 @@ FROM patches AS qt COPY --from=libffi ${LibrariesPath}/libffi-cache / COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=xcb ${LibrariesPath}/xcb-cache / +COPY --from=xcb-wm ${LibrariesPath}/xcb-wm-cache / +COPY --from=xcb-util ${LibrariesPath}/xcb-util-cache / +COPY --from=xcb-image ${LibrariesPath}/xcb-image-cache / +COPY --from=xcb-keysyms ${LibrariesPath}/xcb-keysyms-cache / +COPY --from=xcb-render-util ${LibrariesPath}/xcb-render-util-cache / COPY --from=libXext ${LibrariesPath}/libXext-cache / COPY --from=libXfixes ${LibrariesPath}/libXfixes-cache / COPY --from=libXi ${LibrariesPath}/libXi-cache / @@ -365,37 +423,42 @@ COPY --from=wayland ${LibrariesPath}/wayland-cache / COPY --from=openssl ${LibrariesPath}/openssl-cache / COPY --from=xkbcommon ${LibrariesPath}/xkbcommon-cache / -RUN git clone -b v5.12.8 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT} +RUN git clone -b ${QT_TAG} --depth=1 git://code.qt.io/qt/qt5.git qt_${QT} WORKDIR qt_${QT} RUN perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg RUN git submodule update qtbase qtwayland qtimageformats qtsvg WORKDIR qtbase RUN find ../../patches/qtbase_${QT} -type f -print0 | sort -z | xargs -r0 git apply -WORKDIR ../qtwayland -RUN find ../../patches/qtwayland_${QT} -type f -print0 | sort -z | xargs -r0 git apply WORKDIR .. -RUN scl enable devtoolset-8 -- ./configure -prefix "$QT_PREFIX" \ +# I couldn't make it work with direct ./configure call :( +RUN echo './configure -prefix '$'\"''$QT_PREFIX'$'\"'' \ -release \ -opensource \ -confirm-license \ + -xcb \ -qt-libpng \ -qt-harfbuzz \ -qt-pcre \ - -qt-xcb \ -no-icu \ -no-gtk \ + -no-feature-wayland-server \ -static \ -dbus-runtime \ -openssl-linked \ - -I "$OPENSSL_PREFIX/include" OPENSSL_LIBS="$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -lz -ldl -lpthread" \ + -I '$'\"''$OPENSSL_PREFIX/include'$'\"'' \ + OPENSSL_LIBS='$'\"''$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -lz -ldl -lpthread'$'\"'' \ -nomake examples \ -nomake tests \ - -L /usr/local/lib64 + -L /usr/local/lib64' >> ./run_configure.sh +RUN cat ./run_configure.sh +RUN chmod a+x ./run_configure.sh +RUN ./run_configure.sh +RUN rm ./run_configure.sh -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make INSTALL_ROOT="$LibrariesPath/qt-cache" install +RUN make -j$(nproc) +RUN make INSTALL_ROOT="$LibrariesPath/qt-cache" install WORKDIR .. RUN rm -rf qt_${QT} @@ -419,9 +482,9 @@ RUN git checkout 9f2a7bb1 RUN git apply ../patches/gyp.diff WORKDIR ../breakpad -RUN scl enable devtoolset-8 -- ./configure -RUN scl enable devtoolset-8 -- make -j$(nproc) -RUN scl enable devtoolset-8 -- make DESTDIR="$BreakpadCache" install +RUN ./configure +RUN make -j$(nproc) +RUN make DESTDIR="$BreakpadCache" install WORKDIR src RUN rm -rf testing @@ -432,8 +495,8 @@ RUN sed -i 's/minidump_upload.m/minidump_upload.cc/' linux/tools_linux.gypi RUN ../../../gyp/gyp --depth=. --generator-output=.. -Goutput_dir=../out tools.gyp --format=cmake WORKDIR ../../out/Default -RUN scl enable devtoolset-8 -- cmake3 . -RUN scl enable devtoolset-8 -- cmake3 --build . --target dump_syms -j$(nproc) +RUN cmake3 . +RUN cmake3 --build . --target dump_syms -j$(nproc) RUN mv dump_syms $BreakpadCache WORKDIR .. @@ -451,7 +514,7 @@ RUN git clone $GIT/desktop-app/tg_owt.git WORKDIR tg_owt RUN git checkout c73a471 -RUN scl enable devtoolset-8 -- cmake3 -B out/Release . \ +RUN cmake3 -B out/Release . \ -DCMAKE_BUILD_TYPE=Release \ -DTG_OWT_SPECIAL_TARGET=linux \ -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ @@ -459,7 +522,17 @@ RUN scl enable devtoolset-8 -- cmake3 -B out/Release . \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include -RUN scl enable devtoolset-8 -- cmake3 --build out/Release +RUN cmake3 --build out/Release -- -j8 + +RUN cmake3 -B out/Debug . \ + -DCMAKE_BUILD_TYPE=Debug \ + -DTG_OWT_SPECIAL_TARGET=linux \ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ + -DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PREFIX/include \ + -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ + -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include + +RUN cmake3 --build out/Debug -- -j8 FROM builder @@ -468,6 +541,11 @@ COPY --from=xz ${LibrariesPath}/xz-cache / COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=opus ${LibrariesPath}/opus-cache / COPY --from=xcb ${LibrariesPath}/xcb-cache / +COPY --from=xcb-wm ${LibrariesPath}/xcb-wm-cache / +COPY --from=xcb-util ${LibrariesPath}/xcb-util-cache / +COPY --from=xcb-image ${LibrariesPath}/xcb-image-cache / +COPY --from=xcb-keysyms ${LibrariesPath}/xcb-keysyms-cache / +COPY --from=xcb-render-util ${LibrariesPath}/xcb-render-util-cache / COPY --from=libXext ${LibrariesPath}/libXext-cache / COPY --from=libXfixes ${LibrariesPath}/libXfixes-cache / COPY --from=libXi ${LibrariesPath}/libXi-cache / diff --git a/Telegram/build/docker/centos_env/prepare.sh b/Telegram/build/docker/centos_env/prepare.sh new file mode 100755 index 000000000..c25644aa8 --- /dev/null +++ b/Telegram/build/docker/centos_env/prepare.sh @@ -0,0 +1,7 @@ +set -e +FullExecPath=$PWD +pushd `dirname $0` > /dev/null +FullScriptPath=`pwd` +popd > /dev/null + +docker build -t tdesktop:centos_env "$FullScriptPath/" diff --git a/Telegram/build/docker/centos_env/run.sh b/Telegram/build/docker/centos_env/run.sh new file mode 100755 index 000000000..2e65751cf --- /dev/null +++ b/Telegram/build/docker/centos_env/run.sh @@ -0,0 +1,26 @@ +set -e +FullExecPath=$PWD +pushd `dirname $0` > /dev/null +FullScriptPath=`pwd` +popd > /dev/null + +if [ ! -d "$FullScriptPath/../../../../../DesktopPrivate" ]; then + echo "" + echo "This script is for building the production version of Telegram Desktop." + echo "" + echo "For building custom versions please visit the build instructions page at:" + echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" + exit +fi + +Error () { + cd $FullExecPath + echo "$1" + exit 1 +} + +if [ "$1" == "" ]; then + Error "Script not found!" +fi + +docker run -it --rm --cpus=8 --memory=10g -v $HOME/Telegram/DesktopPrivate:/usr/src/DesktopPrivate -v $HOME/Telegram/tdesktop:/usr/src/tdesktop tdesktop:centos_env $1 From 74d23137844ad43ba746a481373dc4daf5d003a8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 4 Nov 2020 18:50:17 +0300 Subject: [PATCH 010/370] Build Linux version with Qt 5.15.1 in CentOS 7 docker. --- Telegram/SourceFiles/apiwrap.cpp | 5 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 2 +- .../boxes/peers/edit_participant_box.cpp | 3 +- Telegram/SourceFiles/calls/calls_panel.cpp | 2 +- .../chat_helpers/emoji_suggestions_widget.cpp | 6 +- .../chat_helpers/stickers_list_widget.cpp | 4 +- Telegram/SourceFiles/core/application.cpp | 3 +- .../SourceFiles/core/crash_report_window.cpp | 10 +-- Telegram/SourceFiles/core/sandbox.cpp | 6 +- Telegram/SourceFiles/core/update_checker.cpp | 10 ++- .../SourceFiles/data/data_peer_values.cpp | 3 +- Telegram/SourceFiles/data/data_web_page.cpp | 3 +- .../export/view/export_view_settings.cpp | 3 +- .../history/history_item_components.cpp | 3 +- .../media/clip/media_clip_reader.h | 2 +- .../media/view/media_view_overlay_widget.cpp | 20 +++--- .../SourceFiles/media/view/media_view_pip.cpp | 4 +- .../mtproto/details/mtproto_tcp_socket.cpp | 6 +- .../mtproto/details/mtproto_tls_socket.cpp | 6 +- .../mtproto/mtproto_dc_options.cpp | 3 +- .../mtproto/special_config_request.cpp | 4 +- .../SourceFiles/overview/overview_layout.cpp | 3 +- .../platform/linux/file_utilities_linux.cpp | 4 +- .../platform/linux/file_utilities_linux.h | 2 +- .../linux/linux_desktop_environment.cpp | 3 +- .../platform/linux/specific_linux.cpp | 35 +++++---- .../platform/mac/file_utilities_mac.mm | 9 ++- .../platform/win/file_utilities_win.cpp | 6 +- .../platform/win/main_window_win.cpp | 2 +- .../profile/profile_block_peer_list.h | 2 +- .../SourceFiles/storage/file_download_web.cpp | 6 +- .../support/support_autocomplete.cpp | 3 +- Telegram/SourceFiles/ui/countryinput.cpp | 3 +- .../SourceFiles/ui/widgets/separate_panel.cpp | 1 - .../themes/window_theme_editor_block.cpp | 3 +- .../SourceFiles/window/window_title_qt.cpp | 2 +- Telegram/ThirdParty/fcitx-qt5 | 2 +- Telegram/build/build.sh | 72 ++----------------- Telegram/build/docker/build.sh | 17 ++++- Telegram/codegen | 2 +- Telegram/lib_base | 2 +- Telegram/lib_lottie | 2 +- Telegram/lib_qr | 2 +- Telegram/lib_spellcheck | 2 +- Telegram/lib_ui | 2 +- Telegram/lib_webrtc | 2 +- cmake | 2 +- 47 files changed, 131 insertions(+), 168 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index f97437270..d21aec624 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "base/openssl_help.h" #include "base/unixtime.h" +#include "base/qt_adapters.h" #include "base/call_delayed.h" #include "lang/lang_keys.h" #include "mainwindow.h" @@ -3277,7 +3278,7 @@ void ApiWrap::requestMessageAfterDate( // So we request a message with offset_date = desired_date - 1 and add_offset = -1. // This should give us the first message with date >= desired_date. auto offsetId = 0; - auto offsetDate = static_cast(QDateTime(date).toTime_t()) - 1; + auto offsetDate = static_cast(base::QDateToDateTime(date).toTime_t()) - 1; auto addOffset = -1; auto limit = 1; auto maxId = 0; @@ -3365,7 +3366,7 @@ void ApiWrap::jumpToHistoryDate(not_null peer, const QDate &date) { // const QDate &date, // Callback &&callback) { // const auto offsetId = 0; -// const auto offsetDate = static_cast(QDateTime(date).toTime_t()); +// const auto offsetDate = static_cast(base::QDateToDateTime(date).toTime_t()); // const auto addOffset = -2; // const auto limit = 1; // const auto hash = 0; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 9b8a5d51e..5a08ed6e7 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -178,7 +178,7 @@ void PeerListBox::resizeEvent(QResizeEvent *e) { void PeerListBox::paintEvent(QPaintEvent *e) { Painter p(this); - for (auto rect : e->region().rects()) { + for (const auto rect : e->region()) { p.fillRect(rect, st::contactsBg); } } diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index ab18b9f87..c6fbac836 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "core/core_cloud_password.h" #include "base/unixtime.h" +#include "base/qt_adapters.h" #include "apiwrap.h" #include "main/main_session.h" #include "styles/style_layers.h" @@ -694,7 +695,7 @@ void EditRestrictedBox::showRestrictUntil() { highlighted, [this](const QDate &date) { setRestrictUntil( - static_cast(QDateTime(date).toTime_t())); + static_cast(base::QDateToDateTime(date).toTime_t())); }), Ui::LayerOption::KeepOther); _restrictUntilBox->setMaxDate( diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 10b1d6bd3..217924d13 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -981,7 +981,7 @@ void Panel::paint(QRect clip) { if (!_incoming->isHidden()) { region = region.subtracted(QRegion(_incoming->geometry())); } - for (const auto rect : region.rects()) { + for (const auto rect : region) { p.fillRect(rect, st::callBgOpaque); } if (_incoming && _incoming->isHidden()) { diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index c3693d3b3..772d2e0ce 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -171,10 +171,8 @@ bool SuggestionsWidget::eventHook(QEvent *e) { } void SuggestionsWidget::scrollByWheelEvent(not_null e) { - const auto horizontal = (e->angleDelta().x() != 0) - || (e->orientation() == Qt::Horizontal); - const auto vertical = (e->angleDelta().y() != 0) - || (e->orientation() == Qt::Vertical); + const auto horizontal = (e->angleDelta().x() != 0); + const auto vertical = (e->angleDelta().y() != 0); const auto current = scrollCurrent(); const auto scroll = [&] { if (horizontal) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index bece2cd01..d2534c7ed 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -614,8 +614,8 @@ bool StickersListWidget::Footer::eventHook(QEvent *e) { void StickersListWidget::Footer::scrollByWheelEvent( not_null e) { - auto horizontal = (e->angleDelta().x() != 0 || e->orientation() == Qt::Horizontal); - auto vertical = (e->angleDelta().y() != 0 || e->orientation() == Qt::Vertical); + auto horizontal = (e->angleDelta().x() != 0); + auto vertical = (e->angleDelta().y() != 0); if (horizontal) { _horizontal = true; } diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 74152204e..8058a0f32 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -79,6 +79,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include namespace Core { namespace { @@ -941,7 +942,7 @@ QPoint Application::getPointForCallPanelCenter() const { if (const auto window = activeWindow()) { return window->getPointForCallPanelCenter(); } - return QApplication::desktop()->screenGeometry().center(); + return QGuiApplication::primaryScreen()->geometry().center(); } // macOS Qt bug workaround, sometimes no leaveEvent() gets to the nested widgets. diff --git a/Telegram/SourceFiles/core/crash_report_window.cpp b/Telegram/SourceFiles/core/crash_report_window.cpp index 4f69672b8..26277e331 100644 --- a/Telegram/SourceFiles/core/crash_report_window.cpp +++ b/Telegram/SourceFiles/core/crash_report_window.cpp @@ -39,7 +39,7 @@ PreLaunchWindow::PreLaunchWindow(QString title) { setWindowTitle(title.isEmpty() ? qsl("Telegram") : title); QPalette p(palette()); - p.setColor(QPalette::Background, QColor(255, 255, 255)); + p.setColor(QPalette::Window, QColor(255, 255, 255)); setPalette(p); QLabel tmp(this); @@ -79,7 +79,7 @@ PreLaunchLabel::PreLaunchLabel(QWidget *parent) : QLabel(parent) { setFont(labelFont); QPalette p(palette()); - p.setColor(QPalette::Foreground, QColor(0, 0, 0)); + p.setColor(QPalette::WindowText, QColor(0, 0, 0)); setPalette(p); show(); }; @@ -97,7 +97,7 @@ PreLaunchInput::PreLaunchInput(QWidget *parent, bool password) : QLineEdit(paren setFont(logFont); QPalette p(palette()); - p.setColor(QPalette::Foreground, QColor(0, 0, 0)); + p.setColor(QPalette::WindowText, QColor(0, 0, 0)); setPalette(p); QLineEdit::setTextMargins(0, 0, 0, 0); @@ -115,7 +115,7 @@ PreLaunchLog::PreLaunchLog(QWidget *parent) : QTextEdit(parent) { setFont(logFont); QPalette p(palette()); - p.setColor(QPalette::Foreground, QColor(96, 96, 96)); + p.setColor(QPalette::WindowText, QColor(96, 96, 96)); setPalette(p); setReadOnly(true); @@ -783,7 +783,7 @@ void LastCrashedWindow::updateControls() { } QRect scr(QApplication::primaryScreen()->availableGeometry()); - QSize s(2 * padding + QFontMetrics(_label.font()).width(qsl("Last time Telegram Desktop was not closed properly.")) + padding + _networkSettings.width(), h); + QSize s(2 * padding + QFontMetrics(_label.font()).horizontalAdvance(qsl("Last time Telegram Desktop was not closed properly.")) + padding + _networkSettings.width(), h); if (s == size()) { resizeEvent(0); } else { diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index 3f6f47cb5..ecbe4d8f3 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/invoke_queued.h" #include "base/qthelp_url.h" #include "base/qthelp_regex.h" +#include "base/qt_adapters.h" #include "ui/effects/animations.h" #include "facades.h" #include "app.h" @@ -35,9 +36,6 @@ namespace { constexpr auto kEmptyPidForCommandResponse = 0ULL; -using ErrorSignal = void(QLocalSocket::*)(QLocalSocket::LocalSocketError); -const auto QLocalSocket_error = ErrorSignal(&QLocalSocket::error); - QChar _toHex(ushort v) { v = v & 0x000F; return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v)); @@ -112,7 +110,7 @@ int Sandbox::start() { [=] { socketDisconnected(); }); connect( &_localSocket, - QLocalSocket_error, + base::QLocalSocket_error, [=](QLocalSocket::LocalSocketError error) { socketError(error); }); connect( &_localSocket, diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index af4ceec77..79320c9e1 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "base/bytes.h" #include "base/unixtime.h" +#include "base/qt_adapters.h" #include "storage/localstorage.h" #include "core/application.h" #include "core/changelogs.h" @@ -58,9 +59,6 @@ bool UpdaterIsDisabled = false; std::weak_ptr UpdaterInstance; -using ErrorSignal = void(QNetworkReply::*)(QNetworkReply::NetworkError); -const auto QNetworkReply_error = ErrorSignal(&QNetworkReply::error); - using Progress = UpdateChecker::Progress; using State = UpdateChecker::State; @@ -626,7 +624,7 @@ void HttpChecker::start() { _reply->connect(_reply, &QNetworkReply::finished, [=] { gotResponse(); }); - _reply->connect(_reply, QNetworkReply_error, [=](auto e) { + _reply->connect(_reply, base::QNetworkReply_error, [=](auto e) { gotFailure(e); }); } @@ -665,7 +663,7 @@ void HttpChecker::clearSentRequest() { return; } reply->disconnect(reply, &QNetworkReply::finished, nullptr, nullptr); - reply->disconnect(reply, QNetworkReply_error, nullptr, nullptr); + reply->disconnect(reply, base::QNetworkReply_error, nullptr, nullptr); reply->abort(); reply->deleteLater(); _manager = nullptr; @@ -819,7 +817,7 @@ void HttpLoaderActor::sendRequest() { &HttpLoaderActor::partFinished); connect( _reply.get(), - QNetworkReply_error, + base::QNetworkReply_error, this, &HttpLoaderActor::partFailed); connect( diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 4074bd889..d0cf78fd0 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "base/unixtime.h" +#include "base/qt_adapters.h" namespace Data { namespace { @@ -39,7 +40,7 @@ int OnlinePhraseChangeInSeconds(TimeId online, TimeId now) { return (hours + 1) * 3600 - (now - online); } const auto nowFull = base::unixtime::parse(now); - const auto tomorrow = QDateTime(nowFull.date().addDays(1)); + const auto tomorrow = base::QDateToDateTime(nowFull.date().addDays(1)); return std::max(static_cast(nowFull.secsTo(tomorrow)), 0); } diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index e08da3326..d0d5b895b 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_channel.h" #include "data/data_document.h" +#include "base/qt_adapters.h" #include "ui/image/image.h" #include "ui/text/text_entity.h" @@ -26,7 +27,7 @@ QString SiteNameFromUrl(const QString &url) { if (m.hasMatch()) pretty = pretty.mid(m.capturedLength()); int32 slash = pretty.indexOf('/'); if (slash > 0) pretty = pretty.mid(0, slash); - QStringList components = pretty.split('.', QString::SkipEmptyParts); + QStringList components = pretty.split('.', base::QStringSkipEmptyParts); if (components.size() >= 2) { components = components.mid(components.size() - 2); return components.at(0).at(0).toUpper() + components.at(0).mid(1) + '.' + components.at(1); diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp index c06e8e38c..7fefa6639 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.cpp +++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "boxes/calendar_box.h" #include "base/unixtime.h" +#include "base/qt_adapters.h" #include "main/main_session.h" #include "styles/style_widgets.h" #include "styles/style_export.h" @@ -478,7 +479,7 @@ void SettingsWidget::editDateLimit( })); }; const auto callback = crl::guard(this, [=](const QDate &date) { - done(base::unixtime::serialize(QDateTime(date))); + done(base::unixtime::serialize(base::QDateToDateTime(date))); if (const auto weak = shared->data()) { weak->closeBox(); } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 8a7f5e784..4215341c9 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "window/window_session_controller.h" #include "facades.h" +#include "base/qt_adapters.h" #include "styles/style_widgets.h" #include "styles/style_chat.h" @@ -109,7 +110,7 @@ HiddenSenderInfo::HiddenSenderInfo(const QString &name) , colorPeerId(Data::FakePeerIdForJustName(name)) , userpic(Data::PeerUserpicColor(colorPeerId), name) { nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions()); - const auto parts = name.trimmed().split(' ', QString::SkipEmptyParts); + const auto parts = name.trimmed().split(' ', base::QStringSkipEmptyParts); firstName = parts[0]; for (const auto &part : parts.mid(1)) { if (!lastName.isEmpty()) { diff --git a/Telegram/SourceFiles/media/clip/media_clip_reader.h b/Telegram/SourceFiles/media/clip/media_clip_reader.h index 9a86e68eb..b4ea5a3e9 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_reader.h +++ b/Telegram/SourceFiles/media/clip/media_clip_reader.h @@ -226,7 +226,7 @@ public: ~Manager(); int loadLevel() const { - return _loadLevel.load(); + return _loadLevel; } void append(Reader *reader, const Core::FileLocation &location, const QByteArray &data); void start(Reader *reader); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index e3ce5df7b..47c119cd4 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -2968,13 +2968,11 @@ void OverlayWidget::validatePhotoCurrentImage() { void OverlayWidget::paintEvent(QPaintEvent *e) { const auto r = e->rect(); - const auto ®ion = e->region(); - const auto rects = region.rects(); - + const auto region = e->region(); const auto contentShown = _photo || documentContentShown(); - const auto bgRects = contentShown - ? (region - contentRect()).rects() - : rects; + const auto bgRegion = contentShown + ? (region - contentRect()) + : region; auto ms = crl::now(); @@ -2988,7 +2986,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) { const auto m = p.compositionMode(); p.setCompositionMode(QPainter::CompositionMode_Source); const auto bgColor = _fullScreenVideo ? st::mediaviewVideoBg : st::mediaviewBg; - for (const auto &rect : bgRects) { + for (const auto rect : bgRegion) { p.fillRect(rect, bgColor); } p.setCompositionMode(m); @@ -3084,7 +3082,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) { auto o = overLevel(OverLeftNav); if (o > 0) { p.setOpacity(o * co); - for (const auto &rect : rects) { + for (const auto &rect : region) { const auto fill = _leftNav.intersected(rect); if (!fill.isEmpty()) p.fillRect(fill, st::mediaviewControlBg); } @@ -3100,7 +3098,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) { auto o = overLevel(OverRightNav); if (o > 0) { p.setOpacity(o * co); - for (const auto &rect : rects) { + for (const auto &rect : region) { const auto fill = _rightNav.intersected(rect); if (!fill.isEmpty()) p.fillRect(fill, st::mediaviewControlBg); } @@ -3116,7 +3114,7 @@ void OverlayWidget::paintEvent(QPaintEvent *e) { auto o = overLevel(OverClose); if (o > 0) { p.setOpacity(o * co); - for (const auto &rect : rects) { + for (const auto &rect : region) { const auto fill = _closeNav.intersected(rect); if (!fill.isEmpty()) p.fillRect(fill, st::mediaviewControlBg); } @@ -4151,7 +4149,7 @@ bool OverlayWidget::eventHook(QEvent *e) { } else { _accumScroll += ev->angleDelta(); if (ev->phase() == Qt::ScrollEnd) { - if (ev->orientation() == Qt::Horizontal) { + if (ev->angleDelta().x() != 0) { if (_accumScroll.x() * _accumScroll.x() > _accumScroll.y() * _accumScroll.y() && _accumScroll.x() != 0) { moveToNext(_accumScroll.x() > 0 ? -1 : 1); } diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index 6c2dadb3e..3059d8993 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -334,7 +334,7 @@ Qt::Edges RectPartToQtEdges(RectPart rectPart) { return Qt::BottomEdge; } - return 0; + return Qt::Edges(); } } // namespace @@ -591,7 +591,7 @@ void PipPanel::paintEvent(QPaintEvent *e) { QPainter p(this); if (_useTransparency) { - Ui::Platform::StartTranslucentPaint(p, e->region().rects()); + Ui::Platform::StartTranslucentPaint(p, e->region()); } auto request = FrameRequest(); diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.cpp index 53680461a..aec382f3d 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/details/mtproto_tcp_socket.h" #include "base/invoke_queued.h" +#include "base/qt_adapters.h" namespace MTP::details { @@ -33,12 +34,9 @@ TcpSocket::TcpSocket(not_null thread, const QNetworkProxy &proxy) &_socket, &QTcpSocket::readyRead, wrap([=] { _readyRead.fire({}); })); - - using ErrorSignal = void(QTcpSocket::*)(QAbstractSocket::SocketError); - const auto QTcpSocket_error = ErrorSignal(&QAbstractSocket::error); connect( &_socket, - QTcpSocket_error, + base::QTcpSocket_error, wrap([=](Error e) { handleError(e); })); } diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp index b8cb2bb12..34030993a 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/bytes.h" #include "base/invoke_queued.h" #include "base/unixtime.h" +#include "base/qt_adapters.h" #include #include @@ -470,12 +471,9 @@ TlsSocket::TlsSocket( &_socket, &QTcpSocket::readyRead, wrap([=] { plainReadyRead(); })); - - using ErrorSignal = void(QTcpSocket::*)(QAbstractSocket::SocketError); - const auto QTcpSocket_error = ErrorSignal(&QAbstractSocket::error); connect( &_socket, - QTcpSocket_error, + base::QTcpSocket_error, wrap([=](Error e) { handleError(e); })); } diff --git a/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp b/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp index bb9211567..f232c2eb6 100644 --- a/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp +++ b/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/facade.h" #include "mtproto/connection_tcp.h" #include "storage/serialize_common.h" +#include "base/qt_adapters.h" #include #include @@ -779,7 +780,7 @@ bool DcOptions::loadFromFile(const QString &path) { stream.setCodec("UTF-8"); while (!stream.atEnd()) { auto line = stream.readLine(); - auto components = line.split(QRegularExpression(R"(\s)"), QString::SkipEmptyParts); + auto components = line.split(QRegularExpression(R"(\s)"), base::QStringSkipEmptyParts); if (components.isEmpty() || components[0].startsWith('#')) { continue; } diff --git a/Telegram/SourceFiles/mtproto/special_config_request.cpp b/Telegram/SourceFiles/mtproto/special_config_request.cpp index 0d99e881c..f93c8539b 100644 --- a/Telegram/SourceFiles/mtproto/special_config_request.cpp +++ b/Telegram/SourceFiles/mtproto/special_config_request.cpp @@ -79,9 +79,9 @@ bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) { } QByteArray ConcatenateDnsTxtFields(const std::vector &response) { - auto entries = QMap(); + auto entries = QMultiMap(); for (const auto &entry : response) { - entries.insertMulti(INT_MAX - entry.data.size(), entry.data); + entries.insert(INT_MAX - entry.data.size(), entry.data); } return QStringList(entries.values()).join(QString()).toLatin1(); } diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 57d25bdf4..470d68f09 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/view/history_view_cursor_state.h" #include "base/unixtime.h" +#include "base/qt_adapters.h" #include "ui/effects/round_checkbox.h" #include "ui/image/image.h" #include "ui/text/format_values.h" @@ -1518,7 +1519,7 @@ Link::Link( domain = parts.at(2); } - parts = domain.split('@').back().split('.', QString::SkipEmptyParts); + parts = domain.split('@').back().split('.', base::QStringSkipEmptyParts); if (parts.size() > 1) { _letter = parts.at(parts.size() - 2).at(0).toUpper(); if (_title.isEmpty()) { diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 552ed888a..41b68d79c 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/specific_linux.h" #include "storage/localstorage.h" +#include "base/qt_adapters.h" #include @@ -387,7 +388,7 @@ QStringList cleanFilterList(const QString &filter) { int i = regexp.indexIn(f); if (i >= 0) f = regexp.cap(2); - return f.split(QLatin1Char(' '), QString::SkipEmptyParts); + return f.split(QLatin1Char(' '), base::QStringSkipEmptyParts); } } // namespace @@ -585,7 +586,6 @@ GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileD else return GTK_FILE_CHOOSER_ACTION_SAVE; case QFileDialog::Directory: - case QFileDialog::DirectoryOnly: default: if (acceptMode == QFileDialog::AcceptOpen) return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h index 13fe85301..4b57d656e 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h @@ -141,7 +141,7 @@ private: void hideHelper(); // Options - QFileDialog::Options _options = { 0 }; + QFileDialog::Options _options; QString _windowTitle = "Choose file"; QString _initialDirectory; QStringList _initialFiles; diff --git a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp index 61f95452a..9a74925e0 100644 --- a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/specific_linux.h" +#include "base/qt_adapters.h" namespace Platform { namespace DesktopEnvironment { @@ -22,7 +23,7 @@ QString GetEnv(const char *name) { Type Compute() { auto xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP").toLower(); - auto list = xdgCurrentDesktop.split(':', QString::SkipEmptyParts); + auto list = xdgCurrentDesktop.split(':', base::QStringSkipEmptyParts); auto desktopSession = GetEnv("DESKTOP_SESSION").toLower(); auto slash = desktopSession.lastIndexOf('/'); auto kdeSession = GetEnv("KDE_SESSION_VERSION"); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index c1daf15d0..3a833dd78 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/linux_libs.h" #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_xcb_utilities_linux.h" +#include "base/qt_adapters.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" @@ -248,14 +249,14 @@ bool GenerateDesktopFile( QRegularExpression::MultilineOption), qsl("TryExec=") + QFile::encodeName(cExeDir() + cExeName()) - .replace('\\', qsl("\\\\"))); + .replace('\\', "\\\\")); fileText = fileText.replace( QRegularExpression( qsl("^Exec=.*$"), QRegularExpression::MultilineOption), qsl("Exec=") + EscapeShell(QFile::encodeName(cExeDir() + cExeName())) - .replace('\\', qsl("\\\\")) + .replace('\\', "\\\\") + (args.isEmpty() ? QString() : ' ' + args)); } else { fileText = fileText.replace( @@ -1025,7 +1026,7 @@ namespace Platform { void start() { PlatformThemes = QString::fromUtf8(qgetenv("QT_QPA_PLATFORMTHEME")) - .split(':', QString::SkipEmptyParts); + .split(':', base::QStringSkipEmptyParts); LOG(("Launcher filename: %1").arg(GetLauncherFilename())); @@ -1249,27 +1250,35 @@ void OpenSystemSettingsForPermission(PermissionType type) { bool OpenSystemSettings(SystemSettingsType type) { if (type == SystemSettingsType::Audio) { - auto options = std::vector(); - const auto add = [&](const char *option) { - options.emplace_back(option); + struct Command { + QString command; + QStringList arguments; + }; + auto options = std::vector(); + const auto add = [&](const char *option, const char *arg = nullptr) { + auto command = Command{ .command = option }; + if (arg) { + command.arguments.push_back(arg); + } + options.push_back(std::move(command)); }; if (DesktopEnvironment::IsUnity()) { - add("unity-control-center sound"); + add("unity-control-center", "sound"); } else if (DesktopEnvironment::IsKDE()) { - add("kcmshell5 kcm_pulseaudio"); - add("kcmshell4 phonon"); + add("kcmshell5", "kcm_pulseaudio"); + add("kcmshell4", "phonon"); } else if (DesktopEnvironment::IsGnome()) { - add("gnome-control-center sound"); + add("gnome-control-center", "sound"); } else if (DesktopEnvironment::IsCinnamon()) { - add("cinnamon-settings sound"); + add("cinnamon-settings", "sound"); } else if (DesktopEnvironment::IsMATE()) { add("mate-volume-control"); } add("pavucontrol-qt"); add("pavucontrol"); add("alsamixergui"); - return ranges::any_of(options, [](const QString &command) { - return QProcess::startDetached(command); + return ranges::any_of(options, [](const Command &command) { + return QProcess::startDetached(command.command, command.arguments); }); } return true; diff --git a/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm b/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm index 59f7f7329..3d678abe4 100644 --- a/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm +++ b/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm @@ -9,10 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/mac/base_utilities_mac.h" #include "lang/lang_keys.h" +#include "base/qt_adapters.h" #include "styles/style_window.h" #include -#include +#include #include #include @@ -398,7 +399,11 @@ bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) { NSString *file = Q2NSString(filepath); @try { OpenFileWithInterface *menu = [[[OpenFileWithInterface alloc] init:file] autorelease]; - auto r = QApplication::desktop()->screenGeometry(menuPosition); + const auto screen = base::QScreenNearestTo(menuPosition); + if (!screen) { + return false; + } + const auto r = screen->availableGeometry(); auto x = menuPosition.x(); auto y = r.y() + r.height() - menuPosition.y(); return !![menu popupAtX:x andY:y]; diff --git a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp index b515f8e65..6db8cffca 100644 --- a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp +++ b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp @@ -358,11 +358,7 @@ bool Get( dialog.setAcceptMode(QFileDialog::AcceptOpen); } else if (type == Type::ReadFolder) { // save dir dialog.setAcceptMode(QFileDialog::AcceptOpen); - - // We use "obsolete" value ::DirectoryOnly instead of ::Directory + ::ShowDirsOnly - // because in Windows XP native dialog this one works, while the "preferred" one - // shows a native file choose dialog where you can't choose a directory, only open one. - dialog.setFileMode(QFileDialog::DirectoryOnly); + dialog.setFileMode(QFileDialog::Directory); dialog.setOption(QFileDialog::ShowDirsOnly); } else { // save file dialog.setFileMode(QFileDialog::AnyFile); diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index 10d625363..63c43fe43 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -199,7 +199,7 @@ void MainWindow::psRefreshTaskbarIcon() { refresher->setWindowFlags(static_cast(Qt::Tool) | Qt::FramelessWindowHint); refresher->setGeometry(x() + 1, y() + 1, 1, 1); auto palette = refresher->palette(); - palette.setColor(QPalette::Background, (isActiveWindow() ? st::titleBgActive : st::titleBg)->c); + palette.setColor(QPalette::Window, (isActiveWindow() ? st::titleBgActive : st::titleBg)->c); refresher->setPalette(palette); refresher->show(); refresher->activateWindow(); diff --git a/Telegram/SourceFiles/profile/profile_block_peer_list.h b/Telegram/SourceFiles/profile/profile_block_peer_list.h index 7ead3ab7a..69231da3b 100644 --- a/Telegram/SourceFiles/profile/profile_block_peer_list.h +++ b/Telegram/SourceFiles/profile/profile_block_peer_list.h @@ -72,7 +72,7 @@ public: } template void sortItems(Predicate predicate) { - qSort(_items.begin(), _items.end(), std::move(predicate)); + std::sort(_items.begin(), _items.end(), std::move(predicate)); } void setPreloadMoreCallback(Fn callback) { diff --git a/Telegram/SourceFiles/storage/file_download_web.cpp b/Telegram/SourceFiles/storage/file_download_web.cpp index 73722eb50..3a963dc25 100644 --- a/Telegram/SourceFiles/storage/file_download_web.cpp +++ b/Telegram/SourceFiles/storage/file_download_web.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/file_download_web.h" #include "storage/cache/storage_cache_types.h" +#include "base/qt_adapters.h" #include @@ -19,9 +20,6 @@ constexpr auto kResetDownloadPrioritiesTimeout = crl::time(200); std::weak_ptr GlobalLoadManager; -using ErrorSignal = void(QNetworkReply::*)(QNetworkReply::NetworkError); -const auto QNetworkReply_error = ErrorSignal(&QNetworkReply::error); - [[nodiscard]] std::shared_ptr GetManager() { auto result = GlobalLoadManager.lock(); if (!result) { @@ -270,7 +268,7 @@ not_null WebLoadManager::send(int id, const QString &url) { failed(id, result, error); }; connect(result, &QNetworkReply::downloadProgress, handleProgress); - connect(result, QNetworkReply_error, handleError); + connect(result, base::QNetworkReply_error, handleError); return result; } diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index 5222f63ff..e6b96c52c 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "base/unixtime.h" #include "base/call_delayed.h" +#include "base/qt_adapters.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "apiwrap.h" @@ -484,7 +485,7 @@ void Autocomplete::submitValue(const QString &value) { const auto contact = value.mid( prefix.size(), (line > 0) ? (line - prefix.size()) : -1); - const auto parts = contact.split(' ', QString::SkipEmptyParts); + const auto parts = contact.split(' ', base::QStringSkipEmptyParts); if (parts.size() > 1) { const auto phone = parts[0]; const auto firstName = parts[1]; diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index b1bed7429..99ae478d8 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/multi_select.h" #include "ui/effects/ripple_animation.h" #include "data/data_countries.h" +#include "base/qt_adapters.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_intro.h" @@ -248,7 +249,7 @@ CountrySelectBox::Inner::Inner(QWidget *parent, Type type) : QString()); const auto namesList = std::move(full).toLower().split( QRegularExpression("[\\s\\-]"), - QString::SkipEmptyParts); + base::QStringSkipEmptyParts); auto &names = _namesList.emplace_back(); names.reserve(namesList.size()); for (const auto &name : namesList) { diff --git a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp index dfd0478fd..d2caed510 100644 --- a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp +++ b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp @@ -358,7 +358,6 @@ void SeparatePanel::initGeometry(QSize size) { st::lineWidth, st::lineWidth); setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); - const auto screen = QApplication::desktop()->screenGeometry(center); const auto rect = [&] { const QRect initRect(QPoint(), size); return initRect.translated(center - initRect.center()).marginsAdded(_padding); diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp index 573cac4e5..59cfb29ec 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor_block.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/edit_color_box.h" #include "lang/lang_keys.h" #include "base/call_delayed.h" +#include "base/qt_adapters.h" namespace Window { namespace Theme { @@ -154,7 +155,7 @@ void EditorBlock::Row::fillSearchIndex() { _searchWords.clear(); _searchStartChars.clear(); auto toIndex = _name + ' ' + _copyOf + ' ' + TextUtilities::RemoveAccents(_description.toString()) + ' ' + _valueString; - auto words = toIndex.toLower().split(SearchSplitter, QString::SkipEmptyParts); + auto words = toIndex.toLower().split(SearchSplitter, base::QStringSkipEmptyParts); for_const (auto &word, words) { _searchWords.insert(word); _searchStartChars.insert(word[0]); diff --git a/Telegram/SourceFiles/window/window_title_qt.cpp b/Telegram/SourceFiles/window/window_title_qt.cpp index 71cb2118a..fc210e1aa 100644 --- a/Telegram/SourceFiles/window/window_title_qt.cpp +++ b/Telegram/SourceFiles/window/window_title_qt.cpp @@ -393,7 +393,7 @@ Qt::Edges TitleWidgetQt::edgesFromPos(const QPoint &pos) { >= (window()->height() - getResizeArea(Qt::BottomEdge))) { return Qt::BottomEdge; } else { - return 0; + return Qt::Edges(); } } diff --git a/Telegram/ThirdParty/fcitx-qt5 b/Telegram/ThirdParty/fcitx-qt5 index 4abe66549..f95f76d63 160000 --- a/Telegram/ThirdParty/fcitx-qt5 +++ b/Telegram/ThirdParty/fcitx-qt5 @@ -1 +1 @@ -Subproject commit 4abe66549e13b33fd4baa84858d932e0178ce8c0 +Subproject commit f95f76d637990f66f056eb099d46e3b5e6e7366f diff --git a/Telegram/build/build.sh b/Telegram/build/build.sh index 8e8cd5c05..5d9b64514 100755 --- a/Telegram/build/build.sh +++ b/Telegram/build/build.sh @@ -134,73 +134,13 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then Error "Backup folder not found!" fi - ./configure.sh + ./build/docker/centos_env/run.sh /usr/src/tdesktop/Telegram/build/docker/build.sh - cd $ProjectPath - cmake --build . --config Release --target Telegram -- -j8 - cd $ReleasePath - - echo "$BinaryName build complete!" - - if [ ! -f "$ReleasePath/$BinaryName" ]; then - Error "$BinaryName not found!" - fi - - # BadCount=`objdump -T $ReleasePath/$BinaryName | grep GLIBC_2\.1[6-9] | wc -l` - # if [ "$BadCount" != "0" ]; then - # Error "Bad GLIBC usages found: $BadCount" - # fi - - # BadCount=`objdump -T $ReleasePath/$BinaryName | grep GLIBC_2\.2[0-9] | wc -l` - # if [ "$BadCount" != "0" ]; then - # Error "Bad GLIBC usages found: $BadCount" - # fi - - BadCount=`objdump -T $ReleasePath/$BinaryName | grep GCC_4\.[3-9] | wc -l` - if [ "$BadCount" != "0" ]; then - Error "Bad GCC usages found: $BadCount" - fi - - BadCount=`objdump -T $ReleasePath/$BinaryName | grep GCC_[5-9]\. | wc -l` - if [ "$BadCount" != "0" ]; then - Error "Bad GCC usages found: $BadCount" - fi - - if [ ! -f "$ReleasePath/Updater" ]; then - Error "Updater not found!" - fi - - BadCount=`objdump -T $ReleasePath/Updater | grep GLIBC_2\.1[6-9] | wc -l` - if [ "$BadCount" != "0" ]; then - Error "Bad GLIBC usages found: $BadCount" - fi - - BadCount=`objdump -T $ReleasePath/Updater | grep GLIBC_2\.2[0-9] | wc -l` - if [ "$BadCount" != "0" ]; then - Error "Bad GLIBC usages found: $BadCount" - fi - - BadCount=`objdump -T $ReleasePath/Updater | grep GCC_4\.[3-9] | wc -l` - if [ "$BadCount" != "0" ]; then - Error "Bad GCC usages found: $BadCount" - fi - - BadCount=`objdump -T $ReleasePath/Updater | grep GCC_[5-9]\. | wc -l` - if [ "$BadCount" != "0" ]; then - Error "Bad GCC usages found: $BadCount" - fi - - echo "Dumping debug symbols.." - "$HomePath/../../Libraries/breakpad/out/Default/dump_syms" "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym" - echo "Done!" - - echo "Stripping the executable.." - strip -s "$ReleasePath/$BinaryName" - echo "Done!" - - echo "Removing RPATH.." - chrpath -d "$ReleasePath/$BinaryName" - echo "Done!" + echo "Copying from docker result folder." + cp "$ReleasePath/root/$BinaryName" "$ReleasePath/$BinaryName" + cp "$ReleasePath/root/$BinaryName.sym" "$ReleasePath/$BinaryName.sym" + cp "$ReleasePath/root/Updater" "$ReleasePath/Updater" + cp "$ReleasePath/root/Packer" "$ReleasePath/Packer" echo "Preparing version $AppVersionStrFull, executing Packer.." cd "$ReleasePath" diff --git a/Telegram/build/docker/build.sh b/Telegram/build/docker/build.sh index 4745a07bd..c9415e2f8 100755 --- a/Telegram/build/docker/build.sh +++ b/Telegram/build/docker/build.sh @@ -1,10 +1,25 @@ #!/bin/bash +set -e +FullExecPath=$PWD +pushd `dirname $0` > /dev/null +FullScriptPath=`pwd` +popd > /dev/null + +if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then + echo "" + echo "This script is for building the production version of Telegram Desktop." + echo "" + echo "For building custom versions please visit the build instructions page at:" + echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" + exit +fi + Run () { scl enable devtoolset-8 -- "$@" } -HomePath=/usr/src/tdesktop/Telegram +HomePath="$FullScriptPath/../.." cd $HomePath ProjectPath="$HomePath/../out" diff --git a/Telegram/codegen b/Telegram/codegen index 8b4686f24..127968de8 160000 --- a/Telegram/codegen +++ b/Telegram/codegen @@ -1 +1 @@ -Subproject commit 8b4686f24d80f1f8d6a4ad0d6a55bf1bb701f35a +Subproject commit 127968de8129e8ccfa6ac50721c70415a5a087c3 diff --git a/Telegram/lib_base b/Telegram/lib_base index ffe7a4681..03b7b4cd6 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit ffe7a4681ab4b11353c62c36d010db96f0f90fd8 +Subproject commit 03b7b4cd6f26ff53dff311438bf9c3f84dec13aa diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index b83eed168..fb40f379d 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit b83eed16812f4de3c2537cd12722d3dece95021b +Subproject commit fb40f379d82ffa1fc7506e9a8dddcf48847715ae diff --git a/Telegram/lib_qr b/Telegram/lib_qr index 9877397db..92ce41a69 160000 --- a/Telegram/lib_qr +++ b/Telegram/lib_qr @@ -1 +1 @@ -Subproject commit 9877397dbf97b7198d539a3994bf0e9619cf653c +Subproject commit 92ce41a690a463eb462089a4eb1e51e019308018 diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index 053e44a10..8aede3acc 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit 053e44a10107de4a0b7e9ad8826150c577db2a2b +Subproject commit 8aede3acc9d386484d2774316e9d1a3d0c265dd5 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 9c9a4bc4d..d4c99701b 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 9c9a4bc4d85d7d771e6ff1084fe2ffe2cc883941 +Subproject commit d4c99701b5210a2db83b1c0f13da1a62f48dfb80 diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index d7c4f1f0b..52d52cad4 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit d7c4f1f0b902158f065ea9fb6b7ec80fa0ceac45 +Subproject commit 52d52cad4e554dac1907224372d51fd40b9da92f diff --git a/cmake b/cmake index 94025be39..3a2d8a252 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 94025be392ab7e58115ac376cbf449cdca10de22 +Subproject commit 3a2d8a252d7e84547b532605aaa557a8c70d6d0f From d9df82642d2ef25632a5af7673e01493af7798ed Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 5 Nov 2020 16:41:59 +0300 Subject: [PATCH 011/370] Patch FFmpeg asm objects for Xcode 12 linking. --- docs/building-xcode.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/building-xcode.md b/docs/building-xcode.md index f56d70158..949580ee8 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -29,7 +29,7 @@ Go to ***BuildPath*** and run git clone https://github.com/desktop-app/patches.git cd patches - git checkout a77e4d5 + git checkout cdca495 cd ../ git clone https://chromium.googlesource.com/external/gyp git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git @@ -39,14 +39,26 @@ Go to ***BuildPath*** and run git apply ../patches/gyp.diff ./setup.py build sudo ./setup.py install - cd ../.. + cd .. + git clone -b macos_padding https://github.com/desktop-app/yasm.git + cd yasm + ./autogen.sh + make $MAKE_THREADS_CNT + cd .. + + git clone https://github.com/desktop-app/macho_edit.git + cd macho_edit + xcodebuild build -configuration Release -project macho_edit.xcodeproj -target macho_edit + cd .. + + cd .. mkdir -p Libraries/macos cd Libraries/macos git clone https://github.com/desktop-app/patches.git cd patches - git checkout a77e4d5 + git checkout cdca495 cd .. git clone https://git.tukaani.org/xz.git @@ -119,12 +131,15 @@ Go to ***BuildPath*** and run CFLAGS=`freetype-config --cflags` LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig + cp ../patches/macos_yasm_wrap.sh ./ ./configure --prefix=/usr/local/macos \ --extra-cflags="$MIN_VER $UNGUARDED" \ --extra-cxxflags="$MIN_VER $UNGUARDED" \ --extra-ldflags="$MIN_VER" \ - --enable-protocol=file --enable-libopus \ + --x86asmexe=`pwd`/macos_yasm_wrap.sh \ + --enable-protocol=file \ + --enable-libopus \ --disable-programs \ --disable-doc \ --disable-network \ From 951bb22c3822d422bb7dcf76758dc86021828a4c Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 5 Nov 2020 20:18:11 +0300 Subject: [PATCH 012/370] Update docs/docker to use FFmpeg 4.2 / OpenAL 1.20.1. --- Telegram/build/docker/centos_env/Dockerfile | 4 ++-- docs/building-cmake.md | 2 ++ docs/building-msvc.md | 4 ++-- docs/building-xcode.md | 6 +++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index dcf95e11b..cbeaab8f3 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -250,7 +250,7 @@ COPY --from=opus ${LibrariesPath}/opus-cache / COPY --from=libva ${LibrariesPath}/libva-cache / COPY --from=libvdpau ${LibrariesPath}/libvdpau-cache / -RUN git clone -b release/3.4 --depth=1 $GIT/FFmpeg/FFmpeg.git ffmpeg +RUN git clone -b release/4.2 --depth=1 $GIT/FFmpeg/FFmpeg.git ffmpeg WORKDIR ffmpeg RUN ./configure \ @@ -359,7 +359,7 @@ RUN make -j$(nproc) RUN make DESTDIR="$LibrariesPath/ffmpeg-cache" install FROM builder AS openal -RUN git clone -b openal-soft-1.20.1 --depth=1 $GIT/kcat/openal-soft.git +RUN git clone -b fix_mono --depth=1 $GIT/telegramdesktop/openal-soft.git WORKDIR openal-soft RUN cmake3 -B build . \ diff --git a/docs/building-cmake.md b/docs/building-cmake.md index c8337db9a..6f3dccceb 100644 --- a/docs/building-cmake.md +++ b/docs/building-cmake.md @@ -1,5 +1,7 @@ ## Build instructions for CMake under Ubuntu 14.04 +**NB** These are outdated. + ### Prepare folder Choose an empty folder for the future build, for example **/home/user/TBuild**. It will be named ***BuildPath*** in the rest of this document. diff --git a/docs/building-msvc.md b/docs/building-msvc.md index 57d5b856d..92042d06b 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -152,7 +152,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg - git checkout release/3.4 + git checkout release/4.2 set CHERE_INVOKING=enabled_from_arguments set MSYS2_PATH_TYPE=inherit @@ -172,7 +172,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** cd .. configure ^ - -prefix "%LibrariesPath%\Qt-Qt-5.15.1" ^ + -prefix "%LibrariesPath%\Qt-5.15.1" ^ -debug-and-release ^ -force-debug-info ^ -opensource ^ diff --git a/docs/building-xcode.md b/docs/building-xcode.md index 949580ee8..d8c19f8c7 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -127,7 +127,7 @@ Go to ***BuildPath*** and run git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg - git checkout release/3.4 + git checkout release/4.2 CFLAGS=`freetype-config --cflags` LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig @@ -240,9 +240,9 @@ Go to ***BuildPath*** and run sudo make install cd .. - git clone git://repo.or.cz/openal-soft.git + git clone https://github.com/telegramdesktop/openal-soft.git cd openal-soft - git checkout v1.19 + git checkout fix_mono cd build CFLAGS=$UNGUARDED CPPFLAGS=$UNGUARDED cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr/local/macos -D ALSOFT_EXAMPLES=OFF -D LIBTYPE:STRING=STATIC -D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.12 .. make $MAKE_THREADS_CNT From 547251f67cc42015d16475f5215ce2bb12364ce8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 Feb 2020 16:39:47 +0400 Subject: [PATCH 013/370] Fix deprecation warnings when building with FFmpeg 4.2. --- Telegram/SourceFiles/core/utils.cpp | 7 ------- Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp | 2 +- Telegram/SourceFiles/media/audio/media_audio_capture.cpp | 7 ++++--- .../SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp | 2 +- Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp | 2 +- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/core/utils.cpp b/Telegram/SourceFiles/core/utils.cpp index d989ff25e..3a924059f 100644 --- a/Telegram/SourceFiles/core/utils.cpp +++ b/Telegram/SourceFiles/core/utils.cpp @@ -170,17 +170,10 @@ namespace ThirdParty { LOG(("MTP Error: dynlock_create callback is set without dynlock_lock callback!")); } - av_register_all(); - avcodec_register_all(); - - av_lockmgr_register(_ffmpegLockManager); - _sslInited = true; } void finish() { - av_lockmgr_register(nullptr); - CRYPTO_cleanup_all_ex_data(); #ifndef LIBRESSL_VERSION_NUMBER FIPS_mode_set(0); diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp index 5221c92da..ccaec6630 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp @@ -174,7 +174,7 @@ CodecPointer MakeCodecPointer(not_null stream) { LogError(qstr("avcodec_parameters_to_context"), error); return {}; } - av_codec_set_pkt_timebase(context, stream->time_base); + context->pkt_timebase = stream->time_base; av_opt_set(context, "threads", "auto", 0); av_opt_set_int(context, "refcounted_frames", 1, 0); diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp index 580a8f8bc..20795f14a 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp @@ -253,8 +253,9 @@ void Instance::Inner::start(Fn updated, Fn error) { d->ioContext = avio_alloc_context(d->ioBuffer, FFmpeg::kAVBlockSize, 1, static_cast(d.get()), &Private::_read_data, &Private::_write_data, &Private::_seek_data); int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - AVOutputFormat *fmt = 0; - while ((fmt = av_oformat_next(fmt))) { + const AVOutputFormat *fmt = nullptr; + void *i = nullptr; + while ((fmt = av_muxer_iterate(&i))) { if (fmt->name == qstr("opus")) { break; } @@ -265,7 +266,7 @@ void Instance::Inner::start(Fn updated, Fn error) { return; } - if ((res = avformat_alloc_output_context2(&d->fmtContext, fmt, 0, 0)) < 0) { + if ((res = avformat_alloc_output_context2(&d->fmtContext, (AVOutputFormat*)fmt, 0, 0)) < 0) { LOG(("Audio Error: Unable to avformat_alloc_output_context2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); fail(); return; diff --git a/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp index 24c8bac2a..7122e65e1 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp @@ -550,7 +550,7 @@ bool FFMpegLoader::openCodecContext() { )); return false; } - av_codec_set_pkt_timebase(_codecContext, stream->time_base); + _codecContext->pkt_timebase = stream->time_base; av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); if ((res = avcodec_open2(_codecContext, codec, 0)) < 0) { diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp index 15afec3ec..5d46bd822 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp @@ -312,7 +312,7 @@ bool FFMpegReaderImplementation::start(Mode mode, crl::time &positionMs) { LOG(("Gif Error: Unable to avcodec_parameters_to_context %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); return false; } - av_codec_set_pkt_timebase(_codecContext, _fmtContext->streams[_streamId]->time_base); + _codecContext->pkt_timebase = _fmtContext->streams[_streamId]->time_base; av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); const auto codec = avcodec_find_decoder(_codecContext->codec_id); From 71de24641131deaeac9be5d0f99812f11f1ff022 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Nov 2020 08:48:05 +0300 Subject: [PATCH 014/370] Disable Linux GLIBC wraps for special builds. --- .../platform/linux/linux_glibc_wraps.c | 30 ----------- .../platform/linux/linux_glibc_wraps_32.c | 50 ------------------- .../platform/linux/linux_glibc_wraps_64.c | 25 ---------- Telegram/build/docker/centos_env/run.sh | 13 ++--- cmake | 2 +- 5 files changed, 5 insertions(+), 115 deletions(-) delete mode 100644 Telegram/SourceFiles/platform/linux/linux_glibc_wraps.c delete mode 100644 Telegram/SourceFiles/platform/linux/linux_glibc_wraps_32.c delete mode 100644 Telegram/SourceFiles/platform/linux/linux_glibc_wraps_64.c diff --git a/Telegram/SourceFiles/platform/linux/linux_glibc_wraps.c b/Telegram/SourceFiles/platform/linux/linux_glibc_wraps.c deleted file mode 100644 index f937dcd6d..000000000 --- a/Telegram/SourceFiles/platform/linux/linux_glibc_wraps.c +++ /dev/null @@ -1,30 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include -#include -#include - -void *__wrap_aligned_alloc(size_t alignment, size_t size) { - void *result = NULL; - return (posix_memalign(&result, alignment, size) == 0) - ? result - : NULL; -} - -int enable_secure_inited = 0; -int enable_secure = 1; - -char *__wrap_secure_getenv(const char *name) { - if (enable_secure_inited == 0) { - enable_secure_inited = 1; - enable_secure = (geteuid() != getuid()) - || (getegid() != getgid()); - } - return enable_secure ? NULL : getenv(name); -} - diff --git a/Telegram/SourceFiles/platform/linux/linux_glibc_wraps_32.c b/Telegram/SourceFiles/platform/linux/linux_glibc_wraps_32.c deleted file mode 100644 index 5ddf94fe7..000000000 --- a/Telegram/SourceFiles/platform/linux/linux_glibc_wraps_32.c +++ /dev/null @@ -1,50 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include -#include - -#if defined(_M_IX86) || defined(__i386__) -#define GETTIME_GLIBC_VERSION "2.2" -#elif defined(_M_ARM) || defined(__arm__) -#define GETTIME_GLIBC_VERSION "2.4" -#else -#error Please add glibc wraps for your architecture -#endif - -int __clock_gettime_glibc_old(clockid_t clk_id, struct timespec *tp); -__asm__(".symver __clock_gettime_glibc_old,clock_gettime@GLIBC_" GETTIME_GLIBC_VERSION); - -int __wrap_clock_gettime(clockid_t clk_id, struct timespec *tp) { - return __clock_gettime_glibc_old(clk_id, tp); -} - -uint64_t __udivmoddi4(uint64_t num, uint64_t den, uint64_t *rem_p); - -int64_t __wrap___divmoddi4(int64_t num, int64_t den, int64_t *rem_p) { - int minus = 0; - int64_t v; - - if (num < 0) { - num = -num; - minus = 1; - } - if (den < 0) { - den = -den; - minus ^= 1; - } - - v = __udivmoddi4(num, den, (uint64_t *)rem_p); - if (minus) { - v = -v; - if (rem_p) - *rem_p = -(*rem_p); - } - - return v; -} - diff --git a/Telegram/SourceFiles/platform/linux/linux_glibc_wraps_64.c b/Telegram/SourceFiles/platform/linux/linux_glibc_wraps_64.c deleted file mode 100644 index f0ec95577..000000000 --- a/Telegram/SourceFiles/platform/linux/linux_glibc_wraps_64.c +++ /dev/null @@ -1,25 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include - -#if defined(_M_X64) || defined(__x86_64__) -#define GETTIME_GLIBC_VERSION "2.2.5" -#elif defined(__aarch64__) -#define GETTIME_GLIBC_VERSION "2.17" -#else -#error Please add glibc wraps for your architecture -#endif - -int __clock_gettime_glibc_old(clockid_t clk_id, struct timespec *tp); -__asm__(".symver __clock_gettime_glibc_old,clock_gettime@GLIBC_" GETTIME_GLIBC_VERSION); - - -int __wrap_clock_gettime(clockid_t clk_id, struct timespec *tp) { - return __clock_gettime_glibc_old(clk_id, tp); -} - diff --git a/Telegram/build/docker/centos_env/run.sh b/Telegram/build/docker/centos_env/run.sh index 2e65751cf..84e211e50 100755 --- a/Telegram/build/docker/centos_env/run.sh +++ b/Telegram/build/docker/centos_env/run.sh @@ -13,14 +13,9 @@ if [ ! -d "$FullScriptPath/../../../../../DesktopPrivate" ]; then exit fi -Error () { - cd $FullExecPath - echo "$1" - exit 1 -} - -if [ "$1" == "" ]; then - Error "Script not found!" +Command="$1" +if [ "$Command" == "" ]; then + Command="scl enable devtoolset-8 -- bash" fi -docker run -it --rm --cpus=8 --memory=10g -v $HOME/Telegram/DesktopPrivate:/usr/src/DesktopPrivate -v $HOME/Telegram/tdesktop:/usr/src/tdesktop tdesktop:centos_env $1 +docker run -it --rm --cpus=8 --memory=10g -v $HOME/Telegram/DesktopPrivate:/usr/src/DesktopPrivate -v $HOME/Telegram/tdesktop:/usr/src/tdesktop tdesktop:centos_env $Command diff --git a/cmake b/cmake index 3a2d8a252..4bf45519f 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 3a2d8a252d7e84547b532605aaa557a8c70d6d0f +Subproject commit 4bf45519f6b39afa85e394c19680971fd92bfac4 From 65ba81f50409d10f2d592a1594477525d436a3df Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Nov 2020 09:46:45 +0300 Subject: [PATCH 015/370] Beta version 2.4.8. - Upgrade Qt to version 5.15.1. - Upgrade FFmpeg to version 4.2. - Upgrade OpenAL to version 1.20.1. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/changelogs.cpp | 4 ++++ Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/build/version | 10 +++++----- Telegram/lib_ui | 2 +- changelog.txt | 6 ++++++ 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index de007958c..b18c7d93a 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="2.4.8.0" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 3f9b139bd..57b01f04d 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 2,4,7,0 - PRODUCTVERSION 2,4,7,0 + FILEVERSION 2,4,8,0 + PRODUCTVERSION 2,4,8,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "2.4.7.0" + VALUE "FileVersion", "2.4.8.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.7.0" + VALUE "ProductVersion", "2.4.8.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index ca046b74e..dc896ff0c 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 2,4,7,0 - PRODUCTVERSION 2,4,7,0 + FILEVERSION 2,4,8,0 + PRODUCTVERSION 2,4,8,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", "2.4.7.0" + VALUE "FileVersion", "2.4.8.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.7.0" + VALUE "ProductVersion", "2.4.8.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index 79cd1afae..aa9457047 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -85,6 +85,10 @@ std::map BetaLogs() { "- Bug fixes and other minor improvements.\n" }, + { + 2004008, + "- Upgrade several third party libraries to latest versions.\n" + }, }; }; diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index bfbc759cc..839219172 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 = 2004007; -constexpr auto AppVersionStr = "2.4.7"; -constexpr auto AppBetaVersion = false; +constexpr auto AppVersion = 2004008; +constexpr auto AppVersionStr = "2.4.8"; +constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 8a3c6d616..64d838527 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 2004007 +AppVersion 2004008 AppVersionStrMajor 2.4 -AppVersionStrSmall 2.4.7 -AppVersionStr 2.4.7 -BetaChannel 0 +AppVersionStrSmall 2.4.8 +AppVersionStr 2.4.8 +BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 2.4.7 +AppVersionOriginal 2.4.8.beta diff --git a/Telegram/lib_ui b/Telegram/lib_ui index d4c99701b..cffa5e11d 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit d4c99701b5210a2db83b1c0f13da1a62f48dfb80 +Subproject commit cffa5e11d8d1f340bd2356c0944a0d3e91e38f2c diff --git a/changelog.txt b/changelog.txt index d6a7c48e8..1affa8cbe 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +2.4.8 beta (06.11.20) + +- Upgrade Qt to version 5.15.1. +- Upgrade FFmpeg to version 4.2. +- Upgrade OpenAL to version 1.20.1. + 2.4.7 (05.11.20) - Fix playback display in albums of music files. From 05f43cabdf5338c65b8ada00918296680c7b4593 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Nov 2020 17:22:14 +0300 Subject: [PATCH 016/370] Beta version 2.4.9. - Fix crash in tray icon removing. (macOS only) --- 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/docker/centos_env/Dockerfile | 2 +- Telegram/build/version | 8 ++++---- changelog.txt | 4 ++++ docs/building-msvc.md | 4 ++-- docs/building-xcode.md | 4 ++-- 9 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index b18c7d93a..0318c74bd 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="2.4.9.0" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 57b01f04d..5cf4fdcb7 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 2,4,8,0 - PRODUCTVERSION 2,4,8,0 + FILEVERSION 2,4,9,0 + PRODUCTVERSION 2,4,9,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "2.4.8.0" + VALUE "FileVersion", "2.4.9.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.8.0" + VALUE "ProductVersion", "2.4.9.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index dc896ff0c..8be2c6186 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 2,4,8,0 - PRODUCTVERSION 2,4,8,0 + FILEVERSION 2,4,9,0 + PRODUCTVERSION 2,4,9,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", "2.4.8.0" + VALUE "FileVersion", "2.4.9.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.8.0" + VALUE "ProductVersion", "2.4.9.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 839219172..4d81de3c5 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 = 2004008; -constexpr auto AppVersionStr = "2.4.8"; +constexpr auto AppVersion = 2004009; +constexpr auto AppVersionStr = "2.4.9"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index cbeaab8f3..90464e42b 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -27,7 +27,7 @@ WORKDIR $LibrariesPath FROM builder AS patches RUN git clone $GIT/desktop-app/patches.git -RUN cd patches && git checkout b00f25d +RUN cd patches && git checkout e052c49 FROM builder AS libffi RUN git clone -b v3.3 --depth=1 $GIT/libffi/libffi.git diff --git a/Telegram/build/version b/Telegram/build/version index 64d838527..ad3eafa56 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 2004008 +AppVersion 2004009 AppVersionStrMajor 2.4 -AppVersionStrSmall 2.4.8 -AppVersionStr 2.4.8 +AppVersionStrSmall 2.4.9 +AppVersionStr 2.4.9 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 2.4.8.beta +AppVersionOriginal 2.4.9.beta diff --git a/changelog.txt b/changelog.txt index 1affa8cbe..f6702ef31 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +2.4.9 beta (06.11.20) + +- Fix crash in tray icon removing. (macOS only) + 2.4.8 beta (06.11.20) - Upgrade Qt to version 5.15.1. diff --git a/docs/building-msvc.md b/docs/building-msvc.md index 92042d06b..0913e0e1d 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -33,7 +33,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** cd ThirdParty git clone https://github.com/desktop-app/patches.git cd patches - git checkout a77e4d5 + git checkout e052c49 cd ../ git clone https://chromium.googlesource.com/external/gyp cd gyp @@ -63,7 +63,7 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** git clone https://github.com/desktop-app/patches.git cd patches - git checkout a77e4d5 + git checkout e052c49 cd .. git clone https://github.com/desktop-app/lzma.git diff --git a/docs/building-xcode.md b/docs/building-xcode.md index d8c19f8c7..48dfc7d93 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -29,7 +29,7 @@ Go to ***BuildPath*** and run git clone https://github.com/desktop-app/patches.git cd patches - git checkout cdca495 + git checkout e052c49 cd ../ git clone https://chromium.googlesource.com/external/gyp git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git @@ -58,7 +58,7 @@ Go to ***BuildPath*** and run git clone https://github.com/desktop-app/patches.git cd patches - git checkout cdca495 + git checkout e052c49 cd .. git clone https://git.tukaani.org/xz.git From d97dcaec62a480274533bec90bdb5f63d5a4d7b2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Nov 2020 20:22:02 +0300 Subject: [PATCH 017/370] Add possibility to build on Windows for x64. --- .../SourceFiles/platform/win/launcher_win.cpp | 4 +- Telegram/ThirdParty/libtgvoip | 2 +- Telegram/configure.py | 8 + Telegram/lib_base | 2 +- cmake | 2 +- docs/building-msvc-x64.md | 244 ++++++++++++++++++ docs/building-msvc.md | 27 +- 7 files changed, 270 insertions(+), 19 deletions(-) create mode 100644 docs/building-msvc-x64.md diff --git a/Telegram/SourceFiles/platform/win/launcher_win.cpp b/Telegram/SourceFiles/platform/win/launcher_win.cpp index 20d530249..44821f1ea 100644 --- a/Telegram/SourceFiles/platform/win/launcher_win.cpp +++ b/Telegram/SourceFiles/platform/win/launcher_win.cpp @@ -129,11 +129,11 @@ bool Launcher::launch( arguments.toStdWString().c_str(), nativeWorkingDir.empty() ? nullptr : nativeWorkingDir.c_str(), SW_SHOWNORMAL); - if (long(result) < 32) { + if (int64(result) < 32) { DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3" ).arg(binaryPath ).arg(cWorkingDir() - ).arg(long(result) + ).arg(int64(result) )); return false; } diff --git a/Telegram/ThirdParty/libtgvoip b/Telegram/ThirdParty/libtgvoip index 7563a96b8..931f6a49c 160000 --- a/Telegram/ThirdParty/libtgvoip +++ b/Telegram/ThirdParty/libtgvoip @@ -1 +1 @@ -Subproject commit 7563a96b8f8e86b7a5fd1ce783388adf29bf4cf9 +Subproject commit 931f6a49c531995d4daf64f8357042a1eeeef43f diff --git a/Telegram/configure.py b/Telegram/configure.py index 61f88e93d..e42616858 100644 --- a/Telegram/configure.py +++ b/Telegram/configure.py @@ -38,6 +38,12 @@ if os.path.isfile(officialTargetFile): for line in f: officialTarget = line.strip() +arch = '' +if officialTarget == 'win' or officialTarget == 'uwp': + arch = 'x86' +elif officialTarget == 'win64' or officialTarget == 'uwp64': + arch = 'x64' + if officialTarget != '': officialApiIdFile = scriptPath + '/../../DesktopPrivate/custom_api_id.h' if not os.path.isfile(officialApiIdFile): @@ -51,6 +57,8 @@ if officialTarget != '': arguments.append('-DTDESKTOP_API_ID=' + apiIdMatch.group(1)) elif apiHashMatch: arguments.append('-DTDESKTOP_API_HASH=' + apiHashMatch.group(1)) + if arch != '': + arguments.append(arch) finish(run_cmake.run(scriptName, arguments)) elif 'linux' in sys.platform: debugCode = run_cmake.run(scriptName, arguments, "Debug") diff --git a/Telegram/lib_base b/Telegram/lib_base index 03b7b4cd6..2d674eff9 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 03b7b4cd6f26ff53dff311438bf9c3f84dec13aa +Subproject commit 2d674eff931c2540147e4f1ac29965e90ab4af22 diff --git a/cmake b/cmake index 4bf45519f..d9e8a608c 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 4bf45519f6b39afa85e394c19680971fd92bfac4 +Subproject commit d9e8a608c21ca175ed118955d83010580fb46e65 diff --git a/docs/building-msvc-x64.md b/docs/building-msvc-x64.md new file mode 100644 index 000000000..91c1fa4fe --- /dev/null +++ b/docs/building-msvc-x64.md @@ -0,0 +1,244 @@ +# Build instructions for Visual Studio 2019 for 64 bit + +- [Prepare folder](#prepare-folder) +- [Install third party software](#install-third-party-software) +- [Clone source code and prepare libraries](#clone-source-code-and-prepare-libraries) +- [Build the project](#build-the-project) +- [Qt Visual Studio Tools](#qt-visual-studio-tools) + +## Prepare folder + +Choose an empty folder for the future build, for example **D:\\TBuild**. It will be named ***BuildPath*** in the rest of this document. Create two folders there, ***BuildPath*\\ThirdParty** and ***BuildPath*\\Libraries**. + +All commands (if not stated otherwise) will be launched from **x64 Native Tools Command Prompt for VS 2019.bat** (should be in **Start Menu > Visual Studio 2019** menu folder). Pay attention not to use any other Command Prompt. + +### Obtain your API credentials + +You will require **api_id** and **api_hash** to access the Telegram API servers. To learn how to obtain them [click here][api_credentials]. + +## Install third party software + +* Download **Strawberry Perl** installer from [http://strawberryperl.com/](http://strawberryperl.com/) and install to ***BuildPath*\\ThirdParty\\Strawberry** +* Download **NASM** installer from [http://www.nasm.us](http://www.nasm.us) and install to ***BuildPath*\\ThirdParty\\NASM** +* Download **Yasm** executable from [http://yasm.tortall.net/Download.html](http://yasm.tortall.net/Download.html), rename to *yasm.exe* and put to ***BuildPath*\\ThirdParty\\yasm** +* Download **MSYS2** installer from [http://www.msys2.org/](http://www.msys2.org/) and install to ***BuildPath*\\ThirdParty\\msys64** +* Download **jom** archive from [http://download.qt.io/official_releases/jom/jom.zip](http://download.qt.io/official_releases/jom/jom.zip) and unpack to ***BuildPath*\\ThirdParty\\jom** +* Download **Python 2.7** installer from [https://www.python.org/downloads/](https://www.python.org/downloads/) and install to ***BuildPath*\\ThirdParty\\Python27** +* Download **CMake** installer from [https://cmake.org/download/](https://cmake.org/download/) and install to ***BuildPath*\\ThirdParty\\cmake** +* Download **Ninja** executable from [https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip](https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip) and unpack to ***BuildPath*\\ThirdParty\\Ninja** +* Download **Git** installer from [https://git-scm.com/download/win](https://git-scm.com/download/win) and install it. + +Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** and run + + cd ThirdParty + git clone https://github.com/desktop-app/patches.git + cd patches + git checkout 9fb66f2 + cd ../ + git clone https://chromium.googlesource.com/external/gyp + cd gyp + git checkout 9f2a7bb1 + git apply ../patches/gyp.diff + cd ..\.. + +Add **GYP** and **Ninja** to your PATH: + +* Open **Control Panel** -> **System** -> **Advanced system settings** +* Press **Environment Variables...** +* Select **Path** +* Press **Edit** +* Add ***BuildPath*\\ThirdParty\\gyp** value +* Add ***BuildPath*\\ThirdParty\\Ninja** value + +## Clone source code and prepare libraries + +Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** and run + + SET PATH=%cd%\ThirdParty\Strawberry\perl\bin;%cd%\ThirdParty\Python27;%cd%\ThirdParty\NASM;%cd%\ThirdParty\jom;%cd%\ThirdParty\cmake\bin;%cd%\ThirdParty\yasm;%PATH% + + git clone --recursive https://github.com/telegramdesktop/tdesktop.git + + if not exist Libraries\win64 mkdir Libraries\win64 + cd Libraries\win64 + + git clone https://github.com/desktop-app/patches.git + cd patches + git checkout 9fb66f2 + cd .. + + git clone https://github.com/desktop-app/lzma.git + cd lzma\C\Util\LzmaLib + msbuild LzmaLib.sln /property:Configuration=Debug /property:Platform="x64" + msbuild LzmaLib.sln /property:Configuration=Release /property:Platform="x64" + cd ..\..\..\.. + + git clone https://github.com/openssl/openssl.git openssl_1_1_1 + cd openssl_1_1_1 + git checkout OpenSSL_1_1_1-stable + perl Configure no-shared no-tests debug-VC-WIN64A + nmake + mkdir out64.dbg + move libcrypto.lib out64.dbg + move libssl.lib out64.dbg + move ossl_static.pdb out64.dbg\ossl_static + nmake clean + move out64.dbg\ossl_static out64.dbg\ossl_static.pdb + perl Configure no-shared no-tests VC-WIN64A + nmake + mkdir out64 + move libcrypto.lib out64 + move libssl.lib out64 + move ossl_static.pdb out64 + cd .. + + git clone https://github.com/desktop-app/zlib.git + cd zlib\contrib\vstudio\vc14 + msbuild zlibstat.vcxproj /property:Configuration=Debug /property:Platform="x64" + msbuild zlibstat.vcxproj /property:Configuration=ReleaseWithoutAsm /property:Platform="x64" + cd ..\..\..\.. + + git clone -b v4.0.1-rc2 https://github.com/mozilla/mozjpeg.git + cd mozjpeg + cmake . ^ + -G "Visual Studio 16 2019" ^ + -A x64 ^ + -DWITH_JPEG8=ON ^ + -DPNG_SUPPORTED=OFF + cmake --build . --config Debug + cmake --build . --config Release + cd .. + + git clone https://github.com/telegramdesktop/openal-soft.git + cd openal-soft + git checkout fix_mono + cd build + cmake .. ^ + -G "Visual Studio 16 2019" ^ + -A x64 ^ + -D LIBTYPE:STRING=STATIC ^ + -D FORCE_STATIC_VCRT=ON + msbuild OpenAL.vcxproj /property:Configuration=Debug /property:Platform="x64" + msbuild OpenAL.vcxproj /property:Configuration=RelWithDebInfo /property:Platform="x64" + cd ..\.. + + git clone https://github.com/google/breakpad + cd breakpad + git checkout a1dbcdcb43 + git apply ../patches/breakpad.diff + cd src + git clone https://github.com/google/googletest testing + cd client\windows + gyp --no-circular-check breakpad_client.gyp --format=ninja + cd ..\.. + ninja -C out/Debug_x64 common crash_generation_client exception_handler + ninja -C out/Release_x64 common crash_generation_client exception_handler + cd tools\windows\dump_syms + gyp dump_syms.gyp + msbuild dump_syms.vcxproj /property:Configuration=Release /property:Platform="x64" + cd ..\..\..\..\.. + + git clone https://github.com/telegramdesktop/opus.git + cd opus + git checkout tdesktop + cd win32\VS2015 + msbuild opus.sln /property:Configuration=Debug /property:Platform="x64" + msbuild opus.sln /property:Configuration=Release /property:Platform="x64" + + cd ..\..\..\..\.. + SET PATH_BACKUP_=%PATH% + SET PATH=%cd%\ThirdParty\msys64\usr\bin;%PATH% + cd Libraries\win64 + + git clone https://github.com/FFmpeg/FFmpeg.git ffmpeg + cd ffmpeg + git checkout release/4.2 + + set CHERE_INVOKING=enabled_from_arguments + set MSYS2_PATH_TYPE=inherit + bash --login ../patches/build_ffmpeg_win.sh + + SET PATH=%PATH_BACKUP_% + cd .. + + SET LibrariesPath=%cd% + git clone git://code.qt.io/qt/qt5.git qt_5_15_1 + cd qt_5_15_1 + perl init-repository --module-subset=qtbase,qtimageformats + git checkout v5.15.1 + git submodule update qtbase qtimageformats + cd qtbase + for /r %i in (..\..\patches\qtbase_5_15_1\*) do git apply %i + cd .. + + configure ^ + -prefix "%LibrariesPath%\Qt-5.15.1" ^ + -debug-and-release ^ + -force-debug-info ^ + -opensource ^ + -confirm-license ^ + -static ^ + -static-runtime ^ + -no-opengl ^ + -openssl-linked ^ + -I "%LibrariesPath%\openssl_1_1_1\include" ^ + OPENSSL_LIBS_DEBUG="%LibrariesPath%\openssl_1_1_1\out64.dbg\libssl.lib %LibrariesPath%\openssl_1_1_1\out64.dbg\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^ + OPENSSL_LIBS_RELEASE="%LibrariesPath%\openssl_1_1_1\out64\libssl.lib %LibrariesPath%\openssl_1_1_1\out64\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^ + -I "%LibrariesPath%\mozjpeg" ^ + LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" ^ + LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib" ^ + -mp ^ + -nomake examples ^ + -nomake tests ^ + -platform win32-msvc + + jom -j8 + jom -j8 install + cd .. + + git clone https://github.com/desktop-app/tg_owt.git + cd tg_owt + mkdir out + cd out + mkdir Debug + cd Debug + cmake -G Ninja ^ + -DCMAKE_BUILD_TYPE=Debug ^ + -DTG_OWT_SPECIAL_TARGET=win64 ^ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ + -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_1_1_1/include ^ + -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ + -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ../.. + ninja + cd .. + mkdir Release + cd Release + cmake -G Ninja ^ + -DCMAKE_BUILD_TYPE=Release ^ + -DTG_OWT_SPECIAL_TARGET=win64 ^ + -DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^ + -DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_1_1_1/include ^ + -DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^ + -DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ../.. + ninja + cd ..\..\.. + +## Build the project + +Go to ***BuildPath*\\tdesktop\\Telegram** and run (using [your **api_id** and **api_hash**](#obtain-your-api-credentials)) + + configure.bat x64 -D TDESKTOP_API_ID=YOUR_API_ID -D TDESKTOP_API_HASH=YOUR_API_HASH -D DESKTOP_APP_USE_PACKAGED=OFF -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF + +* Open ***BuildPath*\\tdesktop\\out\\Telegram.sln** in Visual Studio 2019 +* Select Telegram project and press Build > Build Telegram (Debug and Release configurations) +* The result Telegram.exe will be located in **D:\TBuild\tdesktop\out\Debug** (and **Release**) + +### Qt Visual Studio Tools + +For better debugging you may want to install Qt Visual Studio Tools: + +* Open **Extensions** -> **Manage Extensions** +* Go to **Online** tab +* Search for **Qt** +* Install **Qt Visual Studio Tools** extension + +[api_credentials]: api_credentials.md diff --git a/docs/building-msvc.md b/docs/building-msvc.md index 0913e0e1d..5d12e6f71 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -68,8 +68,8 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** git clone https://github.com/desktop-app/lzma.git cd lzma\C\Util\LzmaLib - msbuild LzmaLib.sln /property:Configuration=Debug - msbuild LzmaLib.sln /property:Configuration=Release + msbuild LzmaLib.sln /property:Configuration=Debug /property:Platform="x86" + msbuild LzmaLib.sln /property:Configuration=Release /property:Platform="x86" cd ..\..\..\.. git clone https://github.com/openssl/openssl.git openssl_1_1_1 @@ -92,19 +92,18 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** cd .. git clone https://github.com/desktop-app/zlib.git - cd zlib - cd contrib\vstudio\vc14 - msbuild zlibstat.vcxproj /property:Configuration=Debug - msbuild zlibstat.vcxproj /property:Configuration=ReleaseWithoutAsm + cd zlib\contrib\vstudio\vc14 + msbuild zlibstat.vcxproj /property:Configuration=Debug /property:Platform="x86" + msbuild zlibstat.vcxproj /property:Configuration=ReleaseWithoutAsm /property:Platform="x86" cd ..\..\..\.. git clone -b v4.0.1-rc2 https://github.com/mozilla/mozjpeg.git cd mozjpeg cmake . ^ - -G "Visual Studio 16 2019" ^ - -A Win32 ^ - -DWITH_JPEG8=ON ^ - -DPNG_SUPPORTED=OFF + -G "Visual Studio 16 2019" ^ + -A Win32 ^ + -DWITH_JPEG8=ON ^ + -DPNG_SUPPORTED=OFF cmake --build . --config Debug cmake --build . --config Release cd .. @@ -114,10 +113,10 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** git checkout fix_mono cd build cmake .. ^ - -G "Visual Studio 16 2019" ^ - -A Win32 ^ - -D LIBTYPE:STRING=STATIC ^ - -D FORCE_STATIC_VCRT=ON + -G "Visual Studio 16 2019" ^ + -A Win32 ^ + -D LIBTYPE:STRING=STATIC ^ + -D FORCE_STATIC_VCRT=ON msbuild OpenAL.vcxproj /property:Configuration=Debug msbuild OpenAL.vcxproj /property:Configuration=RelWithDebInfo cd ..\.. From eee3049fdd0a0e4cdfc5aa8ee5a032e2122eea60 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 12:22:40 +0400 Subject: [PATCH 018/370] Remove definitions of unused psLocalServerPrefix and psInitLogs --- .../SourceFiles/platform/linux/specific_linux.h | 4 ---- Telegram/SourceFiles/platform/mac/specific_mac.h | 4 ---- Telegram/SourceFiles/platform/mac/specific_mac.mm | 14 -------------- Telegram/SourceFiles/platform/win/specific_win.cpp | 10 ---------- Telegram/SourceFiles/platform/win/specific_win.h | 4 ---- 5 files changed, 36 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index 2dd706914..2bf9d9e61 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -50,11 +50,7 @@ inline void psCheckLocalSocket(const QString &serverName) { void psWriteDump(); -QStringList psInitLogs(); -void psClearInitLogs(); - void psActivateProcess(uint64 pid = 0); -QString psLocalServerPrefix(); QString psAppDataPath(); void psAutoStart(bool start, bool silent = false); void psSendToMenu(bool send, bool silent = false); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index 25c3bc933..00b0bb44c 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -78,11 +78,7 @@ inline void psCheckLocalSocket(const QString &serverName) { void psWriteDump(); -QStringList psInitLogs(); -void psClearInitLogs(); - void psActivateProcess(uint64 pid = 0); -QString psLocalServerPrefix(); QString psAppDataPath(); void psAutoStart(bool start, bool silent = false); void psSendToMenu(bool send, bool silent = false); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index d2becc60d..e856a103d 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -38,12 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -QStringList _initLogs; - -}; - -namespace { - QRect _monitorRect; crl::time _monitorLastGot = 0; @@ -65,14 +59,6 @@ void psWriteDump() { #endif // DESKTOP_APP_DISABLE_CRASH_REPORTS } -QStringList psInitLogs() { - return _initLogs; -} - -void psClearInitLogs() { - _initLogs = QStringList(); -} - void psActivateProcess(uint64 pid) { if (!pid) { objc_activateProgram(App::wnd() ? App::wnd()->winId() : 0); diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index ae492a312..fc0b9957f 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -70,8 +70,6 @@ using namespace Platform; namespace { -QStringList _initLogs; - bool themeInited = false; bool finished = true; QMargins simpleMargins, margins; @@ -108,14 +106,6 @@ BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) { } -QStringList psInitLogs() { - return _initLogs; -} - -void psClearInitLogs() { - _initLogs = QStringList(); -} - void psActivateProcess(uint64 pid) { if (pid) { ::EnumWindows((WNDENUMPROC)_ActivateProcess, (LPARAM)&pid); diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index e86df71bb..c5fa006a5 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -69,11 +69,7 @@ inline void psCheckLocalSocket(const QString &) { void psWriteDump(); -QStringList psInitLogs(); -void psClearInitLogs(); - void psActivateProcess(uint64 pid = 0); -QString psLocalServerPrefix(); QString psAppDataPath(); QString psAppDataPathOld(); void psAutoStart(bool start, bool silent = false); From e04598835be5af3ea3699c34a82937dee2d1dff6 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 13:52:19 +0400 Subject: [PATCH 019/370] Move _monitorRect and _monitorLastGot to psDesktopRect method --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 9 ++------- Telegram/SourceFiles/platform/mac/specific_mac.mm | 9 ++------- Telegram/SourceFiles/platform/win/specific_win.cpp | 9 ++------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 3a833dd78..abad7435b 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -947,14 +947,9 @@ Window::ControlsLayout WindowControlsLayout() { } // namespace Platform -namespace { - -QRect _monitorRect; -auto _monitorLastGot = 0LL; - -} // namespace - QRect psDesktopRect() { + static QRect _monitorRect; + static auto _monitorLastGot = 0LL; auto tnow = crl::now(); if (tnow > _monitorLastGot + 1000LL || tnow < _monitorLastGot) { _monitorLastGot = tnow; diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index e856a103d..712e42c93 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -36,14 +36,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -namespace { - -QRect _monitorRect; -crl::time _monitorLastGot = 0; - -} // namespace - QRect psDesktopRect() { + static QRect _monitorRect; + static crl::time _monitorLastGot = 0; auto tnow = crl::now(); if (tnow > _monitorLastGot + 1000 || tnow < _monitorLastGot) { _monitorLastGot = tnow; diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index fc0b9957f..ad7ee8a2c 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -145,14 +145,9 @@ void psDoCleanup() { } } -namespace { - -QRect _monitorRect; -crl::time _monitorLastGot = 0; - -} // namespace - QRect psDesktopRect() { + static QRect _monitorRect; + static crl::time _monitorLastGot = 0; auto tnow = crl::now(); if (tnow > _monitorLastGot + 1000LL || tnow < _monitorLastGot) { _monitorLastGot = tnow; From 3d18d28dc57bf273c6e44c2eaae51372ecb9e63d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 14:26:26 +0400 Subject: [PATCH 020/370] Use kIconName on icon creating --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index abad7435b..b27979c68 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -1139,7 +1139,7 @@ void InstallLauncher(bool force) { if (!QDir(icons).exists()) QDir().mkpath(icons); - const auto icon = icons + qsl("telegram.png"); + const auto icon = icons + kIconName.utf16() + qsl(".png"); auto iconExists = QFile(icon).exists(); if (Local::oldSettingsVersion() < 10021 && iconExists) { // Icon was changed. From f980cade395bbcb5c26d1e4d064b42a4397f4f11 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 14:21:19 +0400 Subject: [PATCH 021/370] Use static QFile methods in linux platform code --- .../platform/linux/specific_linux.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index b27979c68..deb0e3268 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -272,8 +272,8 @@ bool GenerateDesktopFile( if (IsStaticBinary()) { DEBUG_LOG(("App Info: removing old .desktop files")); - QFile(qsl("%1telegram.desktop").arg(targetPath)).remove(); - QFile(qsl("%1telegramdesktop.desktop").arg(targetPath)).remove(); + QFile::remove(qsl("%1telegram.desktop").arg(targetPath)); + QFile::remove(qsl("%1telegramdesktop.desktop").arg(targetPath)); } return true; @@ -986,9 +986,9 @@ QString psAppDataPath() { if (!home.isEmpty()) { auto oldPath = home + qsl(".TelegramDesktop/"); auto oldSettingsBase = oldPath + qsl("tdata/settings"); - if (QFile(oldSettingsBase + '0').exists() - || QFile(oldSettingsBase + '1').exists() - || QFile(oldSettingsBase + 's').exists()) { + if (QFile::exists(oldSettingsBase + '0') + || QFile::exists(oldSettingsBase + '1') + || QFile::exists(oldSettingsBase + 's')) { return oldPath; } } @@ -1140,15 +1140,15 @@ void InstallLauncher(bool force) { if (!QDir(icons).exists()) QDir().mkpath(icons); const auto icon = icons + kIconName.utf16() + qsl(".png"); - auto iconExists = QFile(icon).exists(); + auto iconExists = QFile::exists(icon); if (Local::oldSettingsVersion() < 10021 && iconExists) { // Icon was changed. - if (QFile(icon).remove()) { + if (QFile::remove(icon)) { iconExists = false; } } if (!iconExists) { - if (QFile(qsl(":/gui/art/logo_256.png")).copy(icon)) { + if (QFile::copy(qsl(":/gui/art/logo_256.png"), icon)) { DEBUG_LOG(("App Info: Icon copied to '%1'").arg(icon)); } } From 876c57dcfb9672e1ca8c9b65572792ff378da8e0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 20:45:42 +0400 Subject: [PATCH 022/370] Fix getting FileChooser portal version --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index deb0e3268..6bd4847f1 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -162,11 +162,11 @@ uint FileChooserPortalVersion() { qsl("version") }); - const QDBusReply reply = QDBusConnection::sessionBus().call( + const QDBusReply reply = QDBusConnection::sessionBus().call( message); if (reply.isValid()) { - return reply.value(); + return reply.value().toUInt(); } else { LOG(("Error getting FileChooser portal version: %1") .arg(reply.error().message())); From e0de4dbc5e15a3ac66fc92b19cc9e2ff3212524a Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 7 Nov 2020 06:58:10 +0400 Subject: [PATCH 023/370] Replace new #ifdef Q_OS_LINUX in main_window.cpp added a month ago --- Telegram/SourceFiles/window/main_window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index cef351733..cf0c168b1 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -365,7 +365,7 @@ void MainWindow::refreshTitleWidget() { _titleShadow.destroy(); } -#ifdef Q_OS_LINUX +#if defined Q_OS_UNIX && !defined Q_OS_MAC // setWindowFlag calls setParent(parentWidget(), newFlags), which // always calls hide() explicitly, we have to show() the window back. const auto hidden = isHidden(); @@ -378,7 +378,7 @@ void MainWindow::refreshTitleWidget() { this, [=] { show(); }); } -#endif // Q_OS_LINUX +#endif // Q_OS_UNIX && !Q_OS_MAC } void MainWindow::updateMinimumSize() { From acaf8e4931dbec11ceb087cd8408958022e6556e Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 8 Nov 2020 00:29:58 +0400 Subject: [PATCH 024/370] Use g_filename_to_uri --- Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 41b68d79c..3a74b6207 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -82,7 +82,7 @@ void UnsafeLaunch(const QString &filepath) { const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); if (!g_app_info_launch_default_for_uri( - ("file://" + absolutePath).toUtf8(), + g_filename_to_uri(absolutePath.toUtf8(), nullptr, nullptr), nullptr, nullptr)) { QDesktopServices::openUrl(QUrl::fromLocalFile(filepath)); From 3a45957ceb2f90407070a57ea37832976e0fa356 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 8 Nov 2020 10:47:51 +0400 Subject: [PATCH 025/370] Set parent window ID for portal autostart dialog --- .../SourceFiles/platform/linux/specific_linux.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6bd4847f1..1e7b5dd44 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "core/crash_reports.h" #include "core/update_checker.h" +#include "window/window_controller.h" +#include "core/application.h" #include #include @@ -108,8 +110,19 @@ void PortalAutostart(bool autostart, bool silent = false) { qsl("org.freedesktop.portal.Background"), qsl("RequestBackground")); + const auto parentWindowId = [&] { + if (const auto activeWindow = Core::App().activeWindow()) { + if (!IsWayland()) { + return qsl("x11:%1").arg(QString::number( + activeWindow->widget().get()->windowHandle()->winId(), + 16)); + } + } + return QString(); + }(); + message.setArguments({ - QString(), + parentWindowId, options }); From 91a2ec225ada7d6c45d5e5c22dfef6c07d2eddc0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 8 Nov 2020 01:28:24 +0400 Subject: [PATCH 026/370] Add support for open with on linux --- .../platform/linux/file_utilities_linux.cpp | 96 ++++++++++++++++++- .../platform/linux/file_utilities_linux.h | 4 - .../SourceFiles/platform/linux/linux_libs.cpp | 9 ++ .../SourceFiles/platform/linux/linux_libs.h | 25 +++++ 4 files changed, 129 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 3a74b6207..84fc2b205 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/specific_linux.h" #include "storage/localstorage.h" #include "base/qt_adapters.h" +#include "window/window_controller.h" +#include "core/application.h" #include @@ -35,6 +37,58 @@ extern "C" { namespace Platform { namespace File { +namespace { + +#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION +bool ShowOpenWithSupported() { + return Platform::internal::GdkHelperLoaded() + && (Libs::gtk_app_chooser_dialog_new != nullptr) + && (Libs::gtk_app_chooser_get_app_info != nullptr) + && (Libs::gtk_app_chooser_get_type != nullptr) + && (Libs::gtk_widget_get_type != nullptr) + && (Libs::gtk_widget_get_window != nullptr) + && (Libs::gtk_widget_realize != nullptr) + && (Libs::gtk_widget_show != nullptr) + && (Libs::gtk_widget_destroy != nullptr); +} + +void HandleAppChooserResponse( + GtkDialog *dialog, + int responseId, + GFile *file) { + GAppInfo *chosenAppInfo = nullptr; + + switch (responseId) { + case GTK_RESPONSE_OK: + chosenAppInfo = Libs::gtk_app_chooser_get_app_info( + Libs::gtk_app_chooser_cast(dialog)); + + if (chosenAppInfo) { + GList *uris = nullptr; + uris = g_list_prepend(uris, g_file_get_uri(file)); + g_app_info_launch_uris(chosenAppInfo, uris, nullptr, nullptr); + g_list_free(uris); + g_object_unref(chosenAppInfo); + } + + g_object_unref(file); + Libs::gtk_widget_destroy(Libs::gtk_widget_cast(dialog)); + break; + + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + g_object_unref(file); + Libs::gtk_widget_destroy(Libs::gtk_widget_cast(dialog)); + break; + + default: + break; + } +} +#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION + +} // namespace + namespace internal { QByteArray EscapeShell(const QByteArray &content) { @@ -78,6 +132,44 @@ void UnsafeOpenEmailLink(const QString &email) { UnsafeOpenUrl(qstr("mailto:") + email); } +bool UnsafeShowOpenWith(const QString &filepath) { +#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION + if (InFlatpak() + || InSnap() + || !ShowOpenWithSupported()) { + return false; + } + + const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); + auto gfileInstance = g_file_new_for_path(absolutePath.toUtf8()); + + auto appChooserDialog = Libs::gtk_app_chooser_dialog_new( + nullptr, + GTK_DIALOG_MODAL, + gfileInstance); + + g_signal_connect( + appChooserDialog, + "response", + G_CALLBACK(HandleAppChooserResponse), + gfileInstance); + + Libs::gtk_widget_realize(appChooserDialog); + + if (const auto activeWindow = Core::App().activeWindow()) { + Platform::internal::XSetTransientForHint( + Libs::gtk_widget_get_window(appChooserDialog), + activeWindow->widget().get()->windowHandle()->winId()); + } + + Libs::gtk_widget_show(appChooserDialog); + + return true; +#else // !TDESKTOP_DISABLE_GTK_INTEGRATION + return false; +#endif // TDESKTOP_DISABLE_GTK_INTEGRATION +} + void UnsafeLaunch(const QString &filepath) { const auto absolutePath = QFileInfo(filepath).absoluteFilePath(); @@ -85,7 +177,9 @@ void UnsafeLaunch(const QString &filepath) { g_filename_to_uri(absolutePath.toUtf8(), nullptr, nullptr), nullptr, nullptr)) { - QDesktopServices::openUrl(QUrl::fromLocalFile(filepath)); + if (!UnsafeShowOpenWith(filepath)) { + QDesktopServices::openUrl(QUrl::fromLocalFile(filepath)); + } } } diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h index 4b57d656e..a0dc9c186 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h @@ -34,10 +34,6 @@ inline bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosit return false; } -inline bool UnsafeShowOpenWith(const QString &filepath) { - return false; -} - inline void PostprocessDownloaded(const QString &filepath) { } diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.cpp b/Telegram/SourceFiles/platform/linux/linux_libs.cpp index 50f53f37a..793b811d7 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_libs.cpp @@ -70,6 +70,7 @@ bool setupGtkBase(QLibrary &lib_gtk) { if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_realize", gtk_widget_realize)) return false; if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_hide_on_delete", gtk_widget_hide_on_delete)) return false; if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_destroy", gtk_widget_destroy)) return false; + if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_get_type", gtk_widget_get_type)) return false; if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_get", gtk_clipboard_get)) return false; if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_store", gtk_clipboard_store)) return false; if (!LOAD_SYMBOL(lib_gtk, "gtk_clipboard_wait_for_contents", gtk_clipboard_wait_for_contents)) return false; @@ -211,6 +212,7 @@ f_gtk_widget_get_window gtk_widget_get_window = nullptr; f_gtk_widget_realize gtk_widget_realize = nullptr; f_gtk_widget_hide_on_delete gtk_widget_hide_on_delete = nullptr; f_gtk_widget_destroy gtk_widget_destroy = nullptr; +f_gtk_widget_get_type gtk_widget_get_type = nullptr; f_gtk_clipboard_get gtk_clipboard_get = nullptr; f_gtk_clipboard_store gtk_clipboard_store = nullptr; f_gtk_clipboard_wait_for_contents gtk_clipboard_wait_for_contents = nullptr; @@ -246,6 +248,9 @@ f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf = nullptr; f_gtk_dialog_get_widget_for_response gtk_dialog_get_widget_for_response = nullptr; f_gtk_button_set_label gtk_button_set_label = nullptr; f_gtk_button_get_type gtk_button_get_type = nullptr; +f_gtk_app_chooser_dialog_new gtk_app_chooser_dialog_new = nullptr; +f_gtk_app_chooser_get_app_info gtk_app_chooser_get_app_info = nullptr; +f_gtk_app_chooser_get_type gtk_app_chooser_get_type = nullptr; f_gdk_set_allowed_backends gdk_set_allowed_backends = nullptr; f_gdk_window_set_modal_hint gdk_window_set_modal_hint = nullptr; f_gdk_window_focus gdk_window_focus = nullptr; @@ -304,6 +309,10 @@ void start() { LOAD_SYMBOL(lib_gtk, "gtk_button_set_label", gtk_button_set_label); LOAD_SYMBOL(lib_gtk, "gtk_button_get_type", gtk_button_get_type); + LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_dialog_new", gtk_app_chooser_dialog_new); + LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_get_app_info", gtk_app_chooser_get_app_info); + LOAD_SYMBOL(lib_gtk, "gtk_app_chooser_get_type", gtk_app_chooser_get_type); + SetIconTheme(); const auto settings = gtk_settings_get_default(); diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h index a1f383534..cf207d88b 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ b/Telegram/SourceFiles/platform/linux/linux_libs.h @@ -18,6 +18,9 @@ extern "C" { #define signals public } // extern "C" +// present start with gtk 3.0, we're building with gtk 2.0 headers +typedef struct _GtkAppChooser GtkAppChooser; + #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION #if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY @@ -172,6 +175,12 @@ extern f_gtk_image_new gtk_image_new; typedef void (*f_gtk_image_set_from_pixbuf)(GtkImage *image, GdkPixbuf *pixbuf); extern f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf; +typedef GtkWidget* (*f_gtk_app_chooser_dialog_new)(GtkWindow *parent, GtkDialogFlags flags, GFile *file); +extern f_gtk_app_chooser_dialog_new gtk_app_chooser_dialog_new; + +typedef GAppInfo* (*f_gtk_app_chooser_get_app_info)(GtkAppChooser *self); +extern f_gtk_app_chooser_get_app_info gtk_app_chooser_get_app_info; + typedef void (*f_gdk_set_allowed_backends)(const gchar *backends); extern f_gdk_set_allowed_backends gdk_set_allowed_backends; @@ -226,6 +235,22 @@ inline GtkWindow *gtk_window_cast(Object *obj) { return g_type_cic_helper(obj, gtk_window_get_type()); } +typedef GType (*f_gtk_widget_get_type)(void) G_GNUC_CONST; +extern f_gtk_widget_get_type gtk_widget_get_type; + +template +inline GtkWidget *gtk_widget_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_widget_get_type()); +} + +typedef GType (*f_gtk_app_chooser_get_type)(void) G_GNUC_CONST; +extern f_gtk_app_chooser_get_type gtk_app_chooser_get_type; + +template +inline GtkAppChooser *gtk_app_chooser_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_app_chooser_get_type()); +} + template inline bool g_type_cit_helper(Object *instance, GType iface_type) { if (!instance) return false; From 167a73ef1bc411a713424352d66c27d2c9181c33 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 9 Nov 2020 12:36:15 +0300 Subject: [PATCH 027/370] Add sent/read checks for some service messages. --- Telegram/SourceFiles/history/history_service.cpp | 5 +++++ Telegram/SourceFiles/history/history_service.h | 4 +--- Telegram/lib_base | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 25a733922..bda13ceac 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -617,6 +617,11 @@ bool HistoryService::updateDependencyItem() { return HistoryItem::updateDependencyItem(); } +bool HistoryService::needCheck() const { + return (GetDependentData() != nullptr) + || Has(); +} + QString HistoryService::inDialogsText(DrawInDialog way) const { return textcmdLink(1, TextUtilities::Clean(notificationText())); } diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index 200f6c8f6..99e93be8d 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -95,9 +95,7 @@ public: Storage::SharedMediaTypesMask sharedMediaTypes() const override; - bool needCheck() const override { - return false; - } + bool needCheck() const override; bool serviceMsg() const override { return true; } diff --git a/Telegram/lib_base b/Telegram/lib_base index 2d674eff9..4f22126e7 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 2d674eff931c2540147e4f1ac29965e90ab4af22 +Subproject commit 4f22126e7e855b517857408abf2467cba163a6f2 From 091b62bed4060fbd2d2e7dd5ef01558c7feda67e Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 9 Nov 2020 12:41:59 +0300 Subject: [PATCH 028/370] Allow cancel search-in-chat and keep search query. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index a69b59264..5e82e23b9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1784,7 +1784,9 @@ bool Widget::onCancelSearch() { void Widget::onCancelSearchInChat() { cancelSearchRequest(); if (_searchInChat) { - if (Adaptive::OneColumn() && !controller()->selectingPeer()) { + if (Adaptive::OneColumn() + && !controller()->selectingPeer() + && _filter->getLastText().trimmed().isEmpty()) { if (const auto peer = _searchInChat.peer()) { Ui::showPeerHistory(peer, ShowAtUnreadMsgId); //} else if (const auto feed = _searchInChat.feed()) { // #feed From 87e4bb10595ec6b65284fac2091009b1b0f8726f Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 9 Nov 2020 12:57:49 +0300 Subject: [PATCH 029/370] Fix scroll position restore with pinned bar. --- .../SourceFiles/history/history_widget.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 11aa0bd02..71e7780e9 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -3183,11 +3183,20 @@ void HistoryWidget::showAnimated( _a_show.stop(); _cacheUnder = params.oldContentCache; + + // If we show pinned bar here, we don't want it to change the + // calculated and prepared scrollTop of the messages history. + _preserveScrollTop = true; show(); _topBar->finishAnimating(); historyDownAnimationFinish(); unreadMentionsAnimationFinish(); + if (_pinnedBar) { + _pinnedBar->finishAnimating(); + } _topShadow->setVisible(params.withTopBarShadow ? false : true); + _preserveScrollTop = false; + _cacheOver = controller()->content()->grabForShowAnimation(params); if (_tabbedPanel) { @@ -3216,8 +3225,12 @@ void HistoryWidget::animationCallback() { if (!_a_show.animating()) { historyDownAnimationFinish(); unreadMentionsAnimationFinish(); + if (_pinnedBar) { + _pinnedBar->finishAnimating(); + } _cacheUnder = _cacheOver = QPixmap(); doneShow(); + synteticScrollToY(_scroll->scrollTop()); } } @@ -4676,11 +4689,7 @@ void HistoryWidget::updateHistoryGeometry( } } const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax()); - if (_scroll->scrollTop() == toY) { - visibleAreaUpdated(); - } else { - synteticScrollToY(toY); - } + synteticScrollToY(toY); } void HistoryWidget::updateListSize() { From db23485fa22ef93b82fa81ffaaa315f9eb8a437d Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 9 Nov 2020 13:47:53 +0300 Subject: [PATCH 030/370] Fix quit from fullscreen on macOS. --- Telegram/SourceFiles/core/sandbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index ecbe4d8f3..ab441b564 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -215,7 +215,7 @@ void Sandbox::setupScreenScale() { Sandbox::~Sandbox() = default; bool Sandbox::event(QEvent *e) { - if (e->type() == QEvent::Close) { + if (e->type() == QEvent::Close || e->type() == QEvent::Quit) { App::quit(); } return QApplication::event(e); From 8a9317f9e11d3dca6a019bcf477bcc1a5927de1f Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 9 Nov 2020 15:05:36 +0300 Subject: [PATCH 031/370] Download video avatars as .mp4 in media viewer. Fixes #9017. --- .../media/view/media_view_overlay_widget.cpp | 80 +++++++++++++++++-- .../media/view/media_view_overlay_widget.h | 9 +++ .../SourceFiles/storage/file_download.cpp | 3 +- 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 47c119cd4..245388078 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -627,6 +627,34 @@ void OverlayWidget::refreshNavVisibility() { } } +bool OverlayWidget::contentCanBeSaved() const { + if (_photo) { + return _photo->hasVideo() || _photoMedia->loaded(); + } else if (_document) { + return _document->filepath(true).isEmpty() && !_document->loading(); + } else { + return false; + } +} + +void OverlayWidget::checkForSaveLoaded() { + if (_savePhotoVideoWhenLoaded == SavePhotoVideo::None) { + return; + } else if (!_photo + || !_photo->hasVideo() + || _photoMedia->videoContent().isEmpty()) { + return; + } else if (_savePhotoVideoWhenLoaded == SavePhotoVideo::QuickSave) { + _savePhotoVideoWhenLoaded = SavePhotoVideo::None; + onDownload(); + } else if (_savePhotoVideoWhenLoaded == SavePhotoVideo::SaveAs) { + _savePhotoVideoWhenLoaded = SavePhotoVideo::None; + onSaveAs(); + } else { + Unexpected("SavePhotoVideo in OverlayWidget::checkForSaveLoaded."); + } +} + void OverlayWidget::updateControls() { if (_document && documentBubbleShown()) { if (_document->loading()) { @@ -658,10 +686,7 @@ void OverlayWidget::updateControls() { updateThemePreviewGeometry(); - _saveVisible = (_photo && _photoMedia->loaded()) - || (_document - && _document->filepath(true).isEmpty() - && !_document->loading()); + _saveVisible = contentCanBeSaved(); _rotateVisible = !_themePreviewShown; const auto navRect = [&](int i) { return myrtlrect(width() - st::mediaviewIconSize.width() * i, @@ -1153,6 +1178,7 @@ OverlayWidget::~OverlayWidget() { } void OverlayWidget::assignMediaPointer(DocumentData *document) { + _savePhotoVideoWhenLoaded = SavePhotoVideo::None; _photo = nullptr; _photoMedia = nullptr; if (_document != document) { @@ -1167,6 +1193,7 @@ void OverlayWidget::assignMediaPointer(DocumentData *document) { } void OverlayWidget::assignMediaPointer(not_null photo) { + _savePhotoVideoWhenLoaded = SavePhotoVideo::None; _document = nullptr; _documentMedia = nullptr; if (_photo != photo) { @@ -1348,6 +1375,31 @@ void OverlayWidget::onSaveAs() { updateControls(); updateOver(_lastMouseMovePos); } + } else if (_photo && _photo->hasVideo()) { + if (const auto bytes = _photoMedia->videoContent(); !bytes.isEmpty()) { + auto filter = qsl("Video Files (*.mp4);;") + FileDialog::AllFilesFilter(); + FileDialog::GetWritePath( + this, + tr::lng_save_video(tr::now), + filter, + filedialogDefaultName( + qsl("photo"), + qsl(".mp4"), + QString(), + false, + _photo->date), + crl::guard(this, [=, photo = _photo](const QString &result) { + QFile f(result); + if (!result.isEmpty() + && _photo == photo + && f.open(QIODevice::WriteOnly)) { + f.write(bytes); + } + })); + } else { + _photo->loadVideo(fileOrigin()); + _savePhotoVideoWhenLoaded = SavePhotoVideo::SaveAs; + } } else { if (!_photo || !_photoMedia->loaded()) { return; @@ -1436,14 +1488,29 @@ void OverlayWidget::onDownload() { DocumentSaveClickHandler::Mode::ToFile); updateControls(); } else { - _saveVisible = false; + _saveVisible = contentCanBeSaved(); update(_saveNav); } updateOver(_lastMouseMovePos); } + } else if (_photo && _photo->hasVideo()) { + if (const auto bytes = _photoMedia->videoContent(); !bytes.isEmpty()) { + if (!QDir().exists(path)) { + QDir().mkpath(path); + } + toName = filedialogDefaultName(qsl("photo"), qsl(".mp4"), path); + QFile f(toName); + if (!f.open(QIODevice::WriteOnly) + || f.write(bytes) != bytes.size()) { + toName = QString(); + } + } else { + _photo->loadVideo(fileOrigin()); + _savePhotoVideoWhenLoaded = SavePhotoVideo::QuickSave; + } } else { if (!_photo || !_photoMedia->loaded()) { - _saveVisible = false; + _saveVisible = contentCanBeSaved(); update(_saveNav); } else { const auto image = _photoMedia->image( @@ -3685,6 +3752,7 @@ void OverlayWidget::setSession(not_null session) { ) | rpl::start_with_next([=] { if (!isHidden()) { updateControls(); + checkForSaveLoaded(); } }, _sessionLifetime); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 69902cfec..598760928 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -159,6 +159,11 @@ private: not_null> data; HistoryItem *item; }; + enum class SavePhotoVideo { + None, + QuickSave, + SaveAs, + }; void paintEvent(QPaintEvent *e) override; @@ -207,6 +212,9 @@ private: bool moveToNext(int delta); void preloadData(int delta); + bool contentCanBeSaved() const; + void checkForSaveLoaded(); + Entity entityForUserPhotos(int index) const; Entity entityForSharedMedia(int index) const; Entity entityForCollage(int index) const; @@ -502,6 +510,7 @@ private: QRect _saveMsg; QTimer _saveMsgUpdater; Ui::Text::String _saveMsgText; + SavePhotoVideo _savePhotoVideoWhenLoaded = SavePhotoVideo::None; base::flat_map _animations; base::flat_map _animationOpacities; diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp index 629f2922b..2a2e9cb59 100644 --- a/Telegram/SourceFiles/storage/file_download.cpp +++ b/Telegram/SourceFiles/storage/file_download.cpp @@ -471,8 +471,9 @@ bool FileLoader::finalizeResult() { _cacheTag)); } } - _session->notifyDownloaderTaskFinished(); + const auto session = _session; _updates.fire_done(); + session->notifyDownloaderTaskFinished(); return true; } From a68d9b45222e947385616f9bef7aa4be8d7a72b9 Mon Sep 17 00:00:00 2001 From: zurg3 <20664125+zurg3@users.noreply.github.com> Date: Fri, 6 Nov 2020 19:10:14 +0300 Subject: [PATCH 032/370] Updated Qt version in GitHub Actions workflows --- .github/workflows/linux.yml | 14 +++++++------- .github/workflows/mac.yml | 18 +++++++++--------- .github/workflows/win.yml | 12 ++++++------ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6d69c0b1a..d71ca2a62 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -58,8 +58,8 @@ jobs: env: GIT: "https://github.com" - QT: "5_12_8" - QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.8" + QT: "5_15_1" + QT_PREFIX: "/usr/local/desktop-app/Qt-5.15.1" OPENSSL_VER: "1_1_1" OPENSSL_PREFIX: "/usr/local/desktop-app/openssl-1.1.1" CMAKE_VER: "3.17.0" @@ -427,18 +427,18 @@ jobs: cd .. rm -rf libxkbcommon - - name: Qt 5.12.8 cache. + - name: Qt 5.15.1 cache. id: cache-qt uses: actions/cache@v2 with: path: ${{ env.LibrariesPath }}/qt-cache - key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qt*_5_12_8/*') }} - - name: Qt 5.12.8 build. + key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qt*_5_15_1/*') }} + - name: Qt 5.15.1 build. if: steps.cache-qt.outputs.cache-hit != 'true' run: | cd $LibrariesPath - git clone -b v5.12.8 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT} + git clone -b v5.15.1 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT} cd qt_${QT} perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg git submodule update qtbase qtwayland qtimageformats qtsvg @@ -471,7 +471,7 @@ jobs: sudo make INSTALL_ROOT="$LibrariesPath/qt-cache" install cd .. rm -rf qt_${QT} - - name: Qt 5.12.8 install. + - name: Qt 5.15.1 install. run: | cd $LibrariesPath sudo cp -R qt-cache/. / diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 48ad4d4da..fe1c08bea 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -57,9 +57,9 @@ jobs: PREFIX: "/usr/local/macos" MACOSX_DEPLOYMENT_TARGET: "10.12" XZ: "xz-5.2.4" - QT: "5_12_8" + QT: "5_15_1" OPENSSL_VER: "1_1_1" - QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.8" + QT_PREFIX: "/usr/local/desktop-app/Qt-5.15.1" LIBICONV_VER: "libiconv-1.16" UPLOAD_ARTIFACT: "false" ONLY_CACHE: "false" @@ -417,20 +417,20 @@ jobs: build/gyp_crashpad.py -Dmac_deployment_target=10.10 ninja -C out/Debug - - name: Qt 5.12.8 cache. + - name: Qt 5.15.1 cache. id: cache-qt uses: actions/cache@v2 with: path: ${{ env.LibrariesPath }}/qt-cache - key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8/*') }} - - name: Use cached Qt 5.12.8. + key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_15_1/*') }} + - name: Use cached Qt 5.15.1. if: steps.cache-qt.outputs.cache-hit == 'true' run: | cd $LibrariesPath - mv qt-cache Qt-5.12.8 + mv qt-cache Qt-5.15.1 sudo mkdir -p $QT_PREFIX - sudo mv -f Qt-5.12.8 "$(dirname "$QT_PREFIX")"/ - - name: Qt 5.12.8 build. + sudo mv -f Qt-5.15.1 "$(dirname "$QT_PREFIX")"/ + - name: Qt 5.15.1 build. if: steps.cache-qt.outputs.cache-hit != 'true' run: | cd $LibrariesPath @@ -438,7 +438,7 @@ jobs: git clone git://code.qt.io/qt/qt5.git qt_$QT cd qt_$QT perl init-repository --module-subset=qtbase,qtimageformats - git checkout v5.12.8 + git checkout v5.15.1 git submodule update qtbase git submodule update qtimageformats cd qtbase diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 83ffcc001..6f3d1d6ce 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -59,8 +59,8 @@ jobs: SDK: "10.0.18362.0" VC: "call vcvars32.bat && cd Libraries" GIT: "https://github.com" - QT: "5_12_8" - QT_VER: "5.12.8" + QT: "5_15_1" + QT_VER: "5.15.1" OPENSSL_VER: "1_1_1" UPLOAD_ARTIFACT: "false" ONLY_CACHE: "false" @@ -301,13 +301,13 @@ jobs: rmdir /S /Q .git - - name: Qt 5.12.8 cache. + - name: Qt 5.15.1 cache. id: cache-qt uses: actions/cache@v2 with: path: ${{ env.LibrariesPath }}/Qt-${{ env.QT_VER }} - key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8/*') }} - - name: Configure Qt 5.12.8. + key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_15_1/*') }} + - name: Configure Qt 5.15.1. if: steps.cache-qt.outputs.cache-hit != 'true' run: | %VC% @@ -344,7 +344,7 @@ jobs: -I "%LibrariesPath%\mozjpeg" ^ LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" ^ LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib" - - name: Qt 5.12.8 build. + - name: Qt 5.15.1 build. if: steps.cache-qt.outputs.cache-hit != 'true' run: | %VC% From a768b652958b03f132dea8ba576f61e7931d65b3 Mon Sep 17 00:00:00 2001 From: zurg3 <20664125+zurg3@users.noreply.github.com> Date: Mon, 9 Nov 2020 22:44:11 +0300 Subject: [PATCH 033/370] Updated FFmpeg version in GitHub Actions workflows --- .github/workflows/linux.yml | 2 +- .github/workflows/mac.yml | 10 ++++++---- .github/workflows/win.yml | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d71ca2a62..fdb5c5ca0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -229,7 +229,7 @@ jobs: run: | cd $LibrariesPath - git clone --branch release/3.4 $GIT/FFmpeg/FFmpeg ffmpeg + git clone --branch release/4.2 $GIT/FFmpeg/FFmpeg ffmpeg cd ffmpeg ./configure \ --disable-debug \ diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index fe1c08bea..26a625dca 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -235,7 +235,7 @@ jobs: git clone $GIT/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg - git checkout release/3.4 + git checkout release/4.2 CFLAGS=`freetype-config --cflags` LDFLAGS=`freetype-config --libs` PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig @@ -244,7 +244,9 @@ jobs: --extra-cflags="$MIN_MAC $UNGUARDED" \ --extra-cxxflags="$MIN_MAC $UNGUARDED" \ --extra-ldflags="$MIN_MAC" \ - --enable-protocol=file --enable-libopus \ + --x86asmexe=`pwd`/macos_yasm_wrap.sh \ + --enable-protocol=file \ + --enable-libopus \ --disable-programs \ --disable-doc \ --disable-network \ @@ -364,9 +366,9 @@ jobs: run: | cd $LibrariesPath - git clone $GIT/kcat/openal-soft.git + git clone https://github.com/telegramdesktop/openal-soft.git cd openal-soft - git checkout openal-soft-1.19.1 + git checkout fix_mono cd build CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake \ diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 6f3d1d6ce..510ce788e 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -294,10 +294,10 @@ jobs: git clone %GIT%/FFmpeg/FFmpeg.git ffmpeg cd ffmpeg - git checkout release/3.4 + git checkout release/4.2 set CHERE_INVOKING=enabled_from_arguments set MSYS2_PATH_TYPE=inherit - call c:\tools\msys64\usr\bin\bash --login ../../%REPO_NAME%/Telegram/Patches/build_ffmpeg_win.sh + call c:\tools\msys64\usr\bin\bash --login ../patches/build_ffmpeg_win.sh rmdir /S /Q .git From 8b0fcee6a60028b9132bb2560379dddde1ad332e Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 12 Nov 2020 13:24:13 +0400 Subject: [PATCH 034/370] Use docker build in linux action --- .github/workflows/linux.yml | 506 ++---------------------------------- 1 file changed, 15 insertions(+), 491 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index fdb5c5ca0..ef2a521e7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -45,9 +45,17 @@ on: jobs: linux: - name: Ubuntu 14.04 + name: CentOS 7 runs-on: ubuntu-latest - container: ubuntu:trusty + container: + image: docker.pkg.github.com/telegramdesktop/tdesktop/centos_env + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + defaults: + run: + shell: scl enable devtoolset-8 -- bash --noprofile --norc -eo pipefail {0} strategy: matrix: @@ -57,59 +65,17 @@ jobs: - "TDESKTOP_DISABLE_GTK_INTEGRATION" env: - GIT: "https://github.com" - QT: "5_15_1" - QT_PREFIX: "/usr/local/desktop-app/Qt-5.15.1" - OPENSSL_VER: "1_1_1" - OPENSSL_PREFIX: "/usr/local/desktop-app/openssl-1.1.1" - CMAKE_VER: "3.17.0" UPLOAD_ARTIFACT: "false" - ONLY_CACHE: "false" - MANUAL_CACHING: "6" - DOC_PATH: "docs/building-cmake.md" - AUTO_CACHING: "1" steps: - name: Get repository name. run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - - name: Disable man for further package installs. + - name: Yum install. run: | - cfgFile="/etc/dpkg/dpkg.cfg.d/no_man" - sudo touch $cfgFile - p() { - sudo echo "path-exclude=/usr/share/$1/*" >> $cfgFile - } - - p man - p locale - p doc - - - name: Apt install. - shell: bash - run: | - sudo apt-get update - sudo apt-get install software-properties-common -y && \ - sudo add-apt-repository ppa:git-core/ppa -y && \ - sudo apt-get update && \ - sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev \ - libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \ - autoconf automake build-essential libxml2-dev libass-dev libfreetype6-dev \ - libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev \ - libvorbis-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev \ - libxcb-screensaver0-dev libjpeg-dev ninja-build \ - libxcb-xfixes0-dev libxcb-keysyms1-dev libxcb-icccm4-dev libatspi2.0-dev \ - libxcb-render-util0-dev libxcb-util0-dev libxcb-xkb-dev libxrender-dev \ - libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev libegl1-mesa-dev \ - libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html bison yasm \ - zlib1g-dev xutils-dev python-xcbgen chrpath gperf wget -y --force-yes && \ - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && \ - sudo apt-get update && \ - sudo apt-get install gcc-8 g++-8 -y && \ - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 60 && \ - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 60 && \ - sudo update-alternatives --config gcc && \ - sudo add-apt-repository --remove ppa:ubuntu-toolchain-r/test -y + yum -y autoremove git + yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm + yum -y install git - name: Clone. uses: actions/checkout@v2 @@ -118,452 +84,11 @@ jobs: path: ${{ env.REPO_NAME }} - name: First set up. - shell: bash run: | gcc --version - - gcc --version > CACHE_KEY.txt - echo $MANUAL_CACHING >> CACHE_KEY.txt - if [ "$AUTO_CACHING" == "1" ]; then - thisFile=$REPO_NAME/.github/workflows/linux.yml - echo `md5sum $thisFile | cut -c -32` >> CACHE_KEY.txt - fi - md5cache=$(md5sum CACHE_KEY.txt | cut -c -32) - echo "CACHE_KEY=$md5cache" >> $GITHUB_ENV - - mkdir -p Libraries - cd Libraries - echo "LibrariesPath=`pwd`" >> $GITHUB_ENV - - - name: Patches. - run: | - echo "Find necessary commit from doc." - checkoutCommit=$(grep -A 1 "cd patches" $REPO_NAME/$DOC_PATH | sed -n 2p) - cd $LibrariesPath - git clone $GIT/desktop-app/patches.git - cd patches - eval $checkoutCommit - - - name: CMake. - run: | - cd $LibrariesPath - - file=cmake-$CMAKE_VER-Linux-x86_64.sh - wget $GIT/Kitware/CMake/releases/download/v$CMAKE_VER/$file - sudo mkdir /opt/cmake - sudo sh $file --prefix=/opt/cmake --skip-license - sudo ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake - rm $file - - cmake --version - - - name: MozJPEG. - run: | - cd $LibrariesPath - - git clone -b v4.0.1-rc2 $GIT/mozilla/mozjpeg.git - cd mozjpeg - cmake -B build . \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/usr/local \ - -DWITH_JPEG8=ON \ - -DPNG_SUPPORTED=OFF - cmake --build build -j$(nproc) - sudo cmake --install build - cd .. - rm -rf mozjpeg - - - name: Opus cache. - id: cache-opus - uses: actions/cache@v2 - with: - path: ${{ env.LibrariesPath }}/opus - key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }} - - name: Opus. - if: steps.cache-opus.outputs.cache-hit != 'true' - run: | - cd $LibrariesPath - - git clone -b v1.3 --depth=1 $GIT/xiph/opus - cd opus - ./autogen.sh - ./configure - make -j$(nproc) - - name: Opus install. - run: | - cd $LibrariesPath/opus - sudo make install - - - name: Libva. - run: | - cd $LibrariesPath - - git clone $GIT/intel/libva.git - cd libva - ./autogen.sh --enable-static - make -j$(nproc) - sudo make install - cd .. - rm -rf libva - - - name: Libvdpau. - run: | - cd $LibrariesPath - - git clone -b libvdpau-1.2 --depth=1 https://gitlab.freedesktop.org/vdpau/libvdpau.git - cd libvdpau - ./autogen.sh --enable-static - make -j$(nproc) - sudo make install - cd .. - rm -rf libvdpau - - - name: FFmpeg cache. - id: cache-ffmpeg - uses: actions/cache@v2 - with: - path: ${{ env.LibrariesPath }}/ffmpeg-cache - key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }} - - name: FFmpeg build. - if: steps.cache-ffmpeg.outputs.cache-hit != 'true' - run: | - cd $LibrariesPath - - git clone --branch release/4.2 $GIT/FFmpeg/FFmpeg ffmpeg - cd ffmpeg - ./configure \ - --disable-debug \ - --disable-programs \ - --disable-doc \ - --disable-network \ - --disable-autodetect \ - --disable-everything \ - --disable-alsa \ - --disable-iconv \ - --enable-libopus \ - --enable-vaapi \ - --enable-vdpau \ - --enable-protocol=file \ - --enable-hwaccel=h264_vaapi \ - --enable-hwaccel=h264_vdpau \ - --enable-hwaccel=mpeg4_vaapi \ - --enable-hwaccel=mpeg4_vdpau \ - --enable-decoder=aac \ - --enable-decoder=aac_fixed \ - --enable-decoder=aac_latm \ - --enable-decoder=aasc \ - --enable-decoder=alac \ - --enable-decoder=flac \ - --enable-decoder=gif \ - --enable-decoder=h264 \ - --enable-decoder=h264_vdpau \ - --enable-decoder=hevc \ - --enable-decoder=mp1 \ - --enable-decoder=mp1float \ - --enable-decoder=mp2 \ - --enable-decoder=mp2float \ - --enable-decoder=mp3 \ - --enable-decoder=mp3adu \ - --enable-decoder=mp3adufloat \ - --enable-decoder=mp3float \ - --enable-decoder=mp3on4 \ - --enable-decoder=mp3on4float \ - --enable-decoder=mpeg4 \ - --enable-decoder=mpeg4_vdpau \ - --enable-decoder=msmpeg4v2 \ - --enable-decoder=msmpeg4v3 \ - --enable-decoder=opus \ - --enable-decoder=pcm_alaw \ - --enable-decoder=pcm_f32be \ - --enable-decoder=pcm_f32le \ - --enable-decoder=pcm_f64be \ - --enable-decoder=pcm_f64le \ - --enable-decoder=pcm_lxf \ - --enable-decoder=pcm_mulaw \ - --enable-decoder=pcm_s16be \ - --enable-decoder=pcm_s16be_planar \ - --enable-decoder=pcm_s16le \ - --enable-decoder=pcm_s16le_planar \ - --enable-decoder=pcm_s24be \ - --enable-decoder=pcm_s24daud \ - --enable-decoder=pcm_s24le \ - --enable-decoder=pcm_s24le_planar \ - --enable-decoder=pcm_s32be \ - --enable-decoder=pcm_s32le \ - --enable-decoder=pcm_s32le_planar \ - --enable-decoder=pcm_s64be \ - --enable-decoder=pcm_s64le \ - --enable-decoder=pcm_s8 \ - --enable-decoder=pcm_s8_planar \ - --enable-decoder=pcm_u16be \ - --enable-decoder=pcm_u16le \ - --enable-decoder=pcm_u24be \ - --enable-decoder=pcm_u24le \ - --enable-decoder=pcm_u32be \ - --enable-decoder=pcm_u32le \ - --enable-decoder=pcm_u8 \ - --enable-decoder=pcm_zork \ - --enable-decoder=vorbis \ - --enable-decoder=wavpack \ - --enable-decoder=wmalossless \ - --enable-decoder=wmapro \ - --enable-decoder=wmav1 \ - --enable-decoder=wmav2 \ - --enable-decoder=wmavoice \ - --enable-encoder=libopus \ - --enable-parser=aac \ - --enable-parser=aac_latm \ - --enable-parser=flac \ - --enable-parser=h264 \ - --enable-parser=hevc \ - --enable-parser=mpeg4video \ - --enable-parser=mpegaudio \ - --enable-parser=opus \ - --enable-parser=vorbis \ - --enable-demuxer=aac \ - --enable-demuxer=flac \ - --enable-demuxer=gif \ - --enable-demuxer=h264 \ - --enable-demuxer=hevc \ - --enable-demuxer=m4v \ - --enable-demuxer=mov \ - --enable-demuxer=mp3 \ - --enable-demuxer=ogg \ - --enable-demuxer=wav \ - --enable-muxer=ogg \ - --enable-muxer=opus - - make -j$(nproc) - sudo make DESTDIR="$LibrariesPath/ffmpeg-cache" install - cd .. - rm -rf ffmpeg - - name: FFmpeg install. - run: | - cd $LibrariesPath - #List of files from cmake/external/ffmpeg/CMakeLists.txt. - copyLib() { - mkdir -p ffmpeg/$1 - yes | cp -i ffmpeg-cache/usr/local/lib/$1.a ffmpeg/$1/$1.a - } - copyLib libavformat - copyLib libavcodec - copyLib libswresample - copyLib libswscale - copyLib libavutil - - sudo cp -R ffmpeg-cache/. / - - - name: OpenAL Soft. - run: | - cd $LibrariesPath - - git clone -b openal-soft-1.20.1 --depth=1 $GIT/kcat/openal-soft.git - cd openal-soft/build - cmake .. \ - -DCMAKE_BUILD_TYPE=Release \ - -DLIBTYPE:STRING=STATIC \ - -DALSOFT_EXAMPLES=OFF \ - -DALSOFT_TESTS=OFF \ - -DALSOFT_UTILS=OFF \ - -DALSOFT_CONFIG=OFF - make -j$(nproc) - sudo make install - cd - - rm -rf openal-soft - - - name: OpenSSL cache. - id: cache-openssl - uses: actions/cache@v2 - with: - path: ${{ env.LibrariesPath }}/openssl-cache - key: ${{ runner.OS }}-${{ env.OPENSSL_VER }}-${{ env.CACHE_KEY }} - - name: OpenSSL build. - if: steps.cache-openssl.outputs.cache-hit != 'true' - run: | - cd $LibrariesPath - - opensslDir=openssl_${OPENSSL_VER} - git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \ - $GIT/openssl/openssl $opensslDir - cd $opensslDir - ./config --prefix="$OPENSSL_PREFIX" no-tests - make -j$(nproc) - sudo make DESTDIR="$LibrariesPath/openssl-cache" install_sw - cd .. - # rm -rf $opensslDir # Keep this folder for WebRTC. - - name: OpenSSL install. - run: | - cd $LibrariesPath - sudo cp -R openssl-cache/. / - - - name: Libwayland. - run: | - cd $LibrariesPath - - git clone -b 1.18.0 https://gitlab.freedesktop.org/wayland/wayland - cd wayland - ./autogen.sh \ - --enable-static \ - --disable-documentation \ - --disable-dtd-validation - make -j$(nproc) - sudo make install - cd .. - rm -rf wayland - - - name: Libxkbcommon. - run: | - cd $LibrariesPath - - git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git - cd libxkbcommon - ./autogen.sh \ - --disable-docs \ - --disable-wayland \ - --with-xkb-config-root=/usr/share/X11/xkb \ - --with-x-locale-root=/usr/share/X11/locale - make -j$(nproc) - sudo make install - cd .. - rm -rf libxkbcommon - - - name: Qt 5.15.1 cache. - id: cache-qt - uses: actions/cache@v2 - with: - path: ${{ env.LibrariesPath }}/qt-cache - key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qt*_5_15_1/*') }} - - name: Qt 5.15.1 build. - if: steps.cache-qt.outputs.cache-hit != 'true' - run: | - cd $LibrariesPath - - git clone -b v5.15.1 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT} - cd qt_${QT} - perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg - git submodule update qtbase qtwayland qtimageformats qtsvg - cd qtbase - find ../../patches/qtbase_${QT} -type f -print0 | sort -z | xargs -r0 git apply - cd .. - cd qtwayland - find ../../patches/qtwayland_${QT} -type f -print0 | sort -z | xargs -r0 git apply - cd .. - - ./configure -prefix "$QT_PREFIX" \ - -release \ - -opensource \ - -confirm-license \ - -qt-zlib \ - -qt-libpng \ - -qt-harfbuzz \ - -qt-pcre \ - -qt-xcb \ - -no-icu \ - -no-gtk \ - -static \ - -dbus-runtime \ - -openssl-linked \ - -I "$OPENSSL_PREFIX/include" OPENSSL_LIBS="$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -ldl -lpthread" \ - -nomake examples \ - -nomake tests - - make -j$(nproc) - sudo make INSTALL_ROOT="$LibrariesPath/qt-cache" install - cd .. - rm -rf qt_${QT} - - name: Qt 5.15.1 install. - run: | - cd $LibrariesPath - sudo cp -R qt-cache/. / - - - name: Breakpad cache. - id: cache-breakpad - uses: actions/cache@v2 - with: - path: ${{ env.LibrariesPath }}/breakpad-cache - key: ${{ runner.OS }}-breakpad-${{ env.CACHE_KEY }} - - name: Breakpad clone. - run: | - cd $LibrariesPath - - git clone https://chromium.googlesource.com/breakpad/breakpad - cd breakpad - git checkout bc8fb886 - git clone https://chromium.googlesource.com/linux-syscall-support src/third_party/lss - cd src/third_party/lss - git checkout a91633d1 - - name: Breakpad build. - if: steps.cache-breakpad.outputs.cache-hit != 'true' - run: | - cd $LibrariesPath - - BreakpadCache=$LibrariesPath/breakpad-cache - - git clone https://chromium.googlesource.com/external/gyp - cd gyp - git checkout 9f2a7bb1 - git apply ../patches/gyp.diff - cd .. - - cd breakpad - ./configure - make -j$(nproc) - sudo make DESTDIR="$BreakpadCache" install - cd src - rm -r testing - git clone $GIT/google/googletest testing - cd tools - sed -i 's/minidump_upload.m/minidump_upload.cc/' linux/tools_linux.gypi - ../../../gyp/gyp --depth=. --generator-output=.. -Goutput_dir=../out tools.gyp --format=cmake - cd ../../out/Default - cmake . - make -j$(nproc) dump_syms - - mv dump_syms $BreakpadCache/ - cd .. - rm -rf gyp breakpad - - name: Breakpad install. - run: | - cd $LibrariesPath - sudo cp -R breakpad-cache/. / - mkdir -p breakpad/out/Default/ - cp breakpad-cache/dump_syms breakpad/out/Default/dump_syms - - - name: WebRTC cache. - id: cache-webrtc - uses: actions/cache@v2 - with: - path: ${{ env.LibrariesPath }}/tg_owt - key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }} - - name: WebRTC. - if: steps.cache-webrtc.outputs.cache-hit != 'true' - run: | - cd $LibrariesPath - - git clone $GIT/desktop-app/tg_owt.git - mkdir -p tg_owt/out/Debug - cd tg_owt/out/Debug - cmake -G Ninja \ - -DCMAKE_BUILD_TYPE=Debug \ - -DTG_OWT_SPECIAL_TARGET=linux \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ - -DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PREFIX/include \ - -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ - -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include \ - ../.. - ninja - - # Cleanup. - cd $LibrariesPath/tg_owt - mv out/Debug/libtg_owt.a libtg_owt.a - rm -rf out - mkdir -p out/Debug - mv libtg_owt.a out/Debug/libtg_owt.a - - rm -rf $LibrariesPath/openssl_${OPENSSL_VER} + ln -s $LibrariesPath Libraries - name: Telegram Desktop build. - if: env.ONLY_CACHE == 'false' run: | cd $REPO_NAME/Telegram @@ -587,7 +112,6 @@ jobs: make -j$(nproc) - name: Check. - if: env.ONLY_CACHE == 'false' run: | filePath="$REPO_NAME/out/Debug/bin/Telegram" if test -f "$filePath"; then From 21133abe132344bab00f9cad2ac139d0960fd596 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 10 Nov 2020 14:00:26 +0400 Subject: [PATCH 035/370] Fix 30s hang in case ibus portal couldn't be started --- .../platform/linux/specific_linux.cpp | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 1e7b5dd44..fc5658557 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION #include #include +#include #include #include #include @@ -90,6 +91,31 @@ constexpr auto kXCBFrameExtentsAtomName = "_GTK_FRAME_EXTENTS"_cs; QStringList PlatformThemes; #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION +QStringList ListDBusActivatableNames() { + static const auto Result = [&] { + const auto message = QDBusMessage::createMethodCall( + qsl("org.freedesktop.DBus"), + qsl("/org/freedesktop/DBus"), + qsl("org.freedesktop.DBus"), + qsl("ListActivatableNames")); + + const QDBusReply reply = QDBusConnection::sessionBus() + .call(message); + + if (reply.isValid()) { + return reply.value(); + } else { + LOG(("App Error: %1: %2") + .arg(reply.error().name()) + .arg(reply.error().message())); + } + + return QStringList{}; + }(); + + return Result; +} + void PortalAutostart(bool autostart, bool silent = false) { if (cExeName().isEmpty()) { return; @@ -155,9 +181,19 @@ bool IsXDGDesktopPortalKDEPresent() { } bool IsIBusPortalPresent() { - static const auto Result = QDBusInterface( - qsl("org.freedesktop.portal.IBus"), - qsl("/org/freedesktop/IBus")).isValid(); + static const auto Result = [&] { + const auto interface = QDBusConnection::sessionBus().interface(); + const auto activatableNames = ListDBusActivatableNames(); + + const auto serviceRegistered = interface + && interface->isServiceRegistered( + qsl("org.freedesktop.portal.IBus")); + + const auto serviceActivatable = activatableNames.contains( + qsl("org.freedesktop.portal.IBus")); + + return serviceRegistered || serviceActivatable; + }(); return Result; } From e64f6f72667d1beccd5464ec4d915b66f6db329d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 12 Nov 2020 19:10:30 +0400 Subject: [PATCH 036/370] Since changing the ibus portal check, it is not compatible with snap anymore --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index fc5658557..61ce860be 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -1158,7 +1158,10 @@ void start() { // Since tdesktop is distributed in static binary form, // it makes sense to use ibus portal whenever it present // to ensure compatibility with the maximum range of distributions. - if (IsIBusPortalPresent()) { + if (AreQtPluginsBundled() + && !InFlatpak() + && !InSnap() + && IsIBusPortalPresent()) { LOG(("IBus portal is present! Using it.")); qputenv("IBUS_USE_PORTAL", "1"); } From c8ce5dfa8bad172dbc35f15f2906defb1b04e322 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 15 Nov 2020 11:18:23 +0400 Subject: [PATCH 037/370] Fix escaping in scheme creation on Linux and set -workdir --- .../platform/linux/specific_linux.cpp | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 61ce860be..9ca496c03 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -124,10 +124,12 @@ void PortalAutostart(bool autostart, bool silent = false) { QVariantMap options; options["reason"] = tr::lng_settings_auto_start(tr::now); options["autostart"] = autostart; - options["commandline"] = QStringList({ + options["commandline"] = QStringList{ cExeName(), + qsl("-workdir"), + cWorkingDir(), qsl("-autostart") - }); + }; options["dbus-activatable"] = false; auto message = QDBusMessage::createMethodCall( @@ -228,6 +230,10 @@ uint FileChooserPortalVersion() { } #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION +QString EscapeShellInLauncher(const QString &content) { + return EscapeShell(content.toUtf8()).replace('\\', "\\\\"); +} + QString FlatpakID() { static const auto Result = [] { if (!qEnvironmentVariableIsEmpty("FLATPAK_ID")) { @@ -291,35 +297,26 @@ bool GenerateDesktopFile( QFile target(targetFile); if (target.open(QIODevice::WriteOnly)) { - if (!Core::UpdaterDisabled()) { - fileText = fileText.replace( - QRegularExpression( - qsl("^TryExec=.*$"), - QRegularExpression::MultilineOption), - qsl("TryExec=") - + QFile::encodeName(cExeDir() + cExeName()) - .replace('\\', "\\\\")); - fileText = fileText.replace( - QRegularExpression( - qsl("^Exec=.*$"), - QRegularExpression::MultilineOption), - qsl("Exec=") - + EscapeShell(QFile::encodeName(cExeDir() + cExeName())) - .replace('\\', "\\\\") - + (args.isEmpty() ? QString() : ' ' + args)); - } else { - fileText = fileText.replace( - QRegularExpression( - qsl("^Exec=(.*) -- %u$"), - QRegularExpression::MultilineOption), - qsl("Exec=\\1") - + (args.isEmpty() ? QString() : ' ' + args)); - } + fileText = fileText.replace( + QRegularExpression( + qsl("^TryExec=.*$"), + QRegularExpression::MultilineOption), + qsl("TryExec=%1").arg( + QString(cExeDir() + cExeName()).replace('\\', "\\\\"))); + + fileText = fileText.replace( + QRegularExpression( + qsl("^Exec=.*$"), + QRegularExpression::MultilineOption), + qsl("Exec=%1 -workdir %2").arg( + EscapeShellInLauncher(cExeDir() + cExeName()), + EscapeShellInLauncher(cWorkingDir())) + + (args.isEmpty() ? QString() : ' ' + args)); target.write(fileText.toUtf8()); target.close(); - if (IsStaticBinary()) { + if (!Core::UpdaterDisabled()) { DEBUG_LOG(("App Info: removing old .desktop files")); QFile::remove(qsl("%1telegram.desktop").arg(targetPath)); QFile::remove(qsl("%1telegramdesktop.desktop").arg(targetPath)); @@ -1217,10 +1214,9 @@ void RegisterCustomScheme(bool force) { GError *error = nullptr; - const auto neededCommandlineBuilder = qsl("%1 --") - .arg(!Core::UpdaterDisabled() - ? cExeDir() + cExeName() - : cExeName()); + const auto neededCommandlineBuilder = qsl("%1 -workdir %2 --").arg( + QString(EscapeShell(QFile::encodeName(cExeDir() + cExeName()))), + QString(EscapeShell(QFile::encodeName(cWorkingDir())))); const auto neededCommandline = qsl("%1 %u") .arg(neededCommandlineBuilder); From 772bd81ea560c190aec6fc63aad94fe4dadbb69f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 15 Nov 2020 19:50:15 +0400 Subject: [PATCH 038/370] Fix typo in the installlauncher cheat code --- Telegram/SourceFiles/settings/settings_codes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/settings/settings_codes.cpp b/Telegram/SourceFiles/settings/settings_codes.cpp index 9f77b4578..a83e690ce 100644 --- a/Telegram/SourceFiles/settings/settings_codes.cpp +++ b/Telegram/SourceFiles/settings/settings_codes.cpp @@ -158,7 +158,7 @@ auto GenerateCodes() { #endif // Q_OS_WIN || Q_OS_MAC #if defined Q_OS_UNIX && !defined Q_OS_MAC - codes.emplace(qsl("installauncher"), [](SessionController *window) { + codes.emplace(qsl("installlauncher"), [](SessionController *window) { Platform::InstallLauncher(true); Ui::Toast::Show("Forced launcher installation."); }); From d7ef484aecf48500371752e8641b0b3f10e7826b Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 14 Nov 2020 01:10:23 +0400 Subject: [PATCH 039/370] Use QWindow::setFlag that doesn't hide the windw --- Telegram/SourceFiles/window/main_window.cpp | 14 +------------- Telegram/SourceFiles/window/window_title_qt.cpp | 12 +----------- Telegram/SourceFiles/window/window_title_qt.h | 6 ------ 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index cf0c168b1..f9733b6a4 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/platform/ui_platform_utility.h" #include "history/history.h" #include "window/themes/window_theme.h" -#include "window/window_title_qt.h" // kShowAfterWindowFlagChangeDelay #include "window/window_session_controller.h" #include "window/window_lock_widgets.h" #include "window/window_outdated_bar.h" @@ -365,20 +364,9 @@ void MainWindow::refreshTitleWidget() { _titleShadow.destroy(); } -#if defined Q_OS_UNIX && !defined Q_OS_MAC - // setWindowFlag calls setParent(parentWidget(), newFlags), which - // always calls hide() explicitly, we have to show() the window back. - const auto hidden = isHidden(); const auto withShadow = hasShadow(); - setWindowFlag(Qt::NoDropShadowWindowHint, withShadow); + windowHandle()->setFlag(Qt::NoDropShadowWindowHint, withShadow); setAttribute(Qt::WA_OpaquePaintEvent, !withShadow); - if (!hidden) { - base::call_delayed( - kShowAfterWindowFlagChangeDelay, - this, - [=] { show(); }); - } -#endif // Q_OS_UNIX && !Q_OS_MAC } void MainWindow::updateMinimumSize() { diff --git a/Telegram/SourceFiles/window/window_title_qt.cpp b/Telegram/SourceFiles/window/window_title_qt.cpp index fc210e1aa..2dbc133b6 100644 --- a/Telegram/SourceFiles/window/window_title_qt.cpp +++ b/Telegram/SourceFiles/window/window_title_qt.cpp @@ -91,17 +91,7 @@ TitleWidgetQt::~TitleWidgetQt() { } void TitleWidgetQt::toggleFramelessWindow(bool enabled) { - // setWindowFlag calls setParent(parentWidget(), newFlags), which - // always calls hide() explicitly, we have to show() the window back. - const auto top = window(); - const auto hidden = top->isHidden(); - top->setWindowFlag(Qt::FramelessWindowHint, enabled); - if (!hidden) { - base::call_delayed( - kShowAfterWindowFlagChangeDelay, - top, - [=] { top->show(); }); - } + window()->windowHandle()->setFlag(Qt::FramelessWindowHint, enabled); } void TitleWidgetQt::init() { diff --git a/Telegram/SourceFiles/window/window_title_qt.h b/Telegram/SourceFiles/window/window_title_qt.h index 126711e95..00b65cc09 100644 --- a/Telegram/SourceFiles/window/window_title_qt.h +++ b/Telegram/SourceFiles/window/window_title_qt.h @@ -22,12 +22,6 @@ class PlainShadow; namespace Window { -// If we toggle frameless window hint in maximized window, and -// show it back too quickly, the mouse position inside the window -// won't be correct (from Qt-s point of view) until we Alt-Tab from -// that window. If we show the window back with this delay it works. -inline constexpr auto kShowAfterWindowFlagChangeDelay = crl::time(1000); - class TitleWidgetQt : public TitleWidget { public: TitleWidgetQt(QWidget *parent); From 620c5962001e3930601958a0cac201f60c276e38 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 12 Nov 2020 16:29:44 +0400 Subject: [PATCH 040/370] Remove the last workaround in tray implementation Since tdesktop gets icon theme pretty well now, there's no need for any workaround. --- .../platform/linux/main_window_linux.cpp | 30 ++----------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index c46d375a3..482a1a0cf 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -52,12 +52,9 @@ extern "C" { } // extern "C" #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION -#include - namespace Platform { namespace { -constexpr auto kDisableTrayCounter = "TDESKTOP_DISABLE_TRAY_COUNTER"_cs; constexpr auto kPanelTrayIconName = "telegram-panel"_cs; constexpr auto kMutePanelTrayIconName = "telegram-mute-panel"_cs; constexpr auto kAttentionPanelTrayIconName = "telegram-attention-panel"_cs; @@ -201,13 +198,6 @@ QIcon TrayIconGen(int counter, bool muted) { const auto iconName = GetTrayIconName(counter, muted); - if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) - && !iconName.isEmpty()) { - const auto result = QIcon::fromTheme(iconName); - UpdateIconRegenerationNeeded(result, counter, muted, iconThemeName); - return result; - } - QIcon result; QIcon systemIcon; @@ -261,8 +251,7 @@ QIcon TrayIconGen(int counter, bool muted) { auto iconImage = currentImageBack; - if (!qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) - && counter > 0) { + if (counter > 0) { const auto &bg = muted ? st::trayCounterBgMute : st::trayCounterBg; @@ -590,15 +579,7 @@ void MainWindow::psTrayMenuUpdated() { void MainWindow::setSNITrayIcon(int counter, bool muted) { const auto iconName = GetTrayIconName(counter, muted); - if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) - && !iconName.isEmpty()) { - if (_sniTrayIcon->iconName() == iconName) { - return; - } - - _sniTrayIcon->setIconByName(iconName); - _sniTrayIcon->setToolTipIconByName(iconName); - } else if (IsIndicatorApplication()) { + if (IsIndicatorApplication()) { if (!IsIconRegenerationNeeded(counter, muted) && _trayIconFile && _sniTrayIcon->iconName() == _trayIconFile->fileName()) { @@ -871,13 +852,6 @@ void MainWindow::LibsLoaded() { qDBusRegisterMetaType(); qDBusRegisterMetaType(); #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - - if (!qEnvironmentVariableIsSet(kDisableTrayCounter.utf8())) { - g_message( - "You can disable tray icon counter with %s " - "and make it look better if it is monochrome.", - kDisableTrayCounter.utf8().constData()); - } } void MainWindow::initTrayMenuHook() { From 5c8a19b7f77f0bad2313452cad55e292d04a9b9d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 12 Nov 2020 16:32:09 +0400 Subject: [PATCH 041/370] Use only really supported icon sizes --- Telegram/SourceFiles/platform/linux/main_window_linux.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 482a1a0cf..9becbfffc 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -202,7 +202,6 @@ QIcon TrayIconGen(int counter, bool muted) { QIcon systemIcon; const auto iconSizes = { - 16, 22, 24, 32, From e4b9900a069373e9f404986bdb3b3eba1d5fb38f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 12 Nov 2020 11:28:31 +0400 Subject: [PATCH 042/370] Construct WindowControlsLayout without variable --- .../platform/linux/specific_linux.cpp | 33 +++++++++---------- .../SourceFiles/platform/mac/specific_mac.mm | 13 ++++---- .../SourceFiles/platform/win/specific_win.cpp | 13 ++++---- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 9ca496c03..ec80da17a 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -964,31 +964,30 @@ Window::ControlsLayout WindowControlsLayout() { ); } - Window::ControlsLayout controls; - controls.left = controlsLeft; - controls.right = controlsRight; - - return controls; + return Window::ControlsLayout{ + .left = controlsLeft, + .right = controlsRight + }; } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - Window::ControlsLayout controls; - if (DesktopEnvironment::IsUnity()) { - controls.left = { - Window::Control::Close, - Window::Control::Minimize, - Window::Control::Maximize, + return Window::ControlsLayout{ + .left = { + Window::Control::Close, + Window::Control::Minimize, + Window::Control::Maximize, + } }; } else { - controls.right = { - Window::Control::Minimize, - Window::Control::Maximize, - Window::Control::Close, + return Window::ControlsLayout{ + .right = { + Window::Control::Minimize, + Window::Control::Maximize, + Window::Control::Close, + } }; } - - return controls; } } // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index 712e42c93..162629e89 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -204,14 +204,13 @@ void IgnoreApplicationActivationRightNow() { } Window::ControlsLayout WindowControlsLayout() { - Window::ControlsLayout controls; - controls.left = { - Window::Control::Close, - Window::Control::Minimize, - Window::Control::Maximize, + return Window::ControlsLayout{ + .left = { + Window::Control::Close, + Window::Control::Minimize, + Window::Control::Maximize, + } }; - - return controls; } } // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index ad7ee8a2c..cec77bfa6 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -325,14 +325,13 @@ bool ShowWindowMenu(QWindow *window) { } Window::ControlsLayout WindowControlsLayout() { - Window::ControlsLayout controls; - controls.right = { - Window::Control::Minimize, - Window::Control::Maximize, - Window::Control::Close, + return Window::ControlsLayout{ + .right = { + Window::Control::Minimize, + Window::Control::Maximize, + Window::Control::Close, + } }; - - return controls; } } // namespace Platform From b4cb47cf7fb2b1eef00d818eb08199c9abee0d06 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 10 Nov 2020 01:36:45 +0400 Subject: [PATCH 043/370] Prefer gtk3 headers --- Telegram/CMakeLists.txt | 2 +- .../platform/linux/file_utilities_linux.cpp | 12 +++++++----- Telegram/SourceFiles/platform/linux/linux_libs.h | 2 +- Telegram/build/docker/centos_env/Dockerfile | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 85f68e378..c3db02293 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -112,7 +112,7 @@ if (LINUX) PkgConfig::X11 ) else() - pkg_search_module(GTK REQUIRED gtk+-2.0 gtk+-3.0) + pkg_search_module(GTK REQUIRED gtk+-3.0 gtk+-2.0) target_include_directories(Telegram PRIVATE ${GTK_INCLUDE_DIRS}) target_link_libraries(Telegram PRIVATE X11) endif() diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 84fc2b205..0cc204e63 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -500,8 +500,10 @@ GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QStr d.reset(new QGtkDialog(Libs::gtk_file_chooser_dialog_new("", nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_OK, nullptr))); + // https://developer.gnome.org/gtk3/stable/GtkFileChooserDialog.html#gtk-file-chooser-dialog-new + // first_button_text doesn't need explicit conversion to char*, while all others are vardict + tr::lng_cancel(tr::now).toUtf8(), GTK_RESPONSE_CANCEL, + tr::lng_box_ok(tr::now).toUtf8().constData(), GTK_RESPONSE_OK, nullptr))); connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted())); connect(d.data(), SIGNAL(reject()), this, SLOT(onRejected())); @@ -737,9 +739,9 @@ void GtkFileDialog::applyOptions() { /*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept)) Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8()); else*/ if (_acceptMode == QFileDialog::AcceptOpen) - Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), GTK_STOCK_OPEN); + Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_open_link(tr::now).toUtf8()); else - Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), GTK_STOCK_SAVE); + Libs::gtk_button_set_label(Libs::gtk_button_cast(acceptButton), tr::lng_settings_save(tr::now).toUtf8()); } GtkWidget *rejectButton = Libs::gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL); @@ -747,7 +749,7 @@ void GtkFileDialog::applyOptions() { /*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject)) Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8()); else*/ - Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), GTK_STOCK_CANCEL); + Libs::gtk_button_set_label(Libs::gtk_button_cast(rejectButton), tr::lng_cancel(tr::now).toUtf8()); } } } diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h index cf207d88b..fa651615d 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ b/Telegram/SourceFiles/platform/linux/linux_libs.h @@ -18,7 +18,7 @@ extern "C" { #define signals public } // extern "C" -// present start with gtk 3.0, we're building with gtk 2.0 headers +// present starting with gtk 3.0, we can build with gtk2 headers typedef struct _GtkAppChooser GtkAppChooser; #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 90464e42b..0bb9d3650 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -11,7 +11,7 @@ ENV OPENSSL_PREFIX /usr/local/desktop-app/openssl-1.1.1 RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm RUN yum -y install centos-release-scl -RUN yum -y install git cmake3 zlib-devel gtk2-devel libICE-devel \ +RUN yum -y install git cmake3 zlib-devel gtk3-devel libICE-devel \ libSM-devel libdrm-devel autoconf automake libtool fontconfig-devel \ freetype-devel libX11-devel at-spi2-core-devel alsa-lib-devel \ pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel \ From 2df7e4181f560ccdc37a758ec81e1c92e44b2413 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 10 Nov 2020 01:52:34 +0400 Subject: [PATCH 044/370] Add a comment about GTK_USE_PORTAL in snap --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 94cce2b3f..3efba1dc7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -18,6 +18,7 @@ apps: common-id: org.telegram.desktop desktop: usr/share/applications/telegram-desktop_telegram-desktop.desktop environment: + # Tell glib to use portals on file associations handling. GTK_USE_PORTAL: 1 plugs: - alsa From c3b638449abd33c02716fc2a45c4b5030e78a22f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 10 Nov 2020 17:14:43 +0400 Subject: [PATCH 045/370] Re-check the screen media viewer appears on before adjusting geometry --- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 245388078..d9a63d55f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -432,9 +432,7 @@ void OverlayWidget::moveToScreen(bool force) { if (activeWindowScreen && myScreen && myScreen != activeWindowScreen) { windowHandle()->setScreen(activeWindowScreen); } - const auto screen = activeWindowScreen - ? activeWindowScreen - : QApplication::primaryScreen(); + const auto screen = widgetScreen(this); const auto available = screen->geometry(); if (!force && geometry() == available) { return; From 9d6e5f2a5bcba1f10d3727de2dcb4d341ed3803e Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 10 Nov 2020 03:09:16 +0400 Subject: [PATCH 046/370] Adapt linux tray icon implementation to the new QIcon::pixmap behavior More info: https://codereview.qt-project.org/c/qt/qtbase/+/314618 --- .../platform/linux/main_window_linux.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 9becbfffc..a85bdbab0 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -201,7 +201,7 @@ QIcon TrayIconGen(int counter, bool muted) { QIcon result; QIcon systemIcon; - const auto iconSizes = { + static const auto iconSizes = { 22, 24, 32, @@ -240,9 +240,12 @@ QIcon TrayIconGen(int counter, bool muted) { currentImageBack = Core::App().logo(); } - if (currentImageBack.size() != desiredSize) { + const auto currentImageBackSize = currentImageBack.size() + / currentImageBack.devicePixelRatio(); + + if (currentImageBackSize != desiredSize) { currentImageBack = currentImageBack.scaled( - desiredSize, + desiredSize * currentImageBack.devicePixelRatio(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } @@ -327,8 +330,7 @@ std::unique_ptr TrayIconFile( static const auto templateName = AppRuntimeDirectory() + kTrayIconFilename.utf16(); - const auto dpr = style::DevicePixelRatio(); - const auto desiredSize = QSize(22 * dpr, 22 * dpr); + static const auto desiredSize = QSize(22, 22); auto ret = std::make_unique( templateName, @@ -346,10 +348,11 @@ std::unique_ptr TrayIconFile( std::less<>(), &QSize::width); - icon - .pixmap(*biggestSize) + const auto iconPixmap = icon.pixmap(*biggestSize); + + iconPixmap .scaled( - desiredSize, + desiredSize * iconPixmap.devicePixelRatio(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation) .save(ret.get()); From 0089692b527fab7cad633a605bc48b85c82d47f1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 5 Nov 2020 19:38:24 +0300 Subject: [PATCH 047/370] Fix build for Mac App Store. --- Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm b/Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm index 27fd68f93..337055443 100644 --- a/Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm +++ b/Telegram/SourceFiles/platform/mac/file_bookmark_mac.mm @@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/mac/file_bookmark_mac.h" +#include "base/platform/mac/base_utilities_mac.h" +#include "logs.h" + +#include +#include + namespace Platform { namespace { @@ -61,7 +67,7 @@ bool FileBookmark::enable() const { #else // OS_MAC_STORE if (!data) return false; - QMutexLocker lock(&_bookmarksMutex); + QMutexLocker lock(&BookmarksMutex); if (data->counter > 0 || [data->url startAccessingSecurityScopedResource] == YES) { ++data->counter; return true; @@ -74,7 +80,7 @@ void FileBookmark::disable() const { #ifdef OS_MAC_STORE if (!data) return; - QMutexLocker lock(&_bookmarksMutex); + QMutexLocker lock(&BookmarksMutex); if (data->counter > 0) { --data->counter; if (!data->counter) { From 358228ce0096e368d46eb1941c92bb0ef671f784 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Nov 2020 13:09:40 +0300 Subject: [PATCH 048/370] Update submodules. --- Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 4f22126e7..ee68d3a63 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 4f22126e7e855b517857408abf2467cba163a6f2 +Subproject commit ee68d3a63b4f1e550e7f93c1f8d6e8e372bc0f33 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index cffa5e11d..a417bc87d 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit cffa5e11d8d1f340bd2356c0944a0d3e91e38f2c +Subproject commit a417bc87d01a7f733cfa72f40dd679b7b5ee449d From 02eea3872445b72d0c16c18ab480ac7a08d2d2f7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Nov 2020 14:22:19 +0300 Subject: [PATCH 049/370] Remove color space before sending in JPG. --- Telegram/SourceFiles/storage/localimageloader.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 6865d5200..0a67661e7 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include namespace { @@ -878,6 +879,11 @@ void FileLoadTask::process(Args &&args) { auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage; auto full = (w > 1280 || h > 1280) ? fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage; { + // We have an example of dark .png image that when being sent without + // removing its color space is displayed fine on tdesktop, but with + // a light gray background on mobile apps. + full.setColorSpace(QColorSpace()); + QBuffer buffer(&filedata); QImageWriter writer(&buffer, "JPEG"); writer.setQuality(87); From e4d2a66f45db3fd210673e959f567b1ea83b2d5e Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Nov 2020 19:31:34 +0300 Subject: [PATCH 050/370] Revert "Re-check the screen media viewer appears on before adjusting geometry" This reverts commit c3b638449abd33c02716fc2a45c4b5030e78a22f. It doesn't work as it was supposed to :( --- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index d9a63d55f..245388078 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -432,7 +432,9 @@ void OverlayWidget::moveToScreen(bool force) { if (activeWindowScreen && myScreen && myScreen != activeWindowScreen) { windowHandle()->setScreen(activeWindowScreen); } - const auto screen = widgetScreen(this); + const auto screen = activeWindowScreen + ? activeWindowScreen + : QApplication::primaryScreen(); const auto available = screen->geometry(); if (!force && geometry() == available) { return; From b71f61dec33c752f8a9d711c5ed5efc6bdb774a5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Nov 2020 19:37:29 +0300 Subject: [PATCH 051/370] Update submodules. --- Telegram/lib_ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index a417bc87d..77b6e43b1 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit a417bc87d01a7f733cfa72f40dd679b7b5ee449d +Subproject commit 77b6e43b17df43d64354ba7198e6a7695a636627 From cd67cb1c62637dae2d00fdc87746c6b5f1365cd1 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 16 Nov 2020 20:47:32 +0400 Subject: [PATCH 052/370] Update media viewer geometry on showing and screen change --- .../media/view/media_view_overlay_widget.cpp | 45 +++++++++++++++---- .../media/view/media_view_overlay_widget.h | 6 ++- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 245388078..e7d4fce54 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -359,6 +359,7 @@ OverlayWidget::OverlayWidget() } else { setWindowFlags(Qt::FramelessWindowHint); } + updateGeometry(QApplication::primaryScreen()->geometry()); moveToScreen(); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); @@ -374,6 +375,17 @@ OverlayWidget::OverlayWidget() setWindowState(Qt::WindowFullScreen); } + connect( + windowHandle(), + &QWindow::visibleChanged, + this, + [=](bool visible) { handleVisibleChanged(visible); }); + connect( + windowHandle(), + &QWindow::screenChanged, + this, + [=](QScreen *screen) { handleScreenChanged(screen); }); + #if defined Q_OS_MAC && !defined OS_OSX TouchBar::SetupMediaViewTouchBar( winId(), @@ -417,7 +429,7 @@ void OverlayWidget::refreshLang() { InvokeQueued(this, [this] { updateThemePreviewGeometry(); }); } -void OverlayWidget::moveToScreen(bool force) { +void OverlayWidget::moveToScreen() { const auto widgetScreen = [&](auto &&widget) -> QScreen* { if (auto handle = widget ? widget->windowHandle() : nullptr) { return handle->screen(); @@ -432,14 +444,10 @@ void OverlayWidget::moveToScreen(bool force) { if (activeWindowScreen && myScreen && myScreen != activeWindowScreen) { windowHandle()->setScreen(activeWindowScreen); } - const auto screen = activeWindowScreen - ? activeWindowScreen - : QApplication::primaryScreen(); - const auto available = screen->geometry(); - if (!force && geometry() == available) { - return; - } - setGeometry(available); +} + +void OverlayWidget::updateGeometry(const QRect &rect) { + setGeometry(rect); auto navSkip = 2 * st::mediaviewControlMargin + st::mediaviewControlSize; _closeNav = myrtlrect(width() - st::mediaviewControlMargin - st::mediaviewControlSize, st::mediaviewControlMargin, st::mediaviewControlSize, st::mediaviewControlSize); @@ -1294,6 +1302,12 @@ void OverlayWidget::onScreenResized(int screen) { const auto changed = (screen >= 0 && screen < screens.size()) ? screens[screen] : nullptr; + if (windowHandle() + && windowHandle()->screen() + && changed + && windowHandle()->screen() == changed) { + updateGeometry(changed->geometry()); + } if (!windowHandle() || !windowHandle()->screen() || !changed @@ -1302,6 +1316,19 @@ void OverlayWidget::onScreenResized(int screen) { } } +void OverlayWidget::handleVisibleChanged(bool visible) { + if (visible) { + const auto screen = windowHandle()->screen() + ? windowHandle()->screen() + : QApplication::primaryScreen(); + updateGeometry(screen->geometry()); + } +} + +void OverlayWidget::handleScreenChanged(QScreen *screen) { + updateGeometry(screen->geometry()); +} + void OverlayWidget::onToMessage() { if (!_session) { return; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 598760928..194659f40 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -208,10 +208,14 @@ private: void assignMediaPointer(not_null photo); void updateOver(QPoint mpos); - void moveToScreen(bool force = false); + void moveToScreen(); bool moveToNext(int delta); void preloadData(int delta); + void updateGeometry(const QRect &rect); + void handleVisibleChanged(bool visible); + void handleScreenChanged(QScreen *screen); + bool contentCanBeSaved() const; void checkForSaveLoaded(); From 8845652f772b2ba50786982fbf332b02c8f180de Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 13 Nov 2020 23:56:30 +0300 Subject: [PATCH 053/370] Fixed macOS build. --- .../SourceFiles/platform/mac/touchbar/mac_touchbar_controls.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_controls.mm b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_controls.mm index 32b4becd2..e2c337034 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_controls.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_controls.mm @@ -229,7 +229,7 @@ NSSliderTouchBarItem *CreateTouchBarSlider( touchesMatchingPhase:NSTouchPhaseEnded inView:nil].count > 0; Core::Sandbox::Instance().customEnterFromEventLoop([=] { - callback(touchUp, seekBar.doubleValue, *lastDurationMs); + callback(touchUp, seekBar.slider.doubleValue, *lastDurationMs); }); } copy]; From f7c6876e1bf9fdc7f30937ab45fd2c274c0a4a48 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 2 Oct 2020 13:36:22 +0300 Subject: [PATCH 054/370] Moved history_view_compose_controls to controls folder. --- Telegram/CMakeLists.txt | 4 ++-- .../view/{ => controls}/history_view_compose_controls.cpp | 2 +- .../view/{ => controls}/history_view_compose_controls.h | 0 .../SourceFiles/history/view/history_view_replies_section.cpp | 2 +- .../history/view/history_view_scheduled_section.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename Telegram/SourceFiles/history/view/{ => controls}/history_view_compose_controls.cpp (99%) rename Telegram/SourceFiles/history/view/{ => controls}/history_view_compose_controls.h (100%) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c3db02293..c487387cf 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -474,6 +474,8 @@ PRIVATE history/admin_log/history_admin_log_section.h # history/feed/history_feed_section.cpp # history/feed/history_feed_section.h + history/view/controls/history_view_compose_controls.cpp + history/view/controls/history_view_compose_controls.h history/view/media/history_view_call.h history/view/media/history_view_call.cpp history/view/media/history_view_contact.h @@ -514,8 +516,6 @@ PRIVATE history/view/media/history_view_theme_document.cpp history/view/media/history_view_web_page.h history/view/media/history_view_web_page.cpp - history/view/history_view_compose_controls.cpp - history/view/history_view_compose_controls.h history/view/history_view_contact_status.cpp history/view/history_view_contact_status.h history/view/history_view_context_menu.cpp diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp similarity index 99% rename from Telegram/SourceFiles/history/view/history_view_compose_controls.cpp rename to Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 6e3ec83d2..7648e7344 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/view/history_view_compose_controls.h" +#include "history/view/controls/history_view_compose_controls.h" #include "base/event_filter.h" #include "base/qt_signal_producer.h" diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h similarity index 100% rename from Telegram/SourceFiles/history/view/history_view_compose_controls.h rename to Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index ead53c4ae..b3170d8cf 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_replies_section.h" -#include "history/view/history_view_compose_controls.h" +#include "history/view/controls/history_view_compose_controls.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index b2417000e..0afcc8d93 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_scheduled_section.h" -#include "history/view/history_view_compose_controls.h" +#include "history/view/controls/history_view_compose_controls.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" From 6d775d6f452ce275a4a398f8e5fa1f4d631c4cc4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 3 Oct 2020 19:43:03 +0300 Subject: [PATCH 055/370] Moved structures of compose controls to separated header. --- Telegram/CMakeLists.txt | 1 + .../view/controls/compose_controls_common.h | 42 +++++++++++++++++++ .../history_view_compose_controls.cpp | 1 + .../controls/history_view_compose_controls.h | 30 ++++--------- 4 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/controls/compose_controls_common.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c487387cf..85aed68e2 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -474,6 +474,7 @@ PRIVATE history/admin_log/history_admin_log_section.h # history/feed/history_feed_section.cpp # history/feed/history_feed_section.h + history/view/controls/compose_controls_common.h history/view/controls/history_view_compose_controls.cpp history/view/controls/history_view_compose_controls.h history/view/media/history_view_call.h diff --git a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h new file mode 100644 index 000000000..d34621c1e --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h @@ -0,0 +1,42 @@ +/* +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 Api { +enum class SendProgressType; +struct SendOptions; +} // namespace Api + +class History; + +namespace HistoryView::Controls { + +struct MessageToEdit { + FullMsgId fullId; + Api::SendOptions options; + TextWithTags textWithTags; +}; +struct VoiceToSend { + QByteArray bytes; + VoiceWaveform waveform; + int duration = 0; +}; +struct SendActionUpdate { + Api::SendProgressType type = Api::SendProgressType(); + int progress = 0; +}; + +struct SetHistoryArgs { + required history; + Fn showSlowmodeError; + rpl::producer slowmodeSecondsLeft; + rpl::producer sendDisabledBySlowmode; + rpl::producer> writeRestriction; +}; + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 7648e7344..0020e9944 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -56,6 +56,7 @@ using PhotoChosen = ComposeControls::PhotoChosen; using MessageToEdit = ComposeControls::MessageToEdit; using VoiceToSend = ComposeControls::VoiceToSend; using SendActionUpdate = ComposeControls::SendActionUpdate; +using SetHistoryArgs = ComposeControls::SetHistoryArgs; [[nodiscard]] auto ShowWebPagePreview(WebPageData *page) { return page && (page->pendingTill >= 0); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 6b1b2f0a1..0bea19106 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/required.h" #include "api/api_common.h" #include "base/unique_qptr.h" +#include "history/view/controls/compose_controls_common.h" #include "ui/rp_widget.h" #include "ui/effects/animations.h" #include "ui/widgets/input_fields.h" @@ -61,26 +62,17 @@ class ComposeControls final { public: using FileChosen = ChatHelpers::TabbedSelector::FileChosen; using PhotoChosen = ChatHelpers::TabbedSelector::PhotoChosen; + + using MessageToEdit = Controls::MessageToEdit; + using VoiceToSend = Controls::VoiceToSend; + using SendActionUpdate = Controls::SendActionUpdate; + using SetHistoryArgs = Controls::SetHistoryArgs; + enum class Mode { Normal, Scheduled, }; - struct MessageToEdit { - FullMsgId fullId; - Api::SendOptions options; - TextWithTags textWithTags; - }; - struct VoiceToSend { - QByteArray bytes; - VoiceWaveform waveform; - int duration = 0; - }; - struct SendActionUpdate { - Api::SendProgressType type = Api::SendProgressType(); - int progress = 0; - }; - ComposeControls( not_null parent, not_null window, @@ -88,14 +80,6 @@ public: ~ComposeControls(); [[nodiscard]] Main::Session &session() const; - - struct SetHistoryArgs { - required history; - Fn showSlowmodeError; - rpl::producer slowmodeSecondsLeft; - rpl::producer sendDisabledBySlowmode; - rpl::producer> writeRestriction; - }; void setHistory(SetHistoryArgs &&args); void finishAnimating(); From 112dea85941ccf7043914c67b30e5f5312558c5e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 2 Oct 2020 21:14:41 +0300 Subject: [PATCH 056/370] Created voice record bar as separated history view class. --- Telegram/CMakeLists.txt | 2 + .../history_view_voice_record_bar.cpp | 279 ++++++++++++++++++ .../controls/history_view_voice_record_bar.h | 93 ++++++ Telegram/SourceFiles/ui/chat/chat.style | 1 + 4 files changed, 375 insertions(+) create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 85aed68e2..e686508b3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -477,6 +477,8 @@ PRIVATE history/view/controls/compose_controls_common.h history/view/controls/history_view_compose_controls.cpp history/view/controls/history_view_compose_controls.h + history/view/controls/history_view_voice_record_bar.cpp + history/view/controls/history_view_voice_record_bar.h history/view/media/history_view_call.h history/view/media/history_view_call.cpp history/view/media/history_view_contact.h diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp new file mode 100644 index 000000000..9685f5d01 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -0,0 +1,279 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/controls/history_view_voice_record_bar.h" + +#include "api/api_send_progress.h" +#include "core/application.h" +#include "lang/lang_keys.h" +#include "mainwindow.h" +#include "media/audio/media_audio.h" +#include "media/audio/media_audio_capture.h" +#include "styles/style_chat.h" +#include "ui/controls/send_button.h" +#include "ui/text/format_values.h" +#include "window/window_session_controller.h" + +namespace HistoryView::Controls { + +namespace { + +using SendActionUpdate = VoiceRecordBar::SendActionUpdate; +using VoiceToSend = VoiceRecordBar::VoiceToSend; + +constexpr auto kRecordingUpdateDelta = crl::time(100); +constexpr auto kMaxSamples = + ::Media::Player::kDefaultFrequency * AudioVoiceMsgMaxLength; + +[[nodiscard]] auto Duration(int samples) { + return samples / ::Media::Player::kDefaultFrequency; +} + +} // namespace + +VoiceRecordBar::VoiceRecordBar( + not_null parent, + not_null controller, + std::shared_ptr send, + int recorderHeight) +: RpWidget(parent) +, _controller(controller) +, _wrap(std::make_unique(parent)) +, _send(send) +, _cancelFont(st::historyRecordFont) +, _recordCancelWidth(_cancelFont->width(tr::lng_record_cancel(tr::now))) +, _recordingAnimation([=](crl::time now) { + return recordingAnimationCallback(now); +}) { + resize(QSize(parent->width(), recorderHeight)); + init(); +} + +VoiceRecordBar::~VoiceRecordBar() { + if (isRecording()) { + stopRecording(false); + } +} + +void VoiceRecordBar::updateControlsGeometry(QSize size) { + _centerY = size.height() / 2; + { + const auto maxD = st::historyRecordSignalMax * 2; + const auto point = _centerY - st::historyRecordSignalMax; + _redCircleRect = { point, point, maxD, maxD }; + } +} + +void VoiceRecordBar::init() { + hide(); + // Keep VoiceRecordBar behind SendButton. + rpl::single( + ) | rpl::then( + _send->events( + ) | rpl::filter([](not_null e) { + return e->type() == QEvent::ZOrderChange; + }) | rpl::to_empty + ) | rpl::start_with_next([=] { + stackUnder(_send.get()); + }, lifetime()); + + sizeValue( + ) | rpl::start_with_next([=](QSize size) { + updateControlsGeometry(size); + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + p.fillRect(rect(), st::historyComposeAreaBg); + + drawRecording(p, _send->recordActiveRatio()); + }, lifetime()); +} + +void VoiceRecordBar::startRecording() { + using namespace ::Media::Capture; + if (!instance()->available()) { + return; + } + show(); + _recording = true; + + instance()->start(); + instance()->updated( + ) | rpl::start_with_next_error([=](const Update &update) { + recordUpdated(update.level, update.samples); + }, [=] { + stopRecording(false); + }, _recordingLifetime); + + _inField = true; + _controller->widget()->setInnerFocus(); + + update(); + _send->setRecordActive(true); + + _send->events( + ) | rpl::filter([=](not_null e) { + return isTypeRecord() + && (e->type() == QEvent::MouseMove + || e->type() == QEvent::MouseButtonRelease); + }) | rpl::start_with_next([=](not_null e) { + const auto type = e->type(); + if (type == QEvent::MouseMove) { + const auto mouse = static_cast(e.get()); + const auto pos = mapFromGlobal(mouse->globalPos()); + const auto inField = rect().contains(pos); + if (inField != _inField) { + _inField = inField; + _send->setRecordActive(_inField); + } + } else if (type == QEvent::MouseButtonRelease) { + stopRecording(_inField); + } + }, _recordingLifetime); +} + +bool VoiceRecordBar::recordingAnimationCallback(crl::time now) { + const auto dt = anim::Disabled() + ? 1. + : ((now - _recordingAnimation.started()) + / float64(kRecordingUpdateDelta)); + if (dt >= 1.) { + _recordingLevel.finish(); + } else { + _recordingLevel.update(dt, anim::linear); + } + if (!anim::Disabled()) { + update(_redCircleRect); + } + return (dt < 1.); +} + +void VoiceRecordBar::recordUpdated(quint16 level, int samples) { + _recordingLevel.start(level); + _recordingAnimation.start(); + _recordingSamples = samples; + if (samples < 0 || samples >= kMaxSamples) { + stopRecording(samples > 0 && _inField); + } + Core::App().updateNonIdle(); + update(); + _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice }); +} + +void VoiceRecordBar::stopRecording(bool send) { + hide(); + _recording = false; + + using namespace ::Media::Capture; + if (send) { + instance()->stop(crl::guard(this, [=](const Result &data) { + if (data.bytes.isEmpty()) { + return; + } + + Window::ActivateWindow(_controller); + const auto duration = Duration(data.samples); + _sendVoiceRequests.fire({ data.bytes, data.waveform, duration }); + })); + } else { + instance()->stop(); + } + + _recordingLevel = anim::value(); + _recordingAnimation.stop(); + + _recordingLifetime.destroy(); + _recordingSamples = 0; + _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); + + _controller->widget()->setInnerFocus(); + + update(); + _send->setRecordActive(false); +} + +void VoiceRecordBar::drawRecording(Painter &p, float64 recordActive) { + p.setPen(Qt::NoPen); + p.setBrush(st::historyRecordSignalColor); + + { + PainterHighQualityEnabler hq(p); + const auto min = st::historyRecordSignalMin; + const auto max = st::historyRecordSignalMax; + const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.); + const auto radii = qRound(min + (delta * (max - min))); + const auto center = _redCircleRect.center() + QPoint(1, 1); + p.drawEllipse(center, radii, radii); + } + + const auto duration = Ui::FormatDurationText(Duration(_recordingSamples)); + p.setFont(_cancelFont); + p.setPen(st::historyRecordDurationFg); + + const auto durationLeft = _redCircleRect.x() + + _redCircleRect.width() + + st::historyRecordDurationSkip; + const auto durationWidth = _cancelFont->width(duration); + p.drawText( + QRect( + durationLeft, + _redCircleRect.y(), + durationWidth, + _redCircleRect.height()), + style::al_left, + duration); + + const auto leftCancel = durationLeft + + durationWidth + + ((_send->width() - st::historyRecordVoice.width()) / 2); + const auto rightCancel = width() - _send->width(); + + p.setPen( + anim::pen( + st::historyRecordCancel, + st::historyRecordCancelActive, + 1. - recordActive)); + p.drawText( + leftCancel + (rightCancel - leftCancel - _recordCancelWidth) / 2, + st::historyRecordTextTop + _cancelFont->ascent, + tr::lng_record_cancel(tr::now)); +} + +rpl::producer VoiceRecordBar::sendActionUpdates() const { + return _sendActionUpdates.events(); +} + +rpl::producer VoiceRecordBar::sendVoiceRequests() const { + return _sendVoiceRequests.events(); +} + +bool VoiceRecordBar::isRecording() const { + return _recording.current(); +} + +void VoiceRecordBar::finishAnimating() { + _recordingAnimation.stop(); +} + +rpl::producer VoiceRecordBar::recordingStateChanges() const { + return _recording.changes(); +} + +rpl::producer<> VoiceRecordBar::startRecordingRequests() const { + return _send->events( + ) | rpl::filter([=](not_null e) { + return isTypeRecord() && (e->type() == QEvent::MouseButtonPress); + }) | rpl::to_empty; +} + +bool VoiceRecordBar::isTypeRecord() const { + return (_send->type() == Ui::SendButton::Type::Record); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h new file mode 100644 index 000000000..3e2c8ce5d --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -0,0 +1,93 @@ +/* +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 "api/api_common.h" +#include "history/view/controls/compose_controls_common.h" +#include "ui/effects/animations.h" +#include "ui/rp_widget.h" + +namespace Ui { +class SendButton; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace HistoryView::Controls { + +class VoiceRecordBar final : public Ui::RpWidget { +public: + using SendActionUpdate = Controls::SendActionUpdate; + using VoiceToSend = Controls::VoiceToSend; + + void startRecording(); + void finishAnimating(); + + [[nodiscard]] rpl::producer sendActionUpdates() const; + [[nodiscard]] rpl::producer sendVoiceRequests() const; + [[nodiscard]] rpl::producer recordingStateChanges() const; + [[nodiscard]] rpl::producer<> startRecordingRequests() const; + + [[nodiscard]] bool isRecording() const; + + VoiceRecordBar( + not_null parent, + not_null controller, + std::shared_ptr send, + int recorderHeight); + ~VoiceRecordBar(); + +private: + void init(); + + void updateControlsGeometry(QSize size); + + void recordError(); + void recordUpdated(quint16 level, int samples); + + bool recordingAnimationCallback(crl::time now); + void stopRecording(bool send); + + void recordStopCallback(bool active); + void recordUpdateCallback(QPoint globalPos); + + bool showRecordButton() const; + void drawRecording(Painter &p, float64 recordActive); + void updateOverStates(QPoint pos); + + bool isTypeRecord() const; + + const not_null _controller; + const std::unique_ptr _wrap; + const std::shared_ptr _send; + + rpl::event_stream _sendActionUpdates; + rpl::event_stream _sendVoiceRequests; + + int _centerY = 0; + QRect _redCircleRect; + + rpl::variable _recording = false; + bool _inField = false; + int _recordingSamples = 0; + + const style::font &_cancelFont; + int _recordCancelWidth; + + rpl::lifetime _recordingLifetime; + + // This can animate for a very long time (like in music playing), + // so it should be a Basic, not a Simple animation. + Ui::Animations::Basic _recordingAnimation; + anim::value _recordingLevel; + +}; + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 855fd3d94..ed8a05b06 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -339,6 +339,7 @@ historyRecordSignalMax: 12px; historyRecordCancel: windowSubTextFg; historyRecordCancelActive: windowActiveTextFg; historyRecordFont: font(13px); +historyRecordDurationSkip: 12px; historyRecordDurationFg: historyComposeAreaFg; historyRecordTextTop: 14px; From fd76b44dbdf150d91761946e6f853390f9035c67 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 4 Oct 2020 00:46:03 +0300 Subject: [PATCH 057/370] Replaced voice record processing with VoiceRecordBar in ComposeControls. --- .../history_view_compose_controls.cpp | 273 +++--------------- .../controls/history_view_compose_controls.h | 32 +- 2 files changed, 57 insertions(+), 248 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 0020e9944..21c8e5e93 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "history/history.h" #include "history/history_item.h" +#include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/history_view_webpage_preview.h" #include "inline_bots/inline_results_widget.h" #include "lang/lang_keys.h" @@ -57,6 +58,7 @@ using MessageToEdit = ComposeControls::MessageToEdit; using VoiceToSend = ComposeControls::VoiceToSend; using SendActionUpdate = ComposeControls::SendActionUpdate; using SetHistoryArgs = ComposeControls::SetHistoryArgs; +using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar; [[nodiscard]] auto ShowWebPagePreview(WebPageData *page) { return page && (page->pendingTill >= 0); @@ -500,7 +502,7 @@ ComposeControls::ComposeControls( , _mode(mode) , _wrap(std::make_unique(parent)) , _writeRestricted(std::make_unique(parent)) -, _send(Ui::CreateChild(_wrap.get())) +, _send(std::make_shared(_wrap.get())) , _attachToggle(Ui::CreateChild( _wrap.get(), st::historyAttach)) @@ -516,18 +518,16 @@ ComposeControls::ComposeControls( , _header(std::make_unique( _wrap.get(), &_window->session().data())) -, _textUpdateEvents(TextUpdateEvent::SendTyping) -, _recordCancelWidth(st::historyRecordFont->width(tr::lng_record_cancel(tr::now))) -, _recordingAnimation([=](crl::time now) { - return recordingAnimationCallback(now); -}) { +, _voiceRecordBar(std::make_unique( + _wrap.get(), + window, + _send, + st::historySendSize.height())) +, _textUpdateEvents(TextUpdateEvent::SendTyping) { init(); } ComposeControls::~ComposeControls() { - if (_recording) { - stopRecording(false); - } setTabbedPanel(nullptr); } @@ -579,91 +579,13 @@ int ComposeControls::heightCurrent() const { } bool ComposeControls::focus() { - if (_recording) { + if (_voiceRecordBar->isRecording()) { return false; } _field->setFocus(); return true; } -void ComposeControls::updateControlsVisibility() { - if (_recording) { - _field->hide(); - _tabbedSelectorToggle->hide(); - //_botKeyboardShow->hide(); - //_botKeyboardHide->hide(); - //_botCommandStart->hide(); - _attachToggle->hide(); - //if (_silent) { - // _silent->hide(); - //} - //if (_scheduled) { - // _scheduled->hide(); - //} - //if (_kbShown) { - // _kbScroll->show(); - //} else { - // _kbScroll->hide(); - //} - } else { - _field->show(); - //if (_kbShown) { - // _kbScroll->show(); - // _tabbedSelectorToggle->hide(); - // _botKeyboardHide->show(); - // _botKeyboardShow->hide(); - // _botCommandStart->hide(); - //} else if (_kbReplyTo) { - // _kbScroll->hide(); - // _tabbedSelectorToggle->show(); - // _botKeyboardHide->hide(); - // _botKeyboardShow->hide(); - // _botCommandStart->hide(); - //} else { - // _kbScroll->hide(); - // _tabbedSelectorToggle->show(); - // _botKeyboardHide->hide(); - // if (_keyboard->hasMarkup()) { - // _botKeyboardShow->show(); - // _botCommandStart->hide(); - // } else { - // _botKeyboardShow->hide(); - // if (_cmdStartShown) { - // _botCommandStart->show(); - // } else { - // _botCommandStart->hide(); - // } - // } - //} - _tabbedSelectorToggle->show(); - _attachToggle->show(); - //if (_silent) { - // _silent->show(); - //} - //if (_scheduled) { - // _scheduled->show(); - //} - //updateFieldPlaceholder(); - } - -} - -bool ComposeControls::recordingAnimationCallback(crl::time now) { - const auto dt = anim::Disabled() - ? 1. - : ((now - _recordingAnimation.started()) - / float64(kRecordingUpdateDelta)); - if (dt >= 1.) { - _recordingLevel.finish(); - } else { - _recordingLevel.update(dt, anim::linear); - } - if (!anim::Disabled()) { - _wrap->update(_attachToggle->geometry()); - } - return (dt < 1.); -} - rpl::producer<> ComposeControls::cancelRequests() const { return _cancelRequests.events(); } @@ -693,7 +615,7 @@ rpl::producer<> ComposeControls::sendRequests() const { } rpl::producer ComposeControls::sendVoiceRequests() const { - return _sendVoiceRequests.events(); + return _voiceRecordBar->sendVoiceRequests(); } rpl::producer ComposeControls::editRequests() const { @@ -792,6 +714,7 @@ void ComposeControls::init() { initTabbedSelector(); initSendButton(); initWriteRestriction(); + initVoiceRecordBar(); _wrap->sizeValue( ) | rpl::start_with_next([=](QSize size) { @@ -841,99 +764,12 @@ void ComposeControls::init() { cancelEditMessage(); }, _wrap->lifetime()); } + + orderControls(); } -void ComposeControls::recordDone( - QByteArray result, - VoiceWaveform waveform, - int samples) { - if (result.isEmpty()) { - return; - } - - Window::ActivateWindow(_window); - const auto duration = samples / ::Media::Player::kDefaultFrequency; - _sendVoiceRequests.fire({ result, waveform, duration }); -} - -void ComposeControls::recordUpdated(quint16 level, int samples) { - if (!_recording) { - return; - } - - _recordingLevel.start(level); - _recordingAnimation.start(); - _recordingSamples = samples; - if (samples < 0 || samples >= ::Media::Player::kDefaultFrequency * AudioVoiceMsgMaxLength) { - stopRecording(samples > 0 && _inField); - } - Core::App().updateNonIdle(); - _wrap->update(); - _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice }); -} - -void ComposeControls::recordStartCallback() { - using namespace ::Media::Capture; - const auto error = _history - ? Data::RestrictionError(_history->peer, ChatRestriction::f_send_media) - : std::nullopt; - if (error) { - Ui::show(Box(*error)); - return; - } else if (_showSlowmodeError && _showSlowmodeError()) { - return; - } else if (!instance()->available()) { - return; - } - - instance()->start(); - instance()->updated( - ) | rpl::start_with_next_error([=](const Update &update) { - recordUpdated(update.level, update.samples); - }, [=] { - stopRecording(false); - }, _recordingLifetime); - - _recording = _inField = true; - updateControlsVisibility(); - _window->widget()->setInnerFocus(); - - _wrap->update(); - - _send->setRecordActive(true); -} - -void ComposeControls::recordStopCallback(bool active) { - stopRecording(active); -} - -void ComposeControls::recordUpdateCallback(QPoint globalPos) { - updateOverStates(_wrap->mapFromGlobal(globalPos)); -} - -void ComposeControls::stopRecording(bool send) { - if (send) { - ::Media::Capture::instance()->stop(crl::guard(_wrap.get(), [=]( - const ::Media::Capture::Result &result) { - recordDone(result.bytes, result.waveform, result.samples); - })); - } else { - ::Media::Capture::instance()->stop(); - } - - _recordingLevel = anim::value(); - _recordingAnimation.stop(); - - _recordingLifetime.destroy(); - _recording = false; - _recordingSamples = 0; - _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); - - updateControlsVisibility(); - _window->widget()->setInnerFocus(); - - _wrap->update(); - _send->setRecordActive(false); +void ComposeControls::orderControls() { + _send->raise(); } bool ComposeControls::showRecordButton() const { @@ -943,30 +779,6 @@ bool ComposeControls::showRecordButton() const { && !isEditingMessage(); } -void ComposeControls::drawRecording(Painter &p, float64 recordActive) { - p.setPen(Qt::NoPen); - p.setBrush(st::historyRecordSignalColor); - - auto delta = qMin(_recordingLevel.current() / 0x4000, 1.); - auto d = 2 * qRound(st::historyRecordSignalMin + (delta * (st::historyRecordSignalMax - st::historyRecordSignalMin))); - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(_attachToggle->x() + (_tabbedSelectorToggle->width() - d) / 2, _attachToggle->y() + (_attachToggle->height() - d) / 2, d, d); - } - - auto duration = Ui::FormatDurationText(_recordingSamples / ::Media::Player::kDefaultFrequency); - p.setFont(st::historyRecordFont); - - p.setPen(st::historyRecordDurationFg); - p.drawText(_attachToggle->x() + _tabbedSelectorToggle->width(), _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, duration); - - int32 left = _attachToggle->x() + _tabbedSelectorToggle->width() + st::historyRecordFont->width(duration) + ((_send->width() - st::historyRecordVoice.width()) / 2); - int32 right = _wrap->width() - _send->width(); - - p.setPen(anim::pen(st::historyRecordCancel, st::historyRecordCancelActive, 1. - recordActive)); - p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, tr::lng_record_cancel(tr::now)); -} - void ComposeControls::drawRestrictedWrite(Painter &p, const QString &error) { p.fillRect(_writeRestricted->rect(), st::historyReplyBg); @@ -1023,7 +835,9 @@ void ComposeControls::fieldChanged() { } rpl::producer ComposeControls::sendActionUpdates() const { - return _sendActionUpdates.events(); + return rpl::merge( + _sendActionUpdates.events(), + _voiceRecordBar->sendActionUpdates()); } void ComposeControls::initTabbedSelector() { @@ -1093,6 +907,29 @@ void ComposeControls::initWriteRestriction() { }, _wrap->lifetime()); } +void ComposeControls::initVoiceRecordBar() { + _voiceRecordBar->recordingStateChanges( + ) | rpl::start_with_next([=](bool active) { + _field->setVisible(!active); + }, _wrap->lifetime()); + + _voiceRecordBar->startRecordingRequests( + ) | rpl::start_with_next([=] { + const auto error = _history + ? Data::RestrictionError( + _history->peer, + ChatRestriction::f_send_media) + : std::nullopt; + if (error) { + Ui::show(Box(*error)); + return; + } else if (_showSlowmodeError && _showSlowmodeError()) { + return; + } + _voiceRecordBar->startRecording(); + }, _wrap->lifetime()); +} + void ComposeControls::updateWrappingVisibility() { const auto restricted = _writeRestriction.current().has_value(); _writeRestricted->setVisible(restricted); @@ -1124,16 +961,11 @@ void ComposeControls::updateSendButtonType() { _send->setSlowmodeDelay(delay); _send->setDisabled(_sendDisabledBySlowmode.current() && (type == Type::Send || type == Type::Record)); - - _send->setRecordStartCallback([=] { recordStartCallback(); }); - _send->setRecordStopCallback([=](bool active) { recordStopCallback(active); }); - _send->setRecordUpdateCallback([=](QPoint globalPos) { recordUpdateCallback(globalPos); }); - _send->setRecordAnimationCallback([=] { _wrap->update(); }); } void ComposeControls::finishAnimating() { _send->finishAnimating(); - _recordingAnimation.stop(); + _voiceRecordBar->finishAnimating(); } void ComposeControls::updateControlsGeometry(QSize size) { @@ -1165,6 +997,11 @@ void ComposeControls::updateControlsGeometry(QSize size) { _send->moveToRight(right, buttonsTop); right += _send->width(); _tabbedSelectorToggle->moveToRight(right, buttonsTop); + + _voiceRecordBar->resizeToWidth(size.width()); + _voiceRecordBar->moveToLeft( + 0, + size.height() - _voiceRecordBar->height()); } void ComposeControls::updateOuterGeometry(QRect rect) { @@ -1178,24 +1015,10 @@ void ComposeControls::updateOuterGeometry(QRect rect) { } } -void ComposeControls::updateOverStates(QPoint pos) { - const auto inField = _wrap->rect().contains(pos); - if (inField != _inField && _recording) { - _inField = inField; - _send->setRecordActive(_inField); - } -} - void ComposeControls::paintBackground(QRect clip) { Painter p(_wrap.get()); p.fillRect(clip, st::historyComposeAreaBg); - if (!_field->isHidden() || _recording) { - //drawField(p, clip); - if (!_send->isHidden() && _recording) { - drawRecording(p, _send->recordActiveRatio()); - } - } } void ComposeControls::escape() { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 0bea19106..3d642ab5d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -56,6 +56,10 @@ enum class SendProgressType; namespace HistoryView { +namespace Controls { +class VoiceRecordBar; +} // namespace Controls + class FieldHeader; class ComposeControls final { @@ -145,6 +149,7 @@ private: void initSendButton(); void initWebpageProcess(); void initWriteRestriction(); + void initVoiceRecordBar(); void updateSendButtonType(); void updateHeight(); void updateWrappingVisibility(); @@ -153,6 +158,8 @@ private: void updateOuterGeometry(QRect rect); void paintBackground(QRect clip); + void orderControls(); + void escape(); void fieldChanged(); void toggleTabbedSelectorMode(); @@ -161,18 +168,7 @@ private: void setTextFromEditingMessage(not_null item); - void recordUpdated(quint16 level, int samples); - void recordDone(QByteArray result, VoiceWaveform waveform, int samples); - - bool recordingAnimationCallback(crl::time now); - void stopRecording(bool send); - - void recordStartCallback(); - void recordStopCallback(bool active); - void recordUpdateCallback(QPoint globalPos); - bool showRecordButton() const; - void drawRecording(Painter &p, float64 recordActive); void drawRestrictedWrite(Painter &p, const QString &error); void updateOverStates(QPoint pos); @@ -188,7 +184,7 @@ private: const std::unique_ptr _wrap; const std::unique_ptr _writeRestricted; - const not_null _send; + const std::shared_ptr _send; const not_null _attachToggle; const not_null _tabbedSelectorToggle; const not_null _field; @@ -197,32 +193,22 @@ private: friend class FieldHeader; const std::unique_ptr _header; + const std::unique_ptr _voiceRecordBar; rpl::event_stream<> _cancelRequests; rpl::event_stream _fileChosen; rpl::event_stream _photoChosen; rpl::event_stream _inlineResultChosen; rpl::event_stream _sendActionUpdates; - rpl::event_stream _sendVoiceRequests; TextWithTags _localSavedText; TextUpdateEvents _textUpdateEvents; - bool _recording = false; - bool _inField = false; //bool _inReplyEditForward = false; //bool _inClickable = false; - int _recordingSamples = 0; - int _recordCancelWidth; - rpl::lifetime _recordingLifetime; rpl::lifetime _uploaderSubscriptions; - // This can animate for a very long time (like in music playing), - // so it should be a Basic, not a Simple animation. - Ui::Animations::Basic _recordingAnimation; - anim::value _recordingLevel; - Fn _raiseEmojiSuggestions; }; From db564ca486cd73dec529a6bb3b537a34d38fd04e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 4 Oct 2020 02:25:25 +0300 Subject: [PATCH 058/370] Replaced voice record processing with VoiceRecordBar in HistoryWidget. --- Telegram/SourceFiles/config.h | 3 - .../SourceFiles/history/history_widget.cpp | 310 ++++++------------ Telegram/SourceFiles/history/history_widget.h | 29 +- .../history_view_voice_record_bar.cpp | 3 +- 4 files changed, 103 insertions(+), 242 deletions(-) diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index f9b9f496d..40ee00143 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -29,9 +29,6 @@ enum { LinksOverviewPerPage = 12, MediaOverviewStartPerPage = 5, - AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes - AudioVoiceMsgChannels = 2, // stereo - PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request SearchPeopleLimit = 5, diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 71e7780e9..2cdfee1ac 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -62,6 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_inner_widget.h" #include "history/history_item_components.h" //#include "history/feed/history_feed_section.h" // #feed +#include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_element.h" #include "history/view/history_view_scheduled_section.h" @@ -176,7 +177,7 @@ HistoryWidget::HistoryWidget( , _supportAutocomplete(session().supportMode() ? object_ptr(this, &session()) : nullptr) -, _send(this) +, _send(std::make_shared(this)) , _unblock(this, tr::lng_unblock_button(tr::now).toUpper(), st::historyUnblock) , _botStart(this, tr::lng_bot_start(tr::now).toUpper(), st::historyComposeButton) , _joinChannel( @@ -192,15 +193,16 @@ HistoryWidget::HistoryWidget( , _botKeyboardShow(this, st::historyBotKeyboardShow) , _botKeyboardHide(this, st::historyBotKeyboardHide) , _botCommandStart(this, st::historyBotCommandStart) +, _voiceRecordBar(std::make_unique( + this, + controller, + _send, + st::historySendSize.height())) , _field( this, st::historyComposeField, Ui::InputField::Mode::MultiLine, tr::lng_message_ph()) -, _recordCancelWidth(st::historyRecordFont->width(tr::lng_record_cancel(tr::now))) -, _recordingAnimation([=](crl::time now) { - return recordingAnimationCallback(now); -}) , _kbScroll(this, st::botKbScroll) , _topShadow(this) { setAcceptDrops(true); @@ -217,7 +219,7 @@ HistoryWidget::HistoryWidget( _send->addClickHandler([=] { sendButtonClicked(); }); SendMenu::SetupMenuAndShortcuts( - _send, + _send.get(), [=] { return sendButtonMenuType(); }, [=] { sendSilent(); }, [=] { sendScheduled(); }); @@ -349,10 +351,7 @@ HistoryWidget::HistoryWidget( _joinChannel->hide(); _muteUnmute->hide(); - _send->setRecordStartCallback([this] { recordStartCallback(); }); - _send->setRecordStopCallback([this](bool active) { recordStopCallback(active); }); - _send->setRecordUpdateCallback([this](QPoint globalPos) { recordUpdateCallback(globalPos); }); - _send->setRecordAnimationCallback([this] { updateField(); }); + initVoiceRecordBar(); _attachToggle->hide(); _tabbedSelectorToggle->hide(); @@ -717,6 +716,48 @@ void HistoryWidget::refreshTabbedPanel() { } } +void HistoryWidget::initVoiceRecordBar() { + _voiceRecordBar->startRecordingRequests( + ) | rpl::start_with_next([=] { + const auto error = _peer + ? Data::RestrictionError(_peer, ChatRestriction::f_send_media) + : std::nullopt; + if (error) { + Ui::show(Box(*error)); + return; + } else if (showSlowmodeError()) { + return; + } + _voiceRecordBar->startRecording(); + }, lifetime()); + + _voiceRecordBar->sendActionUpdates( + ) | rpl::start_with_next([=](const auto &data) { + if (!_history) { + return; + } + session().sendProgressManager().update( + _history, + data.type, + data.progress); + }, lifetime()); + + _voiceRecordBar->sendVoiceRequests( + ) | rpl::start_with_next([=](const auto &data) { + if (!canWriteMessage() || data.bytes.isEmpty() || !_history) { + return; + } + + auto action = Api::SendAction(_history); + action.replyTo = replyToId(); + session().api().sendVoiceMessage( + data.bytes, + data.waveform, + data.duration, + action); + }, lifetime()); +} + void HistoryWidget::initTabbedSelector() { refreshTabbedPanel(); @@ -1145,6 +1186,7 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { } void HistoryWidget::orderWidgets() { + _send->raise(); if (_contactStatus) { _contactStatus->raise(); } @@ -1343,6 +1385,10 @@ void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraf } } +bool HistoryWidget::isRecording() const { + return _voiceRecordBar->isRecording(); +} + void HistoryWidget::activate() { if (_history) { if (!_historyInited) { @@ -1360,7 +1406,7 @@ void HistoryWidget::setInnerFocus() { } else if (_list) { if (_nonEmptySelection || (_list && _list->wasSelectedText()) - || _recording + || isRecording() || isBotStart() || isBlocked() || !_canSendMessages) { @@ -1371,41 +1417,6 @@ void HistoryWidget::setInnerFocus() { } } -void HistoryWidget::recordDone( - QByteArray result, - VoiceWaveform waveform, - int samples) { - if (!canWriteMessage() || result.isEmpty()) { - return; - } - - Window::ActivateWindow(controller()); - const auto duration = samples / Media::Player::kDefaultFrequency; - auto action = Api::SendAction(_history); - action.replyTo = replyToId(); - session().api().sendVoiceMessage(result, waveform, duration, action); -} - -void HistoryWidget::recordUpdate(ushort level, int samples) { - if (!_recording) { - return; - } - - _recordingLevel.start(level); - _recordingAnimation.start(); - _recordingSamples = samples; - if (samples < 0 || samples >= Media::Player::kDefaultFrequency * AudioVoiceMsgMaxLength) { - stopRecording(_peer && samples > 0 && _inField); - } - Core::App().updateNonIdle(); - updateField(); - if (_history) { - session().sendProgressManager().update( - _history, - Api::SendProgressType::RecordVoice); - } -} - bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) { if (samePeerBot) { if (_history) { @@ -2125,63 +2136,45 @@ void HistoryWidget::updateControlsVisibility() { _muteUnmute->hide(); _send->show(); updateSendButtonType(); - if (_recording) { - _field->hide(); + + _field->show(); + if (_kbShown) { + _kbScroll->show(); _tabbedSelectorToggle->hide(); + _botKeyboardHide->show(); + _botKeyboardShow->hide(); + _botCommandStart->hide(); + } else if (_kbReplyTo) { + _kbScroll->hide(); + _tabbedSelectorToggle->show(); + _botKeyboardHide->hide(); _botKeyboardShow->hide(); - _botKeyboardHide->hide(); _botCommandStart->hide(); - _attachToggle->hide(); - if (_silent) { - _silent->hide(); - } - if (_scheduled) { - _scheduled->hide(); - } - if (_kbShown) { - _kbScroll->show(); - } else { - _kbScroll->hide(); - } } else { - _field->show(); - if (_kbShown) { - _kbScroll->show(); - _tabbedSelectorToggle->hide(); - _botKeyboardHide->show(); - _botKeyboardShow->hide(); - _botCommandStart->hide(); - } else if (_kbReplyTo) { - _kbScroll->hide(); - _tabbedSelectorToggle->show(); - _botKeyboardHide->hide(); - _botKeyboardShow->hide(); + _kbScroll->hide(); + _tabbedSelectorToggle->show(); + _botKeyboardHide->hide(); + if (_keyboard->hasMarkup()) { + _botKeyboardShow->show(); _botCommandStart->hide(); } else { - _kbScroll->hide(); - _tabbedSelectorToggle->show(); - _botKeyboardHide->hide(); - if (_keyboard->hasMarkup()) { - _botKeyboardShow->show(); - _botCommandStart->hide(); + _botKeyboardShow->hide(); + if (_cmdStartShown) { + _botCommandStart->show(); } else { - _botKeyboardShow->hide(); - if (_cmdStartShown) { - _botCommandStart->show(); - } else { - _botCommandStart->hide(); - } + _botCommandStart->hide(); } } - _attachToggle->show(); - if (_silent) { - _silent->show(); - } - if (_scheduled) { - _scheduled->show(); - } - updateFieldPlaceholder(); } + _attachToggle->show(); + if (_silent) { + _silent->show(); + } + if (_scheduled) { + _scheduled->show(); + } + updateFieldPlaceholder(); + if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); @@ -3275,22 +3268,6 @@ void HistoryWidget::unreadMentionsAnimationFinish() { updateUnreadMentionsPosition(); } -bool HistoryWidget::recordingAnimationCallback(crl::time now) { - const auto dt = anim::Disabled() - ? 1. - : ((now - _recordingAnimation.started()) - / float64(kRecordingUpdateDelta)); - if (dt >= 1.) { - _recordingLevel.finish(); - } else { - _recordingLevel.update(dt, anim::linear); - } - if (!anim::Disabled()) { - update(_attachToggle->geometry()); - } - return (dt < 1.); -} - void HistoryWidget::chooseAttach() { if (_editMsgId) { Ui::show(Box(tr::lng_edit_caption_attach(tr::now))); @@ -3362,13 +3339,8 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) { } void HistoryWidget::updateOverStates(QPoint pos) { - auto inField = pos.y() >= (_scroll->y() + _scroll->height()) && pos.y() < height() && pos.x() >= 0 && pos.x() < width(); auto inReplyEditForward = QRect(st::historyReplySkip, _field->y() - st::historySendPadding - st::historyReplyHeight, width() - st::historyReplySkip - _fieldBarCancel->width(), st::historyReplyHeight).contains(pos) && (_editMsgId || replyToId() || readyToForward()); auto inClickable = inReplyEditForward; - if (inField != _inField && _recording) { - _inField = inField; - _send->setRecordActive(_inField); - } _inReplyEditForward = inReplyEditForward; if (inClickable != _inClickable) { _inClickable = inClickable; @@ -3382,85 +3354,11 @@ void HistoryWidget::leaveToChildEvent(QEvent *e, QWidget *child) { // e -- from } } -void HistoryWidget::recordStartCallback() { - using namespace Media::Capture; - - const auto error = _peer - ? Data::RestrictionError(_peer, ChatRestriction::f_send_media) - : std::nullopt; - if (error) { - Ui::show(Box(*error)); - return; - } else if (showSlowmodeError()) { - return; - } else if (!instance()->available()) { - return; - } - - instance()->start(); - instance()->updated( - ) | rpl::start_with_next_error([=](const Update &update) { - recordUpdate(update.level, update.samples); - }, [=] { - stopRecording(false); - }, _recordingLifetime); - - _recording = _inField = true; - updateControlsVisibility(); - activate(); - - updateField(); - - _send->setRecordActive(true); -} - -void HistoryWidget::recordStopCallback(bool active) { - stopRecording(_peer && active); -} - -void HistoryWidget::recordUpdateCallback(QPoint globalPos) { - updateOverStates(mapFromGlobal(globalPos)); -} - void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) { if (_replyForwardPressed) { _replyForwardPressed = false; update(0, _field->y() - st::historySendPadding - st::historyReplyHeight, width(), st::historyReplyHeight); } - if (_recording) { - stopRecording(_peer && _inField); - } -} - -void HistoryWidget::stopRecording(bool send) { - if (send) { - const auto weak = Ui::MakeWeak(this); - Media::Capture::instance()->stop(crl::guard(this, [=]( - const Media::Capture::Result &result) { - recordDone(result.bytes, result.waveform, result.samples); - })); - } else { - Media::Capture::instance()->stop(); - } - - _recordingLevel = anim::value(); - _recordingAnimation.stop(); - - _recordingLifetime.destroy(); - _recording = false; - _recordingSamples = 0; - if (_history) { - session().sendProgressManager().update( - _history, - Api::SendProgressType::RecordVoice, - -1); - } - - updateControlsVisibility(); - activate(); - - updateField(); - _send->setRecordActive(false); } void HistoryWidget::sendBotCommand( @@ -3971,6 +3869,7 @@ void HistoryWidget::moveFieldControls() { _field->moveToLeft(left, bottom - _field->height() - st::historySendPadding); auto right = st::historySendRight; _send->moveToRight(right, buttonsBottom); right += _send->width(); + _voiceRecordBar->moveToLeft(0, bottom - _voiceRecordBar->height()); _tabbedSelectorToggle->moveToRight(right, buttonsBottom); _botKeyboardHide->moveToRight(right, buttonsBottom); right += _botKeyboardHide->width(); _botKeyboardShow->moveToRight(right, buttonsBottom); @@ -4445,6 +4344,7 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { void HistoryWidget::updateControlsGeometry() { _topBar->resizeToWidth(width()); _topBar->moveToLeft(0, 0); + _voiceRecordBar->resizeToWidth(width()); moveFieldControls(); @@ -5571,7 +5471,7 @@ void HistoryWidget::editMessage(not_null item) { } } - if (_recording) { + if (isRecording()) { // Just fix some strange inconsistency. _send->clearState(); } @@ -6070,7 +5970,7 @@ void HistoryWidget::updateTopBarSelection() { if (!Ui::isLayerShown() && !Core::App().passcodeLocked()) { if (_nonEmptySelection || (_list && _list->wasSelectedText()) - || _recording + || isRecording() || isBotStart() || isBlocked() || !_canSendMessages) { @@ -6097,7 +5997,7 @@ void HistoryWidget::updateReplyEditText(not_null item) { st::messageTextStyle, item->inReplyText(), Ui::DialogTextOptions()); - if (!_field->isHidden() || _recording) { + if (!_field->isHidden() || isRecording()) { _fieldBarCancel->show(); updateMouseTracking(); } @@ -6400,29 +6300,6 @@ void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int } } -void HistoryWidget::drawRecording(Painter &p, float64 recordActive) { - p.setPen(Qt::NoPen); - p.setBrush(st::historyRecordSignalColor); - - auto delta = qMin(_recordingLevel.current() / 0x4000, 1.); - auto d = 2 * qRound(st::historyRecordSignalMin + (delta * (st::historyRecordSignalMax - st::historyRecordSignalMin))); - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(_attachToggle->x() + (_tabbedSelectorToggle->width() - d) / 2, _attachToggle->y() + (_attachToggle->height() - d) / 2, d, d); - } - - auto duration = Ui::FormatDurationText(_recordingSamples / Media::Player::kDefaultFrequency); - p.setFont(st::historyRecordFont); - - p.setPen(st::historyRecordDurationFg); - p.drawText(_attachToggle->x() + _tabbedSelectorToggle->width(), _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, duration); - - int32 left = _attachToggle->x() + _tabbedSelectorToggle->width() + st::historyRecordFont->width(duration) + ((_send->width() - st::historyRecordVoice.width()) / 2); - int32 right = width() - _send->width(); - - p.setPen(anim::pen(st::historyRecordCancel, st::historyRecordCancelActive, 1. - recordActive)); - p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, tr::lng_record_cancel(tr::now)); -} // //void HistoryWidget::drawPinnedBar(Painter &p) { // //if (_pinnedBar->msg) { @@ -6478,11 +6355,8 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { Painter p(this); const auto clip = e->rect(); if (_list) { - if (!_field->isHidden() || _recording) { + if (!_field->isHidden() || isRecording()) { drawField(p, clip); - if (!_send->isHidden() && _recording) { - drawRecording(p, _send->recordActiveRatio()); - } } else if (const auto error = writeRestriction()) { drawRestrictedWrite(p, *error); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index b2b1220b4..ecd664f6d 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -94,6 +94,9 @@ class TopBarWidget; class ContactStatus; class Element; class PinnedTracker; +namespace Controls { +class VoiceRecordBar; +} // namespace Controls } // namespace HistoryView class DragArea; @@ -108,6 +111,7 @@ class HistoryWidget final : public Window::AbstractSectionWidget { public: using FieldHistoryAction = Ui::InputField::HistoryAction; + using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar; HistoryWidget( QWidget *parent, @@ -201,9 +205,6 @@ public: void updatePreview(); void previewCancel(); - bool recordingAnimationCallback(crl::time now); - void stopRecording(bool send); - void escape(); void sendBotCommand( @@ -345,6 +346,7 @@ private: friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; void initTabbedSelector(); + void initVoiceRecordBar(); void refreshTabbedPanel(); void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); @@ -395,11 +397,6 @@ private: void animationCallback(); void updateOverStates(QPoint pos); - void recordDone(QByteArray result, VoiceWaveform waveform, int samples); - void recordUpdate(ushort level, int samples); - void recordStartCallback(); - void recordStopCallback(bool active); - void recordUpdateCallback(QPoint globalPos); void chooseAttach(); void historyDownAnimationFinish(); void unreadMentionsAnimationFinish(); @@ -495,7 +492,6 @@ private: const QRect &rect, int left, int top) const; - void drawRecording(Painter &p, float64 recordActive); void drawRestrictedWrite(Painter &p, const QString &error); bool paintShowAnimationFrame(); @@ -566,6 +562,8 @@ private: void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result); void inlineBotResolveFail(const RPCError &error, const QString &username); + bool isRecording() const; + bool isBotStart() const; bool isBlocked() const; bool isJoinChannel() const; @@ -673,7 +671,7 @@ private: std::unique_ptr _contactStatus; - object_ptr _send; + const std::shared_ptr _send; object_ptr _unblock; object_ptr _botStart; object_ptr _joinChannel; @@ -685,20 +683,11 @@ private: object_ptr _botCommandStart; object_ptr _silent = { nullptr }; object_ptr _scheduled = { nullptr }; + const std::unique_ptr _voiceRecordBar; bool _cmdStartShown = false; object_ptr _field; - bool _recording = false; - bool _inField = false; bool _inReplyEditForward = false; bool _inClickable = false; - int _recordingSamples = 0; - int _recordCancelWidth; - rpl::lifetime _recordingLifetime; - - // This can animate for a very long time (like in music playing), - // so it should be a Basic, not a Simple animation. - Ui::Animations::Basic _recordingAnimation; - anim::value _recordingLevel; bool kbWasHidden() const; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 9685f5d01..6f52b02d4 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -26,8 +26,9 @@ using SendActionUpdate = VoiceRecordBar::SendActionUpdate; using VoiceToSend = VoiceRecordBar::VoiceToSend; constexpr auto kRecordingUpdateDelta = crl::time(100); +constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes constexpr auto kMaxSamples = - ::Media::Player::kDefaultFrequency * AudioVoiceMsgMaxLength; + ::Media::Player::kDefaultFrequency * kAudioVoiceMaxLength; [[nodiscard]] auto Duration(int samples) { return samples / ::Media::Player::kDefaultFrequency; From 3e4866d3b7312740fe1e1475c5340c3a4958fd6b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 4 Oct 2020 16:32:45 +0300 Subject: [PATCH 059/370] Moved active animation processing from SendButton to VoiceRecordBar. --- .../history_view_voice_record_bar.cpp | 42 ++++++++++++++----- .../controls/history_view_voice_record_bar.h | 6 ++- .../SourceFiles/ui/controls/send_button.cpp | 25 ++++------- .../SourceFiles/ui/controls/send_button.h | 7 ++-- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 6f52b02d4..0a42498b1 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -92,8 +92,28 @@ void VoiceRecordBar::init() { Painter p(this); p.fillRect(rect(), st::historyComposeAreaBg); - drawRecording(p, _send->recordActiveRatio()); + drawRecording(p, activeAnimationRatio()); }, lifetime()); + + _inField.changes( + ) | rpl::start_with_next([=](bool value) { + activeAnimate(value); + }, lifetime()); +} + +void VoiceRecordBar::activeAnimate(bool active) { + const auto to = active ? 1. : 0.; + const auto duration = st::historyRecordVoiceDuration; + if (_activeAnimation.animating()) { + _activeAnimation.change(to, duration); + } else { + auto callback = [=] { + update(); + _send->requestPaintRecord(activeAnimationRatio()); + }; + const auto from = active ? 0. : 1.; + _activeAnimation.start(std::move(callback), from, to, duration); + } } void VoiceRecordBar::startRecording() { @@ -116,7 +136,7 @@ void VoiceRecordBar::startRecording() { _controller->widget()->setInnerFocus(); update(); - _send->setRecordActive(true); + activeAnimate(true); _send->events( ) | rpl::filter([=](not_null e) { @@ -127,14 +147,9 @@ void VoiceRecordBar::startRecording() { const auto type = e->type(); if (type == QEvent::MouseMove) { const auto mouse = static_cast(e.get()); - const auto pos = mapFromGlobal(mouse->globalPos()); - const auto inField = rect().contains(pos); - if (inField != _inField) { - _inField = inField; - _send->setRecordActive(_inField); - } + _inField = rect().contains(mapFromGlobal(mouse->globalPos())); } else if (type == QEvent::MouseButtonRelease) { - stopRecording(_inField); + stopRecording(_inField.current()); } }, _recordingLifetime); } @@ -160,7 +175,7 @@ void VoiceRecordBar::recordUpdated(quint16 level, int samples) { _recordingAnimation.start(); _recordingSamples = samples; if (samples < 0 || samples >= kMaxSamples) { - stopRecording(samples > 0 && _inField); + stopRecording(samples > 0 && _inField.current()); } Core::App().updateNonIdle(); update(); @@ -189,6 +204,8 @@ void VoiceRecordBar::stopRecording(bool send) { _recordingLevel = anim::value(); _recordingAnimation.stop(); + _inField = false; + _recordingLifetime.destroy(); _recordingSamples = 0; _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); @@ -196,7 +213,6 @@ void VoiceRecordBar::stopRecording(bool send) { _controller->widget()->setInnerFocus(); update(); - _send->setRecordActive(false); } void VoiceRecordBar::drawRecording(Painter &p, float64 recordActive) { @@ -277,4 +293,8 @@ bool VoiceRecordBar::isTypeRecord() const { return (_send->type() == Ui::SendButton::Type::Record); } +float64 VoiceRecordBar::activeAnimationRatio() const { + return _activeAnimation.value(_inField.current() ? 1. : 0.); +} + } // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 3e2c8ce5d..f33a1dc27 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -64,6 +64,9 @@ private: bool isTypeRecord() const; + void activeAnimate(bool active); + float64 activeAnimationRatio() const; + const not_null _controller; const std::unique_ptr _wrap; const std::shared_ptr _send; @@ -75,7 +78,7 @@ private: QRect _redCircleRect; rpl::variable _recording = false; - bool _inField = false; + rpl::variable _inField = false; int _recordingSamples = 0; const style::font &_cancelFont; @@ -86,6 +89,7 @@ private: // This can animate for a very long time (like in music playing), // so it should be a Basic, not a Simple animation. Ui::Animations::Basic _recordingAnimation; + Ui::Animations::Simple _activeAnimation; anim::value _recordingLevel; }; diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index 3c7df9e84..e7ed3e87a 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -43,20 +43,7 @@ void SendButton::setType(Type type) { update(); } if (_type != Type::Record) { - _recordActive = false; - _a_recordActive.stop(); - } -} - -void SendButton::setRecordActive(bool recordActive) { - if (_recordActive != recordActive) { - _recordActive = recordActive; - _a_recordActive.start( - [=] { recordAnimationCallback(); }, - _recordActive ? 0. : 1., - _recordActive ? 1. : 0, - st::historyRecordVoiceDuration); - update(); + _recordProgress = 0.; } } @@ -76,7 +63,6 @@ void SendButton::setSlowmodeDelay(int seconds) { void SendButton::finishAnimating() { _a_typeChanged.stop(); - _a_recordActive.stop(); update(); } @@ -89,6 +75,13 @@ void SendButton::mouseMoveEvent(QMouseEvent *e) { } } +void SendButton::requestPaintRecord(float64 progress) { + if (_type == Type::Record) { + _recordProgress = progress; + update(); + } +} + void SendButton::paintEvent(QPaintEvent *e) { Painter p(this); @@ -118,7 +111,7 @@ void SendButton::paintEvent(QPaintEvent *e) { } void SendButton::paintRecord(Painter &p, bool over) { - auto recordActive = recordActiveRatio(); + const auto recordActive = _recordProgress; if (!isDisabled()) { auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive); paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), &rippleColor); diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h index ad6500699..0b7676474 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.h +++ b/Telegram/SourceFiles/ui/controls/send_button.h @@ -46,9 +46,7 @@ public: _recordAnimationCallback = std::move(callback); } - [[nodiscard]] float64 recordActiveRatio() { - return _a_recordActive.value(_recordActive ? 1. : 0.); - } + void requestPaintRecord(float64 progress); protected: void mouseMoveEvent(QMouseEvent *e) override; @@ -76,7 +74,8 @@ private: QPixmap _contentFrom, _contentTo; Ui::Animations::Simple _a_typeChanged; - Ui::Animations::Simple _a_recordActive; + + float64 _recordProgress = 0.; bool _recording = false; Fn _recordStartCallback; From e7454e384954a954f5f5f75e3406944fc0dfbce2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 4 Oct 2020 18:25:03 +0300 Subject: [PATCH 060/370] Removed redundant record methods from SendButton. --- .../SourceFiles/ui/controls/send_button.cpp | 37 ------------------- .../SourceFiles/ui/controls/send_button.h | 24 ------------ 2 files changed, 61 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index e7ed3e87a..f28cff456 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -66,15 +66,6 @@ void SendButton::finishAnimating() { update(); } -void SendButton::mouseMoveEvent(QMouseEvent *e) { - AbstractButton::mouseMoveEvent(e); - if (_recording) { - if (_recordUpdateCallback) { - _recordUpdateCallback(e->globalPos()); - } - } -} - void SendButton::requestPaintRecord(float64 progress) { if (_type == Type::Record) { _recordProgress = progress; @@ -189,27 +180,6 @@ void SendButton::paintSlowmode(Painter &p) { style::al_center); } -void SendButton::onStateChanged(State was, StateChangeSource source) { - RippleButton::onStateChanged(was, source); - - auto down = (state() & StateFlag::Down); - if ((was & StateFlag::Down) != down) { - if (down) { - if (_type == Type::Record) { - _recording = true; - if (_recordStartCallback) { - _recordStartCallback(); - } - } - } else if (_recording) { - _recording = false; - if (_recordStopCallback) { - _recordStopCallback(_recordActive); - } - } - } -} - bool SendButton::isSlowmode() const { return (_slowmodeDelay > 0); } @@ -248,11 +218,4 @@ QPoint SendButton::prepareRippleStartPosition() const { return real - QPoint((width() - size) / 2, y); } -void SendButton::recordAnimationCallback() { - update(); - if (_recordAnimationCallback) { - _recordAnimationCallback(); - } -} - } // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h index 0b7676474..443571d43 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.h +++ b/Telegram/SourceFiles/ui/controls/send_button.h @@ -29,35 +29,18 @@ public: return _type; } void setType(Type state); - void setRecordActive(bool recordActive); void setSlowmodeDelay(int seconds); void finishAnimating(); - void setRecordStartCallback(Fn callback) { - _recordStartCallback = std::move(callback); - } - void setRecordUpdateCallback(Fn callback) { - _recordUpdateCallback = std::move(callback); - } - void setRecordStopCallback(Fn callback) { - _recordStopCallback = std::move(callback); - } - void setRecordAnimationCallback(Fn callback) { - _recordAnimationCallback = std::move(callback); - } - void requestPaintRecord(float64 progress); protected: - void mouseMoveEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; - void onStateChanged(State was, StateChangeSource source) override; QImage prepareRippleMask() const override; QPoint prepareRippleStartPosition() const override; private: - void recordAnimationCallback(); [[nodiscard]] QPixmap grabContent(); [[nodiscard]] bool isSlowmode() const; @@ -70,19 +53,12 @@ private: Type _type = Type::Send; Type _afterSlowmodeType = Type::Send; - bool _recordActive = false; QPixmap _contentFrom, _contentTo; Ui::Animations::Simple _a_typeChanged; float64 _recordProgress = 0.; - bool _recording = false; - Fn _recordStartCallback; - Fn _recordStopCallback; - Fn _recordUpdateCallback; - Fn _recordAnimationCallback; - int _slowmodeDelay = 0; QString _slowmodeDelayText; From 5c006002b6c94cd4783769dc95ffe66cdc0cbbd7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 4 Oct 2020 21:19:34 +0300 Subject: [PATCH 061/370] Added appearance animation to VoiceRecordBar. --- .../history_view_voice_record_bar.cpp | 117 +++++++++++------- .../controls/history_view_voice_record_bar.h | 4 + Telegram/SourceFiles/ui/chat/chat.style | 1 + 3 files changed, 77 insertions(+), 45 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 0a42498b1..5933067b7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -90,6 +90,7 @@ void VoiceRecordBar::init() { paintRequest( ) | rpl::start_with_next([=] { Painter p(this); + p.setOpacity(_showAnimation.value(1.)); p.fillRect(rect(), st::historyComposeAreaBg); drawRecording(p, activeAnimationRatio()); @@ -116,28 +117,46 @@ void VoiceRecordBar::activeAnimate(bool active) { } } -void VoiceRecordBar::startRecording() { - using namespace ::Media::Capture; - if (!instance()->available()) { - return; - } - show(); - _recording = true; +void VoiceRecordBar::visibilityAnimate(bool show, Fn &&callback) { + const auto to = show ? 1. : 0.; + const auto from = show ? 0. : 1.; + const auto duration = st::historyRecordVoiceShowDuration; + auto animationCallback = [=, callback = std::move(callback)](auto value) { + update(); + if ((show && value == 1.) || (!show && value == 0.)) { + if (callback) { + callback(); + } + } + }; + _showAnimation.start(std::move(animationCallback), from, to, duration); +} - instance()->start(); - instance()->updated( - ) | rpl::start_with_next_error([=](const Update &update) { - recordUpdated(update.level, update.samples); - }, [=] { - stopRecording(false); - }, _recordingLifetime); +void VoiceRecordBar::startRecording() { + auto appearanceCallback = [=] { + Expects(!_showAnimation.animating()); + + using namespace ::Media::Capture; + if (!instance()->available()) { + stop(false); + return; + } + + _recording = true; + instance()->start(); + instance()->updated( + ) | rpl::start_with_next_error([=](const Update &update) { + recordUpdated(update.level, update.samples); + }, [=] { + stop(false); + }, _recordingLifetime); + }; + visibilityAnimate(true, std::move(appearanceCallback)); + show(); _inField = true; _controller->widget()->setInnerFocus(); - update(); - activeAnimate(true); - _send->events( ) | rpl::filter([=](not_null e) { return isTypeRecord() @@ -149,7 +168,7 @@ void VoiceRecordBar::startRecording() { const auto mouse = static_cast(e.get()); _inField = rect().contains(mapFromGlobal(mouse->globalPos())); } else if (type == QEvent::MouseButtonRelease) { - stopRecording(_inField.current()); + stop(_inField.current()); } }, _recordingLifetime); } @@ -175,44 +194,51 @@ void VoiceRecordBar::recordUpdated(quint16 level, int samples) { _recordingAnimation.start(); _recordingSamples = samples; if (samples < 0 || samples >= kMaxSamples) { - stopRecording(samples > 0 && _inField.current()); + stop(samples > 0 && _inField.current()); } Core::App().updateNonIdle(); update(); _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice }); } +void VoiceRecordBar::stop(bool send) { + auto disappearanceCallback = [=] { + Expects(!_showAnimation.animating()); + + hide(); + _recording = false; + + stopRecording(send); + + _recordingLevel = anim::value(); + _recordingAnimation.stop(); + + _inField = false; + + _recordingLifetime.destroy(); + _recordingSamples = 0; + _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); + + _controller->widget()->setInnerFocus(); + }; + visibilityAnimate(false, std::move(disappearanceCallback)); +} + void VoiceRecordBar::stopRecording(bool send) { - hide(); - _recording = false; - using namespace ::Media::Capture; - if (send) { - instance()->stop(crl::guard(this, [=](const Result &data) { - if (data.bytes.isEmpty()) { - return; - } - - Window::ActivateWindow(_controller); - const auto duration = Duration(data.samples); - _sendVoiceRequests.fire({ data.bytes, data.waveform, duration }); - })); - } else { + if (!send) { instance()->stop(); + return; } + instance()->stop(crl::guard(this, [=](const Result &data) { + if (data.bytes.isEmpty()) { + return; + } - _recordingLevel = anim::value(); - _recordingAnimation.stop(); - - _inField = false; - - _recordingLifetime.destroy(); - _recordingSamples = 0; - _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); - - _controller->widget()->setInnerFocus(); - - update(); + Window::ActivateWindow(_controller); + const auto duration = Duration(data.samples); + _sendVoiceRequests.fire({ data.bytes, data.waveform, duration }); + })); } void VoiceRecordBar::drawRecording(Painter &p, float64 recordActive) { @@ -276,6 +302,7 @@ bool VoiceRecordBar::isRecording() const { void VoiceRecordBar::finishAnimating() { _recordingAnimation.stop(); + _showAnimation.stop(); } rpl::producer VoiceRecordBar::recordingStateChanges() const { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index f33a1dc27..667d9a558 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -53,7 +53,10 @@ private: void recordUpdated(quint16 level, int samples); bool recordingAnimationCallback(crl::time now); + + void stop(bool send); void stopRecording(bool send); + void visibilityAnimate(bool show, Fn &&callback); void recordStopCallback(bool active); void recordUpdateCallback(QPoint globalPos); @@ -90,6 +93,7 @@ private: // so it should be a Basic, not a Simple animation. Ui::Animations::Basic _recordingAnimation; Ui::Animations::Simple _activeAnimation; + Ui::Animations::Simple _showAnimation; anim::value _recordingLevel; }; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index ed8a05b06..625ef7a59 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -328,6 +328,7 @@ historyScheduledToggle: IconButton(historyAttach) { historyRecordVoiceFg: historyComposeIconFg; historyRecordVoiceFgOver: historyComposeIconFgOver; historyRecordVoiceFgActive: windowBgActive; +historyRecordVoiceShowDuration: 120; historyRecordVoiceDuration: 120; historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; From ebe1fa74089688bcdca36c4935f4fbe21e3a8d42 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 4 Oct 2020 23:13:00 +0300 Subject: [PATCH 062/370] Improved duration text paint in VoiceRecordBar. --- .../history_view_voice_record_bar.cpp | 45 ++++++++++++------- .../controls/history_view_voice_record_bar.h | 1 + 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 5933067b7..5cb05356f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -30,10 +30,23 @@ constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes constexpr auto kMaxSamples = ::Media::Player::kDefaultFrequency * kAudioVoiceMaxLength; +constexpr auto kPrecision = 10; + [[nodiscard]] auto Duration(int samples) { return samples / ::Media::Player::kDefaultFrequency; } +[[nodiscard]] auto FormatVoiceDuration(int samples) { + const int duration = kPrecision + * (float64(samples) / ::Media::Player::kDefaultFrequency); + const auto durationString = Ui::FormatDurationText(duration / kPrecision); + const auto decimalPart = duration % kPrecision; + return QString("%1%2%3") + .arg(durationString) + .arg(QLocale::system().decimalPoint()) + .arg(decimalPart); +} + } // namespace VoiceRecordBar::VoiceRecordBar( @@ -67,6 +80,16 @@ void VoiceRecordBar::updateControlsGeometry(QSize size) { const auto point = _centerY - st::historyRecordSignalMax; _redCircleRect = { point, point, maxD, maxD }; } + { + const auto durationLeft = _redCircleRect.x() + + _redCircleRect.width() + + st::historyRecordDurationSkip; + _durationRect = QRect( + durationLeft, + _redCircleRect.y(), + _cancelFont->width(FormatVoiceDuration(kMaxSamples)), + _redCircleRect.height()); + } } void VoiceRecordBar::init() { @@ -197,7 +220,7 @@ void VoiceRecordBar::recordUpdated(quint16 level, int samples) { stop(samples > 0 && _inField.current()); } Core::App().updateNonIdle(); - update(); + update(_durationRect); _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice }); } @@ -255,25 +278,15 @@ void VoiceRecordBar::drawRecording(Painter &p, float64 recordActive) { p.drawEllipse(center, radii, radii); } - const auto duration = Ui::FormatDurationText(Duration(_recordingSamples)); + const auto duration = FormatVoiceDuration(_recordingSamples); p.setFont(_cancelFont); p.setPen(st::historyRecordDurationFg); - const auto durationLeft = _redCircleRect.x() - + _redCircleRect.width() - + st::historyRecordDurationSkip; - const auto durationWidth = _cancelFont->width(duration); - p.drawText( - QRect( - durationLeft, - _redCircleRect.y(), - durationWidth, - _redCircleRect.height()), - style::al_left, - duration); + p.fillRect(_durationRect, Qt::red); + p.drawText(_durationRect, style::al_left, duration); - const auto leftCancel = durationLeft - + durationWidth + const auto leftCancel = _durationRect.x() + + _durationRect.width() + ((_send->width() - st::historyRecordVoice.width()) / 2); const auto rightCancel = width() - _send->width(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 667d9a558..a0d8b92bf 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -79,6 +79,7 @@ private: int _centerY = 0; QRect _redCircleRect; + QRect _durationRect; rpl::variable _recording = false; rpl::variable _inField = false; From 43635f6e4b9d0b914f2ba69db92432b2d67c021f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 5 Oct 2020 01:16:31 +0300 Subject: [PATCH 063/370] Slightly optimized paint in VoiceRecordBar. --- .../history_view_voice_record_bar.cpp | 72 ++++++++++++------- .../controls/history_view_voice_record_bar.h | 6 +- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 5cb05356f..02a5a73a9 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -59,7 +59,6 @@ VoiceRecordBar::VoiceRecordBar( , _wrap(std::make_unique(parent)) , _send(send) , _cancelFont(st::historyRecordFont) -, _recordCancelWidth(_cancelFont->width(tr::lng_record_cancel(tr::now))) , _recordingAnimation([=](crl::time now) { return recordingAnimationCallback(now); }) { @@ -90,6 +89,18 @@ void VoiceRecordBar::updateControlsGeometry(QSize size) { _cancelFont->width(FormatVoiceDuration(kMaxSamples)), _redCircleRect.height()); } + { + const auto left = _durationRect.x() + + _durationRect.width() + + ((_send->width() - st::historyRecordVoice.width()) / 2); + const auto right = width() - _send->width(); + const auto width = _cancelFont->width(tr::lng_record_cancel(tr::now)); + _messageRect = QRect( + left + (right - left - width) / 2, + st::historyRecordTextTop, + width + st::historyRecordDurationSkip, + _cancelFont->height); + } } void VoiceRecordBar::init() { @@ -111,12 +122,23 @@ void VoiceRecordBar::init() { }, lifetime()); paintRequest( - ) | rpl::start_with_next([=] { + ) | rpl::start_with_next([=](const QRect &clip) { Painter p(this); - p.setOpacity(_showAnimation.value(1.)); - p.fillRect(rect(), st::historyComposeAreaBg); + if (_showAnimation.animating()) { + p.setOpacity(_showAnimation.value(1.)); + } + p.fillRect(clip, st::historyComposeAreaBg); - drawRecording(p, activeAnimationRatio()); + if (clip.intersects(_messageRect)) { + // The message should be painted first to avoid flickering. + drawMessage(p, activeAnimationRatio()); + } + if (clip.intersects(_redCircleRect)) { + drawRecording(p); + } + if (clip.intersects(_durationRect)) { + drawDuration(p); + } }, lifetime()); _inField.changes( @@ -132,7 +154,7 @@ void VoiceRecordBar::activeAnimate(bool active) { _activeAnimation.change(to, duration); } else { auto callback = [=] { - update(); + update(_messageRect); _send->requestPaintRecord(activeAnimationRatio()); }; const auto from = active ? 0. : 1.; @@ -264,40 +286,36 @@ void VoiceRecordBar::stopRecording(bool send) { })); } -void VoiceRecordBar::drawRecording(Painter &p, float64 recordActive) { - p.setPen(Qt::NoPen); - p.setBrush(st::historyRecordSignalColor); - - { - PainterHighQualityEnabler hq(p); - const auto min = st::historyRecordSignalMin; - const auto max = st::historyRecordSignalMax; - const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.); - const auto radii = qRound(min + (delta * (max - min))); - const auto center = _redCircleRect.center() + QPoint(1, 1); - p.drawEllipse(center, radii, radii); - } - +void VoiceRecordBar::drawDuration(Painter &p) { const auto duration = FormatVoiceDuration(_recordingSamples); p.setFont(_cancelFont); p.setPen(st::historyRecordDurationFg); - p.fillRect(_durationRect, Qt::red); p.drawText(_durationRect, style::al_left, duration); +} - const auto leftCancel = _durationRect.x() - + _durationRect.width() - + ((_send->width() - st::historyRecordVoice.width()) / 2); - const auto rightCancel = width() - _send->width(); +void VoiceRecordBar::drawRecording(Painter &p) { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(st::historyRecordSignalColor); + const auto min = st::historyRecordSignalMin; + const auto max = st::historyRecordSignalMax; + const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.); + const auto radii = qRound(min + (delta * (max - min))); + const auto center = _redCircleRect.center() + QPoint(1, 1); + p.drawEllipse(center, radii, radii); +} + +void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { p.setPen( anim::pen( st::historyRecordCancel, st::historyRecordCancelActive, 1. - recordActive)); p.drawText( - leftCancel + (rightCancel - leftCancel - _recordCancelWidth) / 2, - st::historyRecordTextTop + _cancelFont->ascent, + _messageRect.x(), + _messageRect.y() + _cancelFont->ascent, tr::lng_record_cancel(tr::now)); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index a0d8b92bf..8fdc0fc02 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -62,7 +62,9 @@ private: void recordUpdateCallback(QPoint globalPos); bool showRecordButton() const; - void drawRecording(Painter &p, float64 recordActive); + void drawDuration(Painter &p); + void drawRecording(Painter &p); + void drawMessage(Painter &p, float64 recordActive); void updateOverStates(QPoint pos); bool isTypeRecord() const; @@ -80,13 +82,13 @@ private: int _centerY = 0; QRect _redCircleRect; QRect _durationRect; + QRect _messageRect; rpl::variable _recording = false; rpl::variable _inField = false; int _recordingSamples = 0; const style::font &_cancelFont; - int _recordCancelWidth; rpl::lifetime _recordingLifetime; From 478f5f671cd595676b05dcdea05ae52f0d33d625 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 7 Oct 2020 12:19:03 +0300 Subject: [PATCH 064/370] Added initial implementation of voice recording lock. --- Telegram/Resources/langs/lang.strings | 3 + .../SourceFiles/history/history_widget.cpp | 22 ++ Telegram/SourceFiles/history/history_widget.h | 2 + .../history_view_voice_record_bar.cpp | 229 +++++++++++++++++- .../controls/history_view_voice_record_bar.h | 35 ++- Telegram/SourceFiles/ui/chat/chat.style | 3 + 6 files changed, 278 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f10ddf6a6..d6c0eac12 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1341,6 +1341,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; "lng_record_cancel" = "Release outside this field to cancel"; +"lng_record_lock_cancel" = "Click outside of microphone button to cancel"; +"lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?"; +"lng_record_lock_discard" = "Discard"; "lng_will_be_notified" = "Members will be notified when you post"; "lng_wont_be_notified" = "Members will not be notified when you post"; "lng_willbe_history" = "Please select a chat to start messaging"; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 2cdfee1ac..dc7ec4571 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -717,6 +717,16 @@ void HistoryWidget::refreshTabbedPanel() { } void HistoryWidget::initVoiceRecordBar() { + { + auto scrollHeight = rpl::combine( + _scroll->topValue(), + _scroll->heightValue() + ) | rpl::map([=](int top, int height) { + return top + height; + }); + _voiceRecordBar->setLockBottom(std::move(scrollHeight)); + } + _voiceRecordBar->startRecordingRequests( ) | rpl::start_with_next([=] { const auto error = _peer @@ -756,6 +766,12 @@ void HistoryWidget::initVoiceRecordBar() { data.duration, action); }, lifetime()); + + _voiceRecordBar->lockShowStarts( + ) | rpl::start_with_next([=] { + updateHistoryDownVisibility(); + updateUnreadMentionsVisibility(); + }, lifetime()); } void HistoryWidget::initTabbedSelector() { @@ -4787,6 +4803,9 @@ void HistoryWidget::updateHistoryDownVisibility() { if (!_list || _firstLoadRequest) { return false; } + if (_voiceRecordBar->isLockPresent()) { + return false; + } if (!_history->loadedAtBottom() || _replyReturn) { return true; } @@ -4830,6 +4849,9 @@ void HistoryWidget::updateUnreadMentionsVisibility() { if (!showUnreadMentions || _firstLoadRequest) { return false; } + if (_voiceRecordBar->isLockPresent()) { + return false; + } if (!_history->getUnreadMentionsLoadedCount()) { return false; } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index ecd664f6d..b7956ec7c 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -95,6 +95,7 @@ class ContactStatus; class Element; class PinnedTracker; namespace Controls { +class RecordLock; class VoiceRecordBar; } // namespace Controls } // namespace HistoryView @@ -111,6 +112,7 @@ class HistoryWidget final : public Window::AbstractSectionWidget { public: using FieldHistoryAction = Ui::InputField::HistoryAction; + using RecordLock = HistoryView::Controls::RecordLock; using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar; HistoryWidget( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 02a5a73a9..d94221565 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -8,12 +8,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_voice_record_bar.h" #include "api/api_send_progress.h" +#include "base/event_filter.h" +#include "boxes/confirm_box.h" #include "core/application.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "media/audio/media_audio.h" #include "media/audio/media_audio_capture.h" #include "styles/style_chat.h" +#include "styles/style_layers.h" #include "ui/controls/send_button.h" #include "ui/text/format_values.h" #include "window/window_session_controller.h" @@ -25,6 +28,7 @@ namespace { using SendActionUpdate = VoiceRecordBar::SendActionUpdate; using VoiceToSend = VoiceRecordBar::VoiceToSend; +constexpr auto kLockDelay = crl::time(100); constexpr auto kRecordingUpdateDelta = crl::time(100); constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes constexpr auto kMaxSamples = @@ -49,6 +53,77 @@ constexpr auto kPrecision = 10; } // namespace +class RecordLock final : public Ui::RpWidget { +public: + RecordLock(not_null parent); + + void requestPaintProgress(float64 progress); + void reset(); + + [[nodiscard]] rpl::producer<> locks() const; + [[nodiscard]] bool isLocked() const; + +private: + void init(); + + Ui::Animations::Simple _lockAnimation; + + rpl::variable _progress = 0.; +}; + +RecordLock::RecordLock(not_null parent) : RpWidget(parent) { + resize(st::historyRecordLockSize); + init(); +} + +void RecordLock::init() { + setAttribute(Qt::WA_TransparentForMouseEvents); + shownValue( + ) | rpl::start_with_next([=](bool shown) { + if (!shown) { + _lockAnimation.stop(); + _progress = 0.; + } + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=](const QRect &clip) { + Painter p(this); + if (isLocked()) { + const auto color = anim::color( + Qt::red, + Qt::green, + _lockAnimation.value(1.)); + p.fillRect(clip, color); + return; + } + p.fillRect(clip, anim::color(Qt::blue, Qt::red, _progress.current())); + }, lifetime()); + + locks( + ) | rpl::start_with_next([=] { + const auto duration = st::historyRecordVoiceShowDuration * 3; + _lockAnimation.start([=] { update(); }, 0., 1., duration); + }, lifetime()); +} + +void RecordLock::requestPaintProgress(float64 progress) { + if (isHidden() || isLocked()) { + return; + } + _progress = progress; + update(); +} + +bool RecordLock::isLocked() const { + return _progress.current() == 1.; +} + +rpl::producer<> RecordLock::locks() const { + return _progress.changes( + ) | rpl::filter([=] { return isLocked(); }) | rpl::to_empty; +} + VoiceRecordBar::VoiceRecordBar( not_null parent, not_null controller, @@ -56,8 +131,8 @@ VoiceRecordBar::VoiceRecordBar( int recorderHeight) : RpWidget(parent) , _controller(controller) -, _wrap(std::make_unique(parent)) , _send(send) +, _lock(std::make_unique(parent)) , _cancelFont(st::historyRecordFont) , _recordingAnimation([=](crl::time now) { return recordingAnimationCallback(now); @@ -94,7 +169,7 @@ void VoiceRecordBar::updateControlsGeometry(QSize size) { + _durationRect.width() + ((_send->width() - st::historyRecordVoice.width()) / 2); const auto right = width() - _send->width(); - const auto width = _cancelFont->width(tr::lng_record_cancel(tr::now)); + const auto width = _cancelFont->width(cancelMessage()); _messageRect = QRect( left + (right - left - width) / 2, st::historyRecordTextTop, @@ -145,6 +220,44 @@ void VoiceRecordBar::init() { ) | rpl::start_with_next([=](bool value) { activeAnimate(value); }, lifetime()); + + _lockShowing.changes( + ) | rpl::start_with_next([=](bool show) { + const auto to = show ? 1. : 0.; + const auto from = show ? 0. : 1.; + const auto duration = st::historyRecordLockShowDuration; + _lock->show(); + auto callback = [=](auto value) { + const auto right = anim::interpolate( + -_lock->width(), + 0, + value); + _lock->moveToRight(right, _lock->y()); + if (value == 0. && !show) { + _lock->hide(); + } else if (value == 1. && show) { + computeAndSetLockProgress(QCursor::pos()); + } + }; + _showLockAnimation.start(std::move(callback), from, to, duration); + }, lifetime()); + + _lock->hide(); + _lock->locks( + ) | rpl::start_with_next([=] { + + updateControlsGeometry(rect().size()); + update(_messageRect); + + installClickOutsideFilter(); + + _send->clicks( + ) | rpl::filter([=] { + return _send->type() == Ui::SendButton::Type::Record; + }) | rpl::start_with_next([=] { + stop(true); + }, _recordingLifetime); + }, lifetime()); } void VoiceRecordBar::activeAnimate(bool active) { @@ -177,6 +290,14 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn &&callback) { _showAnimation.start(std::move(animationCallback), from, to, duration); } +void VoiceRecordBar::setLockBottom(rpl::producer &&bottom) { + std::move( + bottom + ) | rpl::start_with_next([=](int value) { + _lock->moveToLeft(_lock->x(), value - _lock->height()); + }, lifetime()); +} + void VoiceRecordBar::startRecording() { auto appearanceCallback = [=] { Expects(!_showAnimation.animating()); @@ -187,10 +308,17 @@ void VoiceRecordBar::startRecording() { return; } + const auto shown = _recordingLifetime.make_state(false); + _recording = true; instance()->start(); instance()->updated( ) | rpl::start_with_next_error([=](const Update &update) { + if (!(*shown) && !_showAnimation.animating()) { + // Show the lock widget after the first successful update. + *shown = true; + _lockShowing = true; + } recordUpdated(update.level, update.samples); }, [=] { stop(false); @@ -205,13 +333,20 @@ void VoiceRecordBar::startRecording() { _send->events( ) | rpl::filter([=](not_null e) { return isTypeRecord() + && !_lock->isLocked() && (e->type() == QEvent::MouseMove || e->type() == QEvent::MouseButtonRelease); }) | rpl::start_with_next([=](not_null e) { const auto type = e->type(); if (type == QEvent::MouseMove) { const auto mouse = static_cast(e.get()); - _inField = rect().contains(mapFromGlobal(mouse->globalPos())); + const auto localPos = mapFromGlobal(mouse->globalPos()); + _inField = rect().contains(localPos); + + if (_showLockAnimation.animating()) { + return; + } + computeAndSetLockProgress(mouse->globalPos()); } else if (type == QEvent::MouseButtonRelease) { stop(_inField.current()); } @@ -266,6 +401,7 @@ void VoiceRecordBar::stop(bool send) { _controller->widget()->setInnerFocus(); }; + _lockShowing = false; visibilityAnimate(false, std::move(disappearanceCallback)); } @@ -316,7 +452,7 @@ void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { p.drawText( _messageRect.x(), _messageRect.y() + _cancelFont->ascent, - tr::lng_record_cancel(tr::now)); + cancelMessage()); } rpl::producer VoiceRecordBar::sendActionUpdates() const { @@ -340,10 +476,21 @@ rpl::producer VoiceRecordBar::recordingStateChanges() const { return _recording.changes(); } +rpl::producer VoiceRecordBar::lockShowStarts() const { + return _lockShowing.changes(); +} + +bool VoiceRecordBar::isLockPresent() const { + return _lockShowing.current(); +} + rpl::producer<> VoiceRecordBar::startRecordingRequests() const { return _send->events( ) | rpl::filter([=](not_null e) { - return isTypeRecord() && (e->type() == QEvent::MouseButtonPress); + return isTypeRecord() + && !_showAnimation.animating() + && !_lock->isLocked() + && (e->type() == QEvent::MouseButtonPress); }) | rpl::to_empty; } @@ -355,4 +502,76 @@ float64 VoiceRecordBar::activeAnimationRatio() const { return _activeAnimation.value(_inField.current() ? 1. : 0.); } +QString VoiceRecordBar::cancelMessage() const { + return _lock->isLocked() + ? tr::lng_record_lock_cancel(tr::now) + : tr::lng_record_cancel(tr::now); +} + +void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { + const auto localPos = mapFromGlobal(globalPos); + const auto lower = _lock->height(); + const auto higher = 0; + const auto progress = localPos.y() / (float64)(higher - lower); + _lock->requestPaintProgress(std::clamp(progress, 0., 1.)); +} + +void VoiceRecordBar::installClickOutsideFilter() { + const auto box = _recordingLifetime.make_state>(); + const auto showBox = [=] { + if (*box || _send->underMouse()) { + return; + } + auto sure = [=](Fn &&close) { + stop(false); + close(); + }; + *box = Ui::show(Box( + tr::lng_record_lock_cancel_sure(tr::now), + tr::lng_record_lock_discard(tr::now), + st::attentionBoxButton, + std::move(sure))); + }; + + const auto computeResult = [=](not_null e) { + using Result = base::EventFilterResult; + if (!_lock->isLocked()) { + return Result::Continue; + } + const auto type = e->type(); + const auto noBox = !(*box); + if (type == QEvent::KeyPress) { + if (noBox) { + return Result::Cancel; + } + const auto key = static_cast(e.get())->key(); + const auto cancelOrConfirmBox = (key == Qt::Key_Escape + || (key == Qt::Key_Enter || key == Qt::Key_Return)); + return cancelOrConfirmBox ? Result::Continue : Result::Cancel; + } else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) { + return Result::Cancel; + } else if (type == QEvent::MouseButtonPress) { + return (noBox && !_send->underMouse()) + ? Result::Cancel + : Result::Continue; + } + return Result::Continue; + }; + + auto filterCallback = [=](not_null e) { + const auto result = computeResult(e); + if (result == base::EventFilterResult::Cancel) { + showBox(); + } + return result; + }; + + auto filter = base::install_event_filter( + QCoreApplication::instance(), + std::move(filterCallback)); + + _recordingLifetime.make_state>( + std::move(filter)); +} + } // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 8fdc0fc02..29ed0cab8 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -22,11 +22,20 @@ class SessionController; namespace HistoryView::Controls { +class RecordLock; + class VoiceRecordBar final : public Ui::RpWidget { public: using SendActionUpdate = Controls::SendActionUpdate; using VoiceToSend = Controls::VoiceToSend; + VoiceRecordBar( + not_null parent, + not_null controller, + std::shared_ptr send, + int recorderHeight); + ~VoiceRecordBar(); + void startRecording(); void finishAnimating(); @@ -34,15 +43,12 @@ public: [[nodiscard]] rpl::producer sendVoiceRequests() const; [[nodiscard]] rpl::producer recordingStateChanges() const; [[nodiscard]] rpl::producer<> startRecordingRequests() const; + [[nodiscard]] rpl::producer lockShowStarts() const; + + void setLockBottom(rpl::producer &&bottom); [[nodiscard]] bool isRecording() const; - - VoiceRecordBar( - not_null parent, - not_null controller, - std::shared_ptr send, - int recorderHeight); - ~VoiceRecordBar(); + [[nodiscard]] bool isLockPresent() const; private: void init(); @@ -58,23 +64,26 @@ private: void stopRecording(bool send); void visibilityAnimate(bool show, Fn &&callback); - void recordStopCallback(bool active); - void recordUpdateCallback(QPoint globalPos); - bool showRecordButton() const; void drawDuration(Painter &p); void drawRecording(Painter &p); void drawMessage(Painter &p, float64 recordActive); void updateOverStates(QPoint pos); + void installClickOutsideFilter(); + bool isTypeRecord() const; void activeAnimate(bool active); float64 activeAnimationRatio() const; + void computeAndSetLockProgress(QPoint globalPos); + + QString cancelMessage() const; + const not_null _controller; - const std::unique_ptr _wrap; const std::shared_ptr _send; + const std::unique_ptr _lock; rpl::event_stream _sendActionUpdates; rpl::event_stream _sendVoiceRequests; @@ -92,6 +101,10 @@ private: rpl::lifetime _recordingLifetime; + rpl::variable _lockShowing = false; + + Ui::Animations::Simple _showLockAnimation; + // This can animate for a very long time (like in music playing), // so it should be a Basic, not a Simple animation. Ui::Animations::Basic _recordingAnimation; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 625ef7a59..1bf1b08ba 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -344,6 +344,9 @@ historyRecordDurationSkip: 12px; historyRecordDurationFg: historyComposeAreaFg; historyRecordTextTop: 14px; +historyRecordLockShowDuration: historyToDownDuration; +historyRecordLockSize: size(50px, 150px); + historySilentToggle: IconButton(historyBotKeyboardShow) { icon: icon {{ "send_control_silent_off", historyComposeIconFg }}; iconOver: icon {{ "send_control_silent_off", historyComposeIconFgOver }}; From 326342420d2a8217025ee73f41c1a34c3fe96730 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 8 Oct 2020 17:38:17 +0300 Subject: [PATCH 065/370] Added animation of voice recording lock with dummy lock icons. --- .../Resources/icons/lock/record_lock_body.png | Bin 0 -> 107 bytes .../icons/lock/record_lock_body@2x.png | Bin 0 -> 122 bytes .../icons/lock/record_lock_body@3x.png | Bin 0 -> 176 bytes .../icons/lock/record_lock_body_shadow.png | Bin 0 -> 122 bytes .../icons/lock/record_lock_body_shadow@2x.png | Bin 0 -> 199 bytes .../icons/lock/record_lock_body_shadow@3x.png | Bin 0 -> 341 bytes .../icons/lock/record_lock_bottom.png | Bin 0 -> 369 bytes .../icons/lock/record_lock_bottom@2x.png | Bin 0 -> 657 bytes .../icons/lock/record_lock_bottom@3x.png | Bin 0 -> 1012 bytes .../icons/lock/record_lock_bottom_shadow.png | Bin 0 -> 576 bytes .../lock/record_lock_bottom_shadow@2x.png | Bin 0 -> 2265 bytes .../lock/record_lock_bottom_shadow@3x.png | Bin 0 -> 4705 bytes .../Resources/icons/lock/record_lock_top.png | Bin 0 -> 359 bytes .../icons/lock/record_lock_top@2x.png | Bin 0 -> 661 bytes .../icons/lock/record_lock_top@3x.png | Bin 0 -> 1010 bytes .../icons/lock/record_lock_top_shadow.png | Bin 0 -> 557 bytes .../icons/lock/record_lock_top_shadow@2x.png | Bin 0 -> 2244 bytes .../icons/lock/record_lock_top_shadow@3x.png | Bin 0 -> 4604 bytes .../SourceFiles/history/history_widget.cpp | 2 +- .../history_view_voice_record_bar.cpp | 99 ++++++++++++++++-- Telegram/SourceFiles/ui/chat/chat.style | 16 ++- 21 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 Telegram/Resources/icons/lock/record_lock_body.png create mode 100644 Telegram/Resources/icons/lock/record_lock_body@2x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_body@3x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_body_shadow.png create mode 100644 Telegram/Resources/icons/lock/record_lock_body_shadow@2x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_body_shadow@3x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_bottom.png create mode 100644 Telegram/Resources/icons/lock/record_lock_bottom@2x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_bottom@3x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_bottom_shadow.png create mode 100644 Telegram/Resources/icons/lock/record_lock_bottom_shadow@2x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_bottom_shadow@3x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_top.png create mode 100644 Telegram/Resources/icons/lock/record_lock_top@2x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_top@3x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_top_shadow.png create mode 100644 Telegram/Resources/icons/lock/record_lock_top_shadow@2x.png create mode 100644 Telegram/Resources/icons/lock/record_lock_top_shadow@3x.png diff --git a/Telegram/Resources/icons/lock/record_lock_body.png b/Telegram/Resources/icons/lock/record_lock_body.png new file mode 100644 index 0000000000000000000000000000000000000000..6204b88ce44654acc312fcb73389f27b3dda9807 GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^IzY_C!2~4NIII)^Qk(@Ik;M!QddeWoSh3W;3@9k; z>EaktaVt3?A>qfLpP&CTGC8c4RCw_B_jl&k91IF?er3L9C}069WbkzLb6Mw<&;$Ve CE*@L} literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_body@2x.png b/Telegram/Resources/icons/lock/record_lock_body@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cf955e2914fe6d14b625be80f406dff2084a863f GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^5kSns!2~2*cjvzcQk(@Ik;M!QddeWoSh3W;3@E7M z>EaktaVt52q1k`F-Cy~M4?aIX&vW9ze-1Vc#*aYu#Ri5(^Za`~AJ#B39NYD}#WRED QH&7pgr>mdKI;Vst0GTQ$zyJUM literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_body@3x.png b/Telegram/Resources/icons/lock/record_lock_body@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..1da493f530811593190d65746028aca617b5d1e4 GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^EkMl1!2~3G;}$9dDb50q$YKTtJs?|8nXzK2eHlgoZ|bdAmRI ztR?IR64DQ_%(ttR;+6W#j+S3j3^P6@AiB1ZrK literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_body_shadow@2x.png b/Telegram/Resources/icons/lock/record_lock_body_shadow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3b01ea846d4ba0f185d942490c5c4856bfa24cf5 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^89>a!!2~4Vdc3_3q&N#aB8wRq^pruEv0|xx8Bnm& z)5S5Q;?~nk2YDF;1zHnDFLYe!U!cb+C8Q;2bzmRUw}kIj@9o;(pEvksc5dgCOA^9P z8cV<2yB^hk-~fwQ%7KIKEK3r;g9O)XH9QveS|a(qucFM115G?TiunX5JSw(`SvDmq td&!}*mX7BvtKV321{*1fc-J2K!6_GvkGky!dYcZ}A=J zJtRqb{61Nh6-Ci?-L~x@2+}mIs;cX{X_~feyRPdgcEf;$7i6dbhaG$p5P}>;`6Jm+ z(0`+H{%Jn#E1^zR$IAVUo}>`@fCu1g4Vd?u=Cnq1^=p65-| z48yQ2%W)in{=UHrGSq;>9>=lgd4%|6R4#J2D2ldi$8i)<@7{Ty>$*Y>cv+T7lK8%F nS(c`0BC3e`MpW^Bzdz~={`1fTlQJPq00000NkvXXu0mjf;{A)_ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_bottom.png b/Telegram/Resources/icons/lock/record_lock_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..8be9d5e19ed3df9ea6edea42c605cab82e866671 GIT binary patch literal 369 zcmV-%0gnEOP)YnZbaAh!_RjFB9{JyEX!3@9c6$h1kxQ~`-EZme*=WJ~$i>Z@fZZSuU4+ zT3D~wCK2=boKF#kVVFcrCKEnI%w{u_h;FybrwC2cOd?cOQLn?xvz!lwuXH;G86 z(|n3RaFd8=G|Hz41g8@giv@2M5Sh+tG#b2#K;#P|BuU~;1R`G$Aqc|pc;v?d!iGX2 zx`?jp{D?qU`VLu^`4N%N=f4%<_xpFd9S;@|6JhBGIO(-dJL_{qQDlC}M8J4FPFLx4 zI&6zTK)QuQBEj%QfoREOl5V6@sjw=dTCLLU1cSlVYQ>%fghDj>6-@RN2t~ie<#LTi zBbF>64nmOyVzJm}vth#mf*=lACYQ^xAp$|@PD-Vc(=8wZ-4E`oR;%S)3+;B>;?i)A z*Xtb&22QkqvQa!e5In1m$75$%K*3iYF|FbA`7}*)iUpL4g6TK*UF?RFOmg_m!0q5zbEV(7Jm z!{J)3_L99EOp2*d0NICBDuo{Ik9XWKDcKWI6wBrEQ*W02ey^%3CL;S22n1wV)^+`X z_X~!?fEX5olj9HsL6W3;y>9uP1DuG{F$jhtHi$$bnM_7e6kOQ7UT->`8iujmZa)L# r1HR(Ne!q_cip3%h#wqmAKd<2(0y9^$CoXzt00000NkvXXu0mjf52+w# literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_bottom@3x.png b/Telegram/Resources/icons/lock/record_lock_bottom@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..bd1cd6a8a296d49a9a9bf0ff58c41640bfc5fbf8 GIT binary patch literal 1012 zcmV!x$7FuWr(ZU!@ZKc;jjkiW^)x=II3?#M+ z`~Yt>`UfkdBjHjn3s?iU$n}l(He@`{~fp(KHN~IFj=;!+S+9Zs#voosE&*|x@Nf?<-hHCV4 zbaZ4A#@gB%)#wM>O~RO+ouwN6K)Xp8qobo#qaSED2_qVfQjLC~T}*j=e54(nK)BQ* zpU+c}P9VHy82mxyiJwwc#>U1dNGA|pGmN&jw$IN`I?@AFx3;#*H=LiJQp#eySYWb0^tu4w@4J3;d`uqDSOEr*eDGb9f zZf|etOC8V)$zqZD`FSc+2lR?{!r}1y`#Y_v0Ait6Y;kaKKyfO7Sg}h_PY;JiR1AUi z_V!90WV2c7bC+B$C-o7J$9ZtLdsy`K^-0}u{f>(qA0LYoVzJoA#|J$+2O1$$9J0B& zNtMolMsZF{OUuK<18q710z;vYxWeS*B!xNw0>wp6PEP2wKA=wAB@&60%VnCi3#37v zxK1jSqFlQ`nkZs_f1iG>16`tyU@&-fbw$fofGFq^rF3+3@Z@3TRMFYlDH<3Y9OP`r zH$V;)i8iLDrYOA@)6>(UnT3S~djHDe;-WMJ4qGW1fT|C}Fm`r!m_Ze&u(9StQ63u` z8%zSnM1zyy`J)QDFP>s%X2yrpQ0Ou`E*@lPXvkOBgXrw=@UVCmT%TWFUiu*kbQ7Hw zj}r_A_xARhegshYxRZ+{lgXz1%0j0Uj*6=z5{Wd~{2;ocWTr?P6B83nu$}>3aCsiP zQG`MvToe7$fnJo2;$2(Y;SsvJx-yx}FIq2(Qc*C)Jk i_+ZWd0VISFVu!y_Fv)P)Kp4va0000 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_bottom_shadow.png b/Telegram/Resources/icons/lock/record_lock_bottom_shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..efa0930639db7a235b4a5404120334dafdfc679f GIT binary patch literal 576 zcmV-G0>Ax2&(>B7r7=kSU!` zPbQOJ8qPG$e!u^!aD%}hl}bs&C6h_pwoj*%`eZ;I$02|;Ts$7{bUKH_L47!&pa~!i z7mY@%)#`S;RUZy0XaY#Xg+if9rLtPB)Q1BKngHU4I4{HDP<=R{w~p$UVzF4W+1%}R zDwBZ&3fd)HFc`EfYq?yi3NkT7f`-Qr7GBo_Zl;4gwH3ahJGqa=F}iJU$+e&q#(X<{(_| zHkou?FPF=++06HR7-EY#2=TBYkw~dj8jVIi9$u_4#1?b$LxscPY&Og0@a249f)$3? z3SY#wVB6N~^~GZG@DehS>5WDMs|WYljq__5MxjvXcDr2Y6rA(<>?IS!7|awVG98n< z&!+b!xgEGPSo0@MkSp}r49^Wr&RP>YKp-C6(PBu zue+1@JT`RbRC;>)DF4Y5ClYqnF`X>=AOr&Olf=$0oKQe}_UsCgCf#(n`Y4Yqr?cIWxw?j>$KDc{%c@c?qxSB<9Ul}@2TWbi!2T1rf*0E!vN8&Au zMG?;>k(@j|jdXN$OiZ+B;$V?82id@RTOM51oO12CRaRC8$d5vyfFt4NCi9C+K)Q&0 zu{rO7%-1MeF4s_F@7|F#+8GQgjpoxFql-i`xrqDr0uZG6>({S;)CRY^s#ASVrI12# zEHb5#$zNyMcr&dmPLYd>^4ro5fknfYQ3r@`eWYELPE|NpIak?$2OpSx`#8&MV8d^s z07**Xl?(zU)4^S#mX$`tIX(*B$!WMZU%MX1+2T$M?+hs`rVbXFrThrHY#!xFTz;sR zE~0Si>SoBLORMazEZ}%N9$gXIVnFjH`OIH|q57b2(I@&_^%;!VKX~9XAVB{2 zTvoz)HztjS{~LIM znzOga$T|G$1T<~#&#JR-PrSPO`z@hn2sd-HFO=-J+37Zaqn`Mb^JH`2&6~}cwv;~+ z==u417dN+Du1%xl(OWMctVc7=g>po17^78w*5|oYuuP%T=`)np*8K{*{IyB?StqB{ zRi%BSDk~OYN3YN`PKz@R2;9SkQ?(*P>Cy6$GBWuYWB(X_sCC$Fn&NC~ z$_>y3I6nqrp{PQ-HB-}45*%xdFMT*FI&43ZksD8!2@MTZGD|4WCJ_LyX_#3SOEynM z{8}QGB=iSd5YMzwb>43YE{(nufm0s0!F^0h0_~LDKN)dW-w}PD%dEyUSyg4B*bMX|?-vctCo8uXXBlN#0$=D6OHaIC7Tmk%;q5)( zzp}atla(!2p5gEB?+?%lh9-+5I4V`O%A7MLg9-I#&izCIy=^!I8w z4Usmr76Q*Gxldf0TIeu#OZf4lN4B=M5E7YegG?5Oftf?oDGjb~08^SzKGR`rVseoJ zcqDsn&e_?S`NrShA2~KOWWKisJS!_D7iD3vkxTV}sgy?zkS<^RD4?{h?xTDCqJ4ni zVUGc|kd7OFrG6l$2CdR8&$bDlF_f-c($i)N9FMA93;V znVy`qAACJEH8nh3;sM)jv)=1=v-gGNdN5FVG(pcgW5g(N9pm$^QXEMlb3B literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_bottom_shadow@3x.png b/Telegram/Resources/icons/lock/record_lock_bottom_shadow@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5421949809ff69580454065f6ba8ba7d618d93a8 GIT binary patch literal 4705 zcmai22T+q+(+*9%(vcudA%G&0-UOtEL=t)^ib#iGh*aqU3Iq{C??C|}ia^kS^dbr> zy-B-tq^dMgq&NQ;%Rl#iGv9ofIeBN^$vL}c_t|}(O_Y&=Hq!~76CeIZ-MP()D0O&)tr-QBSR8jFOoU^kM_Pqc+Aky&%*p%IjU0y*} zwydPYh`nMg8fRxWy}Z1mIQBgbJTKIdiRWC%5zj$tau@X3`4rhD8!OHrz{X#LT6`9S<#^yt3 zXJ>D3Z%>aT6p90Wd!swEkV0`9e0{j9ODc||8ouQB1(dPf*4i2w7iV4V0r5n5L++Q7 za9Hf;g@ud<50n)Zw|93bR&~BxUncSmvdGV#xi~xTeD6^|OEn-3Vz3r?q!z5Ksv0~| z>%+h)MpX8J!UX6^=g%(o8>&AqD!OTJZ}0BDIy+nD)b*geynKIue`%=r!>i!Ec=f{s zTBGEGDY~_&lOnz=qhM}sCHF}cVU6Q+8XOqJU~jLqf@tm0F2x2&zLaX1_tPWv?x1WC}MBjIk|?MH~xkBh96EvATN@)21Vx?9}0ay5~P9E%T@Tvt>r>3Spd^lE$>6n?B zxpTPliQ$yQz~CUtDt7mQ=~rs*0TBJApRUoMQ`l2~Y_5zAH8j|E5~YjlH!VCoN)%0@ z5)x14pDw4Ju=O!T{oBwP(GejyM&)~_ES{c)^ApSA;bF=U$4EKZ%*@Obn^b^<1~)P| z_V$EKjEry}d3J`94kPs~_VLE@$hUWQ$5A45Vs6J!o{~tU!a~#{ZUDVY&G}M;Gf#3j zG@kbOx39_c2n5)Q$!BP;ExG>X%PSPi3?k8g{c~?=sZ8M&W1Cc3YXLmZliWueb57sqm3^Mv`ItKVnhjbLRd zPT~NkGRVoxw|z`YOLH00)YP0Yf1Q&P@>23NCwVr6wPcD;rhtmscS?u~cAhl!7y+(G zdi6?8X`->wZt`V7(beL~8m~{^W?PCaYhat{aRR1+%OK;1uv4u$#}tq568|h|6he-O zh^VYoP|9*0?C(#u-X3#*ohKc1*sZkvT}%-!XN78C?_6a7sPY!`pj(Vu zMo(8)2oc$QxxE3}SwBRvqWOieXunxxPL4T&0G0Xb)uVdD!9fd_(`|r6wzjoF&;vt5 zy7DFDMQdB@m;PwJou5dlYpsWJnx%!d#PVMB`l#0jb8%UotPezS$ji(3^(K*-lgLFy znmQ9e;Q>i09WT;B&&!r0ZFOiE7vzP5IL5SxZNZCnC9`4mJeB=sj3S5>wu z4Nb2xxnQx1o-=S3miVm?cWp9ZDJdx>RUhgDub#MqdBYK6OZ!yO3-lB1bW4+Qk%03F z5G`!Dm8IqW!9kN@nn9+b=fls2F0PV5!$)eW zQc_aN%DeaZ?U0pY&0Hhg8Gu1ZlxPFgxc z2Gf4sy}klB2qmlnB}4cgydXewpl8n6+ZeKOX8Y~x4EAf{{cW8n=xyF_eG#9q%`w~n zAkk=ao$pF}PtOfqtc%NB&08Z;Yj6}=Ba{IP27_mYavEqwZGm$87X4%sH=u?_lmNw? zF48(YOnlNKTLE;A=hv%@%YcRzm!0}4P9Mf@*0_7zQsdIyLlsctalM3}IP$4`Q;sLK zho#2(FAu+Xau^ZXj|xp5X7Xwqv)QJZ8kgjHD~ic3lDTh9Q> z^7ZxI+}L1ZVKLOvQ4ZRc;=SYQ`aC6^pK{&<9&Bo1aYF7r6>Cfbt#e?Y$zY@oX|+E; zUeWe}lwH`vhu4uvq@ErlSc&(Qq9O;%yl;blNhr~uicTUYl=&xMVASq`ekK^iyhIwn zUVL&`*3_!3tkYwc#Jwyn#|_mFOwG-a?B}W?bG|#Z_vpyT;TI0{JK>e7!sC5?f?1~z>u2Z~JTz#}dr&cXgE%(WkW@u6 z5~oj@{XJQd2Q>X_*ROXZNgZ&`TVA_%&B`ik*z&9rMkEYG^>fp;nIo`{geqK4CACA( z$wU+u7CwLeJUxAgodEjRtByR;m-08Z@Ut zjld&Z|-q?9;5X-IWu{AZUianHYH+WAWqg0CC^J!9n)s-(s6TBwuaM z0A!lq3}7?};1m;+;}Gmf`JgZjhC zz*PhSk?6~OS{a_9u`~4ky}*)Jwv_WdZBk&Per6MfuO(O@gj&FsZJyE&Hmfg?v2uRo zq(lq-$0;x3$%g0+0@UWBv$Hc3w~XUJ0Xq4d{sU=;W`IPrw6rd| z3~Q_;WMu_^?99+jmzb#b#NXKO&bl;#O;26PA;x~I4*AbBjS61MESjP*wS>b|&avU) z?WR*UnSe&lRPrjI8F6Mmd2(xI%nxXHNlEP4-)d`X0koHi`y8C|ojJ?%)LzQTK}BMJ ztwZu(u1YRx4|ZABliFK~6y150Zp|@BzLfG=G$ltgG&C5h?aTqvT1Qv+G5pCh+Y4ke zndBC3dh_PZq0VN(5B6s++e42zC^XMf$4xOCO#e!3siLg0UyN^d!P>XcVjFc;RaKn& zt{X2e?^+T-QGkN;r_9dIZY_^^&9$=K4gnMpM7%B~a+H#gfG7p`iYjx)yfbBNc8M`W zBF*c3#lK8(P^JakX2c|B{YBnk^9j(vjK{gT5S{#7t@}xd&z?QY%*=G^$*#xtl1fUX z6mp+D5r$q)GPkpXh*M#nL|Q1SHP>IM^dmS%qqrghxS|TVVn(=L1LVcmc={KD(40|T z69!rnC{iV=X;}ggT5@u7Pnhz>=4$TjtyC>^y?y%@Wdqa?(C}auI`IG#D2SskC$7V% zd`DNK%;OLU(Qs}qJPKy;+xHf_KZkd9qiW(!On0s-mnH-Q!W9r5HvqBz@#E#^)!qxn zJat_UFN%nWn0P0Q3{xa5t3Br1Qx_L;w~ysgkR8xfH_YjLWEo7C6XVR-z!=jg%=lL2 zA2zZSs781E`SAs7bz<}QVT``MzK9-CV}^vXDY2z`A}A;b0Azi_?*qos(Fo6{e&=OD zK>;u#kVs+6A_Hf+`&Lh)NG;%<8Y`aYr5g151HGnjy$j`zkTeH$T60KdCf||Cled27 zIc!+dlict7UP5^BgWpIao<6x$OiT>BkeQVQnO|C}b8KUOutg*iUB@a{#%q$j{kFeO z@9ziYO5|$EJh>Ep2LQf_$;l0dBvw0_jC*~q5!=?t)~4|{vE$vsN0vM0V7f<^pO)rv zSiHWY+9V$Fmy$FXFU>panfu-EJ>@L!VZz|R07qXRr~y0kE-EfAt{7nG@ft6DSeV-W z*S9@tdoHuhv9<`SQhp}2y3sX+!b>dh?cp)*tIV1C|dE~r#N}jZUoSZk%IEdkP#QTXvLL>3{cQY_1y^Z`kE@k7vCN?LZd0i))R&%KWW z0s*wqtc|?d`z8y`7y<;_$(G zzv0mH>)MR2z{I$>xdISJ9WY7(3lM)Se2NW++M}IaU1vYW9mU}6j~Qx-S$ntX%WgN= zpW4HoVs53`{sI7~vw=(Rja27bWN*j`r8?#|iXiS`xY(wJxVU(5a4-!GjWJ)MrmN^c zo|pgku8W^KGiW^5Aao3k;fw%X0z+{DQ16^GVrpt?($a~w5o)(u@QwUIbGnC3be7Hk zO1xMj49I=+Z;LPd)HWT*RN<*VdQT3#+Yyx390NM0l#q}Rp%a);Ev{c58yz+FO=6jt zm;eSU>`75;Y8o0m9xv*cCTU|(kZWRYJ`DYz!JR2L+_CwzX@m|ua8DSAPf@2ZF;<5& z>}9-o@uH?iX(z!qEbWm3n>3I#r|wJ?y6woq` zi{qA!aB;?9?!h<;%K4J#|J#uXksT7ow5rae!}=#-@`w{uw6_JLg5%barn&bI@zP%# zb4c7`7q`#9IyL0+4{zG(F;%nu2k_JgVX`n;P8gjBiGnhrOr4P=9WC~aC{8mT!>1*E d{%@M^f^=T)awpZMR{?(yflvqoI0a@K_J7ma=|cbj literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_top.png b/Telegram/Resources/icons/lock/record_lock_top.png new file mode 100644 index 0000000000000000000000000000000000000000..a4ee1db1b2904f7416c09930e3e4923960849cae GIT binary patch literal 359 zcmV-t0hs=YP)tE!@dz$z?!t-!LZ zVHoI8un=pXEwF8S-*-9zti|%j1)8R<>q@7wZ5tH#3M5HtnubmW3W)xVh@yzN0MYFN z!!U>=(A|hA%aUgVbbkv>(h>Cz30avy76x-*8si*(|002ovPDHLk FV1ig0m5l%Z literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_top@2x.png b/Telegram/Resources/icons/lock/record_lock_top@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b578b31059c3132800c1fc8c2b856a61b9f07b8c GIT binary patch literal 661 zcmV;G0&4w*pV`XP{wtNUa08PqD zO2(d=1vad(QFfxF49bGvd)HHMjr8)&YyO?{Tw62e-1jv)&dm7w`sEjEh(scSAe75x zNs@-c;e0;dZnwwdk%+F>>wdpqtyZ(y3?JbD9EO8&3M1u%!C)?ztJmx6^_p~n6B~^N z2EkCy>+kpbMNw1~<#xN#hq&MG7!bo^a0e(zCX={;m&=6=1A}7*2bQ zDPSU5mNBV~6okX!R;y*2f(IR#8UZX;;C8!9rPBF)CQCs8#6TEpEW~25@pw$O0b%0t zxY-u6*{rInWHS&6!Aw%%a=EJ2D%qG?t%i`sTJU&0olb{r4?-fUQ5JkY-(WBxhk&RE zY{+JDI-QcEK;RE=L%m+_Xfz@Rg4hWEvDLBL?UG}`e)ltLr_FyO-T@W<*8=W;i3#%g zynYLbMB;Qhu^|FQP)Fy|Fquqi8(}3{F#M7AaFPw*b{+LsP+R9&1RDr z1=ZfQkWQy9dCFqivz&LIYss=qOw;f8Ut36}QVf3`{(dP?^MwdWl86IP^TjQup->sK;h|Olh zn+Q~Xv=EI(iAzviBSH`aK1HCoMnt7j;Zp>P^SHBqz1McoE3D6g;u;Z)#ez=}%jHrd vVz=AzDFVecBEJ9c$RVn#YD5snd>i-!9z0f8tbs7&00000NkvXXu0mjfRwXgS literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_top@3x.png b/Telegram/Resources/icons/lock/record_lock_top@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8d63fe3c127d7f74b84fa49489a09a67afb148e GIT binary patch literal 1010 zcmV36TP7#7YZ8ft86SG`3hmjV3fGYyq*-#9k@DL|UcS zfpPVX*qu2?V3}QEdbhW?4-XIX z`TW(@RlQzswOZ|V`}Os;-|w>@;0t_`IEuuYX=X=zE4q~UPL3&Q!`A4`T6&ZPqso-qYIT+p!C;Uc7Cast5Ic2@i;D|##{-C+`ZGoE@9!jz2N3IWxu%GbPN&HlKcIJt z7&`f!omqk2@0N%}BIJuV-~D`Tu~;NoyeXARlS|m`_D-imvUt;OwscW5nZeawHN69~tZww9CZk^?Ia6JpF!O gCI)%YieCx70KdJ*Y!AUGEC2ui07*qoM6N<$f)m-hCIA2c literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_top_shadow.png b/Telegram/Resources/icons/lock/record_lock_top_shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..30d0aa4ec92cf5805f4a3e12dcab5c0686d967f2 GIT binary patch literal 557 zcmV+|0@D47P)U{XjV-7ROaSgcekb-P`| zF!uZX`Fs{nfG~ndA(3=Qyk4*54@M=mTCLS;W!tuR3nY>b2~^TPVK5lX=kvW@Z?oBm zkFniup@I^zJjNbcwNQ@H zXoU2GyRBBMt=DVy<`6~t-kryI*l09%yPbM-h$4-Fo8}l_IXH}B>dhgFGzM;(W7=kj vy_y`=08ykdaMK+B0ZiNCpMW$5F8%xfX+W6Sp^+#300000NkvXXu0mjfZC(2- literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/lock/record_lock_top_shadow@2x.png b/Telegram/Resources/icons/lock/record_lock_top_shadow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..bc3943e4e0ece9ac5c056b06a8539e59a809f2de GIT binary patch literal 2244 zcmZuzc{r478=o-~!;GB}jN?8^)3?p^4gz814<v^vDJFX&q2Dy2f zT8gq*y+j?&T!q_XB0Pc(mf~6iaPw62Wr|6;tVpR+8NctZQAn*-B@G_?!HyeaW9_!Z zw&>&3Jj;sqs|9+7`^%=~84pHmiO;+ZVXJTa*>TJnX2Zn85iEQ3r^hast<}V!yAnM; zJ+EB3LLduFo{JwYKUT7(7Y{4EO-!(#sIE^s;F%7FoC&5HQ4> zILlx#Mnch#;!3%Bb8u;^qM~AXd3kbjvbjZ6SU7yKKf5c1|8s2;7k|U; zVX1j!pL(Jag5c;UD3X(1ZQKg>B@+9#6Jl6zo~H0g%b8nPSX5S4Iy*b->FMq6?j|QE z_w@9Xm6gT4rsw43R6EtWw_Zz&tN+O*fk)zf$1-l1F?O>;#qi3&2pgW6F;G-g#Nlx2 z>Yo+|sL=OYOD{!5MVo@=s%mO#s;m8de0%}|0(^aa{r#iAu1OfWy1Evgj@<)n`!Z#I z4)_Q?B*ne}@peCJYI4%1ai7iZ>g*H}5(4m>4JK+XwZ(qdPVn*bTU}dQTUl9QG9{&? z927;my1GhAN-i5RQut-QPfRM+XOE6OeKX3k!vYarbF7T7G_hK>_>g`pgdN?Z5d= z0QhzGUM_FIWsS4C{h{feJV|$ZbTJ_}3~299fcuRQIgi6PaPXV<;>TU4-oA^s*sNpSf$5EyCr{FL<5`vsC*%IvXMf+h^4Hc}LvpZ8jbJ>Y8>w{alr%Zj z44vqtLDKy&)ju?_*%G}Igc{W#zM5?gP0FVf&oA_4iUeO6Z|E66ec<2GAr4S^$xqb+ z2C{ydK+>Yk9{^Y5^T*dtj*hq|qYFTJb?bQKW>wy>ZjSnCgR-=NtZ8z^2v3Hz0cpN7 z8OQ}GB7&cn*OuVO{F6N~ZYtE7m4Yvo6Q43!bHosPT2a)2uWM6DFwO{f6RdlCk%IMw z3-pjP--oMG)D~rhDq-f@?~Rc}Cq>fZ@Do%zonBkZ8Ywxu;*2NnZv6`3(mDe#LG|(- z#+ZFOQUCJhCXwv3+Fp^L85SPSnrrJLj%`&`cJsaHH$MrBkB5xmUiHorZwB|` zIptiwPdy@%E9`ag;PuSu{YhEzsZ=hoKuA1CV2jZ2E*)LTf;6w3`sEM9_a38f)sPrZ zl9|#Cg8UrFcc5b@;$P;Jp3EhG8OapymQa$&@cFE%B7N%*af2)Jzd(&Tz&x5DEm|ER zXJfKHNg`)(@7)Us=XrC5Kc3ogi5-F1tO3QRSy-njJ>3Z0Fgzab?Tz9$x{pIFO{*T$Erp55u1`H!;hl*P5g$+@_d`yMbiIzYc4K{gaZGt>>7Bg1ztep_w%#i> z{7pr7eSN(M3`5l+vD>5E6F@<#%E`%Lv6*)IYHCH4f@|}9@I#a}J8Z$lIY2|GRH`x> zt*@`&+uQ3+B<2da9FpV7(KdFj&&D(_ltki#!%LuJ493@;ozJlfS*2+1BMg=Pc(vO0<;r>FICIJtTz%3U8V9q-8j$GxM(;9-F;UcJHqN+>U1QdL#e)Rgy& zLjG#G-HH)Jz|zy0GbUR#k>VE=WL1`)o<8*Yb!~0!$J;m!jgmXd8~}L<$0R~F#i(>Q zhx6xOUTPz$OZ=XjJ5pC!By@M~_}vu{RCuzx+pOb-hh^iaW8}`pOcQIY(mrBimb|?> zo`>~NU>tXFfgXADP8j+GP6MQ!N)=W`>FDeL1sVMIS;77Lj_0Bdd4uqsHnyo9%AUEW z9k})7OL|&bOW4x6@Nf;bLnj&St$ep55z(BI!gXMzu0uJtvA4|x(gmnu&e{X?Q_+Kf z+!VuCPst(>h=+7K1OoYcbQG zA+6jT#WwOG?b<-Q1)2nfLUDC*i3FX0R(9*Q=np5gIEY?eF9gISC4rCA3`Vc3*_P^m n0v-ZHM7p_20Y^MXj6B!7)PJ;JaOOe{XEZN9%!l%6F>++AP})C9HtH2vw%wz4-9-7^R3DPH+)yP zkp~DQz;=CM_!h`{17Fg4DjRz0xIFUov2eErDOkB!TC=D+S=d->TU%K9xpi6J27zwG zsKOL}3wz6I-4@%b5eon&v ztD&wk`L7=+y^>MAjol7rmt$A2)&3mh$k~|r%L^&e&gV`)s-1@3KKiLcGgU){NL=X5{SJSZL4H$KWQg2pfV-uGDI@t0qvLc?H)7=-fFTZWc<#=6BX zUUVI8{&-HSie}+R>+S8u6I66P*_n%;o1dRwUXDbRH5^SGtPNp;!J(m{%f0EMF4Ofj zGeI(9Vh7ni(?$obyH|uX0z~-uM&$?$(j8jTz6|lW|1u|;pd5Yn&HmTBb5v^nzOOLaiP` zSMW&YB}tZSiJFPxTiA)7H|s%o^>m3h6T>+!3GtKUF}PxqSJNfka%*a8Uca8%C~J~^ z^6TPw=E;+_!GfaDAUG8>bCcJ`D5H$`Pr#>{#y|i|N#7lWnp$@1DcXw^=!RZEnzyCoqdO`9dB{q@)Z``kEtk0jGNf>ZvCs02)~`J_-MTK9F_|`adY(w+fN62~&lYsF%c^ zHCgcz^UDR$CePcd6x-yfBnIt%p()fzC#R)_dtnI(S9)(6E84Go%Wh*8PJQ+2ReE|1 zG&;%=@U642PmqtV-fKhO&24w{M~%9g+SY|z%FCB~Q5RI#MA#<~+>_2Mr=tJBpUJjU zYWfN4G|`sB@Dw90L^v(nA}uO8Ew&}?>Fywp#A8Kg4Xs<|s+*Kjr*o~*#jWGx>5uPP z^>-%Wu(7dmhcr($_)FM{^79jKwb5Bb(PCp`D=RC*_4M`iwY0R1jQqBz8_tinug*5g zu1bxsP8ZHLYi1Vv{ASHY=Kqrq(L;szJs6_g#c&Wf?FjNn3#Pl4JUGZ|7=HpB?bPNt zX`9P~(OrbFwe9q@b$0%C(%EOem~VkkNEjCzn_Hy{hktK+a`hhBA+Mp`;Jpc%j1d*9 z^*`PcY`O>yj}AAsJ@OxU{ATLxa!$ML(KQ8cq_Hgj2LoGg$5Yw~(=WJTsJP-5LWHg8 zRfg9w)I_^rfI=+I&NfTVZETz`XS#ge*zc1|jEiGCXO#9_ZHu88h7Ynu4?Lq(L)@?! zi3oU;r)P>tm}N;=c#zNz>TRag_$<`h3fK68=xz5kX}&SpQoMfiJUR}DCTr|HQl(v1 zdo&mW^OgUWR2^x1o5Zv)a(DiVdBE-$iJ1VUx;&j*vbOf_9v=9RSsn%aV_f17l5l+U zKj#MUx=oj;-7&rco1uj<>$5mR+r2EO*(4`Pt&|vSPP#pIc{CcGPI3Fr9l9$Teq&fk z7nkZjz95RjiDy?qMM;F+mMf%=xO*b9Y=<)){n*U=f0i*O9famLe1Imb#j ztn=A+wyCZ5 zZC6p&e8yCT9;mu#TDPQKw#bLajngI)N`}fvQjw7bhtNY|hQsI$I4^;1h#RaKAs zA1CrHC)-u%kxTH))U^UWGuQl{Jf zM~=?U&USWdY|q`r-&$t<$sL#lfk6@tD)2D%;zo%idT)$Ag}Ssw-vETtW4X8G>B-57 zQWVLYVNj54AXcfY8h6dIZv1aHkzzdnVevI%+<4Pm&|7v-UH5c{)#vV`M~{HwF4%CO z?dmEN5njnAoROMpps&v%eD>xGUyK0&Sd2k%Ft#KMn6h@UQPtqJrzZ!GsmUkr&G`8E zvx7CHgTu!9I)sdjjESkyq7`Q%T-4@UWzxG6VbZWa&SDF^2_XqaIFjdfci^HzLqZhF z%o^UI(WWLQk9yPI`|esApXTT114_A|(bH8YzGCsWr`Tfs7-aE>S7~{eT1)$cdI}S! zoSdA@%&(Q>sL^H(XHm>Sl3weSPJun~15I&%I~vUKRfu-uN-)-og@-OL@cc*@+rz_y zNAB`06q_(NHEh=I!bNKC0#vTJ*u87~gK?xmp+JGbcFdM}D;>)d z#(nFuxbW47ii-W69p4RfjXv5qB`qy2IaxuUo0pfZwJ3J<9d9(~4*{?^;24BvbPwqj zbx%!z%@Nt-<1;f?3yE^m^?n{e!?Dzpn$lLnQ~17CjT{HE3c|aK2-TJ5U>TbWb1;Ra z2)B23b`B4F0?p!!F-@K8(S6_ASv%HM|5g(`pPa_FTT`ynS>Z{|R zl_In`kQbGbG7^qY0mj5k<$qXh-Se`tva-aW?B&asvX(Oqhf~5K>ZR$cY0dxMsZ>5{ ztb1zNEeNMWP6r-O0Wn>kAB%cT)p^CuWM}v~WQK>}NK144MhjaoNrB(|;UO1KQw9m6 zG_ZC@hV>8^3kwE=^(Pk=76MXax|z5>Bn(jbI^!*bRu3@i^_^TdVS0`2{h7%JocA)? zI_teRCxBz%vGR?IimHpT45h=kMWS%cMSv35M$iEb193&&j?#$cV7G<#uUS&hot-vD z(Uh1d?k4fWyXCSzQ6MZfAq4`%9WdQ+Rko1>r#h zD!%s}3pSr`2$6kon5jtlOG`Um)S}r(nuEPP<6767UynmDaRtok4NXh}wx{oiYiuyT zL;S@TmLY*IbdD8*F^=cs*x;Z$aQuRTf`GXrQ^G~o`zNhVMI{`?b7(#&5JFi#i!D-w zLE^nBT3($CET5Q|7#T^aB%`9L@;`PakCLWzM2P*N1~83^I$rl_h5hhd#EWuF@;f-!p0lUQ<(nTazE%S6C!Y>}N_6 znE(SR5AlM-7*F;h^cI#H^>ej>7JO^6@IJGM3?nq~sLpGHgjqH#JDcB!1VvtF`X~8x zj*%pcYDUUf_c2?;ql3)4kyg7&&NftoMpx5w^h-T)t*86p1*-l_3%I(#xkWLdtE z?yqWK0OsJ}z{DlVHxY%RO+JbU3k&n|s%Jkqyn_Zv3c{7yNe10aQp&S}zzX<8_tjDt zmzSY{LIRBw0E%`y&WO&f1d$>bePQeWMC=#trW31o-5X3gEF(~>S`t?CP1ix zXZLFMe1hU4i-VtQ?DZJ3`RVU~e`As{fYwY9azaoimM8QEPz6t1w4)O1_3d7;h4KJ;gDBm=MATjt!z|*pxWj0gFp^A>Uh9gG Y;O2AA$#g7k;GY|istopValue(), _scroll->heightValue() ) | rpl::map([=](int top, int height) { - return top + height; + return top + height - st::historyRecordLockPosition.y(); }); _voiceRecordBar->setLockBottom(std::move(scrollHeight)); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index d94221565..a5d1a3085 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -66,13 +66,18 @@ public: private: void init(); + void drawProgress(Painter &p); + Ui::Animations::Simple _lockAnimation; rpl::variable _progress = 0.; }; RecordLock::RecordLock(not_null parent) : RpWidget(parent) { - resize(st::historyRecordLockSize); + resize( + st::historyRecordLockTopShadow.width(), + st::historyRecordLockSize.height()); + // resize(st::historyRecordLockSize); init(); } @@ -90,23 +95,101 @@ void RecordLock::init() { ) | rpl::start_with_next([=](const QRect &clip) { Painter p(this); if (isLocked()) { - const auto color = anim::color( - Qt::red, - Qt::green, + const auto top = anim::interpolate( + 0, + height() - st::historyRecordLockTopShadow.height() * 2, _lockAnimation.value(1.)); - p.fillRect(clip, color); + p.translate(0, top); + drawProgress(p); return; } - p.fillRect(clip, anim::color(Qt::blue, Qt::red, _progress.current())); + drawProgress(p); }, lifetime()); locks( ) | rpl::start_with_next([=] { - const auto duration = st::historyRecordVoiceShowDuration * 3; + const auto duration = st::historyRecordVoiceShowDuration; _lockAnimation.start([=] { update(); }, 0., 1., duration); }, lifetime()); } +void RecordLock::drawProgress(Painter &p) { + const auto progress = _progress.current(); + + const auto &originTop = st::historyRecordLockTop; + const auto &originBottom = st::historyRecordLockBottom; + const auto &originBody = st::historyRecordLockBody; + const auto &shadowTop = st::historyRecordLockTopShadow; + const auto &shadowBottom = st::historyRecordLockBottomShadow; + const auto &shadowBody = st::historyRecordLockBodyShadow; + const auto &shadowMargins = st::historyRecordLockMargin; + + const auto bottomMargin = anim::interpolate( + 0, + rect().height() - shadowTop.height() - shadowBottom.height(), + progress); + + const auto topMargin = anim::interpolate( + rect().height() / 4, + 0, + progress); + + const auto full = rect().marginsRemoved( + style::margins(0, topMargin, 0, bottomMargin)); + const auto inner = full.marginsRemoved(shadowMargins); + const auto content = inner.marginsRemoved(style::margins( + 0, + originTop.height(), + 0, + originBottom.height())); + const auto contentShadow = full.marginsRemoved(style::margins( + 0, + shadowTop.height(), + 0, + shadowBottom.height())); + + const auto w = full.width(); + { + shadowTop.paint(p, full.topLeft(), w); + originTop.paint(p, inner.topLeft(), w); + } + { + const auto shadowPos = QPoint( + full.x(), + contentShadow.y() + contentShadow.height()); + const auto originPos = QPoint( + inner.x(), + content.y() + content.height()); + shadowBottom.paint(p, shadowPos, w); + originBottom.paint(p, originPos, w); + } + { + shadowBody.fill(p, contentShadow); + originBody.fill(p, content); + } + { + const auto &arrow = st::historyRecordLockArrow; + const auto arrowRect = QRect( + inner.x(), + content.y() + content.height() - arrow.height() / 2, + inner.width(), + arrow.height()); + p.setOpacity(1. - progress); + arrow.paintInCenter(p, arrowRect); + p.setOpacity(1.); + } + { + const auto &icon = isLocked() + ? st::historyRecordLockIcon + : st::historyRecordUnlockIcon; + icon.paint( + p, + inner.x() + (inner.width() - icon.width()) / 2, + inner.y() + (originTop.height() * 2 - icon.height()) / 2, + inner.width()); + } +} + void RecordLock::requestPaintProgress(float64 progress) { if (isHidden() || isLocked()) { return; @@ -230,7 +313,7 @@ void VoiceRecordBar::init() { auto callback = [=](auto value) { const auto right = anim::interpolate( -_lock->width(), - 0, + st::historyRecordLockPosition.x(), value); _lock->moveToRight(right, _lock->y()); if (value == 0. && !show) { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 1bf1b08ba..325c6160d 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -345,7 +345,21 @@ historyRecordDurationFg: historyComposeAreaFg; historyRecordTextTop: 14px; historyRecordLockShowDuration: historyToDownDuration; -historyRecordLockSize: size(50px, 150px); +historyRecordLockSize: size(75px, 150px); + +historyRecordLockTopShadow: icon {{ "lock/record_lock_top_shadow", historyToDownShadow }}; +historyRecordLockTop: icon {{ "lock/record_lock_top", historyToDownBg }}; +historyRecordLockBottomShadow: icon {{ "lock/record_lock_bottom_shadow", historyToDownShadow }}; +historyRecordLockBottom: icon {{ "lock/record_lock_bottom", historyToDownBg }}; +historyRecordLockBodyShadow: icon {{ "lock/record_lock_body_shadow", historyToDownShadow }}; +historyRecordLockBody: icon {{ "lock/record_lock_body", historyToDownBg }}; +historyRecordLockMargin: margins(4px, 4px, 4px, 4px); +historyRecordLockArrow: icon {{ "history_down_arrow-flip_vertical", historyToDownFg }}; + +historyRecordLockPosition: historyToDownPosition; + +historyRecordLockIcon: icon {{ "dialogs_unlock", historyToDownFg, point(1px, 0px) }}; +historyRecordUnlockIcon: icon {{ "dialogs_lock", historyToDownFg, point(0px, 0px) }}; historySilentToggle: IconButton(historyBotKeyboardShow) { icon: icon {{ "send_control_silent_off", historyComposeIconFg }}; From 914e40fb62c93cd5479743d1b2fc5ea7f7e38cbc Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 9 Oct 2020 17:07:58 +0300 Subject: [PATCH 066/370] Added red coloring of record button. --- .../history_view_voice_record_bar.cpp | 21 +++++++++++++ Telegram/SourceFiles/ui/chat/chat.style | 2 ++ .../SourceFiles/ui/controls/send_button.cpp | 30 +++++++++++++++++-- .../SourceFiles/ui/controls/send_button.h | 3 ++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index a5d1a3085..d822a0f05 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -340,6 +340,24 @@ void VoiceRecordBar::init() { }) | rpl::start_with_next([=] { stop(true); }, _recordingLifetime); + + auto hover = _send->events( + ) | rpl::filter([=](not_null e) { + return e->type() == QEvent::Enter + || e->type() == QEvent::Leave; + }) | rpl::map([=](not_null e) { + return (e->type() == QEvent::Enter); + }); + + _send->setLockRecord(true); + _send->setForceRippled(true); + rpl::single( + false + ) | rpl::then( + std::move(hover) + ) | rpl::start_with_next([=](bool enter) { + _inField = enter; + }, _recordingLifetime); }, lifetime()); } @@ -482,6 +500,9 @@ void VoiceRecordBar::stop(bool send) { _recordingSamples = 0; _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); + _send->setForceRippled(false); + _send->clearRecordState(); + _controller->widget()->setInnerFocus(); }; _lockShowing = false; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 325c6160d..46e808eb7 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -333,7 +333,9 @@ historyRecordVoiceDuration: 120; historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; historyRecordVoiceActive: icon {{ "send_control_record", historyRecordVoiceFgActive }}; +historyRecordVoiceCancel: icon {{ "send_control_record", attentionButtonFg }}; historyRecordVoiceRippleBgActive: lightButtonBgOver; +historyRecordVoiceRippleBgCancel: attentionButtonBgRipple; historyRecordSignalColor: attentionButtonFg; historyRecordSignalMin: 5px; historyRecordSignalMax: 12px; diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index f28cff456..ec9635791 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -43,7 +43,7 @@ void SendButton::setType(Type type) { update(); } if (_type != Type::Record) { - _recordProgress = 0.; + clearRecordState(); } } @@ -73,6 +73,19 @@ void SendButton::requestPaintRecord(float64 progress) { } } +void SendButton::setLockRecord(bool lock) { + if (_type == Type::Record) { + _recordLocked = lock; + update(); + } +} + +void SendButton::clearRecordState() { + _recordLocked = false; + _recordProgress = 0.; + update(); +} + void SendButton::paintEvent(QPaintEvent *e) { Painter p(this); @@ -104,8 +117,17 @@ void SendButton::paintEvent(QPaintEvent *e) { void SendButton::paintRecord(Painter &p, bool over) { const auto recordActive = _recordProgress; if (!isDisabled()) { - auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive); - paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), &rippleColor); + auto rippleColor = anim::color( + _recordLocked + ? st::historyRecordVoiceRippleBgCancel + : st::historyAttachEmoji.ripple.color, + st::historyRecordVoiceRippleBgActive, + recordActive); + paintRipple( + p, + (width() - st::historyAttachEmoji.rippleAreaSize) / 2, + st::historyAttachEmoji.rippleAreaPosition.y(), + &rippleColor); } auto fastIcon = [&] { @@ -113,6 +135,8 @@ void SendButton::paintRecord(Painter &p, bool over) { return &st::historyRecordVoice; } else if (recordActive == 1.) { return &st::historyRecordVoiceActive; + } else if (_recordLocked) { + return &st::historyRecordVoiceCancel; } else if (over) { return &st::historyRecordVoiceOver; } diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h index 443571d43..938a0aa0c 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.h +++ b/Telegram/SourceFiles/ui/controls/send_button.h @@ -29,6 +29,8 @@ public: return _type; } void setType(Type state); + void setLockRecord(bool lock); + void clearRecordState(); void setSlowmodeDelay(int seconds); void finishAnimating(); @@ -57,6 +59,7 @@ private: Ui::Animations::Simple _a_typeChanged; + bool _recordLocked = false; float64 _recordProgress = 0.; int _slowmodeDelay = 0; From fe5242d6d2bce3418e99939e20767259de0b7713 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 9 Oct 2020 21:29:03 +0300 Subject: [PATCH 067/370] Added ability to pass filter for Escape key to voice record bar. --- Telegram/SourceFiles/history/history_widget.cpp | 3 +++ .../controls/history_view_voice_record_bar.cpp | 14 +++++++++++--- .../view/controls/history_view_voice_record_bar.h | 3 +++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 2f706afca..1ad11462f 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -726,6 +726,9 @@ void HistoryWidget::initVoiceRecordBar() { }); _voiceRecordBar->setLockBottom(std::move(scrollHeight)); } + _voiceRecordBar->setEscFilter([=]() -> bool { + return _replyToId || (_nonEmptySelection && _list); + }); _voiceRecordBar->startRecordingRequests( ) | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index d822a0f05..02f05e4d3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -391,6 +391,10 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn &&callback) { _showAnimation.start(std::move(animationCallback), from, to, duration); } +void VoiceRecordBar::setEscFilter(Fn &&callback) { + _escFilter = std::move(callback); +} + void VoiceRecordBar::setLockBottom(rpl::producer &&bottom) { std::move( bottom @@ -412,6 +416,7 @@ void VoiceRecordBar::startRecording() { const auto shown = _recordingLifetime.make_state(false); _recording = true; + _controller->widget()->setInnerFocus(); instance()->start(); instance()->updated( ) | rpl::start_with_next_error([=](const Update &update) { @@ -429,7 +434,6 @@ void VoiceRecordBar::startRecording() { show(); _inField = true; - _controller->widget()->setInnerFocus(); _send->events( ) | rpl::filter([=](not_null e) { @@ -645,11 +649,15 @@ void VoiceRecordBar::installClickOutsideFilter() { const auto type = e->type(); const auto noBox = !(*box); if (type == QEvent::KeyPress) { + const auto key = static_cast(e.get())->key(); + const auto isEsc = (key == Qt::Key_Escape); if (noBox) { + if (isEsc && (_escFilter && _escFilter())) { + return Result::Continue; + } return Result::Cancel; } - const auto key = static_cast(e.get())->key(); - const auto cancelOrConfirmBox = (key == Qt::Key_Escape + const auto cancelOrConfirmBox = (isEsc || (key == Qt::Key_Enter || key == Qt::Key_Return)); return cancelOrConfirmBox ? Result::Continue : Result::Cancel; } else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 29ed0cab8..d3c6f28a7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -46,6 +46,7 @@ public: [[nodiscard]] rpl::producer lockShowStarts() const; void setLockBottom(rpl::producer &&bottom); + void setEscFilter(Fn &&callback); [[nodiscard]] bool isRecording() const; [[nodiscard]] bool isLockPresent() const; @@ -93,6 +94,8 @@ private: QRect _durationRect; QRect _messageRect; + Fn _escFilter; + rpl::variable _recording = false; rpl::variable _inField = false; int _recordingSamples = 0; From b6743feec1f36eafd3b748e6f23580ed3abae9fe Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 9 Oct 2020 21:49:14 +0300 Subject: [PATCH 068/370] Added ability to send recording voice message with Enter key. --- .../history_view_voice_record_bar.cpp | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 02f05e4d3..26c26e37c 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -36,6 +36,12 @@ constexpr auto kMaxSamples = constexpr auto kPrecision = 10; +enum class FilterType { + Continue, + ShowBox, + Cancel, +}; + [[nodiscard]] auto Duration(int samples) { return samples / ::Media::Player::kDefaultFrequency; } @@ -627,7 +633,7 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { void VoiceRecordBar::installClickOutsideFilter() { const auto box = _recordingLifetime.make_state>(); const auto showBox = [=] { - if (*box || _send->underMouse()) { + if (*box) { return; } auto sure = [=](Fn &&close) { @@ -642,40 +648,48 @@ void VoiceRecordBar::installClickOutsideFilter() { }; const auto computeResult = [=](not_null e) { - using Result = base::EventFilterResult; + using Type = FilterType; if (!_lock->isLocked()) { - return Result::Continue; + return Type::Continue; } const auto type = e->type(); const auto noBox = !(*box); if (type == QEvent::KeyPress) { const auto key = static_cast(e.get())->key(); const auto isEsc = (key == Qt::Key_Escape); + const auto isEnter = (key == Qt::Key_Enter + || key == Qt::Key_Return); if (noBox) { - if (isEsc && (_escFilter && _escFilter())) { - return Result::Continue; + if (isEnter) { + stop(true); + return Type::Cancel; + } else if (isEsc && (_escFilter && _escFilter())) { + return Type::Continue; } - return Result::Cancel; + return Type::ShowBox; } - const auto cancelOrConfirmBox = (isEsc - || (key == Qt::Key_Enter || key == Qt::Key_Return)); - return cancelOrConfirmBox ? Result::Continue : Result::Cancel; + return (isEsc || isEnter) ? Type::Continue : Type::ShowBox; } else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) { - return Result::Cancel; + return Type::ShowBox; } else if (type == QEvent::MouseButtonPress) { return (noBox && !_send->underMouse()) - ? Result::Cancel - : Result::Continue; + ? Type::ShowBox + : Type::Continue; } - return Result::Continue; + return Type::Continue; }; auto filterCallback = [=](not_null e) { - const auto result = computeResult(e); - if (result == base::EventFilterResult::Cancel) { + using Result = base::EventFilterResult; + switch(computeResult(e)) { + case FilterType::ShowBox: { showBox(); + return Result::Cancel; + } + case FilterType::Continue: return Result::Continue; + case FilterType::Cancel: return Result::Cancel; + default: return Result::Continue; } - return result; }; auto filter = base::install_event_filter( From cdb77d46b1579912d08cae53de601bb7769467f6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 10 Oct 2020 15:03:28 +0300 Subject: [PATCH 069/370] Slightly refactored text drawing in VoiceRecordBar. --- .../history_view_voice_record_bar.cpp | 62 +++++++++++++------ .../controls/history_view_voice_record_bar.h | 3 + Telegram/SourceFiles/ui/chat/chat.style | 9 ++- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 26c26e37c..ccd2ddf48 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -253,18 +253,27 @@ void VoiceRecordBar::updateControlsGeometry(QSize size) { _cancelFont->width(FormatVoiceDuration(kMaxSamples)), _redCircleRect.height()); } - { - const auto left = _durationRect.x() - + _durationRect.width() - + ((_send->width() - st::historyRecordVoice.width()) / 2); - const auto right = width() - _send->width(); - const auto width = _cancelFont->width(cancelMessage()); - _messageRect = QRect( - left + (right - left - width) / 2, - st::historyRecordTextTop, - width + st::historyRecordDurationSkip, - _cancelFont->height); - } + updateMessageGeometry(); +} + +void VoiceRecordBar::updateMessageGeometry() { + const auto left = _durationRect.x() + + _durationRect.width() + + st::historyRecordTextLeft; + const auto right = width() + - _send->width() + - st::historyRecordTextRight; + const auto textWidth = _message.maxWidth(); + const auto width = ((right - left) < textWidth) + ? st::historyRecordTextWidthForWrap + : textWidth; + const auto countLines = std::ceil((float)textWidth / width); + const auto textHeight = _message.minHeight() * countLines; + _messageRect = QRect( + left + (right - left - width) / 2, + (height() - textHeight) / 2, + width, + textHeight); } void VoiceRecordBar::init() { @@ -334,10 +343,6 @@ void VoiceRecordBar::init() { _lock->hide(); _lock->locks( ) | rpl::start_with_next([=] { - - updateControlsGeometry(rect().size()); - update(_messageRect); - installClickOutsideFilter(); _send->clicks( @@ -365,6 +370,22 @@ void VoiceRecordBar::init() { _inField = enter; }, _recordingLifetime); }, lifetime()); + + rpl::merge( + _lock->locks(), + shownValue() | rpl::to_empty + ) | rpl::start_with_next([=] { + const auto direction = Qt::LayoutDirectionAuto; + _message.setText( + st::historyRecordTextStyle, + _lock->isLocked() + ? tr::lng_record_lock_cancel(tr::now) + : tr::lng_record_cancel(tr::now), + TextParseOptions{ TextParseMultiline, 0, 0, direction }); + + updateMessageGeometry(); + update(_messageRect); + }, lifetime()); } void VoiceRecordBar::activeAnimate(bool active) { @@ -563,10 +584,13 @@ void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { st::historyRecordCancel, st::historyRecordCancelActive, 1. - recordActive)); - p.drawText( + + _message.draw( + p, _messageRect.x(), - _messageRect.y() + _cancelFont->ascent, - cancelMessage()); + _messageRect.y(), + _messageRect.width(), + style::al_center); } rpl::producer VoiceRecordBar::sendActionUpdates() const { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index d3c6f28a7..e96ea6583 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -55,6 +55,7 @@ private: void init(); void updateControlsGeometry(QSize size); + void updateMessageGeometry(); void recordError(); void recordUpdated(quint16 level, int samples); @@ -94,6 +95,8 @@ private: QRect _durationRect; QRect _messageRect; + Ui::Text::String _message; + Fn _escFilter; rpl::variable _recording = false; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 46e808eb7..0ea46b9eb 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -344,7 +344,14 @@ historyRecordCancelActive: windowActiveTextFg; historyRecordFont: font(13px); historyRecordDurationSkip: 12px; historyRecordDurationFg: historyComposeAreaFg; -historyRecordTextTop: 14px; + +historyRecordTextStyle: TextStyle(defaultTextStyle) { + font: historyRecordFont; +} + +historyRecordTextWidthForWrap: 210px; +historyRecordTextLeft: 15px; +historyRecordTextRight: 25px; historyRecordLockShowDuration: historyToDownDuration; historyRecordLockSize: size(75px, 150px); From 497074073952f064acd637900f7ce4b4e14bc1f1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 10 Oct 2020 15:16:48 +0300 Subject: [PATCH 070/370] Fixed voice recording lock position on resizing. --- .../history_view_voice_record_bar.cpp | 54 ++++++++++--------- .../controls/history_view_voice_record_bar.h | 2 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index ccd2ddf48..7240cd0e1 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -236,26 +236,6 @@ VoiceRecordBar::~VoiceRecordBar() { } } -void VoiceRecordBar::updateControlsGeometry(QSize size) { - _centerY = size.height() / 2; - { - const auto maxD = st::historyRecordSignalMax * 2; - const auto point = _centerY - st::historyRecordSignalMax; - _redCircleRect = { point, point, maxD, maxD }; - } - { - const auto durationLeft = _redCircleRect.x() - + _redCircleRect.width() - + st::historyRecordDurationSkip; - _durationRect = QRect( - durationLeft, - _redCircleRect.y(), - _cancelFont->width(FormatVoiceDuration(kMaxSamples)), - _redCircleRect.height()); - } - updateMessageGeometry(); -} - void VoiceRecordBar::updateMessageGeometry() { const auto left = _durationRect.x() + _durationRect.width() @@ -276,6 +256,14 @@ void VoiceRecordBar::updateMessageGeometry() { textHeight); } +void VoiceRecordBar::updateLockGeometry() { + const auto right = anim::interpolate( + -_lock->width(), + st::historyRecordLockPosition.x(), + _showLockAnimation.value(_lockShowing.current() ? 1. : 0.)); + _lock->moveToRight(right, _lock->y()); +} + void VoiceRecordBar::init() { hide(); // Keep VoiceRecordBar behind SendButton. @@ -291,7 +279,25 @@ void VoiceRecordBar::init() { sizeValue( ) | rpl::start_with_next([=](QSize size) { - updateControlsGeometry(size); + _centerY = size.height() / 2; + { + const auto maxD = st::historyRecordSignalMax * 2; + const auto point = _centerY - st::historyRecordSignalMax; + _redCircleRect = { point, point, maxD, maxD }; + } + { + const auto durationLeft = _redCircleRect.x() + + _redCircleRect.width() + + st::historyRecordDurationSkip; + const auto &ascent = _cancelFont->ascent; + _durationRect = QRect( + durationLeft, + _redCircleRect.y() - (ascent - _redCircleRect.height()) / 2, + _cancelFont->width(FormatVoiceDuration(kMaxSamples)), + ascent); + } + updateMessageGeometry(); + updateLockGeometry(); }, lifetime()); paintRequest( @@ -326,11 +332,7 @@ void VoiceRecordBar::init() { const auto duration = st::historyRecordLockShowDuration; _lock->show(); auto callback = [=](auto value) { - const auto right = anim::interpolate( - -_lock->width(), - st::historyRecordLockPosition.x(), - value); - _lock->moveToRight(right, _lock->y()); + updateLockGeometry(); if (value == 0. && !show) { _lock->hide(); } else if (value == 1. && show) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index e96ea6583..dfe60f111 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -54,8 +54,8 @@ public: private: void init(); - void updateControlsGeometry(QSize size); void updateMessageGeometry(); + void updateLockGeometry(); void recordError(); void recordUpdated(quint16 level, int samples); From ba3862e70f2cbb7b5b84497749ddf0b6dd3a781e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 10 Oct 2020 21:27:01 +0300 Subject: [PATCH 071/370] Added new send recorded voice button with recording animation. --- .../icons/send_control_record_active.png | Bin 0 -> 869 bytes .../icons/send_control_record_active@2x.png | Bin 0 -> 1396 bytes .../icons/send_control_record_active@3x.png | Bin 0 -> 1877 bytes .../history_view_voice_record_bar.cpp | 221 ++++++++++++++++-- .../controls/history_view_voice_record_bar.h | 3 + Telegram/SourceFiles/ui/chat/chat.style | 8 +- 6 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 Telegram/Resources/icons/send_control_record_active.png create mode 100644 Telegram/Resources/icons/send_control_record_active@2x.png create mode 100644 Telegram/Resources/icons/send_control_record_active@3x.png diff --git a/Telegram/Resources/icons/send_control_record_active.png b/Telegram/Resources/icons/send_control_record_active.png new file mode 100644 index 0000000000000000000000000000000000000000..af937a2014591dd16c3b53a729f7c251298a8c6f GIT binary patch literal 869 zcmV-r1DgDaP)1DYb%Cm*kqXLXw6{iWFDD#i8IJ zy7)EtALuH$DhPrih&cEc__auh?=^)MY45naKc3w4a`znI&*;ps`;vfRIy{$7Nvnl5 zDfo^M%_v3?mYHoWWV0Fc96yiniTaQby?XEKI>JTOW`J*ic$Qh*A)Y7BY&n+TJ>p?j zmW#wE#BojsB)%28;_;i{lE+_yc|NxY_(e~t)WJ#zvobe`r-);QUS+NGh?_)Ib7h(8 z7_D*G$RUjsBq*q26FM9usG5{ncVDvUTSA3AjJ_6X;N&QwOx`%DRqCoz)ks%q>Zpm_ z|L19IGt*x8NDAnGb=}ViAh-`S>aP2H>bi|HAn*cQn{9uEa+>=lz24Sh$3S!kxV&j= z$^me-3yeNnGMAfDfR@!^C@P$8w=3uKS;~eSYGwTEdyxc5`*d1divg458`FuJe z4X)R#mnc$M);qIJF7^dcgtnqp{@4l&kt!}oCWdC%SmeW|PaN3u%>nm@Z_y-Dd56zq5y<9gjyU8I4Bt`~9BY zBAr5wce@>Py1DYb%Cm*kqXLXw6{iWFDD#i8IJ zy7)EtALuH$DhPrih&cEc__auh?=^)MY45naKc3w4a`znI&*;ps`;vfRIy{$7Nvnl5 zDfo^M%_v3?mYHoWWV0Fc96yiniTaQby?XEKI>JTOW`J*ic$Qh*A)Y7BY&n+TJ>p?j zmW#wE#BojsB)%28;_;i{lE+_yc|NxY_(e~t)WJ#zvobe`r-);QUS+NGh?_)Ib7h(8 z7_D*G$RUjsBq*q26FM9usG5{ncVDvUTSA3AjJ_6X;N&QwOx`%DRqCoz)ks%q>Zpm_ z|L19IGt*x8NDAnGb=}ViAh-`S>aP2H>bi|HAn*cQn{9uEa+>=lz24Sh$3S!kxV&j= z$^me-3yeNnGMAfDfRz`Wdhr3gRA_H{@FsfjBq%5ztnHzo`SV2+liBQUx~VAt zz_OXi%s1c8WW#22j4>{*Un75h4Uvf(cwOWmjj*fa^lH7ho5VW$g!f$VH)41q)_#%cROw;`c z;c5FJqgWvZRqcb6A0f1W%KQ5}H92*(c%X#D0*)l|C8Gj?;X}&nwa42;C6~)lQ=;y| za0Pe~!mAxBz8H8+lrmA;?KUeE3W+{xldrF@tXM3jxiK$7VjWmO(EDu}0DNM+T4u5N zW+f9LpC`@AI};(5jJ8tQL+(L@T!}+T1`+y>)&&uARVdUpa6+L~8#tjKtx)I!c99RN zP*Quck_{r{dO~GOBCC8{_irQQO6FsegpQAoS-0Dj*WuxzPh6^xPs>hDPFSzkW6#ge z?BL))V{-9nAmTry)9LV`p&{$*PJDi->CAWJ5E=D_9HIys4>>R|EA;a6qS=j%jIfD` z39VyradBbkSrnYvOhJy};bCpUHy1<_y1u^F6Bx|S&RRMSf1yG#W@ctA$eWuRsY^oC znSO5KBpNCdgXSBG;?&bZjzl#{#<;wTNwMx$ZnLT=RwEDfow zt*u#3P`0+VOh-8$A0OvOM@JD~YCxM@M`&PDL2kuE6M|uHZ_o0@o-{u{Z#wHS*4@s| zj(l49;lh){7I#Vo7W_=v-{04mpQOGvtLf=!PB+1)r>9s_##2$yhz>HqG6lJili++2 z!o=q0rWqs0*ipB)wl(D)RREB&R-jn}DTo6Yj}`RN(shI>!d~`1nY3#p>`y3}O{d+TH8# zhXAoe>@)h8@Y2$fH76E+(68Z4giHpaJf`aP%f$*Hiv0W_wjWjhKSKBS_qw7F4-a}A zGVp!_M3RW_>gp;Nr1;(49mm+^(~m89QHNB*75fj0ES}>VmiDLs00001DYb%Cm*kqXLXw6{iWFDD#i8IJ zy7)EtALuH$DhPrih&cEc__auh?=^)MY45naKc3w4a`znI&*;ps`;vfRIy{$7Nvnl5 zDfo^M%_v3?mYHoWWV0Fc96yiniTaQby?XEKI>JTOW`J*ic$Qh*A)Y7BY&n+TJ>p?j zmW#wE#BojsB)%28;_;i{lE+_yc|NxY_(e~t)WJ#zvobe`r-);QUS+NGh?_)Ib7h(8 z7_D*G$RUjsBq*q26FM9usG5{ncVDvUTSA3AjJ_6X;N&QwOx`%DRqCoz)ks%q>Zpm_ z|L19IGt*x8NDAnGb=}ViAh-`S>aP2H>bi|HAn*cQn{9uEa+>=lz24Sh$3S!kxV&j= z$^me-3yeNnGMAfDfRe5T01W_K^We{=Nbx>Dhjto z5D5~ON}&>=!{5-+DqJHGq9YL!*MvqPL0l3Qg`gG`h(zO(m~Wo_z8$k?XLe_2ch5PI zGs%hBci#7TpJ!%vcFb@;d>YK)SS z5;N8F^Rsbvb!F`B?HOBJTgL6}ZB!J@dkptkD8Kghc5--lNK^}a5ffH^4ksKzxw*My zZf=fze}AiLVt$B0EHF4?LF>VV6&DwilamuhRJQ^QV1*A%F0SrSBGDWS}>1&myeGR zBRf0WcbuPq&(BXICnv{G2=mnzMi&4E5@jV814aKudn)=*mDq^1u*CLv@N7vLu|(XT zX<@-9M`96@GGe+*xSy^`iZDL}b$!#qbbC$|m!68EUQx8Ar=nsc>V52Q$;t(*|D5y}Z1b z^{5YPeDHL*s_->WTc@U`=xS^)qobqJlUTc_+Lg{ZKUE~;puqQEARQ?OJJ@8v^!Ux; zAGQHGWcd~N{u3taP5SOw2!4NmAGQt$9&(6;fs?2}gXPceG2C0CQd(LnG8@;|*Frl@ zD75PsZ*FdcVKPonPfyIB2|GAAU`DtAWH3nHtW{rMUl+#7IK^u)=VfDKqiEWai;D|3Z)p}=Jy|dQz)4h~ zf#di?_Wk``tW3sW3Fm%*=>l$=BBxX=rGO z4`$bSd3k|9Idc1OrLL~d_ z1AJu*g~hF)pnxE^tTZ<_v;0;J{c9J7>xqd883x(j-X@uunIa&YjXp%l(9jUs-`^(> z4-e)P$CN=GHnhPPNqv328@N^* zFoEq>z|BPz+T_B}w!FOThHtJ^_@}R^sK_gkXFD)}g%#MXfRh(*PhkMq+1YW+W467% zzM4Cf>gsBhq&6SKU`GKAuOirC*swUk?jvS7uB@zh parent, + rpl::producer<> leaveWindowEventProducer); + + void requestPaintColor(float64 progress); + void requestPaintProgress(float64 progress); + void requestPaintLevel(quint16 level); + void reset(); + + [[nodiscard]] rpl::producer actives() const; + + [[nodiscard]] bool inCircle(const QPoint &localPos) const; + +private: + void init(); + + void drawProgress(Painter &p); + + const int _height; + const int _center; + + rpl::variable _showProgress = 0.; + rpl::variable _colorProgress = 0.; + rpl::variable _inCircle = false; + + bool recordingAnimationCallback(crl::time now); + + // This can animate for a very long time (like in music playing), + // so it should be a Basic, not a Simple animation. + Ui::Animations::Basic _recordingAnimation; + anim::value _recordingLevel; + + rpl::lifetime _showingLifetime; +}; + class RecordLock final : public Ui::RpWidget { public: RecordLock(not_null parent); @@ -79,6 +116,149 @@ private: rpl::variable _progress = 0.; }; +RecordLevel::RecordLevel( + not_null parent, + rpl::producer<> leaveWindowEventProducer) +: AbstractButton(parent) +, _height(st::historyRecordLevelMaxRadius * 2) +, _center(_height / 2) +, _recordingAnimation([=](crl::time now) { + return recordingAnimationCallback(now); +}) { + resize(_height, _height); + std::move( + leaveWindowEventProducer + ) | rpl::start_with_next([=] { + _inCircle = false; + }, lifetime()); + init(); +} + +void RecordLevel::requestPaintLevel(quint16 level) { + _recordingLevel.start(level); + _recordingAnimation.start(); +} + +bool RecordLevel::recordingAnimationCallback(crl::time now) { + const auto dt = anim::Disabled() + ? 1. + : ((now - _recordingAnimation.started()) + / float64(kRecordingUpdateDelta)); + if (dt >= 1.) { + _recordingLevel.finish(); + } else { + _recordingLevel.update(dt, anim::sineInOut); + } + if (!anim::Disabled()) { + update(); + } + return (dt < 1.); +} + +void RecordLevel::init() { + shownValue( + ) | rpl::start_with_next([=](bool shown) { + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=](const QRect &clip) { + Painter p(this); + + drawProgress(p); + + st::historyRecordVoiceActive.paintInCenter(p, rect()); + }, lifetime()); + + _showProgress.changes( + ) | rpl::map([](auto value) { + return value != 0.; + }) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](bool show) { + setVisible(show); + setMouseTracking(show); + if (!show) { + _recordingLevel = anim::value(); + _recordingAnimation.stop(); + _showingLifetime.destroy(); + } + }, lifetime()); + + actives( + ) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](bool active) { + setPointerCursor(active); + }, lifetime()); +} + +rpl::producer RecordLevel::actives() const { + return events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::MouseMove + || e->type() == QEvent::Leave + || e->type() == QEvent::Enter); + }) | rpl::map([=](not_null e) { + switch(e->type()) { + case QEvent::MouseMove: + return inCircle((static_cast(e.get()))->pos()); + case QEvent::Leave: return false; + case QEvent::Enter: return inCircle(mapFromGlobal(QCursor::pos())); + default: return false; + } + }); +} + +bool RecordLevel::inCircle(const QPoint &localPos) const { + const auto &radii = st::historyRecordLevelMaxRadius; + const auto dx = std::abs(localPos.x() - _center); + if (dx > radii) { + return false; + } + const auto dy = std::abs(localPos.y() - _center); + if (dy > radii) { + return false; + } else if (dx + dy <= radii) { + return true; + } + return ((dx * dx + dy * dy) <= (radii * radii)); +} + +void RecordLevel::drawProgress(Painter &p) { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + const auto color = anim::color( + st::historyRecordSignalColor, + st::historyRecordVoiceFgActive, + _colorProgress.current()); + p.setBrush(color); + + const auto progress = _showProgress.current(); + + const auto center = QPoint(_center, _center); + const int mainRadii = progress * st::historyRecordLevelMainRadius; + + { + p.setOpacity(.5); + const auto min = progress * st::historyRecordLevelMinRadius; + const auto max = progress * st::historyRecordLevelMaxRadius; + const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.); + const auto radii = qRound(min + (delta * (max - min))); + p.drawEllipse(center, radii, radii); + p.setOpacity(1.); + } + + p.drawEllipse(center, mainRadii, mainRadii); +} + +void RecordLevel::requestPaintProgress(float64 progress) { + _showProgress = progress; + update(); +} + +void RecordLevel::requestPaintColor(float64 progress) { + _colorProgress = progress; + update(); +} + RecordLock::RecordLock(not_null parent) : RpWidget(parent) { resize( st::historyRecordLockTopShadow.width(), @@ -222,6 +402,9 @@ VoiceRecordBar::VoiceRecordBar( , _controller(controller) , _send(send) , _lock(std::make_unique(parent)) +, _level(std::make_unique( + parent, + _controller->widget()->leaveEvents())) , _cancelFont(st::historyRecordFont) , _recordingAnimation([=](crl::time now) { return recordingAnimationCallback(now); @@ -264,6 +447,11 @@ void VoiceRecordBar::updateLockGeometry() { _lock->moveToRight(right, _lock->y()); } +void VoiceRecordBar::updateLevelGeometry() { + const auto center = (_send->width() - _level->width()) / 2; + _level->moveToRight(st::historySendRight + center, y() + center); +} + void VoiceRecordBar::init() { hide(); // Keep VoiceRecordBar behind SendButton. @@ -275,6 +463,7 @@ void VoiceRecordBar::init() { }) | rpl::to_empty ) | rpl::start_with_next([=] { stackUnder(_send.get()); + _level->raise(); }, lifetime()); sizeValue( @@ -298,6 +487,7 @@ void VoiceRecordBar::init() { } updateMessageGeometry(); updateLockGeometry(); + updateLevelGeometry(); }, lifetime()); paintRequest( @@ -347,27 +537,17 @@ void VoiceRecordBar::init() { ) | rpl::start_with_next([=] { installClickOutsideFilter(); - _send->clicks( - ) | rpl::filter([=] { - return _send->type() == Ui::SendButton::Type::Record; - }) | rpl::start_with_next([=] { + _level->clicks( + ) | rpl::start_with_next([=] { stop(true); }, _recordingLifetime); - auto hover = _send->events( - ) | rpl::filter([=](not_null e) { - return e->type() == QEvent::Enter - || e->type() == QEvent::Leave; - }) | rpl::map([=](not_null e) { - return (e->type() == QEvent::Enter); - }); - _send->setLockRecord(true); _send->setForceRippled(true); rpl::single( false ) | rpl::then( - std::move(hover) + _level->actives() ) | rpl::start_with_next([=](bool enter) { _inField = enter; }, _recordingLifetime); @@ -398,7 +578,7 @@ void VoiceRecordBar::activeAnimate(bool active) { } else { auto callback = [=] { update(_messageRect); - _send->requestPaintRecord(activeAnimationRatio()); + _level->requestPaintColor(activeAnimationRatio()); }; const auto from = active ? 0. : 1.; _activeAnimation.start(std::move(callback), from, to, duration); @@ -410,6 +590,7 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn &&callback) { const auto from = show ? 0. : 1.; const auto duration = st::historyRecordVoiceShowDuration; auto animationCallback = [=, callback = std::move(callback)](auto value) { + _level->requestPaintProgress(value); update(); if ((show && value == 1.) || (!show && value == 0.)) { if (callback) { @@ -429,6 +610,7 @@ void VoiceRecordBar::setLockBottom(rpl::producer &&bottom) { bottom ) | rpl::start_with_next([=](int value) { _lock->moveToLeft(_lock->x(), value - _lock->height()); + updateLevelGeometry(); }, lifetime()); } @@ -474,8 +656,12 @@ void VoiceRecordBar::startRecording() { const auto type = e->type(); if (type == QEvent::MouseMove) { const auto mouse = static_cast(e.get()); - const auto localPos = mapFromGlobal(mouse->globalPos()); - _inField = rect().contains(localPos); + const auto globalPos = mouse->globalPos(); + const auto localPos = mapFromGlobal(globalPos); + const auto inField = rect().contains(localPos); + _inField = inField + ? inField + : _level->inCircle(_level->mapFromGlobal(globalPos)); if (_showLockAnimation.animating()) { return; @@ -504,6 +690,7 @@ bool VoiceRecordBar::recordingAnimationCallback(crl::time now) { } void VoiceRecordBar::recordUpdated(quint16 level, int samples) { + _level->requestPaintLevel(level); _recordingLevel.start(level); _recordingAnimation.start(); _recordingSamples = samples; @@ -698,7 +885,7 @@ void VoiceRecordBar::installClickOutsideFilter() { } else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) { return Type::ShowBox; } else if (type == QEvent::MouseButtonPress) { - return (noBox && !_send->underMouse()) + return (noBox && !_inField.current()) ? Type::ShowBox : Type::Continue; } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index dfe60f111..79f08ac60 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -22,6 +22,7 @@ class SessionController; namespace HistoryView::Controls { +class RecordLevel; class RecordLock; class VoiceRecordBar final : public Ui::RpWidget { @@ -56,6 +57,7 @@ private: void updateMessageGeometry(); void updateLockGeometry(); + void updateLevelGeometry(); void recordError(); void recordUpdated(quint16 level, int samples); @@ -86,6 +88,7 @@ private: const not_null _controller; const std::shared_ptr _send; const std::unique_ptr _lock; + const std::unique_ptr _level; rpl::event_stream _sendActionUpdates; rpl::event_stream _sendVoiceRequests; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 0ea46b9eb..743831fb1 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -332,8 +332,8 @@ historyRecordVoiceShowDuration: 120; historyRecordVoiceDuration: 120; historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; -historyRecordVoiceActive: icon {{ "send_control_record", historyRecordVoiceFgActive }}; -historyRecordVoiceCancel: icon {{ "send_control_record", attentionButtonFg }}; +historyRecordVoiceActive: icon {{ "send_control_record_active", recordActiveIcon }}; +historyRecordVoiceCancel: icon {{ "send_control_record_active", attentionButtonFg }}; historyRecordVoiceRippleBgActive: lightButtonBgOver; historyRecordVoiceRippleBgCancel: attentionButtonBgRipple; historyRecordSignalColor: attentionButtonFg; @@ -345,6 +345,10 @@ historyRecordFont: font(13px); historyRecordDurationSkip: 12px; historyRecordDurationFg: historyComposeAreaFg; +historyRecordLevelMainRadius: 37px; +historyRecordLevelMinRadius: 38px; +historyRecordLevelMaxRadius: 60px; + historyRecordTextStyle: TextStyle(defaultTextStyle) { font: historyRecordFont; } From cab22c07a5c6a33db8e61c8ed68f1f67ca891d38 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 11 Oct 2020 14:39:43 +0300 Subject: [PATCH 072/370] Replaced recording animation with simple red circle animation. --- .../history_view_voice_record_bar.cpp | 71 +++++++++---------- .../controls/history_view_voice_record_bar.h | 10 ++- Telegram/SourceFiles/ui/chat/chat.style | 3 +- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 0cc734955..5724baf44 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -405,10 +405,7 @@ VoiceRecordBar::VoiceRecordBar( , _level(std::make_unique( parent, _controller->widget()->leaveEvents())) -, _cancelFont(st::historyRecordFont) -, _recordingAnimation([=](crl::time now) { - return recordingAnimationCallback(now); -}) { +, _cancelFont(st::historyRecordFont) { resize(QSize(parent->width(), recorderHeight)); init(); } @@ -470,8 +467,8 @@ void VoiceRecordBar::init() { ) | rpl::start_with_next([=](QSize size) { _centerY = size.height() / 2; { - const auto maxD = st::historyRecordSignalMax * 2; - const auto point = _centerY - st::historyRecordSignalMax; + const auto maxD = st::historyRecordSignalRadius * 2; + const auto point = _centerY - st::historyRecordSignalRadius; _redCircleRect = { point, point, maxD, maxD }; } { @@ -494,7 +491,7 @@ void VoiceRecordBar::init() { ) | rpl::start_with_next([=](const QRect &clip) { Painter p(this); if (_showAnimation.animating()) { - p.setOpacity(_showAnimation.value(1.)); + p.setOpacity(showAnimationRatio()); } p.fillRect(clip, st::historyComposeAreaBg); @@ -502,12 +499,13 @@ void VoiceRecordBar::init() { // The message should be painted first to avoid flickering. drawMessage(p, activeAnimationRatio()); } - if (clip.intersects(_redCircleRect)) { - drawRecording(p); - } if (clip.intersects(_durationRect)) { drawDuration(p); } + if (clip.intersects(_redCircleRect)) { + // Should be the last to be drawn. + drawRedCircle(p); + } }, lifetime()); _inField.changes( @@ -635,6 +633,7 @@ void VoiceRecordBar::startRecording() { // Show the lock widget after the first successful update. *shown = true; _lockShowing = true; + startRedCircleAnimation(); } recordUpdated(update.level, update.samples); }, [=] { @@ -673,26 +672,8 @@ void VoiceRecordBar::startRecording() { }, _recordingLifetime); } -bool VoiceRecordBar::recordingAnimationCallback(crl::time now) { - const auto dt = anim::Disabled() - ? 1. - : ((now - _recordingAnimation.started()) - / float64(kRecordingUpdateDelta)); - if (dt >= 1.) { - _recordingLevel.finish(); - } else { - _recordingLevel.update(dt, anim::linear); - } - if (!anim::Disabled()) { - update(_redCircleRect); - } - return (dt < 1.); -} - void VoiceRecordBar::recordUpdated(quint16 level, int samples) { _level->requestPaintLevel(level); - _recordingLevel.start(level); - _recordingAnimation.start(); _recordingSamples = samples; if (samples < 0 || samples >= kMaxSamples) { stop(samples > 0 && _inField.current()); @@ -711,8 +692,7 @@ void VoiceRecordBar::stop(bool send) { stopRecording(send); - _recordingLevel = anim::value(); - _recordingAnimation.stop(); + _redCircleProgress = 0.; _inField = false; @@ -754,17 +734,31 @@ void VoiceRecordBar::drawDuration(Painter &p) { p.drawText(_durationRect, style::al_left, duration); } -void VoiceRecordBar::drawRecording(Painter &p) { +void VoiceRecordBar::startRedCircleAnimation() { + if (anim::Disabled()) { + return; + } + const auto animation = _recordingLifetime + .make_state(); + animation->init([=](crl::time now) { + const auto diffTime = now - animation->started(); + _redCircleProgress = std::abs(std::sin(diffTime / 400.)); + update(_redCircleRect); + return true; + }); + animation->start(); +} + +void VoiceRecordBar::drawRedCircle(Painter &p) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); p.setBrush(st::historyRecordSignalColor); - const auto min = st::historyRecordSignalMin; - const auto max = st::historyRecordSignalMax; - const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.); - const auto radii = qRound(min + (delta * (max - min))); + p.setOpacity(1. - _redCircleProgress); + const int radii = st::historyRecordSignalRadius * showAnimationRatio(); const auto center = _redCircleRect.center() + QPoint(1, 1); p.drawEllipse(center, radii, radii); + p.setOpacity(1.); } void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { @@ -795,7 +789,6 @@ bool VoiceRecordBar::isRecording() const { } void VoiceRecordBar::finishAnimating() { - _recordingAnimation.stop(); _showAnimation.stop(); } @@ -829,6 +822,12 @@ float64 VoiceRecordBar::activeAnimationRatio() const { return _activeAnimation.value(_inField.current() ? 1. : 0.); } +float64 VoiceRecordBar::showAnimationRatio() const { + // There is no reason to set the final value to zero, + // because at zero this widget is hidden. + return _showAnimation.value(1.); +} + QString VoiceRecordBar::cancelMessage() const { return _lock->isLocked() ? tr::lng_record_lock_cancel(tr::now) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 79f08ac60..e9d4d67ce 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -70,15 +70,17 @@ private: bool showRecordButton() const; void drawDuration(Painter &p); - void drawRecording(Painter &p); + void drawRedCircle(Painter &p); void drawMessage(Painter &p, float64 recordActive); void updateOverStates(QPoint pos); + void startRedCircleAnimation(); void installClickOutsideFilter(); bool isTypeRecord() const; void activeAnimate(bool active); + float64 showAnimationRatio() const; float64 activeAnimationRatio() const; void computeAndSetLockProgress(QPoint globalPos); @@ -105,6 +107,7 @@ private: rpl::variable _recording = false; rpl::variable _inField = false; int _recordingSamples = 0; + float64 _redCircleProgress = 0.; const style::font &_cancelFont; @@ -113,13 +116,8 @@ private: rpl::variable _lockShowing = false; Ui::Animations::Simple _showLockAnimation; - - // This can animate for a very long time (like in music playing), - // so it should be a Basic, not a Simple animation. - Ui::Animations::Basic _recordingAnimation; Ui::Animations::Simple _activeAnimation; Ui::Animations::Simple _showAnimation; - anim::value _recordingLevel; }; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 743831fb1..f37fead11 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -337,8 +337,7 @@ historyRecordVoiceCancel: icon {{ "send_control_record_active", attentionButtonF historyRecordVoiceRippleBgActive: lightButtonBgOver; historyRecordVoiceRippleBgCancel: attentionButtonBgRipple; historyRecordSignalColor: attentionButtonFg; -historyRecordSignalMin: 5px; -historyRecordSignalMax: 12px; +historyRecordSignalRadius: 5px; historyRecordCancel: windowSubTextFg; historyRecordCancelActive: windowActiveTextFg; historyRecordFont: font(13px); From 6ed7615653752cefa8e5a07557239507bcb236fd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 11 Oct 2020 14:49:23 +0300 Subject: [PATCH 073/370] Removed redundant methods for record from SendButton. --- .../history_view_voice_record_bar.cpp | 5 --- Telegram/SourceFiles/ui/chat/chat.style | 2 -- .../SourceFiles/ui/controls/send_button.cpp | 31 ++----------------- .../SourceFiles/ui/controls/send_button.h | 7 ----- 4 files changed, 2 insertions(+), 43 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 5724baf44..d87e563fb 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -540,8 +540,6 @@ void VoiceRecordBar::init() { stop(true); }, _recordingLifetime); - _send->setLockRecord(true); - _send->setForceRippled(true); rpl::single( false ) | rpl::then( @@ -700,9 +698,6 @@ void VoiceRecordBar::stop(bool send) { _recordingSamples = 0; _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); - _send->setForceRippled(false); - _send->clearRecordState(); - _controller->widget()->setInnerFocus(); }; _lockShowing = false; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index f37fead11..bd386eb65 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -333,9 +333,7 @@ historyRecordVoiceDuration: 120; historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; historyRecordVoiceActive: icon {{ "send_control_record_active", recordActiveIcon }}; -historyRecordVoiceCancel: icon {{ "send_control_record_active", attentionButtonFg }}; historyRecordVoiceRippleBgActive: lightButtonBgOver; -historyRecordVoiceRippleBgCancel: attentionButtonBgRipple; historyRecordSignalColor: attentionButtonFg; historyRecordSignalRadius: 5px; historyRecordCancel: windowSubTextFg; diff --git a/Telegram/SourceFiles/ui/controls/send_button.cpp b/Telegram/SourceFiles/ui/controls/send_button.cpp index ec9635791..d1c3c3080 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.cpp +++ b/Telegram/SourceFiles/ui/controls/send_button.cpp @@ -42,9 +42,6 @@ void SendButton::setType(Type type) { setPointerCursor(_type != Type::Slowmode); update(); } - if (_type != Type::Record) { - clearRecordState(); - } } void SendButton::setSlowmodeDelay(int seconds) { @@ -66,26 +63,6 @@ void SendButton::finishAnimating() { update(); } -void SendButton::requestPaintRecord(float64 progress) { - if (_type == Type::Record) { - _recordProgress = progress; - update(); - } -} - -void SendButton::setLockRecord(bool lock) { - if (_type == Type::Record) { - _recordLocked = lock; - update(); - } -} - -void SendButton::clearRecordState() { - _recordLocked = false; - _recordProgress = 0.; - update(); -} - void SendButton::paintEvent(QPaintEvent *e) { Painter p(this); @@ -115,12 +92,10 @@ void SendButton::paintEvent(QPaintEvent *e) { } void SendButton::paintRecord(Painter &p, bool over) { - const auto recordActive = _recordProgress; + const auto recordActive = 0.; if (!isDisabled()) { auto rippleColor = anim::color( - _recordLocked - ? st::historyRecordVoiceRippleBgCancel - : st::historyAttachEmoji.ripple.color, + st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive); paintRipple( @@ -135,8 +110,6 @@ void SendButton::paintRecord(Painter &p, bool over) { return &st::historyRecordVoice; } else if (recordActive == 1.) { return &st::historyRecordVoiceActive; - } else if (_recordLocked) { - return &st::historyRecordVoiceCancel; } else if (over) { return &st::historyRecordVoiceOver; } diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h index 938a0aa0c..da67bcbf5 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.h +++ b/Telegram/SourceFiles/ui/controls/send_button.h @@ -29,13 +29,9 @@ public: return _type; } void setType(Type state); - void setLockRecord(bool lock); - void clearRecordState(); void setSlowmodeDelay(int seconds); void finishAnimating(); - void requestPaintRecord(float64 progress); - protected: void paintEvent(QPaintEvent *e) override; @@ -59,9 +55,6 @@ private: Ui::Animations::Simple _a_typeChanged; - bool _recordLocked = false; - float64 _recordProgress = 0.; - int _slowmodeDelay = 0; QString _slowmodeDelayText; From ad4bf9b5c8f6c509cda2be25f6df43b24cd69c2f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 11 Oct 2020 21:35:09 +0300 Subject: [PATCH 074/370] Improved VoiceRecordBar support in ComposeControls. --- .../SourceFiles/history/history_widget.cpp | 2 + .../history_view_compose_controls.cpp | 36 +++++++++++- .../controls/history_view_compose_controls.h | 5 +- .../history_view_voice_record_bar.cpp | 57 ++++++++++++++----- .../controls/history_view_voice_record_bar.h | 12 +++- .../view/history_view_replies_section.cpp | 8 +++ .../view/history_view_scheduled_section.cpp | 8 +++ 7 files changed, 110 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 1ad11462f..f02f1eb31 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -730,6 +730,8 @@ void HistoryWidget::initVoiceRecordBar() { return _replyToId || (_nonEmptySelection && _list); }); + _voiceRecordBar->setSendButtonGeometryValue(_send->geometryValue()); + _voiceRecordBar->startRecordingRequests( ) | rpl::start_with_next([=] { const auto error = _peer 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 21c8e5e93..69b8f1fe0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -494,7 +494,7 @@ MessageToEdit FieldHeader::queryToEdit() { } ComposeControls::ComposeControls( - not_null parent, + not_null parent, not_null window, Mode mode) : _parent(parent) @@ -520,6 +520,7 @@ ComposeControls::ComposeControls( &_window->session().data())) , _voiceRecordBar(std::make_unique( _wrap.get(), + parent, window, _send, st::historySendSize.height())) @@ -671,6 +672,7 @@ void ComposeControls::showFinished() { _tabbedPanel->hideFast(); } updateWrappingVisibility(); + _voiceRecordBar->orderControls(); } void ComposeControls::showForGrab() { @@ -928,6 +930,30 @@ void ComposeControls::initVoiceRecordBar() { } _voiceRecordBar->startRecording(); }, _wrap->lifetime()); + + { + auto geometry = rpl::merge( + _wrap->geometryValue(), + _send->geometryValue() + ) | rpl::map([=](QRect geometry) { + auto r = _send->geometry(); + r.setY(r.y() + _wrap->y()); + return r; + }); + _voiceRecordBar->setSendButtonGeometryValue(std::move(geometry)); + } + + { + auto bottom = _wrap->geometryValue( + ) | rpl::map([=](QRect geometry) { + return geometry.y() - st::historyRecordLockPosition.y(); + }); + _voiceRecordBar->setLockBottom(std::move(bottom)); + } + + _voiceRecordBar->setEscFilter([=] { + return (isEditingMessage() || replyingToMessage()); + }); } void ComposeControls::updateWrappingVisibility() { @@ -1311,4 +1337,12 @@ FullMsgId ComposeControls::replyingToMessage() const { return _header->replyingToMessage(); } +bool ComposeControls::isLockPresent() const { + return _voiceRecordBar->isLockPresent(); +} + +rpl::producer ComposeControls::lockShowStarts() const { + return _voiceRecordBar->lockShowStarts(); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 3d642ab5d..5df22827d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -78,7 +78,7 @@ public: }; ComposeControls( - not_null parent, + not_null parent, not_null window, Mode mode); ~ComposeControls(); @@ -135,6 +135,9 @@ public: void clear(); void hidePanelsAnimated(); + [[nodiscard]] rpl::producer lockShowStarts() const; + [[nodiscard]] bool isLockPresent() const; + private: enum class TextUpdateEvent { //SaveDraft = (1 << 0), diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index d87e563fb..78b769c3e 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -156,8 +156,15 @@ bool RecordLevel::recordingAnimationCallback(crl::time now) { } void RecordLevel::init() { + const auto hasProgress = [](auto value) { return value != 0.; }; + + // Do not allow the widget to be shown from the outside. shownValue( ) | rpl::start_with_next([=](bool shown) { + const auto shouldShown = hasProgress(_showProgress.current()); + if (shown != shouldShown) { + setVisible(shouldShown); + } }, lifetime()); paintRequest( @@ -170,9 +177,7 @@ void RecordLevel::init() { }, lifetime()); _showProgress.changes( - ) | rpl::map([](auto value) { - return value != 0.; - }) | rpl::distinct_until_changed( + ) | rpl::map(hasProgress) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](bool show) { setVisible(show); setMouseTracking(show); @@ -395,21 +400,31 @@ rpl::producer<> RecordLock::locks() const { VoiceRecordBar::VoiceRecordBar( not_null parent, + not_null sectionWidget, not_null controller, std::shared_ptr send, int recorderHeight) : RpWidget(parent) +, _sectionWidget(sectionWidget) , _controller(controller) , _send(send) -, _lock(std::make_unique(parent)) +, _lock(std::make_unique(sectionWidget)) , _level(std::make_unique( - parent, + sectionWidget, _controller->widget()->leaveEvents())) , _cancelFont(st::historyRecordFont) { resize(QSize(parent->width(), recorderHeight)); init(); } +VoiceRecordBar::VoiceRecordBar( + not_null parent, + not_null controller, + std::shared_ptr send, + int recorderHeight) +: VoiceRecordBar(parent, parent, controller, send, recorderHeight) { +} + VoiceRecordBar::~VoiceRecordBar() { if (isRecording()) { stopRecording(false); @@ -444,11 +459,6 @@ void VoiceRecordBar::updateLockGeometry() { _lock->moveToRight(right, _lock->y()); } -void VoiceRecordBar::updateLevelGeometry() { - const auto center = (_send->width() - _level->width()) / 2; - _level->moveToRight(st::historySendRight + center, y() + center); -} - void VoiceRecordBar::init() { hide(); // Keep VoiceRecordBar behind SendButton. @@ -459,8 +469,7 @@ void VoiceRecordBar::init() { return e->type() == QEvent::ZOrderChange; }) | rpl::to_empty ) | rpl::start_with_next([=] { - stackUnder(_send.get()); - _level->raise(); + orderControls(); }, lifetime()); sizeValue( @@ -484,7 +493,6 @@ void VoiceRecordBar::init() { } updateMessageGeometry(); updateLockGeometry(); - updateLevelGeometry(); }, lifetime()); paintRequest( @@ -606,7 +614,16 @@ void VoiceRecordBar::setLockBottom(rpl::producer &&bottom) { bottom ) | rpl::start_with_next([=](int value) { _lock->moveToLeft(_lock->x(), value - _lock->height()); - updateLevelGeometry(); + }, lifetime()); +} + +void VoiceRecordBar::setSendButtonGeometryValue( + rpl::producer &&geometry) { + std::move( + geometry + ) | rpl::start_with_next([=](QRect r) { + const auto center = (r.width() - _level->width()) / 2; + _level->moveToLeft(r.x() + center, r.y() + center); }, lifetime()); } @@ -660,7 +677,7 @@ void VoiceRecordBar::startRecording() { ? inField : _level->inCircle(_level->mapFromGlobal(globalPos)); - if (_showLockAnimation.animating()) { + if (_showLockAnimation.animating() || !hasDuration()) { return; } computeAndSetLockProgress(mouse->globalPos()); @@ -813,6 +830,10 @@ bool VoiceRecordBar::isTypeRecord() const { return (_send->type() == Ui::SendButton::Type::Record); } +bool VoiceRecordBar::hasDuration() const { + return _recordingSamples > 0; +} + float64 VoiceRecordBar::activeAnimationRatio() const { return _activeAnimation.value(_inField.current() ? 1. : 0.); } @@ -837,6 +858,12 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { _lock->requestPaintProgress(std::clamp(progress, 0., 1.)); } +void VoiceRecordBar::orderControls() { + stackUnder(_send.get()); + _level->raise(); + _lock->raise(); +} + void VoiceRecordBar::installClickOutsideFilter() { const auto box = _recordingLifetime.make_state>(); const auto showBox = [=] { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index e9d4d67ce..bd1b95356 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -30,6 +30,12 @@ public: using SendActionUpdate = Controls::SendActionUpdate; using VoiceToSend = Controls::VoiceToSend; + VoiceRecordBar( + not_null parent, + not_null sectionWidget, + not_null controller, + std::shared_ptr send, + int recorderHeight); VoiceRecordBar( not_null parent, not_null controller, @@ -40,6 +46,8 @@ public: void startRecording(); void finishAnimating(); + void orderControls(); + [[nodiscard]] rpl::producer sendActionUpdates() const; [[nodiscard]] rpl::producer sendVoiceRequests() const; [[nodiscard]] rpl::producer recordingStateChanges() const; @@ -47,6 +55,7 @@ public: [[nodiscard]] rpl::producer lockShowStarts() const; void setLockBottom(rpl::producer &&bottom); + void setSendButtonGeometryValue(rpl::producer &&geometry); void setEscFilter(Fn &&callback); [[nodiscard]] bool isRecording() const; @@ -57,7 +66,6 @@ private: void updateMessageGeometry(); void updateLockGeometry(); - void updateLevelGeometry(); void recordError(); void recordUpdated(quint16 level, int samples); @@ -78,6 +86,7 @@ private: void installClickOutsideFilter(); bool isTypeRecord() const; + bool hasDuration() const; void activeAnimate(bool active); float64 showAnimationRatio() const; @@ -87,6 +96,7 @@ private: QString cancelMessage() const; + const not_null _sectionWidget; const not_null _controller; const std::shared_ptr _send; const std::unique_ptr _lock; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index b3170d8cf..af7297e63 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -507,6 +507,11 @@ void RepliesWidget::setupComposeControls() { Unexpected("action in MimeData hook."); }); + _composeControls->lockShowStarts( + ) | rpl::start_with_next([=] { + updateScrollDownVisibility(); + }, lifetime()); + _composeControls->finishAnimating(); } @@ -1213,6 +1218,9 @@ void RepliesWidget::updateScrollDownVisibility() { } const auto scrollDownIsVisible = [&]() -> std::optional { + if (_composeControls->isLockPresent()) { + return false; + } const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; if (top < _scroll->scrollTopMax() || _replyReturn) { 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 0afcc8d93..4367b4d5c 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -253,6 +253,11 @@ void ScheduledWidget::setupComposeControls() { } Unexpected("action in MimeData hook."); }); + + _composeControls->lockShowStarts( + ) | rpl::start_with_next([=] { + updateScrollDownVisibility(); + }, lifetime()); } void ScheduledWidget::chooseAttach() { @@ -820,6 +825,9 @@ void ScheduledWidget::updateScrollDownVisibility() { } const auto scrollDownIsVisible = [&]() -> std::optional { + if (_composeControls->isLockPresent()) { + return false; + } const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; if (top < _scroll->scrollTopMax()) { return true; From 57eb4f8234995eab3b82588f3bf34c9ad0691a70 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 12 Oct 2020 14:45:48 +0300 Subject: [PATCH 075/370] Disabled drag'n'drop area when user is recording voice message. --- Telegram/SourceFiles/history/history_widget.cpp | 2 +- .../history/view/controls/history_view_compose_controls.cpp | 6 +++++- .../history/view/controls/history_view_compose_controls.h | 1 + .../history/view/history_view_replies_section.cpp | 2 +- .../history/view/history_view_scheduled_section.cpp | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index f02f1eb31..83c81180f 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -368,7 +368,7 @@ HistoryWidget::HistoryWidget( _attachDragAreas = DragArea::SetupDragAreaToContainer( this, crl::guard(this, [=](not_null d) { - return _history && _canSendMessages; + return _history && _canSendMessages && !isRecording(); }), crl::guard(this, [=](bool f) { _field->setAcceptDrops(f); }), crl::guard(this, [=] { updateControlsGeometry(); })); 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 69b8f1fe0..3bd0d458d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -580,7 +580,7 @@ int ComposeControls::heightCurrent() const { } bool ComposeControls::focus() { - if (_voiceRecordBar->isRecording()) { + if (isRecording()) { return false; } _field->setFocus(); @@ -1345,4 +1345,8 @@ rpl::producer ComposeControls::lockShowStarts() const { return _voiceRecordBar->lockShowStarts(); } +bool ComposeControls::isRecording() const { + return _voiceRecordBar->isRecording(); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 5df22827d..926e3da32 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -137,6 +137,7 @@ public: [[nodiscard]] rpl::producer lockShowStarts() const; [[nodiscard]] bool isLockPresent() const; + [[nodiscard]] bool isRecording() const; private: enum class TextUpdateEvent { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index af7297e63..c10451a42 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1761,7 +1761,7 @@ void RepliesWidget::clearSelected() { void RepliesWidget::setupDragArea() { const auto areas = DragArea::SetupDragAreaToContainer( this, - [=](not_null d) { return _history; }, + [=](auto d) { return _history && !_composeControls->isRecording(); }, nullptr, [=] { updateControlsGeometry(); }); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 4367b4d5c..4465ac2ff 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -1188,7 +1188,7 @@ void ScheduledWidget::clearSelected() { void ScheduledWidget::setupDragArea() { const auto areas = DragArea::SetupDragAreaToContainer( this, - [=](not_null d) { return _history; }, + [=](auto d) { return _history && !_composeControls->isRecording(); }, nullptr, [=] { updateControlsGeometry(); }); From 367b487a6ce383483fa9c581e20ddbde869bb359 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 12 Oct 2020 17:40:37 +0300 Subject: [PATCH 076/370] Prettified fast locking of voice record. --- .../history_view_voice_record_bar.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 78b769c3e..17a7f8061 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -110,8 +110,11 @@ private: void init(); void drawProgress(Painter &p); + void setProgress(float64 progress); + void startLockingAnimation(float64 to); Ui::Animations::Simple _lockAnimation; + Ui::Animations::Simple _lockEnderAnimation; rpl::variable _progress = 0.; }; @@ -381,10 +384,24 @@ void RecordLock::drawProgress(Painter &p) { } } +void RecordLock::startLockingAnimation(float64 to) { + auto callback = [=](auto value) { setProgress(value); }; + const auto duration = st::historyRecordVoiceShowDuration; + _lockEnderAnimation.start(std::move(callback), 0., to, duration); +} + void RecordLock::requestPaintProgress(float64 progress) { - if (isHidden() || isLocked()) { + if (isHidden() || isLocked() || _lockEnderAnimation.animating()) { return; } + if (!_progress.current() && (progress > .3)) { + startLockingAnimation(progress); + return; + } + setProgress(progress); +} + +void RecordLock::setProgress(float64 progress) { _progress = progress; update(); } From 041d8571c21b7f92c8150fa238d284e374d39dd9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 12 Oct 2020 19:52:41 +0300 Subject: [PATCH 077/370] Added delay for start recording voice message. --- .../SourceFiles/history/history_widget.cpp | 11 +++-- .../history_view_compose_controls.cpp | 11 +++-- .../history_view_voice_record_bar.cpp | 43 +++++++++++++------ .../controls/history_view_voice_record_bar.h | 6 ++- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 83c81180f..67107ad19 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -732,19 +732,18 @@ void HistoryWidget::initVoiceRecordBar() { _voiceRecordBar->setSendButtonGeometryValue(_send->geometryValue()); - _voiceRecordBar->startRecordingRequests( - ) | rpl::start_with_next([=] { + _voiceRecordBar->setStartRecordingFilter([=] { const auto error = _peer ? Data::RestrictionError(_peer, ChatRestriction::f_send_media) : std::nullopt; if (error) { Ui::show(Box(*error)); - return; + return true; } else if (showSlowmodeError()) { - return; + return true; } - _voiceRecordBar->startRecording(); - }, lifetime()); + return false; + }); _voiceRecordBar->sendActionUpdates( ) | rpl::start_with_next([=](const auto &data) { 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 3bd0d458d..901e31e45 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -915,8 +915,7 @@ void ComposeControls::initVoiceRecordBar() { _field->setVisible(!active); }, _wrap->lifetime()); - _voiceRecordBar->startRecordingRequests( - ) | rpl::start_with_next([=] { + _voiceRecordBar->setStartRecordingFilter([=] { const auto error = _history ? Data::RestrictionError( _history->peer, @@ -924,12 +923,12 @@ void ComposeControls::initVoiceRecordBar() { : std::nullopt; if (error) { Ui::show(Box(*error)); - return; + return true; } else if (_showSlowmodeError && _showSlowmodeError()) { - return; + return true; } - _voiceRecordBar->startRecording(); - }, _wrap->lifetime()); + return false; + }); { auto geometry = rpl::merge( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 17a7f8061..5cee3c4ed 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -429,6 +429,7 @@ VoiceRecordBar::VoiceRecordBar( , _level(std::make_unique( sectionWidget, _controller->widget()->leaveEvents())) +, _startTimer([=] { startRecording(); }) , _cancelFont(st::historyRecordFont) { resize(QSize(parent->width(), recorderHeight)); init(); @@ -589,6 +590,25 @@ void VoiceRecordBar::init() { updateMessageGeometry(); update(_messageRect); }, lifetime()); + + _send->events( + ) | rpl::filter([=](not_null e) { + return isTypeRecord() + && !isRecording() + && !_showAnimation.animating() + && !_lock->isLocked() + && (e->type() == QEvent::MouseButtonPress + || e->type() == QEvent::MouseButtonRelease); + }) | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::MouseButtonPress) { + if (_startRecordingFilter && _startRecordingFilter()) { + return; + } + _startTimer.callOnce(st::historyRecordVoiceShowDuration); + } else if (e->type() == QEvent::MouseButtonRelease) { + _startTimer.cancel(); + } + }, lifetime()); } void VoiceRecordBar::activeAnimate(bool active) { @@ -626,6 +646,10 @@ void VoiceRecordBar::setEscFilter(Fn &&callback) { _escFilter = std::move(callback); } +void VoiceRecordBar::setStartRecordingFilter(Fn &&callback) { + _startRecordingFilter = std::move(callback); +} + void VoiceRecordBar::setLockBottom(rpl::producer &&bottom) { std::move( bottom @@ -645,8 +669,13 @@ void VoiceRecordBar::setSendButtonGeometryValue( } void VoiceRecordBar::startRecording() { + if (isRecording()) { + return; + } auto appearanceCallback = [=] { - Expects(!_showAnimation.animating()); + if(_showAnimation.animating()) { + return; + } using namespace ::Media::Capture; if (!instance()->available()) { @@ -717,7 +746,7 @@ void VoiceRecordBar::recordUpdated(quint16 level, int samples) { void VoiceRecordBar::stop(bool send) { auto disappearanceCallback = [=] { - Expects(!_showAnimation.animating()); + _showAnimation.stop(); hide(); _recording = false; @@ -833,16 +862,6 @@ bool VoiceRecordBar::isLockPresent() const { return _lockShowing.current(); } -rpl::producer<> VoiceRecordBar::startRecordingRequests() const { - return _send->events( - ) | rpl::filter([=](not_null e) { - return isTypeRecord() - && !_showAnimation.animating() - && !_lock->isLocked() - && (e->type() == QEvent::MouseButtonPress); - }) | rpl::to_empty; -} - bool VoiceRecordBar::isTypeRecord() const { return (_send->type() == Ui::SendButton::Type::Record); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index bd1b95356..5e592b595 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "api/api_common.h" +#include "base/timer.h" #include "history/view/controls/compose_controls_common.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" @@ -51,12 +52,12 @@ public: [[nodiscard]] rpl::producer sendActionUpdates() const; [[nodiscard]] rpl::producer sendVoiceRequests() const; [[nodiscard]] rpl::producer recordingStateChanges() const; - [[nodiscard]] rpl::producer<> startRecordingRequests() const; [[nodiscard]] rpl::producer lockShowStarts() const; void setLockBottom(rpl::producer &&bottom); void setSendButtonGeometryValue(rpl::producer &&geometry); void setEscFilter(Fn &&callback); + void setStartRecordingFilter(Fn &&callback); [[nodiscard]] bool isRecording() const; [[nodiscard]] bool isLockPresent() const; @@ -102,6 +103,8 @@ private: const std::unique_ptr _lock; const std::unique_ptr _level; + base::Timer _startTimer; + rpl::event_stream _sendActionUpdates; rpl::event_stream _sendVoiceRequests; @@ -113,6 +116,7 @@ private: Ui::Text::String _message; Fn _escFilter; + Fn _startRecordingFilter; rpl::variable _recording = false; rpl::variable _inField = false; From b3925a3bec8163d03c57763da3a774dac94fa31e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 12 Oct 2020 22:30:14 +0300 Subject: [PATCH 078/370] Added touchbar hiding while recording voice message. --- .../SourceFiles/media/audio/media_audio_capture.cpp | 5 +++++ .../SourceFiles/media/audio/media_audio_capture.h | 5 +++++ .../platform/mac/touchbar/mac_touchbar_manager.mm | 11 ++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp index 20795f14a..06df89bb2 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp @@ -96,6 +96,9 @@ void Instance::start() { _updates.fire_error({}); }); }); + crl::on_main(this, [=] { + _started = true; + }); }); } @@ -103,11 +106,13 @@ void Instance::stop(Fn callback) { InvokeQueued(_inner.get(), [=] { if (!callback) { _inner->stop(); + crl::on_main(this, [=] { _started = false; }); return; } _inner->stop([=](Result &&result) { crl::on_main([=, result = std::move(result)]() mutable { callback(std::move(result)); + _started = false; }); }); }); diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.h b/Telegram/SourceFiles/media/audio/media_audio_capture.h index 10ad66abc..a6db3606b 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.h +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.h @@ -42,6 +42,10 @@ public: return _updates.events(); } + [[nodiscard]] rpl::producer startedChanges() const { + return _started.changes(); + } + void start(); void stop(Fn callback = nullptr); @@ -50,6 +54,7 @@ private: friend class Inner; bool _available = false; + rpl::variable _started = false;; rpl::event_stream _updates; QThread _thread; std::unique_ptr _inner; diff --git a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.mm b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.mm index c26850176..2136d6783 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_manager.mm @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_domain.h" #include "main/main_session.h" #include "mainwidget.h" // MainWidget::closeBothPlayers +#include "media/audio/media_audio_capture.h" #include "media/player/media_player_instance.h" #include "platform/mac/touchbar/mac_touchbar_audio.h" #include "platform/mac/touchbar/mac_touchbar_common.h" @@ -91,20 +92,24 @@ const auto kAudioItemIdentifier = @"touchbarAudio"; Media::Player::instance()->startsPlay(type) | rpl::map_to(true) ); + auto voiceRecording = ::Media::Capture::instance()->startedChanges(); + rpl::combine( std::move(sessionChanges), rpl::single(false) | rpl::then(Core::App().passcodeLockChanges()), - rpl::single(false) | rpl::then(std::move(audioPlayer)) + rpl::single(false) | rpl::then(std::move(audioPlayer)), + rpl::single(false) | rpl::then(std::move(voiceRecording)) ) | rpl::start_with_next([=]( Main::Session *session, bool lock, - bool audio) { + bool audio, + bool recording) { TouchBar::CustomEnterToCocoaEventLoop([=] { _touchBarSwitches.fire({}); if (!audio) { self.defaultItemIdentifiers = @[]; } - self.defaultItemIdentifiers = lock + self.defaultItemIdentifiers = (lock || recording) ? @[] : audio ? @[kAudioItemIdentifier] From cb84e70bdceb8543bcb4c190368f03a63ff65d9b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 14 Oct 2020 02:48:10 +0300 Subject: [PATCH 079/370] Improved display management of voice record controls. --- .../SourceFiles/history/history_widget.cpp | 37 ++++++---- Telegram/SourceFiles/history/history_widget.h | 1 + .../history_view_compose_controls.cpp | 6 ++ .../history_view_voice_record_bar.cpp | 69 ++++++++++--------- .../controls/history_view_voice_record_bar.h | 7 +- 5 files changed, 68 insertions(+), 52 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 67107ad19..2599b63ca 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -776,6 +776,8 @@ void HistoryWidget::initVoiceRecordBar() { updateHistoryDownVisibility(); updateUnreadMentionsVisibility(); }, lifetime()); + + _voiceRecordBar->hideFast(); } void HistoryWidget::initTabbedSelector() { @@ -2063,13 +2065,7 @@ void HistoryWidget::updateControlsVisibility() { updateHistoryDownVisibility(); updateUnreadMentionsVisibility(); if (!_history || _a_show.animating()) { - if (_tabbedPanel) { - _tabbedPanel->hideFast(); - } - if (_pinnedBar) { - _pinnedBar->hide(); - } - hideChildren(); + hideChildWidgets(); return; } @@ -2140,6 +2136,9 @@ void HistoryWidget::updateControlsVisibility() { if (_tabbedPanel) { _tabbedPanel->hide(); } + if (_voiceRecordBar) { + _voiceRecordBar->hideFast(); + } if (_inlineResults) { _inlineResults->hide(); } @@ -2231,6 +2230,9 @@ void HistoryWidget::updateControlsVisibility() { if (_tabbedPanel) { _tabbedPanel->hide(); } + if (_voiceRecordBar) { + _voiceRecordBar->hideFast(); + } if (_inlineResults) { _inlineResults->hide(); } @@ -3005,6 +3007,19 @@ void HistoryWidget::saveEditMsg() { fail); } +void HistoryWidget::hideChildWidgets() { + if (_tabbedPanel) { + _tabbedPanel->hideFast(); + } + if (_pinnedBar) { + _pinnedBar->hide(); + } + if (_voiceRecordBar) { + _voiceRecordBar->hideFast(); + } + hideChildren(); +} + void HistoryWidget::hideSelectorControlsAnimated() { _fieldAutocomplete->hideAnimated(); if (_supportAutocomplete) { @@ -3212,13 +3227,7 @@ void HistoryWidget::showAnimated( _cacheOver = controller()->content()->grabForShowAnimation(params); - if (_tabbedPanel) { - _tabbedPanel->hideFast(); - } - if (_pinnedBar) { - _pinnedBar->hide(); - } - hideChildren(); + hideChildWidgets(); if (params.withTopBarShadow) _topShadow->show(); if (_showDirection == Window::SlideDirection::FromLeft) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index b7956ec7c..57c964f03 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -456,6 +456,7 @@ private: bool replyToNextMessage(); [[nodiscard]] bool showSlowmodeError(); + void hideChildWidgets(); void hideSelectorControlsAnimated(); int countMembersDropdownHeightMax() const; 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 901e31e45..ba9062365 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -660,6 +660,9 @@ void ComposeControls::showStarted() { if (_tabbedPanel) { _tabbedPanel->hideFast(); } + if (_voiceRecordBar) { + _voiceRecordBar->hideFast(); + } _wrap->hide(); _writeRestricted->hide(); } @@ -671,6 +674,9 @@ void ComposeControls::showFinished() { if (_tabbedPanel) { _tabbedPanel->hideFast(); } + if (_voiceRecordBar) { + _voiceRecordBar->hideFast(); + } updateWrappingVisibility(); _voiceRecordBar->orderControls(); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 5cee3c4ed..48e328ea0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -68,7 +68,6 @@ public: void requestPaintColor(float64 progress); void requestPaintProgress(float64 progress); void requestPaintLevel(quint16 level); - void reset(); [[nodiscard]] rpl::producer actives() const; @@ -101,7 +100,6 @@ public: RecordLock(not_null parent); void requestPaintProgress(float64 progress); - void reset(); [[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] bool isLocked() const; @@ -161,26 +159,19 @@ bool RecordLevel::recordingAnimationCallback(crl::time now) { void RecordLevel::init() { const auto hasProgress = [](auto value) { return value != 0.; }; - // Do not allow the widget to be shown from the outside. - shownValue( - ) | rpl::start_with_next([=](bool shown) { - const auto shouldShown = hasProgress(_showProgress.current()); - if (shown != shouldShown) { - setVisible(shouldShown); - } - }, lifetime()); - paintRequest( ) | rpl::start_with_next([=](const QRect &clip) { Painter p(this); drawProgress(p); + p.setOpacity(_showProgress.current()); st::historyRecordVoiceActive.paintInCenter(p, rect()); }, lifetime()); - _showProgress.changes( - ) | rpl::map(hasProgress) | rpl::distinct_until_changed( + rpl::merge( + shownValue(), + _showProgress.value() | rpl::map(hasProgress) ) | rpl::start_with_next([=](bool show) { setVisible(show); setMouseTracking(show); @@ -188,6 +179,7 @@ void RecordLevel::init() { _recordingLevel = anim::value(); _recordingAnimation.stop(); _showingLifetime.destroy(); + _showProgress = 0.; } }, lifetime()); @@ -281,6 +273,7 @@ void RecordLock::init() { ) | rpl::start_with_next([=](bool shown) { if (!shown) { _lockAnimation.stop(); + _lockEnderAnimation.stop(); _progress = 0.; } }, lifetime()); @@ -433,6 +426,7 @@ VoiceRecordBar::VoiceRecordBar( , _cancelFont(st::historyRecordFont) { resize(QSize(parent->width(), recorderHeight)); init(); + hideFast(); } VoiceRecordBar::VoiceRecordBar( @@ -478,7 +472,6 @@ void VoiceRecordBar::updateLockGeometry() { } void VoiceRecordBar::init() { - hide(); // Keep VoiceRecordBar behind SendButton. rpl::single( ) | rpl::then( @@ -490,6 +483,13 @@ void VoiceRecordBar::init() { orderControls(); }, lifetime()); + shownValue( + ) | rpl::start_with_next([=](bool show) { + if (!show) { + finish(); + } + }, lifetime()); + sizeValue( ) | rpl::start_with_next([=](QSize size) { _centerY = size.height() / 2; @@ -556,7 +556,6 @@ void VoiceRecordBar::init() { _showLockAnimation.start(std::move(callback), from, to, duration); }, lifetime()); - _lock->hide(); _lock->locks( ) | rpl::start_with_next([=] { installClickOutsideFilter(); @@ -746,27 +745,35 @@ void VoiceRecordBar::recordUpdated(quint16 level, int samples) { void VoiceRecordBar::stop(bool send) { auto disappearanceCallback = [=] { - _showAnimation.stop(); - hide(); - _recording = false; stopRecording(send); - - _redCircleProgress = 0.; - - _inField = false; - - _recordingLifetime.destroy(); - _recordingSamples = 0; - _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); - - _controller->widget()->setInnerFocus(); }; _lockShowing = false; visibilityAnimate(false, std::move(disappearanceCallback)); } +void VoiceRecordBar::finish() { + _recordingLifetime.destroy(); + _lockShowing = false; + _recording = false; + _inField = false; + _redCircleProgress = 0.; + _recordingSamples = 0; + + _showAnimation.stop(); + + _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); + _controller->widget()->setInnerFocus(); +} + +void VoiceRecordBar::hideFast() { + hide(); + _lock->hide(); + _level->hide(); + stopRecording(false); +} + void VoiceRecordBar::stopRecording(bool send) { using namespace ::Media::Capture; if (!send) { @@ -880,12 +887,6 @@ float64 VoiceRecordBar::showAnimationRatio() const { return _showAnimation.value(1.); } -QString VoiceRecordBar::cancelMessage() const { - return _lock->isLocked() - ? tr::lng_record_lock_cancel(tr::now) - : tr::lng_record_cancel(tr::now); -} - void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { const auto localPos = mapFromGlobal(globalPos); const auto lower = _lock->height(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 5e592b595..a70a0e4db 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -46,6 +46,7 @@ public: void startRecording(); void finishAnimating(); + void hideFast(); void orderControls(); @@ -68,7 +69,6 @@ private: void updateMessageGeometry(); void updateLockGeometry(); - void recordError(); void recordUpdated(quint16 level, int samples); bool recordingAnimationCallback(crl::time now); @@ -81,7 +81,6 @@ private: void drawDuration(Painter &p); void drawRedCircle(Painter &p); void drawMessage(Painter &p, float64 recordActive); - void updateOverStates(QPoint pos); void startRedCircleAnimation(); void installClickOutsideFilter(); @@ -89,14 +88,14 @@ private: bool isTypeRecord() const; bool hasDuration() const; + void finish(); + void activeAnimate(bool active); float64 showAnimationRatio() const; float64 activeAnimationRatio() const; void computeAndSetLockProgress(QPoint globalPos); - QString cancelMessage() const; - const not_null _sectionWidget; const not_null _controller; const std::shared_ptr _send; From 7a32d7868972e337ea9c989dba2642c6e956d612 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 15 Oct 2020 21:58:02 +0300 Subject: [PATCH 080/370] Replaced record circle button with bezier circle. --- Telegram/CMakeLists.txt | 2 + .../history_view_voice_record_bar.cpp | 181 +---- .../controls/history_view_voice_record_bar.h | 4 +- .../history_view_voice_record_button.cpp | 746 ++++++++++++++++++ .../history_view_voice_record_button.h | 58 ++ Telegram/SourceFiles/ui/chat/chat.style | 10 +- 6 files changed, 818 insertions(+), 183 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e686508b3..2decd8466 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -479,6 +479,8 @@ PRIVATE history/view/controls/history_view_compose_controls.h history/view/controls/history_view_voice_record_bar.cpp history/view/controls/history_view_voice_record_bar.h + history/view/controls/history_view_voice_record_button.cpp + history/view/controls/history_view_voice_record_button.h history/view/media/history_view_call.h history/view/media/history_view_call.cpp history/view/media/history_view_contact.h diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 48e328ea0..1a85e370b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "boxes/confirm_box.h" #include "core/application.h" +#include "history/view/controls/history_view_voice_record_button.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "media/audio/media_audio.h" @@ -59,42 +60,6 @@ enum class FilterType { } // namespace -class RecordLevel final : public Ui::AbstractButton { -public: - RecordLevel( - not_null parent, - rpl::producer<> leaveWindowEventProducer); - - void requestPaintColor(float64 progress); - void requestPaintProgress(float64 progress); - void requestPaintLevel(quint16 level); - - [[nodiscard]] rpl::producer actives() const; - - [[nodiscard]] bool inCircle(const QPoint &localPos) const; - -private: - void init(); - - void drawProgress(Painter &p); - - const int _height; - const int _center; - - rpl::variable _showProgress = 0.; - rpl::variable _colorProgress = 0.; - rpl::variable _inCircle = false; - - bool recordingAnimationCallback(crl::time now); - - // This can animate for a very long time (like in music playing), - // so it should be a Basic, not a Simple animation. - Ui::Animations::Basic _recordingAnimation; - anim::value _recordingLevel; - - rpl::lifetime _showingLifetime; -}; - class RecordLock final : public Ui::RpWidget { public: RecordLock(not_null parent); @@ -117,148 +82,6 @@ private: rpl::variable _progress = 0.; }; -RecordLevel::RecordLevel( - not_null parent, - rpl::producer<> leaveWindowEventProducer) -: AbstractButton(parent) -, _height(st::historyRecordLevelMaxRadius * 2) -, _center(_height / 2) -, _recordingAnimation([=](crl::time now) { - return recordingAnimationCallback(now); -}) { - resize(_height, _height); - std::move( - leaveWindowEventProducer - ) | rpl::start_with_next([=] { - _inCircle = false; - }, lifetime()); - init(); -} - -void RecordLevel::requestPaintLevel(quint16 level) { - _recordingLevel.start(level); - _recordingAnimation.start(); -} - -bool RecordLevel::recordingAnimationCallback(crl::time now) { - const auto dt = anim::Disabled() - ? 1. - : ((now - _recordingAnimation.started()) - / float64(kRecordingUpdateDelta)); - if (dt >= 1.) { - _recordingLevel.finish(); - } else { - _recordingLevel.update(dt, anim::sineInOut); - } - if (!anim::Disabled()) { - update(); - } - return (dt < 1.); -} - -void RecordLevel::init() { - const auto hasProgress = [](auto value) { return value != 0.; }; - - paintRequest( - ) | rpl::start_with_next([=](const QRect &clip) { - Painter p(this); - - drawProgress(p); - - p.setOpacity(_showProgress.current()); - st::historyRecordVoiceActive.paintInCenter(p, rect()); - }, lifetime()); - - rpl::merge( - shownValue(), - _showProgress.value() | rpl::map(hasProgress) - ) | rpl::start_with_next([=](bool show) { - setVisible(show); - setMouseTracking(show); - if (!show) { - _recordingLevel = anim::value(); - _recordingAnimation.stop(); - _showingLifetime.destroy(); - _showProgress = 0.; - } - }, lifetime()); - - actives( - ) | rpl::distinct_until_changed( - ) | rpl::start_with_next([=](bool active) { - setPointerCursor(active); - }, lifetime()); -} - -rpl::producer RecordLevel::actives() const { - return events( - ) | rpl::filter([=](not_null e) { - return (e->type() == QEvent::MouseMove - || e->type() == QEvent::Leave - || e->type() == QEvent::Enter); - }) | rpl::map([=](not_null e) { - switch(e->type()) { - case QEvent::MouseMove: - return inCircle((static_cast(e.get()))->pos()); - case QEvent::Leave: return false; - case QEvent::Enter: return inCircle(mapFromGlobal(QCursor::pos())); - default: return false; - } - }); -} - -bool RecordLevel::inCircle(const QPoint &localPos) const { - const auto &radii = st::historyRecordLevelMaxRadius; - const auto dx = std::abs(localPos.x() - _center); - if (dx > radii) { - return false; - } - const auto dy = std::abs(localPos.y() - _center); - if (dy > radii) { - return false; - } else if (dx + dy <= radii) { - return true; - } - return ((dx * dx + dy * dy) <= (radii * radii)); -} - -void RecordLevel::drawProgress(Painter &p) { - PainterHighQualityEnabler hq(p); - p.setPen(Qt::NoPen); - const auto color = anim::color( - st::historyRecordSignalColor, - st::historyRecordVoiceFgActive, - _colorProgress.current()); - p.setBrush(color); - - const auto progress = _showProgress.current(); - - const auto center = QPoint(_center, _center); - const int mainRadii = progress * st::historyRecordLevelMainRadius; - - { - p.setOpacity(.5); - const auto min = progress * st::historyRecordLevelMinRadius; - const auto max = progress * st::historyRecordLevelMaxRadius; - const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.); - const auto radii = qRound(min + (delta * (max - min))); - p.drawEllipse(center, radii, radii); - p.setOpacity(1.); - } - - p.drawEllipse(center, mainRadii, mainRadii); -} - -void RecordLevel::requestPaintProgress(float64 progress) { - _showProgress = progress; - update(); -} - -void RecordLevel::requestPaintColor(float64 progress) { - _colorProgress = progress; - update(); -} - RecordLock::RecordLock(not_null parent) : RpWidget(parent) { resize( st::historyRecordLockTopShadow.width(), @@ -419,7 +242,7 @@ VoiceRecordBar::VoiceRecordBar( , _controller(controller) , _send(send) , _lock(std::make_unique(sectionWidget)) -, _level(std::make_unique( +, _level(std::make_unique( sectionWidget, _controller->widget()->leaveEvents())) , _startTimer([=] { startRecording(); }) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index a70a0e4db..16b7447d3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -23,7 +23,7 @@ class SessionController; namespace HistoryView::Controls { -class RecordLevel; +class VoiceRecordButton; class RecordLock; class VoiceRecordBar final : public Ui::RpWidget { @@ -100,7 +100,7 @@ private: const not_null _controller; const std::shared_ptr _send; const std::unique_ptr _lock; - const std::unique_ptr _level; + const std::unique_ptr _level; base::Timer _startTimer; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp new file mode 100644 index 000000000..bb9b81c0e --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -0,0 +1,746 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/controls/history_view_voice_record_button.h" + +#include "styles/style_chat.h" +#include "styles/style_layers.h" + +#include + +namespace HistoryView::Controls { + +namespace { + +constexpr auto kRecordingUpdateDelta = crl::time(100); + +constexpr auto kSegmentsCount = 12; +constexpr auto kMajorDegreeOffset = 360 / kSegmentsCount; +constexpr auto kSixtyDegrees = 60; + +constexpr auto kEnterIdleAnimationDuration = crl::time(1200); + +constexpr auto kRotationSpeed = 0.36 * 0.1; + +constexpr auto kRandomAdditionFactor = 0.3; + +constexpr auto kIdleRadiusGlobalFactor = 0.56; +constexpr auto kIdleRadiusFactor = 0.15 * 0.5; + +constexpr auto kOpacityMajor = 0.30; +constexpr auto kOpacityMinor = 0.15; + +constexpr auto kIdleRotationSpeed = 0.2; +constexpr auto kIdleRotateDiff = 0.1 * kIdleRotationSpeed; + +constexpr auto kWaveAngle = 0.03; + +constexpr auto kAnimationSpeedMajor = 1.5;// - 0.65; +constexpr auto kAnimationSpeedMinor = 1.5;// - 0.45; +constexpr auto kAnimationSpeedCircle = 1.5;// - 0.25; + +constexpr auto kAmplitudeDiffFactorMax = 500.; +constexpr auto kAmplitudeDiffFactorMajor = 300.; +constexpr auto kAmplitudeDiffFactorMinor = 400.; + +constexpr auto kFlingDistanceFactorMajor = 8 * 16; +constexpr auto kFlingDistanceFactorMinor = 20 * 16; + +constexpr auto kFlingInAnimationDurationMajor = 200; +constexpr auto kFlingInAnimationDurationMinor = 350; +constexpr auto kFlingOutAnimationDurationMajor = 220; +constexpr auto kFlingOutAnimationDurationMinor = 380; + +constexpr auto kSineWaveSpeedMajor = 0.02 * 0.2 * 0.5; +constexpr auto kSineWaveSpeedMinor = 0.026 * 0.2 * 0.5; + +constexpr auto kSmallWaveRadius = 0.55; + +constexpr auto kFlingDistance = 0.50; + +constexpr auto kMinDivider = 100.; + +constexpr auto kMaxAmplitude = 1800.; + +constexpr auto kZeroPoint = QPointF(0, 0); + +void ApplyTo(float64 &value, const float64 &to, const float64 &diff) { + if ((value != to) && ((diff > 0) == (value > to))) { + value = to; + } +} + +template +void Normalize(Number &value, Number right) { + if (value >= right) { + value -= right; + } +} + +float64 RandomAdditional() { + return (rand_value() % 100 / 100.); +} + +void PerformAnimation( + rpl::producer &&animationTicked, + Fn &&applyValue, + Fn &&finishCallback, + float64 duration, + float64 from, + float64 to, + rpl::lifetime &lifetime) { + lifetime.destroy(); + const auto animValue = + lifetime.make_state(from, to); + const auto animStarted = crl::now(); + std::move( + animationTicked + ) | rpl::start_with_next([=, + applyValue = std::move(applyValue), + finishCallback = std::move(finishCallback), + &lifetime](crl::time now) mutable { + const auto dt = anim::Disabled() + ? 1. + : ((now - animStarted) / duration); + if (dt >= 1.) { + animValue->finish(); + applyValue(animValue->current()); + lifetime.destroy(); + if (finishCallback) { + finishCallback(); + } + } else { + animValue->update(dt, anim::linear); + applyValue(animValue->current()); + } + }, lifetime); +} + +} // namespace + +class CircleBezier final { +public: + CircleBezier(int n); + + void computeRandomAdditionals(); + void paintCircle( + Painter &p, + const QColor &c, + float64 radius, + float64 cubicBezierFactor, + float64 idleStateDiff, + float64 radiusDiff, + float64 randomFactor); + +private: + struct Points { + QPointF point; + QPointF control; + }; + + const int _segmentsCount; + const float64 _segmentLength; + std::vector _randomAdditionals; + +}; + +class Wave final { +public: + Wave( + rpl::producer animationTicked, + int n, + float64 rotationOffset, + float64 amplitudeRadius, + float64 amplitudeWaveDiff, + float64 fling, + int flingDistanceFactor, + int flingInAnimationDuration, + int flingOutAnimationDuration, + float64 amplitudeDiffSpeed, + float64 amplitudeDiffFactor, + bool isDirectionClockwise); + + void setValue(float64 value); + void tick(float64 circleRadius, crl::time lastUpdateTime); + + void paint(Painter &p, QColor c); + +private: + + void initEnterIdleAnimation(rpl::producer animationTicked); + void initFlingAnimation(rpl::producer animationTicked); + + Ui::Animations::Simple _flingAnimation; + + const std::unique_ptr _circleBezier; + + const float _rotationOffset; + const float64 _idleGlobalRadius; + const float64 _amplitudeRadius; + const float64 _amplitudeWaveDiff; + const float64 _randomAdditions; + const float64 _fling; + const int _flingDistanceFactor; + const int _flingInAnimationDuration; + const int _flingOutAnimationDuration; + const float64 _amplitudeDiffSpeed; + const float64 _amplitudeDiffFactor; + const int _directionClockwise; + + bool _incRandomAdditionals = false; + bool _isIdle = true; + bool _wasFling = false; + float64 _amplitude = 0.; + float64 _animateAmplitudeDiff = 0.; + float64 _animateAmplitudeSlowDiff = 0.; + float64 _animateToAmplitude = 0.; + float64 _flingRadius = 0.; + float64 _idleRadius = 0.; + float64 _idleRotation = 0.; + float64 _lastRadius = 0.; + float64 _rotation = 0.; + float64 _sineAngleMax = 0.; + float64 _slowAmplitude = 0.; + float64 _waveAngle = 0.; + float64 _waveDiff = 0.; + + rpl::event_stream _flingAnimationRequests; + rpl::event_stream<> _enterIdleAnimationRequests; + rpl::lifetime _animationEnterIdleLifetime; + rpl::lifetime _animationFlingLifetime; + rpl::lifetime _lifetime; +}; + +class RecordCircle final { +public: + RecordCircle(rpl::producer animationTicked); + + void setAmplitude(float64 value); + void paint(Painter &p, QColor c); + +private: + + const std::unique_ptr _majorWave; + const std::unique_ptr _minorWave; + + float64 _amplitude = 0.; + float64 _animateToAmplitude = 0.; + float64 _animateAmplitudeDiff = 0.; + crl::time _lastUpdateTime = 0; + + rpl::lifetime _animationLifetime; + rpl::lifetime _lifetime; + +}; + +CircleBezier::CircleBezier(int n) +: _segmentsCount(n) +, _segmentLength((4.0 / 3.0) * std::tan(M_PI / (2 * n))) +, _randomAdditionals(n) { +} + +void CircleBezier::computeRandomAdditionals() { + ranges::generate(_randomAdditionals, RandomAdditional); +} + +void CircleBezier::paintCircle( + Painter &p, + const QColor &c, + float64 radius, + float64 cubicBezierFactor, + float64 idleStateDiff, + float64 radiusDiff, + float64 randomFactor) { + PainterHighQualityEnabler hq(p); + + const auto r1 = radius - idleStateDiff / 2. - radiusDiff / 2.; + const auto r2 = radius + radiusDiff / 2. + idleStateDiff / 2.; + const auto l = _segmentLength * std::max(r1, r2) * cubicBezierFactor; + + auto m = QMatrix(); + + const auto preparePoints = [&](int i, bool isStart) -> Points { + Normalize(i, _segmentsCount); + const auto randomAddition = randomFactor * _randomAdditionals[i]; + const auto r = ((i % 2 == 0) ? r1 : r2) + randomAddition; + + m.reset(); + m.rotate(360. / _segmentsCount * i); + const auto sign = isStart ? 1 : -1; + + return { + (isStart && i) ? QPointF() : m.map(QPointF(0, -r)), + m.map(QPointF(sign * (l + randomAddition * _segmentLength), -r)), + }; + }; + + const auto &[startPoint, _] = preparePoints(0, true); + + auto path = QPainterPath(); + path.moveTo(startPoint); + + for (auto i = 0; i < _segmentsCount; i++) { + const auto &[_, startControl] = preparePoints(i, true); + const auto &[end, endControl] = preparePoints(i + 1, false); + + path.cubicTo(startControl, endControl, end); + } + + p.setBrush(Qt::NoBrush); + + auto pen = QPen(Qt::NoPen); + pen.setCapStyle(Qt::RoundCap); + pen.setJoinStyle(Qt::RoundJoin); + + p.setPen(pen); + p.fillPath(path, c); + p.drawPath(path); +} + +Wave::Wave( + rpl::producer animationTicked, + int n, + float64 rotationOffset, + float64 amplitudeRadius, + float64 amplitudeWaveDiff, + float64 fling, + int flingDistanceFactor, + int flingInAnimationDuration, + int flingOutAnimationDuration, + float64 amplitudeDiffSpeed, + float64 amplitudeDiffFactor, + bool isDirectionClockwise) +: _circleBezier(std::make_unique(n)) +, _rotationOffset(rotationOffset) +, _idleGlobalRadius(st::historyRecordRadiusDiffMin * kIdleRadiusGlobalFactor) +, _amplitudeRadius(amplitudeRadius) +, _amplitudeWaveDiff(amplitudeWaveDiff) +, _randomAdditions(st::historyRecordRandomAddition * kRandomAdditionFactor) +, _fling(fling) +, _flingDistanceFactor(flingDistanceFactor) +, _flingInAnimationDuration(flingInAnimationDuration) +, _flingOutAnimationDuration(flingOutAnimationDuration) +, _amplitudeDiffSpeed(amplitudeDiffSpeed) +, _amplitudeDiffFactor(amplitudeDiffFactor) +, _directionClockwise(isDirectionClockwise ? 1 : -1) +, _rotation(rotationOffset) { + initEnterIdleAnimation(rpl::duplicate(animationTicked)); + initFlingAnimation(std::move(animationTicked)); +} + +void Wave::setValue(float64 value) { + _animateToAmplitude = value; + + const auto amplitudeDelta = (_animateToAmplitude - _amplitude); + const auto amplitudeSlowDelta = (_animateToAmplitude - _slowAmplitude); + const auto factor = (_animateToAmplitude <= _amplitude) + ? kAmplitudeDiffFactorMax + : _amplitudeDiffFactor; + _animateAmplitudeDiff = amplitudeDelta + / (kMinDivider + factor * _amplitudeDiffSpeed); + _animateAmplitudeSlowDiff = amplitudeSlowDelta + / (kMinDivider + kAmplitudeDiffFactorMax * _amplitudeDiffSpeed); + + const auto idle = value < 0.1; + if (_isIdle != idle && idle) { + _enterIdleAnimationRequests.fire({}); + } + + _isIdle = idle; + + if (!_isIdle) { + _animationEnterIdleLifetime.destroy(); + } +} + +void Wave::initEnterIdleAnimation(rpl::producer animationTicked) { + _enterIdleAnimationRequests.events( + ) | rpl::start_with_next([=] { + const auto &k = kSixtyDegrees; + + const auto rotation = _rotation; + const auto rotationTo = std::round(rotation / k) * k + + _rotationOffset; + const auto waveDiff = _waveDiff; + + auto applyValue = [=](float64 v) { + _rotation = rotationTo + (rotation - rotationTo) * v; + _waveDiff = 1. + (waveDiff - 1.) * v; + _waveAngle = std::acos(_waveDiff * _directionClockwise); + }; + + PerformAnimation( + rpl::duplicate(animationTicked), + std::move(applyValue), + nullptr, + kEnterIdleAnimationDuration, + 1, + 0, + _animationEnterIdleLifetime); + + }, _lifetime); +} + +void Wave::initFlingAnimation(rpl::producer animationTicked) { + _flingAnimationRequests.events( + ) | rpl::start_with_next([=](float64 delta) { + + const auto fling = _fling * 2; + const auto flingDistance = delta + * _amplitudeRadius + * _flingDistanceFactor + * fling; + + const auto applyValue = [=](float64 v) { + _flingRadius = v; + }; + auto finishCallback = [=] { + PerformAnimation( + rpl::duplicate(animationTicked), + applyValue, + nullptr, + _flingOutAnimationDuration * fling, + flingDistance, + 0, + _animationFlingLifetime); + }; + + PerformAnimation( + rpl::duplicate(animationTicked), + applyValue, + std::move(finishCallback), + _flingInAnimationDuration * fling, + _flingRadius, + flingDistance, + _animationFlingLifetime); + + }, _lifetime); +} + +void Wave::tick(float64 circleRadius, crl::time lastUpdateTime) { + const auto dt = (crl::now() - lastUpdateTime); + + if (_animateToAmplitude != _amplitude) { + _amplitude += _animateAmplitudeDiff * dt; + ApplyTo(_amplitude, _animateToAmplitude, _animateAmplitudeDiff); + + if (std::abs(_amplitude - _animateToAmplitude) * _amplitudeRadius + < (st::historyRecordRandomAddition / 2)) { + if (!_wasFling) { + _flingAnimationRequests.fire_copy(_animateAmplitudeDiff); + _wasFling = true; + } + } else { + _wasFling = false; + } + } + + if (_animateToAmplitude != _slowAmplitude) { + _slowAmplitude += _animateAmplitudeSlowDiff * dt; + if (std::abs(_slowAmplitude - _amplitude) > 0.2) { + _slowAmplitude = _amplitude + (_slowAmplitude > _amplitude ? + 0.2 : -0.2); + } + ApplyTo(_slowAmplitude, + _animateToAmplitude, + _animateAmplitudeSlowDiff); + } + + _idleRadius = circleRadius * kIdleRadiusFactor; + + { + const auto delta = (_sineAngleMax - _animateToAmplitude); + if (std::abs(delta) - 0.25 < 0) { + _sineAngleMax = _animateToAmplitude; + } else { + _sineAngleMax -= 0.25 * ((delta < 0) ? -1 : 1); + } + } + + if (!_isIdle) { + _rotation += dt + * (kRotationSpeed * 4. * (_amplitude > 0.5 ? 1 : _amplitude / 0.5) + + kRotationSpeed * 0.5); + Normalize(_rotation, 360.); + } else { + _idleRotation += kIdleRotateDiff * dt; + Normalize(_idleRotation, 360.); + } + + _lastRadius = circleRadius; + + if (!_isIdle) { + _waveAngle += (_amplitudeWaveDiff * _sineAngleMax) * dt; + _waveDiff = std::cos(_waveAngle) * _directionClockwise; + + if ((_waveDiff != 0) && ((_waveDiff > 0) == _incRandomAdditionals)) { + _circleBezier->computeRandomAdditionals(); + _incRandomAdditionals = !_incRandomAdditionals; + } + } +} + + +void Wave::paint(Painter &p, QColor c) { + const auto waveAmplitude = _amplitude < 0.3 ? _amplitude / 0.3 : 1.; + const auto radiusDiff = st::historyRecordRadiusDiffMin + + st::historyRecordRadiusDiff * kWaveAngle * _animateToAmplitude; + + const auto diffFactor = 0.35 * waveAmplitude * _waveDiff; + + const auto radius = (_lastRadius + _amplitudeRadius * _amplitude) + + _idleGlobalRadius + + (_flingRadius * waveAmplitude); + + const auto cubicBezierFactor = 1. + + std::abs(diffFactor) * waveAmplitude + + (1. - waveAmplitude) * kIdleRadiusFactor; + + const auto circleRadiusDiff = std::max( + radiusDiff * diffFactor, + st::historyRecordLevelMainRadius - radius); + + p.rotate((_rotation + _idleRotation) * _directionClockwise); + + _circleBezier->paintCircle( + p, + c, + radius, + cubicBezierFactor, + _idleRadius * (1. - waveAmplitude), + circleRadiusDiff, + waveAmplitude * _waveDiff * _randomAdditions); + + p.rotate(0); +} + +RecordCircle::RecordCircle(rpl::producer animationTicked) +: _majorWave(std::make_unique( + rpl::duplicate(animationTicked), + kSegmentsCount, + kMajorDegreeOffset, + st::historyRecordMajorAmplitudeRadius, + kSineWaveSpeedMajor, + 0., + kFlingDistanceFactorMajor, + kFlingInAnimationDurationMajor, + kFlingOutAnimationDurationMajor, + kAnimationSpeedMajor, + kAmplitudeDiffFactorMajor, + true)) +, _minorWave(std::make_unique( + std::move(animationTicked), + kSegmentsCount, + 0, + st::historyRecordMinorAmplitudeRadius + + st::historyRecordMinorAmplitudeRadius * kSmallWaveRadius, + kSineWaveSpeedMinor, + kFlingDistance, + kFlingDistanceFactorMinor, + kFlingInAnimationDurationMinor, + kFlingOutAnimationDurationMinor, + kAnimationSpeedMinor, + kAmplitudeDiffFactorMinor, + false)) { +} + +void RecordCircle::setAmplitude(float64 value) { + _animateToAmplitude = std::min(kMaxAmplitude, value) / kMaxAmplitude; + _majorWave->setValue(_animateToAmplitude); + _minorWave->setValue(_animateToAmplitude); + _animateAmplitudeDiff = (_animateToAmplitude - _amplitude) + / (kMinDivider + kAmplitudeDiffFactorMax * kAnimationSpeedCircle); +} + +void RecordCircle::paint(Painter &p, QColor c) { + + const auto dt = (crl::now() - _lastUpdateTime); + if (_animateToAmplitude != _amplitude) { + _amplitude += _animateAmplitudeDiff * dt; + ApplyTo(_amplitude, _animateToAmplitude, _animateAmplitudeDiff); + } + + const auto radius = (st::historyRecordLevelMainRadius + + st::historyRecordLevelMainRadiusAmplitude * _amplitude); + + _majorWave->tick(radius, _lastUpdateTime); + _minorWave->tick(radius, _lastUpdateTime); + _lastUpdateTime = crl::now(); + + const auto opacity = p.opacity(); + p.setOpacity(kOpacityMajor); + _majorWave->paint(p, c); + p.setOpacity(kOpacityMinor); + _minorWave->paint(p, c); + p.setOpacity(opacity); + + p.setPen(Qt::NoPen); + p.setBrush(c); + p.drawEllipse(kZeroPoint, radius, radius); +} + +VoiceRecordButton::VoiceRecordButton( + not_null parent, + rpl::producer<> leaveWindowEventProducer) +: AbstractButton(parent) +, _recordCircle(std::make_unique( + _recordAnimationTicked.events())) +, _height(st::historyRecordLevelMaxRadius * 2) +, _center(_height / 2) +, _recordingAnimation([=](crl::time now) { + update(); + _recordAnimationTicked.fire_copy(now); + return true; +}) { + resize(_height, _height); + std::move( + leaveWindowEventProducer + ) | rpl::start_with_next([=] { + _inCircle = false; + }, lifetime()); + init(); +} + +VoiceRecordButton::~VoiceRecordButton() = default; + +void VoiceRecordButton::requestPaintLevel(quint16 level) { + _recordCircle->setAmplitude(level); + update(); +} + +bool VoiceRecordButton::recordingAnimationCallback(crl::time now) { + const auto dt = anim::Disabled() + ? 1. + : ((now - _recordingAnimation.started()) + / float64(kRecordingUpdateDelta)); + if (dt >= 1.) { + _recordingLevel.finish(); + } else { + _recordingLevel.update(dt, anim::sineInOut); + } + if (!anim::Disabled()) { + update(); + } + return (dt < 1.); +} + +void VoiceRecordButton::init() { + const auto hasProgress = [](auto value) { return value != 0.; }; + + paintRequest( + ) | rpl::start_with_next([=](const QRect &clip) { + Painter p(this); + + p.translate(_center, _center); + PainterHighQualityEnabler hq(p); + const auto color = anim::color( + st::historyRecordSignalColor, + st::historyRecordVoiceFgActive, + _colorProgress.current()); + _recordCircle->paint(p, color); + p.resetTransform(); + + p.setOpacity(_showProgress.current()); + st::historyRecordVoiceActive.paintInCenter(p, rect()); + + }, lifetime()); + + rpl::merge( + shownValue(), + _showProgress.value() | rpl::map(hasProgress) + ) | rpl::start_with_next([=](bool show) { + setVisible(show); + setMouseTracking(show); + if (!show) { + _recordingLevel = anim::value(); + _recordingAnimation.stop(); + _showingLifetime.destroy(); + _showProgress = 0.; + } else { + if (!_recordingAnimation.animating()) { + _recordingAnimation.start(); + } + } + }, lifetime()); + + actives( + ) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](bool active) { + setPointerCursor(active); + }, lifetime()); +} + +rpl::producer VoiceRecordButton::actives() const { + return events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::MouseMove + || e->type() == QEvent::Leave + || e->type() == QEvent::Enter); + }) | rpl::map([=](not_null e) { + switch(e->type()) { + case QEvent::MouseMove: + return inCircle((static_cast(e.get()))->pos()); + case QEvent::Leave: return false; + case QEvent::Enter: return inCircle(mapFromGlobal(QCursor::pos())); + default: return false; + } + }); +} + +bool VoiceRecordButton::inCircle(const QPoint &localPos) const { + const auto &radii = st::historyRecordLevelMaxRadius; + const auto dx = std::abs(localPos.x() - _center); + if (dx > radii) { + return false; + } + const auto dy = std::abs(localPos.y() - _center); + if (dy > radii) { + return false; + } else if (dx + dy <= radii) { + return true; + } + return ((dx * dx + dy * dy) <= (radii * radii)); +} + +void VoiceRecordButton::drawProgress(Painter &p) { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + const auto color = anim::color( + st::historyRecordSignalColor, + st::historyRecordVoiceFgActive, + _colorProgress.current()); + p.setBrush(color); + + const auto progress = _showProgress.current(); + + const auto center = QPoint(_center, _center); + const int mainRadii = progress * st::historyRecordLevelMainRadius; + + { + p.setOpacity(.5); + const auto min = progress * st::historyRecordLevelMinRadius; + const auto max = progress * st::historyRecordLevelMaxRadius; + const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.); + const auto radii = qRound(min + (delta * (max - min))); + p.drawEllipse(center, radii, radii); + p.setOpacity(1.); + } + + p.drawEllipse(center, mainRadii, mainRadii); +} + +void VoiceRecordButton::requestPaintProgress(float64 progress) { + _showProgress = progress; + update(); +} + +void VoiceRecordButton::requestPaintColor(float64 progress) { + _colorProgress = progress; + update(); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h new file mode 100644 index 000000000..e44573d81 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h @@ -0,0 +1,58 @@ +/* +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/abstract_button.h" +#include "ui/effects/animations.h" +#include "ui/rp_widget.h" + +namespace HistoryView::Controls { + +class RecordCircle; + +class VoiceRecordButton final : public Ui::AbstractButton { +public: + VoiceRecordButton( + not_null parent, + rpl::producer<> leaveWindowEventProducer); + ~VoiceRecordButton(); + + void requestPaintColor(float64 progress); + void requestPaintProgress(float64 progress); + void requestPaintLevel(quint16 level); + + [[nodiscard]] rpl::producer actives() const; + + [[nodiscard]] bool inCircle(const QPoint &localPos) const; + +private: + void init(); + + void drawProgress(Painter &p); + + rpl::event_stream _recordAnimationTicked; + std::unique_ptr _recordCircle; + + const int _height; + const int _center; + + rpl::variable _showProgress = 0.; + rpl::variable _colorProgress = 0.; + rpl::variable _inCircle = false; + + bool recordingAnimationCallback(crl::time now); + + // This can animate for a very long time (like in music playing), + // so it should be a Basic, not a Simple animation. + Ui::Animations::Basic _recordingAnimation; + anim::value _recordingLevel; + + rpl::lifetime _showingLifetime; +}; + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index bd386eb65..ebaca8649 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -342,9 +342,15 @@ historyRecordFont: font(13px); historyRecordDurationSkip: 12px; historyRecordDurationFg: historyComposeAreaFg; -historyRecordLevelMainRadius: 37px; +historyRecordLevelMainRadius: 23px; +historyRecordLevelMainRadiusAmplitude: 14px; +historyRecordMajorAmplitudeRadius: 14px; +historyRecordMinorAmplitudeRadius: 7px; +historyRecordRandomAddition: 8px; +historyRecordRadiusDiff: 50px; +historyRecordRadiusDiffMin: 10px; historyRecordLevelMinRadius: 38px; -historyRecordLevelMaxRadius: 60px; +historyRecordLevelMaxRadius: 70px; historyRecordTextStyle: TextStyle(defaultTextStyle) { font: historyRecordFont; From 6ae15485adc8d56d515789f576ce7597488c5c04 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 29 Oct 2020 03:32:24 +0300 Subject: [PATCH 081/370] Added show animation to VoiceRecordButton. --- .../view/controls/history_view_voice_record_button.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index bb9b81c0e..2cd2da163 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -635,7 +635,13 @@ void VoiceRecordButton::init() { ) | rpl::start_with_next([=](const QRect &clip) { Painter p(this); + const auto progress = _showProgress.current(); + const auto complete = (progress == 1.); + p.translate(_center, _center); + if (!complete) { + p.scale(progress, progress); + } PainterHighQualityEnabler hq(p); const auto color = anim::color( st::historyRecordSignalColor, @@ -644,7 +650,9 @@ void VoiceRecordButton::init() { _recordCircle->paint(p, color); p.resetTransform(); - p.setOpacity(_showProgress.current()); + if (!complete) { + p.setOpacity(progress); + } st::historyRecordVoiceActive.paintInCenter(p, rect()); }, lifetime()); From ce1ae5ba12af0f12ae0e82d3bbe4d6a5cb61e409 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 29 Oct 2020 03:40:40 +0300 Subject: [PATCH 082/370] Removed redundant code from VoiceRecordButton. --- .../history_view_voice_record_button.cpp | 69 +------------------ .../history_view_voice_record_button.h | 8 --- 2 files changed, 3 insertions(+), 74 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 2cd2da163..68ff4f302 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -16,8 +16,6 @@ namespace HistoryView::Controls { namespace { -constexpr auto kRecordingUpdateDelta = crl::time(100); - constexpr auto kSegmentsCount = 12; constexpr auto kMajorDegreeOffset = 360 / kSegmentsCount; constexpr auto kSixtyDegrees = 60; @@ -196,7 +194,6 @@ private: bool _wasFling = false; float64 _amplitude = 0.; float64 _animateAmplitudeDiff = 0.; - float64 _animateAmplitudeSlowDiff = 0.; float64 _animateToAmplitude = 0.; float64 _flingRadius = 0.; float64 _idleRadius = 0.; @@ -204,7 +201,6 @@ private: float64 _lastRadius = 0.; float64 _rotation = 0.; float64 _sineAngleMax = 0.; - float64 _slowAmplitude = 0.; float64 _waveAngle = 0.; float64 _waveDiff = 0.; @@ -336,14 +332,11 @@ void Wave::setValue(float64 value) { _animateToAmplitude = value; const auto amplitudeDelta = (_animateToAmplitude - _amplitude); - const auto amplitudeSlowDelta = (_animateToAmplitude - _slowAmplitude); const auto factor = (_animateToAmplitude <= _amplitude) ? kAmplitudeDiffFactorMax : _amplitudeDiffFactor; _animateAmplitudeDiff = amplitudeDelta / (kMinDivider + factor * _amplitudeDiffSpeed); - _animateAmplitudeSlowDiff = amplitudeSlowDelta - / (kMinDivider + kAmplitudeDiffFactorMax * _amplitudeDiffSpeed); const auto idle = value < 0.1; if (_isIdle != idle && idle) { @@ -439,17 +432,6 @@ void Wave::tick(float64 circleRadius, crl::time lastUpdateTime) { } } - if (_animateToAmplitude != _slowAmplitude) { - _slowAmplitude += _animateAmplitudeSlowDiff * dt; - if (std::abs(_slowAmplitude - _amplitude) > 0.2) { - _slowAmplitude = _amplitude + (_slowAmplitude > _amplitude ? - 0.2 : -0.2); - } - ApplyTo(_slowAmplitude, - _animateToAmplitude, - _animateAmplitudeSlowDiff); - } - _idleRadius = circleRadius * kIdleRadiusFactor; { @@ -589,14 +571,14 @@ VoiceRecordButton::VoiceRecordButton( : AbstractButton(parent) , _recordCircle(std::make_unique( _recordAnimationTicked.events())) -, _height(st::historyRecordLevelMaxRadius * 2) -, _center(_height / 2) +, _center(st::historyRecordLevelMaxRadius) , _recordingAnimation([=](crl::time now) { update(); _recordAnimationTicked.fire_copy(now); return true; }) { - resize(_height, _height); + const auto h = st::historyRecordLevelMaxRadius * 2; + resize(h, h); std::move( leaveWindowEventProducer ) | rpl::start_with_next([=] { @@ -612,22 +594,6 @@ void VoiceRecordButton::requestPaintLevel(quint16 level) { update(); } -bool VoiceRecordButton::recordingAnimationCallback(crl::time now) { - const auto dt = anim::Disabled() - ? 1. - : ((now - _recordingAnimation.started()) - / float64(kRecordingUpdateDelta)); - if (dt >= 1.) { - _recordingLevel.finish(); - } else { - _recordingLevel.update(dt, anim::sineInOut); - } - if (!anim::Disabled()) { - update(); - } - return (dt < 1.); -} - void VoiceRecordButton::init() { const auto hasProgress = [](auto value) { return value != 0.; }; @@ -664,9 +630,7 @@ void VoiceRecordButton::init() { setVisible(show); setMouseTracking(show); if (!show) { - _recordingLevel = anim::value(); _recordingAnimation.stop(); - _showingLifetime.destroy(); _showProgress = 0.; } else { if (!_recordingAnimation.animating()) { @@ -714,33 +678,6 @@ bool VoiceRecordButton::inCircle(const QPoint &localPos) const { return ((dx * dx + dy * dy) <= (radii * radii)); } -void VoiceRecordButton::drawProgress(Painter &p) { - PainterHighQualityEnabler hq(p); - p.setPen(Qt::NoPen); - const auto color = anim::color( - st::historyRecordSignalColor, - st::historyRecordVoiceFgActive, - _colorProgress.current()); - p.setBrush(color); - - const auto progress = _showProgress.current(); - - const auto center = QPoint(_center, _center); - const int mainRadii = progress * st::historyRecordLevelMainRadius; - - { - p.setOpacity(.5); - const auto min = progress * st::historyRecordLevelMinRadius; - const auto max = progress * st::historyRecordLevelMaxRadius; - const auto delta = std::min(_recordingLevel.current() / 0x4000, 1.); - const auto radii = qRound(min + (delta * (max - min))); - p.drawEllipse(center, radii, radii); - p.setOpacity(1.); - } - - p.drawEllipse(center, mainRadii, mainRadii); -} - void VoiceRecordButton::requestPaintProgress(float64 progress) { _showProgress = progress; update(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h index e44573d81..27d0068eb 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h @@ -33,26 +33,18 @@ public: private: void init(); - void drawProgress(Painter &p); - rpl::event_stream _recordAnimationTicked; std::unique_ptr _recordCircle; - const int _height; const int _center; rpl::variable _showProgress = 0.; rpl::variable _colorProgress = 0.; rpl::variable _inCircle = false; - bool recordingAnimationCallback(crl::time now); - // This can animate for a very long time (like in music playing), // so it should be a Basic, not a Simple animation. Ui::Animations::Basic _recordingAnimation; - anim::value _recordingLevel; - - rpl::lifetime _showingLifetime; }; } // namespace HistoryView::Controls From 71e8bda7bb933ee73c3d294be35bd0231756ac8e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 29 Oct 2020 17:25:36 +0300 Subject: [PATCH 083/370] Replaced raw variables for animations in VoiceRecordButton. --- .../history_view_voice_record_button.cpp | 145 ++++++++++-------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 68ff4f302..e554ce534 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -66,12 +66,6 @@ constexpr auto kMaxAmplitude = 1800.; constexpr auto kZeroPoint = QPointF(0, 0); -void ApplyTo(float64 &value, const float64 &to, const float64 &diff) { - if ((value != to) && ((diff > 0) == (value > to))) { - value = to; - } -} - template void Normalize(Number &value, Number right) { if (value >= right) { @@ -120,6 +114,49 @@ void PerformAnimation( } // namespace +class ContinuousValue { +public: + ContinuousValue() = default; + ContinuousValue(float64 duration) : _duration(duration) { + } + void start(float64 to, float64 duration) { + _to = to; + _delta = (_to - _cur) / duration; + } + void start(float64 to) { + start(to, _duration); + } + + float64 current() const { + return _cur; + } + float64 to() const { + return _to; + } + float64 delta() const { + return _delta; + } + void update(crl::time dt, Fn &&callback = nullptr) { + if (_to != _cur) { + _cur += _delta * dt; + if ((_to != _cur) && ((_delta > 0) == (_cur > _to))) { + _cur = _to; + } + if (callback) { + callback(_cur); + } + } + } + +private: + float64 _duration = 0.; + float64 _to = 0.; + + float64 _cur = 0.; + float64 _delta = 0.; + +}; + class CircleBezier final { public: CircleBezier(int n); @@ -162,8 +199,8 @@ public: float64 amplitudeDiffFactor, bool isDirectionClockwise); - void setValue(float64 value); - void tick(float64 circleRadius, crl::time lastUpdateTime); + void setValue(float64 to); + void tick(float64 circleRadius, crl::time dt); void paint(Painter &p, QColor c); @@ -185,16 +222,13 @@ private: const int _flingDistanceFactor; const int _flingInAnimationDuration; const int _flingOutAnimationDuration; - const float64 _amplitudeDiffSpeed; - const float64 _amplitudeDiffFactor; + const float64 _amplitudeInAnimationDuration; + const float64 _amplitudeOutAnimationDuration; const int _directionClockwise; bool _incRandomAdditionals = false; bool _isIdle = true; bool _wasFling = false; - float64 _amplitude = 0.; - float64 _animateAmplitudeDiff = 0.; - float64 _animateToAmplitude = 0.; float64 _flingRadius = 0.; float64 _idleRadius = 0.; float64 _idleRotation = 0.; @@ -203,6 +237,7 @@ private: float64 _sineAngleMax = 0.; float64 _waveAngle = 0.; float64 _waveDiff = 0.; + ContinuousValue _levelValue; rpl::event_stream _flingAnimationRequests; rpl::event_stream<> _enterIdleAnimationRequests; @@ -223,13 +258,8 @@ private: const std::unique_ptr _majorWave; const std::unique_ptr _minorWave; - float64 _amplitude = 0.; - float64 _animateToAmplitude = 0.; - float64 _animateAmplitudeDiff = 0.; crl::time _lastUpdateTime = 0; - - rpl::lifetime _animationLifetime; - rpl::lifetime _lifetime; + ContinuousValue _levelValue; }; @@ -320,25 +350,23 @@ Wave::Wave( , _flingDistanceFactor(flingDistanceFactor) , _flingInAnimationDuration(flingInAnimationDuration) , _flingOutAnimationDuration(flingOutAnimationDuration) -, _amplitudeDiffSpeed(amplitudeDiffSpeed) -, _amplitudeDiffFactor(amplitudeDiffFactor) +, _amplitudeInAnimationDuration(kMinDivider + + amplitudeDiffFactor * amplitudeDiffSpeed) +, _amplitudeOutAnimationDuration(kMinDivider + + kAmplitudeDiffFactorMax * amplitudeDiffSpeed) , _directionClockwise(isDirectionClockwise ? 1 : -1) , _rotation(rotationOffset) { initEnterIdleAnimation(rpl::duplicate(animationTicked)); initFlingAnimation(std::move(animationTicked)); } -void Wave::setValue(float64 value) { - _animateToAmplitude = value; +void Wave::setValue(float64 to) { + const auto duration = (to <= _levelValue.current()) + ? _amplitudeOutAnimationDuration + : _amplitudeInAnimationDuration; + _levelValue.start(to, duration); - const auto amplitudeDelta = (_animateToAmplitude - _amplitude); - const auto factor = (_animateToAmplitude <= _amplitude) - ? kAmplitudeDiffFactorMax - : _amplitudeDiffFactor; - _animateAmplitudeDiff = amplitudeDelta - / (kMinDivider + factor * _amplitudeDiffSpeed); - - const auto idle = value < 0.1; + const auto idle = to < 0.1; if (_isIdle != idle && idle) { _enterIdleAnimationRequests.fire({}); } @@ -414,30 +442,28 @@ void Wave::initFlingAnimation(rpl::producer animationTicked) { }, _lifetime); } -void Wave::tick(float64 circleRadius, crl::time lastUpdateTime) { - const auto dt = (crl::now() - lastUpdateTime); +void Wave::tick(float64 circleRadius, crl::time dt) { - if (_animateToAmplitude != _amplitude) { - _amplitude += _animateAmplitudeDiff * dt; - ApplyTo(_amplitude, _animateToAmplitude, _animateAmplitudeDiff); - - if (std::abs(_amplitude - _animateToAmplitude) * _amplitudeRadius + auto amplitudeCallback = [&](float64 &value) { + if (std::abs(value - _levelValue.to()) * _amplitudeRadius < (st::historyRecordRandomAddition / 2)) { if (!_wasFling) { - _flingAnimationRequests.fire_copy(_animateAmplitudeDiff); + _flingAnimationRequests.fire_copy(_levelValue.delta()); _wasFling = true; } } else { _wasFling = false; } - } + }; + _levelValue.update(dt, std::move(amplitudeCallback)); _idleRadius = circleRadius * kIdleRadiusFactor; { - const auto delta = (_sineAngleMax - _animateToAmplitude); + const auto to = _levelValue.to(); + const auto delta = (_sineAngleMax - to); if (std::abs(delta) - 0.25 < 0) { - _sineAngleMax = _animateToAmplitude; + _sineAngleMax = to; } else { _sineAngleMax -= 0.25 * ((delta < 0) ? -1 : 1); } @@ -445,7 +471,7 @@ void Wave::tick(float64 circleRadius, crl::time lastUpdateTime) { if (!_isIdle) { _rotation += dt - * (kRotationSpeed * 4. * (_amplitude > 0.5 ? 1 : _amplitude / 0.5) + * (kRotationSpeed * 4. * std::min(_levelValue.current() / .5, 1.) + kRotationSpeed * 0.5); Normalize(_rotation, 360.); } else { @@ -468,13 +494,14 @@ void Wave::tick(float64 circleRadius, crl::time lastUpdateTime) { void Wave::paint(Painter &p, QColor c) { - const auto waveAmplitude = _amplitude < 0.3 ? _amplitude / 0.3 : 1.; + const auto amplitude = _levelValue.current(); + const auto waveAmplitude = std::min(amplitude / .3, 1.); const auto radiusDiff = st::historyRecordRadiusDiffMin - + st::historyRecordRadiusDiff * kWaveAngle * _animateToAmplitude; + + st::historyRecordRadiusDiff * kWaveAngle * _levelValue.to(); const auto diffFactor = 0.35 * waveAmplitude * _waveDiff; - const auto radius = (_lastRadius + _amplitudeRadius * _amplitude) + const auto radius = (_lastRadius + _amplitudeRadius * amplitude) + _idleGlobalRadius + (_flingRadius * waveAmplitude); @@ -527,30 +554,28 @@ RecordCircle::RecordCircle(rpl::producer animationTicked) kFlingOutAnimationDurationMinor, kAnimationSpeedMinor, kAmplitudeDiffFactorMinor, - false)) { + false)) +, _levelValue(kMinDivider + + kAmplitudeDiffFactorMax * kAnimationSpeedCircle) { } void RecordCircle::setAmplitude(float64 value) { - _animateToAmplitude = std::min(kMaxAmplitude, value) / kMaxAmplitude; - _majorWave->setValue(_animateToAmplitude); - _minorWave->setValue(_animateToAmplitude); - _animateAmplitudeDiff = (_animateToAmplitude - _amplitude) - / (kMinDivider + kAmplitudeDiffFactorMax * kAnimationSpeedCircle); + const auto to = std::min(kMaxAmplitude, value) / kMaxAmplitude; + _levelValue.start(to); + _majorWave->setValue(to); + _minorWave->setValue(to); } void RecordCircle::paint(Painter &p, QColor c) { - const auto dt = (crl::now() - _lastUpdateTime); - if (_animateToAmplitude != _amplitude) { - _amplitude += _animateAmplitudeDiff * dt; - ApplyTo(_amplitude, _animateToAmplitude, _animateAmplitudeDiff); - } + const auto dt = crl::now() - _lastUpdateTime; + _levelValue.update(dt); const auto radius = (st::historyRecordLevelMainRadius - + st::historyRecordLevelMainRadiusAmplitude * _amplitude); + + st::historyRecordLevelMainRadiusAmplitude * _levelValue.current()); - _majorWave->tick(radius, _lastUpdateTime); - _minorWave->tick(radius, _lastUpdateTime); + _majorWave->tick(radius, dt); + _minorWave->tick(radius, dt); _lastUpdateTime = crl::now(); const auto opacity = p.opacity(); From dd462eb8cfe35fd4783272d39c8e0a2b28f8c081 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 2 Nov 2020 00:49:17 +0300 Subject: [PATCH 084/370] Slightly improved animation of bezier circle in VoiceRecordButton. --- .../history_view_voice_record_button.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index e554ce534..8a7a84719 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -24,7 +24,7 @@ constexpr auto kEnterIdleAnimationDuration = crl::time(1200); constexpr auto kRotationSpeed = 0.36 * 0.1; -constexpr auto kRandomAdditionFactor = 0.3; +constexpr auto kRandomAdditionFactor = 0.15; constexpr auto kIdleRadiusGlobalFactor = 0.56; constexpr auto kIdleRadiusFactor = 0.15 * 0.5; @@ -37,13 +37,13 @@ constexpr auto kIdleRotateDiff = 0.1 * kIdleRotationSpeed; constexpr auto kWaveAngle = 0.03; -constexpr auto kAnimationSpeedMajor = 1.5;// - 0.65; -constexpr auto kAnimationSpeedMinor = 1.5;// - 0.45; -constexpr auto kAnimationSpeedCircle = 1.5;// - 0.25; +constexpr auto kAnimationSpeedMajor = 1.5 - 0.65; +constexpr auto kAnimationSpeedMinor = 1.5 - 0.45; +constexpr auto kAnimationSpeedCircle = 1.5 - 0.25; -constexpr auto kAmplitudeDiffFactorMax = 500.; -constexpr auto kAmplitudeDiffFactorMajor = 300.; -constexpr auto kAmplitudeDiffFactorMinor = 400.; +constexpr auto kAmplitudeDiffFactorMax = 500. - 100.; +constexpr auto kAmplitudeDiffFactorMajor = 300. - 100.; +constexpr auto kAmplitudeDiffFactorMinor = 400. - 100.; constexpr auto kFlingDistanceFactorMajor = 8 * 16; constexpr auto kFlingDistanceFactorMinor = 20 * 16; @@ -53,8 +53,8 @@ constexpr auto kFlingInAnimationDurationMinor = 350; constexpr auto kFlingOutAnimationDurationMajor = 220; constexpr auto kFlingOutAnimationDurationMinor = 380; -constexpr auto kSineWaveSpeedMajor = 0.02 * 0.2 * 0.5; -constexpr auto kSineWaveSpeedMinor = 0.026 * 0.2 * 0.5; +constexpr auto kSineWaveSpeedMajor = 0.02 * 0.2; +constexpr auto kSineWaveSpeedMinor = 0.026 * 0.2; constexpr auto kSmallWaveRadius = 0.55; From c4897cec0a18d30771fd7aed5f78ce7c830bd9d9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 2 Nov 2020 19:32:38 +0300 Subject: [PATCH 085/370] Replaced dummy lock icons with lock animation. --- .../history_view_voice_record_bar.cpp | 69 ++++++++++++++++--- Telegram/SourceFiles/ui/chat/chat.style | 11 ++- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 1a85e370b..d5cad9a26 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -37,6 +37,8 @@ constexpr auto kMaxSamples = constexpr auto kPrecision = 10; +constexpr auto kLockArcAngle = 15.; + enum class FilterType { Continue, ShowBox, @@ -76,13 +78,22 @@ private: void setProgress(float64 progress); void startLockingAnimation(float64 to); + const QPen _arcPen; + Ui::Animations::Simple _lockAnimation; Ui::Animations::Simple _lockEnderAnimation; rpl::variable _progress = 0.; }; -RecordLock::RecordLock(not_null parent) : RpWidget(parent) { +RecordLock::RecordLock(not_null parent) +: RpWidget(parent) +, _arcPen( + st::historyRecordLockIconFg, + st::historyRecordLockIconLineWidth, + Qt::SolidLine, + Qt::SquareCap, + Qt::RoundJoin) { resize( st::historyRecordLockTopShadow.width(), st::historyRecordLockSize.height()); @@ -189,14 +200,54 @@ void RecordLock::drawProgress(Painter &p) { p.setOpacity(1.); } { - const auto &icon = isLocked() - ? st::historyRecordLockIcon - : st::historyRecordUnlockIcon; - icon.paint( - p, - inner.x() + (inner.width() - icon.width()) / 2, - inner.y() + (originTop.height() * 2 - icon.height()) / 2, - inner.width()); + PainterHighQualityEnabler hq(p); + const auto &size = st::historyRecordLockIconSize; + const auto &blockHeight = st::historyRecordLockIconBottomHeight; + const auto blockRect = QRect( + 0, + size.height() - blockHeight, + size.width(), + blockHeight); + const auto &lineHeight = st::historyRecordLockIconLineHeight; + const auto &offset = st::historyRecordLockIconLineSkip; + + p.setPen(Qt::NoPen); + p.setBrush(st::historyRecordLockIconFg); + p.translate( + inner.x() + (inner.width() - size.width()) / 2, + inner.y() + (originTop.height() * 2 - size.height()) / 2); + p.drawRoundedRect(blockRect, 2, 3); + + p.translate( + size.width() - offset, + blockRect.y()); + + if (progress < 1. && progress > 0.) { + p.rotate(kLockArcAngle * progress); + } + + p.setPen(_arcPen); + const auto rLine = QLineF(0, 0, 0, -lineHeight); + p.drawLine(rLine); + + const auto arcWidth = size.width() - offset * 2; + const auto &arcHeight = st::historyRecordLockIconArcHeight; + p.drawArc( + -arcWidth, + rLine.dy() - arcHeight - _arcPen.width() + rLine.y1(), + arcWidth, + arcHeight * 2, + 0, + 180 * 16); + + const auto lockProgress = 1. - _lockAnimation.value(1.); + if (progress == 1. && lockProgress < 1.) { + p.drawLine( + -arcWidth, + rLine.y2(), + -arcWidth, + rLine.dy() * lockProgress); + } } } diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index ebaca8649..f783f6fd1 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -363,6 +363,14 @@ historyRecordTextRight: 25px; historyRecordLockShowDuration: historyToDownDuration; historyRecordLockSize: size(75px, 150px); +historyRecordLockIconFg: historyToDownFg; +historyRecordLockIconSize: size(14px, 18px); +historyRecordLockIconBottomHeight: 9px; +historyRecordLockIconLineHeight: 2px; +historyRecordLockIconLineSkip: 3px; +historyRecordLockIconLineWidth: 2px; +historyRecordLockIconArcHeight: 4px; + historyRecordLockTopShadow: icon {{ "lock/record_lock_top_shadow", historyToDownShadow }}; historyRecordLockTop: icon {{ "lock/record_lock_top", historyToDownBg }}; historyRecordLockBottomShadow: icon {{ "lock/record_lock_bottom_shadow", historyToDownShadow }}; @@ -374,9 +382,6 @@ historyRecordLockArrow: icon {{ "history_down_arrow-flip_vertical", historyToDow historyRecordLockPosition: historyToDownPosition; -historyRecordLockIcon: icon {{ "dialogs_unlock", historyToDownFg, point(1px, 0px) }}; -historyRecordUnlockIcon: icon {{ "dialogs_lock", historyToDownFg, point(0px, 0px) }}; - historySilentToggle: IconButton(historyBotKeyboardShow) { icon: icon {{ "send_control_silent_off", historyComposeIconFg }}; iconOver: icon {{ "send_control_silent_off", historyComposeIconFgOver }}; From 203539256439f56d11a6121e1bdff46bc3922141 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 3 Nov 2020 18:57:40 +0300 Subject: [PATCH 086/370] Removed bezier circle paint when animations are disabled. --- .../history_view_voice_record_button.cpp | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 8a7a84719..148496977 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -571,19 +571,22 @@ void RecordCircle::paint(Painter &p, QColor c) { const auto dt = crl::now() - _lastUpdateTime; _levelValue.update(dt); + const auto &mainRadius = st::historyRecordLevelMainRadiusAmplitude; const auto radius = (st::historyRecordLevelMainRadius - + st::historyRecordLevelMainRadiusAmplitude * _levelValue.current()); + + (anim::Disabled() ? 0 : mainRadius * _levelValue.current())); - _majorWave->tick(radius, dt); - _minorWave->tick(radius, dt); - _lastUpdateTime = crl::now(); + if (!anim::Disabled()) { + _majorWave->tick(radius, dt); + _minorWave->tick(radius, dt); + _lastUpdateTime = crl::now(); - const auto opacity = p.opacity(); - p.setOpacity(kOpacityMajor); - _majorWave->paint(p, c); - p.setOpacity(kOpacityMinor); - _minorWave->paint(p, c); - p.setOpacity(opacity); + const auto opacity = p.opacity(); + p.setOpacity(kOpacityMajor); + _majorWave->paint(p, c); + p.setOpacity(kOpacityMinor); + _minorWave->paint(p, c); + p.setOpacity(opacity); + } p.setPen(Qt::NoPen); p.setBrush(c); @@ -598,7 +601,9 @@ VoiceRecordButton::VoiceRecordButton( _recordAnimationTicked.events())) , _center(st::historyRecordLevelMaxRadius) , _recordingAnimation([=](crl::time now) { - update(); + if (!anim::Disabled()) { + update(); + } _recordAnimationTicked.fire_copy(now); return true; }) { From 1d120092cff03b40b1396a30b25793acf83386ad Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 3 Nov 2020 19:29:30 +0300 Subject: [PATCH 087/370] Fixed previous values resetting in VoiceRecordButton. --- .../history_view_voice_record_button.cpp | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 148496977..e94604f94 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -126,6 +126,9 @@ public: void start(float64 to) { start(to, _duration); } + void reset() { + _to = _cur = _delta = 0.; + } float64 current() const { return _cur; @@ -201,6 +204,7 @@ public: void setValue(float64 to); void tick(float64 circleRadius, crl::time dt); + void reset(); void paint(Painter &p, QColor c); @@ -250,6 +254,7 @@ class RecordCircle final { public: RecordCircle(rpl::producer animationTicked); + void reset(); void setAmplitude(float64 value); void paint(Painter &p, QColor c); @@ -360,6 +365,21 @@ Wave::Wave( initFlingAnimation(std::move(animationTicked)); } +void Wave::reset() { + _incRandomAdditionals = false; + _isIdle = true; + _wasFling = false; + _flingRadius = 0.; + _idleRadius = 0.; + _idleRotation = 0.; + _lastRadius = 0.; + _rotation = 0.; + _sineAngleMax = 0.; + _waveAngle = 0.; + _waveDiff = 0.; + _levelValue.reset(); +} + void Wave::setValue(float64 to) { const auto duration = (to <= _levelValue.current()) ? _amplitudeOutAnimationDuration @@ -559,6 +579,12 @@ RecordCircle::RecordCircle(rpl::producer animationTicked) + kAmplitudeDiffFactorMax * kAnimationSpeedCircle) { } +void RecordCircle::reset() { + _majorWave->reset(); + _minorWave->reset(); + _levelValue.reset(); +} + void RecordCircle::setAmplitude(float64 value) { const auto to = std::min(kMaxAmplitude, value) / kMaxAmplitude; _levelValue.start(to); @@ -567,7 +593,6 @@ void RecordCircle::setAmplitude(float64 value) { } void RecordCircle::paint(Painter &p, QColor c) { - const auto dt = crl::now() - _lastUpdateTime; _levelValue.update(dt); @@ -655,13 +680,15 @@ void VoiceRecordButton::init() { rpl::merge( shownValue(), - _showProgress.value() | rpl::map(hasProgress) + _showProgress.value( + ) | rpl::map(hasProgress) | rpl::distinct_until_changed() ) | rpl::start_with_next([=](bool show) { setVisible(show); setMouseTracking(show); if (!show) { _recordingAnimation.stop(); _showProgress = 0.; + _recordCircle->reset(); } else { if (!_recordingAnimation.animating()) { _recordingAnimation.start(); From 5d2ffae215f2bd29ff11e2250c072004a336cce8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 9 Nov 2020 17:35:51 +0300 Subject: [PATCH 088/370] Improved VoiceRecordButton colors. --- .../history/view/controls/history_view_voice_record_bar.cpp | 2 +- .../view/controls/history_view_voice_record_button.cpp | 2 +- Telegram/SourceFiles/ui/chat/chat.style | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index d5cad9a26..29a765397 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -691,7 +691,7 @@ void VoiceRecordBar::startRedCircleAnimation() { void VoiceRecordBar::drawRedCircle(Painter &p) { PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); - p.setBrush(st::historyRecordSignalColor); + p.setBrush(st::historyRecordVoiceFgInactive); p.setOpacity(1. - _redCircleProgress); const int radii = st::historyRecordSignalRadius * showAnimationRatio(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index e94604f94..64ef1e235 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -665,7 +665,7 @@ void VoiceRecordButton::init() { } PainterHighQualityEnabler hq(p); const auto color = anim::color( - st::historyRecordSignalColor, + st::historyRecordVoiceFgInactive, st::historyRecordVoiceFgActive, _colorProgress.current()); _recordCircle->paint(p, color); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index f783f6fd1..7fead3ef5 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -327,14 +327,15 @@ historyScheduledToggle: IconButton(historyAttach) { } historyRecordVoiceFg: historyComposeIconFg; historyRecordVoiceFgOver: historyComposeIconFgOver; +historyRecordVoiceFgInactive: attentionButtonFg; historyRecordVoiceFgActive: windowBgActive; +historyRecordVoiceFgActiveIcon: windowFgActive; historyRecordVoiceShowDuration: 120; historyRecordVoiceDuration: 120; historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; -historyRecordVoiceActive: icon {{ "send_control_record_active", recordActiveIcon }}; +historyRecordVoiceActive: icon {{ "send_control_record_active", historyRecordVoiceFgActiveIcon }}; historyRecordVoiceRippleBgActive: lightButtonBgOver; -historyRecordSignalColor: attentionButtonFg; historyRecordSignalRadius: 5px; historyRecordCancel: windowSubTextFg; historyRecordCancelActive: windowActiveTextFg; From ac02e2be9e8b201a048b7f5c9421949e9a50319d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Nov 2020 17:10:08 +0300 Subject: [PATCH 089/370] Add FieldAutocomplete to ComposeControls. --- .../chat_helpers/field_autocomplete.cpp | 192 +++++++++++----- .../chat_helpers/field_autocomplete.h | 173 ++++----------- .../SourceFiles/history/history_widget.cpp | 4 +- .../history_view_compose_controls.cpp | 209 +++++++++++++++++- .../controls/history_view_compose_controls.h | 12 + .../view/history_view_replies_section.cpp | 32 ++- .../view/history_view_replies_section.h | 1 + .../view/history_view_scheduled_section.cpp | 18 +- .../support/support_autocomplete.h | 2 +- 9 files changed, 444 insertions(+), 199 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 41bce617a..3c6100de8 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_stickers.h" #include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu #include "chat_helpers/stickers_lottie.h" +#include "chat_helpers/message_field.h" // PrepareMentionTag. #include "mainwindow.h" #include "apiwrap.h" #include "main/main_session.h" @@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/scroll_area.h" +#include "ui/widgets/input_fields.h" #include "ui/image/image.h" #include "ui/ui_utility.h" #include "ui/cached_round_corners.h" @@ -39,15 +41,105 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +class FieldAutocomplete::Inner final + : public Ui::RpWidget + , private base::Subscriber { + +public: + struct ScrollTo { + int top; + int bottom; + }; + + Inner( + not_null controller, + not_null parent, + not_null mrows, + not_null hrows, + not_null brows, + not_null srows); + + void clearSel(bool hidden = false); + bool moveSel(int key); + bool chooseSelected(FieldAutocomplete::ChooseMethod method) const; + bool chooseAtIndex( + FieldAutocomplete::ChooseMethod method, + int index, + Api::SendOptions options = Api::SendOptions()) const; + + void setRecentInlineBotsInRows(int32 bots); + void rowsUpdated(); + + rpl::producer mentionChosen() const; + rpl::producer hashtagChosen() const; + rpl::producer + botCommandChosen() const; + rpl::producer stickerChosen() const; + rpl::producer scrollToRequested() const; + + void onParentGeometryChanged(); + +private: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + + void enterEventHook(QEvent *e) override; + void leaveEventHook(QEvent *e) override; + + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + + void updateSelectedRow(); + void setSel(int sel, bool scroll = false); + void showPreview(); + void selectByMouse(QPoint global); + + QSize stickerBoundingBox() const; + void setupLottie(StickerSuggestion &suggestion); + void repaintSticker(not_null document); + std::shared_ptr getLottieRenderer(); + + const not_null _controller; + const not_null _parent; + const not_null _mrows; + const not_null _hrows; + const not_null _brows; + const not_null _srows; + rpl::lifetime _stickersLifetime; + std::weak_ptr _lottieRenderer; + base::unique_qptr _menu; + int _stickersPerRow = 1; + int _recentInlineBotsInRows = 0; + int _sel = -1; + int _down = -1; + std::optional _lastMousePosition; + bool _mouseSelection = false; + + bool _overDelete = false; + + bool _previewShown = false; + + rpl::event_stream _mentionChosen; + rpl::event_stream _hashtagChosen; + rpl::event_stream _botCommandChosen; + rpl::event_stream _stickerChosen; + rpl::event_stream _scrollToRequested; + + base::Timer _previewTimer; + +}; + FieldAutocomplete::FieldAutocomplete( QWidget *parent, not_null controller) : RpWidget(parent) , _controller(controller) , _scroll(this, st::mentionScroll) { - _scroll->setGeometry(rect()); + hide(); - using Inner = internal::FieldAutocompleteInner; + _scroll->setGeometry(rect()); _inner = _scroll->setOwnedWidget( object_ptr( @@ -76,6 +168,10 @@ FieldAutocomplete::FieldAutocomplete( &Inner::onParentGeometryChanged); } +not_null FieldAutocomplete::controller() const { + return _controller; +} + auto FieldAutocomplete::mentionChosen() const -> rpl::producer { return _inner->mentionChosen(); @@ -125,9 +221,9 @@ void FieldAutocomplete::showFiltered( if (query.isEmpty()) { _type = Type::Mentions; rowsUpdated( - internal::MentionRows(), - internal::HashtagRows(), - internal::BotCommandRows(), + MentionRows(), + HashtagRows(), + BotCommandRows(), base::take(_srows), false); return; @@ -171,7 +267,7 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) { base::take(_mrows), base::take(_hrows), base::take(_brows), - internal::StickerRows(), + StickerRows(), false); return; } @@ -203,7 +299,7 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) { } } -internal::StickerRows FieldAutocomplete::getStickerSuggestions() { +FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() { const auto list = _controller->session().data().stickers().getListByEmoji( _emoji, _stickersSeed @@ -211,7 +307,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() { auto result = ranges::view::all( list ) | ranges::view::transform([](not_null sticker) { - return internal::StickerSuggestion{ + return StickerSuggestion{ sticker, sticker->createMediaView() }; @@ -223,7 +319,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() { const auto i = ranges::find( result, suggestion.document, - &internal::StickerSuggestion::document); + &StickerSuggestion::document); if (i != end(result)) { i->animated = std::move(suggestion.animated); } @@ -233,10 +329,10 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() { void FieldAutocomplete::updateFiltered(bool resetScroll) { int32 now = base::unixtime::now(), recentInlineBots = 0; - internal::MentionRows mrows; - internal::HashtagRows hrows; - internal::BotCommandRows brows; - internal::StickerRows srows; + MentionRows mrows; + HashtagRows hrows; + BotCommandRows brows; + StickerRows srows; if (_emoji) { srows = getStickerSuggestions(); } else if (_type == Type::Mentions) { @@ -435,10 +531,10 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { } void FieldAutocomplete::rowsUpdated( - internal::MentionRows &&mrows, - internal::HashtagRows &&hrows, - internal::BotCommandRows &&brows, - internal::StickerRows &&srows, + MentionRows &&mrows, + HashtagRows &&hrows, + BotCommandRows &&brows, + StickerRows &&srows, bool resetScroll) { if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) { if (!isHidden()) { @@ -620,9 +716,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) { return QWidget::eventFilter(obj, e); } -namespace internal { - -FieldAutocompleteInner::FieldAutocompleteInner( +FieldAutocomplete::Inner::Inner( not_null controller, not_null parent, not_null mrows, @@ -642,7 +736,7 @@ FieldAutocompleteInner::FieldAutocompleteInner( }, lifetime()); } -void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { +void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(e->rect()); @@ -841,11 +935,11 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) { p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg); } -void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) { +void FieldAutocomplete::Inner::resizeEvent(QResizeEvent *e) { _stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width())); } -void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) { +void FieldAutocomplete::Inner::mouseMoveEvent(QMouseEvent *e) { const auto globalPosition = e->globalPos(); if (!_lastMousePosition) { _lastMousePosition = globalPosition; @@ -857,7 +951,7 @@ void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) { selectByMouse(globalPosition); } -void FieldAutocompleteInner::clearSel(bool hidden) { +void FieldAutocomplete::Inner::clearSel(bool hidden) { _overDelete = false; _mouseSelection = false; _lastMousePosition = std::nullopt; @@ -868,7 +962,7 @@ void FieldAutocompleteInner::clearSel(bool hidden) { } } -bool FieldAutocompleteInner::moveSel(int key) { +bool FieldAutocomplete::Inner::moveSel(int key) { _mouseSelection = false; _lastMousePosition = std::nullopt; @@ -903,12 +997,12 @@ bool FieldAutocompleteInner::moveSel(int key) { return true; } -bool FieldAutocompleteInner::chooseSelected( +bool FieldAutocomplete::Inner::chooseSelected( FieldAutocomplete::ChooseMethod method) const { return chooseAtIndex(method, _sel); } -bool FieldAutocompleteInner::chooseAtIndex( +bool FieldAutocomplete::Inner::chooseAtIndex( FieldAutocomplete::ChooseMethod method, int index, Api::SendOptions options) const { @@ -955,11 +1049,11 @@ bool FieldAutocompleteInner::chooseAtIndex( return false; } -void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) { +void FieldAutocomplete::Inner::setRecentInlineBotsInRows(int32 bots) { _recentInlineBotsInRows = bots; } -void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) { +void FieldAutocomplete::Inner::mousePressEvent(QMouseEvent *e) { selectByMouse(e->globalPos()); if (e->button() == Qt::LeftButton) { if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) { @@ -999,7 +1093,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) { } } -void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) { +void FieldAutocomplete::Inner::mouseReleaseEvent(QMouseEvent *e) { _previewTimer.cancel(); int32 pressed = _down; @@ -1017,7 +1111,7 @@ void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) { chooseSelected(FieldAutocomplete::ChooseMethod::ByClick); } -void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) { +void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) { if (_sel < 0 || _srows->empty() || _down >= 0) { return; } @@ -1040,11 +1134,11 @@ void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) { } } -void FieldAutocompleteInner::enterEventHook(QEvent *e) { +void FieldAutocomplete::Inner::enterEventHook(QEvent *e) { setMouseTracking(true); } -void FieldAutocompleteInner::leaveEventHook(QEvent *e) { +void FieldAutocomplete::Inner::leaveEventHook(QEvent *e) { setMouseTracking(false); if (_mouseSelection) { setSel(-1); @@ -1053,7 +1147,7 @@ void FieldAutocompleteInner::leaveEventHook(QEvent *e) { } } -void FieldAutocompleteInner::updateSelectedRow() { +void FieldAutocomplete::Inner::updateSelectedRow() { if (_sel >= 0) { if (_srows->empty()) { update(0, _sel * st::mentionHeight, width(), st::mentionHeight); @@ -1064,7 +1158,7 @@ void FieldAutocompleteInner::updateSelectedRow() { } } -void FieldAutocompleteInner::setSel(int sel, bool scroll) { +void FieldAutocomplete::Inner::setSel(int sel, bool scroll) { updateSelectedRow(); _sel = sel; updateSelectedRow(); @@ -1084,13 +1178,13 @@ void FieldAutocompleteInner::setSel(int sel, bool scroll) { } } -void FieldAutocompleteInner::rowsUpdated() { +void FieldAutocomplete::Inner::rowsUpdated() { if (_srows->empty()) { _stickersLifetime.destroy(); } } -auto FieldAutocompleteInner::getLottieRenderer() +auto FieldAutocomplete::Inner::getLottieRenderer() -> std::shared_ptr { if (auto result = _lottieRenderer.lock()) { return result; @@ -1100,7 +1194,7 @@ auto FieldAutocompleteInner::getLottieRenderer() return result; } -void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) { +void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) { const auto document = suggestion.document; suggestion.animated = ChatHelpers::LottiePlayerFromDocument( suggestion.documentMedia.get(), @@ -1115,13 +1209,13 @@ void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) { }, _stickersLifetime); } -QSize FieldAutocompleteInner::stickerBoundingBox() const { +QSize FieldAutocomplete::Inner::stickerBoundingBox() const { return QSize( st::stickerPanSize.width() - st::buttonRadius * 2, st::stickerPanSize.height() - st::buttonRadius * 2); } -void FieldAutocompleteInner::repaintSticker( +void FieldAutocomplete::Inner::repaintSticker( not_null document) { const auto i = ranges::find( *_srows, @@ -1140,7 +1234,7 @@ void FieldAutocompleteInner::repaintSticker( st::stickerPanSize.height()); } -void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) { +void FieldAutocomplete::Inner::selectByMouse(QPoint globalPosition) { _mouseSelection = true; _lastMousePosition = globalPosition; const auto mouse = mapFromGlobal(globalPosition); @@ -1186,7 +1280,7 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) { } } -void FieldAutocompleteInner::onParentGeometryChanged() { +void FieldAutocomplete::Inner::onParentGeometryChanged() { const auto globalPosition = QCursor::pos(); if (rect().contains(mapFromGlobal(globalPosition))) { setMouseTracking(true); @@ -1196,7 +1290,7 @@ void FieldAutocompleteInner::onParentGeometryChanged() { } } -void FieldAutocompleteInner::showPreview() { +void FieldAutocomplete::Inner::showPreview() { if (_down >= 0 && _down < _srows->size()) { if (const auto w = App::wnd()) { w->showMediaPreview( @@ -1207,29 +1301,27 @@ void FieldAutocompleteInner::showPreview() { } } -auto FieldAutocompleteInner::mentionChosen() const +auto FieldAutocomplete::Inner::mentionChosen() const -> rpl::producer { return _mentionChosen.events(); } -auto FieldAutocompleteInner::hashtagChosen() const +auto FieldAutocomplete::Inner::hashtagChosen() const -> rpl::producer { return _hashtagChosen.events(); } -auto FieldAutocompleteInner::botCommandChosen() const +auto FieldAutocomplete::Inner::botCommandChosen() const -> rpl::producer { return _botCommandChosen.events(); } -auto FieldAutocompleteInner::stickerChosen() const +auto FieldAutocomplete::Inner::stickerChosen() const -> rpl::producer { return _stickerChosen.events(); } -auto FieldAutocompleteInner::scrollToRequested() const +auto FieldAutocomplete::Inner::scrollToRequested() const -> rpl::producer { return _scrollToRequested.events(); } - -} // namespace internal diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h index e7d3f3205..8adbaa2c3 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { class PopupMenu; class ScrollArea; +class InputField; } // namespace Ui namespace Lottie { @@ -32,42 +33,15 @@ class DocumentMedia; class CloudImageView; } // namespace Data -namespace internal { - -struct StickerSuggestion { - not_null document; - std::shared_ptr documentMedia; - std::unique_ptr animated; -}; - -struct MentionRow { - not_null user; - std::shared_ptr userpic; -}; - -struct BotCommandRow { - not_null user; - not_null command; - std::shared_ptr userpic; -}; - -using HashtagRows = std::vector; -using BotCommandRows = std::vector; -using StickerRows = std::vector; -using MentionRows = std::vector; - -class FieldAutocompleteInner; - -} // namespace internal - class FieldAutocomplete final : public Ui::RpWidget { - public: FieldAutocomplete( QWidget *parent, not_null controller); ~FieldAutocomplete(); + [[nodiscard]] not_null controller() const; + bool clearFilteredBotCommands(); void showFiltered( not_null peer, @@ -140,29 +114,54 @@ protected: void paintEvent(QPaintEvent *e) override; private: + class Inner; + friend class Inner; + + struct StickerSuggestion { + not_null document; + std::shared_ptr documentMedia; + std::unique_ptr animated; + }; + + struct MentionRow { + not_null user; + std::shared_ptr userpic; + }; + + struct BotCommandRow { + not_null user; + not_null command; + std::shared_ptr userpic; + }; + + using HashtagRows = std::vector; + using BotCommandRows = std::vector; + using StickerRows = std::vector; + using MentionRows = std::vector; + void animationCallback(); void hideFinish(); void updateFiltered(bool resetScroll = false); void recount(bool resetScroll = false); - internal::StickerRows getStickerSuggestions(); + StickerRows getStickerSuggestions(); const not_null _controller; QPixmap _cache; - internal::MentionRows _mrows; - internal::HashtagRows _hrows; - internal::BotCommandRows _brows; - internal::StickerRows _srows; + MentionRows _mrows; + HashtagRows _hrows; + BotCommandRows _brows; + StickerRows _srows; void rowsUpdated( - internal::MentionRows &&mrows, - internal::HashtagRows &&hrows, - internal::BotCommandRows &&brows, - internal::StickerRows &&srows, + MentionRows &&mrows, + HashtagRows &&hrows, + BotCommandRows &&brows, + StickerRows &&srows, bool resetScroll); object_ptr _scroll; - QPointer _inner; + QPointer _inner; ChatData *_chat = nullptr; UserData *_user = nullptr; @@ -186,100 +185,4 @@ private: Fn _moderateKeyActivateCallback; - friend class internal::FieldAutocompleteInner; - }; - -namespace internal { - -class FieldAutocompleteInner final - : public Ui::RpWidget - , private base::Subscriber { - -public: - struct ScrollTo { - int top; - int bottom; - }; - - FieldAutocompleteInner( - not_null controller, - not_null parent, - not_null mrows, - not_null hrows, - not_null brows, - not_null srows); - - void clearSel(bool hidden = false); - bool moveSel(int key); - bool chooseSelected(FieldAutocomplete::ChooseMethod method) const; - bool chooseAtIndex( - FieldAutocomplete::ChooseMethod method, - int index, - Api::SendOptions options = Api::SendOptions()) const; - - void setRecentInlineBotsInRows(int32 bots); - void rowsUpdated(); - - rpl::producer mentionChosen() const; - rpl::producer hashtagChosen() const; - rpl::producer - botCommandChosen() const; - rpl::producer stickerChosen() const; - rpl::producer scrollToRequested() const; - - void onParentGeometryChanged(); - -private: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - - void enterEventHook(QEvent *e) override; - void leaveEventHook(QEvent *e) override; - - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void contextMenuEvent(QContextMenuEvent *e) override; - - void updateSelectedRow(); - void setSel(int sel, bool scroll = false); - void showPreview(); - void selectByMouse(QPoint global); - - QSize stickerBoundingBox() const; - void setupLottie(StickerSuggestion &suggestion); - void repaintSticker(not_null document); - std::shared_ptr getLottieRenderer(); - - const not_null _controller; - const not_null _parent; - const not_null _mrows; - const not_null _hrows; - const not_null _brows; - const not_null _srows; - rpl::lifetime _stickersLifetime; - std::weak_ptr _lottieRenderer; - base::unique_qptr _menu; - int _stickersPerRow = 1; - int _recentInlineBotsInRows = 0; - int _sel = -1; - int _down = -1; - std::optional _lastMousePosition; - bool _mouseSelection = false; - - bool _overDelete = false; - - bool _previewShown = false; - - rpl::event_stream _mentionChosen; - rpl::event_stream _hashtagChosen; - rpl::event_stream _botCommandChosen; - rpl::event_stream _stickerChosen; - rpl::event_stream _scrollToRequested; - - base::Timer _previewTimer; - -}; - -} // namespace internal diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 2599b63ca..07cd555fc 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -286,7 +286,6 @@ HistoryWidget::HistoryWidget( _unreadMentions->installEventFilter(this); InitMessageField(controller, _field); - _fieldAutocomplete->hide(); _fieldAutocomplete->mentionChosen( ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) { @@ -1216,6 +1215,9 @@ void HistoryWidget::orderWidgets() { _pinnedBar->raise(); } _topShadow->raise(); + if (_fieldAutocomplete) { + _fieldAutocomplete->raise(); + } if (_membersDropdown) { _membersDropdown->raise(); } 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 ba9062365..8f164e219 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -15,12 +15,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_section.h" #include "chat_helpers/tabbed_selector.h" +#include "chat_helpers/field_autocomplete.h" #include "core/application.h" #include "core/core_settings.h" #include "data/data_changes.h" #include "data/data_messages.h" #include "data/data_session.h" +#include "data/data_user.h" +#include "data/stickers/data_stickers.h" #include "data/data_web_page.h" +#include "storage/storage_account.h" #include "facades.h" #include "boxes/confirm_box.h" #include "history/history.h" @@ -515,6 +519,9 @@ ComposeControls::ComposeControls( st::historyComposeField, Ui::InputField::Mode::MultiLine, tr::lng_message_ph())) +, _autocomplete(std::make_unique( + parent, + window)) , _header(std::make_unique( _wrap.get(), &_window->session().data())) @@ -565,6 +572,12 @@ void ComposeControls::resizeToWidth(int width) { updateHeight(); } +void ComposeControls::setAutocompleteBoundingRect(QRect rect) { + if (_autocomplete) { + _autocomplete->setBoundings(rect); + } +} + rpl::producer ComposeControls::height() const { using namespace rpl::mappers; return rpl::conditional( @@ -619,6 +632,10 @@ rpl::producer ComposeControls::sendVoiceRequests() const { return _voiceRecordBar->sendVoiceRequests(); } +rpl::producer ComposeControls::sendCommandRequests() const { + return _sendCommandRequests.events(); +} + rpl::producer ComposeControls::editRequests() const { auto toValue = rpl::map([=] { return _header->queryToEdit(); }); auto filter = rpl::filter([=] { @@ -681,6 +698,21 @@ void ComposeControls::showFinished() { _voiceRecordBar->orderControls(); } +void ComposeControls::raisePanels() { + if (_autocomplete) { + _autocomplete->raise(); + } + if (_inlineResults) { + _inlineResults->raise(); + } + if (_tabbedPanel) { + _tabbedPanel->raise(); + } + if (_raiseEmojiSuggestions) { + _raiseEmojiSuggestions(); + } +} + void ComposeControls::showForGrab() { showFinished(); } @@ -708,7 +740,9 @@ void ComposeControls::setText(const TextWithTags &textWithTags) { } void ComposeControls::hidePanelsAnimated() { - //_fieldAutocomplete->hideAnimated(); + if (_autocomplete) { + _autocomplete->hideAnimated(); + } if (_tabbedPanel) { _tabbedPanel->hideAnimated(); } @@ -717,6 +751,36 @@ void ComposeControls::hidePanelsAnimated() { } } +void ComposeControls::checkAutocomplete() { + if (!_history) { + return; + } + + const auto peer = _history->peer; + const auto isInlineBot = false;// _inlineBot && !_inlineLookingUpBot; + const auto autocomplete = isInlineBot + ? AutocompleteQuery() + : ParseMentionHashtagBotCommandQuery(_field); + if (!autocomplete.query.isEmpty()) { + if (autocomplete.query[0] == '#' + && cRecentWriteHashtags().isEmpty() + && cRecentSearchHashtags().isEmpty()) { + peer->session().local().readRecentHashtagsAndBots(); + } else if (autocomplete.query[0] == '@' + && cRecentInlineBots().isEmpty()) { + peer->session().local().readRecentHashtagsAndBots(); + } else if (autocomplete.query[0] == '/' + && peer->isUser() + && !peer->asUser()->isBot()) { + return; + } + } + _autocomplete->showFiltered( + peer, + autocomplete.query, + autocomplete.fromStart); +} + void ComposeControls::init() { initField(); initTabbedSelector(); @@ -817,11 +881,12 @@ void ComposeControls::initField() { _field->setSubmitSettings(Core::App().settings().sendSubmitWay()); //Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); }); Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); }); - //Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); }); + Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); }); Ui::Connect(_field, &Ui::InputField::resized, [=] { updateHeight(); }); //Ui::Connect(_field, &Ui::InputField::focused, [=] { fieldFocused(); }); Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); }); InitMessageField(_window, _field); + initAutocomplete(); const auto suggestions = Ui::Emoji::SuggestionsController::Init( _parent, _field, @@ -830,6 +895,112 @@ void ComposeControls::initField() { InitSpellchecker(_window, _field); } +void ComposeControls::initAutocomplete() { + const auto insertHashtagOrBotCommand = [=]( + const QString &string, + FieldAutocomplete::ChooseMethod method) { + // Send bot command at once, if it was not inserted by pressing Tab. + if (string.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) { + _sendCommandRequests.fire_copy(string); + setText( + _field->getTextWithTagsPart(_field->textCursor().position())); + } else { + _field->insertTag(string); + } + }; + const auto insertMention = [=](not_null user) { + auto replacement = QString(); + auto entityTag = QString(); + if (user->username.isEmpty()) { + _field->insertTag( + user->firstName.isEmpty() ? user->name : user->firstName, + PrepareMentionTag(user)); + } else { + _field->insertTag('@' + user->username); + } + }; + + _autocomplete->mentionChosen( + ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) { + insertMention(data.user); + }, _autocomplete->lifetime()); + + _autocomplete->hashtagChosen( + ) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) { + insertHashtagOrBotCommand(data.hashtag, data.method); + }, _autocomplete->lifetime()); + + _autocomplete->botCommandChosen( + ) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) { + insertHashtagOrBotCommand(data.command, data.method); + }, _autocomplete->lifetime()); + + _autocomplete->stickerChosen( + ) | rpl::start_with_next([=](FieldAutocomplete::StickerChosen data) { + setText({}); + //_saveDraftText = true; + //_saveDraftStart = crl::now(); + //onDraftSave(); + //onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft + _fileChosen.fire(FileChosen{ + .document = data.sticker, + .options = data.options, + }); + }, _autocomplete->lifetime()); + + //_autocomplete->setModerateKeyActivateCallback([=](int key) { + // return _keyboard->isHidden() + // ? false + // : _keyboard->moderateKeyActivate(key); + //}); + + _field->rawTextEdit()->installEventFilter(_autocomplete.get()); + + _window->session().data().botCommandsChanges( + ) | rpl::filter([=](not_null user) { + const auto peer = _history ? _history->peer.get() : nullptr; + return peer && (peer == user || !peer->isUser()); + }) | rpl::start_with_next([=](not_null user) { + if (_autocomplete->clearFilteredBotCommands()) { + checkAutocomplete(); + } + }, _autocomplete->lifetime()); + + _window->session().data().stickers().updated( + ) | rpl::start_with_next([=] { + updateStickersByEmoji(); + }, _autocomplete->lifetime()); + + QObject::connect( + _field->rawTextEdit(), + &QTextEdit::cursorPositionChanged, + _autocomplete.get(), + [=] { checkAutocomplete(); }, + Qt::QueuedConnection); +} + +void ComposeControls::updateStickersByEmoji() { + if (!_history) { + return; + } + const auto emoji = [&] { + const auto errorForStickers = Data::RestrictionError( + _history->peer, + ChatRestriction::f_send_stickers); + if (!isEditingMessage() && !errorForStickers) { + const auto &text = _field->getTextWithTags().text; + auto length = 0; + if (const auto emoji = Ui::Emoji::Find(text, &length)) { + if (text.size() <= length) { + return emoji; + } + } + } + return EmojiPtr(nullptr); + }(); + _autocomplete->showStickers(emoji); +} + void ComposeControls::fieldChanged() { if (/*!_inlineBot && */!_header->isEditingMessage() @@ -840,6 +1011,15 @@ void ComposeControls::fieldChanged() { if (showRecordButton()) { //_previewCancelled = false; } + InvokeQueued(_autocomplete.get(), [=] { + updateStickersByEmoji(); + }); +} + +void ComposeControls::fieldTabbed() { + if (!_autocomplete->isHidden()) { + _autocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab); + } } rpl::producer ComposeControls::sendActionUpdates() const { @@ -1136,10 +1316,16 @@ void ComposeControls::updateHeight() { void ComposeControls::editMessage(FullMsgId id) { cancelEditMessage(); _header->editMessage(id); + if (_autocomplete) { + InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); }); + } } void ComposeControls::cancelEditMessage() { _header->editMessage({}); + if (_autocomplete) { + InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); }); + } } void ComposeControls::replyToMessage(FullMsgId id) { @@ -1151,6 +1337,20 @@ void ComposeControls::cancelReplyMessage() { _header->replyToMessage({}); } +bool ComposeControls::handleCancelRequest() { + if (isEditingMessage()) { + cancelEditMessage(); + return true; + } else if (_autocomplete && !_autocomplete->isHidden()) { + _autocomplete->hideAnimated(); + return true; + } else if (replyingToMessage()) { + cancelReplyMessage(); + return true; + } + return false; +} + void ComposeControls::initWebpageProcess() { Expects(_history); const auto peer = _history->peer; @@ -1287,7 +1487,10 @@ void ComposeControls::initWebpageProcess() { Data::PeerUpdate::Flag::Rights ) | rpl::filter([=](const Data::PeerUpdate &update) { return (update.peer.get() == peer); - }) | rpl::start_with_next(checkPreview, lifetime); + }) | rpl::start_with_next([=] { + checkPreview(); + updateStickersByEmoji(); + }, lifetime); _window->session().downloaderTaskFinished( ) | rpl::filter([=] { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 926e3da32..d7c6cc3ce 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_selector.h" class History; +class FieldAutocomplete; namespace ChatHelpers { class TabbedPanel; @@ -89,6 +90,7 @@ public: void move(int x, int y); void resizeToWidth(int width); + void setAutocompleteBoundingRect(QRect rect); [[nodiscard]] rpl::producer height() const; [[nodiscard]] int heightCurrent() const; @@ -96,6 +98,7 @@ public: [[nodiscard]] rpl::producer<> cancelRequests() const; [[nodiscard]] rpl::producer<> sendRequests() const; [[nodiscard]] rpl::producer sendVoiceRequests() const; + [[nodiscard]] rpl::producer sendCommandRequests() const; [[nodiscard]] rpl::producer editRequests() const; [[nodiscard]] rpl::producer<> attachRequests() const; [[nodiscard]] rpl::producer fileChosen() const; @@ -122,6 +125,7 @@ public: void showForGrab(); void showStarted(); void showFinished(); + void raisePanels(); void editMessage(FullMsgId id); void cancelEditMessage(); @@ -129,6 +133,8 @@ public: void replyToMessage(FullMsgId id); void cancelReplyMessage(); + bool handleCancelRequest(); + [[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const; [[nodiscard]] WebPageId webPageId() const; void setText(const TextWithTags &text); @@ -154,6 +160,7 @@ private: void initWebpageProcess(); void initWriteRestriction(); void initVoiceRecordBar(); + void initAutocomplete(); void updateSendButtonType(); void updateHeight(); void updateWrappingVisibility(); @@ -163,9 +170,12 @@ private: void paintBackground(QRect clip); void orderControls(); + void checkAutocomplete(); + void updateStickersByEmoji(); void escape(); void fieldChanged(); + void fieldTabbed(); void toggleTabbedSelectorMode(); void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); @@ -194,6 +204,7 @@ private: const not_null _field; std::unique_ptr _inlineResults; std::unique_ptr _tabbedPanel; + std::unique_ptr _autocomplete; friend class FieldHeader; const std::unique_ptr _header; @@ -204,6 +215,7 @@ private: rpl::event_stream _photoChosen; rpl::event_stream _inlineResultChosen; rpl::event_stream _sendActionUpdates; + rpl::event_stream _sendCommandRequests; TextWithTags _localSavedText; TextUpdateEvents _textUpdateEvents; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index c10451a42..9f931c52e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -253,6 +253,7 @@ RepliesWidget::RepliesWidget( setupScrollDownButton(); setupComposeControls(); + orderWidgets(); } RepliesWidget::~RepliesWidget() { @@ -263,6 +264,17 @@ RepliesWidget::~RepliesWidget() { _history->owner().repliesSendActionPainterRemoved(_history, _rootId); } +void RepliesWidget::orderWidgets() { + if (_topBar) { + _topBar->raise(); + } + if (_rootView) { + _rootView->raise(); + } + _topBarShadow->raise(); + _composeControls->raisePanels(); +} + void RepliesWidget::sendReadTillRequest() { if (!_root) { _readRequestPending = true; @@ -428,6 +440,18 @@ void RepliesWidget::setupComposeControls() { sendVoice(data.bytes, data.waveform, data.duration); }, lifetime()); + _composeControls->sendCommandRequests( + ) | rpl::start_with_next([=](const QString &command) { + if (showSlowmodeError()) { + return; + } + auto message = ApiWrap::MessageToSend(_history); + message.textWithTags = { command }; + message.action.replyTo = replyToId(); + session().api().sendMessage(std::move(message)); + finishSending(); + }, lifetime()); + const auto saveEditMsgRequestId = lifetime().make_state(0); _composeControls->editRequests( ) | rpl::start_with_next([=](auto data) { @@ -1474,6 +1498,7 @@ void RepliesWidget::updateControlsGeometry() { updateInnerVisibleArea(); } _composeControls->move(0, bottom - controlsHeight); + _composeControls->setAutocompleteBoundingRect(_scroll->geometry()); updateScrollDownPosition(); } @@ -1598,12 +1623,7 @@ void RepliesWidget::listCancelRequest() { if (_inner && !_inner->getSelectedItems().empty()) { clearSelected(); return; - } - if (_composeControls->isEditingMessage()) { - _composeControls->cancelEditMessage(); - return; - } else if (_composeControls->replyingToMessage()) { - _composeControls->cancelReplyMessage(); + } else if (_composeControls->handleCancelRequest()) { return; } controller()->showBackFromStack(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index e031369d5..a22b5d92f 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -186,6 +186,7 @@ private: [[nodiscard]] MsgId replyToId() const; [[nodiscard]] HistoryItem *lookupRoot() const; [[nodiscard]] bool computeAreComments() const; + void orderWidgets(); void pushReplyReturn(not_null item); void computeCurrentReplyReturn(); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 4465ac2ff..0a2e506b5 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -180,6 +180,19 @@ void ScheduledWidget::setupComposeControls() { sendVoice(data.bytes, data.waveform, data.duration); }, lifetime()); + _composeControls->sendCommandRequests( + ) | rpl::start_with_next([=](const QString &command) { + const auto callback = [=](Api::SendOptions options) { + auto message = ApiWrap::MessageToSend(_history); + message.textWithTags = { command }; + message.action.options = options; + session().api().sendMessage(std::move(message)); + }; + Ui::show( + PrepareScheduleBox(this, sendMenuType(), callback), + Ui::LayerOption::KeepOther); + }, lifetime()); + const auto saveEditMsgRequestId = lifetime().make_state(0); _composeControls->editRequests( ) | rpl::start_with_next([=](auto data) { @@ -979,6 +992,7 @@ void ScheduledWidget::updateControlsGeometry() { updateInnerVisibleArea(); } _composeControls->move(0, bottom - controlsHeight); + _composeControls->setAutocompleteBoundingRect(_scroll->geometry()); updateScrollDownPosition(); } @@ -1057,9 +1071,7 @@ void ScheduledWidget::listCancelRequest() { if (_inner && !_inner->getSelectedItems().empty()) { clearSelected(); return; - } - if (_composeControls->isEditingMessage()) { - _composeControls->cancelEditMessage(); + } else if (_composeControls->handleCancelRequest()) { return; } controller()->showBackFromStack(); diff --git a/Telegram/SourceFiles/support/support_autocomplete.h b/Telegram/SourceFiles/support/support_autocomplete.h index bcc961a98..d2abc4671 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.h +++ b/Telegram/SourceFiles/support/support_autocomplete.h @@ -35,7 +35,7 @@ struct Contact { QString lastName; }; -class Autocomplete : public Ui::RpWidget { +class Autocomplete final : public Ui::RpWidget { public: Autocomplete(QWidget *parent, not_null session); From cf6ca3b1ac945f957584387eea5b4c833b5233c5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Nov 2020 19:38:21 +0300 Subject: [PATCH 090/370] Handle bot command clicks in Replies / Scheduled. --- .../SourceFiles/core/click_handler_types.cpp | 16 ++---- .../SourceFiles/core/click_handler_types.h | 26 ++++------ .../SourceFiles/core/local_url_handlers.cpp | 13 ++--- Telegram/SourceFiles/core/ui_integration.cpp | 4 +- Telegram/SourceFiles/core/ui_integration.h | 17 ++++--- .../admin_log/history_admin_log_inner.cpp | 5 ++ .../admin_log/history_admin_log_inner.h | 3 ++ .../history/history_inner_widget.cpp | 35 ++++++++++++- .../history/history_inner_widget.h | 3 ++ .../SourceFiles/history/history_message.cpp | 2 +- .../SourceFiles/history/history_widget.cpp | 12 ++--- .../history/view/history_view_element.cpp | 5 ++ .../history/view/history_view_element.h | 6 +++ .../history/view/history_view_list_widget.cpp | 49 ++++++++++++++++++- .../history/view/history_view_list_widget.h | 15 ++++++ .../view/history_view_pinned_section.cpp | 5 ++ .../view/history_view_pinned_section.h | 3 ++ .../view/history_view_replies_section.cpp | 18 +++++-- .../view/history_view_replies_section.h | 3 ++ .../view/history_view_scheduled_section.cpp | 25 ++++++---- .../view/history_view_scheduled_section.h | 3 ++ .../history/view/media/history_view_game.cpp | 4 +- .../history/view/media/history_view_media.cpp | 2 +- .../view/media/history_view_web_page.cpp | 7 +-- .../media/view/media_view_overlay_widget.cpp | 2 +- 25 files changed, 210 insertions(+), 73 deletions(-) diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 793ea5ae9..38a16c4b7 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -180,20 +180,14 @@ auto CashtagClickHandler::getTextEntity() const -> TextEntity { return { EntityType::Cashtag }; } -PeerData *BotCommandClickHandler::_peer = nullptr; -UserData *BotCommandClickHandler::_bot = nullptr; void BotCommandClickHandler::onClick(ClickContext context) const { const auto button = context.button; if (button == Qt::LeftButton || button == Qt::MiddleButton) { - if (auto peer = peerForCommand()) { - if (auto bot = peer->isUser() ? peer->asUser() : botForCommand()) { - Ui::showPeerHistory(peer, ShowAtTheEndMsgId); - App::sendBotCommand(peer, bot, _cmd); - return; - } - } - - if (auto peer = Ui::getPeerForMouseAction()) { // old way + const auto my = context.other.value(); + if (const auto delegate = my.elementDelegate ? my.elementDelegate() : nullptr) { + delegate->elementSendBotCommand(_cmd, my.itemId); + return; + } else if (auto peer = Ui::getPeerForMouseAction()) { // old way auto bot = peer->isUser() ? peer->asUser() : nullptr; if (!bot) { if (const auto view = App::hoveredLinkItem()) { diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index bed3a6bec..152b45076 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -13,8 +13,18 @@ namespace Main { class Session; } // namespace Main +namespace HistoryView { +class ElementDelegate; +} // namespace HistoryView + [[nodiscard]] bool UrlRequiresConfirmation(const QUrl &url); +struct ClickHandlerContext { + FullMsgId itemId; + Fn elementDelegate; +}; +Q_DECLARE_METATYPE(ClickHandlerContext); + class HiddenUrlClickHandler : public UrlClickHandler { public: HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) { @@ -165,30 +175,14 @@ public: return _cmd; } - static void setPeerForCommand(PeerData *peer) { - _peer = peer; - } - static void setBotForCommand(UserData *bot) { - _bot = bot; - } - TextEntity getTextEntity() const override; protected: QString url() const override { return _cmd; } - static PeerData *peerForCommand() { - return _peer; - } - static UserData *botForCommand() { - return _bot; - } private: QString _cmd; - static PeerData *_peer; - static UserData *_bot; - }; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index cda4d9141..19af8f2ba 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "core/update_checker.h" #include "core/application.h" +#include "core/click_handler_types.h" #include "boxes/confirm_phone_box.h" #include "boxes/background_preview_box.h" #include "boxes/confirm_box.h" @@ -76,11 +77,11 @@ bool ShowTheme( if (!controller) { return false; } - const auto clickFromMessageId = context.value(); + const auto fromMessageId = context.value().itemId; Core::App().hideMediaView(); controller->session().data().cloudThemes().resolve( match->captured(1), - clickFromMessageId); + fromMessageId); return true; } @@ -280,7 +281,7 @@ bool ResolveUsername( startToken = gameParam; post = ShowAtGameShareMsgId; } - const auto clickFromMessageId = context.value(); + const auto fromMessageId = context.value().itemId; using Navigation = Window::SessionNavigation; controller->showPeerByLink(Navigation::PeerByLinkInfo{ .usernameOrId = domain, @@ -295,7 +296,7 @@ bool ResolveUsername( } : Navigation::RepliesByLinkInfo{ v::null }, .startToken = startToken, - .clickFromMessageId = clickFromMessageId, + .clickFromMessageId = fromMessageId, }); return true; } @@ -319,7 +320,7 @@ bool ResolvePrivatePost( if (!channelId || !IsServerMsgId(msgId)) { return false; } - const auto clickFromMessageId = context.value(); + const auto fromMessageId = context.value().itemId; using Navigation = Window::SessionNavigation; controller->showPeerByLink(Navigation::PeerByLinkInfo{ .usernameOrId = channelId, @@ -333,7 +334,7 @@ bool ResolvePrivatePost( Navigation::ThreadId{ threadId } } : Navigation::RepliesByLinkInfo{ v::null }, - .clickFromMessageId = clickFromMessageId, + .clickFromMessageId = fromMessageId, }); return true; } diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index e707d2589..fec3bdd66 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -65,7 +65,7 @@ QString UiIntegration::timeFormat() { std::shared_ptr UiIntegration::createLinkHandler( const EntityLinkData &data, const std::any &context) { - const auto my = std::any_cast(&context); + const auto my = std::any_cast(&context); switch (data.type) { case EntityType::Url: return (!data.data.isEmpty() @@ -82,6 +82,7 @@ std::shared_ptr UiIntegration::createLinkHandler( return std::make_shared(data.data); case EntityType::Hashtag: + using HashtagMentionType = MarkedTextContext::HashtagMentionType; if (my && my->type == HashtagMentionType::Twitter) { return std::make_shared( (qsl("https://twitter.com/hashtag/") @@ -101,6 +102,7 @@ std::shared_ptr UiIntegration::createLinkHandler( return std::make_shared(data.data); case EntityType::Mention: + using HashtagMentionType = MarkedTextContext::HashtagMentionType; if (my && my->type == HashtagMentionType::Twitter) { return std::make_shared( qsl("https://twitter.com/") + data.data.mid(1), diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h index c7367b802..2aac9a0aa 100644 --- a/Telegram/SourceFiles/core/ui_integration.h +++ b/Telegram/SourceFiles/core/ui_integration.h @@ -13,20 +13,25 @@ namespace Main { class Session; } // namespace Main +namespace HistoryView { +class ElementDelegate; +} // namespace HistoryView + namespace Core { -class UiIntegration : public Ui::Integration { -public: +struct MarkedTextContext { enum class HashtagMentionType : uchar { Telegram, Twitter, Instagram, }; - struct Context { - Main::Session *session = nullptr; - HashtagMentionType type = HashtagMentionType::Telegram; - }; + Main::Session *session = nullptr; + HashtagMentionType type = HashtagMentionType::Telegram; +}; + +class UiIntegration : public Ui::Integration { +public: void postponeCall(FnMut &&callable) override; void registerLeaveSubscription(not_null widget) override; void unregisterLeaveSubscription(not_null widget) override; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 1bb0e1a8a..9653ecf01 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -600,6 +600,11 @@ bool InnerWidget::elementShownUnread(not_null view) { return view->data()->unread(); } +void InnerWidget::elementSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + void InnerWidget::saveState(not_null memento) { memento->setFilter(std::move(_filter)); memento->setAdmins(std::move(_admins)); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index bf8f97f86..d402460ae 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -116,6 +116,9 @@ public: not_null view) override; bool elementShownUnread( not_null view) override; + void elementSendBotCommand( + const QString &command, + const FullMsgId &context) override; ~InnerWidget(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index b94c38b6a..52720fada 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "core/file_utilities.h" #include "core/crash_reports.h" +#include "core/click_handler_types.h" #include "history/history.h" #include "history/history_message.h" #include "history/view/media/history_view_media.h" @@ -1330,7 +1331,14 @@ void HistoryInner::mouseActionFinish( : FullMsgId(); ActivateClickHandler(window(), activated, { button, - QVariant::fromValue(pressedItemId) + QVariant::fromValue(ClickHandlerContext{ + .itemId = pressedItemId, + .elementDelegate = [weak = Ui::MakeWeak(this)] { + return weak + ? HistoryInner::ElementDelegate().get() + : nullptr; + }, + }) }); return; } @@ -2540,6 +2548,24 @@ bool HistoryInner::elementIsGifPaused() { return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any); } +void HistoryInner::elementSendBotCommand( + const QString &command, + const FullMsgId &context) { + if (auto peer = Ui::getPeerForMouseAction()) { // old way + auto bot = peer->isUser() ? peer->asUser() : nullptr; + if (!bot) { + if (const auto view = App::hoveredLinkItem()) { + // may return nullptr + bot = view->data()->fromOriginal()->asUser(); + } + } + Ui::showPeerHistory(peer, ShowAtTheEndMsgId); + App::sendBotCommand(peer, bot, command); + } else { + App::insertBotCommand(command); + } +} + auto HistoryInner::getSelectionState() const -> HistoryView::TopBarWidget::SelectedState { auto result = HistoryView::TopBarWidget::SelectedState {}; @@ -3434,6 +3460,13 @@ not_null HistoryInner::ElementDelegate() { bool elementShownUnread(not_null view) override { return view->data()->unread(); } + void elementSendBotCommand( + const QString &command, + const FullMsgId &context) { + if (Instance) { + Instance->elementSendBotCommand(command, context); + } + } }; static Result result; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index b671f1675..2532568f8 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -92,6 +92,9 @@ public: const TextWithEntities &text, Fn hiddenCallback); bool elementIsGifPaused(); + void elementSendBotCommand( + const QString &command, + const FullMsgId &context); void updateBotInfo(bool recount = true); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index f11299973..91f7673fb 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1464,7 +1464,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) { } clearIsolatedEmoji(); - const auto context = Core::UiIntegration::Context{ + const auto context = Core::MarkedTextContext{ .session = &history()->session() }; _text.setMarkedText( diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 07cd555fc..75bd8b335 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -3405,15 +3405,9 @@ void HistoryWidget::sendBotCommand( bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, replyTo)); - QString toSend = cmd; - if (bot && (!bot->isUser() || !bot->asUser()->isBot())) { - bot = nullptr; - } - QString username = bot ? bot->asUser()->username : QString(); - int32 botStatus = _peer->isChat() ? _peer->asChat()->botStatus : (_peer->isMegagroup() ? _peer->asChannel()->mgInfo->botStatus : -1); - if (!replyTo && toSend.indexOf('@') < 2 && !username.isEmpty() && (botStatus == 0 || botStatus == 2)) { - toSend += '@' + username; - } + const auto toSend = replyTo + ? cmd + : HistoryView::WrapBotCommandInChat(_peer, cmd, bot); auto message = ApiWrap::MessageToSend(_history); message.textWithTags = { toSend, TextWithTags::Tags() }; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 44b93d055..471932bd8 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -121,6 +121,11 @@ bool SimpleElementDelegate::elementShownUnread( return view->data()->unread(); } +void SimpleElementDelegate::elementSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 800761240..1f14d0a24 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -67,6 +67,9 @@ public: virtual bool elementIsGifPaused() = 0; virtual bool elementHideReply(not_null view) = 0; virtual bool elementShownUnread(not_null view) = 0; + virtual void elementSendBotCommand( + const QString &command, + const FullMsgId &context) = 0; }; @@ -99,6 +102,9 @@ public: bool elementIsGifPaused() override; bool elementHideReply(not_null view) override; bool elementShownUnread(not_null view) override; + void elementSendBotCommand( + const QString &command, + const FullMsgId &context) override; private: const not_null _controller; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 65943d1b4..39ebdf248 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "mainwidget.h" #include "core/application.h" +#include "core/click_handler_types.h" #include "apiwrap.h" #include "layout.h" #include "window/window_session_controller.h" @@ -37,6 +38,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_media_types.h" #include "data/data_document.h" #include "data/data_peer.h" +#include "data/data_user.h" +#include "data/data_chat.h" +#include "data/data_channel.h" #include "facades.h" #include "styles/style_chat.h" @@ -1290,6 +1294,12 @@ bool ListWidget::elementShownUnread(not_null view) { return _delegate->listElementShownUnread(view); } +void ListWidget::elementSendBotCommand( + const QString &command, + const FullMsgId &context) { + return _delegate->listSendBotCommand(command, context); +} + void ListWidget::saveState(not_null memento) { memento->setAroundPosition(_aroundPosition); auto state = countScrollState(); @@ -2186,7 +2196,14 @@ void ListWidget::mouseActionFinish( mouseActionCancel(); ActivateClickHandler(window(), activated, { button, - QVariant::fromValue(pressState.itemId) + QVariant::fromValue(ClickHandlerContext{ + .itemId = pressState.itemId, + .elementDelegate = [weak = Ui::MakeWeak(this)] { + return weak + ? (ElementDelegate*)weak + : nullptr; + }, + }) }); return; } @@ -2786,4 +2803,34 @@ void ConfirmSendNowSelectedItems(not_null widget) { [=] { navigation->showBackFromStack(); }); } +QString WrapBotCommandInChat( + not_null peer, + const QString &command, + const FullMsgId &context) { + auto result = command; + if (const auto item = peer->owner().message(context)) { + if (const auto user = item->fromOriginal()->asUser()) { + return WrapBotCommandInChat(peer, command, user); + } + } + return result; +} + +QString WrapBotCommandInChat( + not_null peer, + const QString &command, + not_null bot) { + if (!bot->isBot() || bot->username.isEmpty()) { + return command; + } + const auto botStatus = peer->isChat() + ? peer->asChat()->botStatus + : peer->isMegagroup() + ? peer->asChannel()->mgInfo->botStatus + : -1; + return ((command.indexOf('@') < 2) && (botStatus == 0 || botStatus == 2)) + ? command + '@' + bot->username + : command; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 38ecc163c..103416658 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -87,6 +87,9 @@ public: virtual bool listElementShownUnread(not_null view) = 0; virtual bool listIsGoodForAroundPosition( not_null view) = 0; + virtual void listSendBotCommand( + const QString &command, + const FullMsgId &context) = 0; }; @@ -233,6 +236,9 @@ public: bool elementIsGifPaused() override; bool elementHideReply(not_null view) override; bool elementShownUnread(not_null view) override; + void elementSendBotCommand( + const QString &command, + const FullMsgId &context) override; ~ListWidget(); @@ -556,4 +562,13 @@ void ConfirmDeleteSelectedItems(not_null widget); void ConfirmForwardSelectedItems(not_null widget); void ConfirmSendNowSelectedItems(not_null widget); +[[nodiscard]] QString WrapBotCommandInChat( + not_null peer, + const QString &command, + const FullMsgId &context); +[[nodiscard]] QString WrapBotCommandInChat( + not_null peer, + const QString &command, + not_null bot); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 5b64324ad..a2902fd39 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -639,6 +639,11 @@ bool PinnedWidget::listIsGoodForAroundPosition( return IsServerMsgId(view->data()->id); } +void PinnedWidget::listSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + void PinnedWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index 8025e2bc4..bfc8517de 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -93,6 +93,9 @@ public: bool listElementHideReply(not_null view) override; bool listElementShownUnread(not_null view) override; bool listIsGoodForAroundPosition(not_null view) override; + void listSendBotCommand( + const QString &command, + const FullMsgId &context) override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 9f931c52e..1d53ed665 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "data/data_session.h" #include "data/data_user.h" +#include "data/data_chat.h" #include "data/data_channel.h" #include "data/data_replies_list.h" #include "data/data_changes.h" @@ -445,11 +446,7 @@ void RepliesWidget::setupComposeControls() { if (showSlowmodeError()) { return; } - auto message = ApiWrap::MessageToSend(_history); - message.textWithTags = { command }; - message.action.replyTo = replyToId(); - session().api().sendMessage(std::move(message)); - finishSending(); + listSendBotCommand(command, FullMsgId()); }, lifetime()); const auto saveEditMsgRequestId = lifetime().make_state(0); @@ -1766,6 +1763,17 @@ bool RepliesWidget::listIsGoodForAroundPosition( return IsServerMsgId(view->data()->id); } +void RepliesWidget::listSendBotCommand( + const QString &command, + const FullMsgId &context) { + const auto text = WrapBotCommandInChat(_history->peer, command, context); + auto message = ApiWrap::MessageToSend(_history); + message.textWithTags = { text }; + message.action.replyTo = replyToId(); + session().api().sendMessage(std::move(message)); + finishSending(); +} + void RepliesWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index a22b5d92f..63e055abc 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -126,6 +126,9 @@ public: bool listElementHideReply(not_null view) override; bool listElementShownUnread(not_null view) override; bool listIsGoodForAroundPosition(not_null view) override; + void listSendBotCommand( + const QString &command, + const FullMsgId &context) override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 0a2e506b5..57a1680a8 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -182,15 +182,7 @@ void ScheduledWidget::setupComposeControls() { _composeControls->sendCommandRequests( ) | rpl::start_with_next([=](const QString &command) { - const auto callback = [=](Api::SendOptions options) { - auto message = ApiWrap::MessageToSend(_history); - message.textWithTags = { command }; - message.action.options = options; - session().api().sendMessage(std::move(message)); - }; - Ui::show( - PrepareScheduleBox(this, sendMenuType(), callback), - Ui::LayerOption::KeepOther); + listSendBotCommand(command, FullMsgId()); }, lifetime()); const auto saveEditMsgRequestId = lifetime().make_state(0); @@ -1185,6 +1177,21 @@ bool ScheduledWidget::listIsGoodForAroundPosition( return true; } +void ScheduledWidget::listSendBotCommand( + const QString &command, + const FullMsgId &context) { + const auto callback = [=](Api::SendOptions options) { + const auto text = WrapBotCommandInChat(_history->peer, command, context); + auto message = ApiWrap::MessageToSend(_history); + message.textWithTags = { text }; + message.action.options = options; + session().api().sendMessage(std::move(message)); + }; + Ui::show( + PrepareScheduleBox(this, sendMenuType(), callback), + Ui::LayerOption::KeepOther); +} + void ScheduledWidget::confirmSendNowSelected() { ConfirmSendNowSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index ad7b55a56..5d564f9cb 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -110,6 +110,9 @@ public: bool listElementHideReply(not_null view) override; bool listElementShownUnread(not_null view) override; bool listIsGoodForAroundPosition(not_null view) override; + void listSendBotCommand( + const QString &command, + const FullMsgId &context) override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index af6f8a282..cda894a14 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -33,7 +33,7 @@ Game::Game( , _title(st::msgMinWidth - st::webPageLeft) , _description(st::msgMinWidth - st::webPageLeft) { if (!consumed.text.isEmpty()) { - const auto context = Core::UiIntegration::Context{ + const auto context = Core::MarkedTextContext{ .session = &history()->session() }; _description.setMarkedText( @@ -418,7 +418,7 @@ void Game::parentTextUpdated() { if (const auto media = _parent->data()->media()) { const auto consumed = media->consumedMessageText(); if (!consumed.text.isEmpty()) { - const auto context = Core::UiIntegration::Context{ + const auto context = Core::MarkedTextContext{ .session = &history()->session() }; _description.setMarkedText( diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 64eb1ff5d..b72638f3b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -139,7 +139,7 @@ Ui::Text::String Media::createCaption( - st::msgPadding.left() - st::msgPadding.right(); auto result = Ui::Text::String(minResizeWidth); - const auto context = Core::UiIntegration::Context{ + const auto context = Core::MarkedTextContext{ .session = &history()->session() }; result.setMarkedText( 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 52a6a76ed..87b096b22 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -201,11 +201,12 @@ QSize WebPage::countOptimalSize() { - st::msgPadding.right() - st::webPageLeft); } - auto context = Core::UiIntegration::Context(); + auto context = Core::MarkedTextContext(); + using MarkedTextContext = Core::MarkedTextContext; if (_data->siteName == qstr("Twitter")) { - context.type = Core::UiIntegration::HashtagMentionType::Twitter; + context.type = MarkedTextContext::HashtagMentionType::Twitter; } else if (_data->siteName == qstr("Instagram")) { - context.type = Core::UiIntegration::HashtagMentionType::Instagram; + context.type = MarkedTextContext::HashtagMentionType::Instagram; } _description.setMarkedText( st::webPageDescriptionStyle, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index e7d4fce54..0f3f1a5f8 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -2018,7 +2018,7 @@ void OverlayWidget::refreshCaption(HistoryItem *item) { const auto base = duration ? DocumentTimestampLinkBase(_document, item->fullId()) : QString(); - const auto context = Core::UiIntegration::Context{ + const auto context = Core::MarkedTextContext{ .session = &item->history()->session() }; _caption.setMarkedText( From a8564b166b5da5e38729d405f308881b0bf8cb67 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Nov 2020 20:26:41 +0300 Subject: [PATCH 091/370] Add inline bots to ComposeControls. --- .../history_view_compose_controls.cpp | 211 +++++++++++++++++- .../controls/history_view_compose_controls.h | 27 ++- 2 files changed, 227 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 8f164e219..b9c2d4237 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -22,10 +22,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_messages.h" #include "data/data_session.h" #include "data/data_user.h" +#include "data/data_channel.h" #include "data/stickers/data_stickers.h" #include "data/data_web_page.h" #include "storage/storage_account.h" #include "facades.h" +#include "apiwrap.h" #include "boxes/confirm_box.h" #include "history/history.h" #include "history/history_item.h" @@ -537,6 +539,7 @@ ComposeControls::ComposeControls( ComposeControls::~ComposeControls() { setTabbedPanel(nullptr); + session().api().request(_inlineBotResolveRequestId).cancel(); } Main::Session &ComposeControls::session() const { @@ -559,6 +562,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { _window->tabbedSelector()->setCurrentPeer( history ? history->peer.get() : nullptr); initWebpageProcess(); + updateFieldPlaceholder(); } void ComposeControls::move(int x, int y) { @@ -757,8 +761,7 @@ void ComposeControls::checkAutocomplete() { } const auto peer = _history->peer; - const auto isInlineBot = false;// _inlineBot && !_inlineLookingUpBot; - const auto autocomplete = isInlineBot + const auto autocomplete = _isInlineBot ? AutocompleteQuery() : ParseMentionHashtagBotCommandQuery(_field); if (!autocomplete.query.isEmpty()) { @@ -878,7 +881,7 @@ void ComposeControls::setTextFromEditingMessage(not_null item) { void ComposeControls::initField() { _field->setMaxHeight(st::historyComposeFieldMaxHeight); - _field->setSubmitSettings(Core::App().settings().sendSubmitWay()); + updateSubmitSettings(); //Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); }); Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); }); Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); }); @@ -895,6 +898,13 @@ void ComposeControls::initField() { InitSpellchecker(_window, _field); } +void ComposeControls::updateSubmitSettings() { + const auto settings = _isInlineBot + ? Ui::InputField::SubmitSettings::None + : Core::App().settings().sendSubmitWay(); + _field->setSubmitSettings(settings); +} + void ComposeControls::initAutocomplete() { const auto insertHashtagOrBotCommand = [=]( const QString &string, @@ -1001,9 +1011,39 @@ void ComposeControls::updateStickersByEmoji() { _autocomplete->showStickers(emoji); } +void ComposeControls::updateFieldPlaceholder() { + if (!isEditingMessage() && _isInlineBot) { + _field->setPlaceholder( + rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)), + _inlineBot->username.size() + 2); + return; + } + + _field->setPlaceholder([&] { + if (isEditingMessage()) { + return tr::lng_edit_message_text(); + } else if (!_history) { + return tr::lng_message_ph(); + } else if (const auto channel = _history->peer->asChannel()) { + if (channel->isBroadcast()) { + return session().data().notifySilentPosts(channel) + ? tr::lng_broadcast_silent_ph() + : tr::lng_broadcast_ph(); + } else if (channel->adminRights() & ChatAdminRight::f_anonymous) { + return tr::lng_send_anonymous_ph(); + } else { + return tr::lng_message_ph(); + } + } else { + return tr::lng_message_ph(); + } + }()); + updateSendButtonType(); +} + void ComposeControls::fieldChanged() { - if (/*!_inlineBot - && */!_header->isEditingMessage() + if (!_inlineBot + && !_header->isEditingMessage() && (_textUpdateEvents & TextUpdateEvent::SendTyping)) { _sendActionUpdates.fire({ Api::SendProgressType::Typing }); } @@ -1012,6 +1052,7 @@ void ComposeControls::fieldChanged() { //_previewCancelled = false; } InvokeQueued(_autocomplete.get(), [=] { + updateInlineBotQuery(); updateStickersByEmoji(); }); } @@ -1073,6 +1114,81 @@ void ComposeControls::initSendButton() { }, _send->lifetime()); _send->finishAnimating(); + + _send->clicks( + ) | rpl::filter([=] { + return (_send->type() == Ui::SendButton::Type::Cancel); + }) | rpl::start_with_next([=] { + cancelInlineBot(); + }, _send->lifetime()); +} + +void ComposeControls::inlineBotResolveDone( + const MTPcontacts_ResolvedPeer &result) { + Expects(result.type() == mtpc_contacts_resolvedPeer); + + _inlineBotResolveRequestId = 0; + const auto &data = result.c_contacts_resolvedPeer(); + const auto resolvedBot = [&]() -> UserData* { + if (const auto result = session().data().processUsers(data.vusers())) { + if (result->isBot() + && !result->botInfo->inlinePlaceholder.isEmpty()) { + return result; + } + } + return nullptr; + }(); + session().data().processChats(data.vchats()); + + const auto query = ParseInlineBotQuery(&session(), _field); + if (_inlineBotUsername == query.username) { + applyInlineBotQuery( + query.lookingUpBot ? resolvedBot : query.bot, + query.query); + } else { + clearInlineBot(); + } +} + +void ComposeControls::inlineBotResolveFail( + const RPCError &error, + const QString &username) { + _inlineBotResolveRequestId = 0; + if (username == _inlineBotUsername) { + clearInlineBot(); + } +} + +void ComposeControls::cancelInlineBot() { + auto &textWithTags = _field->getTextWithTags(); + if (textWithTags.text.size() > _inlineBotUsername.size() + 2) { + setText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }); + } else { + setText({}); + } +} + +void ComposeControls::clearInlineBot() { + if (_inlineBot || _inlineLookingUpBot) { + _inlineBot = nullptr; + _inlineLookingUpBot = false; + inlineBotChanged(); + _field->finishAnimating(); + } + if (_inlineResults) { + _inlineResults->clearInlineBot(); + } + checkAutocomplete(); +} + +void ComposeControls::inlineBotChanged() { + const auto isInlineBot = (_inlineBot && !_inlineLookingUpBot); + if (_isInlineBot != isInlineBot) { + _isInlineBot = isInlineBot; + updateFieldPlaceholder(); + updateSubmitSettings(); + checkAutocomplete(); + } } void ComposeControls::initWriteRestriction() { @@ -1155,8 +1271,8 @@ void ComposeControls::updateSendButtonType() { const auto type = [&] { if (_header->isEditingMessage()) { return Type::Save; - //} else if (_isInlineBot) { - // return Type::Cancel; + } else if (_isInlineBot) { + return Type::Cancel; } else if (showRecordButton()) { return Type::Record; } @@ -1319,6 +1435,7 @@ void ComposeControls::editMessage(FullMsgId id) { if (_autocomplete) { InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); }); } + updateFieldPlaceholder(); } void ComposeControls::cancelEditMessage() { @@ -1326,6 +1443,7 @@ void ComposeControls::cancelEditMessage() { if (_autocomplete) { InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); }); } + updateFieldPlaceholder(); } void ComposeControls::replyToMessage(FullMsgId id) { @@ -1338,7 +1456,10 @@ void ComposeControls::cancelReplyMessage() { } bool ComposeControls::handleCancelRequest() { - if (isEditingMessage()) { + if (_isInlineBot) { + cancelInlineBot(); + return true; + } else if (isEditingMessage()) { cancelEditMessage(); return true; } else if (_autocomplete && !_autocomplete->isHidden()) { @@ -1490,6 +1611,7 @@ void ComposeControls::initWebpageProcess() { }) | rpl::start_with_next([=] { checkPreview(); updateStickersByEmoji(); + updateFieldPlaceholder(); }, lifetime); _window->session().downloaderTaskFinished( @@ -1557,4 +1679,77 @@ bool ComposeControls::isRecording() const { return _voiceRecordBar->isRecording(); } +void ComposeControls::updateInlineBotQuery() { + if (!_history) { + return; + } + const auto query = ParseInlineBotQuery(&session(), _field); + if (_inlineBotUsername != query.username) { + _inlineBotUsername = query.username; + auto &api = session().api(); + if (_inlineBotResolveRequestId) { + api.request(_inlineBotResolveRequestId).cancel(); + _inlineBotResolveRequestId = 0; + } + if (query.lookingUpBot) { + _inlineBot = nullptr; + _inlineLookingUpBot = true; + const auto username = _inlineBotUsername; + _inlineBotResolveRequestId = api.request( + MTPcontacts_ResolveUsername(MTP_string(username)) + ).done([=](const MTPcontacts_ResolvedPeer &result) { + inlineBotResolveDone(result); + }).fail([=](const RPCError &error) { + inlineBotResolveFail(error, username); + }).send(); + } else { + applyInlineBotQuery(query.bot, query.query); + } + } else if (query.lookingUpBot) { + if (!_inlineLookingUpBot) { + applyInlineBotQuery(_inlineBot, query.query); + } + } else { + applyInlineBotQuery(query.bot, query.query); + } +} + +void ComposeControls::applyInlineBotQuery( + UserData *bot, + const QString &query) { + if (_history && bot) { + if (_inlineBot != bot) { + _inlineBot = bot; + _inlineLookingUpBot = false; + inlineBotChanged(); + } + if (!_inlineResults) { + _inlineResults = std::make_unique( + _parent, + _window); + _inlineResults->setResultSelectedCallback([=]( + InlineBots::Result *result, + UserData *bot, + Api::SendOptions options) { + _inlineResultChosen.fire(InlineChosen{ + .result = result, + .bot = bot, + .options = options, + }); + }); + _inlineResults->requesting( + ) | rpl::start_with_next([=](bool requesting) { + _tabbedSelectorToggle->setLoading(requesting); + }, _inlineResults->lifetime()); + updateOuterGeometry(_wrap->geometry()); + } + _inlineResults->queryInlineBot(_inlineBot, _history->peer, query); + if (!_autocomplete->isHidden()) { + _autocomplete->hideAnimated(); + } + } else { + clearInlineBot(); + } +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index d7c6cc3ce..cfb6f7fa2 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -67,6 +67,7 @@ class ComposeControls final { public: using FileChosen = ChatHelpers::TabbedSelector::FileChosen; using PhotoChosen = ChatHelpers::TabbedSelector::PhotoChosen; + using InlineChosen = ChatHelpers::TabbedSelector::InlineChosen; using MessageToEdit = Controls::MessageToEdit; using VoiceToSend = Controls::VoiceToSend; @@ -105,8 +106,7 @@ public: [[nodiscard]] rpl::producer photoChosen() const; [[nodiscard]] rpl::producer scrollRequests() const; [[nodiscard]] rpl::producer> keyEvents() const; - [[nodiscard]] auto inlineResultChosen() const - -> rpl::producer; + [[nodiscard]] rpl::producer inlineResultChosen() const; [[nodiscard]] rpl::producer sendActionUpdates() const; using MimeDataHook = Fn _parent; const not_null _window; History *_history = nullptr; @@ -213,7 +228,7 @@ private: rpl::event_stream<> _cancelRequests; rpl::event_stream _fileChosen; rpl::event_stream _photoChosen; - rpl::event_stream _inlineResultChosen; + rpl::event_stream _inlineResultChosen; rpl::event_stream _sendActionUpdates; rpl::event_stream _sendCommandRequests; @@ -223,6 +238,12 @@ private: //bool _inReplyEditForward = false; //bool _inClickable = false; + UserData *_inlineBot = nullptr; + QString _inlineBotUsername; + bool _inlineLookingUpBot = false; + mtpRequestId _inlineBotResolveRequestId = 0; + bool _isInlineBot = false; + rpl::lifetime _uploaderSubscriptions; Fn _raiseEmojiSuggestions; From 10adbecb9c2b4ccd62c0d5a49ca99775d4c025c5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Nov 2020 20:54:43 +0300 Subject: [PATCH 092/370] Support creating polls in RepliesSection. --- .../view/history_view_replies_section.cpp | 5 +++++ .../view/history_view_replies_section.h | 2 ++ .../view/history_view_top_bar_widget.cpp | 18 +++++++++++------- Telegram/SourceFiles/mainwidget.cpp | 4 +++- Telegram/SourceFiles/window/section_widget.h | 4 ++++ .../SourceFiles/window/window_peer_menu.cpp | 3 ++- Telegram/SourceFiles/window/window_peer_menu.h | 1 + 7 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 1d53ed665..33a088c85 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1398,6 +1398,11 @@ bool RepliesWidget::replyToMessage(not_null item) { return true; } +MsgId RepliesWidget::currentReplyToIdFor( + not_null history) const { + return (_history == history) ? replyToId() : 0; +} + void RepliesWidget::saveState(not_null memento) { memento->setReplies(_replies); memento->setReplyReturns(_replyReturns); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 63e055abc..18acc6030 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -88,6 +88,8 @@ public: const Window::SectionShow ¶ms, MsgId messageId) override; bool replyToMessage(not_null item) override; + MsgId currentReplyToIdFor( + not_null history) const override; void setInternalState( const QRect &geometry, diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 6de1fb644..a4edc14ef 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -238,9 +238,11 @@ void TopBarWidget::showMenu() { peer, FilterId(), addAction, - (_section == Section::Scheduled) + (_section == Section::Scheduled ? Window::PeerMenuSource::ScheduledSection - : Window::PeerMenuSource::History); + : (_section == Section::Replies) + ? Window::PeerMenuSource::RepliesSection + : Window::PeerMenuSource::History)); } else if (const auto folder = _activeChat.folder()) { Window::FillFolderMenu( _controller, @@ -686,12 +688,14 @@ void TopBarWidget::updateControlsVisibility() { _unreadBadge->show(); } const auto historyMode = (_section == Section::History); - const auto scheduledMode = (_section == Section::Scheduled); - const auto showInScheduledMode = (_activeChat.peer() - && _activeChat.peer()->canSendPolls()); + const auto hasPollsMenu = _activeChat.peer() + && _activeChat.peer()->canSendPolls(); + const auto hasMenu = !_activeChat.folder() + && ((_section == Section::Scheduled || _section == Section::Replies) + ? hasPollsMenu + : historyMode); updateSearchVisibility(); - _menuToggle->setVisible(!_activeChat.folder() - && (scheduledMode ? showInScheduledMode : historyMode)); + _menuToggle->setVisible(hasMenu); _infoToggle->setVisible(historyMode && !_activeChat.folder() && !Adaptive::OneColumn() diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 63a1f13e4..e74d2118d 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -820,7 +820,9 @@ crl::time MainWidget::highlightStartTime(not_null item) cons } MsgId MainWidget::currentReplyToIdFor(not_null history) const { - if (_history->history() == history) { + if (_mainSection) { + return _mainSection->currentReplyToIdFor(history); + } else if (_history->history() == history) { return _history->replyToId(); } else if (const auto localDraft = history->localDraft()) { return localDraft->msgId; diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index 6d5f3b7fd..664fe9710 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -128,6 +128,10 @@ public: virtual bool replyToMessage(not_null item) { return false; } + [[nodiscard]] virtual MsgId currentReplyToIdFor( + not_null history) const { + return 0; + } // Create a memento of that section to store it in the history stack. // This method may modify the section ("take" heavy items). diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index d105b7428..90088b65e 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -650,7 +650,8 @@ void Filler::addPollAction(not_null peer) { } void Filler::fill() { - if (_source == PeerMenuSource::ScheduledSection) { + if (_source == PeerMenuSource::ScheduledSection + || _source == PeerMenuSource::RepliesSection) { addPollAction(_peer); return; } diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index e733211a4..f5c9f1a2e 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -37,6 +37,7 @@ enum class PeerMenuSource { History, Profile, ScheduledSection, + RepliesSection, }; using PeerMenuCallback = Fn Date: Tue, 10 Nov 2020 21:51:20 +0300 Subject: [PATCH 093/370] Add start bot command and handle via@ links. --- .../admin_log/history_admin_log_inner.cpp | 3 + .../admin_log/history_admin_log_inner.h | 1 + .../history/history_inner_widget.cpp | 11 ++- .../history/history_inner_widget.h | 1 + .../history/history_item_components.cpp | 11 ++- .../history_view_compose_controls.cpp | 72 +++++++++++++++++-- .../controls/history_view_compose_controls.h | 4 ++ .../history/view/history_view_element.cpp | 3 + .../history/view/history_view_element.h | 2 + .../history/view/history_view_list_widget.cpp | 6 +- .../history/view/history_view_list_widget.h | 2 + .../view/history_view_pinned_section.cpp | 3 + .../view/history_view_pinned_section.h | 1 + .../view/history_view_replies_section.cpp | 4 ++ .../view/history_view_replies_section.h | 1 + .../view/history_view_scheduled_section.cpp | 4 ++ .../view/history_view_scheduled_section.h | 1 + Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- 19 files changed, 122 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 9653ecf01..bf383ddec 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -605,6 +605,9 @@ void InnerWidget::elementSendBotCommand( const FullMsgId &context) { } +void InnerWidget::elementHandleViaClick(not_null bot) { +} + void InnerWidget::saveState(not_null memento) { memento->setFilter(std::move(_filter)); memento->setAdmins(std::move(_admins)); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index d402460ae..32a6fcf42 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -119,6 +119,7 @@ public: void elementSendBotCommand( const QString &command, const FullMsgId &context) override; + void elementHandleViaClick(not_null bot) override; ~InnerWidget(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 52720fada..9e96faf10 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2566,6 +2566,10 @@ void HistoryInner::elementSendBotCommand( } } +void HistoryInner::elementHandleViaClick(not_null bot) { + App::insertBotCommand('@' + bot->username); +} + auto HistoryInner::getSelectionState() const -> HistoryView::TopBarWidget::SelectedState { auto result = HistoryView::TopBarWidget::SelectedState {}; @@ -3462,11 +3466,16 @@ not_null HistoryInner::ElementDelegate() { } void elementSendBotCommand( const QString &command, - const FullMsgId &context) { + const FullMsgId &context) override { if (Instance) { Instance->elementSendBotCommand(command, context); } } + void elementHandleViaClick(not_null bot) override { + if (Instance) { + Instance->elementHandleViaClick(bot); + } + } }; static Result result; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 2532568f8..8e5f5d6f3 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -95,6 +95,7 @@ public: void elementSendBotCommand( const QString &command, const FullMsgId &context); + void elementHandleViaClick(not_null bot); void updateBotInfo(bool recount = true); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 4215341c9..a1292c6bb 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_message.h" #include "history/view/history_view_service_message.h" #include "history/view/media/history_view_document.h" +#include "core/click_handler_types.h" #include "mainwindow.h" #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" @@ -45,7 +46,8 @@ void HistoryMessageVia::create( bot = owner->user(userId); maxWidth = st::msgServiceNameFont->width( tr::lng_inline_bot_via(tr::now, lt_inline_bot, '@' + bot->username)); - link = std::make_shared([bot = this->bot] { + link = std::make_shared([bot = this->bot]( + ClickContext context) { if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) { if (const auto window = App::wnd()) { if (const auto controller = window->sessionController()) { @@ -54,7 +56,12 @@ void HistoryMessageVia::create( } } } - App::insertBotCommand('@' + bot->username); + const auto my = context.other.value(); + if (const auto delegate = my.elementDelegate ? my.elementDelegate() : nullptr) { + delegate->elementHandleViaClick(bot); + } else { + App::insertBotCommand('@' + bot->username); + } }); } 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 b9c2d4237..97189d361 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_messages.h" #include "data/data_session.h" #include "data/data_user.h" +#include "data/data_chat.h" #include "data/data_channel.h" #include "data/stickers/data_stickers.h" #include "data/data_web_page.h" @@ -521,6 +522,9 @@ ComposeControls::ComposeControls( st::historyComposeField, Ui::InputField::Mode::MultiLine, tr::lng_message_ph())) +, _botCommandStart(Ui::CreateChild( + _wrap.get(), + st::historyBotCommandStart)) , _autocomplete(std::make_unique( parent, window)) @@ -562,7 +566,21 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { _window->tabbedSelector()->setCurrentPeer( history ? history->peer.get() : nullptr); initWebpageProcess(); + updateBotCommandShown(); + updateControlsGeometry(_wrap->size()); + updateControlsVisibility(); updateFieldPlaceholder(); + if (!_history) { + return; + } + const auto peer = _history->peer; + if (peer->isChat() && peer->asChat()->noParticipantInfo()) { + session().api().requestFullPeer(peer); + } else if (const auto channel = peer->asMegagroup()) { + if (!channel->mgInfo->botStatus) { + session().api().requestBots(channel); + } + } } void ComposeControls::move(int x, int y) { @@ -791,6 +809,8 @@ void ComposeControls::init() { initWriteRestriction(); initVoiceRecordBar(); + _botCommandStart->setClickedCallback([=] { setText({ "/" }); }); + _wrap->sizeValue( ) | rpl::start_with_next([=](QSize size) { updateControlsGeometry(size); @@ -1051,6 +1071,10 @@ void ComposeControls::fieldChanged() { if (showRecordButton()) { //_previewCancelled = false; } + if (updateBotCommandShown()) { + updateControlsVisibility(); + updateControlsGeometry(_wrap->size()); + } InvokeQueued(_autocomplete.get(), [=] { updateInlineBotQuery(); updateStickersByEmoji(); @@ -1297,13 +1321,14 @@ void ComposeControls::finishAnimating() { void ComposeControls::updateControlsGeometry(QSize size) { // _attachToggle -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel - // (_attachDocument|_attachPhoto) _field _tabbedSelectorToggle _send + // (_attachDocument|_attachPhoto) _field _botCommandStart _tabbedSelectorToggle _send const auto fieldWidth = size.width() - _attachToggle->width() - st::historySendRight - _send->width() - - _tabbedSelectorToggle->width(); + - _tabbedSelectorToggle->width() + - (_botCommandShown ? _botCommandStart->width() : 0); _field->resizeToWidth(fieldWidth); const auto buttonsTop = size.height() - _attachToggle->height(); @@ -1324,6 +1349,8 @@ void ComposeControls::updateControlsGeometry(QSize size) { _send->moveToRight(right, buttonsTop); right += _send->width(); _tabbedSelectorToggle->moveToRight(right, buttonsTop); + right += _tabbedSelectorToggle->width(); + _botCommandStart->moveToRight(right, buttonsTop); _voiceRecordBar->resizeToWidth(size.width()); _voiceRecordBar->moveToLeft( @@ -1331,6 +1358,28 @@ void ComposeControls::updateControlsGeometry(QSize size) { size.height() - _voiceRecordBar->height()); } +void ComposeControls::updateControlsVisibility() { + _botCommandStart->setVisible(_botCommandShown); +} + +bool ComposeControls::updateBotCommandShown() { + auto shown = false; + const auto peer = _history ? _history->peer.get() : nullptr; + if (peer + && ((peer->isChat() && peer->asChat()->botStatus > 0) + || (peer->isMegagroup() && peer->asChannel()->mgInfo->botStatus > 0) + || (peer->isUser() && peer->asUser()->isBot()))) { + if (!HasSendText(_field)) { + shown = true; + } + } + if (_botCommandShown != shown) { + _botCommandShown = shown; + return true; + } + return false; +} + void ComposeControls::updateOuterGeometry(QRect rect) { if (_inlineResults) { _inlineResults->moveBottom(rect.y()); @@ -1604,7 +1653,7 @@ void ComposeControls::initWebpageProcess() { getWebPagePreview(); }); - _window->session().changes().peerUpdates( + session().changes().peerUpdates( Data::PeerUpdate::Flag::Rights ) | rpl::filter([=](const Data::PeerUpdate &update) { return (update.peer.get() == peer); @@ -1614,7 +1663,18 @@ void ComposeControls::initWebpageProcess() { updateFieldPlaceholder(); }, lifetime); - _window->session().downloaderTaskFinished( + base::ObservableViewer( + session().api().fullPeerUpdated() + ) | rpl::filter([=](PeerData *peer) { + return _history && (_history->peer == peer); + }) | rpl::start_with_next([=] { + if (updateBotCommandShown()) { + updateControlsVisibility(); + updateControlsGeometry(_wrap->size()); + } + }, lifetime); + + session().downloaderTaskFinished( ) | rpl::filter([=] { return (*previewData) && ((*previewData)->document || (*previewData)->photo); @@ -1622,7 +1682,7 @@ void ComposeControls::initWebpageProcess() { requestRepaint ), lifetime); - _window->session().data().webPageUpdates( + session().data().webPageUpdates( ) | rpl::filter([=](not_null page) { return (*previewData == page.get()); }) | rpl::start_with_next([=] { @@ -1652,7 +1712,7 @@ WebPageId ComposeControls::webPageId() const { rpl::producer ComposeControls::scrollRequests() const { return _header->scrollToItemRequests( ) | rpl::map([=](FullMsgId id) -> Data::MessagePosition { - if (const auto item = _window->session().data().message(id)) { + if (const auto item = session().data().message(id)) { return item->position(); } return {}; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index cfb6f7fa2..b04a68b31 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -187,6 +187,7 @@ private: bool showRecordButton() const; void drawRestrictedWrite(Painter &p, const QString &error); void updateOverStates(QPoint pos); + bool updateBotCommandShown(); void cancelInlineBot(); void clearInlineBot(); @@ -217,6 +218,8 @@ private: const not_null _attachToggle; const not_null _tabbedSelectorToggle; const not_null _field; + const not_null _botCommandStart; + std::unique_ptr _inlineResults; std::unique_ptr _tabbedPanel; std::unique_ptr _autocomplete; @@ -243,6 +246,7 @@ private: bool _inlineLookingUpBot = false; mtpRequestId _inlineBotResolveRequestId = 0; bool _isInlineBot = false; + bool _botCommandShown = false; rpl::lifetime _uploaderSubscriptions; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 471932bd8..97d128ba0 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -126,6 +126,9 @@ void SimpleElementDelegate::elementSendBotCommand( const FullMsgId &context) { } +void SimpleElementDelegate::elementHandleViaClick(not_null bot) { +} + TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 1f14d0a24..fe64bdf3e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -70,6 +70,7 @@ public: virtual void elementSendBotCommand( const QString &command, const FullMsgId &context) = 0; + virtual void elementHandleViaClick(not_null bot) = 0; }; @@ -105,6 +106,7 @@ public: void elementSendBotCommand( const QString &command, const FullMsgId &context) override; + void elementHandleViaClick(not_null bot) override; private: const not_null _controller; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 39ebdf248..15f02c0af 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1297,7 +1297,11 @@ bool ListWidget::elementShownUnread(not_null view) { void ListWidget::elementSendBotCommand( const QString &command, const FullMsgId &context) { - return _delegate->listSendBotCommand(command, context); + _delegate->listSendBotCommand(command, context); +} + +void ListWidget::elementHandleViaClick(not_null bot) { + _delegate->listHandleViaClick(bot); } void ListWidget::saveState(not_null memento) { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 103416658..dff78abba 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -90,6 +90,7 @@ public: virtual void listSendBotCommand( const QString &command, const FullMsgId &context) = 0; + virtual void listHandleViaClick(not_null bot) = 0; }; @@ -239,6 +240,7 @@ public: void elementSendBotCommand( const QString &command, const FullMsgId &context) override; + void elementHandleViaClick(not_null bot) override; ~ListWidget(); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index a2902fd39..9699c13f0 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -644,6 +644,9 @@ void PinnedWidget::listSendBotCommand( const FullMsgId &context) { } +void PinnedWidget::listHandleViaClick(not_null bot) { +} + void PinnedWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index bfc8517de..ec4250346 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -96,6 +96,7 @@ public: void listSendBotCommand( const QString &command, const FullMsgId &context) override; + void listHandleViaClick(not_null bot) override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 33a088c85..76755b0b3 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1779,6 +1779,10 @@ void RepliesWidget::listSendBotCommand( finishSending(); } +void RepliesWidget::listHandleViaClick(not_null bot) { + _composeControls->setText({ '@' + bot->username + ' ' }); +} + void RepliesWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 18acc6030..308368438 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -131,6 +131,7 @@ public: void listSendBotCommand( const QString &command, const FullMsgId &context) override; + void listHandleViaClick(not_null bot) override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 57a1680a8..f1c14871a 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -1192,6 +1192,10 @@ void ScheduledWidget::listSendBotCommand( Ui::LayerOption::KeepOther); } +void ScheduledWidget::listHandleViaClick(not_null bot) { + _composeControls->setText({ '@' + bot->username + ' ' }); +} + void ScheduledWidget::confirmSendNowSelected() { ConfirmSendNowSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index 5d564f9cb..31716aaee 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -113,6 +113,7 @@ public: void listSendBotCommand( const QString &command, const FullMsgId &context) override; + void listHandleViaClick(not_null bot) override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/lib_base b/Telegram/lib_base index ee68d3a63..4f22126e7 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit ee68d3a63b4f1e550e7f93c1f8d6e8e372bc0f33 +Subproject commit 4f22126e7e855b517857408abf2467cba163a6f2 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 77b6e43b1..f06346fbf 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 77b6e43b17df43d64354ba7198e6a7695a636627 +Subproject commit f06346fbf03900c278e1d59717e1387bffc03f39 From 4a8b59b788dad173d408e07df27320f71cf24c66 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 11 Nov 2020 23:47:40 +0300 Subject: [PATCH 094/370] Pass reply info to Window::PeerMenu. --- .../dialogs/dialogs_inner_widget.cpp | 15 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 6 +- Telegram/SourceFiles/facades.cpp | 8 +- .../SourceFiles/history/history_widget.cpp | 27 ++-- Telegram/SourceFiles/history/history_widget.h | 1 + .../view/history_view_pinned_section.cpp | 6 +- .../view/history_view_replies_section.cpp | 29 ++-- .../view/history_view_replies_section.h | 4 +- .../view/history_view_scheduled_section.cpp | 6 +- .../view/history_view_top_bar_widget.cpp | 151 ++++++++++-------- .../view/history_view_top_bar_widget.h | 13 +- .../SourceFiles/info/info_wrap_widget.cpp | 9 +- Telegram/SourceFiles/mainwidget.cpp | 11 -- Telegram/SourceFiles/mainwidget.h | 2 - Telegram/SourceFiles/window/section_widget.h | 4 - .../SourceFiles/window/window_peer_menu.cpp | 105 ++++++------ .../SourceFiles/window/window_peer_menu.h | 36 +++-- 17 files changed, 236 insertions(+), 197 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 5aa666cc9..86066c2e0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1817,20 +1817,21 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { } else if (const auto history = row.key.history()) { Window::FillPeerMenu( _controller, - history->peer, - _filterId, + Window::PeerMenuRequest{ + .peer = history->peer, + .source = Window::PeerMenuRequest::Source::ChatsList, + .filterId = _filterId, + }, [&](const QString &text, Fn callback) { return _menu->addAction(text, std::move(callback)); - }, - Window::PeerMenuSource::ChatsList); + }); } else if (const auto folder = row.key.folder()) { Window::FillFolderMenu( _controller, - folder, + Window::FolderMenuRequest{ folder }, [&](const QString &text, Fn callback) { return _menu->addAction(text, std::move(callback)); - }, - Window::PeerMenuSource::ChatsList); + }); } connect(_menu.get(), &QObject::destroyed, [=] { if (_menuRow.key) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 5e82e23b9..c41aeb2d3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -523,8 +523,10 @@ void Widget::refreshFolderTopBar() { updateControlsGeometry(); } _folderTopBar->setActiveChat( - _openedFolder, - HistoryView::TopBarWidget::Section::History, + HistoryView::TopBarWidget::ActiveChat{ + .key = _openedFolder, + .section = HistoryView::TopBarWidget::Section::Dialogs, + }, nullptr); } else { _folderTopBar.destroy(); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index bf3c6989c..c672f9072 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -171,7 +171,13 @@ void activateBotCommand( } } if (const auto m = CheckMainWidget(&msg->history()->session())) { - Window::PeerMenuCreatePoll(m->controller(), msg->history()->peer, chosen, disabled); + const auto replyToId = MsgId(0); + Window::PeerMenuCreatePoll( + m->controller(), + msg->history()->peer, + replyToId, + chosen, + disabled); } } break; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 75bd8b335..122792c35 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -707,6 +707,16 @@ void HistoryWidget::setGeometryWithTopMoved( _topDelta = 0; } +void HistoryWidget::refreshTopBarActiveChat() { + _topBar->setActiveChat( + HistoryView::TopBarWidget::ActiveChat{ + .key = _history, + .section = HistoryView::TopBarWidget::Section::History, + .currentReplyToId = replyToId(), + }, + _history->sendActionPainter()); +} + void HistoryWidget::refreshTabbedPanel() { if (_peer && controller()->hasTabbedSelectorOwnership()) { createTabbedPanel(); @@ -1588,6 +1598,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { updateControlsVisibility(); updateControlsGeometry(); } + refreshTopBarActiveChat(); return; } @@ -1607,7 +1618,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { } updateControlsVisibility(); updateControlsGeometry(); - + refreshTopBarActiveChat(); if (_editMsgId || _replyToId) { updateReplyEditTexts(); if (!_replyEditMsg) { @@ -1825,10 +1836,7 @@ void HistoryWidget::showHistory( } _history->setFakeUnreadWhileOpened(true); - _topBar->setActiveChat( - _history, - HistoryView::TopBarWidget::Section::History, - _history->sendActionPainter()); + refreshTopBarActiveChat(); updateTopBarSelection(); if (_channel) { @@ -1915,10 +1923,7 @@ void HistoryWidget::showHistory( unreadCountUpdated(); // set _historyDown badge. showAboutTopPromotion(); } else { - _topBar->setActiveChat( - Dialogs::Key(), - HistoryView::TopBarWidget::Section::History, - nullptr); + refreshTopBarActiveChat(); updateTopBarSelection(); clearFieldText(); @@ -4763,6 +4768,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { updateMouseTracking(); } } + refreshTopBarActiveChat(); updateControlsGeometry(); update(); } @@ -5479,6 +5485,7 @@ void HistoryWidget::replyToMessage(not_null item) { updateReplyToName(); updateControlsGeometry(); updateField(); + refreshTopBarActiveChat(); } _saveDraftText = true; @@ -5605,7 +5612,7 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { } updateBotKeyboard(); - + refreshTopBarActiveChat(); updateControlsGeometry(); update(); } else if (auto localDraft = (_history ? _history->localDraft() : nullptr)) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 57c964f03..b05e6fb36 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -353,6 +353,7 @@ private: void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); void updateField(); + void refreshTopBarActiveChat(); void requestMessageData(MsgId msgId); void messageDataReceived(ChannelData *channel, MsgId msgId); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 9699c13f0..2d8d2fa92 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -102,8 +102,10 @@ PinnedWidget::PinnedWidget( st::historyComposeButton)) , _scrollDown(_scroll.get(), st::historyToDown) { _topBar->setActiveChat( - _history, - TopBarWidget::Section::Pinned, + TopBarWidget::ActiveChat{ + .key = _history, + .section = TopBarWidget::Section::Pinned, + }, nullptr); _topBar->move(0, 0); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 76755b0b3..18fe048d0 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -167,10 +167,7 @@ RepliesWidget::RepliesWidget( session().api().requestFullPeer(_history->peer); - _topBar->setActiveChat( - _history, - TopBarWidget::Section::Replies, - _sendAction.get()); + refreshTopBarActiveChat(); _topBar->move(0, 0); _topBar->resizeToWidth(width()); @@ -220,7 +217,7 @@ RepliesWidget::RepliesWidget( _inner->replyToMessageRequested( ) | rpl::start_with_next([=](auto fullId) { - _composeControls->replyToMessage(fullId); + replyToMessage(fullId); }, _inner->lifetime()); _composeControls->sendActionUpdates( @@ -714,6 +711,7 @@ void RepliesWidget::sendingFilesConfirmed( } if (_composeControls->replyingToMessage().msg == replyTo) { _composeControls->cancelReplyMessage(); + refreshTopBarActiveChat(); } } @@ -1138,6 +1136,17 @@ SendMenu::Type RepliesWidget::sendMenuType() const { : SendMenu::Type::Scheduled; } +void RepliesWidget::refreshTopBarActiveChat() { + _topBar->setActiveChat( + TopBarWidget::ActiveChat{ + .key = _history, + .section = TopBarWidget::Section::Replies, + .rootId = _rootId, + .currentReplyToId = replyToId(), + }, + _sendAction.get()); +} + MsgId RepliesWidget::replyToId() const { const auto custom = _composeControls->replyingToMessage().msg; return custom ? custom : _rootId; @@ -1181,6 +1190,7 @@ void RepliesWidget::finishSending() { //if (_previewData && _previewData->pendingTill) previewCancel(); doSetInnerFocus(); showAtEnd(); + refreshTopBarActiveChat(); } void RepliesWidget::showAtPosition( @@ -1394,13 +1404,13 @@ bool RepliesWidget::replyToMessage(not_null item) { if (item->history() != _history || item->replyToTop() != _rootId) { return false; } - _composeControls->replyToMessage(item->fullId()); + replyToMessage(item->fullId()); return true; } -MsgId RepliesWidget::currentReplyToIdFor( - not_null history) const { - return (_history == history) ? replyToId() : 0; +void RepliesWidget::replyToMessage(FullMsgId itemId) { + _composeControls->replyToMessage(itemId); + refreshTopBarActiveChat(); } void RepliesWidget::saveState(not_null memento) { @@ -1626,6 +1636,7 @@ void RepliesWidget::listCancelRequest() { clearSelected(); return; } else if (_composeControls->handleCancelRequest()) { + refreshTopBarActiveChat(); return; } controller()->showBackFromStack(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 308368438..66e8b7eb0 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -88,8 +88,6 @@ public: const Window::SectionShow ¶ms, MsgId messageId) override; bool replyToMessage(not_null item) override; - MsgId currentReplyToIdFor( - not_null history) const override; void setInternalState( const QRect &geometry, @@ -200,6 +198,8 @@ private: void restoreReplyReturns(const std::vector &list); void checkReplyReturns(); void recountChatWidth(); + void replyToMessage(FullMsgId itemId); + void refreshTopBarActiveChat(); void uploadFile(const QByteArray &fileContent, SendMediaType type); bool confirmSendingFiles( diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index f1c14871a..9c5f32ca5 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -100,8 +100,10 @@ ScheduledWidget::ScheduledWidget( ComposeControls::Mode::Scheduled)) , _scrollDown(_scroll, st::historyToDown) { _topBar->setActiveChat( - _history, - TopBarWidget::Section::Scheduled, + TopBarWidget::ActiveChat{ + .key = _history, + .section = TopBarWidget::Section::Scheduled, + }, nullptr); _topBar->move(0, 0); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index a4edc14ef..432694aaa 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -115,7 +115,7 @@ TopBarWidget::TopBarWidget( using AnimationUpdate = Data::Session::SendActionAnimationUpdate; session().data().sendActionAnimationUpdated( ) | rpl::filter([=](const AnimationUpdate &update) { - return (update.history == _activeChat.history()); + return (update.history == _activeChat.key.history()); }) | rpl::start_with_next([=] { update(); }, lifetime()); @@ -191,13 +191,13 @@ void TopBarWidget::refreshLang() { } void TopBarWidget::onSearch() { - if (_activeChat) { - _controller->content()->searchInChat(_activeChat); + if (_activeChat.key) { + _controller->content()->searchInChat(_activeChat.key); } } void TopBarWidget::onCall() { - if (const auto peer = _activeChat.peer()) { + if (const auto peer = _activeChat.key.peer()) { if (const auto user = peer->asUser()) { Core::App().calls().startOutgoingCall(user, false); } @@ -205,7 +205,7 @@ void TopBarWidget::onCall() { } void TopBarWidget::showMenu() { - if (!_activeChat || _menu) { + if (!_activeChat.key || _menu) { return; } _menu.create(parentWidget()); @@ -228,27 +228,31 @@ void TopBarWidget::showMenu() { })); _menuToggle->installEventFilter(_menu); const auto addAction = [&]( - const QString & text, + const QString &text, Fn callback) { return _menu->addAction(text, std::move(callback)); }; - if (const auto peer = _activeChat.peer()) { + if (const auto peer = _activeChat.key.peer()) { + using Source = Window::PeerMenuRequest::Source; + const auto source = (_activeChat.section == Section::Scheduled) + ? Source::ScheduledSection + : (_activeChat.section == Section::Replies) + ? Source::RepliesSection + : Source::History; Window::FillPeerMenu( _controller, - peer, - FilterId(), - addAction, - (_section == Section::Scheduled - ? Window::PeerMenuSource::ScheduledSection - : (_section == Section::Replies) - ? Window::PeerMenuSource::RepliesSection - : Window::PeerMenuSource::History)); - } else if (const auto folder = _activeChat.folder()) { + Window::PeerMenuRequest{ + .peer = peer, + .source = source, + .rootId = _activeChat.rootId, + .currentReplyToId = _activeChat.currentReplyToId, + }, + addAction); + } else if (const auto folder = _activeChat.key.folder()) { Window::FillFolderMenu( _controller, - folder, - addAction, - Window::PeerMenuSource::History); + Window::FolderMenuRequest{ folder }, + addAction); } else { Unexpected("Empty active chat in TopBarWidget::showMenu."); } @@ -265,13 +269,13 @@ void TopBarWidget::toggleInfoSection() { && (Core::App().settings().thirdSectionInfoEnabled() || Core::App().settings().tabbedReplacedWithInfo())) { _controller->closeThirdSection(); - } else if (_activeChat.peer()) { + } else if (_activeChat.key.peer()) { if (_controller->canShowThirdSection()) { Core::App().settings().setThirdSectionInfoEnabled(true); Core::App().saveSettingsDelayed(); if (Adaptive::ThreeColumn()) { _controller->showSection( - Info::Memento::Default(_activeChat.peer()), + Info::Memento::Default(_activeChat.key.peer()), Window::SectionShow().withThirdColumn()); } else { _controller->resizeForThirdSection(); @@ -325,7 +329,7 @@ void TopBarWidget::paintEvent(QPaintEvent *e) { } void TopBarWidget::paintTopBar(Painter &p) { - if (!_activeChat) { + if (!_activeChat.key) { return; } auto nameleft = _leftTaken; @@ -333,18 +337,18 @@ void TopBarWidget::paintTopBar(Painter &p) { auto statustop = st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height; auto availableWidth = width() - _rightTaken - nameleft; - const auto history = _activeChat.history(); - const auto folder = _activeChat.folder(); + const auto history = _activeChat.key.history(); + const auto folder = _activeChat.key.folder(); if (folder || history->peer->sharedMediaInfo() - || (_section == Section::Scheduled) - || (_section == Section::Pinned)) { + || (_activeChat.section == Section::Scheduled) + || (_activeChat.section == Section::Pinned)) { // #TODO feed name emoji. - auto text = (_section == Section::Scheduled) + auto text = (_activeChat.section == Section::Scheduled) ? ((history && history->peer->isSelf()) ? tr::lng_reminder_messages(tr::now) : tr::lng_scheduled_messages(tr::now)) - : (_section == Section::Pinned) + : (_activeChat.section == Section::Pinned) ? _customTitleText : folder ? folder->chatListName() @@ -362,7 +366,7 @@ void TopBarWidget::paintTopBar(Painter &p) { (height() - st::historySavedFont->height) / 2, width(), text); - } else if (_section == Section::Replies) { + } else if (_activeChat.section == Section::Replies) { p.setPen(st::dialogsNameFg); p.setFont(st::semiboldFont); p.drawTextLeft( @@ -384,7 +388,7 @@ void TopBarWidget::paintTopBar(Painter &p) { p.setPen(st::historyStatusFg); p.drawTextLeft(nameleft, statustop, width(), _customTitleText); } - } else if (const auto history = _activeChat.history()) { + } else if (const auto history = _activeChat.key.history()) { const auto peer = history->peer; const auto &text = peer->topBarNameText(); const auto badgeStyle = Ui::PeerBadgeStyle{ @@ -483,29 +487,30 @@ void TopBarWidget::mousePressEvent(QMouseEvent *e) { } void TopBarWidget::infoClicked() { - if (!_activeChat) { + const auto key = _activeChat.key; + if (!key) { return; - } else if (_activeChat.folder()) { + } else if (key.folder()) { _controller->closeFolder(); //} else if (const auto feed = _activeChat.feed()) { // #feed // _controller->showSection(Info::Memento( // feed, // Info::Section(Info::Section::Type::Profile))); - } else if (_activeChat.peer()->isSelf()) { + } else if (key.peer()->isSelf()) { _controller->showSection(Info::Memento( - _activeChat.peer(), + key.peer(), Info::Section(Storage::SharedMediaType::Photo))); - } else if (_activeChat.peer()->isRepliesChat()) { + } else if (key.peer()->isRepliesChat()) { _controller->showSection(Info::Memento( - _activeChat.peer(), + key.peer(), Info::Section(Storage::SharedMediaType::Photo))); } else { - _controller->showPeerInfo(_activeChat.peer()); + _controller->showPeerInfo(key.peer()); } } void TopBarWidget::backClicked() { - if (_activeChat.folder()) { + if (_activeChat.key.folder()) { _controller->closeFolder(); } else { _controller->showBackFromStack(); @@ -513,14 +518,14 @@ void TopBarWidget::backClicked() { } void TopBarWidget::setActiveChat( - Dialogs::Key chat, - Section section, + ActiveChat activeChat, SendActionPainter *sendAction) { - if (_activeChat == chat && _section == section) { + if (_activeChat.key == activeChat.key + && _activeChat.section == activeChat.section) { + _activeChat = activeChat; return; } - _activeChat = chat; - _section = section; + _activeChat = activeChat; _sendAction = sendAction; _back->clearState(); update(); @@ -544,7 +549,7 @@ void TopBarWidget::setCustomTitle(const QString &title) { } void TopBarWidget::refreshInfoButton() { - if (const auto peer = _activeChat.peer()) { + if (const auto peer = _activeChat.key.peer()) { auto info = object_ptr( this, _controller, @@ -577,14 +582,14 @@ int TopBarWidget::countSelectedButtonsTop(float64 selectedShown) { } void TopBarWidget::updateSearchVisibility() { - const auto historyMode = (_section == Section::History); - const auto smallDialogsColumn = _activeChat.folder() + const auto historyMode = (_activeChat.section == Section::History); + const auto smallDialogsColumn = _activeChat.key.folder() && (width() < _back->width() + _search->width()); _search->setVisible(historyMode && !smallDialogsColumn); } void TopBarWidget::updateControlsGeometry() { - if (!_activeChat) { + if (!_activeChat.key) { return; } auto hasSelected = (_selectedCount > 0); @@ -621,7 +626,7 @@ void TopBarWidget::updateControlsGeometry() { if (_back->isHidden()) { _leftTaken = st::topBarArrowPadding.right(); } else { - const auto smallDialogsColumn = _activeChat.folder() + const auto smallDialogsColumn = _activeChat.key.folder() && (width() < _back->width() + _search->width()); _leftTaken = smallDialogsColumn ? (width() - _back->width()) / 2 : 0; _back->moveToLeft(_leftTaken, otherButtonsTop); @@ -666,7 +671,7 @@ void TopBarWidget::setAnimatingMode(bool enabled) { } void TopBarWidget::updateControlsVisibility() { - if (!_activeChat) { + if (!_activeChat.key) { return; } else if (_animatingMode) { hideChildren(); @@ -679,7 +684,7 @@ void TopBarWidget::updateControlsVisibility() { auto backVisible = Adaptive::OneColumn() || !_controller->content()->stackIsEmpty() - || _activeChat.folder(); + || _activeChat.key.folder(); _back->setVisible(backVisible); if (_info) { _info->setVisible(Adaptive::OneColumn()); @@ -687,21 +692,22 @@ void TopBarWidget::updateControlsVisibility() { if (_unreadBadge) { _unreadBadge->show(); } - const auto historyMode = (_section == Section::History); - const auto hasPollsMenu = _activeChat.peer() - && _activeChat.peer()->canSendPolls(); - const auto hasMenu = !_activeChat.folder() - && ((_section == Section::Scheduled || _section == Section::Replies) + const auto section = _activeChat.section; + const auto historyMode = (section == Section::History); + const auto hasPollsMenu = _activeChat.key.peer() + && _activeChat.key.peer()->canSendPolls(); + const auto hasMenu = !_activeChat.key.folder() + && ((section == Section::Scheduled || section == Section::Replies) ? hasPollsMenu : historyMode); updateSearchVisibility(); _menuToggle->setVisible(hasMenu); _infoToggle->setVisible(historyMode - && !_activeChat.folder() + && !_activeChat.key.folder() && !Adaptive::OneColumn() && _controller->canShowThirdSection()); const auto callsEnabled = [&] { - if (const auto peer = _activeChat.peer()) { + if (const auto peer = _activeChat.key.peer()) { if (const auto user = peer->asUser()) { return session().serverConfig().phoneCallsEnabled.current() && user->hasCalls(); @@ -719,7 +725,7 @@ void TopBarWidget::updateControlsVisibility() { void TopBarWidget::updateMembersShowArea() { const auto membersShowAreaNeeded = [&] { - const auto peer = _activeChat.peer(); + const auto peer = _activeChat.key.peer(); if ((_selectedCount > 0) || !peer) { return false; } else if (const auto chat = peer->asChat()) { @@ -805,7 +811,7 @@ void TopBarWidget::updateAdaptiveLayout() { } void TopBarWidget::refreshUnreadBadge() { - if (!Adaptive::OneColumn() && !_activeChat.folder()) { + if (!Adaptive::OneColumn() && !_activeChat.key.folder()) { _unreadBadge.destroy(); return; } else if (_unreadBadge) { @@ -834,8 +840,9 @@ void TopBarWidget::refreshUnreadBadge() { void TopBarWidget::updateUnreadBadge() { if (!_unreadBadge) return; - const auto muted = session().data().unreadBadgeMutedIgnoreOne(_activeChat); - const auto counter = session().data().unreadBadgeIgnoreOne(_activeChat); + const auto key = _activeChat.key; + const auto muted = session().data().unreadBadgeMutedIgnoreOne(key); + const auto counter = session().data().unreadBadgeIgnoreOne(key); const auto text = [&] { if (!counter) { return QString(); @@ -862,12 +869,15 @@ void TopBarWidget::updateInfoToggleActive() { } void TopBarWidget::updateOnlineDisplay() { - if (!_activeChat.peer()) return; + const auto peer = _activeChat.key.peer(); + if (!peer) { + return; + } QString text; const auto now = base::unixtime::now(); bool titlePeerTextOnline = false; - if (const auto user = _activeChat.peer()->asUser()) { + if (const auto user = peer->asUser()) { if (session().supportMode() && !session().supportHelper().infoCurrent(user).text.empty()) { text = QString::fromUtf8("\xe2\x9a\xa0\xef\xb8\x8f check info"); @@ -876,7 +886,7 @@ void TopBarWidget::updateOnlineDisplay() { text = Data::OnlineText(user, now); titlePeerTextOnline = Data::OnlineTextActive(user, now); } - } else if (const auto chat = _activeChat.peer()->asChat()) { + } else if (const auto chat = peer->asChat()) { if (!chat->amIn()) { text = tr::lng_chat_status_unaccessible(tr::now); } else if (chat->participants.empty()) { @@ -907,7 +917,7 @@ void TopBarWidget::updateOnlineDisplay() { text = tr::lng_group_status(tr::now); } } - } else if (const auto channel = _activeChat.peer()->asChannel()) { + } else if (const auto channel = peer->asChannel()) { if (channel->isMegagroup() && (channel->membersCount() > 0) && (channel->membersCount() @@ -954,7 +964,10 @@ void TopBarWidget::updateOnlineDisplay() { } void TopBarWidget::updateOnlineDisplayTimer() { - if (!_activeChat.peer()) return; + const auto peer = _activeChat.key.peer(); + if (!peer) { + return; + } const auto now = base::unixtime::now(); auto minTimeout = crl::time(86400); @@ -962,13 +975,13 @@ void TopBarWidget::updateOnlineDisplayTimer() { auto hisTimeout = Data::OnlineChangeTimeout(user, now); accumulate_min(minTimeout, hisTimeout); }; - if (const auto user = _activeChat.peer()->asUser()) { + if (const auto user = peer->asUser()) { handleUser(user); - } else if (auto chat = _activeChat.peer()->asChat()) { + } else if (const auto chat = peer->asChat()) { for (const auto user : chat->participants) { handleUser(user); } - } else if (_activeChat.peer()->isChannel()) { + } else if (peer->isChannel()) { } updateOnlineDisplayIn(minTimeout); } diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index 770929d0e..bff509316 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -45,10 +45,17 @@ public: }; enum class Section { History, + Dialogs, // For folder view in dialogs list. Scheduled, Pinned, Replies, }; + struct ActiveChat { + Dialogs::Key key; + Section section = Section::History; + MsgId rootId = 0; + MsgId currentReplyToId = 0; + }; TopBarWidget( QWidget *parent, @@ -66,8 +73,7 @@ public: void setAnimatingMode(bool enabled); void setActiveChat( - Dialogs::Key chat, - Section section, + ActiveChat activeChat, SendActionPainter *sendAction); void setCustomTitle(const QString &title); @@ -131,8 +137,7 @@ private: void updateUnreadBadge(); const not_null _controller; - Dialogs::Key _activeChat; - Section _section = Section::History; + ActiveChat _activeChat; QString _customTitleText; int _selectedCount = 0; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 4190ed4a8..023742cec 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -577,10 +577,11 @@ void WrapWidget::showTopBarMenu() { if (const auto peer = key().peer()) { Window::FillPeerMenu( _controller->parentController(), - peer, - FilterId(), - addAction, - Window::PeerMenuSource::Profile); + Window::PeerMenuRequest{ + .peer = peer, + .source = Window::PeerMenuRequest::Source::Profile, + }, + addAction); //} else if (const auto feed = key().feed()) { // #feed // Window::FillFeedMenu( // _controller->parentController(), diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index e74d2118d..8c67fcfcc 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -819,17 +819,6 @@ crl::time MainWidget::highlightStartTime(not_null item) cons return _history->highlightStartTime(item); } -MsgId MainWidget::currentReplyToIdFor(not_null history) const { - if (_mainSection) { - return _mainSection->currentReplyToIdFor(history); - } else if (_history->history() == history) { - return _history->replyToId(); - } else if (const auto localDraft = history->localDraft()) { - return localDraft->msgId; - } - return 0; -} - void MainWidget::sendBotCommand( not_null peer, UserData *bot, diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index c2d0e6adc..cca639af3 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -175,8 +175,6 @@ public: // While HistoryInner is not HistoryView::ListWidget. crl::time highlightStartTime(not_null item) const; - MsgId currentReplyToIdFor(not_null history) const; - void sendBotCommand( not_null peer, UserData *bot, diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index 664fe9710..6d5f3b7fd 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -128,10 +128,6 @@ public: virtual bool replyToMessage(not_null item) { return false; } - [[nodiscard]] virtual MsgId currentReplyToIdFor( - not_null history) const { - return 0; - } // Create a memento of that section to store it in the history stack. // This method may modify the section ("take" heavy items). diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 90088b65e..fd25686f4 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -107,13 +107,13 @@ class Filler { public: Filler( not_null controller, - not_null peer, - FilterId filterId, - const PeerMenuCallback &addAction, - PeerMenuSource source); + PeerMenuRequest request, + const PeerMenuCallback &addAction); void fill(); private: + using Source = PeerMenuRequest::Source; + [[nodiscard]] bool showInfo(); [[nodiscard]] bool showHidePromotion(); [[nodiscard]] bool showToggleArchived(); @@ -132,10 +132,9 @@ private: void addPollAction(not_null peer); not_null _controller; + PeerMenuRequest _request; not_null _peer; - FilterId _filterId = 0; const PeerMenuCallback &_addAction; - PeerMenuSource _source; }; @@ -143,9 +142,8 @@ class FolderFiller { public: FolderFiller( not_null controller, - not_null folder, - const PeerMenuCallback &addAction, - PeerMenuSource source); + FolderMenuRequest request, + const PeerMenuCallback &addAction); void fill(); private: @@ -158,9 +156,9 @@ private: //void addUngroup(); not_null _controller; + FolderMenuRequest _request; not_null _folder; const PeerMenuCallback &_addAction; - PeerMenuSource _source; }; @@ -279,19 +277,16 @@ void TogglePinnedDialog( Filler::Filler( not_null controller, - not_null peer, - FilterId filterId, - const PeerMenuCallback &addAction, - PeerMenuSource source) + PeerMenuRequest request, + const PeerMenuCallback &addAction) : _controller(controller) -, _peer(peer) -, _filterId(filterId) -, _addAction(addAction) -, _source(source) { +, _request(request) +, _peer(request.peer) +, _addAction(addAction) { } bool Filler::showInfo() { - if (_source == PeerMenuSource::Profile + if (_request.source == Source::Profile || _peer->isSelf() || _peer->isRepliesChat()) { return false; @@ -307,7 +302,7 @@ bool Filler::showInfo() { } bool Filler::showHidePromotion() { - if (_source != PeerMenuSource::ChatsList) { + if (_request.source != Source::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -317,7 +312,7 @@ bool Filler::showHidePromotion() { } bool Filler::showToggleArchived() { - if (_source != PeerMenuSource::ChatsList) { + if (_request.source != Source::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -330,7 +325,7 @@ bool Filler::showToggleArchived() { } bool Filler::showTogglePin() { - if (_source != PeerMenuSource::ChatsList) { + if (_request.source != Source::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -349,7 +344,7 @@ void Filler::addHidePromotion() { void Filler::addTogglePin() { const auto controller = _controller; - const auto filterId = _filterId; + const auto filterId = _request.filterId; const auto peer = _peer; const auto history = peer->owner().history(peer); const auto pinText = [=] { @@ -479,7 +474,7 @@ void Filler::addBlockUser(not_null user) { void Filler::addUserActions(not_null user) { const auto controller = _controller; const auto window = &_controller->window(); - if (_source != PeerMenuSource::ChatsList) { + if (_request.source != Source::ChatsList) { if (user->session().supportMode()) { _addAction("Edit support info", [=] { user->session().supportHelper().editInfo(controller, user); @@ -527,13 +522,13 @@ void Filler::addUserActions(not_null user) { if (!user->isInaccessible() && user != user->session().user() && !user->isRepliesChat() - && _source != PeerMenuSource::ChatsList) { + && _request.source != Source::ChatsList) { addBlockUser(user); } } void Filler::addChatActions(not_null chat) { - if (_source != PeerMenuSource::ChatsList) { + if (_request.source != Source::ChatsList) { const auto controller = _controller; if (EditPeerInfoBox::Available(chat)) { const auto text = tr::lng_manage_group_title(tr::now); @@ -573,7 +568,7 @@ void Filler::addChannelActions(not_null channel) { // [=] { ToggleChannelGrouping(channel, !grouped); }); // } //} - if (_source != PeerMenuSource::ChatsList) { + if (_request.source != Source::ChatsList) { if (channel->isBroadcast()) { if (const auto chat = channel->linkedChat()) { _addAction(tr::lng_profile_view_discussion(tr::now), [=] { @@ -623,7 +618,7 @@ void Filler::addChannelActions(not_null channel) { text, [=] { channel->session().api().joinChannel(channel); }); } - if (_source != PeerMenuSource::ChatsList) { + if (_request.source != Source::ChatsList) { const auto needReport = !channel->amCreator() && (!isGroup || channel->isPublic()); if (needReport) { @@ -639,19 +634,26 @@ void Filler::addPollAction(not_null peer) { return; } const auto controller = _controller; - const auto source = (_source == PeerMenuSource::ScheduledSection) + const auto source = (_request.source == Source::ScheduledSection) ? Api::SendType::Scheduled : Api::SendType::Normal; const auto flag = PollData::Flags(); + const auto replyToId = _request.currentReplyToId; auto callback = [=] { - PeerMenuCreatePoll(controller, peer, flag, flag, source); + PeerMenuCreatePoll( + controller, + peer, + replyToId, + flag, + flag, + source); }; _addAction(tr::lng_polls_create(tr::now), std::move(callback)); } void Filler::fill() { - if (_source == PeerMenuSource::ScheduledSection - || _source == PeerMenuSource::RepliesSection) { + if (_request.source == Source::ScheduledSection + || _request.source == Source::RepliesSection) { addPollAction(_peer); return; } @@ -667,10 +669,10 @@ void Filler::fill() { if (showInfo()) { addInfo(); } - if (_source != PeerMenuSource::Profile && !_peer->isSelf()) { + if (_request.source != Source::Profile && !_peer->isSelf()) { PeerMenuAddMuteAction(_peer, _addAction); } - if (_source == PeerMenuSource::ChatsList) { + if (_request.source == Source::ChatsList) { //addSearch(); addToggleUnreadMark(); } @@ -686,19 +688,16 @@ void Filler::fill() { FolderFiller::FolderFiller( not_null controller, - not_null folder, - const PeerMenuCallback &addAction, - PeerMenuSource source) + FolderMenuRequest request, + const PeerMenuCallback &addAction) : _controller(controller) -, _folder(folder) -, _addAction(addAction) -, _source(source) { +, _request(request) +, _folder(request.folder) +, _addAction(addAction) { } void FolderFiller::fill() { - if (_source == PeerMenuSource::ChatsList) { - addTogglesForArchive(); - } + addTogglesForArchive(); } void FolderFiller::addTogglesForArchive() { @@ -838,6 +837,7 @@ void PeerMenuShareContactBox( void PeerMenuCreatePoll( not_null controller, not_null peer, + MsgId replyToId, PollData::Flags chosen, PollData::Flags disabled, Api::SendType sendType) { @@ -859,9 +859,7 @@ void PeerMenuCreatePoll( auto action = Api::SendAction(peer->owner().history(peer)); action.clearDraft = false; action.options = result.options; - if (const auto id = controller->content()->currentReplyToIdFor(action.history)) { - action.replyTo = id; - } + action.replyTo = replyToId; if (const auto localDraft = action.history->localDraft()) { action.clearDraft = localDraft->textWithTags.text.isEmpty(); } @@ -1314,20 +1312,17 @@ Fn DeleteAndLeaveHandler(not_null peer) { void FillPeerMenu( not_null controller, - not_null peer, - FilterId filterId, - const PeerMenuCallback &callback, - PeerMenuSource source) { - Filler filler(controller, peer, filterId, callback, source); + PeerMenuRequest request, + const PeerMenuCallback &callback) { + Filler filler(controller, request, callback); filler.fill(); } void FillFolderMenu( not_null controller, - not_null folder, - const PeerMenuCallback &callback, - PeerMenuSource source) { - FolderFiller filler(controller, folder, callback, source); + FolderMenuRequest request, + const PeerMenuCallback &callback) { + FolderFiller filler(controller, request, callback); filler.fill(); } diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index f5c9f1a2e..a7944da05 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -32,12 +32,24 @@ class Controller; class SessionController; class SessionNavigation; -enum class PeerMenuSource { - ChatsList, - History, - Profile, - ScheduledSection, - RepliesSection, +struct PeerMenuRequest { + enum class Source { + ChatsList, + History, + Profile, + ScheduledSection, + RepliesSection, + }; + + not_null peer; + Source source = Source::ChatsList; + FilterId filterId = 0; + MsgId rootId = 0; + MsgId currentReplyToId = 0; +}; + +struct FolderMenuRequest { + not_null folder; }; using PeerMenuCallback = Fn controller, - not_null peer, - FilterId filterId, - const PeerMenuCallback &addAction, - PeerMenuSource source); + PeerMenuRequest request, + const PeerMenuCallback &addAction); void FillFolderMenu( not_null controller, - not_null folder, - const PeerMenuCallback &addAction, - PeerMenuSource source); + FolderMenuRequest request, + const PeerMenuCallback &addAction); void PeerMenuAddMuteAction( not_null peer, @@ -80,6 +89,7 @@ void PeerMenuAddChannelMembers( void PeerMenuCreatePoll( not_null controller, not_null peer, + MsgId replyToId = 0, PollData::Flags chosen = PollData::Flags(), PollData::Flags disabled = PollData::Flags(), Api::SendType sendType = Api::SendType::Normal); From f04b3da76a1347c60f7ac6900d3ae7dda288b106 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 12 Nov 2020 18:46:17 +0300 Subject: [PATCH 095/370] Add return from bot switch_pm to Scheduled/Replies. --- Telegram/CMakeLists.txt | 2 + .../chat_helpers/tabbed_selector.h | 8 +- Telegram/SourceFiles/data/data_session.cpp | 2 +- Telegram/SourceFiles/data/data_user.h | 3 +- .../dialogs/dialogs_inner_widget.cpp | 17 +- Telegram/SourceFiles/dialogs/dialogs_key.h | 17 + .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- Telegram/SourceFiles/facades.cpp | 2 +- .../SourceFiles/history/history_widget.cpp | 94 ++- Telegram/SourceFiles/history/history_widget.h | 9 +- .../history_view_compose_controls.cpp | 21 +- .../controls/history_view_compose_controls.h | 4 + .../view/history_view_pinned_section.cpp | 2 +- .../view/history_view_replies_section.cpp | 16 +- .../view/history_view_scheduled_section.cpp | 12 +- .../view/history_view_top_bar_widget.cpp | 28 +- .../view/history_view_top_bar_widget.h | 15 +- .../SourceFiles/info/info_wrap_widget.cpp | 8 +- .../inline_bots/inline_bot_result.h | 8 + .../inline_bots/inline_results_inner.cpp | 769 +++++++++++++++++ .../inline_bots/inline_results_inner.h | 186 +++++ .../inline_bots/inline_results_widget.cpp | 779 +----------------- .../inline_bots/inline_results_widget.h | 163 +--- .../SourceFiles/window/window_peer_menu.cpp | 101 +-- .../SourceFiles/window/window_peer_menu.h | 29 +- 25 files changed, 1158 insertions(+), 1139 deletions(-) create mode 100644 Telegram/SourceFiles/inline_bots/inline_results_inner.cpp create mode 100644 Telegram/SourceFiles/inline_bots/inline_results_inner.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 2decd8466..b2b7236a3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -647,6 +647,8 @@ PRIVATE inline_bots/inline_bot_result.h inline_bots/inline_bot_send_data.cpp inline_bots/inline_bot_send_data.h + inline_bots/inline_results_inner.cpp + inline_bots/inline_results_inner.h inline_bots/inline_results_widget.cpp inline_bots/inline_results_widget.h intro/intro_code.cpp diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 2c1fa9f96..d20c67c1d 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" namespace InlineBots { -class Result; +struct ResultSelected; } // namespace InlineBots namespace Main { @@ -60,11 +60,7 @@ public: not_null photo; Api::SendOptions options; }; - struct InlineChosen { - not_null result; - not_null bot; - Api::SendOptions options; - }; + using InlineChosen = InlineBots::ResultSelected; enum class Mode { Full, EmojiOnly diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 8ae6619a8..f5fe90f92 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -89,7 +89,7 @@ void CheckForSwitchInlineButton(not_null item) { return; } if (const auto user = item->history()->peer->asUser()) { - if (!user->isBot() || !user->botInfo->inlineReturnPeerId) { + if (!user->isBot() || !user->botInfo->inlineReturnTo.key) { return; } if (const auto markup = item->Get()) { diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index bff3be7ec..39665a968 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_peer.h" +#include "dialogs/dialogs_key.h" class BotCommand { public: @@ -34,7 +35,7 @@ struct BotInfo { Ui::Text::String text = { int(st::msgMinWidth) }; // description QString startToken, startGroupToken, shareGameShortName; - PeerId inlineReturnPeerId = 0; + Dialogs::EntryState inlineReturnTo; }; class UserData : public PeerData { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 86066c2e0..169d13e83 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1814,24 +1814,17 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { } else { fillArchiveSearchMenu(_menu.get()); } - } else if (const auto history = row.key.history()) { - Window::FillPeerMenu( + } else { + Window::FillDialogsEntryMenu( _controller, - Window::PeerMenuRequest{ - .peer = history->peer, - .source = Window::PeerMenuRequest::Source::ChatsList, + Dialogs::EntryState{ + .key = row.key, + .section = Dialogs::EntryState::Section::ChatsList, .filterId = _filterId, }, [&](const QString &text, Fn callback) { return _menu->addAction(text, std::move(callback)); }); - } else if (const auto folder = row.key.folder()) { - Window::FillFolderMenu( - _controller, - Window::FolderMenuRequest{ folder }, - [&](const QString &text, Fn callback) { - return _menu->addAction(text, std::move(callback)); - }); } connect(_menu.get(), &QObject::destroyed, [=] { if (_menuRow.key) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 223488d21..2f5967b45 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -109,4 +109,21 @@ inline bool operator>=(const RowDescriptor &a, const RowDescriptor &b) { return !(a < b); } +struct EntryState { + enum class Section { + History, + Profile, + ChatsList, + Scheduled, + Pinned, + Replies, + }; + + Key key; + Section section = Section::History; + FilterId filterId = 0; + MsgId rootId = 0; + MsgId currentReplyToId = 0; +}; + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index c41aeb2d3..624e67f6c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -525,7 +525,7 @@ void Widget::refreshFolderTopBar() { _folderTopBar->setActiveChat( HistoryView::TopBarWidget::ActiveChat{ .key = _openedFolder, - .section = HistoryView::TopBarWidget::Section::Dialogs, + .section = Dialogs::EntryState::Section::ChatsList, }, nullptr); } else { diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index c672f9072..bc542e29e 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -191,7 +191,7 @@ void activateBotCommand( if (samePeer) { Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data), bot, msg->id); return true; - } else if (bot->isBot() && bot->botInfo->inlineReturnPeerId) { + } else if (bot->isBot() && bot->botInfo->inlineReturnTo.key) { if (Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data))) { return true; } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 122792c35..6de4d1f96 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -707,14 +707,20 @@ void HistoryWidget::setGeometryWithTopMoved( _topDelta = 0; } +Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const { + return Dialogs::EntryState{ + .key = _history, + .section = Dialogs::EntryState::Section::History, + .currentReplyToId = replyToId(), + }; +} + void HistoryWidget::refreshTopBarActiveChat() { - _topBar->setActiveChat( - HistoryView::TopBarWidget::ActiveChat{ - .key = _history, - .section = HistoryView::TopBarWidget::Section::History, - .currentReplyToId = replyToId(), - }, - _history->sendActionPainter()); + const auto state = computeDialogsEntryState(); + _topBar->setActiveChat(state, _history->sendActionPainter()); + if (_inlineResults) { + _inlineResults->setCurrentDialogsEntryState(state); + } } void HistoryWidget::refreshTabbedPanel() { @@ -830,7 +836,7 @@ void HistoryWidget::initTabbedSelector() { ) | rpl::filter([=] { return !isHidden(); }) | rpl::start_with_next([=](TabbedSelector::InlineChosen data) { - sendInlineResult(data.result, data.bot, data.options); + sendInlineResult(data); }, lifetime()); selector->setSendMenuType([=] { return sendMenuType(); }); @@ -1195,11 +1201,11 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { if (!_inlineResults) { _inlineResults.create(this, controller()); _inlineResults->setResultSelectedCallback([=]( - InlineBots::Result *result, - UserData *bot, - Api::SendOptions options) { - sendInlineResult(result, bot, options); + InlineBots::ResultSelected result) { + sendInlineResult(result); }); + _inlineResults->setCurrentDialogsEntryState( + computeDialogsEntryState()); _inlineResults->requesting( ) | rpl::start_with_next([=](bool requesting) { _tabbedSelectorToggle->setLoading(requesting); @@ -1460,22 +1466,37 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U applyDraft(); return true; } - } else if (auto bot = _peer ? _peer->asUser() : nullptr) { - const auto toPeerId = bot->isBot() - ? bot->botInfo->inlineReturnPeerId - : PeerId(0); - if (!toPeerId) { + } else if (const auto bot = _peer ? _peer->asUser() : nullptr) { + const auto to = bot->isBot() + ? bot->botInfo->inlineReturnTo + : Dialogs::EntryState(); + const auto history = to.key.history(); + if (!history) { return false; } - bot->botInfo->inlineReturnPeerId = 0; - const auto h = bot->owner().history(toPeerId); + bot->botInfo->inlineReturnTo = Dialogs::EntryState(); + using Section = Dialogs::EntryState::Section; + TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() }; MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX }; - h->setLocalDraft(std::make_unique(textWithTags, 0, cursor, false)); - if (h == _history) { - applyDraft(); + auto draft = std::make_unique( + textWithTags, + to.currentReplyToId, + cursor, + false); + + if (to.section == Section::Replies) { + controller()->showRepliesForMessage(history, to.rootId); } else { - Ui::showPeerHistory(h->peer, ShowAtUnreadMsgId); + history->setLocalDraft(std::move(draft)); + if (to.section == Section::Scheduled) { + controller()->showSection( + HistoryView::ScheduledMemento(history)); + } else if (history == _history) { + applyDraft(); + } else { + Ui::showPeerHistory(history->peer, ShowAtUnreadMsgId); + } } return true; } @@ -1657,9 +1678,7 @@ void HistoryWidget::showHistory( _pinnedClickedId = FullMsgId(); _minPinnedId = std::nullopt; - MsgId wasMsgId = _showAtMsgId; - History *wasHistory = _history; - + const auto wasDialogsEntryState = computeDialogsEntryState(); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); if (startBot) { showAtMsgId = ShowAtTheEndMsgId; @@ -1719,8 +1738,8 @@ void HistoryWidget::showHistory( if (const auto user = _peer->asUser()) { if (const auto &info = user->botInfo) { if (startBot) { - if (wasHistory) { - info->inlineReturnPeerId = wasHistory->peer->id; + if (wasDialogsEntryState.key) { + info->inlineReturnTo = wasDialogsEntryState; } sendBotStartCommand(); _history->clearLocalDraft(); @@ -1894,8 +1913,8 @@ void HistoryWidget::showHistory( if (const auto user = _peer->asUser()) { if (const auto &info = user->botInfo) { if (startBot) { - if (wasHistory) { - info->inlineReturnPeerId = wasHistory->peer->id; + if (wasDialogsEntryState.key) { + info->inlineReturnTo = wasDialogsEntryState; } sendBotStartCommand(); } @@ -5078,17 +5097,14 @@ void HistoryWidget::onFieldTabbed() { } } -void HistoryWidget::sendInlineResult( - not_null result, - not_null bot, - Api::SendOptions options) { +void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) { if (!_peer || !_peer->canWrite()) { return; } else if (showSlowmodeError()) { return; } - auto errorText = result->getErrorOnSend(_history); + auto errorText = result.result->getErrorOnSend(_history); if (!errorText.isEmpty()) { Ui::show(Box(errorText)); return; @@ -5096,9 +5112,9 @@ void HistoryWidget::sendInlineResult( auto action = Api::SendAction(_history); action.replyTo = replyToId(); - action.options = std::move(options); + action.options = std::move(result.options); action.generateLocal = true; - session().api().sendInlineResult(bot, result, action); + session().api().sendInlineResult(result.bot, result.result, action); clearFieldText(); _saveDraftText = true; @@ -5106,14 +5122,14 @@ void HistoryWidget::sendInlineResult( onDraftSave(); auto &bots = cRefRecentInlineBots(); - const auto index = bots.indexOf(bot); + const auto index = bots.indexOf(result.bot); if (index) { if (index > 0) { bots.removeAt(index); } else if (bots.size() >= RecentInlineBotsLimit) { bots.resize(RecentInlineBotsLimit - 1); } - bots.push_front(bot); + bots.push_front(result.bot); session().local().writeRecentHashtagsAndBots(); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index b05e6fb36..bad3b769d 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -38,7 +38,7 @@ namespace Layout { class ItemBase; class Widget; } // namespace Layout -class Result; +struct ResultSelected; } // namespace InlineBots namespace Data { @@ -353,6 +353,8 @@ private: void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); void updateField(); + + [[nodiscard]] Dialogs::EntryState computeDialogsEntryState() const; void refreshTopBarActiveChat(); void requestMessageData(MsgId msgId); @@ -485,10 +487,7 @@ private: int wasScrollTop, int nowScrollTop); - void sendInlineResult( - not_null result, - not_null bot, - Api::SendOptions options); + void sendInlineResult(InlineBots::ResultSelected result); void drawField(Painter &p, const QRect &rect); void paintEditHeader( 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 97189d361..fd852a95d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/history_view_webpage_preview.h" #include "inline_bots/inline_results_widget.h" +#include "inline_bots/inline_bot_result.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "media/audio/media_audio_capture.h" @@ -583,6 +584,13 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { } } +void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { + _currentDialogsEntryState = state; + if (_inlineResults) { + _inlineResults->setCurrentDialogsEntryState(state); + } +} + void ComposeControls::move(int x, int y) { _wrap->move(x, y); _writeRestricted->move(x, y); @@ -1523,6 +1531,7 @@ bool ComposeControls::handleCancelRequest() { void ComposeControls::initWebpageProcess() { Expects(_history); + const auto peer = _history->peer; auto &lifetime = _wrap->lifetime(); const auto requestRepaint = crl::guard(_header.get(), [=] { @@ -1787,15 +1796,11 @@ void ComposeControls::applyInlineBotQuery( _inlineResults = std::make_unique( _parent, _window); + _inlineResults->setCurrentDialogsEntryState( + _currentDialogsEntryState); _inlineResults->setResultSelectedCallback([=]( - InlineBots::Result *result, - UserData *bot, - Api::SendOptions options) { - _inlineResultChosen.fire(InlineChosen{ - .result = result, - .bot = bot, - .options = options, - }); + InlineBots::ResultSelected result) { + _inlineResultChosen.fire_copy(result); }); _inlineResults->requesting( ) | rpl::start_with_next([=](bool requesting) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index b04a68b31..5b6200862 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/required.h" #include "api/api_common.h" #include "base/unique_qptr.h" +#include "dialogs/dialogs_key.h" #include "history/view/controls/compose_controls_common.h" #include "ui/rp_widget.h" #include "ui/effects/animations.h" @@ -87,6 +88,8 @@ public: [[nodiscard]] Main::Session &session() const; void setHistory(SetHistoryArgs &&args); + void setCurrentDialogsEntryState(Dialogs::EntryState state); + void finishAnimating(); void move(int x, int y); @@ -237,6 +240,7 @@ private: TextWithTags _localSavedText; TextUpdateEvents _textUpdateEvents; + Dialogs::EntryState _currentDialogsEntryState; //bool _inReplyEditForward = false; //bool _inClickable = false; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 2d8d2fa92..8ea4d512f 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -104,7 +104,7 @@ PinnedWidget::PinnedWidget( _topBar->setActiveChat( TopBarWidget::ActiveChat{ .key = _history, - .section = TopBarWidget::Section::Pinned, + .section = Dialogs::EntryState::Section::Pinned, }, nullptr); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 18fe048d0..b7135f1be 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1137,14 +1137,14 @@ SendMenu::Type RepliesWidget::sendMenuType() const { } void RepliesWidget::refreshTopBarActiveChat() { - _topBar->setActiveChat( - TopBarWidget::ActiveChat{ - .key = _history, - .section = TopBarWidget::Section::Replies, - .rootId = _rootId, - .currentReplyToId = replyToId(), - }, - _sendAction.get()); + const auto state = Dialogs::EntryState{ + .key = _history, + .section = Dialogs::EntryState::Section::Replies, + .rootId = _rootId, + .currentReplyToId = replyToId(), + }; + _topBar->setActiveChat(state, _sendAction.get()); + _composeControls->setCurrentDialogsEntryState(state); } MsgId RepliesWidget::replyToId() const { diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 9c5f32ca5..15741b1d8 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -99,12 +99,12 @@ ScheduledWidget::ScheduledWidget( controller, ComposeControls::Mode::Scheduled)) , _scrollDown(_scroll, st::historyToDown) { - _topBar->setActiveChat( - TopBarWidget::ActiveChat{ - .key = _history, - .section = TopBarWidget::Section::Scheduled, - }, - nullptr); + const auto state = Dialogs::EntryState{ + .key = _history, + .section = Dialogs::EntryState::Section::Scheduled, + }; + _topBar->setActiveChat(state, nullptr); + _composeControls->setCurrentDialogsEntryState(state); _topBar->move(0, 0); _topBar->resizeToWidth(width()); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 432694aaa..c5eee7f76 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -232,30 +232,10 @@ void TopBarWidget::showMenu() { Fn callback) { return _menu->addAction(text, std::move(callback)); }; - if (const auto peer = _activeChat.key.peer()) { - using Source = Window::PeerMenuRequest::Source; - const auto source = (_activeChat.section == Section::Scheduled) - ? Source::ScheduledSection - : (_activeChat.section == Section::Replies) - ? Source::RepliesSection - : Source::History; - Window::FillPeerMenu( - _controller, - Window::PeerMenuRequest{ - .peer = peer, - .source = source, - .rootId = _activeChat.rootId, - .currentReplyToId = _activeChat.currentReplyToId, - }, - addAction); - } else if (const auto folder = _activeChat.key.folder()) { - Window::FillFolderMenu( - _controller, - Window::FolderMenuRequest{ folder }, - addAction); - } else { - Unexpected("Empty active chat in TopBarWidget::showMenu."); - } + Window::FillDialogsEntryMenu( + _controller, + _activeChat, + addAction); if (_menu->actions().empty()) { _menu.destroy(); } else { diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index bff509316..988758b46 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -43,19 +43,8 @@ public: int canForwardCount = 0; int canSendNowCount = 0; }; - enum class Section { - History, - Dialogs, // For folder view in dialogs list. - Scheduled, - Pinned, - Replies, - }; - struct ActiveChat { - Dialogs::Key key; - Section section = Section::History; - MsgId rootId = 0; - MsgId currentReplyToId = 0; - }; + using ActiveChat = Dialogs::EntryState; + using Section = ActiveChat::Section; TopBarWidget( QWidget *parent, diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 023742cec..22ab43419 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -575,11 +575,11 @@ void WrapWidget::showTopBarMenu() { return _topBarMenu->addAction(text, std::move(callback)); }; if (const auto peer = key().peer()) { - Window::FillPeerMenu( + Window::FillDialogsEntryMenu( _controller->parentController(), - Window::PeerMenuRequest{ - .peer = peer, - .source = Window::PeerMenuRequest::Source::Profile, + Dialogs::EntryState{ + .key = peer->owner().history(peer), + .section = Dialogs::EntryState::Section::Profile, }, addAction); //} else if (const auto feed = key().feed()) { // #feed diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.h b/Telegram/SourceFiles/inline_bots/inline_bot_result.h index 8a086f47c..4969959dd 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.h @@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_cloud_file.h" +#include "api/api_common.h" class FileLoader; class History; +class UserData; namespace Data { class LocationPoint; @@ -125,4 +127,10 @@ private: }; +struct ResultSelected { + not_null result; + not_null bot; + Api::SendOptions options; +}; + } // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp new file mode 100644 index 000000000..6074ab1f3 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -0,0 +1,769 @@ +/* +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 "inline_bots/inline_results_inner.h" + +#include "api/api_common.h" +#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu +#include "data/data_file_origin.h" +#include "data/data_user.h" +#include "data/data_changes.h" +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_item.h" +#include "lang/lang_keys.h" +#include "mainwindow.h" +#include "facades.h" +#include "main/main_session.h" +#include "window/window_session_controller.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "history/view/history_view_cursor_state.h" +#include "styles/style_chat_helpers.h" + +#include + +namespace InlineBots { +namespace Layout { + +Inner::Inner( + QWidget *parent, + not_null controller) +: RpWidget(parent) +, _controller(controller) +, _updateInlineItems([=] { updateInlineItems(); }) +, _previewTimer([=] { showPreview(); }) { + resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, st::inlineResultsMinHeight); + + setMouseTracking(true); + setAttribute(Qt::WA_OpaquePaintEvent); + + _controller->session().downloaderTaskFinished( + ) | rpl::start_with_next([=] { + update(); + }, lifetime()); + + subscribe(controller->gifPauseLevelChanged(), [this] { + if (!_controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults)) { + update(); + } + }); + + _controller->session().changes().peerUpdates( + Data::PeerUpdate::Flag::Rights + ) | rpl::filter([=](const Data::PeerUpdate &update) { + return (update.peer.get() == _inlineQueryPeer); + }) | rpl::start_with_next([=] { + auto isRestricted = (_restrictedLabel != nullptr); + if (isRestricted != isRestrictedView()) { + auto h = countHeight(); + if (h != height()) resize(width(), h); + } + }, lifetime()); +} + +void Inner::visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) { + _visibleBottom = visibleBottom; + if (_visibleTop != visibleTop) { + _visibleTop = visibleTop; + _lastScrolled = crl::now(); + } +} + +void Inner::checkRestrictedPeer() { + if (_inlineQueryPeer) { + const auto error = Data::RestrictionError( + _inlineQueryPeer, + ChatRestriction::f_send_inline); + if (error) { + if (!_restrictedLabel) { + _restrictedLabel.create(this, *error, st::stickersRestrictedLabel); + _restrictedLabel->show(); + _restrictedLabel->move(st::inlineResultsLeft - st::buttonRadius, st::stickerPanPadding); + _restrictedLabel->resizeToNaturalWidth(width() - (st::inlineResultsLeft - st::buttonRadius) * 2); + if (_switchPmButton) { + _switchPmButton->hide(); + } + update(); + } + return; + } + } + if (_restrictedLabel) { + _restrictedLabel.destroy(); + if (_switchPmButton) { + _switchPmButton->show(); + } + update(); + } +} + +bool Inner::isRestrictedView() { + checkRestrictedPeer(); + return (_restrictedLabel != nullptr); +} + +int Inner::countHeight() { + if (isRestrictedView()) { + return st::stickerPanPadding + _restrictedLabel->height() + st::stickerPanPadding; + } else if (_rows.isEmpty() && !_switchPmButton) { + return st::stickerPanPadding + st::normalFont->height + st::stickerPanPadding; + } + auto result = st::stickerPanPadding; + if (_switchPmButton) { + result += _switchPmButton->height() + st::inlineResultsSkip; + } + for (int i = 0, l = _rows.count(); i < l; ++i) { + result += _rows[i].height; + } + return result + st::stickerPanPadding; +} + +QString Inner::tooltipText() const { + if (const auto lnk = ClickHandler::getActive()) { + return lnk->tooltip(); + } + return QString(); +} + +QPoint Inner::tooltipPos() const { + return _lastMousePos; +} + +bool Inner::tooltipWindowActive() const { + return Ui::AppInFocus() && Ui::InFocusChain(window()); +} + +rpl::producer<> Inner::inlineRowsCleared() const { + return _inlineRowsCleared.events(); +} + +Inner::~Inner() = default; + +void Inner::paintEvent(QPaintEvent *e) { + Painter p(this); + QRect r = e ? e->rect() : rect(); + if (r != rect()) { + p.setClipRect(r); + } + p.fillRect(r, st::emojiPanBg); + + paintInlineItems(p, r); +} + +void Inner::paintInlineItems(Painter &p, const QRect &r) { + if (_restrictedLabel) { + return; + } + if (_rows.isEmpty() && !_switchPmButton) { + p.setFont(st::normalFont); + p.setPen(st::noContactsColor); + p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), tr::lng_inline_bot_no_results(tr::now), style::al_center); + return; + } + auto gifPaused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults); + InlineBots::Layout::PaintContext context(crl::now(), false, gifPaused, false); + + auto top = st::stickerPanPadding; + if (_switchPmButton) { + top += _switchPmButton->height() + st::inlineResultsSkip; + } + + auto fromx = rtl() ? (width() - r.x() - r.width()) : r.x(); + auto tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); + for (auto row = 0, rows = _rows.size(); row != rows; ++row) { + auto &inlineRow = _rows[row]; + if (top >= r.top() + r.height()) break; + if (top + inlineRow.height > r.top()) { + auto left = st::inlineResultsLeft - st::buttonRadius; + if (row == rows - 1) context.lastRow = true; + for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { + if (left >= tox) break; + + auto item = inlineRow.items.at(col); + auto w = item->width(); + if (left + w > fromx) { + p.translate(left, top); + item->paint(p, r.translated(-left, -top), &context); + p.translate(-left, -top); + } + left += w; + if (item->hasRightSkip()) { + left += st::inlineResultsSkip; + } + } + } + top += inlineRow.height; + } +} + +void Inner::mousePressEvent(QMouseEvent *e) { + if (e->button() != Qt::LeftButton) { + return; + } + _lastMousePos = e->globalPos(); + updateSelected(); + + _pressed = _selected; + ClickHandler::pressed(); + _previewTimer.callOnce(QApplication::startDragTime()); +} + +void Inner::mouseReleaseEvent(QMouseEvent *e) { + _previewTimer.cancel(); + + auto pressed = std::exchange(_pressed, -1); + auto activated = ClickHandler::unpressed(); + + if (_previewShown) { + _previewShown = false; + return; + } + + _lastMousePos = e->globalPos(); + updateSelected(); + + if (_selected < 0 || _selected != pressed || !activated) { + return; + } + + if (dynamic_cast(activated.get())) { + int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; + selectInlineResult(row, column); + } else { + ActivateClickHandler(window(), activated, e->button()); + } +} + +void Inner::selectInlineResult(int row, int column) { + selectInlineResult(row, column, Api::SendOptions()); +} + +void Inner::selectInlineResult( + int row, + int column, + Api::SendOptions options) { + if (row >= _rows.size() || column >= _rows.at(row).items.size()) { + return; + } + + auto item = _rows[row].items[column]; + if (const auto inlineResult = item->getResult()) { + if (inlineResult->onChoose(item)) { + _resultSelectedCallback({ + .result = inlineResult, + .bot = _inlineBot, + .options = std::move(options) + }); + } + } +} + +void Inner::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); +} + +void Inner::leaveEventHook(QEvent *e) { + clearSelection(); + Ui::Tooltip::Hide(); +} + +void Inner::leaveToChildEvent(QEvent *e, QWidget *child) { + clearSelection(); +} + +void Inner::enterFromChildEvent(QEvent *e, QWidget *child) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void Inner::contextMenuEvent(QContextMenuEvent *e) { + if (_selected < 0 || _pressed >= 0) { + return; + } + const auto row = _selected / MatrixRowShift; + const auto column = _selected % MatrixRowShift; + const auto type = SendMenu::Type::Scheduled; + + _menu = base::make_unique_q(this); + + const auto send = [=](Api::SendOptions options) { + selectInlineResult(row, column, options); + }; + SendMenu::FillSendMenu( + _menu, + [&] { return type; }, + SendMenu::DefaultSilentCallback(send), + SendMenu::DefaultScheduleCallback(this, type, send)); + + if (!_menu->actions().empty()) { + _menu->popup(QCursor::pos()); + } +} + +void Inner::clearSelection() { + if (_selected >= 0) { + int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; + Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); + ClickHandler::clearActive(_rows.at(srow).items.at(scol)); + setCursor(style::cur_default); + } + _selected = _pressed = -1; + update(); +} + +void Inner::hideFinished() { + clearHeavyData(); +} + +void Inner::clearHeavyData() { + clearInlineRows(false); + for (const auto &[result, layout] : _inlineLayouts) { + layout->unloadHeavyPart(); + } +} + +bool Inner::inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth) { + auto layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); + if (!layout) return false; + + layout->preload(); + if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { + layout->setPosition(_rows.size() * MatrixRowShift); + } + + sumWidth += layout->maxWidth(); + if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { + sumWidth += st::inlineResultsSkip; + } + + row.items.push_back(layout); + return true; +} + +bool Inner::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { + if (row.items.isEmpty()) return false; + + auto full = (row.items.size() >= kInlineItemsMaxPerRow); + auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); + if (full || big || force) { + _rows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); + row = Row(); + row.items.reserve(kInlineItemsMaxPerRow); + sumWidth = 0; + return true; + } + return false; +} + +void Inner::inlineBotChanged() { + refreshInlineRows(nullptr, nullptr, nullptr, true); +} + +void Inner::clearInlineRows(bool resultsDeleted) { + if (resultsDeleted) { + _selected = _pressed = -1; + } else { + clearSelection(); + for_const (auto &row, _rows) { + for_const (auto &item, row.items) { + item->setPosition(-1); + } + } + } + _rows.clear(); +} + +ItemBase *Inner::layoutPrepareInlineResult(Result *result, int32 position) { + auto it = _inlineLayouts.find(result); + if (it == _inlineLayouts.cend()) { + if (auto layout = ItemBase::createLayout(this, result, _inlineWithThumb)) { + it = _inlineLayouts.emplace(result, std::move(layout)).first; + it->second->initDimensions(); + } else { + return nullptr; + } + } + if (!it->second->maxWidth()) { + return nullptr; + } + + it->second->setPosition(position); + return it->second.get(); +} + +void Inner::deleteUnusedInlineLayouts() { + if (_rows.isEmpty()) { // delete all + _inlineLayouts.clear(); + } else { + for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { + if (i->second->position() < 0) { + i = _inlineLayouts.erase(i); + } else { + ++i; + } + } + } +} + +Inner::Row &Inner::layoutInlineRow(Row &row, int32 sumWidth) { + auto count = int(row.items.size()); + Assert(count <= kInlineItemsMaxPerRow); + + // enumerate items in the order of growing maxWidth() + // for that sort item indices by maxWidth() + int indices[kInlineItemsMaxPerRow]; + for (auto i = 0; i != count; ++i) { + indices[i] = i; + } + std::sort(indices, indices + count, [&row](int a, int b) -> bool { + return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); + }); + + row.height = 0; + int availw = width() - (st::inlineResultsLeft - st::buttonRadius); + for (int i = 0; i < count; ++i) { + int index = indices[i]; + int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); + int actualw = qMax(w, int(st::inlineResultsMinWidth)); + row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); + if (sumWidth) { + availw -= actualw; + sumWidth -= row.items.at(index)->maxWidth(); + if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { + availw -= st::inlineResultsSkip; + sumWidth -= st::inlineResultsSkip; + } + } + } + return row; +} + +void Inner::preloadImages() { + for (auto row = 0, rows = _rows.size(); row != rows; ++row) { + for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { + _rows[row].items[col]->preload(); + } + } +} + +void Inner::hideInlineRowsPanel() { + clearInlineRows(false); +} + +void Inner::clearInlineRowsPanel() { + clearInlineRows(false); +} + +void Inner::refreshSwitchPmButton(const CacheEntry *entry) { + if (!entry || entry->switchPmText.isEmpty()) { + _switchPmButton.destroy(); + _switchPmStartToken.clear(); + } else { + if (!_switchPmButton) { + _switchPmButton.create(this, nullptr, st::switchPmButton); + _switchPmButton->show(); + _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + _switchPmButton->addClickHandler([=] { switchPm(); }); + } + _switchPmButton->setText(rpl::single(entry->switchPmText)); + _switchPmStartToken = entry->switchPmStartToken; + const auto buttonTop = st::stickerPanPadding; + _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); + if (isRestrictedView()) { + _switchPmButton->hide(); + } + } + update(); +} + +int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *entry, bool resultsDeleted) { + _inlineBot = bot; + _inlineQueryPeer = queryPeer; + refreshSwitchPmButton(entry); + auto clearResults = [&] { + if (!entry) { + return true; + } + if (entry->results.empty() && entry->switchPmText.isEmpty()) { + return true; + } + return false; + }; + auto clearResultsResult = clearResults(); // Clang workaround. + if (clearResultsResult) { + if (resultsDeleted) { + clearInlineRows(true); + deleteUnusedInlineLayouts(); + } + _inlineRowsCleared.fire({}); + return 0; + } + + clearSelection(); + + Assert(_inlineBot != 0); + + auto count = int(entry->results.size()); + auto from = validateExistingInlineRows(entry->results); + auto added = 0; + + if (count) { + _rows.reserve(count); + auto row = Row(); + row.items.reserve(kInlineItemsMaxPerRow); + auto sumWidth = 0; + for (auto i = from; i != count; ++i) { + if (inlineRowsAddItem(entry->results[i].get(), row, sumWidth)) { + ++added; + } + } + inlineRowFinalize(row, sumWidth, true); + } + + auto h = countHeight(); + if (h != height()) resize(width(), h); + update(); + + _lastMousePos = QCursor::pos(); + updateSelected(); + + return added; +} + +int Inner::validateExistingInlineRows(const Results &results) { + int count = results.size(), until = 0, untilrow = 0, untilcol = 0; + for (; until < count;) { + if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { + break; + } + ++until; + if (++untilcol == _rows[untilrow].items.size()) { + ++untilrow; + untilcol = 0; + } + } + if (until == count) { // all items are layed out + if (untilrow == _rows.size()) { // nothing changed + return until; + } + + for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { + for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { + if (skip) { + --skip; + } else { + _rows[i].items[j]->setPosition(-1); + } + } + } + if (!untilcol) { // all good rows are filled + _rows.resize(untilrow); + return until; + } + _rows.resize(untilrow + 1); + _rows[untilrow].items.resize(untilcol); + _rows[untilrow] = layoutInlineRow(_rows[untilrow]); + return until; + } + if (untilrow && !untilcol) { // remove last row, maybe it is not full + --untilrow; + untilcol = _rows[untilrow].items.size(); + } + until -= untilcol; + + for (int i = untilrow, l = _rows.size(); i < l; ++i) { + for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { + _rows[i].items[j]->setPosition(-1); + } + } + _rows.resize(untilrow); + + if (_rows.isEmpty()) { + _inlineWithThumb = false; + for (int i = until; i < count; ++i) { + if (results.at(i)->hasThumbDisplay()) { + _inlineWithThumb = true; + break; + } + } + } + return until; +} + +void Inner::inlineItemLayoutChanged(const ItemBase *layout) { + if (_selected < 0 || !isVisible()) { + return; + } + + int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; + if (row < _rows.size() && col < _rows.at(row).items.size()) { + if (layout == _rows.at(row).items.at(col)) { + updateSelected(); + } + } +} + +void Inner::inlineItemRepaint(const ItemBase *layout) { + auto ms = crl::now(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.callOnce(_lastScrolled + 100 - ms); + } +} + +bool Inner::inlineItemVisible(const ItemBase *layout) { + int32 position = layout->position(); + if (position < 0 || !isVisible()) { + return false; + } + + int row = position / MatrixRowShift, col = position % MatrixRowShift; + Assert((row < _rows.size()) && (col < _rows[row].items.size())); + + auto &inlineItems = _rows[row].items; + int top = st::stickerPanPadding; + for (int32 i = 0; i < row; ++i) { + top += _rows.at(i).height; + } + + return (top < _visibleBottom) && (top + _rows[row].items[col]->height() > _visibleTop); +} + +Data::FileOrigin Inner::inlineItemFileOrigin() { + return Data::FileOrigin(); +} + +void Inner::updateSelected() { + if (_pressed >= 0 && !_previewShown) { + return; + } + + auto newSelected = -1; + auto p = mapFromGlobal(_lastMousePos); + + int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); + int sy = p.y() - st::stickerPanPadding; + if (_switchPmButton) { + sy -= _switchPmButton->height() + st::inlineResultsSkip; + } + int row = -1, col = -1, sel = -1; + ClickHandlerPtr lnk; + ClickHandlerHost *lnkhost = nullptr; + HistoryView::CursorState cursor = HistoryView::CursorState::None; + if (sy >= 0) { + row = 0; + for (int rows = _rows.size(); row < rows; ++row) { + if (sy < _rows[row].height) { + break; + } + sy -= _rows[row].height; + } + } + if (sx >= 0 && row >= 0 && row < _rows.size()) { + auto &inlineItems = _rows[row].items; + col = 0; + for (int cols = inlineItems.size(); col < cols; ++col) { + int width = inlineItems.at(col)->width(); + if (sx < width) { + break; + } + sx -= width; + if (inlineItems.at(col)->hasRightSkip()) { + sx -= st::inlineResultsSkip; + } + } + if (col < inlineItems.size()) { + sel = row * MatrixRowShift + col; + auto result = inlineItems[col]->getState( + QPoint(sx, sy), + HistoryView::StateRequest()); + lnk = result.link; + cursor = result.cursor; + lnkhost = inlineItems[col]; + } else { + row = col = -1; + } + } else { + row = col = -1; + } + int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; + if (_selected != sel) { + if (srow >= 0 && scol >= 0) { + Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); + _rows[srow].items[scol]->update(); + } + _selected = sel; + if (row >= 0 && col >= 0) { + Assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows.at(row).items.size()); + _rows[row].items[col]->update(); + } + if (_previewShown && _selected >= 0 && _pressed != _selected) { + _pressed = _selected; + if (row >= 0 && col >= 0) { + auto layout = _rows.at(row).items.at(col); + if (const auto w = App::wnd()) { + if (const auto previewDocument = layout->getPreviewDocument()) { + w->showMediaPreview( + Data::FileOrigin(), + previewDocument); + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + w->showMediaPreview(Data::FileOrigin(), previewPhoto); + } + } + } + } + } + if (ClickHandler::setActive(lnk, lnkhost)) { + setCursor(lnk ? style::cur_pointer : style::cur_default); + Ui::Tooltip::Hide(); + } + if (lnk) { + Ui::Tooltip::Show(1000, this); + } +} + +void Inner::showPreview() { + if (_pressed < 0) return; + + int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; + if (row < _rows.size() && col < _rows.at(row).items.size()) { + auto layout = _rows.at(row).items.at(col); + if (const auto w = App::wnd()) { + if (const auto previewDocument = layout->getPreviewDocument()) { + _previewShown = w->showMediaPreview(Data::FileOrigin(), previewDocument); + } else if (const auto previewPhoto = layout->getPreviewPhoto()) { + _previewShown = w->showMediaPreview(Data::FileOrigin(), previewPhoto); + } + } + } +} + +void Inner::updateInlineItems() { + auto ms = crl::now(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.callOnce(_lastScrolled + 100 - ms); + } +} + +void Inner::switchPm() { + if (_inlineBot && _inlineBot->isBot()) { + _inlineBot->botInfo->startToken = _switchPmStartToken; + _inlineBot->botInfo->inlineReturnTo = _currentDialogsEntryState; + Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); + } +} + +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.h b/Telegram/SourceFiles/inline_bots/inline_results_inner.h new file mode 100644 index 000000000..6f2e1ab3a --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.h @@ -0,0 +1,186 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" +#include "ui/abstract_button.h" +#include "ui/widgets/tooltip.h" +#include "ui/effects/animations.h" +#include "ui/effects/panel_animation.h" +#include "dialogs/dialogs_key.h" +#include "base/timer.h" +#include "mtproto/sender.h" +#include "inline_bots/inline_bot_layout_item.h" + +namespace Api { +struct SendOptions; +} // namespace Api + +namespace Ui { +class ScrollArea; +class IconButton; +class LinkButton; +class RoundButton; +class FlatLabel; +class RippleAnimation; +class PopupMenu; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace InlineBots { +class Result; +struct ResultSelected; +} // namespace InlineBots + +namespace InlineBots { +namespace Layout { + +constexpr int kInlineItemsMaxPerRow = 5; + +class ItemBase; +using Results = std::vector>; + +struct CacheEntry { + QString nextOffset; + QString switchPmText, switchPmStartToken; + Results results; +}; + +class Inner + : public Ui::RpWidget + , public Ui::AbstractTooltipShower + , public Context + , private base::Subscriber { + +public: + Inner(QWidget *parent, not_null controller); + + void hideFinished(); + + void clearSelection(); + + int refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *results, bool resultsDeleted); + void inlineBotChanged(); + void hideInlineRowsPanel(); + void clearInlineRowsPanel(); + + void preloadImages(); + + void inlineItemLayoutChanged(const ItemBase *layout) override; + void inlineItemRepaint(const ItemBase *layout) override; + bool inlineItemVisible(const ItemBase *layout) override; + Data::FileOrigin inlineItemFileOrigin() override; + + int countHeight(); + + void setResultSelectedCallback(Fn callback) { + _resultSelectedCallback = std::move(callback); + } + void setCurrentDialogsEntryState(Dialogs::EntryState state) { + _currentDialogsEntryState = state; + } + + // Ui::AbstractTooltipShower interface. + QString tooltipText() const override; + QPoint tooltipPos() const override; + bool tooltipWindowActive() const override; + + rpl::producer<> inlineRowsCleared() const; + + ~Inner(); + +protected: + void visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) override; + + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void leaveEventHook(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void enterFromChildEvent(QEvent *e, QWidget *child) override; + void contextMenuEvent(QContextMenuEvent *e) override; + +private: + static constexpr bool kRefreshIconsScrollAnimation = true; + static constexpr bool kRefreshIconsNoAnimation = false; + + struct Row { + int height = 0; + QVector items; + }; + + void switchPm(); + + void updateSelected(); + void checkRestrictedPeer(); + bool isRestrictedView(); + void clearHeavyData(); + + void paintInlineItems(Painter &p, const QRect &r); + + void refreshSwitchPmButton(const CacheEntry *entry); + + void showPreview(); + void updateInlineItems(); + void clearInlineRows(bool resultsDeleted); + ItemBase *layoutPrepareInlineResult(Result *result, int32 position); + + bool inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth); + bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); + + Row &layoutInlineRow(Row &row, int32 sumWidth = 0); + void deleteUnusedInlineLayouts(); + + int validateExistingInlineRows(const Results &results); + void selectInlineResult(int row, int column); + void selectInlineResult(int row, int column, Api::SendOptions options); + + not_null _controller; + + int _visibleTop = 0; + int _visibleBottom = 0; + + UserData *_inlineBot = nullptr; + PeerData *_inlineQueryPeer = nullptr; + crl::time _lastScrolled = 0; + base::Timer _updateInlineItems; + bool _inlineWithThumb = false; + + object_ptr _switchPmButton = { nullptr }; + QString _switchPmStartToken; + Dialogs::EntryState _currentDialogsEntryState; + + object_ptr _restrictedLabel = { nullptr }; + + base::unique_qptr _menu; + + QVector _rows; + + std::map> _inlineLayouts; + + rpl::event_stream<> _inlineRowsCleared; + + int _selected = -1; + int _pressed = -1; + QPoint _lastMousePos; + + base::Timer _previewTimer; + bool _previewShown = false; + + Fn _resultSelectedCallback; + +}; + +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index c3184fbb9..7f94c480a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -7,783 +7,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/inline_results_widget.h" -#include "api/api_common.h" -#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu -#include "data/data_photo.h" -#include "data/data_document.h" -#include "data/data_channel.h" #include "data/data_user.h" #include "data/data_session.h" -#include "data/data_file_origin.h" -#include "data/data_changes.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/popup_menu.h" -#include "ui/widgets/shadow.h" -#include "ui/effects/ripple_animation.h" -#include "ui/image/image_prepare.h" -#include "boxes/confirm_box.h" #include "inline_bots/inline_bot_result.h" -#include "inline_bots/inline_bot_layout_item.h" -#include "dialogs/dialogs_layout.h" -#include "storage/localstorage.h" -#include "lang/lang_keys.h" -#include "mainwindow.h" -#include "mainwidget.h" +#include "inline_bots/inline_results_inner.h" #include "main/main_session.h" #include "window/window_session_controller.h" +#include "ui/widgets/shadow.h" #include "ui/widgets/scroll_area.h" -#include "ui/widgets/labels.h" +#include "ui/image/image_prepare.h" #include "ui/cached_round_corners.h" -#include "history/view/history_view_cursor_state.h" -#include "facades.h" #include "styles/style_chat_helpers.h" -#include - namespace InlineBots { namespace Layout { -namespace internal { namespace { constexpr auto kInlineBotRequestDelay = 400; } // namespace -Inner::Inner( - QWidget *parent, - not_null controller) -: RpWidget(parent) -, _controller(controller) -, _updateInlineItems([=] { updateInlineItems(); }) -, _previewTimer([=] { showPreview(); }) { - resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, st::inlineResultsMinHeight); - - setMouseTracking(true); - setAttribute(Qt::WA_OpaquePaintEvent); - - _controller->session().downloaderTaskFinished( - ) | rpl::start_with_next([=] { - update(); - }, lifetime()); - - subscribe(controller->gifPauseLevelChanged(), [this] { - if (!_controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults)) { - update(); - } - }); - - _controller->session().changes().peerUpdates( - Data::PeerUpdate::Flag::Rights - ) | rpl::filter([=](const Data::PeerUpdate &update) { - return (update.peer.get() == _inlineQueryPeer); - }) | rpl::start_with_next([=] { - auto isRestricted = (_restrictedLabel != nullptr); - if (isRestricted != isRestrictedView()) { - auto h = countHeight(); - if (h != height()) resize(width(), h); - } - }, lifetime()); -} - -void Inner::visibleTopBottomUpdated( - int visibleTop, - int visibleBottom) { - _visibleBottom = visibleBottom; - if (_visibleTop != visibleTop) { - _visibleTop = visibleTop; - _lastScrolled = crl::now(); - } -} - -void Inner::checkRestrictedPeer() { - if (_inlineQueryPeer) { - const auto error = Data::RestrictionError( - _inlineQueryPeer, - ChatRestriction::f_send_inline); - if (error) { - if (!_restrictedLabel) { - _restrictedLabel.create(this, *error, st::stickersRestrictedLabel); - _restrictedLabel->show(); - _restrictedLabel->move(st::inlineResultsLeft - st::buttonRadius, st::stickerPanPadding); - _restrictedLabel->resizeToNaturalWidth(width() - (st::inlineResultsLeft - st::buttonRadius) * 2); - if (_switchPmButton) { - _switchPmButton->hide(); - } - update(); - } - return; - } - } - if (_restrictedLabel) { - _restrictedLabel.destroy(); - if (_switchPmButton) { - _switchPmButton->show(); - } - update(); - } -} - -bool Inner::isRestrictedView() { - checkRestrictedPeer(); - return (_restrictedLabel != nullptr); -} - -int Inner::countHeight() { - if (isRestrictedView()) { - return st::stickerPanPadding + _restrictedLabel->height() + st::stickerPanPadding; - } else if (_rows.isEmpty() && !_switchPmButton) { - return st::stickerPanPadding + st::normalFont->height + st::stickerPanPadding; - } - auto result = st::stickerPanPadding; - if (_switchPmButton) { - result += _switchPmButton->height() + st::inlineResultsSkip; - } - for (int i = 0, l = _rows.count(); i < l; ++i) { - result += _rows[i].height; - } - return result + st::stickerPanPadding; -} - -QString Inner::tooltipText() const { - if (const auto lnk = ClickHandler::getActive()) { - return lnk->tooltip(); - } - return QString(); -} - -QPoint Inner::tooltipPos() const { - return _lastMousePos; -} - -bool Inner::tooltipWindowActive() const { - return Ui::AppInFocus() && Ui::InFocusChain(window()); -} - -rpl::producer<> Inner::inlineRowsCleared() const { - return _inlineRowsCleared.events(); -} - -Inner::~Inner() = default; - -void Inner::paintEvent(QPaintEvent *e) { - Painter p(this); - QRect r = e ? e->rect() : rect(); - if (r != rect()) { - p.setClipRect(r); - } - p.fillRect(r, st::emojiPanBg); - - paintInlineItems(p, r); -} - -void Inner::paintInlineItems(Painter &p, const QRect &r) { - if (_restrictedLabel) { - return; - } - if (_rows.isEmpty() && !_switchPmButton) { - p.setFont(st::normalFont); - p.setPen(st::noContactsColor); - p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), tr::lng_inline_bot_no_results(tr::now), style::al_center); - return; - } - auto gifPaused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults); - InlineBots::Layout::PaintContext context(crl::now(), false, gifPaused, false); - - auto top = st::stickerPanPadding; - if (_switchPmButton) { - top += _switchPmButton->height() + st::inlineResultsSkip; - } - - auto fromx = rtl() ? (width() - r.x() - r.width()) : r.x(); - auto tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); - for (auto row = 0, rows = _rows.size(); row != rows; ++row) { - auto &inlineRow = _rows[row]; - if (top >= r.top() + r.height()) break; - if (top + inlineRow.height > r.top()) { - auto left = st::inlineResultsLeft - st::buttonRadius; - if (row == rows - 1) context.lastRow = true; - for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { - if (left >= tox) break; - - auto item = inlineRow.items.at(col); - auto w = item->width(); - if (left + w > fromx) { - p.translate(left, top); - item->paint(p, r.translated(-left, -top), &context); - p.translate(-left, -top); - } - left += w; - if (item->hasRightSkip()) { - left += st::inlineResultsSkip; - } - } - } - top += inlineRow.height; - } -} - -void Inner::mousePressEvent(QMouseEvent *e) { - if (e->button() != Qt::LeftButton) { - return; - } - _lastMousePos = e->globalPos(); - updateSelected(); - - _pressed = _selected; - ClickHandler::pressed(); - _previewTimer.callOnce(QApplication::startDragTime()); -} - -void Inner::mouseReleaseEvent(QMouseEvent *e) { - _previewTimer.cancel(); - - auto pressed = std::exchange(_pressed, -1); - auto activated = ClickHandler::unpressed(); - - if (_previewShown) { - _previewShown = false; - return; - } - - _lastMousePos = e->globalPos(); - updateSelected(); - - if (_selected < 0 || _selected != pressed || !activated) { - return; - } - - if (dynamic_cast(activated.get())) { - int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; - selectInlineResult(row, column); - } else { - ActivateClickHandler(window(), activated, e->button()); - } -} - -void Inner::selectInlineResult(int row, int column) { - selectInlineResult(row, column, Api::SendOptions()); -} - -void Inner::selectInlineResult( - int row, - int column, - Api::SendOptions options) { - if (row >= _rows.size() || column >= _rows.at(row).items.size()) { - return; - } - - auto item = _rows[row].items[column]; - if (const auto inlineResult = item->getResult()) { - if (inlineResult->onChoose(item)) { - _resultSelectedCallback( - inlineResult, - _inlineBot, - std::move(options)); - } - } -} - -void Inner::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); -} - -void Inner::leaveEventHook(QEvent *e) { - clearSelection(); - Ui::Tooltip::Hide(); -} - -void Inner::leaveToChildEvent(QEvent *e, QWidget *child) { - clearSelection(); -} - -void Inner::enterFromChildEvent(QEvent *e, QWidget *child) { - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -void Inner::contextMenuEvent(QContextMenuEvent *e) { - if (_selected < 0 || _pressed >= 0) { - return; - } - const auto row = _selected / MatrixRowShift; - const auto column = _selected % MatrixRowShift; - const auto type = SendMenu::Type::Scheduled; - - _menu = base::make_unique_q(this); - - const auto send = [=](Api::SendOptions options) { - selectInlineResult(row, column, options); - }; - SendMenu::FillSendMenu( - _menu, - [&] { return type; }, - SendMenu::DefaultSilentCallback(send), - SendMenu::DefaultScheduleCallback(this, type, send)); - - if (!_menu->actions().empty()) { - _menu->popup(QCursor::pos()); - } -} - -void Inner::clearSelection() { - if (_selected >= 0) { - int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; - Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); - ClickHandler::clearActive(_rows.at(srow).items.at(scol)); - setCursor(style::cur_default); - } - _selected = _pressed = -1; - update(); -} - -void Inner::hideFinished() { - clearHeavyData(); -} - -void Inner::clearHeavyData() { - clearInlineRows(false); - for (const auto &[result, layout] : _inlineLayouts) { - layout->unloadHeavyPart(); - } -} - -bool Inner::inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth) { - auto layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); - if (!layout) return false; - - layout->preload(); - if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { - layout->setPosition(_rows.size() * MatrixRowShift); - } - - sumWidth += layout->maxWidth(); - if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { - sumWidth += st::inlineResultsSkip; - } - - row.items.push_back(layout); - return true; -} - -bool Inner::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { - if (row.items.isEmpty()) return false; - - auto full = (row.items.size() >= kInlineItemsMaxPerRow); - auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); - if (full || big || force) { - _rows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); - row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - sumWidth = 0; - return true; - } - return false; -} - -void Inner::inlineBotChanged() { - refreshInlineRows(nullptr, nullptr, nullptr, true); -} - -void Inner::clearInlineRows(bool resultsDeleted) { - if (resultsDeleted) { - _selected = _pressed = -1; - } else { - clearSelection(); - for_const (auto &row, _rows) { - for_const (auto &item, row.items) { - item->setPosition(-1); - } - } - } - _rows.clear(); -} - -ItemBase *Inner::layoutPrepareInlineResult(Result *result, int32 position) { - auto it = _inlineLayouts.find(result); - if (it == _inlineLayouts.cend()) { - if (auto layout = ItemBase::createLayout(this, result, _inlineWithThumb)) { - it = _inlineLayouts.emplace(result, std::move(layout)).first; - it->second->initDimensions(); - } else { - return nullptr; - } - } - if (!it->second->maxWidth()) { - return nullptr; - } - - it->second->setPosition(position); - return it->second.get(); -} - -void Inner::deleteUnusedInlineLayouts() { - if (_rows.isEmpty()) { // delete all - _inlineLayouts.clear(); - } else { - for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { - if (i->second->position() < 0) { - i = _inlineLayouts.erase(i); - } else { - ++i; - } - } - } -} - -Inner::Row &Inner::layoutInlineRow(Row &row, int32 sumWidth) { - auto count = int(row.items.size()); - Assert(count <= kInlineItemsMaxPerRow); - - // enumerate items in the order of growing maxWidth() - // for that sort item indices by maxWidth() - int indices[kInlineItemsMaxPerRow]; - for (auto i = 0; i != count; ++i) { - indices[i] = i; - } - std::sort(indices, indices + count, [&row](int a, int b) -> bool { - return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); - }); - - row.height = 0; - int availw = width() - (st::inlineResultsLeft - st::buttonRadius); - for (int i = 0; i < count; ++i) { - int index = indices[i]; - int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); - int actualw = qMax(w, int(st::inlineResultsMinWidth)); - row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); - if (sumWidth) { - availw -= actualw; - sumWidth -= row.items.at(index)->maxWidth(); - if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { - availw -= st::inlineResultsSkip; - sumWidth -= st::inlineResultsSkip; - } - } - } - return row; -} - -void Inner::preloadImages() { - for (auto row = 0, rows = _rows.size(); row != rows; ++row) { - for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { - _rows[row].items[col]->preload(); - } - } -} - -void Inner::hideInlineRowsPanel() { - clearInlineRows(false); -} - -void Inner::clearInlineRowsPanel() { - clearInlineRows(false); -} - -void Inner::refreshSwitchPmButton(const CacheEntry *entry) { - if (!entry || entry->switchPmText.isEmpty()) { - _switchPmButton.destroy(); - _switchPmStartToken.clear(); - } else { - if (!_switchPmButton) { - _switchPmButton.create(this, nullptr, st::switchPmButton); - _switchPmButton->show(); - _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - _switchPmButton->addClickHandler([=] { onSwitchPm(); }); - } - _switchPmButton->setText(rpl::single(entry->switchPmText)); - _switchPmStartToken = entry->switchPmStartToken; - const auto buttonTop = st::stickerPanPadding; - _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); - if (isRestrictedView()) { - _switchPmButton->hide(); - } - } - update(); -} - -int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *entry, bool resultsDeleted) { - _inlineBot = bot; - _inlineQueryPeer = queryPeer; - refreshSwitchPmButton(entry); - auto clearResults = [&] { - if (!entry) { - return true; - } - if (entry->results.empty() && entry->switchPmText.isEmpty()) { - return true; - } - return false; - }; - auto clearResultsResult = clearResults(); // Clang workaround. - if (clearResultsResult) { - if (resultsDeleted) { - clearInlineRows(true); - deleteUnusedInlineLayouts(); - } - _inlineRowsCleared.fire({}); - return 0; - } - - clearSelection(); - - Assert(_inlineBot != 0); - - auto count = int(entry->results.size()); - auto from = validateExistingInlineRows(entry->results); - auto added = 0; - - if (count) { - _rows.reserve(count); - auto row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - auto sumWidth = 0; - for (auto i = from; i != count; ++i) { - if (inlineRowsAddItem(entry->results[i].get(), row, sumWidth)) { - ++added; - } - } - inlineRowFinalize(row, sumWidth, true); - } - - auto h = countHeight(); - if (h != height()) resize(width(), h); - update(); - - _lastMousePos = QCursor::pos(); - updateSelected(); - - return added; -} - -int Inner::validateExistingInlineRows(const Results &results) { - int count = results.size(), until = 0, untilrow = 0, untilcol = 0; - for (; until < count;) { - if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { - break; - } - ++until; - if (++untilcol == _rows[untilrow].items.size()) { - ++untilrow; - untilcol = 0; - } - } - if (until == count) { // all items are layed out - if (untilrow == _rows.size()) { // nothing changed - return until; - } - - for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { - for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { - if (skip) { - --skip; - } else { - _rows[i].items[j]->setPosition(-1); - } - } - } - if (!untilcol) { // all good rows are filled - _rows.resize(untilrow); - return until; - } - _rows.resize(untilrow + 1); - _rows[untilrow].items.resize(untilcol); - _rows[untilrow] = layoutInlineRow(_rows[untilrow]); - return until; - } - if (untilrow && !untilcol) { // remove last row, maybe it is not full - --untilrow; - untilcol = _rows[untilrow].items.size(); - } - until -= untilcol; - - for (int i = untilrow, l = _rows.size(); i < l; ++i) { - for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { - _rows[i].items[j]->setPosition(-1); - } - } - _rows.resize(untilrow); - - if (_rows.isEmpty()) { - _inlineWithThumb = false; - for (int i = until; i < count; ++i) { - if (results.at(i)->hasThumbDisplay()) { - _inlineWithThumb = true; - break; - } - } - } - return until; -} - -void Inner::inlineItemLayoutChanged(const ItemBase *layout) { - if (_selected < 0 || !isVisible()) { - return; - } - - int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row < _rows.size() && col < _rows.at(row).items.size()) { - if (layout == _rows.at(row).items.at(col)) { - updateSelected(); - } - } -} - -void Inner::inlineItemRepaint(const ItemBase *layout) { - auto ms = crl::now(); - if (_lastScrolled + 100 <= ms) { - update(); - } else { - _updateInlineItems.callOnce(_lastScrolled + 100 - ms); - } -} - -bool Inner::inlineItemVisible(const ItemBase *layout) { - int32 position = layout->position(); - if (position < 0 || !isVisible()) { - return false; - } - - int row = position / MatrixRowShift, col = position % MatrixRowShift; - Assert((row < _rows.size()) && (col < _rows[row].items.size())); - - auto &inlineItems = _rows[row].items; - int top = st::stickerPanPadding; - for (int32 i = 0; i < row; ++i) { - top += _rows.at(i).height; - } - - return (top < _visibleBottom) && (top + _rows[row].items[col]->height() > _visibleTop); -} - -Data::FileOrigin Inner::inlineItemFileOrigin() { - return Data::FileOrigin(); -} - -void Inner::updateSelected() { - if (_pressed >= 0 && !_previewShown) { - return; - } - - auto newSelected = -1; - auto p = mapFromGlobal(_lastMousePos); - - int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); - int sy = p.y() - st::stickerPanPadding; - if (_switchPmButton) { - sy -= _switchPmButton->height() + st::inlineResultsSkip; - } - int row = -1, col = -1, sel = -1; - ClickHandlerPtr lnk; - ClickHandlerHost *lnkhost = nullptr; - HistoryView::CursorState cursor = HistoryView::CursorState::None; - if (sy >= 0) { - row = 0; - for (int rows = _rows.size(); row < rows; ++row) { - if (sy < _rows[row].height) { - break; - } - sy -= _rows[row].height; - } - } - if (sx >= 0 && row >= 0 && row < _rows.size()) { - auto &inlineItems = _rows[row].items; - col = 0; - for (int cols = inlineItems.size(); col < cols; ++col) { - int width = inlineItems.at(col)->width(); - if (sx < width) { - break; - } - sx -= width; - if (inlineItems.at(col)->hasRightSkip()) { - sx -= st::inlineResultsSkip; - } - } - if (col < inlineItems.size()) { - sel = row * MatrixRowShift + col; - auto result = inlineItems[col]->getState( - QPoint(sx, sy), - HistoryView::StateRequest()); - lnk = result.link; - cursor = result.cursor; - lnkhost = inlineItems[col]; - } else { - row = col = -1; - } - } else { - row = col = -1; - } - int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; - int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; - if (_selected != sel) { - if (srow >= 0 && scol >= 0) { - Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); - _rows[srow].items[scol]->update(); - } - _selected = sel; - if (row >= 0 && col >= 0) { - Assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows.at(row).items.size()); - _rows[row].items[col]->update(); - } - if (_previewShown && _selected >= 0 && _pressed != _selected) { - _pressed = _selected; - if (row >= 0 && col >= 0) { - auto layout = _rows.at(row).items.at(col); - if (const auto w = App::wnd()) { - if (const auto previewDocument = layout->getPreviewDocument()) { - w->showMediaPreview( - Data::FileOrigin(), - previewDocument); - } else if (auto previewPhoto = layout->getPreviewPhoto()) { - w->showMediaPreview(Data::FileOrigin(), previewPhoto); - } - } - } - } - } - if (ClickHandler::setActive(lnk, lnkhost)) { - setCursor(lnk ? style::cur_pointer : style::cur_default); - Ui::Tooltip::Hide(); - } - if (lnk) { - Ui::Tooltip::Show(1000, this); - } -} - -void Inner::showPreview() { - if (_pressed < 0) return; - - int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; - if (row < _rows.size() && col < _rows.at(row).items.size()) { - auto layout = _rows.at(row).items.at(col); - if (const auto w = App::wnd()) { - if (const auto previewDocument = layout->getPreviewDocument()) { - _previewShown = w->showMediaPreview(Data::FileOrigin(), previewDocument); - } else if (const auto previewPhoto = layout->getPreviewPhoto()) { - _previewShown = w->showMediaPreview(Data::FileOrigin(), previewPhoto); - } - } - } -} - -void Inner::updateInlineItems() { - auto ms = crl::now(); - if (_lastScrolled + 100 <= ms) { - update(); - } else { - _updateInlineItems.callOnce(_lastScrolled + 100 - ms); - } -} - -void Inner::onSwitchPm() { - if (_inlineBot && _inlineBot->isBot()) { - _inlineBot->botInfo->startToken = _switchPmStartToken; - Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); - } -} - -} // namespace internal - Widget::Widget( QWidget *parent, not_null controller) @@ -801,7 +44,7 @@ Widget::Widget( _scroll->resize(st::emojiPanWidth - st::buttonRadius, _contentHeight); _scroll->move(verticalRect().topLeft()); - _inner = _scroll->setOwnedWidget(object_ptr(this, controller)); + _inner = _scroll->setOwnedWidget(object_ptr(this, controller)); _inner->moveToLeft(0, 0, _scroll->width()); @@ -990,6 +233,14 @@ QImage Widget::grabForPanelAnimation() { return result; } +void Widget::setResultSelectedCallback(Fn callback) { + _inner->setResultSelectedCallback(std::move(callback)); +} + +void Widget::setCurrentDialogsEntryState(Dialogs::EntryState state) { + _inner->setCurrentDialogsEntryState(state); +} + void Widget::hideAnimated() { if (isHidden()) return; if (_hiding) return; @@ -1103,7 +354,7 @@ void Widget::inlineResultsDone(const MTPmessages_BotResults &result) { if (it == _inlineCache.cend()) { it = _inlineCache.emplace( _inlineQuery, - std::make_unique()).first; + std::make_unique()).first; } auto entry = it->second.get(); entry->nextOffset = qs(d.vnext_offset().value_or_empty()); @@ -1163,7 +414,7 @@ void Widget::queryInlineBot(UserData *bot, PeerData *peer, QString query) { showInlineRows(true); } else { _inlineNextQuery = query; - _inlineRequestTimer.callOnce(internal::kInlineBotRequestDelay); + _inlineRequestTimer.callOnce(kInlineBotRequestDelay); } } } @@ -1199,7 +450,7 @@ void Widget::onInlineRequest() { bool Widget::refreshInlineRows(int *added) { auto it = _inlineCache.find(_inlineQuery); - const internal::CacheEntry *entry = nullptr; + const CacheEntry *entry = nullptr; if (it != _inlineCache.cend()) { if (!it->second->results.empty() || !it->second->switchPmText.isEmpty()) { entry = it->second.get(); diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.h b/Telegram/SourceFiles/inline_bots/inline_results_widget.h index e7369759e..62c4acd4f 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.h @@ -30,161 +30,29 @@ class RippleAnimation; class PopupMenu; } // namespace Ui +namespace Dialogs { +struct EntryState; +} // namespace Dialogs + namespace Window { class SessionController; } // namespace Window namespace InlineBots { - class Result; +struct ResultSelected; +} // namespace InlineBots +namespace InlineBots { namespace Layout { -class ItemBase; - -namespace internal { - -constexpr int kInlineItemsMaxPerRow = 5; - -using Results = std::vector>; -using ResultSelected = Fn; - -struct CacheEntry { - QString nextOffset; - QString switchPmText, switchPmStartToken; - Results results; -}; - -class Inner - : public Ui::RpWidget - , public Ui::AbstractTooltipShower - , public Context - , private base::Subscriber { - -public: - Inner(QWidget *parent, not_null controller); - - void hideFinished(); - - void clearSelection(); - - int refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *results, bool resultsDeleted); - void inlineBotChanged(); - void hideInlineRowsPanel(); - void clearInlineRowsPanel(); - - void preloadImages(); - - void inlineItemLayoutChanged(const ItemBase *layout) override; - void inlineItemRepaint(const ItemBase *layout) override; - bool inlineItemVisible(const ItemBase *layout) override; - Data::FileOrigin inlineItemFileOrigin() override; - - int countHeight(); - - void setResultSelectedCallback(ResultSelected callback) { - _resultSelectedCallback = std::move(callback); - } - - // Ui::AbstractTooltipShower interface. - QString tooltipText() const override; - QPoint tooltipPos() const override; - bool tooltipWindowActive() const override; - - rpl::producer<> inlineRowsCleared() const; - - ~Inner(); - -protected: - void visibleTopBottomUpdated( - int visibleTop, - int visibleBottom) override; - - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void leaveEventHook(QEvent *e) override; - void leaveToChildEvent(QEvent *e, QWidget *child) override; - void enterFromChildEvent(QEvent *e, QWidget *child) override; - void contextMenuEvent(QContextMenuEvent *e) override; - -private: - static constexpr bool kRefreshIconsScrollAnimation = true; - static constexpr bool kRefreshIconsNoAnimation = false; - - struct Row { - int height = 0; - QVector items; - }; - - void onSwitchPm(); - - void updateSelected(); - void checkRestrictedPeer(); - bool isRestrictedView(); - void clearHeavyData(); - - void paintInlineItems(Painter &p, const QRect &r); - - void refreshSwitchPmButton(const CacheEntry *entry); - - void showPreview(); - void updateInlineItems(); - void clearInlineRows(bool resultsDeleted); - ItemBase *layoutPrepareInlineResult(Result *result, int32 position); - - bool inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth); - bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); - - Row &layoutInlineRow(Row &row, int32 sumWidth = 0); - void deleteUnusedInlineLayouts(); - - int validateExistingInlineRows(const Results &results); - void selectInlineResult(int row, int column); - void selectInlineResult(int row, int column, Api::SendOptions options); - - not_null _controller; - - int _visibleTop = 0; - int _visibleBottom = 0; - - UserData *_inlineBot = nullptr; - PeerData *_inlineQueryPeer = nullptr; - crl::time _lastScrolled = 0; - base::Timer _updateInlineItems; - bool _inlineWithThumb = false; - - object_ptr _switchPmButton = { nullptr }; - QString _switchPmStartToken; - - object_ptr _restrictedLabel = { nullptr }; - - base::unique_qptr _menu; - - QVector _rows; - - std::map> _inlineLayouts; - - rpl::event_stream<> _inlineRowsCleared; - - int _selected = -1; - int _pressed = -1; - QPoint _lastMousePos; - - base::Timer _previewTimer; - bool _previewShown = false; - - ResultSelected _resultSelectedCallback; - -}; - -} // namespace internal +struct CacheEntry; +class Inner; class Widget : public Ui::RpWidget { - public: Widget(QWidget *parent, not_null controller); + ~Widget(); void moveBottom(int bottom); @@ -201,16 +69,13 @@ public: void showAnimated(); void hideAnimated(); - void setResultSelectedCallback(internal::ResultSelected callback) { - _inner->setResultSelectedCallback(std::move(callback)); - } + void setResultSelectedCallback(Fn callback); + void setCurrentDialogsEntryState(Dialogs::EntryState state); [[nodiscard]] rpl::producer requesting() const { return _requesting.events(); } - ~Widget(); - protected: void paintEvent(QPaintEvent *e) override; @@ -273,9 +138,9 @@ private: bool _inPanelGrab = false; object_ptr _scroll; - QPointer _inner; + QPointer _inner; - std::map> _inlineCache; + std::map> _inlineCache; base::Timer _inlineRequestTimer; UserData *_inlineBot = nullptr; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index fd25686f4..660026281 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -107,12 +107,12 @@ class Filler { public: Filler( not_null controller, - PeerMenuRequest request, + Dialogs::EntryState request, const PeerMenuCallback &addAction); void fill(); private: - using Source = PeerMenuRequest::Source; + using Section = Dialogs::EntryState::Section; [[nodiscard]] bool showInfo(); [[nodiscard]] bool showHidePromotion(); @@ -128,36 +128,14 @@ private: void addBlockUser(not_null user); void addChatActions(not_null chat); void addChannelActions(not_null channel); + void addTogglesForArchive(); void addPollAction(not_null peer); not_null _controller; - PeerMenuRequest _request; - not_null _peer; - const PeerMenuCallback &_addAction; - -}; - -class FolderFiller { -public: - FolderFiller( - not_null controller, - FolderMenuRequest request, - const PeerMenuCallback &addAction); - void fill(); - -private: - void addTogglesForArchive(); - //bool showInfo(); - //void addTogglePin(); - //void addInfo(); - //void addSearch(); - //void addNotifications(); - //void addUngroup(); - - not_null _controller; - FolderMenuRequest _request; - not_null _folder; + Dialogs::EntryState _request; + PeerData *_peer = nullptr; + Data::Folder *_folder = nullptr; const PeerMenuCallback &_addAction; }; @@ -277,16 +255,17 @@ void TogglePinnedDialog( Filler::Filler( not_null controller, - PeerMenuRequest request, + Dialogs::EntryState request, const PeerMenuCallback &addAction) : _controller(controller) , _request(request) -, _peer(request.peer) +, _peer(request.key.peer()) +, _folder(request.key.folder()) , _addAction(addAction) { } bool Filler::showInfo() { - if (_request.source == Source::Profile + if (_request.section == Section::Profile || _peer->isSelf() || _peer->isRepliesChat()) { return false; @@ -302,7 +281,7 @@ bool Filler::showInfo() { } bool Filler::showHidePromotion() { - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -312,7 +291,7 @@ bool Filler::showHidePromotion() { } bool Filler::showToggleArchived() { - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -325,7 +304,7 @@ bool Filler::showToggleArchived() { } bool Filler::showTogglePin() { - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -474,7 +453,7 @@ void Filler::addBlockUser(not_null user) { void Filler::addUserActions(not_null user) { const auto controller = _controller; const auto window = &_controller->window(); - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { if (user->session().supportMode()) { _addAction("Edit support info", [=] { user->session().supportHelper().editInfo(controller, user); @@ -522,13 +501,13 @@ void Filler::addUserActions(not_null user) { if (!user->isInaccessible() && user != user->session().user() && !user->isRepliesChat() - && _request.source != Source::ChatsList) { + && _request.section != Section::ChatsList) { addBlockUser(user); } } void Filler::addChatActions(not_null chat) { - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { const auto controller = _controller; if (EditPeerInfoBox::Available(chat)) { const auto text = tr::lng_manage_group_title(tr::now); @@ -568,7 +547,7 @@ void Filler::addChannelActions(not_null channel) { // [=] { ToggleChannelGrouping(channel, !grouped); }); // } //} - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { if (channel->isBroadcast()) { if (const auto chat = channel->linkedChat()) { _addAction(tr::lng_profile_view_discussion(tr::now), [=] { @@ -618,7 +597,7 @@ void Filler::addChannelActions(not_null channel) { text, [=] { channel->session().api().joinChannel(channel); }); } - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { const auto needReport = !channel->amCreator() && (!isGroup || channel->isPublic()); if (needReport) { @@ -634,7 +613,7 @@ void Filler::addPollAction(not_null peer) { return; } const auto controller = _controller; - const auto source = (_request.source == Source::ScheduledSection) + const auto source = (_request.section == Section::Scheduled) ? Api::SendType::Scheduled : Api::SendType::Normal; const auto flag = PollData::Flags(); @@ -652,8 +631,11 @@ void Filler::addPollAction(not_null peer) { } void Filler::fill() { - if (_request.source == Source::ScheduledSection - || _request.source == Source::RepliesSection) { + if (_folder) { + addTogglesForArchive(); + return; + } else if (_request.section == Section::Scheduled + || _request.section == Section::Replies) { addPollAction(_peer); return; } @@ -669,10 +651,10 @@ void Filler::fill() { if (showInfo()) { addInfo(); } - if (_request.source != Source::Profile && !_peer->isSelf()) { + if (_request.section != Section::Profile && !_peer->isSelf()) { PeerMenuAddMuteAction(_peer, _addAction); } - if (_request.source == Source::ChatsList) { + if (_request.section == Section::ChatsList) { //addSearch(); addToggleUnreadMark(); } @@ -686,21 +668,9 @@ void Filler::fill() { } } -FolderFiller::FolderFiller( - not_null controller, - FolderMenuRequest request, - const PeerMenuCallback &addAction) -: _controller(controller) -, _request(request) -, _folder(request.folder) -, _addAction(addAction) { -} +void Filler::addTogglesForArchive() { + Expects(_folder != nullptr); -void FolderFiller::fill() { - addTogglesForArchive(); -} - -void FolderFiller::addTogglesForArchive() { if (_folder->id() != Data::Folder::kId) { return; } @@ -1310,20 +1280,11 @@ Fn DeleteAndLeaveHandler(not_null peer) { }; } -void FillPeerMenu( +void FillDialogsEntryMenu( not_null controller, - PeerMenuRequest request, + Dialogs::EntryState request, const PeerMenuCallback &callback) { - Filler filler(controller, request, callback); - filler.fill(); -} - -void FillFolderMenu( - not_null controller, - FolderMenuRequest request, - const PeerMenuCallback &callback) { - FolderFiller filler(controller, request, callback); - filler.fill(); + Filler(controller, request, callback).fill(); } } // namespace Window diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index a7944da05..fdaf60363 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -24,6 +24,7 @@ class Session; namespace Dialogs { class MainList; +struct EntryState; } // namespace Dialogs namespace Window { @@ -32,37 +33,13 @@ class Controller; class SessionController; class SessionNavigation; -struct PeerMenuRequest { - enum class Source { - ChatsList, - History, - Profile, - ScheduledSection, - RepliesSection, - }; - - not_null peer; - Source source = Source::ChatsList; - FilterId filterId = 0; - MsgId rootId = 0; - MsgId currentReplyToId = 0; -}; - -struct FolderMenuRequest { - not_null folder; -}; - using PeerMenuCallback = Fn handler)>; -void FillPeerMenu( +void FillDialogsEntryMenu( not_null controller, - PeerMenuRequest request, - const PeerMenuCallback &addAction); -void FillFolderMenu( - not_null controller, - FolderMenuRequest request, + Dialogs::EntryState request, const PeerMenuCallback &addAction); void PeerMenuAddMuteAction( From 4a0efb9114caf8fba9cf4e517709e6c6efaec41a Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 12 Nov 2020 19:19:04 +0300 Subject: [PATCH 096/370] Remove Q_OBJECT from HistoryWidget. --- .../SourceFiles/history/history_widget.cpp | 187 ++++++++++-------- Telegram/SourceFiles/history/history_widget.h | 79 ++++---- .../history_view_compose_controls.cpp | 8 +- .../controls/history_view_compose_controls.h | 2 +- .../view/history_view_replies_section.cpp | 8 - .../view/history_view_scheduled_section.cpp | 8 - Telegram/SourceFiles/mainwidget.cpp | 6 +- 7 files changed, 150 insertions(+), 148 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 6de4d1f96..325f18aae 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/send_button.h" #include "inline_bots/inline_bot_result.h" #include "base/event_filter.h" +#include "base/qt_signal_producer.h" #include "base/unixtime.h" #include "base/call_delayed.h" #include "data/data_changes.h" @@ -171,6 +172,7 @@ HistoryWidget::HistoryWidget( , _previewTimer([=] { requestPreview(); }) , _topBar(this, controller) , _scroll(this, st::historyScroll, false) +, _updateHistoryItems([=] { updateHistoryItemsByTimer(); }) , _historyDown(_scroll, st::historyToDown) , _unreadMentions(_scroll, st::historyUnreadMentions) , _fieldAutocomplete(this, controller) @@ -204,6 +206,10 @@ HistoryWidget::HistoryWidget( Ui::InputField::Mode::MultiLine, tr::lng_message_ph()) , _kbScroll(this, st::botKbScroll) +, _membersDropdownShowTimer([=] { showMembersDropdown(); }) +, _scrollTimer([=] { scrollByTimer(); }) +, _saveDraftTimer([=] { saveDraft(); }) +, _saveCloudDraftTimer([=] { saveCloudDraft(); }) , _topShadow(this) { setAcceptDrops(true); @@ -212,7 +218,9 @@ HistoryWidget::HistoryWidget( update(); }, lifetime()); - connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(_scroll, &Ui::ScrollArea::scrolled, [=] { + handleScroll(); + }); _historyDown->addClickHandler([=] { historyDownClicked(); }); _unreadMentions->addClickHandler([=] { showNextUnreadMention(); }); _fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); }); @@ -235,12 +243,21 @@ HistoryWidget::HistoryWidget( connect(_field, &Ui::InputField::cancelled, [=] { escape(); }); - connect(_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed())); - connect(_field, SIGNAL(resized()), this, SLOT(onFieldResize())); - connect(_field, SIGNAL(focused()), this, SLOT(onFieldFocused())); - connect(_field, SIGNAL(changed()), this, SLOT(onTextChange())); - connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onWindowVisibleChanged())); - connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); + connect(_field, &Ui::InputField::tabbed, [=] { + fieldTabbed(); + }); + connect(_field, &Ui::InputField::resized, [=] { + fieldResized(); + }); + connect(_field, &Ui::InputField::focused, [=] { + fieldFocused(); + }); + connect(_field, &Ui::InputField::changed, [=] { + fieldChanged(); + }); + connect(App::wnd()->windowHandle(), &QWindow::visibleChanged, this, [=] { + windowIsVisibleChanged(); + }); initTabbedSelector(); @@ -249,26 +266,21 @@ HistoryWidget::HistoryWidget( this, [=] { chooseAttach(); })); - _updateHistoryItems.setSingleShot(true); - connect(&_updateHistoryItems, SIGNAL(timeout()), this, SLOT(onUpdateHistoryItems())); - - _scrollTimer.setSingleShot(false); - _highlightTimer.setCallback([this] { updateHighlightedMessage(); }); - _membersDropdownShowTimer.setSingleShot(true); - connect(&_membersDropdownShowTimer, SIGNAL(timeout()), this, SLOT(onMembersDropdownShow())); - - _saveDraftTimer.setSingleShot(true); - connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave())); - _saveCloudDraftTimer.setSingleShot(true); - connect(&_saveCloudDraftTimer, SIGNAL(timeout()), this, SLOT(onCloudDraftSave())); - _field->scrollTop().changes( + const auto rawTextEdit = _field->rawTextEdit().get(); + rpl::merge( + _field->scrollTop().changes() | rpl::to_empty, + base::qt_signal_producer( + rawTextEdit, + &QTextEdit::cursorPositionChanged) ) | rpl::start_with_next([=] { - onDraftSaveDelayed(); + saveDraftDelayed(); }, _field->lifetime()); - connect(_field->rawTextEdit(), SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed())); - connect(_field->rawTextEdit(), SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection); + + connect(rawTextEdit, &QTextEdit::cursorPositionChanged, this, [=] { + checkFieldAutocomplete(); + }, Qt::QueuedConnection); _fieldBarCancel->hide(); @@ -289,17 +301,17 @@ HistoryWidget::HistoryWidget( _fieldAutocomplete->mentionChosen( ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) { - onMentionInsert(data.user); + insertMention(data.user); }, lifetime()); _fieldAutocomplete->hashtagChosen( ) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) { - onHashtagOrBotCommandInsert(data.hashtag, data.method); + insertHashtagOrBotCommand(data.hashtag, data.method); }, lifetime()); _fieldAutocomplete->botCommandChosen( ) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) { - onHashtagOrBotCommandInsert(data.command, data.method); + insertHashtagOrBotCommand(data.command, data.method); }, lifetime()); _fieldAutocomplete->stickerChosen( @@ -459,7 +471,7 @@ HistoryWidget::HistoryWidget( return _peer && (_peer == user || !_peer->isUser()); }) | rpl::start_with_next([=](not_null user) { if (_fieldAutocomplete->clearFilteredBotCommands()) { - onCheckFieldAutocomplete(); + checkFieldAutocomplete(); } }, lifetime()); @@ -676,7 +688,7 @@ HistoryWidget::HistoryWidget( } else { fastShowAtEnd(action.history); if (cancelReply(lastKeyboardUsed) && !action.clearDraft) { - onCloudDraftSave(); + saveCloudDraft(); } } if (action.options.handleSupportSwitch) { @@ -1126,7 +1138,7 @@ void HistoryWidget::start() { }); } -void HistoryWidget::onMentionInsert(UserData *user) { +void HistoryWidget::insertMention(UserData *user) { QString replacement, entityTag; if (user->username.isEmpty()) { replacement = user->firstName; @@ -1140,7 +1152,7 @@ void HistoryWidget::onMentionInsert(UserData *user) { _field->insertTag(replacement, entityTag); } -void HistoryWidget::onHashtagOrBotCommandInsert( +void HistoryWidget::insertHashtagOrBotCommand( QString str, FieldAutocomplete::ChooseMethod method) { if (!_peer) { @@ -1270,7 +1282,7 @@ void HistoryWidget::updateStickersByEmoji() { _fieldAutocomplete->showStickers(emoji); } -void HistoryWidget::onTextChange() { +void HistoryWidget::fieldChanged() { InvokeQueued(this, [=] { updateInlineBotQuery(); updateStickersByEmoji(); @@ -1295,16 +1307,16 @@ void HistoryWidget::onTextChange() { updateControlsGeometry(); } - _saveCloudDraftTimer.stop(); + _saveCloudDraftTimer.cancel(); if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) { return; } _saveDraftText = true; - onDraftSave(true); + saveDraft(true); } -void HistoryWidget::onDraftSaveDelayed() { +void HistoryWidget::saveDraftDelayed() { if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) { return; } @@ -1315,18 +1327,18 @@ void HistoryWidget::onDraftSaveDelayed() { return; } } - onDraftSave(true); + saveDraft(true); } -void HistoryWidget::onDraftSave(bool delayed) { +void HistoryWidget::saveDraft(bool delayed) { if (!_peer) return; if (delayed) { auto ms = crl::now(); if (!_saveDraftStart) { _saveDraftStart = ms; - return _saveDraftTimer.start(kSaveDraftTimeout); + return _saveDraftTimer.callOnce(kSaveDraftTimeout); } else if (ms - _saveDraftStart < kSaveDraftAnywayTimeout) { - return _saveDraftTimer.start(kSaveDraftTimeout); + return _saveDraftTimer.callOnce(kSaveDraftTimeout); } } writeDrafts(nullptr, nullptr); @@ -1347,7 +1359,7 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { } } -void HistoryWidget::onCloudDraftSave() { +void HistoryWidget::saveCloudDraft() { controller()->session().api().saveCurrentDraftToCloud(); } @@ -1357,7 +1369,7 @@ void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraf bool save = _peer && (_saveDraftStart > 0); _saveDraftStart = 0; - _saveDraftTimer.stop(); + _saveDraftTimer.cancel(); if (_saveDraftText) { if (save) { Storage::MessageDraft storedLocalDraft, storedEditDraft; @@ -1421,7 +1433,7 @@ void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraf } if (!_editMsgId && !_inlineBot) { - _saveCloudDraftTimer.start(kSaveCloudDraftIdleTimeout); + _saveCloudDraftTimer.callOnce(kSaveCloudDraftIdleTimeout); } } @@ -1800,7 +1812,7 @@ void HistoryWidget::showHistory( _previewCache.clear(); _fieldBarCancel->hide(); - _membersDropdownShowTimer.stop(); + _membersDropdownShowTimer.cancel(); _scroll->takeWidget().destroy(); clearInlineBot(); @@ -1883,7 +1895,7 @@ void HistoryWidget::showHistory( object_ptr(this, _scroll, controller(), _history)); _list->show(); - _updateHistoryItems.stop(); + _updateHistoryItems.cancel(); setupPinnedTracker(); if (_history->scrollTopItem @@ -2174,7 +2186,7 @@ void HistoryWidget::updateControlsVisibility() { update(); } } else if (editingMessage() || _canSendMessages) { - onCheckFieldAutocomplete(); + checkFieldAutocomplete(); _unblock->hide(); _botStart->hide(); _joinChannel->hide(); @@ -2351,7 +2363,7 @@ void HistoryWidget::unreadCountUpdated() { crl::on_main(this, [=, history = _history] { if (history == _history) { controller()->showBackFromStack(); - emit cancelled(); + _cancelRequests.fire({}); } }); } else { @@ -2784,7 +2796,7 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { }); } -void HistoryWidget::onScroll() { +void HistoryWidget::handleScroll() { preloadHistoryIfNeeded(); visibleAreaUpdated(); updatePinnedViewer(); @@ -2892,7 +2904,7 @@ void HistoryWidget::checkReplyReturns() { } } -void HistoryWidget::onInlineBotCancel() { +void HistoryWidget::cancelInlineBot() { auto &textWithTags = _field->getTextWithTags(); if (textWithTags.text.size() > _inlineBotUsername.size() + 2) { setFieldText( @@ -2906,8 +2918,10 @@ void HistoryWidget::onInlineBotCancel() { } } -void HistoryWidget::onWindowVisibleChanged() { - QTimer::singleShot(0, this, SLOT(preloadHistoryIfNeeded())); +void HistoryWidget::windowIsVisibleChanged() { + InvokeQueued(this, [=] { + preloadHistoryIfNeeded(); + }); } void HistoryWidget::historyDownClicked() { @@ -3100,7 +3114,7 @@ void HistoryWidget::send(Api::SendOptions options) { clearFieldText(); _saveDraftText = true; _saveDraftStart = crl::now(); - onDraftSave(); + saveDraft(); hideSelectorControlsAnimated(); @@ -3376,7 +3390,7 @@ void HistoryWidget::chooseAttach() { void HistoryWidget::sendButtonClicked() { const auto type = _send->type(); if (type == Ui::SendButton::Type::Cancel) { - onInlineBotCancel(); + cancelInlineBot(); } else if (type != Ui::SendButton::Type::Record) { send({}); } @@ -3444,7 +3458,7 @@ void HistoryWidget::sendBotCommand( if (replyTo) { if (_replyToId == replyTo) { cancelReply(); - onCloudDraftSave(); + saveCloudDraft(); } if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) { if (_kbShown) toggleKeyboard(false); @@ -3462,7 +3476,7 @@ void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) { if (replyTo) { if (_replyToId == replyTo) { cancelReply(); - onCloudDraftSave(); + saveCloudDraft(); } if (_keyboard->singleUse() && _keyboard->hasMarkup() && lastKeyboardUsed) { if (_kbShown) toggleKeyboard(false); @@ -3795,20 +3809,20 @@ void HistoryWidget::startBotCommand() { void HistoryWidget::setMembersShowAreaActive(bool active) { if (!active) { - _membersDropdownShowTimer.stop(); + _membersDropdownShowTimer.cancel(); } if (active && _peer && (_peer->isChat() || _peer->isMegagroup())) { if (_membersDropdown) { _membersDropdown->otherEnter(); } else if (!_membersDropdownShowTimer.isActive()) { - _membersDropdownShowTimer.start(kShowMembersDropdownTimeoutMs); + _membersDropdownShowTimer.callOnce(kShowMembersDropdownTimeoutMs); } } else if (_membersDropdown) { _membersDropdown->otherLeave(); } } -void HistoryWidget::onMembersDropdownShow() { +void HistoryWidget::showMembersDropdown() { if (!_peer) { return; } @@ -3981,7 +3995,7 @@ void HistoryWidget::clearInlineBot() { if (_inlineResults) { _inlineResults->clearInlineBot(); } - onCheckFieldAutocomplete(); + checkFieldAutocomplete(); } void HistoryWidget::inlineBotChanged() { @@ -3994,19 +4008,19 @@ void HistoryWidget::inlineBotChanged() { } } -void HistoryWidget::onFieldResize() { +void HistoryWidget::fieldResized() { moveFieldControls(); updateHistoryGeometry(); updateField(); } -void HistoryWidget::onFieldFocused() { +void HistoryWidget::fieldFocused() { if (_list) { _list->clearSelected(true); } } -void HistoryWidget::onCheckFieldAutocomplete() { +void HistoryWidget::checkFieldAutocomplete() { if (!_history || _a_show.animating()) { return; } @@ -4357,19 +4371,22 @@ bool HistoryWidget::skipItemRepaint() { if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) { return false; } - _updateHistoryItems.start( + _updateHistoryItems.callOnce( _lastScrolled + kSkipRepaintWhileScrollMs - ms); return true; } -void HistoryWidget::onUpdateHistoryItems() { - if (!_list) return; +void HistoryWidget::updateHistoryItemsByTimer() { + if (!_list) { + return; + } auto ms = crl::now(); if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) { _list->update(); } else { - _updateHistoryItems.start(_lastScrolled + kSkipRepaintWhileScrollMs - ms); + _updateHistoryItems.callOnce( + _lastScrolled + kSkipRepaintWhileScrollMs - ms); } } @@ -4945,7 +4962,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { e->ignore(); } else if (e->key() == Qt::Key_Back) { controller()->showBackFromStack(); - emit cancelled(); + _cancelRequests.fire({}); } else if (e->key() == Qt::Key_PageDown) { _scroll->keyPressEvent(e); } else if (e->key() == Qt::Key_PageUp) { @@ -5089,7 +5106,7 @@ bool HistoryWidget::showSlowmodeError() { return true; } -void HistoryWidget::onFieldTabbed() { +void HistoryWidget::fieldTabbed() { if (_supportAutocomplete) { _supportAutocomplete->activate(_field.data()); } else if (!_fieldAutocomplete->isHidden()) { @@ -5119,7 +5136,7 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) { clearFieldText(); _saveDraftText = true; _saveDraftStart = crl::now(); - onDraftSave(); + saveDraft(); auto &bots = cRefRecentInlineBots(); const auto index = bots.indexOf(result.bot); @@ -5368,8 +5385,8 @@ bool HistoryWidget::sendExistingDocument( clearFieldText(); //_saveDraftText = true; //_saveDraftStart = crl::now(); - //onDraftSave(); - onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft + //saveDraft(); + saveCloudDraft(); // won't be needed if SendInlineBotResult will clear the cloud draft } hideSelectorControlsAnimated(); @@ -5506,7 +5523,7 @@ void HistoryWidget::replyToMessage(not_null item) { _saveDraftText = true; _saveDraftStart = crl::now(); - onDraftSave(); + saveDraft(); _field->setFocus(); } @@ -5572,7 +5589,7 @@ void HistoryWidget::editMessage(not_null item) { _saveDraftText = true; _saveDraftStart = crl::now(); - onDraftSave(); + saveDraft(); _field->setFocus(); } @@ -5643,7 +5660,7 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { if (wasReply) { _saveDraftText = true; _saveDraftStart = crl::now(); - onDraftSave(); + saveDraft(); } if (!_editMsgId && _keyboard->singleUse() @@ -5658,7 +5675,7 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { void HistoryWidget::cancelReplyAfterMediaSend(bool lastKeyboardUsed) { if (cancelReply(lastKeyboardUsed)) { - onCloudDraftSave(); + saveCloudDraft(); } } @@ -5686,7 +5703,7 @@ void HistoryWidget::cancelEdit() { _saveDraftText = true; _saveDraftStart = crl::now(); - onDraftSave(); + saveDraft(); mouseMoveEvent(nullptr); if (!readyToForward() && (!_previewData || _previewData->pendingTill < 0) && !replyToId()) { @@ -5696,7 +5713,7 @@ void HistoryWidget::cancelEdit() { auto old = _textUpdateEvents; _textUpdateEvents = 0; - onTextChange(); + fieldChanged(); _textUpdateEvents = old; if (!canWriteMessage()) { @@ -5718,7 +5735,7 @@ void HistoryWidget::cancelFieldAreaState() { _saveDraftText = true; _saveDraftStart = crl::now(); - onDraftSave(); + saveDraft(); } else if (_editMsgId) { cancelEdit(); } else if (readyToForward()) { @@ -5878,7 +5895,7 @@ void HistoryWidget::fullPeerUpdated(PeerData *peer) { refreshSilentToggle(); refresh = true; } - onCheckFieldAutocomplete(); + checkFieldAutocomplete(); _list->updateBotInfo(); handlePeerUpdate(); @@ -5967,7 +5984,7 @@ void HistoryWidget::escape() { if (_nonEmptySelection && _list) { clearSelected(); } else if (_isInlineBot) { - onInlineBotCancel(); + cancelInlineBot(); } else if (_editMsgId) { if (_replyEditMsg && PrepareEditText(_replyEditMsg) != _field->getTextWithTags()) { @@ -5989,7 +6006,7 @@ void HistoryWidget::escape() { } else if (_replyToId && _field->getTextWithTags().text.isEmpty()) { cancelReply(); } else { - emit cancelled(); + _cancelRequests.fire({}); } } @@ -6460,8 +6477,10 @@ QPoint HistoryWidget::clampMousePosition(QPoint point) { return point; } -void HistoryWidget::onScrollTimer() { - auto d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(Ui::kMaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(Ui::kMaxScrollSpeed)); +void HistoryWidget::scrollByTimer() { + const auto d = (_scrollDelta > 0) + ? qMin(_scrollDelta * 3 / 20 + 1, int32(Ui::kMaxScrollSpeed)) + : qMax(_scrollDelta * 3 / 20 - 1, -int32(Ui::kMaxScrollSpeed)); _scroll->scrollToY(_scroll->scrollTop() + d); } @@ -6474,14 +6493,14 @@ void HistoryWidget::checkSelectingScroll(QPoint point) { _scrollDelta = 0; } if (_scrollDelta) { - _scrollTimer.start(15); + _scrollTimer.callEach(15); } else { - _scrollTimer.stop(); + _scrollTimer.cancel(); } } void HistoryWidget::noSelectingScroll() { - _scrollTimer.stop(); + _scrollTimer.cancel(); } bool HistoryWidget::touchScroll(const QPoint &delta) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index bad3b769d..0f6b22add 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -108,8 +108,6 @@ class HistoryInner; struct HistoryMessageMarkupButton; class HistoryWidget final : public Window::AbstractSectionWidget { - Q_OBJECT - public: using FieldHistoryAction = Ui::InputField::HistoryAction; using RecordLock = HistoryView::Controls::RecordLock; @@ -241,7 +239,11 @@ public: void updateFieldSubmitSettings(); + void activate(); void setInnerFocus(); + [[nodiscard]] rpl::producer<> cancelRequests() const { + return _cancelRequests.events(); + } void updateNotifyControls(); @@ -290,40 +292,6 @@ protected: void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; -signals: - void cancelled(); - -public slots: - void onScroll(); - - void activate(); - void onTextChange(); - - void onFieldTabbed(); - - void onWindowVisibleChanged(); - - void onFieldFocused(); - void onFieldResize(); - void onCheckFieldAutocomplete(); - void onScrollTimer(); - - void onDraftSaveDelayed(); - void onDraftSave(bool delayed = false); - void onCloudDraftSave(); - - void onUpdateHistoryItems(); - - // checks if we are too close to the top or to the bottom - // in the scroll area and preloads history if needed - void preloadHistoryIfNeeded(); - -private slots: - void onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method); - void onMentionInsert(UserData *user); - void onInlineBotCancel(); - void onMembersDropdownShow(); - private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; @@ -353,6 +321,30 @@ private: void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); void updateField(); + void fieldChanged(); + void fieldTabbed(); + void fieldFocused(); + void fieldResized(); + + void insertHashtagOrBotCommand( + QString str, + FieldAutocomplete::ChooseMethod method); + void insertMention(UserData *user); + void cancelInlineBot(); + void saveDraft(bool delayed = false); + void saveCloudDraft(); + void saveDraftDelayed(); + void checkFieldAutocomplete(); + void showMembersDropdown(); + void windowIsVisibleChanged(); + + // Checks if we are too close to the top or to the bottom + // in the scroll area and preloads history if needed. + void preloadHistoryIfNeeded(); + + void handleScroll(); + void scrollByTimer(); + void updateHistoryItemsByTimer(); [[nodiscard]] Dialogs::EntryState computeDialogsEntryState() const; void refreshTopBarActiveChat(); @@ -580,6 +572,8 @@ private: void setupScheduledToggle(); void refreshScheduledToggle(); + bool kbWasHidden() const; + MTP::Sender _api; MsgId _replyToId = 0; Ui::Text::String _replyToName; @@ -648,7 +642,7 @@ private: int _lastScrollTop = 0; // gifs optimization crl::time _lastScrolled = 0; - QTimer _updateHistoryItems; + base::Timer _updateHistoryItems; crl::time _lastUserScrolled = 0; bool _synteticScrollEvent = false; @@ -692,15 +686,13 @@ private: bool _inReplyEditForward = false; bool _inClickable = false; - bool kbWasHidden() const; - bool _kbShown = false; HistoryItem *_kbReplyTo = nullptr; object_ptr _kbScroll; QPointer _keyboard; object_ptr _membersDropdown = { nullptr }; - QTimer _membersDropdownShowTimer; + base::Timer _membersDropdownShowTimer; object_ptr _inlineResults = { nullptr }; std::unique_ptr _tabbedPanel; @@ -719,7 +711,7 @@ private: Window::SlideDirection _showDirection; QPixmap _cacheUnder, _cacheOver; - QTimer _scrollTimer; + base::Timer _scrollTimer; int32 _scrollDelta = 0; MsgId _highlightedMessageId = 0; @@ -729,7 +721,8 @@ private: crl::time _saveDraftStart = 0; bool _saveDraftText = false; - QTimer _saveDraftTimer, _saveCloudDraftTimer; + base::Timer _saveDraftTimer; + base::Timer _saveCloudDraftTimer; base::weak_ptr _topToast; @@ -738,4 +731,6 @@ private: int _topDelta = 0; + rpl::event_stream<> _cancelRequests; + }; 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 fd852a95d..ba71b97f3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -762,8 +762,8 @@ void ComposeControls::setText(const TextWithTags &textWithTags) { auto cursor = _field->textCursor(); cursor.movePosition(QTextCursor::End); _field->setTextCursor(cursor); - _textUpdateEvents = /*TextUpdateEvent::SaveDraft - | */TextUpdateEvent::SendTyping; + _textUpdateEvents = TextUpdateEvent::SaveDraft + | TextUpdateEvent::SendTyping; //previewCancel(); //_previewCancelled = false; @@ -978,8 +978,8 @@ void ComposeControls::initAutocomplete() { setText({}); //_saveDraftText = true; //_saveDraftStart = crl::now(); - //onDraftSave(); - //onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft + //saveDraft(); + //saveCloudDraft(); // won't be needed if SendInlineBotResult will clear the cloud draft _fileChosen.fire(FileChosen{ .document = data.sticker, .options = data.options, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 5b6200862..01e148585 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -150,7 +150,7 @@ public: private: enum class TextUpdateEvent { - //SaveDraft = (1 << 0), + SaveDraft = (1 << 0), SendTyping = (1 << 1), }; using TextUpdateEvents = base::flags; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index b7135f1be..a076837d6 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1034,14 +1034,6 @@ bool RepliesWidget::sendExistingDocument( message.action.options = options; Api::SendExistingDocument(std::move(message), document); - //if (_fieldAutocomplete->stickersShown()) { - // clearFieldText(); - // //_saveDraftText = true; - // //_saveDraftStart = crl::now(); - // //onDraftSave(); - // onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft - //} - _composeControls->cancelReplyMessage(); finishSending(); 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 15741b1d8..4e09b1040 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -673,14 +673,6 @@ bool ScheduledWidget::sendExistingDocument( message.action.options = options; Api::SendExistingDocument(std::move(message), document); - //if (_fieldAutocomplete->stickersShown()) { - // clearFieldText(); - // //_saveDraftText = true; - // //_saveDraftStart = crl::now(); - // //onDraftSave(); - // onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft - //} - _composeControls->hidePanelsAnimated(); _composeControls->focus(); return true; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 8c67fcfcc..4c47f3421 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -244,7 +244,11 @@ MainWidget::MainWidget( setupConnectingWidget(); connect(_dialogs, SIGNAL(cancelled()), this, SLOT(dialogsCancelled())); - connect(_history, &HistoryWidget::cancelled, [=] { handleHistoryBack(); }); + + _history->cancelRequests( + ) | rpl::start_with_next([=] { + handleHistoryBack(); + }, lifetime()); Core::App().calls().currentCallValue( ) | rpl::start_with_next([=](Calls::Call *call) { From b3eb7858e61254734ffb95652a7264cad72fde8e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Nov 2020 20:27:08 +0300 Subject: [PATCH 097/370] Save local drafts in scheduled / replies sections. Fix inline bot switch inline in scheduled / replies sections. --- Telegram/SourceFiles/apiwrap.cpp | 16 +- Telegram/SourceFiles/data/data_drafts.h | 71 +++ Telegram/SourceFiles/history/history.cpp | 98 ++-- Telegram/SourceFiles/history/history.h | 49 +- .../SourceFiles/history/history_widget.cpp | 148 +++--- Telegram/SourceFiles/history/history_widget.h | 9 +- .../history_view_compose_controls.cpp | 431 ++++++++++++++--- .../controls/history_view_compose_controls.h | 46 +- .../view/history_view_replies_section.cpp | 2 +- Telegram/SourceFiles/mainwidget.cpp | 4 +- .../platform/win/window_title_win.cpp | 2 + .../platform/win/windows_event_filter.cpp | 58 ++- .../SourceFiles/storage/storage_account.cpp | 442 ++++++++++++------ .../SourceFiles/storage/storage_account.h | 35 +- .../SourceFiles/window/window_peer_menu.cpp | 4 +- 15 files changed, 1025 insertions(+), 390 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index d21aec624..0f6ee2581 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2441,16 +2441,14 @@ void ApiWrap::saveCurrentDraftToCloud() { Core::App().saveCurrentDraftsToHistories(); for (const auto controller : _session->windows()) { - if (const auto peer = controller->activeChatCurrent().peer()) { - if (const auto history = _session->data().historyLoaded(peer)) { - _session->local().writeDrafts(history); + if (const auto history = controller->activeChatCurrent().history()) { + _session->local().writeDrafts(history); - const auto localDraft = history->localDraft(); - const auto cloudDraft = history->cloudDraft(); - if (!Data::draftsAreEqual(localDraft, cloudDraft) - && !_session->supportMode()) { - saveDraftToCloudDelayed(history); - } + const auto localDraft = history->localDraft(); + const auto cloudDraft = history->cloudDraft(); + if (!Data::draftsAreEqual(localDraft, cloudDraft) + && !_session->supportMode()) { + saveDraftToCloudDelayed(history); } } } diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h index 57428e62e..b797cb3d3 100644 --- a/Telegram/SourceFiles/data/data_drafts.h +++ b/Telegram/SourceFiles/data/data_drafts.h @@ -48,6 +48,77 @@ struct Draft { mtpRequestId saveRequestId = 0; }; +class DraftKey { +public: + [[nodiscard]] static DraftKey None() { + return 0; + } + [[nodiscard]] static DraftKey Local() { + return kLocalDraftIndex; + } + [[nodiscard]] static DraftKey LocalEdit() { + return kLocalDraftIndex + kEditDraftShift; + } + [[nodiscard]] static DraftKey Cloud() { + return kCloudDraftIndex; + } + [[nodiscard]] static DraftKey Scheduled() { + return kScheduledDraftIndex; + } + [[nodiscard]] static DraftKey ScheduledEdit() { + return kScheduledDraftIndex + kEditDraftShift; + } + [[nodiscard]] static DraftKey Replies(MsgId rootId) { + return rootId; + } + [[nodiscard]] static DraftKey RepliesEdit(MsgId rootId) { + return rootId + kEditDraftShift; + } + + [[nodiscard]] static DraftKey FromSerialized(int32 value) { + return value; + } + [[nodiscard]] int32 serialize() const { + return _value; + } + + inline bool operator<(const DraftKey &other) const { + return _value < other._value; + } + inline bool operator==(const DraftKey &other) const { + return _value == other._value; + } + inline bool operator>(const DraftKey &other) const { + return (other < *this); + } + inline bool operator<=(const DraftKey &other) const { + return !(other < *this); + } + inline bool operator>=(const DraftKey &other) const { + return !(*this < other); + } + inline bool operator!=(const DraftKey &other) const { + return !(*this == other); + } + inline explicit operator bool() const { + return _value != 0; + } + +private: + DraftKey(int value) : _value(value) { + } + + static constexpr auto kLocalDraftIndex = -1; + static constexpr auto kCloudDraftIndex = -2; + static constexpr auto kScheduledDraftIndex = -3; + static constexpr auto kEditDraftShift = ServerMaxMsgId; + + int _value = 0; + +}; + +using HistoryDrafts = base::flat_map>; + inline bool draftStringIsEmpty(const QString &text) { for_const (auto ch, text) { if (!ch.isSpace()) { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 8e999053b..9cc17dac8 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -179,22 +179,22 @@ void History::itemVanished(not_null item) { } } -void History::setLocalDraft(std::unique_ptr &&draft) { - _localDraft = std::move(draft); -} - -void History::takeLocalDraft(History *from) { - if (auto &draft = from->_localDraft) { - if (!draft->textWithTags.text.isEmpty() && !_localDraft) { - _localDraft = std::move(draft); - - // Edit and reply to drafts can't migrate. - // Cloud drafts do not migrate automatically. - _localDraft->msgId = 0; - } - from->clearLocalDraft(); - session().api().saveDraftToCloudDelayed(from); +void History::takeLocalDraft(not_null from) { + const auto i = from->_drafts.find(Data::DraftKey::Local()); + if (i == end(from->_drafts)) { + return; } + auto &draft = i->second; + if (!draft->textWithTags.text.isEmpty() + && !_drafts.contains(Data::DraftKey::Local())) { + // Edit and reply to drafts can't migrate. + // Cloud drafts do not migrate automatically. + draft->msgId = 0; + + setLocalDraft(std::move(draft)); + } + from->clearLocalDraft(); + session().api().saveDraftToCloudDelayed(from); } void History::createLocalDraftFromCloud() { @@ -227,9 +227,51 @@ void History::createLocalDraftFromCloud() { } } -void History::setCloudDraft(std::unique_ptr &&draft) { - _cloudDraft = std::move(draft); - cloudDraftTextCache.clear(); +Data::Draft *History::draft(Data::DraftKey key) const { + if (!key) { + return nullptr; + } + const auto i = _drafts.find(key); + return (i != _drafts.end()) ? i->second.get() : nullptr; +} + +void History::setDraft(Data::DraftKey key, std::unique_ptr &&draft) { + if (!key) { + return; + } + const auto changingCloudDraft = (key == Data::DraftKey::Cloud()); + if (changingCloudDraft) { + cloudDraftTextCache.clear(); + } + if (draft) { + _drafts[key] = std::move(draft); + } else if (_drafts.remove(key) && changingCloudDraft) { + updateChatListSortPosition(); + } +} + +const Data::HistoryDrafts &History::draftsMap() const { + return _drafts; +} + +void History::setDraftsMap(Data::HistoryDrafts &&map) { + for (auto &[key, draft] : _drafts) { + map[key] = std::move(draft); + } + _drafts = std::move(map); +} + +void History::clearDraft(Data::DraftKey key) { + setDraft(key, nullptr); +} + +void History::clearDrafts() { + const auto changingCloudDraft = _drafts.contains(Data::DraftKey::Cloud()); + _drafts.clear(); + if (changingCloudDraft) { + cloudDraftTextCache.clear(); + updateChatListSortPosition(); + } } Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) { @@ -287,22 +329,6 @@ void History::clearSentDraftText(const QString &text) { accumulate_max(_lastSentDraftTime, base::unixtime::now()); } -void History::setEditDraft(std::unique_ptr &&draft) { - _editDraft = std::move(draft); -} - -void History::clearLocalDraft() { - _localDraft = nullptr; -} - -void History::clearCloudDraft() { - if (_cloudDraft) { - _cloudDraft = nullptr; - cloudDraftTextCache.clear(); - updateChatListSortPosition(); - } -} - void History::applyCloudDraft() { if (session().supportMode()) { updateChatListEntry(); @@ -314,10 +340,6 @@ void History::applyCloudDraft() { } } -void History::clearEditDraft() { - _editDraft = nullptr; -} - void History::draftSavedToCloud() { updateChatListEntry(); session().local().writeDrafts(this); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index ebb5e0181..9c9f92055 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_types.h" #include "data/data_peer.h" +#include "data/data_drafts.h" #include "dialogs/dialogs_entry.h" #include "history/view/history_view_send_action.h" #include "base/observer.h" @@ -302,31 +303,48 @@ public: void eraseFromUnreadMentions(MsgId msgId); void addUnreadMentionsSlice(const MTPmessages_Messages &result); + Data::Draft *draft(Data::DraftKey key) const; + void setDraft(Data::DraftKey key, std::unique_ptr &&draft); + void clearDraft(Data::DraftKey key); + + [[nodiscard]] const Data::HistoryDrafts &draftsMap() const; + void setDraftsMap(Data::HistoryDrafts &&map); + Data::Draft *localDraft() const { - return _localDraft.get(); + return draft(Data::DraftKey::Local()); + } + Data::Draft *localEditDraft() const { + return draft(Data::DraftKey::LocalEdit()); } Data::Draft *cloudDraft() const { - return _cloudDraft.get(); + return draft(Data::DraftKey::Cloud()); } - Data::Draft *editDraft() const { - return _editDraft.get(); + void setLocalDraft(std::unique_ptr &&draft) { + setDraft(Data::DraftKey::Local(), std::move(draft)); } - void setLocalDraft(std::unique_ptr &&draft); - void takeLocalDraft(History *from); - void setCloudDraft(std::unique_ptr &&draft); + void setLocalEditDraft(std::unique_ptr &&draft) { + setDraft(Data::DraftKey::LocalEdit(), std::move(draft)); + } + void setCloudDraft(std::unique_ptr &&draft) { + setDraft(Data::DraftKey::Cloud(), std::move(draft)); + } + void clearLocalDraft() { + clearDraft(Data::DraftKey::Local()); + } + void clearCloudDraft() { + clearDraft(Data::DraftKey::Cloud()); + } + void clearLocalEditDraft() { + clearDraft(Data::DraftKey::LocalEdit()); + } + void clearDrafts(); Data::Draft *createCloudDraft(const Data::Draft *fromDraft); bool skipCloudDraft(const QString &text, MsgId replyTo, TimeId date) const; void setSentDraftText(const QString &text); void clearSentDraftText(const QString &text); - void setEditDraft(std::unique_ptr &&draft); - void clearLocalDraft(); - void clearCloudDraft(); + void takeLocalDraft(not_null from); void applyCloudDraft(); - void clearEditDraft(); void draftSavedToCloud(); - Data::Draft *draft() { - return _editDraft ? editDraft() : localDraft(); - } const MessageIdsList &forwardDraft() const { return _forwardDraft; @@ -560,8 +578,7 @@ private: }; std::unique_ptr _buildingFrontBlock; - std::unique_ptr _localDraft, _cloudDraft; - std::unique_ptr _editDraft; + Data::HistoryDrafts _drafts; std::optional _lastSentDraftText; TimeId _lastSentDraftTime = 0; MessageIdsList _forwardDraft; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 325f18aae..b9b47f918 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1331,8 +1331,9 @@ void HistoryWidget::saveDraftDelayed() { } void HistoryWidget::saveDraft(bool delayed) { - if (!_peer) return; - if (delayed) { + if (!_peer) { + return; + } else if (delayed) { auto ms = crl::now(); if (!_saveDraftStart) { _saveDraftStart = ms; @@ -1341,21 +1342,21 @@ void HistoryWidget::saveDraft(bool delayed) { return _saveDraftTimer.callOnce(kSaveDraftTimeout); } } - writeDrafts(nullptr, nullptr); + writeDrafts(); } void HistoryWidget::saveFieldToHistoryLocalDraft() { if (!_history) return; if (_editMsgId) { - _history->setEditDraft(std::make_unique(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId)); + _history->setLocalEditDraft(std::make_unique(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId)); } else { if (_replyToId || !_field->empty()) { _history->setLocalDraft(std::make_unique(_field, _replyToId, _previewCancelled)); } else { _history->clearLocalDraft(); } - _history->clearEditDraft(); + _history->clearLocalEditDraft(); } } @@ -1363,74 +1364,47 @@ void HistoryWidget::saveCloudDraft() { controller()->session().api().saveCurrentDraftToCloud(); } -void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft) { - Data::Draft *historyLocalDraft = _history ? _history->localDraft() : nullptr; - if (!localDraft && _editMsgId) localDraft = &historyLocalDraft; +void HistoryWidget::writeDraftTexts() { + Expects(_history != nullptr); - bool save = _peer && (_saveDraftStart > 0); + session().local().writeDrafts( + _history, + _editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(), + Storage::MessageDraft{ + _editMsgId ? _editMsgId : _replyToId, + _field->getTextWithTags(), + _previewCancelled, + }); + if (_migrated) { + _migrated->clearDrafts(); + session().local().writeDrafts(_migrated); + } +} + +void HistoryWidget::writeDraftCursors() { + Expects(_history != nullptr); + + session().local().writeDraftCursors( + _history, + _editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(), + MessageCursor(_field)); + if (_migrated) { + _migrated->clearDrafts(); + session().local().writeDraftCursors(_migrated); + } +} + +void HistoryWidget::writeDrafts() { + const auto save = (_history != nullptr) && (_saveDraftStart > 0); _saveDraftStart = 0; _saveDraftTimer.cancel(); - if (_saveDraftText) { - if (save) { - Storage::MessageDraft storedLocalDraft, storedEditDraft; - if (localDraft) { - if (*localDraft) { - storedLocalDraft = Storage::MessageDraft{ - (*localDraft)->msgId, - (*localDraft)->textWithTags, - (*localDraft)->previewCancelled - }; - } - } else { - storedLocalDraft = Storage::MessageDraft{ - _replyToId, - _field->getTextWithTags(), - _previewCancelled - }; - } - if (editDraft) { - if (*editDraft) { - storedEditDraft = Storage::MessageDraft{ - (*editDraft)->msgId, - (*editDraft)->textWithTags, - (*editDraft)->previewCancelled - }; - } - } else if (_editMsgId) { - storedEditDraft = Storage::MessageDraft{ - _editMsgId, - _field->getTextWithTags(), - _previewCancelled - }; - } - session().local().writeDrafts(_peer->id, storedLocalDraft, storedEditDraft); - if (_migrated) { - session().local().writeDrafts(_migrated->peer->id, {}, {}); - } - } - _saveDraftText = false; - } if (save) { - MessageCursor localCursor, editCursor; - if (localDraft) { - if (*localDraft) { - localCursor = (*localDraft)->cursor; - } - } else { - localCursor = MessageCursor(_field); - } - if (editDraft) { - if (*editDraft) { - editCursor = (*editDraft)->cursor; - } - } else if (_editMsgId) { - editCursor = MessageCursor(_field); - } - session().local().writeDraftCursors(_peer->id, localCursor, editCursor); - if (_migrated) { - session().local().writeDraftCursors(_migrated->peer->id, {}, {}); + if (_saveDraftText) { + writeDraftTexts(); } + writeDraftCursors(); } + _saveDraftText = false; if (!_editMsgId && !_inlineBot) { _saveCloudDraftTimer.callOnce(kSaveCloudDraftIdleTimeout); @@ -1498,13 +1472,17 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U false); if (to.section == Section::Replies) { + history->setDraft( + Data::DraftKey::Replies(to.rootId), + std::move(draft)); controller()->showRepliesForMessage(history, to.rootId); + } else if (to.section == Section::Scheduled) { + history->setDraft(Data::DraftKey::Scheduled(), std::move(draft)); + controller()->showSection( + HistoryView::ScheduledMemento(history)); } else { history->setLocalDraft(std::move(draft)); - if (to.section == Section::Scheduled) { - controller()->showSection( - HistoryView::ScheduledMemento(history)); - } else if (history == _history) { + if (history == _history) { applyDraft(); } else { Ui::showPeerHistory(history->peer, ShowAtUnreadMsgId); @@ -1619,9 +1597,13 @@ void HistoryWidget::fastShowAtEnd(not_null history) { void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { InvokeQueued(this, [=] { updateStickersByEmoji(); }); - auto draft = _history ? _history->draft() : nullptr; + auto draft = !_history + ? nullptr + : _history->localEditDraft() + ? _history->localEditDraft() + : _history->localDraft(); auto fieldAvailable = canWriteMessage(); - if (!draft || (!_history->editDraft() && !fieldAvailable)) { + if (!draft || (!_history->localEditDraft() && !fieldAvailable)) { auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0); clearFieldText(0, fieldHistoryAction); _field->setFocus(); @@ -1642,7 +1624,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; _previewCancelled = draft->previewCancelled; _replyEditMsg = nullptr; - if (auto editDraft = _history->editDraft()) { + if (const auto editDraft = _history->localEditDraft()) { _editMsgId = editDraft->msgId; _replyToId = 0; } else { @@ -1778,8 +1760,7 @@ void HistoryWidget::showHistory( } controller()->session().api().saveCurrentDraftToCloud(); if (_migrated) { - _migrated->clearLocalDraft(); // use migrated draft only once - _migrated->clearEditDraft(); + _migrated->clearDrafts(); // use migrated draft only once } _history->showAtMsgId = _showAtMsgId; @@ -1910,11 +1891,6 @@ void HistoryWidget::showHistory( handlePeerUpdate(); session().local().readDraftsWithCursors(_history); - if (_migrated) { - session().local().readDraftsWithCursors(_migrated); - _migrated->clearEditDraft(); - _history->takeLocalDraft(_migrated); - } applyDraft(); _send->finishAnimating(); @@ -3006,16 +2982,16 @@ void HistoryWidget::saveEditMsg() { cancelEdit(); } })(); - if (auto editDraft = history->editDraft()) { + if (const auto editDraft = history->localEditDraft()) { if (editDraft->saveRequestId == requestId) { - history->clearEditDraft(); + history->clearLocalEditDraft(); history->session().local().writeDrafts(history); } } }; const auto fail = [=](const RPCError &error, mtpRequestId requestId) { - if (const auto editDraft = history->editDraft()) { + if (const auto editDraft = history->localEditDraft()) { if (editDraft->saveRequestId == requestId) { editDraft->saveRequestId = 0; } @@ -5563,7 +5539,7 @@ void HistoryWidget::editMessage(not_null item) { editData.text.size(), QFIXED_MAX }; - _history->setEditDraft(std::make_unique( + _history->setLocalEditDraft(std::make_unique( editData, item->id, cursor, @@ -5693,7 +5669,7 @@ void HistoryWidget::cancelEdit() { _replyEditMsg = nullptr; _editMsgId = 0; - _history->clearEditDraft(); + _history->clearLocalEditDraft(); applyDraft(); if (_saveEditMsgRequestId) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 0f6b22add..73d967fc7 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -41,10 +41,6 @@ class Widget; struct ResultSelected; } // namespace InlineBots -namespace Data { -struct Draft; -} // namespace Data - namespace Support { class Autocomplete; struct Contact; @@ -526,8 +522,9 @@ private: // This one is syntetic. void synteticScrollToY(int y); - void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft); - void writeDrafts(History *history); + void writeDrafts(); + void writeDraftTexts(); + void writeDraftCursors(); void setFieldText( const TextWithTags &textWithTags, TextUpdateEvents events = 0, 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 ba71b97f3..fdd5d198d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/core_settings.h" #include "data/data_changes.h" +#include "data/data_drafts.h" #include "data/data_messages.h" #include "data/data_session.h" #include "data/data_user.h" @@ -54,6 +55,8 @@ namespace HistoryView { namespace { constexpr auto kRecordingUpdateDelta = crl::time(100); +constexpr auto kSaveDraftTimeout = crl::time(1000); +constexpr auto kSaveDraftAnywayTimeout = 5 * crl::time(1000); constexpr auto kMouseEvents = { QEvent::MouseMove, QEvent::MouseButtonPress, @@ -107,12 +110,24 @@ public: [[nodiscard]] MessageToEdit queryToEdit(); [[nodiscard]] WebPageId webPageId() const; + [[nodiscard]] MsgId getDraftMessageId() const; + [[nodiscard]] rpl::producer<> editCancelled() const { + return _editCancelled.events(); + } + [[nodiscard]] rpl::producer<> replyCancelled() const { + return _replyCancelled.events(); + } + [[nodiscard]] rpl::producer<> previewCancelled() const { + return _previewCancelled.events(); + } + [[nodiscard]] rpl::producer visibleChanged(); private: void updateControlsGeometry(QSize size); void updateVisible(); void setShownMessage(HistoryItem *message); + void resolveMessageData(); void updateShownMessageText(); void paintWebPage(Painter &p); @@ -122,12 +137,16 @@ private: WebPageData *data = nullptr; Ui::Text::String title; Ui::Text::String description; + bool cancelled = false; }; rpl::variable _title; rpl::variable _description; Preview _preview; + rpl::event_stream<> _editCancelled; + rpl::event_stream<> _replyCancelled; + rpl::event_stream<> _previewCancelled; bool hasPreview() const; @@ -202,10 +221,10 @@ void FieldHeader::init() { }) | rpl::start_with_next([=](const Data::MessageUpdate &update) { if (update.flags & Data::MessageUpdate::Flag::Destroyed) { if (_editMsgId.current() == update.item->fullId()) { - editMessage({}); + _editCancelled.fire({}); } if (_replyToId.current() == update.item->fullId()) { - replyToMessage({}); + _replyCancelled.fire({}); } } else { updateShownMessageText(); @@ -215,13 +234,14 @@ void FieldHeader::init() { _cancel->addClickHandler([=] { if (hasPreview()) { _preview = {}; - update(); + _previewCancelled.fire({}); } else if (_editMsgId.current()) { - editMessage({}); + _editCancelled.fire({}); } else if (_replyToId.current()) { - replyToMessage({}); + _replyCancelled.fire({}); } updateVisible(); + update(); }); _title.value( @@ -308,6 +328,7 @@ void FieldHeader::setShownMessage(HistoryItem *item) { } } else { _shownMessageText.clear(); + resolveMessageData(); } if (isEditingMessage()) { _shownMessageName.setText( @@ -322,6 +343,34 @@ void FieldHeader::setShownMessage(HistoryItem *item) { update(); } +void FieldHeader::resolveMessageData() { + const auto id = (isEditingMessage() ? _editMsgId : _replyToId).current(); + if (!id) { + return; + } + const auto channel = id.channel + ? _data->channel(id.channel).get() + : nullptr; + const auto callback = [=](ChannelData *channel, MsgId msgId) { + const auto now = (isEditingMessage() + ? _editMsgId + : _replyToId).current(); + if (now == id && !_shownMessage) { + if (const auto message = _data->message(channel, msgId)) { + setShownMessage(message); + } else if (isEditingMessage()) { + _editCancelled.fire({}); + } else { + _replyCancelled.fire({}); + } + } + }; + _data->session().api().requestMessageData( + channel, + id.msg, + crl::guard(this, callback)); +} + void FieldHeader::previewRequested( rpl::producer title, rpl::producer description, @@ -329,19 +378,25 @@ void FieldHeader::previewRequested( std::move( title - ) | rpl::start_with_next([=](const QString &t) { + ) | rpl::filter([=] { + return !_preview.cancelled; + }) | start_with_next([=](const QString &t) { _title = t; }, lifetime()); std::move( description - ) | rpl::start_with_next([=](const QString &d) { + ) | rpl::filter([=] { + return !_preview.cancelled; + }) | rpl::start_with_next([=](const QString &d) { _description = d; }, lifetime()); std::move( page - ) | rpl::start_with_next([=](WebPageData *p) { + ) | rpl::filter([=] { + return !_preview.cancelled; + }) | rpl::start_with_next([=](WebPageData *p) { _preview.data = p; updateVisible(); }, lifetime()); @@ -392,14 +447,26 @@ void FieldHeader::paintWebPage(Painter &p) { } void FieldHeader::paintEditOrReplyToMessage(Painter &p) { - Expects(_shownMessage != nullptr); - const auto replySkip = st::historyReplySkip; const auto availableWidth = width() - replySkip - _cancel->width() - st::msgReplyPadding.right(); + if (!_shownMessage) { + p.setFont(st::msgDateFont); + p.setPen(st::historyComposeAreaFgService); + const auto top = (st::msgReplyPadding.top() + + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2); + p.drawText( + replySkip, + top + st::msgDateFont->ascent, + st::msgDateFont->elided( + tr::lng_profile_loading(tr::now), + availableWidth)); + return; + } + if (!isEditingMessage()) { const auto user = _shownMessage->displayFrom() ? _shownMessage->displayFrom() @@ -460,6 +527,10 @@ WebPageId FieldHeader::webPageId() const { return hasPreview() ? _preview.data->id : CancelledWebPageId; } +MsgId FieldHeader::getDraftMessageId() const { + return (isEditingMessage() ? _editMsgId : _replyToId).current().msg; +} + void FieldHeader::updateControlsGeometry(QSize size) { _cancel->moveToRight(0, 0); _clickableRect = QRect( @@ -538,11 +609,12 @@ ComposeControls::ComposeControls( window, _send, st::historySendSize.height())) -, _textUpdateEvents(TextUpdateEvent::SendTyping) { +, _saveDraftTimer([=] { saveDraft(); }) { init(); } ComposeControls::~ComposeControls() { + saveFieldToHistoryLocalDraft(); setTabbedPanel(nullptr); session().api().request(_inlineBotResolveRequestId).cancel(); } @@ -582,6 +654,8 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { session().api().requestBots(channel); } } + session().local().readDraftsWithCursors(_history); + applyDraft(); } void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { @@ -752,21 +826,52 @@ TextWithTags ComposeControls::getTextWithAppliedMarkdown() const { } void ComposeControls::clear() { - setText(TextWithTags()); + setText({}); cancelReplyMessage(); } void ComposeControls::setText(const TextWithTags &textWithTags) { - _textUpdateEvents = TextUpdateEvents(); - _field->setTextWithTags(textWithTags, Ui::InputField::HistoryAction::Clear/*fieldHistoryAction*/); + setFieldText(textWithTags); +} + +void ComposeControls::setFieldText( + const TextWithTags &textWithTags, + TextUpdateEvents events, + FieldHistoryAction fieldHistoryAction) { + _textUpdateEvents = events; + _field->setTextWithTags(textWithTags, fieldHistoryAction); auto cursor = _field->textCursor(); cursor.movePosition(QTextCursor::End); _field->setTextCursor(cursor); _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; - //previewCancel(); - //_previewCancelled = false; + _previewCancel(); + _previewCancelled = false; +} + +void ComposeControls::saveFieldToHistoryLocalDraft() { + const auto key = draftKeyCurrent(); + if (!_history || key == Data::DraftKey::None()) { + return; + } + const auto id = _header->getDraftMessageId(); + if (id || !_field->empty()) { + _history->setDraft( + draftKeyCurrent(), + std::make_unique( + _field, + _header->getDraftMessageId(), + _previewCancelled)); + } else { + _history->clearDraft(draftKeyCurrent()); + } +} + +void ComposeControls::clearFieldText( + TextUpdateEvents events, + FieldHistoryAction fieldHistoryAction) { + setFieldText({}, events, fieldHistoryAction); } void ComposeControls::hidePanelsAnimated() { @@ -836,15 +941,27 @@ void ComposeControls::init() { _header->editMsgId( ) | rpl::start_with_next([=](const auto &id) { - if (_header->isEditingMessage()) { - setTextFromEditingMessage(session().data().message(id)); - } else { - setText(_localSavedText); - _localSavedText = {}; - } updateSendButtonType(); }, _wrap->lifetime()); + _header->previewCancelled( + ) | rpl::start_with_next([=] { + _previewCancelled = true; + _saveDraftText = true; + _saveDraftStart = crl::now(); + saveDraft(); + }, _wrap->lifetime()); + + _header->editCancelled( + ) | rpl::start_with_next([=] { + cancelEditMessage(); + }, _wrap->lifetime()); + + _header->replyCancelled( + ) | rpl::start_with_next([=] { + cancelReplyMessage(); + }, _wrap->lifetime()); + _header->visibleChanged( ) | rpl::start_with_next([=] { updateHeight(); @@ -894,19 +1011,6 @@ void ComposeControls::drawRestrictedWrite(Painter &p, const QString &error) { style::al_center); } -void ComposeControls::setTextFromEditingMessage(not_null item) { - if (!_header->isEditingMessage()) { - return; - } - _localSavedText = getTextWithAppliedMarkdown(); - const auto t = item->originalText(); - const auto text = TextWithTags{ - t.text, - TextUtilities::ConvertEntitiesToTextTags(t.entities) - }; - setText(text); -} - void ComposeControls::initField() { _field->setMaxHeight(st::historyComposeFieldMaxHeight); updateSubmitSettings(); @@ -924,6 +1028,16 @@ void ComposeControls::initField() { &_window->session()); _raiseEmojiSuggestions = [=] { suggestions->raise(); }; InitSpellchecker(_window, _field); + + const auto rawTextEdit = _field->rawTextEdit().get(); + rpl::merge( + _field->scrollTop().changes() | rpl::to_empty, + base::qt_signal_producer( + rawTextEdit, + &QTextEdit::cursorPositionChanged) + ) | rpl::start_with_next([=] { + saveDraftDelayed(); + }, _field->lifetime()); } void ComposeControls::updateSubmitSettings() { @@ -1077,7 +1191,7 @@ void ComposeControls::fieldChanged() { } updateSendButtonType(); if (showRecordButton()) { - //_previewCancelled = false; + _previewCancelled = false; } if (updateBotCommandShown()) { updateControlsVisibility(); @@ -1087,6 +1201,133 @@ void ComposeControls::fieldChanged() { updateInlineBotQuery(); updateStickersByEmoji(); }); + + if (!(_textUpdateEvents & TextUpdateEvent::SaveDraft)) { + return; + } + _saveDraftText = true; + saveDraft(true); +} + +void ComposeControls::saveDraftDelayed() { + if (!(_textUpdateEvents & TextUpdateEvent::SaveDraft)) { + return; + } + saveDraft(true); +} + +Data::DraftKey ComposeControls::draftKey(DraftType type) const { + using Section = Dialogs::EntryState::Section; + using Key = Data::DraftKey; + + switch (_currentDialogsEntryState.section) { + case Section::History: + return (type == DraftType::Edit) ? Key::LocalEdit() : Key::Local(); + case Section::Scheduled: + return (type == DraftType::Edit) + ? Key::ScheduledEdit() + : Key::Scheduled(); + case Section::Replies: + return (type == DraftType::Edit) + ? Key::RepliesEdit(_currentDialogsEntryState.rootId) + : Key::Replies(_currentDialogsEntryState.rootId); + } + return Key::None(); +} + +Data::DraftKey ComposeControls::draftKeyCurrent() const { + return draftKey(isEditingMessage() ? DraftType::Edit : DraftType::Normal); +} + +void ComposeControls::saveDraft(bool delayed) { + if (delayed) { + const auto now = crl::now(); + if (!_saveDraftStart) { + _saveDraftStart = now; + return _saveDraftTimer.callOnce(kSaveDraftTimeout); + } else if (now - _saveDraftStart < kSaveDraftAnywayTimeout) { + return _saveDraftTimer.callOnce(kSaveDraftTimeout); + } + } + writeDrafts(); +} + +void ComposeControls::writeDraftTexts() { + Expects(_history != nullptr); + + session().local().writeDrafts( + _history, + draftKeyCurrent(), + Storage::MessageDraft{ + _header->getDraftMessageId(), + _field->getTextWithTags(), + _previewCancelled, + }); +} + +void ComposeControls::writeDraftCursors() { + Expects(_history != nullptr); + + session().local().writeDraftCursors( + _history, + draftKeyCurrent(), + MessageCursor(_field)); +} + +void ComposeControls::writeDrafts() { + const auto save = (_history != nullptr) + && (_saveDraftStart > 0) + && (draftKeyCurrent() != Data::DraftKey::None()); + _saveDraftStart = 0; + _saveDraftTimer.cancel(); + if (save) { + if (_saveDraftText) { + writeDraftTexts(); + } + writeDraftCursors(); + } + _saveDraftText = false; + + //if (!isEditingMessage() && !_inlineBot) { + // _saveCloudDraftTimer.callOnce(kSaveCloudDraftIdleTimeout); + //} +} + +void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { + Expects(_history != nullptr); + + InvokeQueued(_autocomplete.get(), [=] { updateStickersByEmoji(); }); + const auto guard = gsl::finally([&] { + updateSendButtonType(); + updateControlsVisibility(); + updateControlsGeometry(_wrap->size()); + }); + + const auto editDraft = _history->draft(draftKey(DraftType::Edit)); + const auto draft = editDraft + ? editDraft + : _history->draft(draftKey(DraftType::Normal)); + if (!draft) { + clearFieldText(0, fieldHistoryAction); + _field->setFocus(); + _header->editMessage({}); + _header->replyToMessage({}); + return; + } + + _textUpdateEvents = 0; + setFieldText(draft->textWithTags, 0, fieldHistoryAction); + _field->setFocus(); + draft->cursor.applyTo(_field); + _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; + _previewCancelled = draft->previewCancelled; + if (draft == editDraft) { + _header->editMessage({ _history->channelId(), draft->msgId }); + _header->replyToMessage({}); + } else { + _header->replyToMessage({ _history->channelId(), draft->msgId }); + _header->editMessage({}); + } } void ComposeControls::fieldTabbed() { @@ -1192,11 +1433,16 @@ void ComposeControls::inlineBotResolveFail( } void ComposeControls::cancelInlineBot() { - auto &textWithTags = _field->getTextWithTags(); + const auto &textWithTags = _field->getTextWithTags(); if (textWithTags.text.size() > _inlineBotUsername.size() + 2) { - setText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }); + setFieldText( + { '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }, + TextUpdateEvent::SaveDraft, + Ui::InputField::HistoryAction::NewEntry); } else { - setText({}); + clearFieldText( + TextUpdateEvent::SaveDraft, + Ui::InputField::HistoryAction::NewEntry); } } @@ -1487,29 +1733,101 @@ void ComposeControls::updateHeight() { } void ComposeControls::editMessage(FullMsgId id) { - cancelEditMessage(); - _header->editMessage(id); + if (const auto item = session().data().message(id)) { + editMessage(item); + } +} + +void ComposeControls::editMessage(not_null item) { + Expects(_history != nullptr); + Expects(draftKeyCurrent() != Data::DraftKey::None()); + + if (!isEditingMessage()) { + saveFieldToHistoryLocalDraft(); + } + const auto editData = PrepareEditText(item); + const auto cursor = MessageCursor{ + editData.text.size(), + editData.text.size(), + QFIXED_MAX + }; + _history->setDraft( + draftKey(DraftType::Edit), + std::make_unique( + editData, + item->id, + cursor, + false)); + applyDraft(); + if (_autocomplete) { InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); }); } - updateFieldPlaceholder(); } void ComposeControls::cancelEditMessage() { - _header->editMessage({}); - if (_autocomplete) { - InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); }); - } - updateFieldPlaceholder(); + Expects(_history != nullptr); + Expects(draftKeyCurrent() != Data::DraftKey::None()); + + _history->clearDraft(draftKey(DraftType::Edit)); + applyDraft(); + + _saveDraftText = true; + _saveDraftStart = crl::now(); + saveDraft(); } void ComposeControls::replyToMessage(FullMsgId id) { - cancelReplyMessage(); - _header->replyToMessage(id); + Expects(_history != nullptr); + Expects(draftKeyCurrent() != Data::DraftKey::None()); + + if (!id) { + cancelReplyMessage(); + return; + } + if (isEditingMessage()) { + const auto key = draftKey(DraftType::Normal); + if (const auto localDraft = _history->draft(key)) { + localDraft->msgId = id.msg; + } else { + _history->setDraft( + key, + std::make_unique( + TextWithTags(), + id.msg, + MessageCursor(), + false)); + } + } else { + _header->replyToMessage(id); + } + + _saveDraftText = true; + _saveDraftStart = crl::now(); + saveDraft(); } void ComposeControls::cancelReplyMessage() { + Expects(_history != nullptr); + Expects(draftKeyCurrent() != Data::DraftKey::None()); + + const auto wasReply = replyingToMessage(); _header->replyToMessage({}); + const auto key = draftKey(DraftType::Normal); + if (const auto localDraft = _history->draft(key)) { + if (localDraft->msgId) { + if (localDraft->textWithTags.text.isEmpty()) { + _history->clearDraft(key); + } else { + localDraft->msgId = 0; + } + } + } + if (wasReply) { + _saveDraftText = true; + _saveDraftStart = crl::now(); + saveDraft(); + } } bool ComposeControls::handleCancelRequest() { @@ -1544,7 +1862,6 @@ void ComposeControls::initWebpageProcess() { using PreviewCache = std::map; const auto previewCache = lifetime.make_state(); const auto previewRequest = lifetime.make_state(0); - const auto previewCancelled = lifetime.make_state(false); const auto mtpSender = lifetime.make_state(&_window->session().mtp()); @@ -1591,7 +1908,7 @@ void ComposeControls::initWebpageProcess() { if (till > 0 && till <= base::unixtime::now()) { till = -1; } - if (links == *previewLinks && !*previewCancelled) { + if (links == *previewLinks && !_previewCancelled) { *previewData = (page->id && page->pendingTill >= 0) ? page.get() : nullptr; @@ -1599,7 +1916,7 @@ void ComposeControls::initWebpageProcess() { } }, [=](const MTPDmessageMediaEmpty &d) { previewCache->insert({ links, 0 }); - if (links == *previewLinks && !*previewCancelled) { + if (links == *previewLinks && !_previewCancelled) { *previewData = nullptr; updatePreview(); } @@ -1607,7 +1924,7 @@ void ComposeControls::initWebpageProcess() { }); }); - const auto previewCancel = [=] { + _previewCancel = [=] { mtpSender->request(base::take(*previewRequest)).cancel(); *previewData = nullptr; previewLinks->clear(); @@ -1628,8 +1945,8 @@ void ComposeControls::initWebpageProcess() { const auto checkPreview = crl::guard(_wrap.get(), [=] { const auto previewRestricted = peer && peer->amRestricted(ChatRestriction::f_embed_links); - if (/*_previewCancelled ||*/ previewRestricted) { - previewCancel(); + if (_previewCancelled || previewRestricted) { + _previewCancel(); return; } const auto newLinks = parsedLinks->join(' '); @@ -1640,7 +1957,7 @@ void ComposeControls::initWebpageProcess() { *previewLinks = newLinks; if (previewLinks->isEmpty()) { if (ShowWebPagePreview(*previewData)) { - previewCancel(); + _previewCancel(); } } else { const auto i = previewCache->find(*previewLinks); @@ -1650,7 +1967,7 @@ void ComposeControls::initWebpageProcess() { *previewData = _history->owner().webpage(i->second); updatePreview(); } else if (ShowWebPagePreview(*previewData)) { - previewCancel(); + _previewCancel(); } } }); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 01e148585..109a318a0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/required.h" #include "api/api_common.h" #include "base/unique_qptr.h" +#include "base/timer.h" #include "dialogs/dialogs_key.h" #include "history/view/controls/compose_controls_common.h" #include "ui/rp_widget.h" @@ -27,6 +28,8 @@ class TabbedSelector; namespace Data { struct MessagePosition; +struct Draft; +class DraftKey; } // namespace Data namespace InlineBots { @@ -74,6 +77,7 @@ public: using VoiceToSend = Controls::VoiceToSend; using SendActionUpdate = Controls::SendActionUpdate; using SetHistoryArgs = Controls::SetHistoryArgs; + using FieldHistoryAction = Ui::InputField::HistoryAction; enum class Mode { Normal, @@ -148,11 +152,18 @@ public: [[nodiscard]] bool isLockPresent() const; [[nodiscard]] bool isRecording() const; + void applyDraft( + FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); + private: enum class TextUpdateEvent { SaveDraft = (1 << 0), SendTyping = (1 << 1), }; + enum class DraftType { + Normal, + Edit, + }; using TextUpdateEvents = base::flags; friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; @@ -177,6 +188,7 @@ private: void checkAutocomplete(); void updateStickersByEmoji(); void updateFieldPlaceholder(); + void editMessage(not_null item); void escape(); void fieldChanged(); @@ -185,11 +197,8 @@ private: void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); - void setTextFromEditingMessage(not_null item); - bool showRecordButton() const; void drawRestrictedWrite(Painter &p, const QString &error); - void updateOverStates(QPoint pos); bool updateBotCommandShown(); void cancelInlineBot(); @@ -205,6 +214,24 @@ private: void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result); void inlineBotResolveFail(const RPCError &error, const QString &username); + [[nodiscard]] Data::DraftKey draftKey( + DraftType type = DraftType::Normal) const; + [[nodiscard]] Data::DraftKey draftKeyCurrent() const; + void saveDraft(bool delayed = false); + void saveDraftDelayed(); + + void writeDrafts(); + void writeDraftTexts(); + void writeDraftCursors(); + void setFieldText( + const TextWithTags &textWithTags, + TextUpdateEvents events = 0, + FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); + void clearFieldText( + TextUpdateEvents events = 0, + FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); + void saveFieldToHistoryLocalDraft(); + const not_null _parent; const not_null _window; History *_history = nullptr; @@ -238,12 +265,14 @@ private: rpl::event_stream _sendActionUpdates; rpl::event_stream _sendCommandRequests; - TextWithTags _localSavedText; - TextUpdateEvents _textUpdateEvents; + TextUpdateEvents _textUpdateEvents = TextUpdateEvents() + | TextUpdateEvent::SaveDraft + | TextUpdateEvent::SendTyping; Dialogs::EntryState _currentDialogsEntryState; - //bool _inReplyEditForward = false; - //bool _inClickable = false; + crl::time _saveDraftStart = 0; + bool _saveDraftText = false; + base::Timer _saveDraftTimer; UserData *_inlineBot = nullptr; QString _inlineBotUsername; @@ -252,6 +281,9 @@ private: bool _isInlineBot = false; bool _botCommandShown = false; + Fn _previewCancel; + bool _previewCancelled = false; + rpl::lifetime _uploaderSubscriptions; Fn _raiseEmojiSuggestions; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index a076837d6..e5a976a57 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1133,7 +1133,7 @@ void RepliesWidget::refreshTopBarActiveChat() { .key = _history, .section = Dialogs::EntryState::Section::Replies, .rootId = _rootId, - .currentReplyToId = replyToId(), + .currentReplyToId = _composeControls->replyingToMessage().msg, }; _topBar->setActiveChat(state, _sendAction.get()); _composeControls->setCurrentDialogsEntryState(state); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4c47f3421..d17c8af27 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -533,7 +533,7 @@ bool MainWidget::shareUrl( auto history = peer->owner().history(peer); history->setLocalDraft( std::make_unique(textWithTags, 0, cursor, false)); - history->clearEditDraft(); + history->clearLocalEditDraft(); if (_history->peer() == peer) { _history->applyDraft(); } else { @@ -562,7 +562,7 @@ bool MainWidget::inlineSwitchChosen(PeerId peerId, const QString &botAndQuery) { TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() }; MessageCursor cursor = { botAndQuery.size(), botAndQuery.size(), QFIXED_MAX }; h->setLocalDraft(std::make_unique(textWithTags, 0, cursor, false)); - h->clearEditDraft(); + h->clearLocalEditDraft(); const auto opened = _history->peer() && (_history->peer() == peer); if (opened) { _history->applyDraft(); diff --git a/Telegram/SourceFiles/platform/win/window_title_win.cpp b/Telegram/SourceFiles/platform/win/window_title_win.cpp index 80df1d90d..a1c51f491 100644 --- a/Telegram/SourceFiles/platform/win/window_title_win.cpp +++ b/Telegram/SourceFiles/platform/win/window_title_win.cpp @@ -42,6 +42,8 @@ TitleWidget::TitleWidget(QWidget *parent) }); _close->setPointerCursor(false); + window()->windowHandle()->setFlag(Qt::FramelessWindowHint, true); + setAttribute(Qt::WA_OpaquePaintEvent); resize(width(), _st.height); } diff --git a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp index bdd55b957..0cd10f60d 100644 --- a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp +++ b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp @@ -142,30 +142,42 @@ bool EventFilter::customWindowFrameEvent( if (result) *result = 0; } return true; + case WM_SHOWWINDOW: { + SetWindowLongPtr( + hWnd, + GWL_STYLE, + WS_POPUP + | WS_THICKFRAME + | WS_CAPTION + | WS_SYSMENU + | WS_MAXIMIZEBOX + | WS_MINIMIZEBOX); + } return false; + case WM_NCCALCSIZE: { - WINDOWPLACEMENT wp; - wp.length = sizeof(WINDOWPLACEMENT); - if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { - LPNCCALCSIZE_PARAMS params = (LPNCCALCSIZE_PARAMS)lParam; - LPRECT r = (wParam == TRUE) ? ¶ms->rgrc[0] : (LPRECT)lParam; - HMONITOR hMonitor = MonitorFromPoint({ (r->left + r->right) / 2, (r->top + r->bottom) / 2 }, MONITOR_DEFAULTTONEAREST); - if (hMonitor) { - MONITORINFO mi; - mi.cbSize = sizeof(mi); - if (GetMonitorInfo(hMonitor, &mi)) { - *r = mi.rcWork; - UINT uEdge = (UINT)-1; - if (IsTaskbarAutoHidden(&mi.rcMonitor, &uEdge)) { - switch (uEdge) { - case ABE_LEFT: r->left += 1; break; - case ABE_RIGHT: r->right -= 1; break; - case ABE_TOP: r->top += 1; break; - case ABE_BOTTOM: r->bottom -= 1; break; - } - } - } - } - } + //WINDOWPLACEMENT wp; + //wp.length = sizeof(WINDOWPLACEMENT); + //if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { + // LPNCCALCSIZE_PARAMS params = (LPNCCALCSIZE_PARAMS)lParam; + // LPRECT r = (wParam == TRUE) ? ¶ms->rgrc[0] : (LPRECT)lParam; + // HMONITOR hMonitor = MonitorFromPoint({ (r->left + r->right) / 2, (r->top + r->bottom) / 2 }, MONITOR_DEFAULTTONEAREST); + // if (hMonitor) { + // MONITORINFO mi; + // mi.cbSize = sizeof(mi); + // if (GetMonitorInfo(hMonitor, &mi)) { + // *r = mi.rcWork; + // UINT uEdge = (UINT)-1; + // if (IsTaskbarAutoHidden(&mi.rcMonitor, &uEdge)) { + // switch (uEdge) { + // case ABE_LEFT: r->left += 1; break; + // case ABE_RIGHT: r->right -= 1; break; + // case ABE_TOP: r->top += 1; break; + // case ABE_BOTTOM: r->bottom -= 1; break; + // } + // } + // } + // } + //} if (result) *result = 0; return true; } diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 0a541a023..ee882fc6a 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -51,6 +51,7 @@ constexpr auto kSinglePeerTypeChat = qint32(2); constexpr auto kSinglePeerTypeChannel = qint32(3); constexpr auto kSinglePeerTypeSelf = qint32(4); constexpr auto kSinglePeerTypeEmpty = qint32(0); +constexpr auto kMultiDraftTag = quint64(0xFFFFFFFFFFFFFF01ULL); enum { // Local Storage Keys lskUserMap = 0x00, @@ -936,80 +937,200 @@ std::unique_ptr Account::readMtpConfig() { return MTP::Config::FromSerialized(serialized); } -void Account::writeDrafts(not_null history) { - Storage::MessageDraft storedLocalDraft, storedEditDraft; - MessageCursor localCursor, editCursor; - if (const auto localDraft = history->localDraft()) { - if (_owner->session().supportMode() - || !Data::draftsAreEqual(localDraft, history->cloudDraft())) { - storedLocalDraft = Storage::MessageDraft{ - localDraft->msgId, - localDraft->textWithTags, - localDraft->previewCancelled - }; - localCursor = localDraft->cursor; +template +void EnumerateDrafts( + const Data::HistoryDrafts &map, + Data::Draft *cloudDraft, + bool supportMode, + Data::DraftKey replaceKey, + const MessageDraft &replaceDraft, + const MessageCursor &replaceCursor, + Callback &&callback) { + for (const auto &[key, draft] : map) { + if (key == Data::DraftKey::Cloud() || key == replaceKey) { + continue; + } else if (key == Data::DraftKey::Local() + && !supportMode + && Data::draftsAreEqual(draft.get(), cloudDraft)) { + continue; } + callback( + key, + draft->msgId, + draft->textWithTags, + draft->previewCancelled, + draft->cursor); } - if (const auto editDraft = history->editDraft()) { - storedEditDraft = Storage::MessageDraft{ - editDraft->msgId, - editDraft->textWithTags, - editDraft->previewCancelled - }; - editCursor = editDraft->cursor; + if (replaceKey + && (replaceDraft.msgId + || !replaceDraft.textWithTags.text.isEmpty() + || replaceCursor != MessageCursor())) { + callback( + replaceKey, + replaceDraft.msgId, + replaceDraft.textWithTags, + replaceDraft.previewCancelled, + replaceCursor); } - writeDrafts( - history->peer->id, - storedLocalDraft, - storedEditDraft); - writeDraftCursors(history->peer->id, localCursor, editCursor); } void Account::writeDrafts( - const PeerId &peer, - const MessageDraft &localDraft, - const MessageDraft &editDraft) { - if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) { - auto i = _draftsMap.find(peer); + not_null history, + Data::DraftKey replaceKey, + MessageDraft replaceDraft) { + const auto peerId = history->peer->id; + const auto &map = history->draftsMap(); + const auto cloudIt = map.find(Data::DraftKey::Cloud()); + const auto cloudDraft = (cloudIt != end(map)) + ? cloudIt->second.get() + : nullptr; + const auto supportMode = _owner->session().supportMode(); + auto count = 0; + EnumerateDrafts( + map, + cloudDraft, + supportMode, + replaceKey, + replaceDraft, + MessageCursor(), + [&](auto&&...) { ++count; }); + if (!count) { + auto i = _draftsMap.find(peerId); if (i != _draftsMap.cend()) { ClearKey(i->second, _basePath); _draftsMap.erase(i); writeMapDelayed(); } - _draftsNotReadMap.remove(peer); - } else { - auto i = _draftsMap.find(peer); - if (i == _draftsMap.cend()) { - i = _draftsMap.emplace(peer, GenerateKey(_basePath)).first; - writeMapQueued(); - } - - auto msgTags = TextUtilities::SerializeTags( - localDraft.textWithTags.tags); - auto editTags = TextUtilities::SerializeTags( - editDraft.textWithTags.tags); - - int size = sizeof(quint64); - size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32); - size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32); - - EncryptedDescriptor data(size); - data.stream << quint64(peer); - data.stream << localDraft.textWithTags.text << msgTags; - data.stream << qint32(localDraft.msgId) << qint32(localDraft.previewCancelled ? 1 : 0); - data.stream << editDraft.textWithTags.text << editTags; - data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0); - - FileWriteDescriptor file(i->second, _basePath); - file.writeEncrypted(data, _localKey); - - _draftsNotReadMap.remove(peer); + _draftsNotReadMap.remove(peerId); + return; } + + auto i = _draftsMap.find(peerId); + if (i == _draftsMap.cend()) { + i = _draftsMap.emplace(peerId, GenerateKey(_basePath)).first; + writeMapQueued(); + } + + auto size = int(sizeof(quint64) * 2 + sizeof(quint32)); + const auto sizeCallback = [&]( + auto&&, // key + MsgId, // msgId + const TextWithTags &text, + bool, // previewCancelled + auto&&) { // cursor + size += sizeof(qint32) // key + + Serialize::stringSize(text.text) + + sizeof(quint32) + TextUtilities::SerializeTagsSize(text.tags) + + 2 * sizeof(qint32); // msgId, previewCancelled + }; + EnumerateDrafts( + map, + cloudDraft, + supportMode, + replaceKey, + replaceDraft, + MessageCursor(), + sizeCallback); + + EncryptedDescriptor data(size); + data.stream + << quint64(kMultiDraftTag) + << quint64(peerId) + << quint32(count); + + const auto writeCallback = [&]( + const Data::DraftKey &key, + MsgId msgId, + const TextWithTags &text, + bool previewCancelled, + auto&&) { // cursor + data.stream + << key.serialize() + << text.text + << TextUtilities::SerializeTags(text.tags) + << qint32(msgId) + << qint32(previewCancelled ? 1 : 0); + }; + EnumerateDrafts( + map, + cloudDraft, + supportMode, + replaceKey, + replaceDraft, + MessageCursor(), + writeCallback); + + FileWriteDescriptor file(i->second, _basePath); + file.writeEncrypted(data, _localKey); + + _draftsNotReadMap.remove(peerId); } -void Account::clearDraftCursors(const PeerId &peer) { - const auto i = _draftCursorsMap.find(peer); +void Account::writeDraftCursors( + not_null history, + Data::DraftKey replaceKey, + MessageCursor replaceCursor) { + const auto peerId = history->peer->id; + const auto &map = history->draftsMap(); + const auto cloudIt = map.find(Data::DraftKey::Cloud()); + const auto cloudDraft = (cloudIt != end(map)) + ? cloudIt->second.get() + : nullptr; + const auto supportMode = _owner->session().supportMode(); + auto count = 0; + EnumerateDrafts( + map, + cloudDraft, + supportMode, + replaceKey, + MessageDraft(), + replaceCursor, + [&](auto&&...) { ++count; }); + if (!count) { + clearDraftCursors(peerId); + return; + } + auto i = _draftCursorsMap.find(peerId); + if (i == _draftCursorsMap.cend()) { + i = _draftCursorsMap.emplace(peerId, GenerateKey(_basePath)).first; + writeMapQueued(); + } + + auto size = int(sizeof(quint64) * 2 + sizeof(quint32) * 4); + + EncryptedDescriptor data(size); + data.stream + << quint64(kMultiDraftTag) + << quint64(peerId) + << quint32(count); + + const auto writeCallback = [&]( + auto&&, // key + MsgId, // msgId + auto&&, // text + bool, // previewCancelled + const MessageCursor &cursor) { // cursor + data.stream + << qint32(cursor.position) + << qint32(cursor.anchor) + << qint32(cursor.scroll); + }; + EnumerateDrafts( + map, + cloudDraft, + supportMode, + replaceKey, + MessageDraft(), + replaceCursor, + writeCallback); + + FileWriteDescriptor file(i->second, _basePath); + file.writeEncrypted(data, _localKey); +} + +void Account::clearDraftCursors(PeerId peerId) { + const auto i = _draftCursorsMap.find(peerId); if (i != _draftCursorsMap.cend()) { ClearKey(i->second, _basePath); _draftCursorsMap.erase(i); @@ -1017,21 +1138,44 @@ void Account::clearDraftCursors(const PeerId &peer) { } } -void Account::readDraftCursors( - const PeerId &peer, - MessageCursor &localCursor, - MessageCursor &editCursor) { - const auto j = _draftCursorsMap.find(peer); +void Account::readDraftCursors(PeerId peerId, Data::HistoryDrafts &map) { + const auto j = _draftCursorsMap.find(peerId); if (j == _draftCursorsMap.cend()) { return; } FileReadDescriptor draft; if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) { - clearDraftCursors(peer); + clearDraftCursors(peerId); return; } - quint64 draftPeer; + quint64 tag = 0; + draft.stream >> tag; + if (tag != kMultiDraftTag) { + readDraftCursorsLegacy(peerId, draft, tag, map); + return; + } + quint64 draftPeer = 0; + quint32 count = 0; + draft.stream >> draftPeer >> count; + if (!count || count > 1000 || draftPeer != peerId) { + clearDraftCursors(peerId); + return; + } + for (auto i = 0; i != count; ++i) { + qint32 position = 0, anchor = 0, scroll = QFIXED_MAX; + draft.stream >> position >> anchor >> scroll; + if (const auto i = map.find(Data::DraftKey::Local()); i != end(map)) { + i->second->cursor = MessageCursor(position, anchor, scroll); + } + } +} + +void Account::readDraftCursorsLegacy( + PeerId peerId, + details::FileReadDescriptor &draft, + quint64 draftPeer, + Data::HistoryDrafts &map) { qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX; qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX; draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll; @@ -1039,40 +1183,109 @@ void Account::readDraftCursors( draft.stream >> editPosition >> editAnchor >> editScroll; } - if (draftPeer != peer) { - clearDraftCursors(peer); + if (draftPeer != peerId) { + clearDraftCursors(peerId); return; } - localCursor = MessageCursor(localPosition, localAnchor, localScroll); - editCursor = MessageCursor(editPosition, editAnchor, editScroll); + if (const auto i = map.find(Data::DraftKey::Local()); i != end(map)) { + i->second->cursor = MessageCursor( + localPosition, + localAnchor, + localScroll); + } + if (const auto i = map.find(Data::DraftKey::LocalEdit()); i != end(map)) { + i->second->cursor = MessageCursor( + editPosition, + editAnchor, + editScroll); + } } void Account::readDraftsWithCursors(not_null history) { - PeerId peer = history->peer->id; - if (!_draftsNotReadMap.remove(peer)) { - clearDraftCursors(peer); + const auto guard = gsl::finally([&] { + if (const auto migrated = history->migrateFrom()) { + readDraftsWithCursors(migrated); + migrated->clearLocalEditDraft(); + history->takeLocalDraft(migrated); + } + }); + + PeerId peerId = history->peer->id; + if (!_draftsNotReadMap.remove(peerId)) { + clearDraftCursors(peerId); return; } - const auto j = _draftsMap.find(peer); + const auto j = _draftsMap.find(peerId); if (j == _draftsMap.cend()) { - clearDraftCursors(peer); + clearDraftCursors(peerId); return; } FileReadDescriptor draft; if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) { ClearKey(j->second, _basePath); _draftsMap.erase(j); - clearDraftCursors(peer); + clearDraftCursors(peerId); return; } + quint64 tag = 0; + draft.stream >> tag; + if (tag != kMultiDraftTag) { + readDraftsWithCursorsLegacy(history, draft, tag); + return; + } + quint32 count = 0; quint64 draftPeer = 0; + draft.stream >> draftPeer >> count; + if (!count || count > 1000 || draftPeer != peerId) { + ClearKey(j->second, _basePath); + _draftsMap.erase(j); + clearDraftCursors(peerId); + return; + } + auto map = Data::HistoryDrafts(); + for (auto i = 0; i != count; ++i) { + TextWithTags data; + QByteArray tagsSerialized; + qint32 keyValue = 0, messageId = 0, previewCancelled = 0; + draft.stream + >> keyValue + >> data.text + >> tagsSerialized + >> messageId + >> previewCancelled; + data.tags = TextUtilities::DeserializeTags( + tagsSerialized, + data.text.size()); + const auto key = Data::DraftKey::FromSerialized(keyValue); + if (key && key != Data::DraftKey::Cloud()) { + map.emplace(key, std::make_unique( + data, + messageId, + MessageCursor(), + previewCancelled)); + } + } + if (draft.stream.status() != QDataStream::Ok) { + ClearKey(j->second, _basePath); + _draftsMap.erase(j); + clearDraftCursors(peerId); + return; + } + readDraftCursors(peerId, map); + history->setDraftsMap(std::move(map)); +} + +void Account::readDraftsWithCursorsLegacy( + not_null history, + details::FileReadDescriptor &draft, + quint64 draftPeer) { TextWithTags msgData, editData; QByteArray msgTagsSerialized, editTagsSerialized; qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0; - draft.stream >> draftPeer >> msgData.text; + draft.stream >> msgData.text; if (draft.version >= 9048) { draft.stream >> msgTagsSerialized; } @@ -1089,10 +1302,14 @@ void Account::readDraftsWithCursors(not_null history) { } } } - if (draftPeer != peer) { - ClearKey(j->second, _basePath); - _draftsMap.erase(j); - clearDraftCursors(peer); + const auto peerId = history->peer->id; + if (draftPeer != peerId) { + const auto j = _draftsMap.find(peerId); + if (j != _draftsMap.cend()) { + ClearKey(j->second, _basePath); + _draftsMap.erase(j); + } + clearDraftCursors(peerId); return; } @@ -1103,65 +1320,30 @@ void Account::readDraftsWithCursors(not_null history) { editTagsSerialized, editData.text.size()); - MessageCursor msgCursor, editCursor; - readDraftCursors(peer, msgCursor, editCursor); - - if (!history->localDraft()) { - if (msgData.text.isEmpty() && !msgReplyTo) { - history->clearLocalDraft(); - } else { - history->setLocalDraft(std::make_unique( - msgData, - msgReplyTo, - msgCursor, - msgPreviewCancelled)); - } + auto map = base::flat_map>(); + if (!msgData.text.isEmpty() || msgReplyTo) { + map.emplace(Data::DraftKey::Local(), std::make_unique( + msgData, + msgReplyTo, + MessageCursor(), + msgPreviewCancelled)); } - if (!editMsgId) { - history->clearEditDraft(); - } else { - history->setEditDraft(std::make_unique( + if (editMsgId) { + map.emplace(Data::DraftKey::LocalEdit(), std::make_unique( editData, editMsgId, - editCursor, + MessageCursor(), editPreviewCancelled)); } + readDraftCursors(peerId, map); + history->setDraftsMap(std::move(map)); } -void Account::writeDraftCursors( - const PeerId &peer, - const MessageCursor &msgCursor, - const MessageCursor &editCursor) { - if (msgCursor == MessageCursor() && editCursor == MessageCursor()) { - clearDraftCursors(peer); - } else { - auto i = _draftCursorsMap.find(peer); - if (i == _draftCursorsMap.cend()) { - i = _draftCursorsMap.emplace(peer, GenerateKey(_basePath)).first; - writeMapQueued(); - } - - EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3); - data.stream - << quint64(peer) - << qint32(msgCursor.position) - << qint32(msgCursor.anchor) - << qint32(msgCursor.scroll); - data.stream - << qint32(editCursor.position) - << qint32(editCursor.anchor) - << qint32(editCursor.scroll); - - FileWriteDescriptor file(i->second, _basePath); - file.writeEncrypted(data, _localKey); - } -} - -bool Account::hasDraftCursors(const PeerId &peer) { +bool Account::hasDraftCursors(PeerId peer) { return _draftCursorsMap.contains(peer); } -bool Account::hasDraft(const PeerId &peer) { +bool Account::hasDraft(PeerId peer) { return _draftsMap.contains(peer); } diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index d40e4638b..1aeb64d0b 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "storage/cache/storage_cache_database.h" #include "data/stickers/data_stickers_set.h" +#include "data/data_drafts.h" class History; @@ -39,6 +40,7 @@ using AuthKeyPtr = std::shared_ptr; namespace Storage { namespace details { struct ReadSettingsContext; +struct FileReadDescriptor; } // namespace details class EncryptionKey; @@ -76,18 +78,17 @@ public: void writeMtpData(); void writeMtpConfig(); - void writeDrafts(not_null history); void writeDrafts( - const PeerId &peer, - const MessageDraft &localDraft, - const MessageDraft &editDraft); + not_null history, + Data::DraftKey replaceKey = Data::DraftKey::None(), + MessageDraft replaceDraft = MessageDraft()); void readDraftsWithCursors(not_null history); void writeDraftCursors( - const PeerId &peer, - const MessageCursor &localCursor, - const MessageCursor &editCursor); - [[nodiscard]] bool hasDraftCursors(const PeerId &peer); - [[nodiscard]] bool hasDraft(const PeerId &peer); + not_null history, + Data::DraftKey replaceKey = Data::DraftKey::None(), + MessageCursor replaceCursor = MessageCursor()); + [[nodiscard]] bool hasDraftCursors(PeerId peerId); + [[nodiscard]] bool hasDraft(PeerId peerId); void writeFileLocation(MediaKey location, const Core::FileLocation &local); [[nodiscard]] Core::FileLocation readFileLocation(MediaKey location); @@ -182,11 +183,17 @@ private: std::unique_ptr applyReadContext( details::ReadSettingsContext &&context); - void readDraftCursors( - const PeerId &peer, - MessageCursor &localCursor, - MessageCursor &editCursor); - void clearDraftCursors(const PeerId &peer); + void readDraftCursors(PeerId peerId, Data::HistoryDrafts &map); + void readDraftCursorsLegacy( + PeerId peerId, + details::FileReadDescriptor &draft, + quint64 draftPeer, + Data::HistoryDrafts &map); + void clearDraftCursors(PeerId peerId); + void readDraftsWithCursorsLegacy( + not_null history, + details::FileReadDescriptor &draft, + quint64 draftPeer); void writeStickerSet( QDataStream &stream, diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 660026281..02218bd02 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -617,7 +617,9 @@ void Filler::addPollAction(not_null peer) { ? Api::SendType::Scheduled : Api::SendType::Normal; const auto flag = PollData::Flags(); - const auto replyToId = _request.currentReplyToId; + const auto replyToId = _request.currentReplyToId + ? _request.currentReplyToId + : _request.rootId; auto callback = [=] { PeerMenuCreatePoll( controller, From 81723a5d19fcec395c1dc846a39dee9b6810ef28 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 12 Nov 2020 16:04:21 +0300 Subject: [PATCH 098/370] Slightly improved voice lock design. --- .../{lock => voice_lock}/record_lock_body.png | Bin .../record_lock_body@2x.png | Bin .../record_lock_body@3x.png | Bin .../record_lock_body_shadow.png | Bin .../record_lock_body_shadow@2x.png | Bin .../record_lock_body_shadow@3x.png | Bin .../record_lock_bottom.png | Bin .../record_lock_bottom@2x.png | Bin .../record_lock_bottom@3x.png | Bin .../record_lock_bottom_shadow.png | Bin .../record_lock_bottom_shadow@2x.png | Bin .../record_lock_bottom_shadow@3x.png | Bin .../{lock => voice_lock}/record_lock_top.png | Bin .../record_lock_top@2x.png | Bin .../record_lock_top@3x.png | Bin .../record_lock_top_shadow.png | Bin .../record_lock_top_shadow@2x.png | Bin .../record_lock_top_shadow@3x.png | Bin .../icons/voice_lock/voice_arrow.png | Bin 0 -> 354 bytes .../icons/voice_lock/voice_arrow@2x.png | Bin 0 -> 544 bytes .../icons/voice_lock/voice_arrow@3x.png | Bin 0 -> 818 bytes Telegram/SourceFiles/ui/chat/chat.style | 20 +++++++++--------- 22 files changed, 10 insertions(+), 10 deletions(-) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_body.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_body@2x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_body@3x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_body_shadow.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_body_shadow@2x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_body_shadow@3x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_bottom.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_bottom@2x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_bottom@3x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_bottom_shadow.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_bottom_shadow@2x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_bottom_shadow@3x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_top.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_top@2x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_top@3x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_top_shadow.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_top_shadow@2x.png (100%) rename Telegram/Resources/icons/{lock => voice_lock}/record_lock_top_shadow@3x.png (100%) create mode 100644 Telegram/Resources/icons/voice_lock/voice_arrow.png create mode 100644 Telegram/Resources/icons/voice_lock/voice_arrow@2x.png create mode 100644 Telegram/Resources/icons/voice_lock/voice_arrow@3x.png diff --git a/Telegram/Resources/icons/lock/record_lock_body.png b/Telegram/Resources/icons/voice_lock/record_lock_body.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_body.png rename to Telegram/Resources/icons/voice_lock/record_lock_body.png diff --git a/Telegram/Resources/icons/lock/record_lock_body@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_body@2x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_body@2x.png rename to Telegram/Resources/icons/voice_lock/record_lock_body@2x.png diff --git a/Telegram/Resources/icons/lock/record_lock_body@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_body@3x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_body@3x.png rename to Telegram/Resources/icons/voice_lock/record_lock_body@3x.png diff --git a/Telegram/Resources/icons/lock/record_lock_body_shadow.png b/Telegram/Resources/icons/voice_lock/record_lock_body_shadow.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_body_shadow.png rename to Telegram/Resources/icons/voice_lock/record_lock_body_shadow.png diff --git a/Telegram/Resources/icons/lock/record_lock_body_shadow@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_body_shadow@2x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_body_shadow@2x.png rename to Telegram/Resources/icons/voice_lock/record_lock_body_shadow@2x.png diff --git a/Telegram/Resources/icons/lock/record_lock_body_shadow@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_body_shadow@3x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_body_shadow@3x.png rename to Telegram/Resources/icons/voice_lock/record_lock_body_shadow@3x.png diff --git a/Telegram/Resources/icons/lock/record_lock_bottom.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_bottom.png rename to Telegram/Resources/icons/voice_lock/record_lock_bottom.png diff --git a/Telegram/Resources/icons/lock/record_lock_bottom@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom@2x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_bottom@2x.png rename to Telegram/Resources/icons/voice_lock/record_lock_bottom@2x.png diff --git a/Telegram/Resources/icons/lock/record_lock_bottom@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom@3x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_bottom@3x.png rename to Telegram/Resources/icons/voice_lock/record_lock_bottom@3x.png diff --git a/Telegram/Resources/icons/lock/record_lock_bottom_shadow.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_bottom_shadow.png rename to Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow.png diff --git a/Telegram/Resources/icons/lock/record_lock_bottom_shadow@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow@2x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_bottom_shadow@2x.png rename to Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow@2x.png diff --git a/Telegram/Resources/icons/lock/record_lock_bottom_shadow@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow@3x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_bottom_shadow@3x.png rename to Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow@3x.png diff --git a/Telegram/Resources/icons/lock/record_lock_top.png b/Telegram/Resources/icons/voice_lock/record_lock_top.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_top.png rename to Telegram/Resources/icons/voice_lock/record_lock_top.png diff --git a/Telegram/Resources/icons/lock/record_lock_top@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_top@2x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_top@2x.png rename to Telegram/Resources/icons/voice_lock/record_lock_top@2x.png diff --git a/Telegram/Resources/icons/lock/record_lock_top@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_top@3x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_top@3x.png rename to Telegram/Resources/icons/voice_lock/record_lock_top@3x.png diff --git a/Telegram/Resources/icons/lock/record_lock_top_shadow.png b/Telegram/Resources/icons/voice_lock/record_lock_top_shadow.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_top_shadow.png rename to Telegram/Resources/icons/voice_lock/record_lock_top_shadow.png diff --git a/Telegram/Resources/icons/lock/record_lock_top_shadow@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_top_shadow@2x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_top_shadow@2x.png rename to Telegram/Resources/icons/voice_lock/record_lock_top_shadow@2x.png diff --git a/Telegram/Resources/icons/lock/record_lock_top_shadow@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_top_shadow@3x.png similarity index 100% rename from Telegram/Resources/icons/lock/record_lock_top_shadow@3x.png rename to Telegram/Resources/icons/voice_lock/record_lock_top_shadow@3x.png diff --git a/Telegram/Resources/icons/voice_lock/voice_arrow.png b/Telegram/Resources/icons/voice_lock/voice_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..e42af30b0712ad405631d5553f840fbb26d246f3 GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmSN`?>!lvI6-QsS%!O zzP=1vKsE;hV|yk83y{SK#8N=az`(SC2`(bBfEmFCNgn(gJ{?G1^>lFz@!)+s#g?x{ zLBOq0SSv3fJ^7L03!A>V;uB8Kj3{Ih<~k%>5XK`>UvSWOMTyXyw3_@sJ!u?@GyFIG zQc;fnpv|H7XG6!UU2an@CFmSy{4C4DpUmUqdzl4@o|~Vk_>r_>`nKF;)%~n_#fL?m z8iKOEx_GT)js9WL7a+nK63QBM%35gu0rv8p_omEYjlI5Bde>RmJ71+I?OmXATH%iR z#VF@_&-aP%7Z-49DCRqCpfl6@MyR=e(zDNS%G|u)CkWs zUtb0-Ae)1Mu|1Q41;}CqVksbIU|?Fn1Q#(_z>HvnB!B$(GXpA~?CIhdlEM0Rx^GvD zfk5l!dB^vve_+~sm3haf=R8e6iaFmuJ*KSj!tdmad3+|u!jmIB*%r14aClz*eQ&e# zZ-tf%$#=ee%v=3OSBCMQwWAM%FoRXYdVvcL9}0aKauhc(s4eU?Y}20CY|x=|I>%uK^NU@5uSL&Q z&YRt4z`Q_dUa(|a1M{Dn=Q}TKzNx^%^T4MjG<3`RYQ=WNj@JxEAKxtdU~}H=h$(~U z$3Lq!^DN|FpnQ+9Mecy;$7zaJ7!@ArI;~(d@m1dAdn5NS>#8%G?jQMR@3nlMi~^5y z@*~R!l1H}XUin@d%3y(3*}E$jxqOuRboZC_x+~Y2E=*7P-?G); z>-VMo)(Q>U%Ux>?7$+L9yJpB3?$`Y*fk`PnW^FRlx;bKB53;zN+Yz!lvI6-QsS%!O zzP=1vKsE;hV|yk83y{SK#8N=az`(SC2`=KXfEmFCNpAj`auuj#si%u$NCo5DoBO+7 zIY_X6xOw{MF>wK%ErMG%Y!cma?b^9(htfA}-~2&TNB4;Oh7H>;#Oa70@e2vL@?PyU zTi&#@^DK;K&U_@zky!jG{mw+ReR+s4Mch#fAn};cKg+wf5scTjb-?lr(d|m zzB)upPvItW*CEa&=bjsHjncjCQPv>f*YKrk?~2=RZ@C!pi-;c=RN3tC{*gsP3ZIAA zaid9+3ZXLH9w(WP9<`L=d-DIe)8P-|<(nJ|3%A|Y73E@Gbv3KRYVHIC{hk&GX-H`SaZ3@y8iPGkr4Vm!Dg{D}QCkqMJEx+i&;kP3QJi6F!x; z+3WMqAG4-xpCrFB#LGsG|Do;TiT0hxl>h#;nScJg>(?z&4I%0eKYhFue@U-iV{UP> j!h(|y#6>rb6!NIv_V&EfYTub8Knd5=)z4*}Q$iB}Yj#SR literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 7fead3ef5..b3a0f30ee 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -362,26 +362,26 @@ historyRecordTextLeft: 15px; historyRecordTextRight: 25px; historyRecordLockShowDuration: historyToDownDuration; -historyRecordLockSize: size(75px, 150px); +historyRecordLockSize: size(75px, 133px); historyRecordLockIconFg: historyToDownFg; -historyRecordLockIconSize: size(14px, 18px); +historyRecordLockIconSize: size(14px, 17px); historyRecordLockIconBottomHeight: 9px; historyRecordLockIconLineHeight: 2px; historyRecordLockIconLineSkip: 3px; historyRecordLockIconLineWidth: 2px; historyRecordLockIconArcHeight: 4px; -historyRecordLockTopShadow: icon {{ "lock/record_lock_top_shadow", historyToDownShadow }}; -historyRecordLockTop: icon {{ "lock/record_lock_top", historyToDownBg }}; -historyRecordLockBottomShadow: icon {{ "lock/record_lock_bottom_shadow", historyToDownShadow }}; -historyRecordLockBottom: icon {{ "lock/record_lock_bottom", historyToDownBg }}; -historyRecordLockBodyShadow: icon {{ "lock/record_lock_body_shadow", historyToDownShadow }}; -historyRecordLockBody: icon {{ "lock/record_lock_body", historyToDownBg }}; +historyRecordLockTopShadow: icon {{ "voice_lock/record_lock_top_shadow", historyToDownShadow }}; +historyRecordLockTop: icon {{ "voice_lock/record_lock_top", historyToDownBg }}; +historyRecordLockBottomShadow: icon {{ "voice_lock/record_lock_bottom_shadow", historyToDownShadow }}; +historyRecordLockBottom: icon {{ "voice_lock/record_lock_bottom", historyToDownBg }}; +historyRecordLockBodyShadow: icon {{ "voice_lock/record_lock_body_shadow", historyToDownShadow }}; +historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}; historyRecordLockMargin: margins(4px, 4px, 4px, 4px); -historyRecordLockArrow: icon {{ "history_down_arrow-flip_vertical", historyToDownFg }}; +historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }}; -historyRecordLockPosition: historyToDownPosition; +historyRecordLockPosition: point(7px, 35px); historySilentToggle: IconButton(historyBotKeyboardShow) { icon: icon {{ "send_control_silent_off", historyComposeIconFg }}; From 131c2e1c565f6feae153b164700b1a16192faeba Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 5 Nov 2020 11:16:11 +0300 Subject: [PATCH 099/370] Slightly refactored waveform paint in voice messages. --- .../view/media/history_view_document.cpp | 158 +++++++++++------- 1 file changed, 100 insertions(+), 58 deletions(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 3795e115f..da4f6ad6a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -61,6 +61,85 @@ constexpr auto kAudioVoiceMsgUpdateView = crl::time(100); return result; } +void PaintWaveform( + Painter &p, + const VoiceData *voiceData, + int availableWidth, + bool selected, + bool outbg, + float64 progress) { + const auto wf = [&]() -> const VoiceWaveform* { + if (!voiceData) { + return nullptr; + } + if (voiceData->waveform.isEmpty()) { + return nullptr; + } else if (voiceData->waveform.at(0) < 0) { + return nullptr; + } + return &voiceData->waveform; + }(); + + // Rescale waveform by going in waveform.size * bar_count 1D grid. + const auto active = outbg + ? (selected + ? st::msgWaveformOutActiveSelected + : st::msgWaveformOutActive) + : (selected + ? st::msgWaveformInActiveSelected + : st::msgWaveformInActive); + const auto inactive = outbg + ? (selected + ? st::msgWaveformOutInactiveSelected + : st::msgWaveformOutInactive) + : (selected + ? st::msgWaveformInInactiveSelected + : st::msgWaveformInInactive); + const auto wfSize = wf + ? wf->size() + : ::Media::Player::kWaveformSamplesCount; + const auto activeWidth = std::round(availableWidth * progress); + + const auto &barWidth = st::msgWaveformBar; + const auto barCount = std::min( + availableWidth / (barWidth + st::msgWaveformSkip), + wfSize); + const auto barNormValue = (wf ? voiceData->wavemax : 0) + 1; + const auto maxDelta = st::msgWaveformMax - st::msgWaveformMin; + const auto &bottom = st::msgWaveformMax; + p.setPen(Qt::NoPen); + for (auto i = 0, barLeft = 0, sum = 0, maxValue = 0; i < wfSize; ++i) { + const auto value = wf ? wf->at(i) : 0; + if (sum + barCount < wfSize) { + maxValue = std::max(maxValue, value); + sum += barCount; + continue; + } + // Draw bar. + sum = sum + barCount - wfSize; + if (sum < (barCount + 1) / 2) { + maxValue = std::max(maxValue, value); + } + const auto barValue = ((maxValue * maxDelta) + (barNormValue / 2)) + / barNormValue; + const auto barHeight = st::msgWaveformMin + barValue; + const auto barTop = bottom - barValue; + + if ((barLeft < activeWidth) && (barLeft + barWidth > activeWidth)) { + const auto leftWidth = activeWidth - barLeft; + const auto rightWidth = barWidth - leftWidth; + p.fillRect(barLeft, barTop, leftWidth, barHeight, active); + p.fillRect(activeWidth, barTop, rightWidth, barHeight, inactive); + } else { + const auto &color = (barLeft >= activeWidth) ? inactive : active; + p.fillRect(barLeft, barTop, barWidth, barHeight, color); + } + barLeft += barWidth + st::msgWaveformSkip; + + maxValue = (sum < (barCount + 1) / 2) ? 0 : value; + } +} + } // namespace Document::Document( @@ -418,79 +497,42 @@ void Document::draw( if (const auto voice = Get()) { ensureDataMediaCreated(); - const VoiceWaveform *wf = nullptr; - uchar norm_value = 0; if (const auto voiceData = _data->voice()) { - wf = &voiceData->waveform; - if (wf->isEmpty()) { - wf = nullptr; + if (voiceData->waveform.isEmpty()) { if (loaded) { Local::countVoiceWaveform(_dataMedia.get()); } - } else if (wf->at(0) < 0) { - wf = nullptr; - } else { - norm_value = voiceData->wavemax; } } - auto progress = ([voice] { + + const auto progress = [&] { + if (!outbg + && !voice->_playback + && _realParent->hasUnreadMediaFlag()) { + return 1.; + } if (voice->seeking()) { return voice->seekingCurrent(); } else if (voice->_playback) { return voice->_playback->progress.current(); } return 0.; - })(); + }(); if (voice->seeking()) { - voiceStatusOverride = Ui::FormatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000); + voiceStatusOverride = Ui::FormatPlayedText( + std::round(progress * voice->_lastDurationMs) / 1000, + voice->_lastDurationMs / 1000); } - // rescale waveform by going in waveform.size * bar_count 1D grid - auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive); - auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive); - auto wf_size = wf ? wf->size() : ::Media::Player::kWaveformSamplesCount; - auto availw = namewidth + st::msgWaveformSkip; - auto activew = qRound(availw * progress); - if (!outbg - && !voice->_playback - && _realParent->hasUnreadMediaFlag()) { - activew = availw; - } - auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size); - auto max_value = 0; - auto max_delta = st::msgWaveformMax - st::msgWaveformMin; - auto bottom = st.padding.top() - topMinus + st::msgWaveformMax; - p.setPen(Qt::NoPen); - for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { - auto value = wf ? wf->at(i) : 0; - if (sum_i + bar_count >= wf_size) { // draw bar - sum_i = sum_i + bar_count - wf_size; - if (sum_i < (bar_count + 1) / 2) { - if (max_value < value) max_value = value; - } - auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1); - - if (bar_x >= activew) { - p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive); - } else if (bar_x + st::msgWaveformBar <= activew) { - p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active); - } else { - p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active); - p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive); - } - bar_x += st::msgWaveformBar + st::msgWaveformSkip; - - if (sum_i < (bar_count + 1) / 2) { - max_value = 0; - } else { - max_value = value; - } - } else { - if (max_value < value) max_value = value; - - sum_i += bar_count; - } - } + p.save(); + p.translate(nameleft, st.padding.top() - topMinus); + PaintWaveform(p, + _data->voice(), + namewidth + st::msgWaveformSkip, + selected, + outbg, + progress); + p.restore(); } else if (auto named = Get()) { p.setFont(st::semiboldFont); p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg)); From 647cbc546424de316b4dc61a745129e77fa4a39f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 6 Nov 2020 21:29:51 +0300 Subject: [PATCH 100/370] Added initial ability to delete recorded voice data. --- .../history_view_voice_record_bar.cpp | 170 ++++++++++++++++-- .../controls/history_view_voice_record_bar.h | 14 +- Telegram/SourceFiles/ui/chat/chat.style | 5 + 3 files changed, 175 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 29a765397..ec98faa41 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "boxes/confirm_box.h" #include "core/application.h" +#include "data/data_document.h" #include "history/view/controls/history_view_voice_record_button.h" #include "lang/lang_keys.h" #include "mainwindow.h" @@ -60,14 +61,93 @@ enum class FilterType { .arg(decimalPart); } +[[nodiscard]] std::unique_ptr ProcessCaptureResult( + const ::Media::Capture::Result &data) { + auto voiceData = std::make_unique(); + voiceData->duration = Duration(data.samples); + voiceData->waveform = data.waveform; + voiceData->wavemax = voiceData->waveform.empty() + ? uchar(0) + : *ranges::max_element(voiceData->waveform); + return voiceData; +} + } // namespace +class ListenWrap final { +public: + ListenWrap( + not_null parent, + const ::Media::Capture::Result &data); + + void requestPaintProgress(float64 progress); + rpl::producer<> stopRequests() const; + + rpl::lifetime &lifetime(); + +private: + void init(); + + not_null _parent; + + const std::unique_ptr _voiceData; + const style::IconButton &_stDelete; + base::unique_qptr _delete; + + rpl::variable _showProgress = 0.; + + rpl::lifetime _lifetime; + +}; + +ListenWrap::ListenWrap( + not_null parent, + const ::Media::Capture::Result &data) +: _parent(parent) +, _voiceData(ProcessCaptureResult(data)) +, _stDelete(st::historyRecordDelete) +, _delete(base::make_unique_q(parent, _stDelete)) { + init(); +} + +void ListenWrap::init() { + auto deleteShow = _showProgress.value( + ) | rpl::map([](auto value) { + return value == 1.; + }) | rpl::distinct_until_changed(); + _delete->showOn(std::move(deleteShow)); + + _parent->paintRequest( + ) | rpl::start_with_next([=](const QRect &clip) { + const auto progress = _showProgress.current(); + if (progress == 0. || progress == 1.) { + return; + } + Painter p(_parent); + p.setOpacity(progress); + _stDelete.icon.paint(p, _stDelete.iconPosition, _parent->width()); + }, _lifetime); +} + +void ListenWrap::requestPaintProgress(float64 progress) { + _showProgress = progress; +} + +rpl::producer<> ListenWrap::stopRequests() const { + return _delete->clicks() | rpl::to_empty; +} + +rpl::lifetime &ListenWrap::lifetime() { + return _lifetime; +} + class RecordLock final : public Ui::RpWidget { public: RecordLock(not_null parent); void requestPaintProgress(float64 progress); + [[nodiscard]] rpl::producer<> stops() const; [[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] bool isLocked() const; @@ -102,10 +182,11 @@ RecordLock::RecordLock(not_null parent) } void RecordLock::init() { - setAttribute(Qt::WA_TransparentForMouseEvents); shownValue( ) | rpl::start_with_next([=](bool shown) { if (!shown) { + setCursor(style::cur_default); + setAttribute(Qt::WA_TransparentForMouseEvents, true); _lockAnimation.stop(); _lockEnderAnimation.stop(); _progress = 0.; @@ -129,8 +210,17 @@ void RecordLock::init() { locks( ) | rpl::start_with_next([=] { - const auto duration = st::historyRecordVoiceShowDuration; - _lockAnimation.start([=] { update(); }, 0., 1., duration); + const auto &duration = st::historyRecordVoiceShowDuration; + const auto from = 0.; + const auto to = 1.; + auto callback = [=](auto value) { + update(); + if (value == to) { + setCursor(style::cur_pointer); + setAttribute(Qt::WA_TransparentForMouseEvents, false); + } + }; + _lockAnimation.start(std::move(callback), from, to, duration); }, lifetime()); } @@ -282,6 +372,15 @@ rpl::producer<> RecordLock::locks() const { ) | rpl::filter([=] { return isLocked(); }) | rpl::to_empty; } +rpl::producer<> RecordLock::stops() const { + return events( + ) | rpl::filter([=](not_null e) { + return isLocked() + && (_lockAnimation.value(1.) == 1.) + && (e->type() == QEvent::MouseButtonRelease); + }) | rpl::to_empty; +} + VoiceRecordBar::VoiceRecordBar( not_null parent, not_null sectionWidget, @@ -313,7 +412,7 @@ VoiceRecordBar::VoiceRecordBar( VoiceRecordBar::~VoiceRecordBar() { if (isRecording()) { - stopRecording(false); + stopRecording(StopType::Cancel); } } @@ -395,6 +494,7 @@ void VoiceRecordBar::init() { } p.fillRect(clip, st::historyComposeAreaBg); + p.setOpacity(std::min(p.opacity(), 1. - showListenAnimationRatio())); if (clip.intersects(_messageRect)) { // The message should be painted first to avoid flickering. drawMessage(p, activeAnimationRatio()); @@ -430,6 +530,29 @@ void VoiceRecordBar::init() { _showLockAnimation.start(std::move(callback), from, to, duration); }, lifetime()); + _lock->stops( + ) | rpl::start_with_next([=] { + ::Media::Capture::instance()->startedChanges( + ) | rpl::filter([](auto capturing) { + return !capturing; + }) | rpl::take(1) | rpl::start_with_next([=] { + Assert(_listen != nullptr); + + _lockShowing = false; + + const auto to = 1.; + const auto &duration = st::historyRecordVoiceShowDuration; + auto callback = [=](auto value) { + _listen->requestPaintProgress(value); + _level->requestPaintProgress(to - value); + update(); + }; + _showListenAnimation.start(std::move(callback), 0., to, duration); + }, lifetime()); + + stopRecording(StopType::Listen); + }, lifetime()); + _lock->locks( ) | rpl::start_with_next([=] { installClickOutsideFilter(); @@ -504,7 +627,11 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn &&callback) { const auto from = show ? 0. : 1.; const auto duration = st::historyRecordVoiceShowDuration; auto animationCallback = [=, callback = std::move(callback)](auto value) { - _level->requestPaintProgress(value); + if (!_listen) { + _level->requestPaintProgress(value); + } else { + _listen->requestPaintProgress(value); + } update(); if ((show && value == 1.) || (!show && value == 0.)) { if (callback) { @@ -621,7 +748,7 @@ void VoiceRecordBar::stop(bool send) { auto disappearanceCallback = [=] { hide(); - stopRecording(send); + stopRecording(send ? StopType::Send : StopType::Cancel); }; _lockShowing = false; visibilityAnimate(false, std::move(disappearanceCallback)); @@ -637,6 +764,8 @@ void VoiceRecordBar::finish() { _showAnimation.stop(); + _listen = nullptr; + _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); _controller->widget()->setInnerFocus(); } @@ -645,12 +774,12 @@ void VoiceRecordBar::hideFast() { hide(); _lock->hide(); _level->hide(); - stopRecording(false); + stopRecording(StopType::Cancel); } -void VoiceRecordBar::stopRecording(bool send) { +void VoiceRecordBar::stopRecording(StopType type) { using namespace ::Media::Capture; - if (!send) { + if (type == StopType::Cancel) { instance()->stop(); return; } @@ -661,7 +790,17 @@ void VoiceRecordBar::stopRecording(bool send) { Window::ActivateWindow(_controller); const auto duration = Duration(data.samples); - _sendVoiceRequests.fire({ data.bytes, data.waveform, duration }); + if (type == StopType::Send) { + _sendVoiceRequests.fire({ data.bytes, data.waveform, duration }); + } else if (type == StopType::Listen) { + _listen = std::make_unique(this, data); + _listen->stopRequests( + ) | rpl::take(1) | rpl::start_with_next([=] { + visibilityAnimate(false, [=] { hide(); }); + }, _listen->lifetime()); + + _lockShowing = false; + } })); } @@ -693,11 +832,12 @@ void VoiceRecordBar::drawRedCircle(Painter &p) { p.setPen(Qt::NoPen); p.setBrush(st::historyRecordVoiceFgInactive); - p.setOpacity(1. - _redCircleProgress); + const auto opacity = p.opacity(); + p.setOpacity(opacity * (1. - _redCircleProgress)); const int radii = st::historyRecordSignalRadius * showAnimationRatio(); const auto center = _redCircleRect.center() + QPoint(1, 1); p.drawEllipse(center, radii, radii); - p.setOpacity(1.); + p.setOpacity(opacity); } void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { @@ -761,6 +901,10 @@ float64 VoiceRecordBar::showAnimationRatio() const { return _showAnimation.value(1.); } +float64 VoiceRecordBar::showListenAnimationRatio() const { + return _showListenAnimation.value(_listen ? 1. : 0.); +} + void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { const auto localPos = mapFromGlobal(globalPos); const auto lower = _lock->height(); @@ -817,7 +961,7 @@ void VoiceRecordBar::installClickOutsideFilter() { } else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) { return Type::ShowBox; } else if (type == QEvent::MouseButtonPress) { - return (noBox && !_inField.current()) + return (noBox && !_inField.current() && !_lock->underMouse()) ? Type::ShowBox : Type::Continue; } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 16b7447d3..fe4d50f86 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/rp_widget.h" +struct VoiceData; + namespace Ui { class SendButton; } // namespace Ui @@ -24,6 +26,7 @@ class SessionController; namespace HistoryView::Controls { class VoiceRecordButton; +class ListenWrap; class RecordLock; class VoiceRecordBar final : public Ui::RpWidget { @@ -64,6 +67,12 @@ public: [[nodiscard]] bool isLockPresent() const; private: + enum class StopType { + Cancel, + Send, + Listen, + }; + void init(); void updateMessageGeometry(); @@ -74,7 +83,7 @@ private: bool recordingAnimationCallback(crl::time now); void stop(bool send); - void stopRecording(bool send); + void stopRecording(StopType type); void visibilityAnimate(bool show, Fn &&callback); bool showRecordButton() const; @@ -92,6 +101,7 @@ private: void activeAnimate(bool active); float64 showAnimationRatio() const; + float64 showListenAnimationRatio() const; float64 activeAnimationRatio() const; void computeAndSetLockProgress(QPoint globalPos); @@ -101,6 +111,7 @@ private: const std::shared_ptr _send; const std::unique_ptr _lock; const std::unique_ptr _level; + std::unique_ptr _listen; base::Timer _startTimer; @@ -129,6 +140,7 @@ private: rpl::variable _lockShowing = false; Ui::Animations::Simple _showLockAnimation; + Ui::Animations::Simple _showListenAnimation; Ui::Animations::Simple _activeAnimation; Ui::Animations::Simple _showAnimation; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index b3a0f30ee..e5792f13e 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -381,6 +381,11 @@ historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }} historyRecordLockMargin: margins(4px, 4px, 4px, 4px); historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }}; +historyRecordDelete: IconButton(historyAttach) { + icon: icon {{ "info_media_delete", historyComposeIconFg }}; + iconOver: icon {{ "info_media_delete", historyComposeIconFgOver }}; +} + historyRecordLockPosition: point(7px, 35px); historySilentToggle: IconButton(historyBotKeyboardShow) { From a19e3ca3dcf503d3f0da8a7f7379c46a834f3892 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 7 Nov 2020 21:02:08 +0300 Subject: [PATCH 101/370] Added initial ability to send recorded voice data from listen state. --- .../SourceFiles/history/history_widget.cpp | 6 ++ .../history_view_compose_controls.cpp | 6 ++ .../history_view_voice_record_bar.cpp | 84 +++++++++++++++---- .../controls/history_view_voice_record_bar.h | 6 +- 4 files changed, 86 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b9b47f918..23cf33d08 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -804,6 +804,11 @@ void HistoryWidget::initVoiceRecordBar() { updateUnreadMentionsVisibility(); }, lifetime()); + _voiceRecordBar->updateSendButtonTypeRequests( + ) | rpl::start_with_next([=] { + updateSendButtonType(); + }, lifetime()); + _voiceRecordBar->hideFast(); } @@ -3639,6 +3644,7 @@ bool HistoryWidget::isMuteUnmute() const { bool HistoryWidget::showRecordButton() const { return Media::Capture::instance()->available() + && !_voiceRecordBar->isListenState() && !HasSendText(_field) && !readyToForward() && !_editMsgId; 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 fdd5d198d..f2961d993 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -994,6 +994,7 @@ void ComposeControls::orderControls() { bool ComposeControls::showRecordButton() const { return ::Media::Capture::instance()->available() + && !_voiceRecordBar->isListenState() && !HasSendText(_field) //&& !readyToForward() && !isEditingMessage(); @@ -1533,6 +1534,11 @@ void ComposeControls::initVoiceRecordBar() { _voiceRecordBar->setEscFilter([=] { return (isEditingMessage() || replyingToMessage()); }); + + _voiceRecordBar->updateSendButtonTypeRequests( + ) | rpl::start_with_next([=] { + updateSendButtonType(); + }, _wrap->lifetime()); } void ComposeControls::updateWrappingVisibility() { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index ec98faa41..4f3ac5146 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -78,10 +78,11 @@ class ListenWrap final { public: ListenWrap( not_null parent, - const ::Media::Capture::Result &data); + ::Media::Capture::Result &&data); void requestPaintProgress(float64 progress); rpl::producer<> stopRequests() const; + ::Media::Capture::Result *data() const; rpl::lifetime &lifetime(); @@ -90,7 +91,8 @@ private: not_null _parent; - const std::unique_ptr _voiceData; + const std::unique_ptr<::Media::Capture::Result> _data; + // const std::unique_ptr _voiceData; const style::IconButton &_stDelete; base::unique_qptr _delete; @@ -102,9 +104,10 @@ private: ListenWrap::ListenWrap( not_null parent, - const ::Media::Capture::Result &data) + ::Media::Capture::Result &&data) : _parent(parent) -, _voiceData(ProcessCaptureResult(data)) +, _data(std::make_unique<::Media::Capture::Result>(std::move(data))) +// , _voiceData(ProcessCaptureResult(_data)) , _stDelete(st::historyRecordDelete) , _delete(base::make_unique_q(parent, _stDelete)) { init(); @@ -137,6 +140,10 @@ rpl::producer<> ListenWrap::stopRequests() const { return _delete->clicks() | rpl::to_empty; } +::Media::Capture::Result *ListenWrap::data() const { + return _data.get(); +} + rpl::lifetime &ListenWrap::lifetime() { return _lifetime; } @@ -533,11 +540,9 @@ void VoiceRecordBar::init() { _lock->stops( ) | rpl::start_with_next([=] { ::Media::Capture::instance()->startedChanges( - ) | rpl::filter([](auto capturing) { - return !capturing; + ) | rpl::filter([=](auto capturing) { + return !capturing && _listen; }) | rpl::take(1) | rpl::start_with_next([=] { - Assert(_listen != nullptr); - _lockShowing = false; const auto to = 1.; @@ -605,6 +610,50 @@ void VoiceRecordBar::init() { _startTimer.cancel(); } }, lifetime()); + + _listenChanges.events( + ) | rpl::filter([=] { + return _listen != nullptr; + }) | rpl::start_with_next([=] { + _listen->stopRequests( + ) | rpl::take(1) | rpl::start_with_next([=] { + visibilityAnimate(false, [=] { hide(); }); + }, _listen->lifetime()); + + _listen->lifetime().add([=] { _listenChanges.fire({}); }); + + auto filterCallback = [=](not_null e) { + using Result = base::EventFilterResult; + if (_send->type() != Ui::SendButton::Type::Send + && _send->type() != Ui::SendButton::Type::Schedule) { + return Result::Continue; + } + switch(e->type()) { + case QEvent::MouseButtonRelease: { + auto callback = [=] { + const auto data = _listen->data(); + _sendVoiceRequests.fire({ + data->bytes, + data->waveform, + Duration(data->samples) }); + hide(); + }; + visibilityAnimate(false, std::move(callback)); + _send->clearState(); + return Result::Cancel; + } + default: return Result::Continue; + } + }; + + auto filter = base::install_event_filter( + _send.get(), + std::move(filterCallback)); + + _listen->lifetime().make_state>( + std::move(filter)); + + }, lifetime()); } void VoiceRecordBar::activeAnimate(bool active) { @@ -783,8 +832,10 @@ void VoiceRecordBar::stopRecording(StopType type) { instance()->stop(); return; } - instance()->stop(crl::guard(this, [=](const Result &data) { + instance()->stop(crl::guard(this, [=](Result &&data) { if (data.bytes.isEmpty()) { + // Close everything. + stop(false); return; } @@ -793,11 +844,8 @@ void VoiceRecordBar::stopRecording(StopType type) { if (type == StopType::Send) { _sendVoiceRequests.fire({ data.bytes, data.waveform, duration }); } else if (type == StopType::Listen) { - _listen = std::make_unique(this, data); - _listen->stopRequests( - ) | rpl::take(1) | rpl::start_with_next([=] { - visibilityAnimate(false, [=] { hide(); }); - }, _listen->lifetime()); + _listen = std::make_unique(this, std::move(data)); + _listenChanges.fire({}); _lockShowing = false; } @@ -879,10 +927,18 @@ rpl::producer VoiceRecordBar::lockShowStarts() const { return _lockShowing.changes(); } +rpl::producer<> VoiceRecordBar::updateSendButtonTypeRequests() const { + return _listenChanges.events(); +} + bool VoiceRecordBar::isLockPresent() const { return _lockShowing.current(); } +bool VoiceRecordBar::isListenState() const { + return _listen != nullptr; +} + bool VoiceRecordBar::isTypeRecord() const { return (_send->type() == Ui::SendButton::Type::Record); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index fe4d50f86..1df147775 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -57,6 +57,7 @@ public: [[nodiscard]] rpl::producer sendVoiceRequests() const; [[nodiscard]] rpl::producer recordingStateChanges() const; [[nodiscard]] rpl::producer lockShowStarts() const; + [[nodiscard]] rpl::producer<> updateSendButtonTypeRequests() const; void setLockBottom(rpl::producer &&bottom); void setSendButtonGeometryValue(rpl::producer &&geometry); @@ -65,6 +66,7 @@ public: [[nodiscard]] bool isRecording() const; [[nodiscard]] bool isLockPresent() const; + [[nodiscard]] bool isListenState() const; private: enum class StopType { @@ -117,6 +119,7 @@ private: rpl::event_stream _sendActionUpdates; rpl::event_stream _sendVoiceRequests; + rpl::event_stream<> _listenChanges; int _centerY = 0; QRect _redCircleRect; @@ -130,6 +133,7 @@ private: rpl::variable _recording = false; rpl::variable _inField = false; + rpl::variable _lockShowing = false; int _recordingSamples = 0; float64 _redCircleProgress = 0.; @@ -137,8 +141,6 @@ private: rpl::lifetime _recordingLifetime; - rpl::variable _lockShowing = false; - Ui::Animations::Simple _showLockAnimation; Ui::Animations::Simple _showListenAnimation; Ui::Animations::Simple _activeAnimation; From 8b2bb722de283716a6c95e7a60cf9852de6d8fea Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 7 Nov 2020 22:38:03 +0300 Subject: [PATCH 102/370] Added ability to send and cancel recorded voice data with keys. --- .../history_view_voice_record_bar.cpp | 111 +++++++++++++----- .../controls/history_view_voice_record_bar.h | 1 + 2 files changed, 81 insertions(+), 31 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 4f3ac5146..d5ef93345 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -622,37 +622,7 @@ void VoiceRecordBar::init() { _listen->lifetime().add([=] { _listenChanges.fire({}); }); - auto filterCallback = [=](not_null e) { - using Result = base::EventFilterResult; - if (_send->type() != Ui::SendButton::Type::Send - && _send->type() != Ui::SendButton::Type::Schedule) { - return Result::Continue; - } - switch(e->type()) { - case QEvent::MouseButtonRelease: { - auto callback = [=] { - const auto data = _listen->data(); - _sendVoiceRequests.fire({ - data->bytes, - data->waveform, - Duration(data->samples) }); - hide(); - }; - visibilityAnimate(false, std::move(callback)); - _send->clearState(); - return Result::Cancel; - } - default: return Result::Continue; - } - }; - - auto filter = base::install_event_filter( - _send.get(), - std::move(filterCallback)); - - _listen->lifetime().make_state>( - std::move(filter)); - + installListenStateFilter(); }, lifetime()); } @@ -1045,4 +1015,83 @@ void VoiceRecordBar::installClickOutsideFilter() { std::move(filter)); } +void VoiceRecordBar::installListenStateFilter() { + const auto close = [=](bool send) { + auto callback = [=] { + if (send) { + const auto data = _listen->data(); + _sendVoiceRequests.fire({ + data->bytes, + data->waveform, + Duration(data->samples) }); + } + hide(); + }; + visibilityAnimate(false, std::move(callback)); + }; + + const auto isSend = [=] { + return _send->type() == Ui::SendButton::Type::Send + || _send->type() == Ui::SendButton::Type::Schedule; + }; + + auto mouseFilterCallback = [=](not_null e) { + using Result = base::EventFilterResult; + if (!isSend()) { + return Result::Continue; + } + switch(e->type()) { + case QEvent::MouseButtonRelease: { + close(true); + _send->clearState(); + return Result::Cancel; + } + default: return Result::Continue; + } + }; + + auto sendFilter = base::install_event_filter( + _send.get(), + std::move(mouseFilterCallback)); + + _listen->lifetime().make_state>( + std::move(sendFilter)); + + auto keyFilterCallback = [=](not_null e) { + using Result = base::EventFilterResult; + if (!isSend()) { + return Result::Continue; + } + switch(e->type()) { + case QEvent::KeyPress: { + const auto key = static_cast(e.get())->key(); + const auto isEsc = (key == Qt::Key_Escape); + const auto isEnter = (key == Qt::Key_Enter + || key == Qt::Key_Return); + if (isEnter) { + close(true); + return Result::Cancel; + } + if (isEsc) { + if (_escFilter && _escFilter()) { + return Result::Continue; + } else { + close(false); + return Result::Cancel; + } + } + return Result::Continue; + } + default: return Result::Continue; + } + }; + + auto keyFilter = base::install_event_filter( + QCoreApplication::instance(), + std::move(keyFilterCallback)); + + _listen->lifetime().make_state>( + std::move(keyFilter)); +} + } // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 1df147775..0968e6e52 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -95,6 +95,7 @@ private: void startRedCircleAnimation(); void installClickOutsideFilter(); + void installListenStateFilter(); bool isTypeRecord() const; bool hasDuration() const; From 189c9407109a35ae5cab76f81466db48153f99bb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 8 Nov 2020 05:04:50 +0300 Subject: [PATCH 103/370] Added waveform display of recorded voice data. --- .../history_view_voice_record_bar.cpp | 77 +++++++++++++++++-- Telegram/SourceFiles/ui/chat/chat.style | 2 + 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index d5ef93345..4243ffb5d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "core/application.h" #include "data/data_document.h" +#include "history/history_item_components.h" #include "history/view/controls/history_view_voice_record_button.h" #include "lang/lang_keys.h" #include "mainwindow.h" @@ -91,11 +92,13 @@ private: not_null _parent; + const std::unique_ptr _voiceData; const std::unique_ptr<::Media::Capture::Result> _data; - // const std::unique_ptr _voiceData; const style::IconButton &_stDelete; base::unique_qptr _delete; + QRect _waveformBgRect; + rpl::variable _showProgress = 0.; rpl::lifetime _lifetime; @@ -106,8 +109,8 @@ ListenWrap::ListenWrap( not_null parent, ::Media::Capture::Result &&data) : _parent(parent) +, _voiceData(ProcessCaptureResult(data)) , _data(std::make_unique<::Media::Capture::Result>(std::move(data))) -// , _voiceData(ProcessCaptureResult(_data)) , _stDelete(st::historyRecordDelete) , _delete(base::make_unique_q(parent, _stDelete)) { init(); @@ -120,15 +123,75 @@ void ListenWrap::init() { }) | rpl::distinct_until_changed(); _delete->showOn(std::move(deleteShow)); + _parent->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + const auto left = _stDelete.width + st::historyRecordWaveformLeftSkip; + _waveformBgRect = QRect({ 0, 0 }, size) + .marginsRemoved(st::historyRecordWaveformBgMargins); + }, _lifetime); + _parent->paintRequest( ) | rpl::start_with_next([=](const QRect &clip) { - const auto progress = _showProgress.current(); - if (progress == 0. || progress == 1.) { - return; - } Painter p(_parent); + PainterHighQualityEnabler hq(p); + const auto progress = _showProgress.current(); p.setOpacity(progress); - _stDelete.icon.paint(p, _stDelete.iconPosition, _parent->width()); + if (progress > 0. && progress < 1.) { + _stDelete.icon.paint(p, _stDelete.iconPosition, _parent->width()); + } + + { + const auto deleteIconLeft = _stDelete.iconPosition.x(); + const auto bgRectRight = anim::interpolate( + deleteIconLeft, + _stDelete.width, + progress); + const auto bgRectLeft = anim::interpolate( + _parent->width() - deleteIconLeft - _waveformBgRect.height(), + _stDelete.width, + progress); + const auto bgRectMargins = style::margins( + bgRectLeft, + 0, + bgRectRight, + 0); + const auto bgRect = _waveformBgRect.marginsRemoved(bgRectMargins); + + const auto horizontalMargin = bgRect.width() - bgRect.height(); + const auto bgLeftCircleRect = bgRect.marginsRemoved( + style::margins(0, 0, horizontalMargin, 0)); + const auto bgRightCircleRect = bgRect.marginsRemoved( + style::margins(horizontalMargin, 0, 0, 0)); + + const auto halfHeight = bgRect.height() / 2; + const auto bgCenterRect = bgRect.marginsRemoved( + style::margins(halfHeight, 0, halfHeight, 0)); + + p.setPen(Qt::NoPen); + p.setBrush(st::historyRecordCancelActive); + p.drawEllipse(bgLeftCircleRect); + p.drawEllipse(bgRightCircleRect); + p.fillRect(bgCenterRect, st::historyRecordCancelActive); + + // Waveform paint. + { + const auto top = + (bgCenterRect.height() - st::msgWaveformMax) / 2; + const auto rect = bgCenterRect.marginsRemoved( + style::margins(halfHeight, top, halfHeight, top)); + if (rect.width() > 0) { + p.translate(rect.topLeft()); + HistoryDocumentVoice::PaintWaveform( + p, + _voiceData.get(), + rect.width(), + false, + false, + 0.); + p.resetTransform(); + } + } + } }, _lifetime); } diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index e5792f13e..277722678 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -385,6 +385,8 @@ historyRecordDelete: IconButton(historyAttach) { icon: icon {{ "info_media_delete", historyComposeIconFg }}; iconOver: icon {{ "info_media_delete", historyComposeIconFgOver }}; } +historyRecordWaveformLeftSkip: 5px; +historyRecordWaveformBgMargins: margins(5px, 7px, 5px, 7px); historyRecordLockPosition: point(7px, 35px); From 7826d0246df63c2eb0353f212a6f26169a9dd8f4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 9 Nov 2020 21:56:05 +0300 Subject: [PATCH 104/370] Added duration display of recorded voice data. const qptr --- .../history_view_voice_record_bar.cpp | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 4243ffb5d..e8171167a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -79,7 +79,8 @@ class ListenWrap final { public: ListenWrap( not_null parent, - ::Media::Capture::Result &&data); + ::Media::Capture::Result &&data, + const style::font &font); void requestPaintProgress(float64 progress); rpl::producer<> stopRequests() const; @@ -95,9 +96,13 @@ private: const std::unique_ptr _voiceData; const std::unique_ptr<::Media::Capture::Result> _data; const style::IconButton &_stDelete; - base::unique_qptr _delete; + const base::unique_qptr _delete; + const style::font &_durationFont; + const QString _duration; + const int _durationWidth; QRect _waveformBgRect; + QRect _durationRect; rpl::variable _showProgress = 0.; @@ -107,12 +112,17 @@ private: ListenWrap::ListenWrap( not_null parent, - ::Media::Capture::Result &&data) + ::Media::Capture::Result &&data, + const style::font &font) : _parent(parent) , _voiceData(ProcessCaptureResult(data)) , _data(std::make_unique<::Media::Capture::Result>(std::move(data))) , _stDelete(st::historyRecordDelete) -, _delete(base::make_unique_q(parent, _stDelete)) { +, _delete(base::make_unique_q(parent, _stDelete)) +, _durationFont(font) +, _duration(Ui::FormatDurationText( + float64(_data->samples) / ::Media::Player::kDefaultFrequency)) +, _durationWidth(_durationFont->width(_duration)) { init(); } @@ -173,12 +183,29 @@ void ListenWrap::init() { p.drawEllipse(bgRightCircleRect); p.fillRect(bgCenterRect, st::historyRecordCancelActive); + // Duration paint. + { + p.setFont(_durationFont); + p.setPen(st::historyRecordVoiceFgActiveIcon); + + const auto top = computeTopMargin(_durationFont->ascent); + const auto rect = bgCenterRect.marginsRemoved( + style::margins( + bgCenterRect.width() - _durationWidth, + top, + 0, + top)); + p.drawText(rect, style::al_left, _duration); + } + // Waveform paint. { const auto top = (bgCenterRect.height() - st::msgWaveformMax) / 2; + const auto right = st::historyRecordWaveformLeftSkip + + _durationWidth; const auto rect = bgCenterRect.marginsRemoved( - style::margins(halfHeight, top, halfHeight, top)); + style::margins(halfHeight, top, right, top)); if (rect.width() > 0) { p.translate(rect.topLeft()); HistoryDocumentVoice::PaintWaveform( @@ -877,7 +904,10 @@ void VoiceRecordBar::stopRecording(StopType type) { if (type == StopType::Send) { _sendVoiceRequests.fire({ data.bytes, data.waveform, duration }); } else if (type == StopType::Listen) { - _listen = std::make_unique(this, std::move(data)); + _listen = std::make_unique( + this, + std::move(data), + _cancelFont); _listenChanges.fire({}); _lockShowing = false; From 5eba6804836e5ed821a2abb3e04085f6289686e8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 10 Nov 2020 16:52:17 +0300 Subject: [PATCH 105/370] Added play button for recorded voice data. --- .../history_view_voice_record_bar.cpp | 134 +++++++++++++++++- Telegram/SourceFiles/mainwidget.cpp | 8 +- Telegram/SourceFiles/mainwidget.h | 1 + 3 files changed, 136 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index e8171167a..9e43687b7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -9,17 +9,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_send_progress.h" #include "base/event_filter.h" +#include "base/unixtime.h" #include "boxes/confirm_box.h" #include "core/application.h" #include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_session.h" #include "history/history_item_components.h" #include "history/view/controls/history_view_voice_record_button.h" #include "lang/lang_keys.h" +#include "main/main_session.h" +#include "mainwidget.h" // MainWidget::stopAndClosePlayer #include "mainwindow.h" #include "media/audio/media_audio.h" #include "media/audio/media_audio_capture.h" +#include "media/player/media_player_button.h" +#include "media/player/media_player_instance.h" #include "styles/style_chat.h" #include "styles/style_layers.h" +#include "styles/style_media_player.h" #include "ui/controls/send_button.h" #include "ui/text/format_values.h" #include "window/window_session_controller.h" @@ -73,12 +81,29 @@ enum class FilterType { return voiceData; } +[[nodiscard]] not_null DummyDocument( + not_null owner) { + return owner->document( + rand_value(), + uint64(0), + QByteArray(), + base::unixtime::now(), + QVector(), + QString(), + QByteArray(), + ImageWithLocation(), + ImageWithLocation(), + owner->session().mainDcId(), + int32(0)); +} + } // namespace class ListenWrap final { public: ListenWrap( not_null parent, + not_null controller, ::Media::Capture::Result &&data, const style::font &font); @@ -90,19 +115,29 @@ public: private: void init(); + void initPlayButton(); + + int computeTopMargin(int height) const; not_null _parent; + const not_null _controller; + const not_null _document; const std::unique_ptr _voiceData; + const std::shared_ptr _mediaView; const std::unique_ptr<::Media::Capture::Result> _data; const style::IconButton &_stDelete; const base::unique_qptr _delete; const style::font &_durationFont; const QString _duration; const int _durationWidth; + const style::MediaPlayerButton &_playPauseSt; + const base::unique_qptr _playPauseButton; QRect _waveformBgRect; - QRect _durationRect; + QRect _waveformBgFinalCenterRect; + + ::Media::Player::PlayButtonLayout _playPause; rpl::variable _showProgress = 0.; @@ -112,17 +147,24 @@ private: ListenWrap::ListenWrap( not_null parent, + not_null controller, ::Media::Capture::Result &&data, const style::font &font) : _parent(parent) +, _controller(controller) +, _document(DummyDocument(&_controller->session().data())) , _voiceData(ProcessCaptureResult(data)) +, _mediaView(_document->createMediaView()) , _data(std::make_unique<::Media::Capture::Result>(std::move(data))) , _stDelete(st::historyRecordDelete) , _delete(base::make_unique_q(parent, _stDelete)) , _durationFont(font) , _duration(Ui::FormatDurationText( float64(_data->samples) / ::Media::Player::kDefaultFrequency)) -, _durationWidth(_durationFont->width(_duration)) { +, _durationWidth(_durationFont->width(_duration)) +, _playPauseSt(st::mediaPlayerButton) +, _playPauseButton(base::make_unique_q(parent)) +, _playPause(_playPauseSt, [=] { _playPauseButton->update(); }) { init(); } @@ -135,9 +177,20 @@ void ListenWrap::init() { _parent->sizeValue( ) | rpl::start_with_next([=](QSize size) { - const auto left = _stDelete.width + st::historyRecordWaveformLeftSkip; _waveformBgRect = QRect({ 0, 0 }, size) .marginsRemoved(st::historyRecordWaveformBgMargins); + { + const auto m = _stDelete.width + _waveformBgRect.height() / 2; + _waveformBgFinalCenterRect = _waveformBgRect.marginsRemoved( + style::margins(m, 0, m, 0)); + } + { + const auto &play = _playPauseSt.playOuter; + const auto &final = _waveformBgFinalCenterRect; + _playPauseButton->moveToLeft( + final.x() - (final.height() - play.width()) / 2, + final.y()); + } }, _lifetime); _parent->paintRequest( @@ -200,12 +253,13 @@ void ListenWrap::init() { // Waveform paint. { - const auto top = - (bgCenterRect.height() - st::msgWaveformMax) / 2; + const auto &play = _playPauseSt.playOuter; + const auto top = computeTopMargin(st::msgWaveformMax); + const auto left = play.width() / 2 + halfHeight; const auto right = st::historyRecordWaveformLeftSkip + _durationWidth; const auto rect = bgCenterRect.marginsRemoved( - style::margins(halfHeight, top, right, top)); + style::margins(left, top, right, top)); if (rect.width() > 0) { p.translate(rect.topLeft()); HistoryDocumentVoice::PaintWaveform( @@ -220,6 +274,73 @@ void ListenWrap::init() { } } }, _lifetime); + + initPlayButton(); +} + +void ListenWrap::initPlayButton() { + using namespace ::Media::Player; + using State = TrackState; + + _mediaView->setBytes(_data->bytes); + _document->type = VoiceDocument; + + const auto &play = _playPauseSt.playOuter; + const auto &width = _waveformBgFinalCenterRect.height(); + _playPauseButton->resize(width, width); + _playPauseButton->show(); + + _playPauseButton->paintRequest( + ) | rpl::start_with_next([=](const QRect &clip) { + Painter p(_playPauseButton); + + const auto progress = _showProgress.current(); + p.translate(width / 2, width / 2); + if (progress < 1.) { + p.scale(progress, progress); + } + p.translate(-play.width() / 2, -play.height() / 2); + _playPause.paint(p, st::historyRecordVoiceFgActiveIcon); + }, _playPauseButton->lifetime()); + + _playPauseButton->setClickedCallback([=] { + instance()->playPause({ _document, FullMsgId() }); + }); + + const auto showPause = _lifetime.make_state>(false); + showPause->changes( + ) | rpl::start_with_next([=](bool pause) { + _playPause.setState(pause + ? PlayButtonLayout::State::Pause + : PlayButtonLayout::State::Play); + }, _lifetime); + + instance()->updatedNotifier( + ) | rpl::start_with_next([=](const State &state) { + if (state.id.audio() == _document) { + *showPause = ShowPauseIcon(state.state); + } else if (showPause->current()) { + *showPause = false; + } + }, _lifetime); + + instance()->stops( + AudioMsgId::Type::Voice + ) | rpl::start_with_next([=] { + *showPause = false; + }, _lifetime); + + const auto weak = Ui::MakeWeak(_controller->content().get()); + _lifetime.add([=] { + const auto voiceState = instance()->getState(AudioMsgId::Type::Voice); + if (weak && voiceState.id && (voiceState.id.audio() == _document)) { + weak->stopAndClosePlayer(); + } + }); +} + +int ListenWrap::computeTopMargin(int height) const { + return (_waveformBgRect.height() - height) / 2; } void ListenWrap::requestPaintProgress(float64 progress) { @@ -906,6 +1027,7 @@ void VoiceRecordBar::stopRecording(StopType type) { } else if (type == StopType::Listen) { _listen = std::make_unique( this, + _controller, std::move(data), _cancelFont); _listenChanges.fire({}); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d17c8af27..c3b2a27f9 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -483,7 +483,7 @@ void MainWidget::floatPlayerClosed(FullMsgId itemId) { const auto voiceData = Media::Player::instance()->current( AudioMsgId::Type::Voice); if (voiceData.contextId() == itemId) { - _player->entity()->stopAndClose(); + stopAndClosePlayer(); } } } @@ -882,6 +882,12 @@ void MainWidget::closeBothPlayers() { Shortcuts::ToggleMediaShortcuts(false); } +void MainWidget::stopAndClosePlayer() { + if (_player) { + _player->entity()->stopAndClose(); + } +} + void MainWidget::createPlayer() { if (!_player) { _player.create( diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index cca639af3..67f62919b 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -220,6 +220,7 @@ public: using FloatDelegate::floatPlayerAreaUpdated; void closeBothPlayers(); + void stopAndClosePlayer(); public slots: void inlineResultLoadProgress(FileLoader *loader); From 26686197582e32a7ee6b01db1137ac0d24e9d3a4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 10 Nov 2020 21:06:22 +0300 Subject: [PATCH 106/370] Added ability to seek recorded voice data. --- .../history_view_voice_record_bar.cpp | 155 ++++++++++++++++-- 1 file changed, 142 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 9e43687b7..5a51b66d5 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -39,6 +39,7 @@ namespace { using SendActionUpdate = VoiceRecordBar::SendActionUpdate; using VoiceToSend = VoiceRecordBar::VoiceToSend; +constexpr auto kAudioVoiceUpdateView = crl::time(200); constexpr auto kLockDelay = crl::time(100); constexpr auto kRecordingUpdateDelta = crl::time(100); constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes @@ -55,6 +56,10 @@ enum class FilterType { Cancel, }; +[[nodiscard]] auto Progress(int low, int high) { + return std::clamp(float64(low) / high, 0., 1.); +} + [[nodiscard]] auto Duration(int samples) { return samples / ::Media::Player::kDefaultFrequency; } @@ -116,6 +121,10 @@ public: private: void init(); void initPlayButton(); + void initPlayProgress(); + + bool isInPlayer(const ::Media::Player::TrackState &state) const; + bool isInPlayer() const; int computeTopMargin(int height) const; @@ -136,9 +145,12 @@ private: QRect _waveformBgRect; QRect _waveformBgFinalCenterRect; + QRect _waveformFgRect; ::Media::Player::PlayButtonLayout _playPause; + anim::value _playProgress; + rpl::variable _showProgress = 0.; rpl::lifetime _lifetime; @@ -253,13 +265,19 @@ void ListenWrap::init() { // Waveform paint. { - const auto &play = _playPauseSt.playOuter; - const auto top = computeTopMargin(st::msgWaveformMax); - const auto left = play.width() / 2 + halfHeight; - const auto right = st::historyRecordWaveformLeftSkip - + _durationWidth; - const auto rect = bgCenterRect.marginsRemoved( - style::margins(left, top, right, top)); + const auto computeRect = [&] { + const auto &play = _playPauseSt.playOuter; + const auto top = computeTopMargin(st::msgWaveformMax); + const auto left = play.width() / 2 + halfHeight; + const auto right = st::historyRecordWaveformLeftSkip + + _durationWidth; + _waveformFgRect = bgCenterRect.marginsRemoved( + style::margins(left, top, right, top)); + return _waveformFgRect; + }; + const auto rect = (progress == 1.) + ? _waveformFgRect + : computeRect(); if (rect.width() > 0) { p.translate(rect.topLeft()); HistoryDocumentVoice::PaintWaveform( @@ -268,7 +286,7 @@ void ListenWrap::init() { rect.width(), false, false, - 0.); + _playProgress.current()); p.resetTransform(); } } @@ -276,6 +294,7 @@ void ListenWrap::init() { }, _lifetime); initPlayButton(); + initPlayProgress(); } void ListenWrap::initPlayButton() { @@ -317,7 +336,7 @@ void ListenWrap::initPlayButton() { instance()->updatedNotifier( ) | rpl::start_with_next([=](const State &state) { - if (state.id.audio() == _document) { + if (isInPlayer(state)) { *showPause = ShowPauseIcon(state.state); } else if (showPause->current()) { *showPause = false; @@ -332,13 +351,124 @@ void ListenWrap::initPlayButton() { const auto weak = Ui::MakeWeak(_controller->content().get()); _lifetime.add([=] { - const auto voiceState = instance()->getState(AudioMsgId::Type::Voice); - if (weak && voiceState.id && (voiceState.id.audio() == _document)) { + if (weak && isInPlayer()) { weak->stopAndClosePlayer(); } }); } +void ListenWrap::initPlayProgress() { + using namespace ::Media::Player; + using State = TrackState; + + const auto animation = _lifetime.make_state(); + const auto isPointer = _lifetime.make_state>(false); + const auto &voice = AudioMsgId::Type::Voice; + + const auto updateCursor = [=](const QPoint &p) { + *isPointer = isInPlayer() ? _waveformFgRect.contains(p) : false; + }; + + rpl::merge( + instance()->startsPlay(voice) | rpl::map_to(true), + instance()->stops(voice) | rpl::map_to(false) + ) | rpl::start_with_next([=](bool play) { + _parent->setMouseTracking(isInPlayer() && play); + updateCursor(_parent->mapFromGlobal(QCursor::pos())); + }, _lifetime); + + instance()->updatedNotifier( + ) | rpl::start_with_next([=](const State &state) { + if (!isInPlayer(state)) { + return; + } + const auto progress = state.length + ? Progress(state.position, state.length) + : 0.; + if (IsStopped(state.state)) { + _playProgress = anim::value(); + } else { + _playProgress.start(progress); + } + animation->start(); + }, _lifetime); + + auto animationCallback = [=](crl::time now) { + if (anim::Disabled()) { + now += kAudioVoiceUpdateView; + } + + const auto dt = (now - animation->started()) + / float64(kAudioVoiceUpdateView); + if (dt >= 1.) { + animation->stop(); + _playProgress.finish(); + } else { + _playProgress.update(std::min(dt, 1.), anim::linear); + } + _parent->update(); + return (dt < 1.); + }; + animation->init(std::move(animationCallback)); + + const auto isPressed = _lifetime.make_state(false); + + isPointer->changes( + ) | rpl::start_with_next([=](bool pointer) { + _parent->setCursor(pointer ? style::cur_pointer : style::cur_default); + }, _lifetime); + + _parent->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::MouseMove + || e->type() == QEvent::MouseButtonPress + || e->type() == QEvent::MouseButtonRelease); + }) | rpl::start_with_next([=](not_null e) { + if (!isInPlayer()) { + return; + } + + const auto type = e->type(); + const auto isMove = (type == QEvent::MouseMove); + const auto &pos = static_cast(e.get())->pos(); + if (*isPressed) { + *isPointer = true; + } else if (isMove) { + updateCursor(pos); + } + if (type == QEvent::MouseButtonPress) { + if (isPointer->current() && !(*isPressed)) { + instance()->startSeeking(voice); + *isPressed = true; + } + } else if (*isPressed) { + const auto &rect = _waveformFgRect; + const auto left = float64(pos.x() - rect.x()); + const auto progress = Progress(left, rect.width()); + const auto isRelease = (type == QEvent::MouseButtonRelease); + if (isRelease || isMove) { + _playProgress = anim::value(progress, progress); + _parent->update(); + if (isRelease) { + instance()->finishSeeking(voice, progress); + *isPressed = false; + } + } + } + + }, _lifetime); +} + + +bool ListenWrap::isInPlayer(const ::Media::Player::TrackState &state) const { + return (state.id && (state.id.audio() == _document)); +} + +bool ListenWrap::isInPlayer() const { + using Type = AudioMsgId::Type; + return isInPlayer(::Media::Player::instance()->getState(Type::Voice)); +} + int ListenWrap::computeTopMargin(int height) const { return (_waveformBgRect.height() - height) / 2; } @@ -1150,8 +1280,7 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { const auto localPos = mapFromGlobal(globalPos); const auto lower = _lock->height(); const auto higher = 0; - const auto progress = localPos.y() / (float64)(higher - lower); - _lock->requestPaintProgress(std::clamp(progress, 0., 1.)); + _lock->requestPaintProgress(Progress(localPos.y(), higher - lower)); } void VoiceRecordBar::orderControls() { From 6ecc446a8a1939ee2811c38c9806fb47dac4afc8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 10 Nov 2020 23:32:13 +0300 Subject: [PATCH 107/370] Added ability to schedule and send without sound recorded voice data. --- .../SourceFiles/history/history_widget.cpp | 6 ++ .../view/controls/compose_controls_common.h | 1 + .../history_view_voice_record_bar.cpp | 66 ++++++------------- .../controls/history_view_voice_record_bar.h | 3 + 4 files changed, 31 insertions(+), 45 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 23cf33d08..8db4b16a6 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -791,6 +791,7 @@ void HistoryWidget::initVoiceRecordBar() { auto action = Api::SendAction(_history); action.replyTo = replyToId(); + action.options = data.options; session().api().sendVoiceMessage( data.bytes, data.waveform, @@ -3064,6 +3065,11 @@ void HistoryWidget::send(Api::SendOptions options) { return; } + if (_voiceRecordBar && _voiceRecordBar->isListenState()) { + _voiceRecordBar->requestToSendWithOptions(options); + return; + } + const auto webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) diff --git a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h index d34621c1e..355070749 100644 --- a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h +++ b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h @@ -25,6 +25,7 @@ struct VoiceToSend { QByteArray bytes; VoiceWaveform waveform; int duration = 0; + Api::SendOptions options; }; struct SendActionUpdate { Api::SendProgressType type = Api::SendProgressType(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 5a51b66d5..7bd43d9d7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -958,7 +958,7 @@ void VoiceRecordBar::init() { }) | rpl::start_with_next([=] { _listen->stopRequests( ) | rpl::take(1) | rpl::start_with_next([=] { - visibilityAnimate(false, [=] { hide(); }); + hideAnimated(); }, _listen->lifetime()); _listen->lifetime().add([=] { _listenChanges.fire({}); }); @@ -1218,6 +1218,18 @@ void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { style::al_center); } +void VoiceRecordBar::requestToSendWithOptions(Api::SendOptions options) { + if (isListenState()) { + const auto data = _listen->data(); + _sendVoiceRequests.fire({ + data->bytes, + data->waveform, + Duration(data->samples), + options }); + hideAnimated(); + } +} + rpl::producer VoiceRecordBar::sendActionUpdates() const { return _sendActionUpdates.events(); } @@ -1230,6 +1242,10 @@ bool VoiceRecordBar::isRecording() const { return _recording.current(); } +void VoiceRecordBar::hideAnimated() { + visibilityAnimate(false, [=] { hide(); }); +} + void VoiceRecordBar::finishAnimating() { _showAnimation.stop(); } @@ -1360,50 +1376,10 @@ void VoiceRecordBar::installClickOutsideFilter() { } void VoiceRecordBar::installListenStateFilter() { - const auto close = [=](bool send) { - auto callback = [=] { - if (send) { - const auto data = _listen->data(); - _sendVoiceRequests.fire({ - data->bytes, - data->waveform, - Duration(data->samples) }); - } - hide(); - }; - visibilityAnimate(false, std::move(callback)); - }; - - const auto isSend = [=] { - return _send->type() == Ui::SendButton::Type::Send - || _send->type() == Ui::SendButton::Type::Schedule; - }; - - auto mouseFilterCallback = [=](not_null e) { - using Result = base::EventFilterResult; - if (!isSend()) { - return Result::Continue; - } - switch(e->type()) { - case QEvent::MouseButtonRelease: { - close(true); - _send->clearState(); - return Result::Cancel; - } - default: return Result::Continue; - } - }; - - auto sendFilter = base::install_event_filter( - _send.get(), - std::move(mouseFilterCallback)); - - _listen->lifetime().make_state>( - std::move(sendFilter)); - auto keyFilterCallback = [=](not_null e) { using Result = base::EventFilterResult; - if (!isSend()) { + if (!(_send->type() == Ui::SendButton::Type::Send + || _send->type() == Ui::SendButton::Type::Schedule)) { return Result::Continue; } switch(e->type()) { @@ -1413,14 +1389,14 @@ void VoiceRecordBar::installListenStateFilter() { const auto isEnter = (key == Qt::Key_Enter || key == Qt::Key_Return); if (isEnter) { - close(true); + requestToSendWithOptions({}); return Result::Cancel; } if (isEsc) { if (_escFilter && _escFilter()) { return Result::Continue; } else { - close(false); + hideAnimated(); return Result::Cancel; } } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 0968e6e52..59f2d891f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -59,6 +59,8 @@ public: [[nodiscard]] rpl::producer lockShowStarts() const; [[nodiscard]] rpl::producer<> updateSendButtonTypeRequests() const; + void requestToSendWithOptions(Api::SendOptions options); + void setLockBottom(rpl::producer &&bottom); void setSendButtonGeometryValue(rpl::producer &&geometry); void setEscFilter(Fn &&callback); @@ -100,6 +102,7 @@ private: bool isTypeRecord() const; bool hasDuration() const; + void hideAnimated(); void finish(); void activeAnimate(bool active); From e0cc3791ff01edfe08b3875ff56a6f844f22715e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 11 Nov 2020 00:50:22 +0300 Subject: [PATCH 108/370] Added edit message prevent when there is unsent recorded voice data. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/history/history_widget.cpp | 4 ++++ .../history/view/controls/history_view_compose_controls.cpp | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d6c0eac12..086802bfa 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -184,6 +184,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_media_album_error" = "This file cannot be saved as a part of an album."; "lng_edit_media_invalid_file" = "Sorry, no way to use this file."; "lng_edit_caption_attach" = "Sorry, you can't attach a new media while you're editing your message."; +"lng_edit_caption_voice" = "Sorry, you can't edit your message while you're having an unsent voice message."; "lng_intro_about" = "Welcome to the official Telegram Desktop app.\nIt's fast and secure."; "lng_start_msgs" = "START MESSAGING"; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 8db4b16a6..9d9878ead 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -5523,6 +5523,10 @@ void HistoryWidget::editMessage(FullMsgId itemId) { } void HistoryWidget::editMessage(not_null item) { + if (_voiceRecordBar && _voiceRecordBar->isListenState()) { + Ui::show(Box(tr::lng_edit_caption_voice(tr::now))); + return; + } if (const auto media = item->media()) { if (media->allowsEditCaption()) { Ui::show(Box(controller(), item)); 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 f2961d993..61a308db1 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1748,6 +1748,11 @@ void ComposeControls::editMessage(not_null item) { Expects(_history != nullptr); Expects(draftKeyCurrent() != Data::DraftKey::None()); + if (_voiceRecordBar && _voiceRecordBar->isListenState()) { + Ui::show(Box(tr::lng_edit_caption_voice(tr::now))); + return; + } + if (!isEditingMessage()) { saveFieldToHistoryLocalDraft(); } From d2defabd4b4a3b84eefd6e538a5b12462c6bb0e6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 11 Nov 2020 16:50:00 +0300 Subject: [PATCH 109/370] Improved waveform display of recorded voice data. --- .../history_view_voice_record_bar.cpp | 130 +++++++++++++++--- Telegram/SourceFiles/ui/chat/chat.style | 4 +- 2 files changed, 117 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 7bd43d9d7..37baecfa9 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -46,6 +46,8 @@ constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes constexpr auto kMaxSamples = ::Media::Player::kDefaultFrequency * kAudioVoiceMaxLength; +constexpr auto kInactiveWaveformBarAlpha = int(255 * 0.6); + constexpr auto kPrecision = 10; constexpr auto kLockArcAngle = 15.; @@ -56,6 +58,10 @@ enum class FilterType { Cancel, }; +[[nodiscard]] auto InactiveColor(const QColor &c) { + return QColor(c.red(), c.green(), c.blue(), kInactiveWaveformBarAlpha); +} + [[nodiscard]] auto Progress(int low, int high) { return std::clamp(float64(low) / high, 0., 1.); } @@ -102,6 +108,91 @@ enum class FilterType { int32(0)); } +void PaintWaveform( + Painter &p, + not_null voiceData, + int availableWidth, + const QColor &active, + const QColor &inactive, + float64 progress) { + const auto wf = [&]() -> const VoiceWaveform* { + if (voiceData->waveform.isEmpty()) { + return nullptr; + } else if (voiceData->waveform.at(0) < 0) { + return nullptr; + } + return &voiceData->waveform; + }(); + + const auto samplesCount = wf + ? wf->size() + : ::Media::Player::kWaveformSamplesCount; + const auto activeWidth = std::round(availableWidth * progress); + + const auto &barWidth = st::historyRecordWaveformBar; + const auto barFullWidth = barWidth + st::msgWaveformSkip; + const auto totalBarsCountF = (float)availableWidth / barFullWidth; + const auto totalBarsCount = int(totalBarsCountF); + const auto samplesPerBar = samplesCount / totalBarsCountF; + const auto barNormValue = (wf ? voiceData->wavemax : 0) + 1; + const auto maxDelta = st::msgWaveformMax - st::msgWaveformMin; + const auto &bottom = st::msgWaveformMax; + + p.setPen(Qt::NoPen); + int barNum = 0; + const auto paintBar = [&](const auto &barValue) { + const auto barHeight = st::msgWaveformMin + barValue; + const auto barTop = (bottom - barHeight) / 2.; + const auto barLeft = barNum * barFullWidth; + const auto rect = [&](const auto &l, const auto &w) { + return QRectF(l, barTop, w, barHeight); + }; + + if ((barLeft < activeWidth) && (barLeft + barWidth > activeWidth)) { + const auto leftWidth = activeWidth - barLeft; + const auto rightWidth = barWidth - leftWidth; + p.fillRect(rect(barLeft, leftWidth), active); + p.fillRect(rect(activeWidth, rightWidth), inactive); + } else { + const auto &color = (barLeft >= activeWidth) ? inactive : active; + p.fillRect(rect(barLeft, barWidth), color); + } + barNum++; + }; + + auto barCounter = 0.; + auto nextBarNum = 0; + + auto sum = 0; + auto maxValue = 0; + + for (auto i = 0; i < samplesCount; i++) { + const auto value = wf ? wf->at(i) : 0; + if (i != nextBarNum) { + maxValue = std::max(maxValue, value); + sum += totalBarsCount; + continue; + } + + // Compute height. + sum += totalBarsCount - samplesCount; + const auto isSumSmaller = (sum < (totalBarsCount + 1) / 2); + if (isSumSmaller) { + maxValue = std::max(maxValue, value); + } + const auto barValue = ((maxValue * maxDelta) + (barNormValue / 2)) + / barNormValue; + maxValue = isSumSmaller ? 0 : value; + + const auto lastBarNum = nextBarNum; + while (lastBarNum == nextBarNum) { + barCounter += samplesPerBar; + nextBarNum = (int)barCounter; + paintBar(barValue); + } + } +} + } // namespace class ListenWrap final { @@ -127,6 +218,7 @@ private: bool isInPlayer() const; int computeTopMargin(int height) const; + QRect computeWaveformRect(const QRect ¢erRect) const; not_null _parent; @@ -142,6 +234,8 @@ private: const int _durationWidth; const style::MediaPlayerButton &_playPauseSt; const base::unique_qptr _playPauseButton; + const QColor _activeWaveformBar; + const QColor _inactiveWaveformBar; QRect _waveformBgRect; QRect _waveformBgFinalCenterRect; @@ -176,6 +270,8 @@ ListenWrap::ListenWrap( , _durationWidth(_durationFont->width(_duration)) , _playPauseSt(st::mediaPlayerButton) , _playPauseButton(base::make_unique_q(parent)) +, _activeWaveformBar(st::historyRecordVoiceFgActiveIcon->c) +, _inactiveWaveformBar(InactiveColor(_activeWaveformBar)) , _playPause(_playPauseSt, [=] { _playPauseButton->update(); }) { init(); } @@ -203,6 +299,7 @@ void ListenWrap::init() { final.x() - (final.height() - play.width()) / 2, final.y()); } + _waveformFgRect = computeWaveformRect(_waveformBgFinalCenterRect); }, _lifetime); _parent->paintRequest( @@ -265,27 +362,17 @@ void ListenWrap::init() { // Waveform paint. { - const auto computeRect = [&] { - const auto &play = _playPauseSt.playOuter; - const auto top = computeTopMargin(st::msgWaveformMax); - const auto left = play.width() / 2 + halfHeight; - const auto right = st::historyRecordWaveformLeftSkip - + _durationWidth; - _waveformFgRect = bgCenterRect.marginsRemoved( - style::margins(left, top, right, top)); - return _waveformFgRect; - }; const auto rect = (progress == 1.) ? _waveformFgRect - : computeRect(); + : computeWaveformRect(bgCenterRect); if (rect.width() > 0) { p.translate(rect.topLeft()); - HistoryDocumentVoice::PaintWaveform( + PaintWaveform( p, _voiceData.get(), rect.width(), - false, - false, + _activeWaveformBar, + _inactiveWaveformBar, _playProgress.current()); p.resetTransform(); } @@ -406,7 +493,7 @@ void ListenWrap::initPlayProgress() { } else { _playProgress.update(std::min(dt, 1.), anim::linear); } - _parent->update(); + _parent->update(_waveformFgRect); return (dt < 1.); }; animation->init(std::move(animationCallback)); @@ -448,7 +535,7 @@ void ListenWrap::initPlayProgress() { const auto isRelease = (type == QEvent::MouseButtonRelease); if (isRelease || isMove) { _playProgress = anim::value(progress, progress); - _parent->update(); + _parent->update(_waveformFgRect); if (isRelease) { instance()->finishSeeking(voice, progress); *isPressed = false; @@ -469,6 +556,14 @@ bool ListenWrap::isInPlayer() const { return isInPlayer(::Media::Player::instance()->getState(Type::Voice)); } +QRect ListenWrap::computeWaveformRect(const QRect ¢erRect) const { + const auto top = computeTopMargin(st::msgWaveformMax); + const auto left = (_playPauseSt.playOuter.width() + centerRect.height()) + / 2; + const auto right = st::historyRecordWaveformRightSkip + _durationWidth; + return centerRect.marginsRemoved(style::margins(left, top, right, top)); +} + int ListenWrap::computeTopMargin(int height) const { return (_waveformBgRect.height() - height) / 2; } @@ -892,6 +987,9 @@ void VoiceRecordBar::init() { _listen->requestPaintProgress(value); _level->requestPaintProgress(to - value); update(); + if (to == value) { + _recordingLifetime.destroy(); + } }; _showListenAnimation.start(std::move(callback), 0., to, duration); }, lifetime()); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 277722678..0b9f67f03 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -385,9 +385,11 @@ historyRecordDelete: IconButton(historyAttach) { icon: icon {{ "info_media_delete", historyComposeIconFg }}; iconOver: icon {{ "info_media_delete", historyComposeIconFgOver }}; } -historyRecordWaveformLeftSkip: 5px; +historyRecordWaveformRightSkip: 10px; historyRecordWaveformBgMargins: margins(5px, 7px, 5px, 7px); +historyRecordWaveformBar: 3px; + historyRecordLockPosition: point(7px, 35px); historySilentToggle: IconButton(historyBotKeyboardShow) { From 7dac42b523113a2ff51857ecdd083cd304956e26 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 11 Nov 2020 20:27:02 +0300 Subject: [PATCH 110/370] Added ability to play/pause recorded voice data with Space key. --- .../controls/history_view_voice_record_bar.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 37baecfa9..bdd887c41 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -207,6 +207,8 @@ public: rpl::producer<> stopRequests() const; ::Media::Capture::Result *data() const; + void playPause(); + rpl::lifetime &lifetime(); private: @@ -556,6 +558,10 @@ bool ListenWrap::isInPlayer() const { return isInPlayer(::Media::Player::instance()->getState(Type::Voice)); } +void ListenWrap::playPause() { + _playPauseButton->clicked(Qt::NoModifier, Qt::LeftButton); +} + QRect ListenWrap::computeWaveformRect(const QRect ¢erRect) const { const auto top = computeTopMargin(st::msgWaveformMax); const auto left = (_playPauseSt.playOuter.width() + centerRect.height()) @@ -1482,10 +1488,16 @@ void VoiceRecordBar::installListenStateFilter() { } switch(e->type()) { case QEvent::KeyPress: { - const auto key = static_cast(e.get())->key(); + const auto keyEvent = static_cast(e.get()); + const auto key = keyEvent->key(); + const auto isSpace = (key == Qt::Key_Space); const auto isEsc = (key == Qt::Key_Escape); const auto isEnter = (key == Qt::Key_Enter || key == Qt::Key_Return); + if (isSpace && !keyEvent->isAutoRepeat() && _listen) { + _listen->playPause(); + return Result::Cancel; + } if (isEnter) { requestToSendWithOptions({}); return Result::Cancel; From fb2924f2d6a1697e2d69f36e97fe1de44ae48de9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 11 Nov 2020 23:13:57 +0300 Subject: [PATCH 111/370] Fixed size and position of lock/stop button at end of animation. --- .../controls/history_view_voice_record_bar.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index bdd887c41..ff7531982 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -623,16 +623,15 @@ RecordLock::RecordLock(not_null parent) Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin) { - resize( - st::historyRecordLockTopShadow.width(), - st::historyRecordLockSize.height()); - // resize(st::historyRecordLockSize); init(); } void RecordLock::init() { shownValue( ) | rpl::start_with_next([=](bool shown) { + resize( + st::historyRecordLockTopShadow.width(), + st::historyRecordLockSize.height()); if (!shown) { setCursor(style::cur_default); setAttribute(Qt::WA_TransparentForMouseEvents, true); @@ -667,6 +666,10 @@ void RecordLock::init() { if (value == to) { setCursor(style::cur_pointer); setAttribute(Qt::WA_TransparentForMouseEvents, false); + + resize( + st::historyRecordLockTopShadow.width(), + st::historyRecordLockTopShadow.width()); } }; _lockAnimation.start(std::move(callback), from, to, duration); @@ -1115,9 +1118,10 @@ void VoiceRecordBar::setStartRecordingFilter(Fn &&callback) { } void VoiceRecordBar::setLockBottom(rpl::producer &&bottom) { - std::move( - bottom - ) | rpl::start_with_next([=](int value) { + rpl::combine( + std::move(bottom), + _lock->sizeValue() | rpl::map_to(true) // Dummy value. + ) | rpl::start_with_next([=](int value, bool dummy) { _lock->moveToLeft(_lock->x(), value - _lock->height()); }, lifetime()); } From eadd952e6658a1a4ad40e2d5be06c62feeb5c0e7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 13 Nov 2020 02:23:36 +0300 Subject: [PATCH 112/370] Added animation for recorded voice data delete. --- .../history_view_voice_record_bar.cpp | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index ff7531982..534651f1e 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -52,6 +52,8 @@ constexpr auto kPrecision = 10; constexpr auto kLockArcAngle = 15.; +constexpr auto kHideWaveformBgOffset = 50; + enum class FilterType { Continue, ShowBox, @@ -239,6 +241,8 @@ private: const QColor _activeWaveformBar; const QColor _inactiveWaveformBar; + bool _isShowAnimation = true; + QRect _waveformBgRect; QRect _waveformBgFinalCenterRect; QRect _waveformFgRect; @@ -315,19 +319,22 @@ void ListenWrap::init() { } { + const auto hideOffset = _isShowAnimation + ? 0 + : anim::interpolate(kHideWaveformBgOffset, 0, progress); const auto deleteIconLeft = _stDelete.iconPosition.x(); const auto bgRectRight = anim::interpolate( deleteIconLeft, _stDelete.width, - progress); + _isShowAnimation ? progress : 1.); const auto bgRectLeft = anim::interpolate( _parent->width() - deleteIconLeft - _waveformBgRect.height(), _stDelete.width, - progress); + _isShowAnimation ? progress : 1.); const auto bgRectMargins = style::margins( - bgRectLeft, + bgRectLeft - hideOffset, 0, - bgRectRight, + bgRectRight + hideOffset, 0); const auto bgRect = _waveformBgRect.marginsRemoved(bgRectMargins); @@ -341,11 +348,17 @@ void ListenWrap::init() { const auto bgCenterRect = bgRect.marginsRemoved( style::margins(halfHeight, 0, halfHeight, 0)); + if (!_isShowAnimation) { + p.setOpacity(progress); + } p.setPen(Qt::NoPen); p.setBrush(st::historyRecordCancelActive); - p.drawEllipse(bgLeftCircleRect); - p.drawEllipse(bgRightCircleRect); - p.fillRect(bgCenterRect, st::historyRecordCancelActive); + QPainterPath path; + path.setFillRule(Qt::WindingFill); + path.addEllipse(bgLeftCircleRect); + path.addEllipse(bgRightCircleRect); + path.addRect(bgCenterRect); + p.drawPath(path); // Duration paint. { @@ -575,6 +588,7 @@ int ListenWrap::computeTopMargin(int height) const { } void ListenWrap::requestPaintProgress(float64 progress) { + _isShowAnimation = (_showProgress.current() < progress); _showProgress = progress; } From c9314e5e5e5fd1352cc5b1ad5b41f7c45389de01 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 13 Nov 2020 05:13:49 +0300 Subject: [PATCH 113/370] Added ripple animation to stop recording voice button. --- .../history_view_voice_record_bar.cpp | 47 ++++++++++++++----- Telegram/SourceFiles/ui/chat/chat.style | 1 + 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 534651f1e..4d24c2900 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_media_player.h" #include "ui/controls/send_button.h" +#include "ui/effects/ripple_animation.h" #include "ui/text/format_values.h" #include "window/window_session_controller.h" @@ -604,15 +605,19 @@ rpl::lifetime &ListenWrap::lifetime() { return _lifetime; } -class RecordLock final : public Ui::RpWidget { +class RecordLock final : public Ui::RippleButton { public: RecordLock(not_null parent); void requestPaintProgress(float64 progress); - [[nodiscard]] rpl::producer<> stops() const; [[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] bool isLocked() const; + [[nodiscard]] bool isStopState() const; + +protected: + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; private: void init(); @@ -621,6 +626,7 @@ private: void setProgress(float64 progress); void startLockingAnimation(float64 to); + const QRect _rippleRect; const QPen _arcPen; Ui::Animations::Simple _lockAnimation; @@ -630,7 +636,13 @@ private: }; RecordLock::RecordLock(not_null parent) -: RpWidget(parent) +: RippleButton(parent, st::defaultRippleAnimation) +, _rippleRect(QRect( + 0, + 0, + st::historyRecordLockTopShadow.width(), + st::historyRecordLockTopShadow.width()) + .marginsRemoved(st::historyRecordLockRippleMargin)) , _arcPen( st::historyRecordLockIconFg, st::historyRecordLockIconLineWidth, @@ -755,6 +767,9 @@ void RecordLock::drawProgress(Painter &p) { arrow.paintInCenter(p, arrowRect); p.setOpacity(1.); } + if (isLocked()) { + paintRipple(p, _rippleRect.x(), _rippleRect.y()); + } { PainterHighQualityEnabler hq(p); const auto &size = st::historyRecordLockIconSize; @@ -833,18 +848,21 @@ bool RecordLock::isLocked() const { return _progress.current() == 1.; } +bool RecordLock::isStopState() const { + return isLocked() && (_lockAnimation.value(1.) == 1.); +} + rpl::producer<> RecordLock::locks() const { return _progress.changes( ) | rpl::filter([=] { return isLocked(); }) | rpl::to_empty; } -rpl::producer<> RecordLock::stops() const { - return events( - ) | rpl::filter([=](not_null e) { - return isLocked() - && (_lockAnimation.value(1.) == 1.) - && (e->type() == QEvent::MouseButtonRelease); - }) | rpl::to_empty; +QImage RecordLock::prepareRippleMask() const { + return Ui::RippleAnimation::ellipseMask(_rippleRect.size()); +} + +QPoint RecordLock::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()) - _rippleRect.topLeft(); } VoiceRecordBar::VoiceRecordBar( @@ -996,8 +1014,11 @@ void VoiceRecordBar::init() { _showLockAnimation.start(std::move(callback), from, to, duration); }, lifetime()); - _lock->stops( - ) | rpl::start_with_next([=] { + _lock->setClickedCallback([=] { + if (!_lock->isStopState()) { + return; + } + ::Media::Capture::instance()->startedChanges( ) | rpl::filter([=](auto capturing) { return !capturing && _listen; @@ -1018,7 +1039,7 @@ void VoiceRecordBar::init() { }, lifetime()); stopRecording(StopType::Listen); - }, lifetime()); + }); _lock->locks( ) | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 0b9f67f03..a44df79c1 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -380,6 +380,7 @@ historyRecordLockBodyShadow: icon {{ "voice_lock/record_lock_body_shadow", histo historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}; historyRecordLockMargin: margins(4px, 4px, 4px, 4px); historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }}; +historyRecordLockRippleMargin: margins(5px, 5px, 5px, 5px); historyRecordDelete: IconButton(historyAttach) { icon: icon {{ "info_media_delete", historyComposeIconFg }}; From 92298316ab32153deb2c0c78aee2931c93db3874 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 13 Nov 2020 05:37:27 +0300 Subject: [PATCH 114/370] Added transform animation from lock to stop icon for recording voice. --- .../history_view_voice_record_bar.cpp | 47 +++++++++++++++---- Telegram/SourceFiles/ui/chat/chat.style | 1 + 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 4d24c2900..74124766a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -61,6 +61,10 @@ enum class FilterType { Cancel, }; +inline float64 InterpolateF(int a, int b, float64 b_ratio) { + return a + float64(b - a) * b_ratio; +} + [[nodiscard]] auto InactiveColor(const QColor &c) { return QColor(c.red(), c.green(), c.blue(), kInactiveWaveformBarAlpha); } @@ -772,26 +776,51 @@ void RecordLock::drawProgress(Painter &p) { } { PainterHighQualityEnabler hq(p); + const auto lockToStopProgress = + _lockAnimation.value(isLocked() ? 1. : 0); + const auto &arcOffset = st::historyRecordLockIconLineSkip; const auto &size = st::historyRecordLockIconSize; + + const auto arcWidth = size.width() - arcOffset * 2; + const auto &arcHeight = st::historyRecordLockIconArcHeight; + const auto &blockHeight = st::historyRecordLockIconBottomHeight; - const auto blockRect = QRect( - 0, - size.height() - blockHeight, + + const auto blockRectWidth = InterpolateF( size.width(), - blockHeight); + st::historyRecordStopIconWidth, + lockToStopProgress); + const auto blockRectHeight = InterpolateF( + blockHeight, + st::historyRecordStopIconWidth, + lockToStopProgress); + const auto blockRectTop = InterpolateF( + size.height() - blockHeight, + std::round((size.height() - blockRectHeight) / 2.), + lockToStopProgress); + + const auto blockRect = QRectF( + (size.width() - blockRectWidth) / 2, + blockRectTop, + blockRectWidth, + blockRectHeight); const auto &lineHeight = st::historyRecordLockIconLineHeight; - const auto &offset = st::historyRecordLockIconLineSkip; p.setPen(Qt::NoPen); p.setBrush(st::historyRecordLockIconFg); p.translate( inner.x() + (inner.width() - size.width()) / 2, inner.y() + (originTop.height() * 2 - size.height()) / 2); - p.drawRoundedRect(blockRect, 2, 3); + { + const auto xRadius = anim::interpolate(2, 3, lockToStopProgress); + p.drawRoundedRect(blockRect, xRadius, 3); + } + const auto offsetTranslate = lockToStopProgress * + (lineHeight + arcHeight + _arcPen.width() * 2); p.translate( - size.width() - offset, - blockRect.y()); + size.width() - arcOffset, + blockRect.y() + offsetTranslate); if (progress < 1. && progress > 0.) { p.rotate(kLockArcAngle * progress); @@ -801,8 +830,6 @@ void RecordLock::drawProgress(Painter &p) { const auto rLine = QLineF(0, 0, 0, -lineHeight); p.drawLine(rLine); - const auto arcWidth = size.width() - offset * 2; - const auto &arcHeight = st::historyRecordLockIconArcHeight; p.drawArc( -arcWidth, rLine.dy() - arcHeight - _arcPen.width() + rLine.y1(), diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index a44df79c1..8f893aa1c 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -371,6 +371,7 @@ historyRecordLockIconLineHeight: 2px; historyRecordLockIconLineSkip: 3px; historyRecordLockIconLineWidth: 2px; historyRecordLockIconArcHeight: 4px; +historyRecordStopIconWidth: 12px; historyRecordLockTopShadow: icon {{ "voice_lock/record_lock_top_shadow", historyToDownShadow }}; historyRecordLockTop: icon {{ "voice_lock/record_lock_top", historyToDownBg }}; From 79cc4da626440588d8fe252d5f94e90dcabfa127 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 16 Nov 2020 16:21:36 +0300 Subject: [PATCH 115/370] Reduced block size for voice record lock. --- .../icons/voice_lock/record_lock_body.png | Bin 107 -> 1956 bytes .../icons/voice_lock/record_lock_body@2x.png | Bin 122 -> 1961 bytes .../icons/voice_lock/record_lock_body@3x.png | Bin 176 -> 1972 bytes .../voice_lock/record_lock_body_shadow.png | Bin 122 -> 1983 bytes .../voice_lock/record_lock_body_shadow@2x.png | Bin 199 -> 2008 bytes .../voice_lock/record_lock_body_shadow@3x.png | Bin 341 -> 2016 bytes .../icons/voice_lock/record_lock_bottom.png | Bin 369 -> 2393 bytes .../voice_lock/record_lock_bottom@2x.png | Bin 657 -> 2693 bytes .../voice_lock/record_lock_bottom@3x.png | Bin 1012 -> 3014 bytes .../voice_lock/record_lock_bottom_shadow.png | Bin 576 -> 2774 bytes .../record_lock_bottom_shadow@2x.png | Bin 2265 -> 3510 bytes .../record_lock_bottom_shadow@3x.png | Bin 4705 -> 4285 bytes .../icons/voice_lock/record_lock_top.png | Bin 359 -> 2386 bytes .../icons/voice_lock/record_lock_top@2x.png | Bin 661 -> 2685 bytes .../icons/voice_lock/record_lock_top@3x.png | Bin 1010 -> 2995 bytes .../voice_lock/record_lock_top_shadow.png | Bin 557 -> 2629 bytes .../voice_lock/record_lock_top_shadow@2x.png | Bin 2244 -> 3380 bytes .../voice_lock/record_lock_top_shadow@3x.png | Bin 4604 -> 4139 bytes .../history_view_voice_record_bar.cpp | 13 ++++++++----- Telegram/SourceFiles/ui/chat/chat.style | 6 +++--- 20 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Telegram/Resources/icons/voice_lock/record_lock_body.png b/Telegram/Resources/icons/voice_lock/record_lock_body.png index 6204b88ce44654acc312fcb73389f27b3dda9807..e6e25e290348d8a4e09042f3acb10ef1b5aa85ec 100644 GIT binary patch literal 1956 zcmbVNTWl0n7@lRJ6dJUZ$Ss=uk*&@_f{iw``MzL%~Fb@mL=k`6!~o<*Mi0c=LlFSQD(BS(&<`ZwI1ygGS}YB>7!l|}D)JnQM9 z-3NDfcpLs0dbiXPd*k~>r>|^Wb9alct;6%}noR)iwfLBS?7+gQ>8)>n^6|)>MDv5?%@-PfUPAxb6h9!Jy8e|XxcG|`o=>-? zIyXK5;rlDoEAT}2c-yMJzP^s|#J!i!bu8Sm;^g@Biq7Kn+WRY?y|;4hiSY^QyRIJ> zH;gvFb+-97wdb>2>RCWt@gDwNYJTqGF(uoqG>+}+OE+KuRxh7!y>_bg^5or1$4jN3 zp6BFbayx=`FvflgUMTwfcy6vyka&Q+hZiyLx-KMCf7CBbO==_}?DgIG}^xxNh-5pJ-x0d6M{T;AYIJobXzV)m@=8mYR`Mu~(|G$Ww$F za2!+SxI&@eEBJkemE}lpNE{D109b-x?P1-4MOL?4YYZ`LBTF+K&CqF=5h})@6Jdzb zN(yr9SFvK9Arm3nU||3Qgv6ktX2crXHcKOi z66R*Jx~f#d2tpb!kZtzYgYIj-5H&coHiv>orO z-sLp9+(0r!?!kyK$n6NMGS%1GJbpY;RC+YEyuVSaqeWQl!u#Jx$@eqDOQ7%(dgdR zXEJ%HPj##lYKzQl@nG>6GVfO>Vw=*9KOmJlG&3{fztzM8FgjH_zlv&UY4AQDyS>Hz OS4edC#6IfkAN?Cn<#>1i literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^IzY_C!2~4NIII)^Qk(@Ik;M!QddeWoSh3W;3@9k; z>EaktaVt3?A>qfLpP&CTGC8c4RCw_B_jl&k91IF?er3L9C}069WbkzLb6Mw<&;$Ve CE*@L} diff --git a/Telegram/Resources/icons/voice_lock/record_lock_body@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_body@2x.png index cf955e2914fe6d14b625be80f406dff2084a863f..2ac328fef0867cf5ecc278fcbf00a680666fe12b 100644 GIT binary patch literal 1961 zcmbVNTWl0n7@nmUO0imOYzrDPPJ{O1?A-P`VcXK(N>|)cmQAP)!Ja*Hb~|==W|*0F zwfW-$BeSw&$sb}_PsRfCX%*>f{ zzVrRxe>wl0=;_{BRk@{-Ac(49S0IevJ@_uIScjhz3Fc{nustXH{XIJ*HHyga&JMDX z^Ljl^1krc{_V@Fec~D1B_V+JbYiX*~Q{m&sPlo&3Zp>btT_nm+d^KNo+GcAdhY!4d zpt+>{_u*5SO@UW`C_6XbzU9_S_Qq!0w{MKzY~1#l=UgW94Y6?D&NLBk3D<2GJKw%I z_e&Kyyr*ki7(ZOPu(WIJ!#;(KS)U_{duJxZ!*gn}6ZfU>A zd+yXlYSB#mY-4?2UthCl=JxaNH<#|KoxHGA+mc?|cBk&C+jZL}F3b?$cl@}%{7Chi z=c~UXUiBZA3;i#DtNd4(Ske4r7@OmN%N{*AXyPJ))|KLZW&K zl1abcN{okW3;`%BmLE+kcA)?;k&oh8o~1dC<{S~uDe{6S@GVvj*&H;aN^)#u71R~s zFwQLsuKS?4ZX{rW{{%}=jH!AOV6WvQh$BW*;uKkorRY}^s*Vj~b;8Q|AZ{kQ8j}-v zVRcijtsH z{(uUHtTM3n=T3yALd@}xI;Q|70Anm2bs~=Dy$&Z01Qya9E5sz1Gb(xAjsn|mUB-tJ zBv!IoswB+FdpxWYK}fS6E=oJNm_SF}$cd#P5M_ZIdU(Mq{cJUfU9y4mJd!_5d(pxC zC?S*C0ulkV4u+3{Rz)C*%1_H{#N&}H=l7=u5GH*{k>so!YRpUl9qowY*cGz#@JeJJ ztr_kZip2M7)TC5~{vWhl!d#_CMD^f0S4mt9-Y#R_oT8m(cm zC*zM|S4Rq_)^X3?Go?wm-){{Dc0}<1Q)*wgyu2K*KRtTp%%-`mcYpg}Y;1DOHvQgX VW!>){{nPq21UtI}A9nN~`5UKyeO3Sf literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^5kSns!2~2*cjvzcQk(@Ik;M!QddeWoSh3W;3@E7M z>EaktaVt52q1k`F-Cy~M4?aIX&vW9ze-1Vc#*aYu#Ri5(^Za`~AJ#B39NYD}#WRED QH&7pgr>mdKI;Vst0GTQ$zyJUM diff --git a/Telegram/Resources/icons/voice_lock/record_lock_body@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_body@3x.png index 1da493f530811593190d65746028aca617b5d1e4..0f12933eb4605a51d271538ecfd292cf534f62d2 100644 GIT binary patch literal 1972 zcmbVNTTC2P7@n1KDGiDx0wu|2d^wQ{qY0^@aiV5_h(e%NXK2+KV6CaFC6Jzkq-lRyeagv!i zbIy0Z|NAfJpOd{kyDQ4;$`J&q2nPIN_)fvQr0g;HJnmlhAc*TZ)#vN&Q1mE3!@E1t zMoJL)CIo4`CJzkIhJDBc+Xn^~ms^_3&1Cq*iBsW$_G|N3=9iF?liw~BzvFVXp`!<1 zKiFJUa)0#g^ppNqe=I(`u&eIYi|)o|*LQDB+-z+4oIjgR-#`|xx``&_E#|80eAj#D zFaKJBj=mh2WF`-9SzOt3{F9F-ZU!swRaIUnySo+rb6eoBIKBL}i!1%|wCmG7;g((Z zX#P%3q#De{rW$Me`}>>ux!ccw*t}(b^_kg~>Xy_>!=0L^Zr3!No}EK}==`a)M@j%q@2Fa-Uz)DJJb&xbR67036P-)5 zXVOyo@uH&D$g$~j&lVk>Xe;{aqJYd_+56WHkvjkbx77%|Vj;-3D(7|~=iaYHkm6M} z(r5RDUJ_+J;g+0mx>E@Qq7h_gd&-dHVPKY(E>KZi$m7GZpkVE7=+2Y~2@=4T&{QI>Sz^kIWq3t6cBTmo%|Ps7FP6<;U#J)L=_Wu~ zH;2oVheTTiH^q@2mU;oDNvf3~S%Tznk`hT?q$xCaVbGeX42ofYcg`04@?t}_ZHNSs zOeWn)#;uz%0@j8=kTgNlI7Hyqm}W~UT(j!)41QqArfS%#uAvU26xBy;F9wxnT}T)O zTFuIZ2?mTvNd`f=NynuOP>~BbW5kSSj4Lt$;vfMu+k#lCfHj76TepVvhfo)mA20wz z8wwR{Y^WuXD44M9u2JYlE+8ACt;m=Gh%m795mN?TqflnOKCbPI)2}E7FX}a4k*_$t*OaE9&6bIw%*R z9Gu8L2!(Z{NVfE!U_}-Obu%GBuhoPU1B9W)FtiX$(Wl3C6B>rjk*i1})P>sWd z^-U$CHWU(rnq^Cx41#_y1_R_)RYjyYz_toN!6}*LaSy}NxWEkxI41#GiAo&BD0F_m zUzbOmGH~{1PlTe&kmDb9Bu}%XAk#SO5Rt8(D9$sqjFU9W2o%pUJV)i(_L?d@lu~>x ztD{PRj8TeZJOZqA0HQ4J0VKeAQdV$*Vpu`o1eWvgPU&Z=NerkK%=1|8Fzo|}a-+D4 zW(r7@WanUbG1;jIpkTRab%S_3lI7g~;$a?G z(&*;;A1$9*Ve9n&yK?`x{#=0MAxVn?*l!8U@rZCbQ#L)s`pjk4)^xEv9JWShSm?>{ zqtMlX2B}Thv%hGH-iQ6ZDd_Kr1iq1#d1Q5UHC9_ZgoZ|bdAmRI ztR?IR64DQ_%(ttR;+6W#j+S3j3^P6xV`4 z`x@Rj-Y`SFdgh9BoF^_gM}7-5Jbms!GS!e=Jhr_*QIB|jP2+6S^pU2C@$2Ug7K=Zw z=$bxpxCn}mJDqnEBS(%s<9vN@yYtI)0b+cz=dU%QcRMz2i5_{`B#32=_T?asy}z0u z>h3CuKC3VGq6oFD3)ptL3RxYa31WRmK?iUUS!5EW6g5cwa`rq$DpHWz=83T}J%rLq zWS4=q?%I-oy9QxEqB`2i^#u_VWRV5PLN=qCVj)PCctw0~zh)@11hEE#RJneAv0gHy z8Hn__yfowlmh=T&oR<|m-18*Qaz2LjFszqmIg#~?JV#bO6pm&{vKSA)P>F?~f>heF zbdh25`MfLdc4S@i=%(V6Z>e})> z0kE~PSS`kVwPdrk5T@0g!)a6uxi8vG4C{!ABU2kPAnMLxnN4;!x)?GLurwo~X_;!F zdaIJjP{=NfpKOi+sHk=xEpv9EFtAXN;#r=hIgaLp1m_WXx5&BM>>9FVXiSq7d3YYw zo8T}GSJ`(D6!(n;EbyOT35v32WC6}v$$}JObTvhhH7~`GmeC9x7^`#7eILcc#L#3V zgBRuxm6F<6OpL0g1yqQl;UI+#aw&==2BZMXdnHIC9`dx{<9&31hZ5}(l2GhorhJp_z z$joK6RY{o9BY9Y_%(Ar4?FO{KB7si2{W1+?w~rN|8@YMOZv9d>i4n!bbsnx9rmbja z@AiB1ZrK diff --git a/Telegram/Resources/icons/voice_lock/record_lock_body_shadow@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_body_shadow@2x.png index 3b01ea846d4ba0f185d942490c5c4856bfa24cf5..f2173f10dec1c232d6478294a44747cf1ae72c49 100644 GIT binary patch literal 2008 zcmbVNeQXnD7{9s?1_T|V<0F2YHz)hxdLM1CcNfaX+PPKen570>2=wiHZ@ZKB?zp?H z9R{U{3I@%B1U4{XF}VK}A(EJoz)&F=`8FD2G{FRifB5m2glH7sYr8roAhF4{clZ9@ z-}8HZ&+|Ur-qqPYW7@oF1VPLQcZ8z&H;%8$n)~qgU;E#$CkWS5YB1QfRM8WNjJCIu zjhrNjO$5<+9>(LmVQ(KlD;=4Xc1eO)zla>=|a&$t_#U6)?oe7UjVh&YtbUnEYSb2ClE8=iBn!)-ec zpZIA8+4pS6fM;M+<>|3yuYY)O^W|{u^;xw?Ykr(c{xQ8{lRS9#GnX*slijXE%c3ny zuJhux*|9n_lG@ccr@OnmSsb}K|GnnQRdss~jMcT|#u~28e)#I_hTR87h_70|ol?D} z_SNCqFNqgEx}Xg6#L0@Sze%-^9NUpd)h22N)^x|J5zo(^HCBISu>Qp8mE*he`5zu= zJ#%1B9^~(@sJI#1I=J_VidQx-s`&JnM2wzV@#kDwSc8q5YIHnr5ybRa&gCNZzB7j) zs&1;W)%NPhavACwH*oB9=Q0LH6U4&BIRn7;$R-mgrD_4{*Q3WNQdI)fDqn<+7(vvd zc5F1!%8i{dxN$v{6l(Dza$!!!1Q}!lGM7nfmYfSv1zs87JFgjvEI{n_0jgNP)sZeT zsGErNxdj?>UY7JrZcbpmK5hZYvz(t{eGDtmEGM&~%yVSvL*Zzql9Z#Nr%SQ$Q-JER zZ9`_5Y&Pr8dfd91VsLLr49hb-Ph$ja^=mfB(VA61&JaQtG*!b^b&YfwK|=4f0~A(T zOd(^GX*H{46E=*=0fXV(tdmj!sK7GL=rz-Y;0k0=8fB1XTNulgu||(>>sF6`2kP?j zEdj8#kw`hlZM9@Frikj@7 z1QlW&#$nK{P~0~Pu)%+V6(}clGXrqeY6hebV`wRgEPE*j^|Wr{z*wDU;`=ZjCZ?WL z(|BR>P${U5MC7n$*+7FR912j_Ah)V2G7BXD1jy0AlVEADAR#&dl78Bwa3aV1S&rv? z~-3}*Yyc0FWwsTjiVr17 zPh@peDVWih5R;Mz3ABi}&|a?}(vl$gX~hqCUVtL!6?{(X7rIIAP%T{N{?cJui8hq( z(kfYKAQ?dCU<4@WbOchU(rfiLe%w>!(*A4@!lZX;l45jAPuf{vqNOS9-FS8G-igee zGO$43-SyvFJ&VF7>Hl}<{%`#ygJ2KPQV7pmhH^4uoXJ!y4>P%Pg|!J&Y#fe9qjOuH z$@rl>)scp&O+2$-TU9rP=Y1p`S{f6-_<7*ORD!Fie0zA#zC$+nJj8@%ir; zR@YtkZQ611v0o~Q(39^<^F8Yh9{J|+nKPgJ?!|TchK{y}lE?P1%{O;d{mx%-wJ(o9 U+JB>ef%8uiZtDzv&>G+JH|{EorT_o{ literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^89>a!!2~4Vdc3_3q&N#aB8wRq^pruEv0|xx8Bnm& z)5S5Q;?~nk2YDF;1zHnDFLYe!U!cb+C8Q;2bzmRUw}kIj@9o;(pEvksc5dgCOA^9P z8cV<2yB^hk-~fwQ%7KIKEK3r;g9O)XH9QveS|a(qucFM115G?TiunX5JSw(`SvDmq td&!}*mX7BvtKV(BJ>e~Xd(fjphYxX6awLaXk(!GAn{2wAqLOv%~Ao0lg!MSbH4Na z-+wv(Y-w*>JazKC$ry%Bjf6un^d3NWbukC>bY|!rR=wrh;R)Lvj)i#d>UUN* zIII8ceLp`V^ybwGdxjUy8+pZD*WkGHZvVBq`Jemu#ELL zt=aKea4@x_Zcax>M}vRx#tR1;s#eU}wSRP0V{UZ*t=UiAm_2{z{z2?Y%k_!X>s_1n zxh`XGefGV)kHvsB08z}}%4j*cpCQ!ZpKUV9_B@yr5PVZdnnpV(u_#^MV7%!7lEu9a#D{Bg}BUQ7u%n*X6WGK3&Xew?qf`ry%1qh_H=t4%X z(5hxBOekPf4(Jr)rfruBKv}Bb^d2K!FfL0JOv4OREfZmx3RdsZEY0lF9ztDNzRv&( zZ8Tc3@jxw^OvQw0we})6N&$Hw+Kl(oFwQ+KXh?+TQ45(15_wjJT$y%ZX|) zOU8piJ28H|E(#Ip~CM(IlaZqoZ z;Y3aldGCEtR5voPz<+{eNla=+1|YAM3`jvrS5pLDiKQ6S(wcz`BX!)^`3PzzhL%*) zXkmO)DX5J`#fWNJK$T!56d+K5+=?QL30~kC*$2sl2LO_1Se{G>9+s56evaXj66>Qu zd4EWgdh9Z=_ZLrutVxLDA9WJsIoZdsBqIx6Qeb$V^aGkD{k#Mi$tMdO^px3_8VWj; zAU&4VRwW}w*3TiZCrSD|UYw^uAH4mS0eQ= z4@}VeX#J0u&!Vt#`u|dk>g|#tVtPDr3(H>TM zGWw`=b*Lh01NH2Kr*ZpN`&uN_91nkU`!X|a2(Q@$n!oEgevkQL`=$2NBR^j|5dG12 z{QRb-*h{V%+^stVv0~YkFOyeCK4Q*vbYD2?J9_sGg81X@VgL2i&5xT8{S>6DoK+ic ZJFahjZhE-7mA3yVk=C}*!IsYTe*-(Oltlml literal 341 zcmV-b0jmCqP)321{*1fc-J2K!6_GvkGky!dYcZ}A=J zJtRqb{61Nh6-Ci?-L~x@2+}mIs;cX{X_~feyRPdgcEf;$7i6dbhaG$p5P}>;`6Jm+ z(0`+H{%Jn#E1^zR$IAVUo}>`@fCu1g4Vd?u=Cnq1^=p65-| z48yQ2%W)in{=UHrGSq;>9>=lgd4%|6R4#J2D2ldi$8i)<@7{Ty>$*Y>cv+T7lK8%F nS(c`0BC3e`MpW^Bzdz~={`1fTlQJPq00000NkvXXu0mjf;{A)_ diff --git a/Telegram/Resources/icons/voice_lock/record_lock_bottom.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom.png index 8be9d5e19ed3df9ea6edea42c605cab82e866671..882e925e6603113ddf36fcff5c43f08750db3fc4 100644 GIT binary patch literal 2393 zcmb_dYitx%7@blm1q8ReA&A5BuoPzJx!aksTMFGmSJ+aPMW7Pw-I=@X(A}M3cD4@; zkMgEsLjZY22qr-IBLY!0B5jd^s0BpABWeT%3yF}L5J42dJKOCF6_5}&nccg0&i%gg zo$uUxR}~ga=-zc;R}90tyYpRM^qY>J-l-kXw>IqBbqq@yq2%Tkj+IqE#Jv;7;pwy> z@FNlKlvG^IXvSO}_AD+weqz+fu6l)c<;vCG;xVThjx{u4DXYFX+Nm}vDH|`JyQPw!t;RFuK*y7JP_jE>X$Zrst_cT{Eb@T>j$TT@@PFZYO zw%u|DoAbff@^%J0n!NNk!7`|BwZFvTPhB&s$d>{cWY z?buiis(U0S-}f!uv}H*0l116cN9qKu;n?KA28fPXNVqOq{>%u5b?aq5N!XTm`eRti zX~j3gnBkcuN@~akOgU|pAq}B1ENe`q2Bb1*;C@)5gq_xwgY{Nik)77*b`Ryza$%{G zKTn5K=N0&*d1aCyTgPPMS(PFR5P}B4D?`C>M67gLV{t_^Hm6A|9)lQVPHSubW_Swm zTvdm--R2-9nxpVc!A3hM&Q6cU8H&y%DLY9y2#OXdUSw!I{;{HLx*QO_uJQ3)=%3SC zY8aYGk`)ydwhGp!>Ln!dhCosb$uI;$5Rs~|0V;`bWN2Fi7mP@{q8W-B#?6SpuSN~0 z6)|lsA*3b3h9hyAkYHpb&`8=wnI*-5vXsDSQ9T$-E=wc~!VnA_5rm}^Sglkw)JUoN zn9zyW4<$g-dOV37kK_^xB~nC;ymC}VT#!eiBfcsPl3o~5qq+q1$`Q?>W;L3at3zO@ zx=&SuZH+2yqm1X~nvLP{bPte}uvy2*2UbBBFrd@QPz*)TG(mGd+AcDz$T&usJ)~Np z9#vKXRqdb-AI*uJL*(oaL6L7{V1WMw%aRyS^$bJxoS|=QDVf7 zeQ@554ijAsC_(h1{h<+y*0aKr#%3A$ZQD-y`2D6+`Nj6g$5BA^Ta!Et~i{5+5e z8rTIpD>-CVlG@h0R4HoOz+B(DBV<)VG5%p^r!oO8`TYbXp^%cy34{R9hz+2?JnyG@ zK(|GktSe|!g5ZNuO;$3B$U}RkBOr5x%+UhDQ8YvFH0vh-iJVkI>_(GRN0fltL8r2_|XH9#I2E1<>Kx z5~N*Qcb@D-@^KYJK>3sT(=r1bKwt@$5|DDTOcMeN1tODW?Mx;uOMIr#R)0JG$hoY@ zG9t&dIro|Q#|4r~LAV5>^Om$edqq#@&{nGduBhE!{GTf#&9k=EaAbQg#@;<}E~A#l zrqE$;4imQw`jfb)U>J#|qg$vEoZEm z=Q*=plR6X z8HWo$YDt^t{(-u)C}YmO%T?0BSC^gowMVE=)o&L@3NPN?bgydLi<27bKi*b@ty?y|XXZuoDtG4$gfGIox literal 369 zcmV-%0gnEOP)YnZbaAh!_RjFB9{JyEX!3@9c6$h1kxQ~`-EZme*=W8J>lR1~pQ!K+xF9f`o^&^Vr$l*(oc+E_G>KCF@p!2-(@0*=6JI&SrLa zd8z_cD=9vbsyvQ4!BfjgY7^rFdm4=*s1XH;pdqbALuvp`jIpNDlf>FPJhq0THYcZh z&g|Z~_q+f9{r~^n|88~8;)T=0&qZWk?1xWb22!= z!Nb;tnQ#)W*Hg&=)5Y5DgkO3||Z6DDt}~p;loMwCl|zh{Bu=OilTdM#YahVo2mNB%Jb0ab1F0$dRcu= zu|Mn8{l`C_4wpP<-m2cZ`mxgk&;I(Iy%is2N8E{uI6P(Oarm35=GAmv=Lv;&a_bJo zzGtne3+@oqV2mxAZ*px*ip|f@PobKwKJ)vO$CgLe?jDFvEgP6S81v(+F>`n9Zh|gm z{%vwtRmA$nh#u(0gCB5>1avZV-9Pmavyav|ToH~bTUX@U!gzvsGHM{cqb~k<%g4vI zRaSmFBeP?7Z6#CrL}=(e+q$}jr$c{LF+a5Js2*xLwdC%Tv~~qZ+~a<8UI2ooM#+x? zYSL>T)k;>+Dbwe}X=OAp;N>L-F7tYP0lLhf9I;D-u{@0`;Sq?m z(x4ozU#=wwHVPsSYgAf<#Yqg-=~cKEBQ^Lun80uyifK?xi(oj7Q8aN zYWmrjFYsni7D)XgJyk!u{42%p=FF8#Z{Ob(g=`agE)V&=oxX&u_*81y}VBf z04yHF`ilff2ownqF&$j~jsuWdizVpefn2=apo@T%RRY2oOUMJ!0b9ABN3DE7C>B{h zs|3)Dm!t91Mv-SEL9_{iXS`53aC1o#hjtS{qK%8jQxt??8cWI7VXrCzxYtryzP65VhOOxOmj> zb1C6qE@`9S5k%k^uv6bZpADLcC^+37urRTyjIgy>=xkp=Vtg#0Z89i9f>drdM>}*{ zT(4$Hgx5GU2&q+T5K7PK5eKi~v}&Exp{2<2{U(7emSrICA3YJAzycfpW#^w$bAS6br9UR3oI!Y%?e?(2R*&P6NE+0EgOZk_^MjkgjA|RS!<%3~Rva%w0PB}L1 zen39Hm*=tlrA0h2`UsJXdJhOrsgx1<3>V1Vcy%7FMD$@11elUX^Ve!gE$P%!h=z5L z2#GUVgwg15gklJiA#{w^NdVo95}UvuC|6cEjp@df`-Az9Ibe$zpNj|m7FGW6h`ygf z9~RMsv-p3G2$g&7sNm3vS{zxsuP=j=j!c1OFAsya40s9NQ@jr(O9Z!&C4FNb1cfAJ zn=))gmv7X+_qHMdx4|%hmOW1et%FW-MAG(H1|MngD zi@CMB;%krn?EVv>iH)iXOV7+1brtVI+G)l#d&u{+(wwZ*RHe8ZV)Wm4{&ww&si z%iNOowtl~=_N?>3mCfDPq_jEr#035Fi(C1;CU@Lsso*DD-ak>0(YT;8@~ literal 657 zcmV;C0&e|@P)J~$i>Z@fZZSuU4+ zT3D~wCK2=boKF#kVVFcrCKEnI%w{u_h;FybrwC2cOd?cOQLn?xvz!lwuXH;G86 z(|n3RaFd8=G|Hz41g8@giv@2M5Sh+tG#b2#K;#P|BuU~;1R`G$Aqc|pc;v?d!iGX2 zx`?jp{D?qU`VLu^`4N%N=f4%<_xpFd9S;@|6JhBGIO(-dJL_{qQDlC}M8J4FPFLx4 zI&6zTK)QuQBEj%QfoREOl5V6@sjw=dTCLLU1cSlVYQ>%fghDj>6-@RN2t~ie<#LTi zBbF>64nmOyVzJm}vth#mf*=lACYQ^xAp$|@PD-Vc(=8wZ-4E`oR;%S)3+;B>;?i)A z*Xtb&22QkqvQa!e5In1m$75$%K*3iYF|FbA`7}*)iUpL4g6TK*UF?RFOmg_m!0q5zbEV(7Jm z!{J)3_L99EOp2*d0NICBDuo{Ik9XWKDcKWI6wBrEQ*W02ey^%3CL;S22n1wV)^+`X z_X~!?fEX5olj9HsL6W3;y>9uP1DuG{F$jhtHi$$bnM_7e6kOQ7UT->`8iujmZa)L# r1HR(Ne!q_cip3%h#wqmAKd<2(0y9^$CoXzt00000NkvXXu0mjf52+w# diff --git a/Telegram/Resources/icons/voice_lock/record_lock_bottom@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom@3x.png index bd1cd6a8a296d49a9a9bf0ff58c41640bfc5fbf8..ce162a65dd9c8d55cc454ea13b10f735694a5e39 100644 GIT binary patch literal 3014 zcmb_e30xCb9v(pPT55!X7+oC$x?GdYOzv5NLV^-)5HYQYEi#!*AWCLJ5+Gnf<57zu z-~}R{fJdt>NTpics%Sk{1WZ}GUbU`RK)dQ%D_yIs`@*%>wQhg?ZGM@_JHGcnzW?{$ zOMYDJRIj1jp%4Ulsbf^};MWeG@WJlj&1=5>41(My8kNepNIk2g;rOXha1bV!%Yq>& zsF_Mm#?7{L3+lVA-k3+#A#QK_PBmE?RskXhNN=%;9iCXPfwfU}&YV$*neeZEgMEz2MB5 zF~!2-+yQ6Xrmv`}UVKT->Ga_o8Qd`heljd3m)O|&g_~s1i4wQ{)8j)YcjB`4ky<}` zSIYVz|HQ<^5ZSI3Ge< zMj0v_BAu`N*Wdg_;;iG!7_g@(m^ z2|{5a6qd^Qm;@Dxu`n1%F{uC*3s4DyVgxE9a18GK@PIdq-ay2wru6y(Jq0h-W-}84 zK~`24KTF7GEhz$^4Y>ft1vriX1Y*r*Y@{7wtO0!%RJ4_{7|k{#%fK#+q>jz7DR>~# zo)k>xezT0VmnJ}%z)qS47$0?|)D6^A{Wx=mC9T`Jo)XY$w25YHR)EF&vF23P##&R^ zUohQ2{WAxET8*aP$4g@|nfhH=ZPA$^jb1`tineOA&9oq%wz3%(ijK|%VFtLeF%wD) zP1;zCmSxlW3KiEEGOSd(3L}GqG$dtYTzLdPw~AJgHd?{MQ5;1u48cTNOibWHLMRD! z)e!B0YFNF|ko^KwqQyjn7$tD&&!9jzdeTPzPq3aM46MaOf~<`uGKChHnG_z}&n2N` z(^v~|45AZ0KduIciG?*7)4;@whe~&}8V#XltTvLNXtheg0|fDnMm-@k;0BzOi4mEM z6d@v9Mjc`>f5d7{R6_)1J5Lib@YG%EWRJrjug`Mae0!n!*q{h3gUA zAjEN<0hdyk!6p4}H4!mJE2wjJ?`E1sr}wtfjBvMr2$FJb3diN&};>5J``BD{HW2krp~K1#s6_omVSS@Jkt3$;?;BuOnqrR4SDM-Z8)&C8ZEj zESDmBi5!(-C@KLm?92a!_QnA-j=z^6DwSkXXmH*Nc)vZPU#HNXQ2#rl z7xv=+IU|AVtnCq;;6*KV&pmf8gOYX+fy3U_?7w9|r~jU!89j&wK54nRx~aUnQrOJOx60eI^Q__4B;3HhSD$RQ+Ye0*J7<=K6U3&& zXPZja1?KCKr4^ICH5F>ney(#%?27LGKH~Eu;m}fl&-o=jU<9fP}8{yqs3D9(ivQ;r;n|XTi-kSDbXtue-)b z^UGHyRkuH|PpMqv9J{*W@w;2UXdG4*ocNZn7hd+e{PNP$(vfvV{&mCu;k)PJe4Bgn z=D(TkIXNrGXB{28dUVpfdFpNH+ow0ymZTQP?gs4x85nmPdC%c&y>)9@;GORuJzAcw zysO#MQkg!Nw+7CivN*`AflzSmpIS`Y2r8_LCwcXj24Y)G7aH(~wAp#$$U6+^z?<*sdSZ~y46 zhNhA=5rJtp=UQ`fO)1WpMb-dqnBOP6=|JYh%Ck&KRp z_WAaQnKcEXHT%k1)LYw;``jVSsfgiY>z1G4R2eMuwsNbH$BZg|*XoAOvWI2py7C1G z<;Vz#ztr)e9($ViEcX5i*j`mL`j2y8ThV&&9@E~b9f6)mdcrt~&g!g=ebA1kx?_KO zrzULS!i86vg9qneCzQntc5UII#b@bYtBli$bGW^r!w=&@~*bFw#SdNsn#LfC~KcD)s{2Zk)IwaWn+@svX1yKT6Id-Ip~cm2cB*EDzoC* zs(iAkscDIiy8~)|$Q&#$uley|^VGcCk8(C{+t5I#p8&Kb+&;K>KqLozGj9ksUz&M$ U!l#uHuK$hd=vdXqQOQgG4Wy!F^Z)<= literal 1012 zcmV!x$7FuWr(ZU!@ZKc;jjkiW^)x=II3?#M+ z`~Yt>`UfkdBjHjn3s?iU$n}l(He@`{~fp(KHN~IFj=;!+S+9Zs#voosE&*|x@Nf?<-hHCV4 zbaZ4A#@gB%)#wM>O~RO+ouwN6K)Xp8qobo#qaSED2_qVfQjLC~T}*j=e54(nK)BQ* zpU+c}P9VHy82mxyiJwwc#>U1dNGA|pGmN&jw$IN`I?@AFx3;#*H=LiJQp#eySYWb0^tu4w@4J3;d`uqDSOEr*eDGb9f zZf|etOC8V)$zqZD`FSc+2lR?{!r}1y`#Y_v0Ait6Y;kaKKyfO7Sg}h_PY;JiR1AUi z_V!90WV2c7bC+B$C-o7J$9ZtLdsy`K^-0}u{f>(qA0LYoVzJoA#|J$+2O1$$9J0B& zNtMolMsZF{OUuK<18q710z;vYxWeS*B!xNw0>wp6PEP2wKA=wAB@&60%VnCi3#37v zxK1jSqFlQ`nkZs_f1iG>16`tyU@&-fbw$fofGFq^rF3+3@Z@3TRMFYlDH<3Y9OP`r zH$V;)i8iLDrYOA@)6>(UnT3S~djHDe;-WMJ4qGW1fT|C}Fm`r!m_Ze&u(9StQ63u` z8%zSnM1zyy`J)QDFP>s%X2yrpQ0Ou`E*@lPXvkOBgXrw=@UVCmT%TWFUiu*kbQ7Hw zj}r_A_xARhegshYxRZ+{lgXz1%0j0Uj*6=z5{Wd~{2;ocWTr?P6B83nu$}>3aCsiP zQG`MvToe7$fnJo2;$2(Y;SsvJx-yx}FIq2(Qc*C)Jk i_+ZWd0VISFVu!y_Fv)P)Kp4va0000 diff --git a/Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow.png index efa0930639db7a235b4a5404120334dafdfc679f..b675e122cb5022a24fca487a5a68758910f6cc94 100644 GIT binary patch literal 2774 zcmb_e3se(V8lH$ol*h(Lc^!3!Ad28*9(j<=0R$41U=f7WBD$2x%mji-CQb$j99==7 zfa0ST6|v{YX^}oS`084>hpiwA>ZzcBw7%DMmC9DOyW1AKTI$~Ls%5R))3Z5ek~{Z) z_kVo=D7uNIa%*@R59bt>6^Z6;|<&`O!OD?svw_Jq86&Kp3>=22f;Jl3A zXN0-d|kY1)cAdp^Koc@=nq5xZTH3 zKlFn0R>tp=@7g%|eBbi2qeqIa>Lr7-Bu(xQrom4<;y04j9cM+VsjYiO$CjsrMGq3{ zFMJI?Y<*T$h+leodZ@bo-oitnlVA1OSJ&qg=Ijgp!uO?nzQKFz>Y?7)n^VP`C8f2J z9w_Gzmzi1uYI7_3RwJ2ns?wMxF}m+sn{E)Z1mQocFX&=*(CL;t%~i$4_kR<6v2I^6 zRqXBN_S8^PT@&cGr6|hn%qb1j(w;QrPpZ}e;-=Z-S34lcW0va?K{aprL6Er1Y)BK* z5?>~1&MKo^bjqAoJ3vEF#1f~SqSvtkY-F>{wn*t$P0do+%tT6GRVHGIb}gH2jxXfd z)WQ`8x^Nw>VWdl<;0PxP3|Lu#f}K{2%|SXNrNeefFm_F&Qg|35tc#S6)GsYD8P;+< z3oB(RgvJ#Z9Ilb!Domlo7sCXGhohJh#Z(A}lbD($aCr111>ShZM5gGLj`{*`kO28nd8d9W-yY3uev+yDU;hE?0<@ zf=EYFu-eDX+8m=a0m4uxWk+!t=1OT8$k5|BdoFJocFxc!YhkUdO>h7#K903#a{}kc z=6=TX`0`I20BREx$9+6E7OQpKg+qwT18IyB@?5mTP+(`#6xPAz@-!Ql2f_?;Wn(9` zJWB~2Z{Rq~SfP@~LWZ?kS7Fp}NFqg>ZLT~PJ+q6|Q34w&B`^X*a2&xE23$!JauSz^ zxoU`wKodE}Y$})lRT*#vsU%1o`w0~2hM@%Ne}WmBG;zF@0$H1_R2GZcZCO%yoJ&&6 zSvVdz2GPl%o!5hA!gD6G1uRT#D#OtxCX#xaL!fLltJg(J0YNgenITmKO)yGKfe;#& zLKKu*j;Ixk90^yb2opmQN)sv$LKIzPGd%u5usU3iKyiY z95K@28bnSJ3Zt4*Yt%SCW-W;~gF{JKp0(1!1-Kil&Wn|Z{#*tQD(}Vd;|yVB854zoqMBUHVF;x( z0S=XvkujOn1f^of@}CetP_CSmt3ZDmQ|_;hf0Q6Po3drGpx>g>U!T!0Q|L&j|DDl< zv-p3`2zB+^5y7DowK%-?OkV~i9i9Ts-ZdP*Wx&h$J;mApSvj`EB7`?=s(QZT!bK zzk2j;&op1JIdA8E-oK$+vFklUXj)+5)bfXcwg295dmwL*c=O~E z_xPOd6WZ5|%!jKF6~i&<$G!2(f3b(mp8iQjk|rQx&P<6BzE?y@J zUrF_N>;2xG^cj9Pd-|{R3*t$l;H{-C)weAwtU~S(EeIPG8XLZ@t`Gfj*x~}A$NLy?=d1F(l3GGeYdOwN%`C=Fq_Rlgl@ioeJBNpgJ(HaH#6j``!JD0=Jr`w%t?SZvE$q zShRSkuBZRI4N*JKeeILb{O73&Q3sdx*uT7dF1L9x-}d&r?tVeNaR18-KD<`>QbEML zr1>*XXC^oFM&0(uEG?q^m2(Bp%G04kSGa*UrtBuVu5CYBxin~I^4&SU+m{Bt;qK{Z zx+UAUtux%u=bbM1ft1EoE8&`)qb11?W)Bwi&5(S)qxw!+#*u}@d=j_+E=c^e^n;6Q zB&UMzMz`MhtheH!SM8dHqPUQ%%;4IFkG80?vk%>SALk%_nfijqZa6@EH`T4|o3me6 u^}SMiV4iG?RUSAt7>*4KZyMJ9gVjP>({vcN9p5M=nltbZvGEqdfT1= literal 576 zcmV-G0>Ax2&(>B7r7=kSU!` zPbQOJ8qPG$e!u^!aD%}hl}bs&C6h_pwoj*%`eZ;I$02|;Ts$7{bUKH_L47!&pa~!i z7mY@%)#`S;RUZy0XaY#Xg+if9rLtPB)Q1BKngHU4I4{HDP<=R{w~p$UVzF4W+1%}R zDwBZ&3fd)HFc`EfYq?yi3NkT7f`-Qr7GBo_Zl;4gwH3ahJGqa=F}iJU$+e&q#(X<{(_| zHkou?FPF=++06HR7-EY#2=TBYkw~dj8jVIi9$u_4#1?b$LxscPY&Og0@a249f)$3? z3SY#wVB6N~^~GZG@DehS>5WDMs|WYljq__5MxjvXcDr2Y6rA(<>?IS!7|awVG98n< z&!+b!_d2I!8snR0KQrRma7Ogx*6cCH0+V)u$t#88itgr3s>3MU`WbWMi-GBN1@BVYL zZFN}C95Z_}008EM1PjB7r-ZmclUc+stQjc*0FtXLFmUx!6qjO@@StTBcZkpDc>sWW z7b1}`m6{DI%vvI8{mRS3Or;Ia%PR<%_;=Me*LMO&+uAP7evd@*qol@dit{uudYrmL zH&+F%DS%bPQ=xx0ME+pp7g1FaY73=d#C>mupI@}d(B_vcVj-wG~d z6~1fOIvko?eJt}@2>Fo>xz=RFlrnA}{H~znt1n4hCmTbI4%M}V)F{nOZJ3wdi9xd&Kx>JnPU z1TGk4|BFvvc&Kcv+6e$C-Bw28}3X4b&6C>&C z5k5-w_oMiv2?zsnOaoKW2FRs>kO1Tfm=I;^q7vRzXo4VIxMIo|@#afS(rA%^cG<$o}%%k z5|K`(AXmLmPkOwTO;$^k*GNHpW)H)D~@XI!W?%TtLorU>~>v|7ATi7~=4HJ+kEu;r;ln9lla zl!8DN25WGY7{`;R3$=PWWJ+M5zA!wBy9h>P3Vj|PFYUsFumf%x#G!14U!N;HSJhfk&gb_ynKqQmi)p&wN3#+iDi3E4k)%jy3GJY=uHJtj#@iUoH zl+BZZG&YNdQn)C#HyIuOH5QWr&xV?-ZsW z$Wjqsq0vWYM*v{<+7RJVanjB2-tS=iEqY$dc+X+Fk<+dckm_V$P>`3c6H3uxoXoO@ zC6YlL1b5ky4IbK&8-v9z*2mI?UWTIJ@~36b($`#UkJa9&({_D$y^XQaJ~DD7wKjkI_FE(WN*}Pvt(&*AB|179IUdQj-+E_!{Edts zo;-OH-E;oUK`)za*-DGtCd)q49Ys6l7gwB(tXX2c?V1(Y4hwekptvMQrxzY7nza(V z(cj-k%wCYQXXpJ=Ot0S^K#}+dBo#|Owg_c}?0&ZC{BuC}))wQnp_gO)4t8zItaopC z^CUlyo|y{lOiWBXw7NA+Q@;@0ZgB-Jt|(wM3eWI-d`{U=z@Rec61+Dn*lA}=SmvXj zfBrmWflr>@T*;N@W+S{_9VKZU9(JwB3Ob3!Jt#YNjKTBs^(CeB)mW7T9@${Uj2_-f zZgQxss=97Kwrjjs?j$X>{9@?GAJtVoBpdg0hAS5AwI5uXmd_A%CutN4`c;z7RALe( zG>RI@N{bFD@Z&VJ-M)Q$baegLWnHeZP0jMs_pEp8jCYNf-Mo1-Au&;HOZM?8si)oM zyYKnh=SE&(S65ejZ0r!#F)cYQO<~^NxGwJVD`jsS_Ve>QQc;mIR&rpAaZXE4?%mTA z$LF5I;+tdpTm_^dXm(b^i4!M=hldx=I#pc_eB)MS3J#bq-+ysy2&wHXFvqbj4kwtx zFf8m6-`{_cgG1ACpYEk@msbUZkW9l~XKh(!X=(X%eB9j3>_n+0I&|pJ;-t-amQGve z6rcq^0jAZ44OUH?kDNJk^`0hy<_JJcrb?yS60#W`$cpSeZxDcSMelAKTesffsWI zc>=)~wYB-@oW=B&zBvsTsc=8Oc##~(&zw5-?EGhozHNT+;K99n9*1<_ey~h&8VIgR zYCG#O&#bbta?jqqTn^{?lVU@oKrbEyk>)#|WRjN(ROo(Fx_PF>^Sp}Yx;oz^X9ov| zj*gBOb_YgBM;|_X_|Zo$OTq;ow^%!xXBI)>V=w&u{OAlu5Or5hj?tOE1NTguJ3BkC zT$#sS$h7zTcCYo1n~$D0Guc_=u3p!^Z}75V{GQ@s3*xf0d`B!srCgIw)er1TC6v6H z*Zq2xc6A*OX#p!&JZ_x7vdzRW>`G5h&(*6}dwX*_ig7Nlx_xbYeE;?9+b>8n?3+Hn zRCFNc6OT*BErLg!jvq|@#aWu|Ryz0a(N8|va=Nyz&T+frpeppd`TScWBdvEH22@%Y zWo48EY4YYS`q@q~=g4b?MY{^xE(qfX;u~F*aq;o-Yu2nGPIE*=L~LwqOw6-=uDOra z*|;yX3>*b$NfdI;*|R@A$;kLLtYmP*3PDffyynP{ZIPk+JiEoVo0eso*f?!%eS3Hl zY`fcaD8r}qZuSoeVeK}4SG7+q!I0|g7r8nUKX2nNA=8d8&@9oAt0U>Q+d@%$h@0H0 z+`{_@^Tvid`NsBIs-soWjok0ADd}y)75$}_6&3w&zt_w2XwVji3AKg&x1Q3rm2RzR z6-lCu?#iOcttaQgE4@A($kg}*@6UQ1YTW({FSgH276*AirrmC3kU<_|ecN4~#GKXo ztbE7jf5fzy)E+PmQMqGMmgmX!3SF^^5s>JzBw}?!e`#O&c=ygtKRKU#>TuXsyW^d# SjgR&J6GE1U39FV#w)`7-Q!=6e literal 2265 zcmZWrc{r478=pap37?%awqeBKBumuD+8E1NMkLD+W@IVEe6p4$OH`kap_pW{J1L5p zs6k{LjD0(mFt)MRm=;SUzBgUxeAjn<@AW?K^Zs#Pzu$fT?&td5&&@MfYhfr13Ic(I zF*aytU=IS8K7xgEGPSo0@MkSp}r49^Wr&RP>YKp-C6(PBu zue+1@JT`RbRC;>)DF4Y5ClYqnF`X>=AOr&Olf=$0oKQe}_UsCgCf#(n`Y4Yqr?cIWxw?j>$KDc{%c@c?qxSB<9Ul}@2TWbi!2T1rf*0E!vN8&Au zMG?;>k(@j|jdXN$OiZ+B;$V?82id@RTOM51oO12CRaRC8$d5vyfFt4NCi9C+K)Q&0 zu{rO7%-1MeF4s_F@7|F#+8GQgjpoxFql-i`xrqDr0uZG6>({S;)CRY^s#ASVrI12# zEHb5#$zNyMcr&dmPLYd>^4ro5fknfYQ3r@`eWYELPE|NpIak?$2OpSx`#8&MV8d^s z07**Xl?(zU)4^S#mX$`tIX(*B$!WMZU%MX1+2T$M?+hs`rVbXFrThrHY#!xFTz;sR zE~0Si>SoBLORMazEZ}%N9$gXIVnFjH`OIH|q57b2(I@&_^%;!VKX~9XAVB{2 zTvoz)HztjS{~LIM znzOga$T|G$1T<~#&#JR-PrSPO`z@hn2sd-HFO=-J+37Zaqn`Mb^JH`2&6~}cwv;~+ z==u417dN+Du1%xl(OWMctVc7=g>po17^78w*5|oYuuP%T=`)np*8K{*{IyB?StqB{ zRi%BSDk~OYN3YN`PKz@R2;9SkQ?(*P>Cy6$GBWuYWB(X_sCC$Fn&NC~ z$_>y3I6nqrp{PQ-HB-}45*%xdFMT*FI&43ZksD8!2@MTZGD|4WCJ_LyX_#3SOEynM z{8}QGB=iSd5YMzwb>43YE{(nufm0s0!F^0h0_~LDKN)dW-w}PD%dEyUSyg4B*bMX|?-vctCo8uXXBlN#0$=D6OHaIC7Tmk%;q5)( zzp}atla(!2p5gEB?+?%lh9-+5I4V`O%A7MLg9-I#&izCIy=^!I8w z4Usmr76Q*Gxldf0TIeu#OZf4lN4B=M5E7YegG?5Oftf?oDGjb~08^SzKGR`rVseoJ zcqDsn&e_?S`NrShA2~KOWWKisJS!_D7iD3vkxTV}sgy?zkS<^RD4?{h?xTDCqJ4ni zVUGc|kd7OFrG6l$2CdR8&$bDlF_f-c($i)N9FMA93;V znVy`qAACJEH8nh3;sM)jv)=1=v-gGNdN5FVG(pcgW5g(N9pm$^QXEMlb3B diff --git a/Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_bottom_shadow@3x.png index 5421949809ff69580454065f6ba8ba7d618d93a8..3da36093391d03742423618223783e47c50955cf 100644 GIT binary patch literal 4285 zcmb_f3piBk8XouCcHe0fna2GxX3Y&V8_AtWt|`)tnKcY!W||oc+2=gxIrBVgX03m{>-*mC{oemu&tz^`@20G{ zLJ~Ij_=jMno!4QcyrZAXECzVXb za`@Z0paz+Ir`_CCksI%~XHS+Nc}-_Sb3+?UHnZiH%mE1rdqiAVa+sNv?8mtMLUre@ z4`m8(Ijnf@X=!36aX%%s*TnFWO`%YD4|b=+5^W0Gi|dfM=#qQ!=4)j{oUeNhE+LgA3AV_WfIAw{SnB{d(AE;_PW zYGQ!|HSnH)Wm2i zk%nAjkFXVxpa3x-pMns?Fj+j3zz(?(mjs=Qm(fVX0)!uJhy2pNVDAkGCpH&E5G<`w zR16+KSQ9NVRsf!Wu|Qw}j5Qh{paClsfFS`kBrFE8_(4M1xHLM+&)Ib`7j$KZjO6n< zBs4lcKHf4OXUXP9pdoJ%(Et{W#iAetikHCRQv@g$PycHKXOKtbGB|t&n}rZZq=d6$ z`F2Q%>6a2>I7?w!yhWKH!O#K<2aT}=#3d~NY1AbgCzi`xNKT`oK_(ajviLj*i&?^Q zBH4U4FOvOlLNDF^rv#9+-rh?&{*+5h%u)&--z5$zV^NSlMe_m@I3U^&L@*lx8Dv8eK#!#TD z88MUy5Y1slAQ4MilAPE~HWx|^vBUjw-2)mXTsED-gl>F0R2JBJdy_m^JU)d*1wEYY zkdQ!@3fq(6bg}svatf_G%El@rQx7F3tb~Qv#GIS8;GC( zvLk40Diq@%>?n9F6$r-wC~JU@L0N-%B8rH`5>VFGHUydt1rO42l&{fzxD04hQkZ{? zDrQB4B3c7ffDYj3C?Xvqgoh}ih*nfQ$_j4{*x+y&3?V#RZ2d31+!;Km&k2j0$sgRd zc*bNP77Rq9P{kX=4oMX|0;C}qml=Pe$A6W1@%i{j5DNMiCi#+`$ENe+DO_-E1f<>9 z?)9?ok61do3^))F1yqjvk(wiyiq zOS;R%(RR;X&$OJ>G~!X*HpzJ{~gyGLl7n&n$C8?(U&TrSrQ+t}X4kiW=qOy}LOx}M+c2t*Xq8R83L_g0i_Gx2%eSJ+$&5jQo$uTHe={#bc?B<4vkov^;cQ@&+C+jLm z&9?@p%Ihaiy)LY_Kh{7xT5*u1`_VyIQD6V77;)TT-|j66=#}PzeuykTT(ya z?vK_qJ>N<`e9!gwIMKxuXlivz@o-X7(nQ`igBkfO$+|tlBigFDWznMKu}#IJqoZRz zRX@O!rCo(vdhNoJ{uXN3F^78Z9v}Z7&rC8IagSyO}HYMJX0|~6t&6%S)#3PVGHzG+$3&HDL=B~)4Lx_OK%G=D6~oJ zyUZ3S4^;jr($AXl>p1?yaNJ9{Ol>MLEmnxMFq}ahbT3o)Ih5-%O3d>2_n+Qzqo}A! z9ZA|g#k4vaxc&SjooU$KHe8oqreWuQ^2Cf+mvnP<&rizA%0$OtvV89u6!PJt#3|o> zD(%_Qgz2*lf=}-lBAfM|WEpt)Tg6i6`SC)t>F@GYJ$7{!s+GFI>vv3Fq_yNed>G`W z#2>wHXkr~;6i~+T>}uFiu<~XqgPE?qsfD~_Zf5#`?hiqgn+cr^WfhgUxVR`sc>a-` z8v$+Vbe+uaaa97v9pio1m9#8GKi9arxv8$Q6}1j=$c%pThHI%Im;94Y7)WauBrWDSt`A;Z$k?tkgD|6$4GeSN-O031ctd+o z;@#ff{x&_xD37@9g|v#1Mu;eA&x9hZEjV6&)Hgd@Zw)ItIyx%q$lDrOS=j?RL@a@j z!Fc33t=3a1EgO_uu+3v9`_YppO8Xu?rOVc+=-JN<+*UpQfmtK)FS3XhDT^As*7%ux z|NF1?^^~SvSB|V)t?9_zpPijoL=^`8;sw_b<-jwV2;IdAA1C@>byp0BDRbYxY|k_H zaWooa79 zdFr?ML_HdxUsGM}AN#`N2dM&Bo0rY|Ws)IgPkRVKdXmPryQxv`*&#o(X5&Ba-??+= zZWZ0c={ILtP4hI3Mss{+Sp#!fk@~W^ZX|8*Z_G$-*Q&Z?dVcQBQ0w`~r1`ny z%hO4<0)MG-sXcFG{7!FeF+bo?s5vJ!J3H%h(mq3fY?*n`jAl*WwFKU~k$ZfSGLUus zyVEi*XUfk+2YVPa8^i0zpr&n4OuUiw`QiQheytUcdwYX7ZtVHdztq*$^_^*GU+eQ4 z=FtlkY83lIZN|ZEk&z=0D?|1{bD%ys$t=IzG$3c_xpD04UX01%i7NxSv!&c$KK(i| zGH=)y&^2te*3=+&%!P0>d(5~h^vatCp%f&{&Y{XzWk$xv?5CISJ$t6ifQ|20OVl1J zS*8jI3$Ly-YIaS%={!rw?CR~koHC!>P`hLLP5G(2L)_F?ck|72DyFCK+!vyn{>kg6 z)Y4@uft5JjDEP{fjGbOo|Eje5-=ALLb)K*Tf?ob!nlt<9(sazjuu8E#6a%x zl;bUT^Y!HBtG;#Y)*Kztib*lc!m^pk4|Z_l{wb4=;X z%v31jpE_`dUJXqjqlB!jjtxJTYj3pZGs`+0G}Ca+@5u|nu0ftg#EO-~s%KqYT~D80 zV?86mdw$wd#~<}`=$&vg;~#>lb`9yRN!D4lDtUg+F6(2*+S=B%$s0Z$S%VHC6+qZ# zP{IfpkTDSM-gq&$Eo)erDVK6$bWY!%aS$=^#33dmo?04cNp_Xne|py=wRBtS$Bz4P z)w=SEM>`XY1_fEIfg^RU-C96lVN49J!$Vmkt1LZ}it}Kt-*Igg9DOlXJR)i#OpweSd7wTc)YBG11LHC;B+YAbG z)ss^QXLx;c-<>Q?Y~Qidjb&!aZPnult1uw)nwL?eV7f|mS{E*ORek(<6}-|@sygF{ zG0U)EXw#I;qez7-zBw`Z zy-B-tq^dMgq&NQ;%Rl#iGv9ofIeBN^$vL}c_t|}(O_Y&=Hq!~76CeIZ-MP()D0O&)tr-QBSR8jFOoU^kM_Pqc+Aky&%*p%IjU0y*} zwydPYh`nMg8fRxWy}Z1mIQBgbJTKIdiRWC%5zj$tau@X3`4rhD8!OHrz{X#LT6`9S<#^yt3 zXJ>D3Z%>aT6p90Wd!swEkV0`9e0{j9ODc||8ouQB1(dPf*4i2w7iV4V0r5n5L++Q7 za9Hf;g@ud<50n)Zw|93bR&~BxUncSmvdGV#xi~xTeD6^|OEn-3Vz3r?q!z5Ksv0~| z>%+h)MpX8J!UX6^=g%(o8>&AqD!OTJZ}0BDIy+nD)b*geynKIue`%=r!>i!Ec=f{s zTBGEGDY~_&lOnz=qhM}sCHF}cVU6Q+8XOqJU~jLqf@tm0F2x2&zLaX1_tPWv?x1WC}MBjIk|?MH~xkBh96EvATN@)21Vx?9}0ay5~P9E%T@Tvt>r>3Spd^lE$>6n?B zxpTPliQ$yQz~CUtDt7mQ=~rs*0TBJApRUoMQ`l2~Y_5zAH8j|E5~YjlH!VCoN)%0@ z5)x14pDw4Ju=O!T{oBwP(GejyM&)~_ES{c)^ApSA;bF=U$4EKZ%*@Obn^b^<1~)P| z_V$EKjEry}d3J`94kPs~_VLE@$hUWQ$5A45Vs6J!o{~tU!a~#{ZUDVY&G}M;Gf#3j zG@kbOx39_c2n5)Q$!BP;ExG>X%PSPi3?k8g{c~?=sZ8M&W1Cc3YXLmZliWueb57sqm3^Mv`ItKVnhjbLRd zPT~NkGRVoxw|z`YOLH00)YP0Yf1Q&P@>23NCwVr6wPcD;rhtmscS?u~cAhl!7y+(G zdi6?8X`->wZt`V7(beL~8m~{^W?PCaYhat{aRR1+%OK;1uv4u$#}tq568|h|6he-O zh^VYoP|9*0?C(#u-X3#*ohKc1*sZkvT}%-!XN78C?_6a7sPY!`pj(Vu zMo(8)2oc$QxxE3}SwBRvqWOieXunxxPL4T&0G0Xb)uVdD!9fd_(`|r6wzjoF&;vt5 zy7DFDMQdB@m;PwJou5dlYpsWJnx%!d#PVMB`l#0jb8%UotPezS$ji(3^(K*-lgLFy znmQ9e;Q>i09WT;B&&!r0ZFOiE7vzP5IL5SxZNZCnC9`4mJeB=sj3S5>wu z4Nb2xxnQx1o-=S3miVm?cWp9ZDJdx>RUhgDub#MqdBYK6OZ!yO3-lB1bW4+Qk%03F z5G`!Dm8IqW!9kN@nn9+b=fls2F0PV5!$)eW zQc_aN%DeaZ?U0pY&0Hhg8Gu1ZlxPFgxc z2Gf4sy}klB2qmlnB}4cgydXewpl8n6+ZeKOX8Y~x4EAf{{cW8n=xyF_eG#9q%`w~n zAkk=ao$pF}PtOfqtc%NB&08Z;Yj6}=Ba{IP27_mYavEqwZGm$87X4%sH=u?_lmNw? zF48(YOnlNKTLE;A=hv%@%YcRzm!0}4P9Mf@*0_7zQsdIyLlsctalM3}IP$4`Q;sLK zho#2(FAu+Xau^ZXj|xp5X7Xwqv)QJZ8kgjHD~ic3lDTh9Q> z^7ZxI+}L1ZVKLOvQ4ZRc;=SYQ`aC6^pK{&<9&Bo1aYF7r6>Cfbt#e?Y$zY@oX|+E; zUeWe}lwH`vhu4uvq@ErlSc&(Qq9O;%yl;blNhr~uicTUYl=&xMVASq`ekK^iyhIwn zUVL&`*3_!3tkYwc#Jwyn#|_mFOwG-a?B}W?bG|#Z_vpyT;TI0{JK>e7!sC5?f?1~z>u2Z~JTz#}dr&cXgE%(WkW@u6 z5~oj@{XJQd2Q>X_*ROXZNgZ&`TVA_%&B`ik*z&9rMkEYG^>fp;nIo`{geqK4CACA( z$wU+u7CwLeJUxAgodEjRtByR;m-08Z@Ut zjld&Z|-q?9;5X-IWu{AZUianHYH+WAWqg0CC^J!9n)s-(s6TBwuaM z0A!lq3}7?};1m;+;}Gmf`JgZjhC zz*PhSk?6~OS{a_9u`~4ky}*)Jwv_WdZBk&Per6MfuO(O@gj&FsZJyE&Hmfg?v2uRo zq(lq-$0;x3$%g0+0@UWBv$Hc3w~XUJ0Xq4d{sU=;W`IPrw6rd| z3~Q_;WMu_^?99+jmzb#b#NXKO&bl;#O;26PA;x~I4*AbBjS61MESjP*wS>b|&avU) z?WR*UnSe&lRPrjI8F6Mmd2(xI%nxXHNlEP4-)d`X0koHi`y8C|ojJ?%)LzQTK}BMJ ztwZu(u1YRx4|ZABliFK~6y150Zp|@BzLfG=G$ltgG&C5h?aTqvT1Qv+G5pCh+Y4ke zndBC3dh_PZq0VN(5B6s++e42zC^XMf$4xOCO#e!3siLg0UyN^d!P>XcVjFc;RaKn& zt{X2e?^+T-QGkN;r_9dIZY_^^&9$=K4gnMpM7%B~a+H#gfG7p`iYjx)yfbBNc8M`W zBF*c3#lK8(P^JakX2c|B{YBnk^9j(vjK{gT5S{#7t@}xd&z?QY%*=G^$*#xtl1fUX z6mp+D5r$q)GPkpXh*M#nL|Q1SHP>IM^dmS%qqrghxS|TVVn(=L1LVcmc={KD(40|T z69!rnC{iV=X;}ggT5@u7Pnhz>=4$TjtyC>^y?y%@Wdqa?(C}auI`IG#D2SskC$7V% zd`DNK%;OLU(Qs}qJPKy;+xHf_KZkd9qiW(!On0s-mnH-Q!W9r5HvqBz@#E#^)!qxn zJat_UFN%nWn0P0Q3{xa5t3Br1Qx_L;w~ysgkR8xfH_YjLWEo7C6XVR-z!=jg%=lL2 zA2zZSs781E`SAs7bz<}QVT``MzK9-CV}^vXDY2z`A}A;b0Azi_?*qos(Fo6{e&=OD zK>;u#kVs+6A_Hf+`&Lh)NG;%<8Y`aYr5g151HGnjy$j`zkTeH$T60KdCf||Cled27 zIc!+dlict7UP5^BgWpIao<6x$OiT>BkeQVQnO|C}b8KUOutg*iUB@a{#%q$j{kFeO z@9ziYO5|$EJh>Ep2LQf_$;l0dBvw0_jC*~q5!=?t)~4|{vE$vsN0vM0V7f<^pO)rv zSiHWY+9V$Fmy$FXFU>panfu-EJ>@L!VZz|R07qXRr~y0kE-EfAt{7nG@ft6DSeV-W z*S9@tdoHuhv9<`SQhp}2y3sX+!b>dh?cp)*tIV1C|dE~r#N}jZUoSZk%IEdkP#QTXvLL>3{cQY_1y^Z`kE@k7vCN?LZd0i))R&%KWW z0s*wqtc|?d`z8y`7y<;_$(G zzv0mH>)MR2z{I$>xdISJ9WY7(3lM)Se2NW++M}IaU1vYW9mU}6j~Qx-S$ntX%WgN= zpW4HoVs53`{sI7~vw=(Rja27bWN*j`r8?#|iXiS`xY(wJxVU(5a4-!GjWJ)MrmN^c zo|pgku8W^KGiW^5Aao3k;fw%X0z+{DQ16^GVrpt?($a~w5o)(u@QwUIbGnC3be7Hk zO1xMj49I=+Z;LPd)HWT*RN<*VdQT3#+Yyx390NM0l#q}Rp%a);Ev{c58yz+FO=6jt zm;eSU>`75;Y8o0m9xv*cCTU|(kZWRYJ`DYz!JR2L+_CwzX@m|ua8DSAPf@2ZF;<5& z>}9-o@uH?iX(z!qEbWm3n>3I#r|wJ?y6woq` zi{qA!aB;?9?!h<;%K4J#|J#uXksT7ow5rae!}=#-@`w{uw6_JLg5%barn&bI@zP%# zb4c7`7q`#9IyL0+4{zG(F;%nu2k_JgVX`n;P8gjBiGnhrOr4P=9WC~aC{8mT!>1*E d{%@M^f^=T)awpZMR{?(yflvqoI0a@K_J7ma=|cbj diff --git a/Telegram/Resources/icons/voice_lock/record_lock_top.png b/Telegram/Resources/icons/voice_lock/record_lock_top.png index a4ee1db1b2904f7416c09930e3e4923960849cae..198615edf623f82cc3e3defae843d4e81c168d06 100644 GIT binary patch literal 2386 zcmb_ddr%a09N!B>e4qqA0Ci?v#1P%v$36CL^ANZrx#)bH=cuU2-tO;UgS$O;7dRXr zK;|oKlu1Ou!x4+-# z_kDc6pWpYlVa}|X2}9F{A_$V;EO5BtI}<*m;-7;5!AaL{Ac$_VXt&SF6(lb}-81vh zOv-AtOo6y=zNCZ>s+BTGDk(X2dg_#+a*cca`bKxjjPCXm?OjORhEF;Ny{gk?qt(T$ zinC(key!eA|FmP-7lXEU=A>PEUZ0tzJGZ*-d}hWW%l7*Ev&gA4dSVK)(Qrn0AiwEA z$BhKEdTzmH!{(K-r+Nz4y}Q5eyfg83a^m6mYeUdG!wOcit*1ZMnFoK=tb4D}JvHYx zZRs8B83Wo%w`Qgk6%}P!+O9maH!F6*n3g>~W2T0BGJ403zA`qWc~2YidEPgJ;~ElQ z-kJCr^5Vfyg`G6g8MEe=HF5mWMsI1NH-7VyB2OHk>D1(&^sd(Qj`mB(w$|5Q9huj) zr=^~&9}yGtmuF4uj)^fZ)n&&VKWatVPt3lX%9@uz;)VnZ7AXiaELnSW$d1=i5G3xb z=vk;PbiKgxl3&khbm~L?AVed`^ckTb$Cm>Y^@36{V8ebmd<;WH!G!=N`=@Ud>ka}i(g%Z8Py zYLF#}nwlDYjX^KTr3BQ5l^|(?rg4bCmD+&Hh46rq-fzJH6kZmCswf3e%_8TOs#F^W zBkfDUAB>s}C=r?=vI(+w8E~p3 zdnBo%zfg1fLq_d(tuPie)5Y;(K+9vwJ-dK|Q-KYmNt(nd3a5-7%EZzJmNrk-YDo4$ zU6LUBY6n2g9?HlX8J05K2Zg#3IF~A{rJD%kkR5uwlHW2q0jQY4HJkJeKFk{+coXjXpvoeclzx zr`B*X$SsB3^;hT7N+ceZfx=ZkI(`e|h1?qeP63X?jYa{MLGbc8!;y@aw^&U)!}aGs zAbzM^sBjwgr+(!=ar_a2_%bd~3Shq_uqV&x@f6w@>c2A@a2EgPj0mmQ_6d#{sKxNw zJ$)ILG&}{Hy*7;AGVmjMPk{g=ONO^l?uXqM;SF}n>B#jIoV?n6c!VQo)Y|XeO|8|* z#g2WuZu*LSX-Tgy_z~IYAFXb@JQDkAQ)g@8`^)TCx_;~Gv`)MJZD`3z(3s&&I$4>r zc7N*h72OB@i<>igk6dTBNiAT}{P~wH=O)JIq%@4q+@1N>^&PPtiNlY)dC>oCx6*_h z-{R={?#`;Yqb4OZq1);XiD~Xw_fna!+}d@k{9*iI{o_PS`Y)UA zzMZsZ``xb_cI{p~ymQus?0xHh%F&(muF$s_T@y~8y|86x-8FmT8+_Ziz^?h@W9w6I yPA=*!R7~xYi&n?}eza!kmvy0(xbB~W2Hl_87Y?p_Wp9Uehdc9UIo`=DY4{s<@Fg$+ literal 359 zcmV-t0hs=YP)tE!@dz$z?!t-!LZ zVHoI8un=pXEwF8S-*-9zti|%j1)8R<>q@7wZ5tH#3M5HtnubmW3W)xVh@yzN0MYFN z!!U>=(A|hA%aUgVbbkv>(h>Cz30avy76x-*8si*(|002ovPDHLk FV1ig0m5l%Z diff --git a/Telegram/Resources/icons/voice_lock/record_lock_top@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_top@2x.png index b578b31059c3132800c1fc8c2b856a61b9f07b8c..06ce004e2eb557d9b593ccfb9abb3229eb72a8c5 100644 GIT binary patch literal 2685 zcmb_e3se(V8lD(Ilv?AWpy=@oK{SAq%uL?1f;<93Ymj0NTBU3zGdE#0$;8P3L2U&D zwTeBi6%eMA21W+$_}4WK#m zQ&0tAG#X+drh~CsNsqWv06()@Pk$I6J5DIh*|@PP$2za0`Bd{+B>3g_)}U7;l0>v9 z|E2smY4F!YTgoPyesLkFt~DvL`x&JoPV%Rt+M?!9nMZXQrc#+!i;Yo>ZOiPXA_s{0U zCp{nyHzF+IV2@+FV#?B`OXCcCzL@=+xY3KlYwLT$<9$6bHzFo~5fM{UzXv&=a%D_# zxvX-RtP@%O&d2O75^0rI+&9XmAFZ-EWVVp4c}p$9fF!4e^+ccD5q-S5``Grfvg;F4 z&eqqK(Pd9drQca9cI=Fjt}9KHo;YemnolkK*HlWE2ZU666DRDLD2|tK{eaL|l;|AFSUJ zb2ggF3jozBbr?fva8z$p5;|O?CFYGbZ6-iy|x(WxJu&xFlH;O(3fE{tp{1JRnzRHoZ2(ryN1niAxYASK7K6t31XFijms z>Tx}$vC~G3wy`)y5;~ex>+FCAy5apMo++f zM#EwTk}zUMgHD4Rbp#D)g*3dSLHqtbvG1wSr2L~9d z)#zxWnj{Q_(J%c0HBlLy7uLCC=rAn;D~Co-4jm8>MKk_`ksxRMiU6#9XqtNrA3w_T z(Ej2A0F6FDB!k|)yj?7&1(583+znUf$x2lHPzGMQ=*jUD8lxU3Gz6yAYY9xFXGqMT z*Q+s7M*zFQrX~R%$bUrqP`PT@oG20>R_;%Ze~2KafOb0o?6)fUPtWMbDReN@|ITQ{ zS^Pg|r1JOLLBXj;YH?ugk-iK|Ixq#By?+?EW#CKTo&s)2mH=;|?kuScK}MmOresUO zpYPN)ZE8uHR9Pr%^sKW+m!~;%)_sJ-c{GgaQTvwrF8Yk2STeKQyLRmjd9T+peR-uwQ3?(&A{e|~fQ zrf;sY_RPZh_x`a)_p29_wL6+xbM~I=|ElfU-(NcRW@6?1zo9cWOuS_L!zJgD&t6|J z>&Csh;P9&ENl&%x6MuU+&uy6Q)=m3kG9G%(6BV>Pt~&Cm;I2D0LU>;D#4Bl;%NlYc zHU~{s99q{IlHJ++Wr3z;B?(`{ zEo-Q8by*^#rc|8V{BGBs_1ml`Ca0uW^DgA3T%P7V-B)#NljY}^y!?g-YfsUN_SpM< zQ^Q*hZxJI(g6~$pRo>&M_eI7>bthFbGaD_<)a};GVc`94%6M|sx79PG4P&=7$Uf+Q zn48qr)>ymd+`O<3esxp+8`&HC>z_U@J>FKozVmiUeQCFG+TA5r(|12NC;VFCD?y`@ z9*(}!E1sK@yt;g5|LlElo!=z;HG&Jm;k^8}KkdjpbjkU<AX+4>A`0fE$SBh$H#Z8d{WL;MqzdC5|^WT-cao_)UCo?U} Jv_HjK{%@hnyEOm+ literal 661 zcmV;G0&4w*pV`XP{wtNUa08PqD zO2(d=1vad(QFfxF49bGvd)HHMjr8)&YyO?{Tw62e-1jv)&dm7w`sEjEh(scSAe75x zNs@-c;e0;dZnwwdk%+F>>wdpqtyZ(y3?JbD9EO8&3M1u%!C)?ztJmx6^_p~n6B~^N z2EkCy>+kpbMNw1~<#xN#hq&MG7!bo^a0e(zCX={;m&=6=1A}7*2bQ zDPSU5mNBV~6okX!R;y*2f(IR#8UZX;;C8!9rPBF)CQCs8#6TEpEW~25@pw$O0b%0t zxY-u6*{rInWHS&6!Aw%%a=EJ2D%qG?t%i`sTJU&0olb{r4?-fUQ5JkY-(WBxhk&RE zY{+JDI-QcEK;RE=L%m+_Xfz@Rg4hWEvDLBL?UG}`e)ltLr_FyO-T@W<*8=W;i3#%g zynYLbMB;Qhu^|FQP)Fy|Fquqi8(}3{F#M7AaFPw*b{+LsP+R9&1RDr z1=ZfQkWQy9dCFqivz&LIYss=qOw;f8Ut36}QVf3`{(dP?^MwdWl86IP^TjQup->sK;h|Olh zn+Q~Xv=EI(iAzviBSH`aK1HCoMnt7j;Zp>P^SHBqz1McoE3D6g;u;Z)#ez=}%jHrd vVz=AzDFVecBEJ9c$RVn#YD5snd>i-!9z0f8tbs7&00000NkvXXu0mjfRwXgS diff --git a/Telegram/Resources/icons/voice_lock/record_lock_top@3x.png b/Telegram/Resources/icons/voice_lock/record_lock_top@3x.png index c8d63fe3c127d7f74b84fa49489a09a67afb148e..00998824e402f025196a0a209a96062b0772c13a 100644 GIT binary patch literal 2995 zcmb_e30MoRJ>`^e2|547rDpf(McoZnrYC&DRid@wqx>8wf?SyNsYu!G5HqSH3KmY&!-|>F$|NY5{ zj0hexYQiW00LFxdD5BWkWcKm0vt+-yjh!z5z+wg!5D@84=+z_`6+8=cgXMCWI{>&f z;&E|^fmv=O$Hv87Xz+9&WlW07&d!gD^KCqR?sPq1n{%<&roh6&2TY7ji}kRweVVv2 z^KHeF?`%qH{U+R=&2#gxxSXER?B;q@R+5={3ApezkM9oT3ct1}58PZ{bAJq&_(4da zuyD2Yg|@H_pB>I<4(0SZa!%NFj0B&J4q1&BH=MPQjQFz1;`6X5PrqJ7);><K9`zFoaFGxMIqtonW1GOz=a1__; zc$k?^Ub4=>qFK6qlMNWYjATGH8BfvP-0l@=FlfAk9c2Rb0p5}AGeh9P7n>$dyg~~`UKyM^LF;4>F zut)$(lpkST>`uw;lfO&hYHAWgWDGBA{$2F(^RwSEQT&1EAU zNI_>9HcOiZX<`ZECu0Ua%oCVX>IV||FwU^Tn9%Q>!1-hXsUvB|#A4xLtU;@1^d_zT zcT5j2|Hc7Jtx`GcTq*4r8;XbXYveH_-81aF|Op zK%byDvX0s4gfGvBvdzS(*H8)U!mCZCKU$>{4W&&CM&o3t!kfzy#G@zzl}jXuT7U>4 z2_n-#B7~GdazrG85JEsm5uun}k`3)w=lsb>6H*{_kaA!V=#czX%h;a;Z=N$)yrC zBoc~9NDkvTt6~8nmE&q$jFCh6zY;&ITp@}GP?2y*xqmwTL4t5CM#qzEzvXlPd`5pv zp#!1*cSf(A#s70gd~>fI5FGziE%vXy)R)W!RVDdy1r4vW)C4J*(`TE7+2-@kEQp@{gNunkMeA)XZb6RGfh&h zqayEiw2l92%W<ou1n9?k>8kzC$M5bqi7dSm|5PzH4Q7$5*5LIoAH1 ztSNRu`JtTYp_{9luU+d&J#|<5(08Z)$c+b42UX8MiaIDb7b2Rv;mq3`gYez=ySrC< zO}YBlt|zH;=FCa=Nl8mfOHV)0_-$@udmVwWH&#fvWW>XTUS2yR@IB)SEs9v*6SaJA8yuEV273+jj2BkdRga0HY^?gV8R!knz5X&n>Tm$ZO_fi%e$mK z8*XD6)Lcr1Nu<)LQ>T_t{tt`ST!PMj-=nFx7;ayhk>k?zsI$|(f>T^@$LVlcS?uD) za9w67wlwJT`^HL|ri%k>t_W&s|I(X!%E7^*{&L)!;pJ@S`g(fc7Wc5tS#;jGqzkTytdn~ zZmZ>i)Vc01Z#n1H+I0UkCiFy05m`C^(*_NE^5jV`nG8JS8@*)7xWJy?-bCB+lkGRu z-FtNX`UL78GRgkfv16ZJ?aB(I@2)jgPKusl)#|tDyL@|VOFo}ZANyNY!QNZl!Q4%$ zr^SdKgWCg@>ck6l_>Ak(7v^+mz#ug%)+C75G4coByRu1BU?;BbD zWa3l1goK2`_XC*|i?=SKyK5Rdw4&KX^8l)Pa@6C%^J=)fwBXj2P1>Cb)6yfL-Oh;M zx}*i$N@5Ss%;i4onM_3lg>6<8&9hp;k9P+ys;l=0e6pU!{V;udg>ceaw#?W=>+Tpj81%)n??+VGJ$L@F T>SDI}Phx0bgraO#+?rnjA36TP7#7YZ8ft86SG`3hmjV3fGYyq*-#9k@DL|UcS zfpPVX*qu2?V3}QEdbhW?4-XIX z`TW(@RlQzswOZ|V`}Os;-|w>@;0t_`IEuuYX=X=zE4q~UPL3&Q!`A4`T6&ZPqso-qYIT+p!C;Uc7Cast5Ic2@i;D|##{-C+`ZGoE@9!jz2N3IWxu%GbPN&HlKcIJt z7&`f!omqk2@0N%}BIJuV-~D`Tu~;NoyeXARlS|m`_D-imvUt;OwscW5nZeawHN69~tZww9CZk^?Ia6JpF!O gCI)%YieCx70KdJ*Y!AUGEC2ui07*qoM6N<$f)m-hCIA2c diff --git a/Telegram/Resources/icons/voice_lock/record_lock_top_shadow.png b/Telegram/Resources/icons/voice_lock/record_lock_top_shadow.png index 30d0aa4ec92cf5805f4a3e12dcab5c0686d967f2..69bf37e51ebe9971eae3925dd27da499ff1fab55 100644 GIT binary patch literal 2629 zcmb_e4^R_l7T*9;q(wuuf=<0#!+;=c_HROVk06j>lhYuksZz=yo83)FNwRUXB*9TD zC_O6P$*5<6de!#S+VR@+UpsSUtfErTQ>b-%Q|`_gJO0;0>r8d-9A6syH-2Tu%)^rSfOPj0)`3PGwYkI7VC%!^I| zw!csUXX7+&$bq2j0j{Qo2*}GM;n|v+zSFrm$x_(f(z4!OGk4&~XGcy!iEo_fneZEx zst^uUud1FCpZL$vuNza%OaC&Vqo-)b`Cn?Y=cvATZS_~#nI9QC8XM0*ef?TA2U}+~56=6gafV(nhweOz68j|5tnexcV!r`tB5USJKsq@bJ@?l}!8T<0}1AN4Kc< z&9~6Mk0a}2vK&fYmEVac>NcU+#98@Zf$CGEM3X_;Gg?1H{5 z`TJ9eP3q?D>a)xR`A^q7-D+o2TUDhaQ6PxvQ!ZzmYR~9Ca{ln< z#>PvNOHS>0vyp9_6d(W4v9`T)R{WaPh4IHerJ*CAExbRS(N}@QO$=CG4nok=QOSeC&(-u@p=70CSuf#*k!<65u+M-L`AMM zYDVi0LjmIDlmhQncPnAD1r8wK2;QH($d0s$IGu)#005yT(N7_(p&f}G?D$R5!T zD;8O&ST7qj0O@E5zCg^ZKNw9DNEjMn11PS=l#oUgc`l|GsF%DW&Up?Myn;{g%R!)v z$8-a=qAUh$#qTj4TmFs%kXoB9=Hszge7=~Apj;XPVMG)1SnZ&rAt0c3At=^MoKPA9 zFf){B0*px#SXq=DqUaqfRQVWW*kn=)V}P@5Ea&knapXL*OE9ytVAK#8fgw1K;FJT` zF$BrrWUf*}Y*f`I@*Y>iI90s^rx+c^P~TMryy01y{Xu1(V_c%-V?op&AL|y-fZwfw zW4UBZqF0oFV}Oo)RBi>$L=s&dFIX7gR7TL+Y>d?(lvzI~Sj|QaNRZa!;hB8Qh3ROL zMGS;aKqx0KAWqi6BfP;y8c0H*C~h=bu-`0l^$HD?{i7#>7dc?#zvx^7N9s5yjp%i} z4#7#vi5OVgiR2sf6z?KwlB0<+Gz%pUIFzjSQL73n9$3Ulo~H9T62bBfG(wSNK0@m- z4l&Ra#&eVl3{s)~2%8v-CkX1?5IsyU3d^E1uLm9>5W{lH!7yq#g%JX;iHbds$;XfK z9NizT6@bwvkYv<*P;|**RuYQcAa`Tcc`_5x?~5SFhMtU{GdP8O%)lW$pN}Jyo~00$ zr3r)}aGs(KoJ-He;va_}FqdRVjM3@GNdA-YM-#-=vVOM!`Yo#Y=@I=nhK@r0--yPY z#s70esM2dk35Sm7;>g+~eHoN=LGL79b;3alX34V|)3EV;(BWsGlH^Nn`x!7Up z9oll})wtrR%@xGLFZ&kj!@XQaCXtrcP=%&UGP>>WwE+1 zm`BW=wl3}ZU|Y?^l1@_S$?ScQ1xpmrI%jWeeCPK zRob-Hp4*WXlk1t^QG@lSgNx2vvNjER_1gxP?D_JV{K=rVWs$p7B}_fAwmGjiA-C@M z?b@r!h;^s^PgNh<-(NrP_d}sQ&UKl2Je^g+?tX7YrY&pJ*AF@Efx!jm;4NI!bFc5~ zSkPJW&gWkJl0Cou0Z--Yh*AziQ_PZwO?Qqw*-l|CxHDx!kGc(_9 z+Hjm(o%BV~nXUylm#&!gvv_k`*}w1Zd{7Fr_YzWNmfJgHduY_XP>nLjM4Y5EVP Cs+aiy literal 557 zcmV+|0@D47P)U{XjV-7ROaSgcekb-P`| zF!uZX`Fs{nfG~ndA(3=Qyk4*54@M=mTCLS;W!tuR3nY>b2~^TPVK5lX=kvW@Z?oBm zkFniup@I^zJjNbcwNQ@H zXoU2GyRBBMt=DVy<`6~t-kryI*l09%yPbM-h$4-Fo8}l_IXH}B>dhgFGzM;(W7=kj vy_y`=08ykdaMK+B0ZiNCpMW$5F8%xfX+W6Sp^+#300000NkvXXu0mjfZC(2- diff --git a/Telegram/Resources/icons/voice_lock/record_lock_top_shadow@2x.png b/Telegram/Resources/icons/voice_lock/record_lock_top_shadow@2x.png index bc3943e4e0ece9ac5c056b06a8539e59a809f2de..f35d3240c73224c4af30c2b26125243ca3f99e2a 100644 GIT binary patch literal 3380 zcmb_f3se)=79HeMM2SJE2E^wyiUeqq$v+tZMG^~QDnhJCEi#!*AOcB9h6Jd}M+EsO zpeUb*MLwcv1+`VAl#dl$rh?SkmR4y+s}xWawOD5AJ7oKCEUZ( z=CNT%s)KmLSJovROP%{xFlZhYpC_k$O>;ddEJ;nh1f0LfV7dd@oQoDGB)d+WxnmC+ zR{Q63@;6(Z?+?s8R-f{90Qu1@a+B@ARPg6%{+r?Au67IlE1&GOI35`6x%3fR_`@98 zY^)}-kVXj$3-b`x+;ltOVYPO4QFZ@p&xC&0ALhJzbB^oY>Kfq7WxcQ1q>;DnCtn6O ze)ySUKO5+8Ac96ql;t`+7K}zHj~Y!6BcxRq8djM=xY6f=N`nE9@VL|xJsi2%^nfC zCLZ^u5=h5Wh}BNIRqH2cA_!w9AX+Ai0hv=8GgP3HcG`Gd%vf**%EV%@SWJ!U30u~r ztu{)7YxGf?KX5&{{F?v-wNmM1jNio)8#@_7k4p?h8WV*4uC-p4sKuDUm|hdFLotbg zfVse&jTZLPVF<3#$uydn=YE^*EwNu>i3*l^}?r zQYm0Af*?Y+kVO}W zI)umN@OcKy_!)e2#<1v z&4+jpm#$P2ez1lJ+IE6E0Os}8R!wi zpX2920tDj$2 z8ilAMF{0lxseeDCzoyV}sQ;bOOK0)_oDtL9YsUr0d|8WQYcKR=qNHO}M6)+PPQGP` zFO%;nOihrbBi=%rn{~|qVAT^K_LW6_H(Y88m0uT(~`Drb!mkk%LYd*!>26X>TgBHZArhC} zF*IkJw7QS2R$fV=6m%zdToaIXyZFLWocmk9+=!vh$BtjPrGxh!IB)>NFo{HBXnNY+ zFk+wFI{M&&E-^7NDJjWdFyJ_zkns5aeP28(*W&m?!RVG*`T3jO85EMI+osoPw1GQ! z-mkB(udNMOwW?=y|G|R?o12^W?c3MY)ph%JbVf$|?i)Y6^SJn?zcpz|o^5uQ@+^MGg<|58R}Ot=3;p-{Bn z9__z%OY(6`?|0u_?CPS^>93upETVRFpN5=F*H?LBUo0estLqM8@&~KNgJ?orB&%iAAPiK+csjYu+Xt;@jG6X6%}W`zh72X z*4x{A=+xkqBW1P?B1bkj#l>VY(Uz8!lr%Ork}@q_RdI2vzd86z%1DrxvrXG{iskMI zO6S%BXwTup;mg0u4`FwZrn%UjqcfQe4GnXD^?SmqKXKv&L9@~L(~~FbPV#!fzY$N* z_t;lcQ?qB!t2no{X>mb$`4R|9X&D?E@;E;)?L6>#&E>0Cb5>^Q{$amZ=$|&=!C)}* z_u#RO>7?!T1Dk5)DH)lWZfEMjElo{+v?tRlL8o=`msanX_u16eryQy) zPar5SFP}eu{=*A!bDX0in2bBmolBId%EMrF;qABIodTwRS+O z9mIQ6=Au7JLJa{mWS8sRVIK-UfBkS&;gOKcv#CCxxTN&i_9DLRmsfWIYMO|V|*LAeRNV*id3fGbs{NR)oU(AUAy1 t7ak9H-Pq>$NLglTFALuMT2arl-P1gO{Pyf4^Zz3tK=P)zZdpXyzX2XZ5N?8^)3?p^4gz814<v^vDJFX&q2Dy2f zT8gq*y+j?&T!q_XB0Pc(mf~6iaPw62Wr|6;tVpR+8NctZQAn*-B@G_?!HyeaW9_!Z zw&>&3Jj;sqs|9+7`^%=~84pHmiO;+ZVXJTa*>TJnX2Zn85iEQ3r^hast<}V!yAnM; zJ+EB3LLduFo{JwYKUT7(7Y{4EO-!(#sIE^s;F%7FoC&5HQ4> zILlx#Mnch#;!3%Bb8u;^qM~AXd3kbjvbjZ6SU7yKKf5c1|8s2;7k|U; zVX1j!pL(Jag5c;UD3X(1ZQKg>B@+9#6Jl6zo~H0g%b8nPSX5S4Iy*b->FMq6?j|QE z_w@9Xm6gT4rsw43R6EtWw_Zz&tN+O*fk)zf$1-l1F?O>;#qi3&2pgW6F;G-g#Nlx2 z>Yo+|sL=OYOD{!5MVo@=s%mO#s;m8de0%}|0(^aa{r#iAu1OfWy1Evgj@<)n`!Z#I z4)_Q?B*ne}@peCJYI4%1ai7iZ>g*H}5(4m>4JK+XwZ(qdPVn*bTU}dQTUl9QG9{&? z927;my1GhAN-i5RQut-QPfRM+XOE6OeKX3k!vYarbF7T7G_hK>_>g`pgdN?Z5d= z0QhzGUM_FIWsS4C{h{feJV|$ZbTJ_}3~299fcuRQIgi6PaPXV<;>TU4-oA^s*sNpSf$5EyCr{FL<5`vsC*%IvXMf+h^4Hc}LvpZ8jbJ>Y8>w{alr%Zj z44vqtLDKy&)ju?_*%G}Igc{W#zM5?gP0FVf&oA_4iUeO6Z|E66ec<2GAr4S^$xqb+ z2C{ydK+>Yk9{^Y5^T*dtj*hq|qYFTJb?bQKW>wy>ZjSnCgR-=NtZ8z^2v3Hz0cpN7 z8OQ}GB7&cn*OuVO{F6N~ZYtE7m4Yvo6Q43!bHosPT2a)2uWM6DFwO{f6RdlCk%IMw z3-pjP--oMG)D~rhDq-f@?~Rc}Cq>fZ@Do%zonBkZ8Ywxu;*2NnZv6`3(mDe#LG|(- z#+ZFOQUCJhCXwv3+Fp^L85SPSnrrJLj%`&`cJsaHH$MrBkB5xmUiHorZwB|` zIptiwPdy@%E9`ag;PuSu{YhEzsZ=hoKuA1CV2jZ2E*)LTf;6w3`sEM9_a38f)sPrZ zl9|#Cg8UrFcc5b@;$P;Jp3EhG8OapymQa$&@cFE%B7N%*af2)Jzd(&Tz&x5DEm|ER zXJfKHNg`)(@7)Us=XrC5Kc3ogi5-F1tO3QRSy-njJ>3Z0Fgzab?Tz9$x{pIFO{*T$Erp55u1`H!;hl*P5g$+@_d`yMbiIzYc4K{gaZGt>>7Bg1ztep_w%#i> z{7pr7eSN(M3`5l+vD>5E6F@<#%E`%Lv6*)IYHCH4f@|}9@I#a}J8Z$lIY2|GRH`x> zt*@`&+uQ3+B<2da9FpV7(KdFj&&D(_ltki#!%LuJ493@;ozJlfS*2+1BMg=Pc(vO0<;r>FICIJtTz%3U8V9q-8j$GxM(;9-F;UcJHqN+>U1QdL#e)Rgy& zLjG#G-HH)Jz|zy0GbUR#k>VE=WL1`)o<8*Yb!~0!$J;m!jgmXd8~}L<$0R~F#i(>Q zhx6xOUTPz$OZ=XjJ5pC!By@M~_}vu{RCuzx+pOb-hh^iaW8}`pOcQIY(mrBimb|?> zo`>~NU>tXFfgXADP8j+GP6MQ!N)=W`>FDeL1sVMIS;77Lj_0Bdd4uqsHnyo9%AUEW z9k})7OL|&bOW4x6@Nf;bLnj&St$ep55z(BI!gXMzu0uJtvA4|x(gmnu&e{X?Q_+Kf z+!VuCPst(>h=+7K1OoYcbQG zA+6jT#WwOG?b<-Q1)2nfLUDC*i3FX0R(9*Q=np5gIEY?eF9gISC4rCA3`Vc3*_P^m n0v-ZHM7p_20Y^MXj6B!7#)Y;coQC*r-Nlcnc$lf>g;-@M%fylm8;$mX+x&I2=G|w zUi89%fB+lXv3{!`Y&16__7@K#R>lol4w;(tn_4Q1kHK!Ux@T)A8>JpLx&`~DvW%E@g8=jkr2FfdRhSAdzThJZRCZvbbE=Q@d9#ADGK(F5WK(9Fqn3`$aj-$6UURm6GvbH zHJ#YFh$zS!26J$VivoCIpbW_cLq(BH)M!mD3Mt|~|JIXNO7;uw&F2v7zf<08T%rHnWxYRWGIdRGtQP{=70Sr`-bseYR{-bfd*1VmD> zR1A+m#v|?MSOOJKrVwnAL_EO`ho|81R1BWLz|$B+0&@C7LD3|90mFyAdO8;L#Y72Z zvM2@)7aJRkjU{2ll29C^4LS}_#1V-Y$O0pckCXv%m`JI`XAf*p%9DtqWFm1SQtc7o zinq#`CHXs9;C?cMS z#}Eh@0@;^9VGv0SGJU1GhWJmW95G)ci2uTr>PsLqC$`o1-hv6P6l>w1F z(4EahK?Gq%B0htRx1-o`Ndyd)jHh78L=qnZ2)F=7KqFHHG_D==06wp0i+NksGElGo zv?KUp9^~U6=(qrd$F;K~VJI{n8AGL$K@1n*0T>#WLL$;A1Ui8$`0Q=1Lio45asMs@DG>A5_(75#nLq}~7(ACu!jSQNK88ktn4^GXIvs?{LdWwy=l=zMNV%jb zC_O^?A5O`rwsHlb54Pg5LpuF6cR}Wt6;F%E$(b*-?+|^0~|NspoPDP`%3c6_o=p| zQqS9XTwh?lqdcPy?Lx>ptfNr6>do2M-hyz(ezYm^!zd80Y}cDt9qUr%k4kNmk1l6b z6n|Iv!0AC%LwxHmh0;;xtAPW*KavPmk2X8hzIPg;o4*ZE=0aEd_JDahnw|^xxTPsI z1PeG?sjX=ZfN?&}-u>nVv-G^Z!UZ4ps)m0U$utRQc{S8(4!4a_d<>~rM+kM(O}xB3 zneEhYp>)>nL3!yi3w=FcIhbD76YbTr>lCyL>up$BU+mM<|R864y2|3bsP=}3+y zBT_OoEbRBJ7Kub42)epz?HU*uh>wql&enU|Q*TYaJ6T;_eed4Ax1$eh<6jMqbXCz3 zKfcQyy0YX2w%J_giF;y>(S5wGmD!lrr0%+QCAXx_U>rzD82>qH-6Z0 zxP0-GJExuu57VL_XN>*B05ewCh{9xm`)+nqaz zf~P|^YYhzyf-WYEUEhzR+GroFxa~mtUSo~%t{{inojHgIYxKMAN8Z?P8-Lx?6A~1( zG?-I)FZ#RU(o!4MS7`+*RgQs1p_OK3bIOz1SMSR8ZPF0lw}1OBur_hx-HX1wyga{I zdgf=!%I3MY_4hAlP{-|W!aaJ89xSdXfg9gEDn%aA@}iv%tS&!!@?=d-&AD?O9`j91 zOwLwS4Gj)nN*K$uZ7VT)S!a8`PQkM+K=fe>{Nm%^-zcz~yOdEJR_3I9MX`^0+Hvt) zJMH@qYqxI<#6GXH&8*{Fa2*Yf;U4!~PN|B#-+3;&XkR6Z($(3?pisu1-M9DmdwngN zwuyu{!vHH3410UrmP^^+2`#Iin;4oqX6L^dxW4~yStX}ck7pxt5hIw_T^mmaIy(z7 z7DvkO{{%Etgtq>cTSB^}@0p4Kl!b-&A3ogebv+=a^FsV9C1&$MhxlY5FId2kzu53& zknx^*1kOq)r{CXa*(OG$*vVXy0sDC5EY z{=-*RfgmPqpHhipI>tR;HAoV>h?sSJIRBC@yS2Nkt0wzmBfhIrbgLnK?tMR4yVjsX z;bl*LNZcm z^-RAOSI`X+)_Q=>6W(K?jf@`QACVGGd@C9hZ{~YleLFS=>BP);c_4aGdRm&oGIQXFa ziZCVyk#eB*kQ46Xz94dESKLzr2V!PIDcf-+3u+8@l?v9TmO}eqWV~W4>1NCG@D<%9 zVWU>zA`u~+|4l@MYldo~T%QV+W}{}ip-WLw5!}B$DR<6g=ZxWu7jU9$Rkd_st;o~{ zjTVN5k$exN8ZGnIT*xCb4ZjaEx%O^R(N1=Ue_;alb*1Fga87%DSy@^1jeLrBTBcFS#c$sd-?w2Zk`k(&x0HE2D2MuCj`!X6 zqOTtxvcKpWQ1<%yGd$YKX?yEIrgqACBdq4mIYnQkzo?88a5$V}`LUZHS_oMIna#82 zt)tWQm#TmsKb`!P1{7D?i)Re6%*@Ov6pD7z=4C>ir8|G!;HOXXU7{TJThw1zT3Y(C zJ8l`6dU%=46My?mi4MCtUD?0dU~Ft`#rOk78=)$*w`hc5`Eo!zelfgxE`D+~G1I?& z%|4+yv>m@;jdFAf+ko+t?D`*zi?1}BCm>kqFkR8(ALl>b!bUG3M!&Vt^p4A76s})i zsgDn;^NqZBBH+$-EG2!!m`nSADG3SDcYTi7im{s2vr0Jh}Wod%V8M()~lYnG&N l{`>Wd(?v2>!ffpcJ=we;SZ|9{>Ly1RO@PqRXj{|+!IjSm0- literal 4604 zcmZWt1z1yU8y=$@X+{c2cZ^bU$Out7BnAWNZVBm@P?1(Cf#CopMN*Jf8U*PM=`Q&X ze*WM0$9A2a>)PJ;JaOOe{XEZN9%!l%6F>++AP})C9HtH2vw%wz4-9-7^R3DPH+)yP zkp~DQz;=CM_!h`{17Fg4DjRz0xIFUov2eErDOkB!TC=D+S=d->TU%K9xpi6J27zwG zsKOL}3wz6I-4@%b5eon&v ztD&wk`L7=+y^>MAjol7rmt$A2)&3mh$k~|r%L^&e&gV`)s-1@3KKiLcGgU){NL=X5{SJSZL4H$KWQg2pfV-uGDI@t0qvLc?H)7=-fFTZWc<#=6BX zUUVI8{&-HSie}+R>+S8u6I66P*_n%;o1dRwUXDbRH5^SGtPNp;!J(m{%f0EMF4Ofj zGeI(9Vh7ni(?$obyH|uX0z~-uM&$?$(j8jTz6|lW|1u|;pd5Yn&HmTBb5v^nzOOLaiP` zSMW&YB}tZSiJFPxTiA)7H|s%o^>m3h6T>+!3GtKUF}PxqSJNfka%*a8Uca8%C~J~^ z^6TPw=E;+_!GfaDAUG8>bCcJ`D5H$`Pr#>{#y|i|N#7lWnp$@1DcXw^=!RZEnzyCoqdO`9dB{q@)Z``kEtk0jGNf>ZvCs02)~`J_-MTK9F_|`adY(w+fN62~&lYsF%c^ zHCgcz^UDR$CePcd6x-yfBnIt%p()fzC#R)_dtnI(S9)(6E84Go%Wh*8PJQ+2ReE|1 zG&;%=@U642PmqtV-fKhO&24w{M~%9g+SY|z%FCB~Q5RI#MA#<~+>_2Mr=tJBpUJjU zYWfN4G|`sB@Dw90L^v(nA}uO8Ew&}?>Fywp#A8Kg4Xs<|s+*Kjr*o~*#jWGx>5uPP z^>-%Wu(7dmhcr($_)FM{^79jKwb5Bb(PCp`D=RC*_4M`iwY0R1jQqBz8_tinug*5g zu1bxsP8ZHLYi1Vv{ASHY=Kqrq(L;szJs6_g#c&Wf?FjNn3#Pl4JUGZ|7=HpB?bPNt zX`9P~(OrbFwe9q@b$0%C(%EOem~VkkNEjCzn_Hy{hktK+a`hhBA+Mp`;Jpc%j1d*9 z^*`PcY`O>yj}AAsJ@OxU{ATLxa!$ML(KQ8cq_Hgj2LoGg$5Yw~(=WJTsJP-5LWHg8 zRfg9w)I_^rfI=+I&NfTVZETz`XS#ge*zc1|jEiGCXO#9_ZHu88h7Ynu4?Lq(L)@?! zi3oU;r)P>tm}N;=c#zNz>TRag_$<`h3fK68=xz5kX}&SpQoMfiJUR}DCTr|HQl(v1 zdo&mW^OgUWR2^x1o5Zv)a(DiVdBE-$iJ1VUx;&j*vbOf_9v=9RSsn%aV_f17l5l+U zKj#MUx=oj;-7&rco1uj<>$5mR+r2EO*(4`Pt&|vSPP#pIc{CcGPI3Fr9l9$Teq&fk z7nkZjz95RjiDy?qMM;F+mMf%=xO*b9Y=<)){n*U=f0i*O9famLe1Imb#j ztn=A+wyCZ5 zZC6p&e8yCT9;mu#TDPQKw#bLajngI)N`}fvQjw7bhtNY|hQsI$I4^;1h#RaKAs zA1CrHC)-u%kxTH))U^UWGuQl{Jf zM~=?U&USWdY|q`r-&$t<$sL#lfk6@tD)2D%;zo%idT)$Ag}Ssw-vETtW4X8G>B-57 zQWVLYVNj54AXcfY8h6dIZv1aHkzzdnVevI%+<4Pm&|7v-UH5c{)#vV`M~{HwF4%CO z?dmEN5njnAoROMpps&v%eD>xGUyK0&Sd2k%Ft#KMn6h@UQPtqJrzZ!GsmUkr&G`8E zvx7CHgTu!9I)sdjjESkyq7`Q%T-4@UWzxG6VbZWa&SDF^2_XqaIFjdfci^HzLqZhF z%o^UI(WWLQk9yPI`|esApXTT114_A|(bH8YzGCsWr`Tfs7-aE>S7~{eT1)$cdI}S! zoSdA@%&(Q>sL^H(XHm>Sl3weSPJun~15I&%I~vUKRfu-uN-)-og@-OL@cc*@+rz_y zNAB`06q_(NHEh=I!bNKC0#vTJ*u87~gK?xmp+JGbcFdM}D;>)d z#(nFuxbW47ii-W69p4RfjXv5qB`qy2IaxuUo0pfZwJ3J<9d9(~4*{?^;24BvbPwqj zbx%!z%@Nt-<1;f?3yE^m^?n{e!?Dzpn$lLnQ~17CjT{HE3c|aK2-TJ5U>TbWb1;Ra z2)B23b`B4F0?p!!F-@K8(S6_ASv%HM|5g(`pPa_FTT`ynS>Z{|R zl_In`kQbGbG7^qY0mj5k<$qXh-Se`tva-aW?B&asvX(Oqhf~5K>ZR$cY0dxMsZ>5{ ztb1zNEeNMWP6r-O0Wn>kAB%cT)p^CuWM}v~WQK>}NK144MhjaoNrB(|;UO1KQw9m6 zG_ZC@hV>8^3kwE=^(Pk=76MXax|z5>Bn(jbI^!*bRu3@i^_^TdVS0`2{h7%JocA)? zI_teRCxBz%vGR?IimHpT45h=kMWS%cMSv35M$iEb193&&j?#$cV7G<#uUS&hot-vD z(Uh1d?k4fWyXCSzQ6MZfAq4`%9WdQ+Rko1>r#h zD!%s}3pSr`2$6kon5jtlOG`Um)S}r(nuEPP<6767UynmDaRtok4NXh}wx{oiYiuyT zL;S@TmLY*IbdD8*F^=cs*x;Z$aQuRTf`GXrQ^G~o`zNhVMI{`?b7(#&5JFi#i!D-w zLE^nBT3($CET5Q|7#T^aB%`9L@;`PakCLWzM2P*N1~83^I$rl_h5hhd#EWuF@;f-!p0lUQ<(nTazE%S6C!Y>}N_6 znE(SR5AlM-7*F;h^cI#H^>ej>7JO^6@IJGM3?nq~sLpGHgjqH#JDcB!1VvtF`X~8x zj*%pcYDUUf_c2?;ql3)4kyg7&&NftoMpx5w^h-T)t*86p1*-l_3%I(#xkWLdtE z?yqWK0OsJ}z{DlVHxY%RO+JbU3k&n|s%Jkqyn_Zv3c{7yNe10aQp&S}zzX<8_tjDt zmzSY{LIRBw0E%`y&WO&f1d$>bePQeWMC=#trW31o-5X3gEF(~>S`t?CP1ix zXZLFMe1hU4i-VtQ?DZJ3`RVU~e`As{fYwY9azaoimM8QEPz6t1w4)O1_3d7;h4KJ;gDBm=MATjt!z|*pxWj0gFp^A>Uh9gG Y;O2AA$#g7k;GY|is .3)) { @@ -1028,7 +1031,7 @@ void VoiceRecordBar::init() { ) | rpl::start_with_next([=](bool show) { const auto to = show ? 1. : 0.; const auto from = show ? 0. : 1.; - const auto duration = st::historyRecordLockShowDuration; + const auto &duration = st::historyRecordLockShowDuration; _lock->show(); auto callback = [=](auto value) { updateLockGeometry(); @@ -1138,7 +1141,7 @@ void VoiceRecordBar::init() { void VoiceRecordBar::activeAnimate(bool active) { const auto to = active ? 1. : 0.; - const auto duration = st::historyRecordVoiceDuration; + const auto &duration = st::historyRecordVoiceDuration; if (_activeAnimation.animating()) { _activeAnimation.change(to, duration); } else { @@ -1154,7 +1157,7 @@ void VoiceRecordBar::activeAnimate(bool active) { void VoiceRecordBar::visibilityAnimate(bool show, Fn &&callback) { const auto to = show ? 1. : 0.; const auto from = show ? 0. : 1.; - const auto duration = st::historyRecordVoiceShowDuration; + const auto &duration = st::historyRecordVoiceShowDuration; auto animationCallback = [=, callback = std::move(callback)](auto value) { if (!_listen) { _level->requestPaintProgress(value); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 8f893aa1c..efdd96ce6 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -325,6 +325,7 @@ historyScheduledToggle: IconButton(historyAttach) { }; iconPosition: point(-1px, -1px); } + historyRecordVoiceFg: historyComposeIconFg; historyRecordVoiceFgOver: historyComposeIconFgOver; historyRecordVoiceFgInactive: attentionButtonFg; @@ -350,7 +351,6 @@ historyRecordMinorAmplitudeRadius: 7px; historyRecordRandomAddition: 8px; historyRecordRadiusDiff: 50px; historyRecordRadiusDiffMin: 10px; -historyRecordLevelMinRadius: 38px; historyRecordLevelMaxRadius: 70px; historyRecordTextStyle: TextStyle(defaultTextStyle) { @@ -381,7 +381,7 @@ historyRecordLockBodyShadow: icon {{ "voice_lock/record_lock_body_shadow", histo historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}; historyRecordLockMargin: margins(4px, 4px, 4px, 4px); historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }}; -historyRecordLockRippleMargin: margins(5px, 5px, 5px, 5px); +historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px); historyRecordDelete: IconButton(historyAttach) { icon: icon {{ "info_media_delete", historyComposeIconFg }}; @@ -392,7 +392,7 @@ historyRecordWaveformBgMargins: margins(5px, 7px, 5px, 7px); historyRecordWaveformBar: 3px; -historyRecordLockPosition: point(7px, 35px); +historyRecordLockPosition: point(1px, 35px); historySilentToggle: IconButton(historyBotKeyboardShow) { icon: icon {{ "send_control_silent_off", historyComposeIconFg }}; From ab38ddc21d83b682df00279d0e0c4b8b074b9d1b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 16 Nov 2020 19:01:37 +0300 Subject: [PATCH 116/370] Added ability to fill send context menu in ComposeControls. --- .../history_view_compose_controls.cpp | 70 +++++++++++++++---- .../controls/history_view_compose_controls.h | 19 ++++- .../view/history_view_replies_section.cpp | 21 +++--- .../view/history_view_replies_section.h | 6 +- .../view/history_view_scheduled_section.cpp | 3 +- 5 files changed, 92 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 61a308db1..eadde1509 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/message_field.h" +#include "chat_helpers/send_context_menu.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_section.h" #include "chat_helpers/tabbed_selector.h" @@ -34,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/controls/history_view_voice_record_bar.h" +#include "history/view/history_view_schedule_box.h" // HistoryView::PrepareScheduleBox #include "history/view/history_view_webpage_preview.h" #include "inline_bots/inline_results_widget.h" #include "inline_bots/inline_bot_result.h" @@ -575,7 +577,8 @@ MessageToEdit FieldHeader::queryToEdit() { ComposeControls::ComposeControls( not_null parent, not_null window, - Mode mode) + Mode mode, + SendMenu::Type sendMenuType) : _parent(parent) , _window(window) , _mode(mode) @@ -609,6 +612,7 @@ ComposeControls::ComposeControls( window, _send, st::historySendSize.height())) +, _sendMenuType(sendMenuType) , _saveDraftTimer([=] { saveDraft(); }) { init(); } @@ -717,19 +721,21 @@ rpl::producer> ComposeControls::keyEvents() const { }); } -rpl::producer<> ComposeControls::sendRequests() const { +rpl::producer ComposeControls::sendRequests() const { auto filter = rpl::filter([=] { const auto type = (_mode == Mode::Normal) ? Ui::SendButton::Type::Send : Ui::SendButton::Type::Schedule; return (_send->type() == type); }); + auto map = rpl::map_to(Api::SendOptions()); auto submits = base::qt_signal_producer( _field.get(), &Ui::InputField::submitted); return rpl::merge( - _send->clicks() | filter | rpl::to_empty, - std::move(submits) | filter | rpl::to_empty); + _send->clicks() | filter | map, + std::move(submits) | filter | map, + _sendCustomRequests.events()); } rpl::producer ComposeControls::sendVoiceRequests() const { @@ -1395,6 +1401,12 @@ void ComposeControls::initSendButton() { }) | rpl::start_with_next([=] { cancelInlineBot(); }, _send->lifetime()); + + SendMenu::SetupMenuAndShortcuts( + _send.get(), + [=] { return sendButtonMenuType(); }, + [=] { sendSilent(); }, + [=] { sendScheduled(); }); } void ComposeControls::inlineBotResolveDone( @@ -1550,18 +1562,32 @@ void ComposeControls::updateWrappingVisibility() { } } +auto ComposeControls::computeSendButtonType() const { + using Type = Ui::SendButton::Type; + + if (_header->isEditingMessage()) { + return Type::Save; + } else if (_isInlineBot) { + return Type::Cancel; + } else if (showRecordButton()) { + return Type::Record; + } + return (_mode == Mode::Normal) ? Type::Send : Type::Schedule; +} + +SendMenu::Type ComposeControls::sendMenuType() const { + return !_history ? SendMenu::Type::Disabled : _sendMenuType; +} + +SendMenu::Type ComposeControls::sendButtonMenuType() const { + return (computeSendButtonType() == Ui::SendButton::Type::Send) + ? sendMenuType() + : SendMenu::Type::Disabled; +} + void ComposeControls::updateSendButtonType() { using Type = Ui::SendButton::Type; - const auto type = [&] { - if (_header->isEditingMessage()) { - return Type::Save; - } else if (_isInlineBot) { - return Type::Cancel; - } else if (showRecordButton()) { - return Type::Record; - } - return (_mode == Mode::Normal) ? Type::Send : Type::Schedule; - }(); + const auto type = computeSendButtonType(); _send->setType(type); const auto delay = [&] { @@ -2076,6 +2102,22 @@ bool ComposeControls::isRecording() const { return _voiceRecordBar->isRecording(); } +void ComposeControls::sendSilent() { + _sendCustomRequests.fire({ .silent = true }); +} + +void ComposeControls::sendScheduled() { + auto callback = [=](Api::SendOptions options) { + _sendCustomRequests.fire(std::move(options)); + }; + Ui::show( + HistoryView::PrepareScheduleBox( + _wrap.get(), + sendMenuType(), + std::move(callback)), + Ui::LayerOption::KeepOther); +} + void ComposeControls::updateInlineBotQuery() { if (!_history) { return; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 109a318a0..fbdce2296 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -21,6 +21,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; class FieldAutocomplete; +namespace SendMenu { +enum class Type; +} // namespace SendMenu + namespace ChatHelpers { class TabbedPanel; class TabbedSelector; @@ -87,7 +91,8 @@ public: ComposeControls( not_null parent, not_null window, - Mode mode); + Mode mode, + SendMenu::Type sendMenuType); ~ComposeControls(); [[nodiscard]] Main::Session &session() const; @@ -104,7 +109,7 @@ public: bool focus(); [[nodiscard]] rpl::producer<> cancelRequests() const; - [[nodiscard]] rpl::producer<> sendRequests() const; + [[nodiscard]] rpl::producer sendRequests() const; [[nodiscard]] rpl::producer sendVoiceRequests() const; [[nodiscard]] rpl::producer sendCommandRequests() const; [[nodiscard]] rpl::producer editRequests() const; @@ -184,6 +189,13 @@ private: void updateOuterGeometry(QRect rect); void paintBackground(QRect clip); + [[nodiscard]] auto computeSendButtonType() const; + [[nodiscard]] SendMenu::Type sendMenuType() const; + [[nodiscard]] SendMenu::Type sendButtonMenuType() const; + + void sendSilent(); + void sendScheduled(); + void orderControls(); void checkAutocomplete(); void updateStickersByEmoji(); @@ -258,6 +270,9 @@ private: const std::unique_ptr _header; const std::unique_ptr _voiceRecordBar; + const SendMenu::Type _sendMenuType; + + rpl::event_stream _sendCustomRequests; rpl::event_stream<> _cancelRequests; rpl::event_stream _fileChosen; rpl::event_stream _photoChosen; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index e5a976a57..4be479126 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -158,7 +158,8 @@ RepliesWidget::RepliesWidget( , _composeControls(std::make_unique( this, controller, - ComposeControls::Mode::Normal)) + ComposeControls::Mode::Normal, + SendMenu::Type::SilentOnly)) , _scroll(std::make_unique(this, st::historyScroll, false)) , _scrollDown(_scroll.get(), st::historyToDown) , _readRequestTimer([=] { sendReadTillRequest(); }) { @@ -429,13 +430,13 @@ void RepliesWidget::setupComposeControls() { }, lifetime()); _composeControls->sendRequests( - ) | rpl::start_with_next([=] { - send(); + ) | rpl::start_with_next([=](Api::SendOptions options) { + send(options); }, lifetime()); _composeControls->sendVoiceRequests( ) | rpl::start_with_next([=](ComposeControls::VoiceToSend &&data) { - sendVoice(data.bytes, data.waveform, data.duration); + sendVoice(std::move(data)); }, lifetime()); _composeControls->sendCommandRequests( @@ -878,13 +879,15 @@ void RepliesWidget::send() { // Ui::LayerOption::KeepOther); } -void RepliesWidget::sendVoice( - QByteArray bytes, - VoiceWaveform waveform, - int duration) { +void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) { auto action = Api::SendAction(_history); action.replyTo = replyToId(); - session().api().sendVoiceMessage(bytes, waveform, duration, action); + action.options = data.options; + session().api().sendVoiceMessage( + data.bytes, + data.waveform, + data.duration, + std::move(action)); } void RepliesWidget::send(Api::SendOptions options) { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 66e8b7eb0..93ea4fbb1 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -52,6 +52,10 @@ class RepliesList; namespace HistoryView { +namespace Controls { +struct VoiceToSend; +} // namespace Controls + class Element; class TopBarWidget; class RepliesMemento; @@ -180,7 +184,7 @@ private: void send(); void send(Api::SendOptions options); - void sendVoice(QByteArray bytes, VoiceWaveform waveform, int duration); + void sendVoice(Controls::VoiceToSend &&data); void edit( not_null item, Api::SendOptions options, diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 4e09b1040..8fca948b2 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -97,7 +97,8 @@ ScheduledWidget::ScheduledWidget( , _composeControls(std::make_unique( this, controller, - ComposeControls::Mode::Scheduled)) + ComposeControls::Mode::Scheduled, + SendMenu::Type::Disabled)) , _scrollDown(_scroll, st::historyToDown) { const auto state = Dialogs::EntryState{ .key = _history, From 024a35d77096f18ef00f956685a3b37f252683f2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 16 Nov 2020 19:54:47 +0300 Subject: [PATCH 117/370] Added ability to send recorded voice data from ComposeControls. --- .../controls/history_view_compose_controls.cpp | 17 +++++++++++++++-- .../controls/history_view_compose_controls.h | 6 ++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index eadde1509..e5b232f36 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -721,12 +721,15 @@ rpl::producer> ComposeControls::keyEvents() const { }); } -rpl::producer ComposeControls::sendRequests() const { +auto ComposeControls::sendContentRequests(SendRequestType requestType) const { auto filter = rpl::filter([=] { const auto type = (_mode == Mode::Normal) ? Ui::SendButton::Type::Send : Ui::SendButton::Type::Schedule; - return (_send->type() == type); + const auto sendRequestType = _voiceRecordBar->isListenState() + ? SendRequestType::Voice + : SendRequestType::Text; + return (_send->type() == type) && (sendRequestType == requestType); }); auto map = rpl::map_to(Api::SendOptions()); auto submits = base::qt_signal_producer( @@ -738,6 +741,10 @@ rpl::producer ComposeControls::sendRequests() const { _sendCustomRequests.events()); } +rpl::producer ComposeControls::sendRequests() const { + return sendContentRequests(SendRequestType::Text); +} + rpl::producer ComposeControls::sendVoiceRequests() const { return _voiceRecordBar->sendVoiceRequests(); } @@ -973,6 +980,12 @@ void ComposeControls::init() { updateHeight(); }, _wrap->lifetime()); + sendContentRequests( + SendRequestType::Voice + ) | rpl::start_with_next([=](Api::SendOptions options) { + _voiceRecordBar->requestToSendWithOptions(options); + }, _wrap->lifetime()); + { const auto lastMsgId = _wrap->lifetime().make_state(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index fbdce2296..926975cd7 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -169,6 +169,10 @@ private: Normal, Edit, }; + enum class SendRequestType { + Text, + Voice, + }; using TextUpdateEvents = base::flags; friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; @@ -195,6 +199,8 @@ private: void sendSilent(); void sendScheduled(); + [[nodiscard]] auto sendContentRequests( + SendRequestType requestType = SendRequestType::Text) const; void orderControls(); void checkAutocomplete(); From 980ce9fba3505c6b41b82d90c726705ac46b4679 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 16 Nov 2020 20:08:18 +0300 Subject: [PATCH 118/370] Replaced auto types with explicit types in VoiceRecordBar's lambdas. --- .../view/controls/history_view_voice_record_bar.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 01dbed3c4..186a09c9e 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -691,7 +691,7 @@ void RecordLock::init() { const auto &duration = st::historyRecordVoiceShowDuration; const auto from = 0.; const auto to = 1.; - auto callback = [=](auto value) { + auto callback = [=](float64 value) { update(); if (value == to) { setCursor(style::cur_pointer); @@ -850,7 +850,7 @@ void RecordLock::drawProgress(Painter &p) { } void RecordLock::startLockingAnimation(float64 to) { - auto callback = [=](auto value) { setProgress(value); }; + auto callback = [=](float64 value) { setProgress(value); }; const auto &duration = st::historyRecordVoiceShowDuration; _lockEnderAnimation.start(std::move(callback), 0., to, duration); } @@ -1033,7 +1033,7 @@ void VoiceRecordBar::init() { const auto from = show ? 0. : 1.; const auto &duration = st::historyRecordLockShowDuration; _lock->show(); - auto callback = [=](auto value) { + auto callback = [=](float64 value) { updateLockGeometry(); if (value == 0. && !show) { _lock->hide(); @@ -1050,14 +1050,14 @@ void VoiceRecordBar::init() { } ::Media::Capture::instance()->startedChanges( - ) | rpl::filter([=](auto capturing) { + ) | rpl::filter([=](bool capturing) { return !capturing && _listen; }) | rpl::take(1) | rpl::start_with_next([=] { _lockShowing = false; const auto to = 1.; const auto &duration = st::historyRecordVoiceShowDuration; - auto callback = [=](auto value) { + auto callback = [=](float64 value) { _listen->requestPaintProgress(value); _level->requestPaintProgress(to - value); update(); From 04c068d8b3b92c335eaada13ed42f2b7330a1454 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 16 Nov 2020 21:09:53 +0300 Subject: [PATCH 119/370] Added filling send context menu to TabbedSelector from ComposeControls. --- .../history/view/controls/history_view_compose_controls.cpp | 2 ++ .../history/view/history_view_replies_section.cpp | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index e5b232f36..bc753817b 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1396,6 +1396,8 @@ void ComposeControls::initTabbedSelector() { selector->inlineResultChosen( ) | rpl::start_to_stream(_inlineResultChosen, wrap->lifetime()); + + selector->setSendMenuType([=] { return sendMenuType(); }); } void ComposeControls::initSendButton() { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 4be479126..9e7e59245 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -470,17 +470,17 @@ void RepliesWidget::setupComposeControls() { _composeControls->fileChosen( ) | rpl::start_with_next([=](Selector::FileChosen chosen) { - sendExistingDocument(chosen.document); + sendExistingDocument(chosen.document, chosen.options); }, lifetime()); _composeControls->photoChosen( ) | rpl::start_with_next([=](Selector::PhotoChosen chosen) { - sendExistingPhoto(chosen.photo); + sendExistingPhoto(chosen.photo, chosen.options); }, lifetime()); _composeControls->inlineResultChosen( ) | rpl::start_with_next([=](Selector::InlineChosen chosen) { - sendInlineResult(chosen.result, chosen.bot); + sendInlineResult(chosen.result, chosen.bot, chosen.options); }, lifetime()); _composeControls->scrollRequests( From facbaecf302dd1e78986da224db89a25e1d5f9cd Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 17 Nov 2020 14:05:26 +0400 Subject: [PATCH 120/370] Add -Werror to actions --- .github/workflows/linux.yml | 3 ++- .github/workflows/mac.yml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ef2a521e7..1caa8d3ba 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -102,7 +102,8 @@ jobs: fi ./configure.sh \ - -D CMAKE_CXX_FLAGS="-s" \ + -D CMAKE_C_FLAGS="-Werror -s" \ + -D CMAKE_CXX_FLAGS="-Werror -s" \ -D TDESKTOP_API_TEST=ON \ -D DESKTOP_APP_USE_PACKAGED=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 26a625dca..94c164fcf 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -515,6 +515,8 @@ jobs: fi ./configure.sh \ + -D CMAKE_C_FLAGS="-Werror" \ + -D CMAKE_CXX_FLAGS="-Werror" \ -D TDESKTOP_API_TEST=ON \ -D DESKTOP_APP_USE_PACKAGED=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ From 985956e62568f7109f9e435522512c1a44ed2ef6 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 17 Nov 2020 14:13:18 +0400 Subject: [PATCH 121/370] Update lib_base --- Telegram/lib_base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 4f22126e7..c14879d8d 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 4f22126e7e855b517857408abf2467cba163a6f2 +Subproject commit c14879d8d0753f4e3a20890f7559d7dcffa37dd4 From c52e9140602732e5d8300d2b523b1368e680fc63 Mon Sep 17 00:00:00 2001 From: Newbyte Date: Mon, 16 Nov 2020 13:29:24 +0100 Subject: [PATCH 122/370] Fix WebRTC licence link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 379bf9ffa..60e7598d5 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Version **1.8.15** was the last that supports older systems * Qt 5.12.8, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html)) * OpenSSL 1.1.1 and 1.0.1 ([OpenSSL License](https://www.openssl.org/source/license.html)) -* WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/src/LICENSE)) +* WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/LICENSE)) * zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html)) * LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html)) * liblzma ([public domain](http://tukaani.org/xz/)) From c0142726f895926d82d2a7ef4a7fc5a57f6c76b0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 16 Nov 2020 16:29:02 +0400 Subject: [PATCH 123/370] Remove unneeded Xi and Xrender from docker build and add repo with new git --- Telegram/build/docker/centos_env/Dockerfile | 27 +-------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 0bb9d3650..55e04f5c2 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -9,6 +9,7 @@ ENV OPENSSL_VER 1_1_1 ENV OPENSSL_PREFIX /usr/local/desktop-app/openssl-1.1.1 RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm +RUN yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm RUN yum -y install centos-release-scl RUN yum -y install git cmake3 zlib-devel gtk3-devel libICE-devel \ @@ -177,28 +178,6 @@ RUN make DESTDIR="$LibrariesPath/libXfixes-cache" install WORKDIR .. RUN rm -rf libxfixes -FROM builder AS libXi -RUN git clone -b libXi-1.7.10 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxi.git - -WORKDIR libxi -RUN ./autogen.sh --enable-static -RUN make -j$(nproc) -RUN make DESTDIR="$LibrariesPath/libXi-cache" install - -WORKDIR .. -RUN rm -rf libxi - -FROM builder AS libXrender -RUN git clone -b libXrender-0.9.10 --depth=1 https://gitlab.freedesktop.org/xorg/lib/libxrender.git - -WORKDIR libxrender -RUN ./autogen.sh --enable-static -RUN make -j$(nproc) -RUN make DESTDIR="$LibrariesPath/libXrender-cache" install - -WORKDIR .. -RUN rm -rf libxrender - FROM builder AS wayland COPY --from=libffi ${LibrariesPath}/libffi-cache / @@ -417,8 +396,6 @@ COPY --from=xcb-keysyms ${LibrariesPath}/xcb-keysyms-cache / COPY --from=xcb-render-util ${LibrariesPath}/xcb-render-util-cache / COPY --from=libXext ${LibrariesPath}/libXext-cache / COPY --from=libXfixes ${LibrariesPath}/libXfixes-cache / -COPY --from=libXi ${LibrariesPath}/libXi-cache / -COPY --from=libXrender ${LibrariesPath}/libXrender-cache / COPY --from=wayland ${LibrariesPath}/wayland-cache / COPY --from=openssl ${LibrariesPath}/openssl-cache / COPY --from=xkbcommon ${LibrariesPath}/xkbcommon-cache / @@ -548,8 +525,6 @@ COPY --from=xcb-keysyms ${LibrariesPath}/xcb-keysyms-cache / COPY --from=xcb-render-util ${LibrariesPath}/xcb-render-util-cache / COPY --from=libXext ${LibrariesPath}/libXext-cache / COPY --from=libXfixes ${LibrariesPath}/libXfixes-cache / -COPY --from=libXi ${LibrariesPath}/libXi-cache / -COPY --from=libXrender ${LibrariesPath}/libXrender-cache / COPY --from=wayland ${LibrariesPath}/wayland-cache / COPY --from=libva ${LibrariesPath}/libva-cache / COPY --from=libvdpau ${LibrariesPath}/libvdpau-cache / From 96b2e26f4265920dd7221afcff0a659d3ca84067 Mon Sep 17 00:00:00 2001 From: mid-kid Date: Thu, 12 Nov 2020 00:18:18 +0100 Subject: [PATCH 124/370] Build wayland support optionally --- Telegram/CMakeLists.txt | 28 +++-- .../linux/linux_wayland_integration.cpp | 116 ++++++++++++++++++ .../linux/linux_wayland_integration.h | 27 ++++ .../linux/linux_wayland_integration_dummy.cpp | 34 +++++ .../platform/linux/specific_linux.cpp | 96 ++------------- cmake | 2 +- 6 files changed, 207 insertions(+), 96 deletions(-) create mode 100644 Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp create mode 100644 Telegram/SourceFiles/platform/linux/linux_wayland_integration.h create mode 100644 Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b2b7236a3..2320bd52c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -70,7 +70,6 @@ PRIVATE if (LINUX) target_link_libraries(Telegram PRIVATE - desktop-app::external_materialdecoration desktop-app::external_nimf_qt5 desktop-app::external_qt5ct_support desktop-app::external_xcb_screensaver @@ -89,14 +88,22 @@ if (LINUX) ) endif() - if (DESKTOP_APP_USE_PACKAGED AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0) - find_package(PkgConfig REQUIRED) - pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) - - target_include_directories(Telegram + if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION) + target_link_libraries(Telegram PRIVATE - ${WAYLAND_CLIENT_INCLUDE_DIRS} + desktop-app::external_materialdecoration ) + + if (DESKTOP_APP_USE_PACKAGED + AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0) + find_package(PkgConfig REQUIRED) + pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) + + target_include_directories(Telegram + PRIVATE + ${WAYLAND_CLIENT_INCLUDE_DIRS} + ) + endif() endif() if (NOT TDESKTOP_DISABLE_GTK_INTEGRATION) @@ -803,6 +810,8 @@ PRIVATE platform/linux/linux_gdk_helper.h platform/linux/linux_libs.cpp platform/linux/linux_libs.h + platform/linux/linux_wayland_integration.cpp + platform/linux/linux_wayland_integration.h platform/linux/linux_xlib_helper.cpp platform/linux/linux_xlib_helper.h platform/linux/file_utilities_linux.cpp @@ -1088,6 +1097,11 @@ if (NOT LINUX) ) endif() +if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION) + remove_target_sources(Telegram ${src_loc} platform/linux/linux_wayland_integration.cpp) + nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp) +endif() + if (NOT DESKTOP_APP_USE_PACKAGED) nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c) endif() diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp new file mode 100644 index 000000000..1df54e3da --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -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 +*/ +#include "platform/linux/linux_wayland_integration.h" + +#include "base/platform/base_platform_info.h" + +#include + +#include +#include +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) && !defined DESKTOP_APP_QT_PATCHED +#include +#endif // Qt < 5.13 && !DESKTOP_APP_QT_PATCHED + +using QtWaylandClient::QWaylandWindow; + +namespace Platform { +namespace internal { + +namespace { + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) && !defined DESKTOP_APP_QT_PATCHED +enum wl_shell_surface_resize WlResizeFromEdges(Qt::Edges edges) { + if (edges == (Qt::TopEdge | Qt::LeftEdge)) + return WL_SHELL_SURFACE_RESIZE_TOP_LEFT; + if (edges == Qt::TopEdge) + return WL_SHELL_SURFACE_RESIZE_TOP; + if (edges == (Qt::TopEdge | Qt::RightEdge)) + return WL_SHELL_SURFACE_RESIZE_TOP_RIGHT; + if (edges == Qt::RightEdge) + return WL_SHELL_SURFACE_RESIZE_RIGHT; + if (edges == (Qt::RightEdge | Qt::BottomEdge)) + return WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT; + if (edges == Qt::BottomEdge) + return WL_SHELL_SURFACE_RESIZE_BOTTOM; + if (edges == (Qt::BottomEdge | Qt::LeftEdge)) + return WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT; + if (edges == Qt::LeftEdge) + return WL_SHELL_SURFACE_RESIZE_LEFT; + + return WL_SHELL_SURFACE_RESIZE_NONE; +} +#endif // Qt < 5.13 && !DESKTOP_APP_QT_PATCHED + +} // namespace + +WaylandIntegration::WaylandIntegration() { +} + +WaylandIntegration *WaylandIntegration::Instance() { + static WaylandIntegration instance; + return &instance; +} + +bool WaylandIntegration::startMove(QWindow *window) { + // There are startSystemMove on Qt 5.15 +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) && !defined DESKTOP_APP_QT_PATCHED + if (const auto waylandWindow = static_cast( + window->handle())) { + if (const auto seat = waylandWindow->display()->lastInputDevice()) { + if (const auto shellSurface = waylandWindow->shellSurface()) { + return shellSurface->move(seat); + } + } + } +#endif // Qt < 5.15 && !DESKTOP_APP_QT_PATCHED + + return false; +} + +bool WaylandIntegration::startResize(QWindow *window, Qt::Edges edges) { + // There are startSystemResize on Qt 5.15 +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) && !defined DESKTOP_APP_QT_PATCHED + if (const auto waylandWindow = static_cast( + window->handle())) { + if (const auto seat = waylandWindow->display()->lastInputDevice()) { + if (const auto shellSurface = waylandWindow->shellSurface()) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) + shellSurface->resize(seat, edges); + return true; +#else // Qt >= 5.13 + shellSurface->resize(seat, WlResizeFromEdges(edges)); + return true; +#endif // Qt < 5.13 + } + } + } +#endif // Qt < 5.15 && !DESKTOP_APP_QT_PATCHED + + return false; +} + +bool WaylandIntegration::showWindowMenu(QWindow *window) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) || defined DESKTOP_APP_QT_PATCHED + if (const auto waylandWindow = static_cast( + window->handle())) { + if (const auto seat = waylandWindow->display()->lastInputDevice()) { + if (const auto shellSurface = waylandWindow->shellSurface()) { + return shellSurface->showWindowMenu(seat); + } + } + } +#endif // Qt >= 5.13 || DESKTOP_APP_QT_PATCHED + + return false; +} + +} // namespace internal +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h new file mode 100644 index 000000000..73486fef7 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h @@ -0,0 +1,27 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class QWindow; + +namespace Platform { +namespace internal { + +class WaylandIntegration { +public: + static WaylandIntegration *Instance(); + bool startMove(QWindow *window); + bool startResize(QWindow *window, Qt::Edges edges); + bool showWindowMenu(QWindow *window); + +private: + WaylandIntegration(); +}; + +} // namespace internal +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp new file mode 100644 index 000000000..f6c6b39fe --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp @@ -0,0 +1,34 @@ +/* +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 "platform/linux/linux_wayland_integration.h" + +namespace Platform { +namespace internal { + +WaylandIntegration::WaylandIntegration() { +} + +WaylandIntegration *WaylandIntegration::Instance() { + static WaylandIntegration instance; + return &instance; +} + +bool WaylandIntegration::startMove(QWindow *window) { + return false; +} + +bool WaylandIntegration::startResize(QWindow *window, Qt::Edges edges) { + return false; +} + +bool WaylandIntegration::showWindowMenu(QWindow *window) { + return false; +} + +} // namespace internal +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index ec80da17a..6cb1d00f3 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/file_utilities_linux.h" +#include "platform/linux/linux_wayland_integration.h" #include "platform/platform_notifications_manager.h" #include "storage/localstorage.h" #include "core/crash_reports.h" @@ -31,10 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -#include -#include -#include - #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION #include #include @@ -47,10 +44,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) && !defined DESKTOP_APP_QT_PATCHED -#include -#endif // Qt < 5.13 && !DESKTOP_APP_QT_PATCHED - #include extern "C" { @@ -70,7 +63,7 @@ extern "C" { using namespace Platform; using Platform::File::internal::EscapeShell; -using QtWaylandClient::QWaylandWindow; +using Platform::internal::WaylandIntegration; namespace Platform { namespace { @@ -367,29 +360,6 @@ uint XCBMoveResizeFromEdges(Qt::Edges edges) { return 0; } -#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) && !defined DESKTOP_APP_QT_PATCHED -enum wl_shell_surface_resize WlResizeFromEdges(Qt::Edges edges) { - if (edges == (Qt::TopEdge | Qt::LeftEdge)) - return WL_SHELL_SURFACE_RESIZE_TOP_LEFT; - if (edges == Qt::TopEdge) - return WL_SHELL_SURFACE_RESIZE_TOP; - if (edges == (Qt::TopEdge | Qt::RightEdge)) - return WL_SHELL_SURFACE_RESIZE_TOP_RIGHT; - if (edges == Qt::RightEdge) - return WL_SHELL_SURFACE_RESIZE_RIGHT; - if (edges == (Qt::RightEdge | Qt::BottomEdge)) - return WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT; - if (edges == Qt::BottomEdge) - return WL_SHELL_SURFACE_RESIZE_BOTTOM; - if (edges == (Qt::BottomEdge | Qt::LeftEdge)) - return WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT; - if (edges == Qt::LeftEdge) - return WL_SHELL_SURFACE_RESIZE_LEFT; - - return WL_SHELL_SURFACE_RESIZE_NONE; -} -#endif // Qt < 5.13 && !DESKTOP_APP_QT_PATCHED - bool StartXCBMoveResize(QWindow *window, int edges) { const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { @@ -482,59 +452,6 @@ bool ShowXCBWindowMenu(QWindow *window) { return true; } -bool StartWaylandMove(QWindow *window) { - // There are startSystemMove on Qt 5.15 -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) && !defined DESKTOP_APP_QT_PATCHED - if (const auto waylandWindow = static_cast( - window->handle())) { - if (const auto seat = waylandWindow->display()->lastInputDevice()) { - if (const auto shellSurface = waylandWindow->shellSurface()) { - return shellSurface->move(seat); - } - } - } -#endif // Qt < 5.15 && !DESKTOP_APP_QT_PATCHED - - return false; -} - -bool StartWaylandResize(QWindow *window, Qt::Edges edges) { - // There are startSystemResize on Qt 5.15 -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) && !defined DESKTOP_APP_QT_PATCHED - if (const auto waylandWindow = static_cast( - window->handle())) { - if (const auto seat = waylandWindow->display()->lastInputDevice()) { - if (const auto shellSurface = waylandWindow->shellSurface()) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) - shellSurface->resize(seat, edges); - return true; -#else // Qt >= 5.13 - shellSurface->resize(seat, WlResizeFromEdges(edges)); - return true; -#endif // Qt < 5.13 - } - } - } -#endif // Qt < 5.15 && !DESKTOP_APP_QT_PATCHED - - return false; -} - -bool ShowWaylandWindowMenu(QWindow *window) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) || defined DESKTOP_APP_QT_PATCHED - if (const auto waylandWindow = static_cast( - window->handle())) { - if (const auto seat = waylandWindow->display()->lastInputDevice()) { - if (const auto shellSurface = waylandWindow->shellSurface()) { - return shellSurface->showWindowMenu(seat); - } - } - } -#endif // Qt >= 5.13 || DESKTOP_APP_QT_PATCHED - - return false; -} - bool XCBFrameExtentsSupported() { const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { @@ -893,7 +810,8 @@ bool SkipTaskbarSupported() { bool StartSystemMove(QWindow *window) { if (IsWayland()) { - return StartWaylandMove(window); + const auto integration = WaylandIntegration::Instance(); + return integration->startMove(window); } else { return StartXCBMoveResize(window, 16); } @@ -901,7 +819,8 @@ bool StartSystemMove(QWindow *window) { bool StartSystemResize(QWindow *window, Qt::Edges edges) { if (IsWayland()) { - return StartWaylandResize(window, edges); + const auto integration = WaylandIntegration::Instance(); + return integration->startResize(window, edges); } else { return StartXCBMoveResize(window, edges); } @@ -909,7 +828,8 @@ bool StartSystemResize(QWindow *window, Qt::Edges edges) { bool ShowWindowMenu(QWindow *window) { if (IsWayland()) { - return ShowWaylandWindowMenu(window); + const auto integration = WaylandIntegration::Instance(); + return integration->showWindowMenu(window); } else { return ShowXCBWindowMenu(window); } diff --git a/cmake b/cmake index d9e8a608c..4436815d1 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit d9e8a608c21ca175ed118955d83010580fb46e65 +Subproject commit 4436815d19035aaba79cb69b7e2d599ae286297d From 17e8e0a7b06c42e86541912af09b4360b7e29953 Mon Sep 17 00:00:00 2001 From: mid-kid Date: Thu, 12 Nov 2020 00:27:10 +0100 Subject: [PATCH 125/370] Add workflow for DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1caa8d3ba..0a528c0d4 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -62,6 +62,7 @@ jobs: defines: - "" - "DESKTOP_APP_DISABLE_DBUS_INTEGRATION" + - "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION" - "TDESKTOP_DISABLE_GTK_INTEGRATION" env: From 0563e1f8781abeed98c3eb757811a86800f55b87 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 12 Nov 2020 00:54:09 +0100 Subject: [PATCH 126/370] Have the wayland build-time toggle affect the native window title --- Telegram/CMakeLists.txt | 1 + .../platform/linux/window_title_linux.cpp | 38 +++++++++++++++++++ .../platform/linux/window_title_linux.h | 12 +----- 3 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 Telegram/SourceFiles/platform/linux/window_title_linux.cpp diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 2320bd52c..215698590 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -824,6 +824,7 @@ PRIVATE platform/linux/notifications_manager_linux.h platform/linux/specific_linux.cpp platform/linux/specific_linux.h + platform/linux/window_title_linux.cpp platform/linux/window_title_linux.h platform/mac/file_utilities_mac.mm platform/mac/file_utilities_mac.h diff --git a/Telegram/SourceFiles/platform/linux/window_title_linux.cpp b/Telegram/SourceFiles/platform/linux/window_title_linux.cpp new file mode 100644 index 000000000..95e8b6449 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/window_title_linux.cpp @@ -0,0 +1,38 @@ +/* +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 "platform/linux/window_title_linux.h" + +#include "platform/linux/linux_desktop_environment.h" +#include "base/platform/base_platform_info.h" + +namespace Platform { +namespace { + +bool SystemMoveResizeSupported() { +#if !defined DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION || QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED + return true; +#else // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION || Qt >= 5.15 || DESKTOP_APP_QT_PATCHED + return !IsWayland(); +#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION || Qt >= 5.15 || DESKTOP_APP_QT_PATCHED +} + +} // namespace + +bool AllowNativeWindowFrameToggle() { + return SystemMoveResizeSupported() + // https://gitlab.gnome.org/GNOME/mutter/-/issues/217 + && !(DesktopEnvironment::IsGnome() && IsWayland()); +} + +object_ptr CreateTitleWidget(QWidget *parent) { + return SystemMoveResizeSupported() + ? object_ptr(parent) + : object_ptr{ nullptr }; +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/window_title_linux.h b/Telegram/SourceFiles/platform/linux/window_title_linux.h index 4e5f68e02..c493976c2 100644 --- a/Telegram/SourceFiles/platform/linux/window_title_linux.h +++ b/Telegram/SourceFiles/platform/linux/window_title_linux.h @@ -8,8 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "platform/platform_window_title.h" -#include "platform/linux/linux_desktop_environment.h" -#include "base/platform/base_platform_info.h" #include "base/object_ptr.h" namespace Window { @@ -23,14 +21,8 @@ void DefaultPreviewWindowFramePaint(QImage &preview, const style::palette &palet namespace Platform { -inline bool AllowNativeWindowFrameToggle() { - // https://gitlab.gnome.org/GNOME/mutter/-/issues/217 - return !(DesktopEnvironment::IsGnome() && IsWayland()); -} - -inline object_ptr CreateTitleWidget(QWidget *parent) { - return object_ptr(parent); -} +bool AllowNativeWindowFrameToggle(); +object_ptr CreateTitleWidget(QWidget *parent); inline bool NativeTitleRequiresShadow() { return false; From 4ed6918a5e0920f3c820d82ebf99f2b0d630ae3b Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 13 Nov 2020 14:31:17 +0100 Subject: [PATCH 127/370] Disable some static plugins when building without wayland --- Telegram/SourceFiles/qt_static_plugins.cpp | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/qt_static_plugins.cpp b/Telegram/SourceFiles/qt_static_plugins.cpp index 5dffd6a7a..21c0980f2 100644 --- a/Telegram/SourceFiles/qt_static_plugins.cpp +++ b/Telegram/SourceFiles/qt_static_plugins.cpp @@ -21,19 +21,7 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) Q_IMPORT_PLUGIN(QGenericEnginePlugin) #elif defined Q_OS_UNIX // Q_OS_WIN | Q_OS_MAC -Q_IMPORT_PLUGIN(ShmServerBufferPlugin) -Q_IMPORT_PLUGIN(DmaBufServerBufferPlugin) -Q_IMPORT_PLUGIN(DrmEglServerBufferPlugin) -Q_IMPORT_PLUGIN(QWaylandEglClientBufferPlugin) -Q_IMPORT_PLUGIN(QWaylandIviShellIntegrationPlugin) -Q_IMPORT_PLUGIN(QWaylandWlShellIntegrationPlugin) -Q_IMPORT_PLUGIN(QWaylandXdgShellV5IntegrationPlugin) -Q_IMPORT_PLUGIN(QWaylandXdgShellV6IntegrationPlugin) -Q_IMPORT_PLUGIN(QWaylandXdgShellIntegrationPlugin) -Q_IMPORT_PLUGIN(QWaylandBradientDecorationPlugin) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) -Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin) -Q_IMPORT_PLUGIN(QWaylandEglPlatformIntegrationPlugin) Q_IMPORT_PLUGIN(QGenericEnginePlugin) Q_IMPORT_PLUGIN(QComposePlatformInputContextPlugin) Q_IMPORT_PLUGIN(QSvgPlugin) @@ -44,18 +32,34 @@ Q_IMPORT_PLUGIN(QNetworkManagerEnginePlugin) Q_IMPORT_PLUGIN(QIbusPlatformInputContextPlugin) Q_IMPORT_PLUGIN(QXdgDesktopPortalThemePlugin) #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION +#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION +Q_IMPORT_PLUGIN(ShmServerBufferPlugin) +Q_IMPORT_PLUGIN(DmaBufServerBufferPlugin) +Q_IMPORT_PLUGIN(DrmEglServerBufferPlugin) +Q_IMPORT_PLUGIN(QWaylandEglClientBufferPlugin) +Q_IMPORT_PLUGIN(QWaylandIviShellIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandWlShellIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandXdgShellV5IntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandXdgShellV6IntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandXdgShellIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandBradientDecorationPlugin) +Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandEglPlatformIntegrationPlugin) +#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION #endif // Q_OS_WIN | Q_OS_MAC | Q_OS_UNIX #endif // !DESKTOP_APP_USE_PACKAGED #if defined Q_OS_UNIX && !defined Q_OS_MAC #if !defined DESKTOP_APP_USE_PACKAGED || defined DESKTOP_APP_USE_PACKAGED_LAZY -Q_IMPORT_PLUGIN(QWaylandMaterialDecorationPlugin) Q_IMPORT_PLUGIN(NimfInputContextPlugin) #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION Q_IMPORT_PLUGIN(QFcitxPlatformInputContextPlugin) Q_IMPORT_PLUGIN(QFcitx5PlatformInputContextPlugin) Q_IMPORT_PLUGIN(QHimePlatformInputContextPlugin) #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION +#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION +Q_IMPORT_PLUGIN(QWaylandMaterialDecorationPlugin) +#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION #endif // !DESKTOP_APP_USE_PACKAGED || DESKTOP_APP_USE_PACKAGED_LAZY #if !defined DESKTOP_APP_USE_PACKAGED || defined DESKTOP_APP_USE_PACKAGED_LAZY_PLATFORMTHEMES From 49480001f73be83d16506bf0759660927a1cab53 Mon Sep 17 00:00:00 2001 From: mid-kid Date: Fri, 13 Nov 2020 17:05:54 +0100 Subject: [PATCH 128/370] Move IsWayland() checks into WaylandIntegration --- .../platform/linux/linux_wayland_integration.cpp | 1 + .../platform/linux/linux_wayland_integration_dummy.cpp | 3 +++ Telegram/SourceFiles/platform/linux/specific_linux.cpp | 9 +++------ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp index 1df54e3da..e6af77b70 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -55,6 +55,7 @@ WaylandIntegration::WaylandIntegration() { } WaylandIntegration *WaylandIntegration::Instance() { + if (!IsWayland()) return nullptr; static WaylandIntegration instance; return &instance; } diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp index f6c6b39fe..e79fd2d95 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/linux_wayland_integration.h" +#include "base/platform/base_platform_info.h" + namespace Platform { namespace internal { @@ -14,6 +16,7 @@ WaylandIntegration::WaylandIntegration() { } WaylandIntegration *WaylandIntegration::Instance() { + if (!IsWayland()) return nullptr; static WaylandIntegration instance; return &instance; } diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6cb1d00f3..2f9f3d35b 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -809,8 +809,7 @@ bool SkipTaskbarSupported() { } bool StartSystemMove(QWindow *window) { - if (IsWayland()) { - const auto integration = WaylandIntegration::Instance(); + if (const auto integration = WaylandIntegration::Instance()) { return integration->startMove(window); } else { return StartXCBMoveResize(window, 16); @@ -818,8 +817,7 @@ bool StartSystemMove(QWindow *window) { } bool StartSystemResize(QWindow *window, Qt::Edges edges) { - if (IsWayland()) { - const auto integration = WaylandIntegration::Instance(); + if (const auto integration = WaylandIntegration::Instance()) { return integration->startResize(window, edges); } else { return StartXCBMoveResize(window, edges); @@ -827,8 +825,7 @@ bool StartSystemResize(QWindow *window, Qt::Edges edges) { } bool ShowWindowMenu(QWindow *window) { - if (IsWayland()) { - const auto integration = WaylandIntegration::Instance(); + if (const auto integration = WaylandIntegration::Instance()) { return integration->showWindowMenu(window); } else { return ShowXCBWindowMenu(window); From ed50aa0d8ebf3cd122c9e10125dfcdf723744132 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Nov 2020 17:14:21 +0300 Subject: [PATCH 129/370] Fix build with Qt < 5.14. --- Telegram/SourceFiles/storage/localimageloader.cpp | 5 ++--- Telegram/lib_base | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 0a67661e7..d9c53522f 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "core/mime_type.h" #include "base/unixtime.h" +#include "base/qt_adapters.h" #include "media/audio/media_audio.h" #include "media/clip/media_clip_reader.h" #include "mtproto/facade.h" @@ -33,7 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -#include namespace { @@ -882,8 +882,7 @@ void FileLoadTask::process(Args &&args) { // We have an example of dark .png image that when being sent without // removing its color space is displayed fine on tdesktop, but with // a light gray background on mobile apps. - full.setColorSpace(QColorSpace()); - + base::QClearColorSpace(full); QBuffer buffer(&filedata); QImageWriter writer(&buffer, "JPEG"); writer.setQuality(87); diff --git a/Telegram/lib_base b/Telegram/lib_base index c14879d8d..707bdc849 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit c14879d8d0753f4e3a20890f7559d7dcffa37dd4 +Subproject commit 707bdc84918eddfd8c08e776d328dab03dc04b25 From 4f6f654e34e001eebd8dfa6fb80133f1a797dbe4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Nov 2020 18:05:35 +0300 Subject: [PATCH 130/370] Check replies_pts before applying. Fixes #9062. --- Telegram/SourceFiles/api/api_updates.cpp | 4 ++++ Telegram/SourceFiles/api/api_updates.h | 2 ++ Telegram/SourceFiles/history/history_message.cpp | 16 +++++++++++++++- Telegram/SourceFiles/history/history_message.h | 2 ++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 4b853a1e2..f365ecce8 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -791,6 +791,10 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) { } } +int32 Updates::pts() const { + return _ptsWaiter.current(); +} + void Updates::updateOnline() { updateOnline(false); } diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h index 98bd13d60..b65744f51 100644 --- a/Telegram/SourceFiles/api/api_updates.h +++ b/Telegram/SourceFiles/api/api_updates.h @@ -33,6 +33,8 @@ public: void applyUpdatesNoPtsCheck(const MTPUpdates &updates); void applyUpdateNoPtsCheck(const MTPUpdate &update); + [[nodiscard]] int32 pts() const; + void updateOnline(); [[nodiscard]] bool isIdle() const; void checkIdleFinish(); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 91f7673fb..59f165f47 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" // AddTimestampLinks. #include "chat_helpers/stickers_emoji_pack.h" #include "main/main_session.h" +#include "api/api_updates.h" #include "boxes/share_box.h" #include "boxes/confirm_box.h" #include "ui/toast/toast.h" @@ -1083,6 +1084,17 @@ void HistoryMessage::createComponents(const CreateConfig &config) { _fromNameVersion = from ? from->nameVersion : 1; } +bool HistoryMessage::checkRepliesPts(const MTPMessageReplies &data) const { + const auto channel = history()->peer->asChannel(); + const auto pts = channel + ? channel->pts() + : history()->session().updates().pts(); + const auto repliesPts = data.match([&](const MTPDmessageReplies &data) { + return data.vreplies_pts().v; + }); + return (repliesPts >= pts); +} + void HistoryMessage::setupForwardedComponent(const CreateConfig &config) { const auto forwarded = Get(); if (!forwarded) { @@ -1317,7 +1329,9 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) { setForwardsCount(message.vforwards().value_or(-1)); setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities)); if (const auto replies = message.vreplies()) { - setReplies(*replies); + if (checkRepliesPts(*replies)) { + setReplies(*replies); + } } else { clearReplies(); } diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 17d308ec9..610b6044f 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -236,6 +236,8 @@ private: const TextWithEntities &textWithEntities) const; void reapplyText(); + [[nodiscard]] bool checkRepliesPts(const MTPMessageReplies &data) const; + QString _timeText; int _timeWidth = 0; From 91b8ad171aea46828456fd4f43cdcc1fd41db1a2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Nov 2020 18:20:22 +0300 Subject: [PATCH 131/370] Beta version 2.4.10. - Use inline bots and sticker by emoji suggestions in channel comments. - Lock voice message recording, listen to your voice message before sending. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/changelogs.cpp | 6 ++++++ Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/SourceFiles/data/data_types.h | 6 ++++++ Telegram/build/version | 8 ++++---- changelog.txt | 5 +++++ 8 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 0318c74bd..7b1bbb799 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="2.4.10.0" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 5cf4fdcb7..bfe8187b3 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 2,4,9,0 - PRODUCTVERSION 2,4,9,0 + FILEVERSION 2,4,10,0 + PRODUCTVERSION 2,4,10,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "2.4.9.0" + VALUE "FileVersion", "2.4.10.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.9.0" + VALUE "ProductVersion", "2.4.10.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 8be2c6186..095ec4ddd 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 2,4,9,0 - PRODUCTVERSION 2,4,9,0 + FILEVERSION 2,4,10,0 + PRODUCTVERSION 2,4,10,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", "2.4.9.0" + VALUE "FileVersion", "2.4.10.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.9.0" + VALUE "ProductVersion", "2.4.10.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index aa9457047..5bdf0176d 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -89,6 +89,12 @@ std::map BetaLogs() { 2004008, "- Upgrade several third party libraries to latest versions.\n" }, + { + 2004010, + "- Use inline bots and sticker by emoji suggestions in channel comments.\n" + + "- Lock voice message recording, listen to your voice message before sending.\n" + }, }; }; diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 4d81de3c5..d51fc19bf 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 = 2004009; -constexpr auto AppVersionStr = "2.4.9"; +constexpr auto AppVersion = 2004010; +constexpr auto AppVersionStr = "2.4.10"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 7b0360b1a..ffed002f9 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -434,6 +434,12 @@ inline bool operator==( && (a.scroll == b.scroll); } +inline bool operator!=( + const MessageCursor &a, + const MessageCursor &b) { + return !(a == b); +} + class FileClickHandler : public LeftButtonClickHandler { public: FileClickHandler( diff --git a/Telegram/build/version b/Telegram/build/version index ad3eafa56..36009be61 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 2004009 +AppVersion 2004010 AppVersionStrMajor 2.4 -AppVersionStrSmall 2.4.9 -AppVersionStr 2.4.9 +AppVersionStrSmall 2.4.10 +AppVersionStr 2.4.10 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 2.4.9.beta +AppVersionOriginal 2.4.10.beta diff --git a/changelog.txt b/changelog.txt index f6702ef31..c0249e7a5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +2.4.10 beta (17.11.20) + +- Use inline bots and sticker by emoji suggestions in channel comments. +- Lock voice message recording, listen to your voice message before sending. + 2.4.9 beta (06.11.20) - Fix crash in tray icon removing. (macOS only) From 4a73bb7872dc0d52f10306e75f58a572b5e8f249 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Nov 2020 13:32:28 +0300 Subject: [PATCH 132/370] Fix main window on Windows. Fixes #9089, fixes #9090. --- .../platform/win/window_title_win.cpp | 2 - .../platform/win/windows_event_filter.cpp | 58 ++++++++----------- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/Telegram/SourceFiles/platform/win/window_title_win.cpp b/Telegram/SourceFiles/platform/win/window_title_win.cpp index a1c51f491..80df1d90d 100644 --- a/Telegram/SourceFiles/platform/win/window_title_win.cpp +++ b/Telegram/SourceFiles/platform/win/window_title_win.cpp @@ -42,8 +42,6 @@ TitleWidget::TitleWidget(QWidget *parent) }); _close->setPointerCursor(false); - window()->windowHandle()->setFlag(Qt::FramelessWindowHint, true); - setAttribute(Qt::WA_OpaquePaintEvent); resize(width(), _st.height); } diff --git a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp index 0cd10f60d..bdd55b957 100644 --- a/Telegram/SourceFiles/platform/win/windows_event_filter.cpp +++ b/Telegram/SourceFiles/platform/win/windows_event_filter.cpp @@ -142,42 +142,30 @@ bool EventFilter::customWindowFrameEvent( if (result) *result = 0; } return true; - case WM_SHOWWINDOW: { - SetWindowLongPtr( - hWnd, - GWL_STYLE, - WS_POPUP - | WS_THICKFRAME - | WS_CAPTION - | WS_SYSMENU - | WS_MAXIMIZEBOX - | WS_MINIMIZEBOX); - } return false; - case WM_NCCALCSIZE: { - //WINDOWPLACEMENT wp; - //wp.length = sizeof(WINDOWPLACEMENT); - //if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { - // LPNCCALCSIZE_PARAMS params = (LPNCCALCSIZE_PARAMS)lParam; - // LPRECT r = (wParam == TRUE) ? ¶ms->rgrc[0] : (LPRECT)lParam; - // HMONITOR hMonitor = MonitorFromPoint({ (r->left + r->right) / 2, (r->top + r->bottom) / 2 }, MONITOR_DEFAULTTONEAREST); - // if (hMonitor) { - // MONITORINFO mi; - // mi.cbSize = sizeof(mi); - // if (GetMonitorInfo(hMonitor, &mi)) { - // *r = mi.rcWork; - // UINT uEdge = (UINT)-1; - // if (IsTaskbarAutoHidden(&mi.rcMonitor, &uEdge)) { - // switch (uEdge) { - // case ABE_LEFT: r->left += 1; break; - // case ABE_RIGHT: r->right -= 1; break; - // case ABE_TOP: r->top += 1; break; - // case ABE_BOTTOM: r->bottom -= 1; break; - // } - // } - // } - // } - //} + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { + LPNCCALCSIZE_PARAMS params = (LPNCCALCSIZE_PARAMS)lParam; + LPRECT r = (wParam == TRUE) ? ¶ms->rgrc[0] : (LPRECT)lParam; + HMONITOR hMonitor = MonitorFromPoint({ (r->left + r->right) / 2, (r->top + r->bottom) / 2 }, MONITOR_DEFAULTTONEAREST); + if (hMonitor) { + MONITORINFO mi; + mi.cbSize = sizeof(mi); + if (GetMonitorInfo(hMonitor, &mi)) { + *r = mi.rcWork; + UINT uEdge = (UINT)-1; + if (IsTaskbarAutoHidden(&mi.rcMonitor, &uEdge)) { + switch (uEdge) { + case ABE_LEFT: r->left += 1; break; + case ABE_RIGHT: r->right -= 1; break; + case ABE_TOP: r->top += 1; break; + case ABE_BOTTOM: r->bottom -= 1; break; + } + } + } + } + } if (result) *result = 0; return true; } From b2047c955853c2b7a412625373a1ef9b6f0af423 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Nov 2020 14:11:23 +0300 Subject: [PATCH 133/370] Fix first media viewer open zoom. --- .../media/view/media_view_overlay_widget.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 0f3f1a5f8..792d0a3d2 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -965,9 +965,9 @@ void OverlayWidget::resizeContentByScreenSize() { const auto availableWidth = width(); const auto availableHeight = height() - 2 * skipHeight; const auto countZoomFor = [&](int outerw, int outerh) { - auto result = float64(outerw) / _w; - if (_h * result > outerh) { - result = float64(outerh) / _h; + auto result = float64(outerw) / _width; + if (_height * result > outerh) { + result = float64(outerh) / _height; } if (result >= 1.) { result -= 1.; @@ -976,7 +976,7 @@ void OverlayWidget::resizeContentByScreenSize() { } return result; }; - if (_w > 0 && _h > 0) { + if (_width > 0 && _height > 0) { _zoomToDefault = countZoomFor(availableWidth, availableHeight); _zoomToScreen = countZoomFor(width(), height()); } else { @@ -984,15 +984,15 @@ void OverlayWidget::resizeContentByScreenSize() { } const auto usew = _fullScreenVideo ? width() : availableWidth; const auto useh = _fullScreenVideo ? height() : availableHeight; - if ((_w > usew) || (_h > useh) || _fullScreenVideo) { + if ((_width > usew) || (_height > useh) || _fullScreenVideo) { const auto use = _fullScreenVideo ? _zoomToScreen : _zoomToDefault; _zoom = kZoomToScreenLevel; if (use >= 0) { - _w = qRound(_w * (use + 1)); - _h = qRound(_h * (use + 1)); + _w = qRound(_width * (use + 1)); + _h = qRound(_height * (use + 1)); } else { - _w = qRound(_w / (-use + 1)); - _h = qRound(_h / (-use + 1)); + _w = qRound(_width / (-use + 1)); + _h = qRound(_height / (-use + 1)); } } else { _zoom = 0; From 76a7cc922958149f41d2ae75d2dba710eb4f94be Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 18 Nov 2020 03:46:03 +0400 Subject: [PATCH 134/370] Don't set screen for media viewer on Wayland --- .../SourceFiles/media/view/media_view_overlay_widget.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 792d0a3d2..fd5faa3d9 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -441,7 +441,12 @@ void OverlayWidget::moveToScreen() { : nullptr; const auto activeWindowScreen = widgetScreen(window); const auto myScreen = widgetScreen(this); - if (activeWindowScreen && myScreen && myScreen != activeWindowScreen) { + // Wayland doesn't support positioning, but Qt emits screenChanged anyway + // and geometry of the widget become broken + if (activeWindowScreen + && myScreen + && myScreen != activeWindowScreen + && !Platform::IsWayland()) { windowHandle()->setScreen(activeWindowScreen); } } From c3b0e6c503f714ea53a8175fba785ddbf0045cea Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 18 Nov 2020 04:55:38 +0400 Subject: [PATCH 135/370] Move -s to CMAKE_EXE_LINKER_FLAGS --- .github/workflows/linux.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0a528c0d4..1da535e15 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -103,8 +103,9 @@ jobs: fi ./configure.sh \ - -D CMAKE_C_FLAGS="-Werror -s" \ - -D CMAKE_CXX_FLAGS="-Werror -s" \ + -D CMAKE_C_FLAGS="-Werror" \ + -D CMAKE_CXX_FLAGS="-Werror" \ + -D CMAKE_EXE_LINKER_FLAGS="-s" \ -D TDESKTOP_API_TEST=ON \ -D DESKTOP_APP_USE_PACKAGED=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ From c98a3825a55d131bf1099e7cffd7ed24d988f27e Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Nov 2020 14:49:42 +0300 Subject: [PATCH 136/370] Fix sending bot commands from autocomplete. --- Telegram/SourceFiles/history/history_widget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9d9878ead..342199b28 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -3430,7 +3430,8 @@ void HistoryWidget::sendBotCommand( bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, replyTo)); - const auto toSend = replyTo + // 'bot' may be nullptr in case of sending from FieldAutocomplete. + const auto toSend = (replyTo || !bot) ? cmd : HistoryView::WrapBotCommandInChat(_peer, cmd, bot); From 1affb8172f144886d6eda6f297f6c7090329305a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Nov 2020 19:15:26 +0300 Subject: [PATCH 137/370] Fix hime_qt build with -Werror. --- Telegram/build/docker/centos_env/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 55e04f5c2..f3e8278fb 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -12,7 +12,7 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n RUN yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm RUN yum -y install centos-release-scl -RUN yum -y install git cmake3 zlib-devel gtk3-devel libICE-devel \ +RUN yum -y install git cmake3 zlib-devel gtk2-devel gtk3-devel libICE-devel \ libSM-devel libdrm-devel autoconf automake libtool fontconfig-devel \ freetype-devel libX11-devel at-spi2-core-devel alsa-lib-devel \ pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel \ From 00504b61cd595cb0889e27e7b31a0f0767f2dae4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Nov 2020 16:23:51 +0300 Subject: [PATCH 138/370] Allow all messages silent in support mode. --- Telegram/SourceFiles/api/api_sending.cpp | 8 ++--- Telegram/SourceFiles/apiwrap.cpp | 26 +++++----------- .../SourceFiles/history/history_message.cpp | 31 +++++++++++++------ .../SourceFiles/history/history_message.h | 4 +++ .../main/main_session_settings.cpp | 6 ++++ .../SourceFiles/main/main_session_settings.h | 7 +++++ .../platform/mac/specific_mac_p.mm | 12 ++++--- .../SourceFiles/settings/settings_chat.cpp | 14 +++++++++ 8 files changed, 70 insertions(+), 38 deletions(-) diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 3ee6026f6..e2e370145 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -86,8 +86,7 @@ void SendExistingMedia( sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; } const auto anonymousPost = peer->amAnonymous(); - const auto silentPost = message.action.options.silent - || (peer->isBroadcast() && session->data().notifySilentPosts(peer)); + const auto silentPost = ShouldSendSilent(peer, message.action.options); InnerFillMessagePostFlags(message.action.options, peer, flags); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; @@ -261,8 +260,7 @@ bool SendDice(Api::MessageToSend &message) { } const auto replyHeader = NewMessageReplyHeader(message.action); const auto anonymousPost = peer->amAnonymous(); - const auto silentPost = message.action.options.silent - || (peer->isBroadcast() && session->data().notifySilentPosts(peer)); + const auto silentPost = ShouldSendSilent(peer, message.action.options); InnerFillMessagePostFlags(message.action.options, peer, flags); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; @@ -403,7 +401,7 @@ void SendConfirmedFile( } const auto replyHeader = NewMessageReplyHeader(action); const auto anonymousPost = peer->amAnonymous(); - const auto silentPost = file->to.options.silent; + const auto silentPost = ShouldSendSilent(peer, file->to.options); Api::FillMessagePostFlags(action, peer, flags); if (silentPost) { flags |= MTPDmessage::Flag::f_silent; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 0f6ee2581..fb1a263e4 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3955,8 +3955,7 @@ void ApiWrap::forwardMessages( histories.readInbox(history); const auto anonymousPost = peer->amAnonymous(); - const auto silentPost = action.options.silent - || (peer->isBroadcast() && _session->data().notifySilentPosts(peer)); + const auto silentPost = ShouldSendSilent(peer, action.options); auto flags = MTPDmessage::Flags(0); auto clientFlags = MTPDmessage_ClientFlags(); @@ -4155,11 +4154,7 @@ void ApiWrap::sendSharedContact( MTP_string(firstName), MTP_string(lastName), MTP_string(vcard)); - auto options = action.options; - if (_session->data().notifySilentPosts(peer)) { - options.silent = true; - } - sendMedia(item, media, options); + sendMedia(item, media, action.options); _session->data().sendHistoryChangeNotifications(); _session->changes().historyUpdated( @@ -4374,8 +4369,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { flags |= MTPDmessage::Flag::f_media; } const auto anonymousPost = peer->amAnonymous(); - const auto silentPost = action.options.silent - || (peer->isBroadcast() && _session->data().notifySilentPosts(peer)); + const auto silentPost = ShouldSendSilent(peer, action.options); FillMessagePostFlags(action, peer, flags); if (silentPost) { sendFlags |= MTPmessages_SendMessage::Flag::f_silent; @@ -4516,8 +4510,7 @@ void ApiWrap::sendInlineResult( sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id; } const auto anonymousPost = peer->amAnonymous(); - const auto silentPost = action.options.silent - || (peer->isBroadcast() && _session->data().notifySilentPosts(peer)); + const auto silentPost = ShouldSendSilent(peer, action.options); FillMessagePostFlags(action, peer, flags); if (silentPost) { sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_silent; @@ -4683,7 +4676,7 @@ void ApiWrap::sendMediaWithRandomId( | (replyTo ? MTPmessages_SendMedia::Flag::f_reply_to_msg_id : MTPmessages_SendMedia::Flag(0)) - | (options.silent + | (ShouldSendSilent(history->peer, options) ? MTPmessages_SendMedia::Flag::f_silent : MTPmessages_SendMedia::Flag(0)) | (!sentEntities.v.isEmpty() @@ -4790,7 +4783,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) { | (replyTo ? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id : MTPmessages_SendMultiMedia::Flag(0)) - | (album->options.silent + | (ShouldSendSilent(history->peer, album->options) ? MTPmessages_SendMultiMedia::Flag::f_silent : MTPmessages_SendMultiMedia::Flag(0)) | (album->options.scheduled @@ -4828,10 +4821,6 @@ void ApiWrap::sendAlbumIfReady(not_null album) { FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const { const auto peer = action.history->peer; - auto options = action.options; - if (_session->data().notifySilentPosts(peer)) { - options.silent = true; - } return FileLoadTo(peer->id, action.options, action.replyTo); } @@ -5261,8 +5250,7 @@ void ApiWrap::createPoll( history->clearLocalDraft(); history->clearCloudDraft(); } - const auto silentPost = action.options.silent - || (peer->isBroadcast() && _session->data().notifySilentPosts(peer)); + const auto silentPost = ShouldSendSilent(peer, action.options); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 59f165f47..0185d871d 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" // AddTimestampLinks. #include "chat_helpers/stickers_emoji_pack.h" #include "main/main_session.h" +#include "main/main_session_settings.h" #include "api/api_updates.h" #include "boxes/share_box.h" #include "boxes/confirm_box.h" @@ -277,11 +278,8 @@ void FastShareMessage(not_null item) { return; } - const auto sendFlags = MTPmessages_ForwardMessages::Flag(0) + const auto commonSendFlags = MTPmessages_ForwardMessages::Flag(0) | MTPmessages_ForwardMessages::Flag::f_with_my_score - | (options.silent - ? MTPmessages_ForwardMessages::Flag::f_silent - : MTPmessages_ForwardMessages::Flag(0)) | (options.scheduled ? MTPmessages_ForwardMessages::Flag::f_schedule_date : MTPmessages_ForwardMessages::Flag(0)); @@ -311,13 +309,17 @@ void FastShareMessage(not_null item) { } histories.sendRequest(history, requestType, [=](Fn finish) { auto &api = history->session().api(); + const auto sendFlags = commonSendFlags + | (ShouldSendSilent(peer, options) + ? MTPmessages_ForwardMessages::Flag::f_silent + : MTPmessages_ForwardMessages::Flag(0)); history->sendRequestId = api.request(MTPmessages_ForwardMessages( - MTP_flags(sendFlags), - data->peer->input, - MTP_vector(msgIds), - MTP_vector(generateRandom()), - peer->input, - MTP_int(options.scheduled) + MTP_flags(sendFlags), + data->peer->input, + MTP_vector(msgIds), + MTP_vector(generateRandom()), + peer->input, + MTP_int(options.scheduled) )).done([=](const MTPUpdates &updates, mtpRequestId requestId) { history->session().api().applyUpdates(updates); data->requests.remove(requestId); @@ -375,6 +377,15 @@ MTPDmessage::Flags NewMessageFlags(not_null peer) { return result; } +bool ShouldSendSilent( + not_null peer, + const Api::SendOptions &options) { + return options.silent + || (peer->isBroadcast() && peer->owner().notifySilentPosts(peer)) + || (peer->session().supportMode() + && peer->session().settings().supportAllSilent()); +} + MsgId LookupReplyToTop(not_null history, MsgId replyToId) { const auto &owner = history->owner(); if (const auto item = owner.message(history->channelId(), replyToId)) { diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 610b6044f..5bfc295d7 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Api { struct SendAction; +struct SendOptions; } // namespace Api namespace HistoryView { @@ -24,6 +25,9 @@ struct HistoryMessageViews; [[nodiscard]] Fn HistoryDependentItemCallback( not_null item); [[nodiscard]] MTPDmessage::Flags NewMessageFlags(not_null peer); +[[nodiscard]] bool ShouldSendSilent( + not_null peer, + const Api::SendOptions &options); [[nodiscard]] MTPDmessage_ClientFlags NewMessageClientFlags(); [[nodiscard]] MsgId LookupReplyToTop( not_null history, diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index e97770cbf..f38f2e3e4 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -69,6 +69,7 @@ QByteArray SessionSettings::serialize() const { stream << quint64(key) << qint32(value); } stream << qint32(_dialogsFiltersEnabled ? 1 : 0); + stream << qint32(_supportAllSilent ? 1 : 0); } return result; } @@ -127,6 +128,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { qint32 appAutoDownloadDictionaries = app.autoDownloadDictionaries() ? 1 : 0; base::flat_map hiddenPinnedMessages; qint32 dialogsFiltersEnabled = _dialogsFiltersEnabled ? 1 : 0; + qint32 supportAllSilent = _supportAllSilent ? 1 : 0; stream >> versionTag; if (versionTag == kVersionTag) { @@ -321,6 +323,9 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> dialogsFiltersEnabled; } + if (!stream.atEnd()) { + stream >> supportAllSilent; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for SessionSettings::addFromSerialized()")); @@ -362,6 +367,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { _mediaLastPlaybackPosition = std::move(mediaLastPlaybackPosition); _hiddenPinnedMessages = std::move(hiddenPinnedMessages); _dialogsFiltersEnabled = (dialogsFiltersEnabled == 1); + _supportAllSilent = (supportAllSilent == 1); if (version < 2) { app.setLastSeenWarningSeen(appLastSeenWarningSeen == 1); diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index e8c8b1552..2cd331e7e 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -51,6 +51,12 @@ public: void setSupportAllSearchResults(bool all); [[nodiscard]] bool supportAllSearchResults() const; [[nodiscard]] rpl::producer supportAllSearchResultsValue() const; + void setSupportAllSilent(bool enabled) { + _supportAllSilent = enabled; + } + [[nodiscard]] bool supportAllSilent() const { + return _supportAllSilent; + } [[nodiscard]] ChatHelpers::SelectorTab selectorTab() const { return _selectorTab; @@ -131,6 +137,7 @@ private: Support::SwitchSettings _supportSwitch; bool _supportFixChatsOrder = true; bool _supportTemplatesAutocomplete = true; + bool _supportAllSilent = false; rpl::variable _supportChatsTimeSlice = kDefaultSupportChatsLimitSlice; rpl::variable _supportAllSearchResults = false; diff --git a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm index e53afe424..5fdf9397a 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac_p.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac_p.mm @@ -158,11 +158,15 @@ ApplicationDelegate *_sharedDelegate = nil; if (!Core::IsAppLaunched()) { return; } - Core::App().checkLocalTime(); + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Core::App().checkLocalTime(); - LOG(("Audio Info: " - "-receiveWakeNote: received, scheduling detach from audio device")); - Media::Audio::ScheduleDetachFromDeviceSafe(); + LOG(("Audio Info: " + "-receiveWakeNote: received, scheduling detach from audio device")); + Media::Audio::ScheduleDetachFromDeviceSafe(); + + Core::App().settings().setSystemDarkMode(Platform::IsDarkMode()); + }); } - (void) setWatchingMediaKeys:(bool)watching { diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index a27a33f60..ac92f78cb 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -1419,6 +1419,20 @@ void SetupSupport( controller->session().saveSettingsDelayed(); }, inner->lifetime()); + inner->add( + object_ptr( + inner, + "Send all messages without sound", + controller->session().settings().supportAllSilent(), + st::settingsCheckbox), + st::settingsSendTypePadding + )->checkedChanges( + ) | rpl::start_with_next([=](bool checked) { + controller->session().settings().setSupportAllSilent( + checked); + controller->session().saveSettingsDelayed(); + }, inner->lifetime()); + AddSkip(inner, st::settingsCheckboxesSkip); AddSubsectionTitle(inner, rpl::single(qsl("Load chats for a period"))); From 639e6d8e280a13cd8fb4c751236d366ab9c681ad Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Nov 2020 15:37:23 +0300 Subject: [PATCH 139/370] Fix sending albums in slowmode groups. Fixes #9106. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 9 +++++---- Telegram/SourceFiles/media/audio/media_audio_capture.cpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index e7d1f80be..0a943a356 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -280,8 +280,9 @@ void SendFilesBox::enqueueNextPrepare() { } while (!_list.filesToProcess.empty() && _list.filesToProcess.front().information) { - addFile(std::move(_list.filesToProcess.front())); + auto file = std::move(_list.filesToProcess.front()); _list.filesToProcess.pop_front(); + addFile(std::move(file)); } if (_list.filesToProcess.empty()) { return; @@ -638,9 +639,6 @@ void SendFilesBox::setupSendWayControls() { } void SendFilesBox::updateSendWayControlsVisibility() { - if (_sendLimit == SendLimit::One) { - return; - } const auto onlyOne = (_sendLimit == SendLimit::One); _groupFiles->setVisible(_list.hasGroupOption(onlyOne)); _sendImagesAsPhotos->setVisible( @@ -817,10 +815,13 @@ void SendFilesBox::addPreparedAsyncFile(Ui::PreparedFile &&file) { } void SendFilesBox::addFile(Ui::PreparedFile &&file) { + // canBeSentInSlowmode checks for non empty filesToProcess. + auto saved = base::take(_list.filesToProcess); _list.files.push_back(std::move(file)); if (_sendLimit == SendLimit::One && !_list.canBeSentInSlowmode()) { _list.files.pop_back(); } + _list.filesToProcess = std::move(saved); } void SendFilesBox::refreshTitleText() { diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp index 06df89bb2..a5965e0d1 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp @@ -63,7 +63,7 @@ private: Fn _error; struct Private; - std::unique_ptr d; + const std::unique_ptr d; base::Timer _timer; QByteArray _captured; From a086afb152ff3c702751998f3d5f22e8ba301fa0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Nov 2020 16:47:12 +0300 Subject: [PATCH 140/370] Fix legacy group service message in chats list. --- Telegram/SourceFiles/history/history_service.cpp | 5 +++-- Telegram/SourceFiles/platform/linux/main_window_linux.cpp | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index bda13ceac..69460be8d 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -818,8 +818,9 @@ void HistoryService::updateDependentText() { setServiceText(text); history()->owner().requestItemResize(this); - if (history()->textCachedFor == this) { - history()->textCachedFor = nullptr; + const auto inDialogsHistory = history()->migrateToOrMe(); + if (inDialogsHistory->textCachedFor == this) { + inDialogsHistory->textCachedFor = nullptr; } //if (const auto feed = history()->peer->feed()) { // #TODO archive // if (feed->textCachedFor == this) { diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index a85bdbab0..45d4b7736 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -579,8 +579,6 @@ void MainWindow::psTrayMenuUpdated() { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION void MainWindow::setSNITrayIcon(int counter, bool muted) { - const auto iconName = GetTrayIconName(counter, muted); - if (IsIndicatorApplication()) { if (!IsIconRegenerationNeeded(counter, muted) && _trayIconFile From b1ed15447b4e8618c4a241186d4e9c5234460485 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Nov 2020 17:01:50 +0300 Subject: [PATCH 141/370] Scroll to bottom when sending a poll. --- Telegram/SourceFiles/apiwrap.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index fb1a263e4..29a91fb31 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -5273,6 +5273,11 @@ void ApiWrap::createPoll( MTP_int(action.options.scheduled) )).done([=](const MTPUpdates &result) mutable { applyUpdates(result); + _session->changes().historyUpdated( + history, + (action.options.scheduled + ? Data::HistoryUpdate::Flag::ScheduledSent + : Data::HistoryUpdate::Flag::MessageSent)); done(); finish(); }).fail([=](const RPCError &error) mutable { From fff2ee2758365489ad1e1a6f3b120cfa3eb163cf Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Nov 2020 17:16:25 +0300 Subject: [PATCH 142/370] Use 'Next' in send media box in Scheduled section. --- Telegram/SourceFiles/boxes/send_files_box.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 0a943a356..041ae00e5 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -330,7 +330,11 @@ void SendFilesBox::setupShadows() { } void SendFilesBox::prepare() { - _send = addButton(tr::lng_send_button(), [=] { send({}); }); + _send = addButton( + (_sendType == Api::SendType::Normal + ? tr::lng_send_button() + : tr::lng_create_group_next()), + [=] { send({}); }); if (_sendType == Api::SendType::Normal) { SendMenu::SetupMenuAndShortcuts( _send, From 51f960442ec3dfd9a9b5040ded6993a738dade72 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Nov 2020 17:42:57 +0300 Subject: [PATCH 143/370] Make caption area height bigger. --- Telegram/SourceFiles/boxes/boxes.style | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 2269c450d..95e70e64b 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -446,7 +446,7 @@ autoDownloadLimitPadding: margins(22px, 8px, 22px, 8px); confirmCaptionArea: InputField(defaultInputField) { textMargins: margins(1px, 26px, 31px, 4px); - heightMax: 78px; + heightMax: 158px; } confirmBg: windowBgOver; confirmMaxHeight: 245px; From e29ee439cf97ad4dbaf1aba424c16a2503c8c732 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Nov 2020 17:59:43 +0300 Subject: [PATCH 144/370] Improve media viewer hiding workaround on macOS. --- .../media/view/media_view_overlay_widget.cpp | 42 ++++++++++--------- .../media/view/media_view_overlay_widget.h | 2 + 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index fd5faa3d9..1036ae393 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -4295,8 +4295,31 @@ bool OverlayWidget::eventFilter(QObject *obj, QEvent *e) { return OverlayParent::eventFilter(obj, e); } +void OverlayWidget::applyHideWindowWorkaround() { +#ifdef USE_OPENGL_OVERLAY_WIDGET + // QOpenGLWidget can't properly destroy a child widget if + // it is hidden exactly after that, so it must be repainted + // before it is hidden without the child widget. + if (!isHidden()) { + _dropdown->hideFast(); + hideChildren(); + _wasRepainted = false; + repaint(); + if (!_wasRepainted) { + // Qt has some optimization to prevent too frequent repaints. + // If the previous repaint was less than 1/60 second it silently + // converts repaint() call to an update() call. But we have to + // repaint right now, before hide(), with _streamingControls destroyed. + auto event = QEvent(QEvent::UpdateRequest); + QApplication::sendEvent(this, &event); + } + } +#endif // USE_OPENGL_OVERLAY_WIDGET +} + void OverlayWidget::setVisibleHook(bool visible) { if (!visible) { + applyHideWindowWorkaround(); _sharedMedia = nullptr; _sharedMediaData = std::nullopt; _sharedMediaDataKey = std::nullopt; @@ -4313,25 +4336,6 @@ void OverlayWidget::setVisibleHook(bool visible) { _controlsOpacity = anim::value(1, 1); _groupThumbs = nullptr; _groupThumbsRect = QRect(); -#ifdef USE_OPENGL_OVERLAY_WIDGET - // QOpenGLWidget can't properly destroy a child widget if - // it is hidden exactly after that, so it must be repainted - // before it is hidden without the child widget. - if (!isHidden()) { - _dropdown->hideFast(); - hideChildren(); - _wasRepainted = false; - repaint(); - if (!_wasRepainted) { - // Qt has some optimization to prevent too frequent repaints. - // If the previous repaint was less than 1/60 second it silently - // converts repaint() call to an update() call. But we have to - // repaint right now, before hide(), with _streamingControls destroyed. - auto event = QEvent(QEvent::UpdateRequest); - QApplication::sendEvent(this, &event); - } - } -#endif // USE_OPENGL_OVERLAY_WIDGET } OverlayParent::setVisibleHook(visible); if (visible) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 194659f40..88e0b663e 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -372,6 +372,8 @@ private: void clearStreaming(bool savePosition = true); bool canInitStreaming() const; + void applyHideWindowWorkaround(); + QBrush _transparentBrush; Main::Session *_session = nullptr; From 1758f0fd8f44a1cce54feaefd7f53fb468e14b04 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 19 Nov 2020 00:44:17 +0300 Subject: [PATCH 145/370] Added send icon to VoiceRecordButton. --- Telegram/Resources/langs/lang.strings | 2 +- .../history_view_voice_record_bar.cpp | 4 +- .../history_view_voice_record_button.cpp | 47 +++++++++++++++++-- .../history_view_voice_record_button.h | 8 ++++ Telegram/SourceFiles/ui/chat/chat.style | 1 + 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 086802bfa..b6dddb980 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1342,7 +1342,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; "lng_record_cancel" = "Release outside this field to cancel"; -"lng_record_lock_cancel" = "Click outside of microphone button to cancel"; +"lng_record_lock_cancel" = "Click outside of circle to cancel"; "lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?"; "lng_record_lock_discard" = "Discard"; "lng_will_be_notified" = "Members will be notified when you post"; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 186a09c9e..d16acb2d0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -1074,6 +1074,7 @@ void VoiceRecordBar::init() { _lock->locks( ) | rpl::start_with_next([=] { installClickOutsideFilter(); + _level->setType(VoiceRecordButton::Type::Send); _level->clicks( ) | rpl::start_with_next([=] { @@ -1102,7 +1103,8 @@ void VoiceRecordBar::init() { TextParseOptions{ TextParseMultiline, 0, 0, direction }); updateMessageGeometry(); - update(_messageRect); + // Update a whole widget to clear a previous text. + update(); }, lifetime()); _send->events( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 64ef1e235..a8bcc7036 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -213,8 +213,6 @@ private: void initEnterIdleAnimation(rpl::producer animationTicked); void initFlingAnimation(rpl::producer animationTicked); - Ui::Animations::Simple _flingAnimation; - const std::unique_ptr _circleBezier; const float _rotationOffset; @@ -652,6 +650,10 @@ void VoiceRecordButton::requestPaintLevel(quint16 level) { void VoiceRecordButton::init() { const auto hasProgress = [](auto value) { return value != 0.; }; + const auto stateChangedAnimation = + lifetime().make_state(); + const auto currentState = lifetime().make_state(_state.current()); + paintRequest( ) | rpl::start_with_next([=](const QRect &clip) { Painter p(this); @@ -674,8 +676,29 @@ void VoiceRecordButton::init() { if (!complete) { p.setOpacity(progress); } - st::historyRecordVoiceActive.paintInCenter(p, rect()); + // Paint icon. + { + const auto stateProgress = stateChangedAnimation->value(0.); + const auto scale = (std::cos(M_PI * 2 * stateProgress) + 1.) * .5; + p.translate(_center, _center); + if (scale < 1.) { + p.scale(scale, scale); + } + const auto state = *currentState; + const auto icon = (state == Type::Send) + ? st::historySendIcon + : st::historyRecordVoiceActive; + const auto position = (state == Type::Send) + ? st::historyRecordSendIconPosition + : QPoint(0, 0); + icon.paint( + p, + -icon.width() / 2 + position.x(), + -icon.height() / 2 + position.y(), + 0, + st::historyRecordVoiceFgActiveIcon->c); + } }, lifetime()); rpl::merge( @@ -689,6 +712,7 @@ void VoiceRecordButton::init() { _recordingAnimation.stop(); _showProgress = 0.; _recordCircle->reset(); + _state = Type::Record; } else { if (!_recordingAnimation.animating()) { _recordingAnimation.start(); @@ -701,6 +725,19 @@ void VoiceRecordButton::init() { ) | rpl::start_with_next([=](bool active) { setPointerCursor(active); }, lifetime()); + + _state.changes( + ) | rpl::start_with_next([=](Type newState) { + const auto to = 1.; + auto callback = [=](float64 value) { + if (value >= (to * .5)) { + *currentState = newState; + } + update(); + }; + const auto duration = st::historyRecordVoiceDuration * 2; + stateChangedAnimation->start(std::move(callback), 0., to, duration); + }, lifetime()); } rpl::producer VoiceRecordButton::actives() const { @@ -745,4 +782,8 @@ void VoiceRecordButton::requestPaintColor(float64 progress) { update(); } +void VoiceRecordButton::setType(Type state) { + _state = state; +} + } // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h index 27d0068eb..beb96d0d4 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h @@ -22,6 +22,13 @@ public: rpl::producer<> leaveWindowEventProducer); ~VoiceRecordButton(); + enum class Type { + Send, + Record, + }; + + void setType(Type state); + void requestPaintColor(float64 progress); void requestPaintProgress(float64 progress); void requestPaintLevel(quint16 level); @@ -41,6 +48,7 @@ private: rpl::variable _showProgress = 0.; rpl::variable _colorProgress = 0.; rpl::variable _inCircle = false; + rpl::variable _state = Type::Record; // This can animate for a very long time (like in music playing), // so it should be a Basic, not a Simple animation. diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index efdd96ce6..f7ca7a972 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -336,6 +336,7 @@ historyRecordVoiceDuration: 120; historyRecordVoice: icon {{ "send_control_record", historyRecordVoiceFg }}; historyRecordVoiceOver: icon {{ "send_control_record", historyRecordVoiceFgOver }}; historyRecordVoiceActive: icon {{ "send_control_record_active", historyRecordVoiceFgActiveIcon }}; +historyRecordSendIconPosition: point(2px, 0px); historyRecordVoiceRippleBgActive: lightButtonBgOver; historyRecordSignalRadius: 5px; historyRecordCancel: windowSubTextFg; From cbaca6382ec43767a113e8f0877a01d9c095d800 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 19 Nov 2020 01:27:10 +0300 Subject: [PATCH 146/370] Fixed unwanted flickering of record button when recorded data is empty. --- .../history/view/controls/history_view_voice_record_bar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index d16acb2d0..6bcca8844 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -1280,6 +1280,9 @@ void VoiceRecordBar::recordUpdated(quint16 level, int samples) { } void VoiceRecordBar::stop(bool send) { + if (isHidden() && !send) { + return; + } auto disappearanceCallback = [=] { hide(); From f75fb33c29bc801f9d72c78c942e786bf0d29f3e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 19 Nov 2020 01:33:13 +0300 Subject: [PATCH 147/370] Removed delay for voice lock widget appearing. --- .../view/controls/history_view_voice_record_bar.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 6bcca8844..05226bce0 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -1218,19 +1218,14 @@ void VoiceRecordBar::startRecording() { return; } - const auto shown = _recordingLifetime.make_state(false); + _lockShowing = true; + startRedCircleAnimation(); _recording = true; _controller->widget()->setInnerFocus(); instance()->start(); instance()->updated( ) | rpl::start_with_next_error([=](const Update &update) { - if (!(*shown) && !_showAnimation.animating()) { - // Show the lock widget after the first successful update. - *shown = true; - _lockShowing = true; - startRedCircleAnimation(); - } recordUpdated(update.level, update.samples); }, [=] { stop(false); From a6e4ac679c09908593e11b8069e644d63a2e8040 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 19 Nov 2020 05:15:42 +0300 Subject: [PATCH 148/370] Fixed reply stuck display at sending voice in Replies section. --- .../SourceFiles/history/view/history_view_replies_section.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 9e7e59245..b8b780501 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -888,6 +888,9 @@ void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) { data.waveform, data.duration, std::move(action)); + + _composeControls->cancelReplyMessage(); + finishSending(); } void RepliesWidget::send(Api::SendOptions options) { From e1017380ec96bac6c67005d01d96cd836c8c62df Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 19 Nov 2020 05:58:23 +0300 Subject: [PATCH 149/370] Fixed filling menu with send options for inline bots and autocomplete. --- .../chat_helpers/field_autocomplete.cpp | 16 +++++++++++++++- .../chat_helpers/field_autocomplete.h | 6 ++++++ .../controls/history_view_compose_controls.cpp | 3 +++ .../inline_bots/inline_results_inner.cpp | 8 +++++++- .../inline_bots/inline_results_inner.h | 6 ++++++ .../inline_bots/inline_results_widget.cpp | 4 ++++ .../inline_bots/inline_results_widget.h | 5 +++++ 7 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 3c6100de8..95b68324b 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -68,6 +68,7 @@ public: Api::SendOptions options = Api::SendOptions()) const; void setRecentInlineBotsInRows(int32 bots); + void setSendMenuType(Fn &&callback); void rowsUpdated(); rpl::producer mentionChosen() const; @@ -121,6 +122,8 @@ private: bool _previewShown = false; + Fn _sendMenuType; + rpl::event_stream _mentionChosen; rpl::event_stream _hashtagChosen; rpl::event_stream _botCommandChosen; @@ -686,6 +689,10 @@ bool FieldAutocomplete::chooseSelected(ChooseMethod method) const { return _inner->chooseSelected(method); } +void FieldAutocomplete::setSendMenuType(Fn &&callback) { + _inner->setSendMenuType(std::move(callback)); +} + bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) { auto hidden = isHidden(); auto moderate = Core::App().settings().moderateModeEnabled(); @@ -1116,7 +1123,9 @@ void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) { return; } const auto index = _sel; - const auto type = SendMenu::Type::Scheduled; + const auto type = _sendMenuType + ? _sendMenuType() + : SendMenu::Type::Disabled; const auto method = FieldAutocomplete::ChooseMethod::ByClick; _menu = base::make_unique_q(this); @@ -1301,6 +1310,11 @@ void FieldAutocomplete::Inner::showPreview() { } } +void FieldAutocomplete::Inner::setSendMenuType( + Fn &&callback) { + _sendMenuType = std::move(callback); +} + auto FieldAutocomplete::Inner::mentionChosen() const -> rpl::producer { return _mentionChosen.events(); diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h index 8adbaa2c3..cbd593275 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.h +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.h @@ -33,6 +33,11 @@ class DocumentMedia; class CloudImageView; } // namespace Data +namespace SendMenu { +enum class Type; +} // namespace SendMenu + + class FieldAutocomplete final : public Ui::RpWidget { public: FieldAutocomplete( @@ -98,6 +103,7 @@ public: void setModerateKeyActivateCallback(Fn callback) { _moderateKeyActivateCallback = std::move(callback); } + void setSendMenuType(Fn &&callback); void hideFast(); 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 bc753817b..74af9ba30 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1120,6 +1120,8 @@ void ComposeControls::initAutocomplete() { }); }, _autocomplete->lifetime()); + _autocomplete->setSendMenuType([=] { return sendMenuType(); }); + //_autocomplete->setModerateKeyActivateCallback([=](int key) { // return _keyboard->isHidden() // ? false @@ -2187,6 +2189,7 @@ void ComposeControls::applyInlineBotQuery( InlineBots::ResultSelected result) { _inlineResultChosen.fire_copy(result); }); + _inlineResults->setSendMenuType([=] { return sendMenuType(); }); _inlineResults->requesting( ) | rpl::start_with_next([=](bool requesting) { _tabbedSelectorToggle->setLoading(requesting); diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 6074ab1f3..e36111c43 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -290,7 +290,9 @@ void Inner::contextMenuEvent(QContextMenuEvent *e) { } const auto row = _selected / MatrixRowShift; const auto column = _selected % MatrixRowShift; - const auto type = SendMenu::Type::Scheduled; + const auto type = _sendMenuType + ? _sendMenuType() + : SendMenu::Type::Disabled; _menu = base::make_unique_q(this); @@ -765,5 +767,9 @@ void Inner::switchPm() { } } +void Inner::setSendMenuType(Fn &&callback) { + _sendMenuType = std::move(callback); +} + } // namespace Layout } // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.h b/Telegram/SourceFiles/inline_bots/inline_results_inner.h index 6f2e1ab3a..bcf58a49d 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.h @@ -40,6 +40,10 @@ class Result; struct ResultSelected; } // namespace InlineBots +namespace SendMenu { +enum class Type; +} // namespace SendMenu + namespace InlineBots { namespace Layout { @@ -87,6 +91,7 @@ public: void setCurrentDialogsEntryState(Dialogs::EntryState state) { _currentDialogsEntryState = state; } + void setSendMenuType(Fn &&callback); // Ui::AbstractTooltipShower interface. QString tooltipText() const override; @@ -179,6 +184,7 @@ private: bool _previewShown = false; Fn _resultSelectedCallback; + Fn _sendMenuType; }; diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index 7f94c480a..2878c3166 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -237,6 +237,10 @@ void Widget::setResultSelectedCallback(Fn callback) { _inner->setResultSelectedCallback(std::move(callback)); } +void Widget::setSendMenuType(Fn &&callback) { + _inner->setSendMenuType(std::move(callback)); +} + void Widget::setCurrentDialogsEntryState(Dialogs::EntryState state) { _inner->setCurrentDialogsEntryState(state); } diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.h b/Telegram/SourceFiles/inline_bots/inline_results_widget.h index 62c4acd4f..d8fe739bf 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.h @@ -43,6 +43,10 @@ class Result; struct ResultSelected; } // namespace InlineBots +namespace SendMenu { +enum class Type; +} // namespace SendMenu + namespace InlineBots { namespace Layout { @@ -70,6 +74,7 @@ public: void hideAnimated(); void setResultSelectedCallback(Fn callback); + void setSendMenuType(Fn &&callback); void setCurrentDialogsEntryState(Dialogs::EntryState state); [[nodiscard]] rpl::producer requesting() const { From a2e4403b28e6fd581784cc0bf8fd939e0f89acd1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 19 Nov 2020 06:18:45 +0300 Subject: [PATCH 150/370] Slightly refactored code for menu with send options. --- .../chat_helpers/field_autocomplete.cpp | 2 +- .../chat_helpers/gifs_list_widget.cpp | 2 +- .../chat_helpers/send_context_menu.cpp | 6 ++--- .../chat_helpers/send_context_menu.h | 2 +- .../chat_helpers/stickers_list_widget.cpp | 2 +- .../chat_helpers/tabbed_selector.h | 2 +- .../history_view_compose_controls.cpp | 25 +++++-------------- .../inline_bots/inline_results_inner.cpp | 2 +- 8 files changed, 15 insertions(+), 28 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 95b68324b..6faeb1b66 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -1134,7 +1134,7 @@ void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) { }; SendMenu::FillSendMenu( _menu, - [&] { return type; }, + type, SendMenu::DefaultSilentCallback(send), SendMenu::DefaultScheduleCallback(this, type, send)); diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 7e7eaa63e..0960a8cb6 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -377,7 +377,7 @@ void GifsListWidget::fillContextMenu( }; SendMenu::FillSendMenu( menu, - [&] { return type; }, + type, SendMenu::DefaultSilentCallback(send), SendMenu::DefaultScheduleCallback(this, type, send)); diff --git a/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp b/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp index 0f5666dd9..08271abd6 100644 --- a/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp +++ b/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp @@ -40,13 +40,13 @@ Fn DefaultScheduleCallback( FillMenuResult FillSendMenu( not_null menu, - Fn type, + Type type, Fn silent, Fn schedule) { if (!silent && !schedule) { return FillMenuResult::None; } - const auto now = type(); + const auto now = type; if (now == Type::Disabled || (!silent && now == Type::SilentOnly)) { return FillMenuResult::None; @@ -76,7 +76,7 @@ void SetupMenuAndShortcuts( const auto menu = std::make_shared>(); const auto showMenu = [=] { *menu = base::make_unique_q(button); - const auto result = FillSendMenu(*menu, type, silent, schedule); + const auto result = FillSendMenu(*menu, type(), silent, schedule); const auto success = (result == FillMenuResult::Success); if (success) { (*menu)->popup(QCursor::pos()); diff --git a/Telegram/SourceFiles/chat_helpers/send_context_menu.h b/Telegram/SourceFiles/chat_helpers/send_context_menu.h index ec810545e..11f9824b6 100644 --- a/Telegram/SourceFiles/chat_helpers/send_context_menu.h +++ b/Telegram/SourceFiles/chat_helpers/send_context_menu.h @@ -40,7 +40,7 @@ Fn DefaultScheduleCallback( FillMenuResult FillSendMenu( not_null menu, - Fn type, + Type type, Fn silent, Fn schedule); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index d2534c7ed..0cb159ae9 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -2077,7 +2077,7 @@ void StickersListWidget::fillContextMenu( }; SendMenu::FillSendMenu( menu, - [&] { return type; }, + type, SendMenu::DefaultSilentCallback(send), SendMenu::DefaultScheduleCallback(this, type, send)); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index d20c67c1d..a73e97e67 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -109,7 +109,7 @@ public: _beforeHidingCallback = std::move(callback); } - void setSendMenuType(Fn callback) { + void setSendMenuType(Fn &&callback) { _sendMenuType = std::move(callback); } 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 74af9ba30..98269b845 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -35,7 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/controls/history_view_voice_record_bar.h" -#include "history/view/history_view_schedule_box.h" // HistoryView::PrepareScheduleBox #include "history/view/history_view_webpage_preview.h" #include "inline_bots/inline_results_widget.h" #include "inline_bots/inline_bot_result.h" @@ -1419,11 +1418,15 @@ void ComposeControls::initSendButton() { cancelInlineBot(); }, _send->lifetime()); + const auto send = [=](Api::SendOptions options) { + _sendCustomRequests.fire(std::move(options)); + }; + SendMenu::SetupMenuAndShortcuts( _send.get(), [=] { return sendButtonMenuType(); }, - [=] { sendSilent(); }, - [=] { sendScheduled(); }); + SendMenu::DefaultSilentCallback(send), + SendMenu::DefaultScheduleCallback(_wrap.get(), sendMenuType(), send)); } void ComposeControls::inlineBotResolveDone( @@ -2119,22 +2122,6 @@ bool ComposeControls::isRecording() const { return _voiceRecordBar->isRecording(); } -void ComposeControls::sendSilent() { - _sendCustomRequests.fire({ .silent = true }); -} - -void ComposeControls::sendScheduled() { - auto callback = [=](Api::SendOptions options) { - _sendCustomRequests.fire(std::move(options)); - }; - Ui::show( - HistoryView::PrepareScheduleBox( - _wrap.get(), - sendMenuType(), - std::move(callback)), - Ui::LayerOption::KeepOther); -} - void ComposeControls::updateInlineBotQuery() { if (!_history) { return; diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index e36111c43..28acc81c8 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -301,7 +301,7 @@ void Inner::contextMenuEvent(QContextMenuEvent *e) { }; SendMenu::FillSendMenu( _menu, - [&] { return type; }, + type, SendMenu::DefaultSilentCallback(send), SendMenu::DefaultScheduleCallback(this, type, send)); From b1b01385d0720add9c57ec0791620d8950acfb53 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 19 Nov 2020 19:34:32 +0400 Subject: [PATCH 151/370] Restore 16px tray icon size Looks like there are support for this size since b703f4e5557053d4cc320e42861db197c0ce7790 --- Telegram/SourceFiles/platform/linux/main_window_linux.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 45d4b7736..c33070cbe 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -202,6 +202,7 @@ QIcon TrayIconGen(int counter, bool muted) { QIcon systemIcon; static const auto iconSizes = { + 16, 22, 24, 32, From 0a0dcb90540d272e5fe52f37a4d8509ea7d8769e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 19 Nov 2020 18:11:09 +0300 Subject: [PATCH 152/370] Delegated responsibility for clearing listen state to sections. --- Telegram/SourceFiles/history/history_widget.cpp | 1 + .../view/controls/history_view_compose_controls.cpp | 4 ++++ .../view/controls/history_view_compose_controls.h | 1 + .../view/controls/history_view_voice_record_bar.cpp | 10 +++++++++- .../view/controls/history_view_voice_record_bar.h | 3 ++- .../history/view/history_view_replies_section.cpp | 1 + .../history/view/history_view_scheduled_section.cpp | 1 + 7 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 342199b28..649b144d0 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -797,6 +797,7 @@ void HistoryWidget::initVoiceRecordBar() { data.waveform, data.duration, action); + _voiceRecordBar->clearListenState(); }, lifetime()); _voiceRecordBar->lockShowStarts( 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 98269b845..5f440dc47 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1018,6 +1018,10 @@ bool ComposeControls::showRecordButton() const { && !isEditingMessage(); } +void ComposeControls::clearListenState() { + _voiceRecordBar->clearListenState(); +} + void ComposeControls::drawRestrictedWrite(Painter &p, const QString &error) { p.fillRect(_writeRestricted->rect(), st::historyReplyBg); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 926975cd7..4da785239 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -152,6 +152,7 @@ public: void setText(const TextWithTags &text); void clear(); void hidePanelsAnimated(); + void clearListenState(); [[nodiscard]] rpl::producer lockShowStarts() const; [[nodiscard]] bool isLockPresent() const; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 05226bce0..2c0142c71 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -1399,7 +1399,6 @@ void VoiceRecordBar::requestToSendWithOptions(Api::SendOptions options) { data->waveform, Duration(data->samples), options }); - hideAnimated(); } } @@ -1416,6 +1415,9 @@ bool VoiceRecordBar::isRecording() const { } void VoiceRecordBar::hideAnimated() { + if (isHidden()) { + return; + } visibilityAnimate(false, [=] { hide(); }); } @@ -1455,6 +1457,12 @@ float64 VoiceRecordBar::activeAnimationRatio() const { return _activeAnimation.value(_inField.current() ? 1. : 0.); } +void VoiceRecordBar::clearListenState() { + if (isListenState()) { + hideAnimated(); + } +} + float64 VoiceRecordBar::showAnimationRatio() const { // There is no reason to set the final value to zero, // because at zero this widget is hidden. diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 59f2d891f..f976bafb8 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -49,7 +49,9 @@ public: void startRecording(); void finishAnimating(); + void hideAnimated(); void hideFast(); + void clearListenState(); void orderControls(); @@ -102,7 +104,6 @@ private: bool isTypeRecord() const; bool hasDuration() const; - void hideAnimated(); void finish(); void activeAnimate(bool active); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index b8b780501..ea9818aef 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -890,6 +890,7 @@ void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) { std::move(action)); _composeControls->cancelReplyMessage(); + _composeControls->clearListenState(); finishSending(); } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 8fca948b2..723c424c5 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -573,6 +573,7 @@ void ScheduledWidget::sendVoice( auto action = Api::SendAction(_history); action.options = options; session().api().sendVoiceMessage(bytes, waveform, duration, action); + _composeControls->clearListenState(); } void ScheduledWidget::edit( From cc48afac1cf70bf98daf79fa2b5c4626329cd378 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Nov 2020 19:05:16 +0300 Subject: [PATCH 153/370] Beta version 2.4.11. - Improve locked voice message recording. - Fix main window closing to tray on Windows. - Fix crash in bot command sending. - Fix adding additional photos when sending an album to a group with enabled slow mode. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/changelogs.cpp | 10 ++++++++++ Telegram/SourceFiles/core/version.h | 4 ++-- Telegram/build/version | 8 ++++---- changelog.txt | 7 +++++++ 7 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 7b1bbb799..5e36731ba 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="2.4.11.0" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index bfe8187b3..4d2150713 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 2,4,10,0 - PRODUCTVERSION 2,4,10,0 + FILEVERSION 2,4,11,0 + PRODUCTVERSION 2,4,11,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "2.4.10.0" + VALUE "FileVersion", "2.4.11.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.10.0" + VALUE "ProductVersion", "2.4.11.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 095ec4ddd..3d3f6fda6 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 2,4,10,0 - PRODUCTVERSION 2,4,10,0 + FILEVERSION 2,4,11,0 + PRODUCTVERSION 2,4,11,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", "2.4.10.0" + VALUE "FileVersion", "2.4.11.0" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.4.10.0" + VALUE "ProductVersion", "2.4.11.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index 5bdf0176d..279f7e7a4 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -95,6 +95,16 @@ std::map BetaLogs() { "- Lock voice message recording, listen to your voice message before sending.\n" }, + { + 2004011, + "- Improve locked voice message recording.\n" + + "- Fix main window closing to tray on Windows.\n" + + "- Fix crash in bot command sending.\n" + + "- Fix adding additional photos when sending an album to a group with enabled slow mode.\n" + }, }; }; diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index d51fc19bf..ab26b4e00 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 = 2004010; -constexpr auto AppVersionStr = "2.4.10"; +constexpr auto AppVersion = 2004011; +constexpr auto AppVersionStr = "2.4.11"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 36009be61..23d9aea34 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 2004010 +AppVersion 2004011 AppVersionStrMajor 2.4 -AppVersionStrSmall 2.4.10 -AppVersionStr 2.4.10 +AppVersionStrSmall 2.4.11 +AppVersionStr 2.4.11 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 2.4.10.beta +AppVersionOriginal 2.4.11.beta diff --git a/changelog.txt b/changelog.txt index c0249e7a5..2fe67af33 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +2.4.11 beta (19.11.20) + +- Improve locked voice message recording. +- Fix main window closing to tray on Windows. +- Fix crash in bot command sending. +- Fix adding additional photos when sending an album to a group with enabled slow mode. + 2.4.10 beta (17.11.20) - Use inline bots and sticker by emoji suggestions in channel comments. From 51cdb704618c505c88cb4d9c8ff6009b6425f103 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 18 Nov 2020 00:25:47 +0300 Subject: [PATCH 154/370] Moved Issue Closer to separate Github Action. --- .github/workflows/issue_closer.yml | 131 +---------------------------- 1 file changed, 3 insertions(+), 128 deletions(-) diff --git a/.github/workflows/issue_closer.yml b/.github/workflows/issue_closer.yml index 44782fa45..d3784944f 100644 --- a/.github/workflows/issue_closer.yml +++ b/.github/workflows/issue_closer.yml @@ -8,132 +8,7 @@ jobs: comment: runs-on: ubuntu-latest steps: - - name: Get the latest version. - run: | - tag=$(git ls-remote --tags git://github.com/$GITHUB_REPOSITORY | cut -f 2 | tail -n1) - echo $tag - echo "LATEST_TAG=$tag" >> $GITHUB_ENV - - - name: Get the latest macOS version. - shell: python - run: | - import subprocess; - from xml.dom import minidom; - - url = "https://osx.telegram.org/updates/versions.xml"; - subprocess.check_call("wget %s" % url, shell=True); - - xmldoc = minidom.parse('versions.xml'); - itemlist = xmldoc.getElementsByTagName('enclosure'); - ver = itemlist[0].attributes['sparkle:shortVersionString'].value; - print(ver); - - open(os.environ['GITHUB_ENV'], "a").write("LATEST_MACOS=" + ver); - - - name: Check a version from an issue. - uses: actions/github-script@0.4.0 + - name: Process an issue. + uses: desktop-app/action_issue_closer@master with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - let errorStr = "Version not found."; - - function maxIndexOf(str, i) { - let index = str.indexOf(i); - return (index == -1) ? Number.MAX_SAFE_INTEGER : index; - } - - let item1 = "Version of Telegram Desktop"; - let item2 = "Installation source"; - let item3 = "Used theme"; - let item4 = "
"; - let body = context.payload.issue.body; - - console.log("Body of issue:\n" + body); - let index1 = body.indexOf(item1); - let index2 = Math.min( - Math.min( - maxIndexOf(body, item2), - maxIndexOf(body, item3)), - maxIndexOf(body, item4)); - - console.log("Index 1: " + index1); - console.log("Index 2: " + index2); - - if (index1 == -1) { - console.log(errorStr); - return; - } - - function parseVersion(str) { - let pattern = /[0-9]\.[0-9][0-9.]{0,}/g; - return str.match(pattern); - } - function firstNum(version) { - return version[0].split(".")[0]; - } - - let issueVer = parseVersion(body.substring(index1 + item1.length, index2)); - - if (issueVer == undefined) { - console.log(errorStr); - return; - } - console.log("Version from issue: " + issueVer[0]); - - let latestVer = parseVersion(process.env.LATEST_TAG); - - if (latestVer == undefined) { - console.log(errorStr); - return; - } - console.log("Version from tags: " + latestVer[0]); - - let issueNum = firstNum(issueVer); - let latestNum = firstNum(latestVer); - - let macos_ver = process.env.LATEST_MACOS; - console.log("Telegram for MacOS version from website: " + macos_ver); - - if (issueNum <= latestNum && issueNum < macos_ver) { - console.log("Seems the version of this issue is fine!"); - return; - } - if (issueNum > macos_ver) { - let message = `Seems like it's neither the Telegram Desktop\ - nor the Telegram for macOS version. - `; - console.log(message); - return; - } - - let message = ` - Sorry, but according to the version you specify in this issue, \ - you are using the [Telegram for macOS](https://macos.telegram.org), \ - not the [Telegram Desktop](https://desktop.telegram.org). - You can report your issue to [the group](https://t.me/macswift) \ - or to [the repository of Telegram for macOS](https://github.com/overtake/TelegramSwift). - - **If I made a mistake and closed your issue wrongly, please reopen it. Thanks!** - `; - - let params = { - owner: context.issue.owner, - repo: context.issue.repo, - issue_number: context.issue.number - }; - - github.issues.createComment({ - ...params, - body: message - }); - - github.issues.addLabels({ - ...params, - labels: ['TG macOS Swift'] - }); - - github.issues.update({ - ...params, - state: 'closed' - }); - + token: ${{ secrets.GITHUB_TOKEN }} From e283b4895b240b3b613d39294a126ec0cb808c2f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 26 Nov 2020 17:03:51 +0300 Subject: [PATCH 155/370] Fixed vulnerability in Github Action that updates user-agent for DNS. --- .github/workflows/user_agent_updater.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/user_agent_updater.yml b/.github/workflows/user_agent_updater.yml index eee5c8b2c..a19b3271f 100644 --- a/.github/workflows/user_agent_updater.yml +++ b/.github/workflows/user_agent_updater.yml @@ -44,12 +44,14 @@ jobs: git remote set-url origin $url - name: Delete branch. + env: + ref: ${{ github.event.pull_request.head.ref }} if: | env.isPull == '1' && github.event.action == 'closed' - && startsWith(github.head_ref, env.headBranchPrefix) + && startsWith(env.ref, env.headBranchPrefix) run: | - git push origin --delete ${{ github.head_ref }} + git push origin --delete $ref - name: Write a new version of Google Chrome to the user-agent for DNS. if: env.isPull == '0' From 62eaa3f22519f477e392a00f69eee90a861236c3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Nov 2020 15:57:17 +0300 Subject: [PATCH 156/370] Update API scheme to layer 121. --- Telegram/Resources/tl/api.tl | 6 ++++-- Telegram/SourceFiles/data/data_session.cpp | 2 ++ Telegram/SourceFiles/export/data/export_data_types.cpp | 6 ++++++ Telegram/SourceFiles/ui/image/image_location_factory.cpp | 6 ++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 06f21cc28..531ea9f51 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -124,7 +124,7 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; @@ -194,6 +194,7 @@ photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = Phot photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize; photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector = PhotoSize; +photoPathSize#d8214d41 type:string bytes:bytes = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint; @@ -902,6 +903,7 @@ account.webAuthorizations#ed56c9fc authorizations:Vector users inputMessageID#a676a322 id:int = InputMessage; inputMessageReplyTo#bad88395 id:int = InputMessage; inputMessagePinned#86872538 = InputMessage; +inputMessageCallbackQuery#acfa1a7e id:int query_id:long = InputMessage; inputDialogPeer#fcaafeb7 peer:InputPeer = InputDialogPeer; inputDialogPeerFolder#64600527 folder_id:int = InputDialogPeer; @@ -1547,4 +1549,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 120 +// LAYER 121 diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index f5fe90f92..924c61179 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -154,6 +154,8 @@ std::vector ExtractUnavailableReasons( return kInvalid; }, [](const MTPDphotoStrippedSize &) { return kInvalid; + }, [](const MTPDphotoPathSize &) { + return kInvalid; }, [](const auto &data) { return (data.vw().v * data.vh().v); }); diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index bbe8522cb..b5c441e8d 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -245,6 +245,8 @@ Image ParseMaxImage( size.match([](const MTPDphotoSizeEmpty &) { }, [](const MTPDphotoStrippedSize &) { // Max image size should not be a stripped image. + }, [](const MTPDphotoPathSize &) { + // Max image size should not be a path image. }, [&](const auto &data) { const auto area = data.vw().v * int64(data.vh().v); if (area > maxArea) { @@ -397,6 +399,8 @@ Image ParseDocumentThumb( return 0; }, [](const MTPDphotoStrippedSize &) { return 0; + }, [](const MTPDphotoPathSize &) { + return 0; }, [](const auto &data) { return data.vw().v * data.vh().v; }); @@ -410,6 +414,8 @@ Image ParseDocumentThumb( return Image(); }, [](const MTPDphotoStrippedSize &) { return Image(); + }, [](const MTPDphotoPathSize &) { + return Image(); }, [&](const auto &data) { auto result = Image(); result.width = data.vw().v; diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.cpp b/Telegram/SourceFiles/ui/image/image_location_factory.cpp index bc86b8c91..eefbc7157 100644 --- a/Telegram/SourceFiles/ui/image/image_location_factory.cpp +++ b/Telegram/SourceFiles/ui/image/image_location_factory.cpp @@ -91,6 +91,8 @@ ImageWithLocation FromPhotoSize( // .bytes = bytes, // .bytesCount = bytes.size(), //}; + }, [&](const MTPDphotoPathSize &) { + return ImageWithLocation(); }, [&](const MTPDphotoSizeEmpty &) { return ImageWithLocation(); }); @@ -183,6 +185,8 @@ ImageWithLocation FromPhotoSize( // .bytes = bytes, // .bytesCount = bytes.size(), //}; + }, [&](const MTPDphotoPathSize &data) { + return ImageWithLocation(); }, [&](const MTPDphotoSizeEmpty &) { return ImageWithLocation(); }); @@ -263,6 +267,8 @@ ImageWithLocation FromPhotoSize( // .bytes = bytes, // .bytesCount = bytes.size(), //}; + }, [&](const MTPDphotoPathSize &data) { + return ImageWithLocation(); }, [&](const MTPDphotoSizeEmpty &) { return ImageWithLocation(); }); From 3aa2619a7fc75c0e23a3364978153970a5eab14c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Nov 2020 18:19:31 +0300 Subject: [PATCH 157/370] Update API scheme to layer 122. --- Telegram/Resources/tl/api.tl | 31 +++++++++++++++++-- .../export/data/export_data_types.cpp | 4 +++ .../SourceFiles/history/history_service.cpp | 6 ++++ .../history/view/history_view_send_action.cpp | 2 ++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 531ea9f51..7ae43c9b0 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -128,7 +128,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; +channelFull#ef3a6acd flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -182,6 +182,8 @@ messageActionSecureValuesSentMe#1b287353 values:Vector credentials: messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction; +messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction; +messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -363,6 +365,8 @@ updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update; updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector pts:int pts_count:int = Update; +updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector version:int = Update; +updateGroupCall#5724806e channel_id:int call:GroupCall = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -449,6 +453,7 @@ sendMessageChooseContactAction#628cbc6f = SendMessageAction; sendMessageGamePlayAction#dd6a8f48 = SendMessageAction; sendMessageRecordRoundAction#88f27fbc = SendMessageAction; sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction; +speakingInGroupCallAction#d92c2285 = SendMessageAction; contacts.found#b3134d9d my_results:Vector results:Vector chats:Vector users:Vector = contacts.Found; @@ -1037,7 +1042,7 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights; @@ -1179,6 +1184,17 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; +groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; +groupCall#55903081 flags:# id:long access_hash:long participants_count:int params:flags.0?DataJSON version:int = GroupCall; + +inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; + +groupCallParticipant#89a0d26c flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true user_id:int date:int source:int = GroupCallParticipant; + +phone.groupCall#564c9fd8 call:GroupCall sources:Vector participants:Vector users:Vector = phone.GroupCall; + +phone.groupParticipants#3cdb7991 count:int participants:Vector users:Vector version:int = phone.GroupParticipants; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1533,6 +1549,15 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; +phone.createGroupCall#e428fa02 channel:InputChannel random_id:int = Updates; +phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates; +phone.leaveGroupCall#60e98e5f call:InputGroupCall = Updates; +phone.editGroupCallMember#63146ae4 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser = Updates; +phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; +phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; +phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; +phone.getGroupParticipants#de41d3b2 call:InputGroupCall max_date:int limit:int = phone.GroupParticipants; +phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1549,4 +1574,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 121 +// LAYER 122 diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index b5c441e8d..97d917f21 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1112,6 +1112,10 @@ ServiceAction ParseServiceAction( } content.distance = data.vdistance().v; result.content = content; + }, [&](const MTPDmessageActionGroupCall &data) { + // #TODO calls + }, [&](const MTPDmessageActionInviteToGroupCall &data) { + // #TODO calls }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 69460be8d..6dfafdc58 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -323,6 +323,12 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { }, [](const MTPDmessageActionSecureValuesSentMe &) { LOG(("API Error: messageActionSecureValuesSentMe received.")); return PreparedText{ tr::lng_message_empty(tr::now) }; + }, [&](const MTPDmessageActionGroupCall &data) { + // #TODO calls + return PreparedText{ "Group call" }; + }, [&](const MTPDmessageActionInviteToGroupCall &data) { + // #TODO calls + return PreparedText{ "Invite to group call" }; }, [](const MTPDmessageActionEmpty &) { return PreparedText{ tr::lng_message_empty(tr::now) }; }); diff --git a/Telegram/SourceFiles/history/view/history_view_send_action.cpp b/Telegram/SourceFiles/history/view/history_view_send_action.cpp index 705ad5942..f62efa170 100644 --- a/Telegram/SourceFiles/history/view/history_view_send_action.cpp +++ b/Telegram/SourceFiles/history/view/history_view_send_action.cpp @@ -100,6 +100,8 @@ bool SendActionPainter::updateNeedsAnimating( || (i->second.until <= now)) { emplaceAction(Type::PlayGame, kStatusShowClientsidePlayGame); } + }, [&](const MTPDspeakingInGroupCallAction &) { + // #TODO calls }, [&](const MTPDsendMessageCancelAction &) { Unexpected("CancelAction here."); }); From 33941ad1b94e02fd46efe2926e70b88dbc408733 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Nov 2020 22:25:35 +0300 Subject: [PATCH 158/370] Start group call bar in HistoryWidget. --- Telegram/CMakeLists.txt | 6 + Telegram/Resources/langs/lang.strings | 2 +- Telegram/SourceFiles/api/api_updates.cpp | 4 +- Telegram/SourceFiles/calls/calls_call.cpp | 8 +- Telegram/SourceFiles/calls/calls_call.h | 2 +- .../SourceFiles/calls/calls_group_call.cpp | 329 ++++++++++++++++++ Telegram/SourceFiles/calls/calls_group_call.h | 84 +++++ Telegram/SourceFiles/calls/calls_instance.cpp | 98 +++++- Telegram/SourceFiles/calls/calls_instance.h | 40 ++- Telegram/SourceFiles/data/data_changes.h | 3 +- Telegram/SourceFiles/data/data_channel.cpp | 33 ++ Telegram/SourceFiles/data/data_channel.h | 12 + Telegram/SourceFiles/data/data_group_call.cpp | 115 ++++++ Telegram/SourceFiles/data/data_group_call.h | 54 +++ .../SourceFiles/history/history_widget.cpp | 96 ++++- Telegram/SourceFiles/history/history_widget.h | 11 +- .../view/history_view_group_call_tracker.cpp | 42 +++ .../view/history_view_group_call_tracker.h | 32 ++ .../view/history_view_top_bar_widget.cpp | 4 + .../SourceFiles/ui/chat/group_call_bar.cpp | 182 ++++++++++ Telegram/SourceFiles/ui/chat/group_call_bar.h | 69 ++++ Telegram/ThirdParty/tgcalls | 2 +- Telegram/cmake/lib_tgcalls.cmake | 3 + Telegram/cmake/td_ui.cmake | 2 + 24 files changed, 1211 insertions(+), 22 deletions(-) create mode 100644 Telegram/SourceFiles/calls/calls_group_call.cpp create mode 100644 Telegram/SourceFiles/calls/calls_group_call.h create mode 100644 Telegram/SourceFiles/data/data_group_call.cpp create mode 100644 Telegram/SourceFiles/data/data_group_call.h create mode 100644 Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_group_call_tracker.h create mode 100644 Telegram/SourceFiles/ui/chat/group_call_bar.cpp create mode 100644 Telegram/SourceFiles/ui/chat/group_call_bar.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 215698590..c54dd9165 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -264,6 +264,8 @@ PRIVATE calls/calls_box_controller.h calls/calls_call.cpp calls/calls_call.h + calls/calls_group_call.cpp + calls/calls_group_call.h calls/calls_emoji_fingerprint.cpp calls/calls_emoji_fingerprint.h calls/calls_instance.cpp @@ -385,6 +387,8 @@ PRIVATE data/data_file_origin.h data/data_flags.h data/data_game.h + data/data_group_call.cpp + data/data_group_call.h data/data_groups.cpp data/data_groups.h data/data_histories.cpp @@ -536,6 +540,8 @@ PRIVATE history/view/history_view_cursor_state.h history/view/history_view_element.cpp history/view/history_view_element.h + history/view/history_view_group_call_tracker.cpp + history/view/history_view_group_call_tracker.h history/view/history_view_list_widget.cpp history/view/history_view_list_widget.h history/view/history_view_message.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b6dddb980..68a98cebf 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1342,7 +1342,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; "lng_record_cancel" = "Release outside this field to cancel"; -"lng_record_lock_cancel" = "Click outside of circle to cancel"; +"lng_record_lock_cancel" = "Click outside of the circle to cancel"; "lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?"; "lng_record_lock_discard" = "Discard"; "lng_will_be_notified" = "Members will be notified when you post"; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index f365ecce8..9babc7347 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1796,7 +1796,9 @@ void Updates::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updatePhoneCall: - case mtpc_updatePhoneCallSignalingData: { + case mtpc_updatePhoneCallSignalingData: + case mtpc_updateGroupCallParticipants: + case mtpc_updateGroupCall: { Core::App().calls().handleUpdate(&session(), update); } break; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 4ba508eca..cb552e607 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -289,7 +289,7 @@ void Call::startIncoming() { } void Call::answer() { - _delegate->requestPermissionsOrFail(crl::guard(this, [=] { + _delegate->callRequestPermissionsOrFail(crl::guard(this, [=] { actuallyAnswer(); })); } @@ -776,11 +776,11 @@ void Call::createAndStartController(const MTPDphoneCall &call) { auto callLogPath = callLogFolder + qsl("/last_call_log.txt"); auto callLogNative = QDir::toNativeSeparators(callLogPath); #ifdef Q_OS_WIN - descriptor.config.logPath = callLogNative.toStdWString(); + descriptor.config.logPath.data = callLogNative.toStdWString(); #else // Q_OS_WIN const auto callLogUtf = QFile::encodeName(callLogNative); - descriptor.config.logPath.resize(callLogUtf.size()); - ranges::copy(callLogUtf, descriptor.config.logPath.begin()); + descriptor.config.logPath.data.resize(callLogUtf.size()); + ranges::copy(callLogUtf, descriptor.config.logPath.data.begin()); #endif // Q_OS_WIN QFile(callLogPath).remove(); QDir().mkpath(callLogFolder); diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 139a037ad..2075be7ab 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -68,7 +68,7 @@ public: Ended, }; virtual void playSound(Sound sound) = 0; - virtual void requestPermissionsOrFail(Fn onSuccess) = 0; + virtual void callRequestPermissionsOrFail(Fn onSuccess) = 0; virtual auto getVideoCapture() -> std::shared_ptr = 0; diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp new file mode 100644 index 000000000..0dcd19e6b --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -0,0 +1,329 @@ +/* +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 "calls/calls_group_call.h" + +#include "main/main_session.h" +//#include "main/main_account.h" +//#include "main/main_app_config.h" +#include "apiwrap.h" +#include "lang/lang_keys.h" +#include "boxes/confirm_box.h" +//#include "boxes/rate_call_box.h" +//#include "calls/calls_instance.h" +//#include "base/openssl_help.h" +//#include "mtproto/mtproto_dh_utils.h" +//#include "mtproto/mtproto_config.h" +//#include "core/application.h" +//#include "core/core_settings.h" +//#include "media/audio/media_audio_track.h" +//#include "base/platform/base_platform_info.h" +//#include "calls/calls_panel.h" +//#include "webrtc/webrtc_video_track.h" +//#include "webrtc/webrtc_media_devices.h" +#include "data/data_channel.h" +//#include "data/data_session.h" +//#include "facades.h" + +#include + +#include +#include +#include + +namespace tgcalls { +class GroupInstanceImpl; +} // namespace tgcalls + +namespace Calls { + +GroupCall::GroupCall( + not_null delegate, + not_null channel, + const MTPInputGroupCall &inputCall) +: _delegate(delegate) +, _channel(channel) +, _api(&_channel->session().mtp()) { + if (inputCall.c_inputGroupCall().vid().v) { + join(inputCall); + } else { + start(); + } +} + +GroupCall::~GroupCall() { + destroyController(); +} + +void GroupCall::start() { + const auto randomId = rand_value(); + _api.request(MTPphone_CreateGroupCall( + _channel->inputChannel, + MTP_int(randomId) + )).done([=](const MTPUpdates &result) { + _channel->session().api().applyUpdates(result); + }).fail([=](const RPCError &error) { + int a = error.code(); + }).send(); +} + +void GroupCall::join(const MTPInputGroupCall &inputCall) { + inputCall.match([&](const MTPDinputGroupCall &data) { + _id = data.vid().v; + _accessHash = data.vaccess_hash().v; + createAndStartController(); + const auto weak = base::make_weak(this); + _instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { + crl::on_main(weak, [=, payload = std::move(payload)]{ + auto fingerprints = QJsonArray(); + for (const auto print : payload.fingerprints) { + auto object = QJsonObject(); + object.insert("hash", QString::fromStdString(print.hash)); + object.insert("setup", QString::fromStdString(print.setup)); + object.insert( + "fingerprint", + QString::fromStdString(print.fingerprint)); + fingerprints.push_back(object); + } + + auto root = QJsonObject(); + root.insert("ufrag", QString::fromStdString(payload.ufrag)); + root.insert("pwd", QString::fromStdString(payload.pwd)); + root.insert("fingerprints", fingerprints); + root.insert("ssrc", int(payload.ssrc)); + + const auto json = QJsonDocument(root).toJson( + QJsonDocument::Compact); + _api.request(MTPphone_JoinGroupCall( + MTP_flags(_muted.current() + ? MTPphone_JoinGroupCall::Flag::f_muted + : MTPphone_JoinGroupCall::Flag(0)), + inputCall, + MTP_dataJSON(MTP_bytes(json)) + )).done([=](const MTPUpdates &updates) { + _channel->session().api().applyUpdates(updates); + }).fail([=](const RPCError &error) { + int a = error.code(); + }).send(); + }); + }); + }); +} + +void GroupCall::setMuted(bool mute) { + _muted = mute; + if (_instance) { + _instance->setIsMuted(mute); + } +} + +bool GroupCall::handleUpdate(const MTPGroupCall &call) { + return call.match([&](const MTPDgroupCall &data) { + if (_id != data.vid().v + || _accessHash != data.vaccess_hash().v + || !_instance) { + return false; + } + if (const auto params = data.vparams()) { + params->match([&](const MTPDdataJSON &data) { + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson( + data.vdata().v, + &error); + if (error.error != QJsonParseError::NoError) { + LOG(("API Error: " + "Failed to parse group call params, error: %1." + ).arg(error.errorString())); + return; + } else if (!document.isObject()) { + LOG(("API Error: " + "Not an object received in group call params.")); + return; + } + const auto readString = []( + const QJsonObject &object, + const char *key) { + return object.value(key).toString().toStdString(); + }; + const auto root = document.object().value("transport").toObject(); + auto payload = tgcalls::GroupJoinResponsePayload(); + payload.ufrag = readString(root, "ufrag"); + payload.pwd = readString(root, "pwd"); + const auto prints = root.value("fingerprints").toArray(); + const auto candidates = root.value("candidates").toArray(); + for (const auto &print : prints) { + const auto object = print.toObject(); + payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{ + .hash = readString(object, "hash"), + .setup = readString(object, "setup"), + .fingerprint = readString(object, "fingerprint"), + }); + } + for (const auto &candidate : candidates) { + const auto object = candidate.toObject(); + payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{ + .port = readString(object, "port"), + .protocol = readString(object, "protocol"), + .network = readString(object, "network"), + .generation = readString(object, "generation"), + .id = readString(object, "id"), + .component = readString(object, "component"), + .foundation = readString(object, "foundation"), + .priority = readString(object, "priority"), + .ip = readString(object, "ip"), + .type = readString(object, "type"), + .tcpType = readString(object, "tcpType"), + .relAddr = readString(object, "relAddr"), + .relPort = readString(object, "relPort"), + }); + } + _instance->setJoinResponsePayload(payload); + _api.request(MTPphone_GetGroupParticipants( + inputCall(), + MTP_int(0), + MTP_int(10) + )).done([=](const MTPphone_GroupParticipants &result) { + auto sources = std::vector(); + result.match([&](const MTPDphone_groupParticipants &data) { + for (const auto &p : data.vparticipants().v) { + p.match([&](const MTPDgroupCallParticipant &data) { + if (data.vuser_id().v != _channel->session().userId()) { + sources.push_back(data.vsource().v); + } + }); + } + }); + _instance->setSsrcs(std::move(sources)); + _instance->setIsMuted(false); + }).fail([=](const RPCError &error) { + int a = error.code(); + }).send(); + }); + } + return true; + }, [&](const MTPDgroupCallPrivate &data) { + if (_instance || _id) { + return false; + } + join(MTP_inputGroupCall(data.vid(), data.vaccess_hash())); + return true; + }, [&](const MTPDgroupCallDiscarded &data) { + if (data.vid().v != _id) { + return false; + } + return true; + }); +} + +void GroupCall::createAndStartController() { + using AudioLevels = std::vector>; + + const auto weak = base::make_weak(this); + tgcalls::GroupInstanceDescriptor descriptor = { + .config = tgcalls::GroupConfig{ + }, + .networkStateUpdated = [=](bool) { + }, + .audioLevelsUpdated = [=](const AudioLevels &data) { + }, + }; + if (Logs::DebugEnabled()) { + auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); + auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt"); + auto callLogNative = QDir::toNativeSeparators(callLogPath); +#ifdef Q_OS_WIN + descriptor.config.logPath.data = callLogNative.toStdWString(); +#else // Q_OS_WIN + const auto callLogUtf = QFile::encodeName(callLogNative); + descriptor.config.logPath.data.resize(callLogUtf.size()); + ranges::copy(callLogUtf, descriptor.config.logPath.data.begin()); +#endif // Q_OS_WIN + QFile(callLogPath).remove(); + QDir().mkpath(callLogFolder); + } + + LOG(("Call Info: Creating group instance")); + _instance = std::make_unique( + std::move(descriptor)); + + const auto raw = _instance.get(); + if (_muted.current()) { + raw->setIsMuted(_muted.current()); + } + //raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled()); +} + +void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) { + if (_instance) { + const auto id = deviceId.toStdString(); + //if (input) { + // _instance->setAudioInputDevice(id); + //} else { + // _instance->setAudioOutputDevice(id); + //} + } +} + +void GroupCall::setAudioVolume(bool input, float level) { + if (_instance) { + //if (input) { + // _instance->setInputVolume(level); + //} else { + // _instance->setOutputVolume(level); + //} + } +} + +void GroupCall::setAudioDuckingEnabled(bool enabled) { + if (_instance) { + //_instance->setAudioOutputDuckingEnabled(enabled); + } +} + +void GroupCall::handleRequestError(const RPCError &error) { + //if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) { + // Ui::show(Box(tr::lng_call_error_not_available(tr::now, lt_user, _user->name))); + //} else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) { + // Ui::show(Box(tr::lng_call_error_outdated(tr::now, lt_user, _user->name))); + //} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) { + // Ui::show(Box(Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name))); + //} + //finish(FinishType::Failed); +} + +void GroupCall::handleControllerError(const QString &error) { + if (error == u"ERROR_INCOMPATIBLE"_q) { + //Ui::show(Box( + // Lang::Hard::CallErrorIncompatible().replace( + // "{user}", + // _user->name))); + } else if (error == u"ERROR_AUDIO_IO"_q) { + Ui::show(Box(tr::lng_call_error_audio_io(tr::now))); + } + //finish(FinishType::Failed); +} + +MTPInputGroupCall GroupCall::inputCall() const { + Expects(_id != 0); + + return MTP_inputGroupCall( + MTP_long(_id), + MTP_long(_accessHash)); +} + +void GroupCall::destroyController() { + if (_instance) { + //_instance->stop([](tgcalls::FinalState) { + //}); + + DEBUG_LOG(("Call Info: Destroying call controller..")); + _instance.reset(); + DEBUG_LOG(("Call Info: Call controller destroyed.")); + } +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h new file mode 100644 index 000000000..412a8e168 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -0,0 +1,84 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/weak_ptr.h" +#include "base/timer.h" +#include "base/bytes.h" +#include "mtproto/sender.h" +#include "mtproto/mtproto_auth_key.h" + +namespace tgcalls { +class GroupInstanceImpl; +} // namespace tgcalls + +namespace Calls { + +class GroupCall final : public base::has_weak_ptr { +public: + class Delegate { + public: + virtual ~Delegate() = default; + + }; + + GroupCall( + not_null delegate, + not_null channel, + const MTPInputGroupCall &inputCall); + ~GroupCall(); + + [[nodiscard]] not_null channel() const { + return _channel; + } + + void start(); + void join(const MTPInputGroupCall &inputCall); + bool handleUpdate(const MTPGroupCall &call); + + void setMuted(bool mute); + [[nodiscard]] bool muted() const { + return _muted.current(); + } + [[nodiscard]] rpl::producer mutedValue() const { + return _muted.value(); + } + + void setCurrentAudioDevice(bool input, const QString &deviceId); + void setAudioVolume(bool input, float level); + void setAudioDuckingEnabled(bool enabled); + + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + void handleRequestError(const RPCError &error); + void handleControllerError(const QString &error); + void createAndStartController(); + void destroyController(); + + [[nodiscard]] MTPInputGroupCall inputCall() const; + + const not_null _delegate; + const not_null _channel; + MTP::Sender _api; + crl::time _startTime = 0; + + rpl::variable _muted = false; + + uint64 _id = 0; + uint64 _accessHash = 0; + + std::unique_ptr _instance; + + rpl::lifetime _lifetime; + +}; + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index e99958109..b0cd55288 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_call.h" #include "calls/calls_panel.h" #include "data/data_user.h" +#include "data/data_channel.h" #include "data/data_session.h" #include "media/audio/media_audio_track.h" #include "platform/platform_specific.h" @@ -39,8 +40,7 @@ Instance::Instance() = default; Instance::~Instance() = default; void Instance::startOutgoingCall(not_null user, bool video) { - if (alreadyInCall()) { // Already in a call. - _currentCallPanel->showAndActivate(); + if (activateCurrentCall()) { return; } if (user->callsStatus() == UserData::CallsStatus::Private) { @@ -55,6 +55,26 @@ void Instance::startOutgoingCall(not_null user, bool video) { })); } +void Instance::startGroupCall(not_null channel) { + if (activateCurrentCall()) { + return; + } + requestPermissionsOrFail(crl::guard(this, [=] { + createGroupCall(channel, MTP_inputGroupCall(MTPlong(), MTPlong())); + })); +} + +void Instance::joinGroupCall( + not_null channel, + const MTPInputGroupCall &call) { + if (activateCurrentCall()) { + return; + } + requestPermissionsOrFail(crl::guard(this, [=] { + createGroupCall(channel, call); + })); +} + void Instance::callFinished(not_null call) { crl::on_main(call, [=] { destroyCall(call); @@ -142,6 +162,40 @@ void Instance::createCall(not_null user, Call::Type type, bool video) refreshDhConfig(); } +void Instance::destroyGroupCall(not_null call) { + if (_currentGroupCall.get() == call) { + //_currentCallPanel->closeBeforeDestroy(); + //_currentCallPanel = nullptr; + + auto taken = base::take(_currentGroupCall); + _currentGroupCallChanges.fire(nullptr); + taken.reset(); + + if (App::quitting()) { + LOG(("Calls::Instance doesn't prevent quit any more.")); + } + Core::App().quitPreventFinished(); + } +} + +void Instance::createGroupCall( + not_null channel, + const MTPInputGroupCall &inputCall) { + auto call = std::make_unique( + getGroupCallDelegate(), + channel, + inputCall); + const auto raw = call.get(); + + channel->session().account().sessionChanges( + ) | rpl::start_with_next([=] { + destroyGroupCall(raw); + }, raw->lifetime()); + + _currentGroupCall = std::move(call); + _currentGroupCallChanges.fire_copy(raw); +} + void Instance::refreshDhConfig() { Expects(_currentCall != nullptr); @@ -232,6 +286,10 @@ void Instance::handleUpdate( handleCallUpdate(session, data.vphone_call()); }, [&](const MTPDupdatePhoneCallSignalingData &data) { handleSignalingData(data); + }, [&](const MTPDupdateGroupCall &data) { + handleGroupCallUpdate(session, data.vcall()); + }, [&](const MTPDupdateGroupCallParticipants &data) { + handleGroupCallUpdate(session, data); }, [](const auto &) { Unexpected("Update type in Calls::Instance::handleUpdate."); }); @@ -267,7 +325,7 @@ void Instance::handleCallUpdate( LOG(("API Error: Self found in phoneCallRequested.")); } const auto &config = session->serverConfig(); - if (alreadyInCall() || !user || user->isSelf()) { + if (inCall() || inGroupCall() || !user || user->isSelf()) { const auto flags = phoneCall.is_video() ? MTPphone_DiscardCall::Flag::f_video : MTPphone_DiscardCall::Flag(0); @@ -290,6 +348,19 @@ void Instance::handleCallUpdate( } } +void Instance::handleGroupCallUpdate( + not_null session, + const MTPGroupCall &call) { + if (_currentGroupCall) { + _currentGroupCall->handleUpdate(call); + } +} + +void Instance::handleGroupCallUpdate( + not_null session, + const MTPDupdateGroupCallParticipants &update) { +} + void Instance::handleSignalingData( const MTPDupdatePhoneCallSignalingData &data) { if (!_currentCall || !_currentCall->handleSignalingData(data)) { @@ -298,10 +369,24 @@ void Instance::handleSignalingData( } } -bool Instance::alreadyInCall() { +bool Instance::inCall() const { return (_currentCall && _currentCall->state() != Call::State::Busy); } +bool Instance::inGroupCall() const { + return (_currentGroupCall != nullptr); +} + +bool Instance::activateCurrentCall() { + if (inCall()) { + _currentCallPanel->showAndActivate(); + return true; + } else if (inGroupCall()) { + return true; + } + return false; +} + Call *Instance::currentCall() const { return _currentCall.get(); } @@ -335,9 +420,12 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn } })); } else { - if (alreadyInCall()) { + if (inCall()) { _currentCall->hangup(); } + if (inGroupCall()) { + //_currentGroupCall->hangup(); // #TODO calls + } Ui::show(Box(tr::lng_no_mic_permission(tr::now), tr::lng_menu_settings(tr::now), crl::guard(this, [=] { Platform::OpenSystemSettingsForPermission(type); Ui::hideLayer(); diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index a50f99343..390292b97 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" #include "calls/calls_call.h" +#include "calls/calls_group_call.h" namespace Platform { enum class PermissionType; @@ -30,6 +31,7 @@ class Panel; class Instance : private Call::Delegate + , private GroupCall::Delegate , private base::Subscriber , public base::has_weak_ptr { public: @@ -37,6 +39,10 @@ public: ~Instance(); void startOutgoingCall(not_null user, bool video); + void startGroupCall(not_null channel); + void joinGroupCall( + not_null channel, + const MTPInputGroupCall &call); void handleUpdate( not_null session, const MTPUpdate &update); @@ -48,20 +54,33 @@ public: [[nodiscard]] bool isQuitPrevent(); private: - not_null getCallDelegate() { + [[nodiscard]] not_null getCallDelegate() { return static_cast(this); } - DhConfig getDhConfig() const override { + [[nodiscard]] not_null getGroupCallDelegate() { + return static_cast(this); + } + [[nodiscard]] DhConfig getDhConfig() const override { return _dhConfig; } void callFinished(not_null call) override; void callFailed(not_null call) override; void callRedial(not_null call) override; + void callRequestPermissionsOrFail(Fn onSuccess) override { + requestPermissionsOrFail(std::move(onSuccess)); + } + using Sound = Call::Delegate::Sound; void playSound(Sound sound) override; void createCall(not_null user, Call::Type type, bool video); void destroyCall(not_null call); - void requestPermissionsOrFail(Fn onSuccess) override; + + void createGroupCall( + not_null channel, + const MTPInputGroupCall &inputCall); + void destroyGroupCall(not_null call); + + void requestPermissionsOrFail(Fn onSuccess); void requestPermissionOrFail(Platform::PermissionType type, Fn onSuccess); void handleSignalingData(const MTPDupdatePhoneCallSignalingData &data); @@ -70,10 +89,18 @@ private: void refreshServerConfig(not_null session); bytes::const_span updateDhConfig(const MTPmessages_DhConfig &data); - bool alreadyInCall(); + bool activateCurrentCall(); + [[nodiscard]] bool inCall() const; + [[nodiscard]] bool inGroupCall() const; void handleCallUpdate( not_null session, const MTPPhoneCall &call); + void handleGroupCallUpdate( + not_null session, + const MTPGroupCall &call); + void handleGroupCallUpdate( + not_null session, + const MTPDupdateGroupCallParticipants &update); DhConfig _dhConfig; @@ -84,8 +111,9 @@ private: std::unique_ptr _currentCall; rpl::event_stream _currentCallChanges; std::unique_ptr _currentCallPanel; - base::Observable _currentCallChanged; - base::Observable _newServiceMessage; + + std::unique_ptr _currentGroupCall; + rpl::event_stream _currentGroupCallChanges; std::unique_ptr _callConnectingTrack; std::unique_ptr _callEndedTrack; diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 5bd443688..568840d82 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -86,9 +86,10 @@ struct PeerUpdate { ChannelLinkedChat = (1 << 27), ChannelLocation = (1 << 28), Slowmode = (1 << 29), + GroupCall = (1 << 30), // For iteration - LastUsedBit = (1 << 29), + LastUsedBit = (1 << 30), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 102eb637e..47a09086a 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_location.h" #include "data/data_histories.h" +#include "data/data_group_call.h" #include "base/unixtime.h" #include "history/history.h" #include "main/main_session.h" @@ -671,6 +672,32 @@ void ChannelData::privateErrorReceived() { } } +void ChannelData::setCall(const MTPInputGroupCall &call) { + call.match([&](const MTPDinputGroupCall &data) { + if (_call && _call->id() == data.vid().v) { + return; + } else if (!_call && !data.vid().v) { + return; + } else if (!data.vid().v) { + clearCall(); + return; + } + _call = std::make_unique( + this, + data.vid().v, + data.vaccess_hash().v); + session().changes().peerUpdated(this, UpdateFlag::GroupCall); + }); +} + +void ChannelData::clearCall() { + if (!_call) { + return; + } + _call = nullptr; + session().changes().peerUpdated(this, UpdateFlag::GroupCall); +} + namespace Data { void ApplyMigration( @@ -702,6 +729,12 @@ void ApplyChannelUpdate( auto canViewMembers = channel->canViewMembers(); auto canEditStickers = channel->canEditStickers(); + if (const auto call = update.vcall()) { + channel->setCall(*call); + } else { + channel->clearCall(); + } + channel->setFullFlags(update.vflags().v); channel->setUserpicPhoto(update.vchat_photo()); if (const auto migratedFrom = update.vmigrated_from_chat_id()) { diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 418e4a724..070c42502 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_pts_waiter.h" #include "data/data_location.h" +namespace Data { +class GroupCall; +} // namespace Data + struct ChannelLocation { QString address; Data::LocationPoint point; @@ -393,6 +397,12 @@ public: [[nodiscard]] QString invitePeekHash() const; void privateErrorReceived(); + [[nodiscard]] Data::GroupCall *call() const { + return _call.get(); + } + void setCall(const MTPInputGroupCall &call); + void clearCall(); + // Still public data members. uint64 access = 0; @@ -439,6 +449,8 @@ private: QString _inviteLink; std::optional _linkedChat; + std::unique_ptr _call; + int _slowmodeSeconds = 0; TimeId _slowmodeLastMessage = 0; diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp new file mode 100644 index 000000000..147c23ed4 --- /dev/null +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -0,0 +1,115 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_group_call.h" + +#include "data/data_channel.h" +#include "data/data_changes.h" +#include "data/data_session.h" +#include "main/main_session.h" +#include "apiwrap.h" + +namespace Data { +namespace { + +constexpr auto kRequestPerPage = 30; + +} // namespace + +GroupCall::GroupCall( + not_null channel, + uint64 id, + uint64 accessHash) +: _channel(channel) +, _id(id) +, _accessHash(accessHash) { +} + +uint64 GroupCall::id() const { + return _id; +} + +MTPInputGroupCall GroupCall::input() const { + return MTP_inputGroupCall(MTP_long(_id), MTP_long(_accessHash)); +} + +auto GroupCall::participants() const +-> const std::vector & { + return _participants; +} + +void GroupCall::requestParticipants() { + if (_participantsRequestId) { + return; + } else if (_participants.size() >= _fullCount && _allReceived) { + return; + } + const auto requestFromDate = (_allReceived || _participants.empty()) + ? TimeId(0) + : _participants.back().date; + auto &api = _channel->session().api(); + _participantsRequestId = api.request(MTPphone_GetGroupParticipants( + input(), + MTP_int(requestFromDate), + MTP_int(kRequestPerPage) + )).done([=](const MTPphone_GroupParticipants &result) { + result.match([&](const MTPDphone_groupParticipants &data) { + _fullCount = data.vcount().v; + _channel->owner().processUsers(data.vusers()); + for (const auto &p : data.vparticipants().v) { + p.match([&](const MTPDgroupCallParticipant &data) { + const auto userId = data.vuser_id().v; + const auto user = _channel->owner().user(userId); + const auto value = Participant{ + .user = user, + .date = data.vdate().v, + .source = data.vsource().v, + .muted = data.is_muted(), + .canSelfUnmute = data.is_can_self_unmute(), + .left = data.is_left() + }; + const auto i = ranges::find( + _participants, + user, + &Participant::user); + if (i == end(_participants)) { + _participants.push_back(value); + } else { + *i = value; + } + }); + } + if (!_allReceived + && (data.vparticipants().v.size() < kRequestPerPage)) { + _allReceived = true; + } + if (_allReceived) { + _fullCount = _participants.size(); + } + }); + ranges::sort(_participants, std::greater<>(), &Participant::date); + _channel->session().changes().peerUpdated( + _channel, + PeerUpdate::Flag::GroupCall); + }).fail([=](const RPCError &error) { + _allReceived = true; + _fullCount = _participants.size(); + _channel->session().changes().peerUpdated( + _channel, + PeerUpdate::Flag::GroupCall); + }).send(); +} + +int GroupCall::fullCount() const { + return _fullCount; +} + +bool GroupCall::participantsLoaded() const { + return _allReceived; +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h new file mode 100644 index 000000000..c72c20de4 --- /dev/null +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -0,0 +1,54 @@ +/* +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 UserData; +class ChannelData; + +namespace Data { + +class GroupCall final { +public: + GroupCall(not_null channel, uint64 id, uint64 accessHash); + + [[nodiscard]] uint64 id() const; + [[nodiscard]] MTPInputGroupCall input() const; + + struct Participant { + not_null user; + TimeId date = 0; + int source = 0; + bool muted = false; + bool canSelfUnmute = false; + bool left = false; + }; + + [[nodiscard]] auto participants() const + -> const std::vector &; + void requestParticipants(); + [[nodiscard]] bool participantsLoaded() const; + + [[nodiscard]] int fullCount() const; + +private: + const not_null _channel; + const uint64 _id = 0; + const uint64 _accessHash = 0; + + int _version = 0; + UserId _adminId = 0; + uint64 _reflectorId = 0; + mtpRequestId _participantsRequestId = 0; + + std::vector _participants; + int _fullCount = 0; + bool _allReceived = false; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 649b144d0..db6a3c46a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_scheduled_messages.h" #include "data/data_file_origin.h" #include "data/data_histories.h" +#include "data/data_group_call.h" #include "data/stickers/data_stickers.h" #include "history/history.h" #include "history/history_item.h" @@ -74,6 +75,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_pinned_tracker.h" #include "history/view/history_view_pinned_section.h" #include "history/view/history_view_pinned_bar.h" +#include "history/view/history_view_group_call_tracker.h" #include "history/view/media/history_view_media.h" #include "profile/profile_block_group_members.h" #include "info/info_memento.h" @@ -100,6 +102,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/qthelp_regex.h" #include "ui/chat/pinned_bar.h" +#include "ui/chat/group_call_bar.h" #include "ui/widgets/popup_menu.h" #include "ui/item_text_options.h" #include "ui/unread_badge.h" @@ -118,6 +121,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "support/support_common.h" #include "support/support_autocomplete.h" #include "dialogs/dialogs_key.h" +#include "calls/calls_instance.h" #include "facades.h" #include "app.h" #include "styles/style_chat.h" @@ -1775,6 +1779,8 @@ void HistoryWidget::showHistory( destroyUnreadBarOnClose(); _pinnedBar = nullptr; _pinnedTracker = nullptr; + _groupCallBar = nullptr; + _groupCallTracker = nullptr; _membersDropdown.destroy(); _scrollToAnimation.stop(); @@ -1886,6 +1892,7 @@ void HistoryWidget::showHistory( _updateHistoryItems.cancel(); setupPinnedTracker(); + setupGroupCallTracker(); if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) || _history->isReadyFor(_showAtMsgId)) { @@ -2093,6 +2100,9 @@ void HistoryWidget::updateControlsVisibility() { if (_pinnedBar) { _pinnedBar->show(); } + if (_groupCallBar) { + _groupCallBar->show(); + } if (_firstLoadRequest && !_scroll->isHidden()) { _scroll->hide(); } else if (!_firstLoadRequest && _scroll->isHidden()) { @@ -3037,6 +3047,9 @@ void HistoryWidget::hideChildWidgets() { if (_pinnedBar) { _pinnedBar->hide(); } + if (_groupCallBar) { + _groupCallBar->hide(); + } if (_voiceRecordBar) { _voiceRecordBar->hideFast(); } @@ -3250,6 +3263,9 @@ void HistoryWidget::showAnimated( if (_pinnedBar) { _pinnedBar->finishAnimating(); } + if (_groupCallBar) { + _groupCallBar->finishAnimating(); + } _topShadow->setVisible(params.withTopBarShadow ? false : true); _preserveScrollTop = false; @@ -3278,6 +3294,9 @@ void HistoryWidget::animationCallback() { if (_pinnedBar) { _pinnedBar->finishAnimating(); } + if (_groupCallBar) { + _groupCallBar->finishAnimating(); + } _cacheUnder = _cacheOver = QPixmap(); doneShow(); synteticScrollToY(_scroll->scrollTop()); @@ -3301,6 +3320,9 @@ void HistoryWidget::doneShow() { if (_pinnedBar) { _pinnedBar->finishAnimating(); } + if (_groupCallBar) { + _groupCallBar->finishAnimating(); + } checkHistoryActivation(); App::wnd()->setInnerFocus(); _preserveScrollTop = false; @@ -4409,7 +4431,12 @@ void HistoryWidget::updateControlsGeometry() { _pinnedBar->move(0, pinnedBarTop); _pinnedBar->resizeToWidth(width()); } - const auto contactStatusTop = pinnedBarTop + (_pinnedBar ? _pinnedBar->height() : 0); + const auto groupCallTop = pinnedBarTop + (_pinnedBar ? _pinnedBar->height() : 0); + if (_groupCallBar) { + _groupCallBar->move(0, groupCallTop); + _groupCallBar->resizeToWidth(width()); + } + const auto contactStatusTop = groupCallTop + (_groupCallBar ? _groupCallBar->height() : 0); if (_contactStatus) { _contactStatus->move(0, contactStatusTop); } @@ -4572,6 +4599,9 @@ void HistoryWidget::updateHistoryGeometry( if (_pinnedBar) { newScrollHeight -= _pinnedBar->height(); } + if (_groupCallBar) { + newScrollHeight -= _groupCallBar->height(); + } if (_contactStatus) { newScrollHeight -= _contactStatus->height(); } @@ -4804,6 +4834,7 @@ int HistoryWidget::computeMaxFieldHeight() const { - _topBar->height() - (_contactStatus ? _contactStatus->height() : 0) - (_pinnedBar ? _pinnedBar->height() : 0) + - (_groupCallBar ? _groupCallBar->height() : 0) - ((_editMsgId || replyToId() || readyToForward() @@ -5009,6 +5040,7 @@ void HistoryWidget::handlePeerMigration() { _migrated = _history->migrateFrom(); _list->notifyMigrateUpdated(); setupPinnedTracker(); + setupGroupCallTracker(); updateHistoryGeometry(); } const auto from = chat->owner().historyLoaded(chat); @@ -5341,6 +5373,68 @@ void HistoryWidget::refreshPinnedBarButton(bool many) { _pinnedBar->setRightButton(std::move(button)); } +void HistoryWidget::setupGroupCallTracker() { + Expects(_history != nullptr); + + const auto channel = _history->peer->asChannel(); + if (!channel) { + _groupCallTracker = nullptr; + _groupCallBar = nullptr; + return; + } + _groupCallTracker = std::make_unique( + channel); + _groupCallBar = std::make_unique( + this, + _groupCallTracker->content()); + + rpl::single( + rpl::empty_value() + ) | rpl::then( + base::ObservableViewer(Adaptive::Changed()) + ) | rpl::map([] { + return Adaptive::OneColumn(); + }) | rpl::start_with_next([=](bool one) { + _groupCallBar->setShadowGeometryPostprocess([=](QRect geometry) { + if (!one) { + geometry.setLeft(geometry.left() + st::lineWidth); + } + return geometry; + }); + }, _groupCallBar->lifetime()); + + rpl::merge( + _groupCallBar->barClicks(), + _groupCallBar->joinClicks() + ) | rpl::start_with_next([=] { + const auto channel = _history->peer->asChannel(); + if (!channel) { + return; + } + const auto call = channel->call(); + if (!call) { + return; + } + Core::App().calls().joinGroupCall(channel, call->input()); + }, _groupCallBar->lifetime()); + + _groupCallBarHeight = 0; + _groupCallBar->heightValue( + ) | rpl::start_with_next([=](int height) { + _topDelta = _preserveScrollTop ? 0 : (height - _groupCallBarHeight); + _groupCallBarHeight = height; + updateHistoryGeometry(); + updateControlsGeometry(); + _topDelta = 0; + }, _groupCallBar->lifetime()); + + orderWidgets(); + + if (_a_show.animating()) { + _groupCallBar->hide(); + } +} + void HistoryWidget::requestMessageData(MsgId msgId) { const auto callback = [=](ChannelData *channel, MsgId msgId) { messageDataReceived(channel, msgId); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 73d967fc7..62e61c5e7 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -61,6 +61,7 @@ class FlatButton; class LinkButton; class RoundButton; class PinnedBar; +class GroupCallBar; struct PreparedList; class SendFilesWay; namespace Toast { @@ -90,6 +91,7 @@ class TopBarWidget; class ContactStatus; class Element; class PinnedTracker; +class GroupCallTracker; namespace Controls { class RecordLock; class VoiceRecordBar; @@ -475,6 +477,8 @@ private: int wasScrollTop, int nowScrollTop); + void setupGroupCallTracker(); + void sendInlineResult(InlineBots::ResultSelected result); void drawField(Painter &p, const QRect &rect); @@ -591,10 +595,15 @@ private: std::unique_ptr _pinnedTracker; std::unique_ptr _pinnedBar; int _pinnedBarHeight = 0; - bool _preserveScrollTop = false; FullMsgId _pinnedClickedId; std::optional _minPinnedId; + std::unique_ptr _groupCallTracker; + std::unique_ptr _groupCallBar; + int _groupCallBarHeight = 0; + + bool _preserveScrollTop = false; + mtpRequestId _saveEditMsgRequestId = 0; QStringList _parsedLinks; diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp new file mode 100644 index 000000000..9712d559f --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp @@ -0,0 +1,42 @@ +/* +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_group_call_tracker.h" + +#include "data/data_channel.h" +#include "data/data_changes.h" +#include "data/data_group_call.h" +#include "main/main_session.h" +#include "ui/chat/group_call_bar.h" + +namespace HistoryView { + +GroupCallTracker::GroupCallTracker(not_null channel) +: _channel(channel) { +} + +rpl::producer GroupCallTracker::content() const { + const auto channel = _channel; + return channel->session().changes().peerFlagsValue( + channel, + Data::PeerUpdate::Flag::GroupCall + ) | rpl::map([=]() -> Ui::GroupCallBarContent { + const auto call = channel->call(); + if (!call) { + return { .shown = false }; + } else if (!call->fullCount() && !call->participantsLoaded()) { + call->requestParticipants(); + } + return { .count = call->fullCount(), .shown = true }; + }); +} + +rpl::producer<> GroupCallTracker::joinClicks() const { + return _joinClicks.events(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.h b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.h new file mode 100644 index 000000000..1dc6c4f94 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.h @@ -0,0 +1,32 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" + +namespace Ui { +struct GroupCallBarContent; +} // namespace Ui + +namespace HistoryView { + +class GroupCallTracker final { +public: + GroupCallTracker(not_null channel); + + [[nodiscard]] rpl::producer content() const; + [[nodiscard]] rpl::producer<> joinClicks() const; + +private: + not_null _channel; + + rpl::event_stream<> _joinClicks; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index c5eee7f76..1414db537 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -200,6 +200,8 @@ void TopBarWidget::onCall() { if (const auto peer = _activeChat.key.peer()) { if (const auto user = peer->asUser()) { Core::App().calls().startOutgoingCall(user, false); + } else if (const auto megagroup = peer->asMegagroup()) { + Core::App().calls().startGroupCall(megagroup); } } } @@ -691,6 +693,8 @@ void TopBarWidget::updateControlsVisibility() { if (const auto user = peer->asUser()) { return session().serverConfig().phoneCallsEnabled.current() && user->hasCalls(); + } else if (const auto megagroup = peer->asMegagroup()) { + return true; } } return false; diff --git a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp new file mode 100644 index 000000000..497120ad6 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp @@ -0,0 +1,182 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/group_call_bar.h" + +#include "ui/chat/message_bar.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "styles/style_chat.h" +#include "styles/palette.h" + +#include + +namespace Ui { + +GroupCallBar::GroupCallBar( + not_null parent, + rpl::producer content) + : _wrap(parent, object_ptr(parent)) + , _inner(_wrap.entity()) + , _shadow(std::make_unique(_wrap.parentWidget())) { + _wrap.hide(anim::type::instant); + _shadow->hide(); + + _wrap.entity()->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg); + }, lifetime()); + _wrap.setAttribute(Qt::WA_OpaquePaintEvent); + + auto copy = std::move( + content + ) | rpl::start_spawning(_wrap.lifetime()); + + rpl::duplicate( + copy + ) | rpl::start_with_next([=](GroupCallBarContent &&content) { + _content = content; + _inner->update(); + }, lifetime()); + + std::move( + copy + ) | rpl::map([=](const GroupCallBarContent &content) { + return !content.shown; + }) | rpl::start_with_next_done([=](bool hidden) { + _shouldBeShown = !hidden; + if (!_forceHidden) { + _wrap.toggle(_shouldBeShown, anim::type::normal); + } + }, [=] { + _forceHidden = true; + _wrap.toggle(false, anim::type::normal); + }, lifetime()); + + setupInner(); +} + +GroupCallBar::~GroupCallBar() { +} + +void GroupCallBar::setupInner() { + _inner->resize(0, st::historyReplyHeight); + _inner->paintRequest( + ) | rpl::start_with_next([=](QRect rect) { + auto p = Painter(_inner); + paint(p); + }, _inner->lifetime()); + + + // Clicks. + _inner->setCursor(style::cur_pointer); + _inner->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonPress); + }) | rpl::map([=] { + return _inner->events( + ) | rpl::filter([=](not_null event) { + return (event->type() == QEvent::MouseButtonRelease); + }) | rpl::take(1) | rpl::filter([=](not_null event) { + return _inner->rect().contains( + static_cast(event.get())->pos()); + }); + }) | rpl::flatten_latest( + ) | rpl::map([] { + return rpl::empty_value(); + }) | rpl::start_to_stream(_barClicks, _inner->lifetime()); +} + +void GroupCallBar::paint(Painter &p) { + p.fillRect(_inner->rect(), st::historyComposeAreaBg); + p.setPen(st::defaultMessageBar.textFg); + p.setFont(st::defaultMessageBar.text.font); + p.drawText(_inner->rect(), "Voice Chat", style::al_center); +} + +void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) { + _inner->resizeToWidth(wrapGeometry.width()); + const auto hidden = _wrap.isHidden() || !wrapGeometry.height(); + if (_shadow->isHidden() != hidden) { + _shadow->setVisible(!hidden); + } +} + +void GroupCallBar::setShadowGeometryPostprocess(Fn postprocess) { + _shadowGeometryPostprocess = std::move(postprocess); + updateShadowGeometry(_wrap.geometry()); +} + +void GroupCallBar::updateShadowGeometry(QRect wrapGeometry) { + const auto regular = QRect( + wrapGeometry.x(), + wrapGeometry.y() + wrapGeometry.height(), + wrapGeometry.width(), + st::lineWidth); + _shadow->setGeometry(_shadowGeometryPostprocess + ? _shadowGeometryPostprocess(regular) + : regular); +} + +void GroupCallBar::show() { + if (!_forceHidden) { + return; + } + _forceHidden = false; + if (_shouldBeShown) { + _wrap.show(anim::type::instant); + _shadow->show(); + } +} + +void GroupCallBar::hide() { + if (_forceHidden) { + return; + } + _forceHidden = true; + _wrap.hide(anim::type::instant); + _shadow->hide(); +} + +void GroupCallBar::raise() { + _wrap.raise(); + _shadow->raise(); +} + +void GroupCallBar::finishAnimating() { + _wrap.finishAnimating(); +} + +void GroupCallBar::move(int x, int y) { + _wrap.move(x, y); +} + +void GroupCallBar::resizeToWidth(int width) { + _wrap.entity()->resizeToWidth(width); +} + +int GroupCallBar::height() const { + return !_forceHidden + ? _wrap.height() + : _shouldBeShown + ? st::historyReplyHeight + : 0; +} + +rpl::producer GroupCallBar::heightValue() const { + return _wrap.heightValue(); +} + +rpl::producer<> GroupCallBar::barClicks() const { + return _barClicks.events(); +} + +rpl::producer<> GroupCallBar::joinClicks() const { + return rpl::never<>(); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/group_call_bar.h b/Telegram/SourceFiles/ui/chat/group_call_bar.h new file mode 100644 index 000000000..fe0ec25ae --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/group_call_bar.h @@ -0,0 +1,69 @@ +/* +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/wrap/slide_wrap.h" +#include "base/object_ptr.h" + +class Painter; + +namespace Ui { + +class PlainShadow; + +struct GroupCallBarContent { + int count = 0; + bool shown = false; + bool joined = false; + // #TODO calls userpics +}; + +class GroupCallBar final { +public: + GroupCallBar( + not_null parent, + rpl::producer content); + ~GroupCallBar(); + + void show(); + void hide(); + void raise(); + void finishAnimating(); + + void setShadowGeometryPostprocess(Fn postprocess); + + void move(int x, int y); + void resizeToWidth(int width); + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; + [[nodiscard]] rpl::producer<> barClicks() const; + [[nodiscard]] rpl::producer<> joinClicks() const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _wrap.lifetime(); + } + +private: + void updateShadowGeometry(QRect wrapGeometry); + void updateControlsGeometry(QRect wrapGeometry); + void setupInner(); + void paint(Painter &p); + + Ui::SlideWrap<> _wrap; + not_null _inner; + std::unique_ptr _shadow; + rpl::event_stream<> _barClicks; + Fn _shadowGeometryPostprocess; + bool _shouldBeShown = false; + bool _forceHidden = false; + + GroupCallBarContent _content; + +}; + +} // namespace Ui diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index e8e9dd893..176e5fff5 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit e8e9dd8934fdea47b2b2b0dadf2100fc3c17c914 +Subproject commit 176e5fff55478f23deec25c3bbcdaf9d0f3e19b2 diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index b4841e601..6e837df05 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -53,6 +53,9 @@ if (NOT DESKTOP_APP_DISABLE_WEBRTC_INTEGRATION) VideoCaptureInterfaceImpl.h VideoCapturerInterface.h + group/GroupInstanceImpl.cpp + group/GroupInstanceImpl.h + platform/PlatformInterface.h # Android diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 1c7d0888c..016c02a6d 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -78,6 +78,8 @@ PRIVATE ui/chat/attach/attach_single_file_preview.h ui/chat/attach/attach_single_media_preview.cpp ui/chat/attach/attach_single_media_preview.h + ui/chat/group_call_bar.cpp + ui/chat/group_call_bar.h ui/chat/message_bar.cpp ui/chat/message_bar.h ui/chat/pinned_bar.cpp From 25f3c14780028cc0215a3f05f6fbeef4f784075d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Nov 2020 14:56:46 +0300 Subject: [PATCH 159/370] Apply group call updates. --- Telegram/SourceFiles/calls/calls_call.h | 1 + .../SourceFiles/calls/calls_group_call.cpp | 147 +++++++++++++--- Telegram/SourceFiles/calls/calls_group_call.h | 37 +++- Telegram/SourceFiles/calls/calls_instance.cpp | 27 ++- Telegram/SourceFiles/calls/calls_instance.h | 3 + Telegram/SourceFiles/data/data_group_call.cpp | 160 +++++++++++++++--- Telegram/SourceFiles/data/data_group_call.h | 22 ++- Telegram/SourceFiles/data/data_session.cpp | 14 ++ Telegram/SourceFiles/data/data_session.h | 7 + 9 files changed, 357 insertions(+), 61 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 2075be7ab..7bfef7ba6 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -191,6 +191,7 @@ private: Ended, Failed, }; + void handleRequestError(const RPCError &error); void handleControllerError(const QString &error); void finish( diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 0dcd19e6b..15ff367de 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -25,7 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL //#include "calls/calls_panel.h" //#include "webrtc/webrtc_video_track.h" //#include "webrtc/webrtc_media_devices.h" +#include "data/data_changes.h" #include "data/data_channel.h" +#include "data/data_group_call.h" //#include "data/data_session.h" //#include "facades.h" @@ -49,6 +51,7 @@ GroupCall::GroupCall( , _channel(channel) , _api(&_channel->session().mtp()) { if (inputCall.c_inputGroupCall().vid().v) { + _state = State::Joining; join(inputCall); } else { start(); @@ -59,19 +62,51 @@ GroupCall::~GroupCall() { destroyController(); } +void GroupCall::setState(State state) { + if (_state.current() == State::Failed) { + return; + } else if (_state.current() == State::FailedHangingUp + && state != State::Failed) { + return; + } + if (_state.current() == state) { + return; + } + _state = state; + + if (false + || state == State::Ended + || state == State::Failed) { + // Destroy controller before destroying Call Panel, + // so that the panel hide animation is smooth. + destroyController(); + } + switch (state) { + case State::Ended: + _delegate->groupCallFinished(this); + break; + case State::Failed: + _delegate->groupCallFailed(this); + break; + } +} + void GroupCall::start() { const auto randomId = rand_value(); _api.request(MTPphone_CreateGroupCall( _channel->inputChannel, MTP_int(randomId) )).done([=](const MTPUpdates &result) { + _acceptFields = true; _channel->session().api().applyUpdates(result); + _acceptFields = false; }).fail([=](const RPCError &error) { int a = error.code(); }).send(); } void GroupCall::join(const MTPInputGroupCall &inputCall) { + setState(State::Joining); inputCall.match([&](const MTPDinputGroupCall &data) { _id = data.vid().v; _accessHash = data.vaccess_hash().v; @@ -91,10 +126,11 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { } auto root = QJsonObject(); + const auto ssrc = payload.ssrc; root.insert("ufrag", QString::fromStdString(payload.ufrag)); root.insert("pwd", QString::fromStdString(payload.pwd)); root.insert("fingerprints", fingerprints); - root.insert("ssrc", int(payload.ssrc)); + root.insert("ssrc", double(payload.ssrc)); const auto json = QJsonDocument(root).toJson( QJsonDocument::Compact); @@ -105,6 +141,9 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { inputCall, MTP_dataJSON(MTP_bytes(json)) )).done([=](const MTPUpdates &updates) { + _mySsrc = ssrc; + setState(State::Joined); + _channel->session().api().applyUpdates(updates); }).fail([=](const RPCError &error) { int a = error.code(); @@ -112,6 +151,77 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { }); }); }); + _channel->setCall(inputCall); + + _channel->session().changes().peerFlagsValue( + _channel, + Data::PeerUpdate::Flag::GroupCall + ) | rpl::start_with_next([=] { + checkParticipants(); + }, _lifetime); +} + +void GroupCall::checkParticipants() { + if (!joined()) { + return; + } + const auto call = _channel->call(); + if (!call || call->id() != _id) { + finish(FinishType::Ended); + return; + } + const auto &sources = call->sources(); + if (sources.size() != call->fullCount() || sources.empty()) { + call->reload(); + return; + } + auto ssrcs = std::vector(); + ssrcs.reserve(sources.size()); + for (const auto source : sources) { + if (source != _mySsrc) { + ssrcs.push_back(source); + } + } + _instance->setSsrcs(std::move(ssrcs)); + _instance->setIsMuted(false); +} + +void GroupCall::hangup() { + finish(FinishType::Ended); +} + +void GroupCall::finish(FinishType type) { + Expects(type != FinishType::None); + + const auto finalState = (type == FinishType::Ended) + ? State::Ended + : State::Failed; + const auto hangupState = (type == FinishType::Ended) + ? State::HangingUp + : State::FailedHangingUp; + const auto state = _state.current(); + if (state == State::HangingUp + || state == State::FailedHangingUp + || state == State::Ended + || state == State::Failed) { + return; + } + if (!joined()) { + setState(finalState); + return; + } + + setState(hangupState); + _api.request(MTPphone_LeaveGroupCall( + inputCall() + )).done([=](const MTPUpdates &result) { + // Here 'this' could be destroyed by updates, so we set Ended after + // updates being handled, but in a guarded way. + crl::on_main(this, [=] { setState(finalState); }); + _channel->session().api().applyUpdates(result); + }).fail([=](const RPCError &error) { + setState(finalState); + }).send(); } void GroupCall::setMuted(bool mute) { @@ -123,7 +233,13 @@ void GroupCall::setMuted(bool mute) { bool GroupCall::handleUpdate(const MTPGroupCall &call) { return call.match([&](const MTPDgroupCall &data) { - if (_id != data.vid().v + if (_acceptFields) { + if (_instance || _id) { + return false; + } + join(MTP_inputGroupCall(data.vid(), data.vaccess_hash())); + return true; + } else if (_id != data.vid().v || _accessHash != data.vaccess_hash().v || !_instance) { return false; @@ -182,35 +298,10 @@ bool GroupCall::handleUpdate(const MTPGroupCall &call) { }); } _instance->setJoinResponsePayload(payload); - _api.request(MTPphone_GetGroupParticipants( - inputCall(), - MTP_int(0), - MTP_int(10) - )).done([=](const MTPphone_GroupParticipants &result) { - auto sources = std::vector(); - result.match([&](const MTPDphone_groupParticipants &data) { - for (const auto &p : data.vparticipants().v) { - p.match([&](const MTPDgroupCallParticipant &data) { - if (data.vuser_id().v != _channel->session().userId()) { - sources.push_back(data.vsource().v); - } - }); - } - }); - _instance->setSsrcs(std::move(sources)); - _instance->setIsMuted(false); - }).fail([=](const RPCError &error) { - int a = error.code(); - }).send(); + checkParticipants(); }); } return true; - }, [&](const MTPDgroupCallPrivate &data) { - if (_instance || _id) { - return false; - } - join(MTP_inputGroupCall(data.vid(), data.vaccess_hash())); - return true; }, [&](const MTPDgroupCallDiscarded &data) { if (data.vid().v != _id) { return false; diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index 412a8e168..6286f3de5 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -25,6 +25,9 @@ public: public: virtual ~Delegate() = default; + virtual void groupCallFinished(not_null call) = 0; + virtual void groupCallFailed(not_null call) = 0; + }; GroupCall( @@ -38,6 +41,7 @@ public: } void start(); + void hangup(); void join(const MTPInputGroupCall &inputCall); bool handleUpdate(const MTPGroupCall &call); @@ -48,6 +52,25 @@ public: [[nodiscard]] rpl::producer mutedValue() const { return _muted.value(); } + [[nodiscard]] bool joined() const { + return _mySsrc != 0; + } + + enum State { + Creating, + Joining, + Joined, + FailedHangingUp, + Failed, + HangingUp, + Ended, + }; + [[nodiscard]] State state() const { + return _state.current(); + } + [[nodiscard]] rpl::producer stateValue() const { + return _state.value(); + } void setCurrentAudioDevice(bool input, const QString &deviceId); void setAudioVolume(bool input, float level); @@ -58,22 +81,34 @@ public: } private: + enum class FinishType { + None, + Ended, + Failed, + }; + void handleRequestError(const RPCError &error); void handleControllerError(const QString &error); void createAndStartController(); void destroyController(); + void checkParticipants(); + + void setState(State state); + void finish(FinishType type); [[nodiscard]] MTPInputGroupCall inputCall() const; const not_null _delegate; const not_null _channel; MTP::Sender _api; - crl::time _startTime = 0; + rpl::variable _state = State::Creating; rpl::variable _muted = false; + bool _acceptFields = false; uint64 _id = 0; uint64 _accessHash = 0; + uint32 _mySsrc = 0; std::unique_ptr _instance; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index b0cd55288..9dd83f4ec 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_call.h" #include "calls/calls_panel.h" #include "data/data_user.h" +#include "data/data_group_call.h" #include "data/data_channel.h" #include "data/data_session.h" #include "media/audio/media_audio_track.h" @@ -93,6 +94,18 @@ void Instance::callRedial(not_null call) { } } +void Instance::groupCallFinished(not_null call) { + crl::on_main(call, [=] { + destroyGroupCall(call); + }); +} + +void Instance::groupCallFailed(not_null call) { + crl::on_main(call, [=] { + destroyGroupCall(call); + }); +} + void Instance::playSound(Sound sound) { switch (sound) { case Sound::Busy: { @@ -351,6 +364,12 @@ void Instance::handleCallUpdate( void Instance::handleGroupCallUpdate( not_null session, const MTPGroupCall &call) { + const auto callId = call.match([](const auto &data) { + return data.vid().v; + }); + if (const auto existing = session->data().groupCall(callId)) { + existing->applyUpdate(call); + } if (_currentGroupCall) { _currentGroupCall->handleUpdate(call); } @@ -359,6 +378,12 @@ void Instance::handleGroupCallUpdate( void Instance::handleGroupCallUpdate( not_null session, const MTPDupdateGroupCallParticipants &update) { + const auto callId = update.vcall().match([](const auto &data) { + return data.vid().v; + }); + if (const auto existing = session->data().groupCall(callId)) { + existing->applyUpdate(update); + } } void Instance::handleSignalingData( @@ -424,7 +449,7 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn _currentCall->hangup(); } if (inGroupCall()) { - //_currentGroupCall->hangup(); // #TODO calls + _currentGroupCall->hangup(); } Ui::show(Box(tr::lng_no_mic_permission(tr::now), tr::lng_menu_settings(tr::now), crl::guard(this, [=] { Platform::OpenSystemSettingsForPermission(type); diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 390292b97..2c2c2a761 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -70,6 +70,9 @@ private: requestPermissionsOrFail(std::move(onSuccess)); } + void groupCallFinished(not_null call) override; + void groupCallFailed(not_null call) override; + using Sound = Call::Delegate::Sound; void playSound(Sound sound) override; void createCall(not_null user, Call::Type type, bool video); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 147c23ed4..97192f76d 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -29,10 +29,19 @@ GroupCall::GroupCall( , _accessHash(accessHash) { } +GroupCall::~GroupCall() { + _channel->session().api().request(_participantsRequestId).cancel(); + _channel->session().api().request(_reloadRequestId).cancel(); +} + uint64 GroupCall::id() const { return _id; } +not_null GroupCall::channel() const { + return _channel; +} + MTPInputGroupCall GroupCall::input() const { return MTP_inputGroupCall(MTP_long(_id), MTP_long(_accessHash)); } @@ -42,8 +51,12 @@ auto GroupCall::participants() const return _participants; } +const base::flat_set &GroupCall::sources() const { + return _sources; +} + void GroupCall::requestParticipants() { - if (_participantsRequestId) { + if (_participantsRequestId || _reloadRequestId) { return; } else if (_participants.size() >= _fullCount && _allReceived) { return; @@ -58,31 +71,9 @@ void GroupCall::requestParticipants() { MTP_int(kRequestPerPage) )).done([=](const MTPphone_GroupParticipants &result) { result.match([&](const MTPDphone_groupParticipants &data) { - _fullCount = data.vcount().v; _channel->owner().processUsers(data.vusers()); - for (const auto &p : data.vparticipants().v) { - p.match([&](const MTPDgroupCallParticipant &data) { - const auto userId = data.vuser_id().v; - const auto user = _channel->owner().user(userId); - const auto value = Participant{ - .user = user, - .date = data.vdate().v, - .source = data.vsource().v, - .muted = data.is_muted(), - .canSelfUnmute = data.is_can_self_unmute(), - .left = data.is_left() - }; - const auto i = ranges::find( - _participants, - user, - &Participant::user); - if (i == end(_participants)) { - _participants.push_back(value); - } else { - *i = value; - } - }); - } + applyParticipantsSlice(data.vparticipants().v); + _fullCount = data.vcount().v; if (!_allReceived && (data.vparticipants().v.size() < kRequestPerPage)) { _allReceived = true; @@ -91,16 +82,17 @@ void GroupCall::requestParticipants() { _fullCount = _participants.size(); } }); - ranges::sort(_participants, std::greater<>(), &Participant::date); _channel->session().changes().peerUpdated( _channel, PeerUpdate::Flag::GroupCall); + _participantsRequestId = 0; }).fail([=](const RPCError &error) { - _allReceived = true; _fullCount = _participants.size(); + _allReceived = true; _channel->session().changes().peerUpdated( _channel, PeerUpdate::Flag::GroupCall); + _participantsRequestId = 0; }).send(); } @@ -112,4 +104,118 @@ bool GroupCall::participantsLoaded() const { return _allReceived; } +void GroupCall::applyUpdate(const MTPGroupCall &update) { + applyCall(update, false); +} + +void GroupCall::applyCall(const MTPGroupCall &call, bool force) { + call.match([&](const MTPDgroupCall &data) { + const auto changed = (_version != data.vversion().v) + || (_fullCount != data.vparticipants_count().v); + if (!force && !changed) { + return; + } else if (!force && _version > data.vversion().v) { + reload(); + return; + } + _version = data.vversion().v; + _fullCount = data.vparticipants_count().v; + }, [&](const MTPDgroupCallDiscarded &data) { + const auto changed = (_duration != data.vduration().v) + || !_finished; + if (!force && !changed) { + return; + } + _finished = true; + _duration = data.vduration().v; + }); + _channel->session().changes().peerUpdated( + _channel, + PeerUpdate::Flag::GroupCall); +} + +void GroupCall::reload() { + if (_reloadRequestId) { + return; + } else if (_participantsRequestId) { + _channel->session().api().request(_participantsRequestId).cancel(); + _participantsRequestId = 0; + } + _reloadRequestId = _channel->session().api().request( + MTPphone_GetGroupCall(input()) + ).done([=](const MTPphone_GroupCall &result) { + result.match([&](const MTPDphone_groupCall &data) { + _channel->owner().processUsers(data.vusers()); + _participants.clear(); + _sources.clear(); + applyParticipantsSlice(data.vparticipants().v); + for (const auto &source : data.vsources().v) { + _sources.emplace(source.v); + } + _fullCount = _sources.size(); + if (_participants.size() > _fullCount) { + _fullCount = _participants.size(); + } + _allReceived = (_fullCount == _participants.size()); + applyCall(data.vcall(), true); + }); + _reloadRequestId = 0; + }).fail([=](const RPCError &error) { + _reloadRequestId = 0; + }).send(); +} + +void GroupCall::applyParticipantsSlice( + const QVector &list) { + for (const auto &participant : list) { + participant.match([&](const MTPDgroupCallParticipant &data) { + const auto userId = data.vuser_id().v; + const auto user = _channel->owner().user(userId); + const auto i = ranges::find( + _participants, + user, + &Participant::user); + if (data.is_left()) { + if (i != end(_participants)) { + _sources.remove(i->source); + _participants.erase(i); + } + if (_fullCount > _participants.size()) { + --_fullCount; + } + return; + } + const auto value = Participant{ + .user = user, + .date = data.vdate().v, + .source = uint32(data.vsource().v), + .muted = data.is_muted(), + .canSelfUnmute = data.is_can_self_unmute(), + }; + if (i == end(_participants)) { + _participants.push_back(value); + ++_fullCount; + } else { + *i = value; + } + _sources.emplace(uint32(data.vsource().v)); + }); + } + ranges::sort(_participants, std::greater<>(), &Participant::date); +} + +void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) { + if (update.vversion().v <= _version) { + return; + } else if (update.vversion().v != _version + 1) { + reload(); + return; + } + _version = update.vversion().v; + applyParticipantsSlice(update.vparticipants().v); + _channel->session().changes().peerUpdated( + _channel, + PeerUpdate::Flag::GroupCall); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index c72c20de4..add71d960 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -15,38 +15,52 @@ namespace Data { class GroupCall final { public: GroupCall(not_null channel, uint64 id, uint64 accessHash); + ~GroupCall(); [[nodiscard]] uint64 id() const; + [[nodiscard]] not_null channel() const; [[nodiscard]] MTPInputGroupCall input() const; struct Participant { not_null user; TimeId date = 0; - int source = 0; + uint32 source = 0; bool muted = false; bool canSelfUnmute = false; - bool left = false; }; [[nodiscard]] auto participants() const -> const std::vector &; + [[nodiscard]] const base::flat_set &sources() const; void requestParticipants(); [[nodiscard]] bool participantsLoaded() const; + void applyUpdate(const MTPGroupCall &update); + void applyUpdate(const MTPDupdateGroupCallParticipants &update); + [[nodiscard]] int fullCount() const; + void reload(); + [[nodiscard]] bool finished() const; + [[nodiscard]] int duration() const; + private: + void applyCall(const MTPGroupCall &call, bool force); + void applyParticipantsSlice(const QVector &list); + const not_null _channel; const uint64 _id = 0; const uint64 _accessHash = 0; int _version = 0; - UserId _adminId = 0; - uint64 _reflectorId = 0; mtpRequestId _participantsRequestId = 0; + mtpRequestId _reloadRequestId = 0; std::vector _participants; + base::flat_set _sources; int _fullCount = 0; + int _duration = 0; + bool _finished = false; bool _allReceived = false; }; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 924c61179..c0b5cedf6 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name #include "data/stickers/data_stickers.h" #include "data/data_changes.h" +#include "data/data_group_call.h" #include "data/data_media_types.h" #include "data/data_folder.h" #include "data/data_channel.h" @@ -791,6 +792,19 @@ void Session::applyMaximumChatVersions(const MTPVector &data) { } } +void Session::registerGroupCall(not_null call) { + _groupCalls.emplace(call->id(), call); +} + +void Session::unregisterGroupCall(not_null call) { + _groupCalls.remove(call->id()); +} + +GroupCall *Session::groupCall(uint64 callId) const { + const auto i = _groupCalls.find(callId); + return (i != end(_groupCalls)) ? i->second.get() : nullptr; +} + PeerData *Session::peerByUsername(const QString &username) const { const auto uname = username.trimmed(); for (const auto &[peerId, peer] : _peers) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 4474e89b5..fc2fd3b46 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -60,6 +60,7 @@ class Histories; class DocumentMedia; class PhotoMedia; class Stickers; +class GroupCall; class Session final { public: @@ -153,6 +154,10 @@ public: void applyMaximumChatVersions(const MTPVector &data); + void registerGroupCall(not_null call); + void unregisterGroupCall(not_null call); + GroupCall *groupCall(uint64 callId) const; + void enumerateUsers(Fn)> action) const; void enumerateGroups(Fn)> action) const; void enumerateChannels(Fn)> action) const; @@ -906,6 +911,8 @@ private: base::flat_set> _heavyViewParts; + base::flat_map> _groupCalls; + History *_topPromoted = nullptr; NotifySettings _defaultUserNotifySettings; From 8833d3e45b9eafe822284d2f11e18b7eff140265 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Nov 2020 15:54:20 +0300 Subject: [PATCH 160/370] Add empty group call panel. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/calls/calls.style | 3 + .../SourceFiles/calls/calls_group_panel.cpp | 462 ++++++++++++++++++ .../SourceFiles/calls/calls_group_panel.h | 96 ++++ Telegram/SourceFiles/calls/calls_instance.cpp | 22 +- Telegram/SourceFiles/calls/calls_instance.h | 5 + 6 files changed, 588 insertions(+), 2 deletions(-) create mode 100644 Telegram/SourceFiles/calls/calls_group_panel.cpp create mode 100644 Telegram/SourceFiles/calls/calls_group_panel.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c54dd9165..a483dabc4 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -266,6 +266,8 @@ PRIVATE calls/calls_call.h calls/calls_group_call.cpp calls/calls_group_call.h + calls/calls_group_panel.cpp + calls/calls_group_panel.h calls/calls_emoji_fingerprint.cpp calls/calls_emoji_fingerprint.h calls/calls_instance.cpp diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index ae044b7c9..91441586b 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -399,3 +399,6 @@ callTitleShadow: icon {{ "calls_shadow_controls", windowShadowFg }}; callErrorToast: Toast(defaultToast) { minWidth: 240px; } + +groupCallWidth: 380px; +groupCallHeight: 580px; diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp new file mode 100644 index 000000000..4309519e4 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -0,0 +1,462 @@ +/* +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 "calls/calls_group_panel.h" + +#include "data/data_photo.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "data/data_file_origin.h" +#include "data/data_photo_media.h" +#include "data/data_cloud_file.h" +#include "data/data_changes.h" +#include "calls/calls_emoji_fingerprint.h" +#include "calls/calls_signal_bars.h" +#include "calls/calls_userpic.h" +#include "calls/calls_video_bubble.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/window.h" +#include "ui/effects/ripple_animation.h" +#include "ui/image/image.h" +#include "ui/text/format_values.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/platform/ui_platform_utility.h" +#include "ui/toast/toast.h" +#include "ui/empty_userpic.h" +#include "ui/emoji_config.h" +#include "core/application.h" +#include "mainwindow.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "apiwrap.h" +#include "platform/platform_specific.h" +#include "base/platform/base_platform_info.h" +#include "window/main_window.h" +#include "app.h" +#include "webrtc/webrtc_video_track.h" +#include "styles/style_calls.h" +#include "styles/style_chat.h" + +#ifdef Q_OS_WIN +#include "ui/platform/win/ui_window_title_win.h" +#endif // Q_OS_WIN + +#include +#include +#include + +namespace Calls { + +class GroupPanel::Button final : public Ui::RippleButton { +public: + Button( + QWidget *parent, + const style::CallButton &stFrom, + const style::CallButton *stTo = nullptr); + + void setProgress(float64 progress); + void setText(rpl::producer text); + +protected: + void paintEvent(QPaintEvent *e) override; + + void onStateChanged(State was, StateChangeSource source) override; + + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; + +private: + QPoint iconPosition(not_null st) const; + void mixIconMasks(); + + not_null _stFrom; + const style::CallButton *_stTo = nullptr; + float64 _progress = 0.; + + object_ptr _label = { nullptr }; + + QImage _bgMask, _bg; + QPixmap _bgFrom, _bgTo; + QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed; + +}; + +GroupPanel::Button::Button( + QWidget *parent, + const style::CallButton &stFrom, + const style::CallButton *stTo) +: Ui::RippleButton(parent, stFrom.button.ripple) +, _stFrom(&stFrom) +, _stTo(stTo) { + resize(_stFrom->button.width, _stFrom->button.height); + + _bgMask = prepareRippleMask(); + _bgFrom = App::pixmapFromImageInPlace(style::colorizeImage(_bgMask, _stFrom->bg)); + if (_stTo) { + Assert(_stFrom->button.width == _stTo->button.width); + Assert(_stFrom->button.height == _stTo->button.height); + Assert(_stFrom->button.rippleAreaPosition == _stTo->button.rippleAreaPosition); + Assert(_stFrom->button.rippleAreaSize == _stTo->button.rippleAreaSize); + + _bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _bg.setDevicePixelRatio(cRetinaFactor()); + _bgTo = App::pixmapFromImageInPlace(style::colorizeImage(_bgMask, _stTo->bg)); + _iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconMixedMask.setDevicePixelRatio(cRetinaFactor()); + _iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconFrom.setDevicePixelRatio(cRetinaFactor()); + _iconFrom.fill(Qt::black); + { + Painter p(&_iconFrom); + p.drawImage((_stFrom->button.rippleAreaSize - _stFrom->button.icon.width()) / 2, (_stFrom->button.rippleAreaSize - _stFrom->button.icon.height()) / 2, _stFrom->button.icon.instance(Qt::white)); + } + _iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconTo.setDevicePixelRatio(cRetinaFactor()); + _iconTo.fill(Qt::black); + { + Painter p(&_iconTo); + p.drawImage((_stTo->button.rippleAreaSize - _stTo->button.icon.width()) / 2, (_stTo->button.rippleAreaSize - _stTo->button.icon.height()) / 2, _stTo->button.icon.instance(Qt::white)); + } + _iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconMixed.setDevicePixelRatio(cRetinaFactor()); + } +} + +void GroupPanel::Button::setText(rpl::producer text) { + _label.create(this, std::move(text), _stFrom->label); + _label->show(); + rpl::combine( + sizeValue(), + _label->sizeValue() + ) | rpl::start_with_next([=](QSize my, QSize label) { + _label->moveToLeft( + (my.width() - label.width()) / 2, + my.height() - label.height(), + my.width()); + }, _label->lifetime()); +} + +void GroupPanel::Button::setProgress(float64 progress) { + _progress = progress; + update(); +} + +void GroupPanel::Button::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto bgPosition = myrtlpoint(_stFrom->button.rippleAreaPosition); + auto paintFrom = (_progress == 0.) || !_stTo; + auto paintTo = !paintFrom && (_progress == 1.); + + if (paintFrom) { + p.drawPixmap(bgPosition, _bgFrom); + } else if (paintTo) { + p.drawPixmap(bgPosition, _bgTo); + } else { + style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg); + p.drawImage(bgPosition, _bg); + } + + auto rippleColorInterpolated = QColor(); + auto rippleColorOverride = &rippleColorInterpolated; + if (paintFrom) { + rippleColorOverride = nullptr; + } else if (paintTo) { + rippleColorOverride = &_stTo->button.ripple.color->c; + } else { + rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress); + } + paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), rippleColorOverride); + + auto positionFrom = iconPosition(_stFrom); + if (paintFrom) { + const auto icon = &_stFrom->button.icon; + icon->paint(p, positionFrom, width()); + } else { + auto positionTo = iconPosition(_stTo); + if (paintTo) { + _stTo->button.icon.paint(p, positionTo, width()); + } else { + mixIconMasks(); + style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed); + p.drawImage(myrtlpoint(_stFrom->button.rippleAreaPosition), _iconMixed); + } + } +} + +QPoint GroupPanel::Button::iconPosition(not_null st) const { + auto result = st->button.iconPosition; + if (result.x() < 0) { + result.setX((width() - st->button.icon.width()) / 2); + } + if (result.y() < 0) { + result.setY((height() - st->button.icon.height()) / 2); + } + return result; +} + +void GroupPanel::Button::mixIconMasks() { + _iconMixedMask.fill(Qt::black); + + Painter p(&_iconMixedMask); + PainterHighQualityEnabler hq(p); + auto paintIconMask = [this, &p](const QImage &mask, float64 angle) { + auto skipFrom = _stFrom->button.rippleAreaSize / 2; + p.translate(skipFrom, skipFrom); + p.rotate(angle); + p.translate(-skipFrom, -skipFrom); + p.drawImage(0, 0, mask); + }; + p.save(); + paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress); + p.restore(); + p.setOpacity(_progress); + paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress)); +} + +void GroupPanel::Button::onStateChanged(State was, StateChangeSource source) { + RippleButton::onStateChanged(was, source); + + auto over = isOver(); + auto wasOver = static_cast(was & StateFlag::Over); + if (over != wasOver) { + update(); + } +} + +QPoint GroupPanel::Button::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition; +} + +QImage GroupPanel::Button::prepareRippleMask() const { + return Ui::RippleAnimation::ellipseMask(QSize(_stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize)); +} + +GroupPanel::GroupPanel(not_null call) +: _call(call) +, _channel(call->channel()) +, _window(std::make_unique(Core::App().getModalParent())) +#ifdef Q_OS_WIN +, _controls(std::make_unique( + _window.get(), + st::callTitle, + [=](bool maximized) { toggleFullScreen(maximized); })) +#endif // Q_OS_WIN +, _settings(widget(), st::callCancel) +, _hangup(widget(), st::callHangup) +, _mute(widget(), st::callMicrophoneMute, &st::callMicrophoneUnmute) { + initWindow(); + initWidget(); + initControls(); + initLayout(); + showAndActivate(); +} + +GroupPanel::~GroupPanel() = default; + +void GroupPanel::showAndActivate() { + _window->raise(); + _window->setWindowState(_window->windowState() | Qt::WindowActive); + _window->activateWindow(); + _window->setFocus(); +} + +void GroupPanel::initWindow() { + _window->setAttribute(Qt::WA_OpaquePaintEvent); + _window->setAttribute(Qt::WA_NoSystemBackground); + _window->setWindowIcon( + QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly))); + _window->setTitle(u" "_q); + _window->setTitleStyle(st::callTitle); + + _window->events( + ) | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::Close) { + handleClose(); + } else if (e->type() == QEvent::KeyPress) { + if ((static_cast(e.get())->key() == Qt::Key_Escape) + && _window->isFullScreen()) { + _window->showNormal(); + } + } + }, _window->lifetime()); + + _window->setBodyTitleArea([=](QPoint widgetPoint) { + using Flag = Ui::WindowTitleHitTestFlag; + if (!widget()->rect().contains(widgetPoint)) { + return Flag::None | Flag(0); + } +#ifdef Q_OS_WIN + if (_controls->geometry().contains(widgetPoint)) { + return Flag::None | Flag(0); + } +#endif // Q_OS_WIN + const auto inControls = false; + return inControls + ? Flag::None + : (Flag::Move | Flag::FullScreen); + }); + +#ifdef Q_OS_WIN + // On Windows we replace snap-to-top maximizing with fullscreen. + // + // We have to switch first to showNormal, so that showFullScreen + // will remember correct normal window geometry and next showNormal + // will show it instead of a moving maximized window. + // + // We have to do it in InvokeQueued, otherwise it still captures + // the maximized window geometry and saves it. + // + // I couldn't find a less glitchy way to do that *sigh*. + const auto object = _window->windowHandle(); + const auto signal = &QWindow::windowStateChanged; + QObject::connect(object, signal, [=](Qt::WindowState state) { + if (state == Qt::WindowMaximized) { + InvokeQueued(object, [=] { + _window->showNormal(); + _window->showFullScreen(); + }); + } + }); +#endif // Q_OS_WIN +} + +void GroupPanel::initWidget() { + widget()->setMouseTracking(true); + + widget()->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + paint(clip); + }, widget()->lifetime()); + + widget()->sizeValue( + ) | rpl::skip(1) | rpl::start_with_next([=] { + updateControlsGeometry(); + }, widget()->lifetime()); +} + +void GroupPanel::initControls() { + _mute->setClickedCallback([=] { + if (_call) { + _call->setMuted(!_call->muted()); + } + }); + _hangup->setClickedCallback([=] { + if (_call) { + _call->hangup(); + } + }); + _settings->setClickedCallback([=] { + }); + initWithCall(_call); +} + +void GroupPanel::initWithCall(GroupCall *call) { + _callLifetime.destroy(); + _call = call; + if (!_call) { + return; + } + + _channel = _call->channel(); + + _settings->setText(tr::lng_menu_settings()); + _hangup->setText(tr::lng_box_leave()); + + _call->mutedValue( + ) | rpl::start_with_next([=](bool mute) { + _mute->setProgress(mute ? 1. : 0.); + _mute->setText(mute + ? tr::lng_call_unmute_audio() + : tr::lng_call_mute_audio()); + }, _callLifetime); + + _call->stateValue( + ) | rpl::start_with_next([=](State state) { + stateChanged(state); + }, _callLifetime); +} + +void GroupPanel::initLayout() { + initGeometry(); + +#ifdef Q_OS_WIN + _controls->raise(); +#endif // Q_OS_WIN +} + +void GroupPanel::showControls() { + Expects(_call != nullptr); + + widget()->showChildren(); +} + +void GroupPanel::closeBeforeDestroy() { + _window->close(); + initWithCall(nullptr); +} + +void GroupPanel::initGeometry() { + const auto center = Core::App().getPointForCallPanelCenter(); + const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight); + _window->setGeometry(rect.translated(center - rect.center())); + _window->setMinimumSize(rect.size()); + _window->show(); + updateControlsGeometry(); +} + +void GroupPanel::toggleFullScreen(bool fullscreen) { + if (fullscreen) { + _window->showFullScreen(); + } else { + _window->showNormal(); + } +} + +void GroupPanel::updateControlsGeometry() { + if (widget()->size().isEmpty()) { + return; + } + const auto top = widget()->height() - 2 * _mute->height(); + _mute->move((widget()->width() - _mute->width()) / 2, top); + _settings->moveToLeft(_settings->width(), top); + _hangup->moveToRight(_settings->width(), top); +} + +void GroupPanel::paint(QRect clip) { + Painter p(widget()); + + auto region = QRegion(clip); + for (const auto rect : region) { + p.fillRect(rect, st::callBgOpaque); + } +} + +void GroupPanel::handleClose() { + if (_call) { + _call->hangup(); + } +} + +not_null GroupPanel::widget() const { + return _window->body(); +} + +void GroupPanel::stateChanged(State state) { + Expects(_call != nullptr); + + if ((state != State::HangingUp) + && (state != State::Ended) + && (state != State::FailedHangingUp) + && (state != State::Failed)) { + } +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_group_panel.h b/Telegram/SourceFiles/calls/calls_group_panel.h new file mode 100644 index 000000000..cec1fc107 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_group_panel.h @@ -0,0 +1,96 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/weak_ptr.h" +#include "base/timer.h" +#include "base/object_ptr.h" +#include "calls/calls_group_call.h" +#include "ui/effects/animations.h" +#include "ui/rp_widget.h" + +class Image; + +namespace Data { +class PhotoMedia; +class CloudImageView; +} // namespace Data + +namespace Ui { +class IconButton; +class FlatLabel; +template +class FadeWrap; +template +class PaddingWrap; +class Window; +namespace Platform { +class TitleControls; +} // namespace Platform +} // namespace Ui + +namespace style { +struct CallSignalBars; +struct CallBodyLayout; +} // namespace style + +namespace Calls { + +class Userpic; +class SignalBars; + +class GroupPanel final { +public: + GroupPanel(not_null call); + ~GroupPanel(); + + void showAndActivate(); + void closeBeforeDestroy(); + +private: + class Button; + using State = GroupCall::State; + + [[nodiscard]] not_null widget() const; + + void paint(QRect clip); + + void initWindow(); + void initWidget(); + void initControls(); + void initWithCall(GroupCall *call); + void initLayout(); + void initGeometry(); + + void handleClose(); + + void updateControlsGeometry(); + void stateChanged(State state); + void showControls(); + void startDurationUpdateTimer(crl::time currentDuration); + + void toggleFullScreen(bool fullscreen); + + GroupCall *_call = nullptr; + not_null _channel; + + const std::unique_ptr _window; + +#ifdef Q_OS_WIN + std::unique_ptr _controls; +#endif // Q_OS_WIN + + rpl::lifetime _callLifetime; + + object_ptr