From 3c2f8b65ce893dc2120e079e3e2a787c435cce3f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 27 Mar 2025 15:00:30 +0000 Subject: [PATCH 001/190] Let QIcon::fromTheme check whether the tray icon is valid QIcon::fromTheme overload with a fallback does a smarter check whether the icon is valid, use it to prevent getting a half-valid QIcon. --- Telegram/SourceFiles/platform/linux/tray_linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 37ef31e259..9e7949c08a 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -95,7 +95,7 @@ QIcon IconGraphic::systemIcon() const { if (candidate.isEmpty()) { continue; } - const auto icon = QIcon::fromTheme(candidate); + const auto icon = QIcon::fromTheme(candidate, QIcon()); if (icon.name() == candidate) { return icon; } From 7d0beafce059148ecf3aba14c6a98fed173a3e68 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 28 Mar 2025 15:47:24 +0000 Subject: [PATCH 002/190] Ensure currentImageBack isn't null --- Telegram/SourceFiles/platform/linux/tray_linux.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/platform/linux/tray_linux.cpp b/Telegram/SourceFiles/platform/linux/tray_linux.cpp index 9e7949c08a..e15c297ced 100644 --- a/Telegram/SourceFiles/platform/linux/tray_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/tray_linux.cpp @@ -161,6 +161,8 @@ QIcon IconGraphic::trayIcon() { if (currentImageBack.isNull() || _new.iconThemeName != _current.iconThemeName || _new.systemIcon.name() != _current.systemIcon.name()) { + currentImageBack = {}; + if (!_new.systemIcon.isNull()) { // We can't use QIcon::actualSize here // since it works incorrectly with svg icon themes @@ -187,7 +189,9 @@ QIcon IconGraphic::trayIcon() { .toImage(); } } - } else { + } + + if (currentImageBack.isNull()) { currentImageBack = Window::Logo(); } From 260b72fec15a2b6585264970e6a00c0adadaef5d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 29 Mar 2025 22:49:20 +0000 Subject: [PATCH 003/190] Check location picker support early --- Telegram/SourceFiles/core/application.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index f44a792f75..593d6f4a0b 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -85,6 +85,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qthelp_url.h" #include "boxes/premium_limits_box.h" #include "ui/boxes/confirm_box.h" +#include "ui/controls/location_picker.h" #include "styles/style_window.h" #include @@ -323,6 +324,8 @@ void Application::run() { // Check now to avoid re-entrance later. [[maybe_unused]] const auto ivSupported = Iv::ShowButton(); + [[maybe_unused]] const auto lpAvailable = Ui::LocationPicker::Available( + {}); _windows.emplace(nullptr, std::make_unique()); setLastActiveWindow(_windows.front().second.get()); From 8b791e77d67f6dfc66630f88795f4da891ec22b8 Mon Sep 17 00:00:00 2001 From: nyakze Date: Mon, 31 Mar 2025 17:16:06 +0400 Subject: [PATCH 004/190] Update mime_type.cpp add m3u to ip revealing types --- Telegram/SourceFiles/core/mime_type.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/mime_type.cpp b/Telegram/SourceFiles/core/mime_type.cpp index 5e15fd4e3d..5683ef634d 100644 --- a/Telegram/SourceFiles/core/mime_type.cpp +++ b/Telegram/SourceFiles/core/mime_type.cpp @@ -326,7 +326,7 @@ bool NameTypeAllowsThumbnail(NameType type) { bool IsIpRevealingPath(const QString &filepath) { static const auto kExtensions = [] { - const auto joined = u"htm html svg m4v m3u8 xhtml"_q; + const auto joined = u"htm html svg m4v m3u m3u8 xhtml"_q; const auto list = joined.split(' '); return base::flat_set(list.begin(), list.end()); }(); From f318aeb67c351ea2bbb2ab99fa87da6f3be70a01 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 2 Apr 2025 20:59:25 +0000 Subject: [PATCH 005/190] Don't base Docker libjxl stage on patches There's nothing the stage uses in it --- 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 b96cda0bb6..c83af157dc 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -249,7 +249,7 @@ RUN git clone -b v1.18.2 --depth=1 {{ GIT }}/strukturag/libheif.git \ && cd .. \ && rm -rf libheif -FROM patches AS libjxl +FROM builder AS libjxl COPY --link --from=lcms2 {{ LibrariesPath }}/lcms2-cache / COPY --link --from=brotli {{ LibrariesPath }}/brotli-cache / COPY --link --from=highway {{ LibrariesPath }}/highway-cache / From dec8bdb39d16fae4f9de77924770cf46fc334d43 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 4 Apr 2025 12:43:24 +0400 Subject: [PATCH 006/190] Update breakpad in Docker to v2024.02.16 --- Telegram/build/docker/centos_env/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index c83af157dc..0caa78c212 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -753,9 +753,9 @@ RUN git clone -b {{ QT_TAG }} --depth=1 {{ GIT }}/qt/qt5.git \ && rm -rf qt5 FROM builder AS breakpad -RUN git clone -b v2023.06.01 --depth=1 https://chromium.googlesource.com/breakpad/breakpad.git \ +RUN git clone -b v2024.02.16 --depth=1 https://chromium.googlesource.com/breakpad/breakpad.git \ && cd breakpad \ - && git clone -b v2022.10.12 --depth=1 https://chromium.googlesource.com/linux-syscall-support.git src/third_party/lss \ + && git clone -b v2024.02.01 --depth=1 https://chromium.googlesource.com/linux-syscall-support.git src/third_party/lss \ && env -u CFLAGS -u CXXFLAGS ./configure \ && make -j$(nproc) \ && make DESTDIR="{{ LibrariesPath }}/breakpad-cache" install \ From 0aa1031270ce84bdfe014df52dfed8aa655fcc33 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 4 Apr 2025 12:43:51 +0400 Subject: [PATCH 007/190] Use global c(xx)flags for breakpad in Docker, just not LTO --- 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 0caa78c212..0a4eabd772 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -756,7 +756,7 @@ FROM builder AS breakpad RUN git clone -b v2024.02.16 --depth=1 https://chromium.googlesource.com/breakpad/breakpad.git \ && cd breakpad \ && git clone -b v2024.02.01 --depth=1 https://chromium.googlesource.com/linux-syscall-support.git src/third_party/lss \ - && env -u CFLAGS -u CXXFLAGS ./configure \ + && CFLAGS="$CFLAGS -fno-lto" CXXFLAGS="$CXXFLAGS -fno-lto" ./configure \ && make -j$(nproc) \ && make DESTDIR="{{ LibrariesPath }}/breakpad-cache" install \ && cd .. \ From 8b92ab25c776899c5432bf935447cac6f0b3ea2d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 26 Mar 2025 15:44:25 +0000 Subject: [PATCH 008/190] Update Qt 6.8.2 -> 6.9.0 --- Telegram/build/docker/centos_env/Dockerfile | 4 ++-- Telegram/build/prepare/prepare.py | 2 +- Telegram/build/qt_version.py | 2 +- Telegram/lib_base | 2 +- snap/snapcraft.yaml | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 0a4eabd772..2ecc562238 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -2,7 +2,7 @@ {%- set GIT_FREEDESKTOP = GIT ~ "/gitlab-freedesktop-mirrors" -%} {%- set GIT_UPDATE_M4 = "git submodule set-url m4 https://gitlab.freedesktop.org/xorg/util/xcb-util-m4 && git config -f .gitmodules submodule.m4.shallow true && git submodule init && git submodule update" -%} {%- set TOOLSET = "gcc-toolset-12" -%} -{%- set QT = "6.8.2" -%} +{%- set QT = "6.9.0" -%} {%- set QT_TAG = "v" ~ QT -%} {%- set CFLAGS_DEBUG = "$CFLAGS -O0 -fno-lto -U_FORTIFY_SOURCE" -%} {%- set LibrariesPath = "/usr/src/Libraries" -%} @@ -44,7 +44,7 @@ FROM builder AS patches RUN git init patches \ && cd patches \ && git remote add origin {{ GIT }}/desktop-app/patches.git \ - && git fetch --depth=1 origin fb35ec726cf0a99eead201ddf80e1e717b9fa7c2 \ + && git fetch --depth=1 origin 7119a74e3f9b782f3cc29bf52fc78f2e8b0ca352 \ && git reset --hard FETCH_HEAD \ && rm -rf .git diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index b4f7b3e16e..541f0923b5 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -457,7 +457,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 7cb9049583 + git checkout 7119a74e3f """) stage('msys64', """ diff --git a/Telegram/build/qt_version.py b/Telegram/build/qt_version.py index 9be6a89464..df7bb2a92c 100644 --- a/Telegram/build/qt_version.py +++ b/Telegram/build/qt_version.py @@ -5,7 +5,7 @@ def resolve(arch): os.environ['QT'] = '6.2.12' elif sys.platform == 'win32': if arch == 'arm' or 'qt6' in sys.argv: - os.environ['QT'] = '6.8.2' + os.environ['QT'] = '6.9.0' elif os.environ.get('QT') is None: os.environ['QT'] = '5.15.15' elif os.environ.get('QT') is None: diff --git a/Telegram/lib_base b/Telegram/lib_base index b28088164b..720eaf44a5 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit b28088164b7a46c70ae2cfd9daf865f6425610b2 +Subproject commit 720eaf44a529da50f5277fd6874318d9a08b735a diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d66b835aff..95db61c71f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -171,7 +171,7 @@ parts: patches: source: https://github.com/desktop-app/patches.git source-depth: 1 - source-commit: fb35ec726cf0a99eead201ddf80e1e717b9fa7c2 + source-commit: 7119a74e3f9b782f3cc29bf52fc78f2e8b0ca352 plugin: dump override-pull: | craftctl default @@ -382,7 +382,7 @@ parts: - mesa-vulkan-drivers - xkb-data override-pull: | - QT=6.8.2 + QT=6.9.0 git clone -b v${QT} --depth=1 https://github.com/qt/qt5.git . git submodule update --init --recursive --depth=1 qtbase qtdeclarative qtwayland qtimageformats qtsvg qtshadertools From c261c3367a11eeef69e6e346d339706dc4f00406 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 4 Apr 2025 23:33:40 +0000 Subject: [PATCH 009/190] Fix a PiP crash with Qt 6.9 --- Telegram/SourceFiles/media/view/media_view_pip.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index 4e1d7c1360..7c8f958889 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -362,6 +362,10 @@ void PipPanel::init() { ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] { // Workaround Qt's forced transient parent. Ui::Platform::ClearTransientParent(widget()); + }, rp()->lifetime()); + + rp()->shownValue( + ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] { Ui::Platform::SetWindowMargins(widget(), _padding); }, rp()->lifetime()); From 13fbe59164994914f6501dfab750a8be942e42f0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 4 Apr 2025 23:00:47 +0400 Subject: [PATCH 010/190] Free more space in Docker action --- .github/workflows/docker.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ac40f3a330..8142e661b2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,6 +32,8 @@ jobs: - name: Free up some disk space. uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be + with: + tool-cache: true - name: Docker image build. run: | From 25cd6106f563dd5e208bbc327dab84a7444bc170 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 11 Apr 2025 13:57:44 +0000 Subject: [PATCH 011/190] Use Ui::ImageExtensions in PhotoVideoFilesFilter This allows it to have all supported image extensions just like ImagesFilter --- Telegram/SourceFiles/core/file_utilities.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/core/file_utilities.cpp b/Telegram/SourceFiles/core/file_utilities.cpp index 21d912f63c..3c1769cf57 100644 --- a/Telegram/SourceFiles/core/file_utilities.cpp +++ b/Telegram/SourceFiles/core/file_utilities.cpp @@ -332,7 +332,7 @@ QString ImagesOrAllFilter() { } QString PhotoVideoFilesFilter() { - return u"Image and Video Files (*.png *.jpg *.jpeg *.mp4 *.mov *.m4v);;"_q + return u"Image and Video Files (*"_q + Ui::ImageExtensions().join(u" *"_q) + u" *.m4v);;"_q + AllFilesFilter(); } From 9bdc19e2fd4d497c8f403891848383a88faadc25 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 11 Apr 2025 14:01:09 +0000 Subject: [PATCH 012/190] Format markup-lacking Linux notifications using lng_dialogs_text_with_from --- .../linux/notifications_manager_linux.cpp | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index c100e00e06..45cb6a3875 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -537,17 +537,23 @@ void Manager::Private::showNotification( .msgId = info.itemId, }; auto notification = _application - ? Gio::Notification::new_( - info.subtitle.isEmpty() - ? info.title.toStdString() - : info.subtitle.toStdString() - + " (" + info.title.toStdString() + ')') + ? Gio::Notification::new_(info.title.toStdString()) : Gio::Notification(); std::vector actions; auto hints = GLib::VariantDict::new_(); if (notification) { - notification.set_body(info.message.toStdString()); + notification.set_body(info.subtitle.isEmpty() + ? info.message.toStdString() + : tr::lng_dialogs_text_with_from( + tr::now, + lt_from_part, + tr::lng_dialogs_text_from_wrapped( + tr::now, + lt_from, + info.subtitle), + lt_message, + info.message).toStdString()); notification.set_icon( Gio::ThemedIcon::new_(ApplicationIconName().toStdString())); @@ -726,8 +732,6 @@ void Manager::Private::showNotification( const auto hasImage = !imageKey.empty() && hints.lookup_value(imageKey); - const auto hasBodyMarkup = HasCapability("body-markup"); - const auto callbackWrap = gi::unwrap( Gio::AsyncReadyCallback( crl::guard(this, [=]( @@ -766,17 +770,24 @@ void Manager::Private::showNotification( (!hasImage ? ApplicationIconName().toStdString() : std::string()).c_str(), - (hasBodyMarkup || info.subtitle.isEmpty() - ? info.title.toStdString() - : info.subtitle.toStdString() - + " (" + info.title.toStdString() + ')').c_str(), - (hasBodyMarkup + info.title.toStdString().c_str(), + (HasCapability("body-markup") ? info.subtitle.isEmpty() ? info.message.toHtmlEscaped().toStdString() : u"%1\n%2"_q.arg( info.subtitle.toHtmlEscaped(), info.message.toHtmlEscaped()).toStdString() - : info.message.toStdString()).c_str(), + : info.subtitle.isEmpty() + ? info.message.toStdString() + : tr::lng_dialogs_text_with_from( + tr::now, + lt_from_part, + tr::lng_dialogs_text_from_wrapped( + tr::now, + lt_from, + info.subtitle), + lt_message, + info.message).toStdString()).c_str(), !actions.empty() ? (actions | ranges::views::transform(&gi::cstring::c_str) From 79cb4668f1a651752394b1506e645ab1b8b8391d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 24 Apr 2025 06:54:46 +0000 Subject: [PATCH 013/190] Fix a crash on Linux without actions notification capability --- .../platform/linux/notifications_manager_linux.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 45cb6a3875..d49fecb814 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -625,10 +625,10 @@ void Manager::Private::showNotification( actions.push_back( tr::lng_notification_reply(tr::now).toStdString()); } - - actions.push_back({}); } + actions.push_back({}); + if (HasCapability("action-icons")) { hints.insert_value( "action-icons", @@ -788,11 +788,9 @@ void Manager::Private::showNotification( info.subtitle), lt_message, info.message).toStdString()).c_str(), - !actions.empty() - ? (actions - | ranges::views::transform(&gi::cstring::c_str) - | ranges::to_vector).data() - : nullptr, + (actions + | ranges::views::transform(&gi::cstring::c_str) + | ranges::to_vector).data(), hints.end().gobj_(), -1, nullptr, From 29567df40777f26c63159774ecce222801665694 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 20 Feb 2025 04:07:44 +0000 Subject: [PATCH 014/190] Re-enable ffmpeg optimizations on Linux By disabling LTO --- Telegram/build/docker/centos_env/Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 2ecc562238..f0a797d0f4 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -486,11 +486,9 @@ COPY --link --from=nv-codec-headers {{ LibrariesPath }}/nv-codec-headers-cache / RUN git clone -b n6.1.1 --depth=1 {{ GIT }}/FFmpeg/FFmpeg.git \ && cd FFmpeg \ && ./configure \ - --extra-cflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \ - --extra-cxxflags="-DCONFIG_SAFE_BITSTREAM_READER=1" \ + --extra-cflags="-fno-lto -DCONFIG_SAFE_BITSTREAM_READER=1" \ + --extra-cxxflags="-fno-lto -DCONFIG_SAFE_BITSTREAM_READER=1" \ --disable-debug \ - --disable-optimizations \ - --disable-inline-asm \ --disable-programs \ --disable-doc \ --disable-network \ From 1035d787ab6a996abc95994dceda927afb448700 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 1 Apr 2025 00:37:16 +0000 Subject: [PATCH 015/190] Update User-Agent for DNS to Chrome 134.0.0.0. --- .../SourceFiles/mtproto/details/mtproto_domain_resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index c48f0f2240..e7fa32c8bb 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/133.0.0.0 Safari/537.36"); + "Chrome/134.0.0.0 Safari/537.36"); return kResult; } From cd81a540620c452a4de5c9fe104778c706093545 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 01:58:20 +0000 Subject: [PATCH 016/190] Bump jinja2 from 3.1.5 to 3.1.6 in /Telegram/build/docker/centos_env Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.5...3.1.6) --- updated-dependencies: - dependency-name: jinja2 dependency-version: 3.1.6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Telegram/build/docker/centos_env/poetry.lock | 14 ++++++++------ Telegram/build/docker/centos_env/pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Telegram/build/docker/centos_env/poetry.lock b/Telegram/build/docker/centos_env/poetry.lock index 7dd1e73e42..3d25367d8f 100644 --- a/Telegram/build/docker/centos_env/poetry.lock +++ b/Telegram/build/docker/centos_env/poetry.lock @@ -1,14 +1,15 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, - {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] @@ -23,6 +24,7 @@ version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, @@ -67,6 +69,6 @@ files = [ ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.7" -content-hash = "fb25e33fcbae8ccc51354cf0c37289f4f5980fcc253061515b286e1534a84b0d" +content-hash = "375b9a134584d381be4e5f1a5a3a6935f2ff7852fdf3fef8b128d3d1e89263f8" diff --git a/Telegram/build/docker/centos_env/pyproject.toml b/Telegram/build/docker/centos_env/pyproject.toml index 12ea25d3c4..8f43692fb2 100644 --- a/Telegram/build/docker/centos_env/pyproject.toml +++ b/Telegram/build/docker/centos_env/pyproject.toml @@ -6,7 +6,7 @@ authors = [] [tool.poetry.dependencies] python = "^3.7" -Jinja2 = "^3.1.5" +Jinja2 = "^3.1.6" [tool.poetry.dev-dependencies] From 635ed0219846ce7bcc0bc74c639ac0e69bcbb760 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Sat, 12 Apr 2025 11:08:44 -0400 Subject: [PATCH 017/190] update ada-url to v3.2.2 --- Telegram/build/docker/centos_env/Dockerfile | 2 +- Telegram/build/prepare/prepare.py | 2 +- snap/snapcraft.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index f0a797d0f4..3e32922978 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -805,7 +805,7 @@ RUN cmake --build out --config Debug --parallel \ {%- endif %} FROM builder AS ada -RUN git clone -b v3.2.1 --depth=1 {{ GIT }}/ada-url/ada.git \ +RUN git clone -b v3.2.2 --depth=1 {{ GIT }}/ada-url/ada.git \ && cd ada \ && cmake -GNinja -B build . \ -D CMAKE_BUILD_TYPE=None \ diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 541f0923b5..676bb56be2 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1864,7 +1864,7 @@ release: """) stage('ada', """ - git clone -b v3.2.1 https://github.com/ada-url/ada.git + git clone -b v3.2.2 https://github.com/ada-url/ada.git cd ada win: cmake -B out . ^ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 95db61c71f..8c86d17fa6 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -218,7 +218,7 @@ parts: ada: source: https://github.com/ada-url/ada.git source-depth: 1 - source-tag: v3.2.1 + source-tag: v3.2.2 plugin: cmake build-environment: - LDFLAGS: ${LDFLAGS:+$LDFLAGS} -s From 2e168d152f797cd7880b5ed85c4985e1409f6216 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 1 May 2025 00:36:43 +0000 Subject: [PATCH 018/190] Update User-Agent for DNS to Chrome 135.0.0.0. --- .../SourceFiles/mtproto/details/mtproto_domain_resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index e7fa32c8bb..6d09b676ca 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/134.0.0.0 Safari/537.36"); + "Chrome/135.0.0.0 Safari/537.36"); return kResult; } From 130c77a305632375284c97746d0e8eca9aa29ee1 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 30 Apr 2025 23:41:42 +0400 Subject: [PATCH 019/190] Update disptach to 6.1 --- Telegram/ThirdParty/dispatch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/ThirdParty/dispatch b/Telegram/ThirdParty/dispatch index 542b7f3231..886ca22f65 160000 --- a/Telegram/ThirdParty/dispatch +++ b/Telegram/ThirdParty/dispatch @@ -1 +1 @@ -Subproject commit 542b7f32311680b11b6fc8fcb2576955460ba7da +Subproject commit 886ca22f659c53dae66a40ee8266c7aae9bb97cd From 155b5fa83174efd87296b146fd22623925aa4450 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 27 Apr 2025 03:28:20 +0400 Subject: [PATCH 020/190] Don't link openh264 to td_ui It's not used directly --- Telegram/cmake/td_ui.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 82ac02aac5..ad9c8519a5 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -520,6 +520,5 @@ PRIVATE desktop-app::lib_spellcheck desktop-app::lib_stripe desktop-app::external_kcoreaddons - desktop-app::external_openh264 desktop-app::external_webrtc ) From f2b0116c1ee847c4f8c23e7b94cffb9d009dab3c Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 27 Apr 2025 01:24:09 +0400 Subject: [PATCH 021/190] Don't require PkgConfig for libtgvoip --- Telegram/cmake/lib_tgvoip.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Telegram/cmake/lib_tgvoip.cmake b/Telegram/cmake/lib_tgvoip.cmake index fbae709660..5f212e995c 100644 --- a/Telegram/cmake/lib_tgvoip.cmake +++ b/Telegram/cmake/lib_tgvoip.cmake @@ -8,8 +8,10 @@ add_library(lib_tgvoip INTERFACE IMPORTED GLOBAL) add_library(tdesktop::lib_tgvoip ALIAS lib_tgvoip) if (DESKTOP_APP_USE_PACKAGED) - find_package(PkgConfig REQUIRED) - pkg_check_modules(TGVOIP IMPORTED_TARGET tgvoip) + find_package(PkgConfig) + if (PkgConfig_FOUND) + pkg_check_modules(TGVOIP IMPORTED_TARGET tgvoip) + endif() if (TGVOIP_FOUND) target_link_libraries(lib_tgvoip INTERFACE PkgConfig::TGVOIP) From eaf1a532f44e3da698874618432ae1dedc768d97 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 27 Apr 2025 02:34:57 +0400 Subject: [PATCH 022/190] Remove workaround to find the right OpenAL in macOS packaged action --- .github/workflows/mac_packaged.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml index 8d10791f74..a4e99e77fb 100644 --- a/.github/workflows/mac_packaged.yml +++ b/.github/workflows/mac_packaged.yml @@ -139,7 +139,6 @@ jobs: -DCMAKE_C_FLAGS_DEBUG="" \ -DCMAKE_CXX_FLAGS_DEBUG="" \ -DCMAKE_EXE_LINKER_FLAGS="-s" \ - -DCMAKE_FIND_FRAMEWORK=LAST \ -DTDESKTOP_API_TEST=ON \ -DDESKTOP_APP_USE_PACKAGED_LAZY=ON \ $DEFINE From 3ac03b43e144481151eadd92d5e1ded0aa26ed82 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 28 Apr 2025 20:19:53 +0400 Subject: [PATCH 023/190] Add missing dependency on win_directx_helper --- Telegram/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 7fa9bb9361..2dcac37d05 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1732,6 +1732,10 @@ if (WIN32) # COMMENT # $ # ) + + if (QT_VERSION LESS 6) + target_link_libraries(Telegram PRIVATE desktop-app::win_directx_helper) + endif() elseif (APPLE) if (NOT DESKTOP_APP_USE_PACKAGED) target_link_libraries(Telegram PRIVATE desktop-app::external_iconv) From fe3d8ea2be8ba07cf632a4938b45d79132175565 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 30 Apr 2025 23:31:30 +0400 Subject: [PATCH 024/190] Don't check non-MSVC options with MSVC for tgcalls --- Telegram/cmake/lib_tgcalls.cmake | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 44539b54b3..88800c9af6 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -225,8 +225,7 @@ PRIVATE RTC_ENABLE_VP9 ) -if (WIN32) -elseif (APPLE) +if (APPLE) target_compile_options(lib_tgcalls PRIVATE -fobjc-arc @@ -253,14 +252,16 @@ elseif (APPLE) ) endif() -target_compile_options_if_exists(lib_tgcalls -PRIVATE - -Wno-deprecated-volatile - -Wno-ambiguous-reversed-operator - -Wno-deprecated-declarations - -Wno-unqualified-std-cast-call - -Wno-unused-function -) +if (NOT MSVC) + target_compile_options_if_exists(lib_tgcalls + PRIVATE + -Wno-deprecated-volatile + -Wno-ambiguous-reversed-operator + -Wno-deprecated-declarations + -Wno-unqualified-std-cast-call + -Wno-unused-function + ) +endif() remove_target_sources(lib_tgcalls ${tgcalls_loc} platform/android/AndroidContext.cpp From 069d8d376c93265284993573f3b8b0eefde06f72 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 30 Apr 2025 23:31:51 +0400 Subject: [PATCH 025/190] Unify libtgvoip options handling --- Telegram/cmake/lib_tgvoip.cmake | 78 ++++++++++++++++----------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/Telegram/cmake/lib_tgvoip.cmake b/Telegram/cmake/lib_tgvoip.cmake index 5f212e995c..c7c21555a8 100644 --- a/Telegram/cmake/lib_tgvoip.cmake +++ b/Telegram/cmake/lib_tgvoip.cmake @@ -130,20 +130,17 @@ PRIVATE TGVOIP_USE_DESKTOP_DSP ) -target_compile_options_if_exists(lib_tgvoip_bundled +target_include_directories(lib_tgvoip_bundled +PUBLIC + ${tgvoip_loc} +) +target_link_libraries(lib_tgvoip_bundled PRIVATE - -Wno-unqualified-std-cast-call + desktop-app::external_webrtc + desktop-app::external_opus ) -if (WIN32) - target_compile_options_if_exists(lib_tgvoip_bundled - PRIVATE - /wd4005 # 'identifier' : macro redefinition - /wd4068 # unknown pragma - /wd4996 # deprecated - /wd5055 # operator '>' deprecated between enumerations and floating-point types - ) -elseif (APPLE) +if (APPLE) target_compile_definitions(lib_tgvoip_bundled PUBLIC TARGET_OS_OSX @@ -155,35 +152,7 @@ elseif (APPLE) TGVOIP_NO_OSX_PRIVATE_API ) endif() -else() - add_library(lib_tgvoip_bundled_options INTERFACE) - target_compile_options(lib_tgvoip_bundled_options - INTERFACE - -Wno-unused-variable - -Wno-unknown-pragmas - -Wno-error=sequence-point - -Wno-error=unused-result - ) - if (CMAKE_SIZEOF_VOID_P EQUAL 4 AND CMAKE_SYSTEM_PROCESSOR MATCHES "i686.*|i386.*|x86.*") - target_compile_options(lib_tgvoip_bundled_options INTERFACE -msse2) - endif() - target_link_libraries(lib_tgvoip_bundled - PRIVATE - lib_tgvoip_bundled_options - ) -endif() - -target_include_directories(lib_tgvoip_bundled -PUBLIC - ${tgvoip_loc} -) -target_link_libraries(lib_tgvoip_bundled -PRIVATE - desktop-app::external_webrtc - desktop-app::external_opus -) - -if (LINUX) +elseif (LINUX) if (NOT LIBTGVOIP_DISABLE_ALSA) find_package(ALSA REQUIRED) target_include_directories(lib_tgvoip_bundled SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS}) @@ -216,6 +185,35 @@ if (LINUX) endif() endif() +add_library(lib_tgvoip_bundled_options INTERFACE) + +if (MSVC) + target_compile_options(lib_tgvoip_bundled_options + INTERFACE + /wd4005 # 'identifier' : macro redefinition + /wd4068 # unknown pragma + /wd4996 # deprecated + /wd5055 # operator '>' deprecated between enumerations and floating-point types + ) +else() + target_compile_options_if_exists(lib_tgvoip_bundled_options + INTERFACE + -Wno-unqualified-std-cast-call + -Wno-unused-variable + -Wno-unknown-pragmas + -Wno-error=sequence-point + -Wno-error=unused-result + ) + if (CMAKE_SIZEOF_VOID_P EQUAL 4 AND CMAKE_SYSTEM_PROCESSOR MATCHES "i686.*|i386.*|x86.*") + target_compile_options(lib_tgvoip_bundled_options INTERFACE -msse2) + endif() +endif() + +target_link_libraries(lib_tgvoip_bundled +PRIVATE + lib_tgvoip_bundled_options +) + target_link_libraries(lib_tgvoip INTERFACE lib_tgvoip_bundled From cf5987b2b4502b504b1ab162ca0ad8a3d77a5493 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 29 Apr 2025 16:34:57 +0400 Subject: [PATCH 026/190] Update protobuf in Docker --- Telegram/build/docker/centos_env/Dockerfile | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 3e32922978..54f92ad441 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -70,14 +70,8 @@ RUN git clone -b v5.4.4 --depth=1 {{ GIT }}/tukaani-project/xz.git \ && rm -rf xz FROM builder AS protobuf -RUN git clone -b v21.9 --depth=1 --recursive --shallow-submodules {{ GIT }}/protocolbuffers/protobuf.git \ +RUN git clone -b v30.2 --depth=1 --recursive --shallow-submodules {{ GIT }}/protocolbuffers/protobuf.git \ && cd protobuf \ - && git init third_party/abseil-cpp \ - && cd third_party/abseil-cpp \ - && git remote add origin {{ GIT }}/abseil/abseil-cpp.git \ - && git fetch --depth=1 origin 273292d1cfc0a94a65082ee350509af1d113344d \ - && git reset --hard FETCH_HEAD \ - && cd ../.. \ && cmake -GNinja -B build . \ -DCMAKE_BUILD_TYPE=None \ -Dprotobuf_BUILD_TESTS=OFF \ From 89e42af14969cca62d214035fb4d830425d36750 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 30 Apr 2025 16:52:12 +0400 Subject: [PATCH 027/190] Update cmake_helpers --- cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake b/cmake index 90e6d73100..37b57a8dbc 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 90e6d73100a9fd2dc4c30a270c3bbc1d35924f32 +Subproject commit 37b57a8dbca70b0a259e552b4c0793485268cbae From ac6124951a9e4cfb777db1b548deb63b6f71a0df Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 27 Apr 2025 02:38:47 +0400 Subject: [PATCH 028/190] Use C++/WinRT from SDK --- Telegram/CMakeLists.txt | 2 -- Telegram/lib_ui | 2 +- Telegram/lib_webrtc | 2 +- Telegram/lib_webview | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 2dcac37d05..bb6ebbceac 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -47,8 +47,6 @@ if (WIN32) platform/win/windows_quiethours.idl platform/win/windows_toastactivator.idl ) - - nuget_add_winrt(Telegram) endif() set_target_properties(Telegram PROPERTIES AUTOMOC ON) diff --git a/Telegram/lib_ui b/Telegram/lib_ui index a42f0b789c..67c0f3c543 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit a42f0b789c860289adc0b35b65e97bf002f932f1 +Subproject commit 67c0f3c5432281e4e1e9ad90642b1ea93b7505f9 diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 169ba6b1d5..e44439ed95 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 169ba6b1d5e58e9d1cfa7b7d5c85c119e6c6e2db +Subproject commit e44439ed95559bfb4730db65f954937e149b7199 diff --git a/Telegram/lib_webview b/Telegram/lib_webview index f546969919..b9f9e981c8 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit f546969919a5946d49a504f8159041fa5b55c3df +Subproject commit b9f9e981c81a78120a023822d2aa908d38b6795f From fadaf852b73717ee84d50caeb1773a8e3eecbe6f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 1 May 2025 10:42:59 +0400 Subject: [PATCH 029/190] Update lib_lottie --- Telegram/lib_lottie | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index 3eb4a97f1d..00fc0a8fb3 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit 3eb4a97f1dd038bc4b6bd2884262242382a37e79 +Subproject commit 00fc0a8fb3cd845c81913c526320cc2adab23053 From 74b188fa4661578c0303349882ff2dd848934c6f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 29 Mar 2025 23:20:14 +0300 Subject: [PATCH 030/190] Fixed ability to click on bot app button in narrowed chat list. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 7f30ceb753..d038ece3c3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1669,6 +1669,9 @@ void InnerWidget::clearIrrelevantState() { bool InnerWidget::lookupIsInBotAppButton( Row *row, QPoint localPosition) { + if (_narrowRatio) { + return false; + } if (const auto user = MaybeBotWithApp(row)) { const auto it = _rightButtons.find(user->id); if (it != _rightButtons.end()) { From 63578affa42c7c43e848d00b7c2a23c241d81490 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 31 Mar 2025 00:31:26 +0300 Subject: [PATCH 031/190] Added toasts to quick dialog actions. --- Telegram/Resources/langs/lang.strings | 9 +++++++ .../dialogs/dialogs_quick_action.cpp | 26 +++++++++++++++++-- .../SourceFiles/window/window_peer_menu.cpp | 22 +++++++++++++--- .../SourceFiles/window/window_peer_menu.h | 3 ++- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 862ff5565f..bfadf60c39 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1195,6 +1195,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_quick_dialog_action_delete" = "Delete"; "lng_settings_quick_dialog_action_disabled" = "Change folder"; +"lng_quick_dialog_action_toast_mute_success" = "Notifications for this chat have been muted."; +"lng_quick_dialog_action_toast_unmute_success" = "Notifications enabled for this chat."; +"lng_quick_dialog_action_toast_pin_success" = "The chat has been pinned."; +"lng_quick_dialog_action_toast_unpin_success" = "The chat has been unpinned."; +"lng_quick_dialog_action_toast_read_success" = "The chat has been marked as read."; +"lng_quick_dialog_action_toast_unread_success" = "The chat has been marked as unread."; +"lng_quick_dialog_action_toast_archive_success" = "The chat has been archived."; +"lng_quick_dialog_action_toast_unarchive_success" = "The chat has been unarchived."; + "lng_settings_generic_subscribe" = "Subscribe to {link} to use this setting."; "lng_settings_generic_subscribe_link" = "Telegram Premium"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp index 340a9f3b6e..a796b4721e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_quick_action.cpp @@ -76,19 +76,41 @@ void PerformQuickDialogAction( MuteMenu::ThreadDescriptor(history).updateMutePeriod(isMuted ? 0 : std::numeric_limits::max()); + controller->showToast(isMuted + ? tr::lng_quick_dialog_action_toast_unmute_success(tr::now) + : tr::lng_quick_dialog_action_toast_mute_success(tr::now)); } else if (action == Dialogs::Ui::QuickDialogAction::Pin) { const auto entry = (Dialogs::Entry*)(history); - Window::TogglePinnedThread(controller, entry, filterId); + const auto isPinned = entry->isPinnedDialog(filterId); + const auto onToggled = isPinned + ? Fn(nullptr) + : [=] { + controller->showToast( + tr::lng_quick_dialog_action_toast_pin_success(tr::now)); + }; + Window::TogglePinnedThread(controller, entry, filterId, onToggled); + if (isPinned) { + controller->showToast( + tr::lng_quick_dialog_action_toast_unpin_success(tr::now)); + } } else if (action == Dialogs::Ui::QuickDialogAction::Read) { if (Window::IsUnreadThread(history)) { Window::MarkAsReadThread(history); + controller->showToast( + tr::lng_quick_dialog_action_toast_read_success(tr::now)); } else if (history) { peer->owner().histories().changeDialogUnreadMark(history, true); + controller->showToast( + tr::lng_quick_dialog_action_toast_unread_success(tr::now)); } } else if (action == Dialogs::Ui::QuickDialogAction::Archive) { + const auto isArchived = Window::IsArchived(history); + controller->showToast(isArchived + ? tr::lng_quick_dialog_action_toast_unarchive_success(tr::now) + : tr::lng_quick_dialog_action_toast_archive_success(tr::now)); history->session().api().toggleHistoryArchived( history, - !Window::IsArchived(history), + !isArchived, [] {}); } else if (action == Dialogs::Ui::QuickDialogAction::Delete) { Window::DeleteAndLeaveHandler(controller, peer)(); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 996096e276..135985fb10 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -390,7 +390,8 @@ bool PinnedLimitReached( void TogglePinnedThread( not_null controller, - not_null entry) { + not_null entry, + Fn onToggled) { if (!entry->folderKnown()) { return; } @@ -410,6 +411,9 @@ void TogglePinnedThread( MTP_inputDialogPeer(history->peer->input) )).done([=] { owner->notifyPinnedDialogsOrderUpdated(); + if (onToggled) { + onToggled(); + } }).send(); if (isPinned) { controller->content()->dialogsToUp(); @@ -421,6 +425,9 @@ void TogglePinnedThread( MTP_bool(isPinned) )).done([=](const MTPUpdates &result) { owner->session().api().applyUpdates(result); + if (onToggled) { + onToggled(); + } }).send(); } else if (const auto sublist = entry->asSublist()) { const auto flags = isPinned @@ -431,6 +438,9 @@ void TogglePinnedThread( MTP_inputDialogPeer(sublist->peer()->input) )).done([=] { owner->notifyPinnedDialogsOrderUpdated(); + if (onToggled) { + onToggled(); + } }).send(); //if (isPinned) { // controller->content()->dialogsToUp(); @@ -499,7 +509,7 @@ void Filler::addTogglePin() { const auto weak = base::make_weak(entry); const auto pinToggle = [=] { if (const auto strong = weak.get()) { - TogglePinnedThread(controller, strong, filterId); + TogglePinnedThread(controller, strong, filterId, nullptr); } }; _addAction( @@ -3310,9 +3320,10 @@ void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction) { void TogglePinnedThread( not_null controller, not_null entry, - FilterId filterId) { + FilterId filterId, + Fn onToggled) { if (!filterId) { - return TogglePinnedThread(controller, entry); + return TogglePinnedThread(controller, entry, onToggled); } const auto history = entry->asHistory(); if (!history) { @@ -3338,6 +3349,9 @@ void TogglePinnedThread( Api::SaveNewFilterPinned(&owner->session(), filterId); if (isPinned) { controller->content()->dialogsToUp(); + if (onToggled) { + onToggled(); + } } } diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index e2406d8ee7..a125c3c55d 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -202,7 +202,8 @@ void ToggleMessagePinned( void TogglePinnedThread( not_null controller, not_null entry, - FilterId filterId); + FilterId filterId, + Fn onToggled); void HidePinnedBar( not_null navigation, not_null peer, From e0b1ed87e8fb47d24f3efb61140a4cf9ae3b3b76 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 31 Mar 2025 01:01:13 +0300 Subject: [PATCH 032/190] Fixed ability to trigger quick dialog action with non-middle button. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index d038ece3c3..79054d0474 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2387,7 +2387,7 @@ void InnerWidget::mousePressReleased( if (_chatPreviewScheduled) { _controller->cancelScheduledPreview(); } - _pressButton = Qt::NoButton; + const auto pressButton = base::take(_pressButton); const auto wasDragging = finishReorderOnRelease(); @@ -2420,7 +2420,10 @@ void InnerWidget::mousePressReleased( if (_pressedRightButtonData && _pressedRightButtonData->ripple) { _pressedRightButtonData->ripple->lastStop(); } - if (_activeQuickAction && pressed && !_activeQuickAction->data) { + if ((pressButton == Qt::MiddleButton) + && _activeQuickAction + && pressed + && !_activeQuickAction->data) { if (const auto history = pressed->history()) { const auto raw = _activeQuickAction.get(); if (raw->ripple) { From 3a622f111ac9c3dfb92ec1ee1d84b5b62b51cf8d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 29 Mar 2025 23:13:41 +0300 Subject: [PATCH 033/190] Added initial dummy draft for top bar suggestions in dialogs. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 3 + .../dialogs/dialogs_top_bar_suggestion.cpp | 44 ++++++ .../dialogs/dialogs_top_bar_suggestion.h | 30 ++++ .../SourceFiles/dialogs/dialogs_widget.cpp | 53 ++++++- Telegram/SourceFiles/dialogs/dialogs_widget.h | 7 +- .../ui/dialogs_top_bar_suggestion_content.cpp | 136 ++++++++++++++++++ .../ui/dialogs_top_bar_suggestion_content.h | 51 +++++++ Telegram/cmake/td_ui.cmake | 2 + 9 files changed, 320 insertions(+), 8 deletions(-) create mode 100644 Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp create mode 100644 Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h create mode 100644 Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp create mode 100644 Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index bb6ebbceac..5b918517c2 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -690,6 +690,8 @@ PRIVATE dialogs/dialogs_search_from_controllers.h dialogs/dialogs_search_tags.cpp dialogs/dialogs_search_tags.h + dialogs/dialogs_top_bar_suggestion.cpp + dialogs/dialogs_top_bar_suggestion.h dialogs/dialogs_widget.cpp dialogs/dialogs_widget.h editor/color_picker.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bfadf60c39..bc008b1fba 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3863,6 +3863,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_skip_archive_in_search" = "Skip results from archive"; "lng_dialogs_show_archive_in_search" = "With results from archive"; +"lng_dialogs_top_bar_suggestions_birthday_title" = "Add your birthday! 🎂"; +"lng_dialogs_top_bar_suggestions_birthday_about" = "Let your contacts know when you’re celebrating."; + "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp new file mode 100644 index 0000000000..9f25d7c396 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -0,0 +1,44 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "dialogs/dialogs_top_bar_suggestion.h" + +#include "dialogs/ui/dialogs_top_bar_suggestion_content.h" +#include "lang/lang_keys.h" +#include "ui/text/text_utilities.h" +#include "ui/wrap/slide_wrap.h" + +namespace Dialogs { + +object_ptr> CreateTopBarSuggestion( + not_null parent, + not_null session) { + const auto content = Ui::CreateChild(parent); + auto result = object_ptr>( + parent, + object_ptr::fromRaw(content)); + const auto wrap = result.data(); + + content->setContent( + tr::lng_dialogs_top_bar_suggestions_birthday_title( + tr::now, + Ui::Text::Bold), + tr::lng_dialogs_top_bar_suggestions_birthday_about( + tr::now, + TextWithEntities::Simple)); + + rpl::combine( + parent->widthValue(), + content->desiredHeightValue() + ) | rpl::start_with_next([=](int width, int height) { + content->resize(width, height); + }, content->lifetime()); + + return result; +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h new file mode 100644 index 0000000000..631ea2dcad --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h @@ -0,0 +1,30 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +template +class object_ptr; + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { +class RpWidget; +template +class SlideWrap; +} // namespace Ui + +namespace Dialogs { + +[[nodiscard]] object_ptr> CreateTopBarSuggestion( + not_null parent, + not_null); + +} // namespace Dialogs + diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 14cef4d46a..0049af0aca 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/dialogs_suggestions.h" #include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_search_from_controllers.h" +#include "dialogs/dialogs_top_bar_suggestion.h" #include "dialogs/dialogs_quick_action.h" #include "dialogs/dialogs_key.h" #include "history/history.h" @@ -31,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/elastic_scroll.h" #include "ui/widgets/fields/input_field.h" #include "ui/wrap/fade_wrap.h" +#include "ui/wrap/vertical_layout.h" #include "ui/effects/radial_animation.h" #include "ui/chat/requests_bar.h" #include "ui/chat/group_call_bar.h" @@ -375,20 +377,42 @@ Widget::Widget( _scroll->setOverscrollTypes( _stories ? OverscrollType::Virtual : OverscrollType::Real, OverscrollType::Real); - _inner = _scroll->setOwnedWidget(object_ptr( - this, + const auto innerList = _scroll->setOwnedWidget( + object_ptr(this)); + if (_layout != Layout::Child) { + _topBarSuggestion = innerList->add(CreateTopBarSuggestion( + innerList, + &session())); + rpl::combine( + _topBarSuggestion->entity()->desiredHeightValue(), + _childListShown.value() + ) | rpl::start_with_next([=](int desiredHeight, float64 shown) { + const auto newHeight = desiredHeight * (1. - shown); + _topBarSuggestion->entity()->setMaximumHeight(newHeight); + _topBarSuggestion->entity()->setMinimumWidth(width()); + _topBarSuggestion->entity()->resize(width(), newHeight); + }, _topBarSuggestion->lifetime()); + } + _inner = innerList->add(object_ptr( + innerList, controller, rpl::combine( _childListPeerId.value(), _childListShown.value(), makeChildListShown))); _scroll->heightValue() | rpl::start_with_next([=](int height) { - _inner->setMinimumHeight(height); + innerList->setMinimumHeight(height); + _inner->setMinimumHeight(height + - (_topBarSuggestion ? _topBarSuggestion->height() : 0)); _inner->refresh(); - }, _inner->lifetime()); + }, innerList->lifetime()); + _scroll->widthValue() | rpl::start_with_next([=](int width) { + innerList->resizeToWidth(width); + }, innerList->lifetime()); _scrollToTop->raise(); _lockUnlock->toggle(false, anim::type::instant); + _inner->updated( ) | rpl::start_with_next([=] { listScrollUpdated(); @@ -1022,6 +1046,18 @@ void Widget::updateFrozenAccountBar() { } } +void Widget::updateTopBarSuggestions() { + if (_topBarSuggestion) { + if ((_layout == Layout::Child) + || _openedForum + || _openedFolder) { + _topBarSuggestion->toggle(false, anim::type::instant); + } else { + _topBarSuggestion->toggle(true, anim::type::instant); + } + } +} + void Widget::setupMoreChatsBar() { if (_layout == Layout::Child) { return; @@ -1454,7 +1490,7 @@ void Widget::updateControlsVisibility(bool fast) { _frozenAccountBar->show(); } if (_chatFilters) { - _chatFilters->show(); + _chatFilters->setVisible(!_openedForum); } if (_openedFolder || _openedForum) { _subsectionTopBar->show(); @@ -1772,6 +1808,7 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) { storiesExplicitCollapse(); } updateFrozenAccountBar(); + updateTopBarSuggestions(); }, (folder != nullptr), animated); } @@ -1829,6 +1866,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) { _inner->changeOpenedForum(forum); storiesToggleExplicitExpand(false); updateFrozenAccountBar(); + updateTopBarSuggestions(); updateStoriesVisibility(); }, (forum != nullptr), animated); } @@ -3402,7 +3440,8 @@ bool Widget::applySearchState(SearchState state) { : nullptr; _searchState = state; if (_chatFilters && queryEmptyChanged) { - _chatFilters->setVisible(_searchState.query.isEmpty()); + _chatFilters->setVisible(_searchState.query.isEmpty() + && !_openedForum); updateControlsGeometry(); } _searchWithPostsPreview = computeSearchWithPostsPreview(); @@ -3829,7 +3868,7 @@ void Widget::updateControlsGeometry() { _chatFilters->move(0, chatFiltersTop); } const auto scrollTop = chatFiltersTop - + ((_chatFilters && _searchState.query.isEmpty()) + + ((_chatFilters && _searchState.query.isEmpty() && !_openedForum) ? (_chatFilters->height() * (1. - narrowRatio)) : 0); const auto scrollHeight = height() - scrollTop - bottomSkip; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 7d231d3c77..5f2699cb47 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -53,6 +53,8 @@ class JumpDownButton; class ElasticScroll; template class FadeWrapScaled; +template +class SlideWrap; } // namespace Ui namespace Window { @@ -223,6 +225,7 @@ private: void showMainMenu(); void clearSearchCache(bool clearPosts); void setSearchQuery(const QString &query, int cursorPosition = -1); + void updateTopBarSuggestions(); void updateFrozenAccountBar(); void updateControlsVisibility(bool fast = false); void updateLockUnlockVisibility( @@ -313,7 +316,7 @@ private: object_ptr> _chooseFromUser; object_ptr> _jumpToDate; object_ptr _cancelSearch; - object_ptr< Ui::FadeWrapScaled> _lockUnlock; + object_ptr> _lockUnlock; std::unique_ptr _moreChatsBar; @@ -324,6 +327,8 @@ private: base::unique_qptr _chatFilters; + Ui::SlideWrap *_topBarSuggestion = nullptr; + object_ptr _scroll; QPointer _inner; std::unique_ptr _suggestions; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp new file mode 100644 index 0000000000..94e0b2d40f --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp @@ -0,0 +1,136 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "dialogs/ui/dialogs_top_bar_suggestion_content.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_dialogs.h" + +namespace Dialogs { +namespace { +} // namespace + +TopBarSuggestionContent::TopBarSuggestionContent(not_null p) +: Ui::RippleButton(p, st::defaultRippleAnimationBgOver) +, _titleSt(st::semiboldTextStyle) +, _contentTitleSt(st::semiboldTextStyle) +, _contentTextSt(st::defaultTextStyle) { +} + +void TopBarSuggestionContent::draw(QPainter &p) { + const auto kLinesForPhoto = 3; + const auto rightPhotoSize = _titleSt.font->ascent * kLinesForPhoto; + const auto rightPhotoPlaceholder = _titleSt.font->height * kLinesForPhoto; + + const auto r = Ui::RpWidget::rect(); + p.fillRect(r, st::historyPinnedBg); + Ui::RippleButton::paintRipple(p, 0, 0); + const auto leftPadding = st::msgReplyBarSkip + st::msgReplyBarSkip; + const auto rightPadding = st::msgReplyBarSkip; + const auto topPadding = st::msgReplyPadding.top(); + const auto availableWidthNoPhoto = r.width() + - leftPadding + - rightPadding; + const auto availableWidth = availableWidthNoPhoto + - (_rightHide ? _rightHide->width() : 0); + const auto titleRight = leftPadding + + _titleSt.font->spacew * 2; + const auto hasSecondLineTitle = (titleRight + > (availableWidth - _contentTitle.maxWidth())); + p.setPen(st::windowActiveTextFg); + p.setPen(st::windowFg); + { + const auto left = hasSecondLineTitle ? leftPadding : titleRight; + const auto top = hasSecondLineTitle + ? (topPadding + _titleSt.font->height) + : topPadding; + _contentTitle.draw(p, { + .position = QPoint(left, top), + .outerWidth = hasSecondLineTitle + ? availableWidth + : (availableWidth - titleRight), + .availableWidth = availableWidth, + .elisionLines = 1, + }); + } + { + const auto left = leftPadding; + const auto top = hasSecondLineTitle + ? (topPadding + + _titleSt.font->height + + _contentTitleSt.font->height) + : topPadding + _titleSt.font->height; + auto lastContentLineAmount = 0; + const auto lineHeight = _contentTextSt.font->height; + const auto lineLayout = [&](int line) -> Ui::Text::LineGeometry { + line++; + lastContentLineAmount = line; + const auto diff = (st::sponsoredMessageBarMaxHeight) + - line * lineHeight; + if (diff < 3 * lineHeight) { + return { + .width = availableWidthNoPhoto, + .elided = true, + }; + } else if (diff < 2 * lineHeight) { + return {}; + } + line += (hasSecondLineTitle ? 2 : 1) + 1; + return { + .width = (line > kLinesForPhoto) + ? availableWidthNoPhoto + : availableWidth, + }; + }; + _contentText.draw(p, { + .position = QPoint(left, top), + .outerWidth = availableWidth, + .availableWidth = availableWidth, + .geometry = Ui::Text::GeometryDescriptor{ + .layout = std::move(lineLayout), + }, + }); + _lastPaintedContentTop = top; + _lastPaintedContentLineAmount = lastContentLineAmount; + } +} + +void TopBarSuggestionContent::setContent( + TextWithEntities title, + TextWithEntities description) { + _contentTitle.setMarkedText(_contentTitleSt, std::move(title)); + _contentText.setMarkedText(_contentTextSt, std::move(description)); +} + +void TopBarSuggestionContent::paintEvent(QPaintEvent *) { + auto p = QPainter(this); + draw(p); +} + +rpl::producer TopBarSuggestionContent::desiredHeightValue() const { + const auto kLinesForPhoto = 3; + const auto rightPhotoSize = _titleSt.font->ascent * kLinesForPhoto; + const auto rightPhotoPlaceholder = _titleSt.font->height * kLinesForPhoto; + return rpl::combine( + _lastPaintedContentTop.value(), + _lastPaintedContentLineAmount.value() + ) | rpl::distinct_until_changed() | rpl::map([=]( + int lastTop, + int lastLines) { + const auto bottomPadding = st::msgReplyPadding.top(); + const auto desiredHeight = lastTop + + (lastLines * _contentTextSt.font->height) + + bottomPadding; + const auto minHeight = desiredHeight; + return std::clamp( + desiredHeight, + minHeight, + st::sponsoredMessageBarMaxHeight); + }); +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h new file mode 100644 index 0000000000..f29bbde95d --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h @@ -0,0 +1,51 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/widgets/buttons.h" + +namespace Ui { +class DynamicImage; +class IconButton; +} // namespace Ui + +namespace Dialogs { + +class TopBarSuggestionContent : public Ui::RippleButton { +public: + TopBarSuggestionContent(not_null); + + void setContent( + TextWithEntities title, + TextWithEntities description); + + [[nodiscard]] rpl::producer desiredHeightValue() const override; + +protected: + void paintEvent(QPaintEvent *) override; + +private: + void draw(QPainter &p); + + const style::TextStyle &_titleSt; + const style::TextStyle &_contentTitleSt; + const style::TextStyle &_contentTextSt; + + Ui::Text::String _contentTitle; + Ui::Text::String _contentText; + rpl::variable _lastPaintedContentLineAmount = 0; + rpl::variable _lastPaintedContentTop = 0; + + base::unique_qptr _rightHide; + + std::shared_ptr _rightPhoto; + QImage _rightPhotoImage; + +}; + +} // namespace Dialogs diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index ad9c8519a5..6e04f99630 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -105,6 +105,8 @@ PRIVATE dialogs/ui/dialogs_quick_action.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h + dialogs/ui/dialogs_top_bar_suggestion_content.cpp + dialogs/ui/dialogs_top_bar_suggestion_content.h dialogs/ui/top_peers_strip.cpp dialogs/ui/top_peers_strip.h From f3b6c7c09bbcc266780d39398acab5c0004e77fa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 3 Apr 2025 20:54:57 +0300 Subject: [PATCH 034/190] Added birthday suggestion to top bar in dialogs. --- .../dialogs/dialogs_top_bar_suggestion.cpp | 77 +++++++++++++++++-- .../ui/dialogs_top_bar_suggestion_content.cpp | 18 ++++- .../ui/dialogs_top_bar_suggestion_content.h | 3 + 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 9f25d7c396..1560946909 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -7,12 +7,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_top_bar_suggestion.h" +#include "core/application.h" +#include "core/click_handler_types.h" +#include "data/data_birthday.h" +#include "data/data_user.h" #include "dialogs/ui/dialogs_top_bar_suggestion_content.h" +#include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" +#include "main/main_app_config.h" +#include "main/main_session.h" #include "ui/text/text_utilities.h" #include "ui/wrap/slide_wrap.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" namespace Dialogs { +namespace { + +[[nodiscard]] Window::SessionController *FindSessionController( + not_null widget) { + const auto window = Core::App().findWindow(widget); + return window ? window->sessionController() : nullptr; +} + +constexpr auto kSugSetBirthday = "BIRTHDAY_SETUP"_cs; + +} // namespace object_ptr> CreateTopBarSuggestion( not_null parent, @@ -22,14 +42,57 @@ object_ptr> CreateTopBarSuggestion( parent, object_ptr::fromRaw(content)); const auto wrap = result.data(); + wrap->toggle(false, anim::type::instant); + struct State { + rpl::lifetime birthdayLifetime; + }; + const auto state = content->lifetime().make_state(); - content->setContent( - tr::lng_dialogs_top_bar_suggestions_birthday_title( - tr::now, - Ui::Text::Bold), - tr::lng_dialogs_top_bar_suggestions_birthday_about( - tr::now, - TextWithEntities::Simple)); + const auto processCurrentSuggestion = [=](auto repeat) -> void { + if (session->appConfig().suggestionCurrent(kSugSetBirthday.utf8()) + && !Data::IsBirthdayToday(session->user()->birthday())) { + content->setClickedCallback([=] { + const auto controller = FindSessionController(parent); + if (!controller) { + return; + } + Core::App().openInternalUrl( + u"internal:edit_birthday"_q, + QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = base::make_weak(controller), + })); + + state->birthdayLifetime = Info::Profile::BirthdayValue( + session->user() + ) | rpl::map( + Data::IsBirthdayTodayValue + ) | rpl::flatten_latest( + ) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=] { + repeat(repeat); + }); + }); + content->setHideCallback([=] { + session->appConfig().dismissSuggestion( + kSugSetBirthday.utf8()); + repeat(repeat); + }); + content->setContent( + tr::lng_dialogs_top_bar_suggestions_birthday_title( + tr::now, + Ui::Text::Bold), + tr::lng_dialogs_top_bar_suggestions_birthday_about( + tr::now, + TextWithEntities::Simple)); + wrap->toggle(true, anim::type::normal); + } else { + wrap->toggle(false, anim::type::normal); + } + }; + + session->appConfig().refreshed() | rpl::start_with_next([=] { + processCurrentSuggestion(processCurrentSuggestion); + }, content->lifetime()); rpl::combine( parent->widthValue(), diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp index 94e0b2d40f..64fc212f5a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp @@ -18,7 +18,15 @@ TopBarSuggestionContent::TopBarSuggestionContent(not_null p) : Ui::RippleButton(p, st::defaultRippleAnimationBgOver) , _titleSt(st::semiboldTextStyle) , _contentTitleSt(st::semiboldTextStyle) -, _contentTextSt(st::defaultTextStyle) { +, _contentTextSt(st::defaultTextStyle) +, _rightHide( + base::make_unique_q( + this, + st::dialogsCancelSearchInPeer)) { + const auto rightHide = _rightHide.get(); + sizeValue() | rpl::start_with_next([=](const QSize &s) { + rightHide->moveToRight(st::buttonRadius, st::lineWidth); + }, rightHide->lifetime()); } void TopBarSuggestionContent::draw(QPainter &p) { @@ -29,7 +37,7 @@ void TopBarSuggestionContent::draw(QPainter &p) { const auto r = Ui::RpWidget::rect(); p.fillRect(r, st::historyPinnedBg); Ui::RippleButton::paintRipple(p, 0, 0); - const auto leftPadding = st::msgReplyBarSkip + st::msgReplyBarSkip; + const auto leftPadding = st::defaultDialogRow.padding.left(); const auto rightPadding = st::msgReplyBarSkip; const auto topPadding = st::msgReplyPadding.top(); const auto availableWidthNoPhoto = r.width() @@ -44,7 +52,7 @@ void TopBarSuggestionContent::draw(QPainter &p) { p.setPen(st::windowActiveTextFg); p.setPen(st::windowFg); { - const auto left = hasSecondLineTitle ? leftPadding : titleRight; + const auto left = leftPadding; const auto top = hasSecondLineTitle ? (topPadding + _titleSt.font->height) : topPadding; @@ -133,4 +141,8 @@ rpl::producer TopBarSuggestionContent::desiredHeightValue() const { }); } +void TopBarSuggestionContent::setHideCallback(Fn hideCallback) { + _rightHide->setClickedCallback(std::move(hideCallback)); +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h index f29bbde95d..65190aca86 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h @@ -26,6 +26,8 @@ public: [[nodiscard]] rpl::producer desiredHeightValue() const override; + void setHideCallback(Fn); + protected: void paintEvent(QPaintEvent *) override; @@ -42,6 +44,7 @@ private: rpl::variable _lastPaintedContentTop = 0; base::unique_qptr _rightHide; + Fn _hideCallback; std::shared_ptr _rightPhoto; QImage _rightPhotoImage; From 98d9357208171b3a2dfb4fbf184866aa399729b6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 5 Apr 2025 14:02:30 +0300 Subject: [PATCH 035/190] Added auto clean of top bar in dialogs on empty. --- Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp | 4 ++++ Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 1560946909..656686b25b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_top_bar_suggestion.h" +#include "base/call_delayed.h" #include "core/application.h" #include "core/click_handler_types.h" #include "data/data_birthday.h" @@ -87,6 +88,9 @@ object_ptr> CreateTopBarSuggestion( wrap->toggle(true, anim::type::normal); } else { wrap->toggle(false, anim::type::normal); + base::call_delayed(st::slideWrapDuration * 2, wrap, [=] { + delete wrap; + }); } }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 0049af0aca..56a7893ce0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -383,6 +383,9 @@ Widget::Widget( _topBarSuggestion = innerList->add(CreateTopBarSuggestion( innerList, &session())); + _topBarSuggestion->lifetime().add([=] { + _topBarSuggestion = nullptr; + }); rpl::combine( _topBarSuggestion->entity()->desiredHeightValue(), _childListShown.value() From 640db8af7dddba633887f1da4aa34ea308dad379 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 5 Apr 2025 19:17:25 +0300 Subject: [PATCH 036/190] Added suggestion for annual premium to top bar in dialogs. --- Telegram/Resources/langs/lang.strings | 2 + .../SourceFiles/api/api_premium_option.cpp | 1 + .../data/data_premium_subscription_option.h | 1 + Telegram/SourceFiles/dialogs/dialogs.style | 7 ++ .../dialogs/dialogs_top_bar_suggestion.cpp | 46 +++++++++++++ .../SourceFiles/dialogs/dialogs_widget.cpp | 4 +- Telegram/SourceFiles/dialogs/dialogs_widget.h | 2 +- .../ui/dialogs_top_bar_suggestion_content.cpp | 65 ++++++++++++++----- .../ui/dialogs_top_bar_suggestion_content.h | 10 +++ 9 files changed, 118 insertions(+), 20 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bc008b1fba..02dd7a92fd 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3865,6 +3865,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_top_bar_suggestions_birthday_title" = "Add your birthday! 🎂"; "lng_dialogs_top_bar_suggestions_birthday_about" = "Let your contacts know when you’re celebrating."; +"lng_dialogs_top_bar_suggestions_premium_annual_title" = "Telegram Premium with a {text} discount"; +"lng_dialogs_top_bar_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; diff --git a/Telegram/SourceFiles/api/api_premium_option.cpp b/Telegram/SourceFiles/api/api_premium_option.cpp index d3c67e23b5..c4346dbe07 100644 --- a/Telegram/SourceFiles/api/api_premium_option.cpp +++ b/Telegram/SourceFiles/api/api_premium_option.cpp @@ -25,6 +25,7 @@ Data::PremiumSubscriptionOption CreateSubscriptionOption( * kDiscountDivider; }(); return { + .months = months, .duration = Ui::FormatTTL(months * 86400 * 31), .discount = (discount > 0) ? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount) diff --git a/Telegram/SourceFiles/data/data_premium_subscription_option.h b/Telegram/SourceFiles/data/data_premium_subscription_option.h index 6d5bd5e733..df2c4a8c4e 100644 --- a/Telegram/SourceFiles/data/data_premium_subscription_option.h +++ b/Telegram/SourceFiles/data/data_premium_subscription_option.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { struct PremiumSubscriptionOption { + int months = 0; QString duration; QString discount; QString costPerMonth; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 9d4323f19c..0269c5c1a8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -813,3 +813,10 @@ dialogsSponsoredButton: DialogRightButton(dialogRowOpenBot) { } margin: margins(0px, 9px, 10px, 0px); } + +dialogsTopBarSuggestionTitleStyle: TextStyle(defaultTextStyle) { + font: font(semibold 12px); +} +dialogsTopBarSuggestionAboutStyle: TextStyle(defaultTextStyle) { + font: font(11px); +} diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 656686b25b..9589cba5a7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_top_bar_suggestion.h" +#include "api/api_premium.h" +#include "apiwrap.h" #include "base/call_delayed.h" #include "core/application.h" #include "core/click_handler_types.h" @@ -17,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "settings/settings_premium.h" #include "ui/text/text_utilities.h" #include "ui/wrap/slide_wrap.h" #include "window/window_controller.h" @@ -32,6 +35,7 @@ namespace { } constexpr auto kSugSetBirthday = "BIRTHDAY_SETUP"_cs; +constexpr auto kSugPremiumAnnual = "PREMIUM_ANNUAL"_cs; } // namespace @@ -46,12 +50,14 @@ object_ptr> CreateTopBarSuggestion( wrap->toggle(false, anim::type::instant); struct State { rpl::lifetime birthdayLifetime; + rpl::lifetime premiumLifetime; }; const auto state = content->lifetime().make_state(); const auto processCurrentSuggestion = [=](auto repeat) -> void { if (session->appConfig().suggestionCurrent(kSugSetBirthday.utf8()) && !Data::IsBirthdayToday(session->user()->birthday())) { + content->setRightIcon(TopBarSuggestionContent::RightIcon::Close); content->setClickedCallback([=] { const auto controller = FindSessionController(parent); if (!controller) { @@ -86,6 +92,46 @@ object_ptr> CreateTopBarSuggestion( tr::now, TextWithEntities::Simple)); wrap->toggle(true, anim::type::normal); + } else if (session->premiumPossible() + && !session->premium() + && session->appConfig().suggestionCurrent( + kSugPremiumAnnual.utf8())) { + content->setRightIcon(TopBarSuggestionContent::RightIcon::Arrow); + const auto api = &session->api().premium(); + const auto set = [=](QString discount) { + constexpr auto kMinus = QChar(0x2212); + content->setContent( + tr::lng_dialogs_top_bar_suggestions_premium_annual_title( + tr::now, + lt_text, + { discount.replace(kMinus, QChar()) }, + Ui::Text::Bold), + tr::lng_dialogs_top_bar_suggestions_premium_annual_about( + tr::now, + TextWithEntities::Simple)); + content->setClickedCallback([=] { + const auto controller = FindSessionController(parent); + if (!controller) { + return; + } + Settings::ShowPremium(controller, "dialogs_hint"); + session->appConfig().dismissSuggestion( + kSugPremiumAnnual.utf8()); + repeat(repeat); + }); + wrap->toggle(true, anim::type::normal); + }; + api->statusTextValue( + ) | rpl::start_with_next([=] { + for (const auto &option : api->subscriptionOptions()) { + if (option.months == 12) { + set(option.discount); + state->premiumLifetime.destroy(); + return; + } + } + }, state->premiumLifetime); + api->reload(); } else { wrap->toggle(false, anim::type::normal); base::call_delayed(st::slideWrapDuration * 2, wrap, [=] { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 56a7893ce0..ef1913176c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -392,7 +392,9 @@ Widget::Widget( ) | rpl::start_with_next([=](int desiredHeight, float64 shown) { const auto newHeight = desiredHeight * (1. - shown); _topBarSuggestion->entity()->setMaximumHeight(newHeight); - _topBarSuggestion->entity()->setMinimumWidth(width()); + _topBarSuggestion->entity()->setMinimumWidth((shown > 0) + ? width() + : 0); _topBarSuggestion->entity()->resize(width(), newHeight); }, _topBarSuggestion->lifetime()); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 5f2699cb47..2a6e8d4aca 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -301,7 +301,7 @@ private: base::Timer _chooseByDragTimer; const Layout _layout = Layout::Main; - int _narrowWidth = 0; + const int _narrowWidth = 0; std::unique_ptr _frozenAccountBar; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp index 64fc212f5a..c085d9c8e8 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp @@ -6,27 +6,57 @@ For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/ui/dialogs_top_bar_suggestion_content.h" +#include "ui/ui_rpl_filter.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" +#include "styles/style_settings.h" namespace Dialogs { -namespace { -} // namespace TopBarSuggestionContent::TopBarSuggestionContent(not_null p) : Ui::RippleButton(p, st::defaultRippleAnimationBgOver) , _titleSt(st::semiboldTextStyle) -, _contentTitleSt(st::semiboldTextStyle) -, _contentTextSt(st::defaultTextStyle) -, _rightHide( - base::make_unique_q( - this, - st::dialogsCancelSearchInPeer)) { - const auto rightHide = _rightHide.get(); - sizeValue() | rpl::start_with_next([=](const QSize &s) { - rightHide->moveToRight(st::buttonRadius, st::lineWidth); - }, rightHide->lifetime()); +, _contentTitleSt(st::dialogsTopBarSuggestionTitleStyle) +, _contentTextSt(st::dialogsTopBarSuggestionAboutStyle) { + setRightIcon(RightIcon::Close); +} + +void TopBarSuggestionContent::setRightIcon(RightIcon icon) { + if (icon == _rightIcon) { + return; + } + _rightHide = nullptr; + _rightArrow = nullptr; + _rightIcon = icon; + if (icon == RightIcon::Close) { + _rightHide = base::make_unique_q( + this, + st::dialogsCancelSearchInPeer); + const auto rightHide = _rightHide.get(); + sizeValue() | rpl::filter_size( + ) | rpl::start_with_next([=](const QSize &s) { + rightHide->moveToRight(st::buttonRadius, st::lineWidth); + }, rightHide->lifetime()); + rightHide->show(); + } else if (icon == RightIcon::Arrow) { + _rightArrow = base::make_unique_q( + this, + st::backButton); + const auto arrow = _rightArrow.get(); + arrow->setIconOverride( + &st::settingsPremiumArrow, + &st::settingsPremiumArrowOver); + arrow->setAttribute(Qt::WA_TransparentForMouseEvents); + sizeValue() | rpl::filter_size( + ) | rpl::start_with_next([=](const QSize &s) { + const auto &point = st::settingsPremiumArrowShift; + arrow->moveToLeft( + s.width() - arrow->width(), + point.y() + (s.height() - arrow->height()) / 2); + }, arrow->lifetime()); + arrow->show(); + } } void TopBarSuggestionContent::draw(QPainter &p) { @@ -41,28 +71,26 @@ void TopBarSuggestionContent::draw(QPainter &p) { const auto rightPadding = st::msgReplyBarSkip; const auto topPadding = st::msgReplyPadding.top(); const auto availableWidthNoPhoto = r.width() + - (_rightArrow ? _rightArrow->width() / 2 : 0) // Takes full height. - leftPadding - rightPadding; const auto availableWidth = availableWidthNoPhoto - (_rightHide ? _rightHide->width() : 0); - const auto titleRight = leftPadding - + _titleSt.font->spacew * 2; + const auto titleRight = leftPadding; const auto hasSecondLineTitle = (titleRight > (availableWidth - _contentTitle.maxWidth())); p.setPen(st::windowActiveTextFg); p.setPen(st::windowFg); { const auto left = leftPadding; - const auto top = hasSecondLineTitle - ? (topPadding + _titleSt.font->height) - : topPadding; + const auto top = topPadding; _contentTitle.draw(p, { .position = QPoint(left, top), .outerWidth = hasSecondLineTitle ? availableWidth : (availableWidth - titleRight), .availableWidth = availableWidth, - .elisionLines = 1, + .elisionLines = hasSecondLineTitle ? 2 : 1, }); } { @@ -94,6 +122,7 @@ void TopBarSuggestionContent::draw(QPainter &p) { : availableWidth, }; }; + p.setPen(st::windowSubTextFg); _contentText.draw(p, { .position = QPoint(left, top), .outerWidth = availableWidth, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h index 65190aca86..fc9868cf7c 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h @@ -18,6 +18,12 @@ namespace Dialogs { class TopBarSuggestionContent : public Ui::RippleButton { public: + enum class RightIcon { + None, + Close, + Arrow, + }; + TopBarSuggestionContent(not_null); void setContent( @@ -27,6 +33,7 @@ public: [[nodiscard]] rpl::producer desiredHeightValue() const override; void setHideCallback(Fn); + void setRightIcon(RightIcon); protected: void paintEvent(QPaintEvent *) override; @@ -44,8 +51,11 @@ private: rpl::variable _lastPaintedContentTop = 0; base::unique_qptr _rightHide; + base::unique_qptr _rightArrow; Fn _hideCallback; + RightIcon _rightIcon = RightIcon::None; + std::shared_ptr _rightPhoto; QImage _rightPhotoImage; From b280a26317d2c489132c94df2ad51637c5c45bc2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 5 Apr 2025 20:11:49 +0300 Subject: [PATCH 037/190] Replaced management of top bar for suggestion in dialogs with rpl. --- Telegram/Resources/langs/lang.strings | 8 +- .../dialogs/dialogs_top_bar_suggestion.cpp | 217 ++++++++++-------- .../dialogs/dialogs_top_bar_suggestion.h | 5 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 43 ++-- 4 files changed, 153 insertions(+), 120 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 02dd7a92fd..0f859e7465 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3863,10 +3863,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_skip_archive_in_search" = "Skip results from archive"; "lng_dialogs_show_archive_in_search" = "With results from archive"; -"lng_dialogs_top_bar_suggestions_birthday_title" = "Add your birthday! 🎂"; -"lng_dialogs_top_bar_suggestions_birthday_about" = "Let your contacts know when you’re celebrating."; -"lng_dialogs_top_bar_suggestions_premium_annual_title" = "Telegram Premium with a {text} discount"; -"lng_dialogs_top_bar_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; +"lng_dialogs_suggestions_birthday_title" = "Add your birthday! 🎂"; +"lng_dialogs_suggestions_birthday_about" = "Let your contacts know when you’re celebrating."; +"lng_dialogs_suggestions_premium_annual_title" = "Telegram Premium with a {text} discount"; +"lng_dialogs_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 9589cba5a7..f810364acd 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -39,119 +39,140 @@ constexpr auto kSugPremiumAnnual = "PREMIUM_ANNUAL"_cs; } // namespace -object_ptr> CreateTopBarSuggestion( +rpl::producer*> TopBarSuggestionValue( not_null parent, not_null session) { - const auto content = Ui::CreateChild(parent); - auto result = object_ptr>( - parent, - object_ptr::fromRaw(content)); - const auto wrap = result.data(); - wrap->toggle(false, anim::type::instant); - struct State { - rpl::lifetime birthdayLifetime; - rpl::lifetime premiumLifetime; - }; - const auto state = content->lifetime().make_state(); + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); - const auto processCurrentSuggestion = [=](auto repeat) -> void { - if (session->appConfig().suggestionCurrent(kSugSetBirthday.utf8()) - && !Data::IsBirthdayToday(session->user()->birthday())) { - content->setRightIcon(TopBarSuggestionContent::RightIcon::Close); - content->setClickedCallback([=] { - const auto controller = FindSessionController(parent); - if (!controller) { - return; - } - Core::App().openInternalUrl( - u"internal:edit_birthday"_q, - QVariant::fromValue(ClickHandlerContext{ - .sessionWindow = base::make_weak(controller), - })); + struct State { + TopBarSuggestionContent *content = nullptr; + Ui::SlideWrap *wrap = nullptr; + rpl::lifetime birthdayLifetime; + rpl::lifetime premiumLifetime; + }; + const auto state = lifetime.make_state(); + const auto ensureWrap = [=] { + if (!state->content) { + state->content = Ui::CreateChild( + parent); + rpl::combine( + parent->widthValue(), + state->content->desiredHeightValue() + ) | rpl::start_with_next([=](int width, int height) { + state->content->resize(width, height); + }, state->content->lifetime()); + } + if (!state->wrap) { + state->wrap = Ui::CreateChild>( + parent, + object_ptr::fromRaw(state->content)); + state->wrap->toggle(false, anim::type::instant); + } + }; - state->birthdayLifetime = Info::Profile::BirthdayValue( - session->user() - ) | rpl::map( - Data::IsBirthdayTodayValue - ) | rpl::flatten_latest( - ) | rpl::distinct_until_changed( - ) | rpl::start_with_next([=] { - repeat(repeat); - }); - }); - content->setHideCallback([=] { - session->appConfig().dismissSuggestion( - kSugSetBirthday.utf8()); - repeat(repeat); - }); - content->setContent( - tr::lng_dialogs_top_bar_suggestions_birthday_title( - tr::now, - Ui::Text::Bold), - tr::lng_dialogs_top_bar_suggestions_birthday_about( - tr::now, - TextWithEntities::Simple)); - wrap->toggle(true, anim::type::normal); - } else if (session->premiumPossible() - && !session->premium() - && session->appConfig().suggestionCurrent( - kSugPremiumAnnual.utf8())) { - content->setRightIcon(TopBarSuggestionContent::RightIcon::Arrow); - const auto api = &session->api().premium(); - const auto set = [=](QString discount) { - constexpr auto kMinus = QChar(0x2212); - content->setContent( - tr::lng_dialogs_top_bar_suggestions_premium_annual_title( - tr::now, - lt_text, - { discount.replace(kMinus, QChar()) }, - Ui::Text::Bold), - tr::lng_dialogs_top_bar_suggestions_premium_annual_about( - tr::now, - TextWithEntities::Simple)); + const auto processCurrentSuggestion = [=](auto repeat) -> void { + ensureWrap(); + const auto content = state->content; + const auto wrap = state->wrap; + using RightIcon = TopBarSuggestionContent::RightIcon; + if (session->appConfig().suggestionCurrent(kSugSetBirthday.utf8()) + && !Data::IsBirthdayToday(session->user()->birthday())) { + content->setRightIcon(RightIcon::Close); content->setClickedCallback([=] { const auto controller = FindSessionController(parent); if (!controller) { return; } - Settings::ShowPremium(controller, "dialogs_hint"); + Core::App().openInternalUrl( + u"internal:edit_birthday"_q, + QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = base::make_weak(controller), + })); + + state->birthdayLifetime = Info::Profile::BirthdayValue( + session->user() + ) | rpl::map( + Data::IsBirthdayTodayValue + ) | rpl::flatten_latest( + ) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=] { + repeat(repeat); + }); + }); + content->setHideCallback([=] { session->appConfig().dismissSuggestion( - kSugPremiumAnnual.utf8()); + kSugSetBirthday.utf8()); repeat(repeat); }); + content->setContent( + tr::lng_dialogs_suggestions_birthday_title( + tr::now, + Ui::Text::Bold), + tr::lng_dialogs_suggestions_birthday_about( + tr::now, + TextWithEntities::Simple)); wrap->toggle(true, anim::type::normal); - }; - api->statusTextValue( - ) | rpl::start_with_next([=] { - for (const auto &option : api->subscriptionOptions()) { - if (option.months == 12) { - set(option.discount); - state->premiumLifetime.destroy(); - return; + } else if (session->premiumPossible() + && !session->premium() + && session->appConfig().suggestionCurrent( + kSugPremiumAnnual.utf8())) { + content->setRightIcon(RightIcon::Arrow); + const auto api = &session->api().premium(); + const auto set = [=](QString discount) { + constexpr auto kMinus = QChar(0x2212); + content->setContent( + tr::lng_dialogs_suggestions_premium_annual_title( + tr::now, + lt_text, + { discount.replace(kMinus, QChar()) }, + Ui::Text::Bold), + tr::lng_dialogs_suggestions_premium_annual_about( + tr::now, + TextWithEntities::Simple)); + content->setClickedCallback([=] { + const auto controller = FindSessionController(parent); + if (!controller) { + return; + } + Settings::ShowPremium(controller, "dialogs_hint"); + session->appConfig().dismissSuggestion( + kSugPremiumAnnual.utf8()); + repeat(repeat); + }); + wrap->toggle(true, anim::type::normal); + }; + api->statusTextValue( + ) | rpl::start_with_next([=] { + for (const auto &option : api->subscriptionOptions()) { + if (option.months == 12) { + set(option.discount); + state->premiumLifetime.destroy(); + return; + } } - } - }, state->premiumLifetime); - api->reload(); - } else { - wrap->toggle(false, anim::type::normal); - base::call_delayed(st::slideWrapDuration * 2, wrap, [=] { - delete wrap; - }); - } + }, state->premiumLifetime); + api->reload(); + } else { + wrap->toggle(false, anim::type::normal); + base::call_delayed(st::slideWrapDuration * 2, wrap, [=] { + state->content = nullptr; + state->wrap = nullptr; + consumer.put_next(nullptr); + }); + } + }; + + session->appConfig().value() | rpl::start_with_next([=] { + const auto was = state->wrap; + processCurrentSuggestion(processCurrentSuggestion); + if (was != state->wrap) { + consumer.put_next_copy(state->wrap); + } + }, lifetime); + + return lifetime; }; - - session->appConfig().refreshed() | rpl::start_with_next([=] { - processCurrentSuggestion(processCurrentSuggestion); - }, content->lifetime()); - - rpl::combine( - parent->widthValue(), - content->desiredHeightValue() - ) | rpl::start_with_next([=](int width, int height) { - content->resize(width, height); - }, content->lifetime()); - - return result; } } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h index 631ea2dcad..4ff01525c6 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h @@ -22,9 +22,10 @@ class SlideWrap; namespace Dialogs { -[[nodiscard]] object_ptr> CreateTopBarSuggestion( +[[nodiscard]] auto TopBarSuggestionValue( not_null parent, - not_null); + not_null) +-> rpl::producer*>; } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index ef1913176c..dd3934622a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -380,23 +380,34 @@ Widget::Widget( const auto innerList = _scroll->setOwnedWidget( object_ptr(this)); if (_layout != Layout::Child) { - _topBarSuggestion = innerList->add(CreateTopBarSuggestion( + TopBarSuggestionValue( innerList, - &session())); - _topBarSuggestion->lifetime().add([=] { - _topBarSuggestion = nullptr; - }); - rpl::combine( - _topBarSuggestion->entity()->desiredHeightValue(), - _childListShown.value() - ) | rpl::start_with_next([=](int desiredHeight, float64 shown) { - const auto newHeight = desiredHeight * (1. - shown); - _topBarSuggestion->entity()->setMaximumHeight(newHeight); - _topBarSuggestion->entity()->setMinimumWidth((shown > 0) - ? width() - : 0); - _topBarSuggestion->entity()->resize(width(), newHeight); - }, _topBarSuggestion->lifetime()); + &session() + ) | rpl::start_with_next([=](Ui::SlideWrap *raw) { + if (raw) { + _topBarSuggestion = innerList->insert( + 0, + object_ptr>::fromRaw(raw)); + rpl::combine( + _topBarSuggestion->entity()->desiredHeightValue(), + _childListShown.value() + ) | rpl::start_with_next([=]( + int desiredHeight, + float64 shown) { + const auto newHeight = desiredHeight * (1. - shown); + _topBarSuggestion->entity()->setMaximumHeight(newHeight); + _topBarSuggestion->entity()->setMinimumWidth((shown > 0) + ? width() + : 0); + _topBarSuggestion->entity()->resize(width(), newHeight); + }, _topBarSuggestion->lifetime()); + } else { + if (_topBarSuggestion) { + delete _topBarSuggestion; + } + _topBarSuggestion = nullptr; + } + }, lifetime()); } _inner = innerList->add(object_ptr( innerList, From fbfe3fd5ed71a06594cd70ca7d7f3be26b7e6252 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 5 Apr 2025 20:33:28 +0300 Subject: [PATCH 038/190] Added suggestion for upgrade to premium to top bar in dialogs. --- Telegram/Resources/langs/lang.strings | 2 ++ .../dialogs/dialogs_top_bar_suggestion.cpp | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0f859e7465..52c8ef178f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3867,6 +3867,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_suggestions_birthday_about" = "Let your contacts know when you’re celebrating."; "lng_dialogs_suggestions_premium_annual_title" = "Telegram Premium with a {text} discount"; "lng_dialogs_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; +"lng_dialogs_suggestions_premium_upgrade_title" = "Telegram Premium with a {text} discount"; +"lng_dialogs_suggestions_premium_upgrade_about" = "Upgrade to the annual payment plan for Telegram Premium now to get the discount."; "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index f810364acd..4d1c62f601 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -36,6 +36,7 @@ namespace { constexpr auto kSugSetBirthday = "BIRTHDAY_SETUP"_cs; constexpr auto kSugPremiumAnnual = "PREMIUM_ANNUAL"_cs; +constexpr auto kSugPremiumUpgrade = "PREMIUM_UPGRADE"_cs; } // namespace @@ -76,7 +77,8 @@ rpl::producer*> TopBarSuggestionValue( const auto content = state->content; const auto wrap = state->wrap; using RightIcon = TopBarSuggestionContent::RightIcon; - if (session->appConfig().suggestionCurrent(kSugSetBirthday.utf8()) + const auto config = &session->appConfig(); + if (config->suggestionCurrent(kSugSetBirthday.utf8()) && !Data::IsBirthdayToday(session->user()->birthday())) { content->setRightIcon(RightIcon::Close); content->setClickedCallback([=] { @@ -101,8 +103,7 @@ rpl::producer*> TopBarSuggestionValue( }); }); content->setHideCallback([=] { - session->appConfig().dismissSuggestion( - kSugSetBirthday.utf8()); + config->dismissSuggestion(kSugSetBirthday.utf8()); repeat(repeat); }); content->setContent( @@ -113,31 +114,38 @@ rpl::producer*> TopBarSuggestionValue( tr::now, TextWithEntities::Simple)); wrap->toggle(true, anim::type::normal); - } else if (session->premiumPossible() + } else if (const auto isAnnual = config->suggestionCurrent( + kSugPremiumUpgrade.utf8()); + session->premiumPossible() && !session->premium() - && session->appConfig().suggestionCurrent( - kSugPremiumAnnual.utf8())) { + && (isAnnual + || config->suggestionCurrent(kSugPremiumAnnual.utf8()))) { content->setRightIcon(RightIcon::Arrow); const auto api = &session->api().premium(); const auto set = [=](QString discount) { constexpr auto kMinus = QChar(0x2212); + const auto &title = isAnnual + ? tr::lng_dialogs_suggestions_premium_annual_title + : tr::lng_dialogs_suggestions_premium_upgrade_title; + const auto &description = isAnnual + ? tr::lng_dialogs_suggestions_premium_annual_about + : tr::lng_dialogs_suggestions_premium_upgrade_about; content->setContent( - tr::lng_dialogs_suggestions_premium_annual_title( + title( tr::now, lt_text, { discount.replace(kMinus, QChar()) }, Ui::Text::Bold), - tr::lng_dialogs_suggestions_premium_annual_about( - tr::now, - TextWithEntities::Simple)); + description(tr::now, TextWithEntities::Simple)); content->setClickedCallback([=] { const auto controller = FindSessionController(parent); if (!controller) { return; } Settings::ShowPremium(controller, "dialogs_hint"); - session->appConfig().dismissSuggestion( - kSugPremiumAnnual.utf8()); + config->dismissSuggestion(isAnnual + ? kSugPremiumAnnual.utf8() + : kSugPremiumUpgrade.utf8()); repeat(repeat); }); wrap->toggle(true, anim::type::normal); From 2c3cb3f5cef4980e6b1add78ab9f2ddfc89295b5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 5 Apr 2025 21:54:04 +0300 Subject: [PATCH 039/190] Added suggestion for restore premium to top bar in dialogs. --- Telegram/Resources/langs/lang.strings | 2 + .../dialogs/dialogs_top_bar_suggestion.cpp | 58 ++++++++++++------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 52c8ef178f..403cc2fd0b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3869,6 +3869,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; "lng_dialogs_suggestions_premium_upgrade_title" = "Telegram Premium with a {text} discount"; "lng_dialogs_suggestions_premium_upgrade_about" = "Upgrade to the annual payment plan for Telegram Premium now to get the discount."; +"lng_dialogs_suggestions_premium_restore_title" = "Get Premium back with up to {text} off"; +"lng_dialogs_suggestions_premium_restore_about" = "Your Telegram Premium has recently expired. Tap here to extend it."; "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 4d1c62f601..7a8f521425 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -37,6 +37,7 @@ namespace { constexpr auto kSugSetBirthday = "BIRTHDAY_SETUP"_cs; constexpr auto kSugPremiumAnnual = "PREMIUM_ANNUAL"_cs; constexpr auto kSugPremiumUpgrade = "PREMIUM_UPGRADE"_cs; +constexpr auto kSugPremiumRestore = "PREMIUM_RESTORE"_cs; } // namespace @@ -78,6 +79,7 @@ rpl::producer*> TopBarSuggestionValue( const auto wrap = state->wrap; using RightIcon = TopBarSuggestionContent::RightIcon; const auto config = &session->appConfig(); + auto hide = false; if (config->suggestionCurrent(kSugSetBirthday.utf8()) && !Data::IsBirthdayToday(session->user()->birthday())) { content->setRightIcon(RightIcon::Close); @@ -114,21 +116,25 @@ rpl::producer*> TopBarSuggestionValue( tr::now, TextWithEntities::Simple)); wrap->toggle(true, anim::type::normal); - } else if (const auto isAnnual = config->suggestionCurrent( - kSugPremiumUpgrade.utf8()); - session->premiumPossible() - && !session->premium() - && (isAnnual - || config->suggestionCurrent(kSugPremiumAnnual.utf8()))) { - content->setRightIcon(RightIcon::Arrow); - const auto api = &session->api().premium(); + } else if (session->premiumPossible() && !session->premium()) { + const auto isPremiumAnnual = config->suggestionCurrent( + kSugPremiumAnnual.utf8()); + const auto isPremiumRestore = !isPremiumAnnual + && config->suggestionCurrent(kSugPremiumRestore.utf8()); + const auto isPremiumUpgrade = !isPremiumAnnual + && !isPremiumRestore + && config->suggestionCurrent(kSugPremiumUpgrade.utf8()); const auto set = [=](QString discount) { constexpr auto kMinus = QChar(0x2212); - const auto &title = isAnnual + const auto &title = isPremiumAnnual ? tr::lng_dialogs_suggestions_premium_annual_title + : isPremiumRestore + ? tr::lng_dialogs_suggestions_premium_restore_title : tr::lng_dialogs_suggestions_premium_upgrade_title; - const auto &description = isAnnual + const auto &description = isPremiumAnnual ? tr::lng_dialogs_suggestions_premium_annual_about + : isPremiumRestore + ? tr::lng_dialogs_suggestions_premium_restore_about : tr::lng_dialogs_suggestions_premium_upgrade_about; content->setContent( title( @@ -143,25 +149,35 @@ rpl::producer*> TopBarSuggestionValue( return; } Settings::ShowPremium(controller, "dialogs_hint"); - config->dismissSuggestion(isAnnual + config->dismissSuggestion(isPremiumAnnual ? kSugPremiumAnnual.utf8() + : isPremiumRestore + ? kSugPremiumRestore.utf8() : kSugPremiumUpgrade.utf8()); repeat(repeat); }); wrap->toggle(true, anim::type::normal); }; - api->statusTextValue( - ) | rpl::start_with_next([=] { - for (const auto &option : api->subscriptionOptions()) { - if (option.months == 12) { - set(option.discount); - state->premiumLifetime.destroy(); - return; + if (isPremiumAnnual || isPremiumRestore || isPremiumUpgrade) { + content->setRightIcon(RightIcon::Arrow); + const auto api = &session->api().premium(); + api->statusTextValue() | rpl::start_with_next([=] { + for (const auto &o : api->subscriptionOptions()) { + if (o.months == 12) { + set(o.discount); + state->premiumLifetime.destroy(); + return; + } } - } - }, state->premiumLifetime); - api->reload(); + }, state->premiumLifetime); + api->reload(); + } else { + hide = true; + } } else { + hide = true; + } + if (hide) { wrap->toggle(false, anim::type::normal); base::call_delayed(st::slideWrapDuration * 2, wrap, [=] { state->content = nullptr; From 10d472728c52640bfae0badd4bd8577eb5a96666 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 6 Apr 2025 00:47:57 +0300 Subject: [PATCH 040/190] Added userpic suggestion to top bar in dialogs. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/boxes/boxes.style | 7 ++ .../dialogs/dialogs_top_bar_suggestion.cpp | 96 ++++++++++++++++--- .../ui/dialogs_top_bar_suggestion_content.cpp | 8 +- .../ui/dialogs_top_bar_suggestion_content.h | 3 + 5 files changed, 104 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 403cc2fd0b..0871186847 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3871,6 +3871,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_suggestions_premium_upgrade_about" = "Upgrade to the annual payment plan for Telegram Premium now to get the discount."; "lng_dialogs_suggestions_premium_restore_title" = "Get Premium back with up to {text} off"; "lng_dialogs_suggestions_premium_restore_about" = "Your Telegram Premium has recently expired. Tap here to extend it."; +"lng_dialogs_suggestions_userpics_title" = "Add your photo! 📸"; +"lng_dialogs_suggestions_userpics_about" = "Help your friends spot you easily."; "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 52fb1b43b9..f77b417432 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -1129,3 +1129,10 @@ foldersMenu: PopupMenu(popupMenuWithIcons) { itemPadding: margins(54px, 8px, 44px, 8px); } } + +fakeUserpicButton: UserpicButton(defaultUserpicButton) { + size: size(1px, 1px); + photoSize: 1px; + changeIcon: icon {{ "settings/photo", transparent }}; + uploadBg: transparent; +} diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 7a8f521425..4f899240c8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -7,12 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_top_bar_suggestion.h" +#include "api/api_peer_photo.h" #include "api/api_premium.h" #include "apiwrap.h" #include "base/call_delayed.h" #include "core/application.h" #include "core/click_handler_types.h" #include "data/data_birthday.h" +#include "data/data_changes.h" #include "data/data_user.h" #include "dialogs/ui/dialogs_top_bar_suggestion_content.h" #include "info/profile/info_profile_values.h" @@ -20,10 +22,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_app_config.h" #include "main/main_session.h" #include "settings/settings_premium.h" +#include "ui/controls/userpic_button.h" #include "ui/text/text_utilities.h" +#include "ui/ui_utility.h" #include "ui/wrap/slide_wrap.h" #include "window/window_controller.h" #include "window/window_session_controller.h" +#include "styles/style_boxes.h" +#include "styles/style_dialogs.h" namespace Dialogs { namespace { @@ -38,6 +44,7 @@ constexpr auto kSugSetBirthday = "BIRTHDAY_SETUP"_cs; constexpr auto kSugPremiumAnnual = "PREMIUM_ANNUAL"_cs; constexpr auto kSugPremiumUpgrade = "PREMIUM_UPGRADE"_cs; constexpr auto kSugPremiumRestore = "PREMIUM_RESTORE"_cs; +constexpr auto kSugSetUserpic = "USERPIC_SETUP"_cs; } // namespace @@ -52,6 +59,7 @@ rpl::producer*> TopBarSuggestionValue( Ui::SlideWrap *wrap = nullptr; rpl::lifetime birthdayLifetime; rpl::lifetime premiumLifetime; + rpl::lifetime userpicLifetime; }; const auto state = lifetime.make_state(); const auto ensureWrap = [=] { @@ -79,7 +87,6 @@ rpl::producer*> TopBarSuggestionValue( const auto wrap = state->wrap; using RightIcon = TopBarSuggestionContent::RightIcon; const auto config = &session->appConfig(); - auto hide = false; if (config->suggestionCurrent(kSugSetBirthday.utf8()) && !Data::IsBirthdayToday(session->user()->birthday())) { content->setRightIcon(RightIcon::Close); @@ -116,6 +123,7 @@ rpl::producer*> TopBarSuggestionValue( tr::now, TextWithEntities::Simple)); wrap->toggle(true, anim::type::normal); + return; } else if (session->premiumPossible() && !session->premium()) { const auto isPremiumAnnual = config->suggestionCurrent( kSugPremiumAnnual.utf8()); @@ -171,20 +179,86 @@ rpl::producer*> TopBarSuggestionValue( } }, state->premiumLifetime); api->reload(); - } else { - hide = true; + return; } - } else { - hide = true; } - if (hide) { - wrap->toggle(false, anim::type::normal); - base::call_delayed(st::slideWrapDuration * 2, wrap, [=] { - state->content = nullptr; - state->wrap = nullptr; - consumer.put_next(nullptr); + if (config->suggestionCurrent(kSugSetUserpic.utf8()) + && !session->user()->userpicPhotoId()) { + const auto controller = FindSessionController(parent); + if (!controller) { + return; + } + content->setRightIcon(RightIcon::Close); + const auto upload = Ui::CreateChild( + content, + &controller->window(), + Ui::UserpicButton::Role::ChoosePhoto, + st::uploadUserpicButton); + const auto leftPadding = st::defaultDialogRow.padding.left(); + content->sizeValue() | rpl::filter_size( + ) | rpl::start_with_next([=](const QSize &s) { + upload->raise(); + upload->show(); + upload->moveToLeft( + leftPadding, + (s.height() - upload->height()) / 2); + }, content->lifetime()); + content->setLeftPadding(upload->width() + leftPadding); + upload->chosenImages() | rpl::start_with_next([=]( + Ui::UserpicButton::ChosenImage &&chosen) { + if (chosen.type == Ui::UserpicButton::ChosenType::Set) { + session->api().peerPhoto().upload( + session->user(), + { + std::move(chosen.image), + chosen.markup.documentId, + chosen.markup.colors, + }); + } + }, upload->lifetime()); + + state->userpicLifetime = session->changes().peerUpdates( + session->user(), + Data::PeerUpdate::Flag::Photo + ) | rpl::start_with_next([=] { + if (session->user()->userpicPhotoId()) { + repeat(repeat); + } }); + + content->setHideCallback([=] { + config->dismissSuggestion(kSugSetUserpic.utf8()); + repeat(repeat); + }); + + content->setClickedCallback([=] { + const auto syntetic = [=](QEvent::Type type) { + Ui::SendSynteticMouseEvent( + upload, + type, + Qt::LeftButton, + upload->mapToGlobal(QPoint(0, 0))); + }; + syntetic(QEvent::MouseMove); + syntetic(QEvent::MouseButtonPress); + syntetic(QEvent::MouseButtonRelease); + }); + content->setContent( + tr::lng_dialogs_suggestions_userpics_title( + tr::now, + Ui::Text::Bold), + tr::lng_dialogs_suggestions_userpics_about( + tr::now, + TextWithEntities::Simple)); + wrap->toggle(true, anim::type::normal); + return; } + wrap->toggle(false, anim::type::normal); + base::call_delayed(st::slideWrapDuration * 2, wrap, [=] { + state->content = nullptr; + state->wrap = nullptr; + consumer.put_next(nullptr); + }); }; session->appConfig().value() | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp index c085d9c8e8..badde385e0 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp @@ -67,7 +67,8 @@ void TopBarSuggestionContent::draw(QPainter &p) { const auto r = Ui::RpWidget::rect(); p.fillRect(r, st::historyPinnedBg); Ui::RippleButton::paintRipple(p, 0, 0); - const auto leftPadding = st::defaultDialogRow.padding.left(); + const auto leftPadding = st::defaultDialogRow.padding.left() + + _leftPadding; const auto rightPadding = st::msgReplyBarSkip; const auto topPadding = st::msgReplyPadding.top(); const auto availableWidthNoPhoto = r.width() @@ -174,4 +175,9 @@ void TopBarSuggestionContent::setHideCallback(Fn hideCallback) { _rightHide->setClickedCallback(std::move(hideCallback)); } +void TopBarSuggestionContent::setLeftPadding(int value) { + _leftPadding = value; + update(); +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h index fc9868cf7c..82b2f3b9f6 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h @@ -34,6 +34,7 @@ public: void setHideCallback(Fn); void setRightIcon(RightIcon); + void setLeftPadding(int); protected: void paintEvent(QPaintEvent *) override; @@ -54,6 +55,8 @@ private: base::unique_qptr _rightArrow; Fn _hideCallback; + int _leftPadding = 0; + RightIcon _rightIcon = RightIcon::None; std::shared_ptr _rightPhoto; From f83568c6c98002740e9e14fddcb8900cace356c7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 6 Apr 2025 23:41:44 +0300 Subject: [PATCH 041/190] Added birthday reminder of single contact to top bar in dialogs. --- Telegram/Resources/langs/lang.strings | 2 + .../dialogs/dialogs_top_bar_suggestion.cpp | 84 ++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0871186847..e1fdf3dcbf 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3865,6 +3865,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_suggestions_birthday_title" = "Add your birthday! 🎂"; "lng_dialogs_suggestions_birthday_about" = "Let your contacts know when you’re celebrating."; +"lng_dialogs_suggestions_birthday_contact_title" = "It’s {text}'s **birthday** today! 🎂"; +"lng_dialogs_suggestions_birthday_contact_about" = "Send them a Gift."; "lng_dialogs_suggestions_premium_annual_title" = "Telegram Premium with a {text} discount"; "lng_dialogs_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; "lng_dialogs_suggestions_premium_upgrade_title" = "Telegram Premium with a {text} discount"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 4f899240c8..1b489ffd07 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -11,10 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_premium.h" #include "apiwrap.h" #include "base/call_delayed.h" +#include "boxes/star_gift_box.h" // ShowStarGiftBox. #include "core/application.h" #include "core/click_handler_types.h" #include "data/data_birthday.h" #include "data/data_changes.h" +#include "data/data_session.h" #include "data/data_user.h" #include "dialogs/ui/dialogs_top_bar_suggestion_content.h" #include "info/profile/info_profile_values.h" @@ -41,11 +43,37 @@ namespace { } constexpr auto kSugSetBirthday = "BIRTHDAY_SETUP"_cs; +constexpr auto kSugBirthdayContacts = "BIRTHDAY_CONTACTS_TODAY"_cs; constexpr auto kSugPremiumAnnual = "PREMIUM_ANNUAL"_cs; constexpr auto kSugPremiumUpgrade = "PREMIUM_UPGRADE"_cs; constexpr auto kSugPremiumRestore = "PREMIUM_RESTORE"_cs; constexpr auto kSugSetUserpic = "USERPIC_SETUP"_cs; +void RequestBirthdays( + not_null peer, + Fn>)> done) { + peer->session().api().request( + MTPcontacts_GetBirthdays() + ).done([=](const MTPcontacts_ContactBirthdays &result) { + auto users = std::vector>(); + peer->owner().processUsers(result.data().vusers()); + for (const auto &tlContact : result.data().vcontacts().v) { + const auto peerId = tlContact.data().vcontact_id().v; + if (const auto user = peer->owner().user(peerId)) { + const auto &data = tlContact.data().vbirthday().data(); + user->setBirthday(Data::Birthday( + data.vday().v, + data.vmonth().v, + data.vyear().value_or_empty())); + users.push_back(user); + } + } + done(std::move(users)); + }).fail([=](const MTP::Error &error) { + done({}); + }).send(); +} + } // namespace rpl::producer*> TopBarSuggestionValue( @@ -87,7 +115,61 @@ rpl::producer*> TopBarSuggestionValue( const auto wrap = state->wrap; using RightIcon = TopBarSuggestionContent::RightIcon; const auto config = &session->appConfig(); - if (config->suggestionCurrent(kSugSetBirthday.utf8()) + if (config->suggestionCurrent(kSugBirthdayContacts.utf8())) { + using Users = std::vector>; + RequestBirthdays(session->user(), crl::guard(content, [=]( + Users users) { + const auto dismiss = [=] { + config->dismissSuggestion( + kSugBirthdayContacts.utf8()); + repeat(repeat); + }; + if (!session->premiumCanBuy() || users.empty()) { + dismiss(); + return; + } + const auto controller = FindSessionController(parent); + if (!controller) { + dismiss(); + return; + } + if (users.size() != 1) { + return; + } + content->setRightIcon(RightIcon::Close); + content->setClickedCallback([=] { + Ui::ShowStarGiftBox(controller, users.front()); + }); + content->setHideCallback(dismiss); + content->setContent( + tr::lng_dialogs_suggestions_birthday_contact_title( + tr::now, + lt_text, + { users.front()->name() }, + Ui::Text::RichLangValue), + tr::lng_dialogs_suggestions_birthday_contact_about( + tr::now, + TextWithEntities::Simple)); + const auto upload = Ui::CreateChild( + content, + users.front(), + st::uploadUserpicButton); + upload->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto leftPadding = st::defaultDialogRow.padding.left(); + content->sizeValue() | rpl::filter_size( + ) | rpl::start_with_next([=](const QSize &s) { + upload->raise(); + upload->show(); + upload->moveToLeft( + leftPadding, + (s.height() - upload->height()) / 2); + }, content->lifetime()); + content->setLeftPadding(upload->width() + leftPadding); + + wrap->toggle(true, anim::type::normal); + })); + return; + } else if (config->suggestionCurrent(kSugSetBirthday.utf8()) && !Data::IsBirthdayToday(session->user()->birthday())) { content->setRightIcon(RightIcon::Close); content->setClickedCallback([=] { From 8c0de22c149ce37c5fef25ce38d06dc7f2299466 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 7 Apr 2025 00:40:34 +0300 Subject: [PATCH 042/190] Added birthday reminder of multiple contacts to top bar in dialogs. --- Telegram/Resources/langs/lang.strings | 3 + .../dialogs/dialogs_top_bar_suggestion.cpp | 128 ++++++++++++++---- 2 files changed, 106 insertions(+), 25 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e1fdf3dcbf..0315896bff 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3867,6 +3867,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_suggestions_birthday_about" = "Let your contacts know when you’re celebrating."; "lng_dialogs_suggestions_birthday_contact_title" = "It’s {text}'s **birthday** today! 🎂"; "lng_dialogs_suggestions_birthday_contact_about" = "Send them a Gift."; +"lng_dialogs_suggestions_birthday_contacts_title#one" = "{count} contact have **birthdays** today! 🎂"; +"lng_dialogs_suggestions_birthday_contacts_title#other" = "{count} contacts have **birthdays** today! 🎂"; +"lng_dialogs_suggestions_birthday_contacts_about" = "Send them a Gift."; "lng_dialogs_suggestions_premium_annual_title" = "Telegram Premium with a {text} discount"; "lng_dialogs_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; "lng_dialogs_suggestions_premium_upgrade_title" = "Telegram Premium with a {text} discount"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 1b489ffd07..1697dd9eb6 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_user.h" #include "dialogs/ui/dialogs_top_bar_suggestion_content.h" +#include "history/view/history_view_group_call_bar.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "main/main_app_config.h" @@ -31,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" namespace Dialogs { @@ -88,7 +91,9 @@ rpl::producer*> TopBarSuggestionValue( rpl::lifetime birthdayLifetime; rpl::lifetime premiumLifetime; rpl::lifetime userpicLifetime; + rpl::lifetime giftsLifetime; }; + const auto state = lifetime.make_state(); const auto ensureWrap = [=] { if (!state->content) { @@ -129,42 +134,115 @@ rpl::producer*> TopBarSuggestionValue( return; } const auto controller = FindSessionController(parent); - if (!controller) { + if (!controller || users.empty()) { dismiss(); return; } - if (users.size() != 1) { - return; - } + const auto isSingle = users.size() == 1; content->setRightIcon(RightIcon::Close); content->setClickedCallback([=] { - Ui::ShowStarGiftBox(controller, users.front()); + if (isSingle) { + Ui::ShowStarGiftBox(controller, users.front()); + } else { + Ui::ChooseStarGiftRecipient(controller); + } }); content->setHideCallback(dismiss); - content->setContent( - tr::lng_dialogs_suggestions_birthday_contact_title( + auto title = isSingle + ? tr::lng_dialogs_suggestions_birthday_contact_title( tr::now, lt_text, { users.front()->name() }, - Ui::Text::RichLangValue), - tr::lng_dialogs_suggestions_birthday_contact_about( + Ui::Text::RichLangValue) + : tr::lng_dialogs_suggestions_birthday_contacts_title( tr::now, - TextWithEntities::Simple)); - const auto upload = Ui::CreateChild( - content, - users.front(), - st::uploadUserpicButton); - upload->setAttribute(Qt::WA_TransparentForMouseEvents); - const auto leftPadding = st::defaultDialogRow.padding.left(); - content->sizeValue() | rpl::filter_size( - ) | rpl::start_with_next([=](const QSize &s) { - upload->raise(); - upload->show(); - upload->moveToLeft( - leftPadding, - (s.height() - upload->height()) / 2); - }, content->lifetime()); - content->setLeftPadding(upload->width() + leftPadding); + lt_count, + users.size(), + Ui::Text::RichLangValue); + auto text = isSingle + ? tr::lng_dialogs_suggestions_birthday_contact_about( + tr::now, + TextWithEntities::Simple) + : tr::lng_dialogs_suggestions_birthday_contacts_about( + tr::now, + TextWithEntities::Simple); + content->setContent(std::move(title), std::move(text)); + const auto leftPadding + = st::defaultDialogRow.padding.left(); + state->giftsLifetime.destroy(); + if (!isSingle) { + struct UserViews { + std::vector inRow; + QImage userpics; + base::unique_qptr widget; + }; + const auto s + = state->giftsLifetime.template make_state< + UserViews>(); + s->widget = base::make_unique_q( + content); + const auto widget = s->widget.get(); + content->sizeValue() | rpl::filter_size( + ) | rpl::start_with_next([=](const QSize &size) { + widget->resize(size); + widget->show(); + widget->raise(); + }, widget->lifetime()); + for (const auto &user : users) { + s->inRow.push_back({ .peer = user }); + } + widget->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(widget); + const auto regenerate = [&] { + if (s->userpics.isNull()) { + return true; + } + for (auto &entry : s->inRow) { + if (entry.uniqueKey + != entry.peer->userpicUniqueKey( + entry.view)) { + return true; + } + } + return false; + }(); + if (regenerate) { + const auto &st = st::historyCommentsUserpics; + HistoryView::GenerateUserpicsInRow( + s->userpics, + s->inRow, + st, + 3); + content->setLeftPadding(leftPadding + + (users.size() * st.size - st.shift)); + } + p.drawImage( + leftPadding, + (widget->height() + - (s->userpics.height() + / style::DevicePixelRatio())) / 2, + s->userpics); + }, widget->lifetime()); + } else { + using Ptr = base::unique_qptr; + const auto ptr + = state->giftsLifetime.template make_state( + base::make_unique_q( + content, + users.front(), + st::uploadUserpicButton)); + const auto fake = ptr->get(); + fake->setAttribute(Qt::WA_TransparentForMouseEvents); + content->sizeValue() | rpl::filter_size( + ) | rpl::start_with_next([=](const QSize &s) { + fake->raise(); + fake->show(); + fake->moveToLeft( + leftPadding, + (s.height() - fake->height()) / 2); + }, content->lifetime()); + content->setLeftPadding(fake->width() + leftPadding); + } wrap->toggle(true, anim::type::normal); })); From 0638c86211d884295a0cdde29716d302ad86346b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 7 Apr 2025 19:43:03 +0300 Subject: [PATCH 043/190] Removed ability to set TTL to chat with bot verification. --- Telegram/SourceFiles/menu/menu_ttl_validator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/menu/menu_ttl_validator.cpp b/Telegram/SourceFiles/menu/menu_ttl_validator.cpp index 4c1031ad63..ba5ad14ee9 100644 --- a/Telegram/SourceFiles/menu/menu_ttl_validator.cpp +++ b/Telegram/SourceFiles/menu/menu_ttl_validator.cpp @@ -115,6 +115,7 @@ bool TTLValidator::can() const { && !_peer->isNotificationsUser() && !_peer->asUser()->isInaccessible() && !_peer->asUser()->starsPerMessage() + && !_peer->asUser()->isVerifyCodes() && (!_peer->asUser()->requiresPremiumToWrite() || _peer->session().premium())) || (_peer->isChat() From df69a70dd7feb388bebf05aafc35a673a836d1aa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 7 Apr 2025 19:44:04 +0300 Subject: [PATCH 044/190] Removed ability to set wallpaper to chat with bot verification. --- Telegram/SourceFiles/window/window_peer_menu.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 135985fb10..a0dbbfc583 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1186,6 +1186,9 @@ void Filler::addCreatePoll() { } void Filler::addThemeEdit() { + if (_peer->isVerifyCodes()) { + return; + } const auto user = _peer->asUser(); if (!user || user->isInaccessible()) { return; From 16a77e2975e05c6a69e5093b8445fe9977035ddf Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 7 Apr 2025 20:05:06 +0300 Subject: [PATCH 045/190] Removed redundant buttons from profile of bot verification. --- Telegram/SourceFiles/info/profile/info_profile_actions.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index a9f12e7883..26ca73fe3d 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -2101,7 +2101,9 @@ Ui::MultiSlideTracker DetailsFiller::fillUserButtons( tracker); }; - addSendMessageButton(); + if (!user->isVerifyCodes()) { + addSendMessageButton(); + } addReportReaction(tracker); return tracker; From dc1dc8dffa8e6fdbfe5a3e4e15c1ae956cd393a3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 8 Apr 2025 13:41:56 +0300 Subject: [PATCH 046/190] Added today birthdays to data session. --- Telegram/SourceFiles/data/data_session.cpp | 51 ++++++++++++++++++++++ Telegram/SourceFiles/data/data_session.h | 9 ++++ 2 files changed, 60 insertions(+) diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 1114873bb1..5b74b76561 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4909,4 +4909,55 @@ void Session::clearLocalStorage() { _bigFileCache->clear(); } +rpl::producer> Session::contactBirthdays(bool force) { + if ((_contactBirthdaysLastDayRequest != -1) + && (_contactBirthdaysLastDayRequest == QDate::currentDate().day()) + && !force) { + return rpl::single(_contactBirthdays); + } + if (_contactBirthdaysRequestId) { + _session->api().request(_contactBirthdaysRequestId).cancel(); + } + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + _contactBirthdaysRequestId = _session->api().request( + MTPcontacts_GetBirthdays() + ).done([=](const MTPcontacts_ContactBirthdays &result) { + _contactBirthdaysRequestId = 0; + _contactBirthdaysLastDayRequest = QDate::currentDate().day(); + auto users = std::vector(); + Session::processUsers(result.data().vusers()); + for (const auto &tlContact : result.data().vcontacts().v) { + const auto peerId = tlContact.data().vcontact_id().v; + if (const auto user = Session::user(peerId)) { + const auto &data = tlContact.data().vbirthday().data(); + user->setBirthday(Data::Birthday( + data.vday().v, + data.vmonth().v, + data.vyear().value_or_empty())); + users.push_back(peerToUser(user->id)); + } + } + _contactBirthdays = std::move(users); + consumer.put_next_copy(_contactBirthdays); + }).fail([=](const MTP::Error &error) { + _contactBirthdaysRequestId = 0; + _contactBirthdaysLastDayRequest = QDate::currentDate().day(); + _contactBirthdays = {}; + consumer.put_next({}); + }).send(); + + return lifetime; + }; +} + +std::optional> Session::knownContactBirthdays() const { + if ((_contactBirthdaysLastDayRequest == -1) + || (_contactBirthdaysLastDayRequest != QDate::currentDate().day())) { + return std::nullopt; + } + return _contactBirthdays; +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 14f272c6ce..108f42482b 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -816,6 +816,11 @@ public: void sentFromScheduled(SentFromScheduled value); [[nodiscard]] rpl::producer sentFromScheduled() const; + [[nodiscard]] rpl::producer> contactBirthdays( + bool force = false); + [[nodiscard]] std::optional> knownContactBirthdays( + ) const; + void clearLocalStorage(); private: @@ -1130,6 +1135,10 @@ private: not_null, mtpRequestId> _viewAsMessagesRequests; + mtpRequestId _contactBirthdaysRequestId = 0; + int _contactBirthdaysLastDayRequest = -1; + std::vector _contactBirthdays; + Groups _groups; const std::unique_ptr _chatsFilters; const std::unique_ptr _cloudThemes; From 423782fd2b1c4f38d104006c5863437d2e525985 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 8 Apr 2025 00:34:53 +0300 Subject: [PATCH 047/190] Improved birthday reminder of multiple contacts in top bar from dialogs. --- Telegram/Resources/langs/lang.strings | 1 + .../dialogs/dialogs_top_bar_suggestion.cpp | 69 +++++++------------ Telegram/SourceFiles/main/main_app_config.cpp | 23 +++++++ 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0315896bff..0bdd57a30d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3870,6 +3870,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_suggestions_birthday_contacts_title#one" = "{count} contact have **birthdays** today! 🎂"; "lng_dialogs_suggestions_birthday_contacts_title#other" = "{count} contacts have **birthdays** today! 🎂"; "lng_dialogs_suggestions_birthday_contacts_about" = "Send them a Gift."; +"lng_dialogs_suggestions_birthday_contact_dismiss" = "You can send a Gift later in Settings"; "lng_dialogs_suggestions_premium_annual_title" = "Telegram Premium with a {text} discount"; "lng_dialogs_suggestions_premium_annual_about" = "Sign up for the annual payment plan for Telegram Premium now to get the discount."; "lng_dialogs_suggestions_premium_upgrade_title" = "Telegram Premium with a {text} discount"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 1697dd9eb6..a62402fd8e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -52,31 +52,6 @@ constexpr auto kSugPremiumUpgrade = "PREMIUM_UPGRADE"_cs; constexpr auto kSugPremiumRestore = "PREMIUM_RESTORE"_cs; constexpr auto kSugSetUserpic = "USERPIC_SETUP"_cs; -void RequestBirthdays( - not_null peer, - Fn>)> done) { - peer->session().api().request( - MTPcontacts_GetBirthdays() - ).done([=](const MTPcontacts_ContactBirthdays &result) { - auto users = std::vector>(); - peer->owner().processUsers(result.data().vusers()); - for (const auto &tlContact : result.data().vcontacts().v) { - const auto peerId = tlContact.data().vcontact_id().v; - if (const auto user = peer->owner().user(peerId)) { - const auto &data = tlContact.data().vbirthday().data(); - user->setBirthday(Data::Birthday( - data.vday().v, - data.vmonth().v, - data.vyear().value_or_empty())); - users.push_back(user); - } - } - done(std::move(users)); - }).fail([=](const MTP::Error &error) { - done({}); - }).send(); -} - } // namespace rpl::producer*> TopBarSuggestionValue( @@ -120,39 +95,43 @@ rpl::producer*> TopBarSuggestionValue( const auto wrap = state->wrap; using RightIcon = TopBarSuggestionContent::RightIcon; const auto config = &session->appConfig(); - if (config->suggestionCurrent(kSugBirthdayContacts.utf8())) { - using Users = std::vector>; - RequestBirthdays(session->user(), crl::guard(content, [=]( - Users users) { - const auto dismiss = [=] { - config->dismissSuggestion( - kSugBirthdayContacts.utf8()); + if (session->premiumCanBuy() + && config->suggestionCurrent(kSugBirthdayContacts.utf8())) { + session->data().contactBirthdays( + ) | rpl::start_with_next(crl::guard(content, [=]( + std::vector users) { + if (users.empty()) { repeat(repeat); - }; - if (!session->premiumCanBuy() || users.empty()) { - dismiss(); return; } const auto controller = FindSessionController(parent); - if (!controller || users.empty()) { - dismiss(); + if (!controller) { + repeat(repeat); return; } const auto isSingle = users.size() == 1; + const auto first = session->data().user(users.front()); content->setRightIcon(RightIcon::Close); content->setClickedCallback([=] { if (isSingle) { - Ui::ShowStarGiftBox(controller, users.front()); + Ui::ShowStarGiftBox(controller, first); } else { Ui::ChooseStarGiftRecipient(controller); } }); - content->setHideCallback(dismiss); + content->setHideCallback([=] { + config->dismissSuggestion( + kSugBirthdayContacts.utf8()); + controller->showToast( + tr::lng_dialogs_suggestions_birthday_contact_dismiss( + tr::now)); + repeat(repeat); + }); auto title = isSingle ? tr::lng_dialogs_suggestions_birthday_contact_title( tr::now, lt_text, - { users.front()->name() }, + { first->name() }, Ui::Text::RichLangValue) : tr::lng_dialogs_suggestions_birthday_contacts_title( tr::now, @@ -188,8 +167,10 @@ rpl::producer*> TopBarSuggestionValue( widget->show(); widget->raise(); }, widget->lifetime()); - for (const auto &user : users) { - s->inRow.push_back({ .peer = user }); + for (const auto &id : users) { + if (const auto user = session->data().user(id)) { + s->inRow.push_back({ .peer = user }); + } } widget->paintRequest() | rpl::start_with_next([=] { auto p = QPainter(widget); @@ -229,7 +210,7 @@ rpl::producer*> TopBarSuggestionValue( = state->giftsLifetime.template make_state( base::make_unique_q( content, - users.front(), + first, st::uploadUserpicButton)); const auto fake = ptr->get(); fake->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -245,7 +226,7 @@ rpl::producer*> TopBarSuggestionValue( } wrap->toggle(true, anim::type::normal); - })); + }), state->giftsLifetime); return; } else if (config->suggestionCurrent(kSugSetBirthday.utf8()) && !Data::IsBirthdayToday(session->user()->birthday())) { diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 594bad12da..0e74bc4261 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/call_delayed.h" #include "main/main_account.h" +#include "main/main_session.h" +#include "data/data_session.h" #include "ui/chat/chat_style.h" namespace Main { @@ -136,6 +138,15 @@ void AppConfig::refresh(bool force) { } updateIgnoredRestrictionReasons(std::move(was)); + { + const auto dismissedSuggestions = get>( + u"dismissed_suggestions"_q, + std::vector()); + for (const auto &suggestion : dismissedSuggestions) { + _dismissedSuggestions.emplace(suggestion); + } + } + DEBUG_LOG(("getAppConfig result handled.")); _refreshed.fire({}); }, [](const MTPDhelp_appConfigNotModified &) {}); @@ -289,6 +300,18 @@ std::vector AppConfig::getIntArray( } bool AppConfig::suggestionCurrent(const QString &key) const { + if (key == u"BIRTHDAY_CONTACTS_TODAY"_q) { + if (_dismissedSuggestions.contains(key)) { + return false; + } else { + const auto known + = _account->session().data().knownContactBirthdays(); + if (!known) { + return true; + } + return !known->empty(); + } + } return !_dismissedSuggestions.contains(key) && ranges::contains( get>( From be8a8365287242b5851e21cebc022840bc32eac0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 09:14:58 +0300 Subject: [PATCH 048/190] Fixed clearing of inactive quick dialog actions. --- Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 79054d0474..5ed9dd391c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -866,7 +866,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { if (raw->finishedAt && (ms - raw->finishedAt > st::defaultRippleAnimation.hideDuration)) { - _inactiveQuickActions.erase(it); + it = _inactiveQuickActions.erase(it); } else { if (raw->data.msgBareId == history->peer->id.value) { context.quickActionContext = raw; From 8d449f91c6fe410614b1633dc50885518da40480 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 09:17:23 +0300 Subject: [PATCH 049/190] Fixed phrases in list of active group calls from Calls box. --- Telegram/SourceFiles/calls/calls_box_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index a3d0d53694..96d064ec34 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -92,7 +92,7 @@ GroupCallRow::GroupCallRow(not_null peer) : PeerListRow(peer) , _st(st::callGroupCall) { if (const auto channel = peer->asChannel()) { - const auto status = (channel->isMegagroup() + const auto status = (!channel->isMegagroup() ? (channel->isPublic() ? tr::lng_create_public_channel_title : tr::lng_create_private_channel_title) From 2eab7044baa63bb9e3f65cdc846c8aad6ca2bf61 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 10:20:08 +0300 Subject: [PATCH 050/190] Added ability to emplace top bar suggestion later. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 71 +++++++++++-------- Telegram/SourceFiles/dialogs/dialogs_widget.h | 2 + 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index dd3934622a..6ee40e1a69 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -379,36 +379,6 @@ Widget::Widget( OverscrollType::Real); const auto innerList = _scroll->setOwnedWidget( object_ptr(this)); - if (_layout != Layout::Child) { - TopBarSuggestionValue( - innerList, - &session() - ) | rpl::start_with_next([=](Ui::SlideWrap *raw) { - if (raw) { - _topBarSuggestion = innerList->insert( - 0, - object_ptr>::fromRaw(raw)); - rpl::combine( - _topBarSuggestion->entity()->desiredHeightValue(), - _childListShown.value() - ) | rpl::start_with_next([=]( - int desiredHeight, - float64 shown) { - const auto newHeight = desiredHeight * (1. - shown); - _topBarSuggestion->entity()->setMaximumHeight(newHeight); - _topBarSuggestion->entity()->setMinimumWidth((shown > 0) - ? width() - : 0); - _topBarSuggestion->entity()->resize(width(), newHeight); - }, _topBarSuggestion->lifetime()); - } else { - if (_topBarSuggestion) { - delete _topBarSuggestion; - } - _topBarSuggestion = nullptr; - } - }, lifetime()); - } _inner = innerList->add(object_ptr( innerList, controller, @@ -725,6 +695,7 @@ Widget::Widget( } setupFrozenAccountBar(); + setupTopBarSuggestions(innerList); } void Widget::setupSwipeBack() { @@ -1047,6 +1018,46 @@ void Widget::setupFrozenAccountBar() { }, lifetime()); } +void Widget::setupTopBarSuggestions(not_null dialogs) { + if (_layout == Layout::Child) { + return; + } + using namespace rpl::mappers; + crl::on_main(&session(), [=, session = &session()] { + (session->data().chatsListLoaded(nullptr) + ? rpl::single(nullptr) + : session->data().chatsListLoadedEvents() + ) | rpl::filter(_1 == nullptr) | rpl::map([=] { + return TopBarSuggestionValue(dialogs, session); + }) | rpl::flatten_latest() | rpl::start_with_next([=]( + Ui::SlideWrap *raw) { + if (raw) { + _topBarSuggestion = dialogs->insert( + 0, + object_ptr>::fromRaw(raw)); + rpl::combine( + _topBarSuggestion->entity()->desiredHeightValue(), + _childListShown.value() + ) | rpl::start_with_next([=]( + int desiredHeight, + float64 shown) { + const auto newHeight = desiredHeight * (1. - shown); + _topBarSuggestion->entity()->setMaximumHeight(newHeight); + _topBarSuggestion->entity()->setMinimumWidth((shown > 0) + ? width() + : 0); + _topBarSuggestion->entity()->resize(width(), newHeight); + }, _topBarSuggestion->lifetime()); + } else { + if (_topBarSuggestion) { + delete _topBarSuggestion; + } + _topBarSuggestion = nullptr; + } + }, lifetime()); + }); +} + void Widget::updateFrozenAccountBar() { if (_layout == Layout::Child || _openedForum diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 2a6e8d4aca..fb502850fc 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -55,6 +55,7 @@ template class FadeWrapScaled; template class SlideWrap; +class VerticalLayout; } // namespace Ui namespace Window { @@ -210,6 +211,7 @@ private: void setupShortcuts(); void setupStories(); void setupSwipeBack(); + void setupTopBarSuggestions(not_null dialogs); void storiesExplicitCollapse(); void collectStoriesUserpicsViews(Data::StorySourcesList list); void storiesToggleExplicitExpand(bool expand); From b4a9e4521439d9c84bb9a8120d2090699fb05741 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 16:08:50 +0300 Subject: [PATCH 051/190] Improved display of top bar suggestion in non-main chats lists. --- .../dialogs/dialogs_top_bar_suggestion.cpp | 53 ++++++++++++++++--- .../dialogs/dialogs_top_bar_suggestion.h | 3 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 33 ++++++++---- Telegram/SourceFiles/dialogs/dialogs_widget.h | 2 + 4 files changed, 71 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index a62402fd8e..9b5f448e68 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -56,13 +56,22 @@ constexpr auto kSugSetUserpic = "USERPIC_SETUP"_cs; rpl::producer*> TopBarSuggestionValue( not_null parent, - not_null session) { - return [=](auto consumer) { + not_null session, + rpl::producer outerWrapToggleValue) { + return [=, outerWrapToggleValue = rpl::duplicate(outerWrapToggleValue)]( + auto consumer) { auto lifetime = rpl::lifetime(); + struct Toggle { + bool value = false; + anim::type type; + }; + struct State { TopBarSuggestionContent *content = nullptr; Ui::SlideWrap *wrap = nullptr; + rpl::variable desiredWrapToggle; + rpl::variable outerWrapToggle; rpl::lifetime birthdayLifetime; rpl::lifetime premiumLifetime; rpl::lifetime userpicLifetime; @@ -70,6 +79,7 @@ rpl::producer*> TopBarSuggestionValue( }; const auto state = lifetime.make_state(); + state->outerWrapToggle = rpl::duplicate(outerWrapToggleValue); const auto ensureWrap = [=] { if (!state->content) { state->content = Ui::CreateChild( @@ -85,7 +95,8 @@ rpl::producer*> TopBarSuggestionValue( state->wrap = Ui::CreateChild>( parent, object_ptr::fromRaw(state->content)); - state->wrap->toggle(false, anim::type::instant); + state->desiredWrapToggle.force_assign( + Toggle{ false, anim::type::instant }); } }; @@ -225,7 +236,8 @@ rpl::producer*> TopBarSuggestionValue( content->setLeftPadding(fake->width() + leftPadding); } - wrap->toggle(true, anim::type::normal); + state->desiredWrapToggle.force_assign( + Toggle{ true, anim::type::normal }); }), state->giftsLifetime); return; } else if (config->suggestionCurrent(kSugSetBirthday.utf8()) @@ -263,7 +275,8 @@ rpl::producer*> TopBarSuggestionValue( tr::lng_dialogs_suggestions_birthday_about( tr::now, TextWithEntities::Simple)); - wrap->toggle(true, anim::type::normal); + state->desiredWrapToggle.force_assign( + Toggle{ true, anim::type::normal }); return; } else if (session->premiumPossible() && !session->premium()) { const auto isPremiumAnnual = config->suggestionCurrent( @@ -305,7 +318,8 @@ rpl::producer*> TopBarSuggestionValue( : kSugPremiumUpgrade.utf8()); repeat(repeat); }); - wrap->toggle(true, anim::type::normal); + state->desiredWrapToggle.force_assign( + Toggle{ true, anim::type::normal }); }; if (isPremiumAnnual || isPremiumRestore || isPremiumUpgrade) { content->setRightIcon(RightIcon::Arrow); @@ -391,10 +405,12 @@ rpl::producer*> TopBarSuggestionValue( tr::lng_dialogs_suggestions_userpics_about( tr::now, TextWithEntities::Simple)); - wrap->toggle(true, anim::type::normal); + state->desiredWrapToggle.force_assign( + Toggle{ true, anim::type::normal }); return; } - wrap->toggle(false, anim::type::normal); + state->desiredWrapToggle.force_assign( + Toggle{ false, anim::type::normal }); base::call_delayed(st::slideWrapDuration * 2, wrap, [=] { state->content = nullptr; state->wrap = nullptr; @@ -402,6 +418,27 @@ rpl::producer*> TopBarSuggestionValue( }); }; + state->desiredWrapToggle.value() | rpl::combine_previous( + ) | rpl::filter([=] { + return state->wrap != nullptr; + }) | rpl::start_with_next([=](Toggle was, Toggle now) { + state->wrap->toggle( + state->outerWrapToggle.current() && now.value, + (was.value == now.value) + ? anim::type::instant + : now.type); + }, lifetime); + + state->outerWrapToggle.value() | rpl::combine_previous( + ) | rpl::filter([=] { + return state->wrap != nullptr; + }) | rpl::start_with_next([=](bool was, bool now) { + const auto toggle = state->desiredWrapToggle.current(); + state->wrap->toggle( + toggle.value && now, + (was == now) ? toggle.type : anim::type::instant); + }, lifetime); + session->appConfig().value() | rpl::start_with_next([=] { const auto was = state->wrap; processCurrentSuggestion(processCurrentSuggestion); diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h index 4ff01525c6..613299988b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.h @@ -24,7 +24,8 @@ namespace Dialogs { [[nodiscard]] auto TopBarSuggestionValue( not_null parent, - not_null) + not_null, + rpl::producer outerWrapToggleValue) -> rpl::producer*>; } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 6ee40e1a69..b76b82de89 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -386,10 +386,12 @@ Widget::Widget( _childListPeerId.value(), _childListShown.value(), makeChildListShown))); - _scroll->heightValue() | rpl::start_with_next([=](int height) { + rpl::combine( + _scroll->heightValue(), + _topBarSuggestionHeightChanged.events_starting_with(0) + ) | rpl::start_with_next([=](int height, int topBarHeight) { innerList->setMinimumHeight(height); - _inner->setMinimumHeight(height - - (_topBarSuggestion ? _topBarSuggestion->height() : 0)); + _inner->setMinimumHeight(height - topBarHeight); _inner->refresh(); }, innerList->lifetime()); _scroll->widthValue() | rpl::start_with_next([=](int width) { @@ -1028,13 +1030,28 @@ void Widget::setupTopBarSuggestions(not_null dialogs) { ? rpl::single(nullptr) : session->data().chatsListLoadedEvents() ) | rpl::filter(_1 == nullptr) | rpl::map([=] { - return TopBarSuggestionValue(dialogs, session); + auto on = rpl::combine( + controller()->activeChatsFilter(), + _openedFolderOrForumChanges.events_starting_with(false), + widthValue() | rpl::map( + _1 >= st::columnMinimalWidthLeft + ) | rpl::distinct_until_changed() + ) | rpl::map([=](FilterId id, bool folderOrForum, bool wide) { + return !folderOrForum + && wide + && (id == session->data().chatsFilters().defaultId()); + }); + return TopBarSuggestionValue(dialogs, session, std::move(on)); }) | rpl::flatten_latest() | rpl::start_with_next([=]( Ui::SlideWrap *raw) { if (raw) { _topBarSuggestion = dialogs->insert( 0, object_ptr>::fromRaw(raw)); + _topBarSuggestion->heightValue( + ) | rpl::start_to_stream( + _topBarSuggestionHeightChanged, + _topBarSuggestion->entity()->lifetime()); rpl::combine( _topBarSuggestion->entity()->desiredHeightValue(), _childListShown.value() @@ -1075,13 +1092,7 @@ void Widget::updateFrozenAccountBar() { void Widget::updateTopBarSuggestions() { if (_topBarSuggestion) { - if ((_layout == Layout::Child) - || _openedForum - || _openedFolder) { - _topBarSuggestion->toggle(false, anim::type::instant); - } else { - _topBarSuggestion->toggle(true, anim::type::instant); - } + _openedFolderOrForumChanges.fire(_openedForum || _openedFolder); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index fb502850fc..2ed8f98cb8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -330,6 +330,8 @@ private: base::unique_qptr _chatFilters; Ui::SlideWrap *_topBarSuggestion = nullptr; + rpl::event_stream _topBarSuggestionHeightChanged; + rpl::event_stream _openedFolderOrForumChanges; object_ptr _scroll; QPointer _inner; From 664ec62396f2ce27d0c5cf97b4829af24723ba97 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 17:19:52 +0300 Subject: [PATCH 052/190] Slightly improved style of text in top bar suggestion. --- .../dialogs/ui/dialogs_top_bar_suggestion_content.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp index badde385e0..6444f2647b 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp @@ -69,17 +69,18 @@ void TopBarSuggestionContent::draw(QPainter &p) { Ui::RippleButton::paintRipple(p, 0, 0); const auto leftPadding = st::defaultDialogRow.padding.left() + _leftPadding; - const auto rightPadding = st::msgReplyBarSkip; + const auto rightPadding = 0; const auto topPadding = st::msgReplyPadding.top(); const auto availableWidthNoPhoto = r.width() - - (_rightArrow ? _rightArrow->width() / 2 : 0) // Takes full height. + - (_rightArrow + ? (_rightArrow->width() / 4 * 3) // Takes full height. + : 0) - leftPadding - rightPadding; const auto availableWidth = availableWidthNoPhoto - (_rightHide ? _rightHide->width() : 0); const auto titleRight = leftPadding; - const auto hasSecondLineTitle = (titleRight - > (availableWidth - _contentTitle.maxWidth())); + const auto hasSecondLineTitle = availableWidth < _contentTitle.maxWidth(); p.setPen(st::windowActiveTextFg); p.setPen(st::windowFg); { From 7f2bba7c4a86f4c246f5bdcdac4358747dbeea41 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 2 Apr 2025 00:18:28 +0300 Subject: [PATCH 053/190] Fixed ttl phrase for 1 year. --- Telegram/SourceFiles/ui/text/format_values.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/ui/text/format_values.cpp b/Telegram/SourceFiles/ui/text/format_values.cpp index 564e5013f8..d25e571f18 100644 --- a/Telegram/SourceFiles/ui/text/format_values.cpp +++ b/Telegram/SourceFiles/ui/text/format_values.cpp @@ -414,7 +414,7 @@ QString FormatTTL(float64 ttl) { + ' ' + tr::lng_days(tr::now, lt_count, int(days % 7)); } - } else if (ttl < (86400 * 31) * 12) { + } else if (ttl <= (86400 * 31) * 11) { return tr::lng_months(tr::now, lt_count, int(ttl / (86400 * 31))); } else { return tr::lng_years({}, lt_count, std::round(ttl / (86400 * 365))); From 85b073316914d16b11909392639f6abb61c6d935 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 2 Apr 2025 01:04:16 +0300 Subject: [PATCH 054/190] Improved guard of right edge iterator in linear chart view for stats. --- Telegram/SourceFiles/statistics/view/linear_chart_view.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/statistics/view/linear_chart_view.cpp b/Telegram/SourceFiles/statistics/view/linear_chart_view.cpp index de0c882f28..a13067aa18 100644 --- a/Telegram/SourceFiles/statistics/view/linear_chart_view.cpp +++ b/Telegram/SourceFiles/statistics/view/linear_chart_view.cpp @@ -204,6 +204,9 @@ int LinearChartView::findXIndexByPosition( : ((*nearest) < xPercentageLimits.min) ? (nearest + 1) : nearest; + if (resultXPercentageIt == end(chartData.xPercentage)) { + return chartData.xPercentage.size() - 1; + } return std::distance(begin(chartData.xPercentage), resultXPercentageIt); } From d49da1fdd009aab5ab1d22f9fcf8ee368dce9cc7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 2 Apr 2025 10:26:03 +0300 Subject: [PATCH 055/190] Added missed label for current balance in section of bot credits earn. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0bdd57a30d..bcec388ee2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -6211,6 +6211,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_earn_chart_revenue" = "Revenue"; "lng_bot_earn_overview_title" = "Proceeds overview"; "lng_bot_earn_available" = "Available balance"; +"lng_bot_earn_reward" = "Total balance"; "lng_bot_earn_total" = "Total lifetime proceeds"; "lng_bot_earn_balance_title" = "Available balance"; "lng_bot_earn_balance_about" = "Stars from your total balance can be used for ads or withdrawn as rewards 21 days after they are earned."; diff --git a/Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp b/Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp index a4440269ce..426d6704d5 100644 --- a/Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp +++ b/Telegram/SourceFiles/info/bot/earn/info_bot_earn_list.cpp @@ -205,9 +205,11 @@ void InnerWidget::fill() { tr::lng_bot_earn_available); Ui::AddSkip(container); Ui::AddSkip(container); - // addOverview(data.currentBalance, tr::lng_bot_earn_reward); - // Ui::AddSkip(container); - // Ui::AddSkip(container); + addOverview( + rpl::single(data.currentBalance), + tr::lng_bot_earn_reward); + Ui::AddSkip(container); + Ui::AddSkip(container); addOverview( rpl::single( data.overallRevenue From 667e614bf3ae360a8f5281862341469b0382692a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 19:32:44 +0300 Subject: [PATCH 056/190] Simplified proper cleanup of allocated event filters for swipe handler. --- Telegram/SourceFiles/ui/controls/swipe_handler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index f3d99f7c3d..a55634209b 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -76,7 +76,7 @@ void SetupSwipeHandler(SwipeHandlerArgs &&args) { bool touch = false; }; struct State { - base::unique_qptr filterContext; + base::unique_qptr filter; Ui::Animations::Simple animationReach; Ui::Animations::Simple animationEnd; SwipeContextData data; @@ -376,8 +376,8 @@ void SetupSwipeHandler(SwipeHandlerArgs &&args) { return base::EventFilterResult::Continue; }; widget->setAttribute(Qt::WA_AcceptTouchEvents); - state->filterContext = base::make_unique_q(nullptr); - base::install_event_filter(state->filterContext.get(), widget, filter); + state->filter = base::unique_qptr( + base::install_event_filter(widget, filter)); } SwipeBackResult SetupSwipeBack( From 9ad1d1c25db2d9836a18a5919335a3a49af54e0d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 20:44:45 +0300 Subject: [PATCH 057/190] Fixed some unused-but-set-variable warnings. --- .../SourceFiles/api/api_messages_search.cpp | 4 ++-- Telegram/SourceFiles/api/api_statistics.cpp | 2 +- .../boxes/peers/add_participants_box.cpp | 2 +- .../boxes/peers/choose_peer_box.cpp | 2 +- .../boxes/peers/edit_participants_box.cpp | 6 +++--- Telegram/SourceFiles/boxes/star_gift_box.cpp | 2 +- .../SourceFiles/boxes/sticker_set_box.cpp | 4 ++-- Telegram/SourceFiles/boxes/url_auth_box.cpp | 2 +- .../calls/calls_box_controller.cpp | 2 +- .../chat_helpers/emoji_list_widget.cpp | 4 ++-- .../chat_helpers/message_field.cpp | 2 +- .../data/components/location_pickers.cpp | 4 ++-- Telegram/SourceFiles/data/data_channel.cpp | 2 +- .../SourceFiles/data/data_chat_filters.cpp | 2 +- .../SourceFiles/data/data_cloud_themes.cpp | 2 +- .../SourceFiles/data/data_media_types.cpp | 6 +++--- Telegram/SourceFiles/data/data_peer.cpp | 4 ++-- .../SourceFiles/data/data_peer_values.cpp | 4 ++-- .../SourceFiles/data/data_photo_media.cpp | 4 ++-- .../SourceFiles/data/data_replies_list.cpp | 2 +- Telegram/SourceFiles/data/data_session.cpp | 2 +- .../dialogs/dialogs_inner_widget.cpp | 2 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- .../dialogs/ui/dialogs_suggestions.cpp | 4 ++-- .../export/data/export_data_types.cpp | 12 +++++------ Telegram/SourceFiles/history/history.cpp | 21 ++++++++----------- .../history/history_inner_widget.cpp | 9 +++++--- Telegram/SourceFiles/history/history_item.cpp | 6 +++--- .../history/history_item_helpers.cpp | 2 +- .../SourceFiles/history/history_widget.cpp | 2 +- .../controls/history_view_forward_panel.cpp | 2 +- .../history/view/history_view_bottom_info.cpp | 2 +- .../view/history_view_context_menu.cpp | 4 ++-- .../history/view/history_view_list_widget.cpp | 4 ++-- .../history/view/history_view_message.cpp | 6 +++--- .../view/history_view_top_bar_widget.cpp | 2 +- .../view/media/history_view_document.cpp | 4 ++-- .../view/media/history_view_media_generic.cpp | 2 +- .../view/media/history_view_premium_gift.cpp | 2 +- .../starref/info_bot_starref_setup_widget.cpp | 2 +- .../info/profile/info_profile_actions.cpp | 2 +- .../info/profile/info_profile_widget.cpp | 2 +- .../inline_bot_layout_internal.cpp | 4 ++-- Telegram/SourceFiles/mainwidget.cpp | 4 ++-- .../media/player/media_player_widget.cpp | 2 +- .../media/stories/media_stories_reply.cpp | 3 +-- .../SourceFiles/overview/overview_layout.cpp | 2 +- .../settings/settings_credits_graphics.cpp | 2 +- .../SourceFiles/settings/settings_folders.cpp | 2 +- .../SourceFiles/storage/file_download_web.cpp | 2 +- .../storage/storage_media_prepare.cpp | 2 +- .../ui/chat/group_call_userpics.cpp | 2 +- .../ui/effects/reaction_fly_animation.cpp | 4 ++-- .../ui/widgets/chat_filters_tabs_slider.cpp | 2 +- .../SourceFiles/window/window_peer_menu.cpp | 8 +++---- 55 files changed, 98 insertions(+), 99 deletions(-) diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index 8bc7a4e3d4..fb390564ef 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -28,8 +28,8 @@ constexpr auto kSearchPerPage = 50; auto result = MessageIdsList(); for (const auto &message : messages) { const auto peerId = PeerFromMessage(message); - if (const auto peer = data->peerLoaded(peerId)) { - if (const auto lastDate = DateFromMessage(message)) { + if (data->peerLoaded(peerId)) { + if (DateFromMessage(message)) { const auto item = data->addNewMessage( message, MessageFlags(), diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index daed8f1eef..400ad7fcae 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -313,7 +313,7 @@ void PublicForwards::request( const auto msgId = IdFromMessage(message); const auto peerId = PeerFromMessage(message); const auto lastDate = DateFromMessage(message); - if (const auto peer = owner.peerLoaded(peerId)) { + if (owner.peerLoaded(peerId)) { if (!lastDate) { return; } diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index 4701cac1e7..ad0bb57e77 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -808,7 +808,7 @@ void AddParticipantsBoxController::rowClicked(not_null row) { updateTitle(); } else if (const auto channel = _peer ? _peer->asChannel() : nullptr) { if (!_peer->isMegagroup()) { - showBox(Box(_peer->asChannel())); + showBox(Box(channel)); } } else if (count >= serverConfig.chatSizeMax && count < serverConfig.megagroupSizeMax) { diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp index 33ce09ac44..bc336675f8 100644 --- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp @@ -453,7 +453,7 @@ void ChoosePeerBoxController::rowClicked(not_null row) { const auto onstack = callback; onstack({ peer }); }; - if (const auto user = peer->asUser()) { + if (peer->isUser()) { done(); } else { delegate()->peerListUiShow()->showBox( diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index 1147922a04..e168e800e0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -689,7 +689,7 @@ UserData *ParticipantsAdditionalData::applyAdmin( const auto user = _peer->owner().userLoaded(data.userId()); if (!user) { return nullptr; - } else if (const auto chat = _peer->asChat()) { + } else if (_peer->isChat()) { // This can come from saveAdmin callback. _admins.emplace(user); return user; @@ -733,7 +733,7 @@ UserData *ParticipantsAdditionalData::applyRegular(UserId userId) { const auto user = _peer->owner().userLoaded(userId); if (!user) { return nullptr; - } else if (const auto chat = _peer->asChat()) { + } else if (_peer->isChat()) { // This can come from saveAdmin or saveRestricted callback. _admins.erase(user); return user; @@ -913,7 +913,7 @@ void ParticipantsBoxController::setupListChangeViewers() { return; } } - if (const auto row = delegate()->peerListFindRow(user->id.value)) { + if (delegate()->peerListFindRow(user->id.value)) { delegate()->peerListPartitionRows([&](const PeerListRow &row) { return (row.peer() == user); }); diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index f02ea735c8..e20967b2e9 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -1793,7 +1793,7 @@ void SendGiftBox( ShowSentToast(window, details.descriptor, details); } if (const auto strong = weak.data()) { - box->closeBox(); + strong->closeBox(); } }; SendGift(window, peer, api, details, done); diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 8ddbb7720d..3c7380f914 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -1510,7 +1510,7 @@ void StickerSetBox::Inner::fillDeleteStickerBox( sticker->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(sticker); - if (const auto strong = weak.data()) { + if ([[maybe_unused]] const auto strong = weak.data()) { const auto paused = On(PowerSaving::kStickersPanel) || show->paused(ChatHelpers::PauseReason::Layer); paintSticker(p, index, QPoint(), paused, crl::now()); @@ -1564,7 +1564,7 @@ void StickerSetBox::Inner::fillDeleteStickerBox( Data::StickersType::Stickers); }, [](const auto &) { }); - if (const auto strong = weak.data()) { + if ([[maybe_unused]] const auto strong = weak.data()) { applySet(result); } if (const auto strongBox = weakBox.data()) { diff --git a/Telegram/SourceFiles/boxes/url_auth_box.cpp b/Telegram/SourceFiles/boxes/url_auth_box.cpp index ef72584cf5..3dc29d9222 100644 --- a/Telegram/SourceFiles/boxes/url_auth_box.cpp +++ b/Telegram/SourceFiles/boxes/url_auth_box.cpp @@ -144,7 +144,7 @@ void UrlAuthBox::Request( const auto callback = [=](Result result) { if (result == Result::None) { finishWithUrl(url); - } else if (const auto msg = session->data().message(itemId)) { + } else if (session->data().message(itemId)) { const auto allowWrite = (result == Result::AuthAndAllowWrite); using Flag = MTPmessages_AcceptUrlAuth::Flag; const auto flags = (allowWrite ? Flag::f_write_allowed : Flag(0)) diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 96d064ec34..8e093719fc 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -611,7 +611,7 @@ void BoxController::receivedCalls(const QVector &result) { for (const auto &message : result) { const auto msgId = IdFromMessage(message); const auto peerId = PeerFromMessage(message); - if (const auto peer = session().data().peerLoaded(peerId)) { + if (session().data().peerLoaded(peerId)) { const auto item = session().data().addNewMessage( message, MessageFlags(), diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index dde73d671a..ec9df7d3c2 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -2104,7 +2104,7 @@ void EmojiListWidget::colorChosen(EmojiChosen data) { const auto emoji = data.emoji; auto &settings = Core::App().settings(); - if (const auto button = std::get_if(&_pickerSelected)) { + if (v::is(_pickerSelected)) { settings.saveAllEmojiVariants(emoji); for (auto section = int(Section::People) ; section < _staticCount @@ -2425,7 +2425,7 @@ Ui::Text::CustomEmoji *EmojiListWidget::resolveCustomRecent( const auto &data = customId.data; if (const auto document = std::get_if(&data)) { return resolveCustomRecent(document->id); - } else if (const auto emoji = std::get_if(&data)) { + } else if (v::is(data)) { return nullptr; } Unexpected("Custom recent emoji id."); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index b9e26419e0..200ad997e8 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -765,7 +765,7 @@ InlineBotQuery ParseInlineBotQuery( result.username = username.toString(); if (const auto peer = session->data().peerByUsername(result.username)) { if (const auto user = peer->asUser()) { - result.bot = peer->asUser(); + result.bot = user; } else { result.bot = nullptr; } diff --git a/Telegram/SourceFiles/data/components/location_pickers.cpp b/Telegram/SourceFiles/data/components/location_pickers.cpp index 4402e4ccf8..7f15a30da1 100644 --- a/Telegram/SourceFiles/data/components/location_pickers.cpp +++ b/Telegram/SourceFiles/data/components/location_pickers.cpp @@ -25,7 +25,7 @@ Ui::LocationPicker *LocationPickers::lookup(const Api::SendAction &action) { for (auto i = begin(_pickers); i != end(_pickers);) { if (const auto strong = i->picker.get()) { if (i->action == action) { - return i->picker.get(); + return strong; } ++i; } else { @@ -41,4 +41,4 @@ void LocationPickers::emplace( _pickers.push_back({ action, picker }); } -} // namespace Data \ No newline at end of file +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index f21769328a..a4f4092e8b 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -956,7 +956,7 @@ QString ChannelData::invitePeekHash() const { } void ChannelData::privateErrorReceived() { - if (const auto expires = invitePeekExpires()) { + if (invitePeekExpires()) { const auto hash = invitePeekHash(); for (const auto &window : session().windows()) { clearInvitePeek(); diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index a53a8f5c62..4a0d0c2331 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -347,7 +347,7 @@ bool ChatFilter::contains( : user->isContact() ? Flag::Contacts : Flag::NonContacts; - } else if (const auto chat = peer->asChat()) { + } else if (peer->isChat()) { return Flag::Groups; } else if (const auto channel = peer->asChannel()) { if (channel->isBroadcast()) { diff --git a/Telegram/SourceFiles/data/data_cloud_themes.cpp b/Telegram/SourceFiles/data/data_cloud_themes.cpp index 5ac1c2052c..56c7647dac 100644 --- a/Telegram/SourceFiles/data/data_cloud_themes.cpp +++ b/Telegram/SourceFiles/data/data_cloud_themes.cpp @@ -238,7 +238,7 @@ void CloudThemes::showPreview( void CloudThemes::showPreview( not_null controller, const CloudTheme &cloud) { - if (const auto documentId = cloud.documentId) { + if (cloud.documentId) { previewFromDocument(controller, cloud); } else if (cloud.createdBy == _session->userId()) { controller->show(Box( diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 97ef0e6d65..2be78e44fc 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -1111,7 +1111,7 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { return toGroupPreview(group->items, options); } } - if (const auto sticker = _document->sticker()) { + if (_document->sticker()) { return Media::toPreview(options); } auto images = std::vector(); @@ -1178,7 +1178,7 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { } TextWithEntities MediaFile::notificationText() const { - if (const auto sticker = _document->sticker()) { + if (_document->sticker()) { const auto text = _emoji.isEmpty() ? tr::lng_in_dlg_sticker(tr::now) : tr::lng_in_dlg_sticker_emoji(tr::now, lt_emoji, _emoji); @@ -1210,7 +1210,7 @@ TextWithEntities MediaFile::notificationText() const { } QString MediaFile::pinnedTextSubstring() const { - if (const auto sticker = _document->sticker()) { + if (_document->sticker()) { if (!_emoji.isEmpty()) { return tr::lng_action_pinned_media_emoji_sticker( tr::now, diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 41d5d19e68..592fc1edfb 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -703,7 +703,7 @@ bool PeerData::canTransferGifts() const { bool PeerData::canEditMessagesIndefinitely() const { if (const auto user = asUser()) { return user->isSelf(); - } else if (const auto chat = asChat()) { + } else if (isChat()) { return false; } else if (const auto channel = asChannel()) { return channel->isMegagroup() @@ -1410,7 +1410,7 @@ Data::ForumTopic *PeerData::forumTopicFor(MsgId rootId) const { } bool PeerData::allowsForwarding() const { - if (const auto user = asUser()) { + if (isUser()) { return true; } else if (const auto channel = asChannel()) { return channel->allowsForwarding(); diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index b79b1dbe8e..287dd2e42b 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -599,7 +599,7 @@ rpl::producer UniqueReactionsLimitValue( ) | rpl::map([config = &peer->session().appConfig()] { return UniqueReactionsLimit(config); }) | rpl::distinct_until_changed(); - if (const auto channel = peer->asChannel()) { + if (peer->isChannel()) { return rpl::combine( PeerAllowedReactionsValue(peer), std::move(configValue) @@ -608,7 +608,7 @@ rpl::producer UniqueReactionsLimitValue( ? allowedReactions.maxCount : limit; }); - } else if (const auto chat = peer->asChat()) { + } else if (peer->isChat()) { return rpl::combine( PeerAllowedReactionsValue(peer), std::move(configValue) diff --git a/Telegram/SourceFiles/data/data_photo_media.cpp b/Telegram/SourceFiles/data/data_photo_media.cpp index af01588f45..06a52ed4dc 100644 --- a/Telegram/SourceFiles/data/data_photo_media.cpp +++ b/Telegram/SourceFiles/data/data_photo_media.cpp @@ -65,13 +65,13 @@ QByteArray PhotoMedia::imageBytes(PhotoSize size) const { auto PhotoMedia::resolveLoadedImage(PhotoSize size) const -> const PhotoImage * { const auto &original = _images[PhotoSizeIndex(size)]; - if (const auto image = original.data.get()) { + if (original.data) { if (original.goodFor >= size) { return &original; } } const auto &valid = _images[_owner->validSizeIndex(size)]; - if (const auto image = valid.data.get()) { + if (valid.data.get()) { if (valid.goodFor >= size) { return &valid; } diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index 2a962dc1b6..f334c47d1d 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -366,7 +366,7 @@ bool RepliesList::buildFromData(not_null viewer) { const auto around = [&] { if (viewer->around != ShowAtUnreadMsgId) { return viewer->around; - } else if (const auto item = lookupRoot()) { + } else if (lookupRoot()) { return computeInboxReadTillFull(); } else if (_owningTopic) { // Somehow we don't want always to jump to computed inboxReadTill diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 5b74b76561..098315878d 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -2231,7 +2231,7 @@ void Session::applyDialog( } bool Session::pinnedCanPin(not_null entry) const { - if (const auto sublist = entry->asSublist()) { + if ([[maybe_unused]] const auto sublist = entry->asSublist()) { const auto saved = &savedMessages(); return pinnedChatsOrder(saved).size() < pinnedChatsLimit(saved); } else if (const auto topic = entry->asTopic()) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 5ed9dd391c..970d1507de 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -4756,7 +4756,7 @@ bool InnerWidget::chooseRow( } if (!chosen.message.fullId) { if (const auto history = chosen.key.history()) { - if (const auto forum = history->peer->forum()) { + if (history->peer->forum()) { if (pressedTopicRootId) { chosen.message.fullId = { history->peer->id, diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index b76b82de89..9d873db04a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -806,7 +806,7 @@ void Widget::setupSwipeBack() { } return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] { _swipeBackData = {}; - if (const auto forum = controller()->shownForum().current()) { + if (controller()->shownForum().current()) { controller()->closeForum(); } }); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 4ae07e5a92..6b2ab882e6 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -952,7 +952,7 @@ void MyChannelsController::prepare() { const auto add = [&](not_null list) { for (const auto &row : list->indexed()->all()) { if (const auto history = row->history()) { - if (const auto channel = history->peer->asBroadcast()) { + if (history->peer->isBroadcast()) { _channels.push_back(history); } } @@ -981,7 +981,7 @@ void MyChannelsController::prepare() { const auto list = owner->chatsList(folder); for (const auto &row : list->indexed()->all()) { if (const auto history = row->history()) { - if (const auto channel = history->peer->asBroadcast()) { + if (history->peer->isBroadcast()) { if (ranges::contains(_channels, not_null(history))) { _channels.push_back(history); } diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index f4c0c788da..830e64e907 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1724,9 +1724,9 @@ File &Message::file() { } else if (const auto photo = std::get_if( content)) { return photo->photo.image.file; - } else if (const auto wallpaper = std::get_if( - content)) { - // #TODO wallpapers + // } else if (const auto wallpaper = std::get_if( + // content)) { + // #TODO wallpapers } return media.file(); } @@ -1738,9 +1738,9 @@ const File &Message::file() const { } else if (const auto photo = std::get_if( content)) { return photo->photo.image.file; - } else if (const auto wallpaper = std::get_if( - content)) { - // #TODO wallpapers + // } else if (const auto wallpaper = std::get_if( + // content)) { + // #TODO wallpapers } return media.file(); } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 1eb7d91073..bde7012f1b 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -190,7 +190,7 @@ void History::itemVanished(not_null item) { setUnreadCount(unreadCount() - 1); } if (const auto media = item->media()) { - if (const auto gift = media->gift()) { + if (media->gift()) { using GiftAction = Data::GiftUpdate::Action; owner().notifyGiftUpdate({ .id = Data::SavedStarGiftId::User(item->id), @@ -1160,7 +1160,7 @@ void History::applyServiceChanges( auto paid = std::optional(); if (const auto message = payment->msg) { if (const auto media = message->media()) { - if (const auto invoice = media->invoice()) { + if (media->invoice()) { paid = Payments::CheckoutProcess::InvoicePaid( message); } @@ -2555,7 +2555,7 @@ auto History::computeChatListMessageFromLast() const if (!last || !last->isGroupMigrate()) { return _lastMessage; } - if (const auto chat = peer->asChat()) { + if (peer->isChat()) { // In chats we try to take the item before the 'last', which // is the empty-displayed migration message. if (!loadedAtBottom()) { @@ -2633,7 +2633,7 @@ void History::setFakeChatListMessage() { } } return; - } else if (const auto chat = peer->asChat()) { + } else if (peer->isChat()) { // In chats we try to take the item before the 'last', which // is the empty-displayed migration message. owner().histories().requestFakeChatListMessage(this); @@ -2876,10 +2876,8 @@ void History::dialogEntryApplied() { addOlderSlice(QVector()); if (const auto channel = peer->asChannel()) { const auto inviter = channel->inviter; - if (inviter && channel->amIn()) { - if (const auto from = owner().userLoaded(inviter)) { - insertJoinedMessage(); - } + if (inviter && channel->amIn() && owner().userLoaded(inviter)) { + insertJoinedMessage(); } } return; @@ -2890,10 +2888,9 @@ void History::dialogEntryApplied() { const auto inviter = channel->inviter; if (inviter && chatListTimeId() <= channel->inviteDate - && channel->amIn()) { - if (const auto from = owner().userLoaded(inviter)) { - insertJoinedMessage(); - } + && channel->amIn() + && owner().userLoaded(inviter)) { + insertJoinedMessage(); } } } diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 227c1a5fc9..606c788142 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2495,9 +2495,12 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto itemId = item->fullId(); _menu->addAction(tr::lng_context_select_msg(tr::now), [=] { if (const auto item = session->data().message(itemId)) { - if (const auto view = viewByItem(item)) { + if ([[maybe_unused]] const auto view = viewByItem(item)) { if (asGroup) { - changeSelectionAsGroup(&_selected, item, SelectAction::Select); + changeSelectionAsGroup( + &_selected, + item, + SelectAction::Select); } else { changeSelection(&_selected, item, SelectAction::Select); } @@ -4628,7 +4631,7 @@ void HistoryInner::reportAsGroup(FullMsgId itemId) { } void HistoryInner::blockSenderItem(FullMsgId itemId) { - if (const auto item = session().data().message(itemId)) { + if ([[maybe_unused]] const auto item = session().data().message(itemId)) { _controller->show(Box( Window::BlockSenderFromRepliesBox, _controller, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 927ea907a3..86861abed7 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2263,7 +2263,7 @@ void HistoryItem::setRealId(MsgId newId) { _history->owner().groups().refreshMessage(this); _history->owner().requestItemResize(this); - if (const auto reply = Get()) { + if (Has()) { incrementReplyToTopCounter(); } @@ -2440,7 +2440,7 @@ bool HistoryItem::canDeleteForEveryone(TimeId now) const { bool HistoryItem::suggestReport() const { if (out() || isService() || !isRegular()) { return false; - } else if (const auto channel = _history->peer->asChannel()) { + } else if (_history->peer->isChannel()) { return true; } else if (const auto user = _history->peer->asUser()) { return user->isBot(); @@ -3731,7 +3731,7 @@ void HistoryItem::createComponents(CreateConfig &&config) { if (const auto via = Get()) { via->create(&_history->owner(), config.viaBotId); } - if (const auto views = Get()) { + if (Has()) { changeViewsCount(config.viewsCount); if (config.replies.isNull && isSending() diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index c0358488b4..974fdbd8b8 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -1208,7 +1208,7 @@ void ClearMediaAsExpired(not_null item) { ? tr::lng_ttl_round_expired : tr::lng_message_empty)(tr::now, Ui::Text::WithEntities); item->updateServiceText(PreparedServiceText{ std::move(text) }); - } else if (const auto photo = media->photo()) { + } else if (media->photo()) { item->applyEditionToHistoryCleared(); item->updateServiceText(PreparedServiceText{ tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 1e2be03be0..4d34fd0b86 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7416,7 +7416,7 @@ bool HistoryWidget::showSlowmodeError() { Ui::FormatDurationWordsSlowmode(left)); } else if (_peer->slowmodeApplied()) { if (const auto item = _history->latestSendingMessage()) { - if (const auto view = item->mainView()) { + if (item->mainView()) { animatedScrollToItem(item->id); enqueueMessageHighlight({ item }); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index 7502a93c88..44e059ff34 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -102,7 +102,7 @@ void ForwardPanel::checkTexts() { for (const auto item : _data.items) { if (const auto from = item->originalSender()) { version += from->nameVersion(); - } else if (const auto info = item->originalHiddenSenderInfo()) { + } else if (item->originalHiddenSenderInfo()) { ++version; } else { Unexpected("Corrupt forwarded information in message."); diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index e93a5c6faa..4d735d3d8e 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -198,7 +198,7 @@ ClickHandlerPtr BottomInfo::replayEffectLink( const auto weak = base::make_weak(view); return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { + if ([[maybe_unused]] const auto controller = my.sessionWindow.get()) { if (const auto strong = weak.get()) { strong->delegate()->elementStartEffect(strong, nullptr); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index d1cead7548..b28099fe58 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1440,11 +1440,11 @@ void AddSaveSoundForNotifications( return; } else if (int(ringtones.list().size()) >= ringtones.maxSavedCount()) { return; - } else if (const auto song = document->song()) { + } else if (document->song()) { if (document->duration() > ringtones.maxDuration()) { return; } - } else if (const auto voice = document->voice()) { + } else if (document->voice()) { if (document->duration() > ringtones.maxDuration()) { return; } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 1daf0a1133..d70006e3f4 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1489,7 +1489,7 @@ void ListWidget::cancelSelection() { void ListWidget::selectItem(not_null item) { if (hasSelectRestriction()) { return; - } else if (const auto view = viewForItem(item)) { + } else if ([[maybe_unused]] const auto view = viewForItem(item)) { clearTextSelection(); changeSelection( _selected, @@ -1502,7 +1502,7 @@ void ListWidget::selectItem(not_null item) { void ListWidget::selectItemAsGroup(not_null item) { if (hasSelectRestriction()) { return; - } else if (const auto view = viewForItem(item)) { + } else if ([[maybe_unused]] const auto view = viewForItem(item)) { clearTextSelection(); changeSelectionAsGroup( _selected, diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 591e8b37d4..4c76c50f57 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2477,7 +2477,7 @@ bool Message::hasFromPhoto() const { return true; } else if (item->history()->peer->isVerifyCodes()) { return !hasOutLayout(); - } else if (const auto forwarded = item->Get()) { + } else if (item->Has()) { const auto peer = item->history()->peer; if (peer->isSelf() || peer->isRepliesChat()) { return !hasOutLayout(); @@ -3136,7 +3136,7 @@ void Message::updatePressed(QPoint point) { if (const auto reply = Get()) { trect.setTop(trect.top() + reply->height()); } - if (const auto via = item->Get()) { + if (item->Has()) { if (!displayFromName() && !displayForwardedFrom()) { trect.setTop(trect.top() + st::msgNameFont->height); } @@ -4447,7 +4447,7 @@ Ui::BubbleRounding Message::countMessageRounding() const { Ui::BubbleRounding Message::countBubbleRounding( Ui::BubbleRounding messageRounding) const { - if (const auto keyboard = data()->inlineReplyKeyboard()) { + if ([[maybe_unused]] const auto _ = data()->inlineReplyKeyboard()) { messageRounding.bottomLeft = messageRounding.bottomRight = Ui::BubbleCornerRounding::Small; 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 62d5d43a94..2b25656355 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -738,7 +738,7 @@ void TopBarWidget::infoClicked() { return; } else if (const auto topic = key.topic()) { _controller->showSection(std::make_shared(topic)); - } else if (const auto sublist = key.sublist()) { + } else if ([[maybe_unused]] const auto sublist = key.sublist()) { _controller->showSection(std::make_shared( _controller->session().user(), Info::Section(Storage::SharedMediaType::Photo))); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 84f0668846..0c9521d4db 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -271,10 +271,10 @@ void PaintWaveform( }; add(FormatDownloadText(document->size, document->size)); const auto duration = document->duration() / 1000; - if (const auto song = document->song()) { + if (document->song()) { add(FormatPlayedText(duration, duration)); add(FormatDurationAndSizeText(duration, document->size)); - } else if (const auto voice = document->voice() ? document->voice() : document->round()) { + } else if (document->voice() ? document->voice() : document->round()) { add(FormatPlayedText(duration, duration)); add(FormatDurationAndSizeText(duration, document->size)); } else if (document->isVideoFile()) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp index d27ef88168..bfac91b07d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp @@ -448,7 +448,7 @@ void StickerInBubblePart::ensureCreated(Element *replacing) const { return; } else if (const auto data = _lookup()) { const auto sticker = data.sticker; - if (const auto info = sticker->sticker()) { + if (sticker->sticker()) { const auto skipPremiumEffect = true; _link = data.link; _skipTop = data.skipTop; diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp index f9a9b031ca..39679cb448 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -411,7 +411,7 @@ void PremiumGift::ensureStickerCreated() const { const auto count = credits(); const auto months = count ? packs.monthsForStars(count) : _data.count; if (const auto document = packs.lookup(months)) { - if (const auto sticker = document->sticker()) { + if (document->sticker()) { const auto skipPremiumEffect = false; _sticker.emplace(_parent, document, skipPremiumEffect, _parent); _sticker->setPlayingOnce(true); diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp index 409373713d..5d1772c93a 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp @@ -751,7 +751,7 @@ void InnerWidget::setupEnd() { *sent = false; if (!success) { return; - } else if (const auto strong = weak.data()) { + } else if ([[maybe_unused]] const auto strong = weak.data()) { _controller->showBackFromStack(); window->showToast({ .title = tr::lng_star_ref_ended_title(tr::now), diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 26ca73fe3d..caa4ca26a9 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -861,7 +861,7 @@ rpl::producer AddCurrencyAction( ) | rpl::start_with_error_done([=](const QString &error) { currencyLoadLifetime->destroy(); }, [=] { - if (const auto strong = weak.data()) { + if ([[maybe_unused]] const auto strong = weak.data()) { state->balance = currencyLoad->data().currentBalance; currencyLoadLifetime->destroy(); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp index 48b0107edf..6f62b98975 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp @@ -100,7 +100,7 @@ void Widget::setInnerFocus() { } rpl::producer Widget::title() { - if (const auto topic = controller()->key().topic()) { + if (controller()->key().topic()) { return tr::lng_info_topic_title(); } const auto peer = controller()->key().peer(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 7f6f415814..3778522acb 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -1582,11 +1582,11 @@ TextState Game::getState( } void Game::prepareThumbnail(QSize size) const { - if (const auto document = getResultDocument()) { + if ([[maybe_unused]] const auto document = getResultDocument()) { Assert(_documentMedia != nullptr); validateThumbnail(_documentMedia->thumbnail(), size, true); validateThumbnail(_documentMedia->thumbnailInline(), size, false); - } else if (const auto photo = getResultPhoto()) { + } else if ([[maybe_unused]] const auto photo = getResultPhoto()) { using Data::PhotoSize; Assert(_photoMedia != nullptr); validateThumbnail(_photoMedia->image(PhotoSize::Thumbnail), size, true); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index f4ba6983c5..d2437cfa46 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1166,7 +1166,7 @@ float64 MainWidget::chatBackgroundProgress() const { if (_background) { if (_background->generating) { return 1.; - } else if (const auto document = _background->data.document()) { + } else if (_background->data.document()) { return _background->dataMedia->progress(); } } @@ -2602,7 +2602,7 @@ auto MainWidget::thirdSectionForCurrentMainSection( return std::make_shared( peer, Info::Memento::DefaultSection(peer)); - } else if (const auto sublist = key.sublist()) { + } else if (key.sublist()) { return std::make_shared( session().user(), Info::Memento::DefaultSection(session().user())); diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 6425b79513..89d85a5454 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -642,7 +642,7 @@ void Widget::updateTimeText(const TrackState &state) { display = state.position; } else if (state.length) { display = state.length; - } else if (const auto song = document->song()) { + } else if (document->song()) { display = (document->duration() * frequency) / 1000; } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 4587335876..1ba70c7fd5 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -905,8 +905,7 @@ bool ReplyArea::showSlowmodeError() { lt_left, Ui::FormatDurationWordsSlowmode(left)); } else if (peer->slowmodeApplied()) { - const auto history = peer->owner().history(peer); - if (const auto item = history->latestSendingMessage()) { + if (peer->owner().history(peer)->latestSendingMessage()) { return tr::lng_slowmode_no_many(tr::now); } } diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 05e1c0ad78..af3317ca02 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -1017,7 +1017,7 @@ const style::RoundCheckbox &Voice::checkboxStyle() const { } void Voice::updateName() { - if (const auto forwarded = parent()->Get()) { + if (parent()->Has()) { const auto info = parent()->originalHiddenSenderInfo(); const auto name = info ? tr::lng_forwarded(tr::now, lt_user, info->nameText().toString()) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 9aee9306f5..8b9a08cd95 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -1895,7 +1895,7 @@ void GenericCreditsEntryBox( ProcessReceivedSubscriptions(weak, session); }; const auto fail = [=, show = box->uiShow()](const QString &e) { - if (const auto strong = weak.data()) { + if ([[maybe_unused]] const auto strong = weak.data()) { state->confirmButtonBusy = false; } show->showToast(e); diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index 995591894e..bd5d8be137 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -911,7 +911,7 @@ void SetupTagContent( const auto send = [=, weak = Ui::MakeWeak(tagsButton)](bool checked) { session->data().chatsFilters().requestToggleTags(checked, [=] { - if (const auto strong = weak.data()) { + if ([[maybe_unused]] const auto strong = weak.data()) { state->tagsTurnOff.fire(!checked); } }); diff --git a/Telegram/SourceFiles/storage/file_download_web.cpp b/Telegram/SourceFiles/storage/file_download_web.cpp index 1c56d8b063..ddac1f8436 100644 --- a/Telegram/SourceFiles/storage/file_download_web.cpp +++ b/Telegram/SourceFiles/storage/file_download_web.cpp @@ -418,7 +418,7 @@ void WebLoadManager::failed( } void WebLoadManager::failed(int id, not_null reply) { - if (const auto sent = findSent(id, reply)) { + if ([[maybe_unused]] const auto sent = findSent(id, reply)) { removeSent(id); queueFailedUpdate(id); } diff --git a/Telegram/SourceFiles/storage/storage_media_prepare.cpp b/Telegram/SourceFiles/storage/storage_media_prepare.cpp index 290fae458b..588d901440 100644 --- a/Telegram/SourceFiles/storage/storage_media_prepare.cpp +++ b/Telegram/SourceFiles/storage/storage_media_prepare.cpp @@ -319,7 +319,7 @@ void PrepareDetails(PreparedFile &file, int previewWidth, int sideLimit) { file.preview.setDevicePixelRatio(style::DevicePixelRatio()); file.type = PreparedFile::Type::Video; } - } else if (const auto song = std::get_if(&file.information->media)) { + } else if (v::is(file.information->media)) { file.type = PreparedFile::Type::Music; } } diff --git a/Telegram/SourceFiles/ui/chat/group_call_userpics.cpp b/Telegram/SourceFiles/ui/chat/group_call_userpics.cpp index 4044c7d26f..f415151c2f 100644 --- a/Telegram/SourceFiles/ui/chat/group_call_userpics.cpp +++ b/Telegram/SourceFiles/ui/chat/group_call_userpics.cpp @@ -241,7 +241,7 @@ void GroupCallUserpics::sendRandomLevels() { for (auto &userpic : _list) { if (const auto blobs = userpic.blobsAnimation.get()) { const auto value = 30 + base::RandomIndex(70); - userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.); + blobs->blobs.setLevel(float64(value) / 100.); } } } diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp index a193bf32db..239a1e5456 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp @@ -385,10 +385,10 @@ int ReactionFlyAnimation::computeParabolicTop( void ReactionFlyAnimation::startAnimations() { if (const auto center = _center.get()) { - _center->animate(callback()); + center->animate(callback()); } if (const auto effect = _effect.get()) { - _effect->animate(callback()); + effect->animate(callback()); } else if (_scaleOutDuration > 0) { _noEffectScaleStarted = true; _noEffectScaleAnimation.start(callback(), 1, 0, _scaleOutDuration); diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp index 5fddc9e797..be0a808e59 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp @@ -378,7 +378,7 @@ void ChatsFiltersTabs::setHorizontalShift(int index, int shift) { Expects(index >= 0 && index < _sections.size()); auto §ion = _sections[index]; - if (const auto delta = shift - section.horizontalShift) { + if (shift - section.horizontalShift) { section.horizontalShift = shift; update(); } diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index a0dbbfc583..d8e6f452f9 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -585,7 +585,7 @@ void Filler::addStoryArchive() { const auto controller = _controller; const auto weak = base::make_weak(_thread); _addAction(tr::lng_stories_archive_button(tr::now), [=] { - if (const auto strong = weak.get()) { + if ([[maybe_unused]] const auto strong = weak.get()) { controller->showSection(Info::Stories::Make( channel, Info::Stories::Tab::Archive)); @@ -1096,7 +1096,7 @@ void Filler::addViewStatistics() { = (channel->flags() & Flag::CanViewCreditsRevenue); if (canGetStats) { _addAction(tr::lng_stats_title(tr::now), [=] { - if (const auto strong = weak.get()) { + if ([[maybe_unused]] const auto strong = weak.get()) { using namespace Info; controller->showSection(Statistics::Make(peer, {}, {})); } @@ -1106,14 +1106,14 @@ void Filler::addViewStatistics() { || channel->amCreator() || channel->canPostStories()) { _addAction(tr::lng_boosts_title(tr::now), [=] { - if (const auto strong = weak.get()) { + if ([[maybe_unused]] const auto strong = weak.get()) { controller->showSection(Info::Boosts::Make(peer)); } }, &st::menuIconBoosts); } if (canViewEarn || canViewCreditsEarn) { _addAction(tr::lng_channel_earn_title(tr::now), [=] { - if (const auto strong = weak.get()) { + if ([[maybe_unused]] const auto strong = weak.get()) { controller->showSection(Info::ChannelEarn::Make(peer)); } }, &st::menuIconEarn); From 3ed6d1dec111a06bc6995b3f263389fe864c7b84 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 22:35:11 +0300 Subject: [PATCH 058/190] Added strike out cost without discount in premium options in settings. --- .../SourceFiles/api/api_premium_option.cpp | 3 ++ .../data/data_premium_subscription_option.h | 1 + .../SourceFiles/settings/settings_premium.cpp | 1 + .../ui/effects/premium_graphics.cpp | 42 +++++++++++++++---- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/api/api_premium_option.cpp b/Telegram/SourceFiles/api/api_premium_option.cpp index c4346dbe07..f5f0af380d 100644 --- a/Telegram/SourceFiles/api/api_premium_option.cpp +++ b/Telegram/SourceFiles/api/api_premium_option.cpp @@ -33,6 +33,9 @@ Data::PremiumSubscriptionOption CreateSubscriptionOption( .costPerMonth = Ui::FillAmountAndCurrency( amount / float64(months), currency), + .costNoDiscount = Ui::FillAmountAndCurrency( + monthlyAmount * months, + currency), .costTotal = Ui::FillAmountAndCurrency(amount, currency), .botUrl = botUrl, }; diff --git a/Telegram/SourceFiles/data/data_premium_subscription_option.h b/Telegram/SourceFiles/data/data_premium_subscription_option.h index df2c4a8c4e..5b11a532f0 100644 --- a/Telegram/SourceFiles/data/data_premium_subscription_option.h +++ b/Telegram/SourceFiles/data/data_premium_subscription_option.h @@ -14,6 +14,7 @@ struct PremiumSubscriptionOption { QString duration; QString discount; QString costPerMonth; + QString costNoDiscount; QString costTotal; QString total; QString botUrl; diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index a834f9a6d8..ff3868f059 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -85,6 +85,7 @@ using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData; if (option.duration == tr::lng_months(tr::now, lt_count, 1)) { option.costPerMonth = QString(); + option.costNoDiscount = QString(); option.duration = tr::lng_premium_subscribe_months_1(tr::now); } else if (option.duration == tr::lng_months(tr::now, lt_count, 6)) { option.duration = tr::lng_premium_subscribe_months_6(tr::now); diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index ed73836f3c..5b277429d4 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -927,9 +927,19 @@ void AddGiftOptions( const auto costPerMonthIcon = info.costPerMonth.startsWith(kStar) ? GenerateStars(costPerMonthFont->height, 1) : QImage(); - const auto costPerMonthText = costPerMonthIcon.isNull() - ? info.costPerMonth - : removedStar(info.costPerMonth); + const auto costPerMonthLabel + = row->lifetime().make_state(); + costPerMonthLabel->setMarkedText( + st::shareBoxListItem.nameStyle, + TextWithEntities() + .append(Ui::Text::Wrapped( + TextWithEntities{ info.costNoDiscount }, + EntityType::StrikeOut)) + .append(' ') + .append(costPerMonthIcon.isNull() + ? info.costPerMonth + : removedStar(info.costPerMonth))); + const auto costTotalEntry = [&] { if (!info.costTotal.startsWith(kStar)) { return QImage(); @@ -1049,12 +1059,26 @@ void AddGiftOptions( 0); p.setPen(st::windowSubTextFg); p.setFont(costPerMonthFont); - const auto perMonthLeft = costPerMonthFont->spacew - + costPerMonthIcon.width() / style::DevicePixelRatio(); - p.drawText( - perRect.translated(perMonthLeft, 0), - costPerMonthText, - style::al_left); + + { + const auto left = costPerMonthIcon.isNull() + ? 0 + : (costPerMonthFont->spacew + + costPerMonthIcon.width() + / style::DevicePixelRatio()); + const auto costTotalWidth = costTotalFont->width( + info.costTotal); + const auto pos = perRect.translated(left, 0).topLeft(); + const auto availableWidth = row->width() + - pos.x() + - costTotalWidth; + costPerMonthLabel->draw(p, { + .position = pos, + .outerWidth = availableWidth, + .availableWidth = availableWidth, + .elisionLines = 1, + }); + } p.drawImage(perRect.topLeft(), costPerMonthIcon); const auto totalRect = row->rect() From 9b2b8b6796759628e077e33b5e8c47ee3ae64c94 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 23:07:38 +0300 Subject: [PATCH 059/190] Fixed display of top bar suggestion while display filtered dialogs. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 14 ++++++++++++-- Telegram/SourceFiles/dialogs/dialogs_widget.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 9d873db04a..4d6fb3382e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1035,10 +1035,17 @@ void Widget::setupTopBarSuggestions(not_null dialogs) { _openedFolderOrForumChanges.events_starting_with(false), widthValue() | rpl::map( _1 >= st::columnMinimalWidthLeft - ) | rpl::distinct_until_changed() - ) | rpl::map([=](FilterId id, bool folderOrForum, bool wide) { + ) | rpl::distinct_until_changed(), + _searchStateForTopBarSuggestion.events_starting_with( + !_searchState.query.isEmpty()) + ) | rpl::map([=]( + FilterId id, + bool folderOrForum, + bool wide, + bool search) { return !folderOrForum && wide + && !search && (id == session->data().chatsFilters().defaultId()); }); return TopBarSuggestionValue(dialogs, session, std::move(on)); @@ -3482,6 +3489,9 @@ bool Widget::applySearchState(SearchState state) { && !_openedForum); updateControlsGeometry(); } + if (_topBarSuggestion && queryEmptyChanged) { + _searchStateForTopBarSuggestion.fire(!_searchState.query.isEmpty()); + } _searchWithPostsPreview = computeSearchWithPostsPreview(); if (queryChanged) { updateLockUnlockVisibility(anim::type::normal); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 2ed8f98cb8..a38ce878f8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -331,6 +331,7 @@ private: Ui::SlideWrap *_topBarSuggestion = nullptr; rpl::event_stream _topBarSuggestionHeightChanged; + rpl::event_stream _searchStateForTopBarSuggestion; rpl::event_stream _openedFolderOrForumChanges; object_ptr _scroll; From 165cf6809fe3da7fa481c4bd792296fdfd55f69b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 12 Apr 2025 23:55:48 +0300 Subject: [PATCH 060/190] Added experimental option to display date when you joined to channel. --- Telegram/Resources/langs/lang.strings | 2 + .../info/profile/info_profile_actions.cpp | 77 +++++++++++++++---- .../info/profile/info_profile_actions.h | 1 + .../settings/settings_experimental.cpp | 1 + 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bcec388ee2..541a65a91b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2221,6 +2221,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_you_paid_stars#one" = "You paid {count} Star."; "lng_you_paid_stars#other" = "You paid {count} Stars."; +"lng_you_joined_group" = "You joined this group"; + "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; "lng_similar_channels_more" = "More Channels"; diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index caa4ca26a9..e86aca0b39 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -113,6 +113,12 @@ base::options::toggle ShowPeerIdBelowAbout({ " Add contact IDs to exported data.", }); +base::options::toggle ShowChannelJoinedBelowAbout({ + .id = kOptionShowChannelJoinedBelowAbout, + .name = "Show Channel Joined Date in Profile", + .description = "Show when you join Channel under its Description.", +}); + [[nodiscard]] rpl::producer UsernamesSubtext( not_null peer, rpl::producer fallback) { @@ -183,24 +189,47 @@ base::options::toggle ShowPeerIdBelowAbout({ return result; } -[[nodiscard]] rpl::producer AboutWithIdValue( +[[nodiscard]] rpl::producer AboutWithAdvancedValue( not_null peer) { return AboutValue( peer ) | rpl::map([=](TextWithEntities &&value) { - if (!ShowPeerIdBelowAbout.value()) { - return std::move(value); + if (ShowPeerIdBelowAbout.value()) { + using namespace Ui::Text; + if (!value.empty()) { + value.append("\n\n"); + } + value.append(Italic(u"id: "_q)); + const auto raw = peer->id.value & PeerId::kChatTypeMask; + value.append(Link( + Italic(Lang::FormatCountDecimal(raw)), + "internal:~peer_id~:copy:" + QString::number(raw))); } - using namespace Ui::Text; - if (!value.empty()) { - value.append("\n\n"); + if (ShowChannelJoinedBelowAbout.value()) { + if (const auto channel = peer->asChannel()) { + if (!channel->amCreator() && channel->inviteDate) { + if (!value.empty()) { + if (ShowPeerIdBelowAbout.value()) { + value.append("\n"); + } else { + value.append("\n\n"); + } + } + using namespace Ui::Text; + value.append((channel->isMegagroup() + ? tr::lng_you_joined_group + : tr::lng_action_you_joined)( + tr::now, + Ui::Text::Italic)); + value.append(Italic(": ")); + const auto raw = channel->inviteDate; + value.append(Link( + Italic(langDateTimeFull(base::unixtime::parse(raw))), + "internal:~join_date~:show:" + QString::number(raw))); + } + } } - value.append(Italic(u"id: "_q)); - const auto raw = peer->id.value & PeerId::kChatTypeMask; - value.append(Link( - Italic(Lang::FormatCountDecimal(raw)), - "internal:~peer_id~:copy:" + QString::number(raw))); return std::move(value); }); } @@ -1178,6 +1207,23 @@ object_ptr DetailsFiller::setupInfo() { return false; } else if (SetClickContext(handler, context)) { return false; + } else if (handler->url().startsWith(u"internal:~join_date~:"_q)) { + const auto joinDate = handler->url().split( + u"show:"_q, + Qt::SkipEmptyParts).last(); + if (!joinDate.isEmpty()) { + const auto weak = base::make_weak(window); + window->session().api().resolveJumpToDate( + Dialogs::Key(peer->owner().history(peer)), + base::unixtime::parse(joinDate.toULongLong()).date(), + [=](not_null p, MsgId m) { + const auto f = Window::SectionShow::Way::Forward; + if (const auto strong = weak.get()) { + strong->showPeerHistory(p, f, m); + } + }); + return false; + } } else if (SetClickContext(handler, context)) { return false; } @@ -1386,8 +1432,8 @@ object_ptr DetailsFiller::setupInfo() { ? tr::lng_info_about_label() : tr::lng_info_bio_label(); addTranslateToMenu( - addInfoLine(std::move(label), AboutWithIdValue(user)).text, - AboutWithIdValue(user)); + addInfoLine(std::move(label), AboutWithAdvancedValue(user)).text, + AboutWithAdvancedValue(user)); const auto usernameLine = addInfoOneLine( UsernamesSubtext(_peer, tr::lng_info_username_label()), @@ -1517,9 +1563,9 @@ object_ptr DetailsFiller::setupInfo() { const auto about = addInfoLine(tr::lng_info_about_label(), _topic ? rpl::single(TextWithEntities()) - : AboutWithIdValue(_peer)); + : AboutWithAdvancedValue(_peer)); if (!_topic) { - addTranslateToMenu(about.text, AboutWithIdValue(_peer)); + addTranslateToMenu(about.text, AboutWithAdvancedValue(_peer)); } } if (!_peer->isSelf()) { @@ -2641,6 +2687,7 @@ object_ptr ActionsFiller::fill() { } // namespace const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about"; +const char kOptionShowChannelJoinedBelowAbout[] = "show-channel-joined-below-about"; object_ptr SetupDetails( not_null controller, diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.h b/Telegram/SourceFiles/info/profile/info_profile_actions.h index 078ce07204..584f2d7f86 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.h +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.h @@ -25,6 +25,7 @@ class Controller; namespace Info::Profile { extern const char kOptionShowPeerIdBelowAbout[]; +extern const char kOptionShowChannelJoinedBelowAbout[]; class Cover; struct Origin; diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index ef67935a64..7b04129a2a 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -147,6 +147,7 @@ void SetupExperimental( addToggle(Core::kOptionFractionalScalingEnabled); addToggle(Window::kOptionViewProfileInChatsListContextMenu); addToggle(Info::Profile::kOptionShowPeerIdBelowAbout); + addToggle(Info::Profile::kOptionShowChannelJoinedBelowAbout); addToggle(Ui::kOptionUseSmallMsgBubbleRadius); addToggle(Media::Player::kOptionDisableAutoplayNext); addToggle(kOptionSendLargePhotos); From 69e7c27fd04c5aa97e26985de240c38c2244644d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 13 Apr 2025 12:18:21 +0300 Subject: [PATCH 061/190] Added premium grace suggestion to top bar in dialogs. --- Telegram/Resources/langs/lang.strings | 2 ++ .../dialogs/dialogs_top_bar_suggestion.cpp | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 541a65a91b..f8dab61e2e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3879,6 +3879,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_suggestions_premium_upgrade_about" = "Upgrade to the annual payment plan for Telegram Premium now to get the discount."; "lng_dialogs_suggestions_premium_restore_title" = "Get Premium back with up to {text} off"; "lng_dialogs_suggestions_premium_restore_about" = "Your Telegram Premium has recently expired. Tap here to extend it."; +"lng_dialogs_suggestions_premium_grace_title" = "⚠️ Your Premium subscription is expiring!"; +"lng_dialogs_suggestions_premium_grace_about" = "Don’t lose access to exclusive features."; "lng_dialogs_suggestions_userpics_title" = "Add your photo! 📸"; "lng_dialogs_suggestions_userpics_about" = "Help your friends spot you easily."; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index 9b5f448e68..ca0c3b703d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -50,6 +50,7 @@ constexpr auto kSugBirthdayContacts = "BIRTHDAY_CONTACTS_TODAY"_cs; constexpr auto kSugPremiumAnnual = "PREMIUM_ANNUAL"_cs; constexpr auto kSugPremiumUpgrade = "PREMIUM_UPGRADE"_cs; constexpr auto kSugPremiumRestore = "PREMIUM_RESTORE"_cs; +constexpr auto kSugPremiumGrace = "PREMIUM_GRACE"_cs; constexpr auto kSugSetUserpic = "USERPIC_SETUP"_cs; } // namespace @@ -107,6 +108,34 @@ rpl::producer*> TopBarSuggestionValue( using RightIcon = TopBarSuggestionContent::RightIcon; const auto config = &session->appConfig(); if (session->premiumCanBuy() + && config->suggestionCurrent(kSugPremiumGrace.utf8())) { + content->setRightIcon(RightIcon::Close); + content->setClickedCallback([=] { + const auto controller = FindSessionController(parent); + if (!controller) { + return; + } + UrlClickHandler::Open( + u"https://t.me/premiumbot?start=status"_q, + QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = base::make_weak(controller), + })); + }); + content->setHideCallback([=] { + config->dismissSuggestion(kSugPremiumGrace.utf8()); + repeat(repeat); + }); + content->setContent( + tr::lng_dialogs_suggestions_premium_grace_title( + tr::now, + Ui::Text::Bold), + tr::lng_dialogs_suggestions_premium_grace_about( + tr::now, + TextWithEntities::Simple)); + state->desiredWrapToggle.force_assign( + Toggle{ true, anim::type::normal }); + return; + } else if (session->premiumCanBuy() && config->suggestionCurrent(kSugBirthdayContacts.utf8())) { session->data().contactBirthdays( ) | rpl::start_with_next(crl::guard(content, [=]( From 5886a073003e030e411bbe6d2a1839ca3a87eb80 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 13 Apr 2025 12:32:16 +0300 Subject: [PATCH 062/190] Fixed ability to renew credits subscription. --- Telegram/SourceFiles/settings/settings_credits_graphics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 8b9a08cd95..d5ab4d1712 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -1871,7 +1871,7 @@ void GenericCreditsEntryBox( : tr::lng_gift_show_on_page)() : tr::lng_box_ok())); const auto send = [=, weak = Ui::MakeWeak(box)] { - if (toRejoin) { + if (toRejoin && !toRenew) { if (const auto window = show->resolveWindow()) { const auto finish = [=](Payments::CheckoutResult&&) { ProcessReceivedSubscriptions(weak, session); From eb3419f3fc97b47f267ad9d2cd062fad1c28d9ff Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 13 Apr 2025 13:42:27 +0300 Subject: [PATCH 063/190] Added ability to request credits subscriptions with missing balance. --- Telegram/SourceFiles/api/api_credits.cpp | 7 +++++-- Telegram/SourceFiles/api/api_credits.h | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 0dc21c6361..2c06385f6d 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -348,12 +348,15 @@ void CreditsHistory::request( void CreditsHistory::requestSubscriptions( const Data::CreditsStatusSlice::OffsetToken &token, - Fn done) { + Fn done, + bool missingBalance) { if (_requestId) { return; } _requestId = _api.request(MTPpayments_GetStarsSubscriptions( - MTP_flags(0), + MTP_flags(missingBalance + ? MTPpayments_getStarsSubscriptions::Flag::f_missing_balance + : MTPpayments_getStarsSubscriptions::Flags(0)), _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input, MTP_string(token) )).done([=](const MTPpayments_StarsStatus &result) { diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index c7005073e8..c9c87b2e8e 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -82,7 +82,8 @@ public: Fn done); void requestSubscriptions( const Data::CreditsStatusSlice::OffsetToken &token, - Fn done); + Fn done, + bool missingBalance = false); private: using HistoryTL = MTPpayments_GetStarsTransactions; From f34835463d4ddf1a32e6636402f8292b963061bf Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 13 Apr 2025 18:53:37 +0300 Subject: [PATCH 064/190] Added initial ability to create credit icon as non-session custom emoji. --- .../ui/effects/credits_graphics.cpp | 56 +++++++++++++++++++ .../SourceFiles/ui/effects/credits_graphics.h | 8 +++ 2 files changed, 64 insertions(+) diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp index 0fbf095cce..64f21566e3 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/empty_userpic.h" #include "ui/painter.h" #include "ui/rect.h" +#include "ui/text/text_custom_emoji.h" #include "ui/widgets/fields/number_input.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -42,6 +43,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Ui { namespace { +class CreditsIconEmoji final : public Ui::Text::CustomEmoji { +public: + CreditsIconEmoji(int height, int count); + + int width() override; + QString entityData() override; + + void paint(QPainter &p, const Context &context) override; + void unload() override; + bool ready() override; + bool readyInDefaultState() override; + +private: + const int _height; + const int _count; + QImage _image; + +}; + +CreditsIconEmoji::CreditsIconEmoji(int height, int count) +: _height(height) +, _count(count) +, _image(GenerateStars(height, count)) { +} + +int CreditsIconEmoji::width() { + return _image.width() / style::DevicePixelRatio(); +} + +QString CreditsIconEmoji::entityData() { + return u"credits_icon:%1:%2"_q.arg(_height).arg(_count); +} + +void CreditsIconEmoji::paint(QPainter &p, const Context &context) { + p.drawImage(context.position, _image); +} + +void CreditsIconEmoji::unload() { + _image = QImage(); +} + +bool CreditsIconEmoji::ready() { + return true; +} + +bool CreditsIconEmoji::readyInDefaultState() { + return true; +} + PaintRoundImageCallback MultiThumbnail( PaintRoundImageCallback first, PaintRoundImageCallback second, @@ -678,4 +728,10 @@ QImage CreditsWhiteDoubledIcon(int size, float64 outlineRatio) { return result; } +std::unique_ptr MakeCreditsIconEmoji( + int height, + int count) { + return std::make_unique(height, count); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/credits_graphics.h b/Telegram/SourceFiles/ui/effects/credits_graphics.h index a304a9ff5f..022c7cac80 100644 --- a/Telegram/SourceFiles/ui/effects/credits_graphics.h +++ b/Telegram/SourceFiles/ui/effects/credits_graphics.h @@ -19,6 +19,10 @@ namespace Main { class Session; } // namespace Main +namespace Ui::Text { +class CustomEmoji; +} // namespace Ui::Text + namespace Ui { class MaskedInputField; @@ -82,4 +86,8 @@ Fn PaintOutlinedColoredCreditsIconCallback( [[nodiscard]] QImage CreditsWhiteDoubledIcon(int size, float64 outlineRatio); +std::unique_ptr MakeCreditsIconEmoji( + int height, + int count); + } // namespace Ui From 81da453ec4c0fc548123d1626767cdd5d46a483c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 13 Apr 2025 13:42:21 +0300 Subject: [PATCH 065/190] Added suggestion to top-up low credits balance for subscriptions. --- Telegram/Resources/langs/lang.strings | 3 + .../dialogs/dialogs_top_bar_suggestion.cpp | 82 +++++++++++++++++++ .../ui/dialogs_top_bar_suggestion_content.cpp | 32 +++++++- .../ui/dialogs_top_bar_suggestion_content.h | 3 +- 4 files changed, 116 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f8dab61e2e..231f2e352d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3883,6 +3883,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_suggestions_premium_grace_about" = "Don’t lose access to exclusive features."; "lng_dialogs_suggestions_userpics_title" = "Add your photo! 📸"; "lng_dialogs_suggestions_userpics_about" = "Help your friends spot you easily."; +"lng_dialogs_suggestions_credits_sub_low_title#one" = "{emoji} {count} Star needed for {channels}"; +"lng_dialogs_suggestions_credits_sub_low_title#other" = "{emoji} {count} Stars needed for {channels}"; +"lng_dialogs_suggestions_credits_sub_low_about" = "Insufficient funds to cover your subscription."; "lng_about_random" = "Send a {emoji} emoji to any chat to try your luck."; "lng_about_random_send" = "Send"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp index ca0c3b703d..14ff8c2795 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_top_bar_suggestion.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_top_bar_suggestion.h" +#include "api/api_credits.h" #include "api/api_peer_photo.h" #include "api/api_premium.h" #include "apiwrap.h" @@ -24,8 +25,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "settings/settings_credits_graphics.h" #include "settings/settings_premium.h" #include "ui/controls/userpic_button.h" +#include "ui/layers/generic_box.h" +#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/ui_utility.h" #include "ui/wrap/slide_wrap.h" @@ -52,6 +56,7 @@ constexpr auto kSugPremiumUpgrade = "PREMIUM_UPGRADE"_cs; constexpr auto kSugPremiumRestore = "PREMIUM_RESTORE"_cs; constexpr auto kSugPremiumGrace = "PREMIUM_GRACE"_cs; constexpr auto kSugSetUserpic = "USERPIC_SETUP"_cs; +constexpr auto kSugLowCreditsSubs = "STARS_SUBSCRIPTION_LOW_BALANCE"_cs; } // namespace @@ -77,6 +82,8 @@ rpl::producer*> TopBarSuggestionValue( rpl::lifetime premiumLifetime; rpl::lifetime userpicLifetime; rpl::lifetime giftsLifetime; + rpl::lifetime creditsLifetime; + std::unique_ptr creditsHistory; }; const auto state = lifetime.make_state(); @@ -108,6 +115,81 @@ rpl::producer*> TopBarSuggestionValue( using RightIcon = TopBarSuggestionContent::RightIcon; const auto config = &session->appConfig(); if (session->premiumCanBuy() + && config->suggestionCurrent(kSugLowCreditsSubs.utf8())) { + state->creditsHistory = std::make_unique( + session->user(), + false, + false); + const auto show = [=]( + const QString &peers, + uint64 needed, + uint64 whole) { + content->setRightIcon(RightIcon::Close); + content->setClickedCallback([=] { + const auto controller = FindSessionController(parent); + if (!controller) { + return; + } + controller->uiShow()->show(Box( + Settings::SmallBalanceBox, + controller->uiShow(), + needed, + Settings::SmallBalanceSubscription{ peers }, + [=] { + config->dismissSuggestion( + kSugLowCreditsSubs.utf8()); + repeat(repeat); + })); + }); + content->setHideCallback([=] { + config->dismissSuggestion(kSugLowCreditsSubs.utf8()); + repeat(repeat); + }); + content->setContent( + tr::lng_dialogs_suggestions_credits_sub_low_title( + tr::now, + lt_count, + float64(needed - whole), + lt_emoji, + Ui::Text::SingleCustomEmoji(Ui::kCreditsCurrency), + lt_channels, + { peers }, + Ui::Text::Bold), + tr::lng_dialogs_suggestions_credits_sub_low_about( + tr::now, + TextWithEntities::Simple), + true); + state->desiredWrapToggle.force_assign( + Toggle{ true, anim::type::normal }); + }; + session->credits().load(); + state->creditsLifetime.destroy(); + session->credits().balanceValue() | rpl::start_with_next([=] { + state->creditsLifetime.destroy(); + state->creditsHistory->requestSubscriptions( + Data::CreditsStatusSlice::OffsetToken(), + [=](Data::CreditsStatusSlice slice) { + state->creditsHistory = nullptr; + auto peers = QStringList(); + auto credits = uint64(0); + for (const auto &entry : slice.subscriptions) { + if (entry.barePeerId) { + const auto peer = session->data().peer( + PeerId(entry.barePeerId)); + peers.append(peer->name()); + credits += entry.subscription.credits; + } + } + show( + peers.join(", "), + credits, + session->credits().balance().whole()); + }, + true); + }, state->creditsLifetime); + + return; + } else if (session->premiumCanBuy() && config->suggestionCurrent(kSugPremiumGrace.utf8())) { content->setRightIcon(RightIcon::Close); content->setClickedCallback([=] { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp index 6444f2647b..d08ffae49c 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.cpp @@ -6,6 +6,8 @@ For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/ui/dialogs_top_bar_suggestion_content.h" +#include "ui/effects/credits_graphics.h" +#include "ui/text/text_custom_emoji.h" #include "ui/ui_rpl_filter.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" @@ -140,9 +142,33 @@ void TopBarSuggestionContent::draw(QPainter &p) { void TopBarSuggestionContent::setContent( TextWithEntities title, - TextWithEntities description) { - _contentTitle.setMarkedText(_contentTitleSt, std::move(title)); - _contentText.setMarkedText(_contentTextSt, std::move(description)); + TextWithEntities description, + bool makeContext) { + if (makeContext) { + auto customEmojiFactory = [=, h = _contentTitleSt.font->height]( + QStringView data, + const Ui::Text::MarkedContext &context + ) -> std::unique_ptr { + return Ui::MakeCreditsIconEmoji(h, 1); + }; + const auto context = Ui::Text::MarkedContext{ + .repaint = [=] { update(); }, + .customEmojiFactory = std::move(customEmojiFactory), + }; + _contentTitle.setMarkedText( + _contentTitleSt, + std::move(title), + kMarkupTextOptions, + context); + _contentText.setMarkedText( + _contentTextSt, + std::move(description), + kMarkupTextOptions, + context); + } else { + _contentTitle.setMarkedText(_contentTitleSt, std::move(title)); + _contentText.setMarkedText(_contentTextSt, std::move(description)); + } } void TopBarSuggestionContent::paintEvent(QPaintEvent *) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h index 82b2f3b9f6..39aa4f997b 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_top_bar_suggestion_content.h @@ -28,7 +28,8 @@ public: void setContent( TextWithEntities title, - TextWithEntities description); + TextWithEntities description, + bool makeContext = false); [[nodiscard]] rpl::producer desiredHeightValue() const override; From 6008d9e12ba877de5f6964752f93508d9b85fa70 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 13 Apr 2025 20:53:21 +0300 Subject: [PATCH 066/190] Added birthday sublist to contact list in gift box. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/boxes/star_gift_box.cpp | 229 +++++++++++++++---- 2 files changed, 190 insertions(+), 42 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 231f2e352d..f1089b24c1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3455,6 +3455,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_show_on_channel" = "Display in channel's Gifts"; "lng_gift_availability" = "Availability"; "lng_gift_from_hidden" = "Hidden User"; +"lng_gift_subtitle_birthdays" = "Birthdays"; +"lng_gift_list_birthday_status_today" = "{emoji} Birthday today"; +"lng_gift_list_birthday_status_yesterday" = "Birthday yesterday"; "lng_gift_self_status" = "buy yourself a gift"; "lng_gift_self_title" = "Buy a Gift"; "lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later."; diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index e20967b2e9..092fa9c2d2 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "core/ui_integration.h" +#include "data/data_birthday.h" #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_credits.h" @@ -2257,10 +2258,11 @@ void GiftBox( } } -struct SelfOption { +struct CustomList { object_ptr content = { nullptr }; Fn overrideKey; Fn activate; + Fn hasSelection; }; class Controller final : public ContactsBoxController { @@ -2284,32 +2286,39 @@ private: void rowClicked(not_null row) override; const Fn)> _choose; - SelfOption _selfOption; + const std::vector _contactBirthdays; + CustomList _selfOption; + CustomList _birthdayOptions; + + bool _skipUpDirectionSelect = false; }; -[[nodiscard]] SelfOption MakeSelfOption( +[[nodiscard]] CustomList MakeCustomList( not_null session, - Fn activate) { + Fn)> fill, + Fn)> activate, + rpl::producer below) { class SelfController final : public PeerListController { public: SelfController( not_null session, - Fn activate) + Fn)> fill, + Fn)> activate) : _session(session) - , _activate(std::move(activate)) { + , _activate(std::move(activate)) + , _fill(std::move(fill)) { } void prepare() override { - auto row = std::make_unique(_session->user()); - row->setCustomStatus(tr::lng_gift_self_status(tr::now)); - delegate()->peerListAppendRow(std::move(row)); - delegate()->peerListRefreshRows(); + if (_fill) { + _fill(this); + } } void loadMoreRows() override { } void rowClicked(not_null row) override { - _activate(); + _activate(row->peer()); } Main::Session &session() const override { return *_session; @@ -2317,7 +2326,8 @@ private: private: const not_null _session; - Fn _activate; + Fn)> _activate; + Fn)> _fill; }; @@ -2329,9 +2339,12 @@ private: const auto delegate = container->lifetime().make_state< PeerListContentDelegateSimple >(); - const auto controller = container->lifetime().make_state< - SelfController - >(session, activate); + const auto controller + = container->lifetime().make_state( + session, + fill, + activate); + controller->setStyleOverrides(&st::peerListSingleRow); const auto content = container->add(object_ptr( container, @@ -2339,10 +2352,12 @@ private: delegate->setContent(content); controller->setDelegate(delegate); - Ui::AddSkip(container); - container->add(CreatePeerListSectionSubtitle( - container, - tr::lng_contacts_header())); + if (below) { + Ui::AddSkip(container); + container->add(CreatePeerListSectionSubtitle( + container, + std::move(below))); + } const auto overrideKey = [=](int direction, int from, int to) { if (!content->isVisible()) { @@ -2368,11 +2383,19 @@ private: } return false; }; + const auto hasSelection = [=] { + return content->isVisible() && content->hasSelection(); + }; return { .content = std::move(result), .overrideKey = overrideKey, - .activate = activate, + .activate = [=] { + if (content->hasSelection()) { + activate(content->rowAt(content->selectedIndex())->peer()); + } + }, + .hasSelection = hasSelection, }; } @@ -2381,7 +2404,73 @@ Controller::Controller( Fn)> choose) : ContactsBoxController(session) , _choose(std::move(choose)) -, _selfOption(MakeSelfOption(session, [=] { _choose(session->user()); })) { +, _contactBirthdays( + session->data().knownContactBirthdays().value_or(std::vector{})) +, _selfOption( + MakeCustomList( + session, + [=](not_null controller) { + auto row = std::make_unique(session->user()); + row->setCustomStatus(tr::lng_gift_self_status(tr::now)); + controller->delegate()->peerListAppendRow(std::move(row)); + controller->delegate()->peerListRefreshRows(); + }, + [=](not_null peer) { _choose(peer); }, + _contactBirthdays.empty() + ? tr::lng_contacts_header() + : tr::lng_gift_subtitle_birthdays())) +, _birthdayOptions( + MakeCustomList( + session, + [=](not_null controller) { + const auto status = [=](const Data::Birthday &date) { + if (Data::IsBirthdayToday(date)) { + return tr::lng_gift_list_birthday_status_today( + tr::now, + lt_emoji, + Data::BirthdayCake()); + } + const auto yesterday = QDate::currentDate().addDays(-1); + return (date.day() == yesterday.day() + && date.month() == yesterday.month()) + ? tr::lng_gift_list_birthday_status_yesterday(tr::now) + : QString(); + }; + + auto usersWithBirthdays = ranges::views::all( + _contactBirthdays + ) | ranges::views::transform([&](UserId userId) { + return session->data().user(userId); + }) | ranges::to_vector; + + ranges::sort(usersWithBirthdays, [](UserData *a, UserData *b) { + const auto aBirthday = a->birthday(); + const auto bBirthday = b->birthday(); + const auto aIsToday = Data::IsBirthdayToday(aBirthday); + const auto bIsToday = Data::IsBirthdayToday(bBirthday); + if (aIsToday != bIsToday) { + return aIsToday > bIsToday; + } + if (aBirthday.month() != bBirthday.month()) { + return aBirthday.month() < bBirthday.month(); + } + return aBirthday.day() < bBirthday.day(); + }); + + for (const auto user : usersWithBirthdays) { + auto row = std::make_unique(user); + if (auto s = status(user->birthday()); !s.isEmpty()) { + row->setCustomStatus(std::move(s)); + } + controller->delegate()->peerListAppendRow(std::move(row)); + } + + controller->delegate()->peerListRefreshRows(); + }, + [=](not_null peer) { _choose(peer); }, + _contactBirthdays.empty() + ? rpl::producer(nullptr) + : tr::lng_contacts_header())) { setStyleOverrides(&st::peerListSmallSkips); } @@ -2389,18 +2478,65 @@ void Controller::noSearchSubmit() { if (const auto onstack = _selfOption.activate) { onstack(); } + if (const auto onstack = _birthdayOptions.activate) { + onstack(); + } } bool Controller::overrideKeyboardNavigation( int direction, - int fromIndex, - int toIndex) { - return _selfOption.overrideKey - && _selfOption.overrideKey(direction, fromIndex, toIndex); + int from, + int to) { + if (direction == -1 && from == -1 && to == -1 && _skipUpDirectionSelect) { + return true; + } + _skipUpDirectionSelect = false; + if (direction > 0) { + if (!_selfOption.hasSelection() && !_birthdayOptions.hasSelection()) { + return _selfOption.overrideKey(direction, from, to); + } + if (_selfOption.hasSelection() && !_birthdayOptions.hasSelection()) { + if (_selfOption.overrideKey(direction, from, to)) { + return true; + } else { + return _birthdayOptions.overrideKey(direction, from, to); + } + } + if (!_selfOption.hasSelection() && _birthdayOptions.hasSelection()) { + if (_birthdayOptions.overrideKey(direction, from, to)) { + return true; + } + } + } else if (direction < 0) { + if (!_selfOption.hasSelection() && !_birthdayOptions.hasSelection()) { + return _birthdayOptions.overrideKey(direction, from, to); + } + if (!_selfOption.hasSelection() && _birthdayOptions.hasSelection()) { + if (_birthdayOptions.overrideKey(direction, from, to)) { + return true; + } else if (!_birthdayOptions.hasSelection()) { + const auto res = _selfOption.overrideKey(direction, from, to); + _skipUpDirectionSelect = _selfOption.hasSelection(); + return res; + } + } + if (_selfOption.hasSelection() && !_birthdayOptions.hasSelection()) { + if (_selfOption.overrideKey(direction, from, to)) { + _skipUpDirectionSelect = _selfOption.hasSelection(); + return true; + } + } + } + return false; } std::unique_ptr Controller::createRow( not_null user) { + if (const auto birthday = user->owner().knownContactBirthdays()) { + if (ranges::contains(*birthday, peerToUser(user->id))) { + return nullptr; + } + } if (user->isSelf() || user->isBot() || user->isServiceUser() @@ -2411,7 +2547,10 @@ std::unique_ptr Controller::createRow( } void Controller::prepareViewHook() { - delegate()->peerListSetAboveWidget(std::move(_selfOption.content)); + auto list = object_ptr((QWidget*)nullptr); + list->add(std::move(_selfOption.content)); + list->add(std::move(_birthdayOptions.content)); + delegate()->peerListSetAboveWidget(std::move(list)); } void Controller::rowClicked(not_null row) { @@ -2422,23 +2561,29 @@ void Controller::rowClicked(not_null row) { void ChooseStarGiftRecipient( not_null window) { - auto controller = std::make_unique( - &window->session(), - [=](not_null peer) { - ShowStarGiftBox(window, peer); - }); - const auto controllerRaw = controller.get(); - auto initBox = [=](not_null box) { - box->setTitle(tr::lng_gift_premium_or_stars()); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + const auto session = &window->session(); + const auto lifetime = std::make_shared(); + session->data().contactBirthdays( + ) | rpl::start_with_next(crl::guard(session, [=] { + lifetime->destroy(); + auto controller = std::make_unique( + session, + [=](not_null peer) { + ShowStarGiftBox(window, peer); + }); + const auto controllerRaw = controller.get(); + auto initBox = [=](not_null box) { + box->setTitle(tr::lng_gift_premium_or_stars()); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); - box->noSearchSubmits() | rpl::start_with_next([=] { - controllerRaw->noSearchSubmit(); - }, box->lifetime()); - }; - window->show( - Box(std::move(controller), std::move(initBox)), - LayerOption::KeepOther); + box->noSearchSubmits() | rpl::start_with_next([=] { + controllerRaw->noSearchSubmit(); + }, box->lifetime()); + }; + window->show( + Box(std::move(controller), std::move(initBox)), + LayerOption::KeepOther); + }), *lifetime); } void ShowStarGiftBox( From 8e643fbf87ead3e9474c75257264b5665f5ef9a9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Mar 2025 21:08:26 +0400 Subject: [PATCH 067/190] Add tde2e dependency. --- Telegram/CMakeLists.txt | 2 ++ Telegram/SourceFiles/tde2e/tde2e_api.cpp | 22 +++++++++++++++++ Telegram/SourceFiles/tde2e/tde2e_api.h | 9 +++++++ Telegram/cmake/td_tde2e.cmake | 30 ++++++++++++++++++++++++ cmake | 2 +- 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 Telegram/SourceFiles/tde2e/tde2e_api.cpp create mode 100644 Telegram/SourceFiles/tde2e/tde2e_api.h create mode 100644 Telegram/cmake/td_tde2e.cmake diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5b918517c2..d72a22e179 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -34,6 +34,7 @@ include(cmake/td_iv.cmake) include(cmake/td_lang.cmake) include(cmake/td_mtproto.cmake) include(cmake/td_scheme.cmake) +include(cmake/td_tde2e.cmake) include(cmake/td_ui.cmake) include(cmake/generate_appdata_changelog.cmake) @@ -69,6 +70,7 @@ PRIVATE tdesktop::td_lang tdesktop::td_mtproto tdesktop::td_scheme + tdesktop::td_tde2e tdesktop::td_ui desktop-app::lib_webrtc desktop-app::lib_base diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp new file mode 100644 index 0000000000..109536ed33 --- /dev/null +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -0,0 +1,22 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "tde2e/tde2e_api.h" + +#include "base/algorithm.h" + +#include + +namespace TdE2E { + +QByteArray GeneratePrivateKey() { + const auto result = tde2e_api::key_generate_temporary_private_key(); + + return {}; +} + +} // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h new file mode 100644 index 0000000000..73e0063250 --- /dev/null +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -0,0 +1,9 @@ +/* +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 + diff --git a/Telegram/cmake/td_tde2e.cmake b/Telegram/cmake/td_tde2e.cmake new file mode 100644 index 0000000000..d15512c647 --- /dev/null +++ b/Telegram/cmake/td_tde2e.cmake @@ -0,0 +1,30 @@ +# This file is part of Telegram Desktop, +# the official desktop application for the Telegram messaging service. +# +# For license and copyright information please follow this link: +# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL + +add_library(td_tde2e OBJECT) +init_non_host_target(td_tde2e) +add_library(tdesktop::td_tde2e ALIAS td_tde2e) + +nice_target_sources(td_tde2e ${src_loc} +PRIVATE + tde2e/tde2e_api.cpp + tde2e/tde2e_api.h +) + +target_include_directories(td_tde2e +PUBLIC + ${src_loc} +) + +target_link_libraries(td_tde2e +PUBLIC + desktop-app::lib_base +PRIVATE + desktop-app::external_td +) + + + diff --git a/cmake b/cmake index 37b57a8dbc..fb157ccf8c 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 37b57a8dbca70b0a259e552b4c0793485268cbae +Subproject commit fb157ccf8c1db1727f6c098517178e0c37887304 From 214cc83d4aa4bd91b6982d9c3b382dee9f729ec2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 Mar 2025 21:58:06 +0400 Subject: [PATCH 068/190] Update API scheme to layer 202. --- Telegram/Resources/langs/lang.strings | 8 ++++ Telegram/SourceFiles/calls/calls_instance.cpp | 2 + .../calls/group/calls_group_call.cpp | 16 ++++++-- .../calls/group/calls_group_common.cpp | 19 ++++++++++ .../calls/group/calls_group_common.h | 8 ++++ .../SourceFiles/core/local_url_handlers.cpp | 22 +++++++++++ Telegram/SourceFiles/data/data_channel.cpp | 2 + Telegram/SourceFiles/data/data_chat.cpp | 2 + Telegram/SourceFiles/data/data_group_call.cpp | 2 +- Telegram/SourceFiles/data/data_group_call.h | 4 +- .../export/data/export_data_types.cpp | 6 +++ .../export/data/export_data_types.h | 9 ++++- .../export/output/export_output_html.cpp | 10 +++++ .../export/output/export_output_json.cpp | 6 +++ Telegram/SourceFiles/history/history_item.cpp | 14 +++++++ .../history/history_item_helpers.cpp | 2 + Telegram/SourceFiles/mtproto/scheme/api.tl | 19 +++++++--- .../SourceFiles/ui/image/image_location.cpp | 2 + .../window/window_session_controller.cpp | 37 ++++++++++++++++++- .../window/window_session_controller.h | 4 ++ 20 files changed, 179 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f1089b24c1..53ac624a08 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2220,6 +2220,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_message_price_paid#other" = "Messages now cost {count} Stars each in this group."; "lng_you_paid_stars#one" = "You paid {count} Star."; "lng_you_paid_stars#other" = "You paid {count} Stars."; +"lng_action_confcall_invitation" = "Call invitation..."; +"lng_action_confcall_missed" = "Missed conference call"; +"lng_action_confcall_ongoing" = "Ongoing conference call"; +"lng_action_confcall_finished" = "Conference call"; "lng_you_joined_group" = "You joined this group"; @@ -4904,6 +4908,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_rtmp_viewers#one" = "{count} viewer"; "lng_group_call_rtmp_viewers#other" = "{count} viewers"; +"lng_confcall_join_title" = "Group Call"; +"lng_confcall_join_text" = "You are invited to join a Telegram Call."; +"lng_confcall_join_button" = "Join Group Call"; + "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; "lng_player_message_today" = "today at {time}"; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 02c5fb1444..65dc10fb39 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -632,6 +632,8 @@ void Instance::handleGroupCallUpdate( }, [](const MTPDupdateGroupCallParticipants &data) { return data.vcall().match([&](const MTPDinputGroupCall &data) { return data.vid().v; + }, [](const MTPDinputGroupCallSlug &) -> CallId { + Unexpected("slug in Instance::handleGroupCallUpdate"); }); }, [](const auto &) -> CallId { Unexpected("Type in Instance::handleGroupCallUpdate."); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 8c1dbb4fee..ab1b80117c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1091,6 +1091,8 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { inputCall.match([&](const MTPDinputGroupCall &data) { _id = data.vid().v; _accessHash = data.vaccess_hash().v; + }, [&](const MTPDinputGroupCallSlug &) { + Unexpected("inputGroupCallSlug in GroupCall::join."); }); setState(_scheduleDate ? State::Waiting : State::Joining); @@ -1377,7 +1379,8 @@ void GroupCall::rejoin(not_null as) { inputCall(), joinAs()->input, MTP_string(_joinHash), - MTPlong(), // key_fingerprint + MTPint256(), // public_key + MTPint(), // invite_msg_id MTP_dataJSON(MTP_bytes(json)) )).done([=]( const MTPUpdates &updates, @@ -1607,7 +1610,8 @@ void GroupCall::applyMeInCallLocally() { MTPstring(), // Don't update about text in local updates. MTP_long(raisedHandRating), MTPGroupCallParticipantVideo(), - MTPGroupCallParticipantVideo())), + MTPGroupCallParticipantVideo(), + AssertIsDebug() MTPint256())), // public_key MTP_int(0)).c_updateGroupCallParticipants()); } @@ -1654,7 +1658,8 @@ void GroupCall::applyParticipantLocally( MTPstring(), // Don't update about text in local updates. MTP_long(participant->raisedHandRating), MTPGroupCallParticipantVideo(), - MTPGroupCallParticipantVideo())), + MTPGroupCallParticipantVideo(), + AssertIsDebug() MTPint256())), // public_key MTP_int(0)).c_updateGroupCallParticipants()); } @@ -1966,8 +1971,11 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCall &data) { } void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) { - const auto callId = data.vcall().match([](const auto &data) { + const auto callId = data.vcall().match([]( + const MTPDinputGroupCall &data) { return data.vid().v; + }, [](const MTPDinputGroupCallSlug &) -> CallId { + Unexpected("inputGroupCallSlug in GroupCall::handleUpdate."); }); if (_id != callId) { return; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 487629893f..75e6de992f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -50,4 +50,23 @@ object_ptr ScreenSharingPrivacyRequestBox() { #endif // Q_OS_MAC } +void ConferenceCallJoinConfirm( + not_null box, + std::shared_ptr call) { + box->setTitle(tr::lng_confcall_join_title()); + + box->addRow( + object_ptr( + box, + tr::lng_confcall_join_text(), + st::boxLabel)); + + box->addButton(tr::lng_confcall_join_button(), [=] { + + }); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); +} + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 670378aa74..114c43e6d5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class UserData; +namespace Data { +class GroupCall; +} // namespace Data + namespace Ui { class GenericBox; } // namespace Ui @@ -93,4 +97,8 @@ using StickedTooltips = base::flags; [[nodiscard]] object_ptr ScreenSharingPrivacyRequestBox(); +void ConferenceCallJoinConfirm( + not_null box, + std::shared_ptr call); + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 76a4c1aba7..f689603ec9 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -1448,6 +1448,21 @@ bool ResolveUniqueGift( return true; } +bool ResolveConferenceCall( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto slug = match->captured(1); + if (slug.isEmpty()) { + return false; + } + controller->window().activate(); + controller->resolveConferenceCall(match->captured(1)); + return true; +} } // namespace const std::vector &LocalUrlHandlers() { @@ -1544,6 +1559,10 @@ const std::vector &LocalUrlHandlers() { u"^nft/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q, ResolveUniqueGift }, + { + u"^call/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q, + ResolveConferenceCall + }, { u"^([^\\?]+)(\\?|#|$)"_q, HandleUnknown @@ -1704,6 +1723,9 @@ QString TryConvertUrlToLocal(QString url) { } else if (const auto nftMatch = regex_match(u"^nft/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) { const auto slug = nftMatch->captured(1); return u"tg://nft?slug="_q + slug; + } else if (const auto callMatch = regex_match(u"^call/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) { + const auto slug = callMatch->captured(1); + return u"tg://call?slug="_q + slug; } else if (const auto privateMatch = regex_match(u"^" "c/(\\-?\\d+)" "(" diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index a4f4092e8b..d1a99f90b3 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1005,6 +1005,8 @@ void ChannelData::setGroupCall( owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); + }, [&](const MTPDinputGroupCallSlug &) { + clearGroupCall(); }); } diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 6f285e805f..faea166b1d 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -238,6 +238,8 @@ void ChatData::setGroupCall( owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); + }, [&](const MTPDinputGroupCallSlug &) { + clearGroupCall(); }); } diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 0cc71299bb..c30eaeebaa 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -60,7 +60,7 @@ bool GroupCallParticipant::screenPaused() const { GroupCall::GroupCall( not_null peer, CallId id, - CallId accessHash, + uint64 accessHash, TimeId scheduleDate, bool rtmp) : _id(id) diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 6dbaf8debe..8b7d16158d 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -56,7 +56,7 @@ public: GroupCall( not_null peer, CallId id, - CallId accessHash, + uint64 accessHash, TimeId scheduleDate, bool rtmp); ~GroupCall(); @@ -201,7 +201,7 @@ private: [[nodiscard]] Participant *findParticipant(not_null peer); const CallId _id = 0; - const CallId _accessHash = 0; + const uint64 _accessHash = 0; not_null _peer; int _version = 0; diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 830e64e907..e9a91b434f 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1713,6 +1713,12 @@ ServiceAction ParseServiceAction( result.content = ActionPaidMessagesPrice{ .stars = int(data.vstars().v), }; + }, [&](const MTPDmessageActionConferenceCall &data) { + result.content = ActionConferenceCall{ + .duration = data.vduration().value_or_empty(), + .active = data.is_active(), + .missed = data.is_missed(), + }; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index a7518ba056..afdf7251bf 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -671,6 +671,12 @@ struct ActionPaidMessagesPrice { int stars = 0; }; +struct ActionConferenceCall { + int duration = 0; + bool active = false; + bool missed = false; +}; + struct ServiceAction { std::variant< v::null_t, @@ -718,7 +724,8 @@ struct ServiceAction { ActionPrizeStars, ActionStarGift, ActionPaidMessagesRefunded, - ActionPaidMessagesPrice> content; + ActionPaidMessagesPrice, + ActionConferenceCall> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 7aada39659..29f7d734a7 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1387,6 +1387,16 @@ auto HtmlWriter::Wrap::pushMessage( + QString::number(data.stars).toUtf8() + " Telegram Stars."; return result; + }, [&](const ActionConferenceCall &data) { + return data.missed + ? "Missed conference call" + : data.active + ? "Ongoing conference call" + : data.duration + ? "Conference call (" + + NumberToString(data.duration) + + " seconds)" + : "Declined conference call"; }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 1f482db782..e8d083dfb7 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -672,6 +672,12 @@ QByteArray SerializeMessage( pushActor(); pushAction("paid_messages_price_change"); push("price_stars", data.stars); + }, [&](const ActionConferenceCall &data) { + pushActor(); + pushAction("conference_call"); + push("duration_seconds", data.duration); + push("is_missed", data.missed); + push("is_active", data.active); }, [](v::null_t) {}); if (v::is_null(message.action.content)) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 86861abed7..dd80c64291 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5624,6 +5624,19 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &action) { + auto result = PreparedServiceText(); + const auto duration = action.vduration().value_or_empty(); + result.text.text = action.is_missed() + ? tr::lng_action_confcall_missed(tr::now) + : action.is_active() + ? tr::lng_action_confcall_ongoing(tr::now) + : duration + ? tr::lng_action_confcall_finished(tr::now) + : tr::lng_action_confcall_invitation(tr::now); + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -5673,6 +5686,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareStarGiftUnique, preparePaidMessagesRefunded, preparePaidMessagesPrice, + prepareConferenceCall, PrepareEmptyText, PrepareErrorText)); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 974fdbd8b8..0cd2a21733 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -915,6 +915,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { [[nodiscard]] CallId CallIdFromInput(const MTPInputGroupCall &data) { return data.match([&](const MTPDinputGroupCall &data) { return data.vid().v; + }, [](const MTPDinputGroupCallSlug &) -> CallId { + Unexpected("inputGroupCallSlug in CallIdFromInput."); }); } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 37fb71c23f..ec6d3baef5 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -187,6 +187,7 @@ messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?tr messageActionStarGiftUnique#acdfcb81 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long = MessageAction; messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction; messageActionPaidMessagesPrice#bcd71419 stars:long = MessageAction; +messageActionConferenceCall#ff397dea flags:# missed:flags.0?true active:flags.1?true call_id:long slug:string duration:flags.2?int other_participants:flags.3?Vector = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -431,6 +432,7 @@ updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update; updateBotPurchasedPaidMedia#283bd312 user_id:long payload:string qts:int = Update; updatePaidReactionPrivacy#8b725fce private:PaidReactionPrivacy = Update; updateSentPhoneCode#504aa18f sent_code:auth.SentCode = Update; +updateGroupCallChainBlocks#a477288f call:InputGroupCall sub_chain_id:int blocks:Vector next_offset:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1340,11 +1342,12 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#7fe91c14 views_graph:StatsGraph reactions_by_emotion_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#cdf8d3e3 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int conference_from_call:flags.14?long = GroupCall; +groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true conference:flags.14?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; +inputGroupCallSlug#fe06823f slug:string = InputGroupCall; -groupCallParticipant#eba636fe flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo = GroupCallParticipant; +groupCallParticipant#23860077 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo public_key:flags.16?int256 = GroupCallParticipant; phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; @@ -2582,7 +2585,7 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#48cdc6d8 flags:# rtmp_stream:flags.2?true peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; -phone.joinGroupCall#d61e1df3 flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string key_fingerprint:flags.3?long params:DataJSON = Updates; +phone.joinGroupCall#ffae43f4 flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string public_key:flags.3?int256 invite_msg_id:flags.4?int params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; @@ -2603,7 +2606,13 @@ phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels; phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; -phone.createConferenceCall#dfc909ab peer:InputPhoneCall key_fingerprint:long = phone.PhoneCall; +phone.createConferenceCall#dae2632f public_key:int256 zero_block:bytes = phone.GroupCall; +phone.addConferenceCallParticipant#3cb2a504 call:InputGroupCall peer:InputPeer block:bytes = Updates; +phone.deleteConferenceCallParticipant#7b8cc2a3 call:InputGroupCall peer:InputPeer block:bytes = Updates; +phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; +phone.inviteConferenceCallParticipant#3e9cf7ee call:InputGroupCall user_id:InputUser = Updates; +phone.declineConferenceCallInvite#3c479971 msg_id:int = Updates; +phone.getGroupCallChainBlocks#ee9f88a6 call:InputGroupCall sub_chain_id:int offset:int limit:int = Updates; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -2679,4 +2688,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 201 +// LAYER 202 diff --git a/Telegram/SourceFiles/ui/image/image_location.cpp b/Telegram/SourceFiles/ui/image/image_location.cpp index d1fdde5b64..a10c6ba963 100644 --- a/Telegram/SourceFiles/ui/image/image_location.cpp +++ b/Telegram/SourceFiles/ui/image/image_location.cpp @@ -172,6 +172,8 @@ StorageFileLocation::StorageFileLocation( data.vcall().match([&](const MTPDinputGroupCall &data) { _id = data.vid().v; _accessHash = data.vaccess_hash().v; + }, [](const auto &data) { + Unexpected("inputGroupCallSlug in inputGroupCallStream."); }); _volumeId = data.vtime_ms().v; _localId = data.vscale().v; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index ccdeaaa073..c4167c2ca8 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -72,6 +72,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "calls/calls_instance.h" // Core::App().calls().inCall(). #include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_common.h" #include "ui/boxes/calendar_box.h" #include "ui/boxes/collectible_info_box.h" #include "ui/boxes/confirm_box.h" @@ -816,10 +817,9 @@ void SessionNavigation::resolveCollectible( Fn fail) { if (_collectibleEntity == entity) { return; - } else { - _api.request(base::take(_collectibleRequestId)).cancel(); } _collectibleEntity = entity; + _api.request(base::take(_collectibleRequestId)).cancel(); _collectibleRequestId = _api.request(MTPfragment_GetCollectibleInfo( ((Ui::DetectCollectibleType(entity) == Ui::CollectibleType::Phone) ? MTP_inputCollectiblePhone(MTP_string(entity)) @@ -840,6 +840,39 @@ void SessionNavigation::resolveCollectible( }).send(); } +void SessionNavigation::resolveConferenceCall(const QString &slug) { + if (_conferenceCallSlug == slug) { + return; + } + _api.request(base::take(_conferenceCallRequestId)).cancel(); + _conferenceCallSlug = slug; + + const auto limit = 5; + _conferenceCallRequestId = _api.request(MTPphone_GetGroupCall( + MTP_inputGroupCallSlug(MTP_string(slug)), + MTP_int(limit) + )).done([=](const MTPphone_GroupCall &result) { + _conferenceCallRequestId = 0; + _conferenceCallSlug = QString(); + + result.data().vcall().match([&](const auto &data) { + const auto call = std::make_shared( + session().user(), + data.vid().v, + data.vaccess_hash().v, + TimeId(), // scheduleDate + false); // rtmp + call->processFullCall(result); + uiShow()->show( + Box(Calls::Group::ConferenceCallJoinConfirm, call)); + }); + }).fail([=] { + _conferenceCallRequestId = 0; + _conferenceCallSlug = QString(); + showToast(tr::lng_group_invite_bad_link(tr::now)); + }).send(); +} + void SessionNavigation::applyBoost( not_null channel, Fn done) { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 1fc2f0fe77..fd7056a1e0 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -264,6 +264,7 @@ public: PeerId ownerId, const QString &entity, Fn fail = nullptr); + void resolveConferenceCall(const QString &slug); base::weak_ptr showToast( Ui::Toast::Config &&config); @@ -329,6 +330,9 @@ private: QString _collectibleEntity; mtpRequestId _collectibleRequestId = 0; + QString _conferenceCallSlug; + mtpRequestId _conferenceCallRequestId = 0; + }; class SessionController : public SessionNavigation { From 5e6c81a98ea1983df35537d3ca7e92183c86b640 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Mar 2025 00:11:21 +0500 Subject: [PATCH 069/190] Parse e2e participant keys. --- Telegram/SourceFiles/calls/calls_instance.cpp | 42 ++++++++ Telegram/SourceFiles/calls/calls_instance.h | 15 +++ .../calls/group/calls_group_call.cpp | 102 ++++++++++++++---- .../calls/group/calls_group_call.h | 22 ++++ .../calls/group/calls_group_common.cpp | 9 +- .../calls/group/calls_group_common.h | 9 +- Telegram/SourceFiles/data/data_group_call.cpp | 7 ++ Telegram/SourceFiles/data/data_group_call.h | 5 + Telegram/SourceFiles/tde2e/tde2e_api.cpp | 17 ++- Telegram/SourceFiles/tde2e/tde2e_api.h | 28 +++++ .../window/window_session_controller.cpp | 13 ++- 11 files changed, 240 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 65dc10fb39..f9ab9ebe68 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -230,6 +230,30 @@ void Instance::startOrJoinGroupCall( }); } +void Instance::startOrJoinConferenceCall( + std::shared_ptr show, + StartConferenceCallArgs args) { + destroyCurrentCall(); + + auto call = std::make_unique( + _delegate.get(), + Calls::Group::ConferenceInfo{ + .call = args.call, + .linkSlug = args.linkSlug, + .joinMessageId = args.joinMessageId, + }); + const auto raw = call.get(); + + args.call->peer()->session().account().sessionChanges( + ) | rpl::start_with_next([=] { + destroyGroupCall(raw); + }, raw->lifetime()); + + _currentGroupCallPanel = std::make_unique(raw); + _currentGroupCall = std::move(call); + _currentGroupCallChanges.fire_copy(raw); +} + void Instance::confirmLeaveCurrent( std::shared_ptr show, not_null peer, @@ -409,6 +433,24 @@ void Instance::createGroupCall( _currentGroupCallChanges.fire_copy(raw); } +void Instance::createConferenceCall(Group::ConferenceInfo info) { + destroyCurrentCall(); + + auto call = std::make_unique( + _delegate.get(), + std::move(info)); + const auto raw = call.get(); + + raw->peer()->session().account().sessionChanges( + ) | rpl::start_with_next([=] { + destroyGroupCall(raw); + }, raw->lifetime()); + + _currentGroupCallPanel = std::make_unique(raw); + _currentGroupCall = std::move(call); + _currentGroupCallChanges.fire_copy(raw); +} + void Instance::refreshDhConfig() { Expects(_currentCall != nullptr); diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index d52a0e9a23..724140b60b 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -13,6 +13,10 @@ namespace crl { class semaphore; } // namespace crl +namespace Data { +class GroupCall; +} // namespace Data + namespace Platform { enum class PermissionType; } // namespace Platform @@ -31,6 +35,7 @@ class Show; namespace Calls::Group { struct JoinInfo; +struct ConferenceInfo; class Panel; class ChooseJoinAsProcess; class StartRtmpProcess; @@ -59,6 +64,12 @@ struct StartGroupCallArgs { bool scheduleNeeded = false; }; +struct StartConferenceCallArgs { + std::shared_ptr call; + QString linkSlug; + MsgId joinMessageId; +}; + class Instance final : public base::has_weak_ptr { public: Instance(); @@ -69,6 +80,9 @@ public: std::shared_ptr show, not_null peer, StartGroupCallArgs args); + void startOrJoinConferenceCall( + std::shared_ptr show, + StartConferenceCallArgs args); void showStartWithRtmp( std::shared_ptr show, not_null peer); @@ -121,6 +135,7 @@ private: void createGroupCall( Group::JoinInfo info, const MTPInputGroupCall &inputCall); + void createConferenceCall(Group::ConferenceInfo info); void destroyGroupCall(not_null call); void confirmLeaveCurrent( std::shared_ptr show, diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index ab1b80117c..268b356527 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "base/global_shortcuts.h" #include "base/random.h" +#include "tde2e/tde2e_api.h" #include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_create_adm.h" #include "webrtc/webrtc_environment.h" @@ -418,6 +419,21 @@ std::shared_ptr ParseVideoParams( return data; } +std::shared_ptr ParseParticipantState( + const MTPDgroupCallParticipant &data) { + if (!data.vpublic_key() || data.vpeer().type() != mtpc_peerUser) { + return nullptr; + } + const auto &v = *data.vpublic_key(); + const auto userId = data.vpeer().c_peerUser().vuser_id().v; + using State = TdE2E::ParticipantState; + return std::make_shared(State{ + .id = uint64(userId), + .key = { .a = v.h.h, .b = v.h.l, .c = v.l.h, .d = v.l.l }, + }); + +} + GroupCall::LoadPartTask::LoadPartTask( base::weak_ptr call, int64 time, @@ -569,18 +585,38 @@ GroupCall::GroupCall( not_null delegate, Group::JoinInfo info, const MTPInputGroupCall &inputCall) +: GroupCall(delegate, info, {}, inputCall) { +} + +GroupCall::GroupCall( + not_null delegate, + Group::ConferenceInfo info) +: GroupCall(delegate, Group::JoinInfo{ + .peer = info.call->peer(), + .joinAs = info.call->peer(), +}, info, info.call->input()) { +} + +GroupCall::GroupCall( + not_null delegate, + Group::JoinInfo join, + Group::ConferenceInfo conference, + const MTPInputGroupCall &inputCall) : _delegate(delegate) -, _peer(info.peer) +, _conferenceCall(conference.call) +, _peer(join.peer) , _history(_peer->owner().history(_peer)) , _api(&_peer->session().mtp()) -, _joinAs(info.joinAs) -, _possibleJoinAs(std::move(info.possibleJoinAs)) -, _joinHash(info.joinHash) -, _rtmpUrl(info.rtmpInfo.url) -, _rtmpKey(info.rtmpInfo.key) +, _joinAs(join.joinAs) +, _possibleJoinAs(std::move(join.possibleJoinAs)) +, _joinHash(join.joinHash) +, _conferenceLinkSlug(conference.linkSlug) +, _conferenceJoinMessageId(conference.joinMessageId) +, _rtmpUrl(join.rtmpInfo.url) +, _rtmpKey(join.rtmpInfo.key) , _canManage(Data::CanManageGroupCallValue(_peer)) , _id(inputCall.c_inputGroupCall().vid().v) -, _scheduleDate(info.scheduleDate) +, _scheduleDate(join.scheduleDate) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) , _playbackDeviceId( @@ -601,9 +637,14 @@ GroupCall::GroupCall( Webrtc::DeviceIdOrDefault(Core::App().settings().cameraDeviceIdValue())) , _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) , _connectingSoundTimer([=] { playConnectingSoundOnce(); }) -, _listenersHidden(info.rtmp) -, _rtmp(info.rtmp) +, _listenersHidden(join.rtmp) +, _rtmp(join.rtmp) , _rtmpVolume(Group::kDefaultVolume) { + if (_conferenceCall) { + _e2eState = std::make_unique( + TdE2E::CreateCallState()); + } + _muted.value( ) | rpl::combine_previous( ) | rpl::start_with_next([=](MuteState previous, MuteState state) { @@ -657,9 +698,9 @@ GroupCall::GroupCall( setupOutgoingVideo(); if (_id) { - join(inputCall); + this->join(inputCall); } else { - start(info.scheduleDate, info.rtmp); + start(join.scheduleDate, join.rtmp); } if (_scheduleDate) { saveDefaultJoinAs(joinAs()); @@ -1055,6 +1096,9 @@ void GroupCall::setRtmpInfo(const Calls::Group::RtmpInfo &value) { } Data::GroupCall *GroupCall::lookupReal() const { + if (_conferenceCall) { + return _conferenceCall.get(); + } const auto real = _peer->groupCall(); return (real && real->id() == _id) ? real : nullptr; } @@ -1373,14 +1417,24 @@ void GroupCall::rejoin(not_null as) { : Flag::f_invite_hash) | (wasVideoStopped ? Flag::f_video_stopped - : Flag(0)); + : Flag(0)) + | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) + | (_e2eState ? Flag::f_public_key : Flag()); + const auto publicKey = _e2eState + ? _e2eState->myKey + : TdE2E::PublicKey(); _api.request(MTPphone_JoinGroupCall( MTP_flags(flags), - inputCall(), + (_conferenceLinkSlug.isEmpty() + ? inputCall() + : MTP_inputGroupCallSlug( + MTP_string(_conferenceLinkSlug))), joinAs()->input, MTP_string(_joinHash), - MTPint256(), // public_key - MTPint(), // invite_msg_id + MTP_int256( + MTP_int128(publicKey.a, publicKey.b), + MTP_int128(publicKey.c, publicKey.d)), + MTP_int(_conferenceJoinMessageId.bare), MTP_dataJSON(MTP_bytes(json)) )).done([=]( const MTPUpdates &updates, @@ -1586,6 +1640,7 @@ void GroupCall::applyMeInCallLocally() { : participant ? participant->raisedHandRating : FindLocalRaisedHandRating(real->participants()); + const auto publicKey = _e2eState ? _e2eState->myKey : TdE2E::PublicKey(); const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) | (lastActive ? Flag::f_active_date : Flag(0)) | (_joinState.ssrc ? Flag(0) : Flag::f_left) @@ -1594,7 +1649,8 @@ void GroupCall::applyMeInCallLocally() { | Flag::f_volume // Without flag the volume is reset to 100%. | Flag::f_volume_by_admin // Self volume can only be set by admin. | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) - | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)); + | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)) + | (_e2eState ? Flag::f_public_key : Flag(0)); real->applyLocalUpdate( MTP_updateGroupCallParticipants( inputCall(), @@ -1611,7 +1667,9 @@ void GroupCall::applyMeInCallLocally() { MTP_long(raisedHandRating), MTPGroupCallParticipantVideo(), MTPGroupCallParticipantVideo(), - AssertIsDebug() MTPint256())), // public_key + MTP_int256( + MTP_int128(publicKey.a, publicKey.b), + MTP_int128(publicKey.c, publicKey.d)))), MTP_int(0)).c_updateGroupCallParticipants()); } @@ -1642,7 +1700,11 @@ void GroupCall::applyParticipantLocally( | (participantPeer == joinAs() ? Flag::f_self : Flag(0)) | (participant->raisedHandRating ? Flag::f_raise_hand_rating - : Flag(0)); + : Flag(0)) + | (participant->e2eState ? Flag::f_public_key : Flag(0)); + const auto publicKey = participant->e2eState + ? participant->e2eState->key + : TdE2E::PublicKey(); _peer->groupCall()->applyLocalUpdate( MTP_updateGroupCallParticipants( inputCall(), @@ -1659,7 +1721,9 @@ void GroupCall::applyParticipantLocally( MTP_long(participant->raisedHandRating), MTPGroupCallParticipantVideo(), MTPGroupCallParticipantVideo(), - AssertIsDebug() MTPint256())), // public_key + MTP_int256( + MTP_int128(publicKey.a, publicKey.b), + MTP_int128(publicKey.c, publicKey.d)))), MTP_int(0)).c_updateGroupCallParticipants()); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 8f93793bd9..a3c8d07c12 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -42,6 +42,11 @@ struct GroupCallParticipant; class GroupCall; } // namespace Data +namespace TdE2E { +struct ParticipantState; +struct CallState; +} // namespace TdE2E + namespace Calls { namespace Group { @@ -49,6 +54,7 @@ struct MuteRequest; struct VolumeRequest; struct ParticipantState; struct JoinInfo; +struct ConferenceInfo; struct RejoinEvent; struct RtmpInfo; enum class VideoQuality; @@ -164,6 +170,8 @@ struct ParticipantVideoParams; const tl::conditional &camera, const tl::conditional &screen, const std::shared_ptr &existing); +[[nodiscard]] std::shared_ptr ParseParticipantState( + const MTPDgroupCallParticipant &data); [[nodiscard]] const std::string &GetCameraEndpoint( const std::shared_ptr ¶ms); @@ -217,6 +225,9 @@ public: not_null delegate, Group::JoinInfo info, const MTPInputGroupCall &inputCall); + GroupCall( + not_null delegate, + Group::ConferenceInfo info); ~GroupCall(); [[nodiscard]] CallId id() const { @@ -469,6 +480,12 @@ private: return true; } + GroupCall( + not_null delegate, + Group::JoinInfo join, + Group::ConferenceInfo conference, + const MTPInputGroupCall &inputCall); + void broadcastPartStart(std::shared_ptr task); void broadcastPartCancel(not_null task); void mediaChannelDescriptionsStart( @@ -575,6 +592,7 @@ private: [[nodiscard]] MTPInputGroupCall inputCall() const; const not_null _delegate; + const std::shared_ptr _conferenceCall; not_null _peer; // Can change in legacy group migration. rpl::event_stream _peerStream; not_null _history; // Can change in legacy group migration. @@ -601,9 +619,13 @@ private: rpl::variable> _joinAs; std::vector> _possibleJoinAs; QString _joinHash; + QString _conferenceLinkSlug; + MsgId _conferenceJoinMessageId; int64 _serverTimeMs = 0; crl::time _serverTimeMsGotAt = 0; + std::unique_ptr _e2eState; + QString _rtmpUrl; QString _rtmpKey; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 75e6de992f..20223c9ebf 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -52,7 +52,8 @@ object_ptr ScreenSharingPrivacyRequestBox() { void ConferenceCallJoinConfirm( not_null box, - std::shared_ptr call) { + std::shared_ptr call, + Fn join) { box->setTitle(tr::lng_confcall_join_title()); box->addRow( @@ -62,7 +63,11 @@ void ConferenceCallJoinConfirm( st::boxLabel)); box->addButton(tr::lng_confcall_join_button(), [=] { - + const auto weak = Ui::MakeWeak(box); + join(); + if (const auto strong = weak.data()) { + strong->closeBox(); + } }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 114c43e6d5..32d403ed6f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -65,6 +65,12 @@ struct JoinInfo { bool rtmp = false; }; +struct ConferenceInfo { + std::shared_ptr call; + QString linkSlug; + MsgId joinMessageId; +}; + enum class PanelMode { Default, Wide, @@ -99,6 +105,7 @@ using StickedTooltips = base::flags; void ConferenceCallJoinConfirm( not_null box, - std::shared_ptr call); + std::shared_ptr call, + Fn join); } // namespace Calls::Group diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index c30eaeebaa..fc8f9fc260 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -622,15 +622,22 @@ void GroupCall::applyParticipantsSlice( const auto existingVideoParams = (i != end(_participants)) ? i->videoParams : nullptr; + const auto existingState = (i != end(_participants)) + ? i->e2eState + : nullptr; auto videoParams = localUpdate ? existingVideoParams : Calls::ParseVideoParams( data.vvideo(), data.vpresentation(), existingVideoParams); + auto e2eState = localUpdate + ? existingState + : Calls::ParseParticipantState(data); const auto value = Participant{ .peer = participantPeer, .videoParams = std::move(videoParams), + .e2eState = std::move(e2eState), .date = data.vdate().v, .lastActive = lastActive, .raisedHandRating = raisedHandRating, diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 8b7d16158d..d283764b12 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -17,6 +17,10 @@ namespace Calls { struct ParticipantVideoParams; } // namespace Calls +namespace TdE2E { +struct ParticipantState; +} // namespace TdE2E + namespace Data { [[nodiscard]] const std::string &RtmpEndpointId(); @@ -29,6 +33,7 @@ struct LastSpokeTimes { struct GroupCallParticipant { not_null peer; std::shared_ptr videoParams; + std::shared_ptr e2eState; TimeId date = 0; TimeId lastActive = 0; uint64 raisedHandRating = 0; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 109536ed33..f5c8f12001 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -7,16 +7,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "tde2e/tde2e_api.h" -#include "base/algorithm.h" +#include "base/assertion.h" #include namespace TdE2E { -QByteArray GeneratePrivateKey() { - const auto result = tde2e_api::key_generate_temporary_private_key(); +CallState CreateCallState() { + const auto id = tde2e_api::key_generate_temporary_private_key(); + Assert(id.is_ok()); + const auto key = tde2e_api::key_to_public_key(id.value()); + Assert(key.is_ok()); - return {}; + auto result = CallState{ + .myKeyId = PrivateKeyId{ .v = uint64(id.value()) }, + }; + Assert(key.value().size() == sizeof(result.myKey)); + memcpy(&result.myKey, key.value().data(), 32); + + return result; } } // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 73e0063250..689a5aa86c 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -7,3 +7,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/basic_types.h" + +namespace TdE2E { + +struct PrivateKeyId { + uint64 v = 0; +}; + +struct PublicKey { + uint64 a = 0; + uint64 b = 0; + uint64 c = 0; + uint64 d = 0; +}; + +struct ParticipantState { + uint64 id = 0; + PublicKey key; +}; + +struct CallState { + PrivateKeyId myKeyId; + PublicKey myKey; +}; + +[[nodiscard]] CallState CreateCallState(); + +} // namespace TdE2E diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index c4167c2ca8..514f4dd859 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -853,7 +853,7 @@ void SessionNavigation::resolveConferenceCall(const QString &slug) { MTP_int(limit) )).done([=](const MTPphone_GroupCall &result) { _conferenceCallRequestId = 0; - _conferenceCallSlug = QString(); + const auto slug = base::take(_conferenceCallSlug); result.data().vcall().match([&](const auto &data) { const auto call = std::make_shared( @@ -863,8 +863,15 @@ void SessionNavigation::resolveConferenceCall(const QString &slug) { TimeId(), // scheduleDate false); // rtmp call->processFullCall(result); - uiShow()->show( - Box(Calls::Group::ConferenceCallJoinConfirm, call)); + const auto join = [=] { + Core::App().calls().startOrJoinConferenceCall( + uiShow(), + { .call = call, .linkSlug = slug }); + }; + uiShow()->show(Box( + Calls::Group::ConferenceCallJoinConfirm, + call, + join)); }); }).fail([=] { _conferenceCallRequestId = 0; From c80c23e8e81e0ccdb1d430e67b913783e1404662 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Mar 2025 22:38:13 +0500 Subject: [PATCH 070/190] PoC conference call creation. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/api/api_updates.cpp | 20 ++-- Telegram/SourceFiles/calls/calls_instance.cpp | 14 ++- Telegram/SourceFiles/calls/calls_instance.h | 5 + .../calls/group/calls_group_call.cpp | 102 +++++++++++++++--- .../calls/group/calls_group_call.h | 17 ++- .../calls/group/calls_group_common.h | 5 + Telegram/SourceFiles/data/data_group_call.h | 1 + Telegram/SourceFiles/tde2e/tde2e_api.cpp | 71 ++++++++++-- Telegram/SourceFiles/tde2e/tde2e_api.h | 39 ++++++- .../SourceFiles/tde2e/tde2e_integration.cpp | 27 +++++ .../SourceFiles/tde2e/tde2e_integration.h | 20 ++++ .../SourceFiles/window/window_main_menu.cpp | 76 +++++++++++++ 14 files changed, 363 insertions(+), 38 deletions(-) create mode 100644 Telegram/SourceFiles/tde2e/tde2e_integration.cpp create mode 100644 Telegram/SourceFiles/tde2e/tde2e_integration.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d72a22e179..4329b12e51 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1540,6 +1540,8 @@ PRIVATE support/support_preload.h support/support_templates.cpp support/support_templates.h + tde2e/tde2e_integration.cpp + tde2e/tde2e_integration.h ui/boxes/edit_invite_link_session.cpp ui/boxes/edit_invite_link_session.h ui/boxes/peer_qr_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 53ac624a08..c908b48a04 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4911,6 +4911,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_join_title" = "Group Call"; "lng_confcall_join_text" = "You are invited to join a Telegram Call."; "lng_confcall_join_button" = "Join Group Call"; +"lng_confcall_create_link" = "Create Call Link"; +"lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 31a4e73cd3..ec65bd15d3 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -303,14 +303,19 @@ void Updates::feedUpdateVector( auto list = updates.v; const auto hasGroupCallParticipantUpdates = ranges::contains( list, - mtpc_updateGroupCallParticipants, - &MTPUpdate::type); + true, + [](const MTPUpdate &update) { + return update.type() == mtpc_updateGroupCallParticipants + || update.type() == mtpc_updateGroupCallChainBlocks; + }); if (hasGroupCallParticipantUpdates) { ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) { - if (entry.type() == mtpc_updateGroupCallParticipants) { + if (entry.type() == mtpc_updateGroupCallChainBlocks) { return 0; - } else { + } else if (entry.type() == mtpc_updateGroupCallParticipants) { return 1; + } else { + return 2; } }); } else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) { @@ -324,7 +329,8 @@ void Updates::feedUpdateVector( if ((policy == SkipUpdatePolicy::SkipMessageIds && type == mtpc_updateMessageID) || (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants - && type != mtpc_updateGroupCallParticipants)) { + && type != mtpc_updateGroupCallParticipants + && type != mtpc_updateGroupCallChainBlocks)) { continue; } feedUpdate(entry); @@ -954,7 +960,8 @@ void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) { data.vupdates(), SkipUpdatePolicy::SkipExceptGroupCallParticipants); }, [&](const MTPDupdateShort &data) { - if (data.vupdate().type() == mtpc_updateGroupCallParticipants) { + if (data.vupdate().type() == mtpc_updateGroupCallParticipants + || data.vupdate().type() == mtpc_updateGroupCallChainBlocks) { feedUpdate(data.vupdate()); } }, [](const auto &) { @@ -2110,6 +2117,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updatePhoneCall: case mtpc_updatePhoneCallSignalingData: case mtpc_updateGroupCallParticipants: + case mtpc_updateGroupCallChainBlocks: case mtpc_updateGroupCallConnection: case mtpc_updateGroupCall: { Core::App().calls().handleUpdate(&session(), update); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index f9ab9ebe68..a7e7c102ab 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -235,16 +235,18 @@ void Instance::startOrJoinConferenceCall( StartConferenceCallArgs args) { destroyCurrentCall(); + const auto session = &args.call->peer()->session(); auto call = std::make_unique( _delegate.get(), Calls::Group::ConferenceInfo{ - .call = args.call, + .call = std::move(args.call), + .e2e = std::move(args.e2e), .linkSlug = args.linkSlug, .joinMessageId = args.joinMessageId, }); const auto raw = call.get(); - args.call->peer()->session().account().sessionChanges( + session->account().sessionChanges( ) | rpl::start_with_next([=] { destroyGroupCall(raw); }, raw->lifetime()); @@ -547,6 +549,8 @@ void Instance::handleUpdate( handleGroupCallUpdate(session, update); }, [&](const MTPDupdateGroupCallParticipants &data) { handleGroupCallUpdate(session, update); + }, [&](const MTPDupdateGroupCallChainBlocks &data) { + handleGroupCallUpdate(session, update); }, [](const auto &) { Unexpected("Update type in Calls::Instance::handleUpdate."); }); @@ -677,6 +681,12 @@ void Instance::handleGroupCallUpdate( }, [](const MTPDinputGroupCallSlug &) -> CallId { Unexpected("slug in Instance::handleGroupCallUpdate"); }); + }, [](const MTPDupdateGroupCallChainBlocks &data) { + return data.vcall().match([&](const MTPDinputGroupCall &data) { + return data.vid().v; + }, [](const MTPDinputGroupCallSlug &) -> CallId { + Unexpected("slug in Instance::handleGroupCallUpdate"); + }); }, [](const auto &) -> CallId { Unexpected("Type in Instance::handleGroupCallUpdate."); }); diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 724140b60b..2d5df76f2a 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -45,6 +45,10 @@ namespace tgcalls { class VideoCaptureInterface; } // namespace tgcalls +namespace TdE2E { +class Call; +} // namespace TdE2E + namespace Calls { class Call; @@ -66,6 +70,7 @@ struct StartGroupCallArgs { struct StartConferenceCallArgs { std::shared_ptr call; + std::shared_ptr e2e; QString linkSlug; MsgId joinMessageId; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 268b356527..9b00f3da58 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/global_shortcuts.h" #include "base/random.h" #include "tde2e/tde2e_api.h" +#include "tde2e/tde2e_integration.h" #include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_create_adm.h" #include "webrtc/webrtc_environment.h" @@ -53,6 +54,10 @@ constexpr auto kFixManualLargeVideoDuration = 5 * crl::time(1000); constexpr auto kFixSpeakingLargeVideoDuration = 3 * crl::time(1000); constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums. constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. +constexpr auto kShortPollChainBlocksTimeout = 5 * crl::time(1000); +constexpr auto kShortPollChainBlocksPerRequest = 50; +constexpr auto kSubChain0 = 0; +constexpr auto kSubChain1 = 1; [[nodiscard]] const Data::GroupCallParticipant *LookupParticipant( not_null peer, @@ -603,7 +608,8 @@ GroupCall::GroupCall( Group::ConferenceInfo conference, const MTPInputGroupCall &inputCall) : _delegate(delegate) -, _conferenceCall(conference.call) +, _conferenceCall(std::move(conference.call)) +, _e2e(std::move(conference.e2e)) , _peer(join.peer) , _history(_peer->owner().history(_peer)) , _api(&_peer->session().mtp()) @@ -641,8 +647,15 @@ GroupCall::GroupCall( , _rtmp(join.rtmp) , _rtmpVolume(Group::kDefaultVolume) { if (_conferenceCall) { - _e2eState = std::make_unique( - TdE2E::CreateCallState()); + if (!_e2e) { + _e2e = std::make_shared( + TdE2E::MakeUserId(_peer->session().user())); + } + for (auto i = 0; i != kSubChainsCount; ++i) { + _subchains[i].timer.setCallback([=] { + checkChainBlocksRequest(i); + }); + } } _muted.value( @@ -1419,10 +1432,7 @@ void GroupCall::rejoin(not_null as) { ? Flag::f_video_stopped : Flag(0)) | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) - | (_e2eState ? Flag::f_public_key : Flag()); - const auto publicKey = _e2eState - ? _e2eState->myKey - : TdE2E::PublicKey(); + | (_e2e ? Flag::f_public_key : Flag()); _api.request(MTPphone_JoinGroupCall( MTP_flags(flags), (_conferenceLinkSlug.isEmpty() @@ -1431,9 +1441,7 @@ void GroupCall::rejoin(not_null as) { MTP_string(_conferenceLinkSlug))), joinAs()->input, MTP_string(_joinHash), - MTP_int256( - MTP_int128(publicKey.a, publicKey.b), - MTP_int128(publicKey.c, publicKey.d)), + (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), MTP_int(_conferenceJoinMessageId.bare), MTP_dataJSON(MTP_bytes(json)) )).done([=]( @@ -1470,6 +1478,8 @@ void GroupCall::rejoin(not_null as) { real->reloadIfStale(); } } + checkChainBlocksRequest(kSubChain0); + checkChainBlocksRequest(kSubChain1); }).fail([=](const MTP::Error &error) { _joinState.finish(); @@ -1490,6 +1500,40 @@ void GroupCall::rejoin(not_null as) { }); } +void GroupCall::checkChainBlocksRequest(int subchain) { + Expects(subchain >= 0 && subchain < kSubChainsCount); + + auto &state = _subchains[subchain]; + if (state.requestId) { + return; + } + const auto now = crl::now(); + const auto left = state.lastUpdate + kShortPollChainBlocksTimeout - now; + if (left > 0) { + if (!state.timer.isActive()) { + state.timer.callOnce(left); + } + return; + } + state.requestId = _api.request(MTPphone_GetGroupCallChainBlocks( + inputCall(), + MTP_int(subchain), + MTP_int(state.height), + MTP_int(kShortPollChainBlocksPerRequest) + )).done([=](const MTPUpdates &result) { + auto &state = _subchains[subchain]; + state.lastUpdate = crl::now(); + state.requestId = 0; + _peer->session().api().applyUpdates(result); + state.timer.callOnce(kShortPollChainBlocksTimeout + 1); + }).fail([=](const MTP::Error &error) { + auto &state = _subchains[subchain]; + state.lastUpdate = crl::now(); + state.requestId = 0; + state.timer.callOnce(kShortPollChainBlocksTimeout + 1); + }).send(); +} + void GroupCall::checkNextJoinAction() { if (_joinState.action != JoinAction::None) { return; @@ -1640,7 +1684,6 @@ void GroupCall::applyMeInCallLocally() { : participant ? participant->raisedHandRating : FindLocalRaisedHandRating(real->participants()); - const auto publicKey = _e2eState ? _e2eState->myKey : TdE2E::PublicKey(); const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) | (lastActive ? Flag::f_active_date : Flag(0)) | (_joinState.ssrc ? Flag(0) : Flag::f_left) @@ -1650,7 +1693,7 @@ void GroupCall::applyMeInCallLocally() { | Flag::f_volume_by_admin // Self volume can only be set by admin. | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)) - | (_e2eState ? Flag::f_public_key : Flag(0)); + | (_e2e ? Flag::f_public_key : Flag(0)); real->applyLocalUpdate( MTP_updateGroupCallParticipants( inputCall(), @@ -1667,9 +1710,9 @@ void GroupCall::applyMeInCallLocally() { MTP_long(raisedHandRating), MTPGroupCallParticipantVideo(), MTPGroupCallParticipantVideo(), - MTP_int256( - MTP_int128(publicKey.a, publicKey.b), - MTP_int128(publicKey.c, publicKey.d)))), + (_e2e + ? TdE2E::PublicKeyToMTP(_e2e->myKey()) + : MTPint256()))), MTP_int(0)).c_updateGroupCallParticipants()); } @@ -2022,6 +2065,8 @@ void GroupCall::handleUpdate(const MTPUpdate &update) { handleUpdate(data); }, [&](const MTPDupdateGroupCallParticipants &data) { handleUpdate(data); + }, [&](const MTPDupdateGroupCallChainBlocks &data) { + handleUpdate(data); }, [](const auto &) { Unexpected("Type in Instance::applyGroupCallUpdateChecked."); }); @@ -2063,6 +2108,33 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) { } } +void GroupCall::handleUpdate(const MTPDupdateGroupCallChainBlocks &data) { + const auto callId = data.vcall().match([]( + const MTPDinputGroupCall &data) { + return data.vid().v; + }, [](const MTPDinputGroupCallSlug &) -> CallId { + Unexpected("inputGroupCallSlug in GroupCall::handleUpdate."); + }); + if (_id != callId || !_e2e) { + return; + } + const auto subchain = data.vsub_chain_id().v; + if (subchain < 0 || subchain >= kSubChainsCount) { + return; + } + auto &entry = _subchains[subchain]; + entry.lastUpdate = crl::now(); + entry.height = data.vnext_offset().v; + entry.timer.callOnce(kShortPollChainBlocksTimeout + 1); + for (const auto &block : data.vblocks().v) { + const auto result = _e2e->apply({ block.v }); + if (result == TdE2E::Call::ApplyResult::BlockSkipped) { + AssertIsDebug(); + return; + } + } +} + void GroupCall::applyQueuedSelfUpdates() { const auto weak = base::make_weak(this); while (weak diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index a3c8d07c12..148b760acb 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -44,7 +44,7 @@ class GroupCall; namespace TdE2E { struct ParticipantState; -struct CallState; +class Call; } // namespace TdE2E namespace Calls { @@ -437,6 +437,7 @@ private: struct SinkPointer; static constexpr uint32 kDisabledSsrc = uint32(-1); + static constexpr int kSubChainsCount = 2; struct LoadingPart { std::shared_ptr task; @@ -475,6 +476,12 @@ private: ssrc = updatedSsrc; } }; + struct SubChainState { + crl::time lastUpdate = 0; + base::Timer timer; + int height = 0; + mtpRequestId requestId = 0; + }; friend inline constexpr bool is_flag_type(SendUpdateType) { return true; @@ -507,6 +514,7 @@ private: void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); + void handleUpdate(const MTPDupdateGroupCallChainBlocks &data); bool tryCreateController(); void destroyController(); bool tryCreateScreencast(); @@ -535,6 +543,7 @@ private: void rejoinPresentation(); void leavePresentation(); void checkNextJoinAction(); + void checkChainBlocksRequest(int subchain); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); void setInstanceConnected(tgcalls::GroupNetworkState networkState); @@ -593,6 +602,8 @@ private: const not_null _delegate; const std::shared_ptr _conferenceCall; + std::shared_ptr _e2e; + not_null _peer; // Can change in legacy group migration. rpl::event_stream _peerStream; not_null _history; // Can change in legacy group migration. @@ -624,8 +635,6 @@ private: int64 _serverTimeMs = 0; crl::time _serverTimeMsGotAt = 0; - std::unique_ptr _e2eState; - QString _rtmpUrl; QString _rtmpKey; @@ -710,6 +719,8 @@ private: bool _reloadedStaleCall = false; int _rtmpVolume = 0; + SubChainState _subchains[kSubChainsCount]; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 32d403ed6f..ea18eadd3a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -19,6 +19,10 @@ namespace Ui { class GenericBox; } // namespace Ui +namespace TdE2E { +class Call; +} // namespace TdE2E + namespace Calls::Group { constexpr auto kDefaultVolume = 10000; @@ -67,6 +71,7 @@ struct JoinInfo { struct ConferenceInfo { std::shared_ptr call; + std::shared_ptr e2e; QString linkSlug; MsgId joinMessageId; }; diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index d283764b12..506c9019c9 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -19,6 +19,7 @@ struct ParticipantVideoParams; namespace TdE2E { struct ParticipantState; +struct UserId; } // namespace TdE2E namespace Data { diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index f5c8f12001..04ae7340ac 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -12,20 +12,77 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace TdE2E { +namespace { -CallState CreateCallState() { +constexpr auto kPermissionAdd = 1; +constexpr auto kPermissionRemove = 2; + +[[nodiscard]] tde2e_api::Slice Slice(const QByteArray &data) { + return { + data.constData(), + std::string_view::size_type(data.size()) + }; +} + +} // namespace + +Call::Call(UserId myUserId) +: _myUserId(myUserId) { const auto id = tde2e_api::key_generate_temporary_private_key(); Assert(id.is_ok()); - const auto key = tde2e_api::key_to_public_key(id.value()); + _myKeyId = { .v = uint64(id.value()) }; + + const auto key = tde2e_api::key_to_public_key(_myKeyId.v); Assert(key.is_ok()); + Assert(key.value().size() == sizeof(_myKey)); + memcpy(&_myKey, key.value().data(), sizeof(_myKey)); +} - auto result = CallState{ - .myKeyId = PrivateKeyId{ .v = uint64(id.value()) }, +PublicKey Call::myKey() const { + return _myKey; +} + +Block Call::makeZeroBlock() const { + const auto publicKeyView = std::string_view{ + reinterpret_cast(&_myKey), + sizeof(_myKey), }; - Assert(key.value().size() == sizeof(result.myKey)); - memcpy(&result.myKey, key.value().data(), 32); + const auto publicKeyId = tde2e_api::key_from_public_key(publicKeyView); + Assert(publicKeyId.is_ok()); - return result; + const auto myKeyId = std::int64_t(_myKeyId.v); + const auto result = tde2e_api::call_create_zero_block(myKeyId, { + .height = 0, + .participants = { { + .user_id = std::int64_t(_myUserId.v), + .public_key_id = publicKeyId.value(), + .permissions = kPermissionAdd | kPermissionRemove, + } }, + }); + Assert(result.is_ok()); + + return { + .data = QByteArray::fromStdString(result.value()), + }; +} + +void Call::create(const Block &last) { + tde2e_api::call_create(std::int64_t(_myKeyId.v), Slice(last.data)); +} + +Call::ApplyResult Call::apply(const Block &block) { + const auto result = tde2e_api::call_apply_block( + std::int64_t(_id.v), + Slice(block.data)); + + if (!result.is_ok()) { + const auto error = result.error(); + (void)error; + } + + return result.is_ok() + ? ApplyResult::Success + : ApplyResult::BlockSkipped; } } // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 689a5aa86c..36a634576d 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -11,10 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace TdE2E { +struct UserId { + uint64 v = 0; +}; + struct PrivateKeyId { uint64 v = 0; }; +struct CallId { + uint64 v = 0; +}; + struct PublicKey { uint64 a = 0; uint64 b = 0; @@ -23,15 +31,36 @@ struct PublicKey { }; struct ParticipantState { - uint64 id = 0; + UserId id; PublicKey key; }; -struct CallState { - PrivateKeyId myKeyId; - PublicKey myKey; +struct Block { + QByteArray data; }; -[[nodiscard]] CallState CreateCallState(); +class Call final { +public: + explicit Call(UserId myUserId); + + [[nodiscard]] PublicKey myKey() const; + + [[nodiscard]] Block makeZeroBlock() const; + + void create(const Block &last); + + enum class ApplyResult { + Success, + BlockSkipped + }; + [[nodiscard]] ApplyResult apply(const Block &block); + +private: + CallId _id; + UserId _myUserId; + PrivateKeyId _myKeyId; + PublicKey _myKey; + +}; } // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_integration.cpp b/Telegram/SourceFiles/tde2e/tde2e_integration.cpp new file mode 100644 index 0000000000..3f63917d5f --- /dev/null +++ b/Telegram/SourceFiles/tde2e/tde2e_integration.cpp @@ -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 +*/ +#include "tde2e/tde2e_integration.h" + +#include "data/data_user.h" +#include "tde2e/tde2e_api.h" + +namespace TdE2E { + +UserId MakeUserId(not_null user) { + return MakeUserId(peerToUser(user->id)); +} + +UserId MakeUserId(::UserId id) { + return { .v = id.bare }; +} + +MTPint256 PublicKeyToMTP(const PublicKey &key) { + return MTP_int256(MTP_int128(key.a, key.b), MTP_int128(key.c, key.d)); +} + +} // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_integration.h b/Telegram/SourceFiles/tde2e/tde2e_integration.h new file mode 100644 index 0000000000..4715743859 --- /dev/null +++ b/Telegram/SourceFiles/tde2e/tde2e_integration.h @@ -0,0 +1,20 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace TdE2E { + +struct UserId; +struct PublicKey; + +[[nodiscard]] UserId MakeUserId(not_null user); +[[nodiscard]] UserId MakeUserId(::UserId id); + +[[nodiscard]] MTPint256 PublicKeyToMTP(const PublicKey &key); + +} // namespace TdE2E diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 0fd4094309..9dede3dcd5 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -14,17 +14,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_controllers.h" #include "boxes/premium_preview_box.h" #include "calls/calls_box_controller.h" +#include "calls/calls_instance.h" #include "core/application.h" #include "core/click_handler_types.h" #include "data/data_changes.h" #include "data/data_document_media.h" #include "data/data_folder.h" +#include "data/data_group_call.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_user.h" #include "info/info_memento.h" #include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_emoji_status_panel.h" +#include "info/profile/info_profile_icon.h" #include "info/stories/info_stories_widget.h" #include "lang/lang_keys.h" #include "main/main_account.h" @@ -38,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "storage/storage_account.h" #include "support/support_templates.h" +#include "tde2e/tde2e_api.h" +#include "tde2e/tde2e_integration.h" #include "ui/boxes/confirm_box.h" #include "ui/chat/chat_theme.h" #include "ui/controls/swipe_handler.h" @@ -92,6 +97,66 @@ constexpr auto kPlayStatusLimit = 2; || (now.month() == 1 && now.day() == 1); } +[[nodiscard]] not_null AddCreateCallLinkButton( + not_null container, + not_null controller, + Fn done) { + const auto result = container->add(object_ptr( + container, + tr::lng_confcall_create_link(), + st::inviteViaLinkButton), QMargins()); + Ui::AddSkip(container); + Ui::AddDividerText( + container, + tr::lng_confcall_create_link_description(Ui::Text::WithEntities)); + + const auto icon = Ui::CreateChild( + result, + st::inviteViaLinkIcon, + QPoint()); + result->heightValue( + ) | rpl::start_with_next([=](int height) { + icon->moveToLeft( + st::inviteViaLinkIconPosition.x(), + (height - st::inviteViaLinkIcon.height()) / 2); + }, icon->lifetime()); + + const auto creating = result->lifetime().make_state(); + result->setClickedCallback([=] { + if (*creating) { + return; + } + *creating = true; + auto e2e = std::make_shared( + TdE2E::MakeUserId(controller->session().user())); + const auto session = &controller->session(); + session->api().request(MTPphone_CreateConferenceCall( + TdE2E::PublicKeyToMTP(e2e->myKey()), + MTP_bytes(e2e->makeZeroBlock().data) + )).done(crl::guard(controller, [=](const MTPphone_GroupCall &result) { + result.data().vcall().match([&](const auto &data) { + const auto call = std::make_shared( + session->user(), + data.vid().v, + data.vaccess_hash().v, + TimeId(), // scheduleDate + false); // rtmp + call->processFullCall(result); + Core::App().calls().startOrJoinConferenceCall( + controller->uiShow(), + { .call = call, .e2e = e2e }); + }); + if (const auto onstack = done) { + onstack(); + } + })).fail(crl::guard(controller, [=](const MTP::Error &error) { + controller->uiShow()->showToast(error.type()); + *creating = false; + })).send(); + }); + return result; +} + void ShowCallsBox(not_null window) { struct State { State(not_null window) @@ -129,6 +194,17 @@ void ShowCallsBox(not_null window) { Ui::AddDivider(groupCalls->entity()); Ui::AddSkip(groupCalls->entity()); + const auto button = AddCreateCallLinkButton( + box->verticalLayout(), + window, + crl::guard(box, [=] { box->closeBox(); })); + button->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=] { + state->callsDelegate.peerListMouseLeftGeometry(); + }, button->lifetime()); + const auto content = box->addRow( object_ptr(box, &state->callsController), {}); From f9a7c46868c6a0ffefac3c115635a68f5c2d809d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Mar 2025 23:15:30 +0500 Subject: [PATCH 071/190] Update tgcalls. --- .../SourceFiles/calls/group/calls_group_call.cpp | 4 ++-- Telegram/ThirdParty/tgcalls | 2 +- Telegram/cmake/lib_tgcalls.cmake | 14 ++++++-------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 9b00f3da58..2785652891 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -3682,7 +3682,7 @@ void GroupCall::destroyController() { DEBUG_LOG(("Call Info: Destroying call controller..")); invalidate_weak_ptrs(&_instanceGuard); - _instance->stop(); + _instance->stop(nullptr); crl::async([ instance = base::take(_instance), done = _delegate->groupCallAddAsyncWaiter() @@ -3699,7 +3699,7 @@ void GroupCall::destroyScreencast() { DEBUG_LOG(("Call Info: Destroying call screen controller..")); invalidate_weak_ptrs(&_screenInstanceGuard); - _screenInstance->stop(); + _screenInstance->stop(nullptr); crl::async([ instance = base::take(_screenInstance), done = _delegate->groupCallAddAsyncWaiter() diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 9bf4065ea0..6897a6ee1d 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 9bf4065ea00cbed5e63cec348457ed13143459d0 +Subproject commit 6897a6ee1d14ff032f1047993105e92e8589011c diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 88800c9af6..8453aabe26 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -76,6 +76,8 @@ PRIVATE v2/InstanceV2Impl.h v2/NativeNetworkingImpl.cpp v2/NativeNetworkingImpl.h + v2/RawTcpSocket.cpp + v2/RawTcpSocket.h v2/ReflectorPort.cpp v2/ReflectorPort.h v2/ReflectorRelayPortFactory.cpp @@ -86,8 +88,12 @@ PRIVATE v2/SignalingConnection.h v2/SignalingEncryption.cpp v2/SignalingEncryption.h + v2/SignalingKcpConnection.cpp + v2/SignalingKcpConnection.h v2/SignalingSctpConnection.cpp v2/SignalingSctpConnection.h + v2/ikcp.cpp + v2/ikcp.h # Desktop capturer desktop_capturer/DesktopCaptureSource.h @@ -144,10 +150,6 @@ PRIVATE platform/darwin/DesktopSharingCapturer.mm platform/darwin/ExtractCVPixelBuffer.h platform/darwin/ExtractCVPixelBuffer.mm - platform/darwin/GLVideoView.h - platform/darwin/GLVideoView.mm - platform/darwin/GLVideoViewMac.h - platform/darwin/GLVideoViewMac.mm platform/darwin/h265_nalu_rewriter.cc platform/darwin/h265_nalu_rewriter.h platform/darwin/objc_video_encoder_factory.h @@ -231,10 +233,6 @@ if (APPLE) -fobjc-arc ) remove_target_sources(lib_tgcalls ${tgcalls_loc} - platform/darwin/GLVideoView.h - platform/darwin/GLVideoView.mm - platform/darwin/GLVideoViewMac.h - platform/darwin/GLVideoViewMac.mm platform/darwin/VideoCameraCapturer.h platform/darwin/VideoCameraCapturer.mm platform/darwin/VideoMetalView.h From ebe11fdb1ebd33163890a3ef4a0c6cedd768b367 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Mar 2025 23:16:39 +0500 Subject: [PATCH 072/190] Pass encrypt/decrypt callback to tgcalls. --- .../calls/group/calls_group_call.cpp | 9 ++++++ Telegram/SourceFiles/tde2e/tde2e_api.cpp | 32 +++++++++++++++++++ Telegram/SourceFiles/tde2e/tde2e_api.h | 5 +++ 3 files changed, 46 insertions(+) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 2785652891..f086e5416c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -2547,6 +2547,14 @@ bool GroupCall::tryCreateController() { } }); }; + auto e2eEncryptDecrypt = Fn( + const std::vector&, + bool)>(); + if (_e2e) { + e2eEncryptDecrypt = [e2e = _e2e](const std::vector &data, bool encrypt) { + return encrypt ? e2e->encrypt(data) : e2e->decrypt(data); + }; + } tgcalls::GroupInstanceDescriptor descriptor = { .threads = tgcalls::StaticThreads::getThreads(), @@ -2633,6 +2641,7 @@ bool GroupCall::tryCreateController() { }); return result; }, + .e2eEncryptDecrypt = e2eEncryptDecrypt, }; if (Logs::DebugEnabled()) { auto callLogFolder = cWorkingDir() + u"DebugLogs"_q; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 04ae7340ac..4c01426925 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -85,4 +85,36 @@ Call::ApplyResult Call::apply(const Block &block) { : ApplyResult::BlockSkipped; } +std::vector Call::encrypt(const std::vector &data) const { + const auto result = tde2e_api::call_encrypt( + std::int64_t(_id.v), + std::string_view{ + reinterpret_cast(data.data()), + data.size(), + }); + if (!result.is_ok()) { + return {}; + } + const auto &value = result.value(); + const auto start = reinterpret_cast(value.data()); + const auto end = start + value.size(); + return std::vector{ start, end }; +} + +std::vector Call::decrypt(const std::vector &data) const { + const auto result = tde2e_api::call_decrypt( + std::int64_t(_id.v), + std::string_view{ + reinterpret_cast(data.data()), + data.size(), + }); + if (!result.is_ok()) { + return {}; + } + const auto &value = result.value(); + const auto start = reinterpret_cast(value.data()); + const auto end = start + value.size(); + return std::vector{ start, end }; +} + } // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 36a634576d..ae8d70279e 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -55,6 +55,11 @@ public: }; [[nodiscard]] ApplyResult apply(const Block &block); + [[nodiscard]] std::vector encrypt( + const std::vector &data) const; + [[nodiscard]] std::vector decrypt( + const std::vector &data) const; + private: CallId _id; UserId _myUserId; From 33a15e69bb3bbe083a082d073a9d8ca1391f20e4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 26 Mar 2025 11:24:27 +0400 Subject: [PATCH 073/190] Move blocks order checking to tde2e_api module. --- .../calls/group/calls_group_call.cpp | 52 ++----- .../calls/group/calls_group_call.h | 5 +- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 144 +++++++++++++++++- Telegram/SourceFiles/tde2e/tde2e_api.h | 49 +++++- 4 files changed, 198 insertions(+), 52 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index f086e5416c..ce0fddd314 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -54,10 +54,7 @@ constexpr auto kFixManualLargeVideoDuration = 5 * crl::time(1000); constexpr auto kFixSpeakingLargeVideoDuration = 3 * crl::time(1000); constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums. constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. -constexpr auto kShortPollChainBlocksTimeout = 5 * crl::time(1000); constexpr auto kShortPollChainBlocksPerRequest = 50; -constexpr auto kSubChain0 = 0; -constexpr auto kSubChain1 = 1; [[nodiscard]] const Data::GroupCallParticipant *LookupParticipant( not_null peer, @@ -651,11 +648,10 @@ GroupCall::GroupCall( _e2e = std::make_shared( TdE2E::MakeUserId(_peer->session().user())); } - for (auto i = 0; i != kSubChainsCount; ++i) { - _subchains[i].timer.setCallback([=] { - checkChainBlocksRequest(i); - }); - } + _e2e->subchainRequests( + ) | rpl::start_with_next([=](TdE2E::Call::SubchainRequest request) { + requestSubchainBlocks(request.subchain, request.height); + }, _lifetime); } _muted.value( @@ -1478,8 +1474,8 @@ void GroupCall::rejoin(not_null as) { real->reloadIfStale(); } } - checkChainBlocksRequest(kSubChain0); - checkChainBlocksRequest(kSubChain1); + requestSubchainBlocks(0, 0); + requestSubchainBlocks(1, 0); }).fail([=](const MTP::Error &error) { _joinState.finish(); @@ -1500,37 +1496,25 @@ void GroupCall::rejoin(not_null as) { }); } -void GroupCall::checkChainBlocksRequest(int subchain) { +void GroupCall::requestSubchainBlocks(int subchain, int height) { Expects(subchain >= 0 && subchain < kSubChainsCount); auto &state = _subchains[subchain]; - if (state.requestId) { - return; - } - const auto now = crl::now(); - const auto left = state.lastUpdate + kShortPollChainBlocksTimeout - now; - if (left > 0) { - if (!state.timer.isActive()) { - state.timer.callOnce(left); - } - return; - } + _api.request(base::take(state.requestId)).cancel(); state.requestId = _api.request(MTPphone_GetGroupCallChainBlocks( inputCall(), MTP_int(subchain), - MTP_int(state.height), + MTP_int(height), MTP_int(kShortPollChainBlocksPerRequest) )).done([=](const MTPUpdates &result) { auto &state = _subchains[subchain]; - state.lastUpdate = crl::now(); - state.requestId = 0; _peer->session().api().applyUpdates(result); - state.timer.callOnce(kShortPollChainBlocksTimeout + 1); + state.requestId = 0; + _e2e->subchainBlocksRequestFinished(subchain); }).fail([=](const MTP::Error &error) { auto &state = _subchains[subchain]; - state.lastUpdate = crl::now(); state.requestId = 0; - state.timer.callOnce(kShortPollChainBlocksTimeout + 1); + _e2e->subchainBlocksRequestFinished(subchain); }).send(); } @@ -2123,15 +2107,11 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallChainBlocks &data) { return; } auto &entry = _subchains[subchain]; - entry.lastUpdate = crl::now(); - entry.height = data.vnext_offset().v; - entry.timer.callOnce(kShortPollChainBlocksTimeout + 1); + const auto inpoll = entry.requestId != 0; + const auto next = data.vnext_offset().v; + auto now = next - int(data.vblocks().v.size()); for (const auto &block : data.vblocks().v) { - const auto result = _e2e->apply({ block.v }); - if (result == TdE2E::Call::ApplyResult::BlockSkipped) { - AssertIsDebug(); - return; - } + _e2e->apply(subchain, now++, { block.v }, inpoll); } } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 148b760acb..e839eeaff7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -477,9 +477,6 @@ private: } }; struct SubChainState { - crl::time lastUpdate = 0; - base::Timer timer; - int height = 0; mtpRequestId requestId = 0; }; @@ -543,7 +540,7 @@ private: void rejoinPresentation(); void leavePresentation(); void checkNextJoinAction(); - void checkChainBlocksRequest(int subchain); + void requestSubchainBlocks(int subchain, int height); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); void setInstanceConnected(tgcalls::GroupNetworkState networkState); diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 4c01426925..23bdc6bc08 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -8,14 +8,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "tde2e/tde2e_api.h" #include "base/assertion.h" +#include "base/debug_log.h" #include +#define LOG_ERROR(error) \ + LOG(("TdE2E Error %1: %2").arg(int(error.code)).arg(error.message.c_str())) + +#define LOG_AND_FAIL(error, reason) \ + LOG_ERROR(error); \ + fail(reason) + namespace TdE2E { namespace { constexpr auto kPermissionAdd = 1; constexpr auto kPermissionRemove = 2; +constexpr auto kShortPollChainBlocksTimeout = 5 * crl::time(1000); +constexpr auto kShortPollChainBlocksWaitFor = crl::time(1000); [[nodiscard]] tde2e_api::Slice Slice(const QByteArray &data) { return { @@ -38,6 +48,11 @@ Call::Call(UserId myUserId) memcpy(&_myKey, key.value().data(), sizeof(_myKey)); } +void Call::fail(CallFailure reason) { + _failure = reason; + _failures.fire_copy(reason); +} + PublicKey Call::myKey() const { return _myKey; } @@ -67,22 +82,135 @@ Block Call::makeZeroBlock() const { } void Call::create(const Block &last) { - tde2e_api::call_create(std::int64_t(_myKeyId.v), Slice(last.data)); + const auto id = tde2e_api::call_create( + std::int64_t(_myKeyId.v), + Slice(last.data)); + if (!id.is_ok()) { + LOG_AND_FAIL(id.error(), CallFailure::Unknown); + return; + } + + for (auto i = 0; i != kSubChainsCount; ++i) { + auto &entry = _subchains[i]; + entry.waitingTimer.setCallback([=] { + checkWaitingBlocks(i, true); + }); + entry.shortPollTimer.setCallback([=] { + shortPoll(i); + }); + entry.shortPollTimer.callOnce(kShortPollChainBlocksTimeout); + } } -Call::ApplyResult Call::apply(const Block &block) { +void Call::apply( + int subchain, + int index, + const Block &block, + bool fromShortPoll) { + Expects(subchain >= 0 && subchain < kSubChainsCount); + + if (!subchain && !_id.v) { + create(block); + } + if (failed()) { + return; + } + + auto &entry = _subchains[subchain]; + if (!fromShortPoll) { + entry.lastUpdate = crl::now(); + if (index > entry.height + 1) { + entry.waiting.emplace(index, block); + checkWaitingBlocks(subchain); + return; + } + } + const auto result = tde2e_api::call_apply_block( std::int64_t(_id.v), Slice(block.data)); - if (!result.is_ok()) { - const auto error = result.error(); - (void)error; + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return; } - return result.is_ok() - ? ApplyResult::Success - : ApplyResult::BlockSkipped; + entry.height = std::max(entry.height, index); + checkWaitingBlocks(subchain); +} + +void Call::checkWaitingBlocks(int subchain, bool waited) { + Expects(subchain >= 0 && subchain < kSubChainsCount); + + if (failed()) { + return; + } + + auto &entry = _subchains[subchain]; + if (entry.shortPolling) { + return; + } + auto &waiting = entry.waiting; + entry.shortPollTimer.cancel(); + while (!waiting.empty()) { + const auto level = waiting.begin()->first; + if (level > entry.height + 1) { + if (waited) { + shortPoll(subchain); + } else { + entry.waitingTimer.callOnce(kShortPollChainBlocksWaitFor); + } + return; + } else if (level == entry.height + 1) { + const auto result = tde2e_api::call_apply_block( + std::int64_t(_id.v), + Slice(waiting.begin()->second.data)); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return; + } + entry.height = level; + } + waiting.erase(waiting.begin()); + } + entry.waitingTimer.cancel(); + entry.shortPollTimer.callOnce(kShortPollChainBlocksTimeout); +} + +void Call::shortPoll(int subchain) { + Expects(subchain >= 0 && subchain < kSubChainsCount); + + auto &entry = _subchains[subchain]; + entry.waitingTimer.cancel(); + entry.shortPollTimer.cancel(); + entry.shortPolling = true; + _subchainRequests.fire({ subchain, entry.height }); +} + +rpl::producer Call::subchainRequests() const { + return _subchainRequests.events(); +} + +void Call::subchainBlocksRequestFinished(int subchain) { + Expects(subchain >= 0 && subchain < kSubChainsCount); + + if (failed()) { + return; + } + + auto &entry = _subchains[subchain]; + entry.shortPolling = false; + checkWaitingBlocks(subchain); +} + +std::optional Call::failed() const { + return _failure; +} + +rpl::producer Call::failures() const { + if (_failure) { + return rpl::single(*_failure); + } + return _failures.events(); } std::vector Call::encrypt(const std::vector &data) const { diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index ae8d70279e..17634d9835 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -8,6 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/basic_types.h" +#include "base/timer.h" + +#include +#include + +#include namespace TdE2E { @@ -39,6 +45,10 @@ struct Block { QByteArray data; }; +enum class CallFailure { + Unknown, +}; + class Call final { public: explicit Call(UserId myUserId); @@ -49,11 +59,21 @@ public: void create(const Block &last); - enum class ApplyResult { - Success, - BlockSkipped + void apply( + int subchain, + int index, + const Block &block, + bool fromShortPoll); + + struct SubchainRequest { + int subchain = 0; + int height = 0; }; - [[nodiscard]] ApplyResult apply(const Block &block); + [[nodiscard]] rpl::producer subchainRequests() const; + void subchainBlocksRequestFinished(int subchain); + + [[nodiscard]] std::optional failed() const; + [[nodiscard]] rpl::producer failures() const; [[nodiscard]] std::vector encrypt( const std::vector &data) const; @@ -61,10 +81,31 @@ public: const std::vector &data) const; private: + static constexpr int kSubChainsCount = 2; + + struct SubChainState { + base::Timer shortPollTimer; + base::Timer waitingTimer; + crl::time lastUpdate = 0; + base::flat_map waiting; + bool shortPolling = true; + int height = 0; + }; + + void fail(CallFailure reason); + + void checkWaitingBlocks(int subchain, bool waited = false); + void shortPoll(int subchain); + CallId _id; UserId _myUserId; PrivateKeyId _myKeyId; PublicKey _myKey; + std::optional _failure; + rpl::event_stream _failures; + + SubChainState _subchains[kSubChainsCount]; + rpl::event_stream _subchainRequests; }; From 0e46a4402a5dd2a8af69d0682ebf8cbb9b277dd6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 26 Mar 2025 23:21:42 +0500 Subject: [PATCH 074/190] Update API scheme on layer 202. --- Telegram/SourceFiles/calls/calls_call.cpp | 1 - .../calls/group/calls_group_call.cpp | 6 +++++- .../calls/group/calls_group_menu.cpp | 11 ++++++----- Telegram/SourceFiles/data/data_media_types.cpp | 4 ++-- Telegram/SourceFiles/data/data_media_types.h | 2 +- .../export/data/export_data_types.cpp | 4 ++-- .../SourceFiles/export/data/export_data_types.h | 2 +- .../export/output/export_output_json.cpp | 2 ++ Telegram/SourceFiles/mtproto/scheme/api.tl | 17 ++++++++--------- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 8 ++++++++ Telegram/SourceFiles/tde2e/tde2e_api.h | 5 +++++ 11 files changed, 40 insertions(+), 22 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 7a55991dbd..3a34f07836 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -303,7 +303,6 @@ void Call::startOutgoing() { _api.request(MTPphone_RequestCall( MTP_flags(flags), _user->inputUser, - MTPInputGroupCall(), MTP_int(base::RandomValue()), MTP_bytes(_gaHash), MTP_phoneCallProtocol( diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index ce0fddd314..3ab9bfb314 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1428,7 +1428,7 @@ void GroupCall::rejoin(not_null as) { ? Flag::f_video_stopped : Flag(0)) | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) - | (_e2e ? Flag::f_public_key : Flag()); + | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); _api.request(MTPphone_JoinGroupCall( MTP_flags(flags), (_conferenceLinkSlug.isEmpty() @@ -1438,6 +1438,10 @@ void GroupCall::rejoin(not_null as) { joinAs()->input, MTP_string(_joinHash), (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), + (_e2e + ? MTP_bytes( + _e2e->lastBlock0().value_or(TdE2E::Block()).data) + : MTPbytes()), MTP_int(_conferenceJoinMessageId.bare), MTP_dataJSON(MTP_bytes(json)) )).done([=]( diff --git a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp index 51a9068a73..277c64356e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp @@ -490,11 +490,12 @@ void FillMenu( Fn)> showBox) { const auto weak = base::make_weak(call); const auto resolveReal = [=] { - const auto real = peer->groupCall(); - const auto strong = weak.get(); - return (real && strong && (real->id() == strong->id())) - ? real - : nullptr; + if (const auto strong = weak.get()) { + if (const auto real = strong->lookupReal()) { + return real; + } + } + return (Data::GroupCall*)nullptr; }; const auto real = resolveReal(); if (!real) { diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 2be78e44fc..067e8af492 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -467,8 +467,8 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { return CallFinishReason::Hangup; }, [](const MTPDphoneCallDiscardReasonMissed &) { return CallFinishReason::Missed; - }, [](const MTPDphoneCallDiscardReasonAllowGroupCall &) { - return CallFinishReason::AllowGroupCall; + }, [](const MTPDphoneCallDiscardReasonMigrateConferenceCall &) { + return CallFinishReason::MigrateConferenceCall; }); Unexpected("Call reason type."); } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index b6e2a32bbd..14452c7972 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -46,7 +46,7 @@ enum class CallFinishReason : char { Busy, Disconnected, Hangup, - AllowGroupCall, + MigrateConferenceCall, }; struct SharedContact final { diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index e9a91b434f..919b35f8ef 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1471,8 +1471,8 @@ ServiceAction ParseServiceAction( return Reason::Hangup; }, [](const MTPDphoneCallDiscardReasonBusy &data) { return Reason::Busy; - }, [](const MTPDphoneCallDiscardReasonAllowGroupCall &) { - return Reason::AllowGroupCall; + }, [](const MTPDphoneCallDiscardReasonMigrateConferenceCall &) { + return Reason::MigrateConferenceCall; }); } result.content = content; diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index afdf7251bf..3b37de23d9 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -503,7 +503,7 @@ struct ActionPhoneCall { Disconnect, Hangup, Busy, - AllowGroupCall, + MigrateConferenceCall, }; DiscardReason discardReason = DiscardReason::Unknown; int duration = 0; diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index e8d083dfb7..5e263e60d3 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -472,6 +472,8 @@ QByteArray SerializeMessage( case Reason::Disconnect: return "disconnect"; case Reason::Hangup: return "hangup"; case Reason::Missed: return "missed"; + case Reason::MigrateConferenceCall: + return "migrate_conference_all"; } return ""; }()); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index ec6d3baef5..71bf5182ad 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -903,7 +903,7 @@ phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason; phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason; phoneCallDiscardReasonHangup#57adc690 = PhoneCallDiscardReason; phoneCallDiscardReasonBusy#faf7e8c9 = PhoneCallDiscardReason; -phoneCallDiscardReasonAllowGroupCall#afe2b839 encrypted_key:bytes = PhoneCallDiscardReason; +phoneCallDiscardReasonMigrateConferenceCall#9fbbf1f7 slug:string = PhoneCallDiscardReason; dataJSON#7d748d04 data:string = DataJSON; @@ -958,11 +958,11 @@ inputStickerSetItem#32da9e9c flags:# document:InputDocument emoji:string mask_co inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; phoneCallEmpty#5366c915 id:long = PhoneCall; -phoneCallWaiting#eed42858 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long protocol:PhoneCallProtocol receive_date:flags.0?int conference_call:flags.8?InputGroupCall = PhoneCall; -phoneCallRequested#45361c63 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_hash:bytes protocol:PhoneCallProtocol conference_call:flags.8?InputGroupCall = PhoneCall; -phoneCallAccepted#22fd7181 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_b:bytes protocol:PhoneCallProtocol conference_call:flags.8?InputGroupCall = PhoneCall; -phoneCall#3ba5940c flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int custom_parameters:flags.7?DataJSON conference_call:flags.8?InputGroupCall = PhoneCall; -phoneCallDiscarded#f9d25503 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int conference_call:flags.8?InputGroupCall = PhoneCall; +phoneCallWaiting#c5226f17 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; +phoneCallRequested#14b0ed0c flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_b:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCall#30535af5 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int custom_parameters:flags.7?DataJSON = PhoneCall; +phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection; @@ -2576,7 +2576,7 @@ stickers.deleteStickerSet#87704394 stickerset:InputStickerSet = Bool; stickers.replaceSticker#4696459a sticker:InputDocument new_sticker:InputStickerSetItem = messages.StickerSet; phone.getCallConfig#55451fa9 = DataJSON; -phone.requestCall#a6c4600c flags:# video:flags.0?true user_id:InputUser conference_call:flags.1?InputGroupCall random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; +phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall; phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool; @@ -2585,7 +2585,7 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#48cdc6d8 flags:# rtmp_stream:flags.2?true peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; -phone.joinGroupCall#ffae43f4 flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string public_key:flags.3?int256 invite_msg_id:flags.4?int params:DataJSON = Updates; +phone.joinGroupCall#dac17b9e flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string public_key:flags.3?int256 block:flags.3?bytes invite_msg_id:flags.4?int params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; @@ -2607,7 +2607,6 @@ phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallS phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; phone.createConferenceCall#dae2632f public_key:int256 zero_block:bytes = phone.GroupCall; -phone.addConferenceCallParticipant#3cb2a504 call:InputGroupCall peer:InputPeer block:bytes = Updates; phone.deleteConferenceCallParticipant#7b8cc2a3 call:InputGroupCall peer:InputPeer block:bytes = Updates; phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; phone.inviteConferenceCallParticipant#3e9cf7ee call:InputGroupCall user_id:InputUser = Updates; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 23bdc6bc08..190247a8bc 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -109,6 +109,10 @@ void Call::apply( bool fromShortPoll) { Expects(subchain >= 0 && subchain < kSubChainsCount); + if (!subchain && index >= _lastBlock0Height) { + _lastBlock0 = block; + _lastBlock0Height = index; + } if (!subchain && !_id.v) { create(block); } @@ -213,6 +217,10 @@ rpl::producer Call::failures() const { return _failures.events(); } +const std::optional &Call::lastBlock0() const { + return _lastBlock0; +} + std::vector Call::encrypt(const std::vector &data) const { const auto result = tde2e_api::call_encrypt( std::int64_t(_id.v), diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 17634d9835..5385a8681c 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -75,6 +75,8 @@ public: [[nodiscard]] std::optional failed() const; [[nodiscard]] rpl::producer failures() const; + [[nodiscard]] const std::optional &lastBlock0() const; + [[nodiscard]] std::vector encrypt( const std::vector &data) const; [[nodiscard]] std::vector decrypt( @@ -107,6 +109,9 @@ private: SubChainState _subchains[kSubChainsCount]; rpl::event_stream _subchainRequests; + std::optional _lastBlock0; + int _lastBlock0Height = 0; + }; } // namespace TdE2E From 7deb5bfdf22bbc811dff90a31be6ae5b54b04665 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Mar 2025 00:41:05 +0500 Subject: [PATCH 075/190] Update API scheme, something works now. --- Telegram/Resources/langs/lang.strings | 5 + Telegram/SourceFiles/calls/calls.style | 31 ++- Telegram/SourceFiles/calls/calls_instance.cpp | 9 +- .../calls/group/calls_group_call.cpp | 241 +++++++++++------- .../calls/group/calls_group_call.h | 7 + .../calls/group/calls_group_common.cpp | 100 ++++++++ .../calls/group/calls_group_common.h | 11 + Telegram/SourceFiles/data/data_channel.cpp | 3 +- Telegram/SourceFiles/data/data_chat.cpp | 3 +- Telegram/SourceFiles/data/data_group_call.cpp | 14 +- Telegram/SourceFiles/data/data_group_call.h | 24 +- .../bot/starref/info_bot_starref_common.cpp | 15 +- .../bot/starref/info_bot_starref_common.h | 8 + Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 92 +++++-- Telegram/SourceFiles/tde2e/tde2e_api.h | 9 +- .../SourceFiles/window/window_main_menu.cpp | 44 ++-- .../window/window_session_controller.cpp | 3 +- 18 files changed, 464 insertions(+), 157 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c908b48a04..b35a5259d5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4913,6 +4913,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_join_button" = "Join Group Call"; "lng_confcall_create_link" = "Create Call Link"; "lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; +"lng_confcall_link_title" = "Call Link"; +"lng_confcall_link_about" = "Anyone on Telegram can join your call by following the link below."; +"lng_confcall_link_or" = "or"; +"lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}"; +"lng_confcall_link_join_link" = "Open call {arrow}"; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index a28a18169e..3a7c926bd7 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1483,4 +1483,33 @@ groupCallCalendarColors: CalendarColors { titleTextColor: groupCallMembersFg; } -// + +confcallLinkButton: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + style: semiboldTextStyle; +} +confcallLinkBox: Box(defaultBox) { + buttonPadding: margins(22px, 11px, 22px, 64px); + buttonHeight: 42px; + button: confcallLinkButton; + shadowIgnoreTopSkip: true; +} +confcallLinkCopyButton: RoundButton(confcallLinkButton) { + icon: icon {{ "info/edit/links_copy", activeButtonFg }}; + iconOver: icon {{ "info/edit/links_copy", activeButtonFgOver }}; + iconPosition: point(-1px, 5px); +} +confcallLinkShareButton: RoundButton(confcallLinkCopyButton) { + icon: icon {{ "info/edit/links_share", activeButtonFg }}; + iconOver: icon {{ "info/edit/links_share", activeButtonFgOver }}; +} +confcallLinkHeaderIconPadding: margins(0px, 32px, 0px, 10px); +confcallLinkTitlePadding: margins(0px, 0px, 0px, 12px); +confcallLinkCenteredText: FlatLabel(defaultFlatLabel) { + align: align(top); + minWidth: 40px; +} +confcallLinkFooterOr: FlatLabel(confcallLinkCenteredText) { + textFg: windowSubTextFg; +} diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index a7e7c102ab..6c8298f4cb 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -690,7 +690,14 @@ void Instance::handleGroupCallUpdate( }, [](const auto &) -> CallId { Unexpected("Type in Instance::handleGroupCallUpdate."); }); - if (const auto existing = session->data().groupCall(callId)) { + if (update.type() == mtpc_updateGroupCallChainBlocks) { + const auto existing = session->data().groupCall(callId); + if (existing + && _currentGroupCall + && _currentGroupCall->lookupReal() == existing) { + _currentGroupCall->handleUpdate(update); + } + } else if (const auto existing = session->data().groupCall(callId)) { existing->enqueueUpdate(update); } else { applyGroupCallUpdateChecked(session, update); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 3ab9bfb314..066fdddcf6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1405,101 +1405,162 @@ void GroupCall::rejoin(not_null as) { const auto weak = base::make_weak(&_instanceGuard); _instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { crl::on_main(weak, [=, payload = std::move(payload)] { - if (state() != State::Joining) { - _joinState.finish(); - checkNextJoinAction(); - return; - } - const auto ssrc = payload.audioSsrc; + _joinState.payload = { + .ssrc = payload.audioSsrc, + .json = QByteArray::fromStdString(payload.json), + }; LOG(("Call Info: Join payload received, joining with ssrc: %1." - ).arg(ssrc)); - - const auto json = QByteArray::fromStdString(payload.json); - const auto wasMuteState = muted(); - const auto wasVideoStopped = !isSharingCamera(); - using Flag = MTPphone_JoinGroupCall::Flag; - const auto flags = (wasMuteState != MuteState::Active - ? Flag::f_muted - : Flag(0)) - | (_joinHash.isEmpty() - ? Flag(0) - : Flag::f_invite_hash) - | (wasVideoStopped - ? Flag::f_video_stopped - : Flag(0)) - | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) - | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); - _api.request(MTPphone_JoinGroupCall( - MTP_flags(flags), - (_conferenceLinkSlug.isEmpty() - ? inputCall() - : MTP_inputGroupCallSlug( - MTP_string(_conferenceLinkSlug))), - joinAs()->input, - MTP_string(_joinHash), - (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), - (_e2e - ? MTP_bytes( - _e2e->lastBlock0().value_or(TdE2E::Block()).data) - : MTPbytes()), - MTP_int(_conferenceJoinMessageId.bare), - MTP_dataJSON(MTP_bytes(json)) - )).done([=]( - const MTPUpdates &updates, - const MTP::Response &response) { - _serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId); - _serverTimeMsGotAt = crl::now(); - - _joinState.finish(ssrc); - _mySsrcs.emplace(ssrc); - - setState((_instanceState.current() - == InstanceState::Disconnected) - ? State::Connecting - : State::Joined); - applyMeInCallLocally(); - maybeSendMutedUpdate(wasMuteState); - _peer->session().api().applyUpdates(updates); - applyQueuedSelfUpdates(); - checkFirstTimeJoined(); - _screenJoinState.nextActionPending = true; - checkNextJoinAction(); - if (wasVideoStopped == isSharingCamera()) { - sendSelfUpdate(SendUpdateType::CameraStopped); - } - if (isCameraPaused()) { - sendSelfUpdate(SendUpdateType::CameraPaused); - } - sendPendingSelfUpdates(); - if (!_reloadedStaleCall - && _state.current() != State::Joining) { - if (const auto real = lookupReal()) { - _reloadedStaleCall = true; - real->reloadIfStale(); - } - } - requestSubchainBlocks(0, 0); - requestSubchainBlocks(1, 0); - }).fail([=](const MTP::Error &error) { - _joinState.finish(); - - const auto type = error.type(); - LOG(("Call Error: Could not join, error: %1").arg(type)); - - if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { - rejoin(); - return; - } - - hangup(); - Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q) - ? tr::lng_group_not_accessible(tr::now) - : Lang::Hard::ServerError()); - }).send(); + ).arg(_joinState.payload.ssrc)); + sendJoinRequest(); }); }); } +void GroupCall::sendJoinRequest() { + if (state() != State::Joining) { + _joinState.finish(); + checkNextJoinAction(); + return; + } + auto joinBlock = _e2e ? _e2e->makeJoinBlock().data : QByteArray(); + if (_e2e && joinBlock.isEmpty()) { + _joinState.finish(); + LOG(("Call Error: Could not generate join block.")); + hangup(); + Ui::Toast::Show(u"Could not generate join block."_q); + return; + } + const auto wasMuteState = muted(); + const auto wasVideoStopped = !isSharingCamera(); + using Flag = MTPphone_JoinGroupCall::Flag; + const auto flags = (wasMuteState != MuteState::Active + ? Flag::f_muted + : Flag(0)) + | (_joinHash.isEmpty() + ? Flag(0) + : Flag::f_invite_hash) + | (wasVideoStopped + ? Flag::f_video_stopped + : Flag(0)) + | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) + | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); + _api.request(MTPphone_JoinGroupCall( + MTP_flags(flags), + (_conferenceLinkSlug.isEmpty() + ? inputCall() + : MTP_inputGroupCallSlug( + MTP_string(_conferenceLinkSlug))), + joinAs()->input, + MTP_string(_joinHash), + (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), + MTP_bytes(joinBlock), + MTP_int(_conferenceJoinMessageId.bare), + MTP_dataJSON(MTP_bytes(_joinState.payload.json)) + )).done([=]( + const MTPUpdates &updates, + const MTP::Response &response) { + _serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId); + _serverTimeMsGotAt = crl::now(); + + _joinState.finish(_joinState.payload.ssrc); + _mySsrcs.emplace(_joinState.ssrc); + + setState((_instanceState.current() + == InstanceState::Disconnected) + ? State::Connecting + : State::Joined); + applyMeInCallLocally(); + maybeSendMutedUpdate(wasMuteState); + _peer->session().api().applyUpdates(updates); + applyQueuedSelfUpdates(); + checkFirstTimeJoined(); + _screenJoinState.nextActionPending = true; + checkNextJoinAction(); + if (wasVideoStopped == isSharingCamera()) { + sendSelfUpdate(SendUpdateType::CameraStopped); + } + if (isCameraPaused()) { + sendSelfUpdate(SendUpdateType::CameraPaused); + } + sendPendingSelfUpdates(); + if (!_reloadedStaleCall + && _state.current() != State::Joining) { + if (const auto real = lookupReal()) { + _reloadedStaleCall = true; + real->reloadIfStale(); + } + } + if (_e2e) { + _e2e->joined(); + } + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + if (_e2e) { + if (type == u"CONF_WRITE_CHAIN_INVALID"_q) { + refreshLastBlockAndJoin(); + return; + } + } + _joinState.finish(); + + LOG(("Call Error: Could not join, error: %1").arg(type)); + + if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { + rejoin(); + return; + } + + hangup(); + Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q) + ? tr::lng_group_not_accessible(tr::now) + : Lang::Hard::ServerError()); + }).send(); +} + +void GroupCall::refreshLastBlockAndJoin() { + Expects(_e2e != nullptr); + + if (state() != State::Joining) { + _joinState.finish(); + checkNextJoinAction(); + return; + } + _api.request(MTPphone_GetGroupCallChainBlocks( + inputCall(), + MTP_int(0), + MTP_int(-1), + MTP_int(1) + )).done([=](const MTPUpdates &result) { + if (result.type() != mtpc_updates) { + _joinState.finish(); + LOG(("Call Error: Bad result in GroupCallChainBlocks.")); + hangup(); + Ui::Toast::Show(u"Bad Updates in GroupCallChainBlocks."_q); + return; + } + _e2e->refreshLastBlock0({}); + const auto &data = result.c_updates(); + for (const auto &update : data.vupdates().v) { + if (update.type() != mtpc_updateGroupCallChainBlocks) { + continue; + } + const auto &data = update.c_updateGroupCallChainBlocks(); + const auto &blocks = data.vblocks().v; + if (!blocks.isEmpty()) { + _e2e->refreshLastBlock0(TdE2E::Block{ blocks.back().v }); + break; + } + } + sendJoinRequest(); + }).fail([=](const MTP::Error &error) { + _joinState.finish(); + const auto &type = error.type(); + LOG(("Call Error: Could not get last block, error: %1").arg(type)); + hangup(); + Ui::Toast::Show(error.type()); + }).send(); +} + void GroupCall::requestSubchainBlocks(int subchain, int height) { Expects(subchain >= 0 && subchain < kSubChainsCount); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index e839eeaff7..067351761f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -466,9 +466,14 @@ private: Joining, Leaving, }; + struct JoinPayload { + uint32 ssrc = 0; + QByteArray json; + }; struct JoinState { uint32 ssrc = 0; JoinAction action = JoinAction::None; + JoinPayload payload; bool nextActionPending = false; void finish(uint32 updatedSsrc = 0) { @@ -540,6 +545,8 @@ private: void rejoinPresentation(); void leavePresentation(); void checkNextJoinAction(); + void sendJoinRequest(); + void refreshLastBlockAndJoin(); void requestSubchainBlocks(int subchain, int height); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 20223c9ebf..106fa229d8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -8,12 +8,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_common.h" #include "base/platform/base_platform_info.h" +#include "boxes/share_box.h" +#include "data/data_group_call.h" +#include "info/bot/starref/info_bot_starref_common.h" +#include "ui/boxes/boost_box.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" #include "lang/lang_keys.h" +#include "window/window_session_controller.h" #include "styles/style_layers.h" #include "styles/style_calls.h" +#include "styles/style_chat.h" + +#include +#include namespace Calls::Group { @@ -74,4 +85,93 @@ void ConferenceCallJoinConfirm( }); } +void ShowConferenceCallLinkBox( + not_null controller, + std::shared_ptr call, + const QString &link, + bool initial) { + controller->show(Box([=](not_null box) { + box->setStyle(st::confcallLinkBox); + box->setWidth(st::boxWideWidth); + box->setNoContentMargin(true); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + box->addRow( + Info::BotStarRef::CreateLinkHeaderIcon(box, &call->session()), + st::boxRowPadding + st::confcallLinkHeaderIconPadding); + box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_confcall_link_title(), + st::boxTitle)), + st::boxRowPadding + st::confcallLinkTitlePadding); + box->addRow( + object_ptr( + box, + tr::lng_confcall_link_about(), + st::confcallLinkCenteredText), + st::boxRowPadding + )->setTryMakeSimilarLines(true); + + Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 2); + const auto preview = box->addRow( + Info::BotStarRef::MakeLinkLabel(box, link)); + Ui::AddSkip(box->verticalLayout()); + + const auto copyCallback = [=] { + QApplication::clipboard()->setText(link); + box->uiShow()->showToast(tr::lng_username_copied(tr::now)); + }; + const auto shareCallback = [=] { + FastShareLink(controller, link); + }; + preview->setClickedCallback(copyCallback); + [[maybe_unused]] const auto copy = box->addButton( + tr::lng_group_invite_copy(), + copyCallback, + st::confcallLinkCopyButton); + [[maybe_unused]] const auto share = box->addButton( + tr::lng_group_invite_share(), + shareCallback, + st::confcallLinkShareButton); + + const auto sep = Ui::CreateChild( + copy->parentWidget(), + tr::lng_confcall_link_or(), + st::confcallLinkFooterOr); + const auto footer = Ui::CreateChild( + copy->parentWidget(), + tr::lng_confcall_link_join( + lt_link, + tr::lng_confcall_link_join_link( + lt_arrow, + rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), + [](QString v) { return Ui::Text::Link(v); }), + Ui::Text::WithEntities), + st::confcallLinkCenteredText); + footer->setTryMakeSimilarLines(true); + copy->geometryValue() | rpl::start_with_next([=](QRect geometry) { + const auto width = st::boxWideWidth + - st::boxRowPadding.left() + - st::boxRowPadding.right(); + footer->resizeToWidth(width); + const auto &st = box->getDelegate()->style(); + const auto top = geometry.y() + geometry.height(); + const auto available = st.buttonPadding.bottom(); + const auto footerHeight = sep->height() + footer->height(); + const auto skip = (available - footerHeight) / 2; + sep->move( + st::boxRowPadding.left() + (width - sep->width()) / 2, + top + skip); + footer->moveToLeft( + st::boxRowPadding.left(), + top + skip + sep->height()); + }, footer->lifetime()); + })); +} + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index ea18eadd3a..f2716f16bf 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -16,6 +16,7 @@ class GroupCall; } // namespace Data namespace Ui { +class Show; class GenericBox; } // namespace Ui @@ -23,6 +24,10 @@ namespace TdE2E { class Call; } // namespace TdE2E +namespace Window { +class SessionController; +} // namespace Window + namespace Calls::Group { constexpr auto kDefaultVolume = 10000; @@ -113,4 +118,10 @@ void ConferenceCallJoinConfirm( std::shared_ptr call, Fn join); +void ShowConferenceCallLinkBox( + not_null controller, + std::shared_ptr call, + const QString &link, + bool initial = false); + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index d1a99f90b3..0793099c70 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1001,7 +1001,8 @@ void ChannelData::setGroupCall( data.vid().v, data.vaccess_hash().v, scheduleDate, - rtmp); + rtmp, + false); // conference owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index faea166b1d..8bd3a8cac9 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -234,7 +234,8 @@ void ChatData::setGroupCall( data.vid().v, data.vaccess_hash().v, scheduleDate, - rtmp); + rtmp, + false); // conference owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index fc8f9fc260..6a1663a5ee 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -62,7 +62,8 @@ GroupCall::GroupCall( CallId id, uint64 accessHash, TimeId scheduleDate, - bool rtmp) + bool rtmp, + bool conference) : _id(id) , _accessHash(accessHash) , _peer(peer) @@ -70,15 +71,26 @@ GroupCall::GroupCall( , _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) , _scheduleDate(scheduleDate) , _rtmp(rtmp) +, _conference(conference) , _listenersHidden(rtmp) { + if (_conference) { + session().data().registerGroupCall(this); + } } GroupCall::~GroupCall() { + if (_conference) { + session().data().unregisterGroupCall(this); + } api().request(_unknownParticipantPeersRequestId).cancel(); api().request(_participantsRequestId).cancel(); api().request(_reloadRequestId).cancel(); } +Main::Session &GroupCall::session() const { + return _peer->session(); +} + CallId GroupCall::id() const { return _id; } diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 506c9019c9..104ee83b21 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -17,6 +17,10 @@ namespace Calls { struct ParticipantVideoParams; } // namespace Calls +namespace Main { +class Session; +} // namespace Main + namespace TdE2E { struct ParticipantState; struct UserId; @@ -64,9 +68,12 @@ public: CallId id, uint64 accessHash, TimeId scheduleDate, - bool rtmp); + bool rtmp, + bool conference); ~GroupCall(); + [[nodiscard]] Main::Session &session() const; + [[nodiscard]] CallId id() const; [[nodiscard]] bool loaded() const; [[nodiscard]] bool rtmp() const; @@ -247,13 +254,14 @@ private: rpl::event_stream> _participantSpeaking; rpl::event_stream<> _participantsReloaded; - bool _joinMuted = false; - bool _canChangeJoinMuted = true; - bool _allParticipantsLoaded = false; - bool _joinedToTop = false; - bool _applyingQueuedUpdates = false; - bool _rtmp = false; - bool _listenersHidden = false; + bool _joinMuted : 1 = false; + bool _canChangeJoinMuted : 1 = true; + bool _allParticipantsLoaded : 1 = false; + bool _joinedToTop : 1 = false; + bool _applyingQueuedUpdates : 1 = false; + bool _rtmp : 1 = false; + bool _conference : 1 = false; + bool _listenersHidden : 1 = false; }; diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp index a088bc9585..50149d8a63 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp @@ -73,7 +73,7 @@ void ConnectStarRef( [[nodiscard]] object_ptr CreateLinkIcon( not_null parent, - not_null bot, + not_null session, int users) { auto result = object_ptr(parent); const auto raw = result.data(); @@ -92,7 +92,7 @@ void ConnectStarRef( const auto inner = QSize(innerSide, innerSide); const auto state = raw->lifetime().make_state(State{ .icon = ChatHelpers::GenerateLocalTgsSticker( - &bot->session(), + session, u"starref_link"_q), }); state->icon->overrideEmojiUsesTextColor(true); @@ -419,7 +419,7 @@ object_ptr MakeLinkLabel( p.drawText( QRect(skip, margins.top(), available, font->height), style::al_top, - font->elided(link, available)); + font->elided(text, available)); }, raw->lifetime()); return result; @@ -441,7 +441,7 @@ object_ptr StarRefLinkBox( }); box->addRow( - CreateLinkIcon(box, bot, row.state.users), + CreateLinkIcon(box, &bot->session(), row.state.users), st::boxRowPadding + st::starrefJoinUserpicsPadding); box->addRow( object_ptr>( @@ -1058,4 +1058,11 @@ ConnectedBots Parse( return result; } +object_ptr CreateLinkHeaderIcon( + not_null parent, + not_null session, + int usersCount) { + return CreateLinkIcon(parent, session, usersCount); +} + } // namespace Info::BotStarRef \ No newline at end of file diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h index 02b8bf78e9..46e643241d 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h @@ -105,4 +105,12 @@ void FinishProgram( not_null session, const MTPpayments_ConnectedStarRefBots &bots); +[[nodiscard]] object_ptr MakeLinkLabel( + not_null parent, + const QString &link); +[[nodiscard]] object_ptr CreateLinkHeaderIcon( + not_null parent, + not_null session, + int usersCount = 0); + } // namespace Info::BotStarRef diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 71bf5182ad..5857a15726 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -2606,7 +2606,7 @@ phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels; phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; -phone.createConferenceCall#dae2632f public_key:int256 zero_block:bytes = phone.GroupCall; +phone.createConferenceCall#fbcefee6 random_id:int = phone.GroupCall; phone.deleteConferenceCallParticipant#7b8cc2a3 call:InputGroupCall peer:InputPeer block:bytes = Updates; phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; phone.inviteConferenceCallParticipant#3e9cf7ee call:InputGroupCall user_id:InputUser = Updates; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 190247a8bc..bd3940b39c 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -57,7 +57,15 @@ PublicKey Call::myKey() const { return _myKey; } -Block Call::makeZeroBlock() const { +void Call::refreshLastBlock0(std::optional block) { + _lastBlock0 = std::move(block); +} + +Block Call::makeJoinBlock() { + if (failed()) { + return {}; + } + const auto publicKeyView = std::string_view{ reinterpret_cast(&_myKey), sizeof(_myKey), @@ -66,22 +74,48 @@ Block Call::makeZeroBlock() const { Assert(publicKeyId.is_ok()); const auto myKeyId = std::int64_t(_myKeyId.v); - const auto result = tde2e_api::call_create_zero_block(myKeyId, { - .height = 0, - .participants = { { - .user_id = std::int64_t(_myUserId.v), - .public_key_id = publicKeyId.value(), - .permissions = kPermissionAdd | kPermissionRemove, - } }, - }); - Assert(result.is_ok()); + const auto myParticipant = tde2e_api::CallParticipant{ + .user_id = std::int64_t(_myUserId.v), + .public_key_id = publicKeyId.value(), + .permissions = kPermissionAdd | kPermissionRemove, + }; + + const auto result = _lastBlock0 + ? tde2e_api::call_create_self_add_block( + myKeyId, + Slice(_lastBlock0->data), + myParticipant) + : tde2e_api::call_create_zero_block(myKeyId, { + .height = 0, + .participants = { myParticipant }, + }); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return {}; + } return { .data = QByteArray::fromStdString(result.value()), }; } -void Call::create(const Block &last) { +void Call::joined() { + shortPoll(0); + if (_id.v) { + shortPoll(1); + } +} + +void Call::apply(const Block &last) { + if (_id.v) { + const auto result = tde2e_api::call_apply_block( + std::int64_t(_id.v), + Slice(last.data)); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + } + return; + } const auto id = tde2e_api::call_create( std::int64_t(_myKeyId.v), Slice(last.data)); @@ -89,6 +123,7 @@ void Call::create(const Block &last) { LOG_AND_FAIL(id.error(), CallFailure::Unknown); return; } + _id = CallId{ uint64(id.value()) }; for (auto i = 0; i != kSubChainsCount; ++i) { auto &entry = _subchains[i]; @@ -98,7 +133,9 @@ void Call::create(const Block &last) { entry.shortPollTimer.setCallback([=] { shortPoll(i); }); - entry.shortPollTimer.callOnce(kShortPollChainBlocksTimeout); + if (!entry.waitingTimer.isActive()) { + entry.shortPollTimer.callOnce(kShortPollChainBlocksTimeout); + } } } @@ -108,14 +145,12 @@ void Call::apply( const Block &block, bool fromShortPoll) { Expects(subchain >= 0 && subchain < kSubChainsCount); + Expects(_id.v != 0 || !fromShortPoll || !subchain); if (!subchain && index >= _lastBlock0Height) { _lastBlock0 = block; _lastBlock0Height = index; } - if (!subchain && !_id.v) { - create(block); - } if (failed()) { return; } @@ -123,22 +158,19 @@ void Call::apply( auto &entry = _subchains[subchain]; if (!fromShortPoll) { entry.lastUpdate = crl::now(); - if (index > entry.height + 1) { + if (index > entry.height || (!_id.v && subchain != 0)) { entry.waiting.emplace(index, block); checkWaitingBlocks(subchain); return; } } - const auto result = tde2e_api::call_apply_block( - std::int64_t(_id.v), - Slice(block.data)); - if (!result.is_ok()) { - LOG_AND_FAIL(result.error(), CallFailure::Unknown); + if (failed()) { return; + } else if (!_id.v || entry.height == index) { + apply(block); } - - entry.height = std::max(entry.height, index); + entry.height = index + 1; checkWaitingBlocks(subchain); } @@ -150,7 +182,10 @@ void Call::checkWaitingBlocks(int subchain, bool waited) { } auto &entry = _subchains[subchain]; - if (entry.shortPolling) { + if (!_id.v) { + entry.waitingTimer.callOnce(kShortPollChainBlocksWaitFor); + return; + } else if (entry.shortPolling) { return; } auto &waiting = entry.waiting; @@ -186,6 +221,11 @@ void Call::shortPoll(int subchain) { auto &entry = _subchains[subchain]; entry.waitingTimer.cancel(); entry.shortPollTimer.cancel(); + if (subchain && !_id.v) { + // Not ready. + entry.waitingTimer.callOnce(kShortPollChainBlocksWaitFor); + return; + } entry.shortPolling = true; _subchainRequests.fire({ subchain, entry.height }); } @@ -217,10 +257,6 @@ rpl::producer Call::failures() const { return _failures.events(); } -const std::optional &Call::lastBlock0() const { - return _lastBlock0; -} - std::vector Call::encrypt(const std::vector &data) const { const auto result = tde2e_api::call_encrypt( std::int64_t(_id.v), diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 5385a8681c..931702b0fb 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -55,10 +55,7 @@ public: [[nodiscard]] PublicKey myKey() const; - [[nodiscard]] Block makeZeroBlock() const; - - void create(const Block &last); - + void joined(); void apply( int subchain, int index, @@ -75,7 +72,8 @@ public: [[nodiscard]] std::optional failed() const; [[nodiscard]] rpl::producer failures() const; - [[nodiscard]] const std::optional &lastBlock0() const; + void refreshLastBlock0(std::optional block); + [[nodiscard]] Block makeJoinBlock(); [[nodiscard]] std::vector encrypt( const std::vector &data) const; @@ -94,6 +92,7 @@ private: int height = 0; }; + void apply(const Block &last); void fail(CallFailure reason); void checkWaitingBlocks(int subchain, bool waited = false); diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 9dede3dcd5..3606e9cf87 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -10,9 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/event_filter.h" #include "base/qt_signal_producer.h" +#include "base/random.h" #include "boxes/about_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/premium_preview_box.h" +#include "calls/group/calls_group_common.h" #include "calls/calls_box_controller.h" #include "calls/calls_instance.h" #include "core/application.h" @@ -121,18 +123,16 @@ constexpr auto kPlayStatusLimit = 2; (height - st::inviteViaLinkIcon.height()) / 2); }, icon->lifetime()); - const auto creating = result->lifetime().make_state(); + const auto creating = result->lifetime().make_state(); result->setClickedCallback([=] { if (*creating) { return; } - *creating = true; - auto e2e = std::make_shared( - TdE2E::MakeUserId(controller->session().user())); + *creating = base::RandomValue(); + const auto show = controller->uiShow(); const auto session = &controller->session(); session->api().request(MTPphone_CreateConferenceCall( - TdE2E::PublicKeyToMTP(e2e->myKey()), - MTP_bytes(e2e->makeZeroBlock().data) + MTP_int(*creating) )).done(crl::guard(controller, [=](const MTPphone_GroupCall &result) { result.data().vcall().match([&](const auto &data) { const auto call = std::make_shared( @@ -140,18 +140,32 @@ constexpr auto kPlayStatusLimit = 2; data.vid().v, data.vaccess_hash().v, TimeId(), // scheduleDate - false); // rtmp + false, // rtmp + true); // conference call->processFullCall(result); - Core::App().calls().startOrJoinConferenceCall( - controller->uiShow(), - { .call = call, .e2e = e2e }); + using Flag = MTPphone_ExportGroupCallInvite::Flag; + session->api().request(MTPphone_ExportGroupCallInvite( + MTP_flags(Flag::f_can_self_unmute), + MTP_inputGroupCall(data.vid(), data.vaccess_hash()) + )).done(crl::guard(controller, [=]( + const MTPphone_ExportedGroupCallInvite &result) { + const auto link = qs(result.data().vlink()); + Calls::Group::ShowConferenceCallLinkBox( + controller, + call, + link, + true); + if (const auto onstack = done) { + onstack(); + } + })).fail(crl::guard(controller, [=](const MTP::Error &error) { + show->showToast(error.type()); + *creating = 0; + })).send(); }); - if (const auto onstack = done) { - onstack(); - } })).fail(crl::guard(controller, [=](const MTP::Error &error) { - controller->uiShow()->showToast(error.type()); - *creating = false; + show->showToast(error.type()); + *creating = 0; })).send(); }); return result; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 514f4dd859..a493903ab3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -861,7 +861,8 @@ void SessionNavigation::resolveConferenceCall(const QString &slug) { data.vid().v, data.vaccess_hash().v, TimeId(), // scheduleDate - false); // rtmp + false, // rtmp + true); // conference call->processFullCall(result); const auto join = [=] { Core::App().calls().startOrJoinConferenceCall( From e3ef870b291d673543565ee9a0f3973774651d82 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Mar 2025 15:38:40 +0500 Subject: [PATCH 076/190] Remove stale confcall users from blockchain. --- .../calls/group/calls_group_call.cpp | 133 ++++++++---- .../calls/group/calls_group_call.h | 7 +- Telegram/SourceFiles/data/data_group_call.cpp | 136 ++++++++++-- Telegram/SourceFiles/data/data_group_call.h | 16 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 197 +++++++++++++++--- Telegram/SourceFiles/tde2e/tde2e_api.h | 37 +++- 7 files changed, 437 insertions(+), 91 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 066fdddcf6..14125ca7c0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -421,21 +421,6 @@ std::shared_ptr ParseVideoParams( return data; } -std::shared_ptr ParseParticipantState( - const MTPDgroupCallParticipant &data) { - if (!data.vpublic_key() || data.vpeer().type() != mtpc_peerUser) { - return nullptr; - } - const auto &v = *data.vpublic_key(); - const auto userId = data.vpeer().c_peerUser().vuser_id().v; - using State = TdE2E::ParticipantState; - return std::make_shared(State{ - .id = uint64(userId), - .key = { .a = v.h.h, .b = v.h.l, .c = v.l.h, .d = v.l.l }, - }); - -} - GroupCall::LoadPartTask::LoadPartTask( base::weak_ptr call, int64 time, @@ -643,17 +628,6 @@ GroupCall::GroupCall( , _listenersHidden(join.rtmp) , _rtmp(join.rtmp) , _rtmpVolume(Group::kDefaultVolume) { - if (_conferenceCall) { - if (!_e2e) { - _e2e = std::make_shared( - TdE2E::MakeUserId(_peer->session().user())); - } - _e2e->subchainRequests( - ) | rpl::start_with_next([=](TdE2E::Call::SubchainRequest request) { - requestSubchainBlocks(request.subchain, request.height); - }, _lifetime); - } - _muted.value( ) | rpl::combine_previous( ) | rpl::start_with_next([=](MuteState previous, MuteState state) { @@ -705,6 +679,9 @@ GroupCall::GroupCall( setupMediaDevices(); setupOutgoingVideo(); + if (_conferenceCall) { + setupConferenceCall(); + } if (_id) { this->join(inputCall); @@ -724,6 +701,62 @@ GroupCall::~GroupCall() { } } +void GroupCall::setupConferenceCall() { + if (!_e2e) { + _e2e = std::make_shared( + TdE2E::MakeUserId(_peer->session().user())); + } + _e2e->subchainRequests( + ) | rpl::start_with_next([=](TdE2E::Call::SubchainRequest request) { + requestSubchainBlocks(request.subchain, request.height); + }, _lifetime); + _e2e->sendOutboundBlock( + ) | rpl::start_with_next([=](QByteArray &&block) { + sendOutboundBlock(std::move(block)); + }, _lifetime); + + _conferenceCall->staleParticipantId( + ) | rpl::start_with_next([=](UserId staleId) { + removeConferenceParticipant(staleId); + }, _lifetime); + _e2e->participantsSetValue( + ) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) { + auto users = base::flat_set(); + users.reserve(set.list.size()); + auto ids = QStringList(); + for (const auto &id : set.list) { + users.emplace(UserId(id.v)); + ids.push_back('"' + _peer->owner().user(UserId(id.v))->name() + '"'); + } + LOG(("ACCESS: ") + ids.join(", ")); + _conferenceCall->setParticipantsWithAccess(std::move(users)); + }, _lifetime); +} + +void GroupCall::removeConferenceParticipant(UserId id) { + Expects(_e2e != nullptr); + + const auto block = _e2e->makeRemoveBlock(TdE2E::MakeUserId(id)); + if (block.data.isEmpty()) { + return; + } + _api.request(MTPphone_DeleteConferenceCallParticipant( + inputCall(), + _peer->owner().user(id)->input, + MTP_bytes(block.data) + )).done([=](const MTPUpdates &result) { + _peer->session().api().applyUpdates(result); + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + if (type == u"GROUPCALL_FORBIDDEN"_q) { + setState(State::Joining); + rejoin(); + } else { + LOG(("NOTREMOVED: %1").arg(type)); + } + }).send(); +} + bool GroupCall::isSharingScreen() const { return _isSharingScreen.current(); } @@ -1492,11 +1525,15 @@ void GroupCall::sendJoinRequest() { } if (_e2e) { _e2e->joined(); + if (!_pendingOutboundBlock.isEmpty()) { + sendOutboundBlock(base::take(_pendingOutboundBlock)); + } } }).fail([=](const MTP::Error &error) { const auto type = error.type(); if (_e2e) { - if (type == u"CONF_WRITE_CHAIN_INVALID"_q) { + if (type == u"BLOCK_INVALID"_q + || type.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) { refreshLastBlockAndJoin(); return; } @@ -1583,6 +1620,29 @@ void GroupCall::requestSubchainBlocks(int subchain, int height) { }).send(); } +void GroupCall::sendOutboundBlock(QByteArray block) { + _pendingOutboundBlock = QByteArray(); + _api.request(MTPphone_SendConferenceCallBroadcast( + inputCall(), + MTP_bytes(block) + )).done([=](const MTPUpdates &result) { + _peer->session().api().applyUpdates(result); + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + if (type == u"GROUPCALL_FORBIDDEN"_q) { + _pendingOutboundBlock = block; + setState(State::Joining); + rejoin(); + } else if (type == u"BLOCK_INVALID"_q + || type.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) { + LOG(("Call Error: Could not broadcast block: %1").arg(type)); + } else { + LOG(("HMM")); + sendOutboundBlock(block); + } + }).send(); +} + void GroupCall::checkNextJoinAction() { if (_joinState.action != JoinAction::None) { return; @@ -1741,8 +1801,7 @@ void GroupCall::applyMeInCallLocally() { | Flag::f_volume // Without flag the volume is reset to 100%. | Flag::f_volume_by_admin // Self volume can only be set by admin. | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) - | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)) - | (_e2e ? Flag::f_public_key : Flag(0)); + | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)); real->applyLocalUpdate( MTP_updateGroupCallParticipants( inputCall(), @@ -1758,10 +1817,7 @@ void GroupCall::applyMeInCallLocally() { MTPstring(), // Don't update about text in local updates. MTP_long(raisedHandRating), MTPGroupCallParticipantVideo(), - MTPGroupCallParticipantVideo(), - (_e2e - ? TdE2E::PublicKeyToMTP(_e2e->myKey()) - : MTPint256()))), + MTPGroupCallParticipantVideo())), MTP_int(0)).c_updateGroupCallParticipants()); } @@ -1792,11 +1848,7 @@ void GroupCall::applyParticipantLocally( | (participantPeer == joinAs() ? Flag::f_self : Flag(0)) | (participant->raisedHandRating ? Flag::f_raise_hand_rating - : Flag(0)) - | (participant->e2eState ? Flag::f_public_key : Flag(0)); - const auto publicKey = participant->e2eState - ? participant->e2eState->key - : TdE2E::PublicKey(); + : Flag(0)); _peer->groupCall()->applyLocalUpdate( MTP_updateGroupCallParticipants( inputCall(), @@ -1812,10 +1864,7 @@ void GroupCall::applyParticipantLocally( MTPstring(), // Don't update about text in local updates. MTP_long(participant->raisedHandRating), MTPGroupCallParticipantVideo(), - MTPGroupCallParticipantVideo(), - MTP_int256( - MTP_int128(publicKey.a, publicKey.b), - MTP_int128(publicKey.c, publicKey.d)))), + MTPGroupCallParticipantVideo())), MTP_int(0)).c_updateGroupCallParticipants()); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 067351761f..71c124f408 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -43,7 +43,6 @@ class GroupCall; } // namespace Data namespace TdE2E { -struct ParticipantState; class Call; } // namespace TdE2E @@ -170,8 +169,6 @@ struct ParticipantVideoParams; const tl::conditional &camera, const tl::conditional &screen, const std::shared_ptr &existing); -[[nodiscard]] std::shared_ptr ParseParticipantState( - const MTPDgroupCallParticipant &data); [[nodiscard]] const std::string &GetCameraEndpoint( const std::shared_ptr ¶ms); @@ -282,6 +279,7 @@ public: void startScheduledNow(); void toggleScheduleStartSubscribed(bool subscribed); void setNoiseSuppression(bool enabled); + void removeConferenceParticipant(UserId userId); bool emitShareScreenError(); bool emitShareCameraError(); @@ -548,6 +546,7 @@ private: void sendJoinRequest(); void refreshLastBlockAndJoin(); void requestSubchainBlocks(int subchain, int height); + void sendOutboundBlock(QByteArray block); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); void setInstanceConnected(tgcalls::GroupNetworkState networkState); @@ -588,6 +587,7 @@ private: void setupMediaDevices(); void setupOutgoingVideo(); + void setupConferenceCall(); void setScreenEndpoint(std::string endpoint); void setCameraEndpoint(std::string endpoint); void addVideoOutput(const std::string &endpoint, SinkPointer sink); @@ -607,6 +607,7 @@ private: const not_null _delegate; const std::shared_ptr _conferenceCall; std::shared_ptr _e2e; + QByteArray _pendingOutboundBlock; not_null _peer; // Can change in legacy group migration. rpl::event_stream _peerStream; diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 6a1663a5ee..bab56f63aa 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -27,6 +27,7 @@ constexpr auto kSpeakingAfterActive = crl::time(6000); constexpr auto kActiveAfterJoined = crl::time(1000); constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000); constexpr auto kReloadStaleTimeout = 16 * crl::time(1000); +constexpr auto kMaxConferenceMembers = 50; [[nodiscard]] QString ExtractNextOffset(const MTPphone_GroupCall &call) { return call.match([&](const MTPDphone_groupCall &data) { @@ -75,6 +76,30 @@ GroupCall::GroupCall( , _listenersHidden(rtmp) { if (_conference) { session().data().registerGroupCall(this); + + _participantUpdates.events( + ) | rpl::filter([=](const ParticipantUpdate &update) { + return !update.now + && !update.was->peer->isSelf() + && !_participantsWithAccess.current().empty(); + }) | rpl::start_with_next([=](const ParticipantUpdate &update) { + if (const auto id = peerToUser(update.was->peer->id)) { + if (_participantsWithAccess.current().contains(id)) { + _staleParticipantId.fire_copy(id); + } + } + }, _checkStaleLifetime); + + _participantsWithAccess.changes( + ) | rpl::filter([=](const base::flat_set &list) { + return !list.empty(); + }) | rpl::start_with_next([=] { + if (_allParticipantsLoaded) { + checkStaleParticipants(); + } else { + requestParticipants(); + } + }, _checkStaleLifetime); } } @@ -84,6 +109,7 @@ GroupCall::~GroupCall() { } api().request(_unknownParticipantPeersRequestId).cancel(); api().request(_participantsRequestId).cancel(); + api().request(_checkStaleRequestId).cancel(); api().request(_reloadRequestId).cancel(); } @@ -158,7 +184,7 @@ void GroupCall::requestParticipants() { : ApplySliceSource::SliceLoaded)); setServerParticipantsCount(data.vcount().v); if (data.vparticipants().v.isEmpty()) { - _allParticipantsLoaded = true; + setParticipantsLoaded(); } finishParticipantsSliceRequest(); if (reloaded) { @@ -169,7 +195,7 @@ void GroupCall::requestParticipants() { _participantsRequestId = 0; const auto reloaded = processSavedFullCall(); setServerParticipantsCount(_participants.size()); - _allParticipantsLoaded = true; + setParticipantsLoaded(); finishParticipantsSliceRequest(); if (reloaded) { _participantsReloaded.fire({}); @@ -177,6 +203,76 @@ void GroupCall::requestParticipants() { }).send(); } +void GroupCall::setParticipantsLoaded() { + _allParticipantsLoaded = true; + checkStaleParticipants(); +} + +void GroupCall::checkStaleParticipants() { + if (_checkStaleRequestId) { + return; + } + const auto &list = _participantsWithAccess.current(); + if (list.empty()) { + return; + } + auto existing = base::flat_set(); + existing.reserve(_participants.size() + 1); + existing.emplace(session().userId()); + for (const auto &participant : _participants) { + if (const auto id = peerToUser(participant.peer->id)) { + existing.emplace(id); + } + } + if (list.size() > existing.size()) { + checkStaleRequest(); + return; + } + for (const auto &id : list) { + if (!existing.contains(id)) { + checkStaleRequest(); + return; + } + } +} + +void GroupCall::checkStaleRequest() { + if (_checkStaleRequestId) { + return; + } + _checkStaleRequestId = api().request(MTPphone_GetGroupParticipants( + input(), + MTP_vector(), // ids + MTP_vector(), // ssrcs + MTP_string(QString()), + MTP_int(kMaxConferenceMembers) + )).done([=](const MTPphone_GroupParticipants &result) { + _checkStaleRequestId = 0; + const auto &list = _participantsWithAccess.current(); + if (list.empty()) { + return; + } + auto existing = base::flat_set(); + const auto &data = result.data(); + existing.reserve(data.vparticipants().v.size() + 1); + existing.emplace(session().userId()); + for (const auto &participant : data.vparticipants().v) { + const auto peerId = peerFromMTP(participant.data().vpeer()); + if (const auto id = peerToUser(peerId)) { + existing.emplace(id); + } + } + for (const auto &id : list) { + if (!existing.contains(id)) { + _staleParticipantId.fire_copy(id); + return; + } + } + }).fail([=] { + _checkStaleRequestId = 0; + }).send(); +} + bool GroupCall::processSavedFullCall() { if (!_savedFull) { return false; @@ -287,6 +383,29 @@ auto GroupCall::participantSpeaking() const return _participantSpeaking.events(); } +void GroupCall::setParticipantsWithAccess(base::flat_set list) { + _participantsWithAccess = std::move(list); + if (_allParticipantsLoaded) { + checkStaleParticipants(); + } else { + requestParticipants(); + } +} + +auto GroupCall::participantsWithAccessCurrent() const +-> const base::flat_set & { + return _participantsWithAccess.current(); +} + +auto GroupCall::participantsWithAccessValue() const +-> rpl::producer> { + return _participantsWithAccess.value(); +} + +rpl::producer GroupCall::staleParticipantId() const { + return _staleParticipantId.events(); +} + void GroupCall::enqueueUpdate(const MTPUpdate &update) { update.match([&](const MTPDupdateGroupCall &updateData) { updateData.vcall().match([&](const MTPDgroupCall &data) { @@ -471,9 +590,7 @@ void GroupCall::applyEnqueuedUpdate(const MTPUpdate &update) { }, [](const auto &) { Unexpected("Type in GroupCall::applyEnqueuedUpdate."); }); - Core::App().calls().applyGroupCallUpdateChecked( - &_peer->session(), - update); + Core::App().calls().applyGroupCallUpdateChecked(&session(), update); } void GroupCall::processQueuedUpdates() { @@ -634,22 +751,15 @@ void GroupCall::applyParticipantsSlice( const auto existingVideoParams = (i != end(_participants)) ? i->videoParams : nullptr; - const auto existingState = (i != end(_participants)) - ? i->e2eState - : nullptr; auto videoParams = localUpdate ? existingVideoParams : Calls::ParseVideoParams( data.vvideo(), data.vpresentation(), existingVideoParams); - auto e2eState = localUpdate - ? existingState - : Calls::ParseParticipantState(data); const auto value = Participant{ .peer = participantPeer, .videoParams = std::move(videoParams), - .e2eState = std::move(e2eState), .date = data.vdate().v, .lastActive = lastActive, .raisedHandRating = raisedHandRating, @@ -1003,7 +1113,7 @@ bool GroupCall::joinedToTop() const { } ApiWrap &GroupCall::api() const { - return _peer->session().api(); + return session().api(); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 104ee83b21..0c07a9f11d 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -38,7 +38,6 @@ struct LastSpokeTimes { struct GroupCallParticipant { not_null peer; std::shared_ptr videoParams; - std::shared_ptr e2eState; TimeId date = 0; TimeId lastActive = 0; uint64 raisedHandRating = 0; @@ -146,6 +145,16 @@ public: [[nodiscard]] auto participantSpeaking() const -> rpl::producer>; + void setParticipantsWithAccess(base::flat_set list); + [[nodiscard]] auto participantsWithAccessCurrent() const + -> const base::flat_set &; + [[nodiscard]] auto participantsWithAccessValue() const + -> rpl::producer>; + [[nodiscard]] rpl::producer staleParticipantId() const; + void setParticipantsLoaded(); + void checkStaleParticipants(); + void checkStaleRequest(); + void enqueueUpdate(const MTPUpdate &update); void applyLocalUpdate( const MTPDupdateGroupCallParticipants &update); @@ -254,6 +263,11 @@ private: rpl::event_stream> _participantSpeaking; rpl::event_stream<> _participantsReloaded; + rpl::variable> _participantsWithAccess; + rpl::event_stream _staleParticipantId; + mtpRequestId _checkStaleRequestId = 0; + rpl::lifetime _checkStaleLifetime; + bool _joinMuted : 1 = false; bool _canChangeJoinMuted : 1 = true; bool _allParticipantsLoaded : 1 = false; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 5857a15726..5da6c9022e 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1347,7 +1347,7 @@ groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; inputGroupCallSlug#fe06823f slug:string = InputGroupCall; -groupCallParticipant#23860077 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo public_key:flags.16?int256 = GroupCallParticipant; +groupCallParticipant#eba636fe flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo = GroupCallParticipant; phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index bd3940b39c..326cfa048a 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -34,6 +34,24 @@ constexpr auto kShortPollChainBlocksWaitFor = crl::time(1000); }; } +[[nodiscard]] tde2e_api::Slice Slice(const std::vector &data) { + return { + reinterpret_cast(data.data()), + std::string_view::size_type(data.size()), + }; +} + +[[nodiscard]] ParticipantsSet ParseParticipantsSet( + const tde2e_api::CallState &state) { + auto result = ParticipantsSet(); + const auto &list = state.participants; + result.list.reserve(list.size()); + for (const auto &entry : list) { + result.list.emplace(UserId{ uint64(entry.user_id) }); + } + return result; +} + } // namespace Call::Call(UserId myUserId) @@ -48,7 +66,14 @@ Call::Call(UserId myUserId) memcpy(&_myKey, key.value().data(), sizeof(_myKey)); } +Call::~Call() { + if (const auto id = libId()) { + tde2e_api::call_destroy(id); + } +} + void Call::fail(CallFailure reason) { + _emojiHash = QByteArray(); _failure = reason; _failures.fire_copy(reason); } @@ -99,20 +124,92 @@ Block Call::makeJoinBlock() { }; } +Block Call::makeRemoveBlock(UserId id) { + if (failed() || !_id || id == _myUserId) { + return {}; + } + + auto state = tde2e_api::call_get_state(libId()); + if (!state.is_ok()) { + LOG_AND_FAIL(state.error(), CallFailure::Unknown); + return {}; + } + auto found = false; + auto updated = state.value(); + auto &list = updated.participants; + for (auto i = begin(list); i != end(list); ++i) { + if (uint64(i->user_id) == id.v) { + list.erase(i); + found = true; + break; + } + } + if (!found) { + return {}; + } + const auto result = tde2e_api::call_create_change_state_block( + libId(), + updated); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return {}; + } + return { + .data = QByteArray::fromStdString(result.value()), + }; +} + +rpl::producer Call::participantsSetValue() const { + return _participantsSet.value(); +} + void Call::joined() { shortPoll(0); - if (_id.v) { + if (_id) { shortPoll(1); } } -void Call::apply(const Block &last) { - if (_id.v) { - const auto result = tde2e_api::call_apply_block( - std::int64_t(_id.v), +void Call::apply(int subchain, const Block &last) { + Expects(_id || !subchain); + + auto verification = std::optional(); + const auto guard = gsl::finally([&] { + if (failed() || !_id) { + return; + } else if (!verification) { + const auto id = libId(); + auto result = tde2e_api::call_get_verification_state(id); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return; + } + verification = std::move(result.value()); + } + _emojiHash = verification->emoji_hash.has_value() + ? QByteArray::fromStdString(*verification->emoji_hash) + : QByteArray(); + checkForOutboundMessages(); + }); + + if (subchain) { + auto result = tde2e_api::call_receive_inbound_message( + libId(), Slice(last.data)); if (!result.is_ok()) { LOG_AND_FAIL(result.error(), CallFailure::Unknown); + } else { + verification = std::move(result.value()); + } + return; + } else if (_id) { + const auto result = tde2e_api::call_apply_block( + libId(), + Slice(last.data)); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + } else { + _participantsSet = ParseParticipantsSet(result.value()); } return; } @@ -137,6 +234,26 @@ void Call::apply(const Block &last) { entry.shortPollTimer.callOnce(kShortPollChainBlocksTimeout); } } + + const auto state = tde2e_api::call_get_state(libId()); + if (!state.is_ok()) { + LOG_AND_FAIL(state.error(), CallFailure::Unknown); + return; + } + _participantsSet = ParseParticipantsSet(state.value()); +} + +void Call::checkForOutboundMessages() { + Expects(_id); + + const auto result = tde2e_api::call_pull_outbound_messages(libId()); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return; + } else if (!result.value().empty()) { + _outboundBlocks.fire( + QByteArray::fromStdString(result.value().back())); + } } void Call::apply( @@ -145,7 +262,7 @@ void Call::apply( const Block &block, bool fromShortPoll) { Expects(subchain >= 0 && subchain < kSubChainsCount); - Expects(_id.v != 0 || !fromShortPoll || !subchain); + Expects(_id || !fromShortPoll || !subchain); if (!subchain && index >= _lastBlock0Height) { _lastBlock0 = block; @@ -158,7 +275,7 @@ void Call::apply( auto &entry = _subchains[subchain]; if (!fromShortPoll) { entry.lastUpdate = crl::now(); - if (index > entry.height || (!_id.v && subchain != 0)) { + if (index > entry.height || (!_id && subchain != 0)) { entry.waiting.emplace(index, block); checkWaitingBlocks(subchain); return; @@ -167,8 +284,8 @@ void Call::apply( if (failed()) { return; - } else if (!_id.v || entry.height == index) { - apply(block); + } else if (!_id || entry.height == index) { + apply(subchain, block); } entry.height = index + 1; checkWaitingBlocks(subchain); @@ -182,7 +299,7 @@ void Call::checkWaitingBlocks(int subchain, bool waited) { } auto &entry = _subchains[subchain]; - if (!_id.v) { + if (!_id) { entry.waitingTimer.callOnce(kShortPollChainBlocksWaitFor); return; } else if (entry.shortPolling) { @@ -200,12 +317,28 @@ void Call::checkWaitingBlocks(int subchain, bool waited) { } return; } else if (level == entry.height + 1) { - const auto result = tde2e_api::call_apply_block( - std::int64_t(_id.v), - Slice(waiting.begin()->second.data)); - if (!result.is_ok()) { - LOG_AND_FAIL(result.error(), CallFailure::Unknown); - return; + const auto slice = Slice(waiting.begin()->second.data); + if (subchain) { + auto result = tde2e_api::call_receive_inbound_message( + libId(), + slice); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return; + } + _emojiHash = result.value().emoji_hash.has_value() + ? QByteArray::fromStdString(*result.value().emoji_hash) + : QByteArray(); + checkForOutboundMessages(); + } else { + const auto result = tde2e_api::call_apply_block( + libId(), + slice); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return; + } + _participantsSet = ParseParticipantsSet(result.value()); } entry.height = level; } @@ -221,7 +354,7 @@ void Call::shortPoll(int subchain) { auto &entry = _subchains[subchain]; entry.waitingTimer.cancel(); entry.shortPollTimer.cancel(); - if (subchain && !_id.v) { + if (subchain && !_id) { // Not ready. entry.waitingTimer.callOnce(kShortPollChainBlocksWaitFor); return; @@ -230,6 +363,10 @@ void Call::shortPoll(int subchain) { _subchainRequests.fire({ subchain, entry.height }); } +std::int64_t Call::libId() const { + return std::int64_t(_id.v); +} + rpl::producer Call::subchainRequests() const { return _subchainRequests.events(); } @@ -246,6 +383,10 @@ void Call::subchainBlocksRequestFinished(int subchain) { checkWaitingBlocks(subchain); } +rpl::producer Call::sendOutboundBlock() const { + return _outboundBlocks.events(); +} + std::optional Call::failed() const { return _failure; } @@ -257,13 +398,16 @@ rpl::producer Call::failures() const { return _failures.events(); } +QByteArray Call::emojiHash() const { + return _emojiHash.current(); +} + +rpl::producer Call::emojiHashValue() const { + return _emojiHash.value(); +} + std::vector Call::encrypt(const std::vector &data) const { - const auto result = tde2e_api::call_encrypt( - std::int64_t(_id.v), - std::string_view{ - reinterpret_cast(data.data()), - data.size(), - }); + const auto result = tde2e_api::call_encrypt(libId(), Slice(data)); if (!result.is_ok()) { return {}; } @@ -274,12 +418,7 @@ std::vector Call::encrypt(const std::vector &data) const { } std::vector Call::decrypt(const std::vector &data) const { - const auto result = tde2e_api::call_decrypt( - std::int64_t(_id.v), - std::string_view{ - reinterpret_cast(data.data()), - data.size(), - }); + const auto result = tde2e_api::call_decrypt(libId(), Slice(data)); if (!result.is_ok()) { return {}; } diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 931702b0fb..6252c8effa 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -8,10 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/basic_types.h" +#include "base/flat_set.h" #include "base/timer.h" -#include #include +#include +#include #include @@ -19,6 +21,9 @@ namespace TdE2E { struct UserId { uint64 v = 0; + + friend inline constexpr auto operator<=>(UserId, UserId) = default; + friend inline constexpr bool operator==(UserId, UserId) = default; }; struct PrivateKeyId { @@ -27,6 +32,10 @@ struct PrivateKeyId { struct CallId { uint64 v = 0; + + explicit operator bool() const { + return v != 0; + } }; struct PublicKey { @@ -41,6 +50,14 @@ struct ParticipantState { PublicKey key; }; +struct ParticipantsSet { + base::flat_set list; + + friend inline bool operator==( + const ParticipantsSet &, + const ParticipantsSet &) = default; +}; + struct Block { QByteArray data; }; @@ -52,6 +69,7 @@ enum class CallFailure { class Call final { public: explicit Call(UserId myUserId); + ~Call(); [[nodiscard]] PublicKey myKey() const; @@ -69,11 +87,19 @@ public: [[nodiscard]] rpl::producer subchainRequests() const; void subchainBlocksRequestFinished(int subchain); + [[nodiscard]] rpl::producer sendOutboundBlock() const; + [[nodiscard]] std::optional failed() const; [[nodiscard]] rpl::producer failures() const; + [[nodiscard]] QByteArray emojiHash() const; + [[nodiscard]] rpl::producer emojiHashValue() const; + void refreshLastBlock0(std::optional block); [[nodiscard]] Block makeJoinBlock(); + [[nodiscard]] Block makeRemoveBlock(UserId id); + + [[nodiscard]] rpl::producer participantsSetValue() const; [[nodiscard]] std::vector encrypt( const std::vector &data) const; @@ -92,12 +118,15 @@ private: int height = 0; }; - void apply(const Block &last); + void apply(int subchain, const Block &last); void fail(CallFailure reason); + void checkForOutboundMessages(); void checkWaitingBlocks(int subchain, bool waited = false); void shortPoll(int subchain); + [[nodiscard]] std::int64_t libId() const; + CallId _id; UserId _myUserId; PrivateKeyId _myKeyId; @@ -107,10 +136,14 @@ private: SubChainState _subchains[kSubChainsCount]; rpl::event_stream _subchainRequests; + rpl::event_stream _outboundBlocks; std::optional _lastBlock0; int _lastBlock0Height = 0; + rpl::variable _participantsSet; + rpl::variable _emojiHash; + }; } // namespace TdE2E From dfe6ad3a3256b0db3e95e34bf137f99fd5da5b20 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Mar 2025 22:11:49 +0500 Subject: [PATCH 077/190] Update gift icon for input field. --- Telegram/Resources/icons/chat/input_gift.png | Bin 670 -> 679 bytes .../Resources/icons/chat/input_gift@2x.png | Bin 1073 -> 1144 bytes .../Resources/icons/chat/input_gift@3x.png | Bin 1422 -> 1588 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Telegram/Resources/icons/chat/input_gift.png b/Telegram/Resources/icons/chat/input_gift.png index c1f42e5e8428e9ac31dbe7601fe50edfdf9f0662..eac02d4b2a04b1d8bab45a4eb8dddc826294fd73 100644 GIT binary patch delta 561 zcmV-10?z%O1*Zj&fqxN6L_t(I5$#kvj{#8>9t`ma8sZT_BSC0Ps6-_Sg-9eijZWuJ z_ze&f##V z)#~wh+-x>A8cjZ*-)uGv!yJ!CLMw99=`^xnQ5AjIQwk&L3` zgg=|jfMhZmxttB9QYo6Iv7$sGp-?DPDpfX{1(`r=6=6N?G}p#pV9C4gALg)3_{3}h+TiNSlsP)PNx&| zOYQw`QXGiO<$@4$B%$%M*=#r|>9!GqU9VSsv9G%)`k)&igd8YYY!UG%4!a^OFB}eY zO^EstaT^wOXyo zWEu+vue8b`+Uh`{8>x4RJ_T#zAwkPE%6M*oSVwI-2Q*9bj{hw9s>kDOZY zL52iE0T9MUqk+4F7MICH&!)7GKmZMQ6B0rFG}`U<;c$o^!!7-IGPqI%G>{>ABoIxG z2P_(mdMZ2~?s{JSvSs zCkh%A5^N%}5%=bV}Eo0utB&e!uVWcwX%x5D2W->v%kFw}0EqCX=DkukVYNrBbO{tw>zCqce`DxCxAsx91bTIi&-odKA#T;5s2gQcxdEyyRld-E|-f-r_*UZpDz{* zXn>o|CMJN!QGaqtkk97}27{?o3Iiq#Jvtl?;99Ll83M#&aVQj;&1Sh=?s2JDELJL& zNBxApysszX0bwZ=3Z!N;CpPz9rBb1sPJgG*BxbMCXprLddTACTz@6{+ zJMQyr0RH8SAcL4ZEYUMF`pNtU<^Q11hxVA!ZnuM6BocjY`Yt0DqJIR9$K%mx6p2Le zjr=|f0>~hSg({g3lGG-Xsa~&R)4&!)6;i1bc(qzlMk0~mPq138tyYVYKLmk=&1f{r qWHPGw2qBisCAQ$_=#QGefcXYehu4;ZDEJuw00001w)fq&vjL_t(o3GG-*D0E>Mo|#0s6)}XeAt9Hf$U@3aqS#m{O2)!U ztdx>8B_?}O6SHJtg@r=7hMgq2gpoVR{rW$DuikU!`)0=AnEt=cSxoQkd!BdR^WDyC zFzAnF6^M_I@9yrNoSZy7JiNTToSmKR>+8$P%3=be(O6ScGk-ihytcM>dwaXFu`xP2 z+S1Yz5)#5O+6Z9k=;*k+yL*?O*Vosfp`oy_u=Mowm6a9A|JQJQeB98`piL@-f^3-S z?d@%Ue}8jx^ZEIijS~|STU%SKUR_#59jV#>U+=PaPLVbFA3JeTnI}2EzJ3Bkb zk}VLBH-C1{si~=vkrB0Bk;B8o$H&L{5BW_1DX5h^SnBWZ_gfMI5vVzhk5_qy-h2PHN(7AGB?xYy$F_Ymb`PEAdXii(11adB~ZdD&{U^7?m)gOZq-Na=e# z^MAwtn|wiP<)Gcz-26w7m2j@K>!O9lP_1Cs4coSk$200000NkvXXu0mjfvFrTq delta 958 zcmV;v13~=w2(bu|fq$DxL_t(o3GG{SX-NGALG>4u zduiJyH%W`22d!H~SU}iC#Ip^H(JNKJA zckaCkg?z~h{7(gXdwbW`)=o}N5{bn1_4Urq&h+%OTCJuUjDJSs{QSJ%?~ljhu~;k+ z2)JCX=H}*#R0!7P<>gc=^&`5X(P&3U$N2d8!^49p`1AJm=5#vCU(>?E0+GGHzi)4E zqw5L6QGXacJw14{2z`Hlo6TlERFaMU5o>2>rx@t?_?V)bo15b3@bEB2CnhGu zQL#rQcKrcQfv@cA>#OuvrYuko4C4HW_M+XK58XFrK1aN`g+JD;0Jj{4`d8w|h#v=xGRaF)9 zlY!wJi9}9MPkH~{-QCgAk;miVoziw~Z7l?#VOZ%qh(IT(L#WWo%F5*AB$DuI9v>eC z*^-Z1t=3>LAcTChlFSh}k&jkzuCA_5O-%`+nMWuOg(1w|-kx5s&jfN>BO@cEC5)Vj znIOPHfqzz@qu0%1zdrl!?rsc5#14_e(w4l+Ag&2Mae2&;=-y^=^yq^LV2sJe|0Kv+H zpcttbS%G=>{QSJq^C6E9I>F>FoERG#8nD=~T7Rt;iv=b7e7?NXBbwl^g5<-)L#$jS zI?A~)C1ODW2Ywv~2M74U}W+SA$R zTnh{Br)5AD8L%`Qg`b~aadB}*Mn+Url()CHot@p~<>lVq-ha%@%-Go2($bRHg}b{u zR5CL&tU9S4|tdwZXspZ`ez=H}*-lamP( z!ftD8yS=@Y*7$RJdRkpwZDV6YS|%m3va&8NE;#t8>+S8Wv9S^0e0_aaR#u45CstQi zgMxxg8WNmkXMbluK0fl133|J`yVus%1WbPO`1m+9G*nq^O6HiCm)H9GI?_)rQIpZp zQOuC-?d|*fd!UJoj4UfF^Yrv288PwCf4|+`-RtXX6elDkBrh*7Ha3=(#1u_SOEb}w z>*(lUGYxpa#G?72U0ht6o10llqOndSB_)Yfz`5bUcXD#VCPB@g zA~5jY-+$jJoh2nDd{0zm1ODu{{Har za8?C$dU`reVeE)Ng#498V1b%?h6Kq82ps2fbANMLCJOLMgtx|0;-;1sJpVV64OR)VMyTtQ~)OMk_3dLOsJW! zCx14S^%vEOO*CK&#^jg914EWJTuj-nt}bxS&(9_6E3X54G#;)<7U%b~vomTcBYb>( zN=r+ts;aE4{vMu&hK9h=ZH-+mur=ux`aaj+-=EDcn%}pfTLU1;GQjH=a&vPdArN{` zx4Nz9;{IG<0LfxT2;~3s5G>fQ3?G5y+36<+NP!^JQ}bZ#WcGL!^qEBX0IQ=9I0^9!BItV0OnQ1p2ptcei@2* zTsJQ`L%j3A;|6MKYKlFi{1zIp1^2dJhT=!UaDk3Xm57LloSYm=ASC4ySTR8k1pFtm!vPL)aowTh{v z5VB!t6_cGRl?ZATQ%NCY!;q{PK7V0mjo91UvrTi+0vH02EGo&s@U_C5^(t`i}hy?~pO8jbczU gqGdqKfN?VLFB1t&;JmxdasU7T07*qoM6N<$f`+=on*aa+ delta 1311 zcmV+)1>pL$42}zsfPVz|Nkl0DByx|E`}O^P@3%kCyY@Q!cAVqb-fx}B`K@O?Ydyc` zdG=a+?{%Kz6%82QM!#xw$hlGmDCfGBPs4!^3@jeXp;tkB*L(mX^lH$0sHx-rwIj{cmWxySulv zwA|d>{8IVc-`}^kwt9McO620=;#OBzRdj!g_xARRi;KU(6V{}^zdypP!IHX=z0lCm z{QUgK$43%sfPbEz9#>aa)|TTi3@=3BK8>!hurTVyN+YZY78Vv*`TF`g z+vM=m)6??uvLtkNcE*;&DHv>i(y`OiQ!;?HL|74|r>C>>`1p9X$zh}@B_%}?B5&AT zIEAlm{(rS=3}O2_cSSg6?R|{hKL~%_eN6vZ5z7~=C?q5Vmmhb1dwF@)*49QvMR9V= z=G)twRZkMwKZb{gEgu>oIX^$gc4gDr^YQWN>gvK=6kwA8gbBnz&oYAg66Ars1)Ibr zmImk;@{ zM?gRThT*BAzP{e-@gD>5ESj90TwPsFE4Hets=vSgqhIeEFMO1iii!$7b^ZRs!$WC8 z0DWye4NxL_psJ_N^lxu(OC#^@?kF})ztzybj)4-0Nen?%kqy6tOG@6}-V(r-gG*6y z$2g^**c6Waw5*AY{j`kzclM%28|Q&I+*Io|E~htq0J}^`2bIP- z{MN)N3f%!s4%7~Ol-Jnk=%{s(_&#mxsN&TNuR|u!73hJaD*# zNU19wjnL z`1qJ+NlHp0>|nr}J~=tDgY}4r2s%Z9lWZ-X@!4%W%z;suCK{k*+_aV^sxD~Kp`jtQ z-?KHQu*ET$gHa+tC1e`rjCSSb=F+?Xv$L}?F)>C5|Eu?D$6yHNSnD|FG-6uoz?2<7 zFfdS7R>n#W4h}XqH&KPh3e|94UVk3Sc+iWj2zAq3 z0Yfkcqt-fLbd6cgi(-qwz(BiHfmlus32Gkj7Yh>>V<@r}LJ Date: Fri, 28 Mar 2025 22:13:53 +0500 Subject: [PATCH 078/190] PoC confcall invite, emoji. --- .../calls/calls_emoji_fingerprint.cpp | 33 +++++---- .../calls/calls_emoji_fingerprint.h | 2 + .../calls/group/calls_group_call.cpp | 56 +++++++++++++-- .../calls/group/calls_group_call.h | 12 ++++ .../group/calls_group_invite_controller.cpp | 72 ++++++++++++++++++- .../calls/group/calls_group_panel.cpp | 51 +++++++++---- .../calls/group/calls_group_panel.h | 1 + Telegram/SourceFiles/history/history.cpp | 7 ++ Telegram/SourceFiles/history/history_item.cpp | 16 +++++ .../window/window_session_controller.cpp | 27 +++++-- .../window/window_session_controller.h | 3 +- 11 files changed, 238 insertions(+), 42 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp index d6b6ca32a3..5c00536ca3 100644 --- a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp +++ b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp @@ -124,9 +124,23 @@ uint64 ComputeEmojiIndex(bytes::const_span bytes) { } // namespace std::vector ComputeEmojiFingerprint(not_null call) { + if (!call->isKeyShaForFingerprintReady()) { + return {}; + } + return ComputeEmojiFingerprint(call->getKeyShaForFingerprint()); +} + +std::vector ComputeEmojiFingerprint( + bytes::const_span fingerprint) { auto result = std::vector(); constexpr auto EmojiCount = (base::array_size(Offsets) - 1); - for (auto index = 0; index != EmojiCount; ++index) { + constexpr auto kPartSize = 8; + for (auto partOffset = 0 + ; partOffset != fingerprint.size() + ; partOffset += kPartSize) { + auto value = ComputeEmojiIndex( + fingerprint.subspan(partOffset, kPartSize)); + auto index = value % EmojiCount; auto offset = Offsets[index]; auto size = Offsets[index + 1] - offset; auto string = QString::fromRawData( @@ -134,22 +148,7 @@ std::vector ComputeEmojiFingerprint(not_null call) { size); auto emoji = Ui::Emoji::Find(string); Assert(emoji != nullptr); - } - if (call->isKeyShaForFingerprintReady()) { - auto sha256 = call->getKeyShaForFingerprint(); - constexpr auto kPartSize = 8; - for (auto partOffset = 0; partOffset != sha256.size(); partOffset += kPartSize) { - auto value = ComputeEmojiIndex(gsl::make_span(sha256).subspan(partOffset, kPartSize)); - auto index = value % EmojiCount; - auto offset = Offsets[index]; - auto size = Offsets[index + 1] - offset; - auto string = QString::fromRawData( - reinterpret_cast(Data + offset), - size); - auto emoji = Ui::Emoji::Find(string); - Assert(emoji != nullptr); - result.push_back(emoji); - } + result.push_back(emoji); } return result; } diff --git a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h index 9ec4e9b684..285f9ebaff 100644 --- a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h +++ b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h @@ -19,6 +19,8 @@ class Call; [[nodiscard]] std::vector ComputeEmojiFingerprint( not_null call); +[[nodiscard]] std::vector ComputeEmojiFingerprint( + bytes::const_span fingerprint); [[nodiscard]] object_ptr CreateFingerprintAndSignalBars( not_null parent, diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 14125ca7c0..30d940693e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1112,6 +1112,10 @@ bool GroupCall::rtmp() const { return _rtmp; } +bool GroupCall::conference() const { + return _conferenceCall != nullptr; +} + bool GroupCall::listenersHidden() const { return _listenersHidden; } @@ -1152,6 +1156,12 @@ rpl::producer> GroupCall::real() const { return _realChanges.events(); } +rpl::producer GroupCall::emojiHashValue() const { + Expects(_e2e != nullptr); + + return _e2e->emojiHashValue(); +} + void GroupCall::start(TimeId scheduleDate, bool rtmp) { using Flag = MTPphone_CreateGroupCall::Flag; _createRequestId = _api.request(MTPphone_CreateGroupCall( @@ -1610,8 +1620,13 @@ void GroupCall::requestSubchainBlocks(int subchain, int height) { MTP_int(kShortPollChainBlocksPerRequest) )).done([=](const MTPUpdates &result) { auto &state = _subchains[subchain]; - _peer->session().api().applyUpdates(result); state.requestId = 0; + state.inShortPoll = true; + _peer->session().api().applyUpdates(result); + state.inShortPoll = false; + for (const auto &data : base::take(state.pending)) { + applySubChainUpdate(subchain, data.blocks, data.next); + } _e2e->subchainBlocksRequestFinished(subchain); }).fail([=](const MTP::Error &error) { auto &state = _subchains[subchain]; @@ -2221,11 +2236,26 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallChainBlocks &data) { return; } auto &entry = _subchains[subchain]; - const auto inpoll = entry.requestId != 0; + const auto &blocks = data.vblocks().v; const auto next = data.vnext_offset().v; - auto now = next - int(data.vblocks().v.size()); - for (const auto &block : data.vblocks().v) { - _e2e->apply(subchain, now++, { block.v }, inpoll); + if (entry.requestId) { + Assert(!entry.inShortPoll); + entry.pending.push_back({ blocks, next }); + } else { + applySubChainUpdate(subchain, blocks, next); + } +} + +void GroupCall::applySubChainUpdate( + int subchain, + const QVector &blocks, + int next) { + Expects(subchain >= 0 && subchain < kSubChainsCount); + + auto &entry = _subchains[subchain]; + auto now = next - int(blocks.size()); + for (const auto &block : blocks) { + _e2e->apply(subchain, now++, { block.v }, entry.inShortPoll); } } @@ -3667,6 +3697,22 @@ std::variant> GroupCall::inviteUsers( } const auto owner = &_peer->owner(); + if (_conferenceCall) { + for (const auto &user : users) { + _api.request(MTPphone_InviteConferenceCallParticipant( + inputCall(), + user->inputUser + )).send(); + } + auto result = std::variant>(0); + if (users.size() != 1) { + result = int(users.size()); + } else { + result = users.front(); + } + return result; + } + auto count = 0; auto slice = QVector(); auto result = std::variant>(0); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 71c124f408..64d3a15ad5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -245,6 +245,7 @@ public: } [[nodiscard]] bool scheduleStartSubscribed() const; [[nodiscard]] bool rtmp() const; + [[nodiscard]] bool conference() const; [[nodiscard]] bool listenersHidden() const; [[nodiscard]] bool emptyRtmp() const; [[nodiscard]] rpl::producer emptyRtmpValue() const; @@ -256,6 +257,7 @@ public: [[nodiscard]] Data::GroupCall *lookupReal() const; [[nodiscard]] rpl::producer> real() const; + [[nodiscard]] rpl::producer emojiHashValue() const; void start(TimeId scheduleDate, bool rtmp); void hangup(); @@ -479,8 +481,14 @@ private: ssrc = updatedSsrc; } }; + struct SubChainPending { + QVector blocks; + int next = 0; + }; struct SubChainState { + std::vector pending; mtpRequestId requestId = 0; + bool inShortPoll = false; }; friend inline constexpr bool is_flag_type(SendUpdateType) { @@ -515,6 +523,10 @@ private: void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); void handleUpdate(const MTPDupdateGroupCallChainBlocks &data); + void applySubChainUpdate( + int subchain, + const QVector &blocks, + int next); bool tryCreateController(); void destroyController(); bool tryCreateScreencast(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index bb8c908559..1e13d45a82 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -56,6 +56,43 @@ namespace { return result; } +class ConfInviteController final : public ContactsBoxController { +public: + ConfInviteController( + not_null session, + base::flat_set> alreadyIn, + Fn)> choose) + : ContactsBoxController(session) + , _alreadyIn(std::move(alreadyIn)) + , _choose(std::move(choose)) { + } + +protected: + std::unique_ptr createRow( + not_null user) override { + if (user->isSelf() + || user->isBot() + || user->isServiceUser() + || user->isInaccessible()) { + return nullptr; + } + auto result = ContactsBoxController::createRow(user); + if (_alreadyIn.contains(user)) { + result->setDisabledState(PeerListRow::State::DisabledChecked); + } + return result; + } + + void rowClicked(not_null row) override { + _choose(row->peer()); + } + +private: + const base::flat_set> _alreadyIn; + const Fn)> _choose; + +}; + } // namespace InviteController::InviteController( @@ -173,6 +210,7 @@ object_ptr PrepareInviteBox( return nullptr; } const auto peer = call->peer(); + const auto weak = base::make_weak(call); auto alreadyIn = peer->owner().invitedToCallUsers(real->id()); for (const auto &participant : real->participants()) { if (const auto user = participant.peer->asUser()) { @@ -180,6 +218,39 @@ object_ptr PrepareInviteBox( } } alreadyIn.emplace(peer->session().user()); + if (call->conference()) { + const auto close = std::make_shared>(); + const auto invite = [=](not_null peer) { + Expects(peer->isUser()); + + const auto call = weak.get(); + if (!call) { + return; + } + const auto user = peer->asUser(); + call->inviteUsers({ user }); + showToast(tr::lng_group_call_invite_done_user( + tr::now, + lt_user, + Ui::Text::Bold(user->firstName), + Ui::Text::WithEntities)); + (*close)(); + }; + auto controller = std::make_unique( + &real->session(), + alreadyIn, + invite); + controller->setStyleOverrides( + &st::groupCallInviteMembersList, + &st::groupCallMultiSelect); + auto initBox = [=](not_null box) { + box->setTitle(tr::lng_group_call_invite_title()); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + *close = [=] { box->closeBox(); }; + }; + return Box(std::move(controller), initBox); + } + auto controller = std::make_unique(peer, alreadyIn); controller->setStyleOverrides( &st::groupCallInviteMembersList, @@ -194,7 +265,6 @@ object_ptr PrepareInviteBox( &st::groupCallInviteMembersList, &st::groupCallMultiSelect); - const auto weak = base::make_weak(call); const auto invite = [=](const std::vector> &users) { const auto call = weak.get(); if (!call) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 4ae84ffcc6..e50397b40f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_invite_controller.h" #include "calls/group/ui/calls_group_scheduled_labels.h" #include "calls/group/ui/desktop_capture_choose_source.h" +#include "calls/calls_emoji_fingerprint.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/platform/ui_platform_utility.h" #include "ui/controls/call_mute_button.h" @@ -76,6 +77,19 @@ constexpr auto kControlsBackgroundOpacity = 0.8; constexpr auto kOverrideActiveColorBgAlpha = 172; constexpr auto kHideControlsTimeout = 5 * crl::time(1000); +[[nodiscard]] QString ComposeTitle(const QByteArray &hash) { + auto result = tr::lng_confcall_join_title(tr::now); + if (hash.size() >= 32) { + const auto fp = bytes::make_span(hash).subspan(0, 32); + const auto emoji = Calls::ComputeEmojiFingerprint(fp); + result += QString::fromUtf8(" \xc2\xb7 "); + for (const auto &single : emoji) { + result += single->text(); + } + } + return result; +} + class Show final : public Main::SessionShow { public: explicit Show(not_null panel); @@ -367,7 +381,13 @@ void Panel::initWindow() { window()->setAttribute(Qt::WA_NoSystemBackground); window()->setTitleStyle(st::groupCallTitle); - subscribeToPeerChanges(); + if (_call->conference()) { + titleText() | rpl::start_with_next([=](const QString &text) { + window()->setTitle(text); + }, lifetime()); + } else { + subscribeToPeerChanges(); + } base::install_event_filter(window().get(), [=](not_null e) { if (e->type() == QEvent::Close && handleClose()) { @@ -2445,19 +2465,26 @@ void Panel::updateMembersGeometry() { } } +rpl::producer Panel::titleText() { + if (_call->conference()) { + return _call->emojiHashValue() | rpl::map(ComposeTitle); + } + return rpl::combine( + Info::Profile::NameValue(_peer), + rpl::single( + QString() + ) | rpl::then(_call->real( + ) | rpl::map([=](not_null real) { + return real->titleValue(); + }) | rpl::flatten_latest()) + ) | rpl::map([=](const QString &name, const QString &title) { + return title.isEmpty() ? name : title; + }); +} + void Panel::refreshTitle() { if (!_title) { - auto text = rpl::combine( - Info::Profile::NameValue(_peer), - rpl::single( - QString() - ) | rpl::then(_call->real( - ) | rpl::map([=](not_null real) { - return real->titleValue(); - }) | rpl::flatten_latest()) - ) | rpl::map([=](const QString &name, const QString &title) { - return title.isEmpty() ? name : title; - }) | rpl::after_next([=] { + auto text = titleText() | rpl::after_next([=] { refreshTitleGeometry(); }); _title.create( diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 851cc91d8f..a591273d06 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -155,6 +155,7 @@ private: void setupMembers(); void setupVideo(not_null viewport); void setupRealMuteButtonState(not_null real); + [[nodiscard]] rpl::producer titleText(); bool handleClose(); void startScheduledNow(); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index bde7012f1b..9e6f44410c 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "main/main_session.h" #include "window/notifications_manager.h" +#include "window/window_session_controller.h" #include "calls/calls_instance.h" #include "spellcheck/spellcheck_types.h" #include "storage/localstorage.h" @@ -1225,6 +1226,12 @@ void History::applyServiceChanges( topic->setHidden(mtpIsTrue(*hidden)); } } + }, [&](const MTPDmessageActionConferenceCall &data) { + if (!data.is_active() && !data.is_missed()) { + if (const auto window = session().tryResolveWindow()) { + window->resolveConferenceCall(qs(data.vslug()), item->id); + } + } }, [](const auto &) { }); } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index dd80c64291..eb227ac323 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5634,6 +5634,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { : duration ? tr::lng_action_confcall_finished(tr::now) : tr::lng_action_confcall_invitation(tr::now); + + if (duration) { + result.text.text += " (" + QString::number(duration) + " seconds)"; + } + + const auto id = this->id; + const auto slug = qs(action.vslug()); + setCustomServiceLink(std::make_shared([=]( + ClickContext context) { + const auto my = context.other.value(); + const auto weak = my.sessionWindow; + if (const auto strong = weak.get()) { + strong->resolveConferenceCall(slug, id); + } + })); + return result; }; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a493903ab3..a8d30105c7 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -840,12 +840,16 @@ void SessionNavigation::resolveCollectible( }).send(); } -void SessionNavigation::resolveConferenceCall(const QString &slug) { - if (_conferenceCallSlug == slug) { +void SessionNavigation::resolveConferenceCall( + const QString &slug, + MsgId inviteMsgId) { + if (_conferenceCallSlug == slug + && _conferenceCallInviteMsgId == inviteMsgId) { return; } _api.request(base::take(_conferenceCallRequestId)).cancel(); _conferenceCallSlug = slug; + _conferenceCallInviteMsgId = inviteMsgId; const auto limit = 5; _conferenceCallRequestId = _api.request(MTPphone_GetGroupCall( @@ -864,15 +868,26 @@ void SessionNavigation::resolveConferenceCall(const QString &slug) { false, // rtmp true); // conference call->processFullCall(result); + const auto confirmed = std::make_shared(); const auto join = [=] { - Core::App().calls().startOrJoinConferenceCall( - uiShow(), - { .call = call, .linkSlug = slug }); + *confirmed = true; + Core::App().calls().startOrJoinConferenceCall(uiShow(), { + .call = call, + .linkSlug = slug, + .joinMessageId = inviteMsgId, + }); }; - uiShow()->show(Box( + const auto box = uiShow()->show(Box( Calls::Group::ConferenceCallJoinConfirm, call, join)); + box->boxClosing() | rpl::start_with_next([=] { + if (inviteMsgId && !*confirmed) { + _api.request(MTPphone_DeclineConferenceCallInvite( + MTP_int(inviteMsgId) + )).send(); + } + }, box->lifetime()); }); }).fail([=] { _conferenceCallRequestId = 0; diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index fd7056a1e0..33b665a2ba 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -264,7 +264,7 @@ public: PeerId ownerId, const QString &entity, Fn fail = nullptr); - void resolveConferenceCall(const QString &slug); + void resolveConferenceCall(const QString &slug, MsgId inviteMsgId = 0); base::weak_ptr showToast( Ui::Toast::Config &&config); @@ -331,6 +331,7 @@ private: mtpRequestId _collectibleRequestId = 0; QString _conferenceCallSlug; + MsgId _conferenceCallInviteMsgId; mtpRequestId _conferenceCallRequestId = 0; }; From 4eaf03b922675f7539e0673a8219a49a7e814e49 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Mar 2025 23:04:14 +0500 Subject: [PATCH 079/190] Update API scheme on layer 202. --- Telegram/SourceFiles/calls/calls_instance.cpp | 8 +- .../calls/group/calls_group_call.cpp | 115 ++++++++++-------- .../calls/group/calls_group_call.h | 4 +- Telegram/SourceFiles/data/data_channel.cpp | 2 +- Telegram/SourceFiles/data/data_chat.cpp | 2 +- Telegram/SourceFiles/data/data_group_call.cpp | 15 ++- Telegram/SourceFiles/data/data_group_call.h | 5 +- Telegram/SourceFiles/history/history.cpp | 4 +- Telegram/SourceFiles/history/history_item.cpp | 3 +- .../history/history_item_helpers.cpp | 4 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 5 +- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 14 ++- Telegram/SourceFiles/tde2e/tde2e_api.h | 2 +- .../SourceFiles/ui/image/image_location.cpp | 2 +- .../window/window_session_controller.cpp | 15 ++- .../window/window_session_controller.h | 4 +- 16 files changed, 123 insertions(+), 81 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 6c8298f4cb..9a3147e76c 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -678,14 +678,14 @@ void Instance::handleGroupCallUpdate( }, [](const MTPDupdateGroupCallParticipants &data) { return data.vcall().match([&](const MTPDinputGroupCall &data) { return data.vid().v; - }, [](const MTPDinputGroupCallSlug &) -> CallId { - Unexpected("slug in Instance::handleGroupCallUpdate"); + }, [](const auto &) -> CallId { + Unexpected("slug/msg in Instance::handleGroupCallUpdate"); }); }, [](const MTPDupdateGroupCallChainBlocks &data) { return data.vcall().match([&](const MTPDinputGroupCall &data) { return data.vid().v; - }, [](const MTPDinputGroupCallSlug &) -> CallId { - Unexpected("slug in Instance::handleGroupCallUpdate"); + }, [](const auto &) -> CallId { + Unexpected("slug/msg in Instance::handleGroupCallUpdate"); }); }, [](const auto &) -> CallId { Unexpected("Type in Instance::handleGroupCallUpdate."); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 30d940693e..250ca5e49a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -715,44 +715,55 @@ void GroupCall::setupConferenceCall() { sendOutboundBlock(std::move(block)); }, _lifetime); - _conferenceCall->staleParticipantId( - ) | rpl::start_with_next([=](UserId staleId) { - removeConferenceParticipant(staleId); + _conferenceCall->staleParticipantIds( + ) | rpl::start_with_next([=](const base::flat_set &staleIds) { + removeConferenceParticipants(staleIds); }, _lifetime); _e2e->participantsSetValue( ) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) { auto users = base::flat_set(); users.reserve(set.list.size()); - auto ids = QStringList(); for (const auto &id : set.list) { users.emplace(UserId(id.v)); - ids.push_back('"' + _peer->owner().user(UserId(id.v))->name() + '"'); } - LOG(("ACCESS: ") + ids.join(", ")); _conferenceCall->setParticipantsWithAccess(std::move(users)); }, _lifetime); } -void GroupCall::removeConferenceParticipant(UserId id) { +void GroupCall::removeConferenceParticipants( + const base::flat_set userIds) { Expects(_e2e != nullptr); + Expects(!userIds.empty()); - const auto block = _e2e->makeRemoveBlock(TdE2E::MakeUserId(id)); + const auto owner = &_peer->owner(); + auto inputs = QVector(); + inputs.reserve(userIds.size()); + auto ids = base::flat_set(); + ids.reserve(userIds.size()); + for (const auto &id : userIds) { + inputs.push_back(owner->user(id)->input); + ids.emplace(TdE2E::MakeUserId(id)); + } + const auto block = _e2e->makeRemoveBlock(ids); if (block.data.isEmpty()) { return; } - _api.request(MTPphone_DeleteConferenceCallParticipant( + _api.request(MTPphone_DeleteConferenceCallParticipants( inputCall(), - _peer->owner().user(id)->input, + MTP_vector(std::move(inputs)), MTP_bytes(block.data) )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); }).fail([=](const MTP::Error &error) { const auto type = error.type(); if (type == u"GROUPCALL_FORBIDDEN"_q) { - setState(State::Joining); - rejoin(); + LOG(("Call Info: " + "Rejoin after error '%1' in delete confcall participants" + ).arg(type)); + startRejoin(); } else { - LOG(("NOTREMOVED: %1").arg(type)); + LOG(("Call Error: Could not remove confcall participants: %1" + ).arg(type)); } }).send(); } @@ -1187,8 +1198,8 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { inputCall.match([&](const MTPDinputGroupCall &data) { _id = data.vid().v; _accessHash = data.vaccess_hash().v; - }, [&](const MTPDinputGroupCallSlug &) { - Unexpected("inputGroupCallSlug in GroupCall::join."); + }, [&](const auto &) { + Unexpected("slug/msg in GroupCall::join."); }); setState(_scheduleDate ? State::Waiting : State::Joining); @@ -1391,6 +1402,14 @@ void GroupCall::markTrackPaused(const VideoEndpoint &endpoint, bool paused) { : Webrtc::VideoState::Active); } +void GroupCall::startRejoin() { + for (const auto &[task, part] : _broadcastParts) { + _api.request(part.requestId).cancel(); + } + setState(State::Joining); + rejoin(); +} + void GroupCall::rejoin() { rejoin(joinAs()); } @@ -1489,10 +1508,7 @@ void GroupCall::sendJoinRequest() { | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); _api.request(MTPphone_JoinGroupCall( MTP_flags(flags), - (_conferenceLinkSlug.isEmpty() - ? inputCall() - : MTP_inputGroupCallSlug( - MTP_string(_conferenceLinkSlug))), + inputCallSafe(), joinAs()->input, MTP_string(_joinHash), (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), @@ -1573,7 +1589,7 @@ void GroupCall::refreshLastBlockAndJoin() { return; } _api.request(MTPphone_GetGroupCallChainBlocks( - inputCall(), + inputCallSafe(), MTP_int(0), MTP_int(-1), MTP_int(1) @@ -1632,6 +1648,11 @@ void GroupCall::requestSubchainBlocks(int subchain, int height) { auto &state = _subchains[subchain]; state.requestId = 0; _e2e->subchainBlocksRequestFinished(subchain); + if (error.type() == u"GROUPCALL_FORBIDDEN"_q) { + LOG(("Call Info: Rejoin after error '%1' in get chain blocks." + ).arg(error.type())); + startRejoin(); + } }).send(); } @@ -1646,13 +1667,14 @@ void GroupCall::sendOutboundBlock(QByteArray block) { const auto type = error.type(); if (type == u"GROUPCALL_FORBIDDEN"_q) { _pendingOutboundBlock = block; - setState(State::Joining); - rejoin(); + LOG(("Call Info: Rejoin after error '%1' in send confcall block." + ).arg(type)); + startRejoin(); } else if (type == u"BLOCK_INVALID"_q || type.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) { LOG(("Call Error: Could not broadcast block: %1").arg(type)); } else { - LOG(("HMM")); + LOG(("Call Error: Got '%1' in send confcall block.").arg(type)); sendOutboundBlock(block); } }).send(); @@ -2196,8 +2218,8 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) { const auto callId = data.vcall().match([]( const MTPDinputGroupCall &data) { return data.vid().v; - }, [](const MTPDinputGroupCallSlug &) -> CallId { - Unexpected("inputGroupCallSlug in GroupCall::handleUpdate."); + }, [](const auto &) -> CallId { + Unexpected("slug/msg in GroupCall::handleUpdate."); }); if (_id != callId) { return; @@ -2225,8 +2247,8 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallChainBlocks &data) { const auto callId = data.vcall().match([]( const MTPDinputGroupCall &data) { return data.vid().v; - }, [](const MTPDinputGroupCallSlug &) -> CallId { - Unexpected("inputGroupCallSlug in GroupCall::handleUpdate."); + }, [](const auto &) -> CallId { + Unexpected("slug/msg in GroupCall::handleUpdate."); }); if (_id != callId || !_e2e) { return; @@ -2279,8 +2301,7 @@ void GroupCall::applySelfUpdate(const MTPDgroupCallParticipant &data) { // I was removed from the call, rejoin. LOG(("Call Info: " "Rejoin after got 'left' with my ssrc.")); - setState(State::Joining); - rejoin(); + startRejoin(); } return; } else if (data.vsource().v != _joinState.ssrc) { @@ -2307,8 +2328,7 @@ void GroupCall::applySelfUpdate(const MTPDgroupCallParticipant &data) { : MuteState::ForceMuted); } else if (_instanceMode == InstanceMode::Stream) { LOG(("Call Info: Rejoin after unforcemute in stream mode.")); - setState(State::Joining); - rejoin(); + startRejoin(); } else if (mutedByAdmin()) { setMuted(MuteState::Muted); if (!_instanceTransitioning) { @@ -2882,11 +2902,7 @@ void GroupCall::broadcastPartStart(std::shared_ptr task) { }).fail([=](const MTP::Error &error, const MTP::Response &response) { if (error.type() == u"GROUPCALL_JOIN_MISSING"_q || error.type() == u"GROUPCALL_FORBIDDEN"_q) { - for (const auto &[task, part] : _broadcastParts) { - _api.request(part.requestId).cancel(); - } - setState(State::Joining); - rejoin(); + startRejoin(); return; } const auto status = (MTP::IsFloodError(error) @@ -3005,11 +3021,7 @@ void GroupCall::requestCurrentTimeStart( if (error.type() == u"GROUPCALL_JOIN_MISSING"_q || error.type() == u"GROUPCALL_FORBIDDEN"_q) { - for (const auto &[task, part] : _broadcastParts) { - _api.request(part.requestId).cancel(); - } - setState(State::Joining); - rejoin(); + startRejoin(); } }).handleAllErrors().toDC( MTP::groupCallStreamDcId(_broadcastDcId) @@ -3415,7 +3427,7 @@ void GroupCall::checkJoined() { }).fail([=](const MTP::Error &error) { LOG(("Call Info: Full rejoin after error '%1' in checkGroupCall." ).arg(error.type())); - rejoin(); + startRejoin(); }).send(); } @@ -3590,7 +3602,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { if (error.type() == u"GROUPCALL_FORBIDDEN"_q) { LOG(("Call Info: Rejoin after error '%1' in editGroupCallMember." ).arg(error.type())); - rejoin(); + startRejoin(); } }).send(); } @@ -3684,7 +3696,7 @@ void GroupCall::editParticipant( if (error.type() == u"GROUPCALL_FORBIDDEN"_q) { LOG(("Call Info: Rejoin after error '%1' in editGroupCallMember." ).arg(error.type())); - rejoin(); + startRejoin(); } }).send(); } @@ -3700,7 +3712,7 @@ std::variant> GroupCall::inviteUsers( if (_conferenceCall) { for (const auto &user : users) { _api.request(MTPphone_InviteConferenceCallParticipant( - inputCall(), + inputCallSafe(), user->inputUser )).send(); } @@ -3821,9 +3833,16 @@ auto GroupCall::otherParticipantStateValue() const MTPInputGroupCall GroupCall::inputCall() const { Expects(_id != 0); - return MTP_inputGroupCall( - MTP_long(_id), - MTP_long(_accessHash)); + return MTP_inputGroupCall(MTP_long(_id), MTP_long(_accessHash)); +} + +MTPInputGroupCall GroupCall::inputCallSafe() const { + const auto inviteMsgId = _conferenceJoinMessageId.bare; + return inviteMsgId + ? MTP_inputGroupCallInviteMessage(MTP_int(inviteMsgId)) + : _conferenceLinkSlug.isEmpty() + ? inputCall() + : MTP_inputGroupCallSlug(MTP_string(_conferenceLinkSlug)); } void GroupCall::destroyController() { diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 64d3a15ad5..4925eccb57 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -281,7 +281,7 @@ public: void startScheduledNow(); void toggleScheduleStartSubscribed(bool subscribed); void setNoiseSuppression(bool enabled); - void removeConferenceParticipant(UserId userId); + void removeConferenceParticipants(const base::flat_set userIds); bool emitShareScreenError(); bool emitShareCameraError(); @@ -545,6 +545,7 @@ private: const std::optional &was, const Data::GroupCallParticipant &now); void applyMeInCallLocally(); + void startRejoin(); void rejoin(); void leave(); void rejoin(not_null as); @@ -615,6 +616,7 @@ private: [[nodiscard]] int activeVideoSendersCount() const; [[nodiscard]] MTPInputGroupCall inputCall() const; + [[nodiscard]] MTPInputGroupCall inputCallSafe() const; const not_null _delegate; const std::shared_ptr _conferenceCall; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 0793099c70..fb6c980550 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1006,7 +1006,7 @@ void ChannelData::setGroupCall( owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); - }, [&](const MTPDinputGroupCallSlug &) { + }, [&](const auto &) { clearGroupCall(); }); } diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 8bd3a8cac9..fc33c01708 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -239,7 +239,7 @@ void ChatData::setGroupCall( owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); - }, [&](const MTPDinputGroupCallSlug &) { + }, [&](const auto &) { clearGroupCall(); }); } diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index bab56f63aa..47cdc4fabf 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -85,7 +85,7 @@ GroupCall::GroupCall( }) | rpl::start_with_next([=](const ParticipantUpdate &update) { if (const auto id = peerToUser(update.was->peer->id)) { if (_participantsWithAccess.current().contains(id)) { - _staleParticipantId.fire_copy(id); + _staleParticipantIds.fire({ id }); } } }, _checkStaleLifetime); @@ -262,12 +262,16 @@ void GroupCall::checkStaleRequest() { existing.emplace(id); } } + auto stale = base::flat_set(); for (const auto &id : list) { if (!existing.contains(id)) { - _staleParticipantId.fire_copy(id); - return; + stale.reserve(list.size()); + stale.emplace(id); } } + if (!stale.empty()) { + _staleParticipantIds.fire(std::move(stale)); + } }).fail([=] { _checkStaleRequestId = 0; }).send(); @@ -402,8 +406,9 @@ auto GroupCall::participantsWithAccessValue() const return _participantsWithAccess.value(); } -rpl::producer GroupCall::staleParticipantId() const { - return _staleParticipantId.events(); +auto GroupCall::staleParticipantIds() const +-> rpl::producer> { + return _staleParticipantIds.events(); } void GroupCall::enqueueUpdate(const MTPUpdate &update) { diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 0c07a9f11d..d9dfdb8559 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -150,7 +150,8 @@ public: -> const base::flat_set &; [[nodiscard]] auto participantsWithAccessValue() const -> rpl::producer>; - [[nodiscard]] rpl::producer staleParticipantId() const; + [[nodiscard]] auto staleParticipantIds() const + -> rpl::producer>; void setParticipantsLoaded(); void checkStaleParticipants(); void checkStaleRequest(); @@ -264,7 +265,7 @@ private: rpl::event_stream<> _participantsReloaded; rpl::variable> _participantsWithAccess; - rpl::event_stream _staleParticipantId; + rpl::event_stream> _staleParticipantIds; mtpRequestId _checkStaleRequestId = 0; rpl::lifetime _checkStaleLifetime; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 9e6f44410c..8284bd5315 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1227,9 +1227,9 @@ void History::applyServiceChanges( } } }, [&](const MTPDmessageActionConferenceCall &data) { - if (!data.is_active() && !data.is_missed()) { + if (!data.is_active() && !data.is_missed() && !item->out()) { if (const auto window = session().tryResolveWindow()) { - window->resolveConferenceCall(qs(data.vslug()), item->id); + window->resolveConferenceCall(item->id); } } }, [](const auto &) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index eb227ac323..00bae0b60a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -5640,13 +5640,12 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { } const auto id = this->id; - const auto slug = qs(action.vslug()); setCustomServiceLink(std::make_shared([=]( ClickContext context) { const auto my = context.other.value(); const auto weak = my.sessionWindow; if (const auto strong = weak.get()) { - strong->resolveConferenceCall(slug, id); + strong->resolveConferenceCall(id); } })); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 0cd2a21733..b585fd4526 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -915,8 +915,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { [[nodiscard]] CallId CallIdFromInput(const MTPInputGroupCall &data) { return data.match([&](const MTPDinputGroupCall &data) { return data.vid().v; - }, [](const MTPDinputGroupCallSlug &) -> CallId { - Unexpected("inputGroupCallSlug in CallIdFromInput."); + }, [](const auto &) -> CallId { + Unexpected("slug/msg in CallIdFromInput."); }); } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 5da6c9022e..8fbb14c731 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -187,7 +187,7 @@ messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?tr messageActionStarGiftUnique#acdfcb81 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long = MessageAction; messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction; messageActionPaidMessagesPrice#bcd71419 stars:long = MessageAction; -messageActionConferenceCall#ff397dea flags:# missed:flags.0?true active:flags.1?true call_id:long slug:string duration:flags.2?int other_participants:flags.3?Vector = MessageAction; +messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -1346,6 +1346,7 @@ groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; inputGroupCallSlug#fe06823f slug:string = InputGroupCall; +inputGroupCallInviteMessage#8c10603f msg_id:int = InputGroupCall; groupCallParticipant#eba636fe flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo = GroupCallParticipant; @@ -2607,7 +2608,7 @@ phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallS phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; phone.createConferenceCall#fbcefee6 random_id:int = phone.GroupCall; -phone.deleteConferenceCallParticipant#7b8cc2a3 call:InputGroupCall peer:InputPeer block:bytes = Updates; +phone.deleteConferenceCallParticipants#22e547c7 call:InputGroupCall ids:Vector block:bytes = Updates; phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; phone.inviteConferenceCallParticipant#3e9cf7ee call:InputGroupCall user_id:InputUser = Updates; phone.declineConferenceCallInvite#3c479971 msg_id:int = Updates; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 326cfa048a..b4e42a8f8d 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -124,8 +124,8 @@ Block Call::makeJoinBlock() { }; } -Block Call::makeRemoveBlock(UserId id) { - if (failed() || !_id || id == _myUserId) { +Block Call::makeRemoveBlock(const base::flat_set &ids) { + if (failed() || !_id) { return {}; } @@ -137,11 +137,13 @@ Block Call::makeRemoveBlock(UserId id) { auto found = false; auto updated = state.value(); auto &list = updated.participants; - for (auto i = begin(list); i != end(list); ++i) { - if (uint64(i->user_id) == id.v) { - list.erase(i); + for (auto i = begin(list); i != end(list);) { + const auto userId = UserId{ uint64(i->user_id) }; + if (userId != _myUserId && ids.contains(userId)) { + i = list.erase(i); found = true; - break; + } else { + ++i; } } if (!found) { diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 6252c8effa..0ddb750576 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -97,7 +97,7 @@ public: void refreshLastBlock0(std::optional block); [[nodiscard]] Block makeJoinBlock(); - [[nodiscard]] Block makeRemoveBlock(UserId id); + [[nodiscard]] Block makeRemoveBlock(const base::flat_set &ids); [[nodiscard]] rpl::producer participantsSetValue() const; diff --git a/Telegram/SourceFiles/ui/image/image_location.cpp b/Telegram/SourceFiles/ui/image/image_location.cpp index a10c6ba963..1484a98dda 100644 --- a/Telegram/SourceFiles/ui/image/image_location.cpp +++ b/Telegram/SourceFiles/ui/image/image_location.cpp @@ -173,7 +173,7 @@ StorageFileLocation::StorageFileLocation( _id = data.vid().v; _accessHash = data.vaccess_hash().v; }, [](const auto &data) { - Unexpected("inputGroupCallSlug in inputGroupCallStream."); + Unexpected("slug/msg in inputGroupCallStream."); }); _volumeId = data.vtime_ms().v; _localId = data.vscale().v; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a8d30105c7..52a213450d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -840,6 +840,14 @@ void SessionNavigation::resolveCollectible( }).send(); } +void SessionNavigation::resolveConferenceCall(const QString &slug) { + resolveConferenceCall(slug, 0); +} + +void SessionNavigation::resolveConferenceCall(MsgId inviteMsgId) { + resolveConferenceCall(QString(), inviteMsgId); +} + void SessionNavigation::resolveConferenceCall( const QString &slug, MsgId inviteMsgId) { @@ -853,11 +861,14 @@ void SessionNavigation::resolveConferenceCall( const auto limit = 5; _conferenceCallRequestId = _api.request(MTPphone_GetGroupCall( - MTP_inputGroupCallSlug(MTP_string(slug)), + (inviteMsgId + ? MTP_inputGroupCallInviteMessage(MTP_int(inviteMsgId.bare)) + : MTP_inputGroupCallSlug(MTP_string(slug))), MTP_int(limit) )).done([=](const MTPphone_GroupCall &result) { _conferenceCallRequestId = 0; const auto slug = base::take(_conferenceCallSlug); + const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); result.data().vcall().match([&](const auto &data) { const auto call = std::make_shared( @@ -884,7 +895,7 @@ void SessionNavigation::resolveConferenceCall( box->boxClosing() | rpl::start_with_next([=] { if (inviteMsgId && !*confirmed) { _api.request(MTPphone_DeclineConferenceCallInvite( - MTP_int(inviteMsgId) + MTP_int(inviteMsgId.bare) )).send(); } }, box->lifetime()); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 33b665a2ba..cb8432aaf4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -264,7 +264,8 @@ public: PeerId ownerId, const QString &entity, Fn fail = nullptr); - void resolveConferenceCall(const QString &slug, MsgId inviteMsgId = 0); + void resolveConferenceCall(const QString &slug); + void resolveConferenceCall(MsgId inviteMsgId); base::weak_ptr showToast( Ui::Toast::Config &&config); @@ -291,6 +292,7 @@ private: void resolveChannelById( ChannelId channelId, Fn)> done); + void resolveConferenceCall(const QString &slug, MsgId inviteMsgId); void resolveDone( const MTPcontacts_ResolvedPeer &result, From a300a2541968c0e8c7c49cf2474e60ae0409c99e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Mar 2025 23:51:59 +0500 Subject: [PATCH 080/190] Fix complicated crash in async base::Timer destroying. --- .../calls/group/calls_group_call.cpp | 10 +--- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 56 ++++++++++++------- Telegram/SourceFiles/tde2e/tde2e_api.h | 13 +++-- 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 250ca5e49a..d171165f20 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -2691,14 +2691,6 @@ bool GroupCall::tryCreateController() { } }); }; - auto e2eEncryptDecrypt = Fn( - const std::vector&, - bool)>(); - if (_e2e) { - e2eEncryptDecrypt = [e2e = _e2e](const std::vector &data, bool encrypt) { - return encrypt ? e2e->encrypt(data) : e2e->decrypt(data); - }; - } tgcalls::GroupInstanceDescriptor descriptor = { .threads = tgcalls::StaticThreads::getThreads(), @@ -2785,7 +2777,7 @@ bool GroupCall::tryCreateController() { }); return result; }, - .e2eEncryptDecrypt = e2eEncryptDecrypt, + .e2eEncryptDecrypt = _e2e ? _e2e->callbackEncryptDecrypt() : nullptr, }; if (Logs::DebugEnabled()) { auto callLogFolder = cWorkingDir() + u"DebugLogs"_q; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index b4e42a8f8d..89c6f490bf 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -222,7 +222,7 @@ void Call::apply(int subchain, const Block &last) { LOG_AND_FAIL(id.error(), CallFailure::Unknown); return; } - _id = CallId{ uint64(id.value()) }; + setId({ uint64(id.value()) }); for (auto i = 0; i != kSubChainsCount; ++i) { auto &entry = _subchains[i]; @@ -245,6 +245,16 @@ void Call::apply(int subchain, const Block &last) { _participantsSet = ParseParticipantsSet(state.value()); } +void Call::setId(CallId id) { + Expects(!_id); + + _id = id; + if (const auto raw = _guardedId.get()) { + raw->value = id; + raw->exists = true; + } +} + void Call::checkForOutboundMessages() { Expects(_id); @@ -408,26 +418,32 @@ rpl::producer Call::emojiHashValue() const { return _emojiHash.value(); } -std::vector Call::encrypt(const std::vector &data) const { - const auto result = tde2e_api::call_encrypt(libId(), Slice(data)); - if (!result.is_ok()) { - return {}; +auto Call::callbackEncryptDecrypt() +-> Fn(const std::vector&, bool)> { + if (!_guardedId) { + _guardedId = std::make_shared(); + if (const auto raw = _id ? _guardedId.get() : nullptr) { + raw->value = _id; + raw->exists = true; + } } - const auto &value = result.value(); - const auto start = reinterpret_cast(value.data()); - const auto end = start + value.size(); - return std::vector{ start, end }; -} - -std::vector Call::decrypt(const std::vector &data) const { - const auto result = tde2e_api::call_decrypt(libId(), Slice(data)); - if (!result.is_ok()) { - return {}; - } - const auto &value = result.value(); - const auto start = reinterpret_cast(value.data()); - const auto end = start + value.size(); - return std::vector{ start, end }; + return [v = _guardedId](const std::vector &data, bool encrypt) { + if (!v->exists) { + return std::vector(); + } + const auto libId = std::int64_t(v->value.v); + const auto slice = Slice(data); + const auto result = encrypt + ? tde2e_api::call_encrypt(libId, slice) + : tde2e_api::call_decrypt(libId, slice); + if (!result.is_ok()) { + return std::vector(); + } + const auto &value = result.value(); + const auto start = reinterpret_cast(value.data()); + const auto end = start + value.size(); + return std::vector{ start, end }; + }; } } // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 0ddb750576..0b0e518657 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -101,14 +101,17 @@ public: [[nodiscard]] rpl::producer participantsSetValue() const; - [[nodiscard]] std::vector encrypt( - const std::vector &data) const; - [[nodiscard]] std::vector decrypt( - const std::vector &data) const; + [[nodiscard]] auto callbackEncryptDecrypt() + -> Fn(const std::vector&, bool)>; private: static constexpr int kSubChainsCount = 2; + struct GuardedCallId { + CallId value; + std::atomic exists; + }; + struct SubChainState { base::Timer shortPollTimer; base::Timer waitingTimer; @@ -118,6 +121,7 @@ private: int height = 0; }; + void setId(CallId id); void apply(int subchain, const Block &last); void fail(CallFailure reason); @@ -133,6 +137,7 @@ private: PublicKey _myKey; std::optional _failure; rpl::event_stream _failures; + std::shared_ptr _guardedId; SubChainState _subchains[kSubChainsCount]; rpl::event_stream _subchainRequests; From 1d32e5267cc02e8e81c23e0de56b68964ffd9030 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Mar 2025 00:21:18 +0500 Subject: [PATCH 081/190] Fix incorrect waiting blocks applying. --- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 89c6f490bf..526faa18e6 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -195,6 +195,7 @@ void Call::apply(int subchain, const Block &last) { }); if (subchain) { + auto &entry = _subchains[subchain]; auto result = tde2e_api::call_receive_inbound_message( libId(), Slice(last.data)); @@ -299,7 +300,7 @@ void Call::apply( } else if (!_id || entry.height == index) { apply(subchain, block); } - entry.height = index + 1; + entry.height = std::max(entry.height, index + 1); checkWaitingBlocks(subchain); } @@ -320,15 +321,15 @@ void Call::checkWaitingBlocks(int subchain, bool waited) { auto &waiting = entry.waiting; entry.shortPollTimer.cancel(); while (!waiting.empty()) { - const auto level = waiting.begin()->first; - if (level > entry.height + 1) { + const auto index = waiting.begin()->first; + if (index > entry.height) { if (waited) { shortPoll(subchain); } else { entry.waitingTimer.callOnce(kShortPollChainBlocksWaitFor); } return; - } else if (level == entry.height + 1) { + } else if (index == entry.height) { const auto slice = Slice(waiting.begin()->second.data); if (subchain) { auto result = tde2e_api::call_receive_inbound_message( @@ -352,7 +353,7 @@ void Call::checkWaitingBlocks(int subchain, bool waited) { } _participantsSet = ParseParticipantsSet(result.value()); } - entry.height = level; + entry.height = std::max(entry.height, index + 1); } waiting.erase(waiting.begin()); } From 5d5dda548a229c7bb9155f83eb1e06479364cf0f Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Mar 2025 10:40:04 +0500 Subject: [PATCH 082/190] Implement nice call link box. --- Telegram/SourceFiles/calls/calls.style | 5 +- .../calls/group/calls_group_common.cpp | 57 +++++++++++++++---- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 1 - .../window/window_session_controller.cpp | 57 ++++++++++++++++--- .../window/window_session_controller.h | 17 +++++- 5 files changed, 112 insertions(+), 25 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 3a7c926bd7..ea9eddd0b8 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1490,7 +1490,7 @@ confcallLinkButton: RoundButton(defaultActiveButton) { style: semiboldTextStyle; } confcallLinkBox: Box(defaultBox) { - buttonPadding: margins(22px, 11px, 22px, 64px); + buttonPadding: margins(12px, 11px, 24px, 96px); buttonHeight: 42px; button: confcallLinkButton; shadowIgnoreTopSkip: true; @@ -1513,3 +1513,6 @@ confcallLinkCenteredText: FlatLabel(defaultFlatLabel) { confcallLinkFooterOr: FlatLabel(confcallLinkCenteredText) { textFg: windowSubTextFg; } +confcallLinkFooterOrTop: 12px; +confcallLinkFooterOrSkip: 8px; +confcallLinkFooterOrLineTop: 9px; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 106fa229d8..9dad51c406 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "boxes/share_box.h" +#include "core/local_url_handlers.h" #include "data/data_group_call.h" #include "info/bot/starref/info_bot_starref_common.h" #include "ui/boxes/boost_box.h" @@ -130,19 +131,46 @@ void ShowConferenceCallLinkBox( FastShareLink(controller, link); }; preview->setClickedCallback(copyCallback); - [[maybe_unused]] const auto copy = box->addButton( - tr::lng_group_invite_copy(), - copyCallback, - st::confcallLinkCopyButton); [[maybe_unused]] const auto share = box->addButton( tr::lng_group_invite_share(), shareCallback, st::confcallLinkShareButton); + [[maybe_unused]] const auto copy = box->addButton( + tr::lng_group_invite_copy(), + copyCallback, + st::confcallLinkCopyButton); + + rpl::combine( + box->widthValue(), + copy->widthValue(), + share->widthValue() + ) | rpl::start_with_next([=] { + const auto width = st::boxWideWidth; + const auto padding = st::confcallLinkBox.buttonPadding; + const auto available = width - 2 * padding.right(); + const auto buttonWidth = (available - padding.left()) / 2; + copy->resizeToWidth(buttonWidth); + share->resizeToWidth(buttonWidth); + copy->moveToLeft(padding.right(), copy->y(), width); + share->moveToRight(padding.right(), share->y(), width); + }, box->lifetime()); const auto sep = Ui::CreateChild( copy->parentWidget(), tr::lng_confcall_link_or(), st::confcallLinkFooterOr); + sep->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(sep); + const auto text = sep->textMaxWidth(); + const auto white = (sep->width() - 2 * text) / 2; + const auto line = st::lineWidth; + const auto top = st::confcallLinkFooterOrLineTop; + const auto fg = st::windowSubTextFg->b; + p.setOpacity(0.4); + p.fillRect(0, top, white, line, fg); + p.fillRect(sep->width() - white, top, white, line, fg); + }, sep->lifetime()); + const auto footer = Ui::CreateChild( copy->parentWidget(), tr::lng_confcall_link_join( @@ -154,22 +182,29 @@ void ShowConferenceCallLinkBox( Ui::Text::WithEntities), st::confcallLinkCenteredText); footer->setTryMakeSimilarLines(true); + footer->setClickHandlerFilter([=](const auto &...) { + const auto local = Core::TryConvertUrlToLocal(link); + controller->resolveConferenceCall( + local, + crl::guard(box, [=](bool ok) { if (ok) box->closeBox(); }), + true); + return false; + }); copy->geometryValue() | rpl::start_with_next([=](QRect geometry) { const auto width = st::boxWideWidth - st::boxRowPadding.left() - st::boxRowPadding.right(); footer->resizeToWidth(width); - const auto &st = box->getDelegate()->style(); - const auto top = geometry.y() + geometry.height(); - const auto available = st.buttonPadding.bottom(); - const auto footerHeight = sep->height() + footer->height(); - const auto skip = (available - footerHeight) / 2; + const auto top = geometry.y() + + geometry.height() + + st::confcallLinkFooterOrTop; + sep->resizeToWidth(width / 2); sep->move( st::boxRowPadding.left() + (width - sep->width()) / 2, - top + skip); + top); footer->moveToLeft( st::boxRowPadding.left(), - top + skip + sep->height()); + top + sep->height() + st::confcallLinkFooterOrSkip); }, footer->lifetime()); })); } diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 526faa18e6..ee25452805 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -195,7 +195,6 @@ void Call::apply(int subchain, const Block &last) { }); if (subchain) { - auto &entry = _subchains[subchain]; auto result = tde2e_api::call_receive_inbound_message( libId(), Slice(last.data)); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 52a213450d..d0a2eae995 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -840,17 +840,42 @@ void SessionNavigation::resolveCollectible( }).send(); } -void SessionNavigation::resolveConferenceCall(const QString &slug) { - resolveConferenceCall(slug, 0); -} - -void SessionNavigation::resolveConferenceCall(MsgId inviteMsgId) { - resolveConferenceCall(QString(), inviteMsgId); +void SessionNavigation::resolveConferenceCall( + QString slug, + Fn finished, + bool skipConfirm) { + resolveConferenceCall( + std::move(slug), + 0, + std::move(finished), + skipConfirm); } void SessionNavigation::resolveConferenceCall( - const QString &slug, - MsgId inviteMsgId) { + MsgId inviteMsgId, + Fn finished, + bool skipConfirm) { + resolveConferenceCall({}, inviteMsgId, std::move(finished), skipConfirm); +} + +void SessionNavigation::resolveConferenceCall( + QString slug, + MsgId inviteMsgId, + Fn finished, + bool skipConfirm) { + // Accept tg://call?slug= links as well. + const auto parts1 = QStringView(slug).split('#'); + if (!parts1.isEmpty()) { + const auto parts2 = parts1.front().split('&'); + if (!parts2.isEmpty()) { + const auto parts3 = parts2.front().split(u"slug="_q); + if (parts3.size() > 1) { + slug = parts3.back().toString(); + } + } + } + + _conferenceCallResolveFinished = std::move(finished); if (_conferenceCallSlug == slug && _conferenceCallInviteMsgId == inviteMsgId) { return; @@ -869,7 +894,7 @@ void SessionNavigation::resolveConferenceCall( _conferenceCallRequestId = 0; const auto slug = base::take(_conferenceCallSlug); const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); - + const auto finished = base::take(_conferenceCallResolveFinished); result.data().vcall().match([&](const auto &data) { const auto call = std::make_shared( session().user(), @@ -888,6 +913,13 @@ void SessionNavigation::resolveConferenceCall( .joinMessageId = inviteMsgId, }); }; + if (skipConfirm) { + join(); + if (finished) { + finished(true); + } + return; + } const auto box = uiShow()->show(Box( Calls::Group::ConferenceCallJoinConfirm, call, @@ -899,11 +931,18 @@ void SessionNavigation::resolveConferenceCall( )).send(); } }, box->lifetime()); + if (finished) { + finished(true); + } }); }).fail([=] { _conferenceCallRequestId = 0; _conferenceCallSlug = QString(); + const auto finished = base::take(_conferenceCallResolveFinished); showToast(tr::lng_group_invite_bad_link(tr::now)); + if (finished) { + finished(false); + } }).send(); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index cb8432aaf4..c31772fa3d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -264,8 +264,14 @@ public: PeerId ownerId, const QString &entity, Fn fail = nullptr); - void resolveConferenceCall(const QString &slug); - void resolveConferenceCall(MsgId inviteMsgId); + void resolveConferenceCall( + QString slug, + Fn finished = nullptr, + bool skipConfirm = false); + void resolveConferenceCall( + MsgId inviteMsgId, + Fn finished = nullptr, + bool skipConfirm = false); base::weak_ptr showToast( Ui::Toast::Config &&config); @@ -292,7 +298,11 @@ private: void resolveChannelById( ChannelId channelId, Fn)> done); - void resolveConferenceCall(const QString &slug, MsgId inviteMsgId); + void resolveConferenceCall( + QString slug, + MsgId inviteMsgId, + Fn finished, + bool skipConfirm); void resolveDone( const MTPcontacts_ResolvedPeer &result, @@ -334,6 +344,7 @@ private: QString _conferenceCallSlug; MsgId _conferenceCallInviteMsgId; + Fn _conferenceCallResolveFinished; mtpRequestId _conferenceCallRequestId = 0; }; From 0d8e5b139b0bd79493c48582f1691b4b75662087 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Mar 2025 10:05:13 +0400 Subject: [PATCH 083/190] Add some special phrases. --- Telegram/Resources/langs/lang.strings | 2 ++ Telegram/SourceFiles/calls/group/calls_group_menu.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b35a5259d5..e7ae258356 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4722,8 +4722,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_connecting" = "Connecting..."; "lng_group_call_leave" = "Leave"; "lng_group_call_leave_title" = "Leave video chat"; +"lng_group_call_leave_title_call" = "Leave group call"; "lng_group_call_leave_title_channel" = "Leave live stream"; "lng_group_call_leave_sure" = "Do you want to leave this video chat?"; +"lng_group_call_leave_sure_call" = "Do you want to leave this group call?"; "lng_group_call_leave_sure_channel" = "Are you sure you want to leave this live stream?"; "lng_group_call_close" = "Close"; "lng_group_call_close_sure" = "Video chat is scheduled. You can abort it or just close this panel."; diff --git a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp index 277c64356e..4f1b196d65 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp @@ -416,10 +416,13 @@ void LeaveBox( not_null call, bool discardChecked, BoxContext context) { + const auto conference = call->conference(); const auto livestream = call->peer()->isBroadcast(); const auto scheduled = (call->scheduleDate() != 0); if (!scheduled) { - box->setTitle(livestream + box->setTitle(conference + ? tr::lng_group_call_leave_title_call() + : livestream ? tr::lng_group_call_leave_title_channel() : tr::lng_group_call_leave_title()); } @@ -431,7 +434,9 @@ void LeaveBox( ? (livestream ? tr::lng_group_call_close_sure_channel() : tr::lng_group_call_close_sure()) - : (livestream + : (conference + ? tr::lng_group_call_leave_sure_call() + : livestream ? tr::lng_group_call_leave_sure_channel() : tr::lng_group_call_leave_sure())), (inCall ? st::groupCallBoxLabel : st::boxLabel)), From 9f3f715527cbbfa0a992bf53c165f388bc454077 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Mar 2025 12:52:36 +0500 Subject: [PATCH 084/190] Allow sharing link from confcall. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/calls/calls.style | 24 ++++- .../calls/group/calls_group_call.cpp | 8 +- .../calls/group/calls_group_call.h | 1 + .../calls/group/calls_group_common.cpp | 94 ++++++++++++++++--- .../calls/group/calls_group_common.h | 37 +++++++- .../calls/group/calls_group_members.cpp | 33 ++++++- .../calls/group/calls_group_members.h | 5 + .../calls/group/calls_group_panel.cpp | 23 +++-- .../bot/starref/info_bot_starref_common.cpp | 16 ++-- .../bot/starref/info_bot_starref_common.h | 4 +- .../boosts/giveaway/giveaway.style | 3 + .../SourceFiles/window/window_main_menu.cpp | 30 +++--- 13 files changed, 226 insertions(+), 53 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e7ae258356..520df38eed 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4755,6 +4755,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_also_end_channel" = "End live stream"; "lng_group_call_settings_title" = "Settings"; "lng_group_call_invite" = "Invite Members"; +"lng_group_call_invite_conf" = "Add People"; "lng_group_call_invited_status" = "invited"; "lng_group_call_muted_by_me_status" = "muted for you"; "lng_group_call_invite_title" = "Invite members"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index ea9eddd0b8..d74c67f79c 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -804,6 +804,7 @@ groupCallAddMember: SettingsButton(defaultSettingsButton) { ripple: groupCallRipple; } groupCallAddMemberIcon: icon {{ "info/info_add_member", groupCallMemberInactiveIcon, point(0px, 3px) }}; +groupCallShareLinkIcon: icon {{ "menu/links_profile", groupCallMemberInactiveIcon, point(4px, 3px) }}; groupCallSubtitleLabel: FlatLabel(defaultFlatLabel) { maxHeight: 18px; textFg: groupCallMemberNotJoinedStatus; @@ -1489,12 +1490,15 @@ confcallLinkButton: RoundButton(defaultActiveButton) { textTop: 12px; style: semiboldTextStyle; } -confcallLinkBox: Box(defaultBox) { +confcallLinkBoxInitial: Box(defaultBox) { buttonPadding: margins(12px, 11px, 24px, 96px); buttonHeight: 42px; button: confcallLinkButton; shadowIgnoreTopSkip: true; } +confcallLinkBox: Box(confcallLinkBoxInitial) { + buttonPadding: margins(12px, 11px, 24px, 24px); +} confcallLinkCopyButton: RoundButton(confcallLinkButton) { icon: icon {{ "info/edit/links_copy", activeButtonFg }}; iconOver: icon {{ "info/edit/links_copy", activeButtonFgOver }}; @@ -1516,3 +1520,21 @@ confcallLinkFooterOr: FlatLabel(confcallLinkCenteredText) { confcallLinkFooterOrTop: 12px; confcallLinkFooterOrSkip: 8px; confcallLinkFooterOrLineTop: 9px; + +groupCallLinkBox: Box(confcallLinkBox) { + bg: groupCallMembersBg; + title: FlatLabel(boxTitle) { + textFg: groupCallMembersFg; + } + titleAdditionalFg: groupCallMemberNotJoinedStatus; +} +groupCallLinkCenteredText: FlatLabel(confcallLinkCenteredText) { + textFg: groupCallMembersFg; +} +groupCallLinkPreview: InputField(defaultInputField) { + textBg: groupCallMembersBgOver; + textFg: groupCallMembersFg; + textMargins: margins(12px, 8px, 30px, 5px); + style: defaultTextStyle; + heightMin: 35px; +} diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index d171165f20..2a67b56673 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1153,13 +1153,17 @@ void GroupCall::setRtmpInfo(const Calls::Group::RtmpInfo &value) { } Data::GroupCall *GroupCall::lookupReal() const { - if (_conferenceCall) { - return _conferenceCall.get(); + if (const auto conference = _conferenceCall.get()) { + return conference; } const auto real = _peer->groupCall(); return (real && real->id() == _id) ? real : nullptr; } +std::shared_ptr GroupCall::conferenceCall() const { + return _conferenceCall; +} + rpl::producer> GroupCall::real() const { if (const auto real = lookupReal()) { return rpl::single(not_null{ real }); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 4925eccb57..a1534fa72f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -256,6 +256,7 @@ public: void setRtmpInfo(const Group::RtmpInfo &value); [[nodiscard]] Data::GroupCall *lookupReal() const; + [[nodiscard]] std::shared_ptr conferenceCall() const; [[nodiscard]] rpl::producer> real() const; [[nodiscard]] rpl::producer emojiHashValue() const; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 9dad51c406..110e08d76c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/group/calls_group_common.h" +#include "apiwrap.h" #include "base/platform/base_platform_info.h" #include "boxes/share_box.h" #include "core/local_url_handlers.h" @@ -19,8 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/vertical_list.h" #include "lang/lang_keys.h" +#include "main/main_session.h" #include "window/window_session_controller.h" #include "styles/style_layers.h" +#include "styles/style_media_view.h" #include "styles/style_calls.h" #include "styles/style_chat.h" @@ -86,16 +89,34 @@ void ConferenceCallJoinConfirm( }); } +ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle() { + return { + .box = &st::groupCallLinkBox, + .close = &st::storiesStealthBoxClose, + .centerLabel = &st::groupCallLinkCenteredText, + .linkPreview = &st::groupCallLinkPreview, + .shareBox = std::make_shared( + DarkShareBoxStyle()), + }; +} + void ShowConferenceCallLinkBox( - not_null controller, + std::shared_ptr show, std::shared_ptr call, const QString &link, - bool initial) { - controller->show(Box([=](not_null box) { - box->setStyle(st::confcallLinkBox); + ConferenceCallLinkArgs &&args) { + const auto st = args.st; + const auto initial = args.initial; + const auto weakWindow = args.weakWindow; + show->showBox(Box([=](not_null box) { + box->setStyle(st.box + ? *st.box + : initial + ? st::confcallLinkBoxInitial + : st::confcallLinkBox); box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); - box->addTopButton(st::boxTitleClose, [=] { + box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] { box->closeBox(); }); @@ -108,19 +129,24 @@ void ShowConferenceCallLinkBox( object_ptr( box, tr::lng_confcall_link_title(), - st::boxTitle)), + st.box ? st.box->title : st::boxTitle)), st::boxRowPadding + st::confcallLinkTitlePadding); box->addRow( object_ptr( box, tr::lng_confcall_link_about(), - st::confcallLinkCenteredText), + (st.centerLabel + ? *st.centerLabel + : st::confcallLinkCenteredText)), st::boxRowPadding )->setTryMakeSimilarLines(true); Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 2); const auto preview = box->addRow( - Info::BotStarRef::MakeLinkLabel(box, link)); + Info::BotStarRef::MakeLinkLabel( + box, + link, + st.linkPreview)); Ui::AddSkip(box->verticalLayout()); const auto copyCallback = [=] { @@ -128,7 +154,10 @@ void ShowConferenceCallLinkBox( box->uiShow()->showToast(tr::lng_username_copied(tr::now)); }; const auto shareCallback = [=] { - FastShareLink(controller, link); + FastShareLink( + show, + link, + st.shareBox ? *st.shareBox : ShareBoxStyleOverrides()); }; preview->setClickedCallback(copyCallback); [[maybe_unused]] const auto share = box->addButton( @@ -155,6 +184,10 @@ void ShowConferenceCallLinkBox( share->moveToRight(padding.right(), share->y(), width); }, box->lifetime()); + if (!initial) { + return; + } + const auto sep = Ui::CreateChild( copy->parentWidget(), tr::lng_confcall_link_or(), @@ -180,14 +213,18 @@ void ShowConferenceCallLinkBox( rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), [](QString v) { return Ui::Text::Link(v); }), Ui::Text::WithEntities), - st::confcallLinkCenteredText); + (st.centerLabel + ? *st.centerLabel + : st::confcallLinkCenteredText)); footer->setTryMakeSimilarLines(true); footer->setClickHandlerFilter([=](const auto &...) { const auto local = Core::TryConvertUrlToLocal(link); - controller->resolveConferenceCall( - local, - crl::guard(box, [=](bool ok) { if (ok) box->closeBox(); }), - true); + if (const auto controller = weakWindow.get()) { + controller->resolveConferenceCall( + local, + crl::guard(box, [=](bool ok) { if (ok) box->closeBox(); }), + true); + } return false; }); copy->geometryValue() | rpl::start_with_next([=](QRect geometry) { @@ -209,4 +246,33 @@ void ShowConferenceCallLinkBox( })); } +void ExportConferenceCallLink( + std::shared_ptr show, + std::shared_ptr call, + ConferenceCallLinkArgs &&args) { + const auto session = &show->session(); + const auto finished = std::move(args.finished); + + using Flag = MTPphone_ExportGroupCallInvite::Flag; + session->api().request(MTPphone_ExportGroupCallInvite( + MTP_flags(Flag::f_can_self_unmute), + call->input() + )).done([=](const MTPphone_ExportedGroupCallInvite &result) { + const auto link = qs(result.data().vlink()); + Calls::Group::ShowConferenceCallLinkBox( + show, + call, + link, + base::duplicate(args)); + if (const auto onstack = finished) { + finished(true); + } + }).fail([=](const MTP::Error &error) { + show->showToast(error.type()); + if (const auto onstack = finished) { + finished(false); + } + }).send(); +} + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index f2716f16bf..1591fe5a2a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -8,13 +8,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/object_ptr.h" +#include "base/weak_ptr.h" class UserData; +struct ShareBoxStyleOverrides; + +namespace style { +struct Box; +struct FlatLabel; +struct IconButton; +struct InputField; +} // namespace style namespace Data { class GroupCall; } // namespace Data +namespace Main { +class SessionShow; +} // namespace Main + namespace Ui { class Show; class GenericBox; @@ -118,10 +131,30 @@ void ConferenceCallJoinConfirm( std::shared_ptr call, Fn join); +struct ConferenceCallLinkStyleOverrides { + const style::Box *box = nullptr; + const style::IconButton *close = nullptr; + const style::FlatLabel *centerLabel = nullptr; + const style::InputField *linkPreview = nullptr; + std::shared_ptr shareBox; +}; +[[nodiscard]] ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle(); + +struct ConferenceCallLinkArgs { + bool initial = false; + Fn finished; + base::weak_ptr weakWindow = nullptr; + ConferenceCallLinkStyleOverrides st; +}; void ShowConferenceCallLinkBox( - not_null controller, + std::shared_ptr show, std::shared_ptr call, const QString &link, - bool initial = false); + ConferenceCallLinkArgs &&args); + +void ExportConferenceCallLink( + std::shared_ptr show, + std::shared_ptr call, + ConferenceCallLinkArgs &&args); } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 928c3b1f5d..3416b4a014 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1615,6 +1615,7 @@ rpl::producer Members::desiredHeightValue() const { return rpl::combine( heightValue(), _addMemberButton.value(), + _shareLinkButton.value(), _listController->fullCountValue(), _mode.value() ) | rpl::map([=] { @@ -1626,8 +1627,11 @@ void Members::setupAddMember(not_null call) { using namespace rpl::mappers; const auto peer = call->peer(); + const auto conference = call->conference(); const auto canAddByPeer = [=](not_null peer) { - if (peer->isBroadcast()) { + if (conference) { + return rpl::single(true) | rpl::type_erased(); + } else if (peer->isBroadcast()) { return rpl::single(false) | rpl::type_erased(); } return rpl::combine( @@ -1638,6 +1642,9 @@ void Members::setupAddMember(not_null call) { }) | rpl::type_erased(); }; const auto canInviteByLinkByPeer = [=](not_null peer) { + if (conference) { + return rpl::single(true) | rpl::type_erased(); + } const auto channel = peer->asChannel(); if (!channel) { return rpl::single(false) | rpl::type_erased(); @@ -1672,11 +1679,18 @@ void Members::setupAddMember(not_null call) { _addMemberButton = nullptr; updateControlsGeometry(); } + if (const auto old = _shareLinkButton.current()) { + delete old; + _shareLinkButton = nullptr; + updateControlsGeometry(); + } return; } auto addMember = Settings::CreateButtonWithIcon( _layout.get(), - tr::lng_group_call_invite(), + (conference + ? tr::lng_group_call_invite_conf() + : tr::lng_group_call_invite()), st::groupCallAddMember, { .icon = &st::groupCallAddMemberIcon }); addMember->clicks( @@ -1688,6 +1702,21 @@ void Members::setupAddMember(not_null call) { delete _addMemberButton.current(); _addMemberButton = addMember.data(); _layout->insert(3, std::move(addMember)); + if (conference) { + auto shareLink = Settings::CreateButtonWithIcon( + _layout.get(), + tr::lng_group_invite_share(), + st::groupCallAddMember, + { .icon = &st::groupCallShareLinkIcon }); + shareLink->clicks() | rpl::to_empty | rpl::start_to_stream( + _shareLinkRequests, + shareLink->lifetime()); + shareLink->show(); + shareLink->resizeToWidth(_layout->width()); + delete _shareLinkButton.current(); + _shareLinkButton = shareLink.data(); + _layout->insert(4, std::move(shareLink)); + } }, lifetime()); updateControlsGeometry(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 47cb059143..355f45f5f1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -59,6 +59,9 @@ public: [[nodiscard]] rpl::producer<> addMembersRequests() const { return _addMemberRequests.events(); } + [[nodiscard]] rpl::producer<> shareLinkRequests() const { + return _shareLinkRequests.events(); + } [[nodiscard]] MembersRow *lookupRow(not_null peer) const; [[nodiscard]] not_null rtmpFakeRow( @@ -106,10 +109,12 @@ private: const not_null _videoWrap; std::unique_ptr _viewport; rpl::variable _addMemberButton = nullptr; + rpl::variable _shareLinkButton = nullptr; RpWidget *_topSkip = nullptr; RpWidget *_bottomSkip = nullptr; ListWidget *_list = nullptr; rpl::event_stream<> _addMemberRequests; + rpl::event_stream<> _shareLinkRequests; mutable std::unique_ptr _rtmpFakeRow; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index e50397b40f..8dced0375a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -934,16 +934,12 @@ void Panel::setupMembers() { _members->toggleMuteRequests( ) | rpl::start_with_next([=](MuteRequest request) { - if (_call) { - _call->toggleMute(request); - } + _call->toggleMute(request); }, _callLifetime); _members->changeVolumeRequests( ) | rpl::start_with_next([=](VolumeRequest request) { - if (_call) { - _call->changeVolume(request); - } + _call->changeVolume(request); }, _callLifetime); _members->kickParticipantRequests( @@ -964,6 +960,21 @@ void Panel::setupMembers() { } }, _callLifetime); + const auto exporting = std::make_shared(); + _members->shareLinkRequests( + ) | rpl::start_with_next([=] { + Expects(_call->conference()); + + if (*exporting) { + return; + } + *exporting = true; + ExportConferenceCallLink(uiShow(), _call->conferenceCall(), { + .st = DarkConferenceCallLinkStyle(), + .finished = [=](bool) { *exporting = false; }, + }); + }, _callLifetime); + _call->videoEndpointLargeValue( ) | rpl::start_with_next([=](const VideoEndpoint &large) { if (large && mode() != PanelMode::Wide) { diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp index 50149d8a63..26130b96f0 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp @@ -390,14 +390,16 @@ void AddFullWidthButtonFooter( object_ptr MakeLinkLabel( not_null parent, - const QString &link) { + const QString &link, + const style::InputField *stOverride) { + const auto &st = stOverride ? *stOverride : st::dialogsFilter; const auto text = link.startsWith(u"https://"_q) ? link.mid(8) : link.startsWith(u"http://"_q) ? link.mid(7) : link; - const auto margins = st::dialogsFilter.textMargins; - const auto height = st::dialogsFilter.heightMin; + const auto margins = st.textMargins; + const auto height = st.heightMin; const auto skip = margins.left(); auto result = object_ptr(parent); @@ -408,12 +410,12 @@ object_ptr MakeLinkLabel( auto p = QPainter(raw); auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); - p.setBrush(st::dialogsFilter.textBg); + p.setBrush(st.textBg); const auto radius = st::roundRadiusLarge; p.drawRoundedRect(0, 0, raw->width(), height, radius, radius); - const auto font = st::dialogsFilter.style.font; - p.setPen(st::dialogsFilter.textFg); + const auto font = st.style.font; + p.setPen(st.textFg); p.setFont(font); const auto available = raw->width() - skip * 2; p.drawText( @@ -1065,4 +1067,4 @@ object_ptr CreateLinkHeaderIcon( return CreateLinkIcon(parent, session, usersCount); } -} // namespace Info::BotStarRef \ No newline at end of file +} // namespace Info::BotStarRef diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h index 46e643241d..d69864bf15 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h @@ -21,6 +21,7 @@ class Show; namespace style { struct RoundButton; +struct InputField; } // namespace style namespace Main { @@ -107,7 +108,8 @@ void FinishProgram( [[nodiscard]] object_ptr MakeLinkLabel( not_null parent, - const QString &link); + const QString &link, + const style::InputField *stOverride = nullptr); [[nodiscard]] object_ptr CreateLinkHeaderIcon( not_null parent, not_null session, diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style index 1558fb259b..d70bc257b7 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style @@ -259,3 +259,6 @@ darkGiftTableMessage: FlatLabel(giveawayGiftMessage) { textFg: groupCallMembersFg; palette: darkGiftPalette; } +darkGiftCodeLink: FlatLabel(giveawayGiftCodeLink) { + textFg: mediaviewMenuFg; +} diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 3606e9cf87..361ed511e0 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -123,7 +123,7 @@ constexpr auto kPlayStatusLimit = 2; (height - st::inviteViaLinkIcon.height()) / 2); }, icon->lifetime()); - const auto creating = result->lifetime().make_state(); + const auto creating = std::make_shared(); result->setClickedCallback([=] { if (*creating) { return; @@ -143,25 +143,19 @@ constexpr auto kPlayStatusLimit = 2; false, // rtmp true); // conference call->processFullCall(result); - using Flag = MTPphone_ExportGroupCallInvite::Flag; - session->api().request(MTPphone_ExportGroupCallInvite( - MTP_flags(Flag::f_can_self_unmute), - MTP_inputGroupCall(data.vid(), data.vaccess_hash()) - )).done(crl::guard(controller, [=]( - const MTPphone_ExportedGroupCallInvite &result) { - const auto link = qs(result.data().vlink()); - Calls::Group::ShowConferenceCallLinkBox( - controller, - call, - link, - true); - if (const auto onstack = done) { + const auto finished = [=](bool ok) { + if (!ok) { + *creating = 0; + } else if (const auto onstack = done) { onstack(); } - })).fail(crl::guard(controller, [=](const MTP::Error &error) { - show->showToast(error.type()); - *creating = 0; - })).send(); + }; + const auto show = controller->uiShow(); + Calls::Group::ExportConferenceCallLink(show, call, { + .initial = true, + .finished = finished, + .weakWindow = controller, + }); }); })).fail(crl::guard(controller, [=](const MTP::Error &error) { show->showToast(error.type()); From 6451e1cfe2cf1fc3e486632a7aa9d2aadaa99bdd Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Mar 2025 16:28:13 +0500 Subject: [PATCH 085/190] Remove debug logging in swipes. --- Telegram/SourceFiles/ui/controls/swipe_handler.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp index a55634209b..b39afc3b06 100644 --- a/Telegram/SourceFiles/ui/controls/swipe_handler.cpp +++ b/Telegram/SourceFiles/ui/controls/swipe_handler.cpp @@ -7,8 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/controls/swipe_handler.h" -#include "base/debug_log.h" - #include "base/platform/base_platform_haptic.h" #include "base/platform/base_platform_info.h" #include "base/qt/qt_common_adapters.h" @@ -191,10 +189,8 @@ void SetupSwipeHandler(SwipeHandlerArgs &&args) { const auto updateWith = [=, generateFinish = args.init](UpdateArgs args) { const auto fillFinishByTop = [&] { if (!args.delta.x()) { - LOG(("SKIPPING fillFinishByTop.")); return; } - LOG(("SETTING DIRECTION")); state->direction = (args.delta.x() < 0) ? Qt::RightToLeft : Qt::LeftToRight; @@ -211,7 +207,6 @@ void SetupSwipeHandler(SwipeHandlerArgs &&args) { } }; if (!state->started || state->touch != args.touch) { - LOG(("STARTING")); state->started = true; state->data.reachRatio = 0.; state->touch = args.touch; @@ -232,10 +227,6 @@ void SetupSwipeHandler(SwipeHandlerArgs &&args) { const auto diffXtoY = std::abs(args.delta.x()) - std::abs(args.delta.y()); constexpr auto kOrientationThreshold = 1.; - LOG(("SETTING ORIENTATION WITH: %1,%2, diff %3" - ).arg(args.delta.x() - ).arg(args.delta.y() - ).arg(diffXtoY)); if (diffXtoY > kOrientationThreshold) { if (!state->dontStart) { setOrientation(Qt::Horizontal); @@ -339,10 +330,8 @@ void SetupSwipeHandler(SwipeHandlerArgs &&args) { .delta = state->startAt - touches[0].pos(), .touch = true, }; - LOG(("ORIENTATION UPDATING WITH: %1, %2").arg(args.delta.x()).arg(args.delta.y())); updateWith(args); } - LOG(("ORIENTATION: %1").arg(!state->orientation ? "none" : (state->orientation == Qt::Horizontal) ? "horizontal" : "vertical")); return (touchscreen && state->orientation != Qt::Horizontal) ? base::EventFilterResult::Continue : base::EventFilterResult::Cancel; From 9dbd134601cb2c99176873c8792e5b7c1119c3ce Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 29 Mar 2025 16:28:17 +0500 Subject: [PATCH 086/190] Implement multi-inviting to confcalls. --- Telegram/Resources/langs/lang.strings | 7 + Telegram/SourceFiles/calls/calls.style | 17 ++ .../calls/group/calls_group_call.cpp | 71 ++++-- .../calls/group/calls_group_call.h | 11 +- .../group/calls_group_invite_controller.cpp | 232 +++++++++++++----- .../group/calls_group_invite_controller.h | 3 +- .../calls/group/calls_group_members.cpp | 27 +- .../calls/group/calls_group_panel.cpp | 49 ++-- .../calls/group/calls_group_panel.h | 2 + Telegram/SourceFiles/data/data_group_call.cpp | 3 +- Telegram/SourceFiles/data/data_group_call.h | 2 + Telegram/SourceFiles/data/data_session.cpp | 32 ++- Telegram/SourceFiles/data/data_session.h | 19 +- .../SourceFiles/window/window_main_menu.cpp | 8 +- .../window/window_session_controller.cpp | 8 +- 15 files changed, 366 insertions(+), 125 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 520df38eed..4a3f1bc052 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4765,6 +4765,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_add_to_group_all" = "Those users aren't members of «{group}». Add them to the group?"; "lng_group_call_invite_members" = "Group members"; "lng_group_call_invite_search_results" = "Search results"; +"lng_group_call_invite_limit" = "This is currently the maximum allowed number of participants."; "lng_group_call_new_muted" = "Mute new participants"; "lng_group_call_speakers" = "Speakers"; "lng_group_call_microphone" = "Microphone"; @@ -4921,6 +4922,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_link_or" = "or"; "lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}"; "lng_confcall_link_join_link" = "Open call {arrow}"; +"lng_confcall_invite_done_user" = "You invited {user} to join the call."; +"lng_confcall_invite_done_many#one" = "You invited **{count} person** to join the call."; +"lng_confcall_invite_done_many#other" = "You invited **{count} people** to join the call."; +"lng_confcall_invite_fail_user" = "You cannot call {user} because of their privacy settings."; +"lng_confcall_invite_fail_many#one" = "You cannot call **{count} person** because of their privacy settings."; +"lng_confcall_invite_fail_many#other" = "You cannot call **{count} people** because of their privacy settings."; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index d74c67f79c..d67b208f9b 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1538,3 +1538,20 @@ groupCallLinkPreview: InputField(defaultInputField) { style: defaultTextStyle; heightMin: 35px; } +groupCallInviteLink: SettingsButton(defaultSettingsButton) { + textFg: mediaviewTextLinkFg; + textFgOver: mediaviewTextLinkFg; + textBg: groupCallMembersBg; + textBgOver: groupCallMembersBgOver; + + style: TextStyle(defaultTextStyle) { + font: font(14px semibold); + } + + height: 20px; + padding: margins(63px, 8px, 8px, 9px); + + ripple: groupCallRipple; +} +groupCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", mediaviewTextLinkFg }}; +groupCallInviteLinkIconPosition: point(23px, 2px); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 2a67b56673..19950598f4 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -3697,61 +3697,88 @@ void GroupCall::editParticipant( }).send(); } -std::variant> GroupCall::inviteUsers( - const std::vector> &users) { +void GroupCall::inviteUsers( + const std::vector> &users, + Fn done) { const auto real = lookupReal(); if (!real) { - return 0; + if (done) { + done({}); + } + return; } const auto owner = &_peer->owner(); - if (_conferenceCall) { + struct State { + InviteResult result; + int requests = 0; + }; + const auto state = std::make_shared(); + const auto finishRequest = [=] { + if (!--state->requests) { + if (done) { + done(std::move(state->result)); + } + } + }; + + if (const auto call = _conferenceCall.get()) { for (const auto &user : users) { _api.request(MTPphone_InviteConferenceCallParticipant( inputCallSafe(), user->inputUser - )).send(); + )).done([=](const MTPUpdates &result) { + owner->registerInvitedToCallUser(_id, call, user); + _peer->session().api().applyUpdates(result); + state->result.invited.push_back(user); + finishRequest(); + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + if (type == u"USER_PRIVACY_RESTRICTED"_q) { + state->result.privacyRestricted.push_back(user); + } else if (type == u"USER_ALREADY_PARTICIPANT"_q) { + state->result.alreadyIn.push_back(user); + } + finishRequest(); + }).send(); + ++state->requests; } - auto result = std::variant>(0); - if (users.size() != 1) { - result = int(users.size()); - } else { - result = users.front(); - } - return result; + return; } - auto count = 0; + auto usersSlice = std::vector>(); + usersSlice.reserve(kMaxInvitePerSlice); auto slice = QVector(); - auto result = std::variant>(0); slice.reserve(kMaxInvitePerSlice); const auto sendSlice = [&] { - count += slice.size(); _api.request(MTPphone_InviteToGroupCall( inputCall(), MTP_vector(slice) )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); + for (const auto &user : usersSlice) { + state->result.invited.push_back(user); + } + finishRequest(); + }).fail([=](const MTP::Error &error) { + finishRequest(); }).send(); + ++state->requests; + slice.clear(); + usersSlice.clear(); }; for (const auto &user : users) { - if (!count && slice.empty()) { - result = user; - } owner->registerInvitedToCallUser(_id, _peer, user); + usersSlice.push_back(user); slice.push_back(user->inputUser); if (slice.size() == kMaxInvitePerSlice) { sendSlice(); } } - if (count != 0 || slice.size() != 1) { - result = int(count + slice.size()); - } if (!slice.empty()) { sendSlice(); } - return result; } auto GroupCall::ensureGlobalShortcutManager() diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index a1534fa72f..c81ada53c1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -416,8 +416,15 @@ public: void toggleMute(const Group::MuteRequest &data); void changeVolume(const Group::VolumeRequest &data); - std::variant> inviteUsers( - const std::vector> &users); + + struct InviteResult { + std::vector> invited; + std::vector> alreadyIn; + std::vector> privacyRestricted; + }; + void inviteUsers( + const std::vector> &users, + Fn done); std::shared_ptr ensureGlobalShortcutManager(); void applyGlobalShortcutChanges(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 1e13d45a82..05ef1fc92f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -15,12 +15,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_session.h" #include "data/data_group_call.h" +#include "info/profile/info_profile_icon.h" #include "main/main_session.h" +#include "main/session/session_show.h" #include "ui/text/text_utilities.h" #include "ui/layers/generic_box.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "apiwrap.h" #include "lang/lang_keys.h" +#include "styles/style_boxes.h" // membersMarginTop #include "styles/style_calls.h" #include "styles/style_dialogs.h" // searchedBarHeight @@ -61,38 +65,139 @@ public: ConfInviteController( not_null session, base::flat_set> alreadyIn, - Fn)> choose) - : ContactsBoxController(session) - , _alreadyIn(std::move(alreadyIn)) - , _choose(std::move(choose)) { - } + Fn shareLink); + + [[nodiscard]] rpl::producer hasSelectedValue() const; protected: - std::unique_ptr createRow( - not_null user) override { - if (user->isSelf() - || user->isBot() - || user->isServiceUser() - || user->isInaccessible()) { - return nullptr; - } - auto result = ContactsBoxController::createRow(user); - if (_alreadyIn.contains(user)) { - result->setDisabledState(PeerListRow::State::DisabledChecked); - } - return result; - } + void prepareViewHook() override; - void rowClicked(not_null row) override { - _choose(row->peer()); - } + std::unique_ptr createRow( + not_null user) override; + + void rowClicked(not_null row) override; private: - const base::flat_set> _alreadyIn; - const Fn)> _choose; + [[nodiscard]] int fullCount() const; + + base::flat_set> _alreadyIn; + const Fn _shareLink; + rpl::variable _hasSelected; }; +ConfInviteController::ConfInviteController( + not_null session, + base::flat_set> alreadyIn, + Fn shareLink) +: ContactsBoxController(session) +, _alreadyIn(std::move(alreadyIn)) +, _shareLink(std::move(shareLink)) { + _alreadyIn.remove(session->user()); +} + +rpl::producer ConfInviteController::hasSelectedValue() const { + return _hasSelected.value(); +} + +std::unique_ptr ConfInviteController::createRow( + not_null user) { + if (user->isSelf() + || user->isBot() + || user->isServiceUser() + || user->isInaccessible()) { + return nullptr; + } + auto result = ContactsBoxController::createRow(user); + if (_alreadyIn.contains(user)) { + result->setDisabledState(PeerListRow::State::DisabledChecked); + } + return result; +} + +int ConfInviteController::fullCount() const { + return _alreadyIn.size() + delegate()->peerListSelectedRowsCount(); +} + +void ConfInviteController::rowClicked(not_null row) { + auto count = fullCount(); + auto limit = Data::kMaxConferenceMembers; + if (count < limit || row->checked()) { + delegate()->peerListSetRowChecked(row, !row->checked()); + _hasSelected = (delegate()->peerListSelectedRowsCount() > 0); + } else { + delegate()->peerListUiShow()->showToast( + tr::lng_group_call_invite_limit(tr::now)); + } +} + +void ConfInviteController::prepareViewHook() { + auto button = object_ptr>( + nullptr, + object_ptr( + nullptr, + tr::lng_profile_add_via_link(), + st::groupCallInviteLink), + style::margins(0, st::membersMarginTop, 0, 0)); + + const auto icon = Ui::CreateChild( + button->entity(), + st::groupCallInviteLinkIcon, + QPoint()); + button->entity()->heightValue( + ) | rpl::start_with_next([=](int height) { + icon->moveToLeft( + st::groupCallInviteLinkIconPosition.x(), + (height - st::groupCallInviteLinkIcon.height()) / 2); + }, icon->lifetime()); + + button->entity()->setClickedCallback(_shareLink); + button->entity()->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=] { + delegate()->peerListMouseLeftGeometry(); + }, button->lifetime()); + delegate()->peerListSetAboveWidget(std::move(button)); +} + +[[nodiscard]] TextWithEntities ComposeInviteResultToast( + const GroupCall::InviteResult &result) { + auto text = TextWithEntities(); + const auto invited = int(result.invited.size()); + const auto restricted = int(result.privacyRestricted.size()); + if (invited == 1) { + text.append(tr::lng_confcall_invite_done_user( + tr::now, + lt_user, + Ui::Text::Bold(result.invited.front()->shortName()), + Ui::Text::RichLangValue)); + } else if (invited > 1) { + text.append(tr::lng_confcall_invite_done_many( + tr::now, + lt_count, + invited, + Ui::Text::RichLangValue)); + } + if (invited && restricted) { + text.append(u"\n\n"_q); + } + if (restricted == 1) { + text.append(tr::lng_confcall_invite_fail_user( + tr::now, + lt_user, + Ui::Text::Bold(result.privacyRestricted.front()->shortName()), + Ui::Text::RichLangValue)); + } else if (restricted > 1) { + text.append(tr::lng_confcall_invite_fail_many( + tr::now, + lt_count, + restricted, + Ui::Text::RichLangValue)); + } + return text; +} + } // namespace InviteController::InviteController( @@ -204,7 +309,8 @@ std::unique_ptr InviteContactsController::createRow( object_ptr PrepareInviteBox( not_null call, - Fn showToast) { + Fn showToast, + Fn finished)> shareConferenceLink) { const auto real = call->lookupReal(); if (!real) { return nullptr; @@ -220,33 +326,44 @@ object_ptr PrepareInviteBox( alreadyIn.emplace(peer->session().user()); if (call->conference()) { const auto close = std::make_shared>(); - const auto invite = [=](not_null peer) { - Expects(peer->isUser()); - - const auto call = weak.get(); - if (!call) { - return; - } - const auto user = peer->asUser(); - call->inviteUsers({ user }); - showToast(tr::lng_group_call_invite_done_user( - tr::now, - lt_user, - Ui::Text::Bold(user->firstName), - Ui::Text::WithEntities)); - (*close)(); + const auto shareLink = [=] { + Assert(shareConferenceLink != nullptr); + shareConferenceLink([=](bool ok) { if (ok) (*close)(); }); }; auto controller = std::make_unique( &real->session(), alreadyIn, - invite); - controller->setStyleOverrides( + shareLink); + const auto raw = controller.get(); + raw->setStyleOverrides( &st::groupCallInviteMembersList, &st::groupCallMultiSelect); auto initBox = [=](not_null box) { - box->setTitle(tr::lng_group_call_invite_title()); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); - *close = [=] { box->closeBox(); }; + box->setTitle(tr::lng_group_call_invite_conf()); + raw->hasSelectedValue() | rpl::start_with_next([=](bool has) { + box->clearButtons(); + if (has) { + box->addButton(tr::lng_group_call_invite_button(), [=] { + const auto call = weak.get(); + if (!call) { + return; + } + auto peers = box->collectSelectedRows(); + auto users = ranges::views::all( + peers + ) | ranges::views::transform([](not_null peer) { + return not_null(peer->asUser()); + }) | ranges::to_vector; + const auto done = [=](GroupCall::InviteResult result) { + (*close)(); + showToast({ ComposeInviteResultToast(result) }); + }; + call->inviteUsers(users, done); + }); + } + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }, box->lifetime()); + *close = crl::guard(box, [=] { box->closeBox(); }); }; return Box(std::move(controller), initBox); } @@ -270,24 +387,23 @@ object_ptr PrepareInviteBox( if (!call) { return; } - const auto result = call->inviteUsers(users); - if (const auto user = std::get_if>(&result)) { - showToast(tr::lng_group_call_invite_done_user( - tr::now, - lt_user, - Ui::Text::Bold((*user)->firstName), - Ui::Text::WithEntities)); - } else if (const auto count = std::get_if(&result)) { - if (*count > 0) { + call->inviteUsers(users, [=](GroupCall::InviteResult result) { + if (result.invited.size() == 1) { + showToast(tr::lng_group_call_invite_done_user( + tr::now, + lt_user, + Ui::Text::Bold(result.invited.front()->firstName), + Ui::Text::WithEntities)); + } else if (result.invited.size() > 1) { showToast(tr::lng_group_call_invite_done_many( tr::now, lt_count, - *count, + result.invited.size(), Ui::Text::RichLangValue)); + } else { + Unexpected("Result in GroupCall::inviteUsers."); } - } else { - Unexpected("Result in GroupCall::inviteUsers."); - } + }); }; const auto inviteWithAdd = [=]( std::shared_ptr show, diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h index e1e10ff4e7..47508d131a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h @@ -77,6 +77,7 @@ private: [[nodiscard]] object_ptr PrepareInviteBox( not_null call, - Fn showToast); + Fn showToast, + Fn finished)> shareConferenceLink = nullptr); } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 3416b4a014..03f9610706 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1742,13 +1742,15 @@ void Members::setMode(PanelMode mode) { } QRect Members::getInnerGeometry() const { + const auto shareLink = _shareLinkButton.current(); const auto addMembers = _addMemberButton.current(); + const auto share = shareLink ? shareLink->height() : 0; const auto add = addMembers ? addMembers->height() : 0; return QRect( 0, -_scroll->scrollTop(), width(), - _list->y() + _list->height() + _bottomSkip->height() + add); + _list->y() + _list->height() + _bottomSkip->height() + add + share); } rpl::producer Members::fullCountValue() const { @@ -1923,16 +1925,22 @@ void Members::setupFakeRoundCorners() { const auto bottomleft = create({ 0, shift }); const auto bottomright = create({ shift, shift }); + const auto heightValue = [=](Ui::RpWidget *widget) { + topleft->raise(); + topright->raise(); + bottomleft->raise(); + bottomright->raise(); + return widget ? widget->heightValue() : rpl::single(0); + }; rpl::combine( _list->geometryValue(), - _addMemberButton.value() | rpl::map([=](Ui::RpWidget *widget) { - topleft->raise(); - topright->raise(); - bottomleft->raise(); - bottomright->raise(); - return widget ? widget->heightValue() : rpl::single(0); - }) | rpl::flatten_latest() - ) | rpl::start_with_next([=](QRect list, int addMembers) { + _addMemberButton.value() | rpl::map( + heightValue + ) | rpl::flatten_latest(), + _shareLinkButton.value() | rpl::map( + heightValue + ) | rpl::flatten_latest() + ) | rpl::start_with_next([=](QRect list, int addMembers, int shareLink) { const auto left = list.x(); const auto top = list.y() - _topSkip->height(); const auto right = left + list.width() - topright->width(); @@ -1941,6 +1949,7 @@ void Members::setupFakeRoundCorners() { + list.height() + _bottomSkip->height() + addMembers + + shareLink - bottomleft->height(); topleft->move(left, top); topright->move(right, top); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 8dced0375a..31f55e304a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -949,7 +949,9 @@ void Panel::setupMembers() { _members->addMembersRequests( ) | rpl::start_with_next([=] { - if (!_peer->isBroadcast() + if (_call->conference()) { + addMembers(); + } else if (!_peer->isBroadcast() && Data::CanSend(_peer, ChatRestriction::SendOther, false) && _call->joinAs()->isSelf()) { addMembers(); @@ -960,19 +962,9 @@ void Panel::setupMembers() { } }, _callLifetime); - const auto exporting = std::make_shared(); _members->shareLinkRequests( - ) | rpl::start_with_next([=] { - Expects(_call->conference()); - - if (*exporting) { - return; - } - *exporting = true; - ExportConferenceCallLink(uiShow(), _call->conferenceCall(), { - .st = DarkConferenceCallLinkStyle(), - .finished = [=](bool) { *exporting = false; }, - }); + ) | rpl::start_with_next([cb = shareConferenceLinkCallback()] { + cb(nullptr); }, _callLifetime); _call->videoEndpointLargeValue( @@ -984,6 +976,28 @@ void Panel::setupMembers() { }, _callLifetime); } +Fn finished)> Panel::shareConferenceLinkCallback() { + const auto exporting = std::make_shared(); + return [=](Fn finished) { + Expects(_call->conference()); + + if (*exporting) { + return; + } + *exporting = true; + const auto done = [=](bool ok) { + *exporting = false; + if (const auto onstack = finished) { + onstack(ok); + } + }; + ExportConferenceCallLink(uiShow(), _call->conferenceCall(), { + .finished = done, + .st = DarkConferenceCallLinkStyle(), + }); + }; +} + void Panel::enlargeVideo() { _lastSmallGeometry = window()->geometry(); @@ -1536,10 +1550,17 @@ void Panel::showMainMenu() { } void Panel::addMembers() { + if (_call->conference() + && _call->conferenceCall()->fullCount() >= Data::kMaxConferenceMembers) { + showToast({ tr::lng_group_call_invite_limit(tr::now) }); + } const auto showToastCallback = [=](TextWithEntities &&text) { showToast(std::move(text)); }; - if (auto box = PrepareInviteBox(_call, showToastCallback)) { + const auto link = _call->conference() + ? shareConferenceLinkCallback() + : nullptr; + if (auto box = PrepareInviteBox(_call, showToastCallback, link)) { showBox(std::move(box)); } } diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index a591273d06..05c399dea6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -193,6 +193,8 @@ private: void toggleWideControls(bool shown); void updateWideControlsVisibility(); [[nodiscard]] bool videoButtonInNarrowMode() const; + [[nodiscard]] auto shareConferenceLinkCallback() + -> Fn finished)>; void endCall(); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 47cdc4fabf..b90e0f24c3 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -27,7 +27,6 @@ constexpr auto kSpeakingAfterActive = crl::time(6000); constexpr auto kActiveAfterJoined = crl::time(1000); constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000); constexpr auto kReloadStaleTimeout = 16 * crl::time(1000); -constexpr auto kMaxConferenceMembers = 50; [[nodiscard]] QString ExtractNextOffset(const MTPphone_GroupCall &call) { return call.match([&](const MTPDphone_groupCall &data) { @@ -245,7 +244,7 @@ void GroupCall::checkStaleRequest() { MTP_vector(), // ids MTP_vector(), // ssrcs MTP_string(QString()), - MTP_int(kMaxConferenceMembers) + MTP_int(kMaxConferenceMembers + 10) )).done([=](const MTPphone_GroupParticipants &result) { _checkStaleRequestId = 0; const auto &list = _participantsWithAccess.current(); diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index d9dfdb8559..f413f3f70b 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -30,6 +30,8 @@ namespace Data { [[nodiscard]] const std::string &RtmpEndpointId(); +inline constexpr auto kMaxConferenceMembers = 10; + struct LastSpokeTimes { crl::time anything = 0; crl::time voice = 0; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 098315878d..fd205d0e03 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1135,6 +1135,30 @@ GroupCall *Session::groupCall(CallId callId) const { return (i != end(_groupCalls)) ? i->second.get() : nullptr; } +std::shared_ptr Session::sharedConferenceCall( + CallId id, + uint64 accessHash) { + const auto i = _conferenceCalls.find(id); + if (i != end(_conferenceCalls)) { + if (auto result = i->second.lock()) { + return result; + } + } + auto result = std::make_shared( + session().user(), + id, + accessHash, + TimeId(), // scheduledDate + false, // rtmp + true); // conference + if (i != end(_conferenceCalls)) { + i->second = result; + } else { + _conferenceCalls.emplace(id, result); + } + return result; +} + void Session::watchForOffline(not_null user, TimeId now) { if (!now) { now = base::unixtime::now(); @@ -1205,7 +1229,13 @@ void Session::registerInvitedToCallUser( CallId callId, not_null peer, not_null user) { - const auto call = peer->groupCall(); + registerInvitedToCallUser(callId, peer->groupCall(), user); +} + +void Session::registerInvitedToCallUser( + CallId callId, + GroupCall *call, + not_null user) { if (call && call->id() == callId) { const auto inCall = ranges::contains( call->participants(), diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 108f42482b..011d98dd43 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -230,7 +230,11 @@ public: void registerGroupCall(not_null call); void unregisterGroupCall(not_null call); - GroupCall *groupCall(CallId callId) const; + [[nodiscard]] GroupCall *groupCall(CallId callId) const; + + [[nodiscard]] std::shared_ptr sharedConferenceCall( + CallId id, + uint64 accessHash); void watchForOffline(not_null user, TimeId now = 0); void maybeStopWatchForOffline(not_null user); @@ -241,7 +245,13 @@ public: CallId callId, not_null peer, not_null user); - void unregisterInvitedToCallUser(CallId callId, not_null user); + void registerInvitedToCallUser( + CallId callId, + GroupCall *call, + not_null user); + void unregisterInvitedToCallUser( + CallId callId, + not_null user); struct InviteToCall { CallId id = 0; @@ -1096,10 +1106,11 @@ private: base::flat_set> _heavyViewParts; - base::flat_map> _groupCalls; + base::flat_map> _groupCalls; + base::flat_map> _conferenceCalls; rpl::event_stream _invitesToCalls; base::flat_map< - uint64, + CallId, base::flat_set>> _invitedToCallUsers; base::flat_set> _shownSpoilers; diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 361ed511e0..9620615dd8 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -135,13 +135,9 @@ constexpr auto kPlayStatusLimit = 2; MTP_int(*creating) )).done(crl::guard(controller, [=](const MTPphone_GroupCall &result) { result.data().vcall().match([&](const auto &data) { - const auto call = std::make_shared( - session->user(), + const auto call = session->data().sharedConferenceCall( data.vid().v, - data.vaccess_hash().v, - TimeId(), // scheduleDate - false, // rtmp - true); // conference + data.vaccess_hash().v); call->processFullCall(result); const auto finished = [=](bool ok) { if (!ok) { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index d0a2eae995..648e498da9 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -896,13 +896,9 @@ void SessionNavigation::resolveConferenceCall( const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); const auto finished = base::take(_conferenceCallResolveFinished); result.data().vcall().match([&](const auto &data) { - const auto call = std::make_shared( - session().user(), + const auto call = session().data().sharedConferenceCall( data.vid().v, - data.vaccess_hash().v, - TimeId(), // scheduleDate - false, // rtmp - true); // conference + data.vaccess_hash().v); call->processFullCall(result); const auto confirmed = std::make_shared(); const auto join = [=] { From d052eac019e8e2b214074ab9915b72082ee234f7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Mar 2025 00:53:11 +0500 Subject: [PATCH 087/190] Initial call->confcall migration. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/calls/calls.style | 14 +- Telegram/SourceFiles/calls/calls_call.cpp | 60 +++++- Telegram/SourceFiles/calls/calls_call.h | 13 +- Telegram/SourceFiles/calls/calls_instance.cpp | 38 ++-- Telegram/SourceFiles/calls/calls_instance.h | 11 +- Telegram/SourceFiles/calls/calls_panel.cpp | 196 ++++++++++++++++-- Telegram/SourceFiles/calls/calls_panel.h | 55 ++++- .../calls/group/calls_group_call.cpp | 47 +++++ .../calls/group/calls_group_call.h | 4 + .../calls/group/calls_group_common.cpp | 86 +++++++- .../calls/group/calls_group_common.h | 15 +- .../group/calls_group_invite_controller.cpp | 81 ++++---- .../group/calls_group_invite_controller.h | 6 + .../calls/group/calls_group_panel.cpp | 19 +- .../calls/group/calls_group_panel.h | 3 + Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- .../SourceFiles/window/window_main_menu.cpp | 44 ++-- .../window/window_session_controller.cpp | 38 +--- .../window/window_session_controller.h | 9 +- 20 files changed, 561 insertions(+), 182 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4a3f1bc052..86ea166e86 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4655,6 +4655,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_error_camera_not_started" = "You can switch to video call once you're connected."; "lng_call_error_camera_outdated" = "{user}'s app does not support video calls. They need to update their app before you can call them."; "lng_call_error_audio_io" = "There seems to be a problem with your sound card. Please make sure that your computer's speakers and microphone are working and try again."; +"lng_call_error_add_not_started" = "You can add more people once you're connected."; "lng_call_bar_hangup" = "End call"; "lng_call_leave_to_other_sure" = "End your active call and join this video chat?"; @@ -4691,6 +4692,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_start_video" = "Start Video"; "lng_call_stop_video" = "Stop Video"; "lng_call_screencast" = "Screencast"; +"lng_call_add_people" = "Add People"; "lng_call_end_call" = "End Call"; "lng_call_mute_audio" = "Mute"; "lng_call_unmute_audio" = "Unmute"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index d67b208f9b..9c8e600509 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -24,7 +24,7 @@ CallSignalBars { inactiveOpacity: double; } -callWidthMin: 300px; +callWidthMin: 372px; callHeightMin: 440px; callWidth: 720px; callHeight: 540px; @@ -183,6 +183,18 @@ callCameraUnmute: CallButton(callMicrophoneUnmute) { } } } +callAddPeople: CallButton(callAnswer) { + button: IconButton(callButton) { + icon: icon {{ "settings/group", callIconFgActive }}; + iconPosition: point(-1px, 24px); + ripple: RippleAnimation(defaultRippleAnimation) { + color: callIconActiveRipple; + } + } + bg: callIconBgActive; + outerBg: callIconBgActive; + label: callButtonLabel; +} callCornerButtonInner: IconButton { width: 20px; height: 20px; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 3a34f07836..2ff549c93d 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_panel.h" #include "core/application.h" #include "core/core_settings.h" +#include "data/data_group_call.h" #include "data/data_session.h" #include "data/data_user.h" #include "lang/lang_keys.h" @@ -527,20 +528,23 @@ crl::time Call::getDurationMs() const { return _startTime ? (crl::now() - _startTime) : 0; } -void Call::hangup() { +void Call::hangup(Data::GroupCall *migrateCall, const QString &migrateSlug) { const auto state = _state.current(); - if (state == State::Busy) { + if (state == State::Busy || state == State::MigrationHangingUp) { _delegate->callFinished(this); } else { const auto missed = (state == State::Ringing || (state == State::Waiting && _type == Type::Outgoing)); const auto declined = isIncomingWaiting(); - const auto reason = missed + const auto reason = !migrateSlug.isEmpty() + ? MTP_phoneCallDiscardReasonMigrateConferenceCall( + MTP_string(migrateSlug)) + : missed ? MTP_phoneCallDiscardReasonMissed() : declined ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup(); - finish(FinishType::Ended, reason); + finish(FinishType::Ended, reason, migrateCall); } } @@ -740,7 +744,10 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { && reason->type() == mtpc_phoneCallDiscardReasonDisconnect) { LOG(("Call Info: Discarded with DISCONNECT reason.")); } - if (reason && reason->type() == mtpc_phoneCallDiscardReasonBusy) { + if (reason && reason->type() == mtpc_phoneCallDiscardReasonMigrateConferenceCall) { + const auto slug = qs(reason->c_phoneCallDiscardReasonMigrateConferenceCall().vslug()); + finishByMigration(slug); + } else if (reason && reason->type() == mtpc_phoneCallDiscardReasonBusy) { setState(State::Busy); } else if (_type == Type::Outgoing || _state.current() == State::HangingUp) { @@ -768,6 +775,32 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { Unexpected("phoneCall type inside an existing call handleUpdate()"); } +void Call::finishByMigration(const QString &slug) { + if (_state.current() == State::MigrationHangingUp) { + return; + } + setState(State::MigrationHangingUp); + const auto limit = 5; + const auto session = &_user->session(); + session->api().request(MTPphone_GetGroupCall( + MTP_inputGroupCallSlug(MTP_string(slug)), + MTP_int(limit) + )).done([=](const MTPphone_GroupCall &result) { + result.data().vcall().match([&](const auto &data) { + const auto call = session->data().sharedConferenceCall( + data.vid().v, + data.vaccess_hash().v); + call->processFullCall(result); + Core::App().calls().startOrJoinConferenceCall({ + .call = call, + .linkSlug = slug, + }); + }); + }).fail(crl::guard(this, [=] { + setState(State::Failed); + })).send(); +} + void Call::updateRemoteMediaState( tgcalls::AudioState audio, tgcalls::VideoState video) { @@ -1059,6 +1092,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) { const auto track = (state != State::FailedHangingUp) && (state != State::Failed) && (state != State::HangingUp) + && (state != State::MigrationHangingUp) && (state != State::Ended) && (state != State::EndedByOtherDevice) && (state != State::Busy); @@ -1175,6 +1209,11 @@ void Call::setState(State state) { && state != State::Failed) { return; } + if (was == State::MigrationHangingUp + && state != State::Ended + && state != State::Failed) { + return; + } if (was != state) { _state = state; @@ -1323,7 +1362,10 @@ rpl::producer Call::cameraDeviceIdValue() const { return _cameraDeviceId.value(); } -void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { +void Call::finish( + FinishType type, + const MTPPhoneCallDiscardReason &reason, + Data::GroupCall *migrateCall) { Expects(type != FinishType::None); setSignalBarCount(kSignalBarFinished); @@ -1371,6 +1413,12 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { // We want to discard request still being sent and processed even if // the call is already destroyed. + if (migrateCall) { + _user->owner().registerInvitedToCallUser( + migrateCall->id(), + migrateCall, + _user); + } const auto session = &_user->session(); const auto weak = base::make_weak(this); session->api().request(MTPphone_DiscardCall( // We send 'discard' here. diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 1d01d6d5f5..2e024cea83 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_auth_key.h" #include "webrtc/webrtc_device_resolver.h" +namespace Data { +class GroupCall; +} // namespace Data + namespace Media { namespace Audio { class Track; @@ -122,6 +126,7 @@ public: FailedHangingUp, Failed, HangingUp, + MigrationHangingUp, Ended, EndedByOtherDevice, ExchangingKeys, @@ -198,7 +203,9 @@ public: void applyUserConfirmation(); void answer(); - void hangup(); + void hangup( + Data::GroupCall *migrateCall = nullptr, + const QString &migrateSlug = QString()); void redial(); bool isKeyShaForFingerprintReady() const; @@ -246,7 +253,9 @@ private: void finish( FinishType type, const MTPPhoneCallDiscardReason &reason - = MTP_phoneCallDiscardReasonDisconnect()); + = MTP_phoneCallDiscardReasonDisconnect(), + Data::GroupCall *migrateCall = nullptr); + void finishByMigration(const QString &slug); void startOutgoing(); void startIncoming(); void startWaitingTrack(); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 9a3147e76c..effbcfb8bb 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -230,12 +230,13 @@ void Instance::startOrJoinGroupCall( }); } -void Instance::startOrJoinConferenceCall( - std::shared_ptr show, - StartConferenceCallArgs args) { - destroyCurrentCall(); +void Instance::startOrJoinConferenceCall(StartConferenceCallArgs args) { + destroyCurrentCall( + args.migrating ? args.call.get() : nullptr, + args.migrating ? args.linkSlug : QString()); const auto session = &args.call->peer()->session(); + const auto showShareLink = args.migrating && args.invite.empty(); auto call = std::make_unique( _delegate.get(), Calls::Group::ConferenceInfo{ @@ -254,6 +255,11 @@ void Instance::startOrJoinConferenceCall( _currentGroupCallPanel = std::make_unique(raw); _currentGroupCall = std::move(call); _currentGroupCallChanges.fire_copy(raw); + if (!args.invite.empty()) { + _currentGroupCallPanel->migrationInviteUsers(std::move(args.invite)); + } else if (args.migrating) { + _currentGroupCallPanel->migrationShowShareLink(); + } } void Instance::confirmLeaveCurrent( @@ -435,24 +441,6 @@ void Instance::createGroupCall( _currentGroupCallChanges.fire_copy(raw); } -void Instance::createConferenceCall(Group::ConferenceInfo info) { - destroyCurrentCall(); - - auto call = std::make_unique( - _delegate.get(), - std::move(info)); - const auto raw = call.get(); - - raw->peer()->session().account().sessionChanges( - ) | rpl::start_with_next([=] { - destroyGroupCall(raw); - }, raw->lifetime()); - - _currentGroupCallPanel = std::make_unique(raw); - _currentGroupCall = std::move(call); - _currentGroupCallChanges.fire_copy(raw); -} - void Instance::refreshDhConfig() { Expects(_currentCall != nullptr); @@ -744,9 +732,11 @@ bool Instance::inGroupCall() const { && (state != GroupCall::State::Failed); } -void Instance::destroyCurrentCall() { +void Instance::destroyCurrentCall( + Data::GroupCall *migrateCall, + const QString &migrateSlug) { if (const auto current = currentCall()) { - current->hangup(); + current->hangup(migrateCall, migrateSlug); if (const auto still = currentCall()) { destroyCall(still); } diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 2d5df76f2a..ad55bb5dec 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -73,6 +73,8 @@ struct StartConferenceCallArgs { std::shared_ptr e2e; QString linkSlug; MsgId joinMessageId; + std::vector> invite; + bool migrating = false; }; class Instance final : public base::has_weak_ptr { @@ -85,9 +87,7 @@ public: std::shared_ptr show, not_null peer, StartGroupCallArgs args); - void startOrJoinConferenceCall( - std::shared_ptr show, - StartConferenceCallArgs args); + void startOrJoinConferenceCall(StartConferenceCallArgs args); void showStartWithRtmp( std::shared_ptr show, not_null peer); @@ -140,7 +140,6 @@ private: void createGroupCall( Group::JoinInfo info, const MTPInputGroupCall &inputCall); - void createConferenceCall(Group::ConferenceInfo info); void destroyGroupCall(not_null call); void confirmLeaveCurrent( std::shared_ptr show, @@ -156,7 +155,9 @@ private: void refreshServerConfig(not_null session); bytes::const_span updateDhConfig(const MTPmessages_DhConfig &data); - void destroyCurrentCall(); + void destroyCurrentCall( + Data::GroupCall *migrateCall = nullptr, + const QString &migrateSlug = QString()); void handleCallUpdate( not_null session, const MTPPhoneCall &call); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index e5af06c171..1b48630320 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_cloud_file.h" #include "data/data_changes.h" #include "calls/group/calls_group_common.h" +#include "calls/group/calls_group_invite_controller.h" #include "calls/ui/calls_device_menu.h" #include "calls/calls_emoji_fingerprint.h" #include "calls/calls_signal_bars.h" @@ -45,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/integration.h" #include "core/application.h" #include "lang/lang_keys.h" +#include "main/session/session_show.h" #include "main/main_session.h" #include "apiwrap.h" #include "platform/platform_specific.h" @@ -92,6 +94,77 @@ constexpr auto kHideControlsQuickTimeout = 2 * crl::time(1000); )"; } +class Show final : public Main::SessionShow { +public: + explicit Show(not_null panel); + ~Show(); + + void showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + Ui::LayerOptions options, + anim::type animated) const override; + [[nodiscard]] not_null toastParent() const override; + [[nodiscard]] bool valid() const override; + operator bool() const override; + + [[nodiscard]] Main::Session &session() const override; + +private: + const base::weak_ptr _panel; + +}; + +Show::Show(not_null panel) +: _panel(base::make_weak(panel)) { +} + +Show::~Show() = default; + +void Show::showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + Ui::LayerOptions options, + anim::type animated) const { + using UniqueLayer = std::unique_ptr; + using ObjectBox = object_ptr; + if (auto layerWidget = std::get_if(&layer)) { + if (const auto panel = _panel.get()) { + panel->showLayer(std::move(*layerWidget), options, animated); + } + } else if (auto box = std::get_if(&layer)) { + if (const auto panel = _panel.get()) { + panel->showBox(std::move(*box), options, animated); + } + } else if (const auto panel = _panel.get()) { + panel->hideLayer(animated); + } +} + +not_null Show::toastParent() const { + const auto panel = _panel.get(); + Assert(panel != nullptr); + return panel->widget(); +} + +bool Show::valid() const { + return !_panel.empty(); +} + +Show::operator bool() const { + return valid(); +} + +Main::Session &Show::session() const { + const auto panel = _panel.get(); + Assert(panel != nullptr); + return panel->user()->session(); +} + } // namespace Panel::Panel(not_null call) @@ -121,6 +194,9 @@ Panel::Panel(not_null call) widget(), st::callMicrophoneMute, &st::callMicrophoneUnmute)) +, _addPeople( + widget(), + object_ptr(widget(), st::callAddPeople)) , _name(widget(), st::callName) , _status(widget(), st::callStatus) , _hideControlsTimer([=] { requestControlsHidden(true); }) @@ -133,6 +209,8 @@ Panel::Panel(not_null call) _cancel->setDuration(st::callPanelDuration); _cancel->entity()->setText(tr::lng_call_cancel()); _screencast->setDuration(st::callPanelDuration); + _addPeople->setDuration(st::callPanelDuration); + _addPeople->entity()->setText(tr::lng_call_add_people()); initWindow(); initWidget(); @@ -153,6 +231,62 @@ bool Panel::isActive() const { return window()->isActiveWindow() && isVisible(); } +base::weak_ptr Panel::showToast( + const QString &text, + crl::time duration) { + return showToast({ + .text = { text }, + .duration = duration, + }); +} + +base::weak_ptr Panel::showToast( + TextWithEntities &&text, + crl::time duration) { + return showToast({ + .text = std::move(text), + .duration = duration, + }); +} + +base::weak_ptr Panel::showToast( + Ui::Toast::Config &&config) { + if (!config.st) { + config.st = &st::callErrorToast; + } + return Show(this).showToast(std::move(config)); +} + +void Panel::showBox(object_ptr box) { + showBox(std::move(box), Ui::LayerOption::KeepOther, anim::type::normal); +} + +void Panel::showBox( + object_ptr box, + Ui::LayerOptions options, + anim::type animated) { + _layerBg->showBox(std::move(box), options, animated); +} + +void Panel::showLayer( + std::unique_ptr layer, + Ui::LayerOptions options, + anim::type animated) { + _layerBg->showLayer(std::move(layer), options, animated); +} + +void Panel::hideLayer(anim::type animated) { + _layerBg->hideAll(animated); +} + +bool Panel::isLayerShown() const { + return _layerBg->topShownLayer() != nullptr; +} + +std::shared_ptr Panel::uiShow() { + return std::make_shared(this); +} + void Panel::showAndActivate() { if (window()->isHidden()) { window()->show(); @@ -303,7 +437,7 @@ void Panel::initControls() { return; } else if (!env->desktopCaptureAllowed()) { if (auto box = Group::ScreenSharingPrivacyRequestBox()) { - _layerBg->showBox(std::move(box)); + showBox(std::move(box)); } } else if (const auto source = env->uniqueDesktopCaptureSource()) { if (!chooseSourceActiveDeviceId().isEmpty()) { @@ -318,9 +452,42 @@ void Panel::initControls() { _camera->setClickedCallback([=] { if (!_call) { return; - } else { - _call->toggleCameraSharing(!_call->isSharingCamera()); } + _call->toggleCameraSharing(!_call->isSharingCamera()); + }); + _addPeople->entity()->setClickedCallback([=] { + if (!_call || _call->state() != Call::State::Established) { + showToast(tr::lng_call_error_add_not_started(tr::now)); + return; + } + const auto call = _call; + const auto creating = std::make_shared(); + const auto finish = [=](QString link) { + if (link.isEmpty()) { + *creating = false; + } + }; + const auto create = [=](std::vector> users) { + if (*creating) { + return; + } + *creating = true; + Group::MakeConferenceCall({ + .show = uiShow(), + .finished = finish, + .invite = std::move(users), + .joining = true, + .migrating = true, + }); + }; + const auto invite = crl::guard(call, [=]( + std::vector> users) { + create(std::move(users)); + }); + const auto share = crl::guard(call, [=] { + create({}); + }); + showBox(Group::PrepareInviteBox(call, invite, share)); }); _updateDurationTimer.setCallback([this] { @@ -605,6 +772,7 @@ void Panel::reinitWithCall(Call *call) { && state != State::EndedByOtherDevice && state != State::Failed && state != State::FailedHangingUp + && state != State::MigrationHangingUp && state != State::HangingUp) { refreshOutgoingPreviewInBody(state); } @@ -630,10 +798,7 @@ void Panel::reinitWithCall(Call *call) { } Unexpected("Error type in _call->errors()."); }(); - Ui::Toast::Show(widget(), Ui::Toast::Config{ - .text = { text }, - .st = &st::callErrorToast, - }); + showToast(text); }, _callLifetime); _name->setText(_user->name()); @@ -647,6 +812,7 @@ void Panel::reinitWithCall(Call *call) { _startVideo->raise(); } _mute->raise(); + _addPeople->raise(); _powerSaveBlocker = std::make_unique( base::PowerSaveBlockType::PreventDisplaySleep, @@ -1077,11 +1243,7 @@ void Panel::updateHangupGeometry() { // Screencast - Camera - Cancel/Decline - Answer/Hangup/Redial - Mute. const auto buttonWidth = st::callCancel.button.width; const auto cancelWidth = buttonWidth * (1. - hangupProgress); - const auto cancelLeft = (isWaitingUser) - ? ((widget()->width() - buttonWidth) / 2) - : (_mute->animating()) - ? ((widget()->width() - cancelWidth) / 2) - : ((widget()->width() / 2) - cancelWidth); + const auto cancelLeft = (widget()->width() - buttonWidth) / 2; _cancel->moveToLeft(cancelLeft, _buttonsTop); _decline->moveToLeft(cancelLeft, _buttonsTop); @@ -1089,6 +1251,7 @@ void Panel::updateHangupGeometry() { _screencast->moveToLeft(_camera->x() - buttonWidth, _buttonsTop); _answerHangupRedial->moveToLeft(cancelLeft + cancelWidth, _buttonsTop); _mute->moveToLeft(_answerHangupRedial->x() + buttonWidth, _buttonsTop); + _addPeople->moveToLeft(_mute->x() + buttonWidth, _buttonsTop); if (_startVideo) { _startVideo->moveToLeft(_camera->x(), _camera->y()); } @@ -1136,12 +1299,17 @@ not_null Panel::widget() const { return _window.widget(); } +not_null Panel::user() const { + return _user; +} + void Panel::stateChanged(State state) { Expects(_call != nullptr); updateStatusText(state); if ((state != State::HangingUp) + && (state != State::MigrationHangingUp) && (state != State::Ended) && (state != State::EndedByOtherDevice) && (state != State::FailedHangingUp) @@ -1182,6 +1350,7 @@ void Panel::stateChanged(State state) { toggleButton( _screencast, !(isBusy || isWaitingUser || incomingWaiting)); + toggleButton(_addPeople, !isWaitingUser); const auto hangupShown = !_decline->toggled() && !_cancel->toggled(); if (_hangupShown != hangupShown) { @@ -1232,7 +1401,8 @@ void Panel::updateStatusText(State state) { switch (state) { case State::Starting: case State::WaitingInit: - case State::WaitingInitAck: return tr::lng_call_status_connecting(tr::now); + case State::WaitingInitAck: + case State::MigrationHangingUp: return tr::lng_call_status_connecting(tr::now); case State::Established: { if (_call) { auto durationMs = _call->getDurationMs(); diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index ee24ab2c4a..fdb61333ab 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -27,7 +27,15 @@ namespace Data { class PhotoMedia; } // namespace Data +namespace Main { +class SessionShow; +} // namespace Main + namespace Ui { +class BoxContent; +class LayerWidget; +enum class LayerOption; +using LayerOptions = base::flags; class IconButton; class CallButton; class LayerManager; @@ -38,14 +46,17 @@ template class PaddingWrap; class RpWindow; class PopupMenu; -namespace GL { -enum class Backend; -} // namespace GL -namespace Platform { -struct SeparateTitleControls; -} // namespace Platform } // namespace Ui +namespace Ui::Toast { +class Instance; +struct Config; +} // namespace Ui::Toast + +namespace Ui::Platform { +struct SeparateTitleControls; +} // namespace Ui::Platform + namespace style { struct CallSignalBars; struct CallBodyLayout; @@ -58,13 +69,39 @@ class SignalBars; class VideoBubble; struct DeviceSelection; -class Panel final : private Group::Ui::DesktopCapture::ChooseSourceDelegate { +class Panel final + : public base::has_weak_ptr + , private Group::Ui::DesktopCapture::ChooseSourceDelegate { public: Panel(not_null call); ~Panel(); + [[nodiscard]] not_null widget() const; + [[nodiscard]] not_null user() const; [[nodiscard]] bool isVisible() const; [[nodiscard]] bool isActive() const; + + base::weak_ptr showToast( + const QString &text, + crl::time duration = 0); + base::weak_ptr showToast( + TextWithEntities &&text, + crl::time duration = 0); + base::weak_ptr showToast( + Ui::Toast::Config &&config); + + void showBox(object_ptr box); + void showBox( + object_ptr box, + Ui::LayerOptions options, + anim::type animated = anim::type::normal); + void showLayer( + std::unique_ptr layer, + Ui::LayerOptions options, + anim::type animated = anim::type::normal); + void hideLayer(anim::type animated = anim::type::normal); + [[nodiscard]] bool isLayerShown() const; + void showAndActivate(); void minimize(); void toggleFullScreen(); @@ -83,6 +120,8 @@ public: [[nodiscard]] rpl::producer startOutgoingRequests() const; + [[nodiscard]] std::shared_ptr uiShow(); + [[nodiscard]] rpl::lifetime &lifetime(); private: @@ -97,7 +136,6 @@ private: }; [[nodiscard]] not_null window() const; - [[nodiscard]] not_null widget() const; void paint(QRect clip); @@ -170,6 +208,7 @@ private: base::unique_qptr _startVideo; object_ptr> _mute; Ui::CallButton *_audioDeviceToggle = nullptr; + object_ptr < Ui::FadeWrap> _addPeople; object_ptr _name; object_ptr _status; object_ptr _fingerprint = { nullptr }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 19950598f4..bd1bf41863 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "lang/lang_hardcoded.h" #include "boxes/peers/edit_participants_box.h" // SubscribeToMigration. +#include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/ui_utility.h" #include "base/unixtime.h" @@ -1164,6 +1165,15 @@ std::shared_ptr GroupCall::conferenceCall() const { return _conferenceCall; } +QString GroupCall::existingConferenceLink() const { + Expects(!_conferenceLinkSlug.isEmpty()); + + const auto session = &_peer->session(); + return !_conferenceLinkSlug.isEmpty() + ? session->createInternalLinkFull("call/" + _conferenceLinkSlug) + : QString(); +} + rpl::producer> GroupCall::real() const { if (const auto real = lookupReal()) { return rpl::single(not_null{ real }); @@ -3902,4 +3912,41 @@ void GroupCall::destroyScreencast() { } } +TextWithEntities ComposeInviteResultToast( + const GroupCall::InviteResult &result) { + auto text = TextWithEntities(); + const auto invited = int(result.invited.size()); + const auto restricted = int(result.privacyRestricted.size()); + if (invited == 1) { + text.append(tr::lng_confcall_invite_done_user( + tr::now, + lt_user, + Ui::Text::Bold(result.invited.front()->shortName()), + Ui::Text::RichLangValue)); + } else if (invited > 1) { + text.append(tr::lng_confcall_invite_done_many( + tr::now, + lt_count, + invited, + Ui::Text::RichLangValue)); + } + if (invited && restricted) { + text.append(u"\n\n"_q); + } + if (restricted == 1) { + text.append(tr::lng_confcall_invite_fail_user( + tr::now, + lt_user, + Ui::Text::Bold(result.privacyRestricted.front()->shortName()), + Ui::Text::RichLangValue)); + } else if (restricted > 1) { + text.append(tr::lng_confcall_invite_fail_many( + tr::now, + lt_count, + restricted, + Ui::Text::RichLangValue)); + } + return text; +} + } // namespace Calls diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index c81ada53c1..f30abd8b17 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -257,6 +257,7 @@ public: [[nodiscard]] Data::GroupCall *lookupReal() const; [[nodiscard]] std::shared_ptr conferenceCall() const; + [[nodiscard]] QString existingConferenceLink() const; [[nodiscard]] rpl::producer> real() const; [[nodiscard]] rpl::producer emojiHashValue() const; @@ -752,4 +753,7 @@ private: }; +[[nodiscard]] TextWithEntities ComposeInviteResultToast( + const GroupCall::InviteResult &result); + } // namespace Calls diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 110e08d76c..f0dbb9628f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -9,9 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/platform/base_platform_info.h" +#include "base/random.h" #include "boxes/share_box.h" +#include "calls/calls_instance.h" +#include "core/application.h" #include "core/local_url_handlers.h" #include "data/data_group_call.h" +#include "data/data_session.h" #include "info/bot/starref/info_bot_starref_common.h" #include "ui/boxes/boost_box.h" #include "ui/widgets/buttons.h" @@ -31,6 +35,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Calls::Group { +namespace { + +[[nodiscard]] QString ExtractConferenceSlug(const QString &link) { + const auto local = Core::TryConvertUrlToLocal(link); + const auto parts1 = QStringView(local).split('#'); + if (!parts1.isEmpty()) { + const auto parts2 = parts1.front().split('&'); + if (!parts2.isEmpty()) { + const auto parts3 = parts2.front().split(u"slug="_q); + if (parts3.size() > 1) { + return parts3.back().toString(); + } + } + } + return QString(); +} + +} // namespace object_ptr ScreenSharingPrivacyRequestBox() { #ifdef Q_OS_MAC @@ -107,7 +129,6 @@ void ShowConferenceCallLinkBox( ConferenceCallLinkArgs &&args) { const auto st = args.st; const auto initial = args.initial; - const auto weakWindow = args.weakWindow; show->showBox(Box([=](not_null box) { box->setStyle(st.box ? *st.box @@ -151,7 +172,7 @@ void ShowConferenceCallLinkBox( const auto copyCallback = [=] { QApplication::clipboard()->setText(link); - box->uiShow()->showToast(tr::lng_username_copied(tr::now)); + show->showToast(tr::lng_username_copied(tr::now)); }; const auto shareCallback = [=] { FastShareLink( @@ -218,12 +239,11 @@ void ShowConferenceCallLinkBox( : st::confcallLinkCenteredText)); footer->setTryMakeSimilarLines(true); footer->setClickHandlerFilter([=](const auto &...) { - const auto local = Core::TryConvertUrlToLocal(link); - if (const auto controller = weakWindow.get()) { - controller->resolveConferenceCall( - local, - crl::guard(box, [=](bool ok) { if (ok) box->closeBox(); }), - true); + if (auto slug = ExtractConferenceSlug(link); !slug.isEmpty()) { + Core::App().calls().startOrJoinConferenceCall({ + .call = call, + .linkSlug = std::move(slug), + }); } return false; }); @@ -251,6 +271,7 @@ void ExportConferenceCallLink( std::shared_ptr call, ConferenceCallLinkArgs &&args) { const auto session = &show->session(); + const auto invite = std::move(args.invite); const auto finished = std::move(args.finished); using Flag = MTPphone_ExportGroupCallInvite::Flag; @@ -259,18 +280,63 @@ void ExportConferenceCallLink( call->input() )).done([=](const MTPphone_ExportedGroupCallInvite &result) { const auto link = qs(result.data().vlink()); + if (args.joining) { + if (auto slug = ExtractConferenceSlug(link); !slug.isEmpty()) { + Core::App().calls().startOrJoinConferenceCall({ + .call = call, + .linkSlug = std::move(slug), + .invite = invite, + .migrating = args.migrating, + }); + } + if (const auto onstack = finished) { + finished(QString()); + } + return; + } Calls::Group::ShowConferenceCallLinkBox( show, call, link, base::duplicate(args)); if (const auto onstack = finished) { - finished(true); + finished(link); } }).fail([=](const MTP::Error &error) { show->showToast(error.type()); if (const auto onstack = finished) { - finished(false); + finished(QString()); + } + }).send(); +} + +void MakeConferenceCall(ConferenceFactoryArgs &&args) { + const auto show = std::move(args.show); + const auto finished = std::move(args.finished); + const auto joining = args.joining; + const auto migrating = args.migrating; + const auto invite = std::move(args.invite); + const auto session = &show->session(); + session->api().request(MTPphone_CreateConferenceCall( + MTP_int(base::RandomValue()) + )).done([=](const MTPphone_GroupCall &result) { + result.data().vcall().match([&](const auto &data) { + const auto call = session->data().sharedConferenceCall( + data.vid().v, + data.vaccess_hash().v); + call->processFullCall(result); + Calls::Group::ExportConferenceCallLink(show, call, { + .initial = true, + .joining = joining, + .migrating = migrating, + .finished = finished, + .invite = invite, + }); + }); + }).fail([=](const MTP::Error &error) { + show->showToast(error.type()); + if (const auto onstack = finished) { + onstack(QString()); } }).send(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 1591fe5a2a..03c0770d47 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -142,8 +142,10 @@ struct ConferenceCallLinkStyleOverrides { struct ConferenceCallLinkArgs { bool initial = false; - Fn finished; - base::weak_ptr weakWindow = nullptr; + bool joining = false; + bool migrating = false; + Fn finished; + std::vector> invite; ConferenceCallLinkStyleOverrides st; }; void ShowConferenceCallLinkBox( @@ -157,4 +159,13 @@ void ExportConferenceCallLink( std::shared_ptr call, ConferenceCallLinkArgs &&args); +struct ConferenceFactoryArgs { + std::shared_ptr show; + Fn finished; + std::vector> invite; + bool joining = false; + bool migrating = false; +}; +void MakeConferenceCall(ConferenceFactoryArgs &&args); + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 05ef1fc92f..6182597c1c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_participants.h" #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_menu.h" +#include "calls/calls_call.h" #include "boxes/peer_lists_box.h" #include "data/data_user.h" #include "data/data_channel.h" @@ -80,7 +81,7 @@ protected: private: [[nodiscard]] int fullCount() const; - base::flat_set> _alreadyIn; + const base::flat_set> _alreadyIn; const Fn _shareLink; rpl::variable _hasSelected; @@ -93,7 +94,6 @@ ConfInviteController::ConfInviteController( : ContactsBoxController(session) , _alreadyIn(std::move(alreadyIn)) , _shareLink(std::move(shareLink)) { - _alreadyIn.remove(session->user()); } rpl::producer ConfInviteController::hasSelectedValue() const { @@ -161,43 +161,6 @@ void ConfInviteController::prepareViewHook() { delegate()->peerListSetAboveWidget(std::move(button)); } -[[nodiscard]] TextWithEntities ComposeInviteResultToast( - const GroupCall::InviteResult &result) { - auto text = TextWithEntities(); - const auto invited = int(result.invited.size()); - const auto restricted = int(result.privacyRestricted.size()); - if (invited == 1) { - text.append(tr::lng_confcall_invite_done_user( - tr::now, - lt_user, - Ui::Text::Bold(result.invited.front()->shortName()), - Ui::Text::RichLangValue)); - } else if (invited > 1) { - text.append(tr::lng_confcall_invite_done_many( - tr::now, - lt_count, - invited, - Ui::Text::RichLangValue)); - } - if (invited && restricted) { - text.append(u"\n\n"_q); - } - if (restricted == 1) { - text.append(tr::lng_confcall_invite_fail_user( - tr::now, - lt_user, - Ui::Text::Bold(result.privacyRestricted.front()->shortName()), - Ui::Text::RichLangValue)); - } else if (restricted > 1) { - text.append(tr::lng_confcall_invite_fail_many( - tr::now, - lt_count, - restricted, - Ui::Text::RichLangValue)); - } - return text; -} - } // namespace InviteController::InviteController( @@ -494,4 +457,44 @@ object_ptr PrepareInviteBox( return Box(std::move(controllers), initBox); } +object_ptr PrepareInviteBox( + not_null call, + Fn>)> inviteUsers, + Fn shareLink) { + const auto user = call->user(); + const auto weak = base::make_weak(call); + auto alreadyIn = base::flat_set>{ user }; + auto controller = std::make_unique( + &user->session(), + alreadyIn, + shareLink); + const auto raw = controller.get(); + raw->setStyleOverrides( + &st::groupCallInviteMembersList, + &st::groupCallMultiSelect); + auto initBox = [=](not_null box) { + box->setTitle(tr::lng_group_call_invite_conf()); + raw->hasSelectedValue() | rpl::start_with_next([=](bool has) { + box->clearButtons(); + if (has) { + box->addButton(tr::lng_group_call_invite_button(), [=] { + const auto call = weak.get(); + if (!call) { + return; + } + auto peers = box->collectSelectedRows(); + auto users = ranges::views::all( + peers + ) | ranges::views::transform([](not_null peer) { + return not_null(peer->asUser()); + }) | ranges::to_vector; + inviteUsers(std::move(users)); + }); + } + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }, box->lifetime()); + }; + return Box(std::move(controller), initBox); +} + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h index 47508d131a..b41fbfba67 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/add_participants_box.h" namespace Calls { +class Call; class GroupCall; } // namespace Calls @@ -80,4 +81,9 @@ private: Fn showToast, Fn finished)> shareConferenceLink = nullptr); +[[nodiscard]] object_ptr PrepareInviteBox( + not_null call, + Fn>)> inviteUsers, + Fn shareLink); + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 31f55e304a..7e3fe18dbb 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -985,10 +985,10 @@ Fn finished)> Panel::shareConferenceLinkCallback() { return; } *exporting = true; - const auto done = [=](bool ok) { + const auto done = [=](QString link) { *exporting = false; if (const auto onstack = finished) { - onstack(ok); + onstack(!link.isEmpty()); } }; ExportConferenceCallLink(uiShow(), _call->conferenceCall(), { @@ -998,6 +998,21 @@ Fn finished)> Panel::shareConferenceLinkCallback() { }; } +void Panel::migrationShowShareLink() { + ShowConferenceCallLinkBox( + uiShow(), + _call->conferenceCall(), + _call->existingConferenceLink(), + { .st = DarkConferenceCallLinkStyle() }); +} + +void Panel::migrationInviteUsers(std::vector> users) { + const auto done = [=](GroupCall::InviteResult result) { + showToast({ ComposeInviteResultToast(result) }); + }; + _call->inviteUsers(std::move(users), crl::guard(this, done)); +} + void Panel::enlargeVideo() { _lastSmallGeometry = window()->geometry(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 05c399dea6..2af708af80 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -115,6 +115,9 @@ public: void hideLayer(anim::type animated = anim::type::normal); [[nodiscard]] bool isLayerShown() const; + void migrationShowShareLink(); + void migrationInviteUsers(std::vector> users); + void minimize(); void toggleFullScreen(); void close(); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 8fbb14c731..68200532f5 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1342,7 +1342,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#7fe91c14 views_graph:StatsGraph reactions_by_emotion_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true conference:flags.14?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; +groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true conference:flags.14?true creator:flags.15?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; inputGroupCallSlug#fe06823f slug:string = InputGroupCall; diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 9620615dd8..189adccc03 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/event_filter.h" #include "base/qt_signal_producer.h" -#include "base/random.h" #include "boxes/about_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/premium_preview_box.h" @@ -123,40 +122,23 @@ constexpr auto kPlayStatusLimit = 2; (height - st::inviteViaLinkIcon.height()) / 2); }, icon->lifetime()); - const auto creating = std::make_shared(); + const auto creating = std::make_shared(); result->setClickedCallback([=] { if (*creating) { return; } - *creating = base::RandomValue(); - const auto show = controller->uiShow(); - const auto session = &controller->session(); - session->api().request(MTPphone_CreateConferenceCall( - MTP_int(*creating) - )).done(crl::guard(controller, [=](const MTPphone_GroupCall &result) { - result.data().vcall().match([&](const auto &data) { - const auto call = session->data().sharedConferenceCall( - data.vid().v, - data.vaccess_hash().v); - call->processFullCall(result); - const auto finished = [=](bool ok) { - if (!ok) { - *creating = 0; - } else if (const auto onstack = done) { - onstack(); - } - }; - const auto show = controller->uiShow(); - Calls::Group::ExportConferenceCallLink(show, call, { - .initial = true, - .finished = finished, - .weakWindow = controller, - }); - }); - })).fail(crl::guard(controller, [=](const MTP::Error &error) { - show->showToast(error.type()); - *creating = 0; - })).send(); + *creating = true; + const auto finished = [=](QString link) { + if (link.isEmpty()) { + *creating = false; + } else if (const auto onstack = done) { + onstack(); + } + }; + Calls::Group::MakeConferenceCall({ + .show = controller->uiShow(), + .finished = finished, + }); }); return result; } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 648e498da9..980d77a19a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -842,39 +842,20 @@ void SessionNavigation::resolveCollectible( void SessionNavigation::resolveConferenceCall( QString slug, - Fn finished, - bool skipConfirm) { - resolveConferenceCall( - std::move(slug), - 0, - std::move(finished), - skipConfirm); + Fn finished) { + resolveConferenceCall(std::move(slug), 0, std::move(finished)); } void SessionNavigation::resolveConferenceCall( MsgId inviteMsgId, - Fn finished, - bool skipConfirm) { - resolveConferenceCall({}, inviteMsgId, std::move(finished), skipConfirm); + Fn finished) { + resolveConferenceCall({}, inviteMsgId, std::move(finished)); } void SessionNavigation::resolveConferenceCall( QString slug, MsgId inviteMsgId, - Fn finished, - bool skipConfirm) { - // Accept tg://call?slug= links as well. - const auto parts1 = QStringView(slug).split('#'); - if (!parts1.isEmpty()) { - const auto parts2 = parts1.front().split('&'); - if (!parts2.isEmpty()) { - const auto parts3 = parts2.front().split(u"slug="_q); - if (parts3.size() > 1) { - slug = parts3.back().toString(); - } - } - } - + Fn finished) { _conferenceCallResolveFinished = std::move(finished); if (_conferenceCallSlug == slug && _conferenceCallInviteMsgId == inviteMsgId) { @@ -903,19 +884,12 @@ void SessionNavigation::resolveConferenceCall( const auto confirmed = std::make_shared(); const auto join = [=] { *confirmed = true; - Core::App().calls().startOrJoinConferenceCall(uiShow(), { + Core::App().calls().startOrJoinConferenceCall({ .call = call, .linkSlug = slug, .joinMessageId = inviteMsgId, }); }; - if (skipConfirm) { - join(); - if (finished) { - finished(true); - } - return; - } const auto box = uiShow()->show(Box( Calls::Group::ConferenceCallJoinConfirm, call, diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index c31772fa3d..ceb30f1bd3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -266,12 +266,10 @@ public: Fn fail = nullptr); void resolveConferenceCall( QString slug, - Fn finished = nullptr, - bool skipConfirm = false); + Fn finished = nullptr); void resolveConferenceCall( MsgId inviteMsgId, - Fn finished = nullptr, - bool skipConfirm = false); + Fn finished = nullptr); base::weak_ptr showToast( Ui::Toast::Config &&config); @@ -301,8 +299,7 @@ private: void resolveConferenceCall( QString slug, MsgId inviteMsgId, - Fn finished, - bool skipConfirm); + Fn finished); void resolveDone( const MTPcontacts_ResolvedPeer &result, From c846eeed9d1f688dede4383165419e062b7eee75 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Mar 2025 15:25:47 +0500 Subject: [PATCH 088/190] Fix cal buttons positioning. --- Telegram/SourceFiles/calls/calls_panel.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 1b48630320..82190632a0 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -1233,8 +1233,11 @@ void Panel::updateOutgoingVideoBubbleGeometry() { } void Panel::updateHangupGeometry() { + const auto isBusy = (_call + && _call->state() == State::Busy); const auto isWaitingUser = (_call && _call->state() == State::WaitingUserConfirmation); + const auto incomingWaiting = _call && _call->isIncomingWaiting(); const auto hangupProgress = isWaitingUser ? 0. : _hangupShownProgress.value(_hangupShown ? 1. : 0.); @@ -1243,7 +1246,8 @@ void Panel::updateHangupGeometry() { // Screencast - Camera - Cancel/Decline - Answer/Hangup/Redial - Mute. const auto buttonWidth = st::callCancel.button.width; const auto cancelWidth = buttonWidth * (1. - hangupProgress); - const auto cancelLeft = (widget()->width() - buttonWidth) / 2; + const auto cancelLeft = (widget()->width() - buttonWidth) / 2 + - ((isBusy || incomingWaiting) ? buttonWidth : 0); _cancel->moveToLeft(cancelLeft, _buttonsTop); _decline->moveToLeft(cancelLeft, _buttonsTop); From e33a866a63a747676a79eca301ff3769456ea3f0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Mar 2025 16:14:48 +0500 Subject: [PATCH 089/190] Give some admin rights to confcall creator. --- Telegram/SourceFiles/calls/calls_top_bar.cpp | 2 +- .../SourceFiles/calls/group/calls_group_call.cpp | 5 +++++ .../calls/group/calls_group_members.cpp | 14 +++++++------- .../SourceFiles/calls/group/calls_group_menu.cpp | 9 ++++++--- .../calls/group/calls_group_settings.cpp | 2 +- Telegram/SourceFiles/data/data_group_call.cpp | 5 +++++ Telegram/SourceFiles/data/data_group_call.h | 2 ++ 7 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index dea58fd2f1..06863bc34f 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -424,7 +424,7 @@ void TopBar::initControls() { if (const auto call = _call.get()) { call->hangup(); } else if (const auto group = _groupCall.get()) { - if (!group->peer()->canManageGroupCall()) { + if (!group->canManage()) { group->hangup(); } else { _show->showBox( diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index bd1bf41863..f7e4355c4e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1239,8 +1239,13 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { } }, _lifetime); + if (_conferenceCall) { + _canManage = _conferenceCall->canManage(); + return; + } _peer->session().updates().addActiveChat( _peerStream.events_starting_with_copy(_peer)); + _canManage = Data::CanManageGroupCallValue(_peer); SubscribeToMigration(_peer, _lifetime, [=](not_null group) { _peer = group; _canManage = Data::CanManageGroupCallValue(_peer); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 03f9610706..cf5c3b42bf 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -615,7 +615,7 @@ bool Members::Controller::needToReorder(not_null row) const { if (row->speaking()) { return !allRowsAboveAreSpeaking(row); - } else if (!_peer->canManageGroupCall()) { + } else if (!_call->canManage()) { // Raising hands reorder participants only for voice chat admins. return false; } @@ -684,7 +684,7 @@ void Members::Controller::checkRowPosition(not_null row) { return proj(a) > proj(b); }; }; - delegate()->peerListSortRows(_peer->canManageGroupCall() + delegate()->peerListSortRows(_call->canManage() ? makeComparator(projForAdmin) : makeComparator(projForOther)); } @@ -919,7 +919,7 @@ bool Members::Controller::rowIsMe(not_null participantPeer) { } bool Members::Controller::rowCanMuteMembers() { - return _peer->canManageGroupCall(); + return _call->canManage(); } void Members::Controller::rowUpdateRow(not_null row) { @@ -1317,7 +1317,7 @@ base::unique_qptr Members::Controller::createRowContextMenu( false, static_cast(row.get())); } else if (participant - && (!isMe(participantPeer) || _peer->canManageGroupCall()) + && (!isMe(participantPeer) || _call->canManage()) && (participant->ssrc != 0 || GetAdditionalAudioSsrc(participant->videoParams) != 0)) { addMuteActionsToContextMenu( @@ -1387,11 +1387,11 @@ void Members::Controller::addMuteActionsToContextMenu( bool participantIsCallAdmin, not_null row) { const auto muteUnmuteString = [=](bool muted, bool mutedByMe) { - return (muted && _peer->canManageGroupCall()) + return (muted && _call->canManage()) ? tr::lng_group_call_context_unmute(tr::now) : mutedByMe ? tr::lng_group_call_context_unmute_for_me(tr::now) - : _peer->canManageGroupCall() + : _call->canManage() ? tr::lng_group_call_context_mute(tr::now) : tr::lng_group_call_context_mute_for_me(tr::now); }; @@ -1488,7 +1488,7 @@ void Members::Controller::addMuteActionsToContextMenu( || isMe(participantPeer) || (muteState == Row::State::Inactive && participantIsCallAdmin - && _peer->canManageGroupCall())) { + && _call->canManage())) { return nullptr; } auto callback = [=] { diff --git a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp index 4f1b196d65..b41dac66c6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp @@ -441,7 +441,7 @@ void LeaveBox( : tr::lng_group_call_leave_sure())), (inCall ? st::groupCallBoxLabel : st::boxLabel)), scheduled ? st::boxPadding : st::boxRowPadding); - const auto discard = call->peer()->canManageGroupCall() + const auto discard = call->canManage() ? box->addRow(object_ptr( box.get(), (scheduled @@ -507,9 +507,12 @@ void FillMenu( return; } + const auto conference = call->conference(); const auto addEditJoinAs = call->showChooseJoinAs(); - const auto addEditTitle = call->canManage(); - const auto addEditRecording = call->canManage() && !real->scheduleDate(); + const auto addEditTitle = !conference && call->canManage(); + const auto addEditRecording = !conference + && call->canManage() + && !real->scheduleDate(); const auto addScreenCast = !wide && call->videoIsWorking() && !real->scheduleDate(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 7c2bdfd4ce..0adbb8e3e9 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -766,7 +766,7 @@ void SettingsBox( }, volumeItem->lifetime()); } - if (peer->canManageGroupCall()) { + if (call->canManage()) { layout->add(object_ptr( layout, (peer->isBroadcast() diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index b90e0f24c3..dd6e449cbb 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -128,6 +128,10 @@ bool GroupCall::rtmp() const { return _rtmp; } +bool GroupCall::canManage() const { + return _conference ? _creator : _peer->canManageGroupCall(); +} + bool GroupCall::listenersHidden() const { return _listenersHidden; } @@ -539,6 +543,7 @@ void GroupCall::applyCallFields(const MTPDgroupCall &data) { _version = 1; } _rtmp = data.is_rtmp_stream(); + _creator = data.is_creator(); _listenersHidden = data.is_listeners_hidden(); _joinMuted = data.is_join_muted(); _canChangeJoinMuted = data.is_can_change_join_muted(); diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index f413f3f70b..54c6990015 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -78,6 +78,7 @@ public: [[nodiscard]] CallId id() const; [[nodiscard]] bool loaded() const; [[nodiscard]] bool rtmp() const; + [[nodiscard]] bool canManage() const; [[nodiscard]] bool listenersHidden() const; [[nodiscard]] not_null peer() const; [[nodiscard]] MTPInputGroupCall input() const; @@ -271,6 +272,7 @@ private: mtpRequestId _checkStaleRequestId = 0; rpl::lifetime _checkStaleLifetime; + bool _creator : 1 = false; bool _joinMuted : 1 = false; bool _canChangeJoinMuted : 1 = true; bool _allParticipantsLoaded : 1 = false; From be611c19204af79f2ca5ba2b033afc094e07a564 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 30 Mar 2025 23:22:33 +0500 Subject: [PATCH 090/190] Show confcall users that are only on blockchain. --- Telegram/Resources/langs/lang.strings | 1 + .../calls/group/calls_group_members.cpp | 170 +++++++++++++++--- .../calls/group/calls_group_members_row.cpp | 81 ++++++--- .../calls/group/calls_group_members_row.h | 7 +- 4 files changed, 209 insertions(+), 50 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 86ea166e86..85bbaec270 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4759,6 +4759,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_invite" = "Invite Members"; "lng_group_call_invite_conf" = "Add People"; "lng_group_call_invited_status" = "invited"; +"lng_group_call_blockchain_only_status" = "syncing..."; "lng_group_call_muted_by_me_status" = "muted for you"; "lng_group_call_invite_title" = "Invite members"; "lng_group_call_invite_button" = "Invite"; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index cf5c3b42bf..4d14d0f812 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -108,6 +108,8 @@ private: const Data::GroupCallParticipant &participant); [[nodiscard]] std::unique_ptr createInvitedRow( not_null participantPeer); + [[nodiscard]] std::unique_ptr createWithAccessRow( + not_null participantPeer); [[nodiscard]] bool isMe(not_null participantPeer) const; void prepareRows(not_null real); @@ -128,7 +130,8 @@ private: void updateRow( not_null row, const std::optional &was, - const Data::GroupCallParticipant *participant); + const Data::GroupCallParticipant *participant, + Row::State noParticipantState = Row::State::Invited); void updateRowInSoundingMap( not_null row, bool wasSounding, @@ -162,8 +165,13 @@ private: const VideoEndpoint &endpoint, bool active); - void appendInvitedUsers(); + void partitionRows(); + void setupInvitedUsers(); + [[nodiscard]] bool appendInvitedUsers(); + void setupWithAccessUsers(); + [[nodiscard]] bool appendWithAccessUsers(); void scheduleRaisedHandStatusRemove(); + void refreshWithAccessRows(base::flat_set &&nowIds); void hideRowsWithVideoExcept(const VideoEndpoint &large); void showAllHiddenRows(); @@ -205,6 +213,8 @@ private: Ui::RoundRect _narrowRoundRect; QImage _narrowShadow; + base::flat_set _withAccess; + rpl::lifetime _lifetime; }; @@ -414,6 +424,9 @@ void Members::Controller::subscribeToChanges(not_null real) { if (const auto row = findRow(participantPeer)) { if (isMe(participantPeer)) { updateRow(row, update.was, nullptr); + } else if (_withAccess.contains(peerToUser(participantPeer->id))) { + updateRow(row, update.was, nullptr, Row::State::WithAccess); + partitionRows(); } else { removeRow(row); delegate()->peerListRefreshRows(); @@ -431,10 +444,6 @@ void Members::Controller::subscribeToChanges(not_null real) { ) | rpl::start_with_next([=](const VideoStateToggle &update) { toggleVideoEndpointActive(update.endpoint, update.value); }, _lifetime); - - if (_prepared) { - appendInvitedUsers(); - } } void Members::Controller::toggleVideoEndpointActive( @@ -481,13 +490,21 @@ void Members::Controller::toggleVideoEndpointActive( } -void Members::Controller::appendInvitedUsers() { +bool Members::Controller::appendInvitedUsers() { + auto changed = false; if (const auto id = _call->id()) { for (const auto &user : _peer->owner().invitedToCallUsers(id)) { if (auto row = createInvitedRow(user)) { delegate()->peerListAppendRow(std::move(row)); + changed = true; } } + } + return changed; +} + +void Members::Controller::setupInvitedUsers() { + if (appendInvitedUsers()) { delegate()->peerListRefreshRows(); } @@ -503,15 +520,79 @@ void Members::Controller::appendInvitedUsers() { }, _lifetime); } +bool Members::Controller::appendWithAccessUsers() { + auto changed = false; + for (const auto id : _withAccess) { + if (auto row = createWithAccessRow(_peer->owner().user(id))) { + changed = true; + delegate()->peerListAppendRow(std::move(row)); + } + } + return changed; +} + +void Members::Controller::setupWithAccessUsers() { + const auto conference = _call->conferenceCall().get(); + if (!conference) { + return; + } + conference->participantsWithAccessValue( + ) | rpl::start_with_next([=](base::flat_set &&nowIds) { + for (auto i = begin(_withAccess); i != end(_withAccess);) { + const auto oldId = *i; + if (nowIds.remove(oldId)) { + ++i; + continue; + } + const auto user = _peer->owner().user(oldId); + if (const auto row = findRow(user)) { + if (row->state() == Row::State::WithAccess) { + removeRow(row); + } + } + i = _withAccess.erase(i); + } + auto partition = false; + auto partitionChecked = false; + for (const auto nowId : nowIds) { + const auto user = _peer->owner().user(nowId); + if (!findRow(user)) { + if (auto row = createWithAccessRow(user)) { + if (!partitionChecked) { + partitionChecked = true; + if (const auto count = delegate()->peerListFullRowsCount()) { + const auto last = delegate()->peerListRowAt(count - 1); + const auto state = static_cast(last.get())->state(); + if (state == Row::State::Invited) { + partition = true; + } + } + } + delegate()->peerListAppendRow(std::move(row)); + } + } + _withAccess.emplace(nowId); + } + if (partition) { + delegate()->peerListPartitionRows([](const PeerListRow &row) { + const auto state = static_cast(row).state(); + return (state != Row::State::Invited); + }); + } + delegate()->peerListRefreshRows(); + }, _lifetime); +} + void Members::Controller::updateRow( const std::optional &was, const Data::GroupCallParticipant &now) { - auto reorderIfInvitedBefore = 0; + auto reorderIfNonRealBefore = 0; auto checkPosition = (Row*)nullptr; auto addedToBottom = (Row*)nullptr; if (const auto row = findRow(now.peer)) { - if (row->state() == Row::State::Invited) { - reorderIfInvitedBefore = row->absoluteIndex(); + if (row->state() == Row::State::Invited + || row->state() == Row::State::WithAccess) { + reorderIfNonRealBefore = row->absoluteIndex(); } updateRow(row, was, &now); if ((now.speaking && (!was || !was->speaking)) @@ -523,7 +604,7 @@ void Members::Controller::updateRow( if (row->speaking()) { delegate()->peerListPrependRow(std::move(row)); } else { - reorderIfInvitedBefore = delegate()->peerListFullRowsCount(); + reorderIfNonRealBefore = delegate()->peerListFullRowsCount(); if (now.raisedHandRating != 0) { checkPosition = row.get(); } else { @@ -533,20 +614,19 @@ void Members::Controller::updateRow( } delegate()->peerListRefreshRows(); } - static constexpr auto kInvited = Row::State::Invited; const auto reorder = [&] { - const auto count = reorderIfInvitedBefore; + const auto count = reorderIfNonRealBefore; if (count <= 0) { return false; } const auto row = delegate()->peerListRowAt( - reorderIfInvitedBefore - 1).get(); - return (static_cast(row)->state() == kInvited); + reorderIfNonRealBefore - 1).get(); + using State = Row::State; + const auto state = static_cast(row)->state(); + return (state == State::Invited) || (state == State::WithAccess); }(); if (reorder) { - delegate()->peerListPartitionRows([](const PeerListRow &row) { - return static_cast(row).state() != kInvited; - }); + partitionRows(); } if (checkPosition) { checkRowPosition(checkPosition); @@ -570,6 +650,24 @@ void Members::Controller::updateRow( } } +void Members::Controller::partitionRows() { + auto hadWithAccess = false; + delegate()->peerListPartitionRows([&](const PeerListRow &row) { + using State = Row::State; + const auto state = static_cast(row).state(); + if (state == State::WithAccess) { + hadWithAccess = true; + } + return (state != State::Invited) && (state != State::WithAccess); + }); + if (hadWithAccess) { + delegate()->peerListPartitionRows([](const PeerListRow &row) { + const auto state = static_cast(row).state(); + return (state != Row::State::Invited); + }); + } +} + bool Members::Controller::allRowsAboveAreSpeaking(not_null row) const { const auto count = delegate()->peerListFullRowsCount(); for (auto i = 0; i != count; ++i) { @@ -692,14 +790,21 @@ void Members::Controller::checkRowPosition(not_null row) { void Members::Controller::updateRow( not_null row, const std::optional &was, - const Data::GroupCallParticipant *participant) { + const Data::GroupCallParticipant *participant, + Row::State noParticipantState) { const auto wasSounding = row->sounding(); const auto wasSsrc = was ? was->ssrc : 0; const auto wasAdditionalSsrc = was ? GetAdditionalAudioSsrc(was->videoParams) : 0; row->setSkipLevelUpdate(_skipRowLevelUpdate); - row->updateState(participant); + if (participant) { + row->updateState(*participant); + } else if (noParticipantState == Row::State::WithAccess) { + row->updateStateWithAccess(); + } else { + row->updateStateInvited(); + } const auto wasNoSounding = _soundingRowBySsrc.empty(); updateRowInSoundingMap( @@ -842,7 +947,8 @@ void Members::Controller::prepare() { } loadMoreRows(); - appendInvitedUsers(); + setupWithAccessUsers(); + setupInvitedUsers(); _prepared = true; setupListChangeViewers(); @@ -893,6 +999,12 @@ void Members::Controller::prepareRows(not_null real) { delegate()->peerListAppendRow(std::move(row)); } } + if (appendWithAccessUsers()) { + changed = true; + } + if (appendInvitedUsers()) { + changed = true; + } if (changed) { delegate()->peerListRefreshRows(); } @@ -1354,8 +1466,9 @@ base::unique_qptr Members::Controller::createRowContextMenu( } const auto canKick = [&] { const auto user = participantPeer->asUser(); - if (static_cast(row.get())->state() - == Row::State::Invited) { + const auto state = static_cast(row.get())->state(); + if (state == Row::State::Invited + || state == Row::State::WithAccess) { return false; } else if (const auto chat = _peer->asChat()) { return chat->amCreator() @@ -1484,6 +1597,7 @@ void Members::Controller::addMuteActionsToContextMenu( const auto muteAction = [&]() -> QAction* { if (muteState == Row::State::Invited + || muteState == Row::State::WithAccess || _call->rtmp() || isMe(participantPeer) || (muteState == Row::State::Inactive @@ -1547,6 +1661,16 @@ std::unique_ptr Members::Controller::createInvitedRow( return result; } +std::unique_ptr Members::Controller::createWithAccessRow( + not_null participantPeer) { + if (findRow(participantPeer)) { + return nullptr; + } + auto result = std::make_unique(this, participantPeer); + updateRow(result.get(), std::nullopt, nullptr, Row::State::WithAccess); + return result; +} + Members::Members( not_null parent, not_null call, diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp index 02ae9cd685..fd91ebbcb0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp @@ -138,41 +138,52 @@ void MembersRow::setSkipLevelUpdate(bool value) { _skipLevelUpdate = value; } -void MembersRow::updateState( - const Data::GroupCallParticipant *participant) { - setVolume(participant - ? participant->volume - : Group::kDefaultVolume); - if (!participant) { - setState(State::Invited); - setSounding(false); - setSpeaking(false); - _mutedByMe = false; - _raisedHandRating = 0; - } else if (!participant->muted - || (participant->sounding && participant->ssrc != 0) - || (participant->additionalSounding - && GetAdditionalAudioSsrc(participant->videoParams) != 0)) { +void MembersRow::updateStateInvited() { + setVolume(Group::kDefaultVolume); + setState(State::Invited); + setSounding(false); + setSpeaking(false); + _mutedByMe = false; + _raisedHandRating = 0; + refreshStatus(); +} + +void MembersRow::updateStateWithAccess() { + setVolume(Group::kDefaultVolume); + setState(State::WithAccess); + setSounding(false); + setSpeaking(false); + _mutedByMe = false; + _raisedHandRating = 0; + refreshStatus(); +} + +void MembersRow::updateState(const Data::GroupCallParticipant &participant) { + setVolume(participant.volume); + if (!participant.muted + || (participant.sounding && participant.ssrc != 0) + || (participant.additionalSounding + && GetAdditionalAudioSsrc(participant.videoParams) != 0)) { setState(State::Active); - setSounding((participant->sounding && participant->ssrc != 0) - || (participant->additionalSounding - && GetAdditionalAudioSsrc(participant->videoParams) != 0)); - setSpeaking((participant->speaking && participant->ssrc != 0) - || (participant->additionalSpeaking - && GetAdditionalAudioSsrc(participant->videoParams) != 0)); - _mutedByMe = participant->mutedByMe; + setSounding((participant.sounding && participant.ssrc != 0) + || (participant.additionalSounding + && GetAdditionalAudioSsrc(participant.videoParams) != 0)); + setSpeaking((participant.speaking && participant.ssrc != 0) + || (participant.additionalSpeaking + && GetAdditionalAudioSsrc(participant.videoParams) != 0)); + _mutedByMe = participant.mutedByMe; _raisedHandRating = 0; - } else if (participant->canSelfUnmute) { + } else if (participant.canSelfUnmute) { setState(State::Inactive); setSounding(false); setSpeaking(false); - _mutedByMe = participant->mutedByMe; + _mutedByMe = participant.mutedByMe; _raisedHandRating = 0; } else { setSounding(false); setSpeaking(false); - _mutedByMe = participant->mutedByMe; - _raisedHandRating = participant->raisedHandRating; + _mutedByMe = participant.mutedByMe; + _raisedHandRating = participant.raisedHandRating; setState(_raisedHandRating ? State::RaisedHand : State::Muted); } refreshStatus(); @@ -450,6 +461,20 @@ void MembersRow::paintMuteIcon( _delegate->rowPaintIcon(p, iconRect, computeIconState(style)); } +QString MembersRow::generateName() { + const auto result = peer()->name(); + return result.isEmpty() + ? u"User #%1"_q.arg(peerToUser(peer()->id).bare) + : result; +} + +QString MembersRow::generateShortName() { + const auto result = peer()->shortName(); + return result.isEmpty() + ? u"User #%1"_q.arg(peerToUser(peer()->id).bare) + : result; +} + auto MembersRow::generatePaintUserpicCallback(bool forceRound) -> PaintRoundImageCallback { return [=](Painter &p, int x, int y, int outerWidth, int size) { @@ -613,11 +638,13 @@ void MembersRow::paintComplexStatusText( availableWidth -= skip; const auto &font = st::normalFont; const auto useAbout = !_about.isEmpty() + && (_state != State::WithAccess) && (style != MembersRowStyle::Video) && ((_state == State::RaisedHand && !_raisedHandStatus) || (_state != State::RaisedHand && !_speaking)); if (!useAbout && _state != State::Invited + && _state != State::WithAccess && !_mutedByMe) { paintStatusIcon(p, x, y, st, font, selected, narrowMode); @@ -663,6 +690,8 @@ void MembersRow::paintComplexStatusText( ? tr::lng_group_call_muted_by_me_status(tr::now) : _delegate->rowIsMe(peer()) ? tr::lng_status_connecting(tr::now) + : (_state == State::WithAccess) + ? tr::lng_group_call_blockchain_only_status(tr::now) : tr::lng_group_call_invited_status(tr::now))); } } diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.h b/Telegram/SourceFiles/calls/group/calls_group_members_row.h index 5ba910c8e2..ef59b2686c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.h @@ -75,11 +75,14 @@ public: Muted, RaisedHand, Invited, + WithAccess, }; void setAbout(const QString &about); void setSkipLevelUpdate(bool value); - void updateState(const Data::GroupCallParticipant *participant); + void updateState(const Data::GroupCallParticipant &participant); + void updateStateInvited(); + void updateStateWithAccess(); void updateLevel(float level); void updateBlobAnimation(crl::time now); void clearRaisedHandStatus(); @@ -122,6 +125,8 @@ public: bool selected, bool actionSelected) override; + QString generateName() override; + QString generateShortName() override; PaintRoundImageCallback generatePaintUserpicCallback( bool forceRound) override; void paintComplexUserpic( From 346f7aadd6edda3bf921278b88d1caeb5293fafe Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Mar 2025 00:06:02 +0500 Subject: [PATCH 091/190] Render confcall messages as phone calls. --- Telegram/Resources/langs/lang.strings | 6 +-- .../calls/calls_box_controller.cpp | 6 +-- Telegram/SourceFiles/calls/calls_instance.cpp | 1 - .../SourceFiles/data/data_media_types.cpp | 44 +++++++++++++------ Telegram/SourceFiles/data/data_media_types.h | 15 ++++--- .../export/data/export_data_types.cpp | 33 +++++++++----- .../export/data/export_data_types.h | 17 +++---- .../export/output/export_output_abstract.cpp | 2 +- .../export/output/export_output_html.cpp | 30 +++++-------- .../export/output/export_output_json.cpp | 37 ++++++++-------- Telegram/SourceFiles/history/history_item.cpp | 34 ++++---------- .../history/view/media/history_view_call.cpp | 32 +++++++++----- .../history/view/media/history_view_call.h | 7 +-- 13 files changed, 137 insertions(+), 127 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 85bbaec270..a5d537fe32 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2220,10 +2220,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_message_price_paid#other" = "Messages now cost {count} Stars each in this group."; "lng_you_paid_stars#one" = "You paid {count} Star."; "lng_you_paid_stars#other" = "You paid {count} Stars."; -"lng_action_confcall_invitation" = "Call invitation..."; -"lng_action_confcall_missed" = "Missed conference call"; -"lng_action_confcall_ongoing" = "Ongoing conference call"; -"lng_action_confcall_finished" = "Conference call"; "lng_you_joined_group" = "You joined this group"; @@ -4684,6 +4680,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_video_declined" = "Declined video call"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; +"lng_call_invitation" = "Call invitation"; +"lng_call_ongoing" = "Ongoing call"; "lng_call_rate_label" = "Please rate the quality of your call"; "lng_call_rate_comment" = "Comment (optional)"; diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 8e093719fc..1f3337569d 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -430,9 +430,9 @@ BoxController::Row::Type BoxController::Row::ComputeType( return Type::Out; } else if (auto media = item->media()) { if (const auto call = media->call()) { - const auto reason = call->finishReason; - if (reason == Data::Call::FinishReason::Busy - || reason == Data::Call::FinishReason::Missed) { + using State = Data::CallState; + const auto state = call->state; + if (state == State::Busy || state == State::Missed) { return Type::Missed; } } diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index effbcfb8bb..833b40e93f 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -236,7 +236,6 @@ void Instance::startOrJoinConferenceCall(StartConferenceCallArgs args) { args.migrating ? args.linkSlug : QString()); const auto session = &args.call->peer()->session(); - const auto showShareLink = args.migrating && args.invite.empty(); auto call = std::make_unique( _delegate.get(), Calls::Group::ConferenceInfo{ diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 067e8af492..ab2c7e4712 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -457,28 +457,42 @@ Invoice ComputeInvoiceData( Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { auto result = Call(); - result.finishReason = [&] { + result.state = [&] { if (const auto reason = call.vreason()) { return reason->match([](const MTPDphoneCallDiscardReasonBusy &) { - return CallFinishReason::Busy; + return CallState::Busy; }, [](const MTPDphoneCallDiscardReasonDisconnect &) { - return CallFinishReason::Disconnected; + return CallState::Disconnected; }, [](const MTPDphoneCallDiscardReasonHangup &) { - return CallFinishReason::Hangup; + return CallState::Hangup; }, [](const MTPDphoneCallDiscardReasonMissed &) { - return CallFinishReason::Missed; + return CallState::Missed; }, [](const MTPDphoneCallDiscardReasonMigrateConferenceCall &) { - return CallFinishReason::MigrateConferenceCall; + return CallState::MigrateConferenceCall; }); Unexpected("Call reason type."); } - return CallFinishReason::Hangup; + return CallState::Hangup; }(); result.duration = call.vduration().value_or_empty(); result.video = call.is_video(); return result; } +Call ComputeCallData(const MTPDmessageActionConferenceCall &call) { + return { + .conferenceId = call.vcall_id().v, + .duration = call.vduration().value_or_empty(), + .state = (call.vduration().value_or_empty() + ? CallState::Hangup + : call.is_missed() + ? CallState::Missed + : call.is_active() + ? CallState::Active + : CallState::Invitation), + }; +} + GiveawayStart ComputeGiveawayStartData( not_null item, const MTPDmessageMediaGiveaway &data) { @@ -1686,7 +1700,7 @@ const Call *MediaCall::call() const { } TextWithEntities MediaCall::notificationText() const { - auto result = Text(parent(), _call.finishReason, _call.video); + auto result = Text(parent(), _call.state, _call.video); if (_call.duration > 0) { result = tr::lng_call_type_and_duration( tr::now, @@ -1727,21 +1741,25 @@ std::unique_ptr MediaCall::createView( QString MediaCall::Text( not_null item, - CallFinishReason reason, + CallState state, bool video) { - if (item->out()) { - return ((reason == CallFinishReason::Missed) + if (state == CallState::Invitation) { + return tr::lng_call_invitation(tr::now); + } else if (state == CallState::Active) { + return tr::lng_call_ongoing(tr::now); + } else if (item->out()) { + return ((state == CallState::Missed) ? (video ? tr::lng_call_video_cancelled : tr::lng_call_cancelled) : (video ? tr::lng_call_video_outgoing : tr::lng_call_outgoing))(tr::now); - } else if (reason == CallFinishReason::Missed) { + } else if (state == CallState::Missed) { return (video ? tr::lng_call_video_missed : tr::lng_call_missed)(tr::now); - } else if (reason == CallFinishReason::Busy) { + } else if (state == CallState::Busy) { return (video ? tr::lng_call_video_declined : tr::lng_call_declined)(tr::now); diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 14452c7972..f47197816e 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -41,12 +41,14 @@ class WallPaper; class Session; struct UniqueGift; -enum class CallFinishReason : char { +enum class CallState : char { Missed, Busy, Disconnected, Hangup, MigrateConferenceCall, + Invitation, + Active, }; struct SharedContact final { @@ -78,10 +80,11 @@ struct SharedContact final { }; struct Call { - using FinishReason = CallFinishReason; + using State = CallState; + CallId conferenceId = 0; int duration = 0; - FinishReason finishReason = FinishReason::Missed; + State state = State::Missed; bool video = false; }; @@ -462,9 +465,9 @@ public: not_null realParent, HistoryView::Element *replacing = nullptr) override; - static QString Text( + [[nodiscard]] static QString Text( not_null item, - CallFinishReason reason, + CallState state, bool video); private: @@ -799,6 +802,8 @@ private: const MTPDmessageMediaPaidMedia &data); [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); +[[nodiscard]] Call ComputeCallData( + const MTPDmessageActionConferenceCall &call); [[nodiscard]] GiveawayStart ComputeGiveawayStartData( not_null item, diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 919b35f8ef..e3bf2f2100 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1461,18 +1461,18 @@ ServiceAction ParseServiceAction( content.duration = duration->v; } if (const auto reason = data.vreason()) { - using Reason = ActionPhoneCall::DiscardReason; - content.discardReason = reason->match( + using State = ActionPhoneCall::State; + content.state = reason->match( [](const MTPDphoneCallDiscardReasonMissed &data) { - return Reason::Missed; + return State::Missed; }, [](const MTPDphoneCallDiscardReasonDisconnect &data) { - return Reason::Disconnect; + return State::Disconnect; }, [](const MTPDphoneCallDiscardReasonHangup &data) { - return Reason::Hangup; + return State::Hangup; }, [](const MTPDphoneCallDiscardReasonBusy &data) { - return Reason::Busy; + return State::Busy; }, [](const MTPDphoneCallDiscardReasonMigrateConferenceCall &) { - return Reason::MigrateConferenceCall; + return State::MigrateConferenceCall; }); } result.content = content; @@ -1714,11 +1714,20 @@ ServiceAction ParseServiceAction( .stars = int(data.vstars().v), }; }, [&](const MTPDmessageActionConferenceCall &data) { - result.content = ActionConferenceCall{ - .duration = data.vduration().value_or_empty(), - .active = data.is_active(), - .missed = data.is_missed(), - }; + auto content = ActionPhoneCall(); + using State = ActionPhoneCall::State; + content.conferenceId = data.vcall_id().v; + if (const auto duration = data.vduration()) { + content.duration = duration->v; + } + content.state = data.is_missed() + ? State::Missed + : data.is_active() + ? State::Active + : data.vduration().value_or_empty() + ? State::Hangup + : State::Invitation; + result.content = content; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 3b37de23d9..ca9a95916d 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -497,15 +497,19 @@ struct ActionPaymentSent { }; struct ActionPhoneCall { - enum class DiscardReason { + enum class State { Unknown, Missed, Disconnect, Hangup, Busy, MigrateConferenceCall, + Invitation, + Active, }; - DiscardReason discardReason = DiscardReason::Unknown; + + uint64 conferenceId = 0; + State state = State::Unknown; int duration = 0; }; @@ -671,12 +675,6 @@ struct ActionPaidMessagesPrice { int stars = 0; }; -struct ActionConferenceCall { - int duration = 0; - bool active = false; - bool missed = false; -}; - struct ServiceAction { std::variant< v::null_t, @@ -724,8 +722,7 @@ struct ServiceAction { ActionPrizeStars, ActionStarGift, ActionPaidMessagesRefunded, - ActionPaidMessagesPrice, - ActionConferenceCall> content; + ActionPaidMessagesPrice> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.cpp b/Telegram/SourceFiles/export/output/export_output_abstract.cpp index dbdb1bf999..cadd6dbb3c 100644 --- a/Telegram/SourceFiles/export/output/export_output_abstract.cpp +++ b/Telegram/SourceFiles/export/output/export_output_abstract.cpp @@ -408,7 +408,7 @@ Stats AbstractWriter::produceTestExample( auto message = serviceMessage(); auto action = Data::ActionPhoneCall(); action.duration = counter(); - action.discardReason = Data::ActionPhoneCall::DiscardReason::Busy; + action.state = Data::ActionPhoneCall::State::Busy; message.action.content = action; return message; }()); diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 29f7d734a7..3d960a38e2 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1387,16 +1387,6 @@ auto HtmlWriter::Wrap::pushMessage( + QString::number(data.stars).toUtf8() + " Telegram Stars."; return result; - }, [&](const ActionConferenceCall &data) { - return data.missed - ? "Missed conference call" - : data.active - ? "Ongoing conference call" - : data.duration - ? "Conference call (" - + NumberToString(data.duration) - + " seconds)" - : "Declined conference call"; }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { @@ -2286,16 +2276,20 @@ MediaData HtmlWriter::Wrap::prepareMediaData( if (const auto call = std::get_if(&action.content)) { result.classes = "media_call"; result.title = peers.peer(message.out - ? message.peerId - : message.selfId).name(); + ? message.peerId + : message.selfId).name(); result.status = [&] { - using Reason = ActionPhoneCall::DiscardReason; - const auto reason = call->discardReason; - if (message.out) { - return reason == Reason::Missed ? "Cancelled" : "Outgoing"; - } else if (reason == Reason::Missed) { + using State = ActionPhoneCall::State; + const auto state = call->state; + if (state == State::Invitation) { + return "Invitation"; + } else if (state == State::Active) { + return "Ongoing"; + } else if (message.out) { + return (state == State::Missed) ? "Cancelled" : "Outgoing"; + } else if (state == State::Missed) { return "Missed"; - } else if (reason == Reason::Busy) { + } else if (state == State::Busy) { return "Declined"; } return "Incoming"; diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 5e263e60d3..772bc6f3f2 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -461,22 +461,27 @@ QByteArray SerializeMessage( } }, [&](const ActionPhoneCall &data) { pushActor(); - pushAction("phone_call"); + pushAction(data.conferenceId ? "conference_call" : "phone_call"); if (data.duration) { push("duration_seconds", data.duration); } - using Reason = ActionPhoneCall::DiscardReason; - push("discard_reason", [&] { - switch (data.discardReason) { - case Reason::Busy: return "busy"; - case Reason::Disconnect: return "disconnect"; - case Reason::Hangup: return "hangup"; - case Reason::Missed: return "missed"; - case Reason::MigrateConferenceCall: - return "migrate_conference_all"; - } - return ""; - }()); + using State = ActionPhoneCall::State; + if (data.conferenceId) { + push("is_active", data.state == State::Active); + push("is_missed", data.state == State::Missed); + } else { + push("discard_reason", [&] { + switch (data.state) { + case State::Busy: return "busy"; + case State::Disconnect: return "disconnect"; + case State::Hangup: return "hangup"; + case State::Missed: return "missed"; + case State::MigrateConferenceCall: + return "migrate_conference_all"; + } + return ""; + }()); + } }, [&](const ActionScreenshotTaken &data) { pushActor(); pushAction("take_screenshot"); @@ -674,12 +679,6 @@ QByteArray SerializeMessage( pushActor(); pushAction("paid_messages_price_change"); push("price_stars", data.stars); - }, [&](const ActionConferenceCall &data) { - pushActor(); - pushAction("conference_call"); - push("duration_seconds", data.duration); - push("is_missed", data.missed); - push("is_active", data.active); }, [](v::null_t) {}); if (v::is_null(message.action.content)) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 00bae0b60a..43a732f3e9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -483,6 +483,12 @@ HistoryItem::HistoryItem( this, Data::ComputeCallData(data)); setTextValue({}); + }, [&](const MTPDmessageActionConferenceCall &data) { + createComponents(CreateConfig()); + _media = std::make_unique( + this, + Data::ComputeCallData(data)); + setTextValue({}); }, [&](const auto &) { createServiceFromMtp(data); }); @@ -5624,32 +5630,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; - auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &action) { - auto result = PreparedServiceText(); - const auto duration = action.vduration().value_or_empty(); - result.text.text = action.is_missed() - ? tr::lng_action_confcall_missed(tr::now) - : action.is_active() - ? tr::lng_action_confcall_ongoing(tr::now) - : duration - ? tr::lng_action_confcall_finished(tr::now) - : tr::lng_action_confcall_invitation(tr::now); - - if (duration) { - result.text.text += " (" + QString::number(duration) + " seconds)"; - } - - const auto id = this->id; - setCustomServiceLink(std::make_shared([=]( - ClickContext context) { - const auto my = context.other.value(); - const auto weak = my.sessionWindow; - if (const auto strong = weak.get()) { - strong->resolveConferenceCall(id); - } - })); - - return result; + auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &) -> PreparedServiceText { + Unexpected("PhoneCall type in setServiceMessageFromMtp."); }; setServiceText(action.match( diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.cpp b/Telegram/SourceFiles/history/view/media/history_view_call.cpp index 954ce579ea..730975957f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_call.cpp @@ -17,20 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "core/application.h" +#include "core/click_handler_types.h" #include "calls/calls_instance.h" #include "data/data_media_types.h" #include "data/data_user.h" #include "main/main_session.h" +#include "window/window_session_controller.h" #include "styles/style_chat.h" namespace HistoryView { namespace { -using FinishReason = Data::CallFinishReason; +using State = Data::CallState; -[[nodiscard]] int ComputeDuration(FinishReason reason, int duration) { - return (reason != FinishReason::Missed - && reason != FinishReason::Busy) +[[nodiscard]] int ComputeDuration(State state, int duration) { + return (state != State::Missed && state != State::Busy) ? duration : 0; } @@ -41,11 +42,12 @@ Call::Call( not_null parent, not_null call) : Media(parent) -, _duration(ComputeDuration(call->finishReason, call->duration)) -, _reason(call->finishReason) +, _duration(ComputeDuration(call->state, call->duration)) +, _state(call->state) +, _conference(call->conferenceId != 0) , _video(call->video) { const auto item = parent->data(); - _text = Data::MediaCall::Text(item, _reason, _video); + _text = Data::MediaCall::Text(item, _state, _video); _status = QLocale().toString( parent->dateTime().time(), QLocale::ShortFormat); @@ -61,13 +63,20 @@ Call::Call( QSize Call::countOptimalSize() { const auto user = _parent->history()->peer->asUser(); + const auto conference = _conference; const auto video = _video; - _link = std::make_shared([=] { - if (user) { + const auto id = _parent->data()->id; + _link = std::make_shared([=](ClickContext context) { + if (conference) { + const auto my = context.other.value(); + const auto weak = my.sessionWindow; + if (const auto strong = weak.get()) { + strong->resolveConferenceCall(id); + } + } else if (user) { Core::App().calls().startOutgoingCall(user, video); } }); - auto maxWidth = st::historyCallWidth; auto minHeight = st::historyCallHeight; if (!isBubbleTop()) { @@ -96,8 +105,7 @@ void Call::draw(Painter &p, const PaintContext &context) const { p.drawTextLeft(nameleft, nametop, paintw, _text); auto statusleft = nameleft; - auto missed = (_reason == FinishReason::Missed) - || (_reason == FinishReason::Busy); + auto missed = (_state == State::Missed) || (_state == State::Busy); const auto &arrow = missed ? stm->historyCallArrowMissed : stm->historyCallArrow; diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.h b/Telegram/SourceFiles/history/view/media/history_view_call.h index 8689b4ce96..891d7620b9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.h +++ b/Telegram/SourceFiles/history/view/media/history_view_call.h @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" namespace Data { -enum class CallFinishReason : char; +enum class CallState : char; struct Call; } // namespace Data @@ -40,12 +40,13 @@ public: } private: - using FinishReason = Data::CallFinishReason; + using State = Data::CallState; QSize countOptimalSize() override; const int _duration = 0; - const FinishReason _reason; + const State _state = {}; + const bool _conference = false; const bool _video = false; QString _text; From 41c0a5ee3ba785204363c197206c73d384a44096 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Mar 2025 00:13:39 +0500 Subject: [PATCH 092/190] Add "Join Call" button to confcall link previews. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/data/data_web_page.cpp | 2 ++ Telegram/SourceFiles/data/data_web_page.h | 1 + .../SourceFiles/history/view/media/history_view_web_page.cpp | 3 +++ 4 files changed, 7 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a5d537fe32..3b4a0ef79e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5856,6 +5856,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_view_button_stickerset" = "View stickers"; "lng_view_button_emojipack" = "View emoji"; "lng_view_button_collectible" = "View collectible"; +"lng_view_button_call" = "Join call"; "lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_title" = "What are sponsored messages?"; diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index bd14735eb8..74db4d6b5a 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -159,6 +159,8 @@ WebPageType ParseWebPageType( return WebPageType::VoiceChat; } else if (type == u"telegram_livestream"_q) { return WebPageType::Livestream; + } else if (type == u"telegram_call"_q) { + return WebPageType::ConferenceCall; } else if (type == u"telegram_user"_q) { return WebPageType::User; } else if (type == u"telegram_botapp"_q) { diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index f8a19f1c15..e6c004e64e 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -55,6 +55,7 @@ enum class WebPageType : uint8 { VoiceChat, Livestream, + ConferenceCall, Factcheck, }; 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 160962ee53..f97e6277b3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -220,6 +220,8 @@ constexpr auto kSponsoredUserpicLines = 2; ? tr::lng_view_button_voice_chat(tr::now) : (type == WebPageType::Livestream) ? tr::lng_view_button_voice_chat_channel(tr::now) + : (type == WebPageType::ConferenceCall) + ? tr::lng_view_button_call(tr::now) : (type == WebPageType::Bot) ? tr::lng_view_button_bot(tr::now) : (type == WebPageType::User) @@ -258,6 +260,7 @@ constexpr auto kSponsoredUserpicLines = 2; || (type == WebPageType::User) || (type == WebPageType::VoiceChat) || (type == WebPageType::Livestream) + || (type == WebPageType::ConferenceCall) || (type == WebPageType::BotApp) || ((type == WebPageType::Theme) && webpage->document From 8f313b4603094d4531a81986cc6e7da21dace537 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Mar 2025 00:25:57 +0500 Subject: [PATCH 093/190] Update API scheme on layer 202. --- Telegram/SourceFiles/calls/group/calls_group_call.cpp | 7 +++---- Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index f7e4355c4e..c2209c7102 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -736,13 +736,12 @@ void GroupCall::removeConferenceParticipants( Expects(_e2e != nullptr); Expects(!userIds.empty()); - const auto owner = &_peer->owner(); - auto inputs = QVector(); + auto inputs = QVector(); inputs.reserve(userIds.size()); auto ids = base::flat_set(); ids.reserve(userIds.size()); for (const auto &id : userIds) { - inputs.push_back(owner->user(id)->input); + inputs.push_back(MTP_long(peerToUser(id).bare)); ids.emplace(TdE2E::MakeUserId(id)); } const auto block = _e2e->makeRemoveBlock(ids); @@ -751,7 +750,7 @@ void GroupCall::removeConferenceParticipants( } _api.request(MTPphone_DeleteConferenceCallParticipants( inputCall(), - MTP_vector(std::move(inputs)), + MTP_vector(std::move(inputs)), MTP_bytes(block.data) )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 68200532f5..5761f160c0 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -2608,7 +2608,7 @@ phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallS phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; phone.createConferenceCall#fbcefee6 random_id:int = phone.GroupCall; -phone.deleteConferenceCallParticipants#22e547c7 call:InputGroupCall ids:Vector block:bytes = Updates; +phone.deleteConferenceCallParticipants#f9231114 call:InputGroupCall ids:Vector block:bytes = Updates; phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; phone.inviteConferenceCallParticipant#3e9cf7ee call:InputGroupCall user_id:InputUser = Updates; phone.declineConferenceCallInvite#3c479971 msg_id:int = Updates; From 7c709fddba500cf0b4b52ccb2b3db2197e3a93ca Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Mar 2025 00:48:03 +0500 Subject: [PATCH 094/190] Allow editing confcall messages. --- Telegram/SourceFiles/history/history_item.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 43a732f3e9..3330c835c6 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1900,6 +1900,16 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) { applyServiceDateEdition(message); finishEditionToEmpty(); _flags &= ~MessageFlag::DisplayFromChecked; + } else if (message.vaction().type() == mtpc_messageActionConferenceCall) { + removeFromSharedMediaIndex(); + _media = nullptr; + _media = std::make_unique( + this, + Data::ComputeCallData( + message.vaction().c_messageActionConferenceCall())); + addToSharedMediaIndex(); + finishEdition(-1); + _flags &= ~MessageFlag::DisplayFromChecked; } else if (isService()) { if (const auto reply = Get()) { reply->clearData(this); From a3ba99c682fb186b67805d8a6bce17eb6e51759d Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Mar 2025 11:44:39 +0400 Subject: [PATCH 095/190] Implement incoming confcall window. --- Telegram/SourceFiles/calls/calls_call.cpp | 109 +++++++++++++- Telegram/SourceFiles/calls/calls_call.h | 18 +++ Telegram/SourceFiles/calls/calls_instance.cpp | 133 +++++++++++++++++- Telegram/SourceFiles/calls/calls_instance.h | 28 ++++ .../calls/group/calls_group_members.cpp | 9 +- Telegram/SourceFiles/data/data_group_call.cpp | 11 +- .../SourceFiles/data/data_media_types.cpp | 26 +++- Telegram/SourceFiles/data/data_session.cpp | 1 + Telegram/SourceFiles/data/data_session.h | 1 + Telegram/SourceFiles/history/history.cpp | 5 +- .../window/window_session_controller.cpp | 11 +- 11 files changed, 327 insertions(+), 25 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 2ff549c93d..02f3516871 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -247,7 +247,49 @@ Call::Call( setupOutgoingVideo(); } +Call::Call( + not_null delegate, + not_null user, + CallId conferenceId, + MsgId conferenceInviteMsgId) +: _delegate(delegate) +, _user(user) +, _api(&_user->session().mtp()) +, _type(Type::Incoming) +, _state(State::WaitingIncoming) +, _discardByTimeoutTimer([=] { hangup(); }) +, _playbackDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Playback, + Webrtc::DeviceIdValueWithFallback( + Core::App().settings().callPlaybackDeviceIdValue(), + Core::App().settings().playbackDeviceIdValue())) +, _captureDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Capture, + Webrtc::DeviceIdValueWithFallback( + Core::App().settings().callCaptureDeviceIdValue(), + Core::App().settings().captureDeviceIdValue())) +, _cameraDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Camera, + Core::App().settings().cameraDeviceIdValue()) +, _id(base::RandomValue()) +, _conferenceId(conferenceId) +, _conferenceInviteMsgId(conferenceInviteMsgId) +, _videoIncoming( + std::make_unique( + StartVideoState(false))) +, _videoOutgoing( + std::make_unique( + StartVideoState(false))) { + startWaitingTrack(); + setupOutgoingVideo(); +} + void Call::generateModExpFirst(bytes::const_span randomSeed) { + Expects(!conferenceInvite()); + auto first = MTP::CreateModExp(_dhConfig.g, _dhConfig.p, randomSeed); if (first.modexp.empty()) { LOG(("Call Error: Could not compute mod-exp first.")); @@ -273,6 +315,8 @@ bool Call::isIncomingWaiting() const { } void Call::start(bytes::const_span random) { + Expects(!conferenceInvite()); + // Save config here, because it is possible that it changes between // different usages inside the same call. _dhConfig = _delegate->getDhConfig(); @@ -297,6 +341,7 @@ void Call::startOutgoing() { Expects(_type == Type::Outgoing); Expects(_state.current() == State::Requesting); Expects(_gaHash.size() == kSha256Size); + Expects(!conferenceInvite()); const auto flags = _videoCapture ? MTPphone_RequestCall::Flag::f_video @@ -350,6 +395,7 @@ void Call::startOutgoing() { void Call::startIncoming() { Expects(_type == Type::Incoming); Expects(_state.current() == State::Starting); + Expects(!conferenceInvite()); _api.request(MTPphone_ReceivedCall( MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)) @@ -363,6 +409,8 @@ void Call::startIncoming() { } void Call::applyUserConfirmation() { + Expects(!conferenceInvite()); + if (_state.current() == State::WaitingUserConfirmation) { setState(State::Requesting); } @@ -375,9 +423,44 @@ void Call::answer() { }), video); } +void Call::acceptConferenceInvite() { + Expects(conferenceInvite()); + + if (_state.current() != State::WaitingIncoming) { + return; + } + setState(State::ExchangingKeys); + const auto limit = 5; + const auto messageId = _conferenceInviteMsgId; + const auto session = &_user->session(); + session->api().request(MTPphone_GetGroupCall( + MTP_inputGroupCallInviteMessage(MTP_int(messageId.bare)), + MTP_int(limit) + )).done([session, messageId](const MTPphone_GroupCall &result) { + result.data().vcall().match([&](const auto &data) { + const auto call = session->data().sharedConferenceCall( + data.vid().v, + data.vaccess_hash().v); + call->processFullCall(result); + Core::App().calls().startOrJoinConferenceCall({ + .call = call, + .joinMessageId = messageId, + .migrating = true, + }); + }); + }).fail(crl::guard(this, [=](const MTP::Error &error) { + handleRequestError(error.type()); + })).send(); +} + void Call::actuallyAnswer() { Expects(_type == Type::Incoming); + if (conferenceInvite()) { + acceptConferenceInvite(); + return; + } + const auto state = _state.current(); if (state != State::Starting && state != State::WaitingIncoming) { if (state != State::ExchangingKeys @@ -435,6 +518,8 @@ void Call::setMuted(bool mute) { } void Call::setupMediaDevices() { + Expects(!conferenceInvite()); + _playbackDeviceId.changes() | rpl::filter([=] { return _instance && _setDeviceIdCallback; }) | rpl::start_with_next([=](const Webrtc::DeviceResolvedId &deviceId) { @@ -530,7 +615,8 @@ crl::time Call::getDurationMs() const { void Call::hangup(Data::GroupCall *migrateCall, const QString &migrateSlug) { const auto state = _state.current(); - if (state == State::Busy || state == State::MigrationHangingUp) { + if (state == State::Busy + || state == State::MigrationHangingUp) { _delegate->callFinished(this); } else { const auto missed = (state == State::Ringing @@ -549,6 +635,8 @@ void Call::hangup(Data::GroupCall *migrateCall, const QString &migrateSlug) { } void Call::redial() { + Expects(!conferenceInvite()); + if (_state.current() != State::Busy) { return; } @@ -578,6 +666,8 @@ void Call::startWaitingTrack() { } void Call::sendSignalingData(const QByteArray &data) { + Expects(!conferenceInvite()); + _api.request(MTPphone_SendSignalingData( MTP_inputPhoneCall( MTP_long(_id), @@ -776,6 +866,8 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { } void Call::finishByMigration(const QString &slug) { + Expects(!conferenceInvite()); + if (_state.current() == State::MigrationHangingUp) { return; } @@ -841,6 +933,7 @@ bool Call::handleSignalingData( void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { Expects(_type == Type::Outgoing); + Expects(!conferenceInvite()); if (_state.current() == State::ExchangingKeys || _instance) { @@ -893,6 +986,7 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { void Call::startConfirmedCall(const MTPDphoneCall &call) { Expects(_type == Type::Incoming); + Expects(!conferenceInvite()); const auto firstBytes = bytes::make_span(call.vg_a_or_b().v); if (_gaHash != openssl::Sha256(firstBytes)) { @@ -919,6 +1013,8 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) { } void Call::createAndStartController(const MTPDphoneCall &call) { + Expects(!conferenceInvite()); + _discardByTimeoutTimer.cancel(); if (!checkCallFields(call) || _authKey.size() != kAuthKeySize) { return; @@ -1116,6 +1212,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) { } void Call::handleControllerStateChange(tgcalls::State state) { + Expects(!conferenceInvite()); + switch (state) { case tgcalls::State::WaitInit: { DEBUG_LOG(("Call Info: State changed to WaitingInit.")); @@ -1390,8 +1488,11 @@ void Call::finish( || state == State::Ended || state == State::Failed) { return; - } - if (!_id) { + } else if (conferenceInvite()) { + Core::App().calls().declineIncomingConferenceInvites(_conferenceId); + setState(finalState); + return; + } else if (!_id) { setState(finalState); return; } @@ -1460,7 +1561,7 @@ void Call::handleRequestError(const QString &error) { ? Lang::Hard::CallErrorIncompatible().replace( "{user}", _user->name()) - : QString(); + : error; if (!inform.isEmpty()) { if (const auto window = Core::App().windowFor( Window::SeparateId(_user))) { diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 2e024cea83..6be26dd259 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -102,6 +102,11 @@ public: not_null user, Type type, bool video); + Call( + not_null delegate, + not_null user, + CallId conferenceId, + MsgId conferenceInviteMsgId); [[nodiscard]] Type type() const { return _type; @@ -112,6 +117,15 @@ public: [[nodiscard]] CallId id() const { return _id; } + [[nodiscard]] bool conferenceInvite() const { + return _conferenceId != 0; + } + [[nodiscard]] CallId conferenceId() const { + return _conferenceId; + } + [[nodiscard]] MsgId conferenceInviteMsgId() const { + return _conferenceInviteMsgId; + } [[nodiscard]] bool isIncomingWaiting() const; void start(bytes::const_span random); @@ -272,6 +286,7 @@ private: bool checkCallFields(const MTPDphoneCallAccepted &call); void actuallyAnswer(); + void acceptConferenceInvite(); void confirmAcceptedCall(const MTPDphoneCallAccepted &call); void startConfirmedCall(const MTPDphoneCall &call); void setState(State state); @@ -325,6 +340,9 @@ private: uint64 _accessHash = 0; uint64 _keyFingerprint = 0; + CallId _conferenceId = 0; + MsgId _conferenceInviteMsgId = 0; + std::unique_ptr _instance; std::shared_ptr _videoCapture; QString _videoCaptureDeviceId; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 833b40e93f..0e122b7c4c 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_choose_join_as.h" #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_rtmp.h" +#include "history/history.h" +#include "history/history_item.h" #include "mtproto/mtproto_dh_utils.h" #include "core/application.h" #include "core/core_settings.h" @@ -26,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_panel.h" #include "data/data_user.h" #include "data/data_group_call.h" +#include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_session.h" @@ -103,6 +106,8 @@ void Instance::Delegate::callFailed(not_null call) { } void Instance::Delegate::callRedial(not_null call) { + Expects(!call->conferenceInvite()); + if (_instance->_currentCall.get() == call) { _instance->refreshDhConfig(); } @@ -256,7 +261,7 @@ void Instance::startOrJoinConferenceCall(StartConferenceCallArgs args) { _currentGroupCallChanges.fire_copy(raw); if (!args.invite.empty()) { _currentGroupCallPanel->migrationInviteUsers(std::move(args.invite)); - } else if (args.migrating) { + } else if (args.migrating && !args.linkSlug.isEmpty()) { _currentGroupCallPanel->migrationShowShareLink(); } } @@ -442,6 +447,7 @@ void Instance::createGroupCall( void Instance::refreshDhConfig() { Expects(_currentCall != nullptr); + Expects(!_currentCall->conferenceInvite()); const auto weak = base::make_weak(_currentCall); _currentCall->user()->session().api().request(MTPmessages_GetDhConfig( @@ -899,4 +905,129 @@ std::shared_ptr Instance::getVideoCapture( return result; } +const ConferenceInvites &Instance::conferenceInvites( + CallId conferenceId) const { + static const auto kEmpty = ConferenceInvites(); + const auto i = _conferenceInvites.find(conferenceId); + return (i != end(_conferenceInvites)) ? i->second : kEmpty; +} + +void Instance::registerConferenceInvite( + CallId conferenceId, + not_null user, + MsgId messageId, + bool incoming) { + auto &info = _conferenceInvites[conferenceId].users[user]; + (incoming ? info.incoming : info.outgoing).emplace(messageId); +} + +void Instance::unregisterConferenceInvite( + CallId conferenceId, + not_null user, + MsgId messageId, + bool incoming) { + const auto i = _conferenceInvites.find(conferenceId); + if (i == end(_conferenceInvites)) { + return; + } + const auto j = i->second.users.find(user); + if (j == end(i->second.users)) { + return; + } + auto &info = j->second; + (incoming ? info.incoming : info.outgoing).remove(messageId); + if (!incoming) { + user->owner().unregisterInvitedToCallUser(conferenceId, user); + } + if (info.incoming.empty() && info.outgoing.empty()) { + i->second.users.erase(j); + if (i->second.users.empty()) { + _conferenceInvites.erase(i); + } + } + if (_currentCall + && _currentCall->user() == user + && _currentCall->conferenceInviteMsgId() == messageId + && _currentCall->state() == Call::State::WaitingIncoming) { + destroyCurrentCall(); + } +} + +void Instance::declineIncomingConferenceInvites(CallId conferenceId) { + const auto i = _conferenceInvites.find(conferenceId); + if (i == end(_conferenceInvites)) { + return; + } + for (auto j = begin(i->second.users); j != end(i->second.users);) { + const auto api = &j->first->session().api(); + for (const auto &messageId : base::take(j->second.incoming)) { + api->request(MTPphone_DeclineConferenceCallInvite( + MTP_int(messageId.bare) + )).send(); + } + if (j->second.outgoing.empty()) { + j = i->second.users.erase(j); + } else { + ++j; + } + } + if (i->second.users.empty()) { + _conferenceInvites.erase(i); + } +} + +void Instance::showConferenceInvite( + not_null user, + MsgId conferenceInviteMsgId) { + const auto item = user->owner().message(user, conferenceInviteMsgId); + const auto media = item ? item->media() : nullptr; + const auto call = media ? media->call() : nullptr; + const auto conferenceId = call ? call->conferenceId : 0; + if (!conferenceId + || call->state != Data::CallState::Invitation + || user->isSelf()) { + return; + } else if (_currentCall + && _currentCall->conferenceId() == conferenceId) { + return; + } else if (inGroupCall() + && _currentGroupCall->conference() + && _currentGroupCall->conferenceCall()->id() == conferenceId) { + return; + } + + const auto &config = user->session().serverConfig(); + if (inCall() || inGroupCall()) { + declineIncomingConferenceInvites(conferenceId); + } else if (item->date() + (config.callRingTimeoutMs / 1000) + < base::unixtime::now()) { + declineIncomingConferenceInvites(conferenceId); + LOG(("Ignoring too old conference call invitation.")); + } else { + const auto delegate = _delegate.get(); + auto call = std::make_unique( + delegate, + user, + conferenceId, + conferenceInviteMsgId); + const auto raw = call.get(); + + user->session().account().sessionChanges( + ) | rpl::start_with_next([=] { + destroyCall(raw); + }, raw->lifetime()); + + if (_currentCall) { + _currentCallPanel->replaceCall(raw); + std::swap(_currentCall, call); + call->hangup(); + } else { + _currentCallPanel = std::make_unique(raw); + _currentCall = std::move(call); + } + _currentCallChanges.fire_copy(raw); + } +} + + } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index ad55bb5dec..f2fb73ee96 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -77,6 +77,15 @@ struct StartConferenceCallArgs { bool migrating = false; }; +struct ConferenceInviteMessages { + base::flat_set incoming; + base::flat_set outgoing; +}; + +struct ConferenceInvites { + base::flat_map, ConferenceInviteMessages> users; +}; + class Instance final : public base::has_weak_ptr { public: Instance(); @@ -122,6 +131,23 @@ public: -> std::shared_ptr; void requestPermissionsOrFail(Fn onSuccess, bool video = true); + [[nodiscard]] const ConferenceInvites &conferenceInvites( + CallId conferenceId) const; + void registerConferenceInvite( + CallId conferenceId, + not_null user, + MsgId messageId, + bool incoming); + void unregisterConferenceInvite( + CallId conferenceId, + not_null user, + MsgId messageId, + bool incoming); + void showConferenceInvite( + not_null user, + MsgId conferenceInviteMsgId); + void declineIncomingConferenceInvites(CallId conferenceId); + [[nodiscard]] FnMut addAsyncWaiter(); [[nodiscard]] bool isSharingScreen() const; @@ -188,6 +214,8 @@ private: const std::unique_ptr _chooseJoinAs; const std::unique_ptr _startWithRtmp; + base::flat_map _conferenceInvites; + base::flat_set> _asyncWaiters; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 4d14d0f812..b1b1fa0c2b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -513,7 +513,14 @@ void Members::Controller::setupInvitedUsers() { ) | rpl::filter([=](const Invite &invite) { return (invite.id == _call->id()); }) | rpl::start_with_next([=](const Invite &invite) { - if (auto row = createInvitedRow(invite.user)) { + if (invite.removed) { + if (const auto row = findRow(invite.user)) { + if (row->state() == Row::State::Invited) { + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); + } + } + } else if (auto row = createInvitedRow(invite.user)) { delegate()->peerListAppendRow(std::move(row)); delegate()->peerListRefreshRows(); } diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index dd6e449cbb..1dc6fef8ca 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -789,7 +789,8 @@ void GroupCall::applyParticipantsSlice( .videoJoined = videoJoined, .applyVolumeFromMin = applyVolumeFromMin, }; - if (i == end(_participants)) { + const auto adding = (i == end(_participants)); + if (adding) { if (value.ssrc) { _participantPeerByAudioSsrc.emplace( value.ssrc, @@ -802,9 +803,6 @@ void GroupCall::applyParticipantsSlice( participantPeer); } _participants.push_back(value); - if (const auto user = participantPeer->asUser()) { - _peer->owner().unregisterInvitedToCallUser(_id, user); - } } else { if (i->ssrc != value.ssrc) { _participantPeerByAudioSsrc.erase(i->ssrc); @@ -836,6 +834,11 @@ void GroupCall::applyParticipantsSlice( .now = value, }); } + if (adding) { + if (const auto user = participantPeer->asUser()) { + _peer->owner().unregisterInvitedToCallUser(_id, user); + } + } }); } if (sliceSource == ApplySliceSource::UpdateReceived) { diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index ab2c7e4712..8ef7f28c59 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -68,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "calls/calls_instance.h" #include "core/application.h" #include "core/click_handler_types.h" // ClickHandlerContext #include "lang/lang_keys.h" @@ -1684,11 +1685,32 @@ std::unique_ptr MediaLocation::createView( MediaCall::MediaCall(not_null parent, const Call &call) : Media(parent) , _call(call) { - parent->history()->owner().registerCallItem(parent); + const auto peer = parent->history()->peer; + peer->owner().registerCallItem(parent); + if (const auto user = _call.conferenceId ? peer->asUser() : nullptr) { + if (_call.state == CallState::Invitation) { + Core::App().calls().registerConferenceInvite( + _call.conferenceId, + user, + parent->id, + !parent->out()); + } + } } MediaCall::~MediaCall() { - parent()->history()->owner().unregisterCallItem(parent()); + const auto parent = this->parent(); + const auto peer = parent->history()->peer; + peer->owner().unregisterCallItem(parent); + if (const auto user = _call.conferenceId ? peer->asUser() : nullptr) { + if (_call.state == CallState::Invitation) { + Core::App().calls().unregisterConferenceInvite( + _call.conferenceId, + user, + parent->id, + !parent->out()); + } + } } std::unique_ptr MediaCall::clone(not_null parent) { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index fd205d0e03..641845ae0e 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1257,6 +1257,7 @@ void Session::unregisterInvitedToCallUser( i->second.remove(user); if (i->second.empty()) { _invitedToCallUsers.erase(i); + _invitesToCalls.fire({ callId, user, true }); } } } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 011d98dd43..e1563d8563 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -256,6 +256,7 @@ public: struct InviteToCall { CallId id = 0; not_null user; + bool removed = false; }; [[nodiscard]] rpl::producer invitesToCalls() const { return _invitesToCalls.events(); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 8284bd5315..35dc39acfc 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -52,7 +52,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "main/main_session.h" #include "window/notifications_manager.h" -#include "window/window_session_controller.h" #include "calls/calls_instance.h" #include "spellcheck/spellcheck_types.h" #include "storage/localstorage.h" @@ -1228,8 +1227,8 @@ void History::applyServiceChanges( } }, [&](const MTPDmessageActionConferenceCall &data) { if (!data.is_active() && !data.is_missed() && !item->out()) { - if (const auto window = session().tryResolveWindow()) { - window->resolveConferenceCall(item->id); + if (const auto user = item->history()->peer->asUser()) { + Core::App().calls().showConferenceInvite(user, item->id); } } }, [](const auto &) { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 980d77a19a..c689c9e9aa 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -881,26 +881,17 @@ void SessionNavigation::resolveConferenceCall( data.vid().v, data.vaccess_hash().v); call->processFullCall(result); - const auto confirmed = std::make_shared(); const auto join = [=] { - *confirmed = true; Core::App().calls().startOrJoinConferenceCall({ .call = call, .linkSlug = slug, .joinMessageId = inviteMsgId, }); }; - const auto box = uiShow()->show(Box( + uiShow()->show(Box( Calls::Group::ConferenceCallJoinConfirm, call, join)); - box->boxClosing() | rpl::start_with_next([=] { - if (inviteMsgId && !*confirmed) { - _api.request(MTPphone_DeclineConferenceCallInvite( - MTP_int(inviteMsgId.bare) - )).send(); - } - }, box->lifetime()); if (finished) { finished(true); } From a5f44b3ed6303d0837143e881258d3b77ea529a3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Mar 2025 22:14:40 +0500 Subject: [PATCH 096/190] Fix incorrect naming userpic as public photo. --- Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp | 2 +- Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp index 9341b7089a..4e170736bc 100644 --- a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp @@ -361,7 +361,7 @@ bool ProcessCurrent( && peer->asUser()->hasPersonalPhoto()) ? tr::lng_profile_photo_by_you(tr::now) : ((state->current.index == (state->current.count - 1)) - && SyncUserFallbackPhotoViewer(peer->asUser())) + && SyncUserFallbackPhotoViewer(peer->asUser()) == state->photoId) ? tr::lng_profile_public_photo(tr::now) : QString(); state->waitingLoad = false; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index f5cf304fd0..1ea8c3f560 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -6677,7 +6677,8 @@ void OverlayWidget::updateHeader() { } else { if (_user && (index == count - 1) - && SyncUserFallbackPhotoViewer(_user)) { + && _photo + && SyncUserFallbackPhotoViewer(_user) == _photo->id) { _headerText = tr::lng_mediaview_profile_public_photo(tr::now); } else if (_user && _user->hasPersonalPhoto() From 01a6b432f3906901177d35055657f667da1430bb Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Mar 2025 22:52:23 +0500 Subject: [PATCH 097/190] Improve some confcall graphics. --- Telegram/Resources/icons/calls/call_group.png | Bin 0 -> 721 bytes Telegram/Resources/icons/calls/call_group@2x.png | Bin 0 -> 1399 bytes Telegram/Resources/icons/calls/call_group@3x.png | Bin 0 -> 2055 bytes .../Resources/icons/calls/calls_add_people.png | Bin 0 -> 529 bytes .../icons/calls/calls_add_people@2x.png | Bin 0 -> 956 bytes .../icons/calls/calls_add_people@3x.png | Bin 0 -> 1368 bytes Telegram/Resources/langs/lang.strings | 2 +- Telegram/SourceFiles/calls/calls.style | 15 +++++---------- .../history/view/media/history_view_call.cpp | 4 +++- Telegram/SourceFiles/ui/chat/chat.style | 4 ++++ Telegram/SourceFiles/ui/chat/chat_style.cpp | 6 ++++++ Telegram/SourceFiles/ui/chat/chat_style.h | 1 + 12 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 Telegram/Resources/icons/calls/call_group.png create mode 100644 Telegram/Resources/icons/calls/call_group@2x.png create mode 100644 Telegram/Resources/icons/calls/call_group@3x.png create mode 100644 Telegram/Resources/icons/calls/calls_add_people.png create mode 100644 Telegram/Resources/icons/calls/calls_add_people@2x.png create mode 100644 Telegram/Resources/icons/calls/calls_add_people@3x.png diff --git a/Telegram/Resources/icons/calls/call_group.png b/Telegram/Resources/icons/calls/call_group.png new file mode 100644 index 0000000000000000000000000000000000000000..d60c581b1f0651dd8c433cb419244c0cb202110c GIT binary patch literal 721 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1SIp4_|F3=#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz07S-^~7gA{7|~gl3}1KaD5q(kI=PymRYnO-g;Ze2GYOYqI=51GAY%1luG{|#lkPILD znyS733M{@<*@|_$vIS?dH8alRNtx*3QZT_oMQyU@^2;A9Y*Hq9Ej60i!?5`NG%r=A znO;i|-FZ;O)q3b}ox8*ioB8M2*Ia)sYBKqx%i6HygepZDzQ-r0omQQG`skfgPmS(z z8O2P!o2M_=Hte6XY$RQcV)ZZ%V*oo z7iKWI?Y6kFFLL!&j(|po4{xt84bl3)L+^v*+AwXU=FR^?6>0*wByUN6WMyLHx{>WO zVb0~3EH%$R-^?)Cr>5a5vi)|im&~i%2?hZQ@yT7sA8X8>1~ut4}`p zwTgFhg@ufhpyk|i*FPLhGJL_}VkyB@qCJ)q>|>FVdQ&MBb@0O^}8!Tz zkd88P zF~}g5W3~kbsn(IJHu@?9CIz9r0S$eYGpYu8DhwO%?+@6ks=)vtlMGPb9#ILR5&)o9 z2?VIA9Jsw#3Ht98Q>pf)-X1J)_FVt~)a6*DS1{?Tk0-EptC8mEBA;v=SCi;9-Q;{~ z1l&l>s4vdbu}7ZhDY#YtmrV?jg~77o!mX2j4b6^w?r}kOcj2FkHH5NLhFaTnPfp@3 zm!1AxKC{7Fo-DH*T0GCnq0&-`lHASeTs+4-3;bGLwtMVhs(A z0V_Nn@95}gYipZDCXvjIjqeMEbOr-^LxwUpH(yy!y_1kgTs}@KduNG8Wcv^&y;g>4!%L`g@J+Zs?wsPttD1as4Zx1Z4Cy4 z)yze(N=i&7@x>%EIdHPIswyLpS6W(aBJS%W0d=g$Kdr8aM7kgG7?&k1s*=x_N+e!` z8P^{@I zq|s<;X=x!LA&7oDo$l-FtGY|tudS`E`k^wd-rim`8huVG9YCQ_D{ojY*=#mEUnCMG zP$+~%@J}?u^9;uAa~2jBP$-oBLZL{b(}lUiVtUn%U41nyfxyoGkV2v8IO%8bnaO01 zjeRe8OKNXzjkXyb8L8Qq&bhkkqt=7)UEbIj?&-k}=4EE8#WxWy%R4;d?e`UPIPgf4 zsj)E&q6AI%_ZRd-<%3e`w;O;7B(9<(fMuvrWNPcV`}RXhk(SRj9*-wR>=xa*vtRzF zzm6u~G|qd_J-%N=zfeq>_e>wvJT$TJq*-QC{pIhj=)Stx6lb;nSS z&x6|9oC99b6?!{%#3*aHzub3ccM?<-Q7C(38=IStr@e*4;a!MmqtnJ**xv3ZPf$}D zN2(RKE{sIe;o;#6&joIF)ew*XN7dqU-5xuc`S|&16}{fvbcdCe_2Fkq%gW+@ zj2+s1~h6|ICvmqR6D-E|1m~IK#cv%PDVi#B9Fn*wN>megmm+oH~cY+1lEIin0oM<>jYdP*6rufWwCm zzc%M4mK`@7X1JzgbhOSSX<^)Yv4Ob& zIkV;0bO}eJE&cA+i~+pTbnKa%oh1;T#j!#y9+GOU)~=TX&!R5!tNT^d6zF@T6Fthy x%bm%AVRBtfr`%R_pm?8j^8fMjC2jI{E;%*~#TFdRw!=eI^4c^h0{sn@Uc$NSF literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/call_group@3x.png b/Telegram/Resources/icons/calls/call_group@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..67f363752b4c3639041f07f246f93d1e86258c31 GIT binary patch literal 2055 zcmb7Fdpy&B7vD^g`z^{XO(JcWOA?aWFpS7TNf*ELXo^je%_C{$vhYh7V?*XTS#wME z3y-m#dv|GY)2}-R7|5#u=uiv>WD4lAAaGI`NcvX@K-vI-K#(FZ z2m(m(ueTzp|9HcTApg~W1#XRf)CYlN@Mxs7-yQH`0nQEUq0z;wW*Tpqh+WXBVfPOC zx}5g8hx*OW9cn5^Q>Cpr!zzD=!bQ#~lnc^t%}APZ+)b$7ZVC78)_B&tkoJ+c5W8IX zdU+yzW5JQs&#N22)0HhreKZ?fd>^BW_90ChH^WT z#UKfZxIUj2%Nt%=Pt&u%-I2Qc^5x6JWtcPCl9g#!98P>+Mw#JAD$6?mc(U%= z02BShTE4%)&g$^t!%j{YtwwCfyc{?VhZ|ln%=`ir;$IWQ!G9rcEa{b}CMoZ`-j?Lv%e>6H@Fu-WG*_$FC#_PK6SFW{?DzBM z_VTi7eMtEdvYs-R#D0Ju)nMy^Jd(o?g>X!z& zudakwwzajTs2kT39J_n64{O2CF{wPNzrTN!#^_y!NF*98ab=~pj(z4)S!BNBM@3;3 zmIYhC1azFE`FJfu-!YX@8mNiu1jfdy?abK6&UDKl5-^_yDoRR9Rg+rWE}`%W%aPaJ zeR&ZWx)lQPxFJQq%QR$@VIO10TJYQz>d5zL{sh$Dc;^rNbf;^EemC|#pHB!L5Rb5V zG+*Ht0k`q}((kn~%ahpHu+7bHi|5BKUc;tE?ATOW znvShPB}XneIQUx(YU(MQZ60bx!t@XvZ;x?q#l-MjI@F_Pdcz_jv_@X|ta4kNbkd%8 zEbd&H8!oSoK9v%!Qtp|T2$zX3E-oHDG|geNmE&kMS`Uv0d?LcN4jY0|S5s5d&>*W8 zwY2QJw@<-2mlA*}N9UMb=xKiTFtfhin&QyO-`rf8CJxF}9((5y`BwsA_BiR=!V_cf z+ENvnHZxLgl14B|Aa_#X;d?*1*w|Qyl2fZd)=kpx(iH}S5iRDUcgDrVJx}wQF&(DU z>9~;^y>r>C3~g=gto{^cR8-WC?TMDVEy>QMN}8X>8gD#V7|2CHAnBI=c|X_1BRTN% z(vJA*k1wNUvluB%&VKb3^zP#-9N1h!iRZP|nZAO=I}L~vl%vxDdd<{u$UB?oaaL=) zx!Dk9IP=`RSZr6?&v3+v9&vYDY+pY2=}u*Do6dnz#eo9564*ab#O291 zzSGKj$J!62gXrA7DSnY+K0KY;Qw=Oa>K?KZRM3-nmdP>9ns@i|n$eK_z0NA~FtvG& z!XJ2TWMot&XT6WpYGz{cuJ_7=ue`URyWD@17YGCe(8T^a%|}TG8Wmg#t1}o+^N0J( z7RO$MKzN*z-dDtR(}S9&?L%t~xDh7wMZ{F6R6aa?DMj5(SO0k+R4ux~m#J`3=7yKM zyHI4Ha%oPRv?A<~*Mzn9lS9s0$=mNm5=R4Gl+~t?e>{_i+D=YR9wdy^*H|ApGM_e; zmzP%-JrExz70c|{mkzaA0Oug6= z|Ez1J?ScHkuYdN|e3=euf?N%q#eC9ftRd1>XORd50`Sz=c;;GwXCsNas)_YN{fsaj z@rf=Kx(nJrO#IsW%Erdcnn&ISN% zu50@5!Vl=nY~9=w$Ng7ARb`cHkknDaCt0_!r|mU=uvi{gvP5>fqQV4&GlRAF9f_}d zOj)fQ9Syi@s5;(>)!`=6%gST(p zu!}d>bLsRngOUqw_(x54$TeAV81DM@qq5-xguBT@`PsnMN`4M%W*P2)p1yJ)KmY#F zihQUrz*PlV=^-S(n(z^Npu0EU{!PhW+RR+u{=Olcz<8?{W~YnJ&;cyEG4X1>pKf1p ziKcHK80_a~U3;u37{@s`*?{v849qJP?+?1caEPsCd{8y*l7R0ST8!5=$1F1h8e|D6 z@$Q>pmt?MXR>PqG8(~vcB1k!O61XFhb&2w7W>S*OF@t9Fj*U~Y?77{>cK_!kn^oJC Y!Ki&0U26|H|LX{&QC`TJla~_z4Y|t1B>(^b literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/calls_add_people.png b/Telegram/Resources/icons/calls/calls_add_people.png new file mode 100644 index 0000000000000000000000000000000000000000..4e07a92f475276ac0016cb0fdd9620628c2530b5 GIT binary patch literal 529 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgftlHDXF~maf z?WEmZhXMrJ_@zZve60?8Pd=m@w8Z;^Yz^c61IO$tIS(Fk6VOST>XsvvbE#86DI&6B zWl$uGm`hLFn{!oSGoR#$Ow*nFYX1K-HHm7@C$#^iGH=p}o+iq2ux$6+vfVc4|Ff>v zIQ6vXr^~}#d3w{U4?VKb3*8u@Bk;t1+wHd@kxFmBR;|9u6@4_xuuDnIM6SP3@`d@F z{r8!y9U2etU48!9GF8oCrMbuY>#HAItg)YcHe&LZ-+%j~9i)96w?3~dS-eO?j*s1D zZsshVr`11Sf0erBa3EidrSa9>Jooln@4rt{$(*%pl~&Aw5-VB0b{&m}H@2PSo4?|O z-@LiZiUoDOZi~ZKhpxVQ`Q?+}|I#)`>P^4t<773rFHz!qSJhrSsouWh%5ef$4_qqU z8=!Hg+t_vU&6c&BIo_Ok{yAvnl+K3sqMba!59aCE&h(LMj9MEOmVS!=@Pwv1`x7;G z|6f?YcV+%PRHjdwu_{~4CF5HlNcIw^5??KVw>FVdQ&MBb@07V_q A7XSbN literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/calls_add_people@2x.png b/Telegram/Resources/icons/calls/calls_add_people@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..bf55e663c391e5d259b779cef68cbd23a7959eac GIT binary patch literal 956 zcmV;t14I0YP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFCP_p=R9Fe^S4&7LQ5a6UkYolK zN`@^o=mE7%Y11YOl2%GV%OJ2#&@OQ2MmOzhSwgfD7ZyRQNTeXBom3RkCM}2xA5dCk z_kP^D4Ts~LdFbSYduLYv^ZUQ^&pH2@X=>6Qtpi#Iv=00`9nk4?UavP6i{0MdzP-IY zJUkp89xg2{wYIkYg+)3Xj?>fApF+>w-QC2*L_<0C_4PeIJ_?cfWBd5{n4O(%5T}-w zmaD5P5jLFb_4RdNV4xA4mY0_$*>C|nJ3Dm|!qU6Cx+Eb|sgz7Yl}g28u}A_62iWa) zY(`WH27|&8zpb5}oif>QVLqQv1Pv}d%RN0k-QC>{uix)y#lX(Rhy+kB$#^CqgTcUI z@d5?SshBr{c9u~xo=J#>NZB))%=`O0WfW0wZf-=paMeqwR4OGB3Go`dw^%G59Ubwi zpinO%fUd5t3PM*K*VfkLwzIdlHm+iJCf9A4Az?rt`lC1Yxo`5OwjHA2*srot(!sV+0=TL|IP(P%Vc8!*eV z9Y5GExgHe8h8rIrA08ea8XB_MY}oSHbsE-n=}Gc&`T zmy3%F%EhagUhC`YPft(O$zrJ(>+yJeY;252P#|F4;c$3wZ_nj&Ri|}ybVMQ%%uH5o zaXT)M!NI|5*cvUMcz=H{UQ*JFX|k}eP$L5}ZDeGmP$)>&lN0dy`MJ2bNP;Vt@llJB zD5sdr81$*Xzh5yAK4E@-US=ivVOv{UJU#?vb7*pMl1x@brv4$75yFg7g#-ygVJag; z0)3OLj1ayhROf+VMq(->bar+|CaWUXg=sRGqR}Y+(&Zl_kx2CW6gA`6MbbHUhz;ejb#W5s< z^=;Hm?Yuyd{m&}poYFKsyTm^|a8%?EnxZJ@5oaUd&((iQenCZu2EW&&0I!KVc_fWf zPR!6uUbbVu^xSiQzx_OS^LNefn(fiQt8Z1m-CFm0`!&mS{_Ohwb)EB1G@!#Gwwx(P z*}UJreQRuF^y$;5px|IO0tmDOfK=kMO#yMO=v7#p=u-@cXEL@2*3IP~q~$H>S?!^HLXWTmBx zi;5mSxi2B{L{8`G;Q*PpZ{C=+394T?cFfK7p2?w0mxBCsDh{)-u-v(S|MHOqD~byW z7Cdg<@>I5!DXz41YkPb9A%?jcN=l1XtmyCypFVB+^zfvlMSV|nb#*oExT+MN1;tL$ z?CI+6o zdrRGzd%?O@?W(z$q-5vT&_k(JNsf+=lNPG-hRLit@aD~%Cv$vueNx=?_sbU-u2PSP z!WB%H4>}uJ+S*RNHO=JQxpOnl@bdGl77#O3^IP)i+O=!9Z%b>2Pn3|m@%#7h4<9yg zG0#$*lbUhmiB%grTY7A4Y|->Jxo4Kz+S;xO3;9)8Somq?uRnk0obp*_Vri-Ao{;tW z^yzr@7eI@D{i>45Or7%2WaI6Q-rlJ#KtEKxT>7s3@#oLQ#l@R7Tr#1@s7vfh6DIJwkxYhcdw54HwRCxzJA*pw6&29^W|1v$N1?QM{_WchOK zMTQ*GQc_p$2J|iJNj#VB>U#9`Yi-qYckjmf`Sn?9unS}`6YA^hD=RBgQc^N9 zI=&h#>-@j?!Tb06@daVw;hMUC|NNOXYZfr#o;+KV%VKG7A0Hh2_}MeJm(v}6>t-^@ z&zhz&dCS(V4_T9Won7U=9#(v(HfiMpNw&_bw{M^J>h7+~&ery9o6W*4B0kN^q2N`2f4{o&!_DD3 z`uge%*D4>3T^VTJ{6m@HRhZSJv!_|o!;CtYvHJ!&2MdXbwe42*HGM3y!Bla*u*IG( zl@*1C8cNCD4yQz2mR$O;A=o&xJ`0$`pRunsY>HBS*>+g?@RW+eLPw`)-=^f*f%{gi zT2*oOu2sRNO`CeScy>gnPP9C|`u~^4hr%=F&NVgDVis0g;L7HL0{=y_)iH0?TcFvt S{YV+8RP%K8b6Mw<&;$VCt6zBl literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3b4a0ef79e..1957187894 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4757,7 +4757,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_invite" = "Invite Members"; "lng_group_call_invite_conf" = "Add People"; "lng_group_call_invited_status" = "invited"; -"lng_group_call_blockchain_only_status" = "syncing..."; +"lng_group_call_blockchain_only_status" = "listening"; "lng_group_call_muted_by_me_status" = "muted for you"; "lng_group_call_invite_title" = "Invite members"; "lng_group_call_invite_button" = "Invite"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 9c8e600509..89fc1a1da0 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -132,7 +132,6 @@ callHangup: CallButton(callAnswer) { } bg: callHangupBg; outerBg: callHangupBg; - label: callButtonLabel; } callCancel: CallButton(callAnswer) { button: IconButton(callButton) { @@ -143,7 +142,6 @@ callCancel: CallButton(callAnswer) { } bg: callIconBgActive; outerBg: callIconBgActive; - label: callButtonLabel; } callMicrophoneMute: CallButton(callAnswer) { button: IconButton(callButton) { @@ -154,7 +152,6 @@ callMicrophoneMute: CallButton(callAnswer) { } bg: callIconBg; outerBg: callMuteRipple; - label: callButtonLabel; cornerButtonPosition: point(40px, 4px); cornerButtonBorder: 2px; } @@ -185,15 +182,14 @@ callCameraUnmute: CallButton(callMicrophoneUnmute) { } callAddPeople: CallButton(callAnswer) { button: IconButton(callButton) { - icon: icon {{ "settings/group", callIconFgActive }}; - iconPosition: point(-1px, 24px); + icon: icon {{ "calls/calls_add_people", callIconFg }}; + iconPosition: point(-1px, 22px); ripple: RippleAnimation(defaultRippleAnimation) { - color: callIconActiveRipple; + color: callMuteRipple; } } - bg: callIconBgActive; - outerBg: callIconBgActive; - label: callButtonLabel; + bg: callIconBg; + outerBg: callMuteRipple; } callCornerButtonInner: IconButton { width: 20px; @@ -930,7 +926,6 @@ groupCallHangup: CallButton(callHangup) { button: groupCallHangupInner; bg: groupCallLeaveBg; outerBg: groupCallLeaveBg; - label: callButtonLabel; } groupCallSettingsSmall: CallButton(groupCallSettings) { button: IconButton(groupCallSettingsInner) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.cpp b/Telegram/SourceFiles/history/view/media/history_view_call.cpp index 730975957f..d136b733d4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_call.cpp @@ -116,7 +116,9 @@ void Call::draw(Painter &p, const PaintContext &context) const { p.setPen(stm->mediaFg); p.drawTextLeft(statusleft, statustop, paintw, _status); - const auto &icon = _video + const auto &icon = _conference + ? stm->historyCallGroupIcon + : _video ? stm->historyCallCameraIcon : stm->historyCallIcon; icon.paint(p, paintw - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, paintw); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 3a44fffc2b..f285966d14 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -437,6 +437,10 @@ historyCallCameraInIcon: icon {{ "calls/call_camera_active", msgFileInBg }}; historyCallCameraInIconSelected: icon {{ "calls/call_camera_active", msgFileInBgSelected }}; historyCallCameraOutIcon: icon {{ "calls/call_camera_active", msgFileOutBg }}; historyCallCameraOutIconSelected: icon {{ "calls/call_camera_active", msgFileOutBgSelected }}; +historyCallGroupInIcon: icon {{ "calls/call_group", msgFileInBg }}; +historyCallGroupInIconSelected: icon {{ "calls/call_group", msgFileInBgSelected }}; +historyCallGroupOutIcon: icon {{ "calls/call_group", msgFileOutBg }}; +historyCallGroupOutIconSelected: icon {{ "calls/call_group", msgFileOutBgSelected }}; historyCallIconPosition: point(12px, 10px); historyCallLeft: 16px; historyCallTop: 9px; diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index 784d9e434e..7929c9ce0d 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -387,6 +387,12 @@ ChatStyle::ChatStyle(rpl::producer colorIndices) { st::historyCallCameraInIconSelected, st::historyCallCameraOutIcon, st::historyCallCameraOutIconSelected); + make( + &MessageStyle::historyCallGroupIcon, + st::historyCallGroupInIcon, + st::historyCallGroupInIconSelected, + st::historyCallGroupOutIcon, + st::historyCallGroupOutIconSelected); make( &MessageStyle::historyFilePlay, st::historyFileInPlay, diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 8205d23049..0d47d16611 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -76,6 +76,7 @@ struct MessageStyle { style::icon historyCallArrowMissed = { Qt::Uninitialized }; style::icon historyCallIcon = { Qt::Uninitialized }; style::icon historyCallCameraIcon = { Qt::Uninitialized }; + style::icon historyCallGroupIcon = { Qt::Uninitialized }; style::icon historyFilePlay = { Qt::Uninitialized }; style::icon historyFileWaiting = { Qt::Uninitialized }; style::icon historyFileDownload = { Qt::Uninitialized }; From 698d9c208faf17eb6097f8b8611a94333d5f5223 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 31 Mar 2025 22:52:37 +0500 Subject: [PATCH 098/190] Improve confcall invite management. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/calls/calls_call.cpp | 8 ++++-- Telegram/SourceFiles/calls/calls_instance.cpp | 27 +++++++++++++++++++ Telegram/SourceFiles/calls/calls_instance.h | 3 +++ .../calls/group/calls_group_call.cpp | 3 +++ .../group/calls_group_invite_controller.cpp | 4 +++ .../calls/group/calls_group_members.cpp | 23 +++++++++++++--- .../calls/group/calls_group_members_row.cpp | 1 + Telegram/SourceFiles/mtproto/scheme/api.tl | 4 +-- 9 files changed, 67 insertions(+), 7 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 1957187894..4d5aafe974 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4805,6 +4805,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_context_pin_screen" = "Pin screencast"; "lng_group_call_context_unpin_screen" = "Unpin screencast"; "lng_group_call_context_remove" = "Remove"; +"lng_group_call_context_cancel_invite" = "Cancel invitation"; "lng_group_call_remove_channel" = "Remove {channel} from the video chat and ban them?"; "lng_group_call_remove_channel_from_channel" = "Remove {channel} from the live stream?"; "lng_group_call_mac_access" = "Telegram Desktop does not have access to system wide keyboard input required for Push to Talk."; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 02f3516871..4f81f6f37f 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -1489,8 +1489,12 @@ void Call::finish( || state == State::Failed) { return; } else if (conferenceInvite()) { - Core::App().calls().declineIncomingConferenceInvites(_conferenceId); - setState(finalState); + if (migrateCall) { + _delegate->callFinished(this); + } else { + Core::App().calls().declineIncomingConferenceInvites(_conferenceId); + setState(finalState); + } return; } else if (!_id) { setState(finalState); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 0e122b7c4c..f3e9365fd3 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -976,6 +976,33 @@ void Instance::declineIncomingConferenceInvites(CallId conferenceId) { } } +void Instance::declineOutgoingConferenceInvite( + CallId conferenceId, + not_null user) { + const auto i = _conferenceInvites.find(conferenceId); + if (i == end(_conferenceInvites)) { + return; + } + const auto j = i->second.users.find(user); + if (j == end(i->second.users)) { + return; + } + const auto api = &user->session().api(); + for (const auto &messageId : base::take(j->second.outgoing)) { + api->request(MTPphone_DeclineConferenceCallInvite( + MTP_int(messageId.bare) + )).send(); + } + if (!j->second.incoming.empty()) { + return; + } + i->second.users.erase(j); + if (i->second.users.empty()) { + _conferenceInvites.erase(i); + } + user->owner().unregisterInvitedToCallUser(conferenceId, user); +} + void Instance::showConferenceInvite( not_null user, MsgId conferenceInviteMsgId) { diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index f2fb73ee96..9936c69c92 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -147,6 +147,9 @@ public: not_null user, MsgId conferenceInviteMsgId); void declineIncomingConferenceInvites(CallId conferenceId); + void declineOutgoingConferenceInvite( + CallId conferenceId, + not_null user); [[nodiscard]] FnMut addAsyncWaiter(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index c2209c7102..057177db83 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -3739,6 +3739,7 @@ void GroupCall::inviteUsers( if (const auto call = _conferenceCall.get()) { for (const auto &user : users) { _api.request(MTPphone_InviteConferenceCallParticipant( + MTP_flags(0), inputCallSafe(), user->inputUser )).done([=](const MTPUpdates &result) { @@ -3752,6 +3753,8 @@ void GroupCall::inviteUsers( state->result.privacyRestricted.push_back(user); } else if (type == u"USER_ALREADY_PARTICIPANT"_q) { state->result.alreadyIn.push_back(user); + } else if (type == u"GROUPCALL_FORBIDDEN"_q) { + startRejoin(); } finishRequest(); }).send(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 6182597c1c..c8b2601270 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -319,6 +319,10 @@ object_ptr PrepareInviteBox( }) | ranges::to_vector; const auto done = [=](GroupCall::InviteResult result) { (*close)(); + if (result.invited.empty() + && result.privacyRestricted.empty()) { + return; + } showToast({ ComposeInviteResultToast(result) }); }; call->inviteUsers(users, done); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index b1b1fa0c2b..0e3d2eb1d7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_volume_item.h" #include "calls/group/calls_group_members_row.h" #include "calls/group/calls_group_viewport.h" +#include "calls/calls_instance.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" @@ -1308,6 +1309,9 @@ base::unique_qptr Members::Controller::createRowContextMenu( const auto participantPeer = row->peer(); const auto real = static_cast(row.get()); const auto muteState = real->state(); + if (muteState == Row::State::WithAccess) { + return nullptr; + } const auto muted = (muteState == Row::State::Muted) || (muteState == Row::State::RaisedHand); const auto addCover = !_call->rtmp(); @@ -1459,6 +1463,20 @@ base::unique_qptr Members::Controller::createRowContextMenu( removeHand); } } else { + const auto conference = _call->conferenceCall().get(); + if (muteState == Row::State::Invited + && participantPeer->isUser() + && conference) { + const auto id = conference->id(); + const auto cancelInvite = [=] { + Core::App().calls().declineOutgoingConferenceInvite( + id, + participantPeer->asUser()); + }; + result->addAction( + tr::lng_group_call_context_cancel_invite(tr::now), + cancelInvite); + } result->addAction( (participantPeer->isUser() ? tr::lng_context_view_profile(tr::now) @@ -1473,9 +1491,8 @@ base::unique_qptr Members::Controller::createRowContextMenu( } const auto canKick = [&] { const auto user = participantPeer->asUser(); - const auto state = static_cast(row.get())->state(); - if (state == Row::State::Invited - || state == Row::State::WithAccess) { + if (muteState == Row::State::Invited + || muteState == Row::State::WithAccess) { return false; } else if (const auto chat = _peer->asChat()) { return chat->amCreator() diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp index fd91ebbcb0..7dddb9240a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp @@ -639,6 +639,7 @@ void MembersRow::paintComplexStatusText( const auto &font = st::normalFont; const auto useAbout = !_about.isEmpty() && (_state != State::WithAccess) + && (_state != State::Invited) && (style != MembersRowStyle::Video) && ((_state == State::RaisedHand && !_raisedHandStatus) || (_state != State::RaisedHand && !_speaking)); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 5761f160c0..51e38326f9 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -187,7 +187,7 @@ messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?tr messageActionStarGiftUnique#acdfcb81 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long = MessageAction; messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction; messageActionPaidMessagesPrice#bcd71419 stars:long = MessageAction; -messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; +messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -2610,7 +2610,7 @@ phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; phone.createConferenceCall#fbcefee6 random_id:int = phone.GroupCall; phone.deleteConferenceCallParticipants#f9231114 call:InputGroupCall ids:Vector block:bytes = Updates; phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; -phone.inviteConferenceCallParticipant#3e9cf7ee call:InputGroupCall user_id:InputUser = Updates; +phone.inviteConferenceCallParticipant#bcf22685 flags:# video:flags.0?true call:InputGroupCall user_id:InputUser = Updates; phone.declineConferenceCallInvite#3c479971 msg_id:int = Updates; phone.getGroupCallChainBlocks#ee9f88a6 call:InputGroupCall sub_chain_id:int offset:int limit:int = Updates; From 021115b463c4dda447c464d0af9e31e991e25ec0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Apr 2025 00:28:01 +0500 Subject: [PATCH 099/190] Nice join confcall confirm box. --- .../Resources/icons/calls/group_call_logo.png | Bin 0 -> 1092 bytes .../icons/calls/group_call_logo@2x.png | Bin 0 -> 2052 bytes .../icons/calls/group_call_logo@3x.png | Bin 0 -> 3149 bytes Telegram/Resources/langs/lang.strings | 6 + Telegram/SourceFiles/boxes/boxes.style | 6 + .../boxes/peers/add_participants_box.cpp | 1 + .../boxes/peers/replace_boost_box.cpp | 43 +++--- .../boxes/peers/replace_boost_box.h | 5 + Telegram/SourceFiles/calls/calls.style | 16 ++ .../calls/group/calls_group_common.cpp | 137 +++++++++++++++++- .../calls/group/calls_group_common.h | 1 + .../SourceFiles/core/local_url_handlers.cpp | 3 +- .../history/view/media/history_view_call.cpp | 4 +- .../bot/starref/info_bot_starref_common.h | 2 +- Telegram/SourceFiles/ui/effects/premium.style | 5 + .../window/window_session_controller.cpp | 27 ++-- .../window/window_session_controller.h | 8 +- 17 files changed, 217 insertions(+), 47 deletions(-) create mode 100644 Telegram/Resources/icons/calls/group_call_logo.png create mode 100644 Telegram/Resources/icons/calls/group_call_logo@2x.png create mode 100644 Telegram/Resources/icons/calls/group_call_logo@3x.png diff --git a/Telegram/Resources/icons/calls/group_call_logo.png b/Telegram/Resources/icons/calls/group_call_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b083149297bd7ee81d1e4ef1fb2aaba860574049 GIT binary patch literal 1092 zcmV-K1iSl*P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFu1Q2eR9Fe^S4}9aVHEyw^Pln; zibOKgNEWhUvS4E|vlzw3EJ#A5)GR3yB@0^{v0yV;SRhKxWC#mCi%I^{C6xT7Y5XNT zx3~NDdcW^`&3Dap7x(*C=RM~<=bZP5K0m}qEd_=tGJm56>}VIeZNwY61QSt+ofDjFLb<4lv|+}xZhMv^QR z3yXSue2j^SA^Pj<>qK;Qbrl{SE-<1jLJ_d0DgE;Dl8}&~jFBY#Slip%RC#c4kZSJl z?-M?vjEah){9p-G47JM{pickS)% z(C_c>ry{5g4-ZE~M9A%^a!N}}DV`&0Yil_eOwZ2Fa%$|ct*s4^wY4>t+1J;{*%2M_ zG8OB3b#+z#XsfEKSWj{~5C}v^M>jV&v!e6!b9n@+{Qy}VA0Ouc&(F_|jg1g-{QUfU zj>8>eV`I_8@!)^{{*I20rl+SlH5^5wqoAMwRmoy{dV0FLx>ydO;SaTpMF7Fcx0^Hctw+?d*XyN80)f5fTO*J=!@jn+ zw?|__D?$s9cSr)nkVgy9HW}nDKjq}+=3ZP}(3n7AT%@F=us}4V-rnB(`+G>SY@D8+ zVqCafF0|9X5?@J42}U;?)9vl8)9H+ij6_(Naag)pDN7@Ic0FRR@g*9&2Q~-R6KFJI zuE;nAa#Y)Az7!C^Sd%g46kUp$4u|7Y->Sn^fP8p(sH>}MZEZzI5U9yT!IaUT1VlUy z4Grbx=1jGaY0Dutky0ImP zosYo>(Z3={es68wWG|G>b5+i ziW}zU)nYPG8PQT3sYV-QAq;f2ozD9~LENI(K&^Q{7`A=W4k;cuWOb zbD{*9#qaCsIU~%^&!4NU5)=(uKwW|h3kwGa1}=`^R>!N?TfAG#9h-XM@dU!c zrkRNewQu3}dJpk6RFpH&ujJ2fql#rjpA3C{{nD}hpWiEg5np6yXDe6kX83+-rjFb~ zGMd90Aef!+U%oIHNZjuBvbC4jo17d;YxyW2@MN^D7wxxunf(`fV0&2`tWKd~ zwSYByHtP}Z$+gM%_iy;V3))&>GF|e<3U59?F+k{CkB=8nqwbg*7(~MElUnMDOhY)0E((<1p$F->gr#bLbNnBP1zyA!9BgbFzU$u-ktzZG*4uL zq~)9a_8+sFD4vfWKbDFh3L6?iK9@NdI}-?q=R(Fl;p>l8BUQ1(w3K{GWW?J1d?8xA z(IPA?42YgA>wSJn3CSG_g+lKKot93q9 zr9cXY>JM8W?~`=>)y$#WN=1!xqwcdKYRLPYvPocOTt;y zZzuV)+V|z96{+V711u4?y?_|rXEa-jH=h35t?JPdf$d|6XFxAJ9^divIZW4o=2{B4 zm$L_T+2Cyrc*5!^#_M4mYy$1&e z+&+^CdOG{}wdtmW42kD0Lv+Pj{$Gj7pO(TkBwdpz6#3b$q7E7@g0(qkHJdkrG?HuV zu0x|ogi2tN)zb{a&RiZ|ckk6yGZs?KgSJ6`A_|5SM;3rX=4Vc5PPl_yg=F0oJiBDB zoM*_5Fi1aFC(J}##Jjt@2XGZlJI=aoD%yfch6Z|GL5J;*Nw7gl2qf|*N+WS4{tT?V zG0;@&UTudX`!4w1OHKUjCaa2zUC~q5R2j3luwaG7mejnKa{;b z*PYH;DW^Ijvnx#oiA2hZzcpF8yamtIa-S9pFt|;?D-@V)zf!pzFUiK*<(HH!U# z@m2()_4@ivU%F;=8L=qGE4@bJU*M zxiu3{J1;6AKP2TpQ-Whj;iBkq1m!wVdo_kfh)UG9w?v_mXS7?Zt$6QIfCbJe z5jMmhpSgH--Ebf+8eA$102R|Y0=vvdkKDB-MOFnuq{L(Q;G&{NdxAxyE7TXs#bVKD z&fzx(&3m%Hp43+WUTYC@RWigDFLZ~VmG2n z+ceH_I1IcE+uj{o+#@=Dz8JLE+AQBJAD}vX*!^v8TEc!PaCm4SQ^MX(w=fy1tVBmg zOib|q268~?!D|0~lbVgo^TVoVt^TLG<{BD((I7#mAH_f={m!wwgvQmj#u&T78=>nu zm)SHyLy=%fgkuo-uip3T%Uv;yeHVOxhqC2)G*U(ki(Vc?D`h=!qyZ~9R8@@VM)YZB ziu+Zl_c^Lq{#ojP%utSM<|>-&*3L{7OyC5cC@Wrk=;hH1s%>lWYr<;Auj-2!cc>NH_f@torJ zo+k_K!NF4~t60O(x<;q*(#nFc2Jhb!@thz*1Lv6<$Ju%}XyDnOzEnX+MYjl>lD8a) zT*a&E=lyeG)+4zx?qR!3W{>2&w`_kDW-uuRHnb{msmEVlUj;Bp<6@Oqt?wsq>0Xez z8E0&`20v>HWERw_)kycM80kpmrkg!NjTJt+Iotmh@v}3Wipc35X&pS8;r#L(6L(Yz7Nd4Z6bK8Clr6_| z0d&6Z&5(A>3ChP;HcUBR>ih8!9kNsS>ey>I=b>^9nduTPj&&<}sk+)YnjQLjE=hkd zOY&F>_CY^_q)MmSZZONL-NBa0nxySfx`@7gGoF+xi*}RIvVmGW$F&gf`epik3%NGe zO7c>}DlqK5FYVAr*?dllAhCdxZ7Fd!nU`OavE5D| zpw!xGd-!}&O&p3AtnkvJ*_< zg=zh@YDx$+!(+fL^X4e#&V71Rru!Y_tej>5tqsZh2G_8&O=0&)n5fnLt&8K|1flQ6 zY;btB9GJYl{$pm-w=%N?4X|sNCM=!gGj*YC_Ss>1U$HBK{LT?KEB@pL~WB@p|GKBABT^f0&})}TOH7a2V_|G6p)=nbKfJ9&&D zcB$rbY}RmFEOM-Ym+x^FgFcKl69(~jr5t56SrZmSqp!Bc%j_A4D|EgWAa8r_Hxy1+ zVVnwP1Jg8eKA>M;`PYv(7DBzZQj8V00}RWYEZ$F1A)^N`|Qp&Mq{mPH#=Ydy@`6@RsBgd z(%;e>AMJ{{6Tt5v()mu#|H#2pOUbcLgnHD$x5e;6Rabl0iyde?UB=xT`(-`}e^6%pwAA!zA;V1H+ zFJ@ZxVR!6{X~I3P-xC$_DJpbAgl~h; z1A3XgDA2sfd>H7eYBfF|0k;}KZE{SgUoPIZcHUoyYzckRUg*BtZ-mU-JxdG~GmvvH zjkM(I((?Ix;$o-B_xeD=lK5bKxXK|YZ!%l{mDAA_>0EV2HSI*Xv~+kX<7_z_Lek!( zaC!jlV)oVw&T%fLqI4w=;l<#8LL5S<1Vv2gniqSjQ^yVbDbl{zuIDogBtO_mvP!qc_QHda#xuPEAivRZos8 z>~uHs&K8r3E*cf?$=YlbV#cVL+nbe7vn#b4=*S?skF_Z=_3R7C+Qumh;Sa?#YV||+ume5 zq^Quj#~b(X=SDzbXjC0vV2|0RMfzGFVpzQoy__aw92Ud;!=%QcLo**Pc%pf^$d`O1 zw^ofP_X)^w&Z$mYK8vLeT~POx*XJEAn2Zi0k`ZF~aQzAH7Z}jrCfk&=1r;xQB+k}W z?i58l+9mo#(afBVKlqK`GUOI;{{g+Lq{9e@NS8FX)&Q)|dD@!GNJUVHn1E4M8X>NL zBTXW%;-O$54RfH%{?tgUEl4#?0gA z)=VbT62HC-N65_Fv%D`|f%Vp%c5!d8Sztb6S_zXuo(24XurpqEC&qy5&<(*|)~V8D z^&+|q*d52(9ghsGg%8J60gwjr(j$k$KR@&uR67%80-DSv^%r$*cv5g;-rXZ7CkzYW;P}755?SaCQLI^>X7oV1*1u663D<&E Is#u2o2ZTDv=Kufz literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4d5aafe974..8b340596dc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4916,6 +4916,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_join_title" = "Group Call"; "lng_confcall_join_text" = "You are invited to join a Telegram Call."; +"lng_confcall_join_text_inviter" = "{user} is inviting you to join a Telegram Call."; +"lng_confcall_already_joined_one" = "{user} already joined this call."; +"lng_confcall_already_joined_two" = "{user} and {other} already joined this call."; +"lng_confcall_already_joined_three" = "{user}, {other} and {third} already joined this call."; +"lng_confcall_already_joined_many#one" = "{user}, {other} and **{count}** other person already joined this call."; +"lng_confcall_already_joined_many#other" = "{user}, {other} and **{count}** other people already joined this call."; "lng_confcall_join_button" = "Join Group Call"; "lng_confcall_create_link" = "Create Call Link"; "lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index f77b417432..1e3b7f740d 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -24,6 +24,12 @@ UserpicButton { uploadIcon: icon; uploadIconPosition: point; } +UserpicsRow { + button: UserpicButton; + shift: pixels; + stroke: pixels; + invert: bool; +} ShortInfoBox { label: FlatLabel; labeled: FlatLabel; diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index ad0bb57e77..25853e0f85 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -179,6 +179,7 @@ void FillUpgradeToPremiumCover( CreateUserpicsWithMoreBadge( container, rpl::single(std::move(userpicPeers)), + st::boostReplaceUserpicsRow, kUserpicsLimit), st::inviteForbiddenUserpicsPadding) )->entity()->setAttribute(Qt::WA_TransparentForMouseEvents); diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 634e4a2480..f132e82e94 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -550,13 +550,13 @@ object_ptr CreateUserpicsTransfer( rpl::variable count = 0; bool painting = false; }; - const auto full = st::boostReplaceUserpic.size.height() + const auto st = &st::boostReplaceUserpicsRow; + const auto full = st->button.size.height() + st::boostReplaceIconAdd.y() + st::lineWidth; auto result = object_ptr(parent, full); const auto raw = result.data(); - const auto &st = st::boostReplaceUserpic; - const auto right = CreateChild(raw, to, st); + const auto right = CreateChild(raw, to, st->button); const auto overlay = CreateChild(raw); const auto state = raw->lifetime().make_state(); @@ -564,7 +564,6 @@ object_ptr CreateUserpicsTransfer( from ) | rpl::start_with_next([=]( const std::vector> &list) { - const auto &st = st::boostReplaceUserpic; auto was = base::take(state->from); auto buttons = base::take(state->buttons); state->from.reserve(list.size()); @@ -578,7 +577,7 @@ object_ptr CreateUserpicsTransfer( state->buttons.push_back(std::move(buttons[index])); } else { state->buttons.push_back( - std::make_unique(raw, peer, st)); + std::make_unique(raw, peer, st->button)); const auto raw = state->buttons.back().get(); base::install_event_filter(raw, [=](not_null e) { return (e->type() == QEvent::Paint && !state->painting) @@ -598,7 +597,7 @@ object_ptr CreateUserpicsTransfer( const auto skip = st::boostReplaceUserpicsSkip; const auto left = width - 2 * right->width() - skip; const auto shift = std::min( - st::boostReplaceUserpicsShift, + st->shift, (count > 1 ? (left / (count - 1)) : width)); const auto total = right->width() + (count ? (skip + right->width() + (count - 1) * shift) : 0); @@ -630,7 +629,7 @@ object_ptr CreateUserpicsTransfer( auto q = QPainter(&state->layer); auto hq = PainterHighQualityEnabler(q); - const auto stroke = st::boostReplaceIconOutline; + const auto stroke = st->stroke; const auto half = stroke / 2.; auto pen = st::windowBg->p; pen.setWidthF(stroke * 2.); @@ -684,6 +683,7 @@ object_ptr CreateUserpicsTransfer( object_ptr CreateUserpicsWithMoreBadge( not_null parent, rpl::producer>> peers, + const style::UserpicsRow &st, int limit) { struct State { std::vector> from; @@ -693,7 +693,7 @@ object_ptr CreateUserpicsWithMoreBadge( rpl::variable count = 0; bool painting = false; }; - const auto full = st::boostReplaceUserpic.size.height() + const auto full = st.button.size.height() + st::boostReplaceIconAdd.y() + st::lineWidth; auto result = object_ptr(parent, full); @@ -703,9 +703,8 @@ object_ptr CreateUserpicsWithMoreBadge( const auto state = raw->lifetime().make_state(); std::move( peers - ) | rpl::start_with_next([=]( + ) | rpl::start_with_next([=, &st]( const std::vector> &list) { - const auto &st = st::boostReplaceUserpic; auto was = base::take(state->from); auto buttons = base::take(state->buttons); state->from.reserve(list.size()); @@ -719,7 +718,7 @@ object_ptr CreateUserpicsWithMoreBadge( state->buttons.push_back(std::move(buttons[index])); } else { state->buttons.push_back( - std::make_unique(raw, peer, st)); + std::make_unique(raw, peer, st.button)); const auto raw = state->buttons.back().get(); base::install_event_filter(raw, [=](not_null e) { return (e->type() == QEvent::Paint && !state->painting) @@ -735,13 +734,12 @@ object_ptr CreateUserpicsWithMoreBadge( rpl::combine( raw->widthValue(), state->count.value() - ) | rpl::start_with_next([=](int width, int count) { - const auto &st = st::boostReplaceUserpic; - const auto single = st.size.width(); + ) | rpl::start_with_next([=, &st](int width, int count) { + const auto single = st.button.size.width(); const auto left = width - single; const auto used = std::min(count, int(state->buttons.size())); const auto shift = std::min( - st::boostReplaceUserpicsShift, + st.shift, (used > 1 ? (left / (used - 1)) : width)); const auto total = used ? (single + (used - 1) * shift) : 0; auto x = (width - total) / 2; @@ -755,7 +753,7 @@ object_ptr CreateUserpicsWithMoreBadge( overlay->paintRequest( ) | rpl::filter([=] { return !state->buttons.empty(); - }) | rpl::start_with_next([=] { + }) | rpl::start_with_next([=, &st] { const auto outerw = overlay->width(); const auto ratio = style::DevicePixelRatio(); if (state->layer.size() != QSize(outerw, full) * ratio) { @@ -768,17 +766,26 @@ object_ptr CreateUserpicsWithMoreBadge( auto q = QPainter(&state->layer); auto hq = PainterHighQualityEnabler(q); - const auto stroke = st::boostReplaceIconOutline; + const auto stroke = st.stroke; const auto half = stroke / 2.; auto pen = st::windowBg->p; pen.setWidthF(stroke * 2.); state->painting = true; - for (const auto &button : state->buttons) { + const auto paintOne = [&](not_null button) { q.setPen(pen); q.setBrush(Qt::NoBrush); q.drawEllipse(button->geometry()); const auto position = button->pos(); button->render(&q, position, QRegion(), QWidget::DrawChildren); + }; + if (st.invert) { + for (const auto &button : ranges::views::reverse(state->buttons)) { + paintOne(button.get()); + } + } else { + for (const auto &button : state->buttons) { + paintOne(button.get()); + } } state->painting = false; const auto last = state->buttons.back().get(); diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h index 6e622479dc..71f5bab64d 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" +namespace style { +struct UserpicsRow; +} // namespace style + class ChannelData; namespace Main { @@ -66,4 +70,5 @@ enum class UserpicsTransferType { [[nodiscard]] object_ptr CreateUserpicsWithMoreBadge( not_null parent, rpl::producer>> peers, + const style::UserpicsRow &st, int limit); diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 89fc1a1da0..3fbdecc1cc 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1527,6 +1527,22 @@ confcallLinkFooterOr: FlatLabel(confcallLinkCenteredText) { confcallLinkFooterOrTop: 12px; confcallLinkFooterOrSkip: 8px; confcallLinkFooterOrLineTop: 9px; +confcallJoinBox: Box(confcallLinkBox) { + buttonPadding: margins(24px, 24px, 24px, 24px); +} +confcallJoinLogo: icon {{ "calls/group_call_logo", windowFgActive }}; +confcallJoinLogoPadding: margins(8px, 8px, 8px, 8px); +confcallJoinSepPadding: margins(0px, 8px, 0px, 8px); +confcallJoinUserpics: UserpicsRow { + button: UserpicButton(defaultUserpicButton) { + size: size(36px, 36px); + photoSize: 36px; + } + shift: 16px; + stroke: 2px; + invert: true; +} +confcallJoinUserpicsPadding: margins(0px, 0px, 0px, 16px); groupCallLinkBox: Box(confcallLinkBox) { bg: groupCallMembersBg; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index f0dbb9628f..e1ac887e00 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/platform/base_platform_info.h" #include "base/random.h" +#include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge #include "boxes/share_box.h" #include "calls/calls_instance.h" #include "core/application.h" @@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/painter.h" #include "ui/vertical_list.h" #include "lang/lang_keys.h" #include "main/main_session.h" @@ -90,25 +92,144 @@ object_ptr ScreenSharingPrivacyRequestBox() { void ConferenceCallJoinConfirm( not_null box, std::shared_ptr call, + UserData *maybeInviter, Fn join) { - box->setTitle(tr::lng_confcall_join_title()); + box->setStyle(st::confcallJoinBox); + box->setWidth(st::boxWideWidth); + box->setNoContentMargin(true); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + const auto logoSize = st::confcallJoinLogo.size(); + const auto logoOuter = logoSize.grownBy(st::confcallJoinLogoPadding); + const auto logo = box->addRow( + object_ptr(box), + st::boxRowPadding + st::confcallLinkHeaderIconPadding); + logo->resize(logo->width(), logoOuter.height()); + logo->paintRequest() | rpl::start_with_next([=] { + if (logo->width() < logoOuter.width()) { + return; + } + auto p = QPainter(logo); + auto hq = PainterHighQualityEnabler(p); + const auto x = (logo->width() - logoOuter.width()) / 2; + const auto outer = QRect(QPoint(x, 0), logoOuter); + p.setBrush(st::windowBgActive); + p.setPen(Qt::NoPen); + p.drawEllipse(outer); + st::confcallJoinLogo.paintInCenter(p, outer); + }, logo->lifetime()); + + box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_confcall_join_title(), + st::boxTitle)), + st::boxRowPadding + st::confcallLinkTitlePadding); + const auto wrapName = [&](not_null peer) { + return rpl::single(Ui::Text::Bold(peer->shortName())); + }; box->addRow( object_ptr( box, - tr::lng_confcall_join_text(), - st::boxLabel)); + (maybeInviter + ? tr::lng_confcall_join_text_inviter( + lt_user, + wrapName(maybeInviter), + Ui::Text::RichLangValue) + : tr::lng_confcall_join_text(Ui::Text::RichLangValue)), + st::confcallLinkCenteredText), + st::boxRowPadding + )->setTryMakeSimilarLines(true); - box->addButton(tr::lng_confcall_join_button(), [=] { + const auto &participants = call->participants(); + const auto known = int(participants.size()); + if (known) { + const auto sep = box->addRow( + object_ptr(box), + st::boxRowPadding + st::confcallJoinSepPadding); + sep->resize(sep->width(), st::normalFont->height); + sep->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(sep); + const auto line = st::lineWidth; + const auto top = st::confcallLinkFooterOrLineTop; + const auto fg = st::windowSubTextFg->b; + p.setOpacity(0.2); + p.fillRect(0, top, sep->width(), line, fg); + }, sep->lifetime()); + + auto peers = std::vector>(); + for (const auto &participant : participants) { + peers.push_back(participant.peer); + if (peers.size() == 3) { + break; + } + } + box->addRow( + CreateUserpicsWithMoreBadge( + box, + rpl::single(peers), + st::confcallJoinUserpics, + known), + st::boxRowPadding + st::confcallJoinUserpicsPadding); + + const auto wrapByIndex = [&](int index) { + Expects(index >= 0 && index < known); + + return wrapName(participants[index].peer); + }; + auto text = (known == 1) + ? tr::lng_confcall_already_joined_one( + lt_user, + wrapByIndex(0), + Ui::Text::RichLangValue) + : (known == 2) + ? tr::lng_confcall_already_joined_two( + lt_user, + wrapByIndex(0), + lt_other, + wrapByIndex(1), + Ui::Text::RichLangValue) + : (known == 3) + ? tr::lng_confcall_already_joined_three( + lt_user, + wrapByIndex(0), + lt_other, + wrapByIndex(1), + lt_third, + wrapByIndex(2), + Ui::Text::RichLangValue) + : tr::lng_confcall_already_joined_many( + lt_count, + rpl::single(1. * (std::max(known, call->fullCount()) - 2)), + lt_user, + wrapByIndex(0), + lt_other, + wrapByIndex(1), + Ui::Text::RichLangValue); + box->addRow( + object_ptr( + box, + std::move(text), + st::confcallLinkCenteredText), + st::boxRowPadding + )->setTryMakeSimilarLines(true); + } + const auto joinAndClose = [=] { const auto weak = Ui::MakeWeak(box); join(); if (const auto strong = weak.data()) { strong->closeBox(); } - }); - box->addButton(tr::lng_cancel(), [=] { - box->closeBox(); - }); + }; + Info::BotStarRef::AddFullWidthButton( + box, + tr::lng_confcall_join_button(), + joinAndClose, + &st::confcallLinkButton); } ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle() { diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 03c0770d47..0d40b1f14d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -129,6 +129,7 @@ using StickedTooltips = base::flags; void ConferenceCallJoinConfirm( not_null box, std::shared_ptr call, + UserData *maybeInviter, Fn join); struct ConferenceCallLinkStyleOverrides { diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index f689603ec9..9092ba251b 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -1459,8 +1459,9 @@ bool ResolveConferenceCall( if (slug.isEmpty()) { return false; } + const auto myContext = context.value(); controller->window().activate(); - controller->resolveConferenceCall(match->captured(1)); + controller->resolveConferenceCall(match->captured(1), myContext.itemId); return true; } } // namespace diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.cpp b/Telegram/SourceFiles/history/view/media/history_view_call.cpp index d136b733d4..cd12e9bc45 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_call.cpp @@ -65,13 +65,15 @@ QSize Call::countOptimalSize() { const auto user = _parent->history()->peer->asUser(); const auto conference = _conference; const auto video = _video; + const auto contextId = _parent->data()->fullId(); const auto id = _parent->data()->id; _link = std::make_shared([=](ClickContext context) { if (conference) { const auto my = context.other.value(); const auto weak = my.sessionWindow; if (const auto strong = weak.get()) { - strong->resolveConferenceCall(id); + QSize(); + strong->resolveConferenceCall(id, contextId); } } else if (user) { Core::App().calls().startOutgoingCall(user, video); diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h index d69864bf15..a48586b1be 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h @@ -55,7 +55,7 @@ using ConnectedBots = std::vector; rpl::producer subtitle, bool newBadge = false); -[[nodiscard]] not_null AddFullWidthButton( +not_null AddFullWidthButton( not_null box, rpl::producer text, Fn callback = nullptr, diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 261beffafa..daeece6ce2 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -311,6 +311,11 @@ boostReplaceIconSkip: 3px; boostReplaceIconOutline: 2px; boostReplaceIconAdd: point(4px, 2px); boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }}; +boostReplaceUserpicsRow: UserpicsRow { + button: boostReplaceUserpic; + shift: boostReplaceUserpicsShift; + stroke: boostReplaceIconOutline; +} showOrIconLastSeen: icon{{ "settings/premium/large_lastseen", windowFgActive }}; showOrIconReadTime: icon{{ "settings/premium/large_readtime", windowFgActive }}; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index c689c9e9aa..c2e69511ae 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -842,21 +842,21 @@ void SessionNavigation::resolveCollectible( void SessionNavigation::resolveConferenceCall( QString slug, - Fn finished) { - resolveConferenceCall(std::move(slug), 0, std::move(finished)); + FullMsgId contextId) { + resolveConferenceCall(std::move(slug), 0, contextId); } void SessionNavigation::resolveConferenceCall( MsgId inviteMsgId, - Fn finished) { - resolveConferenceCall({}, inviteMsgId, std::move(finished)); + FullMsgId contextId) { + resolveConferenceCall({}, inviteMsgId, contextId); } void SessionNavigation::resolveConferenceCall( QString slug, MsgId inviteMsgId, - Fn finished) { - _conferenceCallResolveFinished = std::move(finished); + FullMsgId contextId) { + _conferenceCallResolveContextId = contextId; if (_conferenceCallSlug == slug && _conferenceCallInviteMsgId == inviteMsgId) { return; @@ -875,7 +875,7 @@ void SessionNavigation::resolveConferenceCall( _conferenceCallRequestId = 0; const auto slug = base::take(_conferenceCallSlug); const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); - const auto finished = base::take(_conferenceCallResolveFinished); + const auto contextId = base::take(_conferenceCallResolveContextId); result.data().vcall().match([&](const auto &data) { const auto call = session().data().sharedConferenceCall( data.vid().v, @@ -888,22 +888,21 @@ void SessionNavigation::resolveConferenceCall( .joinMessageId = inviteMsgId, }); }; + const auto context = session().data().message(contextId); + const auto inviter = context + ? context->from()->asUser() + : nullptr; uiShow()->show(Box( Calls::Group::ConferenceCallJoinConfirm, call, + (inviter && !inviter->isSelf()) ? inviter : nullptr, join)); - if (finished) { - finished(true); - } }); }).fail([=] { _conferenceCallRequestId = 0; _conferenceCallSlug = QString(); - const auto finished = base::take(_conferenceCallResolveFinished); + _conferenceCallResolveContextId = FullMsgId(); showToast(tr::lng_group_invite_bad_link(tr::now)); - if (finished) { - finished(false); - } }).send(); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index ceb30f1bd3..040739589d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -266,10 +266,10 @@ public: Fn fail = nullptr); void resolveConferenceCall( QString slug, - Fn finished = nullptr); + FullMsgId contextId); void resolveConferenceCall( MsgId inviteMsgId, - Fn finished = nullptr); + FullMsgId contextId); base::weak_ptr showToast( Ui::Toast::Config &&config); @@ -299,7 +299,7 @@ private: void resolveConferenceCall( QString slug, MsgId inviteMsgId, - Fn finished); + FullMsgId contextId); void resolveDone( const MTPcontacts_ResolvedPeer &result, @@ -341,7 +341,7 @@ private: QString _conferenceCallSlug; MsgId _conferenceCallInviteMsgId; - Fn _conferenceCallResolveFinished; + FullMsgId _conferenceCallResolveContextId; mtpRequestId _conferenceCallRequestId = 0; }; From c5531b1bd80123a6140622fcfe328bbc94932857 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Apr 2025 16:09:23 +0500 Subject: [PATCH 100/190] Implement audio/video confcall invitations. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/calls/calls.style | 20 ++ Telegram/SourceFiles/calls/calls_call.cpp | 8 +- Telegram/SourceFiles/calls/calls_call.h | 3 +- Telegram/SourceFiles/calls/calls_instance.cpp | 4 +- Telegram/SourceFiles/calls/calls_instance.h | 3 +- Telegram/SourceFiles/calls/calls_panel.cpp | 13 +- .../calls/group/calls_group_call.cpp | 13 +- .../calls/group/calls_group_call.h | 12 +- .../calls/group/calls_group_common.h | 18 +- .../group/calls_group_invite_controller.cpp | 231 ++++++++++++++++-- .../group/calls_group_invite_controller.h | 3 +- .../calls/group/calls_group_panel.cpp | 4 +- .../calls/group/calls_group_panel.h | 6 +- .../SourceFiles/data/data_media_types.cpp | 1 + 15 files changed, 293 insertions(+), 47 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8b340596dc..e5b20fb088 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4642,6 +4642,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_status_failed" = "failed to connect"; "lng_call_status_ringing" = "ringing..."; "lng_call_status_busy" = "line busy"; +"lng_call_status_group_invite" = "Telegram Group Call"; "lng_call_status_sure" = "Click on the Camera icon if you want to start a video call."; "lng_call_fingerprint_tooltip" = "If the emoji on {user}'s screen are the same, this call is 100% secure"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 3fbdecc1cc..1119b6d69e 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1543,6 +1543,26 @@ confcallJoinUserpics: UserpicsRow { invert: true; } confcallJoinUserpicsPadding: margins(0px, 0px, 0px, 16px); +confcallInviteVideo: IconButton { + width: 36px; + height: 52px; + + icon: icon {{ "info/info_media_video", groupCallMemberInactiveIcon }}; + iconOver: icon {{ "info/info_media_video", groupCallMemberInactiveIcon }}; + iconPosition: point(-1px, -1px); + + ripple: groupCallRipple; + rippleAreaPosition: point(0px, 8px); + rippleAreaSize: 36px; +} +confcallInviteVideoActive: icon {{ "info/info_media_video", groupCallActiveFg }}; +confcallInviteVideoMargins: margins(0px, 0px, 10px, 0px); +confcallInviteAudio: IconButton(confcallInviteVideo) { + icon: icon {{ "menu/phone", groupCallMemberInactiveIcon }}; + iconOver: icon {{ "menu/phone", groupCallMemberInactiveIcon }}; +} +confcallInviteAudioActive: icon {{ "menu/phone", groupCallActiveFg }}; +confcallInviteAudioMargins: margins(0px, 0px, 4px, 0px); groupCallLinkBox: Box(confcallLinkBox) { bg: groupCallMembersBg; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 4f81f6f37f..32beb069bd 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/random.h" #include "boxes/abstract_box.h" +#include "calls/group/calls_group_common.h" #include "calls/calls_instance.h" #include "calls/calls_panel.h" #include "core/application.h" @@ -251,7 +252,8 @@ Call::Call( not_null delegate, not_null user, CallId conferenceId, - MsgId conferenceInviteMsgId) + MsgId conferenceInviteMsgId, + bool video) : _delegate(delegate) , _user(user) , _api(&_user->session().mtp()) @@ -279,10 +281,10 @@ Call::Call( , _conferenceInviteMsgId(conferenceInviteMsgId) , _videoIncoming( std::make_unique( - StartVideoState(false))) + StartVideoState(video))) , _videoOutgoing( std::make_unique( - StartVideoState(false))) { + StartVideoState(video))) { startWaitingTrack(); setupOutgoingVideo(); } diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 6be26dd259..6483db2fe4 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -106,7 +106,8 @@ public: not_null delegate, not_null user, CallId conferenceId, - MsgId conferenceInviteMsgId); + MsgId conferenceInviteMsgId, + bool video); [[nodiscard]] Type type() const { return _type; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index f3e9365fd3..c5f8a8c3a9 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -1010,6 +1010,7 @@ void Instance::showConferenceInvite( const auto media = item ? item->media() : nullptr; const auto call = media ? media->call() : nullptr; const auto conferenceId = call ? call->conferenceId : 0; + const auto video = call->video; if (!conferenceId || call->state != Data::CallState::Invitation || user->isSelf()) { @@ -1036,7 +1037,8 @@ void Instance::showConferenceInvite( delegate, user, conferenceId, - conferenceInviteMsgId); + conferenceInviteMsgId, + video); const auto raw = call.get(); user->session().account().sessionChanges( diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 9936c69c92..1c46af7a77 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -56,6 +56,7 @@ enum class CallType; class GroupCall; class Panel; struct DhConfig; +struct InviteRequest; struct StartGroupCallArgs { enum class JoinConfirm { @@ -73,7 +74,7 @@ struct StartConferenceCallArgs { std::shared_ptr e2e; QString linkSlug; MsgId joinMessageId; - std::vector> invite; + std::vector invite; bool migrating = false; }; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 82190632a0..23fe1975e9 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -467,7 +467,7 @@ void Panel::initControls() { *creating = false; } }; - const auto create = [=](std::vector> users) { + const auto create = [=](std::vector users) { if (*creating) { return; } @@ -481,7 +481,7 @@ void Panel::initControls() { }); }; const auto invite = crl::guard(call, [=]( - std::vector> users) { + std::vector users) { create(std::move(users)); }); const auto share = crl::guard(call, [=] { @@ -1285,7 +1285,9 @@ void Panel::paint(QRect clip) { bool Panel::handleClose() const { if (_call) { if (_call->state() == Call::State::WaitingUserConfirmation - || _call->state() == Call::State::Busy) { + || _call->state() == Call::State::Busy + || _call->state() == Call::State::Starting + || _call->state() == Call::State::WaitingIncoming) { _call->hangup(); } else { window()->hide(); @@ -1424,7 +1426,10 @@ void Panel::updateStatusText(State state) { case State::ExchangingKeys: return tr::lng_call_status_exchanging(tr::now); case State::Waiting: return tr::lng_call_status_waiting(tr::now); case State::Requesting: return tr::lng_call_status_requesting(tr::now); - case State::WaitingIncoming: return tr::lng_call_status_incoming(tr::now); + case State::WaitingIncoming: + return (_call->conferenceInvite() + ? tr::lng_call_status_group_invite(tr::now) + : tr::lng_call_status_incoming(tr::now)); case State::Ringing: return tr::lng_call_status_ringing(tr::now); case State::Busy: return tr::lng_call_status_busy(tr::now); case State::WaitingUserConfirmation: return tr::lng_call_status_sure(tr::now); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 057177db83..832b5e8fad 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -3712,7 +3712,7 @@ void GroupCall::editParticipant( } void GroupCall::inviteUsers( - const std::vector> &users, + const std::vector &requests, Fn done) { const auto real = lookupReal(); if (!real) { @@ -3737,9 +3737,11 @@ void GroupCall::inviteUsers( }; if (const auto call = _conferenceCall.get()) { - for (const auto &user : users) { + for (const auto &request : requests) { + using Flag = MTPphone_InviteConferenceCallParticipant::Flag; + const auto user = request.user; _api.request(MTPphone_InviteConferenceCallParticipant( - MTP_flags(0), + MTP_flags(request.video ? Flag::f_video : Flag()), inputCallSafe(), user->inputUser )).done([=](const MTPUpdates &result) { @@ -3785,7 +3787,8 @@ void GroupCall::inviteUsers( slice.clear(); usersSlice.clear(); }; - for (const auto &user : users) { + for (const auto &request : requests) { + const auto user = request.user; owner->registerInvitedToCallUser(_id, _peer, user); usersSlice.push_back(user); slice.push_back(user->inputUser); @@ -3920,7 +3923,7 @@ void GroupCall::destroyScreencast() { } TextWithEntities ComposeInviteResultToast( - const GroupCall::InviteResult &result) { + const InviteResult &result) { auto text = TextWithEntities(); const auto invited = int(result.invited.size()); const auto restricted = int(result.privacyRestricted.size()); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index f30abd8b17..2b5be388fb 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -60,6 +60,9 @@ enum class VideoQuality; enum class Error; } // namespace Group +struct InviteRequest; +struct InviteResult; + enum class MuteState { Active, PushToTalk, @@ -418,13 +421,8 @@ public: void toggleMute(const Group::MuteRequest &data); void changeVolume(const Group::VolumeRequest &data); - struct InviteResult { - std::vector> invited; - std::vector> alreadyIn; - std::vector> privacyRestricted; - }; void inviteUsers( - const std::vector> &users, + const std::vector &requests, Fn done); std::shared_ptr ensureGlobalShortcutManager(); @@ -754,6 +752,6 @@ private: }; [[nodiscard]] TextWithEntities ComposeInviteResultToast( - const GroupCall::InviteResult &result); + const InviteResult &result); } // namespace Calls diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 0d40b1f14d..63ab74dd89 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -41,6 +41,20 @@ namespace Window { class SessionController; } // namespace Window +namespace Calls { + +struct InviteRequest { + not_null user; + bool video = false; +}; +struct InviteResult { + std::vector> invited; + std::vector> alreadyIn; + std::vector> privacyRestricted; +}; + +} // namespace Calls + namespace Calls::Group { constexpr auto kDefaultVolume = 10000; @@ -146,7 +160,7 @@ struct ConferenceCallLinkArgs { bool joining = false; bool migrating = false; Fn finished; - std::vector> invite; + std::vector invite; ConferenceCallLinkStyleOverrides st; }; void ShowConferenceCallLinkBox( @@ -163,7 +177,7 @@ void ExportConferenceCallLink( struct ConferenceFactoryArgs { std::shared_ptr show; Fn finished; - std::vector> invite; + std::vector invite; bool joining = false; bool migrating = false; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index c8b2601270..05477bd8e8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_participants.h" #include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_common.h" #include "calls/group/calls_group_menu.h" #include "calls/calls_call.h" #include "boxes/peer_lists_box.h" @@ -19,10 +20,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_icon.h" #include "main/main_session.h" #include "main/session/session_show.h" +#include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/layers/generic_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/painter.h" #include "apiwrap.h" #include "lang/lang_keys.h" #include "styles/style_boxes.h" // membersMarginTop @@ -61,6 +64,36 @@ namespace { return result; } +class ConfInviteRow final : public PeerListRow { +public: + using PeerListRow::PeerListRow; + + void setAlreadyIn(bool alreadyIn); + void setVideo(bool video); + + int elementsCount() const override; + QRect elementGeometry(int element, int outerWidth) const override; + bool elementDisabled(int element) const override; + bool elementOnlySelect(int element) const override; + void elementAddRipple( + int element, + QPoint point, + Fn updateCallback) override; + void elementsStopLastRipple() override; + void elementsPaint( + Painter &p, + int outerWidth, + bool selected, + int selectedElement) override; + +private: + std::unique_ptr _videoRipple; + std::unique_ptr _audioRipple; + bool _alreadyIn = false; + bool _video = false; + +}; + class ConfInviteController final : public ContactsBoxController { public: ConfInviteController( @@ -69,6 +102,8 @@ public: Fn shareLink); [[nodiscard]] rpl::producer hasSelectedValue() const; + [[nodiscard]] std::vector requests( + const std::vector> &peers) const; protected: void prepareViewHook() override; @@ -77,16 +112,132 @@ protected: not_null user) override; void rowClicked(not_null row) override; + void rowElementClicked(not_null row, int element) override; private: [[nodiscard]] int fullCount() const; + void toggleRowSelected(not_null row, bool video); const base::flat_set> _alreadyIn; const Fn _shareLink; rpl::variable _hasSelected; + base::flat_set> _withVideo; + bool _lastSelectWithVideo = false; }; +void ConfInviteRow::setAlreadyIn(bool alreadyIn) { + _alreadyIn = alreadyIn; + setDisabledState(alreadyIn ? State::DisabledChecked : State::Active); +} + +void ConfInviteRow::setVideo(bool video) { + _video = video; +} + +int ConfInviteRow::elementsCount() const { + return _alreadyIn ? 0 : 2; +} + +QRect ConfInviteRow::elementGeometry(int element, int outerWidth) const { + if (_alreadyIn || (element != 1 && element != 2)) { + return QRect(); + } + const auto &st = (element == 1) + ? st::confcallInviteVideo + : st::confcallInviteAudio; + const auto size = QSize(st.width, st.height); + const auto margins = (element == 1) + ? st::confcallInviteVideoMargins + : st::confcallInviteAudioMargins; + const auto right = margins.right(); + const auto top = margins.top(); + const auto side = (element == 1) + ? outerWidth + : elementGeometry(1, outerWidth).x(); + const auto left = side - right - size.width(); + return QRect(QPoint(left, top), size); +} + +bool ConfInviteRow::elementDisabled(int element) const { + return _alreadyIn + || (checked() + && ((_video && element == 1) || (!_video && element == 2))); +} + +bool ConfInviteRow::elementOnlySelect(int element) const { + return false; +} + +void ConfInviteRow::elementAddRipple( + int element, + QPoint point, + Fn updateCallback) { + if (_alreadyIn || (element != 1 && element != 2)) { + return; + } + auto &ripple = (element == 1) ? _videoRipple : _audioRipple; + const auto &st = (element == 1) + ? st::confcallInviteVideo + : st::confcallInviteAudio; + if (!ripple) { + auto mask = Ui::RippleAnimation::EllipseMask(QSize( + st.rippleAreaSize, + st.rippleAreaSize)); + ripple = std::make_unique( + st.ripple, + std::move(mask), + std::move(updateCallback)); + } + ripple->add(point - st.rippleAreaPosition); +} + +void ConfInviteRow::elementsStopLastRipple() { + if (_videoRipple) { + _videoRipple->lastStop(); + } + if (_audioRipple) { + _audioRipple->lastStop(); + } +} + +void ConfInviteRow::elementsPaint( + Painter &p, + int outerWidth, + bool selected, + int selectedElement) { + if (_alreadyIn) { + return; + } + const auto paintElement = [&](int element) { + const auto &st = (element == 1) + ? st::confcallInviteVideo + : st::confcallInviteAudio; + auto &ripple = (element == 1) ? _videoRipple : _audioRipple; + const auto active = checked() && ((element == 1) ? _video : !_video); + const auto geometry = elementGeometry(element, outerWidth); + if (ripple) { + ripple->paint( + p, + geometry.x() + st.rippleAreaPosition.x(), + geometry.y() + st.rippleAreaPosition.y(), + outerWidth); + if (ripple->empty()) { + ripple.reset(); + } + } + const auto selected = (element == selectedElement); + const auto &icon = active + ? (element == 1 + ? st::confcallInviteVideoActive + : st::confcallInviteAudioActive) + : (selected ? st.iconOver : st.icon); + icon.paintInCenter(p, geometry); + }; + paintElement(1); + paintElement(2); +} + ConfInviteController::ConfInviteController( not_null session, base::flat_set> alreadyIn, @@ -100,6 +251,18 @@ rpl::producer ConfInviteController::hasSelectedValue() const { return _hasSelected.value(); } +std::vector ConfInviteController::requests( + const std::vector> &peers) const { + auto result = std::vector(); + result.reserve(peers.size()); + for (const auto &peer : peers) { + if (const auto user = peer->asUser()) { + result.push_back({ user, _withVideo.contains(user) }); + } + } + return result; +} + std::unique_ptr ConfInviteController::createRow( not_null user) { if (user->isSelf() @@ -108,9 +271,12 @@ std::unique_ptr ConfInviteController::createRow( || user->isInaccessible()) { return nullptr; } - auto result = ContactsBoxController::createRow(user); + auto result = std::make_unique(user); if (_alreadyIn.contains(user)) { - result->setDisabledState(PeerListRow::State::DisabledChecked); + result->setAlreadyIn(true); + } + if (_withVideo.contains(user)) { + result->setVideo(true); } return result; } @@ -120,10 +286,42 @@ int ConfInviteController::fullCount() const { } void ConfInviteController::rowClicked(not_null row) { + toggleRowSelected(row, _lastSelectWithVideo); +} + +void ConfInviteController::rowElementClicked( + not_null row, + int element) { + if (row->checked()) { + static_cast(row.get())->setVideo(element == 1); + _lastSelectWithVideo = (element == 1); + } else if (element == 1) { + toggleRowSelected(row, true); + } else if (element == 2) { + toggleRowSelected(row, false); + } +} + +void ConfInviteController::toggleRowSelected( + not_null row, + bool video) { auto count = fullCount(); auto limit = Data::kMaxConferenceMembers; if (count < limit || row->checked()) { + const auto real = static_cast(row.get()); + if (!row->checked()) { + real->setVideo(video); + _lastSelectWithVideo = video; + } + const auto user = row->peer()->asUser(); + if (!row->checked() && video) { + _withVideo.emplace(user); + } else { + _withVideo.remove(user); + } delegate()->peerListSetRowChecked(row, !row->checked()); + + // row may have been destroyed here, from search. _hasSelected = (delegate()->peerListSelectedRowsCount() > 0); } else { delegate()->peerListUiShow()->showToast( @@ -311,13 +509,7 @@ object_ptr PrepareInviteBox( if (!call) { return; } - auto peers = box->collectSelectedRows(); - auto users = ranges::views::all( - peers - ) | ranges::views::transform([](not_null peer) { - return not_null(peer->asUser()); - }) | ranges::to_vector; - const auto done = [=](GroupCall::InviteResult result) { + const auto done = [=](InviteResult result) { (*close)(); if (result.invited.empty() && result.privacyRestricted.empty()) { @@ -325,7 +517,9 @@ object_ptr PrepareInviteBox( } showToast({ ComposeInviteResultToast(result) }); }; - call->inviteUsers(users, done); + call->inviteUsers( + raw->requests(box->collectSelectedRows()), + done); }); } box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); @@ -354,7 +548,12 @@ object_ptr PrepareInviteBox( if (!call) { return; } - call->inviteUsers(users, [=](GroupCall::InviteResult result) { + auto requests = ranges::views::all( + users + ) | ranges::views::transform([](not_null user) { + return InviteRequest{ user }; + }) | ranges::to_vector; + call->inviteUsers(std::move(requests), [=](InviteResult result) { if (result.invited.size() == 1) { showToast(tr::lng_group_call_invite_done_user( tr::now, @@ -463,7 +662,7 @@ object_ptr PrepareInviteBox( object_ptr PrepareInviteBox( not_null call, - Fn>)> inviteUsers, + Fn)> inviteUsers, Fn shareLink) { const auto user = call->user(); const auto weak = base::make_weak(call); @@ -486,13 +685,7 @@ object_ptr PrepareInviteBox( if (!call) { return; } - auto peers = box->collectSelectedRows(); - auto users = ranges::views::all( - peers - ) | ranges::views::transform([](not_null peer) { - return not_null(peer->asUser()); - }) | ranges::to_vector; - inviteUsers(std::move(users)); + inviteUsers(raw->requests(box->collectSelectedRows())); }); } box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h index b41fbfba67..8c136d19f7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Calls { class Call; class GroupCall; +struct InviteRequest; } // namespace Calls namespace Calls::Group { @@ -83,7 +84,7 @@ private: [[nodiscard]] object_ptr PrepareInviteBox( not_null call, - Fn>)> inviteUsers, + Fn)> inviteUsers, Fn shareLink); } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 7e3fe18dbb..418d13943c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -1006,8 +1006,8 @@ void Panel::migrationShowShareLink() { { .st = DarkConferenceCallLinkStyle() }); } -void Panel::migrationInviteUsers(std::vector> users) { - const auto done = [=](GroupCall::InviteResult result) { +void Panel::migrationInviteUsers(std::vector users) { + const auto done = [=](InviteResult result) { showToast({ ComposeInviteResultToast(result) }); }; _call->inviteUsers(std::move(users), crl::guard(this, done)); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 2af708af80..86108ca4b8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -73,6 +73,10 @@ struct CallSignalBars; struct CallBodyLayout; } // namespace style +namespace Calls { +struct InviteRequest; +} // namespace Calls + namespace Calls::Group { class Toasts; @@ -116,7 +120,7 @@ public: [[nodiscard]] bool isLayerShown() const; void migrationShowShareLink(); - void migrationInviteUsers(std::vector> users); + void migrationInviteUsers(std::vector users); void minimize(); void toggleFullScreen(); diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 8ef7f28c59..969cbd2340 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -491,6 +491,7 @@ Call ComputeCallData(const MTPDmessageActionConferenceCall &call) { : call.is_active() ? CallState::Active : CallState::Invitation), + .video = call.is_video(), }; } From aaa37a3e0d88da2a6a70a9f6829139a13369c678 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Apr 2025 19:09:43 +0500 Subject: [PATCH 101/190] Update API scheme on layer 202. --- Telegram/SourceFiles/mtproto/scheme/api.tl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 51e38326f9..a8a7efaa62 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1489,6 +1489,7 @@ inputInvoiceStarGift#e8625e92 flags:# hide_name:flags.0?true include_upgrade:fla inputInvoiceStarGiftUpgrade#4d818d5d flags:# keep_original_details:flags.0?true stargift:InputSavedStarGift = InputInvoice; inputInvoiceStarGiftTransfer#4a5f5bd9 stargift:InputSavedStarGift to_id:InputPeer = InputInvoice; inputInvoicePremiumGiftStars#dabab2ef flags:# user_id:InputUser months:int message:flags.0?TextWithEntities = InputInvoice; +inputInvoiceBusinessBotTransferStars#f4997e42 bot:InputUser stars:long = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1848,7 +1849,7 @@ starsTransactionPeerAPI#f9677aad = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#a39fd94a flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int = StarsTransaction; +starsTransaction#a39fd94a flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int = StarsTransaction; payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; From 09229812f4e612f846142f6207ac82a1f5bd887c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Apr 2025 21:50:39 +0500 Subject: [PATCH 102/190] Show nice confcall invites. --- Telegram/Resources/langs/lang.strings | 8 ++- Telegram/SourceFiles/boxes/boxes.style | 2 + .../boxes/peers/replace_boost_box.cpp | 27 +++++--- Telegram/SourceFiles/calls/calls.style | 19 +++++ Telegram/SourceFiles/calls/calls_call.cpp | 2 + Telegram/SourceFiles/calls/calls_call.h | 6 ++ Telegram/SourceFiles/calls/calls_instance.cpp | 6 ++ Telegram/SourceFiles/calls/calls_panel.cpp | 69 ++++++++++++++++++- Telegram/SourceFiles/calls/calls_panel.h | 2 + .../SourceFiles/data/data_media_types.cpp | 36 ++++++++-- Telegram/SourceFiles/data/data_media_types.h | 7 +- Telegram/SourceFiles/history/history_item.cpp | 5 +- .../history/view/media/history_view_call.cpp | 8 +-- Telegram/SourceFiles/ui/effects/premium.style | 2 + 14 files changed, 170 insertions(+), 29 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e5b20fb088..bfaa87a884 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4671,18 +4671,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_outgoing" = "Outgoing call"; "lng_call_video_outgoing" = "Outgoing video call"; +"lng_call_group_outgoing" = "Outgoing group call"; "lng_call_incoming" = "Incoming call"; "lng_call_video_incoming" = "Incoming video call"; +"lng_call_group_incoming" = "Incoming group call"; "lng_call_missed" = "Missed call"; "lng_call_video_missed" = "Missed video call"; +"lng_call_group_missed" = "Missed group call"; "lng_call_cancelled" = "Canceled call"; "lng_call_video_cancelled" = "Canceled video call"; "lng_call_declined" = "Declined call"; "lng_call_video_declined" = "Declined video call"; +"lng_call_group_declined" = "Declined group call"; "lng_call_duration_info" = "{time}, {duration}"; "lng_call_type_and_duration" = "{type} ({duration})"; -"lng_call_invitation" = "Call invitation"; -"lng_call_ongoing" = "Ongoing call"; +"lng_call_invitation" = "Group call invitation"; +"lng_call_ongoing" = "Ongoing group call"; "lng_call_rate_label" = "Please rate the quality of your call"; "lng_call_rate_comment" = "Comment (optional)"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 1e3b7f740d..33184a2fe9 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -26,8 +26,10 @@ UserpicButton { } UserpicsRow { button: UserpicButton; + bg: color; shift: pixels; stroke: pixels; + complex: bool; invert: bool; } ShortInfoBox { diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index f132e82e94..354b1bb5a5 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -694,8 +694,7 @@ object_ptr CreateUserpicsWithMoreBadge( bool painting = false; }; const auto full = st.button.size.height() - + st::boostReplaceIconAdd.y() - + st::lineWidth; + + (st.complex ? (st::boostReplaceIconAdd.y() + st::lineWidth) : 0); auto result = object_ptr(parent, full); const auto raw = result.data(); const auto overlay = CreateChild(raw); @@ -731,6 +730,12 @@ object_ptr CreateUserpicsWithMoreBadge( overlay->update(); }, raw->lifetime()); + if (const auto count = state->count.current()) { + const auto single = st.button.size.width(); + const auto used = std::min(count, int(state->buttons.size())); + const auto shift = st.shift; + raw->resize(used ? (single + (used - 1) * shift) : 0, raw->height()); + } rpl::combine( raw->widthValue(), state->count.value() @@ -768,7 +773,7 @@ object_ptr CreateUserpicsWithMoreBadge( auto hq = PainterHighQualityEnabler(q); const auto stroke = st.stroke; const auto half = stroke / 2.; - auto pen = st::windowBg->p; + auto pen = st.bg->p; pen.setWidthF(stroke * 2.); state->painting = true; const auto paintOne = [&](not_null button) { @@ -788,18 +793,18 @@ object_ptr CreateUserpicsWithMoreBadge( } } state->painting = false; - const auto last = state->buttons.back().get(); - const auto add = st::boostReplaceIconAdd; - const auto skip = st::boostReplaceIconSkip; - const auto w = st::boostReplaceIcon.width() + 2 * skip; - const auto h = st::boostReplaceIcon.height() + 2 * skip; - const auto x = last->x() + last->width() - w + add.x(); - const auto y = last->y() + last->height() - h + add.y(); const auto text = (state->count.current() > limit) ? ('+' + QString::number(state->count.current() - limit)) : QString(); - if (!text.isEmpty()) { + if (st.complex && !text.isEmpty()) { + const auto last = state->buttons.back().get(); + const auto add = st::boostReplaceIconAdd; + const auto skip = st::boostReplaceIconSkip; + const auto w = st::boostReplaceIcon.width() + 2 * skip; + const auto h = st::boostReplaceIcon.height() + 2 * skip; + const auto x = last->x() + last->width() - w + add.x(); + const auto y = last->y() + last->height() - h + add.y(); const auto &font = st::semiboldFont; const auto width = font->width(text); const auto padded = std::max(w, width + 2 * font->spacew); diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 1119b6d69e..18052cd98e 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -37,6 +37,7 @@ CallBodyLayout { photoSize: pixels; nameTop: pixels; statusTop: pixels; + participantsTop: pixels; muteStroke: pixels; muteSize: pixels; mutePosition: point; @@ -48,6 +49,7 @@ callBodyLayout: CallBodyLayout { photoSize: 160px; nameTop: 221px; statusTop: 254px; + participantsTop: 294px; muteStroke: 3px; muteSize: 36px; mutePosition: point(142px, 135px); @@ -58,6 +60,7 @@ callBodyWithPreview: CallBodyLayout { photoSize: 100px; nameTop: 132px; statusTop: 163px; + participantsTop: 193px; muteStroke: 3px; muteSize: 0px; mutePosition: point(90px, 84px); @@ -1538,11 +1541,27 @@ confcallJoinUserpics: UserpicsRow { size: size(36px, 36px); photoSize: 36px; } + bg: boxBg; shift: 16px; stroke: 2px; invert: true; } confcallJoinUserpicsPadding: margins(0px, 0px, 0px, 16px); +confcallInviteUserpicsBg: groupCallMembersBg; +confcallInviteUserpics: UserpicsRow { + button: UserpicButton(defaultUserpicButton) { + size: size(24px, 24px); + photoSize: 24px; + } + bg: confcallInviteUserpicsBg; + shift: 10px; + stroke: 2px; + invert: true; +} +confcallInviteParticipants: FlatLabel(defaultFlatLabel) { + textFg: callNameFg; +} +confcallInviteParticipantsPadding: margins(8px, 3px, 12px, 2px); confcallInviteVideo: IconButton { width: 36px; height: 52px; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 32beb069bd..ec27eb5f24 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -253,6 +253,7 @@ Call::Call( not_null user, CallId conferenceId, MsgId conferenceInviteMsgId, + std::vector> conferenceParticipants, bool video) : _delegate(delegate) , _user(user) @@ -279,6 +280,7 @@ Call::Call( , _id(base::RandomValue()) , _conferenceId(conferenceId) , _conferenceInviteMsgId(conferenceInviteMsgId) +, _conferenceParticipants(std::move(conferenceParticipants)) , _videoIncoming( std::make_unique( StartVideoState(video))) diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 6483db2fe4..90a8eed489 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -107,6 +107,7 @@ public: not_null user, CallId conferenceId, MsgId conferenceInviteMsgId, + std::vector> conferenceParticipants, bool video); [[nodiscard]] Type type() const { @@ -127,6 +128,10 @@ public: [[nodiscard]] MsgId conferenceInviteMsgId() const { return _conferenceInviteMsgId; } + [[nodiscard]] auto conferenceParticipants() const + -> const std::vector> & { + return _conferenceParticipants; + } [[nodiscard]] bool isIncomingWaiting() const; void start(bytes::const_span random); @@ -343,6 +348,7 @@ private: CallId _conferenceId = 0; MsgId _conferenceInviteMsgId = 0; + std::vector> _conferenceParticipants; std::unique_ptr _instance; std::shared_ptr _videoCapture; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index c5f8a8c3a9..d3eb954eb0 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -1024,6 +1024,11 @@ void Instance::showConferenceInvite( return; } + auto conferenceParticipants = call->otherParticipants; + if (!ranges::contains(conferenceParticipants, user)) { + conferenceParticipants.push_back(user); + } + const auto &config = user->session().serverConfig(); if (inCall() || inGroupCall()) { declineIncomingConferenceInvites(conferenceId); @@ -1038,6 +1043,7 @@ void Instance::showConferenceInvite( user, conferenceId, conferenceInviteMsgId, + std::move(conferenceParticipants), video); const auto raw = call.get(); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 23fe1975e9..0d0628f371 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_panel.h" +#include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge #include "data/data_photo.h" #include "data/data_session.h" #include "data/data_user.h" @@ -215,6 +216,7 @@ Panel::Panel(not_null call) initWindow(); initWidget(); initControls(); + initConferenceInvite(); initLayout(); initMediaDeviceToggles(); showAndActivate(); @@ -534,6 +536,65 @@ void Panel::initControls() { _screencast->finishAnimating(); } +void Panel::initConferenceInvite() { + const auto &participants = _call->conferenceParticipants(); + const auto count = int(participants.size()); + if (count < 2) { + return; + } + _conferenceParticipants.create(widget()); + _conferenceParticipants->show(); + const auto raw = _conferenceParticipants.data(); + + auto peers = std::vector>(); + for (const auto &peer : participants) { + if (peer == _user && count > 3) { + continue; + } + peers.push_back(peer); + if (peers.size() == 3) { + break; + } + } + + const auto userpics = CreateUserpicsWithMoreBadge( + raw, + rpl::single(peers), + st::confcallInviteUserpics, + peers.size()).release(); + + const auto label = Ui::CreateChild( + raw, + tr::lng_group_call_members(tr::now, lt_count, count), + st::confcallInviteParticipants); + const auto padding = st::confcallInviteParticipantsPadding; + const auto add = padding.bottom(); + const auto width = add + + userpics->width() + + padding.left() + + label->width() + + padding.right(); + const auto height = add + userpics->height() + add; + + _status->geometryValue() | rpl::start_with_next([=] { + const auto top = _bodyTop + _bodySt->participantsTop; + const auto left = (widget()->width() - width) / 2; + raw->setGeometry(left, top, width, height); + userpics->move(add, add); + label->move(add + userpics->width() + padding.left(), padding.top()); + }, raw->lifetime()); + + raw->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(raw); + auto hq = PainterHighQualityEnabler(p); + const auto radius = raw->height() / 2.; + + p.setPen(Qt::NoPen); + p.setBrush(st::confcallInviteUserpicsBg); + p.drawRoundedRect(raw->rect(), radius, radius); + }, raw->lifetime()); +} + void Panel::setIncomingSize(QSize size) { if (_incomingFrameSize == size) { return; @@ -1160,7 +1221,11 @@ void Panel::updateControlsGeometry() { std::min( bodyPreviewSizeMax.height(), st::callOutgoingPreviewMax.height())); - const auto contentHeight = _bodySt->height + const auto bodyContentHeight = _bodySt->height + + (_conferenceParticipants + ? (_bodySt->participantsTop - _bodySt->statusTop) + : 0); + const auto contentHeight = bodyContentHeight + (_outgoingPreviewInBody ? bodyPreviewSize.height() : 0); const auto remainingHeight = available - contentHeight; const auto skipHeight = remainingHeight @@ -1172,7 +1237,7 @@ void Panel::updateControlsGeometry() { widget()->height(), _buttonsTopShown, shown); - const auto previewTop = _bodyTop + _bodySt->height + skipHeight; + const auto previewTop = _bodyTop + bodyContentHeight + skipHeight; _userpic->setGeometry( (widget()->width() - _bodySt->photoSize) / 2, diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index fdb61333ab..539342d2bc 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -142,6 +142,7 @@ private: void initWindow(); void initWidget(); void initControls(); + void initConferenceInvite(); void reinitWithCall(Call *call); void initLayout(); void initMediaDeviceToggles(); @@ -211,6 +212,7 @@ private: object_ptr < Ui::FadeWrap> _addPeople; object_ptr _name; object_ptr _status; + object_ptr _conferenceParticipants = { nullptr }; object_ptr _fingerprint = { nullptr }; object_ptr> _remoteAudioMute = { nullptr }; object_ptr> _remoteLowBattery diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 969cbd2340..f715cc0bb1 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -456,7 +456,9 @@ Invoice ComputeInvoiceData( return result; } -Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { +Call ComputeCallData( + not_null owner, + const MTPDmessageActionPhoneCall &call) { auto result = Call(); result.state = [&] { if (const auto reason = call.vreason()) { @@ -480,8 +482,18 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { return result; } -Call ComputeCallData(const MTPDmessageActionConferenceCall &call) { +Call ComputeCallData( + not_null owner, + const MTPDmessageActionConferenceCall &call) { + auto participants = std::vector>(); + if (const auto list = call.vother_participants()) { + participants.reserve(list->v.size()); + for (const auto &participant : list->v) { + participants.push_back(owner->peer(peerFromMTP(participant))); + } + } return { + .otherParticipants = std::move(participants), .conferenceId = call.vcall_id().v, .duration = call.vduration().value_or_empty(), .state = (call.vduration().value_or_empty() @@ -1723,7 +1735,8 @@ const Call *MediaCall::call() const { } TextWithEntities MediaCall::notificationText() const { - auto result = Text(parent(), _call.state, _call.video); + const auto conference = (_call.conferenceId != 0); + auto result = Text(parent(), _call.state, conference, _call.video); if (_call.duration > 0) { result = tr::lng_call_type_and_duration( tr::now, @@ -1765,6 +1778,7 @@ std::unique_ptr MediaCall::createView( QString MediaCall::Text( not_null item, CallState state, + bool conference, bool video) { if (state == CallState::Invitation) { return tr::lng_call_invitation(tr::now); @@ -1772,14 +1786,20 @@ QString MediaCall::Text( return tr::lng_call_ongoing(tr::now); } else if (item->out()) { return ((state == CallState::Missed) - ? (video + ? (conference + ? tr::lng_call_group_declined + : video ? tr::lng_call_video_cancelled : tr::lng_call_cancelled) - : (video + : (conference + ? tr::lng_call_group_outgoing + : video ? tr::lng_call_video_outgoing : tr::lng_call_outgoing))(tr::now); } else if (state == CallState::Missed) { - return (video + return (conference + ? tr::lng_call_group_missed + : video ? tr::lng_call_video_missed : tr::lng_call_missed)(tr::now); } else if (state == CallState::Busy) { @@ -1787,7 +1807,9 @@ QString MediaCall::Text( ? tr::lng_call_video_declined : tr::lng_call_declined)(tr::now); } - return (video + return (conference + ? tr::lng_call_group_incoming + : video ? tr::lng_call_video_incoming : tr::lng_call_incoming)(tr::now); } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index f47197816e..a8a0a34ac9 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -82,6 +82,7 @@ struct SharedContact final { struct Call { using State = CallState; + std::vector> otherParticipants; CallId conferenceId = 0; int duration = 0; State state = State::Missed; @@ -468,6 +469,7 @@ public: [[nodiscard]] static QString Text( not_null item, CallState state, + bool conference, bool video); private: @@ -801,8 +803,11 @@ private: not_null item, const MTPDmessageMediaPaidMedia &data); -[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); [[nodiscard]] Call ComputeCallData( + not_null owner, + const MTPDmessageActionPhoneCall &call); +[[nodiscard]] Call ComputeCallData( + not_null owner, const MTPDmessageActionConferenceCall &call); [[nodiscard]] GiveawayStart ComputeGiveawayStartData( diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 3330c835c6..f93592c270 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -481,13 +481,13 @@ HistoryItem::HistoryItem( createComponents(CreateConfig()); _media = std::make_unique( this, - Data::ComputeCallData(data)); + Data::ComputeCallData(&history->owner(), data)); setTextValue({}); }, [&](const MTPDmessageActionConferenceCall &data) { createComponents(CreateConfig()); _media = std::make_unique( this, - Data::ComputeCallData(data)); + Data::ComputeCallData(&history->owner(), data)); setTextValue({}); }, [&](const auto &) { createServiceFromMtp(data); @@ -1906,6 +1906,7 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) { _media = std::make_unique( this, Data::ComputeCallData( + &history()->owner(), message.vaction().c_messageActionConferenceCall())); addToSharedMediaIndex(); finishEdition(-1); diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.cpp b/Telegram/SourceFiles/history/view/media/history_view_call.cpp index cd12e9bc45..6cfd8c938b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_call.cpp @@ -47,7 +47,7 @@ Call::Call( , _conference(call->conferenceId != 0) , _video(call->video) { const auto item = parent->data(); - _text = Data::MediaCall::Text(item, _state, _video); + _text = Data::MediaCall::Text(item, _state, _conference, _video); _status = QLocale().toString( parent->dateTime().time(), QLocale::ShortFormat); @@ -118,10 +118,10 @@ void Call::draw(Painter &p, const PaintContext &context) const { p.setPen(stm->mediaFg); p.drawTextLeft(statusleft, statustop, paintw, _status); - const auto &icon = _conference - ? stm->historyCallGroupIcon - : _video + const auto &icon = _video ? stm->historyCallCameraIcon + : _conference + ? stm->historyCallGroupIcon : stm->historyCallIcon; icon.paint(p, paintw - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, paintw); } diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index daeece6ce2..a97c5b16bc 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -313,8 +313,10 @@ boostReplaceIconAdd: point(4px, 2px); boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }}; boostReplaceUserpicsRow: UserpicsRow { button: boostReplaceUserpic; + bg: windowBg; shift: boostReplaceUserpicsShift; stroke: boostReplaceIconOutline; + complex: true; } showOrIconLastSeen: icon{{ "settings/premium/large_lastseen", windowFgActive }}; From 6528567746c8a5f350ab1694140f6048a9e3ab6b Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 1 Apr 2025 22:48:44 +0500 Subject: [PATCH 103/190] Log applying blocks and fix sub_chain_id:1. --- .../calls/group/calls_group_call.cpp | 5 ++++ Telegram/SourceFiles/tde2e/tde2e_api.cpp | 29 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 832b5e8fad..dbb7298671 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -729,6 +729,11 @@ void GroupCall::setupConferenceCall() { } _conferenceCall->setParticipantsWithAccess(std::move(users)); }, _lifetime); + + _e2e->failures() | rpl::start_with_next([=] { + LOG(("TdE2E: Got failure!")); + hangup(); + }, _lifetime); } void GroupCall::removeConferenceParticipants( diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index ee25452805..a70ba8d6f5 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -19,6 +19,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL LOG_ERROR(error); \ fail(reason) +#define LOG_APPLY(subchain, slice) \ + DEBUG_LOG(("TdE2E Apply[%1]: %2").arg(subchain).arg(WrapForLog(slice))) + namespace TdE2E { namespace { @@ -52,6 +55,23 @@ constexpr auto kShortPollChainBlocksWaitFor = crl::time(1000); return result; } +[[nodiscard]] QString WrapForLog(std::string_view v) { + auto result = QString(); + const auto count = std::min(int(v.size()), 16); + result.reserve(count * 3 + 2); + for (auto i = 0; i != count; ++i) { + const auto byte = uint8(v[i]); + result += QString::number(byte, 16).rightJustified(2, '0'); + if (i + 1 != count) { + result += ' '; + } + } + if (v.size() > count) { + result += "..."; + } + return result.toUpper(); +} + } // namespace Call::Call(UserId myUserId) @@ -195,6 +215,7 @@ void Call::apply(int subchain, const Block &last) { }); if (subchain) { + LOG_APPLY(1, Slice(last.data)); auto result = tde2e_api::call_receive_inbound_message( libId(), Slice(last.data)); @@ -205,6 +226,7 @@ void Call::apply(int subchain, const Block &last) { } return; } else if (_id) { + LOG_APPLY(0, Slice(last.data)); const auto result = tde2e_api::call_apply_block( libId(), Slice(last.data)); @@ -215,6 +237,7 @@ void Call::apply(int subchain, const Block &last) { } return; } + LOG_APPLY(-1, Slice(last.data)); const auto id = tde2e_api::call_create( std::int64_t(_myKeyId.v), Slice(last.data)); @@ -296,7 +319,9 @@ void Call::apply( if (failed()) { return; - } else if (!_id || entry.height == index) { + } else if (!_id + || (subchain && !entry.height && fromShortPoll) + || (entry.height == index)) { apply(subchain, block); } entry.height = std::max(entry.height, index + 1); @@ -331,6 +356,7 @@ void Call::checkWaitingBlocks(int subchain, bool waited) { } else if (index == entry.height) { const auto slice = Slice(waiting.begin()->second.data); if (subchain) { + LOG_APPLY(1, slice); auto result = tde2e_api::call_receive_inbound_message( libId(), slice); @@ -343,6 +369,7 @@ void Call::checkWaitingBlocks(int subchain, bool waited) { : QByteArray(); checkForOutboundMessages(); } else { + LOG_APPLY(0, slice); const auto result = tde2e_api::call_apply_block( libId(), slice); From eb06c0da08bab90cf24029aed617945bdbfea4f9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Apr 2025 00:13:14 +0500 Subject: [PATCH 104/190] Try migrating mute/video state. --- Telegram/SourceFiles/calls/calls_call.cpp | 28 ++++++--- Telegram/SourceFiles/calls/calls_call.h | 2 + Telegram/SourceFiles/calls/calls_instance.cpp | 11 +--- Telegram/SourceFiles/calls/calls_instance.h | 16 +---- Telegram/SourceFiles/calls/calls_panel.cpp | 11 +++- .../calls/group/calls_group_call.cpp | 63 +++++++++++++++---- .../calls/group/calls_group_call.h | 10 +-- .../calls/group/calls_group_common.cpp | 18 +++--- .../calls/group/calls_group_common.h | 30 +++++---- 9 files changed, 118 insertions(+), 71 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index ec27eb5f24..29dce419fa 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -437,20 +437,24 @@ void Call::acceptConferenceInvite() { const auto limit = 5; const auto messageId = _conferenceInviteMsgId; const auto session = &_user->session(); + auto info = StartConferenceInfo{ + .joinMessageId = messageId, + .migrating = true, + .muted = muted(), + .videoCapture = isSharingVideo() ? _videoCapture : nullptr, + .videoCaptureScreenId = screenSharingDeviceId(), + }; session->api().request(MTPphone_GetGroupCall( MTP_inputGroupCallInviteMessage(MTP_int(messageId.bare)), MTP_int(limit) - )).done([session, messageId](const MTPphone_GroupCall &result) { + )).done([session, messageId, info](const MTPphone_GroupCall &result) { result.data().vcall().match([&](const auto &data) { - const auto call = session->data().sharedConferenceCall( + auto copy = info; + copy.call = session->data().sharedConferenceCall( data.vid().v, data.vaccess_hash().v); - call->processFullCall(result); - Core::App().calls().startOrJoinConferenceCall({ - .call = call, - .joinMessageId = messageId, - .migrating = true, - }); + copy.call->processFullCall(result); + Core::App().calls().startOrJoinConferenceCall(std::move(copy)); }); }).fail(crl::guard(this, [=](const MTP::Error &error) { handleRequestError(error.type()); @@ -561,7 +565,8 @@ void Call::setupOutgoingVideo() { _videoOutgoing->setState(Webrtc::VideoState::Inactive); } else if (_state.current() != State::Established && (state != Webrtc::VideoState::Inactive) - && (started == Webrtc::VideoState::Inactive)) { + && (started == Webrtc::VideoState::Inactive) + && !conferenceInvite()) { _errors.fire({ ErrorType::NotStartedCall }); _videoOutgoing->setState(Webrtc::VideoState::Inactive); } else if (state != Webrtc::VideoState::Inactive @@ -1451,6 +1456,11 @@ void Call::toggleScreenSharing(std::optional uniqueId) { _videoOutgoing->setState(Webrtc::VideoState::Active); } +auto Call::peekVideoCapture() const +-> std::shared_ptr { + return _videoCapture; +} + auto Call::playbackDeviceIdValue() const -> rpl::producer { return _playbackDeviceId.value(); diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 90a8eed489..8675d54338 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -247,6 +247,8 @@ public: [[nodiscard]] QString screenSharingDeviceId() const; void toggleCameraSharing(bool enabled); void toggleScreenSharing(std::optional uniqueId); + [[nodiscard]] auto peekVideoCapture() const + -> std::shared_ptr; [[nodiscard]] auto playbackDeviceIdValue() const -> rpl::producer; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index d3eb954eb0..eb622b6bc3 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -235,20 +235,13 @@ void Instance::startOrJoinGroupCall( }); } -void Instance::startOrJoinConferenceCall(StartConferenceCallArgs args) { +void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { destroyCurrentCall( args.migrating ? args.call.get() : nullptr, args.migrating ? args.linkSlug : QString()); const auto session = &args.call->peer()->session(); - auto call = std::make_unique( - _delegate.get(), - Calls::Group::ConferenceInfo{ - .call = std::move(args.call), - .e2e = std::move(args.e2e), - .linkSlug = args.linkSlug, - .joinMessageId = args.joinMessageId, - }); + auto call = std::make_unique(_delegate.get(), args); const auto raw = call.get(); session->account().sessionChanges( diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 1c46af7a77..65088d9495 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -45,10 +45,6 @@ namespace tgcalls { class VideoCaptureInterface; } // namespace tgcalls -namespace TdE2E { -class Call; -} // namespace TdE2E - namespace Calls { class Call; @@ -57,6 +53,7 @@ class GroupCall; class Panel; struct DhConfig; struct InviteRequest; +struct StartConferenceInfo; struct StartGroupCallArgs { enum class JoinConfirm { @@ -69,15 +66,6 @@ struct StartGroupCallArgs { bool scheduleNeeded = false; }; -struct StartConferenceCallArgs { - std::shared_ptr call; - std::shared_ptr e2e; - QString linkSlug; - MsgId joinMessageId; - std::vector invite; - bool migrating = false; -}; - struct ConferenceInviteMessages { base::flat_set incoming; base::flat_set outgoing; @@ -97,7 +85,7 @@ public: std::shared_ptr show, not_null peer, StartGroupCallArgs args); - void startOrJoinConferenceCall(StartConferenceCallArgs args); + void startOrJoinConferenceCall(StartConferenceInfo args); void showStartWithRtmp( std::shared_ptr show, not_null peer); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 0d0628f371..ab2605faa9 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -477,9 +477,16 @@ void Panel::initControls() { Group::MakeConferenceCall({ .show = uiShow(), .finished = finish, - .invite = std::move(users), .joining = true, - .migrating = true, + .info = { + .invite = std::move(users), + .migrating = true, + .muted = call->muted(), + .videoCapture = (call->isSharingVideo() + ? call->peekVideoCapture() + : nullptr), + .videoCaptureScreenId = call->screenSharingDeviceId(), + }, }); }; const auto invite = crl::guard(call, [=]( diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index dbb7298671..89d177146d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -58,13 +58,10 @@ constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. constexpr auto kShortPollChainBlocksPerRequest = 50; [[nodiscard]] const Data::GroupCallParticipant *LookupParticipant( - not_null peer, - CallId id, + not_null call, not_null participantPeer) { - const auto call = peer->groupCall(); - return (id && call && call->id() == id) - ? call->participantByPeer(participantPeer) - : nullptr; + const auto real = call->lookupReal(); + return real ? real->participantByPeer(participantPeer) : nullptr; } [[nodiscard]] double TimestampFromMsgId(mtpMsgId msgId) { @@ -578,7 +575,7 @@ GroupCall::GroupCall( GroupCall::GroupCall( not_null delegate, - Group::ConferenceInfo info) + StartConferenceInfo info) : GroupCall(delegate, Group::JoinInfo{ .peer = info.call->peer(), .joinAs = info.call->peer(), @@ -588,7 +585,7 @@ GroupCall::GroupCall( GroupCall::GroupCall( not_null delegate, Group::JoinInfo join, - Group::ConferenceInfo conference, + StartConferenceInfo conference, const MTPInputGroupCall &inputCall) : _delegate(delegate) , _conferenceCall(std::move(conference.call)) @@ -682,6 +679,13 @@ GroupCall::GroupCall( setupOutgoingVideo(); if (_conferenceCall) { setupConferenceCall(); + if (conference.migrating) { + if (!conference.muted) { + setMuted(MuteState::Active); + } + _migratedConferenceInfo = std::make_shared( + std::move(conference)); + } } if (_id) { @@ -694,6 +698,40 @@ GroupCall::GroupCall( } } +void GroupCall::processMigration(StartConferenceInfo conference) { + if (!conference.videoCapture) { + return; + } + const auto weak = base::make_weak(this); + if (!conference.videoCaptureScreenId.isEmpty()) { + _screenCapture = std::move(conference.videoCapture); + _screenDeviceId = conference.videoCaptureScreenId; + _screenCapture->setOnFatalError([=] { + crl::on_main(weak, [=] { + emitShareScreenError(Error::ScreenFailed); + }); + }); + _screenCapture->setOnPause([=](bool paused) { + crl::on_main(weak, [=] { + if (isSharingScreen()) { + _screenState = paused + ? Webrtc::VideoState::Paused + : Webrtc::VideoState::Active; + } + }); + }); + _screenState = Webrtc::VideoState::Active; + } else { + _cameraCapture = std::move(conference.videoCapture); + _cameraCapture->setOnFatalError([=] { + crl::on_main(weak, [=] { + emitShareCameraError(Error::CameraFailed); + }); + }); + _cameraState = Webrtc::VideoState::Active; + } +} + GroupCall::~GroupCall() { destroyScreencast(); destroyController(); @@ -1578,6 +1616,9 @@ void GroupCall::sendJoinRequest() { sendOutboundBlock(base::take(_pendingOutboundBlock)); } } + if (const auto once = base::take(_migratedConferenceInfo)) { + processMigration(*once); + } }).fail([=](const MTP::Error &error) { const auto type = error.type(); if (_e2e) { @@ -1885,7 +1926,7 @@ void GroupCall::applyParticipantLocally( not_null participantPeer, bool mute, std::optional volume) { - const auto participant = LookupParticipant(_peer, _id, participantPeer); + const auto participant = LookupParticipant(this, participantPeer); if (!participant || !participant->ssrc) { return; } @@ -2370,7 +2411,7 @@ void GroupCall::applyOtherParticipantUpdate( } const auto participantPeer = _peer->owner().peer( peerFromMTP(data.vpeer())); - if (!LookupParticipant(_peer, _id, participantPeer)) { + if (!LookupParticipant(this, participantPeer)) { return; } _otherParticipantStateValue.fire(Group::ParticipantState{ @@ -3686,7 +3727,7 @@ void GroupCall::editParticipant( not_null participantPeer, bool mute, std::optional volume) { - const auto participant = LookupParticipant(_peer, _id, participantPeer); + const auto participant = LookupParticipant(this, participantPeer); if (!participant) { return; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 2b5be388fb..80bce2fb9f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -62,6 +62,7 @@ enum class Error; struct InviteRequest; struct InviteResult; +struct StartConferenceInfo; enum class MuteState { Active, @@ -225,9 +226,7 @@ public: not_null delegate, Group::JoinInfo info, const MTPInputGroupCall &inputCall); - GroupCall( - not_null delegate, - Group::ConferenceInfo info); + GroupCall(not_null delegate, StartConferenceInfo info); ~GroupCall(); [[nodiscard]] CallId id() const { @@ -505,7 +504,7 @@ private: GroupCall( not_null delegate, Group::JoinInfo join, - Group::ConferenceInfo conference, + StartConferenceInfo conference, const MTPInputGroupCall &inputCall); void broadcastPartStart(std::shared_ptr task); @@ -620,6 +619,8 @@ private: void markTrackPaused(const VideoEndpoint &endpoint, bool paused); void markTrackShown(const VideoEndpoint &endpoint, bool shown); + void processMigration(StartConferenceInfo conference); + [[nodiscard]] int activeVideoSendersCount() const; [[nodiscard]] MTPInputGroupCall inputCall() const; @@ -629,6 +630,7 @@ private: const std::shared_ptr _conferenceCall; std::shared_ptr _e2e; QByteArray _pendingOutboundBlock; + std::shared_ptr _migratedConferenceInfo; not_null _peer; // Can change in legacy group migration. rpl::event_stream _peerStream; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index e1ac887e00..94c330e2d4 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -392,7 +392,7 @@ void ExportConferenceCallLink( std::shared_ptr call, ConferenceCallLinkArgs &&args) { const auto session = &show->session(); - const auto invite = std::move(args.invite); + const auto info = std::move(args.info); const auto finished = std::move(args.finished); using Flag = MTPphone_ExportGroupCallInvite::Flag; @@ -403,12 +403,10 @@ void ExportConferenceCallLink( const auto link = qs(result.data().vlink()); if (args.joining) { if (auto slug = ExtractConferenceSlug(link); !slug.isEmpty()) { - Core::App().calls().startOrJoinConferenceCall({ - .call = call, - .linkSlug = std::move(slug), - .invite = invite, - .migrating = args.migrating, - }); + auto copy = info; + copy.call = call; + copy.linkSlug = std::move(slug); + Core::App().calls().startOrJoinConferenceCall(info); } if (const auto onstack = finished) { finished(QString()); @@ -435,8 +433,7 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) { const auto show = std::move(args.show); const auto finished = std::move(args.finished); const auto joining = args.joining; - const auto migrating = args.migrating; - const auto invite = std::move(args.invite); + const auto info = std::move(args.info); const auto session = &show->session(); session->api().request(MTPphone_CreateConferenceCall( MTP_int(base::RandomValue()) @@ -449,9 +446,8 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) { Calls::Group::ExportConferenceCallLink(show, call, { .initial = true, .joining = joining, - .migrating = migrating, .finished = finished, - .invite = invite, + .info = info, }); }); }).fail([=](const MTP::Error &error) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 63ab74dd89..ed050f479c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -37,6 +37,10 @@ namespace TdE2E { class Call; } // namespace TdE2E +namespace tgcalls { +class VideoCaptureInterface; +} // namespace tgcalls + namespace Window { class SessionController; } // namespace Window @@ -47,12 +51,25 @@ struct InviteRequest { not_null user; bool video = false; }; + struct InviteResult { std::vector> invited; std::vector> alreadyIn; std::vector> privacyRestricted; }; +struct StartConferenceInfo { + std::shared_ptr call; + std::shared_ptr e2e; + QString linkSlug; + MsgId joinMessageId; + std::vector invite; + bool migrating = false; + bool muted = false; + std::shared_ptr videoCapture; + QString videoCaptureScreenId; +}; + } // namespace Calls namespace Calls::Group { @@ -101,13 +118,6 @@ struct JoinInfo { bool rtmp = false; }; -struct ConferenceInfo { - std::shared_ptr call; - std::shared_ptr e2e; - QString linkSlug; - MsgId joinMessageId; -}; - enum class PanelMode { Default, Wide, @@ -158,9 +168,8 @@ struct ConferenceCallLinkStyleOverrides { struct ConferenceCallLinkArgs { bool initial = false; bool joining = false; - bool migrating = false; Fn finished; - std::vector invite; + StartConferenceInfo info; ConferenceCallLinkStyleOverrides st; }; void ShowConferenceCallLinkBox( @@ -177,9 +186,8 @@ void ExportConferenceCallLink( struct ConferenceFactoryArgs { std::shared_ptr show; Fn finished; - std::vector invite; bool joining = false; - bool migrating = false; + StartConferenceInfo info; }; void MakeConferenceCall(ConferenceFactoryArgs &&args); From 3a68dd50ce8b4a169ab8227e6715dd67219f9a84 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Apr 2025 23:41:31 +0500 Subject: [PATCH 105/190] Fix applying some Data::GroupCall updates. --- Telegram/SourceFiles/calls/group/calls_group_call.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 89d177146d..4a151422c8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1092,8 +1092,8 @@ void GroupCall::setState(State state) { if (state == State::Joined) { stopConnectingSound(); - if (const auto call = _peer->groupCall(); call && call->id() == _id) { - call->setInCall(); + if (const auto real = lookupReal()) { + real->setInCall(); } } @@ -1950,7 +1950,7 @@ void GroupCall::applyParticipantLocally( | (participant->raisedHandRating ? Flag::f_raise_hand_rating : Flag(0)); - _peer->groupCall()->applyLocalUpdate( + lookupReal()->applyLocalUpdate( MTP_updateGroupCallParticipants( inputCall(), MTP_vector( @@ -2893,6 +2893,7 @@ bool GroupCall::tryCreateScreencast() { .createAudioDeviceModule = Webrtc::LoopbackAudioDeviceModuleCreator(), .videoCapture = _screenCapture, .videoContentType = tgcalls::VideoContentType::Screencast, + .e2eEncryptDecrypt = _e2e ? _e2e->callbackEncryptDecrypt() : nullptr, }; LOG(("Call Info: Creating group screen instance")); From c36f402b881e8e98aa53c1442dcaf05d838a7032 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Apr 2025 23:41:48 +0500 Subject: [PATCH 106/190] Fix bug in OpenAL patches on Windows. --- Telegram/build/prepare/prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 676bb56be2..e24287724a 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -1381,7 +1381,7 @@ stage('openal-soft', """ git clone https://github.com/telegramdesktop/openal-soft.git cd openal-soft win: - git checkout 5e9429354d + git checkout 291c0fdbbd cmake -B build . ^ -A %WIN32X64% ^ -D LIBTYPE:STRING=STATIC ^ From e9280777fd5d29b483d8ea02176c2ef736382669 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Apr 2025 16:50:33 +0500 Subject: [PATCH 107/190] Fix confcall video encryption. --- Telegram/ThirdParty/tgcalls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 6897a6ee1d..4c2c1104b2 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 6897a6ee1d14ff032f1047993105e92e8589011c +Subproject commit 4c2c1104b2c9a13651f4290512daf5bb2e42ac50 From 502045f1fac465518b0fae363db6d826d09be10d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Apr 2025 16:50:55 +0500 Subject: [PATCH 108/190] Improve mute/video migration to confcall. --- Telegram/SourceFiles/calls/calls_call.cpp | 44 ++++++++++--------- Telegram/SourceFiles/calls/calls_call.h | 5 +++ Telegram/SourceFiles/calls/calls_instance.cpp | 2 +- Telegram/SourceFiles/calls/calls_panel.cpp | 2 + .../calls/group/calls_group_common.cpp | 3 +- .../calls/group/calls_group_common.h | 1 + 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 29dce419fa..3a5246fec7 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -427,6 +427,14 @@ void Call::answer() { }), video); } +StartConferenceInfo Call::migrateConferenceInfo(StartConferenceInfo extend) { + extend.migrating = true; + extend.muted = muted(); + extend.videoCapture = isSharingVideo() ? _videoCapture : nullptr; + extend.videoCaptureScreenId = screenSharingDeviceId(); + return extend; +} + void Call::acceptConferenceInvite() { Expects(conferenceInvite()); @@ -436,29 +444,24 @@ void Call::acceptConferenceInvite() { setState(State::ExchangingKeys); const auto limit = 5; const auto messageId = _conferenceInviteMsgId; - const auto session = &_user->session(); - auto info = StartConferenceInfo{ - .joinMessageId = messageId, - .migrating = true, - .muted = muted(), - .videoCapture = isSharingVideo() ? _videoCapture : nullptr, - .videoCaptureScreenId = screenSharingDeviceId(), - }; - session->api().request(MTPphone_GetGroupCall( + _api.request(MTPphone_GetGroupCall( MTP_inputGroupCallInviteMessage(MTP_int(messageId.bare)), MTP_int(limit) - )).done([session, messageId, info](const MTPphone_GroupCall &result) { + )).done([=](const MTPphone_GroupCall &result) { result.data().vcall().match([&](const auto &data) { - auto copy = info; - copy.call = session->data().sharedConferenceCall( + auto call = _user->owner().sharedConferenceCall( data.vid().v, data.vaccess_hash().v); - copy.call->processFullCall(result); - Core::App().calls().startOrJoinConferenceCall(std::move(copy)); + call->processFullCall(result); + Core::App().calls().startOrJoinConferenceCall( + migrateConferenceInfo({ + .call = std::move(call), + .joinMessageId = messageId, + })); }); - }).fail(crl::guard(this, [=](const MTP::Error &error) { + }).fail([=](const MTP::Error &error) { handleRequestError(error.type()); - })).send(); + }).send(); } void Call::actuallyAnswer() { @@ -892,10 +895,11 @@ void Call::finishByMigration(const QString &slug) { data.vid().v, data.vaccess_hash().v); call->processFullCall(result); - Core::App().calls().startOrJoinConferenceCall({ - .call = call, - .linkSlug = slug, - }); + Core::App().calls().startOrJoinConferenceCall( + migrateConferenceInfo({ + .call = call, + .linkSlug = slug, + })); }); }).fail(crl::guard(this, [=] { setState(State::Failed); diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 8675d54338..3ba17518a2 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -40,6 +40,8 @@ struct DeviceResolvedId; namespace Calls { +struct StartConferenceInfo; + struct DhConfig { int32 version = 0; int32 g = 0; @@ -312,6 +314,9 @@ private: tgcalls::AudioState audio, tgcalls::VideoState video); + [[nodiscard]] StartConferenceInfo migrateConferenceInfo( + StartConferenceInfo extend); + const not_null _delegate; const not_null _user; MTP::Sender _api; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index eb622b6bc3..daf06a64f5 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -254,7 +254,7 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { _currentGroupCallChanges.fire_copy(raw); if (!args.invite.empty()) { _currentGroupCallPanel->migrationInviteUsers(std::move(args.invite)); - } else if (args.migrating && !args.linkSlug.isEmpty()) { + } else if (args.sharingLink && !args.linkSlug.isEmpty()) { _currentGroupCallPanel->migrationShowShareLink(); } } diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index ab2605faa9..6197ee5bec 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -474,12 +474,14 @@ void Panel::initControls() { return; } *creating = true; + const auto sharingLink = users.empty(); Group::MakeConferenceCall({ .show = uiShow(), .finished = finish, .joining = true, .info = { .invite = std::move(users), + .sharingLink = sharingLink, .migrating = true, .muted = call->muted(), .videoCapture = (call->isSharingVideo() diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 94c330e2d4..9eb576b52e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -406,7 +406,8 @@ void ExportConferenceCallLink( auto copy = info; copy.call = call; copy.linkSlug = std::move(slug); - Core::App().calls().startOrJoinConferenceCall(info); + Core::App().calls().startOrJoinConferenceCall( + std::move(copy)); } if (const auto onstack = finished) { finished(QString()); diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index ed050f479c..6b8d5cedc2 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -64,6 +64,7 @@ struct StartConferenceInfo { QString linkSlug; MsgId joinMessageId; std::vector invite; + bool sharingLink = false; bool migrating = false; bool muted = false; std::shared_ptr videoCapture; From a5fa59562711e8e7f660a5476197d8215e315e62 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Apr 2025 11:18:10 +0400 Subject: [PATCH 109/190] Update API scheme on layer 202. --- .../calls/group/calls_group_call.cpp | 21 +++++++++++++++++-- .../calls/group/calls_group_call.h | 7 ++++++- Telegram/SourceFiles/main/main_app_config.cpp | 5 +++++ Telegram/SourceFiles/main/main_app_config.h | 2 ++ Telegram/SourceFiles/mtproto/scheme/api.tl | 4 ++-- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 18 ++++++++++------ Telegram/SourceFiles/tde2e/tde2e_api.h | 5 ++++- Telegram/ThirdParty/tgcalls | 2 +- 8 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 4a151422c8..dc9d1f6af3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_common.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "api/api_send_progress.h" #include "api/api_updates.h" @@ -756,7 +757,7 @@ void GroupCall::setupConferenceCall() { _conferenceCall->staleParticipantIds( ) | rpl::start_with_next([=](const base::flat_set &staleIds) { - removeConferenceParticipants(staleIds); + removeConferenceParticipants(staleIds, true); }, _lifetime); _e2e->participantsSetValue( ) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) { @@ -775,7 +776,8 @@ void GroupCall::setupConferenceCall() { } void GroupCall::removeConferenceParticipants( - const base::flat_set userIds) { + const base::flat_set userIds, + bool removingStale) { Expects(_e2e != nullptr); Expects(!userIds.empty()); @@ -791,7 +793,9 @@ void GroupCall::removeConferenceParticipants( if (block.data.isEmpty()) { return; } + using Flag = MTPphone_DeleteConferenceCallParticipants::Flag; _api.request(MTPphone_DeleteConferenceCallParticipants( + MTP_flags(removingStale ? Flag::f_only_left : Flag::f_kick), inputCall(), MTP_vector(std::move(inputs)), MTP_bytes(block.data) @@ -2722,6 +2726,15 @@ void GroupCall::toggleRecording( }).send(); } +auto GroupCall::lookupVideoCodecPreferences() const +-> std::vector { + auto result = std::vector(); + if (_peer->session().appConfig().confcallPrioritizeVP8()) { + result.push_back(tgcalls::VideoCodecName::VP8); + } + return result; +} + bool GroupCall::tryCreateController() { if (_instance) { return false; @@ -2828,6 +2841,7 @@ bool GroupCall::tryCreateController() { .videoContentType = tgcalls::VideoContentType::Generic, .initialEnableNoiseSuppression = settings.groupCallNoiseSuppression(), + .videoCodecPreferences = lookupVideoCodecPreferences(), .requestMediaChannelDescriptions = [=, call = base::make_weak(this)]( const std::vector &ssrcs, std::functioncallbackEncryptDecrypt() : nullptr, }; @@ -3023,6 +3038,7 @@ bool GroupCall::mediaChannelDescriptionsFill( add(Channel{ .type = Channel::Type::Audio, .audioSsrc = ssrc, + .userId = int64_t(peerToUser(byAudio->id).bare), }); } else if (!resolved) { _unresolvedSsrcs.emplace(ssrc); @@ -3155,6 +3171,7 @@ void GroupCall::updateRequestedVideoChannels() { } channels.push_back({ .audioSsrc = participant->ssrc, + .userId = int64_t(peerToUser(participant->peer->id).bare), .endpointId = endpointId, .ssrcGroups = (params->camera.endpointId == endpointId ? params->camera.ssrcGroups diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 80bce2fb9f..99c0a1e54c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -23,6 +23,7 @@ struct GroupLevelsUpdate; struct GroupNetworkState; struct GroupParticipantDescription; class VideoCaptureInterface; +enum class VideoCodecName; } // namespace tgcalls namespace base { @@ -285,7 +286,9 @@ public: void startScheduledNow(); void toggleScheduleStartSubscribed(bool subscribed); void setNoiseSuppression(bool enabled); - void removeConferenceParticipants(const base::flat_set userIds); + void removeConferenceParticipants( + const base::flat_set userIds, + bool removingStale = false); bool emitShareScreenError(); bool emitShareCameraError(); @@ -533,6 +536,8 @@ private: int subchain, const QVector &blocks, int next); + [[nodiscard]] auto lookupVideoCodecPreferences() const + -> std::vector; bool tryCreateController(); void destroyController(); bool tryCreateScreencast(); diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 0e74bc4261..26d4aecfb0 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -108,6 +108,11 @@ int AppConfig::pinnedGiftsLimit() const { return get(u"stargifts_pinned_to_top_limit"_q, 6); } +bool AppConfig::confcallPrioritizeVP8() const { + AssertIsDebug(); + return true; +} + void AppConfig::refresh(bool force) { if (_requestId || !_api) { if (force) { diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 1cf3aa89cd..a5a804cdcc 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -79,6 +79,8 @@ public: [[nodiscard]] int pinnedGiftsLimit() const; + [[nodiscard]] bool confcallPrioritizeVP8() const; + void refresh(bool force = false); private: diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index a8a7efaa62..9b1250e1b7 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -961,7 +961,7 @@ phoneCallEmpty#5366c915 id:long = PhoneCall; phoneCallWaiting#c5226f17 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; phoneCallRequested#14b0ed0c flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_b:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCall#30535af5 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int custom_parameters:flags.7?DataJSON = PhoneCall; +phoneCall#30535af5 flags:# p2p_allowed:flags.5?true video:flags.6?true conference_supported:flags.8?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int custom_parameters:flags.7?DataJSON = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; @@ -2609,7 +2609,7 @@ phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallS phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; phone.createConferenceCall#fbcefee6 random_id:int = phone.GroupCall; -phone.deleteConferenceCallParticipants#f9231114 call:InputGroupCall ids:Vector block:bytes = Updates; +phone.deleteConferenceCallParticipants#8ca60525 flags:# only_left:flags.0?true kick:flags.1?true call:InputGroupCall ids:Vector block:bytes = Updates; phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; phone.inviteConferenceCallParticipant#bcf22685 flags:# video:flags.0?true call:InputGroupCall user_id:InputUser = Updates; phone.declineConferenceCallInvite#3c479971 msg_id:int = Updates; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index a70ba8d6f5..8827677618 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -239,6 +239,7 @@ void Call::apply(int subchain, const Block &last) { } LOG_APPLY(-1, Slice(last.data)); const auto id = tde2e_api::call_create( + std::int64_t(_myUserId.v), std::int64_t(_myKeyId.v), Slice(last.data)); if (!id.is_ok()) { @@ -446,7 +447,7 @@ rpl::producer Call::emojiHashValue() const { } auto Call::callbackEncryptDecrypt() --> Fn(const std::vector&, bool)> { +-> Fn(const std::vector&, int64_t, bool)> { if (!_guardedId) { _guardedId = std::make_shared(); if (const auto raw = _id ? _guardedId.get() : nullptr) { @@ -454,15 +455,20 @@ auto Call::callbackEncryptDecrypt() raw->exists = true; } } - return [v = _guardedId](const std::vector &data, bool encrypt) { - if (!v->exists) { + return [id = _guardedId]( + const std::vector &data, + int64_t userId, + bool encrypt) { + const auto raw = id.get(); + if (!raw->exists) { return std::vector(); } - const auto libId = std::int64_t(v->value.v); + const auto libId = std::int64_t(raw->value.v); + const auto channelId = tde2e_api::CallChannelId(0); const auto slice = Slice(data); const auto result = encrypt - ? tde2e_api::call_encrypt(libId, slice) - : tde2e_api::call_decrypt(libId, slice); + ? tde2e_api::call_encrypt(libId, channelId, slice) + : tde2e_api::call_decrypt(libId, userId, channelId, slice); if (!result.is_ok()) { return std::vector(); } diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 0b0e518657..53d8cdb61f 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -102,7 +102,10 @@ public: [[nodiscard]] rpl::producer participantsSetValue() const; [[nodiscard]] auto callbackEncryptDecrypt() - -> Fn(const std::vector&, bool)>; + -> Fn( + const std::vector&, + int64_t, + bool)>; private: static constexpr int kSubChainsCount = 2; diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 4c2c1104b2..fd1cfbd815 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 4c2c1104b2c9a13651f4290512daf5bb2e42ac50 +Subproject commit fd1cfbd8151b2c32d5471a4f5431faa6274ce421 From fbbcbc8753687e581a6575b3aa7567cf4b8c6be9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Apr 2025 16:15:33 +0500 Subject: [PATCH 110/190] Respect conference_supported flag. --- Telegram/SourceFiles/calls/calls.style | 6 +++--- Telegram/SourceFiles/calls/calls_call.cpp | 2 ++ Telegram/SourceFiles/calls/calls_call.h | 5 +++++ Telegram/SourceFiles/calls/calls_panel.cpp | 20 +++++++++++++++----- Telegram/SourceFiles/calls/calls_panel.h | 1 + 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 18052cd98e..4f4d90b336 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -24,8 +24,8 @@ CallSignalBars { inactiveOpacity: double; } -callWidthMin: 372px; -callHeightMin: 440px; +callWidthMin: 380px; +callHeightMin: 520px; callWidth: 720px; callHeight: 540px; @@ -532,7 +532,7 @@ callErrorToast: Toast(defaultToast) { } groupCallWidth: 380px; -groupCallHeight: 580px; +groupCallHeight: 520px; groupCallWidthRtmp: 720px; groupCallWidthRtmpMin: 240px; groupCallHeightRtmp: 580px; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 3a5246fec7..7455ea2be6 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -1033,6 +1033,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) { return; } + _conferenceSupported = call.is_conference_supported(); + const auto &protocol = call.vprotocol().c_phoneCallProtocol(); const auto &serverConfig = _user->session().serverConfig(); diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 3ba17518a2..41ee5dd9ad 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -170,6 +170,10 @@ public: return _errors.events(); } + [[nodiscard]] rpl::producer confereceSupportedValue() const { + return _conferenceSupported.value(); + } + enum class RemoteAudioState { Muted, Active, @@ -322,6 +326,7 @@ private: MTP::Sender _api; Type _type = Type::Outgoing; rpl::variable _state = State::Starting; + rpl::variable _conferenceSupported = false; rpl::variable _remoteAudioState = RemoteAudioState::Active; rpl::variable _remoteVideoState; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 6197ee5bec..02cb070432 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -690,6 +690,16 @@ void Panel::reinitWithCall(Call *call) { _user = _call->user(); + _call->confereceSupportedValue( + ) | rpl::start_with_next([=](bool supported) { + _conferenceSupported = supported; + _addPeople->toggle(_conferenceSupported + && (_call->state() != State::WaitingUserConfirmation), + window()->isHidden() ? anim::type::instant : anim::type::normal); + + updateHangupGeometry(); + }, _callLifetime); + auto remoteMuted = _call->remoteAudioStateValue( ) | rpl::map(rpl::mappers::_1 == Call::RemoteAudioState::Muted); rpl::duplicate( @@ -1321,7 +1331,8 @@ void Panel::updateHangupGeometry() { const auto buttonWidth = st::callCancel.button.width; const auto cancelWidth = buttonWidth * (1. - hangupProgress); const auto cancelLeft = (widget()->width() - buttonWidth) / 2 - - ((isBusy || incomingWaiting) ? buttonWidth : 0); + - ((isBusy || incomingWaiting) ? buttonWidth : 0) + + ((isWaitingUser || _conferenceSupported) ? 0 : (buttonWidth / 2)); _cancel->moveToLeft(cancelLeft, _buttonsTop); _decline->moveToLeft(cancelLeft, _buttonsTop); @@ -1413,12 +1424,11 @@ void Panel::stateChanged(State state) { } _camera->setVisible(!_startVideo); + const auto windowHidden = window()->isHidden(); const auto toggleButton = [&](auto &&button, bool visible) { button->toggle( visible, - window()->isHidden() - ? anim::type::instant - : anim::type::normal); + (windowHidden ? anim::type::instant : anim::type::normal)); }; const auto incomingWaiting = _call->isIncomingWaiting(); if (incomingWaiting) { @@ -1430,7 +1440,7 @@ void Panel::stateChanged(State state) { toggleButton( _screencast, !(isBusy || isWaitingUser || incomingWaiting)); - toggleButton(_addPeople, !isWaitingUser); + toggleButton(_addPeople, !isWaitingUser && _conferenceSupported); const auto hangupShown = !_decline->toggled() && !_cancel->toggled(); if (_hangupShown != hangupShown) { diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index 539342d2bc..b07e0ac7ed 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -200,6 +200,7 @@ private: object_ptr> _decline; object_ptr> _cancel; bool _hangupShown = false; + bool _conferenceSupported = false; bool _outgoingPreviewInBody = false; std::optional _answerHangupRedialState; Ui::Animations::Simple _hangupShownProgress; From c507382d19d7efaaa91184c36f1e5fc4f311c0c1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Apr 2025 22:35:57 +0500 Subject: [PATCH 111/190] Keep window geometry on confcall migration. --- Telegram/SourceFiles/calls/calls_instance.cpp | 25 ++++++++---- Telegram/SourceFiles/calls/calls_panel.cpp | 8 ++++ Telegram/SourceFiles/calls/calls_panel.h | 3 ++ .../calls/group/calls_group_common.h | 5 +++ .../calls/group/calls_group_panel.cpp | 40 +++++++++++++------ .../calls/group/calls_group_panel.h | 8 ++-- 6 files changed, 66 insertions(+), 23 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index daf06a64f5..475b2af982 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -236,9 +236,12 @@ void Instance::startOrJoinGroupCall( } void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { - destroyCurrentCall( - args.migrating ? args.call.get() : nullptr, - args.migrating ? args.linkSlug : QString()); + const auto migrationInfo = (args.migrating && _currentCallPanel) + ? _currentCallPanel->migrationInfo() + : ConferencePanelMigration(); + if (!args.migrating) { + destroyCurrentCall(); + } const auto session = &args.call->peer()->session(); auto call = std::make_unique(_delegate.get(), args); @@ -249,7 +252,9 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { destroyGroupCall(raw); }, raw->lifetime()); - _currentGroupCallPanel = std::make_unique(raw); + _currentGroupCallPanel = std::make_unique( + raw, + migrationInfo); _currentGroupCall = std::move(call); _currentGroupCallChanges.fire_copy(raw); if (!args.invite.empty()) { @@ -257,6 +262,10 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { } else if (args.sharingLink && !args.linkSlug.isEmpty()) { _currentGroupCallPanel->migrationShowShareLink(); } + + if (args.migrating) { + destroyCurrentCall(args.call.get(), args.linkSlug); + } } void Instance::confirmLeaveCurrent( @@ -740,9 +749,11 @@ void Instance::destroyCurrentCall( } } if (const auto current = currentGroupCall()) { - current->hangup(); - if (const auto still = currentGroupCall()) { - destroyGroupCall(still); + if (!migrateCall || current->lookupReal() != migrateCall) { + current->hangup(); + if (const auto still = currentGroupCall()) { + destroyGroupCall(still); + } } } } diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 02cb070432..3f2c0b31ac 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -233,6 +233,14 @@ bool Panel::isActive() const { return window()->isActiveWindow() && isVisible(); } +ConferencePanelMigration Panel::migrationInfo() const { + const auto handle = window()->windowHandle(); + return handle ? ConferencePanelMigration{ + .screen = handle->screen(), + .geometry = window()->geometry(), + } : ConferencePanelMigration(); +} + base::weak_ptr Panel::showToast( const QString &text, crl::time duration) { diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index b07e0ac7ed..13ba30c258 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -68,6 +68,7 @@ class Userpic; class SignalBars; class VideoBubble; struct DeviceSelection; +struct ConferencePanelMigration; class Panel final : public base::has_weak_ptr @@ -81,6 +82,8 @@ public: [[nodiscard]] bool isVisible() const; [[nodiscard]] bool isActive() const; + [[nodiscard]] ConferencePanelMigration migrationInfo() const; + base::weak_ptr showToast( const QString &text, crl::time duration = 0); diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 6b8d5cedc2..267df9b324 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -71,6 +71,11 @@ struct StartConferenceInfo { QString videoCaptureScreenId; }; +struct ConferencePanelMigration { + QScreen *screen = nullptr; + QRect geometry; +}; + } // namespace Calls namespace Calls::Group { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 418d13943c..138c92d06e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -191,6 +191,10 @@ struct Panel::ControlsBackgroundNarrow { }; Panel::Panel(not_null call) +: Panel(call, ConferencePanelMigration()) { +} + +Panel::Panel(not_null call, ConferencePanelMigration info) : _call(call) , _peer(call->peer()) , _layerBg(std::make_unique(widget())) @@ -253,7 +257,7 @@ Panel::Panel(not_null call) initWindow(); initWidget(); initControls(); - initLayout(); + initLayout(info); showAndActivate(); } @@ -1673,8 +1677,8 @@ void Panel::kickParticipantSure(not_null participantPeer) { } } -void Panel::initLayout() { - initGeometry(); +void Panel::initLayout(ConferencePanelMigration info) { + initGeometry(info); #ifndef Q_OS_MAC _controls->wrap.raise(); @@ -1704,22 +1708,32 @@ rpl::lifetime &Panel::lifetime() { return window()->lifetime(); } -void Panel::initGeometry() { - const auto center = Core::App().getPointForCallPanelCenter(); - const auto width = _call->rtmp() - ? st::groupCallWidthRtmp - : st::groupCallWidth; - const auto height = _call->rtmp() - ? st::groupCallHeightRtmp - : st::groupCallHeight; +void Panel::initGeometry(ConferencePanelMigration info) { const auto minWidth = _call->rtmp() ? st::groupCallWidthRtmpMin : st::groupCallWidth; const auto minHeight = _call->rtmp() ? st::groupCallHeightRtmpMin : st::groupCallHeight; - const auto rect = QRect(0, 0, width, height); - window()->setGeometry(rect.translated(center - rect.center())); + if (info.screen && !info.geometry.isEmpty()) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + window()->setScreen(info.screen); +#else // Qt >= 6.0.0 + window()->createWinId(); + window()->windowHandle()->setScreen(info.screen); +#endif // Qt < 6.0.0 + window()->setGeometry(info.geometry); + } else { + const auto center = Core::App().getPointForCallPanelCenter(); + const auto width = _call->rtmp() + ? st::groupCallWidthRtmp + : st::groupCallWidth; + const auto height = _call->rtmp() + ? st::groupCallHeightRtmp + : st::groupCallHeight; + const auto rect = QRect(0, 0, width, height); + window()->setGeometry(rect.translated(center - rect.center())); + } window()->setMinimumSize({ minWidth, minHeight }); window()->show(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 86108ca4b8..882049cb86 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -75,6 +75,7 @@ struct CallBodyLayout; namespace Calls { struct InviteRequest; +struct ConferencePanelMigration; } // namespace Calls namespace Calls::Group { @@ -90,7 +91,8 @@ class Panel final : public base::has_weak_ptr , private Ui::DesktopCapture::ChooseSourceDelegate { public: - Panel(not_null call); + explicit Panel(not_null call); + Panel(not_null call, ConferencePanelMigration info); ~Panel(); [[nodiscard]] not_null widget() const; @@ -156,8 +158,8 @@ private: void initWidget(); void initControls(); void initShareAction(); - void initLayout(); - void initGeometry(); + void initLayout(ConferencePanelMigration info); + void initGeometry(ConferencePanelMigration info); void setupScheduledLabels(rpl::producer date); void setupMembers(); void setupVideo(not_null viewport); From c972485555ae6d84a01e41fa0f4811e9d14898a4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 4 Apr 2025 23:53:54 +0500 Subject: [PATCH 112/190] Allow revoking confcall link. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/calls/calls.style | 19 +++++ .../calls/group/calls_group_common.cpp | 75 ++++++++++++++++++- .../calls/group/calls_group_common.h | 4 + Telegram/SourceFiles/ui/menu_icons.style | 1 + 5 files changed, 99 insertions(+), 3 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index bfaa87a884..700c14af2d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4930,6 +4930,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_join_button" = "Join Group Call"; "lng_confcall_create_link" = "Create Call Link"; "lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; +"lng_confcall_link_revoke" = "Revoke link"; +"lng_confcall_link_revoked_title" = "Link Revoked"; +"lng_confcall_link_revoked_text" = "A new link has been generated."; "lng_confcall_link_title" = "Call Link"; "lng_confcall_link_about" = "Anyone on Telegram can join your call by following the link below."; "lng_confcall_link_or" = "or"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 4f4d90b336..09aad20fce 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -578,6 +578,13 @@ groupCallPopupMenu: PopupMenu(defaultPopupMenu) { menu: groupCallMenu; animation: groupCallPanelAnimation; } +groupCallPopupMenuWithIcons: PopupMenu(popupMenuWithIcons) { + shadow: groupCallMenuShadow; + menu: Menu(groupCallMenu, menuWithIcons) { + arrow: icon {{ "menu/submenu_arrow", groupCallMemberNotJoinedStatus }}; + } + animation: groupCallPanelAnimation; +} groupCallPopupMenuWithVolume: PopupMenu(groupCallPopupMenu) { scrollPadding: margins(0px, 3px, 0px, 8px); menu: Menu(groupCallMenu) { @@ -1617,3 +1624,15 @@ groupCallInviteLink: SettingsButton(defaultSettingsButton) { } groupCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", mediaviewTextLinkFg }}; groupCallInviteLinkIconPosition: point(23px, 2px); + +confcallLinkMenu: IconButton(boxTitleClose) { + icon: icon {{ "title_menu_dots", boxTitleCloseFg }}; + iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }}; +} +groupCallLinkMenu: IconButton(confcallLinkMenu) { + icon: icon {{ "title_menu_dots", groupCallMemberInactiveIcon }}; + iconOver: icon {{ "title_menu_dots", groupCallMemberInactiveIcon }}; + ripple: RippleAnimation(defaultRippleAnimation) { + color: groupCallMembersBgOver; + } +} diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 9eb576b52e..b2e368513d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -21,15 +21,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/boost_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/widgets/popup_menu.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/painter.h" #include "ui/vertical_list.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "window/window_session_controller.h" +#include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_media_view.h" +#include "styles/style_menu_icons.h" #include "styles/style_calls.h" #include "styles/style_chat.h" @@ -235,9 +239,12 @@ void ConferenceCallJoinConfirm( ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle() { return { .box = &st::groupCallLinkBox, + .menuToggle = &st::groupCallLinkMenu, + .menu = &st::groupCallPopupMenuWithIcons, .close = &st::storiesStealthBoxClose, .centerLabel = &st::groupCallLinkCenteredText, .linkPreview = &st::groupCallLinkPreview, + .contextRevoke = &st::mediaMenuIconRemove, .shareBox = std::make_shared( DarkShareBoxStyle()), }; @@ -251,6 +258,12 @@ void ShowConferenceCallLinkBox( const auto st = args.st; const auto initial = args.initial; show->showBox(Box([=](not_null box) { + struct State { + base::unique_qptr menu; + bool resetting = false; + }; + const auto state = box->lifetime().make_state(); + box->setStyle(st.box ? *st.box : initial @@ -258,9 +271,65 @@ void ShowConferenceCallLinkBox( : st::confcallLinkBox); box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); - box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] { - box->closeBox(); - }); + const auto close = box->addTopButton( + st.close ? *st.close : st::boxTitleClose, + [=] { box->closeBox(); }); + + if (!args.initial && call->canManage()) { + const auto toggle = Ui::CreateChild( + close->parentWidget(), + st.menuToggle ? *st.menuToggle : st::confcallLinkMenu); + const auto handler = [=] { + if (state->resetting) { + return; + } + state->resetting = true; + using Flag = MTPphone_ToggleGroupCallSettings::Flag; + call->session().api().request( + MTPphone_ToggleGroupCallSettings( + MTP_flags(Flag::f_reset_invite_hash), + call->input(), + MTPbool()) // join_muted + ).done([=] { + auto copy = args; + const auto weak = Ui::MakeWeak(box); + copy.finished = [=](QString link) { + if (const auto strong = weak.data()) { + strong->closeBox(); + } + show->showToast({ + .title = tr::lng_confcall_link_revoked_title( + tr::now), + .text = tr::lng_confcall_link_revoked_text( + tr::now), + }); + }; + ExportConferenceCallLink( + show, + call, + std::move(copy)); + }).send(); + }; + toggle->setClickedCallback([=] { + state->menu = base::make_unique_q( + toggle, + st.menu ? *st.menu : st::popupMenuWithIcons); + state->menu->addAction( + tr::lng_confcall_link_revoke(tr::now), + handler, + (st.contextRevoke + ? st.contextRevoke + : &st::menuIconRemove)); + state->menu->popup(QCursor::pos()); + }); + + close->geometryValue( + ) | rpl::start_with_next([=](QRect geometry) { + toggle->moveToLeft( + geometry.x() - toggle->width(), + geometry.y()); + }, close->lifetime()); + } box->addRow( Info::BotStarRef::CreateLinkHeaderIcon(box, &call->session()), diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 267df9b324..e82a12f611 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -18,6 +18,7 @@ struct Box; struct FlatLabel; struct IconButton; struct InputField; +struct PopupMenu; } // namespace style namespace Data { @@ -164,9 +165,12 @@ void ConferenceCallJoinConfirm( struct ConferenceCallLinkStyleOverrides { const style::Box *box = nullptr; + const style::IconButton *menuToggle = nullptr; + const style::PopupMenu *menu = nullptr; const style::IconButton *close = nullptr; const style::FlatLabel *centerLabel = nullptr; const style::InputField *linkPreview = nullptr; + const style::icon *contextRevoke = nullptr; std::shared_ptr shareBox; }; [[nodiscard]] ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle(); diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index c7b94f0b06..4c49be52ff 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -212,6 +212,7 @@ mediaMenuIconArchiveStory: icon {{ "menu/stories_archive", mediaviewMenuFg }}; mediaMenuIconStealthLocked: icon {{ "menu/stealth_locked", mediaviewMenuFg }}; mediaMenuIconStealth: icon {{ "menu/stealth", mediaviewMenuFg }}; mediaMenuIconStats: icon {{ "menu/stats", mediaviewMenuFg }}; +mediaMenuIconRemove: icon {{ "menu/remove", mediaviewMenuFg }}; mediaMenuIconInfo: icon {{ "menu/info", mediaviewMenuFg }}; mediaMenuIconBlock: icon {{ "menu/block", mediaviewMenuFg }}; From 783c5c12e95a1fbebdf2b11599fe07d814cdf036 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 5 Apr 2025 00:06:31 +0500 Subject: [PATCH 113/190] Improve couple of phrases. --- Telegram/Resources/langs/lang.strings | 2 ++ .../SourceFiles/calls/group/calls_group_invite_controller.cpp | 2 +- Telegram/SourceFiles/window/window_session_controller.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 700c14af2d..23729e3c43 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4766,6 +4766,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_muted_by_me_status" = "muted for you"; "lng_group_call_invite_title" = "Invite members"; "lng_group_call_invite_button" = "Invite"; +"lng_group_call_confcall_add" = "Call"; "lng_group_call_add_to_group_one" = "{user} isn't a member of «{group}». Add them to the group?"; "lng_group_call_add_to_group_some" = "Some of those users aren't members of «{group}». Add them to the group?"; "lng_group_call_add_to_group_all" = "Those users aren't members of «{group}». Add them to the group?"; @@ -4932,6 +4933,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; "lng_confcall_link_revoke" = "Revoke link"; "lng_confcall_link_revoked_title" = "Link Revoked"; +"lng_confcall_link_inactive" = "This link is no longer active."; "lng_confcall_link_revoked_text" = "A new link has been generated."; "lng_confcall_link_title" = "Call Link"; "lng_confcall_link_about" = "Anyone on Telegram can join your call by following the link below."; diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 05477bd8e8..e5eb07be35 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -504,7 +504,7 @@ object_ptr PrepareInviteBox( raw->hasSelectedValue() | rpl::start_with_next([=](bool has) { box->clearButtons(); if (has) { - box->addButton(tr::lng_group_call_invite_button(), [=] { + box->addButton(tr::lng_group_call_confcall_add(), [=] { const auto call = weak.get(); if (!call) { return; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index c2e69511ae..3bb46a6a39 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -902,7 +902,7 @@ void SessionNavigation::resolveConferenceCall( _conferenceCallRequestId = 0; _conferenceCallSlug = QString(); _conferenceCallResolveContextId = FullMsgId(); - showToast(tr::lng_group_invite_bad_link(tr::now)); + showToast(tr::lng_confcall_link_inactive(tr::now)); }).send(); } From e5f31dbe8ed915ce419a31832c3d55708883f68d Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 5 Apr 2025 09:57:47 +0500 Subject: [PATCH 114/190] Remove unnecessary stale participant check. --- Telegram/SourceFiles/data/data_group_call.cpp | 55 ++----------------- Telegram/SourceFiles/data/data_group_call.h | 4 +- 2 files changed, 6 insertions(+), 53 deletions(-) diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 1dc6fef8ca..0318001421 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -108,7 +108,6 @@ GroupCall::~GroupCall() { } api().request(_unknownParticipantPeersRequestId).cancel(); api().request(_participantsRequestId).cancel(); - api().request(_checkStaleRequestId).cancel(); api().request(_reloadRequestId).cancel(); } @@ -212,9 +211,6 @@ void GroupCall::setParticipantsLoaded() { } void GroupCall::checkStaleParticipants() { - if (_checkStaleRequestId) { - return; - } const auto &list = _participantsWithAccess.current(); if (list.empty()) { return; @@ -227,57 +223,16 @@ void GroupCall::checkStaleParticipants() { existing.emplace(id); } } - if (list.size() > existing.size()) { - checkStaleRequest(); - return; - } + auto stale = base::flat_set(); for (const auto &id : list) { if (!existing.contains(id)) { - checkStaleRequest(); - return; + stale.reserve(list.size()); + stale.emplace(id); } } -} - -void GroupCall::checkStaleRequest() { - if (_checkStaleRequestId) { - return; + if (!stale.empty()) { + _staleParticipantIds.fire(std::move(stale)); } - _checkStaleRequestId = api().request(MTPphone_GetGroupParticipants( - input(), - MTP_vector(), // ids - MTP_vector(), // ssrcs - MTP_string(QString()), - MTP_int(kMaxConferenceMembers + 10) - )).done([=](const MTPphone_GroupParticipants &result) { - _checkStaleRequestId = 0; - const auto &list = _participantsWithAccess.current(); - if (list.empty()) { - return; - } - auto existing = base::flat_set(); - const auto &data = result.data(); - existing.reserve(data.vparticipants().v.size() + 1); - existing.emplace(session().userId()); - for (const auto &participant : data.vparticipants().v) { - const auto peerId = peerFromMTP(participant.data().vpeer()); - if (const auto id = peerToUser(peerId)) { - existing.emplace(id); - } - } - auto stale = base::flat_set(); - for (const auto &id : list) { - if (!existing.contains(id)) { - stale.reserve(list.size()); - stale.emplace(id); - } - } - if (!stale.empty()) { - _staleParticipantIds.fire(std::move(stale)); - } - }).fail([=] { - _checkStaleRequestId = 0; - }).send(); } bool GroupCall::processSavedFullCall() { diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 54c6990015..da804a442a 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -30,7 +30,7 @@ namespace Data { [[nodiscard]] const std::string &RtmpEndpointId(); -inline constexpr auto kMaxConferenceMembers = 10; +inline constexpr auto kMaxConferenceMembers = 200; struct LastSpokeTimes { crl::time anything = 0; @@ -157,7 +157,6 @@ public: -> rpl::producer>; void setParticipantsLoaded(); void checkStaleParticipants(); - void checkStaleRequest(); void enqueueUpdate(const MTPUpdate &update); void applyLocalUpdate( @@ -269,7 +268,6 @@ private: rpl::variable> _participantsWithAccess; rpl::event_stream> _staleParticipantIds; - mtpRequestId _checkStaleRequestId = 0; rpl::lifetime _checkStaleLifetime; bool _creator : 1 = false; From 8ab1a7268bd569ae82a7ab8e8ffdeea382c6813b Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 5 Apr 2025 09:55:11 +0400 Subject: [PATCH 115/190] Handle all types of confcall invite responses. --- Telegram/Resources/langs/lang.strings | 23 +++++-- .../calls/group/calls_group_call.cpp | 62 +++++++++++++++++-- .../calls/group/calls_group_common.h | 2 + .../group/calls_group_invite_controller.cpp | 6 -- 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 23729e3c43..90b11ff4cf 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4812,6 +4812,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_context_unpin_screen" = "Unpin screencast"; "lng_group_call_context_remove" = "Remove"; "lng_group_call_context_cancel_invite" = "Cancel invitation"; +"lng_group_call_context_stop_ringing" = "Stop calling"; +"lng_group_call_context_ban_from_call" = "Ban from call"; "lng_group_call_remove_channel" = "Remove {channel} from the video chat and ban them?"; "lng_group_call_remove_channel_from_channel" = "Remove {channel} from the live stream?"; "lng_group_call_mac_access" = "Telegram Desktop does not have access to system wide keyboard input required for Push to Talk."; @@ -4940,12 +4942,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_link_or" = "or"; "lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}"; "lng_confcall_link_join_link" = "Open call {arrow}"; -"lng_confcall_invite_done_user" = "You invited {user} to join the call."; -"lng_confcall_invite_done_many#one" = "You invited **{count} person** to join the call."; -"lng_confcall_invite_done_many#other" = "You invited **{count} people** to join the call."; -"lng_confcall_invite_fail_user" = "You cannot call {user} because of their privacy settings."; -"lng_confcall_invite_fail_many#one" = "You cannot call **{count} person** because of their privacy settings."; -"lng_confcall_invite_fail_many#other" = "You cannot call **{count} people** because of their privacy settings."; +"lng_confcall_invite_done_user" = "You're calling {user} to join."; +"lng_confcall_invite_done_many#one" = "You're calling **{count} person** to join."; +"lng_confcall_invite_done_many#other" = "You're calling **{count} people** to join."; +"lng_confcall_invite_already_user" = "{user} is already in the call."; +"lng_confcall_invite_already_many#one" = "**{count} person** is already in the call."; +"lng_confcall_invite_already_many#other" = "**{count} people** are already in the call."; +"lng_confcall_invite_privacy_user" = "You cannot call {user} because of their privacy settings."; +"lng_confcall_invite_privacy_many#one" = "You cannot call **{count} person** because of their privacy settings."; +"lng_confcall_invite_privacy_many#other" = "You cannot call **{count} people** because of their privacy settings."; +"lng_confcall_invite_fail_user" = "Couldn't call {user} to join."; +"lng_confcall_invite_fail_many#one" = "Couldn't call **{count} person** to join."; +"lng_confcall_invite_fail_many#other" = "Couldn't call **{count} people** to join."; +"lng_confcall_invite_kicked_user" = "{user} was removed from the call."; +"lng_confcall_invite_kicked_many#one" = "**{count} person** was removed from the call."; +"lng_confcall_invite_kicked_many#other" = "**{count} people** were removed from the call."; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index dc9d1f6af3..bd0c703ccc 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -3819,8 +3819,12 @@ void GroupCall::inviteUsers( state->result.privacyRestricted.push_back(user); } else if (type == u"USER_ALREADY_PARTICIPANT"_q) { state->result.alreadyIn.push_back(user); + } else if (type == u"USER_WAS_KICKED"_q) { + state->result.kicked.push_back(user); } else if (type == u"GROUPCALL_FORBIDDEN"_q) { startRejoin(); + } else { + state->result.failed.push_back(user); } finishRequest(); }).send(); @@ -3989,37 +3993,83 @@ void GroupCall::destroyScreencast() { TextWithEntities ComposeInviteResultToast( const InviteResult &result) { auto text = TextWithEntities(); + const auto append = [&](TextWithEntities part) { + if (!text.empty()) { + text.append(u"\n\n"_q); + } + text.append(part); + }; + const auto invited = int(result.invited.size()); + const auto already = int(result.alreadyIn.size()); const auto restricted = int(result.privacyRestricted.size()); + const auto kicked = int(result.kicked.size()); + const auto failed = int(result.failed.size()); if (invited == 1) { - text.append(tr::lng_confcall_invite_done_user( + append(tr::lng_confcall_invite_done_user( tr::now, lt_user, Ui::Text::Bold(result.invited.front()->shortName()), Ui::Text::RichLangValue)); } else if (invited > 1) { - text.append(tr::lng_confcall_invite_done_many( + append(tr::lng_confcall_invite_done_many( tr::now, lt_count, invited, Ui::Text::RichLangValue)); } - if (invited && restricted) { - text.append(u"\n\n"_q); + if (already == 1) { + append(tr::lng_confcall_invite_already_user( + tr::now, + lt_user, + Ui::Text::Bold(result.alreadyIn.front()->shortName()), + Ui::Text::RichLangValue)); + } else if (already > 1) { + append(tr::lng_confcall_invite_already_many( + tr::now, + lt_count, + already, + Ui::Text::RichLangValue)); } if (restricted == 1) { - text.append(tr::lng_confcall_invite_fail_user( + append(tr::lng_confcall_invite_fail_user( tr::now, lt_user, Ui::Text::Bold(result.privacyRestricted.front()->shortName()), Ui::Text::RichLangValue)); } else if (restricted > 1) { - text.append(tr::lng_confcall_invite_fail_many( + append(tr::lng_confcall_invite_fail_many( tr::now, lt_count, restricted, Ui::Text::RichLangValue)); } + if (kicked == 1) { + append(tr::lng_confcall_invite_kicked_user( + tr::now, + lt_user, + Ui::Text::Bold(result.kicked.front()->shortName()), + Ui::Text::RichLangValue)); + } else if (kicked > 1) { + append(tr::lng_confcall_invite_kicked_many( + tr::now, + lt_count, + kicked, + Ui::Text::RichLangValue)); + } + if (failed == 1) { + append(tr::lng_confcall_invite_fail_user( + tr::now, + lt_user, + Ui::Text::Bold(result.failed.front()->shortName()), + Ui::Text::RichLangValue)); + } else if (failed > 1) { + append(tr::lng_confcall_invite_fail_many( + tr::now, + lt_count, + failed, + Ui::Text::RichLangValue)); + } return text; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index e82a12f611..93412787c0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -57,6 +57,8 @@ struct InviteResult { std::vector> invited; std::vector> alreadyIn; std::vector> privacyRestricted; + std::vector> kicked; + std::vector> failed; }; struct StartConferenceInfo { diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index e5eb07be35..68182df06a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -511,10 +511,6 @@ object_ptr PrepareInviteBox( } const auto done = [=](InviteResult result) { (*close)(); - if (result.invited.empty() - && result.privacyRestricted.empty()) { - return; - } showToast({ ComposeInviteResultToast(result) }); }; call->inviteUsers( @@ -566,8 +562,6 @@ object_ptr PrepareInviteBox( lt_count, result.invited.size(), Ui::Text::RichLangValue)); - } else { - Unexpected("Result in GroupCall::inviteUsers."); } }); }; From 344c0f6427748f00f8c3db79be2342252d767632 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 5 Apr 2025 10:12:32 +0400 Subject: [PATCH 116/190] Check phone access on join. --- Telegram/Resources/langs/lang.strings | 1 + .../SourceFiles/calls/group/calls_group_call.cpp | 4 ++-- .../SourceFiles/calls/group/calls_group_common.cpp | 5 +++-- .../window/window_session_controller.cpp | 14 ++++++++++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 90b11ff4cf..2052b3f8b5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4957,6 +4957,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_invite_kicked_user" = "{user} was removed from the call."; "lng_confcall_invite_kicked_many#one" = "**{count} person** was removed from the call."; "lng_confcall_invite_kicked_many#other" = "**{count} people** were removed from the call."; +"lng_confcall_not_accessible" = "This call is no longer accessible."; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index bd0c703ccc..ce2d214f18 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1643,8 +1643,8 @@ void GroupCall::sendJoinRequest() { hangup(); Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q) - ? tr::lng_group_not_accessible(tr::now) - : Lang::Hard::ServerError()); + ? tr::lng_confcall_not_accessible(tr::now) + : type); }).send(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index b2e368513d..f0e09c6f03 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -300,8 +300,9 @@ void ShowConferenceCallLinkBox( show->showToast({ .title = tr::lng_confcall_link_revoked_title( tr::now), - .text = tr::lng_confcall_link_revoked_text( - tr::now), + .text = { + tr::lng_confcall_link_revoked_text(tr::now), + }, }); }; ExportConferenceCallLink( diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 3bb46a6a39..342313b3b6 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -876,7 +876,7 @@ void SessionNavigation::resolveConferenceCall( const auto slug = base::take(_conferenceCallSlug); const auto inviteMsgId = base::take(_conferenceCallInviteMsgId); const auto contextId = base::take(_conferenceCallResolveContextId); - result.data().vcall().match([&](const auto &data) { + result.data().vcall().match([&](const MTPDgroupCall &data) { const auto call = session().data().sharedConferenceCall( data.vid().v, data.vaccess_hash().v); @@ -897,12 +897,22 @@ void SessionNavigation::resolveConferenceCall( call, (inviter && !inviter->isSelf()) ? inviter : nullptr, join)); + }, [&](const MTPDgroupCallDiscarded &data) { + if (inviteMsgId) { + showToast(tr::lng_confcall_not_accessible(tr::now)); + } else { + showToast(tr::lng_confcall_link_inactive(tr::now)); + } }); }).fail([=] { _conferenceCallRequestId = 0; _conferenceCallSlug = QString(); _conferenceCallResolveContextId = FullMsgId(); - showToast(tr::lng_confcall_link_inactive(tr::now)); + if (base::take(_conferenceCallResolveContextId)) { + showToast(tr::lng_confcall_not_accessible(tr::now)); + } else { + showToast(tr::lng_confcall_link_inactive(tr::now)); + } }).send(); } From 55c05d1a6ef25ff7bc753aa116fdcf2e682e5e3c Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 5 Apr 2025 13:51:23 +0500 Subject: [PATCH 117/190] Allow discard invite or cancel ringing. --- Telegram/Resources/langs/lang.strings | 2 +- Telegram/SourceFiles/calls/calls_instance.cpp | 24 +++++++++++++++---- Telegram/SourceFiles/calls/calls_instance.h | 3 ++- .../calls/group/calls_group_members.cpp | 11 ++++++--- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2052b3f8b5..788df16212 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4811,7 +4811,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_context_pin_screen" = "Pin screencast"; "lng_group_call_context_unpin_screen" = "Unpin screencast"; "lng_group_call_context_remove" = "Remove"; -"lng_group_call_context_cancel_invite" = "Cancel invitation"; +"lng_group_call_context_cancel_invite" = "Discard invite"; "lng_group_call_context_stop_ringing" = "Stop calling"; "lng_group_call_context_ban_from_call" = "Ban from call"; "lng_group_call_remove_channel" = "Remove {channel} from the video chat and ban them?"; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 475b2af982..0b21caa06a 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_chat.h" +#include "data/data_histories.h" #include "data/data_session.h" #include "media/audio/media_audio_track.h" #include "platform/platform_specific.h" @@ -982,7 +983,8 @@ void Instance::declineIncomingConferenceInvites(CallId conferenceId) { void Instance::declineOutgoingConferenceInvite( CallId conferenceId, - not_null user) { + not_null user, + bool discard) { const auto i = _conferenceInvites.find(conferenceId); if (i == end(_conferenceInvites)) { return; @@ -992,10 +994,22 @@ void Instance::declineOutgoingConferenceInvite( return; } const auto api = &user->session().api(); - for (const auto &messageId : base::take(j->second.outgoing)) { - api->request(MTPphone_DeclineConferenceCallInvite( - MTP_int(messageId.bare) - )).send(); + auto ids = base::take(j->second.outgoing); + auto inputs = QVector(); + for (const auto &messageId : ids) { + if (discard) { + inputs.push_back(MTP_int(messageId.bare)); + } else { + api->request(MTPphone_DeclineConferenceCallInvite( + MTP_int(messageId.bare) + )).send(); + } + } + if (!inputs.empty()) { + user->owner().histories().deleteMessages( + user->owner().history(user), + std::move(inputs), + true); } if (!j->second.incoming.empty()) { return; diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 65088d9495..39896cbc1c 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -138,7 +138,8 @@ public: void declineIncomingConferenceInvites(CallId conferenceId); void declineOutgoingConferenceInvite( CallId conferenceId, - not_null user); + not_null user, + bool discard = false); [[nodiscard]] FnMut addAsyncWaiter(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 0e3d2eb1d7..3ec4f87411 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1468,14 +1468,19 @@ base::unique_qptr Members::Controller::createRowContextMenu( && participantPeer->isUser() && conference) { const auto id = conference->id(); - const auto cancelInvite = [=] { + const auto cancelInvite = [=](bool discard) { Core::App().calls().declineOutgoingConferenceInvite( id, - participantPeer->asUser()); + participantPeer->asUser(), + discard); }; + result->addAction( + tr::lng_group_call_context_stop_ringing(tr::now), + [=] { cancelInvite(false); }); result->addAction( tr::lng_group_call_context_cancel_invite(tr::now), - cancelInvite); + [=] { cancelInvite(true); }); + result->addSeparator(); } result->addAction( (participantPeer->isUser() From b036bedbc3abbe4d44047cc8e21610b6df4fe361 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 6 Apr 2025 17:21:24 +0400 Subject: [PATCH 118/190] Support distinct calling/invited states. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/calls/calls.style | 3 + Telegram/SourceFiles/calls/calls_call.cpp | 3 +- Telegram/SourceFiles/calls/calls_instance.cpp | 19 ++++- Telegram/SourceFiles/calls/calls_instance.h | 3 +- .../calls/group/calls_group_call.cpp | 73 +++++++++++------ .../calls/group/calls_group_call.h | 5 ++ .../group/calls_group_invite_controller.cpp | 14 +++- .../calls/group/calls_group_members.cpp | 82 +++++++++++++------ .../calls/group/calls_group_members_row.cpp | 14 +++- .../calls/group/calls_group_members_row.h | 6 +- Telegram/SourceFiles/data/data_group_call.cpp | 5 +- .../SourceFiles/data/data_media_types.cpp | 24 +++--- Telegram/SourceFiles/data/data_session.cpp | 39 ++++++--- Telegram/SourceFiles/data/data_session.h | 14 ++-- Telegram/SourceFiles/history/history_item.cpp | 21 +++-- 16 files changed, 221 insertions(+), 105 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 788df16212..1c234f6755 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4762,6 +4762,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_invite" = "Invite Members"; "lng_group_call_invite_conf" = "Add People"; "lng_group_call_invited_status" = "invited"; +"lng_group_call_calling_status" = "calling..."; "lng_group_call_blockchain_only_status" = "listening"; "lng_group_call_muted_by_me_status" = "muted for you"; "lng_group_call_invite_title" = "Invite members"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 09aad20fce..b3b6595030 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -893,6 +893,8 @@ groupCallMemberColoredCrossLine: CrossLineAnimation(groupCallMemberInactiveCross fg: groupCallMemberMutedIcon; icon: icon {{ "calls/group_calls_unmuted", groupCallMemberActiveIcon }}; } +groupCallMemberCalling: icon {{ "calls/call_answer", groupCallMemberInactiveIcon }}; +groupCallMemberCallingPosition: point(0px, 8px); groupCallMemberInvited: icon {{ "calls/group_calls_invited", groupCallMemberInactiveIcon }}; groupCallMemberInvitedPosition: point(2px, 12px); groupCallMemberRaisedHand: icon {{ "calls/group_calls_raised_hand", groupCallMemberInactiveStatus }}; @@ -1327,6 +1329,7 @@ groupCallNarrowRaisedHand: icon {{ "calls/video_mini_speak", groupCallMemberInac groupCallNarrowCameraIcon: icon {{ "calls/video_mini_video", groupCallMemberNotJoinedStatus }}; groupCallNarrowScreenIcon: icon {{ "calls/video_mini_screencast", groupCallMemberNotJoinedStatus }}; groupCallNarrowInvitedIcon: icon {{ "calls/video_mini_invited", groupCallMemberNotJoinedStatus }}; +groupCallNarrowCallingIcon: icon {{ "calls/video_mini_invited", groupCallMemberNotJoinedStatus }}; groupCallNarrowIconPosition: point(-4px, 2px); groupCallNarrowIconSkip: 15px; groupCallOutline: 2px; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 7455ea2be6..0ba4672931 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -1542,7 +1542,8 @@ void Call::finish( _user->owner().registerInvitedToCallUser( migrateCall->id(), migrateCall, - _user); + _user, + true); } const auto session = &_user->session(); const auto weak = base::make_weak(this); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 0b21caa06a..7e2ae032b0 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -930,7 +930,8 @@ void Instance::unregisterConferenceInvite( CallId conferenceId, not_null user, MsgId messageId, - bool incoming) { + bool incoming, + bool onlyStopCalling) { const auto i = _conferenceInvites.find(conferenceId); if (i == end(_conferenceInvites)) { return; @@ -940,9 +941,14 @@ void Instance::unregisterConferenceInvite( return; } auto &info = j->second; - (incoming ? info.incoming : info.outgoing).remove(messageId); + if (!(incoming ? info.incoming : info.outgoing).remove(messageId)) { + return; + } if (!incoming) { - user->owner().unregisterInvitedToCallUser(conferenceId, user); + user->owner().unregisterInvitedToCallUser( + conferenceId, + user, + onlyStopCalling); } if (info.incoming.empty() && info.outgoing.empty()) { i->second.users.erase(j); @@ -1010,6 +1016,11 @@ void Instance::declineOutgoingConferenceInvite( user->owner().history(user), std::move(inputs), true); + for (const auto &messageId : ids) { + if (const auto item = user->owner().message(user, messageId)) { + item->destroy(); + } + } } if (!j->second.incoming.empty()) { return; @@ -1018,7 +1029,7 @@ void Instance::declineOutgoingConferenceInvite( if (i->second.users.empty()) { _conferenceInvites.erase(i); } - user->owner().unregisterInvitedToCallUser(conferenceId, user); + user->owner().unregisterInvitedToCallUser(conferenceId, user, !discard); } void Instance::showConferenceInvite( diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 39896cbc1c..1157c777a0 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -131,7 +131,8 @@ public: CallId conferenceId, not_null user, MsgId messageId, - bool incoming); + bool incoming, + bool onlyStopCalling = false); void showConferenceInvite( not_null user, MsgId conferenceInviteMsgId); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index ce2d214f18..2ea46d9dd5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1623,6 +1623,9 @@ void GroupCall::sendJoinRequest() { if (const auto once = base::take(_migratedConferenceInfo)) { processMigration(*once); } + for (const auto &callback : base::take(_rejoinedCallbacks)) { + callback(); + } }).fail([=](const MTP::Error &error) { const auto type = error.type(); if (_e2e) { @@ -3775,6 +3778,45 @@ void GroupCall::editParticipant( }).send(); } + +void GroupCall::inviteToConference( + InviteRequest request, + Fn()> resultAddress, + Fn finishRequest) { + using Flag = MTPphone_InviteConferenceCallParticipant::Flag; + const auto user = request.user; + _api.request(MTPphone_InviteConferenceCallParticipant( + MTP_flags(request.video ? Flag::f_video : Flag()), + inputCall(), + user->inputUser + )).done([=](const MTPUpdates &result) { + const auto call = _conferenceCall.get(); + user->owner().registerInvitedToCallUser(_id, call, user, true); + _peer->session().api().applyUpdates(result); + resultAddress()->invited.push_back(user); + finishRequest(); + }).fail([=](const MTP::Error &error) { + const auto result = resultAddress(); + const auto type = error.type(); + if (type == u"USER_PRIVACY_RESTRICTED"_q) { + result->privacyRestricted.push_back(user); + } else if (type == u"USER_ALREADY_PARTICIPANT"_q) { + result->alreadyIn.push_back(user); + } else if (type == u"USER_WAS_KICKED"_q) { + result->kicked.push_back(user); + } else if (type == u"GROUPCALL_FORBIDDEN"_q) { + startRejoin(); + _rejoinedCallbacks.push_back([=] { + inviteToConference(request, resultAddress, finishRequest); + }); + return; + } else { + result->failed.push_back(user); + } + finishRequest(); + }).send(); +} + void GroupCall::inviteUsers( const std::vector &requests, Fn done) { @@ -3802,32 +3844,9 @@ void GroupCall::inviteUsers( if (const auto call = _conferenceCall.get()) { for (const auto &request : requests) { - using Flag = MTPphone_InviteConferenceCallParticipant::Flag; - const auto user = request.user; - _api.request(MTPphone_InviteConferenceCallParticipant( - MTP_flags(request.video ? Flag::f_video : Flag()), - inputCallSafe(), - user->inputUser - )).done([=](const MTPUpdates &result) { - owner->registerInvitedToCallUser(_id, call, user); - _peer->session().api().applyUpdates(result); - state->result.invited.push_back(user); - finishRequest(); - }).fail([=](const MTP::Error &error) { - const auto type = error.type(); - if (type == u"USER_PRIVACY_RESTRICTED"_q) { - state->result.privacyRestricted.push_back(user); - } else if (type == u"USER_ALREADY_PARTICIPANT"_q) { - state->result.alreadyIn.push_back(user); - } else if (type == u"USER_WAS_KICKED"_q) { - state->result.kicked.push_back(user); - } else if (type == u"GROUPCALL_FORBIDDEN"_q) { - startRejoin(); - } else { - state->result.failed.push_back(user); - } - finishRequest(); - }).send(); + inviteToConference(request, [=] { + return &state->result; + }, finishRequest); ++state->requests; } return; @@ -3857,7 +3876,7 @@ void GroupCall::inviteUsers( }; for (const auto &request : requests) { const auto user = request.user; - owner->registerInvitedToCallUser(_id, _peer, user); + owner->registerInvitedToCallUser(_id, _peer, user, false); usersSlice.push_back(user); slice.push_back(user->inputUser); if (slice.size() == kMaxInvitePerSlice) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 99c0a1e54c..c7f8368061 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -625,6 +625,10 @@ private: void markTrackShown(const VideoEndpoint &endpoint, bool shown); void processMigration(StartConferenceInfo conference); + void inviteToConference( + InviteRequest request, + Fn()> resultAddress, + Fn finishRequest); [[nodiscard]] int activeVideoSendersCount() const; @@ -645,6 +649,7 @@ private: rpl::variable _state = State::Creating; base::flat_set _unresolvedSsrcs; rpl::event_stream _errors; + std::vector> _rejoinedCallbacks; bool _recordingStoppedByMe = false; bool _requestedVideoChannelsUpdateScheduled = false; diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 68182df06a..876e24de95 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -477,15 +477,23 @@ object_ptr PrepareInviteBox( return nullptr; } const auto peer = call->peer(); + const auto conference = call->conference(); const auto weak = base::make_weak(call); - auto alreadyIn = peer->owner().invitedToCallUsers(real->id()); + const auto &invited = peer->owner().invitedToCallUsers(real->id()); + auto alreadyIn = base::flat_set>(); + alreadyIn.reserve(invited.size() + real->participants().size() + 1); + alreadyIn.emplace(peer->session().user()); for (const auto &participant : real->participants()) { if (const auto user = participant.peer->asUser()) { alreadyIn.emplace(user); } } - alreadyIn.emplace(peer->session().user()); - if (call->conference()) { + for (const auto &[user, calling] : invited) { + if (!conference || calling) { + alreadyIn.emplace(user); + } + } + if (conference) { const auto close = std::make_shared>(); const auto shareLink = [=] { Assert(shareConferenceLink != nullptr); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 3ec4f87411..52d787421d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -108,7 +108,8 @@ private: [[nodiscard]] std::unique_ptr createRow( const Data::GroupCallParticipant &participant); [[nodiscard]] std::unique_ptr createInvitedRow( - not_null participantPeer); + not_null participantPeer, + bool calling); [[nodiscard]] std::unique_ptr createWithAccessRow( not_null participantPeer); @@ -494,8 +495,9 @@ void Members::Controller::toggleVideoEndpointActive( bool Members::Controller::appendInvitedUsers() { auto changed = false; if (const auto id = _call->id()) { - for (const auto &user : _peer->owner().invitedToCallUsers(id)) { - if (auto row = createInvitedRow(user)) { + const auto &invited = _peer->owner().invitedToCallUsers(id); + for (const auto &[user, calling] : invited) { + if (auto row = createInvitedRow(user, calling)) { delegate()->peerListAppendRow(std::move(row)); changed = true; } @@ -514,14 +516,16 @@ void Members::Controller::setupInvitedUsers() { ) | rpl::filter([=](const Invite &invite) { return (invite.id == _call->id()); }) | rpl::start_with_next([=](const Invite &invite) { + const auto user = invite.user; if (invite.removed) { - if (const auto row = findRow(invite.user)) { - if (row->state() == Row::State::Invited) { + if (const auto row = findRow(user)) { + if (row->state() == Row::State::Invited + || row->state() == Row::State::Calling) { delegate()->peerListRemoveRow(row); delegate()->peerListRefreshRows(); } } - } else if (auto row = createInvitedRow(invite.user)) { + } else if (auto row = createInvitedRow(user, invite.calling)) { delegate()->peerListAppendRow(std::move(row)); delegate()->peerListRefreshRows(); } @@ -571,7 +575,8 @@ void Members::Controller::setupWithAccessUsers() { if (const auto count = delegate()->peerListFullRowsCount()) { const auto last = delegate()->peerListRowAt(count - 1); const auto state = static_cast(last.get())->state(); - if (state == Row::State::Invited) { + if (state == Row::State::Invited + || state == Row::State::Calling) { partition = true; } } @@ -584,7 +589,8 @@ void Members::Controller::setupWithAccessUsers() { if (partition) { delegate()->peerListPartitionRows([](const PeerListRow &row) { const auto state = static_cast(row).state(); - return (state != Row::State::Invited); + return (state != Row::State::Invited) + && (state != Row::State::Calling); }); } delegate()->peerListRefreshRows(); @@ -599,6 +605,7 @@ void Members::Controller::updateRow( auto addedToBottom = (Row*)nullptr; if (const auto row = findRow(now.peer)) { if (row->state() == Row::State::Invited + || row->state() == Row::State::Calling || row->state() == Row::State::WithAccess) { reorderIfNonRealBefore = row->absoluteIndex(); } @@ -631,7 +638,9 @@ void Members::Controller::updateRow( reorderIfNonRealBefore - 1).get(); using State = Row::State; const auto state = static_cast(row)->state(); - return (state == State::Invited) || (state == State::WithAccess); + return (state == State::Invited) + || (state == State::Calling) + || (state == State::WithAccess); }(); if (reorder) { partitionRows(); @@ -666,12 +675,15 @@ void Members::Controller::partitionRows() { if (state == State::WithAccess) { hadWithAccess = true; } - return (state != State::Invited) && (state != State::WithAccess); + return (state != State::Invited) + && (state != State::Calling) + && (state != State::WithAccess); }); if (hadWithAccess) { delegate()->peerListPartitionRows([](const PeerListRow &row) { const auto state = static_cast(row).state(); - return (state != Row::State::Invited); + return (state != Row::State::Invited) + && (state != Row::State::Calling); }); } } @@ -811,7 +823,7 @@ void Members::Controller::updateRow( } else if (noParticipantState == Row::State::WithAccess) { row->updateStateWithAccess(); } else { - row->updateStateInvited(); + row->updateStateInvited(noParticipantState == Row::State::Calling); } const auto wasNoSounding = _soundingRowBySsrc.empty(); @@ -1093,15 +1105,21 @@ void Members::Controller::rowPaintIcon( return; } const auto narrow = (state.style == MembersRowStyle::Narrow); - if (state.invited) { + if (state.invited || state.calling) { if (narrow) { - st::groupCallNarrowInvitedIcon.paintInCenter(p, rect); + (state.invited + ? st::groupCallNarrowInvitedIcon + : st::groupCallNarrowCallingIcon).paintInCenter(p, rect); } else { - st::groupCallMemberInvited.paintInCenter( + const auto &icon = state.invited + ? st::groupCallMemberInvited + : st::groupCallMemberCalling; + const auto shift = state.invited + ? st::groupCallMemberInvitedPosition + : st::groupCallMemberCallingPosition; + icon.paintInCenter( p, - QRect( - rect.topLeft() + st::groupCallMemberInvitedPosition, - st::groupCallMemberInvited.size())); + QRect(rect.topLeft() + shift, icon.size())); } return; } @@ -1464,9 +1482,10 @@ base::unique_qptr Members::Controller::createRowContextMenu( } } else { const auto conference = _call->conferenceCall().get(); - if (muteState == Row::State::Invited + if (conference && participantPeer->isUser() - && conference) { + && (muteState == Row::State::Invited + || muteState == Row::State::Calling)) { const auto id = conference->id(); const auto cancelInvite = [=](bool discard) { Core::App().calls().declineOutgoingConferenceInvite( @@ -1474,9 +1493,11 @@ base::unique_qptr Members::Controller::createRowContextMenu( participantPeer->asUser(), discard); }; - result->addAction( - tr::lng_group_call_context_stop_ringing(tr::now), - [=] { cancelInvite(false); }); + if (muteState == Row::State::Calling) { + result->addAction( + tr::lng_group_call_context_stop_ringing(tr::now), + [=] { cancelInvite(false); }); + } result->addAction( tr::lng_group_call_context_cancel_invite(tr::now), [=] { cancelInvite(true); }); @@ -1497,6 +1518,7 @@ base::unique_qptr Members::Controller::createRowContextMenu( const auto canKick = [&] { const auto user = participantPeer->asUser(); if (muteState == Row::State::Invited + || muteState == Row::State::Calling || muteState == Row::State::WithAccess) { return false; } else if (const auto chat = _peer->asChat()) { @@ -1626,6 +1648,7 @@ void Members::Controller::addMuteActionsToContextMenu( const auto muteAction = [&]() -> QAction* { if (muteState == Row::State::Invited + || muteState == Row::State::Calling || muteState == Row::State::WithAccess || _call->rtmp() || isMe(participantPeer) @@ -1681,12 +1704,19 @@ std::unique_ptr Members::Controller::createRow( } std::unique_ptr Members::Controller::createInvitedRow( - not_null participantPeer) { - if (findRow(participantPeer)) { + not_null participantPeer, + bool calling) { + if (const auto row = findRow(participantPeer)) { + if (row->state() == Row::State::Invited + || row->state() == Row::State::Calling) { + row->updateStateInvited(calling); + delegate()->peerListUpdateRow(row); + } return nullptr; } + const auto state = calling ? Row::State::Calling : Row::State::Invited; auto result = std::make_unique(this, participantPeer); - updateRow(result.get(), std::nullopt, nullptr); + updateRow(result.get(), std::nullopt, nullptr, state); return result; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp index 7dddb9240a..3f213d61d5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp @@ -138,9 +138,9 @@ void MembersRow::setSkipLevelUpdate(bool value) { _skipLevelUpdate = value; } -void MembersRow::updateStateInvited() { +void MembersRow::updateStateInvited(bool calling) { setVolume(Group::kDefaultVolume); - setState(State::Invited); + setState(calling ? State::Calling : State::Invited); setSounding(false); setSpeaking(false); _mutedByMe = false; @@ -640,11 +640,13 @@ void MembersRow::paintComplexStatusText( const auto useAbout = !_about.isEmpty() && (_state != State::WithAccess) && (_state != State::Invited) + && (_state != State::Calling) && (style != MembersRowStyle::Video) && ((_state == State::RaisedHand && !_raisedHandStatus) || (_state != State::RaisedHand && !_speaking)); if (!useAbout && _state != State::Invited + && _state != State::Calling && _state != State::WithAccess && !_mutedByMe) { paintStatusIcon(p, x, y, st, font, selected, narrowMode); @@ -693,6 +695,8 @@ void MembersRow::paintComplexStatusText( ? tr::lng_status_connecting(tr::now) : (_state == State::WithAccess) ? tr::lng_group_call_blockchain_only_status(tr::now) + : (_state == State::Calling) + ? tr::lng_group_call_calling_status(tr::now) : tr::lng_group_call_invited_status(tr::now))); } } @@ -706,6 +710,7 @@ QSize MembersRow::rightActionSize() const { bool MembersRow::rightActionDisabled() const { return _delegate->rowIsMe(peer()) || (_state == State::Invited) + || (_state == State::Calling) || !_delegate->rowCanMuteMembers(); } @@ -731,7 +736,9 @@ void MembersRow::rightActionPaint( size.width(), size.height(), outerWidth); - if (_state == State::Invited) { + if (_state == State::Invited + || _state == State::Calling + || _state == State::WithAccess) { _actionRipple = nullptr; } if (_actionRipple) { @@ -761,6 +768,7 @@ MembersRowDelegate::IconState MembersRow::computeIconState( .mutedByMe = _mutedByMe, .raisedHand = (_state == State::RaisedHand), .invited = (_state == State::Invited), + .calling = (_state == State::Calling), .style = style, }; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.h b/Telegram/SourceFiles/calls/group/calls_group_members_row.h index ef59b2686c..2aab2ead48 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.h @@ -24,7 +24,7 @@ struct PeerUserpicView; namespace Calls::Group { -enum class MembersRowStyle { +enum class MembersRowStyle : uchar { Default, Narrow, Video, @@ -40,6 +40,7 @@ public: bool mutedByMe = false; bool raisedHand = false; bool invited = false; + bool calling = false; MembersRowStyle style = MembersRowStyle::Default; }; virtual bool rowIsMe(not_null participantPeer) = 0; @@ -75,13 +76,14 @@ public: Muted, RaisedHand, Invited, + Calling, WithAccess, }; void setAbout(const QString &about); void setSkipLevelUpdate(bool value); void updateState(const Data::GroupCallParticipant &participant); - void updateStateInvited(); + void updateStateInvited(bool calling); void updateStateWithAccess(); void updateLevel(float level); void updateBlobAnimation(crl::time now); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 0318001421..6747b01b98 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -791,7 +791,10 @@ void GroupCall::applyParticipantsSlice( } if (adding) { if (const auto user = participantPeer->asUser()) { - _peer->owner().unregisterInvitedToCallUser(_id, user); + _peer->owner().unregisterInvitedToCallUser( + _id, + user, + false); } } }); diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index f715cc0bb1..2686a39af9 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -1701,13 +1701,11 @@ MediaCall::MediaCall(not_null parent, const Call &call) const auto peer = parent->history()->peer; peer->owner().registerCallItem(parent); if (const auto user = _call.conferenceId ? peer->asUser() : nullptr) { - if (_call.state == CallState::Invitation) { - Core::App().calls().registerConferenceInvite( - _call.conferenceId, - user, - parent->id, - !parent->out()); - } + Core::App().calls().registerConferenceInvite( + _call.conferenceId, + user, + parent->id, + !parent->out()); } } @@ -1716,13 +1714,11 @@ MediaCall::~MediaCall() { const auto peer = parent->history()->peer; peer->owner().unregisterCallItem(parent); if (const auto user = _call.conferenceId ? peer->asUser() : nullptr) { - if (_call.state == CallState::Invitation) { - Core::App().calls().unregisterConferenceInvite( - _call.conferenceId, - user, - parent->id, - !parent->out()); - } + Core::App().calls().unregisterConferenceInvite( + _call.conferenceId, + user, + parent->id, + !parent->out()); } } diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 641845ae0e..6bc8eaca38 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1219,8 +1219,8 @@ void Session::checkLocalUsersWentOffline() { } auto Session::invitedToCallUsers(CallId callId) const --> const base::flat_set> & { - static const base::flat_set> kEmpty; +-> const base::flat_map, bool> & { + static const base::flat_map, bool> kEmpty; const auto i = _invitedToCallUsers.find(callId); return (i != _invitedToCallUsers.end()) ? i->second : kEmpty; } @@ -1228,14 +1228,16 @@ auto Session::invitedToCallUsers(CallId callId) const void Session::registerInvitedToCallUser( CallId callId, not_null peer, - not_null user) { - registerInvitedToCallUser(callId, peer->groupCall(), user); + not_null user, + bool calling) { + registerInvitedToCallUser(callId, peer->groupCall(), user, calling); } void Session::registerInvitedToCallUser( CallId callId, GroupCall *call, - not_null user) { + not_null user, + bool calling) { if (call && call->id() == callId) { const auto inCall = ranges::contains( call->participants(), @@ -1245,19 +1247,32 @@ void Session::registerInvitedToCallUser( return; } } - _invitedToCallUsers[callId].emplace(user); - _invitesToCalls.fire({ callId, user }); + _invitedToCallUsers[callId][user] = calling; + _invitesToCalls.fire({ callId, user, calling }); } void Session::unregisterInvitedToCallUser( CallId callId, - not_null user) { + not_null user, + bool onlyStopCalling) { const auto i = _invitedToCallUsers.find(callId); if (i != _invitedToCallUsers.end()) { - i->second.remove(user); - if (i->second.empty()) { - _invitedToCallUsers.erase(i); - _invitesToCalls.fire({ callId, user, true }); + const auto j = i->second.find(user); + if (j != end(i->second)) { + if (onlyStopCalling) { + if (!j->second) { + return; + } + j->second = false; + } else { + i->second.erase(j); + if (i->second.empty()) { + _invitedToCallUsers.erase(i); + } + } + const auto calling = false; + const auto removed = !onlyStopCalling; + _invitesToCalls.fire({ callId, user, calling, removed }); } } } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index e1563d8563..bb810a7e37 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -240,22 +240,26 @@ public: void maybeStopWatchForOffline(not_null user); [[nodiscard]] auto invitedToCallUsers(CallId callId) const - -> const base::flat_set> &; + -> const base::flat_map, bool> &; void registerInvitedToCallUser( CallId callId, not_null peer, - not_null user); + not_null user, + bool calling); void registerInvitedToCallUser( CallId callId, GroupCall *call, - not_null user); + not_null user, + bool calling); void unregisterInvitedToCallUser( CallId callId, - not_null user); + not_null user, + bool onlyStopCalling); struct InviteToCall { CallId id = 0; not_null user; + bool calling = false; bool removed = false; }; [[nodiscard]] rpl::producer invitesToCalls() const { @@ -1112,7 +1116,7 @@ private: rpl::event_stream _invitesToCalls; base::flat_map< CallId, - base::flat_set>> _invitedToCallUsers; + base::flat_map, bool>> _invitedToCallUsers; base::flat_set> _shownSpoilers; base::flat_map< diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f93592c270..6309d4965a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1902,12 +1902,21 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) { _flags &= ~MessageFlag::DisplayFromChecked; } else if (message.vaction().type() == mtpc_messageActionConferenceCall) { removeFromSharedMediaIndex(); + const auto owner = &history()->owner(); + const auto &data = message.vaction().c_messageActionConferenceCall(); + const auto info = Data::ComputeCallData(owner, data); + if (const auto user = history()->peer->asUser()) { + if (const auto conferenceId = out() ? info.conferenceId : 0) { + Core::App().calls().unregisterConferenceInvite( + conferenceId, + user, + id, + !out(), + true); + } + } _media = nullptr; - _media = std::make_unique( - this, - Data::ComputeCallData( - &history()->owner(), - message.vaction().c_messageActionConferenceCall())); + _media = std::make_unique(this, info); addToSharedMediaIndex(); finishEdition(-1); _flags &= ~MessageFlag::DisplayFromChecked; @@ -4896,7 +4905,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { for (const auto &id : action.vusers().v) { const auto user = owner->user(id.v); if (callId) { - owner->registerInvitedToCallUser(callId, peer, user); + owner->registerInvitedToCallUser(callId, peer, user, false); } }; const auto linkCallId = PeerHasThisCall(peer, callId).value_or(false) From d50fad615f93f310c1387449d8be5418fd7de3de Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 7 Apr 2025 13:30:58 +0400 Subject: [PATCH 119/190] Check confcall size limit. --- Telegram/Resources/langs/lang.strings | 1 + .../calls/group/calls_group_common.cpp | 12 ++++++------ .../calls/group/calls_group_common.h | 2 +- .../group/calls_group_invite_controller.cpp | 7 ++++--- .../calls/group/calls_group_panel.cpp | 5 ++++- Telegram/SourceFiles/data/data_group_call.h | 2 -- Telegram/SourceFiles/main/main_app_config.cpp | 7 +++++++ Telegram/SourceFiles/main/main_app_config.h | 1 + .../window/window_session_controller.cpp | 19 +++++++++++++------ 9 files changed, 37 insertions(+), 19 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 1c234f6755..173c80b597 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4959,6 +4959,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_invite_kicked_many#one" = "**{count} person** was removed from the call."; "lng_confcall_invite_kicked_many#other" = "**{count} people** were removed from the call."; "lng_confcall_not_accessible" = "This call is no longer accessible."; +"lng_confcall_participants_limit" = "This call reached the participants limit."; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index f0e09c6f03..d313a030b0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -97,7 +97,7 @@ void ConferenceCallJoinConfirm( not_null box, std::shared_ptr call, UserData *maybeInviter, - Fn join) { + Fn close)> join) { box->setStyle(st::confcallJoinBox); box->setWidth(st::boxWideWidth); box->setNoContentMargin(true); @@ -223,11 +223,11 @@ void ConferenceCallJoinConfirm( )->setTryMakeSimilarLines(true); } const auto joinAndClose = [=] { - const auto weak = Ui::MakeWeak(box); - join(); - if (const auto strong = weak.data()) { - strong->closeBox(); - } + join([weak = Ui::MakeWeak(box)] { + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }); }; Info::BotStarRef::AddFullWidthButton( box, diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 93412787c0..ce06d3223c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -163,7 +163,7 @@ void ConferenceCallJoinConfirm( not_null box, std::shared_ptr call, UserData *maybeInviter, - Fn join); + Fn close)> join); struct ConferenceCallLinkStyleOverrides { const style::Box *box = nullptr; diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 876e24de95..1523857d56 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -18,8 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_group_call.h" #include "info/profile/info_profile_icon.h" -#include "main/main_session.h" #include "main/session/session_show.h" +#include "main/main_app_config.h" +#include "main/main_session.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/layers/generic_box.h" @@ -306,8 +307,8 @@ void ConfInviteController::toggleRowSelected( not_null row, bool video) { auto count = fullCount(); - auto limit = Data::kMaxConferenceMembers; - if (count < limit || row->checked()) { + const auto conferenceLimit = session().appConfig().confcallSizeLimit(); + if (count < conferenceLimit || row->checked()) { const auto real = static_cast(row.get()); if (!row->checked()) { real->setVideo(video); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 138c92d06e..748438bb49 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_changes.h" #include "main/session/session_show.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "base/event_filter.h" #include "base/unixtime.h" @@ -1569,8 +1570,10 @@ void Panel::showMainMenu() { } void Panel::addMembers() { + const auto &appConfig = _call->peer()->session().appConfig(); + const auto conferenceLimit = appConfig.confcallSizeLimit(); if (_call->conference() - && _call->conferenceCall()->fullCount() >= Data::kMaxConferenceMembers) { + && _call->conferenceCall()->fullCount() >= conferenceLimit) { showToast({ tr::lng_group_call_invite_limit(tr::now) }); } const auto showToastCallback = [=](TextWithEntities &&text) { diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index da804a442a..dfce953e3f 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -30,8 +30,6 @@ namespace Data { [[nodiscard]] const std::string &RtmpEndpointId(); -inline constexpr auto kMaxConferenceMembers = 200; - struct LastSpokeTimes { crl::time anything = 0; crl::time voice = 0; diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 26d4aecfb0..a24382b82e 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -108,9 +108,16 @@ int AppConfig::pinnedGiftsLimit() const { return get(u"stargifts_pinned_to_top_limit"_q, 6); } +int AppConfig::confcallSizeLimit() const { + return get( + u"conference_call_size_limit"_q, + _account->mtp().isTestMode() ? 5 : 100); +} + bool AppConfig::confcallPrioritizeVP8() const { AssertIsDebug(); return true; + return get(u"confcall_use_vp8"_q, false); } void AppConfig::refresh(bool force) { diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index a5a804cdcc..eac50671e0 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -79,6 +79,7 @@ public: [[nodiscard]] int pinnedGiftsLimit() const; + [[nodiscard]] int confcallSizeLimit() const; [[nodiscard]] bool confcallPrioritizeVP8() const; void refresh(bool force = false); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 342313b3b6..d35e50f0b4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -881,12 +881,19 @@ void SessionNavigation::resolveConferenceCall( data.vid().v, data.vaccess_hash().v); call->processFullCall(result); - const auto join = [=] { - Core::App().calls().startOrJoinConferenceCall({ - .call = call, - .linkSlug = slug, - .joinMessageId = inviteMsgId, - }); + const auto join = [=](Fn close) { + const auto &appConfig = call->peer()->session().appConfig(); + const auto conferenceLimit = appConfig.confcallSizeLimit(); + if (call->fullCount() >= conferenceLimit) { + showToast(tr::lng_confcall_participants_limit(tr::now)); + } else { + Core::App().calls().startOrJoinConferenceCall({ + .call = call, + .linkSlug = slug, + .joinMessageId = inviteMsgId, + }); + close(); + } }; const auto context = session().data().message(contextId); const auto inviter = context From 909bd3dd2d4df8a613f3b00efebfb4959c6bb112 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 7 Apr 2025 14:42:38 +0400 Subject: [PATCH 120/190] Allow kicking from confcall by creator. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/calls/group/calls_group_call.cpp | 3 ++- .../SourceFiles/calls/group/calls_group_members.cpp | 7 +++++-- Telegram/SourceFiles/calls/group/calls_group_panel.cpp | 10 ++++++++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 173c80b597..71f5e3ef49 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4960,6 +4960,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_invite_kicked_many#other" = "**{count} people** were removed from the call."; "lng_confcall_not_accessible" = "This call is no longer accessible."; "lng_confcall_participants_limit" = "This call reached the participants limit."; +"lng_confcall_sure_remove" = "Remove {user} from the call?"; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 2ea46d9dd5..c8399d35c9 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1645,7 +1645,8 @@ void GroupCall::sendJoinRequest() { } hangup(); - Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q) + Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q + || type == u"GROUPCALL_INVALID"_q) ? tr::lng_confcall_not_accessible(tr::now) : type); }).send(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 52d787421d..5b4e87b622 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1481,11 +1481,12 @@ base::unique_qptr Members::Controller::createRowContextMenu( removeHand); } } else { + const auto invited = (muteState == Row::State::Invited) + || (muteState == Row::State::Calling); const auto conference = _call->conferenceCall().get(); if (conference && participantPeer->isUser() - && (muteState == Row::State::Invited - || muteState == Row::State::Calling)) { + && invited) { const auto id = conference->id(); const auto cancelInvite = [=](bool discard) { Core::App().calls().declineOutgoingConferenceInvite( @@ -1521,6 +1522,8 @@ base::unique_qptr Members::Controller::createRowContextMenu( || muteState == Row::State::Calling || muteState == Row::State::WithAccess) { return false; + } else if (conference && _call->canManage()) { + return true; } else if (const auto chat = _peer->asChat()) { return chat->amCreator() || (user diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 748438bb49..27771e27db 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -1599,7 +1599,9 @@ void Panel::kickParticipant(not_null participantPeer) { tr::now, lt_channel, participantPeer->name()) - : (_peer->isBroadcast() + : (_call->conference() + ? tr::lng_confcall_sure_remove + : _peer->isBroadcast() ? tr::lng_profile_sure_kick_channel : tr::lng_profile_sure_kick)( tr::now, @@ -1660,7 +1662,11 @@ bool Panel::isLayerShown() const { } void Panel::kickParticipantSure(not_null participantPeer) { - if (const auto chat = _peer->asChat()) { + if (_call->conference()) { + if (const auto user = participantPeer->asUser()) { + _call->removeConferenceParticipants({ peerToUser(user->id) }); + } + } else if (const auto chat = _peer->asChat()) { chat->session().api().chatParticipants().kick(chat, participantPeer); } else if (const auto channel = _peer->asChannel()) { const auto currentRestrictedRights = [&] { From 915dec7ba56545e03268465d962b26e190d832b1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 7 Apr 2025 14:42:53 +0400 Subject: [PATCH 121/190] Show right call top bar after migration. --- Telegram/SourceFiles/calls/calls_top_bar.cpp | 8 +++---- Telegram/SourceFiles/calls/calls_top_bar.h | 8 +++---- Telegram/SourceFiles/mainwidget.cpp | 22 +++++++++++--------- Telegram/SourceFiles/mainwidget.h | 2 +- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index 06863bc34f..03e92a3ee4 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -228,14 +228,14 @@ private: TopBar::TopBar( QWidget *parent, - const base::weak_ptr &call, + Call *call, std::shared_ptr show) : TopBar(parent, show, call, nullptr) { } TopBar::TopBar( QWidget *parent, - const base::weak_ptr &call, + GroupCall *call, std::shared_ptr show) : TopBar(parent, show, nullptr, call) { } @@ -243,8 +243,8 @@ TopBar::TopBar( TopBar::TopBar( QWidget *parent, std::shared_ptr show, - const base::weak_ptr &call, - const base::weak_ptr &groupCall) + Call *call, + GroupCall *groupCall) : RpWidget(parent) , _call(call) , _groupCall(groupCall) diff --git a/Telegram/SourceFiles/calls/calls_top_bar.h b/Telegram/SourceFiles/calls/calls_top_bar.h index 7b62e6673a..6f3610773c 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.h +++ b/Telegram/SourceFiles/calls/calls_top_bar.h @@ -42,11 +42,11 @@ class TopBar : public Ui::RpWidget { public: TopBar( QWidget *parent, - const base::weak_ptr &call, + Call *call, std::shared_ptr show); TopBar( QWidget *parent, - const base::weak_ptr &call, + GroupCall *call, std::shared_ptr show); ~TopBar(); @@ -64,8 +64,8 @@ private: TopBar( QWidget *parent, std::shared_ptr show, - const base::weak_ptr &call, - const base::weak_ptr &groupCall); + Call *call, + GroupCall *groupCall); void initControls(); void setupInitialBrush(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d2437cfa46..2a458c24e9 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -926,15 +926,15 @@ void MainWidget::setCurrentCall(Calls::Call *call) { } _currentCallLifetime.destroy(); _currentCall = call; - if (_currentCall) { + if (call) { _callTopBar.destroy(); - _currentCall->stateValue( + call->stateValue( ) | rpl::start_with_next([=](Calls::Call::State state) { using State = Calls::Call::State; if (state != State::Established) { destroyCallTopBar(); } else if (!_callTopBar) { - createCallTopBar(); + createCallTopBar(call, nullptr); } }, _currentCallLifetime); } else { @@ -948,7 +948,7 @@ void MainWidget::setCurrentGroupCall(Calls::GroupCall *call) { } _currentCallLifetime.destroy(); _currentGroupCall = call; - if (_currentGroupCall) { + if (call) { _callTopBar.destroy(); _currentGroupCall->stateValue( ) | rpl::start_with_next([=](Calls::GroupCall::State state) { @@ -960,7 +960,7 @@ void MainWidget::setCurrentGroupCall(Calls::GroupCall *call) { && state != State::Connecting) { destroyCallTopBar(); } else if (!_callTopBar) { - createCallTopBar(); + createCallTopBar(nullptr, call); } }, _currentCallLifetime); } else { @@ -968,15 +968,17 @@ void MainWidget::setCurrentGroupCall(Calls::GroupCall *call) { } } -void MainWidget::createCallTopBar() { - Expects(_currentCall != nullptr || _currentGroupCall != nullptr); +void MainWidget::createCallTopBar( + Calls::Call *call, + Calls::GroupCall *group) { + Expects(call || group); const auto show = controller()->uiShow(); _callTopBar.create( this, - (_currentCall - ? object_ptr(this, _currentCall, show) - : object_ptr(this, _currentGroupCall, show))); + (call + ? object_ptr(this, call, show) + : object_ptr(this, group, show))); _callTopBar->entity()->initBlobsUnder(this, _callTopBar->geometryValue()); _callTopBar->heightValue( ) | rpl::start_with_next([this](int value) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 5d63798962..48ec64735b 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -244,7 +244,7 @@ private: void setCurrentCall(Calls::Call *call); void setCurrentGroupCall(Calls::GroupCall *call); - void createCallTopBar(); + void createCallTopBar(Calls::Call *call, Calls::GroupCall *group); void destroyCallTopBar(); void callTopBarHeightUpdated(int callTopBarHeight); From 59e56600bc36dbffab8a4c5bb9da282d6eaa4672 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Apr 2025 17:24:31 +0400 Subject: [PATCH 122/190] Reuse p2p call window for migrated confcall. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 2 +- .../SourceFiles/calls/calls_box_controller.h | 10 +- Telegram/SourceFiles/calls/calls_call.cpp | 6 +- .../calls/calls_emoji_fingerprint.cpp | 6 +- .../calls/calls_emoji_fingerprint.h | 4 +- Telegram/SourceFiles/calls/calls_instance.cpp | 6 +- Telegram/SourceFiles/calls/calls_panel.cpp | 259 ++++---------- Telegram/SourceFiles/calls/calls_panel.h | 75 ++-- Telegram/SourceFiles/calls/calls_window.cpp | 250 ++++++++++++++ Telegram/SourceFiles/calls/calls_window.h | 112 ++++++ .../calls/group/calls_group_common.h | 5 +- .../calls/group/calls_group_members.cpp | 8 +- .../calls/group/calls_group_panel.cpp | 322 ++++++------------ .../calls/group/calls_group_panel.h | 68 +--- .../calls/group/calls_group_toasts.cpp | 14 +- Telegram/lib_base | 2 +- 17 files changed, 606 insertions(+), 545 deletions(-) create mode 100644 Telegram/SourceFiles/calls/calls_window.cpp create mode 100644 Telegram/SourceFiles/calls/calls_window.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 4329b12e51..7714f518d8 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -396,6 +396,8 @@ PRIVATE calls/calls_video_bubble.h calls/calls_video_incoming.cpp calls/calls_video_incoming.h + calls/calls_window.cpp + calls/calls_window.h chat_helpers/compose/compose_features.h chat_helpers/compose/compose_show.cpp chat_helpers/compose/compose_show.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 71f5e3ef49..22ee39de43 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4955,7 +4955,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_invite_fail_user" = "Couldn't call {user} to join."; "lng_confcall_invite_fail_many#one" = "Couldn't call **{count} person** to join."; "lng_confcall_invite_fail_many#other" = "Couldn't call **{count} people** to join."; -"lng_confcall_invite_kicked_user" = "{user} was removed from the call."; +"lng_confcall_invite_kicked_user" = "{user} was banned from the call."; "lng_confcall_invite_kicked_many#one" = "**{count} person** was removed from the call."; "lng_confcall_invite_kicked_many#other" = "**{count} people** were removed from the call."; "lng_confcall_not_accessible" = "This call is no longer accessible."; diff --git a/Telegram/SourceFiles/calls/calls_box_controller.h b/Telegram/SourceFiles/calls/calls_box_controller.h index 860839cdf2..05b943b607 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.h +++ b/Telegram/SourceFiles/calls/calls_box_controller.h @@ -20,7 +20,7 @@ namespace GroupCalls { class ListController : public PeerListController { public: - explicit ListController(not_null window); + explicit ListController(not_null<::Window::SessionController*> window); [[nodiscard]] rpl::producer shownValue() const; @@ -30,7 +30,7 @@ public: void rowRightActionClicked(not_null row) override; private: - const not_null _window; + const not_null<::Window::SessionController*> _window; base::flat_map> _groupCalls; rpl::variable _fullCount; @@ -40,7 +40,7 @@ private: class BoxController : public PeerListController { public: - explicit BoxController(not_null window); + explicit BoxController(not_null<::Window::SessionController*> window); Main::Session &session() const override; void prepare() override; @@ -68,7 +68,7 @@ private: std::unique_ptr createRow( not_null item) const; - const not_null _window; + const not_null<::Window::SessionController*> _window; MTP::Sender _api; MsgId _offsetId = 0; @@ -79,6 +79,6 @@ private: void ClearCallsBox( not_null box, - not_null window); + not_null<::Window::SessionController*> window); } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 0ba4672931..d4d0093350 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -811,7 +811,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { } if (data.is_need_rating() && _id && _accessHash) { const auto window = Core::App().windowFor( - Window::SeparateId(_user)); + ::Window::SeparateId(_user)); const auto session = &_user->session(); const auto callId = _id; const auto callAccessHash = _accessHash; @@ -1589,7 +1589,7 @@ void Call::handleRequestError(const QString &error) { : error; if (!inform.isEmpty()) { if (const auto window = Core::App().windowFor( - Window::SeparateId(_user))) { + ::Window::SeparateId(_user))) { window->show(Ui::MakeInformBox(inform)); } else { Ui::show(Ui::MakeInformBox(inform)); @@ -1608,7 +1608,7 @@ void Call::handleControllerError(const QString &error) { : QString(); if (!inform.isEmpty()) { if (const auto window = Core::App().windowFor( - Window::SeparateId(_user))) { + ::Window::SeparateId(_user))) { window->show(Ui::MakeInformBox(inform)); } else { Ui::show(Ui::MakeInformBox(inform)); diff --git a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp index 5c00536ca3..f6c568b48c 100644 --- a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp +++ b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp @@ -153,7 +153,7 @@ std::vector ComputeEmojiFingerprint( return result; } -object_ptr CreateFingerprintAndSignalBars( +base::unique_qptr CreateFingerprintAndSignalBars( not_null parent, not_null call) { class EmojiTooltipShower final : public Ui::AbstractTooltipShower { @@ -179,8 +179,8 @@ object_ptr CreateFingerprintAndSignalBars( }; - auto result = object_ptr(parent); - const auto raw = result.data(); + auto result = base::make_unique_q(parent); + const auto raw = result.get(); // Emoji tooltip. const auto shower = raw->lifetime().make_state( diff --git a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h index 285f9ebaff..36a10d4929 100644 --- a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h +++ b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "base/object_ptr.h" +#include "base/unique_qptr.h" namespace Ui { class RpWidget; @@ -22,7 +22,7 @@ class Call; [[nodiscard]] std::vector ComputeEmojiFingerprint( bytes::const_span fingerprint); -[[nodiscard]] object_ptr CreateFingerprintAndSignalBars( +[[nodiscard]] base::unique_qptr CreateFingerprintAndSignalBars( not_null parent, not_null call); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 7e2ae032b0..10302759de 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -349,7 +349,11 @@ void Instance::playSoundOnce(const QString &key) { void Instance::destroyCall(not_null call) { if (_currentCall.get() == call) { - _currentCallPanel->closeBeforeDestroy(); + const auto groupCallWindow = _currentGroupCallPanel + ? _currentGroupCallPanel->window().get() + : nullptr; + const auto reused = (_currentCallPanel->window() == groupCallWindow); + _currentCallPanel->closeBeforeDestroy(reused); _currentCallPanel = nullptr; auto taken = base::take(_currentCall); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 3f2c0b31ac..5a95153d36 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_userpic.h" #include "calls/calls_video_bubble.h" #include "calls/calls_video_incoming.h" +#include "calls/calls_window.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/widgets/call_button.h" #include "ui/widgets/buttons.h" @@ -95,116 +96,49 @@ constexpr auto kHideControlsQuickTimeout = 2 * crl::time(1000); )"; } -class Show final : public Main::SessionShow { -public: - explicit Show(not_null panel); - ~Show(); - - void showOrHideBoxOrLayer( - std::variant< - v::null_t, - object_ptr, - std::unique_ptr> &&layer, - Ui::LayerOptions options, - anim::type animated) const override; - [[nodiscard]] not_null toastParent() const override; - [[nodiscard]] bool valid() const override; - operator bool() const override; - - [[nodiscard]] Main::Session &session() const override; - -private: - const base::weak_ptr _panel; - -}; - -Show::Show(not_null panel) -: _panel(base::make_weak(panel)) { -} - -Show::~Show() = default; - -void Show::showOrHideBoxOrLayer( - std::variant< - v::null_t, - object_ptr, - std::unique_ptr> &&layer, - Ui::LayerOptions options, - anim::type animated) const { - using UniqueLayer = std::unique_ptr; - using ObjectBox = object_ptr; - if (auto layerWidget = std::get_if(&layer)) { - if (const auto panel = _panel.get()) { - panel->showLayer(std::move(*layerWidget), options, animated); - } - } else if (auto box = std::get_if(&layer)) { - if (const auto panel = _panel.get()) { - panel->showBox(std::move(*box), options, animated); - } - } else if (const auto panel = _panel.get()) { - panel->hideLayer(animated); - } -} - -not_null Show::toastParent() const { - const auto panel = _panel.get(); - Assert(panel != nullptr); - return panel->widget(); -} - -bool Show::valid() const { - return !_panel.empty(); -} - -Show::operator bool() const { - return valid(); -} - -Main::Session &Show::session() const { - const auto panel = _panel.get(); - Assert(panel != nullptr); - return panel->user()->session(); -} - } // namespace Panel::Panel(not_null call) : _call(call) , _user(call->user()) -, _layerBg(std::make_unique(widget())) -#ifndef Q_OS_MAC -, _controls(Ui::Platform::SetupSeparateTitleControls( - window(), - st::callTitle, - [=](bool maximized) { toggleFullScreen(maximized); })) -#endif // !Q_OS_MAC +, _window(std::make_shared()) , _bodySt(&st::callBodyLayout) -, _answerHangupRedial(widget(), st::callAnswer, &st::callHangup) -, _decline(widget(), object_ptr(widget(), st::callHangup)) -, _cancel(widget(), object_ptr(widget(), st::callCancel)) +, _answerHangupRedial( + std::in_place, + widget(), + st::callAnswer, + &st::callHangup) +, _decline( + std::in_place, + widget(), + object_ptr(widget(), st::callHangup)) +, _cancel( + std::in_place, + widget(), + object_ptr(widget(), st::callCancel)) , _screencast( + std::in_place, widget(), object_ptr( widget(), st::callScreencastOn, &st::callScreencastOff)) -, _camera(widget(), st::callCameraMute, &st::callCameraUnmute) +, _camera(std::in_place, widget(), st::callCameraMute, &st::callCameraUnmute) , _mute( + std::in_place, widget(), object_ptr( widget(), st::callMicrophoneMute, &st::callMicrophoneUnmute)) , _addPeople( + std::in_place, widget(), object_ptr(widget(), st::callAddPeople)) -, _name(widget(), st::callName) -, _status(widget(), st::callStatus) +, _name(std::in_place, widget(), st::callName) +, _status(std::in_place, widget(), st::callStatus) , _hideControlsTimer([=] { requestControlsHidden(true); }) , _controlsShownForceTimer([=] { controlsShownForce(false); }) { - _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox); - _layerBg->setHideByBackgroundClick(true); - _decline->setDuration(st::callPanelDuration); _decline->entity()->setText(tr::lng_call_decline()); _cancel->setDuration(st::callPanelDuration); @@ -234,67 +168,15 @@ bool Panel::isActive() const { } ConferencePanelMigration Panel::migrationInfo() const { - const auto handle = window()->windowHandle(); - return handle ? ConferencePanelMigration{ - .screen = handle->screen(), - .geometry = window()->geometry(), - } : ConferencePanelMigration(); + return ConferencePanelMigration{ .window = _window }; } -base::weak_ptr Panel::showToast( - const QString &text, - crl::time duration) { - return showToast({ - .text = { text }, - .duration = duration, - }); +std::shared_ptr Panel::sessionShow() { + return Main::MakeSessionShow(uiShow(), &_user->session()); } -base::weak_ptr Panel::showToast( - TextWithEntities &&text, - crl::time duration) { - return showToast({ - .text = std::move(text), - .duration = duration, - }); -} - -base::weak_ptr Panel::showToast( - Ui::Toast::Config &&config) { - if (!config.st) { - config.st = &st::callErrorToast; - } - return Show(this).showToast(std::move(config)); -} - -void Panel::showBox(object_ptr box) { - showBox(std::move(box), Ui::LayerOption::KeepOther, anim::type::normal); -} - -void Panel::showBox( - object_ptr box, - Ui::LayerOptions options, - anim::type animated) { - _layerBg->showBox(std::move(box), options, animated); -} - -void Panel::showLayer( - std::unique_ptr layer, - Ui::LayerOptions options, - anim::type animated) { - _layerBg->showLayer(std::move(layer), options, animated); -} - -void Panel::hideLayer(anim::type animated) { - _layerBg->hideAll(animated); -} - -bool Panel::isLayerShown() const { - return _layerBg->topShownLayer() != nullptr; -} - -std::shared_ptr Panel::uiShow() { - return std::make_shared(this); +std::shared_ptr Panel::uiShow() { + return _window->uiShow(); } void Panel::showAndActivate() { @@ -359,20 +241,16 @@ void Panel::initWindow() { } } return base::EventFilterResult::Continue; - }); + }, lifetime()); + const auto guard = base::make_weak(this); window()->setBodyTitleArea([=](QPoint widgetPoint) { using Flag = Ui::WindowTitleHitTestFlag; - if (!widget()->rect().contains(widgetPoint)) { + if (!guard + || !widget()->rect().contains(widgetPoint) + || _window->controlsHasHitTest(widgetPoint)) { return Flag::None | Flag(0); } -#ifndef Q_OS_MAC - using Result = Ui::Platform::HitTestResult; - const auto windowPoint = widget()->mapTo(window(), widgetPoint); - if (_controls->controls.hitTest(windowPoint) != Result::None) { - return Flag::None | Flag(0); - } -#endif // !Q_OS_MAC const auto buttonWidth = st::callCancel.button.width; const auto buttonsWidth = buttonWidth * 4; const auto inControls = (_fingerprint @@ -387,12 +265,15 @@ void Panel::initWindow() { if (inControls) { return Flag::None | Flag(0); } - const auto shown = _layerBg->topShownLayer(); + const auto shown = _window->topShownLayer(); return (!shown || !shown->geometry().contains(widgetPoint)) ? (Flag::Move | Flag::Menu | Flag::FullScreen) : Flag::None; }); + _window->maximizeRequests() | rpl::start_with_next([=](bool maximized) { + toggleFullScreen(maximized); + }, lifetime()); // Don't do that, it looks awful :( //#ifdef Q_OS_WIN // // On Windows we replace snap-to-top maximizing with fullscreen. @@ -426,12 +307,12 @@ void Panel::initWidget() { widget()->paintRequest( ) | rpl::start_with_next([=](QRect clip) { paint(clip); - }, widget()->lifetime()); + }, lifetime()); widget()->sizeValue( ) | rpl::skip(1) | rpl::start_with_next([=] { updateControlsGeometry(); - }, widget()->lifetime()); + }, lifetime()); } void Panel::initControls() { @@ -447,7 +328,7 @@ void Panel::initControls() { return; } else if (!env->desktopCaptureAllowed()) { if (auto box = Group::ScreenSharingPrivacyRequestBox()) { - showBox(std::move(box)); + uiShow()->showBox(std::move(box)); } } else if (const auto source = env->uniqueDesktopCaptureSource()) { if (!chooseSourceActiveDeviceId().isEmpty()) { @@ -467,7 +348,7 @@ void Panel::initControls() { }); _addPeople->entity()->setClickedCallback([=] { if (!_call || _call->state() != Call::State::Established) { - showToast(tr::lng_call_error_add_not_started(tr::now)); + uiShow()->showToast(tr::lng_call_error_add_not_started(tr::now)); return; } const auto call = _call; @@ -484,7 +365,7 @@ void Panel::initControls() { *creating = true; const auto sharingLink = users.empty(); Group::MakeConferenceCall({ - .show = uiShow(), + .show = sessionShow(), .finished = finish, .joining = true, .info = { @@ -506,7 +387,7 @@ void Panel::initControls() { const auto share = crl::guard(call, [=] { create({}); }); - showBox(Group::PrepareInviteBox(call, invite, share)); + uiShow()->showBox(Group::PrepareInviteBox(call, invite, share)); }); _updateDurationTimer.setCallback([this] { @@ -559,9 +440,9 @@ void Panel::initConferenceInvite() { if (count < 2) { return; } - _conferenceParticipants.create(widget()); + _conferenceParticipants = base::make_unique_q(widget()); _conferenceParticipants->show(); - const auto raw = _conferenceParticipants.data(); + const auto raw = _conferenceParticipants.get(); auto peers = std::vector>(); for (const auto &peer : participants) { @@ -689,10 +570,9 @@ void Panel::reinitWithCall(Call *call) { updateControlsShown(); }); if (!_call) { - _fingerprint.destroy(); + _fingerprint = nullptr; _incoming = nullptr; _outgoingVideoBubble = nullptr; - _powerSaveBlocker = nullptr; return; } @@ -716,7 +596,7 @@ void Panel::reinitWithCall(Call *call) { if (muted) { createRemoteAudioMute(); } else { - _remoteAudioMute.destroy(); + _remoteAudioMute = nullptr; showRemoteLowBattery(); } }, _callLifetime); @@ -725,7 +605,7 @@ void Panel::reinitWithCall(Call *call) { if (state == Call::RemoteBatteryState::Low) { createRemoteLowBattery(); } else { - _remoteLowBattery.destroy(); + _remoteLowBattery = nullptr; } }, _callLifetime); _userpic = std::make_unique( @@ -738,7 +618,7 @@ void Panel::reinitWithCall(Call *call) { _incoming = std::make_unique( widget(), _call->videoIncoming(), - _window.backend()); + _window->backend()); _incoming->widget()->hide(); _incoming->rp()->shownValue() | rpl::start_with_next([=] { @@ -886,7 +766,7 @@ void Panel::reinitWithCall(Call *call) { } Unexpected("Error type in _call->errors()."); }(); - showToast(text); + uiShow()->showToast(text); }, _callLifetime); _name->setText(_user->name()); @@ -902,16 +782,11 @@ void Panel::reinitWithCall(Call *call) { _mute->raise(); _addPeople->raise(); - _powerSaveBlocker = std::make_unique( - base::PowerSaveBlockType::PreventDisplaySleep, - u"Video call is active"_q, - window()->windowHandle()); - _incoming->widget()->lower(); } void Panel::createRemoteAudioMute() { - _remoteAudioMute.create( + _remoteAudioMute = base::make_unique_q>( widget(), object_ptr( widget(), @@ -948,7 +823,7 @@ void Panel::createRemoteAudioMute() { } void Panel::createRemoteLowBattery() { - _remoteLowBattery.create( + _remoteLowBattery = base::make_unique_q>( widget(), object_ptr( widget(), @@ -964,7 +839,7 @@ void Panel::createRemoteLowBattery() { style::PaletteChanged( ) | rpl::start_with_next([=] { - _remoteLowBattery.destroy(); + _remoteLowBattery = nullptr; createRemoteLowBattery(); }, _remoteLowBattery->lifetime()); @@ -1032,11 +907,9 @@ void Panel::initLayout() { }) | rpl::start_with_next([=](const Data::PeerUpdate &update) { _name->setText(_call->user()->name()); updateControlsGeometry(); - }, widget()->lifetime()); + }, lifetime()); -#ifndef Q_OS_MAC - _controls->wrap.raise(); -#endif // !Q_OS_MAC + _window->raiseControls(); } void Panel::showControls() { @@ -1058,13 +931,16 @@ void Panel::showControls() { showRemoteLowBattery(); } -void Panel::closeBeforeDestroy() { - window()->close(); +void Panel::closeBeforeDestroy(bool windowIsReused) { + if (!windowIsReused) { + window()->close(); + } reinitWithCall(nullptr); + _lifetime.destroy(); } rpl::lifetime &Panel::lifetime() { - return window()->lifetime(); + return _lifetime; } void Panel::initGeometry() { @@ -1202,7 +1078,7 @@ void Panel::updateControlsGeometry() { _controlsShown ? 1. : 0.); if (_fingerprint) { #ifndef Q_OS_MAC - const auto controlsGeometry = _controls->controls.geometry(); + const auto controlsGeometry = _window->controlsGeometry(); const auto halfWidth = widget()->width() / 2; const auto minLeft = (controlsGeometry.center().x() < halfWidth) ? (controlsGeometry.width() + st::callFingerprintTop) @@ -1391,11 +1267,11 @@ bool Panel::handleClose() const { } not_null Panel::window() const { - return _window.window(); + return _window->window(); } not_null Panel::widget() const { - return _window.widget(); + return _window->widget(); } not_null Panel::user() const { @@ -1407,17 +1283,16 @@ void Panel::stateChanged(State state) { updateStatusText(state); + const auto isBusy = (state == State::Busy); + const auto isWaitingUser = (state == State::WaitingUserConfirmation); + _window->togglePowerSaveBlocker(!isBusy && !isWaitingUser); + if ((state != State::HangingUp) && (state != State::MigrationHangingUp) && (state != State::Ended) && (state != State::EndedByOtherDevice) && (state != State::FailedHangingUp) && (state != State::Failed)) { - const auto isBusy = (state == State::Busy); - const auto isWaitingUser = (state == State::WaitingUserConfirmation); - if (isBusy) { - _powerSaveBlocker = nullptr; - } if (_startVideo && !isWaitingUser) { _startVideo = nullptr; } else if (!_startVideo && isWaitingUser) { @@ -1472,7 +1347,7 @@ void Panel::stateChanged(State state) { refreshAnswerHangupRedialLabel(); } if (!_call->isKeyShaForFingerprintReady()) { - _fingerprint.destroy(); + _fingerprint = nullptr; } else if (!_fingerprint) { _fingerprint = CreateFingerprintAndSignalBars(widget(), _call); updateControlsGeometry(); diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index 13ba30c258..bcdf373c42 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -7,14 +7,9 @@ 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 "base/unique_qptr.h" #include "calls/calls_call.h" #include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/effects/animations.h" -#include "ui/gl/gl_window.h" #include "ui/rp_widget.h" class Image; @@ -32,6 +27,7 @@ class SessionShow; } // namespace Main namespace Ui { +class Show; class BoxContent; class LayerWidget; enum class LayerOption; @@ -64,6 +60,7 @@ struct CallBodyLayout; namespace Calls { +class Window; class Userpic; class SignalBars; class VideoBubble; @@ -84,32 +81,11 @@ public: [[nodiscard]] ConferencePanelMigration migrationInfo() const; - base::weak_ptr showToast( - const QString &text, - crl::time duration = 0); - base::weak_ptr showToast( - TextWithEntities &&text, - crl::time duration = 0); - base::weak_ptr showToast( - Ui::Toast::Config &&config); - - void showBox(object_ptr box); - void showBox( - object_ptr box, - Ui::LayerOptions options, - anim::type animated = anim::type::normal); - void showLayer( - std::unique_ptr layer, - Ui::LayerOptions options, - anim::type animated = anim::type::normal); - void hideLayer(anim::type animated = anim::type::normal); - [[nodiscard]] bool isLayerShown() const; - void showAndActivate(); void minimize(); void toggleFullScreen(); void replaceCall(not_null call); - void closeBeforeDestroy(); + void closeBeforeDestroy(bool windowIsReused = false); QWidget *chooseSourceParent() override; QString chooseSourceActiveDeviceId() override; @@ -123,7 +99,10 @@ public: [[nodiscard]] rpl::producer startOutgoingRequests() const; - [[nodiscard]] std::shared_ptr uiShow(); + [[nodiscard]] std::shared_ptr sessionShow(); + [[nodiscard]] std::shared_ptr uiShow(); + + [[nodiscard]] not_null window() const; [[nodiscard]] rpl::lifetime &lifetime(); @@ -138,8 +117,6 @@ private: StartCall, }; - [[nodiscard]] not_null window() const; - void paint(QRect clip); void initWindow(); @@ -184,43 +161,35 @@ private: Call *_call = nullptr; not_null _user; - Ui::GL::Window _window; - const std::unique_ptr _layerBg; + std::shared_ptr _window; std::unique_ptr _incoming; -#ifndef Q_OS_MAC - std::unique_ptr _controls; -#endif // !Q_OS_MAC - - std::unique_ptr _powerSaveBlocker; - QSize _incomingFrameSize; rpl::lifetime _callLifetime; not_null _bodySt; - object_ptr _answerHangupRedial; - object_ptr> _decline; - object_ptr> _cancel; + base::unique_qptr _answerHangupRedial; + base::unique_qptr> _decline; + base::unique_qptr> _cancel; bool _hangupShown = false; bool _conferenceSupported = false; bool _outgoingPreviewInBody = false; std::optional _answerHangupRedialState; Ui::Animations::Simple _hangupShownProgress; - object_ptr> _screencast; - object_ptr _camera; + base::unique_qptr> _screencast; + base::unique_qptr _camera; Ui::CallButton *_cameraDeviceToggle = nullptr; base::unique_qptr _startVideo; - object_ptr> _mute; + base::unique_qptr> _mute; Ui::CallButton *_audioDeviceToggle = nullptr; - object_ptr < Ui::FadeWrap> _addPeople; - object_ptr _name; - object_ptr _status; - object_ptr _conferenceParticipants = { nullptr }; - object_ptr _fingerprint = { nullptr }; - object_ptr> _remoteAudioMute = { nullptr }; - object_ptr> _remoteLowBattery - = { nullptr }; + base::unique_qptr> _addPeople; + base::unique_qptr _name; + base::unique_qptr _status; + base::unique_qptr _conferenceParticipants; + base::unique_qptr _fingerprint; + base::unique_qptr> _remoteAudioMute; + base::unique_qptr> _remoteLowBattery; std::unique_ptr _userpic; std::unique_ptr _outgoingVideoBubble; QPixmap _bottomShadow; @@ -245,6 +214,8 @@ private: rpl::event_stream _startOutgoingRequests; + rpl::lifetime _lifetime; + }; } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_window.cpp b/Telegram/SourceFiles/calls/calls_window.cpp new file mode 100644 index 0000000000..16b754d660 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_window.cpp @@ -0,0 +1,250 @@ +/* +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_window.h" + +#include "base/power_save_blocker.h" +#include "ui/platform/ui_platform_window_title.h" +#include "ui/widgets/rp_window.h" +#include "ui/layers/layer_manager.h" +#include "ui/layers/show.h" +#include "styles/style_calls.h" + +namespace Calls { +namespace { + +class Show final : public Ui::Show { +public: + explicit Show(not_null window); + ~Show(); + + void showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + Ui::LayerOptions options, + anim::type animated) const override; + [[nodiscard]] not_null toastParent() const override; + [[nodiscard]] bool valid() const override; + operator bool() const override; + +private: + const base::weak_ptr _window; + +}; + +Show::Show(not_null window) +: _window(base::make_weak(window)) { +} + +Show::~Show() = default; + +void Show::showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + Ui::LayerOptions options, + anim::type animated) const { + using UniqueLayer = std::unique_ptr; + using ObjectBox = object_ptr; + if (auto layerWidget = std::get_if(&layer)) { + if (const auto window = _window.get()) { + window->showLayer(std::move(*layerWidget), options, animated); + } + } else if (auto box = std::get_if(&layer)) { + if (const auto window = _window.get()) { + window->showBox(std::move(*box), options, animated); + } + } else if (const auto window = _window.get()) { + window->hideLayer(animated); + } +} + +not_null Show::toastParent() const { + const auto window = _window.get(); + Assert(window != nullptr); + return window->widget(); +} + +bool Show::valid() const { + return !_window.empty(); +} + +Show::operator bool() const { + return valid(); +} + +} // namespace + +Window::Window() +: _layerBg(std::make_unique(widget())) +#ifndef Q_OS_MAC +, _controls(Ui::Platform::SetupSeparateTitleControls( + window(), + st::callTitle, + [=](bool maximized) { _maximizeRequests.fire_copy(maximized); }, + _controlsTop.value())) +#endif // !Q_OS_MAC +{ + _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox); + _layerBg->setHideByBackgroundClick(true); +} + +Window::~Window() = default; + +Ui::GL::Backend Window::backend() const { + return _window.backend(); +} + +not_null Window::window() const { + return _window.window(); +} + +not_null Window::widget() const { + return _window.widget(); +} + +void Window::raiseControls() { +#ifndef Q_OS_MAC + _controls->wrap.raise(); +#endif // !Q_OS_MAC +} + +void Window::setControlsStyle(const style::WindowTitle &st) { +#ifndef Q_OS_MAC + _controls->controls.setStyle(st); +#endif // Q_OS_MAC +} + +void Window::setControlsShown(float64 shown) { +#ifndef Q_OS_MAC + _controlsTop = anim::interpolate(-_controls->wrap.height(), 0, shown); +#endif // Q_OS_MAC +} + +int Window::controlsWrapTop() const { +#ifndef Q_OS_MAC + return _controls->wrap.y(); +#else // Q_OS_MAC + return 0; +#endif // Q_OS_MAC +} + +QRect Window::controlsGeometry() const { +#ifndef Q_OS_MAC + return _controls->controls.geometry(); +#else // Q_OS_MAC + return QRect(); +#endif // Q_OS_MAC +} + +auto Window::controlsLayoutChanges() const +-> rpl::producer { +#ifndef Q_OS_MAC + return _controls->controls.layout().changes(); +#else // Q_OS_MAC + return rpl::never(); +#endif // Q_OS_MAC +} + +bool Window::controlsHasHitTest(QPoint widgetPoint) const { +#ifndef Q_OS_MAC + using Result = Ui::Platform::HitTestResult; + const auto windowPoint = widget()->mapTo(window(), widgetPoint); + return (_controls->controls.hitTest(windowPoint) != Result::None); +#else // Q_OS_MAC + return false; +#endif // Q_OS_MAC +} + +rpl::producer Window::maximizeRequests() const { + return _maximizeRequests.events(); +} + +base::weak_ptr Window::showToast( + const QString &text, + crl::time duration) { + return Show(this).showToast(text, duration); +} + +base::weak_ptr Window::showToast( + TextWithEntities &&text, + crl::time duration) { + return Show(this).showToast(std::move(text), duration); +} + +base::weak_ptr Window::showToast( + Ui::Toast::Config &&config) { + return Show(this).showToast(std::move(config)); +} + +void Window::raiseLayers() { + _layerBg->raise(); +} + +const Ui::LayerWidget *Window::topShownLayer() const { + return _layerBg->topShownLayer(); +} + +void Window::showBox(object_ptr box) { + showBox(std::move(box), Ui::LayerOption::KeepOther, anim::type::normal); +} + +void Window::showBox( + object_ptr box, + Ui::LayerOptions options, + anim::type animated) { + _showingLayer.fire({}); + if (window()->width() < st::groupCallWidth + || window()->height() < st::groupCallWidth) { + window()->resize( + std::max(window()->width(), st::groupCallWidth), + std::max(window()->height(), st::groupCallWidth)); + } + _layerBg->showBox(std::move(box), options, animated); +} + +void Window::showLayer( + std::unique_ptr layer, + Ui::LayerOptions options, + anim::type animated) { + _showingLayer.fire({}); + if (window()->width() < st::groupCallWidth + || window()->height() < st::groupCallWidth) { + window()->resize( + std::max(window()->width(), st::groupCallWidth), + std::max(window()->height(), st::groupCallWidth)); + } + _layerBg->showLayer(std::move(layer), options, animated); +} + +void Window::hideLayer(anim::type animated) { + _layerBg->hideAll(animated); +} + +bool Window::isLayerShown() const { + return _layerBg->topShownLayer() != nullptr; +} + +std::shared_ptr Window::uiShow() { + return std::make_shared(this); +} + +void Window::togglePowerSaveBlocker(bool enabled) { + if (!enabled) { + _powerSaveBlocker = nullptr; + } else if (!_powerSaveBlocker) { + _powerSaveBlocker = std::make_unique( + base::PowerSaveBlockType::PreventDisplaySleep, + u"Video call is active"_q, + window()->windowHandle()); + } +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_window.h b/Telegram/SourceFiles/calls/calls_window.h new file mode 100644 index 0000000000..5963b4fc4d --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_window.h @@ -0,0 +1,112 @@ +/* +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/object_ptr.h" +#include "ui/effects/animations.h" +#include "ui/gl/gl_window.h" + +namespace base { +class PowerSaveBlocker; +} // namespace base + +namespace style { +struct WindowTitle; +} // namespace style + +namespace Ui { +class BoxContent; +class RpWindow; +class RpWidget; +class LayerManager; +class LayerWidget; +enum class LayerOption; +using LayerOptions = base::flags; +class Show; +} // namespace Ui + +namespace Ui::Platform { +struct SeparateTitleControls; +struct TitleLayout; +} // namespace Ui::Platform + +namespace Ui::Toast { +struct Config; +class Instance; +} // namespace Ui::Toast + +namespace Calls { + +class Window final : public base::has_weak_ptr { +public: + Window(); + ~Window(); + + [[nodiscard]] Ui::GL::Backend backend() const; + [[nodiscard]] not_null window() const; + [[nodiscard]] not_null widget() const; + + void raiseControls(); + void setControlsStyle(const style::WindowTitle &st); + void setControlsShown(float64 shown); + [[nodiscard]] int controlsWrapTop() const; + [[nodiscard]] QRect controlsGeometry() const; + [[nodiscard]] auto controlsLayoutChanges() const + -> rpl::producer; + [[nodiscard]] bool controlsHasHitTest(QPoint widgetPoint) const; + [[nodiscard]] rpl::producer maximizeRequests() const; + + void raiseLayers(); + [[nodiscard]] const Ui::LayerWidget *topShownLayer() const; + + base::weak_ptr showToast( + const QString &text, + crl::time duration = 0); + base::weak_ptr showToast( + TextWithEntities &&text, + crl::time duration = 0); + base::weak_ptr showToast( + Ui::Toast::Config &&config); + + void showBox(object_ptr box); + void showBox( + object_ptr box, + Ui::LayerOptions options, + anim::type animated = anim::type::normal); + void showLayer( + std::unique_ptr layer, + Ui::LayerOptions options, + anim::type animated = anim::type::normal); + void hideLayer(anim::type animated = anim::type::normal); + [[nodiscard]] bool isLayerShown() const; + + [[nodiscard]] rpl::producer<> showingLayer() const { + return _showingLayer.events(); + } + + [[nodiscard]] std::shared_ptr uiShow(); + + void togglePowerSaveBlocker(bool enabled); + +private: + Ui::GL::Window _window; + const std::unique_ptr _layerBg; + +#ifndef Q_OS_MAC + rpl::variable _controlsTop = 0; + const std::unique_ptr _controls; +#endif // !Q_OS_MAC + + std::unique_ptr _powerSaveBlocker; + + rpl::event_stream _maximizeRequests; + rpl::event_stream<> _showingLayer; + +}; + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index ce06d3223c..57b160c8c3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -48,6 +48,8 @@ class SessionController; namespace Calls { +class Window; + struct InviteRequest { not_null user; bool video = false; @@ -75,8 +77,7 @@ struct StartConferenceInfo { }; struct ConferencePanelMigration { - QScreen *screen = nullptr; - QRect geometry; + std::shared_ptr window; }; } // namespace Calls diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 5b4e87b622..f9a1727729 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1359,22 +1359,22 @@ base::unique_qptr Members::Controller::createRowContextMenu( window->invokeForSessionController( account, participantPeer, - [&](not_null newController) { + [&](not_null<::Window::SessionController*> newController) { callback(newController); newController->widget()->activate(); }); } }; const auto showProfile = [=] { - withActiveWindow([=](not_null window) { + withActiveWindow([=](not_null<::Window::SessionController*> window) { window->showPeerInfo(participantPeer); }); }; const auto showHistory = [=] { - withActiveWindow([=](not_null window) { + withActiveWindow([=](not_null<::Window::SessionController*> window) { window->showPeerHistory( participantPeer, - Window::SectionShow::Way::Forward); + ::Window::SectionShow::Way::Forward); }); }; const auto removeFromVoiceChat = crl::guard(this, [=] { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 27771e27db..d22dc923f5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -17,7 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/ui/calls_group_scheduled_labels.h" #include "calls/group/ui/desktop_capture_choose_source.h" #include "calls/calls_emoji_fingerprint.h" -#include "ui/platform/ui_platform_window_title.h" +#include "calls/calls_window.h" +#include "ui/platform/ui_platform_window_title.h" // TitleLayout #include "ui/platform/ui_platform_utility.h" #include "ui/controls/call_mute_button.h" #include "ui/widgets/buttons.h" @@ -29,7 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/rp_window.h" #include "ui/chat/group_call_bar.h" #include "ui/controls/userpic_button.h" -#include "ui/layers/layer_manager.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" @@ -54,7 +54,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "base/qt_signal_producer.h" #include "base/timer_rpl.h" -#include "base/power_save_blocker.h" #include "apiwrap.h" // api().kick. #include "api/api_chat_participants.h" // api().kick. #include "webrtc/webrtc_environment.h" @@ -91,77 +90,6 @@ constexpr auto kHideControlsTimeout = 5 * crl::time(1000); return result; } -class Show final : public Main::SessionShow { -public: - explicit Show(not_null panel); - ~Show(); - - void showOrHideBoxOrLayer( - std::variant< - v::null_t, - object_ptr, - std::unique_ptr> &&layer, - Ui::LayerOptions options, - anim::type animated) const override; - [[nodiscard]] not_null toastParent() const override; - [[nodiscard]] bool valid() const override; - operator bool() const override; - - [[nodiscard]] Main::Session &session() const override; - -private: - const base::weak_ptr _panel; - -}; - -Show::Show(not_null panel) -: _panel(base::make_weak(panel)) { -} - -Show::~Show() = default; - -void Show::showOrHideBoxOrLayer( - std::variant< - v::null_t, - object_ptr, - std::unique_ptr> &&layer, - Ui::LayerOptions options, - anim::type animated) const { - using UniqueLayer = std::unique_ptr; - using ObjectBox = object_ptr; - if (auto layerWidget = std::get_if(&layer)) { - if (const auto panel = _panel.get()) { - panel->showLayer(std::move(*layerWidget), options, animated); - } - } else if (auto box = std::get_if(&layer)) { - if (const auto panel = _panel.get()) { - panel->showBox(std::move(*box), options, animated); - } - } else if (const auto panel = _panel.get()) { - panel->hideLayer(animated); - } -} - -not_null Show::toastParent() const { - const auto panel = _panel.get(); - Assert(panel != nullptr); - return panel->widget(); -} - -bool Show::valid() const { - return !_panel.empty(); -} - -Show::operator bool() const { - return valid(); -} - -Main::Session &Show::session() const { - const auto panel = _panel.get(); - Assert(panel != nullptr); - return panel->call()->peer()->session(); -} - #ifdef Q_OS_WIN void UnpinMaximized(not_null widget) { SetWindowPos( @@ -198,20 +126,12 @@ Panel::Panel(not_null call) Panel::Panel(not_null call, ConferencePanelMigration info) : _call(call) , _peer(call->peer()) -, _layerBg(std::make_unique(widget())) -#ifndef Q_OS_MAC -, _controls(Ui::Platform::SetupSeparateTitleControls( - window(), - st::groupCallTitle, - nullptr, - _controlsTop.value())) -#endif // !Q_OS_MAC -, _powerSaveBlocker(std::make_unique( - base::PowerSaveBlockType::PreventDisplaySleep, - u"Video chat is active"_q, - window()->windowHandle())) +, _window(info.window ? info.window : std::make_shared()) , _viewport( - std::make_unique(widget(), PanelMode::Wide, _window.backend())) + std::make_unique( + widget(), + PanelMode::Wide, + _window->backend())) , _mute(std::make_unique( widget(), st::callMuteButton, @@ -241,9 +161,6 @@ Panel::Panel(not_null call, ConferencePanelMigration info) return result; }) , _hideControlsTimer([=] { toggleWideControls(false); }) { - _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox); - _layerBg->setHideByBackgroundClick(true); - _viewport->widget()->hide(); if (!_viewport->requireARGB32()) { _call->setNotRequireARGB32(); @@ -287,25 +204,12 @@ bool Panel::isActive() const { return window()->isActiveWindow() && isVisible(); } -base::weak_ptr Panel::showToast( - const QString &text, - crl::time duration) { - return Show(this).showToast(text, duration); +std::shared_ptr Panel::sessionShow() { + return Main::MakeSessionShow(uiShow(), &_peer->session()); } -base::weak_ptr Panel::showToast( - TextWithEntities &&text, - crl::time duration) { - return Show(this).showToast(std::move(text), duration); -} - -base::weak_ptr Panel::showToast( - Ui::Toast::Config &&config) { - return Show(this).showToast(std::move(config)); -} - -std::shared_ptr Panel::uiShow() { - return std::make_shared(this); +std::shared_ptr Panel::uiShow() { + return _window->uiShow(); } void Panel::minimize() { @@ -394,12 +298,20 @@ void Panel::initWindow() { subscribeToPeerChanges(); } + const auto updateFullScreen = [=] { + const auto state = window()->windowState(); + const auto full = (state & Qt::WindowFullScreen) + || (state & Qt::WindowMaximized); + _rtmpFull = _call->rtmp() && full; + _fullScreenOrMaximized = full; + }; base::install_event_filter(window().get(), [=](not_null e) { - if (e->type() == QEvent::Close && handleClose()) { + const auto type = e->type(); + if (type == QEvent::Close && handleClose()) { e->ignore(); return base::EventFilterResult::Cancel; - } else if (e->type() == QEvent::KeyPress - || e->type() == QEvent::KeyRelease) { + } else if (_call->rtmp() + && (type == QEvent::KeyPress || type == QEvent::KeyRelease)) { const auto key = static_cast(e.get())->key(); if (key == Qt::Key_Space) { _call->pushToTalk( @@ -409,16 +321,19 @@ void Panel::initWindow() { && _fullScreenOrMaximized.current()) { toggleFullScreen(); } - } else if (e->type() == QEvent::WindowStateChange && _call->rtmp()) { - const auto state = window()->windowState(); - _fullScreenOrMaximized = (state & Qt::WindowFullScreen) - || (state & Qt::WindowMaximized); + } else if (type == QEvent::WindowStateChange) { + updateFullScreen(); } return base::EventFilterResult::Continue; - }); + }, lifetime()); + updateFullScreen(); + const auto guard = base::make_weak(this); window()->setBodyTitleArea([=](QPoint widgetPoint) { using Flag = Ui::WindowTitleHitTestFlag; + if (!guard) { + return (Flag::None | Flag(0)); + } const auto titleRect = QRect( 0, 0, @@ -434,7 +349,7 @@ void Panel::initWindow() { if (!moveable) { return (Flag::None | Flag(0)); } - const auto shown = _layerBg->topShownLayer(); + const auto shown = _window->topShownLayer(); return (!shown || !shown->geometry().contains(widgetPoint)) ? (Flag::Move | Flag::Menu | Flag::Maximize) : Flag::None; @@ -444,6 +359,23 @@ void Panel::initWindow() { ) | rpl::start_with_next([=] { updateMode(); }, lifetime()); + + _window->maximizeRequests() | rpl::start_with_next([=](bool maximized) { + if (_call->rtmp()) { + toggleFullScreen(maximized); + } else { + window()->setWindowState(maximized + ? Qt::WindowMaximized + : Qt::WindowNoState); + } + }, lifetime()); + + _window->showingLayer() | rpl::start_with_next([=] { + hideStickedTooltip(StickedTooltipHide::Unavailable); + }, lifetime()); + + _window->setControlsStyle(st::groupCallTitle); + _window->togglePowerSaveBlocker(true); } void Panel::initWidget() { @@ -462,7 +394,7 @@ void Panel::initWidget() { // some geometries depends on _controls->controls.geometry, // which is not updated here yet. - crl::on_main(widget(), [=] { updateControlsGeometry(); }); + crl::on_main(this, [=] { updateControlsGeometry(); }); }, lifetime()); } @@ -471,7 +403,7 @@ void Panel::endCall() { _call->hangup(); return; } - showBox(Box( + uiShow()->showBox(Box( LeaveBox, _call, false, @@ -501,7 +433,7 @@ void Panel::startScheduledNow() { .confirmText = tr::lng_group_call_start_now(), }); *box = owned.data(); - showBox(std::move(owned)); + uiShow()->showBox(std::move(owned)); } } @@ -608,10 +540,15 @@ void Panel::initControls() { } void Panel::toggleFullScreen() { - if (_fullScreenOrMaximized.current() || window()->isFullScreen()) { - window()->showNormal(); - } else { + toggleFullScreen( + !_fullScreenOrMaximized.current() && !window()->isFullScreen()); +} + +void Panel::toggleFullScreen(bool fullscreen) { + if (fullscreen) { window()->showFullScreen(); + } else { + window()->showNormal(); } } @@ -630,7 +567,7 @@ void Panel::refreshLeftButton() { _callShare.destroy(); _settings.create(widget(), st::groupCallSettings); _settings->setClickedCallback([=] { - showBox(Box(SettingsBox, _call)); + uiShow()->showBox(Box(SettingsBox, _call)); }); trackControls(_trackControls, true); } @@ -915,13 +852,13 @@ void Panel::setupMembers() { _countdown.destroy(); _startsWhen.destroy(); - _members.create(widget(), _call, mode(), _window.backend()); + _members.create(widget(), _call, mode(), _window->backend()); setupVideo(_viewport.get()); setupVideo(_members->viewport()); _viewport->mouseInsideValue( ) | rpl::filter([=] { - return !_fullScreenOrMaximized.current(); + return !_rtmpFull; }) | rpl::start_with_next([=](bool inside) { toggleWideControls(inside); }, _viewport->lifetime()); @@ -996,7 +933,7 @@ Fn finished)> Panel::shareConferenceLinkCallback() { onstack(!link.isEmpty()); } }; - ExportConferenceCallLink(uiShow(), _call->conferenceCall(), { + ExportConferenceCallLink(sessionShow(), _call->conferenceCall(), { .finished = done, .st = DarkConferenceCallLinkStyle(), }); @@ -1004,8 +941,9 @@ Fn finished)> Panel::shareConferenceLinkCallback() { } void Panel::migrationShowShareLink() { + uiShow()->hideLayer(anim::type::instant); ShowConferenceCallLinkBox( - uiShow(), + sessionShow(), _call->conferenceCall(), _call->existingConferenceLink(), { .st = DarkConferenceCallLinkStyle() }); @@ -1013,7 +951,7 @@ void Panel::migrationShowShareLink() { void Panel::migrationInviteUsers(std::vector users) { const auto done = [=](InviteResult result) { - showToast({ ComposeInviteResultToast(result) }); + uiShow()->showToast({ ComposeInviteResultToast(result) }); }; _call->inviteUsers(std::move(users), crl::guard(this, done)); } @@ -1100,7 +1038,7 @@ void Panel::raiseControls() { if (_pinOnTop) { _pinOnTop->raise(); } - _layerBg->raise(); + _window->raiseLayers(); if (_niceTooltip) { _niceTooltip->raise(); } @@ -1178,7 +1116,7 @@ void Panel::toggleWideControls(bool shown) { return; } _showWideControls = shown; - crl::on_main(widget(), [=] { + crl::on_main(this, [=] { updateWideControlsVisibility(); }); } @@ -1189,7 +1127,7 @@ void Panel::updateWideControlsVisibility() { if (_wideControlsShown == shown) { return; } - _viewport->setCursorShown(!_fullScreenOrMaximized.current() || shown); + _viewport->setCursorShown(!_rtmpFull || shown); _wideControlsShown = shown; _wideControlsAnimation.start( [=] { updateButtonsGeometry(); }, @@ -1216,7 +1154,7 @@ void Panel::subscribeToChanges(not_null real) { const auto skip = st::groupCallRecordingMarkSkip; _recordingMark->resize(size + 2 * skip, size + 2 * skip); _recordingMark->setClickedCallback([=] { - showToast({ (livestream + uiShow()->showToast({ (livestream ? tr::lng_group_call_is_recorded_channel : real->recordVideo() ? tr::lng_group_call_is_recorded_video @@ -1262,7 +1200,7 @@ void Panel::subscribeToChanges(not_null real) { *startedAsVideo = isVideo; } validateRecordingMark(recorded); - showToast((recorded + uiShow()->showToast((recorded ? (livestream ? tr::lng_group_call_recording_started_channel : isVideo @@ -1323,7 +1261,7 @@ void Panel::createPinOnTop() { pin ? &st::groupCallPinnedOnTop : nullptr, pin ? &st::groupCallPinnedOnTop : nullptr); if (!_pinOnTop->isHidden()) { - showToast({ pin + uiShow()->showToast({ pin ? tr::lng_group_call_pinned_on_top(tr::now) : tr::lng_group_call_unpinned_on_top(tr::now) }); } @@ -1331,11 +1269,9 @@ void Panel::createPinOnTop() { }; _fullScreenOrMaximized.value( ) | rpl::start_with_next([=](bool fullScreenOrMaximized) { -#ifndef Q_OS_MAC - _controls->controls.setStyle(fullScreenOrMaximized + _window->setControlsStyle(fullScreenOrMaximized ? st::callTitle : st::groupCallTitle); -#endif // Q_OS_MAC _pinOnTop->setVisible(!fullScreenOrMaximized); if (fullScreenOrMaximized) { @@ -1425,7 +1361,7 @@ void Panel::refreshTopButton() { void Panel::screenSharingPrivacyRequest() { if (auto box = ScreenSharingPrivacyRequestBox()) { - showBox(std::move(box)); + uiShow()->showBox(std::move(box)); } } @@ -1476,7 +1412,7 @@ void Panel::chooseShareScreenSource() { .confirmText = tr::lng_continue(), }); *shared = box.data(); - showBox(std::move(box)); + uiShow()->showBox(std::move(box)); } void Panel::chooseJoinAs() { @@ -1487,7 +1423,7 @@ void Panel::chooseJoinAs() { _joinAsProcess.start( _peer, context, - std::make_shared(this), + uiShow(), callback, _call->joinAs()); } @@ -1508,7 +1444,7 @@ void Panel::showMainMenu() { wide, [=] { chooseJoinAs(); }, [=] { chooseShareScreenSource(); }, - [=](auto box) { showBox(std::move(box)); }); + [=](auto box) { uiShow()->showBox(std::move(box)); }); if (_menu->empty()) { _wideMenuShown = false; _menu.destroy(); @@ -1574,21 +1510,21 @@ void Panel::addMembers() { const auto conferenceLimit = appConfig.confcallSizeLimit(); if (_call->conference() && _call->conferenceCall()->fullCount() >= conferenceLimit) { - showToast({ tr::lng_group_call_invite_limit(tr::now) }); + uiShow()->showToast({ tr::lng_group_call_invite_limit(tr::now) }); } const auto showToastCallback = [=](TextWithEntities &&text) { - showToast(std::move(text)); + uiShow()->showToast(std::move(text)); }; const auto link = _call->conference() ? shareConferenceLinkCallback() : nullptr; if (auto box = PrepareInviteBox(_call, showToastCallback, link)) { - showBox(std::move(box)); + uiShow()->showBox(std::move(box)); } } void Panel::kickParticipant(not_null participantPeer) { - showBox(Box([=](not_null box) { + uiShow()->showBox(Box([=](not_null box) { box->addRow( object_ptr( box.get(), @@ -1621,46 +1557,6 @@ void Panel::kickParticipant(not_null participantPeer) { })); } -void Panel::showBox(object_ptr box) { - showBox(std::move(box), Ui::LayerOption::KeepOther, anim::type::normal); -} - -void Panel::showBox( - object_ptr box, - Ui::LayerOptions options, - anim::type animated) { - hideStickedTooltip(StickedTooltipHide::Unavailable); - if (window()->width() < st::groupCallWidth - || window()->height() < st::groupCallWidth) { - window()->resize( - std::max(window()->width(), st::groupCallWidth), - std::max(window()->height(), st::groupCallWidth)); - } - _layerBg->showBox(std::move(box), options, animated); -} - -void Panel::showLayer( - std::unique_ptr layer, - Ui::LayerOptions options, - anim::type animated) { - hideStickedTooltip(StickedTooltipHide::Unavailable); - if (window()->width() < st::groupCallWidth - || window()->height() < st::groupCallWidth) { - window()->resize( - std::max(window()->width(), st::groupCallWidth), - std::max(window()->height(), st::groupCallWidth)); - } - _layerBg->showLayer(std::move(layer), options, animated); -} - -void Panel::hideLayer(anim::type animated) { - _layerBg->hideAll(animated); -} - -bool Panel::isLayerShown() const { - return _layerBg->topShownLayer() != nullptr; -} - void Panel::kickParticipantSure(not_null participantPeer) { if (_call->conference()) { if (const auto user = participantPeer->asUser()) { @@ -1689,17 +1585,16 @@ void Panel::kickParticipantSure(not_null participantPeer) { void Panel::initLayout(ConferencePanelMigration info) { initGeometry(info); -#ifndef Q_OS_MAC - _controls->wrap.raise(); + _window->raiseControls(); - _controls->controls.layout().changes( + _window->controlsLayoutChanges( ) | rpl::start_with_next([=] { // _menuToggle geometry depends on _controls arrangement. - crl::on_main(widget(), [=] { updateControlsGeometry(); }); + crl::on_main(this, [=] { updateControlsGeometry(); }); }, lifetime()); raiseControls(); -#endif // !Q_OS_MAC + updateControlsGeometry(); } void Panel::showControls() { @@ -1714,7 +1609,7 @@ void Panel::closeBeforeDestroy() { } rpl::lifetime &Panel::lifetime() { - return window()->lifetime(); + return _lifetime; } void Panel::initGeometry(ConferencePanelMigration info) { @@ -1724,15 +1619,7 @@ void Panel::initGeometry(ConferencePanelMigration info) { const auto minHeight = _call->rtmp() ? st::groupCallHeightRtmpMin : st::groupCallHeight; - if (info.screen && !info.geometry.isEmpty()) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - window()->setScreen(info.screen); -#else // Qt >= 6.0.0 - window()->createWinId(); - window()->windowHandle()->setScreen(info.screen); -#endif // Qt < 6.0.0 - window()->setGeometry(info.geometry); - } else { + if (!info.window) { const auto center = Core::App().getPointForCallPanelCenter(); const auto width = _call->rtmp() ? st::groupCallWidthRtmp @@ -1763,7 +1650,7 @@ QRect Panel::computeTitleRect() const { #ifdef Q_OS_MAC return QRect(70, 0, width - remove - 70, 28); #else // Q_OS_MAC - const auto controls = _controls->controls.geometry(); + const auto controls = _window->controlsGeometry(); const auto right = controls.x() + controls.width() + skip; return (controls.center().x() < width / 2) ? QRect(right, 0, width - right - remove, controls.height()) @@ -1925,7 +1812,7 @@ void Panel::refreshControlsBackground() { } void Panel::refreshTitleBackground() { - if (!_fullScreenOrMaximized.current()) { + if (!_rtmpFull) { _titleBackground.destroy(); return; } else if (_titleBackground) { @@ -2070,7 +1957,7 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) { } void Panel::trackControlOver(not_null control, bool over) { - if (_fullScreenOrMaximized.current()) { + if (_rtmpFull) { return; } else if (_stickedTooltipClose) { if (!over) { @@ -2111,7 +1998,7 @@ void Panel::showStickedTooltip() { && callReady && _mute && !_call->mutedByAdmin() - && !_layerBg->topShownLayer()) { + && !_window->topShownLayer()) { if (_stickedTooltipClose) { // Showing already. return; @@ -2314,10 +2201,10 @@ void Panel::updateControlsGeometry() { const auto controlsOnTheLeft = true; const auto controlsPadding = 0; #else // Q_OS_MAC - const auto center = _controls->controls.geometry().center(); + const auto center = _window->controlsGeometry().center(); const auto controlsOnTheLeft = center.x() < widget()->width() / 2; - const auto controlsPadding = _controls->wrap.y(); + const auto controlsPadding = _window->controlsWrapTop(); #endif // Q_OS_MAC const auto menux = st::groupCallMenuTogglePosition.x(); const auto menuy = st::groupCallMenuTogglePosition.y(); @@ -2425,7 +2312,7 @@ void Panel::updateButtonsGeometry() { _controlsBackgroundWide->setGeometry( rect.marginsAdded(st::groupCallControlsBackMargin)); } - if (_fullScreenOrMaximized.current()) { + if (_rtmpFull) { refreshTitleGeometry(); } } else { @@ -2493,10 +2380,9 @@ void Panel::updateMembersGeometry() { _members->setVisible(!_call->rtmp()); const auto desiredHeight = _members->desiredHeight(); if (mode() == PanelMode::Wide) { - const auto full = _fullScreenOrMaximized.current(); - const auto skip = full ? 0 : st::groupCallNarrowSkip; + const auto skip = _rtmpFull ? 0 : st::groupCallNarrowSkip; const auto membersWidth = st::groupCallNarrowMembersWidth; - const auto top = full ? 0 : st::groupCallWideVideoTop; + const auto top = _rtmpFull ? 0 : st::groupCallWideVideoTop; _members->setGeometry( widget()->width() - skip - membersWidth, top, @@ -2505,7 +2391,7 @@ void Panel::updateMembersGeometry() { const auto viewportSkip = _call->rtmp() ? 0 : (skip + membersWidth); - _viewport->setGeometry(full, { + _viewport->setGeometry(_rtmpFull, { skip, top, widget()->width() - viewportSkip - 2 * skip, @@ -2654,9 +2540,8 @@ void Panel::refreshTitleGeometry() { ? st::groupCallTitleTop : (st::groupCallWideVideoTop - st::groupCallTitleLabel.style.font->height) / 2; - const auto shown = _fullScreenOrMaximized.current() - ? _wideControlsAnimation.value( - _wideControlsShown ? 1. : 0.) + const auto shown = _rtmpFull + ? _wideControlsAnimation.value(_wideControlsShown ? 1. : 0.) : 1.; const auto top = anim::interpolate( -_title->height() - st::boxRadius, @@ -2720,10 +2605,7 @@ void Panel::refreshTitleGeometry() { } else { layout(left + titleRect.width() - best); } - -#ifndef Q_OS_MAC - _controlsTop = anim::interpolate(-_controls->wrap.height(), 0, shown); -#endif // Q_OS_MAC + _window->setControlsShown(shown); } void Panel::refreshTitleColors() { @@ -2760,11 +2642,11 @@ bool Panel::handleClose() { } not_null Panel::window() const { - return _window.window(); + return _window->window(); } not_null Panel::widget() const { - return _window.widget(); + return _window->widget(); } } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 882049cb86..9110c495e9 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -7,32 +7,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "base/weak_ptr.h" -#include "base/timer.h" -#include "base/flags.h" #include "base/object_ptr.h" -#include "base/unique_qptr.h" #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_common.h" #include "calls/group/calls_choose_join_as.h" #include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/effects/animations.h" -#include "ui/gl/gl_window.h" -#include "ui/layers/show.h" -#include "ui/rp_widget.h" class Image; -namespace base { -class PowerSaveBlocker; -} // namespace base - namespace Data { class PhotoMedia; class GroupCall; } // namespace Data +namespace Main { +class SessionShow; +} // namespace Main + namespace Ui { +class Show; class BoxContent; class LayerWidget; enum class LayerOption; @@ -45,13 +39,13 @@ class CallMuteButton; class IconButton; class FlatLabel; class RpWidget; +class RpWindow; template class FadeWrap; template class PaddingWrap; class ScrollArea; class GenericBox; -class LayerManager; class GroupCallScheduledLeft; } // namespace Ui @@ -60,14 +54,6 @@ class Instance; struct Config; } // namespace Ui::Toast -namespace Ui::Platform { -struct SeparateTitleControls; -} // namespace Ui::Platform - -namespace Main { -class SessionShow; -} // namespace Main - namespace style { struct CallSignalBars; struct CallBodyLayout; @@ -76,6 +62,7 @@ struct CallBodyLayout; namespace Calls { struct InviteRequest; struct ConferencePanelMigration; +class Window; } // namespace Calls namespace Calls::Group { @@ -100,37 +87,20 @@ public: [[nodiscard]] bool isVisible() const; [[nodiscard]] bool isActive() const; - base::weak_ptr showToast( - const QString &text, - crl::time duration = 0); - base::weak_ptr showToast( - TextWithEntities &&text, - crl::time duration = 0); - base::weak_ptr showToast( - Ui::Toast::Config &&config); - - void showBox(object_ptr box); - void showBox( - object_ptr box, - Ui::LayerOptions options, - anim::type animated = anim::type::normal); - void showLayer( - std::unique_ptr layer, - Ui::LayerOptions options, - anim::type animated = anim::type::normal); - void hideLayer(anim::type animated = anim::type::normal); - [[nodiscard]] bool isLayerShown() const; - void migrationShowShareLink(); void migrationInviteUsers(std::vector users); void minimize(); void toggleFullScreen(); + void toggleFullScreen(bool fullscreen); void close(); void showAndActivate(); void closeBeforeDestroy(); - [[nodiscard]] std::shared_ptr uiShow(); + [[nodiscard]] std::shared_ptr sessionShow(); + [[nodiscard]] std::shared_ptr uiShow(); + + [[nodiscard]] not_null window() const; rpl::lifetime &lifetime(); @@ -148,8 +118,6 @@ private: Discarded, }; - [[nodiscard]] not_null window() const; - [[nodiscard]] PanelMode mode() const; void paint(QRect clip); @@ -237,18 +205,11 @@ private: const not_null _call; not_null _peer; - Ui::GL::Window _window; - const std::unique_ptr _layerBg; + std::shared_ptr _window; rpl::variable _mode; rpl::variable _fullScreenOrMaximized = false; bool _unpinnedMaximized = false; - -#ifndef Q_OS_MAC - rpl::variable _controlsTop = 0; - const std::unique_ptr _controls; -#endif // !Q_OS_MAC - - const std::unique_ptr _powerSaveBlocker; + bool _rtmpFull = false; rpl::lifetime _callLifetime; @@ -305,6 +266,7 @@ private: rpl::lifetime _hideControlsTimerLifetime; rpl::lifetime _peerLifetime; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_toasts.cpp b/Telegram/SourceFiles/calls/group/calls_group_toasts.cpp index 55e67be95e..584a30b551 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_toasts.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_toasts.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_panel.h" #include "data/data_peer.h" #include "data/data_group_call.h" +#include "ui/layers/show.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "lang/lang_keys.h" @@ -49,7 +50,7 @@ void Toasts::setupJoinAsChanged() { return (state == State::Joined); }) | rpl::take(1); }) | rpl::flatten_latest() | rpl::start_with_next([=] { - _panel->showToast((_call->peer()->isBroadcast() + _panel->uiShow()->showToast((_call->peer()->isBroadcast() ? tr::lng_group_call_join_as_changed_channel : tr::lng_group_call_join_as_changed)( tr::now, @@ -69,7 +70,7 @@ void Toasts::setupTitleChanged() { ? peer->name() : peer->groupCall()->title(); }) | rpl::start_with_next([=](const QString &title) { - _panel->showToast((_call->peer()->isBroadcast() + _panel->uiShow()->showToast((_call->peer()->isBroadcast() ? tr::lng_group_call_title_changed_channel : tr::lng_group_call_title_changed)( tr::now, @@ -83,7 +84,8 @@ void Toasts::setupAllowedToSpeak() { _call->allowedToSpeakNotifications( ) | rpl::start_with_next([=] { if (_panel->isActive()) { - _panel->showToast(tr::lng_group_call_can_speak_here(tr::now)); + _panel->uiShow()->showToast( + tr::lng_group_call_can_speak_here(tr::now)); } else { const auto real = _call->lookupReal(); const auto name = (real && !real->title().isEmpty()) @@ -137,7 +139,7 @@ void Toasts::setupPinnedVideo() { : tr::lng_group_call_unpinned_screen); return key(tr::now, lt_user, peer->shortName()); }(); - _panel->showToast(text); + _panel->uiShow()->showToast(text); }, _lifetime); } @@ -146,7 +148,7 @@ void Toasts::setupRequestedToSpeak() { ) | rpl::combine_previous( ) | rpl::start_with_next([=](MuteState was, MuteState now) { if (was == MuteState::ForceMuted && now == MuteState::RaisedHand) { - _panel->showToast( + _panel->uiShow()->showToast( tr::lng_group_call_tooltip_raised_hand(tr::now)); } }, _lifetime); @@ -173,7 +175,7 @@ void Toasts::setupError() { } Unexpected("Error in Calls::Group::Toasts::setupErrorToasts."); }(); - _panel->showToast({ key(tr::now) }, kErrorDuration); + _panel->uiShow()->showToast({ key(tr::now) }, kErrorDuration); }, _lifetime); } diff --git a/Telegram/lib_base b/Telegram/lib_base index 720eaf44a5..419049fcbe 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 720eaf44a529da50f5277fd6874318d9a08b735a +Subproject commit 419049fcbe88c68486609232c4db7550833c74ca From a569495f5d31a03247eadc3c23b0dfd3b8d8bb7c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Apr 2025 20:07:22 +0400 Subject: [PATCH 123/190] Fix discarding group call invites. --- Telegram/SourceFiles/calls/calls_instance.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 10302759de..2cb521d116 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -1026,12 +1026,11 @@ void Instance::declineOutgoingConferenceInvite( } } } - if (!j->second.incoming.empty()) { - return; - } - i->second.users.erase(j); - if (i->second.users.empty()) { - _conferenceInvites.erase(i); + if (j->second.incoming.empty()) { + i->second.users.erase(j); + if (i->second.users.empty()) { + _conferenceInvites.erase(i); + } } user->owner().unregisterInvitedToCallUser(conferenceId, user, !discard); } From 2a7aac76d965ce52536edb86e21558297860aa0e Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 9 Apr 2025 10:28:21 +0400 Subject: [PATCH 124/190] Update API scheme on layer 202. --- Telegram/SourceFiles/calls/calls_panel.cpp | 7 +- .../calls/group/calls_group_call.cpp | 11 -- .../calls/group/calls_group_call.h | 1 - .../calls/group/calls_group_common.cpp | 160 +++++++++--------- .../calls/group/calls_group_common.h | 15 +- .../group/calls_group_invite_controller.cpp | 8 +- .../group/calls_group_invite_controller.h | 2 +- .../calls/group/calls_group_panel.cpp | 23 +-- .../calls/group/calls_group_panel.h | 3 +- Telegram/SourceFiles/data/data_group_call.cpp | 5 + Telegram/SourceFiles/data/data_group_call.h | 2 + Telegram/SourceFiles/main/main_app_config.cpp | 2 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 6 +- .../SourceFiles/window/window_main_menu.cpp | 4 +- 14 files changed, 104 insertions(+), 145 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 5a95153d36..7abf76b029 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -353,11 +353,6 @@ void Panel::initControls() { } const auto call = _call; const auto creating = std::make_shared(); - const auto finish = [=](QString link) { - if (link.isEmpty()) { - *creating = false; - } - }; const auto create = [=](std::vector users) { if (*creating) { return; @@ -366,7 +361,7 @@ void Panel::initControls() { const auto sharingLink = users.empty(); Group::MakeConferenceCall({ .show = sessionShow(), - .finished = finish, + .finished = [=](bool) { *creating = false; }, .joining = true, .info = { .invite = std::move(users), diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index c8399d35c9..5f27e80ca5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1211,15 +1211,6 @@ std::shared_ptr GroupCall::conferenceCall() const { return _conferenceCall; } -QString GroupCall::existingConferenceLink() const { - Expects(!_conferenceLinkSlug.isEmpty()); - - const auto session = &_peer->session(); - return !_conferenceLinkSlug.isEmpty() - ? session->createInternalLinkFull("call/" + _conferenceLinkSlug) - : QString(); -} - rpl::producer> GroupCall::real() const { if (const auto real = lookupReal()) { return rpl::single(not_null{ real }); @@ -1569,7 +1560,6 @@ void GroupCall::sendJoinRequest() { | (wasVideoStopped ? Flag::f_video_stopped : Flag(0)) - | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); _api.request(MTPphone_JoinGroupCall( MTP_flags(flags), @@ -1578,7 +1568,6 @@ void GroupCall::sendJoinRequest() { MTP_string(_joinHash), (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), MTP_bytes(joinBlock), - MTP_int(_conferenceJoinMessageId.bare), MTP_dataJSON(MTP_bytes(_joinState.payload.json)) )).done([=]( const MTPUpdates &updates, diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index c7f8368061..7b29f357c3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -260,7 +260,6 @@ public: [[nodiscard]] Data::GroupCall *lookupReal() const; [[nodiscard]] std::shared_ptr conferenceCall() const; - [[nodiscard]] QString existingConferenceLink() const; [[nodiscard]] rpl::producer> real() const; [[nodiscard]] rpl::producer emojiHashValue() const; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index d313a030b0..a1db17f0a7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -253,10 +253,10 @@ ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle() { void ShowConferenceCallLinkBox( std::shared_ptr show, std::shared_ptr call, - const QString &link, - ConferenceCallLinkArgs &&args) { + const ConferenceCallLinkArgs &args) { const auto st = args.st; const auto initial = args.initial; + const auto link = call->conferenceInviteLink(); show->showBox(Box([=](not_null box) { struct State { base::unique_qptr menu; @@ -285,30 +285,25 @@ void ShowConferenceCallLinkBox( } state->resetting = true; using Flag = MTPphone_ToggleGroupCallSettings::Flag; + const auto weak = Ui::MakeWeak(box); call->session().api().request( MTPphone_ToggleGroupCallSettings( MTP_flags(Flag::f_reset_invite_hash), call->input(), MTPbool()) // join_muted - ).done([=] { - auto copy = args; - const auto weak = Ui::MakeWeak(box); - copy.finished = [=](QString link) { - if (const auto strong = weak.data()) { - strong->closeBox(); - } - show->showToast({ - .title = tr::lng_confcall_link_revoked_title( - tr::now), - .text = { - tr::lng_confcall_link_revoked_text(tr::now), - }, - }); - }; - ExportConferenceCallLink( - show, - call, - std::move(copy)); + ).done([=](const MTPUpdates &result) { + call->session().api().applyUpdates(result); + ShowConferenceCallLinkBox(show, call, args); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + show->showToast({ + .title = tr::lng_confcall_link_revoked_title( + tr::now), + .text = { + tr::lng_confcall_link_revoked_text(tr::now), + }, + }); }).send(); }; toggle->setClickedCallback([=] { @@ -355,10 +350,7 @@ void ShowConferenceCallLinkBox( Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 2); const auto preview = box->addRow( - Info::BotStarRef::MakeLinkLabel( - box, - link, - st.linkPreview)); + Info::BotStarRef::MakeLinkLabel(box, link, st.linkPreview)); Ui::AddSkip(box->verticalLayout()); const auto copyCallback = [=] { @@ -372,11 +364,11 @@ void ShowConferenceCallLinkBox( st.shareBox ? *st.shareBox : ShareBoxStyleOverrides()); }; preview->setClickedCallback(copyCallback); - [[maybe_unused]] const auto share = box->addButton( + const auto share = box->addButton( tr::lng_group_invite_share(), shareCallback, st::confcallLinkShareButton); - [[maybe_unused]] const auto copy = box->addButton( + const auto copy = box->addButton( tr::lng_group_invite_copy(), copyCallback, st::confcallLinkCopyButton); @@ -457,21 +449,57 @@ void ShowConferenceCallLinkBox( })); } -void ExportConferenceCallLink( - std::shared_ptr show, - std::shared_ptr call, - ConferenceCallLinkArgs &&args) { - const auto session = &show->session(); - const auto info = std::move(args.info); +void MakeConferenceCall(ConferenceFactoryArgs &&args) { + const auto show = std::move(args.show); const auto finished = std::move(args.finished); - - using Flag = MTPphone_ExportGroupCallInvite::Flag; - session->api().request(MTPphone_ExportGroupCallInvite( - MTP_flags(Flag::f_can_self_unmute), - call->input() - )).done([=](const MTPphone_ExportedGroupCallInvite &result) { - const auto link = qs(result.data().vlink()); - if (args.joining) { + const auto joining = args.joining; + const auto info = std::move(args.info); + const auto session = &show->session(); + const auto fail = [=](QString error) { + show->showToast(error); + if (const auto onstack = finished) { + onstack(false); + } + }; + session->api().request(MTPphone_CreateConferenceCall( + MTP_flags(0), + MTP_int(base::RandomValue()), + MTPint256(), // public_key + MTPbytes(), // block + MTPDataJSON() // params + )).done([=](const MTPUpdates &result) { + session->api().applyUpdates(result); + const auto updates = result.match([&](const MTPDupdates &data) { + return &data.vupdates().v; + }, [&](const MTPDupdatesCombined &data) { + return &data.vupdates().v; + }, [](const auto &) { + return (const QVector*)nullptr; + }); + if (!updates) { + fail(u"Call not found!"_q); + return; + } + auto call = std::shared_ptr(); + for (const auto &update : *updates) { + update.match([&](const MTPDupdateGroupCall &data) { + data.vcall().match([&](const auto &data) { + call = session->data().sharedConferenceCall( + data.vid().v, + data.vaccess_hash().v); + call->enqueueUpdate(update); + }); + }, [](const auto &) {}); + if (call) { + break; + } + } + const auto link = call ? call->conferenceInviteLink() : QString(); + if (link.isEmpty()) { + fail(u"Call link not found!"_q); + return; + } + if (joining) { if (auto slug = ExtractConferenceSlug(link); !slug.isEmpty()) { auto copy = info; copy.call = call; @@ -479,53 +507,17 @@ void ExportConferenceCallLink( Core::App().calls().startOrJoinConferenceCall( std::move(copy)); } - if (const auto onstack = finished) { - finished(QString()); - } - return; + } else { + Calls::Group::ShowConferenceCallLinkBox( + show, + call, + { .initial = true }); } - Calls::Group::ShowConferenceCallLinkBox( - show, - call, - link, - base::duplicate(args)); if (const auto onstack = finished) { - finished(link); + finished(true); } }).fail([=](const MTP::Error &error) { - show->showToast(error.type()); - if (const auto onstack = finished) { - finished(QString()); - } - }).send(); -} - -void MakeConferenceCall(ConferenceFactoryArgs &&args) { - const auto show = std::move(args.show); - const auto finished = std::move(args.finished); - const auto joining = args.joining; - const auto info = std::move(args.info); - const auto session = &show->session(); - session->api().request(MTPphone_CreateConferenceCall( - MTP_int(base::RandomValue()) - )).done([=](const MTPphone_GroupCall &result) { - result.data().vcall().match([&](const auto &data) { - const auto call = session->data().sharedConferenceCall( - data.vid().v, - data.vaccess_hash().v); - call->processFullCall(result); - Calls::Group::ExportConferenceCallLink(show, call, { - .initial = true, - .joining = joining, - .finished = finished, - .info = info, - }); - }); - }).fail([=](const MTP::Error &error) { - show->showToast(error.type()); - if (const auto onstack = finished) { - onstack(QString()); - } + fail(error.type()); }).send(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 57b160c8c3..8407791535 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -179,26 +179,17 @@ struct ConferenceCallLinkStyleOverrides { [[nodiscard]] ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle(); struct ConferenceCallLinkArgs { - bool initial = false; - bool joining = false; - Fn finished; - StartConferenceInfo info; ConferenceCallLinkStyleOverrides st; + bool initial = false; }; void ShowConferenceCallLinkBox( std::shared_ptr show, std::shared_ptr call, - const QString &link, - ConferenceCallLinkArgs &&args); - -void ExportConferenceCallLink( - std::shared_ptr show, - std::shared_ptr call, - ConferenceCallLinkArgs &&args); + const ConferenceCallLinkArgs &args); struct ConferenceFactoryArgs { std::shared_ptr show; - Fn finished; + Fn finished; bool joining = false; StartConferenceInfo info; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 1523857d56..d3313b10b0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -472,7 +472,7 @@ std::unique_ptr InviteContactsController::createRow( object_ptr PrepareInviteBox( not_null call, Fn showToast, - Fn finished)> shareConferenceLink) { + Fn shareConferenceLink) { const auto real = call->lookupReal(); if (!real) { return nullptr; @@ -497,8 +497,10 @@ object_ptr PrepareInviteBox( if (conference) { const auto close = std::make_shared>(); const auto shareLink = [=] { - Assert(shareConferenceLink != nullptr); - shareConferenceLink([=](bool ok) { if (ok) (*close)(); }); + Expects(shareConferenceLink != nullptr); + + shareConferenceLink(); + (*close)(); }; auto controller = std::make_unique( &real->session(), diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h index 8c136d19f7..4c1ba17d6f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h @@ -80,7 +80,7 @@ private: [[nodiscard]] object_ptr PrepareInviteBox( not_null call, Fn showToast, - Fn finished)> shareConferenceLink = nullptr); + Fn shareConferenceLink = nullptr); [[nodiscard]] object_ptr PrepareInviteBox( not_null call, diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index d22dc923f5..b55e375891 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -905,9 +905,7 @@ void Panel::setupMembers() { }, _callLifetime); _members->shareLinkRequests( - ) | rpl::start_with_next([cb = shareConferenceLinkCallback()] { - cb(nullptr); - }, _callLifetime); + ) | rpl::start_with_next(shareConferenceLinkCallback(), _callLifetime); _call->videoEndpointLargeValue( ) | rpl::start_with_next([=](const VideoEndpoint &large) { @@ -918,23 +916,11 @@ void Panel::setupMembers() { }, _callLifetime); } -Fn finished)> Panel::shareConferenceLinkCallback() { - const auto exporting = std::make_shared(); - return [=](Fn finished) { +Fn Panel::shareConferenceLinkCallback() { + return [=] { Expects(_call->conference()); - if (*exporting) { - return; - } - *exporting = true; - const auto done = [=](QString link) { - *exporting = false; - if (const auto onstack = finished) { - onstack(!link.isEmpty()); - } - }; - ExportConferenceCallLink(sessionShow(), _call->conferenceCall(), { - .finished = done, + ShowConferenceCallLinkBox(sessionShow(), _call->conferenceCall(), { .st = DarkConferenceCallLinkStyle(), }); }; @@ -945,7 +931,6 @@ void Panel::migrationShowShareLink() { ShowConferenceCallLinkBox( sessionShow(), _call->conferenceCall(), - _call->existingConferenceLink(), { .st = DarkConferenceCallLinkStyle() }); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 9110c495e9..94519c9a99 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -170,8 +170,7 @@ private: void toggleWideControls(bool shown); void updateWideControlsVisibility(); [[nodiscard]] bool videoButtonInNarrowMode() const; - [[nodiscard]] auto shareConferenceLinkCallback() - -> Fn finished)>; + [[nodiscard]] Fn shareConferenceLinkCallback(); void endCall(); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 6747b01b98..cb4b06cb32 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -295,6 +295,10 @@ rpl::producer GroupCall::fullCountValue() const { return _fullCount.value(); } +QString GroupCall::conferenceInviteLink() const { + return _conferenceInviteLink; +} + bool GroupCall::participantsLoaded() const { return _allParticipantsLoaded; } @@ -515,6 +519,7 @@ void GroupCall::applyCallFields(const MTPDgroupCall &data) { _unmutedVideoLimit = data.vunmuted_video_limit().v; _allParticipantsLoaded = (_serverParticipantsCount == _participants.size()); + _conferenceInviteLink = qs(data.vinvite_link().value_or_empty()); } void GroupCall::applyLocalUpdate( diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index dfce953e3f..f335e84680 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -176,6 +176,7 @@ public: [[nodiscard]] int fullCount() const; [[nodiscard]] rpl::producer fullCountValue() const; + [[nodiscard]] QString conferenceInviteLink() const; void setInCall(); void reload(); @@ -232,6 +233,7 @@ private: mtpRequestId _reloadRequestId = 0; crl::time _reloadLastFinished = 0; rpl::variable _title; + QString _conferenceInviteLink; base::flat_multi_map< std::pair, diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index a24382b82e..d925d12787 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -116,7 +116,7 @@ int AppConfig::confcallSizeLimit() const { bool AppConfig::confcallPrioritizeVP8() const { AssertIsDebug(); - return true; + return false; return get(u"confcall_use_vp8"_q, false); } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 9b1250e1b7..af06c44507 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1342,7 +1342,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#7fe91c14 views_graph:StatsGraph reactions_by_emotion_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true conference:flags.14?true creator:flags.15?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; +groupCall#553b0ba1 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true conference:flags.14?true creator:flags.15?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int invite_link:flags.16?string = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; inputGroupCallSlug#fe06823f slug:string = InputGroupCall; @@ -2587,7 +2587,7 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#48cdc6d8 flags:# rtmp_stream:flags.2?true peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; -phone.joinGroupCall#dac17b9e flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string public_key:flags.3?int256 block:flags.3?bytes invite_msg_id:flags.4?int params:DataJSON = Updates; +phone.joinGroupCall#8fb53057 flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string public_key:flags.3?int256 block:flags.3?bytes params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; @@ -2608,7 +2608,7 @@ phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels; phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; -phone.createConferenceCall#fbcefee6 random_id:int = phone.GroupCall; +phone.createConferenceCall#7d0444bb flags:# muted:flags.0?true video_stopped:flags.2?true join:flags.3?true random_id:int public_key:flags.3?int256 block:flags.3?bytes params:flags.3?DataJSON = Updates; phone.deleteConferenceCallParticipants#8ca60525 flags:# only_left:flags.0?true kick:flags.1?true call:InputGroupCall ids:Vector block:bytes = Updates; phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; phone.inviteConferenceCallParticipant#bcf22685 flags:# video:flags.0?true call:InputGroupCall user_id:InputUser = Updates; diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 189adccc03..97256293bb 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -128,8 +128,8 @@ constexpr auto kPlayStatusLimit = 2; return; } *creating = true; - const auto finished = [=](QString link) { - if (link.isEmpty()) { + const auto finished = [=](bool ok) { + if (!ok) { *creating = false; } else if (const auto onstack = done) { onstack(); From c72cf46db7244282bc463427c5bda37cf97ccd4c Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 9 Apr 2025 14:18:14 +0400 Subject: [PATCH 125/190] Implement fast confcall migration. --- Telegram/SourceFiles/calls/calls_instance.cpp | 82 +++-- Telegram/SourceFiles/calls/calls_instance.h | 4 + Telegram/SourceFiles/calls/calls_panel.cpp | 23 +- .../calls/group/calls_group_call.cpp | 314 ++++++++++++------ .../calls/group/calls_group_call.h | 16 +- .../calls/group/calls_group_common.cpp | 62 ++-- .../calls/group/calls_group_common.h | 4 +- .../calls/group/calls_group_panel.cpp | 3 +- Telegram/SourceFiles/data/data_group_call.cpp | 13 +- Telegram/SourceFiles/data/data_group_call.h | 2 +- Telegram/SourceFiles/data/data_session.cpp | 30 ++ Telegram/SourceFiles/data/data_session.h | 2 + .../window/window_session_controller.cpp | 2 +- 13 files changed, 366 insertions(+), 191 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 2cb521d116..145869b109 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 "mtproto/mtproto_dh_utils.h" #include "core/application.h" #include "core/core_settings.h" +#include "main/session/session_show.h" #include "main/main_session.h" #include "main/main_account.h" #include "apiwrap.h" @@ -237,14 +238,20 @@ void Instance::startOrJoinGroupCall( } void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { - const auto migrationInfo = (args.migrating && _currentCallPanel) + Expects(args.call || (args.migrating && args.show)); + + const auto migrationInfo = (args.migrating + && args.call + && _currentCallPanel) ? _currentCallPanel->migrationInfo() : ConferencePanelMigration(); - if (!args.migrating) { + if (args.call && !args.migrating) { destroyCurrentCall(); } - const auto session = &args.call->peer()->session(); + const auto session = args.show + ? &args.show->session() + : &args.call->session(); auto call = std::make_unique(_delegate.get(), args); const auto raw = call.get(); @@ -253,20 +260,46 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { destroyGroupCall(raw); }, raw->lifetime()); + if (args.call) { + _currentGroupCallPanel = std::make_unique( + raw, + migrationInfo); + _currentGroupCall = std::move(call); + _currentGroupCallChanges.fire_copy(raw); + if (args.migrating) { + destroyCurrentCall(args.call.get(), args.linkSlug); + } + } else { + if (const auto was = base::take(_migratingGroupCall)) { + destroyGroupCall(was.get()); + } + _migratingGroupCall = std::move(call); + } +} + +void Instance::migratedConferenceReady( + not_null call, + StartConferenceInfo args) { + if (_migratingGroupCall.get() != call) { + return; + } + const auto migrationInfo = _currentCallPanel + ? _currentCallPanel->migrationInfo() + : ConferencePanelMigration(); _currentGroupCallPanel = std::make_unique( - raw, + call, migrationInfo); - _currentGroupCall = std::move(call); - _currentGroupCallChanges.fire_copy(raw); + _currentGroupCall = std::move(_migratingGroupCall); + _currentGroupCallChanges.fire_copy(call); + const auto real = call->conferenceCall().get(); + const auto link = real->conferenceInviteLink(); + const auto slug = Group::ExtractConferenceSlug(link); if (!args.invite.empty()) { _currentGroupCallPanel->migrationInviteUsers(std::move(args.invite)); - } else if (args.sharingLink && !args.linkSlug.isEmpty()) { + } else if (args.sharingLink) { _currentGroupCallPanel->migrationShowShareLink(); } - - if (args.migrating) { - destroyCurrentCall(args.call.get(), args.linkSlug); - } + destroyCurrentCall(real, slug); } void Instance::confirmLeaveCurrent( @@ -428,6 +461,8 @@ void Instance::destroyGroupCall(not_null call) { LOG(("Calls::Instance doesn't prevent quit any more.")); } Core::App().quitPreventFinished(); + } else if (_migratingGroupCall.get() == call) { + base::take(_migratingGroupCall); } } @@ -658,12 +693,14 @@ void Instance::handleCallUpdate( void Instance::handleGroupCallUpdate( not_null session, const MTPUpdate &update) { - if (_currentGroupCall - && (&_currentGroupCall->peer()->session() == session)) { + const auto groupCall = _currentGroupCall + ? _currentGroupCall.get() + : _migratingGroupCall.get(); + if (groupCall && (&groupCall->peer()->session() == session)) { update.match([&](const MTPDupdateGroupCall &data) { - _currentGroupCall->handlePossibleCreateOrJoinResponse(data); + groupCall->handlePossibleCreateOrJoinResponse(data); }, [&](const MTPDupdateGroupCallConnection &data) { - _currentGroupCall->handlePossibleCreateOrJoinResponse(data); + groupCall->handlePossibleCreateOrJoinResponse(data); }, [](const auto &) { }); } @@ -692,10 +729,8 @@ void Instance::handleGroupCallUpdate( }); if (update.type() == mtpc_updateGroupCallChainBlocks) { const auto existing = session->data().groupCall(callId); - if (existing - && _currentGroupCall - && _currentGroupCall->lookupReal() == existing) { - _currentGroupCall->handleUpdate(update); + if (existing && groupCall && groupCall->lookupReal() == existing) { + groupCall->handleUpdate(update); } } else if (const auto existing = session->data().groupCall(callId)) { existing->enqueueUpdate(update); @@ -707,9 +742,11 @@ void Instance::handleGroupCallUpdate( void Instance::applyGroupCallUpdateChecked( not_null session, const MTPUpdate &update) { - if (_currentGroupCall - && (&_currentGroupCall->peer()->session() == session)) { - _currentGroupCall->handleUpdate(update); + const auto groupCall = _currentGroupCall + ? _currentGroupCall.get() + : _migratingGroupCall.get(); + if (groupCall && (&groupCall->peer()->session() == session)) { + groupCall->handleUpdate(update); } } @@ -761,6 +798,7 @@ void Instance::destroyCurrentCall( } } } + base::take(_migratingGroupCall); } bool Instance::hasVisiblePanel(Main::Session *session) const { diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 1157c777a0..e42bf32af4 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -86,6 +86,9 @@ public: not_null peer, StartGroupCallArgs args); void startOrJoinConferenceCall(StartConferenceInfo args); + void migratedConferenceReady( + not_null call, + StartConferenceInfo args); void showStartWithRtmp( std::shared_ptr show, not_null peer); @@ -200,6 +203,7 @@ private: std::unique_ptr _currentCallPanel; std::unique_ptr _currentGroupCall; + std::unique_ptr _migratingGroupCall; rpl::event_stream _currentGroupCallChanges; std::unique_ptr _currentGroupCallPanel; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 7abf76b029..7f35109fc1 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_invite_controller.h" #include "calls/ui/calls_device_menu.h" #include "calls/calls_emoji_fingerprint.h" +#include "calls/calls_instance.h" #include "calls/calls_signal_bars.h" #include "calls/calls_userpic.h" #include "calls/calls_video_bubble.h" @@ -359,20 +360,16 @@ void Panel::initControls() { } *creating = true; const auto sharingLink = users.empty(); - Group::MakeConferenceCall({ + Core::App().calls().startOrJoinConferenceCall({ .show = sessionShow(), - .finished = [=](bool) { *creating = false; }, - .joining = true, - .info = { - .invite = std::move(users), - .sharingLink = sharingLink, - .migrating = true, - .muted = call->muted(), - .videoCapture = (call->isSharingVideo() - ? call->peekVideoCapture() - : nullptr), - .videoCaptureScreenId = call->screenSharingDeviceId(), - }, + .invite = std::move(users), + .sharingLink = sharingLink, + .migrating = true, + .muted = call->muted(), + .videoCapture = (call->isSharingVideo() + ? call->peekVideoCapture() + : nullptr), + .videoCaptureScreenId = call->screenSharingDeviceId(), }); }; const auto invite = crl::guard(call, [=]( diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 5f27e80ca5..56d690b771 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_common.h" +#include "calls/calls_instance.h" +#include "main/session/session_show.h" #include "main/main_app_config.h" #include "main/main_session.h" #include "api/api_send_progress.h" @@ -578,9 +580,11 @@ GroupCall::GroupCall( not_null delegate, StartConferenceInfo info) : GroupCall(delegate, Group::JoinInfo{ - .peer = info.call->peer(), - .joinAs = info.call->peer(), -}, info, info.call->input()) { + .peer = info.call ? info.call->peer() : info.show->session().user(), + .joinAs = info.call ? info.call->peer() : info.show->session().user(), +}, info, info.call + ? info.call->input() + : MTP_inputGroupCall(MTP_long(0), MTP_long(0))) { } GroupCall::GroupCall( @@ -590,7 +594,6 @@ GroupCall::GroupCall( const MTPInputGroupCall &inputCall) : _delegate(delegate) , _conferenceCall(std::move(conference.call)) -, _e2e(std::move(conference.e2e)) , _peer(join.peer) , _history(_peer->owner().history(_peer)) , _api(&_peer->session().mtp()) @@ -602,7 +605,6 @@ GroupCall::GroupCall( , _rtmpUrl(join.rtmpInfo.url) , _rtmpKey(join.rtmpInfo.key) , _canManage(Data::CanManageGroupCallValue(_peer)) -, _id(inputCall.c_inputGroupCall().vid().v) , _scheduleDate(join.scheduleDate) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) @@ -627,6 +629,8 @@ GroupCall::GroupCall( , _listenersHidden(join.rtmp) , _rtmp(join.rtmp) , _rtmpVolume(Group::kDefaultVolume) { + applyInputCall(inputCall); + _muted.value( ) | rpl::combine_previous( ) | rpl::start_with_next([=](MuteState previous, MuteState state) { @@ -658,7 +662,7 @@ GroupCall::GroupCall( if (!canManage() && real->joinMuted()) { _muted = MuteState::ForceMuted; } - } else { + } else if (!conference.migrating) { _peer->session().changes().peerFlagsValue( _peer, Data::PeerUpdate::Flag::GroupCall @@ -678,19 +682,19 @@ GroupCall::GroupCall( setupMediaDevices(); setupOutgoingVideo(); - if (_conferenceCall) { - setupConferenceCall(); - if (conference.migrating) { - if (!conference.muted) { - setMuted(MuteState::Active); - } - _migratedConferenceInfo = std::make_shared( - std::move(conference)); + if (_conferenceCall || conference.migrating) { + setupConference(); + } + if (conference.migrating) { + if (!conference.muted) { + setMuted(MuteState::Active); } + _migratedConferenceInfo = std::make_shared( + std::move(conference)); } - if (_id) { - this->join(inputCall); + if (_id || (!_conferenceCall && _migratedConferenceInfo)) { + initialJoin(); } else { start(join.scheduleDate, join.rtmp); } @@ -703,6 +707,7 @@ void GroupCall::processMigration(StartConferenceInfo conference) { if (!conference.videoCapture) { return; } + fillActiveVideoEndpoints(); const auto weak = base::make_weak(this); if (!conference.videoCaptureScreenId.isEmpty()) { _screenCapture = std::move(conference.videoCapture); @@ -741,7 +746,7 @@ GroupCall::~GroupCall() { } } -void GroupCall::setupConferenceCall() { +void GroupCall::setupConference() { if (!_e2e) { _e2e = std::make_shared( TdE2E::MakeUserId(_peer->session().user())); @@ -755,10 +760,24 @@ void GroupCall::setupConferenceCall() { sendOutboundBlock(std::move(block)); }, _lifetime); + _e2e->failures() | rpl::start_with_next([=] { + LOG(("TdE2E: Got failure!")); + hangup(); + }, _lifetime); + + if (_conferenceCall) { + setupConferenceCall(); + } +} + +void GroupCall::setupConferenceCall() { + Expects(_conferenceCall != nullptr && _e2e != nullptr); + _conferenceCall->staleParticipantIds( ) | rpl::start_with_next([=](const base::flat_set &staleIds) { removeConferenceParticipants(staleIds, true); }, _lifetime); + _e2e->participantsSetValue( ) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) { auto users = base::flat_set(); @@ -768,11 +787,6 @@ void GroupCall::setupConferenceCall() { } _conferenceCall->setParticipantsWithAccess(std::move(users)); }, _lifetime); - - _e2e->failures() | rpl::start_with_next([=] { - LOG(("TdE2E: Got failure!")); - hangup(); - }, _lifetime); } void GroupCall::removeConferenceParticipants( @@ -917,7 +931,7 @@ void GroupCall::setScheduledDate(TimeId date) { const auto was = _scheduleDate; _scheduleDate = date; if (was && !date) { - join(inputCall()); + initialJoin(); } } @@ -1171,7 +1185,7 @@ bool GroupCall::rtmp() const { } bool GroupCall::conference() const { - return _conferenceCall != nullptr; + return _conferenceCall || _migratedConferenceInfo; } bool GroupCall::listenersHidden() const { @@ -1234,31 +1248,40 @@ void GroupCall::start(TimeId scheduleDate, bool rtmp) { MTPstring(), // title MTP_int(scheduleDate) )).done([=](const MTPUpdates &result) { + _createRequestId = 0; _reloadedStaleCall = true; _acceptFields = true; _peer->session().api().applyUpdates(result); _acceptFields = false; }).fail([=](const MTP::Error &error) { + _createRequestId = 0; LOG(("Call Error: Could not create, error: %1" ).arg(error.type())); hangup(); }).send(); } -void GroupCall::join(const MTPInputGroupCall &inputCall) { +void GroupCall::applyInputCall(const MTPInputGroupCall &inputCall) { inputCall.match([&](const MTPDinputGroupCall &data) { _id = data.vid().v; _accessHash = data.vaccess_hash().v; }, [&](const auto &) { Unexpected("slug/msg in GroupCall::join."); }); - setState(_scheduleDate ? State::Waiting : State::Joining); +} +void GroupCall::initialJoin() { + setState(_scheduleDate ? State::Waiting : State::Joining); if (_scheduleDate) { return; } rejoin(); + if (_id) { + initialJoinRequested(); + } +} +void GroupCall::initialJoinRequested() { using Update = Data::GroupCall::ParticipantUpdate; const auto real = lookupReal(); Assert(real != nullptr); @@ -1283,10 +1306,10 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { _peer->session().updates().addActiveChat( _peerStream.events_starting_with_copy(_peer)); _canManage = Data::CanManageGroupCallValue(_peer); - SubscribeToMigration(_peer, _lifetime, [=](not_null group) { - _peer = group; + SubscribeToMigration(_peer, _lifetime, [=](not_null peer) { + _peer = peer; _canManage = Data::CanManageGroupCallValue(_peer); - _peerStream.fire_copy(group); + _peerStream.fire_copy(peer); }); } @@ -1499,7 +1522,7 @@ void GroupCall::rejoin(not_null as) { && state() != State::Joined && state() != State::Connecting) { return; - } else if (_joinState.action != JoinAction::None) { + } else if (_joinState.action != JoinAction::None || _createRequestId) { return; } @@ -1529,7 +1552,11 @@ void GroupCall::rejoin(not_null as) { }; LOG(("Call Info: Join payload received, joining with ssrc: %1." ).arg(_joinState.payload.ssrc)); - sendJoinRequest(); + if (!_conferenceCall && _migratedConferenceInfo) { + startConference(); + } else { + sendJoinRequest(); + } }); }); } @@ -1540,7 +1567,7 @@ void GroupCall::sendJoinRequest() { checkNextJoinAction(); return; } - auto joinBlock = _e2e ? _e2e->makeJoinBlock().data : QByteArray(); + const auto joinBlock = _e2e ? _e2e->makeJoinBlock().data : QByteArray(); if (_e2e && joinBlock.isEmpty()) { _joinState.finish(); LOG(("Call Error: Could not generate join block.")); @@ -1554,12 +1581,8 @@ void GroupCall::sendJoinRequest() { const auto flags = (wasMuteState != MuteState::Active ? Flag::f_muted : Flag(0)) - | (_joinHash.isEmpty() - ? Flag(0) - : Flag::f_invite_hash) - | (wasVideoStopped - ? Flag::f_video_stopped - : Flag(0)) + | (_joinHash.isEmpty() ? Flag(0) : Flag::f_invite_hash) + | (wasVideoStopped ? Flag::f_video_stopped : Flag(0)) | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); _api.request(MTPphone_JoinGroupCall( MTP_flags(flags), @@ -1570,74 +1593,15 @@ void GroupCall::sendJoinRequest() { MTP_bytes(joinBlock), MTP_dataJSON(MTP_bytes(_joinState.payload.json)) )).done([=]( - const MTPUpdates &updates, + const MTPUpdates &result, const MTP::Response &response) { - _serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId); - _serverTimeMsGotAt = crl::now(); - - _joinState.finish(_joinState.payload.ssrc); - _mySsrcs.emplace(_joinState.ssrc); - - setState((_instanceState.current() - == InstanceState::Disconnected) - ? State::Connecting - : State::Joined); - applyMeInCallLocally(); - maybeSendMutedUpdate(wasMuteState); - _peer->session().api().applyUpdates(updates); - applyQueuedSelfUpdates(); - checkFirstTimeJoined(); - _screenJoinState.nextActionPending = true; - checkNextJoinAction(); - if (wasVideoStopped == isSharingCamera()) { - sendSelfUpdate(SendUpdateType::CameraStopped); - } - if (isCameraPaused()) { - sendSelfUpdate(SendUpdateType::CameraPaused); - } - sendPendingSelfUpdates(); - if (!_reloadedStaleCall - && _state.current() != State::Joining) { - if (const auto real = lookupReal()) { - _reloadedStaleCall = true; - real->reloadIfStale(); - } - } - if (_e2e) { - _e2e->joined(); - if (!_pendingOutboundBlock.isEmpty()) { - sendOutboundBlock(base::take(_pendingOutboundBlock)); - } - } - if (const auto once = base::take(_migratedConferenceInfo)) { - processMigration(*once); - } - for (const auto &callback : base::take(_rejoinedCallbacks)) { - callback(); - } + joinDone( + TimestampInMsFromMsgId(response.outerMsgId), + result, + wasMuteState, + wasVideoStopped); }).fail([=](const MTP::Error &error) { - const auto type = error.type(); - if (_e2e) { - if (type == u"BLOCK_INVALID"_q - || type.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) { - refreshLastBlockAndJoin(); - return; - } - } - _joinState.finish(); - - LOG(("Call Error: Could not join, error: %1").arg(type)); - - if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { - rejoin(); - return; - } - - hangup(); - Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q - || type == u"GROUPCALL_INVALID"_q) - ? tr::lng_confcall_not_accessible(tr::now) - : type); + joinFail(error.type()); }).send(); } @@ -1685,6 +1649,144 @@ void GroupCall::refreshLastBlockAndJoin() { }).send(); } +void GroupCall::startConference() { + Expects(_e2e != nullptr && _migratedConferenceInfo != nullptr); + + const auto joinBlock = _e2e->makeJoinBlock().data; + Assert(!joinBlock.isEmpty()); + + const auto wasMuteState = muted(); + const auto wasVideoStopped = !isSharingCamera(); + using Flag = MTPphone_CreateConferenceCall::Flag; + const auto flags = Flag::f_join + | Flag::f_public_key + | Flag::f_block + | Flag::f_params + | ((wasMuteState != MuteState::Active) ? Flag::f_muted : Flag(0)) + | (wasVideoStopped ? Flag::f_video_stopped : Flag(0)); + _createRequestId = _api.request(MTPphone_CreateConferenceCall( + MTP_flags(flags), + MTP_int(base::RandomValue()), + TdE2E::PublicKeyToMTP(_e2e->myKey()), + MTP_bytes(joinBlock), + MTP_dataJSON(MTP_bytes(_joinState.payload.json)) + )).done([=]( + const MTPUpdates &result, + const MTP::Response &response) { + _createRequestId = 0; + _conferenceCall = _peer->owner().sharedConferenceCallFind(result); + if (!_conferenceCall) { + joinFail(u"Call not found!"_q); + return; + } + applyInputCall(_conferenceCall->input()); + initialJoinRequested(); + joinDone( + TimestampInMsFromMsgId(response.outerMsgId), + result, + wasMuteState, + wasVideoStopped, + true); + }).fail([=](const MTP::Error &error) { + _createRequestId = 0; + LOG(("Call Error: Could not create, error: %1" + ).arg(error.type())); + hangup(); + }).send(); +} + +void GroupCall::joinDone( + int64 serverTimeMs, + const MTPUpdates &result, + MuteState wasMuteState, + bool wasVideoStopped, + bool justCreated) { + Expects(!justCreated || _migratedConferenceInfo != nullptr); + + _serverTimeMs = serverTimeMs; + _serverTimeMsGotAt = crl::now(); + + _joinState.finish(_joinState.payload.ssrc); + _mySsrcs.emplace(_joinState.ssrc); + + setState((_instanceState.current() + == InstanceState::Disconnected) + ? State::Connecting + : State::Joined); + applyMeInCallLocally(); + maybeSendMutedUpdate(wasMuteState); + + _peer->session().api().applyUpdates(result); + + if (justCreated) { + subscribeToReal(_conferenceCall.get()); + setupConferenceCall(); + _conferenceLinkSlug = Group::ExtractConferenceSlug( + _conferenceCall->conferenceInviteLink()); + Core::App().calls().migratedConferenceReady( + this, + *_migratedConferenceInfo); + } + + applyQueuedSelfUpdates(); + checkFirstTimeJoined(); + _screenJoinState.nextActionPending = true; + checkNextJoinAction(); + if (wasVideoStopped == isSharingCamera()) { + sendSelfUpdate(SendUpdateType::CameraStopped); + } + if (isCameraPaused()) { + sendSelfUpdate(SendUpdateType::CameraPaused); + } + sendPendingSelfUpdates(); + if (!_reloadedStaleCall + && _state.current() != State::Joining) { + if (const auto real = lookupReal()) { + _reloadedStaleCall = true; + real->reloadIfStale(); + } + } + if (_e2e) { + _e2e->joined(); + if (!_pendingOutboundBlock.isEmpty()) { + sendOutboundBlock(base::take(_pendingOutboundBlock)); + } + } + if (const auto once = base::take(_migratedConferenceInfo)) { + processMigration(*once); + } + for (const auto &callback : base::take(_rejoinedCallbacks)) { + callback(); + } +} + +void GroupCall::joinFail(const QString &error) { + if (_e2e) { + if (error == u"BLOCK_INVALID"_q + || error.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) { + if (_id) { + refreshLastBlockAndJoin(); + } else { + hangup(); + } + return; + } + } + _joinState.finish(); + LOG(("Call Error: Could not join, error: %1").arg(error)); + + if (_id && error == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { + rejoin(); + return; + } + + hangup(); + Ui::Toast::Show((error == u"GROUPCALL_FORBIDDEN"_q + || error == u"GROUPCALL_INVALID"_q) + ? tr::lng_confcall_not_accessible(tr::now) + : error); +} + void GroupCall::requestSubchainBlocks(int subchain, int height) { Expects(subchain >= 0 && subchain < kSubChainsCount); @@ -2168,7 +2270,8 @@ void GroupCall::handlePossibleCreateOrJoinResponse( } else { Unexpected("Peer type in GroupCall::join."); } - join(input); + applyInputCall(input); + initialJoin(); } return; } else if (_id != data.vid().v || !_instance) { @@ -3768,7 +3871,6 @@ void GroupCall::editParticipant( }).send(); } - void GroupCall::inviteToConference( InviteRequest request, Fn()> resultAddress, diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 7b29f357c3..236290a189 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -263,12 +263,15 @@ public: [[nodiscard]] rpl::producer> real() const; [[nodiscard]] rpl::producer emojiHashValue() const; + void applyInputCall(const MTPInputGroupCall &inputCall); + void startConference(); void start(TimeId scheduleDate, bool rtmp); void hangup(); void discard(); void rejoinAs(Group::JoinInfo info); void rejoinWithHash(const QString &hash); - void join(const MTPInputGroupCall &inputCall); + void initialJoin(); + void initialJoinRequested(); void handleUpdate(const MTPUpdate &update); void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data); void handlePossibleCreateOrJoinResponse( @@ -292,6 +295,14 @@ public: bool emitShareScreenError(); bool emitShareCameraError(); + void joinDone( + int64 serverTimeMs, + const MTPUpdates &result, + MuteState wasMuteState, + bool wasVideoStopped, + bool justCreated = false); + void joinFail(const QString &error); + [[nodiscard]] rpl::producer errors() const { return _errors.events(); } @@ -610,6 +621,7 @@ private: void setupMediaDevices(); void setupOutgoingVideo(); + void setupConference(); void setupConferenceCall(); void setScreenEndpoint(std::string endpoint); void setCameraEndpoint(std::string endpoint); @@ -635,7 +647,7 @@ private: [[nodiscard]] MTPInputGroupCall inputCallSafe() const; const not_null _delegate; - const std::shared_ptr _conferenceCall; + std::shared_ptr _conferenceCall; std::shared_ptr _e2e; QByteArray _pendingOutboundBlock; std::shared_ptr _migratedConferenceInfo; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index a1db17f0a7..1b98ad2306 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_group_call.h" #include "data/data_session.h" #include "info/bot/starref/info_bot_starref_common.h" +#include "tde2e/tde2e_api.h" +#include "tde2e/tde2e_integration.h" #include "ui/boxes/boost_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" @@ -41,24 +43,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Calls::Group { -namespace { - -[[nodiscard]] QString ExtractConferenceSlug(const QString &link) { - const auto local = Core::TryConvertUrlToLocal(link); - const auto parts1 = QStringView(local).split('#'); - if (!parts1.isEmpty()) { - const auto parts2 = parts1.front().split('&'); - if (!parts2.isEmpty()) { - const auto parts3 = parts2.front().split(u"slug="_q); - if (parts3.size() > 1) { - return parts3.back().toString(); - } - } - } - return QString(); -} - -} // namespace object_ptr ScreenSharingPrivacyRequestBox() { #ifdef Q_OS_MAC @@ -468,32 +452,13 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) { MTPbytes(), // block MTPDataJSON() // params )).done([=](const MTPUpdates &result) { - session->api().applyUpdates(result); - const auto updates = result.match([&](const MTPDupdates &data) { - return &data.vupdates().v; - }, [&](const MTPDupdatesCombined &data) { - return &data.vupdates().v; - }, [](const auto &) { - return (const QVector*)nullptr; - }); - if (!updates) { + auto call = session->data().sharedConferenceCallFind(result); + if (!call) { fail(u"Call not found!"_q); return; } - auto call = std::shared_ptr(); - for (const auto &update : *updates) { - update.match([&](const MTPDupdateGroupCall &data) { - data.vcall().match([&](const auto &data) { - call = session->data().sharedConferenceCall( - data.vid().v, - data.vaccess_hash().v); - call->enqueueUpdate(update); - }); - }, [](const auto &) {}); - if (call) { - break; - } - } + session->api().applyUpdates(result); + const auto link = call ? call->conferenceInviteLink() : QString(); if (link.isEmpty()) { fail(u"Call link not found!"_q); @@ -521,4 +486,19 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) { }).send(); } +QString ExtractConferenceSlug(const QString &link) { + const auto local = Core::TryConvertUrlToLocal(link); + const auto parts1 = QStringView(local).split('#'); + if (!parts1.isEmpty()) { + const auto parts2 = parts1.front().split('&'); + if (!parts2.isEmpty()) { + const auto parts3 = parts2.front().split(u"slug="_q); + if (parts3.size() > 1) { + return parts3.back().toString(); + } + } + } + return QString(); +} + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 8407791535..0afe320964 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -64,8 +64,8 @@ struct InviteResult { }; struct StartConferenceInfo { + std::shared_ptr show; std::shared_ptr call; - std::shared_ptr e2e; QString linkSlug; MsgId joinMessageId; std::vector invite; @@ -195,4 +195,6 @@ struct ConferenceFactoryArgs { }; void MakeConferenceCall(ConferenceFactoryArgs &&args); +[[nodiscard]] QString ExtractConferenceSlug(const QString &link); + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index b55e375891..fd70aa6e69 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -376,6 +376,8 @@ void Panel::initWindow() { _window->setControlsStyle(st::groupCallTitle); _window->togglePowerSaveBlocker(true); + + uiShow()->hideLayer(anim::type::instant); } void Panel::initWidget() { @@ -927,7 +929,6 @@ Fn Panel::shareConferenceLinkCallback() { } void Panel::migrationShowShareLink() { - uiShow()->hideLayer(anim::type::instant); ShowConferenceCallLinkBox( sessionShow(), _call->conferenceCall(), diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index cb4b06cb32..05ee4bc787 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -374,6 +374,7 @@ auto GroupCall::staleParticipantIds() const } void GroupCall::enqueueUpdate(const MTPUpdate &update) { + const auto initial = !_version; update.match([&](const MTPDupdateGroupCall &updateData) { updateData.vcall().match([&](const MTPDgroupCall &data) { const auto version = data.vversion().v; @@ -427,7 +428,7 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) { }, [](const auto &) { Unexpected("Type in GroupCall::enqueueUpdate."); }); - processQueuedUpdates(); + processQueuedUpdates(initial); } void GroupCall::discard(const MTPDgroupCallDiscarded &data) { @@ -562,7 +563,7 @@ void GroupCall::applyEnqueuedUpdate(const MTPUpdate &update) { Core::App().calls().applyGroupCallUpdateChecked(&session(), update); } -void GroupCall::processQueuedUpdates() { +void GroupCall::processQueuedUpdates(bool initial) { if (!_version || _applyingQueuedUpdates) { return; } @@ -574,7 +575,13 @@ void GroupCall::processQueuedUpdates() { const auto type = entry.first.second; const auto incremented = (type == QueuedType::VersionedParticipant); if ((version < _version) - || (version == _version && incremented)) { + || (version == _version && incremented && !initial)) { + // There is a case for a new conference call we receive: + // - updateGroupCall, version = 2 + // - updateGroupCallParticipants, version = 2, versioned + // In case we were joining together with creation, + // in that case we don't want to skip the participants update, + // so we pass the `initial` flag specifically for that case. _queuedUpdates.erase(_queuedUpdates.begin()); } else if (version == _version || (version == _version + 1 && incremented)) { diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index f335e84680..20c5b08f8a 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -215,7 +215,7 @@ private: void applyEnqueuedUpdate(const MTPUpdate &update); void setServerParticipantsCount(int count); void computeParticipantsCount(); - void processQueuedUpdates(); + void processQueuedUpdates(bool initial = false); void processFullCallUsersChats(const MTPphone_GroupCall &call); void processFullCallFields(const MTPphone_GroupCall &call); [[nodiscard]] bool requestParticipantsAfterReload( diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 6bc8eaca38..78974d85e3 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1159,6 +1159,36 @@ std::shared_ptr Session::sharedConferenceCall( return result; } +std::shared_ptr Session::sharedConferenceCallFind( + const MTPUpdates &response) { + const auto list = response.match([&](const MTPDupdates &data) { + return &data.vupdates().v; + }, [&](const MTPDupdatesCombined &data) { + return &data.vupdates().v; + }, [](const auto &) { + return (const QVector*)nullptr; + }); + const auto empty = std::shared_ptr(); + if (!list) { + return empty; + } + for (const auto &update : *list) { + const auto call = update.match([&](const MTPDupdateGroupCall &data) { + return data.vcall().match([&](const MTPDgroupCall &data) { + return data.is_conference() + ? sharedConferenceCall( + data.vid().v, + data.vaccess_hash().v) + : nullptr; + }, [&](const auto &) { return empty; }); + }, [&](const auto &) { return empty; }); + if (call) { + return call; + } + } + return empty; +} + void Session::watchForOffline(not_null user, TimeId now) { if (!now) { now = base::unixtime::now(); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index bb810a7e37..d46290d775 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -235,6 +235,8 @@ public: [[nodiscard]] std::shared_ptr sharedConferenceCall( CallId id, uint64 accessHash); + [[nodiscard]] std::shared_ptr sharedConferenceCallFind( + const MTPUpdates &response); void watchForOffline(not_null user, TimeId now = 0); void maybeStopWatchForOffline(not_null user); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index d35e50f0b4..9ca1596b89 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -882,7 +882,7 @@ void SessionNavigation::resolveConferenceCall( data.vaccess_hash().v); call->processFullCall(result); const auto join = [=](Fn close) { - const auto &appConfig = call->peer()->session().appConfig(); + const auto &appConfig = call->session().appConfig(); const auto conferenceLimit = appConfig.confcallSizeLimit(); if (call->fullCount() >= conferenceLimit) { showToast(tr::lng_confcall_participants_limit(tr::now)); From 042f51e58fa6bbf89eef9cf041811ed6827b7630 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 10 Apr 2025 09:33:04 +0400 Subject: [PATCH 126/190] Optimize confcall join process. --- Telegram/SourceFiles/calls/group/calls_group_call.cpp | 5 +++++ Telegram/SourceFiles/data/data_group_call.cpp | 4 ++++ Telegram/SourceFiles/data/data_group_call.h | 1 + Telegram/SourceFiles/tde2e/tde2e_api.cpp | 4 ++++ Telegram/SourceFiles/tde2e/tde2e_api.h | 1 + Telegram/ThirdParty/tgcalls | 2 +- 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 56d690b771..68bf1aa85e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1554,6 +1554,11 @@ void GroupCall::rejoin(not_null as) { ).arg(_joinState.payload.ssrc)); if (!_conferenceCall && _migratedConferenceInfo) { startConference(); + } else if (_conferenceCall + && !_conferenceCall->blockchainMayBeEmpty() + && !_e2e->hasLastBlock0()) { + //sendJoinRequest(); AssertIsDebug(); + refreshLastBlockAndJoin(); } else { sendJoinRequest(); } diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 05ee4bc787..f17e64aeb7 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -135,6 +135,10 @@ bool GroupCall::listenersHidden() const { return _listenersHidden; } +bool GroupCall::blockchainMayBeEmpty() const { + return _version < 2; +} + not_null GroupCall::peer() const { return _peer; } diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 20c5b08f8a..ef2cf38b7b 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -78,6 +78,7 @@ public: [[nodiscard]] bool rtmp() const; [[nodiscard]] bool canManage() const; [[nodiscard]] bool listenersHidden() const; + [[nodiscard]] bool blockchainMayBeEmpty() const; [[nodiscard]] not_null peer() const; [[nodiscard]] MTPInputGroupCall input() const; [[nodiscard]] QString title() const { diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 8827677618..e0a28936db 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -102,6 +102,10 @@ PublicKey Call::myKey() const { return _myKey; } +bool Call::hasLastBlock0() const { + return _lastBlock0.has_value(); +} + void Call::refreshLastBlock0(std::optional block) { _lastBlock0 = std::move(block); } diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 53d8cdb61f..1af99b309f 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -95,6 +95,7 @@ public: [[nodiscard]] QByteArray emojiHash() const; [[nodiscard]] rpl::producer emojiHashValue() const; + [[nodiscard]] bool hasLastBlock0() const; void refreshLastBlock0(std::optional block); [[nodiscard]] Block makeJoinBlock(); [[nodiscard]] Block makeRemoveBlock(const base::flat_set &ids); diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index fd1cfbd815..7f04d5360f 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit fd1cfbd8151b2c32d5471a4f5431faa6274ce421 +Subproject commit 7f04d5360f3208d032dffdf634c8ff9c508da2f1 From c6b2967da0ff05ad2f24e4386a318296101316b9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 10 Apr 2025 13:38:13 +0400 Subject: [PATCH 127/190] Implement "Create New Call" interface. --- Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/calls/calls.style | 55 ++++-- .../calls/calls_box_controller.cpp | 151 +++++++++++++++- .../SourceFiles/calls/calls_box_controller.h | 2 + Telegram/SourceFiles/calls/calls_instance.cpp | 24 +-- Telegram/SourceFiles/calls/calls_instance.h | 4 +- .../calls/group/calls_group_call.cpp | 28 +-- .../calls/group/calls_group_call.h | 4 +- .../calls/group/calls_group_common.cpp | 20 +-- .../group/calls_group_invite_controller.cpp | 165 ++++++++++++++++-- .../group/calls_group_invite_controller.h | 4 + .../SourceFiles/window/window_main_menu.cpp | 141 +-------------- 12 files changed, 375 insertions(+), 227 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 22ee39de43..f676ad892b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4932,6 +4932,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_already_joined_many#one" = "{user}, {other} and **{count}** other person already joined this call."; "lng_confcall_already_joined_many#other" = "{user}, {other} and **{count}** other people already joined this call."; "lng_confcall_join_button" = "Join Group Call"; +"lng_confcall_create_call" = "Create New Call"; +"lng_confcall_create_call_description#one" = "You can add up to {count} participant to a call."; +"lng_confcall_create_call_description#other" = "You can add up to {count} participants to a call."; +"lng_confcall_create_title" = "New Call"; "lng_confcall_create_link" = "Create Call Link"; "lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; "lng_confcall_link_revoke" = "Revoke link"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index b3b6595030..f7b2e78eb7 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1505,6 +1505,42 @@ groupCallCalendarColors: CalendarColors { titleTextColor: groupCallMembersFg; } +createCallInviteLink: SettingsButton(defaultSettingsButton) { + textFg: windowActiveTextFg; + textFgOver: windowActiveTextFg; + textBg: windowBg; + textBgOver: windowBgOver; + + style: TextStyle(defaultTextStyle) { + font: font(14px semibold); + } + + height: 20px; + padding: margins(74px, 8px, 8px, 9px); +} +createCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", windowActiveTextFg }}; +createCallInviteLinkIconPosition: point(23px, 2px); +createCallVideo: IconButton { + width: 36px; + height: 52px; + + icon: icon {{ "info/info_media_video", menuIconFg }}; + iconOver: icon {{ "info/info_media_video", menuIconFgOver }}; + iconPosition: point(-1px, -1px); + + ripple: defaultRippleAnimation; + rippleAreaPosition: point(0px, 8px); + rippleAreaSize: 36px; +} +createCallVideoActive: icon {{ "info/info_media_video", windowActiveTextFg }}; +createCallVideoMargins: margins(0px, 0px, 10px, 0px); +createCallAudio: IconButton(createCallVideo) { + icon: icon {{ "menu/phone", menuIconFg }}; + iconOver: icon {{ "menu/phone", menuIconFgOver }}; +} +createCallAudioActive: icon {{ "menu/phone", windowActiveTextFg }}; +createCallAudioMargins: margins(0px, 0px, 4px, 0px); + confcallLinkButton: RoundButton(defaultActiveButton) { height: 42px; textTop: 12px; @@ -1572,26 +1608,17 @@ confcallInviteParticipants: FlatLabel(defaultFlatLabel) { textFg: callNameFg; } confcallInviteParticipantsPadding: margins(8px, 3px, 12px, 2px); -confcallInviteVideo: IconButton { - width: 36px; - height: 52px; - +confcallInviteVideo: IconButton(createCallVideo) { icon: icon {{ "info/info_media_video", groupCallMemberInactiveIcon }}; iconOver: icon {{ "info/info_media_video", groupCallMemberInactiveIcon }}; - iconPosition: point(-1px, -1px); - ripple: groupCallRipple; - rippleAreaPosition: point(0px, 8px); - rippleAreaSize: 36px; } confcallInviteVideoActive: icon {{ "info/info_media_video", groupCallActiveFg }}; -confcallInviteVideoMargins: margins(0px, 0px, 10px, 0px); confcallInviteAudio: IconButton(confcallInviteVideo) { icon: icon {{ "menu/phone", groupCallMemberInactiveIcon }}; iconOver: icon {{ "menu/phone", groupCallMemberInactiveIcon }}; } confcallInviteAudioActive: icon {{ "menu/phone", groupCallActiveFg }}; -confcallInviteAudioMargins: margins(0px, 0px, 4px, 0px); groupCallLinkBox: Box(confcallLinkBox) { bg: groupCallMembersBg; @@ -1610,23 +1637,17 @@ groupCallLinkPreview: InputField(defaultInputField) { style: defaultTextStyle; heightMin: 35px; } -groupCallInviteLink: SettingsButton(defaultSettingsButton) { +groupCallInviteLink: SettingsButton(createCallInviteLink) { textFg: mediaviewTextLinkFg; textFgOver: mediaviewTextLinkFg; textBg: groupCallMembersBg; textBgOver: groupCallMembersBgOver; - style: TextStyle(defaultTextStyle) { - font: font(14px semibold); - } - - height: 20px; padding: margins(63px, 8px, 8px, 9px); ripple: groupCallRipple; } groupCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", mediaviewTextLinkFg }}; -groupCallInviteLinkIconPosition: point(23px, 2px); confcallLinkMenu: IconButton(boxTitleClose) { icon: icon {{ "title_menu_dots", boxTitleCloseFg }}; diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 1f3337569d..372aa37c53 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -9,17 +9,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/menu/menu_add_action_callback.h" +#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/labels.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/popup_menu.h" +#include "ui/wrap/slide_wrap.h" #include "ui/painter.h" +#include "ui/vertical_list.h" #include "core/application.h" +#include "calls/group/calls_group_common.h" +#include "calls/group/calls_group_invite_controller.h" #include "calls/calls_instance.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_helpers.h" #include "mainwidget.h" #include "window/window_session_controller.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "data/data_session.h" #include "data/data_changes.h" @@ -32,6 +41,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "api/api_updates.h" #include "apiwrap.h" +#include "info/profile/info_profile_icon.h" +#include "settings/settings_calls.h" +#include "styles/style_info.h" // infoTopBarMenu #include "styles/style_layers.h" // st::boxLabel. #include "styles/style_calls.h" #include "styles/style_boxes.h" @@ -150,7 +162,7 @@ void GroupCallRow::rightActionStopLastRipple() { namespace GroupCalls { -ListController::ListController(not_null window) +ListController::ListController(not_null<::Window::SessionController*> window) : _window(window) { setStyleOverrides(&st::peerListSingleRow); } @@ -227,7 +239,7 @@ void ListController::rowClicked(not_null row) { crl::on_main(window, [=, peer = row->peer()] { window->showPeerHistory( peer, - Window::SectionShow::Way::ClearStack); + ::Window::SectionShow::Way::ClearStack); }); } @@ -470,7 +482,7 @@ void BoxController::Row::rightActionStopLastRipple() { } } -BoxController::BoxController(not_null window) +BoxController::BoxController(not_null<::Window::SessionController*> window) : _window(window) , _api(&_window->session().mtp()) { } @@ -591,7 +603,7 @@ void BoxController::rowClicked(not_null row) { crl::on_main(window, [=, peer = row->peer()] { window->showPeerHistory( peer, - Window::SectionShow::Way::ClearStack, + ::Window::SectionShow::Way::ClearStack, itemId); }); } @@ -698,7 +710,7 @@ std::unique_ptr BoxController::createRow( void ClearCallsBox( not_null box, - not_null window) { + not_null<::Window::SessionController*> window) { const auto weak = Ui::MakeWeak(box); box->addRow( object_ptr( @@ -756,4 +768,133 @@ void ClearCallsBox( box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } +[[nodiscard]] not_null AddCreateCallButton( + not_null container, + not_null<::Window::SessionController*> controller, + Fn done) { + const auto result = container->add(object_ptr( + container, + tr::lng_confcall_create_call(), + st::inviteViaLinkButton), QMargins()); + Ui::AddSkip(container); + Ui::AddDividerText( + container, + tr::lng_confcall_create_call_description( + lt_count, + rpl::single(controller->session().appConfig().confcallSizeLimit() + * 1.), + Ui::Text::WithEntities)); + + const auto icon = Ui::CreateChild( + result, + st::inviteViaLinkIcon, + QPoint()); + result->heightValue( + ) | rpl::start_with_next([=](int height) { + icon->moveToLeft( + st::inviteViaLinkIconPosition.x(), + (height - st::inviteViaLinkIcon.height()) / 2); + }, icon->lifetime()); + + result->setClickedCallback([=] { + controller->show(Group::PrepareCreateCallBox(controller, done)); + }); + + return result; +} + +void ShowCallsBox(not_null<::Window::SessionController*> window) { + struct State { + State(not_null<::Window::SessionController*> window) + : callsController(window) + , groupCallsController(window) { + } + Calls::BoxController callsController; + PeerListContentDelegateSimple callsDelegate; + + Calls::GroupCalls::ListController groupCallsController; + PeerListContentDelegateSimple groupCallsDelegate; + + base::unique_qptr menu; + }; + + window->show(Box([=](not_null box) { + const auto state = box->lifetime().make_state(window); + + const auto groupCalls = box->addRow( + object_ptr>( + box, + object_ptr(box)), + {}); + groupCalls->hide(anim::type::instant); + groupCalls->toggleOn(state->groupCallsController.shownValue()); + + Ui::AddSubsectionTitle( + groupCalls->entity(), + tr::lng_call_box_groupcalls_subtitle()); + state->groupCallsDelegate.setContent(groupCalls->entity()->add( + object_ptr(box, &state->groupCallsController), + {})); + state->groupCallsController.setDelegate(&state->groupCallsDelegate); + Ui::AddSkip(groupCalls->entity()); + Ui::AddDivider(groupCalls->entity()); + Ui::AddSkip(groupCalls->entity()); + + const auto button = AddCreateCallButton( + box->verticalLayout(), + window, + crl::guard(box, [=] { box->closeBox(); })); + button->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=] { + state->callsDelegate.peerListMouseLeftGeometry(); + }, button->lifetime()); + + const auto content = box->addRow( + object_ptr(box, &state->callsController), + {}); + state->callsDelegate.setContent(content); + state->callsController.setDelegate(&state->callsDelegate); + + box->setWidth(state->callsController.contentWidth()); + state->callsController.boxHeightValue( + ) | rpl::start_with_next([=](int height) { + box->setMinHeight(height); + }, box->lifetime()); + box->setTitle(tr::lng_call_box_title()); + box->addButton(tr::lng_close(), [=] { + box->closeBox(); + }); + const auto menuButton = box->addTopButton(st::infoTopBarMenu); + menuButton->setClickedCallback([=] { + state->menu = base::make_unique_q( + menuButton, + st::popupMenuWithIcons); + const auto showSettings = [=] { + window->showSettings( + Settings::Calls::Id(), + ::Window::SectionShow(anim::type::instant)); + }; + const auto clearAll = crl::guard(box, [=] { + box->uiShow()->showBox(Box(Calls::ClearCallsBox, window)); + }); + state->menu->addAction( + tr::lng_settings_section_call_settings(tr::now), + showSettings, + &st::menuIconSettings); + if (state->callsDelegate.peerListFullRowsCount() > 0) { + Ui::Menu::CreateAddActionCallback(state->menu)({ + .text = tr::lng_call_box_clear_all(tr::now), + .handler = clearAll, + .icon = &st::menuIconDeleteAttention, + .isAttention = true, + }); + } + state->menu->popup(QCursor::pos()); + return true; + }); + })); +} + } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_box_controller.h b/Telegram/SourceFiles/calls/calls_box_controller.h index 05b943b607..9c183f1088 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.h +++ b/Telegram/SourceFiles/calls/calls_box_controller.h @@ -81,4 +81,6 @@ void ClearCallsBox( not_null box, not_null<::Window::SessionController*> window); +void ShowCallsBox(not_null<::Window::SessionController*> window); + } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 145869b109..b3c0cf3c4b 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -238,14 +238,14 @@ void Instance::startOrJoinGroupCall( } void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { - Expects(args.call || (args.migrating && args.show)); + Expects(args.call || args.show); const auto migrationInfo = (args.migrating && args.call && _currentCallPanel) ? _currentCallPanel->migrationInfo() : ConferencePanelMigration(); - if (args.call && !args.migrating) { + if (!args.migrating) { destroyCurrentCall(); } @@ -270,17 +270,17 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { destroyCurrentCall(args.call.get(), args.linkSlug); } } else { - if (const auto was = base::take(_migratingGroupCall)) { + if (const auto was = base::take(_startingGroupCall)) { destroyGroupCall(was.get()); } - _migratingGroupCall = std::move(call); + _startingGroupCall = std::move(call); } } -void Instance::migratedConferenceReady( +void Instance::startedConferenceReady( not_null call, StartConferenceInfo args) { - if (_migratingGroupCall.get() != call) { + if (_startingGroupCall.get() != call) { return; } const auto migrationInfo = _currentCallPanel @@ -289,7 +289,7 @@ void Instance::migratedConferenceReady( _currentGroupCallPanel = std::make_unique( call, migrationInfo); - _currentGroupCall = std::move(_migratingGroupCall); + _currentGroupCall = std::move(_startingGroupCall); _currentGroupCallChanges.fire_copy(call); const auto real = call->conferenceCall().get(); const auto link = real->conferenceInviteLink(); @@ -461,8 +461,8 @@ void Instance::destroyGroupCall(not_null call) { LOG(("Calls::Instance doesn't prevent quit any more.")); } Core::App().quitPreventFinished(); - } else if (_migratingGroupCall.get() == call) { - base::take(_migratingGroupCall); + } else if (_startingGroupCall.get() == call) { + base::take(_startingGroupCall); } } @@ -695,7 +695,7 @@ void Instance::handleGroupCallUpdate( const MTPUpdate &update) { const auto groupCall = _currentGroupCall ? _currentGroupCall.get() - : _migratingGroupCall.get(); + : _startingGroupCall.get(); if (groupCall && (&groupCall->peer()->session() == session)) { update.match([&](const MTPDupdateGroupCall &data) { groupCall->handlePossibleCreateOrJoinResponse(data); @@ -744,7 +744,7 @@ void Instance::applyGroupCallUpdateChecked( const MTPUpdate &update) { const auto groupCall = _currentGroupCall ? _currentGroupCall.get() - : _migratingGroupCall.get(); + : _startingGroupCall.get(); if (groupCall && (&groupCall->peer()->session() == session)) { groupCall->handleUpdate(update); } @@ -798,7 +798,7 @@ void Instance::destroyCurrentCall( } } } - base::take(_migratingGroupCall); + base::take(_startingGroupCall); } bool Instance::hasVisiblePanel(Main::Session *session) const { diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index e42bf32af4..90b0d2f259 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -86,7 +86,7 @@ public: not_null peer, StartGroupCallArgs args); void startOrJoinConferenceCall(StartConferenceInfo args); - void migratedConferenceReady( + void startedConferenceReady( not_null call, StartConferenceInfo args); void showStartWithRtmp( @@ -203,7 +203,7 @@ private: std::unique_ptr _currentCallPanel; std::unique_ptr _currentGroupCall; - std::unique_ptr _migratingGroupCall; + std::unique_ptr _startingGroupCall; rpl::event_stream _currentGroupCallChanges; std::unique_ptr _currentGroupCallPanel; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 68bf1aa85e..19f29d16cc 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -662,7 +662,7 @@ GroupCall::GroupCall( if (!canManage() && real->joinMuted()) { _muted = MuteState::ForceMuted; } - } else if (!conference.migrating) { + } else if (!conference.migrating && !conference.show) { _peer->session().changes().peerFlagsValue( _peer, Data::PeerUpdate::Flag::GroupCall @@ -682,18 +682,18 @@ GroupCall::GroupCall( setupMediaDevices(); setupOutgoingVideo(); - if (_conferenceCall || conference.migrating) { + if (_conferenceCall || conference.migrating || conference.show) { setupConference(); } - if (conference.migrating) { + if (conference.migrating || (conference.show && !_conferenceCall)) { if (!conference.muted) { setMuted(MuteState::Active); } - _migratedConferenceInfo = std::make_shared( + _startConferenceInfo = std::make_shared( std::move(conference)); } - if (_id || (!_conferenceCall && _migratedConferenceInfo)) { + if (_id || (!_conferenceCall && _startConferenceInfo)) { initialJoin(); } else { start(join.scheduleDate, join.rtmp); @@ -703,7 +703,7 @@ GroupCall::GroupCall( } } -void GroupCall::processMigration(StartConferenceInfo conference) { +void GroupCall::processConferenceStart(StartConferenceInfo conference) { if (!conference.videoCapture) { return; } @@ -1185,7 +1185,7 @@ bool GroupCall::rtmp() const { } bool GroupCall::conference() const { - return _conferenceCall || _migratedConferenceInfo; + return _conferenceCall || _startConferenceInfo; } bool GroupCall::listenersHidden() const { @@ -1552,7 +1552,7 @@ void GroupCall::rejoin(not_null as) { }; LOG(("Call Info: Join payload received, joining with ssrc: %1." ).arg(_joinState.payload.ssrc)); - if (!_conferenceCall && _migratedConferenceInfo) { + if (!_conferenceCall && _startConferenceInfo) { startConference(); } else if (_conferenceCall && !_conferenceCall->blockchainMayBeEmpty() @@ -1655,7 +1655,7 @@ void GroupCall::refreshLastBlockAndJoin() { } void GroupCall::startConference() { - Expects(_e2e != nullptr && _migratedConferenceInfo != nullptr); + Expects(_e2e != nullptr && _startConferenceInfo != nullptr); const auto joinBlock = _e2e->makeJoinBlock().data; Assert(!joinBlock.isEmpty()); @@ -1706,7 +1706,7 @@ void GroupCall::joinDone( MuteState wasMuteState, bool wasVideoStopped, bool justCreated) { - Expects(!justCreated || _migratedConferenceInfo != nullptr); + Expects(!justCreated || _startConferenceInfo != nullptr); _serverTimeMs = serverTimeMs; _serverTimeMsGotAt = crl::now(); @@ -1728,9 +1728,9 @@ void GroupCall::joinDone( setupConferenceCall(); _conferenceLinkSlug = Group::ExtractConferenceSlug( _conferenceCall->conferenceInviteLink()); - Core::App().calls().migratedConferenceReady( + Core::App().calls().startedConferenceReady( this, - *_migratedConferenceInfo); + *_startConferenceInfo); } applyQueuedSelfUpdates(); @@ -1757,8 +1757,8 @@ void GroupCall::joinDone( sendOutboundBlock(base::take(_pendingOutboundBlock)); } } - if (const auto once = base::take(_migratedConferenceInfo)) { - processMigration(*once); + if (const auto once = base::take(_startConferenceInfo)) { + processConferenceStart(*once); } for (const auto &callback : base::take(_rejoinedCallbacks)) { callback(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 236290a189..6773391e18 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -635,7 +635,7 @@ private: void markTrackPaused(const VideoEndpoint &endpoint, bool paused); void markTrackShown(const VideoEndpoint &endpoint, bool shown); - void processMigration(StartConferenceInfo conference); + void processConferenceStart(StartConferenceInfo conference); void inviteToConference( InviteRequest request, Fn()> resultAddress, @@ -650,7 +650,7 @@ private: std::shared_ptr _conferenceCall; std::shared_ptr _e2e; QByteArray _pendingOutboundBlock; - std::shared_ptr _migratedConferenceInfo; + std::shared_ptr _startConferenceInfo; not_null _peer; // Can change in legacy group migration. rpl::event_stream _peerStream; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 1b98ad2306..79c79e169a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -436,8 +436,6 @@ void ShowConferenceCallLinkBox( void MakeConferenceCall(ConferenceFactoryArgs &&args) { const auto show = std::move(args.show); const auto finished = std::move(args.finished); - const auto joining = args.joining; - const auto info = std::move(args.info); const auto session = &show->session(); const auto fail = [=](QString error) { show->showToast(error); @@ -464,20 +462,10 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) { fail(u"Call link not found!"_q); return; } - if (joining) { - if (auto slug = ExtractConferenceSlug(link); !slug.isEmpty()) { - auto copy = info; - copy.call = call; - copy.linkSlug = std::move(slug); - Core::App().calls().startOrJoinConferenceCall( - std::move(copy)); - } - } else { - Calls::Group::ShowConferenceCallLinkBox( - show, - call, - { .initial = true }); - } + Calls::Group::ShowConferenceCallLinkBox( + show, + call, + { .initial = true }); if (const auto onstack = finished) { finished(true); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index d3313b10b0..14c55d2aa6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_common.h" #include "calls/group/calls_group_menu.h" #include "calls/calls_call.h" +#include "calls/calls_instance.h" +#include "core/application.h" #include "boxes/peer_lists_box.h" #include "data/data_user.h" #include "data/data_channel.h" @@ -29,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "apiwrap.h" #include "lang/lang_keys.h" +#include "window/window_session_controller.h" #include "styles/style_boxes.h" // membersMarginTop #include "styles/style_calls.h" #include "styles/style_dialogs.h" // searchedBarHeight @@ -65,9 +68,18 @@ namespace { return result; } +struct ConfInviteStyles { + const style::IconButton *video = nullptr; + const style::icon *videoActive = nullptr; + const style::IconButton *audio = nullptr; + const style::icon *audioActive = nullptr; + const style::SettingsButton *inviteViaLink = nullptr; + const style::icon *inviteViaLinkIcon = nullptr; +}; + class ConfInviteRow final : public PeerListRow { public: - using PeerListRow::PeerListRow; + ConfInviteRow(not_null user, const ConfInviteStyles &st); void setAlreadyIn(bool alreadyIn); void setVideo(bool video); @@ -88,6 +100,9 @@ public: int selectedElement) override; private: + [[nodiscard]] const style::IconButton &buttonSt(int element) const; + + const ConfInviteStyles &_st; std::unique_ptr _videoRipple; std::unique_ptr _audioRipple; bool _alreadyIn = false; @@ -99,6 +114,7 @@ class ConfInviteController final : public ContactsBoxController { public: ConfInviteController( not_null session, + ConfInviteStyles st, base::flat_set> alreadyIn, Fn shareLink); @@ -119,6 +135,7 @@ private: [[nodiscard]] int fullCount() const; void toggleRowSelected(not_null row, bool video); + const ConfInviteStyles _st; const base::flat_set> _alreadyIn; const Fn _shareLink; rpl::variable _hasSelected; @@ -127,6 +144,33 @@ private: }; +[[nodiscard]] ConfInviteStyles ConfInviteDarkStyles() { + return { + .video = &st::confcallInviteVideo, + .videoActive = &st::confcallInviteVideoActive, + .audio = &st::confcallInviteAudio, + .audioActive = &st::confcallInviteAudioActive, + .inviteViaLink = &st::groupCallInviteLink, + .inviteViaLinkIcon = &st::groupCallInviteLinkIcon, + }; +} + +[[nodiscard]] ConfInviteStyles ConfInviteDefaultStyles() { + return { + .video = &st::createCallVideo, + .videoActive = &st::createCallVideoActive, + .audio = &st::createCallAudio, + .audioActive = &st::createCallAudioActive, + .inviteViaLink = &st::createCallInviteLink, + .inviteViaLinkIcon = &st::createCallInviteLinkIcon, + }; +} + +ConfInviteRow::ConfInviteRow(not_null user, const ConfInviteStyles &st) +: PeerListRow(user) +, _st(st) { +} + void ConfInviteRow::setAlreadyIn(bool alreadyIn) { _alreadyIn = alreadyIn; setDisabledState(alreadyIn ? State::DisabledChecked : State::Active); @@ -136,6 +180,12 @@ void ConfInviteRow::setVideo(bool video) { _video = video; } +const style::IconButton &ConfInviteRow::buttonSt(int element) const { + return (element == 1) + ? (_st.video ? *_st.video : st::createCallVideo) + : (_st.audio ? *_st.audio : st::createCallAudio); +} + int ConfInviteRow::elementsCount() const { return _alreadyIn ? 0 : 2; } @@ -144,13 +194,11 @@ QRect ConfInviteRow::elementGeometry(int element, int outerWidth) const { if (_alreadyIn || (element != 1 && element != 2)) { return QRect(); } - const auto &st = (element == 1) - ? st::confcallInviteVideo - : st::confcallInviteAudio; + const auto &st = buttonSt(element); const auto size = QSize(st.width, st.height); const auto margins = (element == 1) - ? st::confcallInviteVideoMargins - : st::confcallInviteAudioMargins; + ? st::createCallVideoMargins + : st::createCallAudioMargins; const auto right = margins.right(); const auto top = margins.top(); const auto side = (element == 1) @@ -178,9 +226,7 @@ void ConfInviteRow::elementAddRipple( return; } auto &ripple = (element == 1) ? _videoRipple : _audioRipple; - const auto &st = (element == 1) - ? st::confcallInviteVideo - : st::confcallInviteAudio; + const auto &st = buttonSt(element); if (!ripple) { auto mask = Ui::RippleAnimation::EllipseMask(QSize( st.rippleAreaSize, @@ -211,9 +257,7 @@ void ConfInviteRow::elementsPaint( return; } const auto paintElement = [&](int element) { - const auto &st = (element == 1) - ? st::confcallInviteVideo - : st::confcallInviteAudio; + const auto &st = buttonSt(element); auto &ripple = (element == 1) ? _videoRipple : _audioRipple; const auto active = checked() && ((element == 1) ? _video : !_video); const auto geometry = elementGeometry(element, outerWidth); @@ -230,8 +274,12 @@ void ConfInviteRow::elementsPaint( const auto selected = (element == selectedElement); const auto &icon = active ? (element == 1 - ? st::confcallInviteVideoActive - : st::confcallInviteAudioActive) + ? (_st.videoActive + ? *_st.videoActive + : st::createCallVideoActive) + : (_st.audioActive + ? *_st.audioActive + : st::createCallAudioActive)) : (selected ? st.iconOver : st.icon); icon.paintInCenter(p, geometry); }; @@ -241,9 +289,11 @@ void ConfInviteRow::elementsPaint( ConfInviteController::ConfInviteController( not_null session, + ConfInviteStyles st, base::flat_set> alreadyIn, Fn shareLink) : ContactsBoxController(session) +, _st(st) , _alreadyIn(std::move(alreadyIn)) , _shareLink(std::move(shareLink)) { } @@ -272,7 +322,7 @@ std::unique_ptr ConfInviteController::createRow( || user->isInaccessible()) { return nullptr; } - auto result = std::make_unique(user); + auto result = std::make_unique(user, _st); if (_alreadyIn.contains(user)) { result->setAlreadyIn(true); } @@ -283,7 +333,9 @@ std::unique_ptr ConfInviteController::createRow( } int ConfInviteController::fullCount() const { - return _alreadyIn.size() + delegate()->peerListSelectedRowsCount(); + return _alreadyIn.size() + + delegate()->peerListSelectedRowsCount() + + (_alreadyIn.contains(session().user()) ? 1 : 0); } void ConfInviteController::rowClicked(not_null row) { @@ -336,17 +388,21 @@ void ConfInviteController::prepareViewHook() { object_ptr( nullptr, tr::lng_profile_add_via_link(), - st::groupCallInviteLink), + (_st.inviteViaLink + ? *_st.inviteViaLink + : st::createCallInviteLink)), style::margins(0, st::membersMarginTop, 0, 0)); const auto icon = Ui::CreateChild( button->entity(), - st::groupCallInviteLinkIcon, + (_st.inviteViaLinkIcon + ? *_st.inviteViaLinkIcon + : st::createCallInviteLinkIcon), QPoint()); button->entity()->heightValue( ) | rpl::start_with_next([=](int height) { icon->moveToLeft( - st::groupCallInviteLinkIconPosition.x(), + st::createCallInviteLinkIconPosition.x(), (height - st::groupCallInviteLinkIcon.height()) / 2); }, icon->lifetime()); @@ -504,6 +560,7 @@ object_ptr PrepareInviteBox( }; auto controller = std::make_unique( &real->session(), + ConfInviteDarkStyles(), alreadyIn, shareLink); const auto raw = controller.get(); @@ -674,6 +731,7 @@ object_ptr PrepareInviteBox( auto alreadyIn = base::flat_set>{ user }; auto controller = std::make_unique( &user->session(), + ConfInviteDarkStyles(), alreadyIn, shareLink); const auto raw = controller.get(); @@ -699,4 +757,73 @@ object_ptr PrepareInviteBox( return Box(std::move(controller), initBox); } +object_ptr PrepareCreateCallBox( + not_null<::Window::SessionController*> window, + Fn created) { + struct State { + bool creatingLink = false; + QPointer box; + }; + const auto state = std::make_shared(); + const auto finished = [=](bool ok) { + if (!ok) { + state->creatingLink = false; + } else { + if (const auto strong = state->box.data()) { + strong->closeBox(); + } + if (const auto onstack = created) { + onstack(); + } + } + }; + const auto shareLink = [=] { + if (state->creatingLink) { + return; + } + state->creatingLink = true; + MakeConferenceCall({ + .show = window->uiShow(), + .finished = finished, + }); + }; + auto controller = std::make_unique( + &window->session(), + ConfInviteDefaultStyles(), + base::flat_set>(), + shareLink); + const auto raw = controller.get(); + const auto initBox = [=](not_null box) { + box->setTitle(tr::lng_confcall_create_title()); + + const auto create = [=] { + auto selected = raw->requests(box->collectSelectedRows()); + if (selected.size() != 1) { + Core::App().calls().startOrJoinConferenceCall({ + .show = window->uiShow(), + .invite = std::move(selected), + }); + } else { + const auto &invite = selected.front(); + Core::App().calls().startOutgoingCall( + invite.user, + invite.video); + } + finished(true); + }; + box->addButton( + rpl::conditional( + raw->hasSelectedValue(), + tr::lng_group_call_confcall_add(), + tr::lng_create_group_create()), + create); + box->addButton(tr::lng_close(), [=] { + box->closeBox(); + }); + }; + auto result = Box(std::move(controller), initBox); + state->box = result.data(); + return result; +} + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h index 4c1ba17d6f..72779f6d6e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.h @@ -87,4 +87,8 @@ private: Fn)> inviteUsers, Fn shareLink); +[[nodiscard]] object_ptr PrepareCreateCallBox( + not_null<::Window::SessionController*> window, + Fn created = nullptr); + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 97256293bb..7e7f796831 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -98,145 +98,6 @@ constexpr auto kPlayStatusLimit = 2; || (now.month() == 1 && now.day() == 1); } -[[nodiscard]] not_null AddCreateCallLinkButton( - not_null container, - not_null controller, - Fn done) { - const auto result = container->add(object_ptr( - container, - tr::lng_confcall_create_link(), - st::inviteViaLinkButton), QMargins()); - Ui::AddSkip(container); - Ui::AddDividerText( - container, - tr::lng_confcall_create_link_description(Ui::Text::WithEntities)); - - const auto icon = Ui::CreateChild( - result, - st::inviteViaLinkIcon, - QPoint()); - result->heightValue( - ) | rpl::start_with_next([=](int height) { - icon->moveToLeft( - st::inviteViaLinkIconPosition.x(), - (height - st::inviteViaLinkIcon.height()) / 2); - }, icon->lifetime()); - - const auto creating = std::make_shared(); - result->setClickedCallback([=] { - if (*creating) { - return; - } - *creating = true; - const auto finished = [=](bool ok) { - if (!ok) { - *creating = false; - } else if (const auto onstack = done) { - onstack(); - } - }; - Calls::Group::MakeConferenceCall({ - .show = controller->uiShow(), - .finished = finished, - }); - }); - return result; -} - -void ShowCallsBox(not_null window) { - struct State { - State(not_null window) - : callsController(window) - , groupCallsController(window) { - } - Calls::BoxController callsController; - PeerListContentDelegateSimple callsDelegate; - - Calls::GroupCalls::ListController groupCallsController; - PeerListContentDelegateSimple groupCallsDelegate; - - base::unique_qptr menu; - }; - - window->show(Box([=](not_null box) { - const auto state = box->lifetime().make_state(window); - - const auto groupCalls = box->addRow( - object_ptr>( - box, - object_ptr(box)), - {}); - groupCalls->hide(anim::type::instant); - groupCalls->toggleOn(state->groupCallsController.shownValue()); - - Ui::AddSubsectionTitle( - groupCalls->entity(), - tr::lng_call_box_groupcalls_subtitle()); - state->groupCallsDelegate.setContent(groupCalls->entity()->add( - object_ptr(box, &state->groupCallsController), - {})); - state->groupCallsController.setDelegate(&state->groupCallsDelegate); - Ui::AddSkip(groupCalls->entity()); - Ui::AddDivider(groupCalls->entity()); - Ui::AddSkip(groupCalls->entity()); - - const auto button = AddCreateCallLinkButton( - box->verticalLayout(), - window, - crl::guard(box, [=] { box->closeBox(); })); - button->events( - ) | rpl::filter([=](not_null e) { - return (e->type() == QEvent::Enter); - }) | rpl::start_with_next([=] { - state->callsDelegate.peerListMouseLeftGeometry(); - }, button->lifetime()); - - const auto content = box->addRow( - object_ptr(box, &state->callsController), - {}); - state->callsDelegate.setContent(content); - state->callsController.setDelegate(&state->callsDelegate); - - box->setWidth(state->callsController.contentWidth()); - state->callsController.boxHeightValue( - ) | rpl::start_with_next([=](int height) { - box->setMinHeight(height); - }, box->lifetime()); - box->setTitle(tr::lng_call_box_title()); - box->addButton(tr::lng_close(), [=] { - box->closeBox(); - }); - const auto menuButton = box->addTopButton(st::infoTopBarMenu); - menuButton->setClickedCallback([=] { - state->menu = base::make_unique_q( - menuButton, - st::popupMenuWithIcons); - const auto showSettings = [=] { - window->showSettings( - Settings::Calls::Id(), - Window::SectionShow(anim::type::instant)); - }; - const auto clearAll = crl::guard(box, [=] { - box->uiShow()->showBox(Box(Calls::ClearCallsBox, window)); - }); - state->menu->addAction( - tr::lng_settings_section_call_settings(tr::now), - showSettings, - &st::menuIconSettings); - if (state->callsDelegate.peerListFullRowsCount() > 0) { - Ui::Menu::CreateAddActionCallback(state->menu)({ - .text = tr::lng_call_box_clear_all(tr::now), - .handler = clearAll, - .icon = &st::menuIconDeleteAttention, - .isAttention = true, - }); - } - state->menu->popup(QCursor::pos()); - return true; - }); - })); -} - [[nodiscard]] rpl::producer SetStatusLabel( not_null session) { const auto self = session->user(); @@ -835,7 +696,7 @@ void MainMenu::setupMenu() { tr::lng_menu_calls(), { &st::menuIconPhone } )->setClickedCallback([=] { - ShowCallsBox(controller); + ::Calls::ShowCallsBox(controller); }); addAction( tr::lng_saved_messages(), From eb8372eb910e01c772da76b6f07586121fd322ec Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 10 Apr 2025 14:53:43 +0400 Subject: [PATCH 128/190] Poll sub_chain_id:1 blocks after call_create. --- Telegram/SourceFiles/calls/group/calls_group_call.cpp | 1 - Telegram/SourceFiles/tde2e/tde2e_api.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 19f29d16cc..ca6f47df40 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1557,7 +1557,6 @@ void GroupCall::rejoin(not_null as) { } else if (_conferenceCall && !_conferenceCall->blockchainMayBeEmpty() && !_e2e->hasLastBlock0()) { - //sendJoinRequest(); AssertIsDebug(); refreshLastBlockAndJoin(); } else { sendJoinRequest(); diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index e0a28936db..7a6ab8b572 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -251,6 +251,7 @@ void Call::apply(int subchain, const Block &last) { return; } setId({ uint64(id.value()) }); + shortPoll(1); for (auto i = 0; i != kSubChainsCount; ++i) { auto &entry = _subchains[i]; From 2b6dfbf4eb3e867b629b7a90b17bf28da2f7e786 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 10 Apr 2025 18:05:53 +0400 Subject: [PATCH 129/190] Closed alpha version 5.13.1.1. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 2 +- Telegram/build/version | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 8fa9ad8dc6..55be74746e 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.13.1.1" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 1ca3963c65..cb5f0c568b 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,13,1,0 - PRODUCTVERSION 5,13,1,0 + FILEVERSION 5,13,1,1 + PRODUCTVERSION 5,13,1,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "5.13.1.0" + VALUE "FileVersion", "5.13.1.1" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.13.1.0" + VALUE "ProductVersion", "5.13.1.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 61a8d1724e..8e87cac32a 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,13,1,0 - PRODUCTVERSION 5,13,1,0 + FILEVERSION 5,13,1,1 + PRODUCTVERSION 5,13,1,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "5.13.1.0" + VALUE "FileVersion", "5.13.1.1" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "5.13.1.0" + VALUE "ProductVersion", "5.13.1.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index eb493a6feb..55245f77cd 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/const_string.h" -#define TDESKTOP_REQUESTED_ALPHA_VERSION (0ULL) +#define TDESKTOP_REQUESTED_ALPHA_VERSION (5013001001ULL) #ifdef TDESKTOP_ALLOW_CLOSED_ALPHA #define TDESKTOP_ALPHA_VERSION TDESKTOP_REQUESTED_ALPHA_VERSION diff --git a/Telegram/build/version b/Telegram/build/version index 04f1467076..d2880d7c53 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -3,5 +3,5 @@ AppVersionStrMajor 5.13 AppVersionStrSmall 5.13.1 AppVersionStr 5.13.1 BetaChannel 0 -AlphaVersion 0 -AppVersionOriginal 5.13.1 +AlphaVersion 5013001001 +AppVersionOriginal 5.13.1.1 From 4c2ec15f70603067b328be7337443ac17603468d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 10 Apr 2025 21:09:03 +0400 Subject: [PATCH 130/190] Fix "Add People" button visibility update. --- Telegram/SourceFiles/calls/calls_panel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 7f35109fc1..dbc9da8630 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -911,6 +911,7 @@ void Panel::showControls() { _decline->setVisible(_decline->toggled()); _cancel->setVisible(_cancel->toggled()); _screencast->setVisible(_screencast->toggled()); + _addPeople->setVisible(_addPeople->toggled()); const auto shown = !_incomingFrameSize.isEmpty(); _incoming->widget()->setVisible(shown); From 59abfcbd6db46b9e552231c902183d41aee2c08e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Apr 2025 13:31:16 +0400 Subject: [PATCH 131/190] Support "Device Storage" web app feature. --- Telegram/CMakeLists.txt | 2 + .../inline_bots/bot_attach_web_view.cpp | 26 ++- .../inline_bots/bot_attach_web_view.h | 8 + .../inline_bots/inline_bot_storage.cpp | 181 ++++++++++++++++++ .../inline_bots/inline_bot_storage.h | 52 +++++ .../SourceFiles/storage/storage_account.cpp | 86 +++++++++ .../SourceFiles/storage/storage_account.h | 5 + .../ui/chat/attach/attach_bot_webview.cpp | 71 +++++++ .../ui/chat/attach/attach_bot_webview.h | 15 ++ 9 files changed, 441 insertions(+), 5 deletions(-) create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_storage.cpp create mode 100644 Telegram/SourceFiles/inline_bots/inline_bot_storage.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 7714f518d8..bc0d1b9016 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1083,6 +1083,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_bot_storage.cpp + inline_bots/inline_bot_storage.h inline_bots/inline_results_inner.cpp inline_bots/inline_results_inner.h inline_bots/inline_results_widget.cpp diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index d7fad92224..92664d402e 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_confirm_prepared.h" #include "inline_bots/inline_bot_downloads.h" +#include "inline_bots/inline_bot_storage.h" #include "iv/iv_instance.h" #include "lang/lang_keys.h" #include "main/main_app_config.h" @@ -1502,7 +1503,7 @@ void WebViewInstance::botHandleInvoice(QString slug) { } }; Payments::CheckoutProcess::Start( - &_bot->session(), + _session, slug, reactivate, nonPanelPaymentFormFactory(reactivate)); @@ -1698,6 +1699,20 @@ void WebViewInstance::botAllowWriteAccess(Fn callback) { }).send(); } +bool WebViewInstance::botStorageWrite( + QString key, + std::optional value) { + return _session->attachWebView().storage().write(_bot->id, key, value); +} + +std::optional WebViewInstance::botStorageRead(QString key) { + return _session->attachWebView().storage().read(_bot->id, key); +} + +void WebViewInstance::botStorageClear() { + _session->attachWebView().storage().clear(_bot->id); +} + void WebViewInstance::botRequestEmojiStatusAccess( Fn callback) { if (_bot->botInfo->canManageEmojiStatus) { @@ -1955,20 +1970,20 @@ void WebViewInstance::botDownloadFile( callback(false); return; } - _bot->session().attachWebView().downloads().start({ + _session->attachWebView().downloads().start({ .bot = _bot, .url = request.url, .path = path, }); callback(true); }; - _bot->session().api().request(MTPbots_CheckDownloadFileParams( + _session->api().request(MTPbots_CheckDownloadFileParams( _bot->inputUser, MTP_string(request.name), MTP_string(request.url) )).done([=] { _panel->showBox(Box(DownloadFileBox, DownloadBoxArgs{ - .session = &_bot->session(), + .session = _session, .bot = _bot->name(), .name = base::FileNameFromUserString(request.name), .url = request.url, @@ -2018,7 +2033,7 @@ void WebViewInstance::botOpenPrivacyPolicy() { }; const auto openUrl = [=](const QString &url) { Core::App().iv().openWithIvPreferred( - &_bot->session(), + _session, url, makeOtherContext(false)); }; @@ -2092,6 +2107,7 @@ std::shared_ptr WebViewInstance::uiShow() { AttachWebView::AttachWebView(not_null session) : _session(session) , _downloads(std::make_unique(session)) +, _storage(std::make_unique(session)) , _refreshTimer([=] { requestBots(); }) { _refreshTimer.callEach(kRefreshBotsTimeout); } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 43dc766890..442cc3fc78 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -53,6 +53,7 @@ namespace InlineBots { class WebViewInstance; class Downloads; +class Storage; enum class PeerType : uint8 { SameBot = 0x01, @@ -276,6 +277,9 @@ private: QString query) override; void botCheckWriteAccess(Fn callback) override; void botAllowWriteAccess(Fn callback) override; + bool botStorageWrite(QString key, std::optional value) override; + std::optional botStorageRead(QString key) override; + void botStorageClear() override; void botRequestEmojiStatusAccess( Fn callback) override; void botSharePhone(Fn callback) override; @@ -322,6 +326,9 @@ public: [[nodiscard]] Downloads &downloads() const { return *_downloads; } + [[nodiscard]] Storage &storage() const { + return *_storage; + } void open(WebViewDescriptor &&descriptor); void openByUsername( @@ -394,6 +401,7 @@ private: const not_null _session; const std::unique_ptr _downloads; + const std::unique_ptr _storage; base::Timer _refreshTimer; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_storage.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_storage.cpp new file mode 100644 index 0000000000..8d753de2e9 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_storage.cpp @@ -0,0 +1,181 @@ +/* +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_bot_storage.h" + +#include "main/main_session.h" +#include "storage/storage_account.h" + +#include + +namespace InlineBots { +namespace { + +constexpr auto kMaxStorageSize = (5 << 20); + +[[nodiscard]] uint64 KeyHash(const QString &key) { + return XXH64(key.data(), key.size(), 0); +} + +} // namespace + +Storage::Storage(not_null session) +: _session(session) { +} + +bool Storage::write( + PeerId botId, + const QString &key, + const std::optional &value) { + if (value && value->size() > kMaxStorageSize) { + return false; + } + readFromDisk(botId); + auto i = _lists.find(botId); + if (i == end(_lists)) { + if (!value) { + return true; + } + i = _lists.emplace(botId).first; + } + auto &list = i->second; + const auto hash = KeyHash(key); + auto j = list.data.find(hash); + if (j == end(list.data)) { + if (!value) { + return true; + } + j = list.data.emplace(hash).first; + } + auto &bykey = j->second; + const auto k = ranges::find(bykey, key, &Entry::key); + if (k == end(bykey) && !value) { + return true; + } + const auto size = list.totalSize + - (k != end(bykey) ? (key.size() + k->value.size()) : 0) + + (value ? (key.size() + value->size()) : 0); + if (size > kMaxStorageSize) { + return false; + } + if (k == end(bykey)) { + bykey.emplace_back(key, *value); + ++list.keysCount; + } else if (value) { + k->value = *value; + } else { + bykey.erase(k); + --list.keysCount; + } + if (bykey.empty()) { + list.data.erase(j); + if (list.data.empty()) { + Assert(size == 0); + _lists.erase(i); + } else { + list.totalSize = size; + } + } else { + list.totalSize = size; + } + saveToDisk(botId); + return true; +} + +std::optional Storage::read(PeerId botId, const QString &key) { + readFromDisk(botId); + const auto i = _lists.find(botId); + if (i == end(_lists)) { + return std::nullopt; + } + const auto &list = i->second; + const auto j = list.data.find(KeyHash(key)); + if (j == end(list.data)) { + return std::nullopt; + } + const auto &bykey = j->second; + const auto k = ranges::find(bykey, key, &Entry::key); + if (k == end(bykey)) { + return std::nullopt; + } + return k->value; +} + +void Storage::clear(PeerId botId) { + if (_lists.remove(botId)) { + saveToDisk(botId); + } +} + +void Storage::saveToDisk(PeerId botId) { + const auto i = _lists.find(botId); + if (i != end(_lists)) { + _session->local().writeBotStorage(botId, Serialize(i->second)); + } else { + _session->local().writeBotStorage(botId, QByteArray()); + } +} + +void Storage::readFromDisk(PeerId botId) { + const auto serialized = _session->local().readBotStorage(botId); + if (!serialized.isEmpty()) { + _lists[botId] = Deserialize(serialized); + } +} + +QByteArray Storage::Serialize(const List &list) { + auto result = QByteArray(); + const auto size = sizeof(quint32) + + (list.keysCount * sizeof(quint32)) + + (list.totalSize * sizeof(ushort)); + result.reserve(size); + { + QDataStream stream(&result, QIODevice::WriteOnly); + auto count = 0; + stream.setVersion(QDataStream::Qt_5_1); + stream << quint32(list.keysCount); + for (const auto &[hash, bykey] : list.data) { + for (const auto &entry : bykey) { + stream << entry.key << entry.value; + ++count; + } + } + Assert(count == list.keysCount); + } + return result; +} + +Storage::List Storage::Deserialize(const QByteArray &serialized) { + QDataStream stream(serialized); + stream.setVersion(QDataStream::Qt_5_1); + + auto count = quint32(); + auto result = List(); + stream >> count; + if (count > kMaxStorageSize) { + return {}; + } + for (auto i = 0; i != count; ++i) { + auto entry = Entry(); + stream >> entry.key >> entry.value; + const auto hash = KeyHash(entry.key); + auto j = result.data.find(hash); + if (j == end(result.data)) { + j = result.data.emplace(hash).first; + } + auto &bykey = j->second; + const auto k = ranges::find(bykey, entry.key, &Entry::key); + if (k == end(bykey)) { + bykey.push_back(entry); + result.totalSize += entry.key.size() + entry.value.size(); + ++result.keysCount; + } + } + return result; +} + +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_storage.h b/Telegram/SourceFiles/inline_bots/inline_bot_storage.h new file mode 100644 index 0000000000..975fa576b5 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_bot_storage.h @@ -0,0 +1,52 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/chat/attach/attach_bot_webview.h" + +namespace Main { +class Session; +} // namespace Main + +namespace InlineBots { + +class Storage final { +public: + explicit Storage(not_null session); + + bool write( + PeerId botId, + const QString &key, + const std::optional &value); + std::optional read(PeerId botId, const QString &key); + void clear(PeerId botId); + +private: + struct Entry { + QString key; + QString value; + }; + struct List { + base::flat_map> data; + int keysCount = 0; + int totalSize = 0; + }; + + void saveToDisk(PeerId botId); + void readFromDisk(PeerId botId); + + [[nodiscard]] static QByteArray Serialize(const List &list); + [[nodiscard]] static List Deserialize(const QByteArray &serialized); + + const not_null _session; + + base::flat_map _lists; + +}; + +} // namespace InlineBots diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index ea673e5039..339840de23 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -98,6 +98,7 @@ enum { // Local Storage Keys lskRoundPlaceholder = 0x1a, // no data lskInlineBotsDownloads = 0x1b, // no data lskMediaLastPlaybackPositions = 0x1c, // no data + lskBotStorages = 0x1d, // data: PeerId botId }; auto EmptyMessageDraftSources() @@ -255,6 +256,9 @@ base::flat_set Account::collectGoodNames() const { for (const auto &[key, value] : _draftCursorsMap) { push(value); } + for (const auto &[key, value] : _botStoragesMap) { + push(value); + } for (const auto &value : keys) { push(value); } @@ -308,6 +312,8 @@ Account::ReadMapResult Account::readMapWith( base::flat_map draftsMap; base::flat_map draftCursorsMap; base::flat_map draftsNotReadMap; + base::flat_map botStoragesMap; + base::flat_map botStoragesNotReadMap; quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedPeersKey = 0; quint64 recentStickersKeyOld = 0; quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0; @@ -443,6 +449,18 @@ Account::ReadMapResult Account::readMapWith( >> webviewStorageTokenBots >> webviewStorageTokenOther; } break; + case lskBotStorages: { + quint32 count = 0; + map.stream >> count; + for (quint32 i = 0; i < count; ++i) { + FileKey key; + quint64 peerIdSerialized; + map.stream >> key >> peerIdSerialized; + const auto peerId = DeserializePeerId(peerIdSerialized); + botStoragesMap.emplace(peerId, key); + botStoragesNotReadMap.emplace(peerId, true); + } + } break; default: LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType)); return ReadMapResult::Failed; @@ -457,6 +475,8 @@ Account::ReadMapResult Account::readMapWith( _draftsMap = draftsMap; _draftCursorsMap = draftCursorsMap; _draftsNotReadMap = draftsNotReadMap; + _botStoragesMap = botStoragesMap; + _botStoragesNotReadMap = botStoragesNotReadMap; _locationsKey = locationsKey; _trustedPeersKey = trustedPeersKey; @@ -599,6 +619,7 @@ void Account::writeMap() { if (_roundPlaceholderKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_inlineBotsDownloadsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_mediaLastPlaybackPositionsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (!_botStoragesMap.empty()) mapSize += sizeof(quint32) * 2 + _botStoragesMap.size() * sizeof(quint64) * 2; EncryptedDescriptor mapData(mapSize); if (!self.isEmpty()) { @@ -681,6 +702,12 @@ void Account::writeMap() { mapData.stream << quint32(lskMediaLastPlaybackPositions); mapData.stream << quint64(_mediaLastPlaybackPositionsKey); } + if (!_botStoragesMap.empty()) { + mapData.stream << quint32(lskBotStorages) << quint32(_botStoragesMap.size()); + for (const auto &[key, value] : _botStoragesMap) { + mapData.stream << quint64(value) << SerializePeerId(key); + } + } map.writeEncrypted(mapData, _localKey); _mapChanged = false; @@ -693,6 +720,8 @@ void Account::reset() { _draftsMap.clear(); _draftCursorsMap.clear(); _draftsNotReadMap.clear(); + _botStoragesMap.clear(); + _botStoragesNotReadMap.clear(); _locationsKey = _trustedPeersKey = 0; _recentStickersKeyOld = 0; _installedStickersKey = 0; @@ -3473,6 +3502,63 @@ void Account::writeInlineBotsDownloads(const QByteArray &bytes) { file.writeEncrypted(data, _localKey); } +void Account::writeBotStorage(PeerId botId, const QByteArray &serialized) { + if (serialized.isEmpty()) { + auto i = _botStoragesMap.find(botId); + if (i != _botStoragesMap.cend()) { + ClearKey(i->second, _basePath); + _botStoragesMap.erase(i); + writeMapDelayed(); + } + _botStoragesNotReadMap.remove(botId); + return; + } + + auto i = _botStoragesMap.find(botId); + if (i == _botStoragesMap.cend()) { + i = _botStoragesMap.emplace(botId, GenerateKey(_basePath)).first; + writeMapQueued(); + } + + auto size = Serialize::bytearraySize(serialized); + + EncryptedDescriptor data(size); + data.stream << serialized; + + FileWriteDescriptor file(i->second, _basePath); + file.writeEncrypted(data, _localKey); + + _botStoragesNotReadMap.remove(botId); +} + +QByteArray Account::readBotStorage(PeerId botId) { + if (!_botStoragesNotReadMap.remove(botId)) { + return {}; + } + + const auto j = _botStoragesMap.find(botId); + if (j == _botStoragesMap.cend()) { + return {}; + } + FileReadDescriptor storage; + if (!ReadEncryptedFile(storage, j->second, _basePath, _localKey)) { + ClearKey(j->second, _basePath); + _botStoragesMap.erase(j); + writeMapDelayed(); + return {}; + } + + auto result = QByteArray(); + storage.stream >> result; + if (storage.stream.status() != QDataStream::Ok) { + ClearKey(j->second, _basePath); + _botStoragesMap.erase(j); + writeMapDelayed(); + return {}; + } + return result; +} + bool Account::encrypt( const void *src, void *dst, diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index 86bdabde6f..c096039941 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -191,6 +191,9 @@ public: [[nodiscard]] QByteArray readInlineBotsDownloads(); void writeInlineBotsDownloads(const QByteArray &bytes); + void writeBotStorage(PeerId botId, const QByteArray &serialized); + [[nodiscard]] QByteArray readBotStorage(PeerId botId); + [[nodiscard]] bool encrypt( const void *src, void *dst, @@ -293,6 +296,8 @@ private: base::flat_map< not_null, base::flat_map> _draftSources; + base::flat_map _botStoragesMap; + base::flat_map _botStoragesNotReadMap; QMultiMap _fileLocations; QMap> _fileLocationPairs; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index df4d7bc17a..526dceceed 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -997,6 +997,20 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { processEmojiStatusRequest(arguments); } else if (command == "web_app_request_emoji_status_access") { processEmojiStatusAccessRequest(); + } else if (command == "web_app_device_storage_save_key") { + processStorageSaveKey(arguments); + } else if (command == "web_app_device_storage_get_key") { + processStorageGetKey(arguments); + } else if (command == "web_app_device_storage_clear") { + processStorageClear(arguments); + } else if (command == "web_app_secure_storage_save_key") { + secureStorageFailed(arguments); + } else if (command == "web_app_secure_storage_get_key") { + secureStorageFailed(arguments); + } else if (command == "web_app_secure_storage_restore_key") { + secureStorageFailed(arguments); + } else if (command == "web_app_secure_storage_clear") { + secureStorageFailed(arguments); } else if (command == "share_score") { _delegate->botHandleMenuButton(MenuButton::ShareGame); } @@ -1201,6 +1215,63 @@ void Panel::processEmojiStatusAccessRequest() { _delegate->botRequestEmojiStatusAccess(std::move(callback)); } +void Panel::processStorageSaveKey(const QJsonObject &args) { + const auto keyObject = args["key"]; + const auto valueObject = args["value"]; + const auto key = keyObject.toString(); + if (!keyObject.isString()) { + deviceStorageFailed(args, u"KEY_INVALID"_q); + } else if (valueObject.isNull()) { + _delegate->botStorageWrite(key, std::nullopt); + replyDeviceStorage(args, u"device_storage_key_saved"_q, {}); + } else if (!valueObject.isString()) { + deviceStorageFailed(args, u"VALUE_INVALID"_q); + } else if (_delegate->botStorageWrite(key, valueObject.toString())) { + replyDeviceStorage(args, u"device_storage_key_saved"_q, {}); + } else { + deviceStorageFailed(args, u"QUOTA_EXCEEDED"_q); + } +} + +void Panel::processStorageGetKey(const QJsonObject &args) { + const auto keyObject = args["key"]; + const auto key = keyObject.toString(); + if (!keyObject.isString()) { + deviceStorageFailed(args, u"KEY_INVALID"_q); + } else { + const auto value = _delegate->botStorageRead(key); + replyDeviceStorage(args, u"device_storage_key_received"_q, { + { u"value"_q, value ? QJsonValue(*value) : QJsonValue::Null }, + }); + } +} + +void Panel::processStorageClear(const QJsonObject &args) { + _delegate->botStorageClear(); + replyDeviceStorage(args, u"device_storage_cleared"_q, {}); +} + +void Panel::replyDeviceStorage( + const QJsonObject &args, + const QString &event, + QJsonObject response) { + response[u"req_id"_q] = args[u"req_id"_q]; + postEvent(event, response); +} + +void Panel::deviceStorageFailed(const QJsonObject &args, QString error) { + replyDeviceStorage(args, u"device_storage_failed"_q, { + { u"error"_q, error }, + }); +} + +void Panel::secureStorageFailed(const QJsonObject &args) { + postEvent(u"secure_storage_failed"_q, QJsonObject{ + { u"req_id"_q, args["req_id"] }, + { u"error"_q, u"UNSUPPORTED"_q }, + }); +} + void Panel::openTgLink(const QJsonObject &args) { if (args.isEmpty()) { LOG(("BotWebView Error: Bad arguments in 'web_app_open_tg_link'.")); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index b17a648a49..72edb0b391 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -91,6 +91,12 @@ public: QString query) = 0; virtual void botCheckWriteAccess(Fn callback) = 0; virtual void botAllowWriteAccess(Fn callback) = 0; + virtual bool botStorageWrite( + QString key, + std::optional value) = 0; + [[nodiscard]] virtual std::optional botStorageRead( + QString key) = 0; + virtual void botStorageClear() = 0; virtual void botRequestEmojiStatusAccess( Fn callback) = 0; virtual void botSharePhone(Fn callback) = 0; @@ -165,6 +171,9 @@ private: void processSendMessageRequest(const QJsonObject &args); void processEmojiStatusRequest(const QJsonObject &args); void processEmojiStatusAccessRequest(); + void processStorageSaveKey(const QJsonObject &args); + void processStorageGetKey(const QJsonObject &args); + void processStorageClear(const QJsonObject &args); void processButtonMessage( std::unique_ptr