Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
838a3b23c7 | ||
|
|
a030911ad5 | ||
|
|
8ae1b10b91 | ||
|
|
adc8d6a6d1 | ||
|
|
1704cb345a | ||
|
|
0b85d0f185 | ||
|
|
348dfefbaa | ||
|
|
7508980f62 | ||
|
|
e11efe483e | ||
|
|
b23e4fa491 | ||
|
|
b6b7f5706f | ||
|
|
613bf98283 | ||
|
|
0bdb38753b | ||
|
|
d557e0f2b7 | ||
|
|
e81f4e8545 | ||
|
|
3b7d5d3c80 | ||
|
|
0c37990ccd | ||
|
|
0fbea454bc | ||
|
|
d4d688d494 | ||
|
|
b3892f49fa | ||
|
|
daa3a2f62f | ||
|
|
5affb168a2 | ||
|
|
99af2a7058 | ||
|
|
b9acea9cef | ||
|
|
8fb6ece796 | ||
|
|
15a9842b9f | ||
|
|
b28da30038 | ||
|
|
8ce0bd5575 | ||
|
|
5d68d224e5 | ||
|
|
373635a765 | ||
|
|
0ecd4d3b40 | ||
|
|
3fd62d51aa | ||
|
|
818624e051 | ||
|
|
a6eb241ec1 | ||
|
|
1d7fb6c4ce | ||
|
|
2af63ec48f | ||
|
|
0709bc6d70 | ||
|
|
3a34881488 | ||
|
|
39f9147790 | ||
|
|
19a5dcbffc | ||
|
|
e864aa2ff2 | ||
|
|
fe85a8256a | ||
|
|
f24b0c6237 | ||
|
|
3940d57c3d | ||
|
|
8da33113a2 | ||
|
|
f66cfb5684 | ||
|
|
d648d294ca | ||
|
|
0c8033414e | ||
|
|
28f29b51c0 | ||
|
|
8142e83395 | ||
|
|
e247be7e33 | ||
|
|
e594b75f4c | ||
|
|
28f857f763 | ||
|
|
486424af4f | ||
|
|
f3614d6402 | ||
|
|
71151f6bf6 | ||
|
|
2c0ef9c4e9 | ||
|
|
930e971881 | ||
|
|
a576025d4f | ||
|
|
bcd2560e8f | ||
|
|
aede42b0b6 | ||
|
|
56728a066e | ||
|
|
1951b7a8a1 | ||
|
|
0dc0f588c4 | ||
|
|
7d22c631ca | ||
|
|
cf5cc3646a | ||
|
|
375820d5cf | ||
|
|
3955543699 | ||
|
|
c03da00e37 | ||
|
|
edcd462fb9 | ||
|
|
e99558abeb | ||
|
|
feff514a07 | ||
|
|
b1b25b0df9 | ||
|
|
dfee8238c6 | ||
|
|
b7216c40fc | ||
|
|
670d618439 | ||
|
|
d16bc36bae | ||
|
|
31417fd005 | ||
|
|
ae6decf70b | ||
|
|
c80da25450 | ||
|
|
7fd09084fd | ||
|
|
d7496f9824 | ||
|
|
f94280be7f | ||
|
|
0ff6c555b1 | ||
|
|
596c7892c7 | ||
|
|
902e0fc8fb | ||
|
|
91e97b3d65 | ||
|
|
92bc278052 | ||
|
|
16c7ec5b05 | ||
|
|
348712059b | ||
|
|
b3f6fe1c10 | ||
|
|
055ce1ee24 | ||
|
|
c14313d64a | ||
|
|
25665167fa | ||
|
|
af6c7c7d09 | ||
|
|
9c20cf3543 | ||
|
|
4ef2918bcc | ||
|
|
eaf9b58337 | ||
|
|
b85bbadd74 | ||
|
|
7a07acb124 | ||
|
|
2fb220985a | ||
|
|
4b41962ff6 | ||
|
|
e73b522411 | ||
|
|
c5ad7c7c89 | ||
|
|
d301601360 | ||
|
|
f8039f9b99 | ||
|
|
50ed60f443 | ||
|
|
a0b0799399 | ||
|
|
f0b8d4e62b | ||
|
|
d799dfe564 | ||
|
|
cae7e9c502 | ||
|
|
7aa12b6e07 | ||
|
|
bb8647dd4c | ||
|
|
9dc6f117a7 | ||
|
|
cabd7a276b | ||
|
|
a85be4ccd0 | ||
|
|
29e7ea9b36 | ||
|
|
d732a35904 | ||
|
|
b68d5f854d | ||
|
|
146a9c2794 | ||
|
|
00fac70140 | ||
|
|
6b5c422e95 | ||
|
|
945fa2dd4b | ||
|
|
9493a3e8d2 | ||
|
|
823409175e | ||
|
|
703b21b4e5 | ||
|
|
b550eb5ab2 | ||
|
|
aa5e8422bf | ||
|
|
15620b5c2d | ||
|
|
7f7e7b94d6 | ||
|
|
df9d19b16a | ||
|
|
af17046a76 | ||
|
|
cb6db82809 | ||
|
|
ad2d2c203f | ||
|
|
7ab919e249 | ||
|
|
d69caacded | ||
|
|
b6483cb65c | ||
|
|
baba7e272d | ||
|
|
77775b5f7c | ||
|
|
dc7a754418 | ||
|
|
24b8377a2a | ||
|
|
c8643aa1ee | ||
|
|
de6b460754 | ||
|
|
bb4be4f3dd | ||
|
|
62fe44bde8 | ||
|
|
de20ff05eb | ||
|
|
1b624d67b8 | ||
|
|
cd3b989e70 | ||
|
|
e9e4c7a8cc | ||
|
|
144e2e217f | ||
|
|
dcd1e62c36 | ||
|
|
c9d2ef278f | ||
|
|
78a666a19d | ||
|
|
2b91eedcd4 | ||
|
|
7217d14f09 | ||
|
|
fe23ba086a | ||
|
|
424ba1dbea | ||
|
|
49b8340695 | ||
|
|
4b31b4792a | ||
|
|
df420e4ccf | ||
|
|
cc35653c2c | ||
|
|
64c791a9ce | ||
|
|
7809cb8d30 | ||
|
|
379a3d74e2 | ||
|
|
c4f985ca38 | ||
|
|
6af180d6b5 | ||
|
|
05147016b0 | ||
|
|
3297bdadb5 |
16
.github/workflows/copyright_year_updater.yml
vendored
Normal file
16
.github/workflows/copyright_year_updater.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Copyright year updater.
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: ["Restart copyright_year_updater workflow."]
|
||||
schedule:
|
||||
# At 03:00 on January 1.
|
||||
- cron: "0 3 1 1 *"
|
||||
|
||||
jobs:
|
||||
Copyright-year:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: desktop-app/action_code_updater@master
|
||||
with:
|
||||
type: "license-year"
|
||||
6
.github/workflows/linux.yml
vendored
6
.github/workflows/linux.yml
vendored
@@ -72,12 +72,6 @@ jobs:
|
||||
- name: Get repository name.
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Yum install.
|
||||
run: |
|
||||
yum -y autoremove git
|
||||
yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm
|
||||
yum -y install git
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
|
||||
4
.github/workflows/mac.yml
vendored
4
.github/workflows/mac.yml
vendored
@@ -102,6 +102,8 @@ jobs:
|
||||
cd Libraries/macos
|
||||
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
|
||||
|
||||
curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
|
||||
|
||||
- name: Patches.
|
||||
run: |
|
||||
echo "Find necessary commit from doc."
|
||||
@@ -475,7 +477,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_owt
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
|
||||
- name: WebRTC.
|
||||
if: steps.cache-webrtc.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
|
||||
1
.github/workflows/snap.yml
vendored
1
.github/workflows/snap.yml
vendored
@@ -51,6 +51,7 @@ jobs:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: First set up.
|
||||
|
||||
144
.github/workflows/user_agent_updater.yml
vendored
144
.github/workflows/user_agent_updater.yml
vendored
@@ -5,152 +5,14 @@ on:
|
||||
types: ["Restart user_agent_updater workflow."]
|
||||
schedule:
|
||||
# At 00:00 on day-of-month 1.
|
||||
- cron: '0 0 1 * *'
|
||||
- cron: "0 0 1 * *"
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
User-agent:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
codeFile: "Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp"
|
||||
headBranchPrefix: "chrome_"
|
||||
baseBranch: "dev"
|
||||
isPull: "0"
|
||||
|
||||
steps:
|
||||
- name: Set env.
|
||||
if: startsWith(github.event_name, 'pull_request')
|
||||
run: |
|
||||
echo "isPull=1" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up git.
|
||||
run: |
|
||||
token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }}
|
||||
if [ -z "${token}" ]; then
|
||||
echo "Token is unset. Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY
|
||||
|
||||
git config --global user.email "action@github.com"
|
||||
git config --global user.name "GitHub Action"
|
||||
|
||||
git remote set-url origin $url
|
||||
|
||||
- name: Delete branch.
|
||||
env:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
if: |
|
||||
env.isPull == '1'
|
||||
&& github.event.action == 'closed'
|
||||
&& startsWith(env.ref, env.headBranchPrefix)
|
||||
run: |
|
||||
git push origin --delete $ref
|
||||
|
||||
- name: Write a new version of Google Chrome to the user-agent for DNS.
|
||||
if: env.isPull == '0'
|
||||
shell: python
|
||||
run: |
|
||||
import subprocess, os, re;
|
||||
|
||||
regExpVersion = "[0-9]+.[0-9]+.[0-9]+.[0-9]+";
|
||||
chrome = "Chrome/";
|
||||
|
||||
def newVersion():
|
||||
output = subprocess.check_output(["google-chrome", "--version"]);
|
||||
version = re.search(regExpVersion, output);
|
||||
if not version:
|
||||
print("Can't find a Chrome version.");
|
||||
exit();
|
||||
return version.group(0);
|
||||
|
||||
newChromeVersion = newVersion();
|
||||
print(newChromeVersion);
|
||||
|
||||
def setEnv(value):
|
||||
open(os.environ['GITHUB_ENV'], "a").write(value);
|
||||
|
||||
def writeUserAgent():
|
||||
p = os.environ['codeFile'];
|
||||
w = open(p, "r");
|
||||
content = w.read();
|
||||
w.close();
|
||||
|
||||
regExpChrome = chrome + regExpVersion;
|
||||
|
||||
version = re.search(regExpChrome, content);
|
||||
if not version:
|
||||
print("Can't find an user-agent in the code.");
|
||||
exit();
|
||||
content = re.sub(regExpChrome, chrome + newChromeVersion, content);
|
||||
|
||||
w = open(p, "w");
|
||||
w.write(content);
|
||||
|
||||
setEnv("ChromeVersion=" + newChromeVersion);
|
||||
|
||||
writeUserAgent();
|
||||
|
||||
- name: Push to a new branch.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
run: |
|
||||
git diff > git_diff.txt
|
||||
if [[ ! -s git_diff.txt ]]; then
|
||||
echo "Nothing to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git checkout -b $headBranchPrefix$ChromeVersion
|
||||
git add $codeFile
|
||||
git commit -m "Update User-Agent for DNS to Chrome $ChromeVersion."
|
||||
|
||||
git push origin $headBranchPrefix$ChromeVersion
|
||||
echo "Done!"
|
||||
|
||||
- name: Close previous pull requests.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
uses: actions/github-script@0.4.0
|
||||
- uses: desktop-app/action_code_updater@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const common = {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
};
|
||||
|
||||
github.pulls.list(common).then(response => {
|
||||
response.data.forEach((item, _) => {
|
||||
if (item.head.ref.startsWith(process.env.headBranchPrefix)) {
|
||||
console.log(`Close ${item.title} #${item.number}.`);
|
||||
github.pulls.update({
|
||||
pull_number: item.number,
|
||||
state: "closed",
|
||||
...common
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
- name: Create a new pull request.
|
||||
if: env.isPull == '0' && env.ChromeVersion != ''
|
||||
uses: actions/github-script@0.4.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const version = process.env.ChromeVersion;
|
||||
const title = `Update User-Agent for DNS to Chrome ${version}.`;
|
||||
|
||||
github.pulls.create({
|
||||
title: title,
|
||||
body: "",
|
||||
head: `${process.env.headBranchPrefix}${version}`,
|
||||
base: process.env.baseBranch,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
type: "user-agent"
|
||||
|
||||
5
.github/workflows/win.yml
vendored
5
.github/workflows/win.yml
vendored
@@ -103,6 +103,7 @@ jobs:
|
||||
- name: Generate cache key.
|
||||
shell: bash
|
||||
run: |
|
||||
curl -o $LibrariesPath/tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
if [ "$AUTO_CACHING" == "1" ]; then
|
||||
thisFile=$REPO_NAME/.github/workflows/win.yml
|
||||
@@ -218,7 +219,7 @@ jobs:
|
||||
-A Win32 ^
|
||||
-D LIBTYPE:STRING=STATIC ^
|
||||
-D FORCE_STATIC_VCRT=ON ^
|
||||
-D ALSOFT_BACKEND_WASAPI=OFF
|
||||
-D ALSOFT_BACKEND_DSOUND=OFF
|
||||
|
||||
msbuild -m OpenAL.vcxproj /property:Configuration=Debug
|
||||
|
||||
@@ -359,7 +360,7 @@ jobs:
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_owt
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
|
||||
- name: WebRTC.
|
||||
if: steps.cache-webrtc.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
|
||||
2
LEGAL
2
LEGAL
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2020 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2021 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
10
README.md
10
README.md
@@ -17,13 +17,17 @@ The latest version is available for
|
||||
|
||||
* [Windows 7 and above](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
|
||||
* [macOS 10.12 and above](https://telegram.org/dl/desktop/mac)
|
||||
* [OS X 10.10 and 10.11](https://telegram.org/dl/desktop/osx)
|
||||
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux) ([32 bit](https://telegram.org/dl/desktop/linux32))
|
||||
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux)
|
||||
* [Snap](https://snapcraft.io/telegram-desktop)
|
||||
* [Flatpak](https://flathub.org/apps/details/org.telegram.desktop)
|
||||
|
||||
## Old system versions
|
||||
|
||||
Version **2.4.4** was the last that supports older systems
|
||||
|
||||
* [OS X 10.10 and 10.11](https://updates.tdesktop.com/tosx/tsetup-osx.2.4.4.dmg)
|
||||
* [Linux static build for 32 bit](https://updates.tdesktop.com/tlinux32/tsetup32.2.4.4.tar.xz)
|
||||
|
||||
Version **1.8.15** was the last that supports older systems
|
||||
|
||||
* [Windows XP and Vista](https://updates.tdesktop.com/tsetup/tsetup.1.8.15.exe) ([portable](https://updates.tdesktop.com/tsetup/tportable.1.8.15.zip))
|
||||
@@ -32,7 +36,7 @@ Version **1.8.15** was the last that supports older systems
|
||||
|
||||
## Third-party
|
||||
|
||||
* Qt 5.12.8, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
|
||||
* Qt 5.15.2, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
|
||||
* OpenSSL 1.1.1 and 1.0.1 ([OpenSSL License](https://www.openssl.org/source/license.html))
|
||||
* WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/LICENSE))
|
||||
* zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html))
|
||||
|
||||
@@ -33,6 +33,7 @@ include(cmake/td_mtproto.cmake)
|
||||
include(cmake/td_lang.cmake)
|
||||
include(cmake/td_scheme.cmake)
|
||||
include(cmake/td_ui.cmake)
|
||||
include(cmake/generate_appdata_changelog.cmake)
|
||||
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
|
||||
|
||||
@@ -59,6 +60,7 @@ PRIVATE
|
||||
desktop-app::external_rlottie
|
||||
desktop-app::external_zlib
|
||||
desktop-app::external_minizip
|
||||
desktop-app::external_qt_static_plugins
|
||||
desktop-app::external_qt
|
||||
desktop-app::external_qr_code_generator
|
||||
desktop-app::external_crash_reports
|
||||
@@ -70,9 +72,6 @@ PRIVATE
|
||||
if (LINUX)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_nimf_qt5
|
||||
desktop-app::external_qt5ct_support
|
||||
desktop-app::external_xcb_screensaver
|
||||
desktop-app::external_xcb
|
||||
desktop-app::external_glib
|
||||
)
|
||||
@@ -82,28 +81,26 @@ if (LINUX)
|
||||
PRIVATE
|
||||
desktop-app::external_statusnotifieritem
|
||||
desktop-app::external_dbusmenu_qt
|
||||
desktop-app::external_fcitx_qt5
|
||||
desktop-app::external_fcitx5_qt5
|
||||
desktop-app::external_hime_qt
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_materialdecoration
|
||||
desktop-app::external_kwayland
|
||||
)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED
|
||||
AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
|
||||
if (DESKTOP_APP_USE_PACKAGED
|
||||
AND NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
|
||||
AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
|
||||
|
||||
target_include_directories(Telegram
|
||||
PRIVATE
|
||||
${WAYLAND_CLIENT_INCLUDE_DIRS}
|
||||
)
|
||||
endif()
|
||||
target_include_directories(Telegram
|
||||
PRIVATE
|
||||
${WAYLAND_CLIENT_INCLUDE_DIRS}
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT TDESKTOP_DISABLE_GTK_INTEGRATION)
|
||||
@@ -234,6 +231,8 @@ PRIVATE
|
||||
boxes/peer_list_box.h
|
||||
boxes/peer_list_controllers.cpp
|
||||
boxes/peer_list_controllers.h
|
||||
boxes/peer_lists_box.cpp
|
||||
boxes/peer_lists_box.h
|
||||
boxes/passcode_box.cpp
|
||||
boxes/passcode_box.h
|
||||
boxes/photo_crop_box.cpp
|
||||
@@ -820,6 +819,8 @@ PRIVATE
|
||||
platform/linux/linux_desktop_environment.h
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_libs.cpp
|
||||
platform/linux/linux_libs.h
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
@@ -1097,7 +1098,6 @@ PRIVATE
|
||||
mainwidget.h
|
||||
mainwindow.cpp
|
||||
mainwindow.h
|
||||
qt_static_plugins.cpp
|
||||
settings.cpp
|
||||
settings.h
|
||||
stdafx.h
|
||||
@@ -1110,6 +1110,19 @@ if (NOT LINUX)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/notifications_manager_linux.cpp
|
||||
)
|
||||
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
platform/linux/notifications_manager_linux_dummy.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc} platform/linux/linux_wayland_integration.cpp)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp)
|
||||
@@ -1319,6 +1332,7 @@ endif()
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
include(GNUInstallDirs)
|
||||
configure_file("../lib/xdg/telegramdesktop.appdata.xml.in" "${CMAKE_CURRENT_BINARY_DIR}/telegramdesktop.appdata.xml" @ONLY)
|
||||
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/telegramdesktop.appdata.xml")
|
||||
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png")
|
||||
|
||||
BIN
Telegram/Resources/icons/calls/group_calls_invited.png
Normal file
BIN
Telegram/Resources/icons/calls/group_calls_invited.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 665 B |
BIN
Telegram/Resources/icons/calls/group_calls_invited@2x.png
Normal file
BIN
Telegram/Resources/icons/calls/group_calls_invited@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/calls/group_calls_invited@3x.png
Normal file
BIN
Telegram/Resources/icons/calls/group_calls_invited@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1343,8 +1343,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_broadcast_silent_ph" = "Silent broadcast...";
|
||||
"lng_send_anonymous_ph" = "Send anonymously...";
|
||||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_record_lock_cancel" = "Click outside of the circle to cancel";
|
||||
"lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?";
|
||||
"lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?";
|
||||
"lng_record_lock_discard" = "Discard";
|
||||
"lng_will_be_notified" = "Members will be notified when you post";
|
||||
"lng_wont_be_notified" = "Members will not be notified when you post";
|
||||
@@ -1498,6 +1498,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_add_to_group" = "Add to group";
|
||||
|
||||
"lng_context_copy_link" = "Copy Link";
|
||||
"lng_context_copy_message_link" = "Copy Message Link";
|
||||
"lng_context_copy_post_link" = "Copy Post Link";
|
||||
"lng_context_copy_email" = "Copy Email Address";
|
||||
"lng_context_copy_hashtag" = "Copy Hashtag";
|
||||
@@ -1819,8 +1820,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_inactive" = "listening";
|
||||
"lng_group_call_settings" = "Settings";
|
||||
"lng_group_call_unmute" = "Unmute";
|
||||
"lng_group_call_unmute_sub" = "or hold spacebar to talk";
|
||||
"lng_group_call_you_are_live" = "You are Live";
|
||||
"lng_group_call_force_muted" = "You are muted";
|
||||
"lng_group_call_force_muted" = "Muted by admin";
|
||||
"lng_group_call_force_muted_sub" = "You are in Listen Only mode";
|
||||
"lng_group_call_connecting" = "Connecting...";
|
||||
"lng_group_call_leave" = "Leave";
|
||||
"lng_group_call_leave_title" = "Leave voice chat";
|
||||
@@ -1829,8 +1832,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_create_sure" = "Do you really want to start a voice chat in this group?";
|
||||
"lng_group_call_also_end" = "End voice chat";
|
||||
"lng_group_call_settings_title" = "Settings";
|
||||
"lng_group_call_invite" = "Invite Member";
|
||||
"lng_group_call_invited_status" = "invited";
|
||||
"lng_group_call_invite_title" = "Invite members";
|
||||
"lng_group_call_invite_button" = "Invite";
|
||||
"lng_group_call_add_to_group_one" = "{user} isn't a member of «{group}» yet. Add them to the group?";
|
||||
"lng_group_call_add_to_group_some" = "Some of those users aren't members of «{group}» yet. Add them to the group?";
|
||||
"lng_group_call_add_to_group_all" = "Those users aren't members of «{group}» yet. Add them to the group?";
|
||||
"lng_group_call_invite_members" = "Group members";
|
||||
"lng_group_call_invite_search_results" = "Search results";
|
||||
"lng_group_call_new_muted" = "Mute new members";
|
||||
"lng_group_call_speakers" = "Speakers";
|
||||
"lng_group_call_microphone" = "Microphone";
|
||||
@@ -1847,8 +1857,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_invite_done_many#one" = "You invited **{count} member** to the voice chat.";
|
||||
"lng_group_call_invite_done_many#other" = "You invited **{count} members** to the voice chat.";
|
||||
"lng_group_call_no_members" = "click to join";
|
||||
"lng_group_call_members#one" = "{count} member";
|
||||
"lng_group_call_members#other" = "{count} members";
|
||||
"lng_group_call_members#one" = "{count} participant";
|
||||
"lng_group_call_members#other" = "{count} participants";
|
||||
"lng_group_call_no_anonymous" = "Sorry, anonymous group admins can't join voice chats.";
|
||||
"lng_group_call_too_many" = "Sorry, there are too many members in this voice chat. Please try again later.";
|
||||
"lng_group_call_context_mute" = "Mute";
|
||||
|
||||
@@ -6,5 +6,8 @@
|
||||
<file alias="call_end.mp3">../../sounds/call_end.mp3</file>
|
||||
<file alias="call_incoming.mp3">../../sounds/call_incoming.mp3</file>
|
||||
<file alias="call_outgoing.mp3">../../sounds/call_outgoing.mp3</file>
|
||||
<file alias="group_call_start.mp3">../../sounds/group_call_start.mp3</file>
|
||||
<file alias="group_call_connect.mp3">../../sounds/group_call_connect.mp3</file>
|
||||
<file alias="group_call_end.mp3">../../sounds/group_call_end.mp3</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
BIN
Telegram/Resources/sounds/group_call_connect.mp3
Normal file
BIN
Telegram/Resources/sounds/group_call_connect.mp3
Normal file
Binary file not shown.
BIN
Telegram/Resources/sounds/group_call_end.mp3
Normal file
BIN
Telegram/Resources/sounds/group_call_end.mp3
Normal file
Binary file not shown.
BIN
Telegram/Resources/sounds/group_call_start.mp3
Normal file
BIN
Telegram/Resources/sounds/group_call_start.mp3
Normal file
Binary file not shown.
@@ -63,7 +63,7 @@ inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMe
|
||||
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
|
||||
inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia;
|
||||
inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
|
||||
inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaDocument#33473058 flags:# id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia;
|
||||
inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
|
||||
inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
@@ -122,12 +122,12 @@ userStatusLastWeek#7bf09fc = UserStatus;
|
||||
userStatusLastMonth#77ebc742 = UserStatus;
|
||||
|
||||
chatEmpty#9ba2d800 id:int = Chat;
|
||||
chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
|
||||
chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
|
||||
chatForbidden#7328bdb id:int title:string = Chat;
|
||||
channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
|
||||
channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
|
||||
|
||||
chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull;
|
||||
chatFull#dc8c181 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall = ChatFull;
|
||||
channelFull#ef3a6acd flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull;
|
||||
|
||||
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
|
||||
@@ -365,8 +365,9 @@ updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update;
|
||||
updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update;
|
||||
updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector<int> pts:int pts_count:int = Update;
|
||||
updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector<int> pts:int pts_count:int = Update;
|
||||
updateChat#1330a196 chat_id:int = Update;
|
||||
updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector<GroupCallParticipant> version:int = Update;
|
||||
updateGroupCall#5724806e channel_id:int call:GroupCall = Update;
|
||||
updateGroupCall#a45eb99b chat_id:int call:GroupCall = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -547,7 +548,7 @@ inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
|
||||
inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
|
||||
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
|
||||
|
||||
stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
|
||||
stickerSet#40e237a8 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
|
||||
|
||||
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
|
||||
|
||||
@@ -1194,7 +1195,7 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2
|
||||
|
||||
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
|
||||
|
||||
groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant;
|
||||
groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant;
|
||||
|
||||
phone.groupCall#66ab0bfc call:GroupCall participants:Vector<GroupCallParticipant> participants_next_offset:string users:Vector<User> = phone.GroupCall;
|
||||
|
||||
@@ -1560,7 +1561,7 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati
|
||||
phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
|
||||
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
|
||||
phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
|
||||
phone.createGroupCall#e428fa02 channel:InputChannel random_id:int = Updates;
|
||||
phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates;
|
||||
phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates;
|
||||
phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
|
||||
phone.editGroupCallMember#63146ae4 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser = Updates;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.4.13.0" />
|
||||
Version="2.5.5.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,4,13,0
|
||||
PRODUCTVERSION 2,4,13,0
|
||||
FILEVERSION 2,5,5,0
|
||||
PRODUCTVERSION 2,5,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.4.13.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "FileVersion", "2.5.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.4.13.0"
|
||||
VALUE "ProductVersion", "2.5.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,4,13,0
|
||||
PRODUCTVERSION 2,4,13,0
|
||||
FILEVERSION 2,5,5,0
|
||||
PRODUCTVERSION 2,5,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.4.13.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "FileVersion", "2.5.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.4.13.0"
|
||||
VALUE "ProductVersion", "2.5.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -125,13 +125,20 @@ void Authorizations::requestTerminate(
|
||||
Fn<void(const MTPBool &result)> &&done,
|
||||
Fn<void(const RPCError &error)> &&fail,
|
||||
std::optional<uint64> hash) {
|
||||
auto request = hash
|
||||
? MTPaccount_ResetAuthorization(MTP_long(*hash))
|
||||
: MTPaccount_ResetAuthorization();
|
||||
_api.request(std::move(request))
|
||||
.done(std::move(done))
|
||||
.fail(std::move(fail))
|
||||
.send();
|
||||
const auto send = [&](auto request) {
|
||||
_api.request(
|
||||
std::move(request)
|
||||
).done(
|
||||
std::move(done)
|
||||
).fail(
|
||||
std::move(fail)
|
||||
).send();
|
||||
};
|
||||
if (hash) {
|
||||
send(MTPaccount_ResetAuthorization(MTP_long(*hash)));
|
||||
} else {
|
||||
send(MTPauth_ResetAuthorizations());
|
||||
}
|
||||
}
|
||||
|
||||
Authorizations::List Authorizations::list() const {
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kCancelTypingActionTimeout = crl::time(5000);
|
||||
constexpr auto kSetMyActionForMs = 10 * crl::time(1000);
|
||||
constexpr auto kSendMySpeakingInterval = 3 * crl::time(1000);
|
||||
constexpr auto kSendMyTypingInterval = 5 * crl::time(1000);
|
||||
constexpr auto kSendTypingsToOfflineFor = TimeId(30);
|
||||
|
||||
} // namespace
|
||||
@@ -82,12 +83,15 @@ bool SendProgressManager::updated(const Key &key, bool doing) {
|
||||
const auto now = crl::now();
|
||||
const auto i = _updated.find(key);
|
||||
if (doing) {
|
||||
const auto sendEach = (key.type == SendProgressType::Speaking)
|
||||
? kSendMySpeakingInterval
|
||||
: kSendMyTypingInterval;
|
||||
if (i == end(_updated)) {
|
||||
_updated.emplace(key, now + kSetMyActionForMs);
|
||||
} else if (i->second > now + (kSetMyActionForMs / 2)) {
|
||||
_updated.emplace(key, now + 2 * sendEach);
|
||||
} else if (i->second > now + sendEach) {
|
||||
return false;
|
||||
} else {
|
||||
i->second = now + kSetMyActionForMs;
|
||||
i->second = now + 2 * sendEach;
|
||||
}
|
||||
} else {
|
||||
if (i == end(_updated)) {
|
||||
|
||||
@@ -182,7 +182,8 @@ void SendExistingDocument(
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(0),
|
||||
document->mtpInput(),
|
||||
MTPint());
|
||||
MTPint(), // ttl_seconds
|
||||
MTPstring()); // query
|
||||
};
|
||||
SendExistingMedia(
|
||||
std::move(message),
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_folder.h"
|
||||
@@ -232,6 +233,27 @@ Updates::Updates(not_null<Main::Session*> session)
|
||||
)).done([=](const MTPupdates_State &result) {
|
||||
stateDone(result);
|
||||
}).send();
|
||||
|
||||
using namespace rpl::mappers;
|
||||
base::ObservableViewer(
|
||||
api().fullPeerUpdated()
|
||||
) | rpl::filter([](not_null<PeerData*> peer) {
|
||||
return peer->isChat() || peer->isMegagroup();
|
||||
}) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||
if (const auto users = _pendingSpeakingCallMembers.take(peer)) {
|
||||
if (const auto call = peer->groupCall()) {
|
||||
for (const auto [userId, when] : *users) {
|
||||
call->applyActiveUpdate(
|
||||
userId,
|
||||
Data::LastSpokeTimes{
|
||||
.anything = when,
|
||||
.voice = when
|
||||
},
|
||||
peer->owner().userLoaded(userId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
Main::Session &Updates::session() const {
|
||||
@@ -890,6 +912,58 @@ bool Updates::isQuitPrevent() {
|
||||
updateOnline();
|
||||
return true;
|
||||
}
|
||||
void Updates::handleSendActionUpdate(
|
||||
PeerId peerId,
|
||||
MsgId rootId,
|
||||
UserId userId,
|
||||
const MTPSendMessageAction &action) {
|
||||
const auto history = session().data().historyLoaded(peerId);
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
const auto peer = history->peer;
|
||||
const auto user = (userId == session().userId())
|
||||
? session().user().get()
|
||||
: session().data().userLoaded(userId);
|
||||
const auto isSpeakingInCall = (action.type()
|
||||
== mtpc_speakingInGroupCallAction);
|
||||
if (isSpeakingInCall) {
|
||||
if (!peer->isChat() && !peer->isChannel()) {
|
||||
return;
|
||||
}
|
||||
const auto call = peer->groupCall();
|
||||
const auto now = crl::now();
|
||||
if (call) {
|
||||
call->applyActiveUpdate(
|
||||
userId,
|
||||
Data::LastSpokeTimes{ .anything = now, .voice = now },
|
||||
user);
|
||||
} else {
|
||||
const auto chat = peer->asChat();
|
||||
const auto channel = peer->asChannel();
|
||||
const auto active = chat
|
||||
? (chat->flags() & MTPDchat::Flag::f_call_active)
|
||||
: (channel->flags() & MTPDchannel::Flag::f_call_active);
|
||||
if (active) {
|
||||
_pendingSpeakingCallMembers.emplace(
|
||||
peer).first->second[userId] = now;
|
||||
session().api().requestFullPeer(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!user || user->isSelf()) {
|
||||
return;
|
||||
}
|
||||
const auto when = requestingDifference()
|
||||
? 0
|
||||
: base::unixtime::now();
|
||||
session().data().registerSendAction(
|
||||
history,
|
||||
rootId,
|
||||
user,
|
||||
action,
|
||||
when);
|
||||
}
|
||||
|
||||
void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
switch (updates.type()) {
|
||||
@@ -1580,57 +1654,29 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateUserTyping: {
|
||||
auto &d = update.c_updateUserTyping();
|
||||
const auto userId = peerFromUser(d.vuser_id());
|
||||
const auto history = session().data().historyLoaded(userId);
|
||||
const auto user = session().data().userLoaded(d.vuser_id().v);
|
||||
if (history && user) {
|
||||
const auto when = requestingDifference() ? 0 : base::unixtime::now();
|
||||
session().data().registerSendAction(
|
||||
history,
|
||||
MsgId(),
|
||||
user,
|
||||
d.vaction(),
|
||||
when);
|
||||
}
|
||||
handleSendActionUpdate(
|
||||
peerFromUser(d.vuser_id()),
|
||||
0,
|
||||
d.vuser_id().v,
|
||||
d.vaction());
|
||||
} break;
|
||||
|
||||
case mtpc_updateChatUserTyping: {
|
||||
auto &d = update.c_updateChatUserTyping();
|
||||
const auto history = session().data().historyLoaded(
|
||||
peerFromChat(d.vchat_id()));
|
||||
const auto user = (d.vuser_id().v == session().userId())
|
||||
? nullptr
|
||||
: session().data().userLoaded(d.vuser_id().v);
|
||||
if (history && user) {
|
||||
const auto when = requestingDifference() ? 0 : base::unixtime::now();
|
||||
session().data().registerSendAction(
|
||||
history,
|
||||
MsgId(),
|
||||
user,
|
||||
d.vaction(),
|
||||
when);
|
||||
}
|
||||
handleSendActionUpdate(
|
||||
peerFromChat(d.vchat_id()),
|
||||
0,
|
||||
d.vuser_id().v,
|
||||
d.vaction());
|
||||
} break;
|
||||
|
||||
case mtpc_updateChannelUserTyping: {
|
||||
const auto &d = update.c_updateChannelUserTyping();
|
||||
const auto history = session().data().historyLoaded(
|
||||
peerFromChannel(d.vchannel_id()));
|
||||
const auto user = (d.vuser_id().v == session().userId())
|
||||
? nullptr
|
||||
: session().data().userLoaded(d.vuser_id().v);
|
||||
if (history && user) {
|
||||
const auto when = requestingDifference()
|
||||
? 0
|
||||
: base::unixtime::now();
|
||||
const auto rootId = d.vtop_msg_id().value_or_empty();
|
||||
session().data().registerSendAction(
|
||||
history,
|
||||
rootId,
|
||||
user,
|
||||
d.vaction(),
|
||||
when);
|
||||
}
|
||||
handleSendActionUpdate(
|
||||
peerFromChannel(d.vchannel_id()),
|
||||
d.vtop_msg_id().value_or_empty(),
|
||||
d.vuser_id().v,
|
||||
d.vaction());
|
||||
} break;
|
||||
|
||||
case mtpc_updateChatParticipants: {
|
||||
@@ -1799,15 +1845,6 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
case mtpc_updatePhoneCallSignalingData:
|
||||
case mtpc_updateGroupCallParticipants:
|
||||
case mtpc_updateGroupCall: {
|
||||
if (update.type() == mtpc_updateGroupCall) {
|
||||
const auto &data = update.c_updateGroupCall();
|
||||
if (data.vcall().type() == mtpc_groupCallDiscarded) {
|
||||
const auto &call = data.vcall().c_groupCallDiscarded();
|
||||
session().data().groupCallDiscarded(
|
||||
call.vid().v,
|
||||
call.vduration().v);
|
||||
}
|
||||
}
|
||||
Core::App().calls().handleUpdate(&session(), update);
|
||||
} break;
|
||||
|
||||
|
||||
@@ -122,6 +122,12 @@ private:
|
||||
base::flat_map<not_null<ChannelData*>, crl::time> &whenMap,
|
||||
crl::time &curTime);
|
||||
|
||||
void handleSendActionUpdate(
|
||||
PeerId peerId,
|
||||
MsgId rootId,
|
||||
UserId userId,
|
||||
const MTPSendMessageAction &action);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
int32 _updatesDate = 0;
|
||||
@@ -160,6 +166,9 @@ private:
|
||||
bool _handlingChannelDifference = false;
|
||||
|
||||
base::flat_map<int, ActiveChatTracker> _activeChats;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
base::flat_map<UserId, crl::time>> _pendingSpeakingCallMembers;
|
||||
|
||||
mtpRequestId _onlineRequest = 0;
|
||||
base::Timer _idleFinishTimer;
|
||||
|
||||
@@ -3412,7 +3412,7 @@ void ApiWrap::jumpToHistoryDate(not_null<PeerData*> peer, const QDate &date) {
|
||||
// requestMessageAfterDate(feed, date, [=](Data::MessagePosition result) {
|
||||
// Ui::hideLayer();
|
||||
// App::wnd()->sessionController()->showSection(
|
||||
// HistoryFeed::Memento(feed, result));
|
||||
// std::make_shared<HistoryFeed::Memento>(feed, result));
|
||||
// });
|
||||
//}
|
||||
|
||||
@@ -3458,7 +3458,8 @@ void ApiWrap::checkForUnreadMentions(
|
||||
|
||||
void ApiWrap::addChatParticipants(
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<not_null<UserData*>> &users) {
|
||||
const std::vector<not_null<UserData*>> &users,
|
||||
Fn<void(bool)> done) {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
for (const auto user : users) {
|
||||
request(MTPmessages_AddChatUser(
|
||||
@@ -3467,8 +3468,10 @@ void ApiWrap::addChatParticipants(
|
||||
MTP_int(kForwardMessagesOnAdd)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
if (done) done(true);
|
||||
}).fail([=](const RPCError &error) {
|
||||
ShowAddParticipantsError(error.type(), peer, { 1, user });
|
||||
if (done) done(false);
|
||||
}).afterDelay(crl::time(5)).send();
|
||||
}
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
@@ -3480,14 +3483,17 @@ void ApiWrap::addChatParticipants(
|
||||
auto list = QVector<MTPInputUser>();
|
||||
list.reserve(qMin(int(users.size()), int(kMaxUsersPerInvite)));
|
||||
const auto send = [&] {
|
||||
const auto callback = base::take(done);
|
||||
request(MTPchannels_InviteToChannel(
|
||||
channel->inputChannel,
|
||||
MTP_vector<MTPInputUser>(list)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
requestParticipantsCountDelayed(channel);
|
||||
if (callback) callback(true);
|
||||
}).fail([=](const RPCError &error) {
|
||||
ShowAddParticipantsError(error.type(), peer, users);
|
||||
if (callback) callback(false);
|
||||
}).afterDelay(crl::time(5)).send();
|
||||
};
|
||||
for (const auto user : users) {
|
||||
@@ -4639,7 +4645,8 @@ void ApiWrap::uploadAlbumMedia(
|
||||
fields.vid(),
|
||||
fields.vaccess_hash(),
|
||||
fields.vfile_reference()),
|
||||
MTP_int(data.vttl_seconds().value_or_empty()));
|
||||
MTP_int(data.vttl_seconds().value_or_empty()),
|
||||
MTPstring()); // query
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
} break;
|
||||
}
|
||||
|
||||
@@ -366,7 +366,8 @@ public:
|
||||
Fn<void()> callbackNotModified = nullptr);
|
||||
void addChatParticipants(
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<not_null<UserData*>> &users);
|
||||
const std::vector<not_null<UserData*>> &users,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
|
||||
rpl::producer<SendAction> sendActions() const {
|
||||
return _sendActions.events();
|
||||
|
||||
@@ -632,7 +632,7 @@ void GroupInfoBox::submit() {
|
||||
not_null<PeerListBox*> box) {
|
||||
auto create = [box, title, weak] {
|
||||
if (weak) {
|
||||
auto rows = box->peerListCollectSelectedRows();
|
||||
auto rows = box->collectSelectedRows();
|
||||
if (!rows.empty()) {
|
||||
weak->createGroup(box, title, rows);
|
||||
}
|
||||
@@ -643,7 +643,8 @@ void GroupInfoBox::submit() {
|
||||
};
|
||||
Ui::show(
|
||||
Box<PeerListBox>(
|
||||
std::make_unique<AddParticipantsBoxController>(_navigation),
|
||||
std::make_unique<AddParticipantsBoxController>(
|
||||
&_navigation->session()),
|
||||
std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -801,7 +801,10 @@ void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
|
||||
|
||||
void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
deleteAndClear();
|
||||
// Don't make the clearing history so easy.
|
||||
if (!_wipeHistoryPeer) {
|
||||
deleteAndClear();
|
||||
}
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ auto ListFromMimeData(not_null<const QMimeData*> data) {
|
||||
if (result.error == Error::None) {
|
||||
return result;
|
||||
} else if (data->hasImage()) {
|
||||
auto image = Platform::GetClipboardImage();
|
||||
auto image = Platform::GetImageFromClipboard();
|
||||
if (image.isNull()) {
|
||||
image = qvariant_cast<QImage>(data->imageData());
|
||||
}
|
||||
|
||||
@@ -33,38 +33,36 @@ namespace {
|
||||
class PrivacyExceptionsBoxController : public ChatsListBoxController {
|
||||
public:
|
||||
PrivacyExceptionsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const std::vector<not_null<PeerData*>> &selected);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
std::vector<not_null<PeerData*>> getResult() const;
|
||||
|
||||
protected:
|
||||
void prepareViewHook() override;
|
||||
std::unique_ptr<Row> createRow(not_null<History*> history) override;
|
||||
|
||||
private:
|
||||
not_null<Window::SessionNavigation*> _navigation;
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::producer<QString> _title;
|
||||
std::vector<not_null<PeerData*>> _selected;
|
||||
|
||||
};
|
||||
|
||||
PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const std::vector<not_null<PeerData*>> &selected)
|
||||
: ChatsListBoxController(navigation)
|
||||
, _navigation(navigation)
|
||||
: ChatsListBoxController(session)
|
||||
, _session(session)
|
||||
, _title(std::move(title))
|
||||
, _selected(selected) {
|
||||
}
|
||||
|
||||
Main::Session &PrivacyExceptionsBoxController::session() const {
|
||||
return _navigation->session();
|
||||
return *_session;
|
||||
}
|
||||
|
||||
void PrivacyExceptionsBoxController::prepareViewHook() {
|
||||
@@ -72,10 +70,6 @@ void PrivacyExceptionsBoxController::prepareViewHook() {
|
||||
delegate()->peerListAddSelectedPeers(_selected);
|
||||
}
|
||||
|
||||
std::vector<not_null<PeerData*>> PrivacyExceptionsBoxController::getResult() const {
|
||||
return delegate()->peerListCollectSelectedRows();
|
||||
}
|
||||
|
||||
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto peer = row->peer();
|
||||
|
||||
@@ -146,13 +140,13 @@ void EditPrivacyBox::editExceptions(
|
||||
Exception exception,
|
||||
Fn<void()> done) {
|
||||
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
|
||||
_window,
|
||||
&_window->session(),
|
||||
_controller->exceptionBoxTitle(exception),
|
||||
exceptions(exception));
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
|
||||
exceptions(exception) = controller->getResult();
|
||||
exceptions(exception) = box->collectSelectedRows();
|
||||
const auto type = [&] {
|
||||
switch (exception) {
|
||||
case Exception::Always: return Exception::Never;
|
||||
|
||||
@@ -320,7 +320,7 @@ void EditExceptions(
|
||||
const auto include = (options & Flag::Contacts) != Flags(0);
|
||||
const auto rules = data->current();
|
||||
auto controller = std::make_unique<EditFilterChatsListController>(
|
||||
window,
|
||||
&window->session(),
|
||||
(include
|
||||
? tr::lng_filters_include_title()
|
||||
: tr::lng_filters_exclude_title()),
|
||||
@@ -331,7 +331,7 @@ void EditExceptions(
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setCloseByOutsideClick(false);
|
||||
box->addButton(tr::lng_settings_save(), crl::guard(context, [=] {
|
||||
const auto peers = box->peerListCollectSelectedRows();
|
||||
const auto peers = box->collectSelectedRows();
|
||||
const auto rules = data->current();
|
||||
auto &&histories = ranges::view::all(
|
||||
peers
|
||||
|
||||
@@ -68,7 +68,6 @@ public:
|
||||
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
|
||||
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
|
||||
int peerListSelectedRowsCount() override;
|
||||
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
|
||||
void peerListScrollToTop() override;
|
||||
void peerListAddSelectedPeerInBunch(
|
||||
not_null<PeerData*> peer) override;
|
||||
@@ -209,11 +208,6 @@ int TypeDelegate::peerListSelectedRowsCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto TypeDelegate::peerListCollectSelectedRows()
|
||||
-> std::vector<not_null<PeerData*>> {
|
||||
return {};
|
||||
}
|
||||
|
||||
void TypeDelegate::peerListScrollToTop() {
|
||||
}
|
||||
|
||||
@@ -347,13 +341,13 @@ void PaintFilterChatsTypeIcon(
|
||||
}
|
||||
|
||||
EditFilterChatsListController::EditFilterChatsListController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
Flags options,
|
||||
Flags selected,
|
||||
const base::flat_set<not_null<History*>> &peers)
|
||||
: ChatsListBoxController(navigation)
|
||||
, _navigation(navigation)
|
||||
: ChatsListBoxController(session)
|
||||
, _session(session)
|
||||
, _title(std::move(title))
|
||||
, _peers(peers)
|
||||
, _options(options)
|
||||
@@ -361,7 +355,7 @@ EditFilterChatsListController::EditFilterChatsListController(
|
||||
}
|
||||
|
||||
Main::Session &EditFilterChatsListController::session() const {
|
||||
return _navigation->session();
|
||||
return *_session;
|
||||
}
|
||||
|
||||
void EditFilterChatsListController::rowClicked(not_null<PeerListRow*> row) {
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
using Flags = Data::ChatFilter::Flags;
|
||||
|
||||
EditFilterChatsListController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
Flags options,
|
||||
Flags selected,
|
||||
@@ -64,7 +64,7 @@ private:
|
||||
|
||||
void updateTitle();
|
||||
|
||||
const not_null<Window::SessionNavigation*> _navigation;
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::producer<QString> _title;
|
||||
base::flat_set<not_null<History*>> _peers;
|
||||
Flags _options;
|
||||
|
||||
@@ -399,7 +399,7 @@ int PeerListBox::peerListSelectedRowsCount() {
|
||||
return _select ? _select->entity()->getItemsCount() : 0;
|
||||
}
|
||||
|
||||
auto PeerListBox::peerListCollectSelectedRows()
|
||||
auto PeerListBox::collectSelectedRows()
|
||||
-> std::vector<not_null<PeerData*>> {
|
||||
auto result = std::vector<not_null<PeerData*>>();
|
||||
auto items = _select
|
||||
@@ -982,6 +982,18 @@ void PeerListContent::setAboveWidget(object_ptr<TWidget> widget) {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::setAboveSearchWidget(object_ptr<TWidget> widget) {
|
||||
_aboveSearchWidget = std::move(widget);
|
||||
if (_aboveSearchWidget) {
|
||||
_aboveSearchWidget->setParent(this);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::setHideEmpty(bool hide) {
|
||||
_hideEmpty = hide;
|
||||
resizeToWidth(width());
|
||||
}
|
||||
|
||||
void PeerListContent::setBelowWidget(object_ptr<TWidget> widget) {
|
||||
_belowWidget = std::move(widget);
|
||||
if (_belowWidget) {
|
||||
@@ -990,6 +1002,9 @@ void PeerListContent::setBelowWidget(object_ptr<TWidget> widget) {
|
||||
}
|
||||
|
||||
int PeerListContent::labelHeight() const {
|
||||
if (_hideEmpty && !shownRowsCount()) {
|
||||
return 0;
|
||||
}
|
||||
auto computeLabelHeight = [](auto &label) {
|
||||
if (!label) {
|
||||
return 0;
|
||||
@@ -1082,34 +1097,45 @@ void PeerListContent::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
|
||||
int PeerListContent::resizeGetHeight(int newWidth) {
|
||||
const auto rowsCount = shownRowsCount();
|
||||
const auto hideAll = !rowsCount && _hideEmpty;
|
||||
_aboveHeight = 0;
|
||||
if (_aboveWidget) {
|
||||
_aboveWidget->resizeToWidth(newWidth);
|
||||
_aboveWidget->moveToLeft(0, 0, newWidth);
|
||||
if (showingSearch()) {
|
||||
if (hideAll || showingSearch()) {
|
||||
_aboveWidget->hide();
|
||||
} else {
|
||||
_aboveWidget->show();
|
||||
_aboveHeight = _aboveWidget->height();
|
||||
}
|
||||
}
|
||||
const auto rowsCount = shownRowsCount();
|
||||
if (_aboveSearchWidget) {
|
||||
_aboveSearchWidget->resizeToWidth(newWidth);
|
||||
_aboveSearchWidget->moveToLeft(0, 0, newWidth);
|
||||
if (hideAll || !showingSearch()) {
|
||||
_aboveSearchWidget->hide();
|
||||
} else {
|
||||
_aboveSearchWidget->show();
|
||||
_aboveHeight = _aboveSearchWidget->height();
|
||||
}
|
||||
}
|
||||
const auto labelTop = rowsTop() + qMax(1, shownRowsCount()) * _rowHeight;
|
||||
const auto labelWidth = newWidth - 2 * st::contactsPadding.left();
|
||||
if (_description) {
|
||||
_description->resizeToWidth(labelWidth);
|
||||
_description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
|
||||
_description->setVisible(!showingSearch());
|
||||
_description->setVisible(!hideAll && !showingSearch());
|
||||
}
|
||||
if (_searchNoResults) {
|
||||
_searchNoResults->resizeToWidth(labelWidth);
|
||||
_searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
|
||||
_searchNoResults->setVisible(showingSearch() && _filterResults.empty() && !_controller->isSearchLoading());
|
||||
_searchNoResults->setVisible(!hideAll && showingSearch() && _filterResults.empty() && !_controller->isSearchLoading());
|
||||
}
|
||||
if (_searchLoading) {
|
||||
_searchLoading->resizeToWidth(labelWidth);
|
||||
_searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
|
||||
_searchLoading->setVisible(showingSearch() && _filterResults.empty() && _controller->isSearchLoading());
|
||||
_searchLoading->setVisible(!hideAll && showingSearch() && _filterResults.empty() && _controller->isSearchLoading());
|
||||
}
|
||||
const auto label = labelHeight();
|
||||
const auto belowTop = (label > 0 || rowsCount > 0)
|
||||
@@ -1119,7 +1145,7 @@ int PeerListContent::resizeGetHeight(int newWidth) {
|
||||
if (_belowWidget) {
|
||||
_belowWidget->resizeToWidth(newWidth);
|
||||
_belowWidget->moveToLeft(0, belowTop, newWidth);
|
||||
if (showingSearch()) {
|
||||
if (hideAll || showingSearch()) {
|
||||
_belowWidget->hide();
|
||||
} else {
|
||||
_belowWidget->show();
|
||||
@@ -1203,33 +1229,56 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
|
||||
void PeerListContent::showRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
|
||||
showRowMenu(findRowIndex(row), QCursor::pos(), std::move(destroyed));
|
||||
}
|
||||
|
||||
bool PeerListContent::showRowMenu(
|
||||
RowIndex index,
|
||||
QPoint globalPos,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
|
||||
if (_contextMenu) {
|
||||
_contextMenu->deleteLater();
|
||||
_contextMenu->setDestroyedCallback(nullptr);
|
||||
_contextMenu = nullptr;
|
||||
}
|
||||
setContexted(Selected());
|
||||
if (e->reason() == QContextMenuEvent::Mouse) {
|
||||
handleMouseMove(e->globalPos());
|
||||
}
|
||||
|
||||
setContexted(_selected);
|
||||
if (_pressButton != Qt::LeftButton) {
|
||||
mousePressReleased(_pressButton);
|
||||
}
|
||||
|
||||
if (const auto row = getRow(_contexted.index)) {
|
||||
_contextMenu = _controller->rowContextMenu(this, row);
|
||||
if (_contextMenu) {
|
||||
_contextMenu->setDestroyedCallback(crl::guard(
|
||||
this,
|
||||
[this] {
|
||||
setContexted(Selected());
|
||||
handleMouseMove(QCursor::pos());
|
||||
}));
|
||||
_contextMenu->popup(e->globalPos());
|
||||
e->accept();
|
||||
}
|
||||
const auto row = getRow(index);
|
||||
if (!row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_contextMenu = _controller->rowContextMenu(this, row);
|
||||
const auto raw = _contextMenu.get();
|
||||
if (!raw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setContexted({ index, false });
|
||||
raw->setDestroyedCallback(crl::guard(
|
||||
this,
|
||||
[=] {
|
||||
setContexted(Selected());
|
||||
handleMouseMove(QCursor::pos());
|
||||
if (destroyed) {
|
||||
destroyed(raw);
|
||||
}
|
||||
}));
|
||||
raw->popup(globalPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
|
||||
if (e->reason() == QContextMenuEvent::Mouse) {
|
||||
handleMouseMove(e->globalPos());
|
||||
}
|
||||
if (showRowMenu(_selected.index, e->globalPos())) {
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1351,15 +1400,18 @@ crl::time PeerListContent::paintRow(
|
||||
return (refreshStatusAt - ms);
|
||||
}
|
||||
|
||||
void PeerListContent::selectSkip(int direction) {
|
||||
if (_pressed.index.value >= 0) {
|
||||
return;
|
||||
PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
|
||||
if (hasPressed()) {
|
||||
return { _selected.index.value, _selected.index.value };
|
||||
}
|
||||
_mouseSelection = false;
|
||||
_lastMousePosition = std::nullopt;
|
||||
|
||||
auto newSelectedIndex = _selected.index.value + direction;
|
||||
|
||||
auto result = SkipResult();
|
||||
result.shouldMoveTo = newSelectedIndex;
|
||||
|
||||
auto rowsCount = shownRowsCount();
|
||||
auto index = 0;
|
||||
auto firstEnabled = -1, lastEnabled = -1;
|
||||
@@ -1415,14 +1467,36 @@ void PeerListContent::selectSkip(int direction) {
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
_selectedIndex = _selected.index.value;
|
||||
result.reallyMovedTo = _selected.index.value;
|
||||
return result;
|
||||
}
|
||||
|
||||
void PeerListContent::selectSkipPage(int height, int direction) {
|
||||
auto rowsToSkip = height / _rowHeight;
|
||||
if (!rowsToSkip) return;
|
||||
if (!rowsToSkip) {
|
||||
return;
|
||||
}
|
||||
selectSkip(rowsToSkip * direction);
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerListContent::selectedIndexValue() const {
|
||||
return _selectedIndex.value();
|
||||
}
|
||||
|
||||
bool PeerListContent::hasSelection() const {
|
||||
return _selected.index.value >= 0;
|
||||
}
|
||||
|
||||
bool PeerListContent::hasPressed() const {
|
||||
return _pressed.index.value >= 0;
|
||||
}
|
||||
|
||||
void PeerListContent::clearSelection() {
|
||||
setSelected(Selected());
|
||||
}
|
||||
|
||||
void PeerListContent::loadProfilePhotos() {
|
||||
if (_visibleTop >= _visibleBottom) return;
|
||||
|
||||
@@ -1569,14 +1643,17 @@ void PeerListContent::setSearchQuery(
|
||||
clearSearchRows();
|
||||
}
|
||||
|
||||
void PeerListContent::submitted() {
|
||||
bool PeerListContent::submitted() {
|
||||
if (const auto row = getRow(_selected.index)) {
|
||||
_controller->rowClicked(row);
|
||||
return true;
|
||||
} else if (showingSearch()) {
|
||||
if (const auto row = getRow(RowIndex(0))) {
|
||||
_controller->rowClicked(row);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PeerListContent::visibleTopBottomUpdated(
|
||||
@@ -1590,11 +1667,14 @@ void PeerListContent::visibleTopBottomUpdated(
|
||||
|
||||
void PeerListContent::setSelected(Selected selected) {
|
||||
updateRow(_selected.index);
|
||||
if (_selected != selected) {
|
||||
_selected = selected;
|
||||
updateRow(_selected.index);
|
||||
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
|
||||
if (_selected == selected) {
|
||||
return;
|
||||
}
|
||||
_selected = selected;
|
||||
updateRow(_selected.index);
|
||||
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
|
||||
|
||||
_selectedIndex = _selected.index.value;
|
||||
}
|
||||
|
||||
void PeerListContent::setContexted(Selected contexted) {
|
||||
|
||||
@@ -254,10 +254,12 @@ class PeerListDelegate {
|
||||
public:
|
||||
virtual void peerListSetTitle(rpl::producer<QString> title) = 0;
|
||||
virtual void peerListSetAdditionalTitle(rpl::producer<QString> title) = 0;
|
||||
virtual void peerListSetHideEmpty(bool hide) = 0;
|
||||
virtual void peerListSetDescription(object_ptr<Ui::FlatLabel> description) = 0;
|
||||
virtual void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) = 0;
|
||||
virtual void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) = 0;
|
||||
virtual void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) = 0;
|
||||
virtual void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) = 0;
|
||||
virtual void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) = 0;
|
||||
virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0;
|
||||
virtual void peerListAppendRow(std::unique_ptr<PeerListRow> row) = 0;
|
||||
@@ -298,8 +300,10 @@ public:
|
||||
peerListFinishSelectedRowsBunch();
|
||||
}
|
||||
|
||||
virtual void peerListShowRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) = 0;
|
||||
virtual int peerListSelectedRowsCount() = 0;
|
||||
virtual std::vector<not_null<PeerData*>> peerListCollectSelectedRows() = 0;
|
||||
virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
|
||||
virtual void peerListRestoreState(
|
||||
std::unique_ptr<PeerListState> state) = 0;
|
||||
@@ -499,13 +503,20 @@ public:
|
||||
QWidget *parent,
|
||||
not_null<PeerListController*> controller);
|
||||
|
||||
void selectSkip(int direction);
|
||||
struct SkipResult {
|
||||
int shouldMoveTo = 0;
|
||||
int reallyMovedTo = 0;
|
||||
};
|
||||
SkipResult selectSkip(int direction);
|
||||
void selectSkipPage(int height, int direction);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> selectedIndexValue() const;
|
||||
[[nodiscard]] bool hasSelection() const;
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
|
||||
void searchQueryChanged(QString query);
|
||||
void submitted();
|
||||
bool submitted();
|
||||
|
||||
// Interface for the controller.
|
||||
void appendRow(std::unique_ptr<PeerListRow> row);
|
||||
@@ -525,7 +536,9 @@ public:
|
||||
void setSearchLoading(object_ptr<Ui::FlatLabel> loading);
|
||||
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults);
|
||||
void setAboveWidget(object_ptr<TWidget> widget);
|
||||
void setAboveSearchWidget(object_ptr<TWidget> widget);
|
||||
void setBelowWidget(object_ptr<TWidget> width);
|
||||
void setHideEmpty(bool hide);
|
||||
void refreshRows();
|
||||
|
||||
void setSearchMode(PeerListSearchMode mode);
|
||||
@@ -547,6 +560,10 @@ public:
|
||||
std::unique_ptr<PeerListState> saveState() const;
|
||||
void restoreState(std::unique_ptr<PeerListState> state);
|
||||
|
||||
void showRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
|
||||
|
||||
auto scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
@@ -630,6 +647,11 @@ private:
|
||||
RowIndex findRowIndex(not_null<PeerListRow*> row, RowIndex hint = RowIndex());
|
||||
QRect getActiveActionRect(not_null<PeerListRow*> row, RowIndex index) const;
|
||||
|
||||
bool showRowMenu(
|
||||
RowIndex index,
|
||||
QPoint globalPos,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr);
|
||||
|
||||
crl::time paintRow(Painter &p, crl::time ms, RowIndex index);
|
||||
|
||||
void addRowEntry(not_null<PeerListRow*> row);
|
||||
@@ -667,6 +689,7 @@ private:
|
||||
Selected _selected;
|
||||
Selected _pressed;
|
||||
Selected _contexted;
|
||||
rpl::variable<int> _selectedIndex = -1;
|
||||
bool _mouseSelection = false;
|
||||
std::optional<QPoint> _lastMousePosition;
|
||||
Qt::MouseButton _pressButton = Qt::LeftButton;
|
||||
@@ -685,7 +708,9 @@ private:
|
||||
|
||||
int _aboveHeight = 0;
|
||||
int _belowHeight = 0;
|
||||
bool _hideEmpty = false;
|
||||
object_ptr<TWidget> _aboveWidget = { nullptr };
|
||||
object_ptr<TWidget> _aboveSearchWidget = { nullptr };
|
||||
object_ptr<TWidget> _belowWidget = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _description = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
|
||||
@@ -703,6 +728,9 @@ public:
|
||||
_content = content;
|
||||
}
|
||||
|
||||
void peerListSetHideEmpty(bool hide) override {
|
||||
_content->setHideEmpty(hide);
|
||||
}
|
||||
void peerListAppendRow(
|
||||
std::unique_ptr<PeerListRow> row) override {
|
||||
_content->appendRow(std::move(row));
|
||||
@@ -767,6 +795,9 @@ public:
|
||||
void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) override {
|
||||
_content->setAboveWidget(std::move(aboveWidget));
|
||||
}
|
||||
void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) override {
|
||||
_content->setAboveSearchWidget(std::move(aboveWidget));
|
||||
}
|
||||
void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) override {
|
||||
_content->setBelowWidget(std::move(belowWidget));
|
||||
}
|
||||
@@ -804,6 +835,11 @@ public:
|
||||
std::unique_ptr<PeerListState> state) override {
|
||||
_content->restoreState(std::move(state));
|
||||
}
|
||||
void peerListShowRowMenu(
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) override {
|
||||
_content->showRowMenu(row, std::move(destroyed));
|
||||
}
|
||||
|
||||
protected:
|
||||
not_null<PeerListContent*> content() const {
|
||||
@@ -824,6 +860,8 @@ public:
|
||||
std::unique_ptr<PeerListController> controller,
|
||||
Fn<void(not_null<PeerListBox*>)> init);
|
||||
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
||||
|
||||
void peerListSetTitle(rpl::producer<QString> title) override {
|
||||
setTitle(std::move(title));
|
||||
}
|
||||
@@ -840,7 +878,6 @@ public:
|
||||
anim::type animated) override;
|
||||
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
|
||||
int peerListSelectedRowsCount() override;
|
||||
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
|
||||
void peerListScrollToTop() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "history/history.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_session_controller.h" // onShowAddContact()
|
||||
#include "facades.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_profile.h"
|
||||
@@ -115,7 +115,8 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
[=] { controller->widget()->onShowAddContact(); });
|
||||
};
|
||||
return Box<PeerListBox>(
|
||||
std::make_unique<ContactsBoxController>(controller),
|
||||
std::make_unique<ContactsBoxController>(
|
||||
&sessionController->session()),
|
||||
std::move(delegate));
|
||||
}
|
||||
|
||||
@@ -159,9 +160,9 @@ void PeerListRowWithLink::paintAction(
|
||||
}
|
||||
|
||||
PeerListGlobalSearchController::PeerListGlobalSearchController(
|
||||
not_null<Window::SessionNavigation*> navigation)
|
||||
: _navigation(navigation)
|
||||
, _api(&_navigation->session().mtp()) {
|
||||
not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _api(&session->mtp()) {
|
||||
_timer.setCallback([this] { searchOnServer(); });
|
||||
}
|
||||
|
||||
@@ -210,8 +211,8 @@ void PeerListGlobalSearchController::searchDone(
|
||||
auto &contacts = result.c_contacts_found();
|
||||
auto query = _query;
|
||||
if (requestId) {
|
||||
_navigation->session().data().processUsers(contacts.vusers());
|
||||
_navigation->session().data().processChats(contacts.vchats());
|
||||
_session->data().processUsers(contacts.vusers());
|
||||
_session->data().processChats(contacts.vchats());
|
||||
auto it = _queries.find(requestId);
|
||||
if (it != _queries.cend()) {
|
||||
query = it->second;
|
||||
@@ -221,7 +222,7 @@ void PeerListGlobalSearchController::searchDone(
|
||||
}
|
||||
const auto feedList = [&](const MTPVector<MTPPeer> &list) {
|
||||
for (const auto &mtpPeer : list.v) {
|
||||
const auto peer = _navigation->session().data().peerLoaded(
|
||||
const auto peer = _session->data().peerLoaded(
|
||||
peerFromMTP(mtpPeer));
|
||||
if (peer) {
|
||||
delegate()->peerListSearchAddRow(peer);
|
||||
@@ -246,9 +247,9 @@ ChatsListBoxController::Row::Row(not_null<History*> history)
|
||||
}
|
||||
|
||||
ChatsListBoxController::ChatsListBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation)
|
||||
not_null<Main::Session*> session)
|
||||
: ChatsListBoxController(
|
||||
std::make_unique<PeerListGlobalSearchController>(navigation)) {
|
||||
std::make_unique<PeerListGlobalSearchController>(session)) {
|
||||
}
|
||||
|
||||
ChatsListBoxController::ChatsListBoxController(
|
||||
@@ -354,21 +355,21 @@ bool ChatsListBoxController::appendRow(not_null<History*> history) {
|
||||
}
|
||||
|
||||
ContactsBoxController::ContactsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation)
|
||||
: PeerListController(
|
||||
std::make_unique<PeerListGlobalSearchController>(navigation))
|
||||
, _navigation(navigation) {
|
||||
not_null<Main::Session*> session)
|
||||
: ContactsBoxController(
|
||||
session,
|
||||
std::make_unique<PeerListGlobalSearchController>(session)) {
|
||||
}
|
||||
|
||||
ContactsBoxController::ContactsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Main::Session*> session,
|
||||
std::unique_ptr<PeerListSearchController> searchController)
|
||||
: PeerListController(std::move(searchController))
|
||||
, _navigation(navigation) {
|
||||
, _session(session) {
|
||||
}
|
||||
|
||||
Main::Session &ContactsBoxController::session() const {
|
||||
return _navigation->session();
|
||||
return *_session;
|
||||
}
|
||||
|
||||
void ContactsBoxController::prepare() {
|
||||
@@ -435,26 +436,24 @@ bool ContactsBoxController::appendRow(not_null<UserData*> user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ContactsBoxController::createRow(not_null<UserData*> user) {
|
||||
std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
|
||||
not_null<UserData*> user) {
|
||||
return std::make_unique<PeerListRow>(user);
|
||||
}
|
||||
|
||||
void AddBotToGroupBoxController::Start(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot) {
|
||||
void AddBotToGroupBoxController::Start(not_null<UserData*> bot) {
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
|
||||
};
|
||||
Ui::show(Box<PeerListBox>(
|
||||
std::make_unique<AddBotToGroupBoxController>(navigation, bot),
|
||||
std::make_unique<AddBotToGroupBoxController>(bot),
|
||||
std::move(initBox)));
|
||||
}
|
||||
|
||||
AddBotToGroupBoxController::AddBotToGroupBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot)
|
||||
: ChatsListBoxController(SharingBotGame(bot)
|
||||
? std::make_unique<PeerListGlobalSearchController>(navigation)
|
||||
? std::make_unique<PeerListGlobalSearchController>(&bot->session())
|
||||
: nullptr)
|
||||
, _bot(bot) {
|
||||
}
|
||||
@@ -572,15 +571,15 @@ void AddBotToGroupBoxController::prepareViewHook() {
|
||||
}
|
||||
|
||||
ChooseRecipientBoxController::ChooseRecipientBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Main::Session*> session,
|
||||
FnMut<void(not_null<PeerData*>)> callback)
|
||||
: ChatsListBoxController(navigation)
|
||||
, _navigation(navigation)
|
||||
: ChatsListBoxController(session)
|
||||
, _session(session)
|
||||
, _callback(std::move(callback)) {
|
||||
}
|
||||
|
||||
Main::Session &ChooseRecipientBoxController::session() const {
|
||||
return _navigation->session();
|
||||
return *_session;
|
||||
}
|
||||
|
||||
void ChooseRecipientBoxController::prepareViewHook() {
|
||||
|
||||
@@ -32,7 +32,6 @@ class History;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
@@ -65,8 +64,7 @@ private:
|
||||
|
||||
class PeerListGlobalSearchController : public PeerListSearchController {
|
||||
public:
|
||||
PeerListGlobalSearchController(
|
||||
not_null<Window::SessionNavigation*> navigation);
|
||||
explicit PeerListGlobalSearchController(not_null<Main::Session*> session);
|
||||
|
||||
void searchQuery(const QString &query) override;
|
||||
bool isLoading() override;
|
||||
@@ -79,7 +77,7 @@ private:
|
||||
void searchOnServer();
|
||||
void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId);
|
||||
|
||||
const not_null<Window::SessionNavigation*> _navigation;
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
base::Timer _timer;
|
||||
QString _query;
|
||||
@@ -104,7 +102,7 @@ public:
|
||||
|
||||
};
|
||||
|
||||
ChatsListBoxController(not_null<Window::SessionNavigation*> navigation);
|
||||
ChatsListBoxController(not_null<Main::Session*> session);
|
||||
ChatsListBoxController(
|
||||
std::unique_ptr<PeerListSearchController> searchController);
|
||||
|
||||
@@ -127,15 +125,15 @@ private:
|
||||
|
||||
class ContactsBoxController : public PeerListController {
|
||||
public:
|
||||
explicit ContactsBoxController(not_null<Main::Session*> session);
|
||||
ContactsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation);
|
||||
ContactsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Main::Session*> session,
|
||||
std::unique_ptr<PeerListSearchController> searchController);
|
||||
|
||||
Main::Session &session() const override;
|
||||
[[nodiscard]] Main::Session &session() const override;
|
||||
void prepare() override final;
|
||||
std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override final;
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createSearchRow(
|
||||
not_null<PeerData*> peer) override final;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
protected:
|
||||
@@ -150,7 +148,7 @@ private:
|
||||
void checkForEmptyRows();
|
||||
bool appendRow(not_null<UserData*> user);
|
||||
|
||||
const not_null<Window::SessionNavigation*> _navigation;
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
};
|
||||
|
||||
@@ -158,13 +156,9 @@ class AddBotToGroupBoxController
|
||||
: public ChatsListBoxController
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
static void Start(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot);
|
||||
static void Start(not_null<UserData*> bot);
|
||||
|
||||
AddBotToGroupBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot);
|
||||
explicit AddBotToGroupBoxController(not_null<UserData*> bot);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
@@ -186,7 +180,7 @@ private:
|
||||
void shareBotGame(not_null<PeerData*> chat);
|
||||
void addBotToGroup(not_null<PeerData*> chat);
|
||||
|
||||
not_null<UserData*> _bot;
|
||||
const not_null<UserData*> _bot;
|
||||
|
||||
};
|
||||
|
||||
@@ -195,7 +189,7 @@ class ChooseRecipientBoxController
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
ChooseRecipientBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Main::Session*> session,
|
||||
FnMut<void(not_null<PeerData*>)> callback);
|
||||
|
||||
Main::Session &session() const override;
|
||||
@@ -210,7 +204,7 @@ protected:
|
||||
std::unique_ptr<Row> createRow(not_null<History*> history) override;
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionNavigation*> _navigation;
|
||||
const not_null<Main::Session*> _session;
|
||||
FnMut<void(not_null<PeerData*>)> _callback;
|
||||
|
||||
};
|
||||
|
||||
429
Telegram/SourceFiles/boxes/peer_lists_box.cpp
Normal file
429
Telegram/SourceFiles/boxes/peer_lists_box.cpp
Normal file
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peer_lists_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
PeerListsBox::PeerListsBox(
|
||||
QWidget*,
|
||||
std::vector<std::unique_ptr<PeerListController>> controllers,
|
||||
Fn<void(not_null<PeerListsBox*>)> init)
|
||||
: _lists(makeLists(std::move(controllers)))
|
||||
, _init(std::move(init)) {
|
||||
Expects(!_lists.empty());
|
||||
}
|
||||
|
||||
auto PeerListsBox::collectSelectedRows()
|
||||
-> std::vector<not_null<PeerData*>> {
|
||||
auto result = std::vector<not_null<PeerData*>>();
|
||||
auto items = _select
|
||||
? _select->entity()->getItems()
|
||||
: QVector<uint64>();
|
||||
if (!items.empty()) {
|
||||
result.reserve(items.size());
|
||||
const auto session = &firstController()->session();
|
||||
for (const auto itemId : items) {
|
||||
const auto foreign = [&] {
|
||||
for (const auto &list : _lists) {
|
||||
if (list.controller->isForeignRow(itemId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
if (!foreign) {
|
||||
result.push_back(session->data().peer(itemId));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
PeerListsBox::List PeerListsBox::makeList(
|
||||
std::unique_ptr<PeerListController> controller) {
|
||||
auto delegate = std::make_unique<Delegate>(this, controller.get());
|
||||
return {
|
||||
std::move(controller),
|
||||
std::move(delegate),
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<PeerListsBox::List> PeerListsBox::makeLists(
|
||||
std::vector<std::unique_ptr<PeerListController>> controllers) {
|
||||
auto result = std::vector<List>();
|
||||
result.reserve(controllers.size());
|
||||
for (auto &controller : controllers) {
|
||||
result.push_back(makeList(std::move(controller)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<PeerListController*> PeerListsBox::firstController() const {
|
||||
return _lists.front().controller.get();
|
||||
}
|
||||
|
||||
void PeerListsBox::createMultiSelect() {
|
||||
Expects(_select == nullptr);
|
||||
|
||||
auto entity = object_ptr<Ui::MultiSelect>(
|
||||
this,
|
||||
(firstController()->selectSt()
|
||||
? *firstController()->selectSt()
|
||||
: st::defaultMultiSelect),
|
||||
tr::lng_participant_filter());
|
||||
_select.create(this, std::move(entity));
|
||||
_select->heightValue(
|
||||
) | rpl::start_with_next(
|
||||
[this] { updateScrollSkips(); },
|
||||
lifetime());
|
||||
_select->entity()->setSubmittedCallback([=](Qt::KeyboardModifiers) {
|
||||
for (const auto &list : _lists) {
|
||||
if (list.content->submitted()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
_select->entity()->setQueryChangedCallback([=](const QString &query) {
|
||||
searchQueryChanged(query);
|
||||
});
|
||||
_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
|
||||
for (const auto &list : _lists) {
|
||||
if (list.controller->handleDeselectForeignRow(itemId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto session = &firstController()->session();
|
||||
if (const auto peer = session->data().peerLoaded(itemId)) {
|
||||
const auto id = peer->id;
|
||||
for (const auto &list : _lists) {
|
||||
if (const auto row = list.delegate->peerListFindRow(id)) {
|
||||
list.content->changeCheckState(
|
||||
row,
|
||||
false,
|
||||
anim::type::normal);
|
||||
update();
|
||||
}
|
||||
list.controller->itemDeselectedHook(peer);
|
||||
}
|
||||
}
|
||||
});
|
||||
_select->resizeToWidth(firstController()->contentWidth());
|
||||
_select->moveToLeft(0, 0);
|
||||
}
|
||||
|
||||
int PeerListsBox::getTopScrollSkip() const {
|
||||
auto result = 0;
|
||||
if (_select && !_select->isHidden()) {
|
||||
result += _select->height();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void PeerListsBox::updateScrollSkips() {
|
||||
// If we show / hide the search field scroll top is fixed.
|
||||
// If we resize search field by bubbles scroll bottom is fixed.
|
||||
setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed);
|
||||
if (!_select->animating()) {
|
||||
_scrollBottomFixed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::prepare() {
|
||||
auto rows = setInnerWidget(
|
||||
object_ptr<Ui::VerticalLayout>(this),
|
||||
st::boxScroll);
|
||||
for (auto &list : _lists) {
|
||||
const auto content = rows->add(object_ptr<PeerListContent>(
|
||||
rows,
|
||||
list.controller.get()));
|
||||
list.content = content;
|
||||
list.delegate->setContent(content);
|
||||
list.controller->setDelegate(list.delegate.get());
|
||||
|
||||
content->scrollToRequests(
|
||||
) | rpl::start_with_next([=](Ui::ScrollToRequest request) {
|
||||
const auto skip = content->y();
|
||||
onScrollToY(
|
||||
skip + request.ymin,
|
||||
(request.ymax >= 0) ? (skip + request.ymax) : request.ymax);
|
||||
}, lifetime());
|
||||
|
||||
content->selectedIndexValue(
|
||||
) | rpl::filter([=](int index) {
|
||||
return (index >= 0);
|
||||
}) | rpl::start_with_next([=] {
|
||||
for (const auto &list : _lists) {
|
||||
if (list.content && list.content != content) {
|
||||
list.content->clearSelection();
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
rows->resizeToWidth(firstController()->contentWidth());
|
||||
|
||||
setDimensions(firstController()->contentWidth(), st::boxMaxListHeight);
|
||||
if (_select) {
|
||||
_select->finishAnimating();
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
_scrollBottomFixed = true;
|
||||
onScrollToY(0);
|
||||
}
|
||||
|
||||
if (_init) {
|
||||
_init(this);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::keyPressEvent(QKeyEvent *e) {
|
||||
const auto skipRows = [&](int rows) {
|
||||
if (rows == 0) {
|
||||
return;
|
||||
}
|
||||
for (const auto &list : _lists) {
|
||||
if (list.content->hasPressed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto from = begin(_lists), till = end(_lists);
|
||||
auto i = from;
|
||||
for (; i != till; ++i) {
|
||||
if (i->content->hasSelection()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == till && rows < 0) {
|
||||
return;
|
||||
}
|
||||
if (rows > 0) {
|
||||
if (i == till) {
|
||||
i = from;
|
||||
}
|
||||
for (; i != till; ++i) {
|
||||
const auto result = i->content->selectSkip(rows);
|
||||
if (result.shouldMoveTo - result.reallyMovedTo >= rows) {
|
||||
continue;
|
||||
} else if (result.reallyMovedTo >= result.shouldMoveTo) {
|
||||
return;
|
||||
} else {
|
||||
rows = result.shouldMoveTo - result.reallyMovedTo;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (++i; i != from;) {
|
||||
const auto result = (--i)->content->selectSkip(rows);
|
||||
if (result.shouldMoveTo - result.reallyMovedTo <= rows) {
|
||||
continue;
|
||||
} else if (result.reallyMovedTo <= result.shouldMoveTo) {
|
||||
return;
|
||||
} else {
|
||||
rows = result.shouldMoveTo - result.reallyMovedTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto rowsInPage = [&] {
|
||||
const auto rowHeight = firstController()->computeListSt().item.height;
|
||||
return height() / rowHeight;
|
||||
};
|
||||
if (e->key() == Qt::Key_Down) {
|
||||
skipRows(1);
|
||||
} else if (e->key() == Qt::Key_Up) {
|
||||
skipRows(-1);
|
||||
} else if (e->key() == Qt::Key_PageDown) {
|
||||
skipRows(rowsInPage());
|
||||
} else if (e->key() == Qt::Key_PageUp) {
|
||||
skipRows(-rowsInPage());
|
||||
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
|
||||
_select->entity()->clearQuery();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::searchQueryChanged(const QString &query) {
|
||||
onScrollToY(0);
|
||||
for (const auto &list : _lists) {
|
||||
list.content->searchQueryChanged(query);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
if (_select) {
|
||||
_select->resizeToWidth(width());
|
||||
_select->moveToLeft(0, 0);
|
||||
|
||||
updateScrollSkips();
|
||||
}
|
||||
|
||||
for (const auto &list : _lists) {
|
||||
list.content->resizeToWidth(width());
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
const auto &bg = (firstController()->listSt()
|
||||
? *firstController()->listSt()
|
||||
: st::peerListBox).bg;
|
||||
for (const auto rect : e->region()) {
|
||||
p.fillRect(rect, bg);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::setInnerFocus() {
|
||||
if (!_select || !_select->toggled()) {
|
||||
_lists.front().content->setFocus();
|
||||
} else {
|
||||
_select->entity()->setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
PeerListsBox::Delegate::Delegate(
|
||||
not_null<PeerListsBox*> box,
|
||||
not_null<PeerListController*> controller)
|
||||
: _box(box)
|
||||
, _controller(controller) {
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListSetTitle(rpl::producer<QString> title) {
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListSetAdditionalTitle(
|
||||
rpl::producer<QString> title) {
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListSetRowChecked(
|
||||
not_null<PeerListRow*> row,
|
||||
bool checked) {
|
||||
if (checked) {
|
||||
_box->addSelectItem(row, anim::type::normal);
|
||||
PeerListContentDelegate::peerListSetRowChecked(row, checked);
|
||||
peerListUpdateRow(row);
|
||||
|
||||
// This call deletes row from _searchRows.
|
||||
_box->_select->entity()->clearQuery();
|
||||
} else {
|
||||
// The itemRemovedCallback will call changeCheckState() here.
|
||||
_box->_select->entity()->removeItem(row->id());
|
||||
peerListUpdateRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListSetForeignRowChecked(
|
||||
not_null<PeerListRow*> row,
|
||||
bool checked,
|
||||
anim::type animated) {
|
||||
if (checked) {
|
||||
_box->addSelectItem(row, animated);
|
||||
|
||||
// This call deletes row from _searchRows.
|
||||
_box->_select->entity()->clearQuery();
|
||||
} else {
|
||||
// The itemRemovedCallback will call changeCheckState() here.
|
||||
_box->_select->entity()->removeItem(row->id());
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListScrollToTop() {
|
||||
_box->onScrollToY(0);
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListSetSearchMode(PeerListSearchMode mode) {
|
||||
PeerListContentDelegate::peerListSetSearchMode(mode);
|
||||
_box->setSearchMode(mode);
|
||||
}
|
||||
|
||||
void PeerListsBox::setSearchMode(PeerListSearchMode mode) {
|
||||
auto selectVisible = (mode != PeerListSearchMode::Disabled);
|
||||
if (selectVisible && !_select) {
|
||||
createMultiSelect();
|
||||
_select->toggle(!selectVisible, anim::type::instant);
|
||||
}
|
||||
if (_select) {
|
||||
_select->toggle(selectVisible, anim::type::normal);
|
||||
_scrollBottomFixed = false;
|
||||
setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() {
|
||||
Expects(_box->_select != nullptr);
|
||||
|
||||
_box->_select->entity()->finishItemsBunch();
|
||||
}
|
||||
|
||||
bool PeerListsBox::Delegate::peerListIsRowChecked(
|
||||
not_null<PeerListRow*> row) {
|
||||
return _box->_select
|
||||
? _box->_select->entity()->hasItem(row->id())
|
||||
: false;
|
||||
}
|
||||
|
||||
int PeerListsBox::Delegate::peerListSelectedRowsCount() {
|
||||
return _box->_select ? _box->_select->entity()->getItemsCount() : 0;
|
||||
}
|
||||
|
||||
void PeerListsBox::addSelectItem(
|
||||
not_null<PeerData*> peer,
|
||||
anim::type animated) {
|
||||
addSelectItem(
|
||||
peer->id,
|
||||
peer->shortName(),
|
||||
PaintUserpicCallback(peer, false),
|
||||
animated);
|
||||
}
|
||||
|
||||
void PeerListsBox::addSelectItem(
|
||||
not_null<PeerListRow*> row,
|
||||
anim::type animated) {
|
||||
addSelectItem(
|
||||
row->id(),
|
||||
row->generateShortName(),
|
||||
row->generatePaintUserpicCallback(),
|
||||
animated);
|
||||
}
|
||||
|
||||
void PeerListsBox::addSelectItem(
|
||||
uint64 itemId,
|
||||
const QString &text,
|
||||
Ui::MultiSelect::PaintRoundImage paintUserpic,
|
||||
anim::type animated) {
|
||||
if (!_select) {
|
||||
createMultiSelect();
|
||||
_select->hide(anim::type::instant);
|
||||
}
|
||||
const auto &activeBg = (firstController()->selectSt()
|
||||
? *firstController()->selectSt()
|
||||
: st::defaultMultiSelect).item.textActiveBg;
|
||||
if (animated == anim::type::instant) {
|
||||
_select->entity()->addItemInBunch(
|
||||
itemId,
|
||||
text,
|
||||
activeBg,
|
||||
std::move(paintUserpic));
|
||||
} else {
|
||||
_select->entity()->addItem(
|
||||
itemId,
|
||||
text,
|
||||
activeBg,
|
||||
std::move(paintUserpic));
|
||||
}
|
||||
}
|
||||
101
Telegram/SourceFiles/boxes/peer_lists_box.h
Normal file
101
Telegram/SourceFiles/boxes/peer_lists_box.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/peer_list_box.h"
|
||||
|
||||
class PeerListsBox : public Ui::BoxContent {
|
||||
public:
|
||||
PeerListsBox(
|
||||
QWidget*,
|
||||
std::vector<std::unique_ptr<PeerListController>> controllers,
|
||||
Fn<void(not_null<PeerListsBox*>)> init);
|
||||
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
class Delegate final : public PeerListContentDelegate {
|
||||
public:
|
||||
Delegate(
|
||||
not_null<PeerListsBox*> box,
|
||||
not_null<PeerListController*> controller);
|
||||
|
||||
void peerListSetTitle(rpl::producer<QString> title) override;
|
||||
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
|
||||
void peerListSetSearchMode(PeerListSearchMode mode) override;
|
||||
void peerListSetRowChecked(
|
||||
not_null<PeerListRow*> row,
|
||||
bool checked) override;
|
||||
void peerListSetForeignRowChecked(
|
||||
not_null<PeerListRow*> row,
|
||||
bool checked,
|
||||
anim::type animated) override;
|
||||
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
|
||||
int peerListSelectedRowsCount() override;
|
||||
void peerListScrollToTop() override;
|
||||
|
||||
void peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) override {
|
||||
_box->addSelectItem(peer, anim::type::instant);
|
||||
}
|
||||
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override {
|
||||
_box->addSelectItem(row, anim::type::instant);
|
||||
}
|
||||
void peerListFinishSelectedRowsBunch() override;
|
||||
|
||||
private:
|
||||
const not_null<PeerListsBox*> _box;
|
||||
const not_null<PeerListController*> _controller;
|
||||
|
||||
};
|
||||
struct List {
|
||||
std::unique_ptr<PeerListController> controller;
|
||||
std::unique_ptr<Delegate> delegate;
|
||||
PeerListContent *content = nullptr;
|
||||
};
|
||||
|
||||
friend class Delegate;
|
||||
|
||||
[[nodiscard]] List makeList(
|
||||
std::unique_ptr<PeerListController> controller);
|
||||
[[nodiscard]] std::vector<List> makeLists(
|
||||
std::vector<std::unique_ptr<PeerListController>> controllers);
|
||||
|
||||
[[nodiscard]] not_null<PeerListController*> firstController() const;
|
||||
|
||||
void addSelectItem(
|
||||
not_null<PeerData*> peer,
|
||||
anim::type animated);
|
||||
void addSelectItem(
|
||||
not_null<PeerListRow*> row,
|
||||
anim::type animated);
|
||||
void addSelectItem(
|
||||
uint64 itemId,
|
||||
const QString &text,
|
||||
PaintRoundImageCallback paintUserpic,
|
||||
anim::type animated);
|
||||
void setSearchMode(PeerListSearchMode mode);
|
||||
void createMultiSelect();
|
||||
int getTopScrollSkip() const;
|
||||
void updateScrollSkips();
|
||||
void searchQueryChanged(const QString &query);
|
||||
|
||||
object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
|
||||
|
||||
std::vector<List> _lists;
|
||||
Fn<void(PeerListsBox*)> _init;
|
||||
bool _scrollBottomFixed = false;
|
||||
|
||||
};
|
||||
@@ -51,28 +51,21 @@ base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
|
||||
} // namespace
|
||||
|
||||
AddParticipantsBoxController::AddParticipantsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation)
|
||||
: ContactsBoxController(
|
||||
navigation,
|
||||
std::make_unique<PeerListGlobalSearchController>(navigation)) {
|
||||
not_null<Main::Session*> session)
|
||||
: ContactsBoxController(session) {
|
||||
}
|
||||
|
||||
AddParticipantsBoxController::AddParticipantsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer)
|
||||
: AddParticipantsBoxController(
|
||||
navigation,
|
||||
peer,
|
||||
GetAlreadyInFromPeer(peer)) {
|
||||
}
|
||||
|
||||
AddParticipantsBoxController::AddParticipantsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
base::flat_set<not_null<UserData*>> &&alreadyIn)
|
||||
: ContactsBoxController(
|
||||
navigation,
|
||||
std::make_unique<PeerListGlobalSearchController>(navigation))
|
||||
: ContactsBoxController(&peer->session())
|
||||
, _peer(peer)
|
||||
, _alreadyIn(std::move(alreadyIn)) {
|
||||
subscribeToMigration();
|
||||
@@ -179,7 +172,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers(
|
||||
not_null<PeerListBox*> box) const {
|
||||
Expects(_peer != nullptr);
|
||||
|
||||
const auto rows = box->peerListCollectSelectedRows();
|
||||
const auto rows = box->collectSelectedRows();
|
||||
const auto users = ranges::view::all(
|
||||
rows
|
||||
) | ranges::view::transform([](not_null<PeerData*> peer) {
|
||||
@@ -198,9 +191,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers(
|
||||
void AddParticipantsBoxController::Start(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChatData*> chat) {
|
||||
auto controller = std::make_unique<AddParticipantsBoxController>(
|
||||
navigation,
|
||||
chat);
|
||||
auto controller = std::make_unique<AddParticipantsBoxController>(chat);
|
||||
const auto weak = controller.get();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_participant_invite(), [=] {
|
||||
@@ -223,7 +214,6 @@ void AddParticipantsBoxController::Start(
|
||||
base::flat_set<not_null<UserData*>> &&alreadyIn,
|
||||
bool justCreated) {
|
||||
auto controller = std::make_unique<AddParticipantsBoxController>(
|
||||
navigation,
|
||||
channel,
|
||||
std::move(alreadyIn));
|
||||
const auto weak = controller.get();
|
||||
|
||||
@@ -27,16 +27,16 @@ public:
|
||||
not_null<ChannelData*> channel,
|
||||
base::flat_set<not_null<UserData*>> &&alreadyIn);
|
||||
|
||||
explicit AddParticipantsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation);
|
||||
explicit AddParticipantsBoxController(not_null<Main::Session*> session);
|
||||
explicit AddParticipantsBoxController(not_null<PeerData*> peer);
|
||||
AddParticipantsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer);
|
||||
AddParticipantsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
base::flat_set<not_null<UserData*>> &&alreadyIn);
|
||||
|
||||
[[nodiscard]] not_null<PeerData*> peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void itemDeselectedHook(not_null<PeerData*> peer) override;
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ bool ParticipantsAdditionalData::canRemoveUser(
|
||||
if (canRestrictUser(user)) {
|
||||
return true;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return chat->invitedByMe.contains(user);
|
||||
return !user->isSelf() && chat->invitedByMe.contains(user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1212,6 +1212,9 @@ void ParticipantsBoxController::rebuildChatAdmins(
|
||||
return true;
|
||||
}();
|
||||
if (same) {
|
||||
if (!_allLoaded && !delegate()->peerListFullRowsCount()) {
|
||||
chatListReady();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1369,9 +1372,12 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto added = false;
|
||||
_additional.fillFromPeer();
|
||||
for (const auto user : info->lastParticipants) {
|
||||
appendRow(user);
|
||||
if (appendRow(user)) {
|
||||
added = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Don't count lastParticipants in _offset, because we don't know
|
||||
@@ -1383,7 +1389,7 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
|
||||
if (_onlineSorter) {
|
||||
_onlineSorter->sort();
|
||||
}
|
||||
return true;
|
||||
return added;
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
|
||||
@@ -1000,13 +1000,15 @@ void Controller::fillManageSection() {
|
||||
st::infoIconBlacklist);
|
||||
}
|
||||
if (hasRecentActions) {
|
||||
auto callback = [=] {
|
||||
_navigation->showSection(
|
||||
std::make_shared<AdminLog::SectionMemento>(channel));
|
||||
};
|
||||
AddButtonWithCount(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_recent_actions(),
|
||||
rpl::single(QString()), //Empty count.
|
||||
[=] {
|
||||
_navigation->showSection(AdminLog::SectionMemento(channel));
|
||||
},
|
||||
std::move(callback),
|
||||
st::infoIconRecentActions);
|
||||
}
|
||||
|
||||
|
||||
@@ -767,7 +767,7 @@ bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
||||
if (result.error == Ui::PreparedList::Error::None) {
|
||||
return result;
|
||||
} else if (data->hasImage()) {
|
||||
auto image = Platform::GetClipboardImage();
|
||||
auto image = Platform::GetImageFromClipboard();
|
||||
if (image.isNull()) {
|
||||
image = qvariant_cast<QImage>(data->imageData());
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ void SessionsContent::terminateAll() {
|
||||
auto callback = [=] {
|
||||
const auto reset = crl::guard(weak, [=] {
|
||||
_authorizations->cancelCurrentRequest();
|
||||
shortPollSessions();
|
||||
_authorizations->reload();
|
||||
});
|
||||
_authorizations->requestTerminate(
|
||||
[=](const MTPBool &result) { reset(); },
|
||||
|
||||
@@ -334,14 +334,20 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
_setHash = set.vhash().v;
|
||||
_setFlags = set.vflags().v;
|
||||
_setInstallDate = set.vinstalled_date().value_or(0);
|
||||
if (const auto thumb = set.vthumb()) {
|
||||
_setThumbnail = Images::FromPhotoSize(
|
||||
&_controller->session(),
|
||||
set,
|
||||
*thumb);
|
||||
} else {
|
||||
_setThumbnail = ImageWithLocation();
|
||||
}
|
||||
_setThumbnail = [&] {
|
||||
if (const auto thumbs = set.vthumbs()) {
|
||||
for (const auto &thumb : thumbs->v) {
|
||||
const auto result = Images::FromPhotoSize(
|
||||
&_controller->session(),
|
||||
set,
|
||||
thumb);
|
||||
if (result.location.valid()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImageWithLocation();
|
||||
}();
|
||||
const auto &sets = _controller->session().data().stickers().sets();
|
||||
const auto it = sets.find(_setId);
|
||||
if (it != sets.cend()) {
|
||||
|
||||
@@ -9,6 +9,7 @@ using "ui/basic.style";
|
||||
|
||||
using "ui/widgets/widgets.style";
|
||||
using "ui/layers/layers.style";
|
||||
using "ui/chat/chat.style"; // GroupCallUserpics
|
||||
using "window/window.style";
|
||||
|
||||
CallSignalBars {
|
||||
@@ -465,8 +466,8 @@ groupCallMembersListItem: PeerListItem(defaultPeerListItem) {
|
||||
}
|
||||
height: 52px;
|
||||
photoPosition: point(12px, 6px);
|
||||
namePosition: point(68px, 7px);
|
||||
statusPosition: point(68px, 26px);
|
||||
namePosition: point(63px, 7px);
|
||||
statusPosition: point(63px, 26px);
|
||||
photoSize: 40px;
|
||||
nameFg: groupCallMembersFg;
|
||||
nameFgChecked: groupCallMembersFg;
|
||||
@@ -477,11 +478,17 @@ groupCallMembersListItem: PeerListItem(defaultPeerListItem) {
|
||||
groupCallMembersList: PeerList(defaultPeerList) {
|
||||
bg: groupCallMembersBg;
|
||||
about: FlatLabel(defaultPeerListAbout) {
|
||||
textFg: groupCallMemberInactiveStatus;
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
}
|
||||
item: groupCallMembersListItem;
|
||||
}
|
||||
groupCallInviteDividerLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
}
|
||||
groupCallInviteDividerPadding: margins(17px, 7px, 17px, 7px);
|
||||
|
||||
groupCallInviteMembersList: PeerList(groupCallMembersList) {
|
||||
padding: margins(0px, 10px, 0px, 10px);
|
||||
item: PeerListItem(groupCallMembersListItem) {
|
||||
statusFg: groupCallMemberNotJoinedStatus;
|
||||
statusFgOver: groupCallMemberNotJoinedStatus;
|
||||
@@ -512,26 +519,36 @@ groupCallMultiSelect: MultiSelect(defaultMultiSelect) {
|
||||
}
|
||||
}
|
||||
|
||||
groupCallMembersHeader: 47px;
|
||||
groupCallMembersTop: 62px;
|
||||
groupCallTitleTop: 14px;
|
||||
groupCallSubtitleTop: 33px;
|
||||
|
||||
groupCallMembersMargin: margins(16px, 16px, 16px, 28px);
|
||||
groupCallAddMember: IconButton(defaultIconButton) {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
iconPosition: point(3px, 5px);
|
||||
icon: icon {{ "info_add_member", groupCallMemberInactiveIcon }};
|
||||
iconOver: icon {{ "info_add_member", groupCallMemberInactiveIcon }};
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 36px;
|
||||
groupCallAddMember: SettingsButton(defaultSettingsButton) {
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
textFgOver: groupCallMemberNotJoinedStatus;
|
||||
textBg: groupCallMembersBg;
|
||||
textBgOver: groupCallMembersBgOver;
|
||||
|
||||
font: semiboldFont;
|
||||
|
||||
height: 22px;
|
||||
padding: margins(63px, 17px, 22px, 11px);
|
||||
|
||||
ripple: groupCallRipple;
|
||||
}
|
||||
groupCallHeaderPosition: point(16px, 16px);
|
||||
groupCallHeaderLabel: FlatLabel(defaultFlatLabel) {
|
||||
groupCallAddMemberIcon: icon {{ "info_add_member", groupCallMemberInactiveIcon, point(0px, 3px) }};
|
||||
groupCallAddMemberIconLeft: 16px;
|
||||
groupCallSubtitleLabel: FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 18px;
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
}
|
||||
groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
|
||||
textFg: groupCallMembersFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: semiboldFont;
|
||||
linkFont: semiboldFont;
|
||||
linkFontOver: semiboldFont;
|
||||
font: font(semibold 14px);
|
||||
linkFont: font(semibold 14px);
|
||||
linkFontOver: font(semibold 14px);
|
||||
}
|
||||
}
|
||||
groupCallAddButtonPosition: point(10px, 7px);
|
||||
@@ -562,6 +579,8 @@ groupCallMemberColoredCrossLine: CrossLineAnimation(groupCallMemberInactiveCross
|
||||
fg: groupCallMemberMutedIcon;
|
||||
icon: icon {{ "calls/group_calls_unmuted", groupCallMemberActiveIcon }};
|
||||
}
|
||||
groupCallMemberInvited: icon {{ "calls/group_calls_invited", groupCallMemberInactiveIcon }};
|
||||
groupCallMemberInvitedPosition: point(2px, 12px);
|
||||
|
||||
groupCallSettings: CallButton(callMicrophoneMute) {
|
||||
button: IconButton(callButton) {
|
||||
@@ -584,12 +603,15 @@ groupCallHangup: CallButton(callHangup) {
|
||||
label: callButtonLabel;
|
||||
}
|
||||
groupCallButtonSkip: 43px;
|
||||
groupCallButtonBottomSkip: 134px;
|
||||
groupCallMuteBottomSkip: 149px;
|
||||
groupCallButtonBottomSkip: 145px;
|
||||
groupCallMuteBottomSkip: 160px;
|
||||
|
||||
groupCallTopBarUserpicSize: 28px;
|
||||
groupCallTopBarUserpicShift: 8px;
|
||||
groupCallTopBarUserpicStroke: 2px;
|
||||
groupCallTopBarUserpics: GroupCallUserpics {
|
||||
size: 28px;
|
||||
shift: 8px;
|
||||
stroke: 2px;
|
||||
align: align(left);
|
||||
}
|
||||
groupCallTopBarJoin: RoundButton(defaultActiveButton) {
|
||||
width: -26px;
|
||||
height: 26px;
|
||||
@@ -722,8 +744,8 @@ groupCallTitleCloseIconOver: icon {
|
||||
};
|
||||
groupCallTitle: WindowTitle(defaultWindowTitle) {
|
||||
height: 0px;
|
||||
bg: groupCallBg;
|
||||
bgActive: groupCallBg;
|
||||
bg: transparent;
|
||||
bgActive: transparent;
|
||||
fg: transparent;
|
||||
fgActive: transparent;
|
||||
minimize: IconButton(groupCallTitleButton) {
|
||||
@@ -750,16 +772,11 @@ groupCallTitle: WindowTitle(defaultWindowTitle) {
|
||||
closeIconActiveOver: groupCallTitleCloseIconOver;
|
||||
}
|
||||
|
||||
groupCallMajorBlobMinRadius: 2px;
|
||||
groupCallMajorBlobMaxRadius: 2px;
|
||||
groupCallMajorBlobIdleRadius: 2px;
|
||||
groupCallMajorBlobMaxRadius: 4px;
|
||||
|
||||
groupCallMinorBlobMinRadius: 3px;
|
||||
groupCallMinorBlobMaxRadius: 9px;
|
||||
|
||||
groupCallMajorBlobTopOffset: 0px;
|
||||
groupCallMinorBlobTopOffset: 6px;
|
||||
|
||||
groupCallBlobWidthAdditional: 40px;
|
||||
groupCallMinorBlobIdleRadius: 3px;
|
||||
groupCallMinorBlobMaxRadius: 12px;
|
||||
|
||||
callTopBarMuteCrossLine: CrossLineAnimation {
|
||||
fg: callBarFg;
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/calls_panel.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "facades.h"
|
||||
@@ -368,10 +369,10 @@ void Call::setupOutgoingVideo() {
|
||||
_errors.fire({ ErrorType::NoCamera });
|
||||
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
|
||||
} else if (_state.current() != State::Established
|
||||
&& state != started
|
||||
&& !_videoCapture) {
|
||||
&& (state != Webrtc::VideoState::Inactive)
|
||||
&& (started == Webrtc::VideoState::Inactive)) {
|
||||
_errors.fire({ ErrorType::NotStartedCall });
|
||||
_videoOutgoing->setState(started);
|
||||
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
|
||||
} else if (state != Webrtc::VideoState::Inactive
|
||||
&& _instance
|
||||
&& !_instance->supportsVideo()) {
|
||||
@@ -779,6 +780,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
sendSignalingData(bytes);
|
||||
});
|
||||
},
|
||||
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
|
||||
settings.callAudioBackend()),
|
||||
};
|
||||
if (Logs::DebugEnabled()) {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
@@ -951,20 +954,20 @@ void Call::setState(State state) {
|
||||
_startTime = crl::now();
|
||||
break;
|
||||
case State::ExchangingKeys:
|
||||
_delegate->playSound(Delegate::Sound::Connecting);
|
||||
_delegate->callPlaySound(Delegate::CallSound::Connecting);
|
||||
break;
|
||||
case State::Ended:
|
||||
_delegate->playSound(Delegate::Sound::Ended);
|
||||
_delegate->callPlaySound(Delegate::CallSound::Ended);
|
||||
[[fallthrough]];
|
||||
case State::EndedByOtherDevice:
|
||||
_delegate->callFinished(this);
|
||||
break;
|
||||
case State::Failed:
|
||||
_delegate->playSound(Delegate::Sound::Ended);
|
||||
_delegate->callPlaySound(Delegate::CallSound::Ended);
|
||||
_delegate->callFailed(this);
|
||||
break;
|
||||
case State::Busy:
|
||||
_delegate->playSound(Delegate::Sound::Busy);
|
||||
_delegate->callPlaySound(Delegate::CallSound::Busy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,15 +62,16 @@ public:
|
||||
virtual void callFailed(not_null<Call*> call) = 0;
|
||||
virtual void callRedial(not_null<Call*> call) = 0;
|
||||
|
||||
enum class Sound {
|
||||
enum class CallSound {
|
||||
Connecting,
|
||||
Busy,
|
||||
Ended,
|
||||
};
|
||||
virtual void playSound(Sound sound) = 0;
|
||||
virtual void callPlaySound(CallSound sound) = 0;
|
||||
virtual void callRequestPermissionsOrFail(
|
||||
Fn<void()> onSuccess,
|
||||
bool video) = 0;
|
||||
|
||||
virtual auto getVideoCapture()
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> = 0;
|
||||
|
||||
|
||||
@@ -12,17 +12,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lang/lang_hardcoded.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_session.h"
|
||||
#include "base/global_shortcuts.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
|
||||
#include <tgcalls/group/GroupInstanceImpl.h>
|
||||
|
||||
@@ -41,20 +44,31 @@ constexpr auto kMaxInvitePerSlice = 10;
|
||||
constexpr auto kCheckLastSpokeInterval = crl::time(1000);
|
||||
constexpr auto kCheckJoinedTimeout = 4 * crl::time(1000);
|
||||
constexpr auto kUpdateSendActionEach = crl::time(500);
|
||||
constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Webrtc::MediaDevices> CreateMediaDevices() {
|
||||
const auto &settings = Core::App().settings();
|
||||
return Webrtc::CreateMediaDevices(
|
||||
settings.callInputDeviceId(),
|
||||
settings.callOutputDeviceId(),
|
||||
settings.callVideoInputDeviceId());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GroupCall::GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> peer,
|
||||
const MTPInputGroupCall &inputCall)
|
||||
: _delegate(delegate)
|
||||
, _channel(channel)
|
||||
, _history(channel->owner().history(channel))
|
||||
, _api(&_channel->session().mtp())
|
||||
, _peer(peer)
|
||||
, _history(peer->owner().history(peer))
|
||||
, _api(&peer->session().mtp())
|
||||
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
|
||||
, _checkJoinedTimer([=] { checkJoined(); })
|
||||
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) {
|
||||
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
|
||||
, _connectingSoundTimer([=] { playConnectingSoundOnce(); })
|
||||
, _mediaDevices(CreateMediaDevices()) {
|
||||
_muted.value(
|
||||
) | rpl::combine_previous(
|
||||
) | rpl::start_with_next([=](MuteState previous, MuteState state) {
|
||||
@@ -70,8 +84,8 @@ GroupCall::GroupCall(
|
||||
|
||||
const auto id = inputCall.c_inputGroupCall().vid().v;
|
||||
if (id) {
|
||||
if (const auto call = _channel->call(); call && call->id() == id) {
|
||||
if (!_channel->canManageCall() && call->joinMuted()) {
|
||||
if (const auto call = _peer->groupCall(); call && call->id() == id) {
|
||||
if (!_peer->canManageGroupCall() && call->joinMuted()) {
|
||||
_muted = MuteState::ForceMuted;
|
||||
}
|
||||
}
|
||||
@@ -80,6 +94,22 @@ GroupCall::GroupCall(
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
|
||||
_mediaDevices->audioInputId(
|
||||
) | rpl::start_with_next([=](QString id) {
|
||||
_audioInputId = id;
|
||||
if (_instance) {
|
||||
_instance->setAudioInputDevice(id.toStdString());
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
_mediaDevices->audioOutputId(
|
||||
) | rpl::start_with_next([=](QString id) {
|
||||
_audioOutputId = id;
|
||||
if (_instance) {
|
||||
_instance->setAudioOutputDevice(id.toStdString());
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
GroupCall::~GroupCall() {
|
||||
@@ -108,9 +138,22 @@ void GroupCall::setState(State state) {
|
||||
}
|
||||
_state = state;
|
||||
|
||||
if (_state.current() == State::Joined && !_pushToTalkStarted) {
|
||||
_pushToTalkStarted = true;
|
||||
applyGlobalShortcutChanges();
|
||||
if (state == State::Joined) {
|
||||
stopConnectingSound();
|
||||
if (!_hadJoinedState) {
|
||||
_hadJoinedState = true;
|
||||
applyGlobalShortcutChanges();
|
||||
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
|
||||
}
|
||||
if (const auto call = _peer->groupCall(); call && call->id() == _id) {
|
||||
call->setInCall();
|
||||
}
|
||||
} else if (state == State::Connecting || state == State::Joining) {
|
||||
if (_hadJoinedState) {
|
||||
playConnectingSound();
|
||||
}
|
||||
} else {
|
||||
stopConnectingSound();
|
||||
}
|
||||
|
||||
if (false
|
||||
@@ -121,6 +164,10 @@ void GroupCall::setState(State state) {
|
||||
destroyController();
|
||||
}
|
||||
switch (state) {
|
||||
case State::HangingUp:
|
||||
case State::FailedHangingUp:
|
||||
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Ended);
|
||||
break;
|
||||
case State::Ended:
|
||||
_delegate->groupCallFinished(this);
|
||||
break;
|
||||
@@ -135,13 +182,29 @@ void GroupCall::setState(State state) {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::playConnectingSound() {
|
||||
if (_connectingSoundTimer.isActive()) {
|
||||
return;
|
||||
}
|
||||
playConnectingSoundOnce();
|
||||
_connectingSoundTimer.callEach(kPlayConnectingEach);
|
||||
}
|
||||
|
||||
void GroupCall::stopConnectingSound() {
|
||||
_connectingSoundTimer.cancel();
|
||||
}
|
||||
|
||||
void GroupCall::playConnectingSoundOnce() {
|
||||
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Connecting);
|
||||
}
|
||||
|
||||
void GroupCall::start() {
|
||||
_createRequestId = _api.request(MTPphone_CreateGroupCall(
|
||||
_channel->inputChannel,
|
||||
_peer->input,
|
||||
MTP_int(rand_value<int32>())
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_acceptFields = true;
|
||||
_channel->session().api().applyUpdates(result);
|
||||
_peer->session().api().applyUpdates(result);
|
||||
_acceptFields = false;
|
||||
}).fail([=](const RPCError &error) {
|
||||
LOG(("Call Error: Could not create, error: %1"
|
||||
@@ -157,7 +220,13 @@ void GroupCall::start() {
|
||||
|
||||
void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
||||
setState(State::Joining);
|
||||
_channel->setCall(inputCall);
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
chat->setGroupCall(inputCall);
|
||||
} else if (const auto group = _peer->asMegagroup()) {
|
||||
group->setGroupCall(inputCall);
|
||||
} else {
|
||||
Unexpected("Peer type in GroupCall::join.");
|
||||
}
|
||||
|
||||
inputCall.match([&](const MTPDinputGroupCall &data) {
|
||||
_id = data.vid().v;
|
||||
@@ -166,7 +235,7 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
||||
});
|
||||
|
||||
using Update = Data::GroupCall::ParticipantUpdate;
|
||||
_channel->call()->participantUpdated(
|
||||
_peer->groupCall()->participantUpdated(
|
||||
) | rpl::filter([=](const Update &update) {
|
||||
return (_instance != nullptr) && !update.now;
|
||||
}) | rpl::start_with_next([=](const Update &update) {
|
||||
@@ -174,6 +243,10 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
||||
|
||||
_instance->removeSsrcs({ update.was->ssrc });
|
||||
}, _lifetime);
|
||||
|
||||
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) {
|
||||
_peer = group;
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::rejoin() {
|
||||
@@ -229,7 +302,7 @@ void GroupCall::rejoin() {
|
||||
: State::Connecting);
|
||||
applySelfInCallLocally();
|
||||
maybeSendMutedUpdate(wasMuteState);
|
||||
_channel->session().api().applyUpdates(updates);
|
||||
_peer->session().api().applyUpdates(updates);
|
||||
}).fail([=](const RPCError &error) {
|
||||
const auto type = error.type();
|
||||
LOG(("Call Error: Could not join, error: %1").arg(type));
|
||||
@@ -255,13 +328,13 @@ void GroupCall::rejoin() {
|
||||
}
|
||||
|
||||
void GroupCall::applySelfInCallLocally() {
|
||||
const auto call = _channel->call();
|
||||
const auto call = _peer->groupCall();
|
||||
if (!call || call->id() != _id) {
|
||||
return;
|
||||
}
|
||||
using Flag = MTPDgroupCallParticipant::Flag;
|
||||
const auto &participants = call->participants();
|
||||
const auto self = _channel->session().user();
|
||||
const auto self = _peer->session().user();
|
||||
const auto i = ranges::find(
|
||||
participants,
|
||||
self,
|
||||
@@ -307,7 +380,7 @@ void GroupCall::discard() {
|
||||
// Here 'this' could be destroyed by updates, so we set Ended after
|
||||
// updates being handled, but in a guarded way.
|
||||
crl::on_main(this, [=] { hangup(); });
|
||||
_channel->session().api().applyUpdates(result);
|
||||
_peer->session().api().applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
hangup();
|
||||
}).send();
|
||||
@@ -338,7 +411,7 @@ void GroupCall::finish(FinishType type) {
|
||||
|
||||
// We want to leave request still being sent and processed even if
|
||||
// the call is already destroyed.
|
||||
const auto session = &_channel->session();
|
||||
const auto session = &_peer->session();
|
||||
const auto weak = base::make_weak(this);
|
||||
session->api().request(MTPphone_LeaveGroupCall(
|
||||
inputCall(),
|
||||
@@ -453,7 +526,7 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto self = _channel->session().userId();
|
||||
const auto self = _peer->session().userId();
|
||||
for (const auto &participant : data.vparticipants().v) {
|
||||
participant.match([&](const MTPDgroupCallParticipant &data) {
|
||||
if (data.vuser_id().v != self) {
|
||||
@@ -482,31 +555,35 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
|
||||
}
|
||||
|
||||
void GroupCall::createAndStartController() {
|
||||
using AudioLevels = std::vector<std::pair<uint32_t, float>>;
|
||||
|
||||
const auto &settings = Core::App().settings();
|
||||
|
||||
const auto weak = base::make_weak(this);
|
||||
const auto myLevel = std::make_shared<float>();
|
||||
const auto myLevel = std::make_shared<tgcalls::GroupLevelValue>();
|
||||
tgcalls::GroupInstanceDescriptor descriptor = {
|
||||
.config = tgcalls::GroupConfig{
|
||||
},
|
||||
.networkStateUpdated = [=](bool connected) {
|
||||
crl::on_main(weak, [=] { setInstanceConnected(connected); });
|
||||
},
|
||||
.audioLevelsUpdated = [=](const AudioLevels &data) {
|
||||
if (!data.empty()) {
|
||||
crl::on_main(weak, [=] { audioLevelsUpdated(data); });
|
||||
.audioLevelsUpdated = [=](const tgcalls::GroupLevelsUpdate &data) {
|
||||
const auto &updates = data.updates;
|
||||
if (updates.empty()) {
|
||||
return;
|
||||
} else if (updates.size() == 1 && !updates.front().ssrc) {
|
||||
const auto &value = updates.front().value;
|
||||
// Don't send many 0 while we're muted.
|
||||
if (myLevel->level == value.level
|
||||
&& myLevel->voice == value.voice) {
|
||||
return;
|
||||
}
|
||||
*myLevel = updates.front().value;
|
||||
}
|
||||
crl::on_main(weak, [=] { audioLevelsUpdated(data); });
|
||||
},
|
||||
.myAudioLevelUpdated = [=](float level) {
|
||||
if (*myLevel != level) { // Don't send many 0 while we're muted.
|
||||
*myLevel = level;
|
||||
crl::on_main(weak, [=] { myLevelUpdated(level); });
|
||||
}
|
||||
},
|
||||
.initialInputDeviceId = settings.callInputDeviceId().toStdString(),
|
||||
.initialOutputDeviceId = settings.callOutputDeviceId().toStdString(),
|
||||
.initialInputDeviceId = _audioInputId.toStdString(),
|
||||
.initialOutputDeviceId = _audioOutputId.toStdString(),
|
||||
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
|
||||
settings.callAudioBackend()),
|
||||
};
|
||||
if (Logs::DebugEnabled()) {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
@@ -526,6 +603,7 @@ void GroupCall::createAndStartController() {
|
||||
LOG(("Call Info: Creating group instance"));
|
||||
_instance = std::make_unique<tgcalls::GroupInstanceImpl>(
|
||||
std::move(descriptor));
|
||||
|
||||
updateInstanceMuteState();
|
||||
|
||||
//raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled());
|
||||
@@ -539,28 +617,32 @@ void GroupCall::updateInstanceMuteState() {
|
||||
&& state != MuteState::PushToTalk);
|
||||
}
|
||||
|
||||
void GroupCall::handleLevelsUpdated(
|
||||
gsl::span<const std::pair<std::uint32_t, float>> data) {
|
||||
Expects(!data.empty());
|
||||
void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) {
|
||||
Expects(!data.updates.empty());
|
||||
|
||||
auto check = false;
|
||||
auto checkNow = false;
|
||||
const auto now = crl::now();
|
||||
for (const auto &[ssrc, level] : data) {
|
||||
for (const auto &[ssrcOrZero, value] : data.updates) {
|
||||
const auto ssrc = ssrcOrZero ? ssrcOrZero : _mySsrc;
|
||||
const auto level = value.level;
|
||||
const auto voice = value.voice;
|
||||
const auto self = (ssrc == _mySsrc);
|
||||
_levelUpdates.fire(LevelUpdate{
|
||||
.ssrc = ssrc,
|
||||
.value = level,
|
||||
.voice = voice,
|
||||
.self = self
|
||||
});
|
||||
if (level <= kSpeakLevelThreshold) {
|
||||
continue;
|
||||
}
|
||||
if (self
|
||||
&& voice
|
||||
&& (!_lastSendProgressUpdate
|
||||
|| _lastSendProgressUpdate + kUpdateSendActionEach < now)) {
|
||||
_lastSendProgressUpdate = now;
|
||||
_channel->session().sendProgressManager().update(
|
||||
_peer->session().sendProgressManager().update(
|
||||
_history,
|
||||
Api::SendProgressType::Speaking);
|
||||
}
|
||||
@@ -568,13 +650,21 @@ void GroupCall::handleLevelsUpdated(
|
||||
check = true;
|
||||
const auto i = _lastSpoke.find(ssrc);
|
||||
if (i == _lastSpoke.end()) {
|
||||
_lastSpoke.emplace(ssrc, now);
|
||||
_lastSpoke.emplace(ssrc, Data::LastSpokeTimes{
|
||||
.anything = now,
|
||||
.voice = voice ? now : 0,
|
||||
});
|
||||
checkNow = true;
|
||||
} else {
|
||||
if (i->second + kCheckLastSpokeInterval / 3 <= now) {
|
||||
if ((i->second.anything + kCheckLastSpokeInterval / 3 <= now)
|
||||
|| (voice
|
||||
&& i->second.voice + kCheckLastSpokeInterval / 3 <= now)) {
|
||||
checkNow = true;
|
||||
}
|
||||
i->second = now;
|
||||
i->second.anything = now;
|
||||
if (voice) {
|
||||
i->second.voice = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkNow) {
|
||||
@@ -584,18 +674,8 @@ void GroupCall::handleLevelsUpdated(
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::myLevelUpdated(float level) {
|
||||
const auto pair = std::pair<std::uint32_t, float>{ _mySsrc, level };
|
||||
handleLevelsUpdated({ &pair, &pair + 1 });
|
||||
}
|
||||
|
||||
void GroupCall::audioLevelsUpdated(
|
||||
const std::vector<std::pair<std::uint32_t, float>> &data) {
|
||||
handleLevelsUpdated(gsl::make_span(data));
|
||||
}
|
||||
|
||||
void GroupCall::checkLastSpoke() {
|
||||
const auto real = _channel->call();
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real || real->id() != _id) {
|
||||
return;
|
||||
}
|
||||
@@ -605,7 +685,7 @@ void GroupCall::checkLastSpoke() {
|
||||
auto list = base::take(_lastSpoke);
|
||||
for (auto i = list.begin(); i != list.end();) {
|
||||
const auto [ssrc, when] = *i;
|
||||
if (when + kCheckLastSpokeInterval >= now) {
|
||||
if (when.anything + kCheckLastSpokeInterval >= now) {
|
||||
hasRecent = true;
|
||||
++i;
|
||||
} else {
|
||||
@@ -678,7 +758,7 @@ void GroupCall::sendMutedUpdate() {
|
||||
MTP_inputUserSelf()
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_updateMuteRequestId = 0;
|
||||
_channel->session().api().applyUpdates(result);
|
||||
_peer->session().api().applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
_updateMuteRequestId = 0;
|
||||
if (error.type() == u"GROUPCALL_FORBIDDEN"_q) {
|
||||
@@ -689,14 +769,20 @@ void GroupCall::sendMutedUpdate() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GroupCall::connectingValue() const {
|
||||
using namespace rpl::mappers;
|
||||
return _state.value() | rpl::map(
|
||||
_1 == State::Creating
|
||||
|| _1 == State::Joining
|
||||
|| _1 == State::Connecting
|
||||
) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
|
||||
if (_instance) {
|
||||
const auto id = deviceId.toStdString();
|
||||
if (input) {
|
||||
_instance->setAudioInputDevice(id);
|
||||
} else {
|
||||
_instance->setAudioOutputDevice(id);
|
||||
}
|
||||
if (input) {
|
||||
_mediaDevices->switchToAudioInput(deviceId);
|
||||
} else {
|
||||
_mediaDevices->switchToAudioOutput(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +797,7 @@ void GroupCall::toggleMute(not_null<UserData*> user, bool mute) {
|
||||
inputCall(),
|
||||
user->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_channel->session().api().applyUpdates(result);
|
||||
_peer->session().api().applyUpdates(result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
if (error.type() == u"GROUPCALL_FORBIDDEN"_q) {
|
||||
LOG(("Call Info: Rejoin after error '%1' in editGroupCallMember."
|
||||
@@ -723,11 +809,11 @@ void GroupCall::toggleMute(not_null<UserData*> user, bool mute) {
|
||||
|
||||
std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
|
||||
const std::vector<not_null<UserData*>> &users) {
|
||||
const auto real = _channel->call();
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real || real->id() != _id) {
|
||||
return 0;
|
||||
}
|
||||
const auto owner = &_channel->owner();
|
||||
const auto owner = &_peer->owner();
|
||||
const auto &invited = owner->invitedToCallUsers(_id);
|
||||
const auto &participants = real->participants();
|
||||
auto &&toInvite = users | ranges::view::filter([&](
|
||||
@@ -748,7 +834,7 @@ std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
|
||||
inputCall(),
|
||||
MTP_vector<MTPInputUser>(slice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_channel->session().api().applyUpdates(result);
|
||||
_peer->session().api().applyUpdates(result);
|
||||
}).send();
|
||||
slice.clear();
|
||||
};
|
||||
@@ -756,7 +842,7 @@ std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
|
||||
if (!count && slice.empty()) {
|
||||
result = user;
|
||||
}
|
||||
owner->registerInvitedToCallUser(_id, _channel, user);
|
||||
owner->registerInvitedToCallUser(_id, _peer, user);
|
||||
slice.push_back(user->inputUser);
|
||||
if (slice.size() == kMaxInvitePerSlice) {
|
||||
sendSlice();
|
||||
@@ -808,21 +894,26 @@ void GroupCall::applyGlobalShortcutChanges() {
|
||||
}
|
||||
_pushToTalk = shortcut;
|
||||
_shortcutManager->startWatching(_pushToTalk, [=](bool pressed) {
|
||||
const auto delay = Core::App().settings().groupCallPushToTalkDelay();
|
||||
if (muted() == MuteState::ForceMuted
|
||||
|| muted() == MuteState::Active) {
|
||||
return;
|
||||
} else if (pressed) {
|
||||
_pushToTalkCancelTimer.cancel();
|
||||
setMuted(MuteState::PushToTalk);
|
||||
} else if (delay) {
|
||||
_pushToTalkCancelTimer.callOnce(delay);
|
||||
} else {
|
||||
pushToTalkCancel();
|
||||
}
|
||||
pushToTalk(
|
||||
pressed,
|
||||
Core::App().settings().groupCallPushToTalkDelay());
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::pushToTalk(bool pressed, crl::time delay) {
|
||||
if (muted() == MuteState::ForceMuted
|
||||
|| muted() == MuteState::Active) {
|
||||
return;
|
||||
} else if (pressed) {
|
||||
_pushToTalkCancelTimer.cancel();
|
||||
setMuted(MuteState::PushToTalk);
|
||||
} else if (delay) {
|
||||
_pushToTalkCancelTimer.callOnce(delay);
|
||||
} else {
|
||||
pushToTalkCancel();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::pushToTalkCancel() {
|
||||
_pushToTalkCancelTimer.cancel();
|
||||
if (muted() == MuteState::PushToTalk) {
|
||||
@@ -864,7 +955,7 @@ void GroupCall::handleControllerError(const QString &error) {
|
||||
// "{user}",
|
||||
// _user->name)));
|
||||
} else if (error == u"ERROR_AUDIO_IO"_q) {
|
||||
Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
|
||||
//Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
|
||||
}
|
||||
//finish(FinishType::Failed);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class History;
|
||||
|
||||
namespace tgcalls {
|
||||
class GroupInstanceImpl;
|
||||
struct GroupLevelsUpdate;
|
||||
} // namespace tgcalls
|
||||
|
||||
namespace base {
|
||||
@@ -24,6 +25,14 @@ class GlobalShortcutManager;
|
||||
class GlobalShortcutValue;
|
||||
} // namespace base
|
||||
|
||||
namespace Webrtc {
|
||||
class MediaDevices;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Data {
|
||||
struct LastSpokeTimes;
|
||||
} // namespace Data
|
||||
|
||||
namespace Calls {
|
||||
|
||||
enum class MuteState {
|
||||
@@ -42,6 +51,7 @@ enum class MuteState {
|
||||
struct LevelUpdate {
|
||||
uint32 ssrc = 0;
|
||||
float value = 0.;
|
||||
bool voice = false;
|
||||
bool self = false;
|
||||
};
|
||||
|
||||
@@ -55,21 +65,28 @@ public:
|
||||
virtual void groupCallFailed(not_null<GroupCall*> call) = 0;
|
||||
virtual void groupCallRequestPermissionsOrFail(
|
||||
Fn<void()> onSuccess) = 0;
|
||||
|
||||
enum class GroupCallSound {
|
||||
Started,
|
||||
Connecting,
|
||||
Ended,
|
||||
};
|
||||
virtual void groupCallPlaySound(GroupCallSound sound) = 0;
|
||||
};
|
||||
|
||||
using GlobalShortcutManager = base::GlobalShortcutManager;
|
||||
|
||||
GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> peer,
|
||||
const MTPInputGroupCall &inputCall);
|
||||
~GroupCall();
|
||||
|
||||
[[nodiscard]] uint64 id() const {
|
||||
return _id;
|
||||
}
|
||||
[[nodiscard]] not_null<ChannelData*> channel() const {
|
||||
return _channel;
|
||||
[[nodiscard]] not_null<PeerData*> peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
void start();
|
||||
@@ -103,6 +120,7 @@ public:
|
||||
[[nodiscard]] rpl::producer<State> stateValue() const {
|
||||
return _state.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> connectingValue() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
|
||||
return _levelUpdates.events();
|
||||
@@ -120,6 +138,8 @@ public:
|
||||
std::shared_ptr<GlobalShortcutManager> ensureGlobalShortcutManager();
|
||||
void applyGlobalShortcutChanges();
|
||||
|
||||
void pushToTalk(bool pressed, crl::time delay);
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
@@ -146,11 +166,7 @@ private:
|
||||
void applySelfInCallLocally();
|
||||
void rejoin();
|
||||
|
||||
void myLevelUpdated(float level);
|
||||
void audioLevelsUpdated(
|
||||
const std::vector<std::pair<std::uint32_t, float>> &data);
|
||||
void handleLevelsUpdated(
|
||||
gsl::span<const std::pair<std::uint32_t, float>> data);
|
||||
void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data);
|
||||
void setInstanceConnected(bool connected);
|
||||
void checkLastSpoke();
|
||||
void pushToTalkCancel();
|
||||
@@ -158,11 +174,15 @@ private:
|
||||
void checkGlobalShortcutAvailability();
|
||||
void checkJoined();
|
||||
|
||||
void playConnectingSound();
|
||||
void stopConnectingSound();
|
||||
void playConnectingSoundOnce();
|
||||
|
||||
[[nodiscard]] MTPInputGroupCall inputCall() const;
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
const not_null<ChannelData*> _channel;
|
||||
const not_null<History*> _history;
|
||||
not_null<PeerData*> _peer; // Can change in legacy group migration.
|
||||
not_null<History*> _history; // Can change in legacy group migration.
|
||||
MTP::Sender _api;
|
||||
rpl::variable<State> _state = State::Creating;
|
||||
bool _instanceConnected = false;
|
||||
@@ -178,7 +198,7 @@ private:
|
||||
|
||||
std::unique_ptr<tgcalls::GroupInstanceImpl> _instance;
|
||||
rpl::event_stream<LevelUpdate> _levelUpdates;
|
||||
base::flat_map<uint32, crl::time> _lastSpoke;
|
||||
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
|
||||
base::Timer _lastSpokeCheckTimer;
|
||||
base::Timer _checkJoinedTimer;
|
||||
|
||||
@@ -187,7 +207,12 @@ private:
|
||||
std::shared_ptr<GlobalShortcutManager> _shortcutManager;
|
||||
std::shared_ptr<GlobalShortcutValue> _pushToTalk;
|
||||
base::Timer _pushToTalkCancelTimer;
|
||||
bool _pushToTalkStarted = false;
|
||||
base::Timer _connectingSoundTimer;
|
||||
bool _hadJoinedState = false;
|
||||
|
||||
std::unique_ptr<Webrtc::MediaDevices> _mediaDevices;
|
||||
QString _audioInputId;
|
||||
QString _audioOutputId;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Ui {
|
||||
class ScrollArea;
|
||||
class SettingsButton;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
@@ -36,6 +37,7 @@ public:
|
||||
|
||||
[[nodiscard]] int desiredHeight() const;
|
||||
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
||||
[[nodiscard]] rpl::producer<int> fullCountValue() const;
|
||||
[[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
|
||||
[[nodiscard]] auto kickMemberRequests() const
|
||||
-> rpl::producer<not_null<UserData*>>;
|
||||
@@ -55,10 +57,10 @@ private:
|
||||
// PeerListContentDelegate interface.
|
||||
void peerListSetTitle(rpl::producer<QString> title) override;
|
||||
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
|
||||
void peerListSetHideEmpty(bool hide) override;
|
||||
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
|
||||
int peerListSelectedRowsCount() override;
|
||||
void peerListScrollToTop() override;
|
||||
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
|
||||
void peerListAddSelectedPeerInBunch(
|
||||
not_null<PeerData*> peer) override;
|
||||
void peerListAddSelectedRowInBunch(
|
||||
@@ -67,25 +69,22 @@ private:
|
||||
void peerListSetDescription(
|
||||
object_ptr<Ui::FlatLabel> description) override;
|
||||
|
||||
void setupHeader(not_null<GroupCall*> call);
|
||||
object_ptr<Ui::FlatLabel> setupTitle(not_null<GroupCall*> call);
|
||||
void setupAddMember(not_null<GroupCall*> call);
|
||||
void resizeToList();
|
||||
void setupList();
|
||||
void setupFakeRoundCorners();
|
||||
|
||||
void setupButtons(not_null<GroupCall*> call);
|
||||
|
||||
void updateHeaderControlsGeometry(int newWidth);
|
||||
void updateControlsGeometry();
|
||||
|
||||
const base::weak_ptr<GroupCall> _call;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
std::unique_ptr<PeerListController> _listController;
|
||||
object_ptr<Ui::RpWidget> _header = { nullptr };
|
||||
object_ptr<Ui::SettingsButton> _addMember = { nullptr };
|
||||
rpl::variable<Ui::SettingsButton*> _addMemberButton = nullptr;
|
||||
ListWidget *_list = { nullptr };
|
||||
rpl::event_stream<> _addMemberRequests;
|
||||
|
||||
Ui::RpWidget *_titleWrap = nullptr;
|
||||
Ui::FlatLabel *_title = nullptr;
|
||||
Ui::IconButton *_addMember = nullptr;
|
||||
rpl::variable<bool> _canAddMembers;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -22,12 +22,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/add_participants_box.h"
|
||||
#include "boxes/peer_lists_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "app.h"
|
||||
#include "apiwrap.h" // api().kickParticipant.
|
||||
#include "styles/style_calls.h"
|
||||
@@ -44,12 +48,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSpacePushToTalkDelay = crl::time(250);
|
||||
|
||||
class InviteController final : public ParticipantsBoxController {
|
||||
public:
|
||||
InviteController(
|
||||
not_null<ChannelData*> channel,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn,
|
||||
int fullInCount);
|
||||
not_null<PeerData*> peer,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn);
|
||||
|
||||
void prepare() override;
|
||||
|
||||
@@ -60,45 +65,100 @@ public:
|
||||
|
||||
void itemDeselectedHook(not_null<PeerData*> peer) override;
|
||||
|
||||
std::variant<int, not_null<UserData*>> inviteSelectedUsers(
|
||||
not_null<PeerListBox*> box,
|
||||
not_null<GroupCall*> call) const;
|
||||
[[nodiscard]] auto peersWithRows() const
|
||||
-> not_null<const base::flat_set<not_null<UserData*>>*>;
|
||||
[[nodiscard]] rpl::producer<not_null<UserData*>> rowAdded() const;
|
||||
|
||||
[[nodiscard]] bool hasRowFor(not_null<PeerData*> peer) const;
|
||||
|
||||
private:
|
||||
void updateTitle() const;
|
||||
[[nodiscard]] int alreadyInCount() const;
|
||||
[[nodiscard]] bool isAlreadyIn(not_null<UserData*> user) const;
|
||||
[[nodiscard]] int fullCount() const;
|
||||
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) const override;
|
||||
|
||||
const not_null<ChannelData*> _channel;
|
||||
not_null<PeerData*> _peer;
|
||||
const base::flat_set<not_null<UserData*>> _alreadyIn;
|
||||
const int _fullInCount = 0;
|
||||
mutable base::flat_set<not_null<UserData*>> _skippedUsers;
|
||||
mutable base::flat_set<not_null<UserData*>> _inGroup;
|
||||
rpl::event_stream<not_null<UserData*>> _rowAdded;
|
||||
|
||||
};
|
||||
|
||||
class InviteContactsController final : public AddParticipantsBoxController {
|
||||
public:
|
||||
InviteContactsController(
|
||||
not_null<PeerData*> peer,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn,
|
||||
not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
|
||||
rpl::producer<not_null<UserData*>> discoveredInGroup);
|
||||
|
||||
private:
|
||||
void prepareViewHook() override;
|
||||
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override;
|
||||
|
||||
const not_null<const base::flat_set<not_null<UserData*>>*> _inGroup;
|
||||
rpl::producer<not_null<UserData*>> _discoveredInGroup;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateSectionSubtitle(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> text) {
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(
|
||||
parent,
|
||||
st::searchedBarHeight);
|
||||
|
||||
const auto raw = result.data();
|
||||
raw->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(raw);
|
||||
p.fillRect(clip, st::groupCallMembersBgOver);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st::groupCallBoxLabel);
|
||||
raw->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto padding = st::groupCallInviteDividerPadding;
|
||||
const auto available = width - padding.left() - padding.right();
|
||||
label->resizeToNaturalWidth(available);
|
||||
label->moveToLeft(padding.left(), padding.top(), width);
|
||||
}, label->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
InviteController::InviteController(
|
||||
not_null<ChannelData*> channel,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn,
|
||||
int fullInCount)
|
||||
: ParticipantsBoxController(CreateTag{}, nullptr, channel, Role::Members)
|
||||
, _channel(channel)
|
||||
, _alreadyIn(std::move(alreadyIn))
|
||||
, _fullInCount(std::max(fullInCount, int(_alreadyIn.size()))) {
|
||||
_skippedUsers.emplace(channel->session().user());
|
||||
not_null<PeerData*> peer,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn)
|
||||
: ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members)
|
||||
, _peer(peer)
|
||||
, _alreadyIn(std::move(alreadyIn)) {
|
||||
SubscribeToMigration(
|
||||
_peer,
|
||||
lifetime(),
|
||||
[=](not_null<ChannelData*> channel) { _peer = channel; });
|
||||
}
|
||||
|
||||
void InviteController::prepare() {
|
||||
delegate()->peerListSetHideEmpty(true);
|
||||
ParticipantsBoxController::prepare();
|
||||
updateTitle();
|
||||
delegate()->peerListSetAboveWidget(CreateSectionSubtitle(
|
||||
nullptr,
|
||||
tr::lng_group_call_invite_members()));
|
||||
delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle(
|
||||
nullptr,
|
||||
tr::lng_group_call_invite_members()));
|
||||
}
|
||||
|
||||
void InviteController::rowClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListSetRowChecked(row, !row->checked());
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> InviteController::rowContextMenu(
|
||||
@@ -108,63 +168,73 @@ base::unique_qptr<Ui::PopupMenu> InviteController::rowContextMenu(
|
||||
}
|
||||
|
||||
void InviteController::itemDeselectedHook(not_null<PeerData*> peer) {
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
int InviteController::alreadyInCount() const {
|
||||
return std::max(_fullInCount, int(_alreadyIn.size()));
|
||||
bool InviteController::hasRowFor(not_null<PeerData*> peer) const {
|
||||
return (delegate()->peerListFindRow(peer->id) != nullptr);
|
||||
}
|
||||
|
||||
bool InviteController::isAlreadyIn(not_null<UserData*> user) const {
|
||||
return _alreadyIn.contains(user);
|
||||
}
|
||||
|
||||
int InviteController::fullCount() const {
|
||||
return alreadyInCount() + delegate()->peerListSelectedRowsCount();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> InviteController::createRow(
|
||||
not_null<UserData*> user) const {
|
||||
if (user->isSelf() || user->isBot()) {
|
||||
if (_skippedUsers.emplace(user).second) {
|
||||
updateTitle();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<PeerListRow>(user);
|
||||
_rowAdded.fire_copy(user);
|
||||
_inGroup.emplace(user);
|
||||
if (isAlreadyIn(user)) {
|
||||
result->setDisabledState(PeerListRow::State::DisabledChecked);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void InviteController::updateTitle() const {
|
||||
const auto inOrInvited = fullCount() - 1; // minus self
|
||||
const auto canBeInvited = std::max({
|
||||
delegate()->peerListFullRowsCount(), // minus self and bots
|
||||
_channel->membersCount() - int(_skippedUsers.size()), // self + bots
|
||||
inOrInvited
|
||||
});
|
||||
const auto additional = canBeInvited
|
||||
? qsl("%1 / %2").arg(inOrInvited).arg(canBeInvited)
|
||||
: QString();
|
||||
delegate()->peerListSetTitle(tr::lng_group_call_invite_title());
|
||||
delegate()->peerListSetAdditionalTitle(rpl::single(additional));
|
||||
auto InviteController::peersWithRows() const
|
||||
-> not_null<const base::flat_set<not_null<UserData*>>*> {
|
||||
return &_inGroup;
|
||||
}
|
||||
|
||||
std::variant<int, not_null<UserData*>> InviteController::inviteSelectedUsers(
|
||||
not_null<PeerListBox*> box,
|
||||
not_null<GroupCall*> call) const {
|
||||
const auto rows = box->peerListCollectSelectedRows();
|
||||
const auto users = ranges::view::all(
|
||||
rows
|
||||
) | ranges::view::transform([](not_null<PeerData*> peer) {
|
||||
Expects(peer->isUser());
|
||||
Expects(!peer->isSelf());
|
||||
rpl::producer<not_null<UserData*>> InviteController::rowAdded() const {
|
||||
return _rowAdded.events();
|
||||
}
|
||||
|
||||
return not_null<UserData*>(peer->asUser());
|
||||
}) | ranges::to_vector;
|
||||
return call->inviteUsers(users);
|
||||
InviteContactsController::InviteContactsController(
|
||||
not_null<PeerData*> peer,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn,
|
||||
not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
|
||||
rpl::producer<not_null<UserData*>> discoveredInGroup)
|
||||
: AddParticipantsBoxController(peer, std::move(alreadyIn))
|
||||
, _inGroup(inGroup)
|
||||
, _discoveredInGroup(std::move(discoveredInGroup)) {
|
||||
}
|
||||
|
||||
void InviteContactsController::prepareViewHook() {
|
||||
AddParticipantsBoxController::prepareViewHook();
|
||||
|
||||
delegate()->peerListSetAboveWidget(CreateSectionSubtitle(
|
||||
nullptr,
|
||||
tr::lng_contacts_header()));
|
||||
delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle(
|
||||
nullptr,
|
||||
tr::lng_group_call_invite_search_results()));
|
||||
|
||||
std::move(
|
||||
_discoveredInGroup
|
||||
) | rpl::start_with_next([=](not_null<UserData*> user) {
|
||||
if (auto row = delegate()->peerListFindRow(user->id)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> InviteContactsController::createRow(
|
||||
not_null<UserData*> user) {
|
||||
return _inGroup->contains(user)
|
||||
? nullptr
|
||||
: AddParticipantsBoxController::createRow(user);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -180,7 +250,7 @@ void LeaveGroupCallBox(
|
||||
box.get(),
|
||||
tr::lng_group_call_leave_sure(),
|
||||
(inCall ? st::groupCallBoxLabel : st::boxLabel)));
|
||||
const auto discard = call->channel()->canManageCall()
|
||||
const auto discard = call->peer()->canManageGroupCall()
|
||||
? box->addRow(object_ptr<Ui::Checkbox>(
|
||||
box.get(),
|
||||
tr::lng_group_call_end(),
|
||||
@@ -209,9 +279,24 @@ void LeaveGroupCallBox(
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
void GroupCallConfirmBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const QString &text,
|
||||
rpl::producer<QString> button,
|
||||
Fn<void()> callback) {
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
text,
|
||||
st::groupCallBoxLabel),
|
||||
st::boxPadding);
|
||||
box->addButton(std::move(button), callback);
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
GroupPanel::GroupPanel(not_null<GroupCall*> call)
|
||||
: _call(call)
|
||||
, _channel(call->channel())
|
||||
, _peer(call->peer())
|
||||
, _window(std::make_unique<Ui::Window>(Core::App().getModalParent()))
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(_window->body()))
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -231,6 +316,12 @@ GroupPanel::GroupPanel(not_null<GroupCall*> call)
|
||||
, _hangup(widget(), st::groupCallHangup) {
|
||||
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
|
||||
_settings->setColorOverrides(_mute->colorOverrides());
|
||||
_layerBg->setHideByBackgroundClick(true);
|
||||
|
||||
SubscribeToMigration(
|
||||
_peer,
|
||||
_window->lifetime(),
|
||||
[=](not_null<ChannelData*> channel) { migrate(channel); });
|
||||
|
||||
initWindow();
|
||||
initWidget();
|
||||
@@ -247,33 +338,65 @@ bool GroupPanel::isActive() const {
|
||||
&& !(_window->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void GroupPanel::minimize() {
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void GroupPanel::close() {
|
||||
_window->close();
|
||||
}
|
||||
|
||||
void GroupPanel::showAndActivate() {
|
||||
if (_window->isHidden()) {
|
||||
_window->show();
|
||||
}
|
||||
const auto state = _window->windowState();
|
||||
if (state & Qt::WindowMinimized) {
|
||||
_window->setWindowState(state & ~Qt::WindowMinimized);
|
||||
}
|
||||
_window->raise();
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowActive);
|
||||
_window->activateWindow();
|
||||
_window->setFocus();
|
||||
}
|
||||
|
||||
void GroupPanel::migrate(not_null<ChannelData*> channel) {
|
||||
_peer = channel;
|
||||
_peerLifetime.destroy();
|
||||
subscribeToPeerChanges();
|
||||
_title.destroy();
|
||||
refreshTitle();
|
||||
}
|
||||
|
||||
void GroupPanel::subscribeToPeerChanges() {
|
||||
Info::Profile::NameValue(
|
||||
_peer
|
||||
) | rpl::start_with_next([=](const TextWithEntities &name) {
|
||||
_window->setTitle(name.text);
|
||||
}, _peerLifetime);
|
||||
}
|
||||
|
||||
void GroupPanel::initWindow() {
|
||||
_window->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
_window->setAttribute(Qt::WA_NoSystemBackground);
|
||||
_window->setWindowIcon(
|
||||
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
|
||||
_window->setTitleStyle(st::callTitle);
|
||||
_window->setTitleStyle(st::groupCallTitle);
|
||||
|
||||
Info::Profile::NameValue(
|
||||
_channel
|
||||
) | rpl::start_with_next([=](const TextWithEntities &name) {
|
||||
_window->setTitle(name.text);
|
||||
}, _window->lifetime());
|
||||
subscribeToPeerChanges();
|
||||
|
||||
base::install_event_filter(_window.get(), [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Close && handleClose()) {
|
||||
e->ignore();
|
||||
return base::EventFilterResult::Cancel;
|
||||
} else if (e->type() == QEvent::KeyPress
|
||||
|| e->type() == QEvent::KeyRelease) {
|
||||
if (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Space) {
|
||||
if (_call) {
|
||||
_call->pushToTalk(
|
||||
e->type() == QEvent::KeyPress,
|
||||
kSpacePushToTalkDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
@@ -309,30 +432,35 @@ void GroupPanel::initWidget() {
|
||||
}, widget()->lifetime());
|
||||
}
|
||||
|
||||
void GroupPanel::hangup(bool discardCallChecked) {
|
||||
void GroupPanel::endCall() {
|
||||
if (!_call) {
|
||||
return;
|
||||
} else if (!_call->peer()->canManageGroupCall()) {
|
||||
_call->hangup();
|
||||
return;
|
||||
}
|
||||
_layerBg->showBox(Box(
|
||||
LeaveGroupCallBox,
|
||||
_call,
|
||||
discardCallChecked,
|
||||
false,
|
||||
BoxContext::GroupCallPanel));
|
||||
}
|
||||
|
||||
void GroupPanel::initControls() {
|
||||
_mute->clicks(
|
||||
) | rpl::filter([=](Qt::MouseButton button) {
|
||||
return (button == Qt::LeftButton)
|
||||
&& _call
|
||||
&& (_call->muted() != MuteState::ForceMuted);
|
||||
return (button == Qt::LeftButton) && (_call != nullptr);
|
||||
}) | rpl::start_with_next([=] {
|
||||
_call->setMuted((_call->muted() == MuteState::Muted)
|
||||
? MuteState::Active
|
||||
: MuteState::Muted);
|
||||
if (_call->muted() == MuteState::ForceMuted) {
|
||||
_mute->shake();
|
||||
} else {
|
||||
_call->setMuted((_call->muted() == MuteState::Muted)
|
||||
? MuteState::Active
|
||||
: MuteState::Muted);
|
||||
}
|
||||
}, _mute->lifetime());
|
||||
|
||||
_hangup->setClickedCallback([=] { hangup(false); });
|
||||
_hangup->setClickedCallback([=] { endCall(); });
|
||||
_settings->setClickedCallback([=] {
|
||||
if (_call) {
|
||||
_layerBg->showBox(Box(GroupCallSettingsBox, _call));
|
||||
@@ -340,7 +468,7 @@ void GroupPanel::initControls() {
|
||||
});
|
||||
|
||||
_settings->setText(tr::lng_menu_settings());
|
||||
_hangup->setText(tr::lng_box_leave());
|
||||
_hangup->setText(tr::lng_group_call_leave());
|
||||
|
||||
_members->desiredHeightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -357,7 +485,7 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||
return;
|
||||
}
|
||||
|
||||
_channel = _call->channel();
|
||||
_peer = _call->peer();
|
||||
|
||||
call->stateValue(
|
||||
) | rpl::filter([](State state) {
|
||||
@@ -395,14 +523,9 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||
}
|
||||
}, _callLifetime);
|
||||
|
||||
using namespace rpl::mappers;
|
||||
rpl::combine(
|
||||
_call->mutedValue() | MapPushToTalkToActive(),
|
||||
_call->stateValue() | rpl::map(
|
||||
_1 == State::Creating
|
||||
|| _1 == State::Joining
|
||||
|| _1 == State::Connecting
|
||||
)
|
||||
_call->connectingValue()
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](MuteState mute, bool connecting) {
|
||||
_mute->setState(Ui::CallMuteButtonState{
|
||||
@@ -413,6 +536,13 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||
: mute == MuteState::Muted
|
||||
? tr::lng_group_call_unmute(tr::now)
|
||||
: tr::lng_group_call_you_are_live(tr::now)),
|
||||
.subtext = (connecting
|
||||
? QString()
|
||||
: mute == MuteState::ForceMuted
|
||||
? tr::lng_group_call_force_muted_sub(tr::now)
|
||||
: mute == MuteState::Muted
|
||||
? tr::lng_group_call_unmute_sub(tr::now)
|
||||
: QString()),
|
||||
.type = (connecting
|
||||
? Ui::CallMuteButtonType::Connecting
|
||||
: mute == MuteState::ForceMuted
|
||||
@@ -425,63 +555,142 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||
}
|
||||
|
||||
void GroupPanel::addMembers() {
|
||||
const auto real = _channel->call();
|
||||
const auto real = _peer->groupCall();
|
||||
if (!_call || !real || real->id() != _call->id()) {
|
||||
return;
|
||||
}
|
||||
auto alreadyIn = _channel->owner().invitedToCallUsers(real->id());
|
||||
auto alreadyIn = _peer->owner().invitedToCallUsers(real->id());
|
||||
for (const auto &participant : real->participants()) {
|
||||
alreadyIn.emplace(participant.user);
|
||||
}
|
||||
alreadyIn.emplace(_channel->session().user());
|
||||
alreadyIn.emplace(_peer->session().user());
|
||||
auto controller = std::make_unique<InviteController>(
|
||||
_channel,
|
||||
std::move(alreadyIn),
|
||||
real->fullCount());
|
||||
_peer,
|
||||
alreadyIn);
|
||||
controller->setStyleOverrides(
|
||||
&st::groupCallInviteMembersList,
|
||||
&st::groupCallMultiSelect);
|
||||
|
||||
const auto weak = base::make_weak(_call);
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_group_call_invite_button(), [=] {
|
||||
if (const auto call = weak.get()) {
|
||||
const auto result = controller->inviteSelectedUsers(box, call);
|
||||
auto contactsController = std::make_unique<InviteContactsController>(
|
||||
_peer,
|
||||
std::move(alreadyIn),
|
||||
controller->peersWithRows(),
|
||||
controller->rowAdded());
|
||||
contactsController->setStyleOverrides(
|
||||
&st::groupCallInviteMembersList,
|
||||
&st::groupCallMultiSelect);
|
||||
|
||||
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
|
||||
Ui::Toast::Show(
|
||||
widget(),
|
||||
Ui::Toast::Config{
|
||||
.text = tr::lng_group_call_invite_done_user(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold((*user)->firstName),
|
||||
Ui::Text::WithEntities),
|
||||
.st = &st::defaultToast,
|
||||
});
|
||||
} else if (const auto count = std::get_if<int>(&result)) {
|
||||
if (*count > 0) {
|
||||
Ui::Toast::Show(
|
||||
widget(),
|
||||
Ui::Toast::Config{
|
||||
.text = tr::lng_group_call_invite_done_many(
|
||||
tr::now,
|
||||
lt_count,
|
||||
*count,
|
||||
Ui::Text::RichLangValue),
|
||||
.st = &st::defaultToast,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Unexpected("Result in GroupCall::inviteUsers.");
|
||||
}
|
||||
const auto weak = base::make_weak(_call);
|
||||
const auto invite = [=](const std::vector<not_null<UserData*>> &users) {
|
||||
const auto call = weak.get();
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
const auto result = call->inviteUsers(users);
|
||||
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
|
||||
Ui::Toast::Show(
|
||||
widget(),
|
||||
Ui::Toast::Config{
|
||||
.text = tr::lng_group_call_invite_done_user(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold((*user)->firstName),
|
||||
Ui::Text::WithEntities),
|
||||
.st = &st::defaultToast,
|
||||
});
|
||||
} else if (const auto count = std::get_if<int>(&result)) {
|
||||
if (*count > 0) {
|
||||
Ui::Toast::Show(
|
||||
widget(),
|
||||
Ui::Toast::Config{
|
||||
.text = tr::lng_group_call_invite_done_many(
|
||||
tr::now,
|
||||
lt_count,
|
||||
*count,
|
||||
Ui::Text::RichLangValue),
|
||||
.st = &st::defaultToast,
|
||||
});
|
||||
}
|
||||
box->closeBox();
|
||||
} else {
|
||||
Unexpected("Result in GroupCall::inviteUsers.");
|
||||
}
|
||||
};
|
||||
const auto inviteWithAdd = [=](
|
||||
const std::vector<not_null<UserData*>> &users,
|
||||
const std::vector<not_null<UserData*>> &nonMembers,
|
||||
Fn<void()> finish) {
|
||||
_peer->session().api().addChatParticipants(
|
||||
_peer,
|
||||
nonMembers,
|
||||
[=](bool) { invite(users); finish(); });
|
||||
};
|
||||
const auto inviteWithConfirmation = [=](
|
||||
const std::vector<not_null<UserData*>> &users,
|
||||
const std::vector<not_null<UserData*>> &nonMembers,
|
||||
Fn<void()> finish) {
|
||||
if (nonMembers.empty()) {
|
||||
invite(users);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
const auto name = _peer->name;
|
||||
const auto text = (nonMembers.size() == 1)
|
||||
? tr::lng_group_call_add_to_group_one(
|
||||
tr::now,
|
||||
lt_user,
|
||||
nonMembers.front()->shortName(),
|
||||
lt_group,
|
||||
name)
|
||||
: (nonMembers.size() < users.size())
|
||||
? tr::lng_group_call_add_to_group_some(tr::now, lt_group, name)
|
||||
: tr::lng_group_call_add_to_group_all(tr::now, lt_group, name);
|
||||
const auto shared = std::make_shared<QPointer<Ui::GenericBox>>();
|
||||
const auto finishWithConfirm = [=] {
|
||||
if (*shared) {
|
||||
(*shared)->closeBox();
|
||||
}
|
||||
finish();
|
||||
};
|
||||
auto box = Box(
|
||||
GroupCallConfirmBox,
|
||||
text,
|
||||
tr::lng_participant_invite(),
|
||||
[=] { inviteWithAdd(users, nonMembers, finishWithConfirm); });
|
||||
*shared = box.data();
|
||||
_layerBg->showBox(std::move(box));
|
||||
};
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListsBox*> box) {
|
||||
box->setTitle(tr::lng_group_call_invite_title());
|
||||
box->addButton(tr::lng_group_call_invite_button(), [=] {
|
||||
const auto rows = box->collectSelectedRows();
|
||||
|
||||
const auto users = ranges::view::all(
|
||||
rows
|
||||
) | ranges::view::transform([](not_null<PeerData*> peer) {
|
||||
return not_null<UserData*>(peer->asUser());
|
||||
}) | ranges::to_vector;
|
||||
|
||||
const auto nonMembers = ranges::view::all(
|
||||
users
|
||||
) | ranges::view::filter([&](not_null<UserData*> user) {
|
||||
return !controller->hasRowFor(user);
|
||||
}) | ranges::to_vector;
|
||||
|
||||
const auto finish = [box = Ui::MakeWeak(box)]() {
|
||||
if (box) {
|
||||
box->closeBox();
|
||||
}
|
||||
};
|
||||
inviteWithConfirmation(users, nonMembers, finish);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
};
|
||||
_layerBg->showBox(Box<PeerListBox>(std::move(controller), initBox));
|
||||
|
||||
auto controllers = std::vector<std::unique_ptr<PeerListController>>();
|
||||
controllers.push_back(std::move(controller));
|
||||
controllers.push_back(std::move(contactsController));
|
||||
_layerBg->showBox(Box<PeerListsBox>(std::move(controllers), initBox));
|
||||
}
|
||||
|
||||
void GroupPanel::kickMember(not_null<UserData*> user) {
|
||||
@@ -508,17 +717,21 @@ void GroupPanel::kickMember(not_null<UserData*> user) {
|
||||
}
|
||||
|
||||
void GroupPanel::kickMemberSure(not_null<UserData*> user) {
|
||||
const auto currentRestrictedRights = [&]() -> MTPChatBannedRights {
|
||||
const auto it = _channel->mgInfo->lastRestricted.find(user);
|
||||
return (it != _channel->mgInfo->lastRestricted.cend())
|
||||
? it->second.rights
|
||||
: MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
|
||||
}();
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
chat->session().api().kickParticipant(chat, user);
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
const auto currentRestrictedRights = [&]() -> MTPChatBannedRights {
|
||||
const auto it = channel->mgInfo->lastRestricted.find(user);
|
||||
return (it != channel->mgInfo->lastRestricted.cend())
|
||||
? it->second.rights
|
||||
: MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
|
||||
}();
|
||||
|
||||
_channel->session().api().kickParticipant(
|
||||
_channel,
|
||||
user,
|
||||
currentRestrictedRights);
|
||||
channel->session().api().kickParticipant(
|
||||
channel,
|
||||
user,
|
||||
currentRestrictedRights);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupPanel::initLayout() {
|
||||
@@ -550,22 +763,22 @@ void GroupPanel::initGeometry() {
|
||||
}
|
||||
|
||||
int GroupPanel::computeMembersListTop() const {
|
||||
#ifdef Q_OS_WIN
|
||||
return st::callTitleButton.height + st::groupCallMembersMargin.top() / 2;
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
return st::groupCallMembersMargin.top() * 2;
|
||||
#else // Q_OS_WIN || Q_OS_MAC
|
||||
return st::groupCallMembersMargin.top();
|
||||
#endif // Q_OS_WIN || Q_OS_MAC
|
||||
if (computeTitleRect().has_value()) {
|
||||
return st::groupCallMembersTop;
|
||||
}
|
||||
return st::groupCallMembersTop
|
||||
- (st::groupCallSubtitleTop - st::groupCallTitleTop);
|
||||
}
|
||||
|
||||
std::optional<QRect> GroupPanel::computeTitleRect() const {
|
||||
#ifdef Q_OS_WIN
|
||||
const auto controls = _controls->geometry();
|
||||
return QRect(0, 0, controls.x(), controls.height());
|
||||
#else // Q_OS_WIN
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
return QRect(70, 0, widget()->width() - 70, 28);
|
||||
#else // Q_OS_WIN || Q_OS_MAC
|
||||
return std::nullopt;
|
||||
#endif // Q_OS_WIN
|
||||
#endif // Q_OS_WIN || Q_OS_MAC
|
||||
}
|
||||
|
||||
void GroupPanel::updateControlsGeometry() {
|
||||
@@ -609,13 +822,14 @@ void GroupPanel::refreshTitle() {
|
||||
if (!_title) {
|
||||
_title.create(
|
||||
widget(),
|
||||
Info::Profile::NameValue(_channel),
|
||||
st::groupCallHeaderLabel);
|
||||
Info::Profile::NameValue(_peer),
|
||||
st::groupCallTitleLabel);
|
||||
_title->show();
|
||||
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
const auto best = _title->naturalWidth();
|
||||
const auto from = (widget()->width() - best) / 2;
|
||||
const auto top = (computeMembersListTop() - _title->height()) / 2;
|
||||
const auto top = st::groupCallTitleTop;
|
||||
const auto left = titleRect->x();
|
||||
if (from >= left && from + best <= left + titleRect->width()) {
|
||||
_title->resizeToWidth(best);
|
||||
@@ -633,6 +847,25 @@ void GroupPanel::refreshTitle() {
|
||||
} else if (_title) {
|
||||
_title.destroy();
|
||||
}
|
||||
if (!_subtitle) {
|
||||
_subtitle.create(
|
||||
widget(),
|
||||
tr::lng_group_call_members(
|
||||
lt_count_decimal,
|
||||
_members->fullCountValue() | tr::to_count()),
|
||||
st::groupCallSubtitleLabel);
|
||||
_subtitle->show();
|
||||
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
const auto middle = _title
|
||||
? (_title->x() + _title->width() / 2)
|
||||
: (widget()->width() / 2);
|
||||
const auto top = _title
|
||||
? st::groupCallSubtitleTop
|
||||
: st::groupCallTitleTop;
|
||||
_subtitle->moveToLeft(
|
||||
(widget()->width() - _subtitle->width()) / 2,
|
||||
top);
|
||||
}
|
||||
|
||||
void GroupPanel::paint(QRect clip) {
|
||||
|
||||
@@ -68,6 +68,8 @@ public:
|
||||
~GroupPanel();
|
||||
|
||||
[[nodiscard]] bool isActive() const;
|
||||
void minimize();
|
||||
void close();
|
||||
void showAndActivate();
|
||||
void closeBeforeDestroy();
|
||||
|
||||
@@ -90,7 +92,7 @@ private:
|
||||
void updateControlsGeometry();
|
||||
void showControls();
|
||||
|
||||
void hangup(bool discardCallChecked);
|
||||
void endCall();
|
||||
|
||||
void addMembers();
|
||||
void kickMember(not_null<UserData*> user);
|
||||
@@ -99,8 +101,11 @@ private:
|
||||
[[nodiscard]] std::optional<QRect> computeTitleRect() const;
|
||||
void refreshTitle();
|
||||
|
||||
void migrate(not_null<ChannelData*> channel);
|
||||
void subscribeToPeerChanges();
|
||||
|
||||
GroupCall *_call = nullptr;
|
||||
not_null<ChannelData*> _channel;
|
||||
not_null<PeerData*> _peer;
|
||||
|
||||
const std::unique_ptr<Ui::Window> _window;
|
||||
const std::unique_ptr<Ui::LayerManager> _layerBg;
|
||||
@@ -112,12 +117,15 @@ private:
|
||||
rpl::lifetime _callLifetime;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _title = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _subtitle = { nullptr };
|
||||
object_ptr<GroupMembers> _members;
|
||||
|
||||
object_ptr<Ui::CallButton> _settings;
|
||||
std::unique_ptr<Ui::CallMuteButton> _mute;
|
||||
object_ptr<Ui::CallButton> _hangup;
|
||||
|
||||
rpl::lifetime _peerLifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/global_shortcuts.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "core/application.h"
|
||||
#include "boxes/single_choice_box.h"
|
||||
@@ -44,19 +45,19 @@ constexpr auto kDelaysCount = 201;
|
||||
constexpr auto kCheckAccessibilityInterval = crl::time(500);
|
||||
|
||||
void SaveCallJoinMuted(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> peer,
|
||||
uint64 callId,
|
||||
bool joinMuted) {
|
||||
const auto call = channel->call();
|
||||
const auto call = peer->groupCall();
|
||||
if (!call
|
||||
|| call->id() != callId
|
||||
|| !channel->canManageCall()
|
||||
|| !peer->canManageGroupCall()
|
||||
|| !call->canChangeJoinMuted()
|
||||
|| call->joinMuted() == joinMuted) {
|
||||
return;
|
||||
}
|
||||
call->setJoinMutedLocally(joinMuted);
|
||||
channel->session().api().request(MTPphone_ToggleGroupCallSettings(
|
||||
peer->session().api().request(MTPphone_ToggleGroupCallSettings(
|
||||
MTP_flags(MTPphone_ToggleGroupCallSettings::Flag::f_join_muted),
|
||||
call->input(),
|
||||
MTP_bool(joinMuted)
|
||||
@@ -101,8 +102,8 @@ void GroupCallSettingsBox(
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto channel = call->channel();
|
||||
const auto real = channel->call();
|
||||
const auto peer = call->peer();
|
||||
const auto real = peer->groupCall();
|
||||
const auto id = call->id();
|
||||
const auto goodReal = (real && real->id() == id);
|
||||
|
||||
@@ -111,7 +112,7 @@ void GroupCallSettingsBox(
|
||||
|
||||
const auto joinMuted = goodReal ? real->joinMuted() : false;
|
||||
const auto canChangeJoinMuted = (goodReal && real->canChangeJoinMuted());
|
||||
const auto addCheck = (channel->canManageCall() && canChangeJoinMuted);
|
||||
const auto addCheck = (peer->canManageGroupCall() && canChangeJoinMuted);
|
||||
if (addCheck) {
|
||||
AddSkip(layout);
|
||||
}
|
||||
@@ -402,11 +403,24 @@ void GroupCallSettingsBox(
|
||||
//AddSkip(layout);
|
||||
|
||||
const auto lookupLink = [=] {
|
||||
return channel->hasUsername()
|
||||
? channel->session().createInternalLinkFull(channel->username)
|
||||
: channel->inviteLink();
|
||||
if (const auto group = peer->asMegagroup()) {
|
||||
return group->hasUsername()
|
||||
? group->session().createInternalLinkFull(group->username)
|
||||
: group->inviteLink();
|
||||
} else if (const auto chat = peer->asChat()) {
|
||||
return chat->inviteLink();
|
||||
}
|
||||
return QString();
|
||||
};
|
||||
if (!lookupLink().isEmpty() || channel->canHaveInviteLink()) {
|
||||
const auto canCreateLink = [&] {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
return chat->canHaveInviteLink();
|
||||
} else if (const auto group = peer->asMegagroup()) {
|
||||
return group->canHaveInviteLink();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!lookupLink().isEmpty() || canCreateLink()) {
|
||||
const auto copyLink = [=] {
|
||||
const auto link = lookupLink();
|
||||
if (link.isEmpty()) {
|
||||
@@ -427,12 +441,17 @@ void GroupCallSettingsBox(
|
||||
)->addClickHandler([=] {
|
||||
if (!copyLink() && !state->generatingLink) {
|
||||
state->generatingLink = true;
|
||||
channel->session().api().request(MTPmessages_ExportChatInvite(
|
||||
channel->input
|
||||
peer->session().api().request(MTPmessages_ExportChatInvite(
|
||||
peer->input
|
||||
)).done([=](const MTPExportedChatInvite &result) {
|
||||
if (result.type() == mtpc_chatInviteExported) {
|
||||
channel->setInviteLink(
|
||||
qs(result.c_chatInviteExported().vlink()));
|
||||
const auto link = qs(
|
||||
result.c_chatInviteExported().vlink());
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->setInviteLink(link);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setInviteLink(link);
|
||||
}
|
||||
copyLink();
|
||||
}
|
||||
}).send();
|
||||
@@ -440,7 +459,7 @@ void GroupCallSettingsBox(
|
||||
});
|
||||
}
|
||||
|
||||
if (channel->canManageCall()) {
|
||||
if (peer->canManageGroupCall()) {
|
||||
AddButton(
|
||||
layout,
|
||||
tr::lng_group_call_end(),
|
||||
@@ -461,6 +480,7 @@ void GroupCallSettingsBox(
|
||||
// Means we finished showing the box.
|
||||
crl::on_main(box, [=] {
|
||||
state->micTester = std::make_unique<Webrtc::AudioInputTester>(
|
||||
Core::App().settings().callAudioBackend(),
|
||||
Core::App().settings().callInputDeviceId());
|
||||
state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
|
||||
});
|
||||
@@ -472,7 +492,7 @@ void GroupCallSettingsBox(
|
||||
if (canChangeJoinMuted
|
||||
&& muteJoined
|
||||
&& muteJoined->toggled() != joinMuted) {
|
||||
SaveCallJoinMuted(channel, id, muteJoined->toggled());
|
||||
SaveCallJoinMuted(peer, id, muteJoined->toggled());
|
||||
}
|
||||
}, box->lifetime());
|
||||
box->addButton(tr::lng_box_done(), [=] {
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_session.h"
|
||||
#include "media/audio/media_audio_track.h"
|
||||
#include "platform/platform_specific.h"
|
||||
@@ -58,12 +59,12 @@ void Instance::startOutgoingCall(not_null<UserData*> user, bool video) {
|
||||
}), video);
|
||||
}
|
||||
|
||||
void Instance::startOrJoinGroupCall(not_null<ChannelData*> channel) {
|
||||
void Instance::startOrJoinGroupCall(not_null<PeerData*> peer) {
|
||||
destroyCurrentCall();
|
||||
|
||||
const auto call = channel->call();
|
||||
const auto call = peer->groupCall();
|
||||
createGroupCall(
|
||||
channel,
|
||||
peer,
|
||||
call ? call->input() : MTP_inputGroupCall(MTPlong(), MTPlong()));
|
||||
}
|
||||
|
||||
@@ -97,35 +98,45 @@ void Instance::groupCallFailed(not_null<GroupCall*> call) {
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::playSound(Sound sound) {
|
||||
switch (sound) {
|
||||
case Sound::Busy: {
|
||||
if (!_callBusyTrack) {
|
||||
_callBusyTrack = Media::Audio::Current().createTrack();
|
||||
_callBusyTrack->fillFromFile(
|
||||
Core::App().settings().getSoundPath(qsl("call_busy")));
|
||||
}
|
||||
_callBusyTrack->playOnce();
|
||||
} break;
|
||||
|
||||
case Sound::Ended: {
|
||||
if (!_callEndedTrack) {
|
||||
_callEndedTrack = Media::Audio::Current().createTrack();
|
||||
_callEndedTrack->fillFromFile(
|
||||
Core::App().settings().getSoundPath(qsl("call_end")));
|
||||
}
|
||||
_callEndedTrack->playOnce();
|
||||
} break;
|
||||
|
||||
case Sound::Connecting: {
|
||||
if (!_callConnectingTrack) {
|
||||
_callConnectingTrack = Media::Audio::Current().createTrack();
|
||||
_callConnectingTrack->fillFromFile(
|
||||
Core::App().settings().getSoundPath(qsl("call_connect")));
|
||||
}
|
||||
_callConnectingTrack->playOnce();
|
||||
} break;
|
||||
not_null<Media::Audio::Track*> Instance::ensureSoundLoaded(
|
||||
const QString &key) {
|
||||
const auto i = _tracks.find(key);
|
||||
if (i != end(_tracks)) {
|
||||
return i->second.get();
|
||||
}
|
||||
const auto result = _tracks.emplace(
|
||||
key,
|
||||
Media::Audio::Current().createTrack()).first->second.get();
|
||||
result->fillFromFile(Core::App().settings().getSoundPath(key));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Instance::playSoundOnce(const QString &key) {
|
||||
ensureSoundLoaded(key)->playOnce();
|
||||
}
|
||||
|
||||
void Instance::callPlaySound(CallSound sound) {
|
||||
playSoundOnce([&] {
|
||||
switch (sound) {
|
||||
case CallSound::Busy: return "call_busy";
|
||||
case CallSound::Ended: return "call_end";
|
||||
case CallSound::Connecting: return "call_connect";
|
||||
}
|
||||
Unexpected("CallSound in Instance::callPlaySound.");
|
||||
return "";
|
||||
}());
|
||||
}
|
||||
|
||||
void Instance::groupCallPlaySound(GroupCallSound sound) {
|
||||
playSoundOnce([&] {
|
||||
switch (sound) {
|
||||
case GroupCallSound::Started: return "group_call_start";
|
||||
case GroupCallSound::Ended: return "group_call_end";
|
||||
case GroupCallSound::Connecting: return "group_call_connect";
|
||||
}
|
||||
Unexpected("GroupCallSound in Instance::groupCallPlaySound.");
|
||||
return "";
|
||||
}());
|
||||
}
|
||||
|
||||
void Instance::destroyCall(not_null<Call*> call) {
|
||||
@@ -183,17 +194,17 @@ void Instance::destroyGroupCall(not_null<GroupCall*> call) {
|
||||
}
|
||||
|
||||
void Instance::createGroupCall(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> peer,
|
||||
const MTPInputGroupCall &inputCall) {
|
||||
destroyCurrentCall();
|
||||
|
||||
auto call = std::make_unique<GroupCall>(
|
||||
getGroupCallDelegate(),
|
||||
channel,
|
||||
peer,
|
||||
inputCall);
|
||||
const auto raw = call.get();
|
||||
|
||||
channel->session().account().sessionChanges(
|
||||
peer->session().account().sessionChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
destroyGroupCall(raw);
|
||||
}, raw->lifetime());
|
||||
@@ -387,7 +398,7 @@ void Instance::handleGroupCallUpdate(
|
||||
existing->applyUpdate(call);
|
||||
}
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->channel()->session() == session)) {
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
_currentGroupCall->handleUpdate(call);
|
||||
}
|
||||
}
|
||||
@@ -402,7 +413,7 @@ void Instance::handleGroupCallUpdate(
|
||||
existing->applyUpdate(update);
|
||||
}
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->channel()->session() == session)
|
||||
&& (&_currentGroupCall->peer()->session() == session)
|
||||
&& (_currentGroupCall->id() == callId)) {
|
||||
_currentGroupCall->handleUpdate(update);
|
||||
}
|
||||
@@ -458,7 +469,7 @@ bool Instance::hasActivePanel(not_null<Main::Session*> session) const {
|
||||
return (&_currentCall->user()->session() == session)
|
||||
&& _currentCallPanel->isActive();
|
||||
} else if (inGroupCall()) {
|
||||
return (&_currentGroupCall->channel()->session() == session)
|
||||
return (&_currentGroupCall->peer()->session() == session)
|
||||
&& _currentGroupCallPanel->isActive();
|
||||
}
|
||||
return false;
|
||||
@@ -475,6 +486,25 @@ bool Instance::activateCurrentCall() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Instance::minimizeCurrentActiveCall() {
|
||||
if (inCall() && _currentCallPanel->isActive()) {
|
||||
_currentCallPanel->minimize();
|
||||
return true;
|
||||
} else if (inGroupCall() && _currentGroupCallPanel->isActive()) {
|
||||
_currentGroupCallPanel->minimize();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Instance::closeCurrentActiveCall() {
|
||||
if (inGroupCall() && _currentGroupCallPanel->isActive()) {
|
||||
_currentGroupCallPanel->close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Call *Instance::currentCall() const {
|
||||
return _currentCall.get();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
~Instance();
|
||||
|
||||
void startOutgoingCall(not_null<UserData*> user, bool video);
|
||||
void startOrJoinGroupCall(not_null<ChannelData*> channel);
|
||||
void startOrJoinGroupCall(not_null<PeerData*> peer);
|
||||
void handleUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update);
|
||||
@@ -55,6 +55,8 @@ public:
|
||||
[[nodiscard]] bool hasActivePanel(
|
||||
not_null<Main::Session*> session) const;
|
||||
bool activateCurrentCall();
|
||||
bool minimizeCurrentActiveCall();
|
||||
bool closeCurrentActiveCall();
|
||||
auto getVideoCapture()
|
||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
|
||||
void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true);
|
||||
@@ -64,6 +66,9 @@ public:
|
||||
[[nodiscard]] bool isQuitPrevent();
|
||||
|
||||
private:
|
||||
using CallSound = Call::Delegate::CallSound;
|
||||
using GroupCallSound = GroupCall::Delegate::GroupCallSound;
|
||||
|
||||
[[nodiscard]] not_null<Call::Delegate*> getCallDelegate() {
|
||||
return static_cast<Call::Delegate*>(this);
|
||||
}
|
||||
@@ -73,6 +78,10 @@ private:
|
||||
[[nodiscard]] DhConfig getDhConfig() const override {
|
||||
return _dhConfig;
|
||||
}
|
||||
|
||||
not_null<Media::Audio::Track*> ensureSoundLoaded(const QString &key);
|
||||
void playSoundOnce(const QString &key);
|
||||
|
||||
void callFinished(not_null<Call*> call) override;
|
||||
void callFailed(not_null<Call*> call) override;
|
||||
void callRedial(not_null<Call*> call) override;
|
||||
@@ -81,24 +90,26 @@ private:
|
||||
bool video) override {
|
||||
requestPermissionsOrFail(std::move(onSuccess), video);
|
||||
}
|
||||
void callPlaySound(CallSound sound) override;
|
||||
|
||||
void groupCallFinished(not_null<GroupCall*> call) override;
|
||||
void groupCallFailed(not_null<GroupCall*> call) override;
|
||||
void groupCallRequestPermissionsOrFail(Fn<void()> onSuccess) override {
|
||||
requestPermissionsOrFail(std::move(onSuccess), false);
|
||||
}
|
||||
void groupCallPlaySound(GroupCallSound sound) override;
|
||||
|
||||
using Sound = Call::Delegate::Sound;
|
||||
void playSound(Sound sound) override;
|
||||
void createCall(not_null<UserData*> user, Call::Type type, bool video);
|
||||
void destroyCall(not_null<Call*> call);
|
||||
|
||||
void createGroupCall(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> peer,
|
||||
const MTPInputGroupCall &inputCall);
|
||||
void destroyGroupCall(not_null<GroupCall*> call);
|
||||
|
||||
void requestPermissionOrFail(Platform::PermissionType type, Fn<void()> onSuccess);
|
||||
void requestPermissionOrFail(
|
||||
Platform::PermissionType type,
|
||||
Fn<void()> onSuccess);
|
||||
|
||||
void refreshDhConfig();
|
||||
void refreshServerConfig(not_null<Main::Session*> session);
|
||||
@@ -132,9 +143,7 @@ private:
|
||||
rpl::event_stream<GroupCall*> _currentGroupCallChanges;
|
||||
std::unique_ptr<GroupPanel> _currentGroupCallPanel;
|
||||
|
||||
std::unique_ptr<Media::Audio::Track> _callConnectingTrack;
|
||||
std::unique_ptr<Media::Audio::Track> _callEndedTrack;
|
||||
std::unique_ptr<Media::Audio::Track> _callBusyTrack;
|
||||
base::flat_map<QString, std::unique_ptr<Media::Audio::Track>> _tracks;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -209,12 +209,22 @@ bool Panel::isActive() const {
|
||||
}
|
||||
|
||||
void Panel::showAndActivate() {
|
||||
if (_window->isHidden()) {
|
||||
_window->show();
|
||||
}
|
||||
const auto state = _window->windowState();
|
||||
if (state & Qt::WindowMinimized) {
|
||||
_window->setWindowState(state & ~Qt::WindowMinimized);
|
||||
}
|
||||
_window->raise();
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowActive);
|
||||
_window->activateWindow();
|
||||
_window->setFocus();
|
||||
}
|
||||
|
||||
void Panel::minimize() {
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::replaceCall(not_null<Call*> call) {
|
||||
reinitWithCall(call);
|
||||
updateControlsGeometry();
|
||||
|
||||
@@ -53,6 +53,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool isActive() const;
|
||||
void showAndActivate();
|
||||
void minimize();
|
||||
void replaceCall(not_null<Call*> call);
|
||||
void closeBeforeDestroy();
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/paint/blobs_linear.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/chat/group_call_userpics.h" // Ui::GroupCallUser.
|
||||
#include "ui/chat/group_call_bar.h" // Ui::GroupCallBarContent.
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "core/application.h"
|
||||
#include "calls/calls_call.h"
|
||||
@@ -24,73 +26,98 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_group_call_tracker.h" // ContentByCall.
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "base/timer.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_chat.h" // style::GroupCallUserpics
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Calls {
|
||||
|
||||
enum class BarState {
|
||||
Connecting,
|
||||
Active,
|
||||
Muted,
|
||||
ForceMuted,
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxUsersInBar = 3;
|
||||
constexpr auto kUpdateDebugTimeoutMs = crl::time(500);
|
||||
constexpr auto kSwitchStateDuration = 120;
|
||||
|
||||
constexpr auto kMinorBlobAlpha = 76. / 255.;
|
||||
|
||||
constexpr auto kBlobLevelDuration1 = 250;
|
||||
constexpr auto kBlobLevelDuration2 = 120;
|
||||
constexpr auto kHideBlobsDuration = crl::time(500);
|
||||
constexpr auto kBlobLevelDuration = crl::time(250);
|
||||
constexpr auto kBlobUpdateInterval = crl::time(100);
|
||||
|
||||
auto LinearBlobs() -> std::array<Ui::Paint::LinearBlobs::BlobData, 3> {
|
||||
return { {
|
||||
auto BarStateFromMuteState(MuteState state, bool connecting) {
|
||||
return (connecting
|
||||
? BarState::Connecting
|
||||
: state == MuteState::ForceMuted
|
||||
? BarState::ForceMuted
|
||||
: state == MuteState::Muted
|
||||
? BarState::Muted
|
||||
: BarState::Active);
|
||||
};
|
||||
|
||||
auto LinearBlobs() {
|
||||
return std::vector<Ui::Paint::LinearBlobs::BlobData>{
|
||||
{
|
||||
.segmentsCount = 5,
|
||||
.minScale = 1.,
|
||||
.minRadius = (float)st::groupCallMajorBlobMinRadius,
|
||||
.minRadius = 0.,
|
||||
.maxRadius = (float)st::groupCallMajorBlobMaxRadius,
|
||||
.idleRadius = (float)st::groupCallMinorBlobIdleRadius,
|
||||
.speedScale = .3,
|
||||
.alpha = 1.,
|
||||
.topOffset = st::groupCallMajorBlobTopOffset,
|
||||
},
|
||||
{
|
||||
.segmentsCount = 7,
|
||||
.minScale = 1.,
|
||||
.minRadius = (float)st::groupCallMinorBlobMinRadius,
|
||||
.minRadius = 0.,
|
||||
.maxRadius = (float)st::groupCallMinorBlobMaxRadius,
|
||||
.idleRadius = (float)st::groupCallMinorBlobIdleRadius,
|
||||
.speedScale = .7,
|
||||
.alpha = kMinorBlobAlpha,
|
||||
.topOffset = st::groupCallMinorBlobTopOffset,
|
||||
},
|
||||
{
|
||||
.segmentsCount = 8,
|
||||
.minScale = 1.,
|
||||
.minRadius = (float)st::groupCallMinorBlobMinRadius,
|
||||
.minRadius = 0.,
|
||||
.maxRadius = (float)st::groupCallMinorBlobMaxRadius,
|
||||
.idleRadius = (float)st::groupCallMinorBlobIdleRadius,
|
||||
.speedScale = .7,
|
||||
.alpha = kMinorBlobAlpha,
|
||||
.topOffset = st::groupCallMinorBlobTopOffset,
|
||||
},
|
||||
} };
|
||||
};
|
||||
}
|
||||
|
||||
auto Colors() {
|
||||
using Vector = std::vector<QColor>;
|
||||
return base::flat_map<MuteState, Vector>{
|
||||
using Colors = anim::gradient_colors;
|
||||
return base::flat_map<BarState, Colors>{
|
||||
{
|
||||
MuteState::ForceMuted,
|
||||
Vector{ st::groupCallMembersBg->c, st::groupCallMembersBg->c }
|
||||
BarState::ForceMuted,
|
||||
Colors(QGradientStops{
|
||||
{ 0.0, st::groupCallForceMutedBar1->c },
|
||||
{ .35, st::groupCallForceMutedBar2->c },
|
||||
{ 1.0, st::groupCallForceMutedBar3->c } })
|
||||
},
|
||||
{
|
||||
MuteState::Active,
|
||||
Vector{ st::groupCallLive1->c, st::groupCallLive2->c }
|
||||
BarState::Active,
|
||||
Colors(Vector{ st::groupCallLive1->c, st::groupCallLive2->c })
|
||||
},
|
||||
{
|
||||
MuteState::Muted,
|
||||
Vector{ st::groupCallMuted1->c, st::groupCallMuted2->c }
|
||||
BarState::Muted,
|
||||
Colors(Vector{ st::groupCallMuted1->c, st::groupCallMuted2->c })
|
||||
},
|
||||
{
|
||||
BarState::Connecting,
|
||||
Colors(st::callBarBgMuted->c)
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -139,6 +166,10 @@ void DebugInfoBox::updateText() {
|
||||
|
||||
} // namespace
|
||||
|
||||
struct TopBar::User {
|
||||
Ui::GroupCallUser data;
|
||||
};
|
||||
|
||||
class Mute final : public Ui::IconButton {
|
||||
public:
|
||||
Mute(QWidget *parent, const style::IconButton &st)
|
||||
@@ -209,6 +240,12 @@ TopBar::TopBar(
|
||||
: RpWidget(parent)
|
||||
, _call(call)
|
||||
, _groupCall(groupCall)
|
||||
, _userpics(call
|
||||
? nullptr
|
||||
: std::make_unique<Ui::GroupCallUserpics>(
|
||||
st::groupCallTopBarUserpics,
|
||||
rpl::single(true),
|
||||
[=] { updateUserpics(); }))
|
||||
, _durationLabel(_call
|
||||
? object_ptr<Ui::LabelSimple>(this, st::callBarLabel)
|
||||
: object_ptr<Ui::LabelSimple>(nullptr))
|
||||
@@ -237,7 +274,9 @@ void TopBar::initControls() {
|
||||
if (const auto call = _call.get()) {
|
||||
call->setMuted(!call->muted());
|
||||
} else if (const auto group = _groupCall.get()) {
|
||||
if (group->muted() != MuteState::ForceMuted) {
|
||||
if (group->muted() == MuteState::ForceMuted) {
|
||||
Ui::Toast::Show(tr::lng_group_call_force_muted_sub(tr::now));
|
||||
} else {
|
||||
group->setMuted((group->muted() == MuteState::Muted)
|
||||
? MuteState::Active
|
||||
: MuteState::Muted);
|
||||
@@ -248,27 +287,36 @@ void TopBar::initControls() {
|
||||
const auto mapToState = [](bool muted) {
|
||||
return muted ? MuteState::Muted : MuteState::Active;
|
||||
};
|
||||
const auto fromState = _mute->lifetime().make_state<MuteState>(
|
||||
_call ? mapToState(_call->muted()) : _groupCall->muted());
|
||||
const auto fromState = _mute->lifetime().make_state<BarState>(
|
||||
BarStateFromMuteState(
|
||||
_call
|
||||
? mapToState(_call->muted())
|
||||
: _groupCall->muted(),
|
||||
false));
|
||||
auto muted = _call
|
||||
? _call->mutedValue() | rpl::map(mapToState)
|
||||
: (_groupCall->mutedValue()
|
||||
| MapPushToTalkToActive()
|
||||
| rpl::distinct_until_changed()
|
||||
| rpl::type_erased());
|
||||
? rpl::combine(
|
||||
_call->mutedValue() | rpl::map(mapToState),
|
||||
rpl::single(false)) | rpl::type_erased()
|
||||
: rpl::combine(
|
||||
(_groupCall->mutedValue()
|
||||
| MapPushToTalkToActive()
|
||||
| rpl::distinct_until_changed()
|
||||
| rpl::type_erased()),
|
||||
_groupCall->connectingValue());
|
||||
std::move(
|
||||
muted
|
||||
) | rpl::start_with_next([=](MuteState state) {
|
||||
setMuted(state != MuteState::Active);
|
||||
) | rpl::map(
|
||||
BarStateFromMuteState
|
||||
) | rpl::start_with_next([=](BarState state) {
|
||||
_isGroupConnecting = (state == BarState::Connecting);
|
||||
setMuted(state != BarState::Active);
|
||||
update();
|
||||
|
||||
const auto isForceMuted = (state == MuteState::ForceMuted);
|
||||
const auto isForceMuted = (state == BarState::ForceMuted);
|
||||
if (isForceMuted) {
|
||||
_mute->clearState();
|
||||
}
|
||||
_mute->setAttribute(
|
||||
Qt::WA_TransparentForMouseEvents,
|
||||
isForceMuted);
|
||||
_mute->setPointerCursor(!isForceMuted);
|
||||
|
||||
const auto to = 1.;
|
||||
const auto from = _switchStateAnimation.animating()
|
||||
@@ -278,8 +326,8 @@ void TopBar::initControls() {
|
||||
const auto toMuted = state;
|
||||
*fromState = state;
|
||||
|
||||
const auto crossFrom = (fromMuted != MuteState::Active) ? 1. : 0.;
|
||||
const auto crossTo = (toMuted != MuteState::Active) ? 1. : 0.;
|
||||
const auto crossFrom = (fromMuted != BarState::Active) ? 1. : 0.;
|
||||
const auto crossTo = (toMuted != BarState::Active) ? 1. : 0.;
|
||||
|
||||
auto animationCallback = [=](float64 value) {
|
||||
if (_groupCall) {
|
||||
@@ -290,7 +338,7 @@ void TopBar::initControls() {
|
||||
|
||||
const auto crossProgress = (crossFrom == crossTo)
|
||||
? crossTo
|
||||
: (crossFrom + float64(crossTo - crossFrom) * value);
|
||||
: anim::interpolateF(crossFrom, crossTo, value);
|
||||
_mute->setProgress(crossProgress);
|
||||
};
|
||||
|
||||
@@ -305,6 +353,14 @@ void TopBar::initControls() {
|
||||
|
||||
if (const auto group = _groupCall.get()) {
|
||||
subscribeToMembersChanges(group);
|
||||
|
||||
_isGroupConnecting.value(
|
||||
) | rpl::start_with_next([=](bool isConnecting) {
|
||||
_mute->setAttribute(
|
||||
Qt::WA_TransparentForMouseEvents,
|
||||
isConnecting);
|
||||
updateInfoLabels();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
if (const auto call = _call.get()) {
|
||||
@@ -335,11 +391,15 @@ void TopBar::initControls() {
|
||||
if (const auto call = _call.get()) {
|
||||
call->hangup();
|
||||
} else if (const auto group = _groupCall.get()) {
|
||||
Ui::show(Box(
|
||||
LeaveGroupCallBox,
|
||||
group,
|
||||
false,
|
||||
BoxContext::MainWindow));
|
||||
if (!group->peer()->canManageGroupCall()) {
|
||||
group->hangup();
|
||||
} else {
|
||||
Ui::show(Box(
|
||||
LeaveGroupCallBox,
|
||||
group,
|
||||
false,
|
||||
BoxContext::MainWindow));
|
||||
}
|
||||
}
|
||||
});
|
||||
updateDurationText();
|
||||
@@ -353,14 +413,12 @@ void TopBar::initBlobsUnder(
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr auto kHideDuration = kBlobLevelDuration1 * 2;
|
||||
|
||||
struct State {
|
||||
Ui::Paint::LinearBlobs paint = {
|
||||
LinearBlobs() | ranges::to_vector,
|
||||
kBlobLevelDuration1,
|
||||
kBlobLevelDuration2,
|
||||
1.
|
||||
LinearBlobs(),
|
||||
kBlobLevelDuration,
|
||||
1.,
|
||||
Ui::Paint::LinearBlob::Direction::TopDown
|
||||
};
|
||||
Ui::Animations::Simple hideAnimation;
|
||||
Ui::Animations::Basic animation;
|
||||
@@ -369,8 +427,6 @@ void TopBar::initBlobsUnder(
|
||||
crl::time lastTime = 0;
|
||||
float lastLevel = 0.;
|
||||
float levelBeforeLast = 0.;
|
||||
int maxHeight = st::groupCallMinorBlobMinRadius
|
||||
+ st::groupCallMinorBlobMaxRadius;
|
||||
};
|
||||
|
||||
_blobs = base::make_unique_q<Ui::RpWidget>(blobsParent);
|
||||
@@ -387,7 +443,7 @@ void TopBar::initBlobsUnder(
|
||||
|
||||
state->animation.init([=](crl::time now) {
|
||||
if (const auto last = state->hideLastTime; (last > 0)
|
||||
&& (now - last >= kHideDuration)) {
|
||||
&& (now - last >= kHideBlobsDuration)) {
|
||||
state->animation.stop();
|
||||
return false;
|
||||
}
|
||||
@@ -408,23 +464,14 @@ void TopBar::initBlobsUnder(
|
||||
auto hideBlobs = rpl::combine(
|
||||
rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
|
||||
Core::App().appDeactivatedValue(),
|
||||
group->stateValue(
|
||||
) | rpl::map([](Calls::GroupCall::State state) {
|
||||
using State = Calls::GroupCall::State;
|
||||
if (state != State::Creating
|
||||
&& state != State::Joining
|
||||
&& state != State::Joined
|
||||
&& state != State::Connecting) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}) | rpl::distinct_until_changed()
|
||||
) | rpl::map([](bool animDisabled, bool hide, bool isBadState) {
|
||||
return isBadState || animDisabled || hide;
|
||||
group->connectingValue()
|
||||
) | rpl::map([](bool animDisabled, bool hide, bool connecting) {
|
||||
return connecting || animDisabled || hide;
|
||||
});
|
||||
|
||||
std::move(
|
||||
hideBlobs
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](bool hide) {
|
||||
if (hide) {
|
||||
state->paint.setLevel(0.);
|
||||
@@ -443,7 +490,7 @@ void TopBar::initBlobsUnder(
|
||||
const auto to = hide ? 1. : 0.;
|
||||
state->hideAnimation.start([=](float64) {
|
||||
_blobs->update();
|
||||
}, from, to, kHideDuration);
|
||||
}, from, to, kHideBlobsDuration);
|
||||
}, lifetime());
|
||||
|
||||
std::move(
|
||||
@@ -451,7 +498,7 @@ void TopBar::initBlobsUnder(
|
||||
) | rpl::start_with_next([=](QRect rect) {
|
||||
_blobs->resize(
|
||||
rect.width(),
|
||||
std::min(state->maxHeight, rect.height()));
|
||||
(int)state->paint.maxRadius());
|
||||
_blobs->moveToLeft(rect.x(), rect.y() + rect.height());
|
||||
}, lifetime());
|
||||
|
||||
@@ -473,12 +520,9 @@ void TopBar::initBlobsUnder(
|
||||
p.setOpacity(1. - hidden);
|
||||
}
|
||||
const auto top = -_blobs->height() * hidden;
|
||||
const auto drawUnder = QRect(
|
||||
0,
|
||||
top,
|
||||
_blobs->width() + st::groupCallBlobWidthAdditional,
|
||||
0);
|
||||
state->paint.paint(p, _groupBrush, drawUnder, 0, 0);
|
||||
const auto width = _blobs->width();
|
||||
p.translate(0, top);
|
||||
state->paint.paint(p, _groupBrush, width);
|
||||
}, _blobs->lifetime());
|
||||
|
||||
group->levelUpdates(
|
||||
@@ -501,12 +545,12 @@ void TopBar::initBlobsUnder(
|
||||
}
|
||||
|
||||
void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||
const auto channel = call->channel();
|
||||
channel->session().changes().peerFlagsValue(
|
||||
channel,
|
||||
const auto peer = call->peer();
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=] {
|
||||
return channel->call();
|
||||
return peer->groupCall();
|
||||
}) | rpl::filter([=](Data::GroupCall *real) {
|
||||
const auto call = _groupCall.get();
|
||||
return call && real && (real->id() == call->id());
|
||||
@@ -515,20 +559,46 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||
) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
||||
return HistoryView::GroupCallTracker::ContentByCall(
|
||||
real,
|
||||
HistoryView::UserpicsInRowStyle{
|
||||
.size = st::groupCallTopBarUserpicSize,
|
||||
.shift = st::groupCallTopBarUserpicShift,
|
||||
.stroke = st::groupCallTopBarUserpicStroke,
|
||||
});
|
||||
st::groupCallTopBarUserpics.size);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
|
||||
const auto changed = (_userpics.size() != content.userpics.size());
|
||||
_userpics = content.userpics;
|
||||
if (changed) {
|
||||
updateControlsGeometry();
|
||||
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
|
||||
if (_users.size() != content.users.size()) {
|
||||
return true;
|
||||
}
|
||||
update();
|
||||
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
|
||||
if (_users[i].userpicKey != content.users[i].userpicKey
|
||||
|| _users[i].id != content.users[i].id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
|
||||
_users = content.users;
|
||||
for (auto &user : _users) {
|
||||
user.speaking = false;
|
||||
}
|
||||
_userpics->update(_users, !isHidden());
|
||||
}, lifetime());
|
||||
|
||||
_userpics->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
_userpicsWidth = width;
|
||||
updateControlsGeometry();
|
||||
}, lifetime());
|
||||
|
||||
call->peer()->session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::Name
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
// _peer may change for the same Panel.
|
||||
const auto call = _groupCall.get();
|
||||
return (call != nullptr) && (update.peer == call->peer());
|
||||
}) | rpl::start_with_next([=] {
|
||||
updateInfoLabels();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void TopBar::updateUserpics() {
|
||||
update(_mute->width(), 0, _userpics->maxWidth(), height());
|
||||
}
|
||||
|
||||
void TopBar::updateInfoLabels() {
|
||||
@@ -544,10 +614,13 @@ void TopBar::setInfoLabels() {
|
||||
_fullInfoLabel->setText(fullName.toUpper());
|
||||
_shortInfoLabel->setText(shortName.toUpper());
|
||||
} else if (const auto group = _groupCall.get()) {
|
||||
const auto channel = group->channel();
|
||||
const auto name = channel->name;
|
||||
_fullInfoLabel->setText(name.toUpper());
|
||||
_shortInfoLabel->setText(name.toUpper());
|
||||
const auto peer = group->peer();
|
||||
const auto name = peer->name;
|
||||
const auto text = _isGroupConnecting.current()
|
||||
? tr::lng_group_call_connecting(tr::now)
|
||||
: name.toUpper();
|
||||
_fullInfoLabel->setText(text);
|
||||
_shortInfoLabel->setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,8 +661,13 @@ void TopBar::updateControlsGeometry() {
|
||||
_durationLabel->moveToLeft(left, st::callBarLabelTop);
|
||||
left += _durationLabel->width() + st::callBarSkip;
|
||||
}
|
||||
if (!_userpics.isNull()) {
|
||||
left += _userpics.width() / _userpics.devicePixelRatio();
|
||||
if (_userpicsWidth) {
|
||||
const auto single = st::groupCallTopBarUserpics.size;
|
||||
const auto skip = anim::interpolate(
|
||||
0,
|
||||
st::callBarSkip,
|
||||
std::min(_userpicsWidth, single) / float64(single));
|
||||
left += _userpicsWidth + skip;
|
||||
}
|
||||
if (_signalBars) {
|
||||
_signalBars->moveToLeft(left, (height() - _signalBars->height()) / 2);
|
||||
@@ -641,11 +719,10 @@ void TopBar::paintEvent(QPaintEvent *e) {
|
||||
: (_muted ? st::callBarBgMuted : st::callBarBg);
|
||||
p.fillRect(e->rect(), std::move(brush));
|
||||
|
||||
if (!_userpics.isNull()) {
|
||||
const auto imageSize = _userpics.size()
|
||||
/ _userpics.devicePixelRatio();
|
||||
const auto top = (height() - imageSize.height()) / 2;
|
||||
p.drawImage(_mute->width(), top, _userpics);
|
||||
if (_userpicsWidth) {
|
||||
const auto size = st::groupCallTopBarUserpics.size;
|
||||
const auto top = (height() - size) / 2;
|
||||
_userpics->paint(p, _mute->width(), top, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ class IconButton;
|
||||
class AbstractButton;
|
||||
class LabelSimple;
|
||||
class FlatLabel;
|
||||
struct GroupCallUser;
|
||||
class GroupCallUserpics;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Main {
|
||||
@@ -33,6 +35,7 @@ class GroupCall;
|
||||
class SignalBars;
|
||||
class Mute;
|
||||
enum class MuteState;
|
||||
enum class BarState;
|
||||
|
||||
class TopBar : public Ui::RpWidget {
|
||||
public:
|
||||
@@ -49,6 +52,8 @@ protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
struct User;
|
||||
|
||||
TopBar(
|
||||
QWidget *parent,
|
||||
const base::weak_ptr<Call> &call,
|
||||
@@ -63,12 +68,15 @@ private:
|
||||
void setMuted(bool mute);
|
||||
|
||||
void subscribeToMembersChanges(not_null<GroupCall*> call);
|
||||
void updateUserpics();
|
||||
|
||||
const base::weak_ptr<Call> _call;
|
||||
const base::weak_ptr<GroupCall> _groupCall;
|
||||
|
||||
bool _muted = false;
|
||||
QImage _userpics;
|
||||
std::vector<Ui::GroupCallUser> _users;
|
||||
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
|
||||
int _userpicsWidth = 0;
|
||||
object_ptr<Ui::LabelSimple> _durationLabel;
|
||||
object_ptr<SignalBars> _signalBars;
|
||||
object_ptr<Ui::FlatLabel> _fullInfoLabel;
|
||||
@@ -79,8 +87,10 @@ private:
|
||||
object_ptr<Ui::IconButton> _hangup;
|
||||
base::unique_qptr<Ui::RpWidget> _blobs;
|
||||
|
||||
rpl::variable<bool> _isGroupConnecting = false;
|
||||
|
||||
QBrush _groupBrush;
|
||||
anim::linear_gradients<MuteState> _gradients;
|
||||
anim::linear_gradients<BarState> _gradients;
|
||||
Ui::Animations::Simple _switchStateAnimation;
|
||||
|
||||
base::Timer _updateDurationTimer;
|
||||
|
||||
@@ -877,11 +877,8 @@ void TabbedSelector::scrollToY(int y) {
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::contextMenuEvent(QContextMenuEvent *e) {
|
||||
void TabbedSelector::showMenuWithType(SendMenu::Type type) {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(this);
|
||||
const auto type = _sendMenuType
|
||||
? _sendMenuType()
|
||||
: SendMenu::Type::Disabled;
|
||||
currentTab()->widget()->fillContextMenu(_menu, type);
|
||||
|
||||
if (!_menu->actions().empty()) {
|
||||
@@ -889,6 +886,13 @@ void TabbedSelector::contextMenuEvent(QContextMenuEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> TabbedSelector::contextMenuRequested() const {
|
||||
return events(
|
||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||
return e->type() == QEvent::ContextMenu;
|
||||
}) | rpl::to_empty;
|
||||
}
|
||||
|
||||
TabbedSelector::Inner::Inner(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
rpl::producer<> cancelled() const;
|
||||
rpl::producer<> checkForHide() const;
|
||||
rpl::producer<> slideFinished() const;
|
||||
rpl::producer<> contextMenuRequested() const;
|
||||
|
||||
void setRoundRadius(int radius);
|
||||
void refreshStickers();
|
||||
@@ -109,9 +110,7 @@ public:
|
||||
_beforeHidingCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void setSendMenuType(Fn<SendMenu::Type()> &&callback) {
|
||||
_sendMenuType = std::move(callback);
|
||||
}
|
||||
void showMenuWithType(SendMenu::Type type);
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e);
|
||||
@@ -127,7 +126,6 @@ public:
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
private:
|
||||
class Tab {
|
||||
@@ -228,8 +226,6 @@ private:
|
||||
Fn<void(SelectorTab)> _afterShownCallback;
|
||||
Fn<void(SelectorTab)> _beforeHidingCallback;
|
||||
|
||||
Fn<SendMenu::Type()> _sendMenuType;
|
||||
|
||||
rpl::event_stream<> _showRequests;
|
||||
rpl::event_stream<> _slideFinished;
|
||||
|
||||
|
||||
@@ -789,9 +789,17 @@ bool Application::openCustomUrl(
|
||||
|
||||
}
|
||||
|
||||
void Application::preventOrInvoke(Fn<void()> &&callback) {
|
||||
_window->preventOrInvoke(std::move(callback));
|
||||
}
|
||||
|
||||
void Application::lockByPasscode() {
|
||||
_passcodeLock = true;
|
||||
_window->setupPasscodeLock();
|
||||
preventOrInvoke([=] {
|
||||
if (_window) {
|
||||
_passcodeLock = true;
|
||||
_window->setupPasscodeLock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Application::unlockPasscode() {
|
||||
@@ -907,18 +915,25 @@ bool Application::closeActiveWindow() {
|
||||
if (hideMediaView()) {
|
||||
return true;
|
||||
}
|
||||
if (const auto window = activeWindow()) {
|
||||
window->close();
|
||||
return true;
|
||||
if (!calls().closeCurrentActiveCall()) {
|
||||
if (const auto window = activeWindow()) {
|
||||
if (window->widget()->isVisible()
|
||||
&& window->widget()->isActive()) {
|
||||
window->close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Application::minimizeActiveWindow() {
|
||||
hideMediaView();
|
||||
if (const auto window = activeWindow()) {
|
||||
window->minimize();
|
||||
return true;
|
||||
if (!calls().minimizeCurrentActiveCall()) {
|
||||
if (const auto window = activeWindow()) {
|
||||
window->minimize();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -277,6 +277,8 @@ public:
|
||||
void switchFreeType();
|
||||
void writeInstallBetaVersionsSetting();
|
||||
|
||||
void preventOrInvoke(Fn<void()> &&callback);
|
||||
|
||||
void call_handleObservables();
|
||||
|
||||
protected:
|
||||
|
||||
@@ -21,60 +21,6 @@ namespace {
|
||||
|
||||
std::map<int, const char*> BetaLogs() {
|
||||
return {
|
||||
{
|
||||
2001008,
|
||||
"- Add support for full group message history export.\n"
|
||||
|
||||
"- Allow export of a single chat message history in JSON format."
|
||||
},
|
||||
{
|
||||
2001014,
|
||||
"- Support for multiple accounts."
|
||||
},
|
||||
{
|
||||
2001017,
|
||||
"- Fix messages editing in a non-active account.\n"
|
||||
|
||||
"- Fix large animated emoji messages editing.\n"
|
||||
|
||||
"- Fix high definition GIF animations opening in media viewer.\n"
|
||||
|
||||
"- Multiple crash fixes."
|
||||
},
|
||||
{
|
||||
2001018,
|
||||
"- Fix a possible crash in Picture-in-Picture video player.\n"
|
||||
|
||||
"- Fix copying links from message texts.\n"
|
||||
|
||||
"- Raise file size limit to 2000 MB.\n"
|
||||
|
||||
"- Allow using system window frame in Windows and Linux."
|
||||
},
|
||||
{
|
||||
2001019,
|
||||
"- File uploading in an inactive account correctly finishes.\n"
|
||||
|
||||
"- Stickers panel works correctly after switching between accounts.\n"
|
||||
|
||||
"- Large .webp files are not shown as stickers.\n"
|
||||
|
||||
"- MacBook TouchBar support was fully rewritten with fixes for multiple accounts.\n"
|
||||
|
||||
"- Custom window title bar works in all Linux versions.\n"
|
||||
|
||||
"- Passcode doesn't auto-lock while you're active in other apps on Linux X11."
|
||||
},
|
||||
{
|
||||
2001021,
|
||||
"- Edit your scheduled messages.\n"
|
||||
|
||||
"- See the unread messages indicator for your additional accounts on the main menu button.\n"
|
||||
|
||||
"- Use Auto-Night Mode to make Telegram night mode match the system Dark Mode settings.\n"
|
||||
|
||||
"- Enjoy dark native window frame for Telegram night mode on Windows.\n"
|
||||
},
|
||||
{
|
||||
2004006,
|
||||
"- Fix image compression option when sending files with drag-n-drop.\n"
|
||||
@@ -109,6 +55,56 @@ std::map<int, const char*> BetaLogs() {
|
||||
2004012,
|
||||
"- Voice chats in groups. (alpha version)\n"
|
||||
},
|
||||
{
|
||||
2004014,
|
||||
"- Create voice chats in legacy groups.\n"
|
||||
|
||||
"- Fix sticker pack opening.\n"
|
||||
|
||||
"- Fix group status display.\n"
|
||||
|
||||
"- Fix group members display.\n"
|
||||
},
|
||||
{
|
||||
2004015,
|
||||
"- Improve design of voice chats.\n"
|
||||
|
||||
"- Fix sending of voice messages as replies.\n"
|
||||
|
||||
"- Fix 'Open With' menu position in macOS.\n"
|
||||
|
||||
"- Fix freeze on secondary screen disconnect.\n"
|
||||
},
|
||||
{
|
||||
2005002,
|
||||
"- Fix possible crash in video calls.\n"
|
||||
|
||||
"- Fix possible crash in connecting to voice chats.\n"
|
||||
|
||||
"- Use different audio module code on Windows in calls.\n"
|
||||
},
|
||||
{
|
||||
2005003,
|
||||
"- Allow using mouse buttons in Push-to-Talk shortcut.\n"
|
||||
|
||||
"- Fix blurred thumbnails in Shared Links section.\n"
|
||||
},
|
||||
{
|
||||
2005004,
|
||||
"- Implement new audio module code for calls and voice chats.\n"
|
||||
|
||||
"- Allow retracting votes from polls in comments to channel posts.\n"
|
||||
|
||||
"- Show small voice chat button for empty voice chats.\n"
|
||||
|
||||
"- Fix media viewer updating when screen resolution is changed.\n"
|
||||
},
|
||||
{
|
||||
2005005,
|
||||
"- Fix recording of audio in voice chats.\n"
|
||||
|
||||
"- Fix media viewer zoom and crashing.\n"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -13,12 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "facades.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
Settings::Settings()
|
||||
: _sendSubmitWay(Ui::InputSubmitSettings::Enter)
|
||||
: _callAudioBackend(Webrtc::Backend::OpenAL)
|
||||
, _sendSubmitWay(Ui::InputSubmitSettings::Enter)
|
||||
, _floatPlayerColumn(Window::Column::Second)
|
||||
, _floatPlayerCorner(RectPart::TopRight)
|
||||
, _dialogsWidthRatio(DefaultDialogsWidthRatio()) {
|
||||
@@ -112,7 +114,8 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_ipRevealWarning ? 1 : 0)
|
||||
<< qint32(_groupCallPushToTalk ? 1 : 0)
|
||||
<< _groupCallPushToTalkShortcut
|
||||
<< qint64(_groupCallPushToTalkDelay);
|
||||
<< qint64(_groupCallPushToTalkDelay)
|
||||
<< qint32(_callAudioBackend);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -183,6 +186,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 groupCallPushToTalk = _groupCallPushToTalk ? 1 : 0;
|
||||
QByteArray groupCallPushToTalkShortcut = _groupCallPushToTalkShortcut;
|
||||
qint64 groupCallPushToTalkDelay = _groupCallPushToTalkDelay;
|
||||
qint32 callAudioBackend = static_cast<qint32>(_callAudioBackend);
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -275,6 +279,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
>> groupCallPushToTalkShortcut
|
||||
>> groupCallPushToTalkDelay;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> callAudioBackend;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -369,6 +376,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_groupCallPushToTalk = (groupCallPushToTalk == 1);
|
||||
_groupCallPushToTalkShortcut = groupCallPushToTalkShortcut;
|
||||
_groupCallPushToTalkDelay = groupCallPushToTalkDelay;
|
||||
auto uncheckedBackend = static_cast<Webrtc::Backend>(callAudioBackend);
|
||||
switch (uncheckedBackend) {
|
||||
case Webrtc::Backend::OpenAL:
|
||||
case Webrtc::Backend::ADM:
|
||||
case Webrtc::Backend::ADM2: _callAudioBackend = uncheckedBackend; break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::chatWide() const {
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace Window {
|
||||
enum class Column;
|
||||
} // namespace Window
|
||||
|
||||
namespace Webrtc {
|
||||
enum class Backend;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Settings final {
|
||||
@@ -217,6 +221,12 @@ public:
|
||||
void setCallAudioDuckingEnabled(bool value) {
|
||||
_callAudioDuckingEnabled = value;
|
||||
}
|
||||
[[nodiscard]] Webrtc::Backend callAudioBackend() const {
|
||||
return _callAudioBackend;
|
||||
}
|
||||
void setCallAudioBackend(Webrtc::Backend backend) {
|
||||
_callAudioBackend = backend;
|
||||
}
|
||||
[[nodiscard]] bool groupCallPushToTalk() const {
|
||||
return _groupCallPushToTalk;
|
||||
}
|
||||
@@ -531,13 +541,14 @@ private:
|
||||
int _callOutputVolume = 100;
|
||||
int _callInputVolume = 100;
|
||||
bool _callAudioDuckingEnabled = true;
|
||||
Webrtc::Backend _callAudioBackend = Webrtc::Backend();
|
||||
bool _groupCallPushToTalk = false;
|
||||
QByteArray _groupCallPushToTalkShortcut;
|
||||
crl::time _groupCallPushToTalkDelay = 20;
|
||||
Window::Theme::AccentColors _themesAccentColors;
|
||||
bool _lastSeenWarningSeen = false;
|
||||
Ui::SendFilesWay _sendFilesWay;
|
||||
Ui::InputSubmitSettings _sendSubmitWay;
|
||||
Ui::SendFilesWay _sendFilesWay = Ui::SendFilesWay();
|
||||
Ui::InputSubmitSettings _sendSubmitWay = Ui::InputSubmitSettings();
|
||||
base::flat_map<QString, QString> _soundOverrides;
|
||||
bool _exeLaunchWarning = true;
|
||||
bool _ipRevealWarning = true;
|
||||
@@ -553,8 +564,8 @@ private:
|
||||
rpl::variable<bool> _autoDownloadDictionaries = true;
|
||||
rpl::variable<bool> _mainMenuAccountsShown = true;
|
||||
bool _tabbedSelectorSectionEnabled = false; // per-window
|
||||
Window::Column _floatPlayerColumn; // per-window
|
||||
RectPart _floatPlayerCorner; // per-window
|
||||
Window::Column _floatPlayerColumn = Window::Column(); // per-window
|
||||
RectPart _floatPlayerCorner = RectPart(); // per-window
|
||||
bool _thirdSectionInfoEnabled = true; // per-window
|
||||
rpl::event_stream<bool> _thirdSectionInfoEnabledValue; // per-window
|
||||
int _thirdSectionExtendedBy = -1; // per-window
|
||||
|
||||
@@ -270,14 +270,10 @@ std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
|
||||
|
||||
Launcher::Launcher(
|
||||
int argc,
|
||||
char *argv[],
|
||||
const QString &deviceModel,
|
||||
const QString &systemVersion)
|
||||
char *argv[])
|
||||
: _argc(argc)
|
||||
, _argv(argv)
|
||||
, _baseIntegration(_argc, _argv)
|
||||
, _deviceModel(deviceModel)
|
||||
, _systemVersion(systemVersion) {
|
||||
, _baseIntegration(_argc, _argv) {
|
||||
base::Integration::Set(&_baseIntegration);
|
||||
}
|
||||
|
||||
@@ -328,6 +324,23 @@ int Launcher::exec() {
|
||||
// Must be started before Platform is started.
|
||||
Logs::start(this);
|
||||
|
||||
if (Logs::DebugEnabled()) {
|
||||
const auto openalLogPath = QDir::toNativeSeparators(
|
||||
cWorkingDir() + qsl("DebugLogs/last_openal_log.txt"));
|
||||
|
||||
qputenv("ALSOFT_LOGLEVEL", "3");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_wputenv_s(
|
||||
L"ALSOFT_LOGFILE",
|
||||
openalLogPath.toStdWString().c_str());
|
||||
#else // Q_OS_WIN
|
||||
qputenv(
|
||||
"ALSOFT_LOGFILE",
|
||||
QFile::encodeName(openalLogPath));
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
|
||||
// Must be started before Sandbox is created.
|
||||
Platform::start();
|
||||
Ui::DisableCustomScaling();
|
||||
@@ -419,14 +432,6 @@ void Launcher::prepareSettings() {
|
||||
processArguments();
|
||||
}
|
||||
|
||||
QString Launcher::deviceModel() const {
|
||||
return _deviceModel;
|
||||
}
|
||||
|
||||
QString Launcher::systemVersion() const {
|
||||
return _systemVersion;
|
||||
}
|
||||
|
||||
uint64 Launcher::installationTag() const {
|
||||
return InstallationTag;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ class Launcher {
|
||||
public:
|
||||
Launcher(
|
||||
int argc,
|
||||
char *argv[],
|
||||
const QString &deviceModel,
|
||||
const QString &systemVersion);
|
||||
char *argv[]);
|
||||
|
||||
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
|
||||
|
||||
@@ -26,8 +24,6 @@ public:
|
||||
QString argumentsString() const;
|
||||
bool customWorkingDir() const;
|
||||
|
||||
QString deviceModel() const;
|
||||
QString systemVersion() const;
|
||||
uint64 installationTag() const;
|
||||
|
||||
bool checkPortableVersionFolder();
|
||||
@@ -67,9 +63,6 @@ private:
|
||||
QStringList _arguments;
|
||||
BaseIntegration _baseIntegration;
|
||||
|
||||
const QString _deviceModel;
|
||||
const QString _systemVersion;
|
||||
|
||||
bool _customWorkingDir = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -1591,7 +1591,7 @@ void UpdateApplication() {
|
||||
if (const auto window = App::wnd()) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
controller->showSection(
|
||||
Info::Memento(
|
||||
std::make_shared<Info::Memento>(
|
||||
Info::Settings::Tag{ controller->session().user() },
|
||||
Info::Section::SettingsType::Advanced),
|
||||
Window::SectionShow());
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 2004013;
|
||||
constexpr auto AppVersionStr = "2.4.13";
|
||||
constexpr auto AppVersion = 2005005;
|
||||
constexpr auto AppVersionStr = "2.5.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -104,13 +104,9 @@ void ChannelData::setInviteLink(const QString &newInviteLink) {
|
||||
}
|
||||
}
|
||||
|
||||
QString ChannelData::inviteLink() const {
|
||||
return _inviteLink;
|
||||
}
|
||||
|
||||
bool ChannelData::canHaveInviteLink() const {
|
||||
return (adminRights() & AdminRight::f_invite_users)
|
||||
|| amCreator();
|
||||
return amCreator()
|
||||
|| (adminRights() & AdminRight::f_invite_users);
|
||||
}
|
||||
|
||||
void ChannelData::setLocation(const MTPChannelLocation &data) {
|
||||
@@ -524,10 +520,6 @@ bool ChannelData::canRestrictUser(not_null<UserData*> user) const {
|
||||
return adminRights() & AdminRight::f_ban_users;
|
||||
}
|
||||
|
||||
bool ChannelData::canManageCall() const {
|
||||
return amCreator() || (adminRights() & AdminRight::f_manage_call);
|
||||
}
|
||||
|
||||
void ChannelData::setAdminRights(const MTPChatAdminRights &rights) {
|
||||
if (rights.c_chatAdminRights().vflags().v == adminRights()) {
|
||||
return;
|
||||
@@ -677,14 +669,24 @@ void ChannelData::privateErrorReceived() {
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelData::setCall(const MTPInputGroupCall &call) {
|
||||
void ChannelData::migrateCall(std::unique_ptr<Data::GroupCall> call) {
|
||||
Expects(_call == nullptr);
|
||||
Expects(call != nullptr);
|
||||
|
||||
_call = std::move(call);
|
||||
_call->setPeer(this);
|
||||
session().changes().peerUpdated(this, UpdateFlag::GroupCall);
|
||||
addFlags(MTPDchannel::Flag::f_call_active);
|
||||
}
|
||||
|
||||
void ChannelData::setGroupCall(const MTPInputGroupCall &call) {
|
||||
call.match([&](const MTPDinputGroupCall &data) {
|
||||
if (_call && _call->id() == data.vid().v) {
|
||||
return;
|
||||
} else if (!_call && !data.vid().v) {
|
||||
return;
|
||||
} else if (!data.vid().v) {
|
||||
clearCall();
|
||||
clearGroupCall();
|
||||
return;
|
||||
}
|
||||
const auto hasCall = (_call != nullptr);
|
||||
@@ -701,7 +703,7 @@ void ChannelData::setCall(const MTPInputGroupCall &call) {
|
||||
});
|
||||
}
|
||||
|
||||
void ChannelData::clearCall() {
|
||||
void ChannelData::clearGroupCall() {
|
||||
if (!_call) {
|
||||
return;
|
||||
}
|
||||
@@ -744,9 +746,9 @@ void ApplyChannelUpdate(
|
||||
auto canEditStickers = channel->canEditStickers();
|
||||
|
||||
if (const auto call = update.vcall()) {
|
||||
channel->setCall(*call);
|
||||
channel->setGroupCall(*call);
|
||||
} else {
|
||||
channel->clearCall();
|
||||
channel->clearGroupCall();
|
||||
}
|
||||
|
||||
channel->setFullFlags(update.vflags().v);
|
||||
|
||||
@@ -11,10 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_pts_waiter.h"
|
||||
#include "data/data_location.h"
|
||||
|
||||
namespace Data {
|
||||
class GroupCall;
|
||||
} // namespace Data
|
||||
|
||||
struct ChannelLocation {
|
||||
QString address;
|
||||
Data::LocationPoint point;
|
||||
@@ -307,10 +303,11 @@ public:
|
||||
[[nodiscard]] bool canDelete() const;
|
||||
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canManageCall() const;
|
||||
|
||||
void setInviteLink(const QString &newInviteLink);
|
||||
[[nodiscard]] QString inviteLink() const;
|
||||
[[nodiscard]] QString inviteLink() const {
|
||||
return _inviteLink;
|
||||
}
|
||||
[[nodiscard]] bool canHaveInviteLink() const;
|
||||
|
||||
void setLocation(const MTPChannelLocation &data);
|
||||
@@ -399,11 +396,12 @@ public:
|
||||
[[nodiscard]] QString invitePeekHash() const;
|
||||
void privateErrorReceived();
|
||||
|
||||
[[nodiscard]] Data::GroupCall *call() const {
|
||||
[[nodiscard]] Data::GroupCall *groupCall() const {
|
||||
return _call.get();
|
||||
}
|
||||
void setCall(const MTPInputGroupCall &call);
|
||||
void clearCall();
|
||||
void migrateCall(std::unique_ptr<Data::GroupCall> call);
|
||||
void setGroupCall(const MTPInputGroupCall &call);
|
||||
void clearGroupCall();
|
||||
|
||||
// Still public data members.
|
||||
uint64 access = 0;
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -24,6 +25,14 @@ using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
ChatData::ChatData(not_null<Data::Session*> owner, PeerId id)
|
||||
: PeerData(owner, id)
|
||||
, inputChat(MTP_int(bareId())) {
|
||||
_flags.changes(
|
||||
) | rpl::start_with_next([=](const Flags::Change &change) {
|
||||
if (change.diff & MTPDchat::Flag::f_call_not_empty) {
|
||||
if (const auto history = this->owner().historyLoaded(this)) {
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void ChatData::setPhoto(const MTPChatPhoto &photo) {
|
||||
@@ -124,6 +133,11 @@ void ChatData::setInviteLink(const QString &newInviteLink) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatData::canHaveInviteLink() const {
|
||||
return amCreator()
|
||||
|| (adminRights() & AdminRight::f_invite_users);
|
||||
}
|
||||
|
||||
void ChatData::setAdminRights(const MTPChatAdminRights &rights) {
|
||||
if (rights.c_chatAdminRights().vflags().v == adminRights()) {
|
||||
return;
|
||||
@@ -176,6 +190,47 @@ void ChatData::setMigrateToChannel(ChannelData *channel) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChatData::setGroupCall(const MTPInputGroupCall &call) {
|
||||
if (migrateTo()) {
|
||||
return;
|
||||
}
|
||||
call.match([&](const MTPDinputGroupCall &data) {
|
||||
if (_call && _call->id() == data.vid().v) {
|
||||
return;
|
||||
} else if (!_call && !data.vid().v) {
|
||||
return;
|
||||
} else if (!data.vid().v) {
|
||||
clearGroupCall();
|
||||
return;
|
||||
}
|
||||
const auto hasCall = (_call != nullptr);
|
||||
if (hasCall) {
|
||||
owner().unregisterGroupCall(_call.get());
|
||||
}
|
||||
_call = std::make_unique<Data::GroupCall>(
|
||||
this,
|
||||
data.vid().v,
|
||||
data.vaccess_hash().v);
|
||||
owner().registerGroupCall(_call.get());
|
||||
session().changes().peerUpdated(this, UpdateFlag::GroupCall);
|
||||
addFlags(MTPDchat::Flag::f_call_active);
|
||||
});
|
||||
}
|
||||
|
||||
void ChatData::clearGroupCall() {
|
||||
if (!_call) {
|
||||
return;
|
||||
} else if (const auto group = migrateTo(); group && !group->groupCall()) {
|
||||
group->migrateCall(base::take(_call));
|
||||
} else {
|
||||
owner().unregisterGroupCall(_call.get());
|
||||
_call = nullptr;
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::GroupCall);
|
||||
removeFlags(MTPDchat::Flag::f_call_active
|
||||
| MTPDchat::Flag::f_call_not_empty);
|
||||
}
|
||||
|
||||
namespace Data {
|
||||
|
||||
void ApplyChatUpdate(
|
||||
@@ -310,6 +365,12 @@ void ApplyChatUpdate(
|
||||
void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
|
||||
ApplyChatUpdate(chat, update.vparticipants());
|
||||
|
||||
if (const auto call = update.vcall()) {
|
||||
chat->setGroupCall(*call);
|
||||
} else {
|
||||
chat->clearGroupCall();
|
||||
}
|
||||
|
||||
if (const auto info = update.vbot_info()) {
|
||||
for (const auto &item : info->v) {
|
||||
item.match([&](const MTPDbotInfo &data) {
|
||||
|
||||
@@ -18,6 +18,7 @@ public:
|
||||
| MTPDchat::Flag::f_deactivated
|
||||
| MTPDchat::Flag::f_migrated_to
|
||||
| MTPDchat::Flag::f_admin_rights
|
||||
| MTPDchat::Flag::f_call_not_empty
|
||||
| MTPDchat::Flag::f_default_banned_rights;
|
||||
using Flags = Data::Flags<
|
||||
MTPDchat::Flags,
|
||||
@@ -141,9 +142,10 @@ public:
|
||||
void applyEditAdmin(not_null<UserData*> user, bool isAdmin);
|
||||
|
||||
void setInviteLink(const QString &newInviteLink);
|
||||
QString inviteLink() const {
|
||||
[[nodiscard]] QString inviteLink() const {
|
||||
return _inviteLink;
|
||||
}
|
||||
[[nodiscard]] bool canHaveInviteLink() const;
|
||||
void refreshBotStatus();
|
||||
|
||||
enum class UpdateStatus {
|
||||
@@ -162,6 +164,12 @@ public:
|
||||
ChannelData *getMigrateToChannel() const;
|
||||
void setMigrateToChannel(ChannelData *channel);
|
||||
|
||||
[[nodiscard]] Data::GroupCall *groupCall() const {
|
||||
return _call.get();
|
||||
}
|
||||
void setGroupCall(const MTPInputGroupCall &call);
|
||||
void clearGroupCall();
|
||||
|
||||
// Still public data members.
|
||||
const MTPint inputChat;
|
||||
|
||||
@@ -185,7 +193,10 @@ private:
|
||||
AdminRightFlags _adminRights;
|
||||
int _version = 0;
|
||||
|
||||
std::unique_ptr<Data::GroupCall> _call;
|
||||
|
||||
ChannelData *_migratedTo = nullptr;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -7,30 +7,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_group_call.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "calls/calls_group_call.h"
|
||||
#include "core/application.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRequestPerPage = 30;
|
||||
constexpr auto kSpeakingAfterActive = crl::time(6000);
|
||||
constexpr auto kActiveAfterJoined = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
GroupCall::GroupCall(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> peer,
|
||||
uint64 id,
|
||||
uint64 accessHash)
|
||||
: _channel(channel)
|
||||
, _id(id)
|
||||
, _accessHash(accessHash) {
|
||||
: _id(id)
|
||||
, _accessHash(accessHash)
|
||||
, _peer(peer)
|
||||
, _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) {
|
||||
}
|
||||
|
||||
GroupCall::~GroupCall() {
|
||||
api().request(_unknownSsrcsRequestId).cancel();
|
||||
api().request(_unknownUsersRequestId).cancel();
|
||||
api().request(_participantsRequestId).cancel();
|
||||
api().request(_reloadRequestId).cancel();
|
||||
}
|
||||
@@ -39,14 +47,21 @@ uint64 GroupCall::id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
not_null<ChannelData*> GroupCall::channel() const {
|
||||
return _channel;
|
||||
not_null<PeerData*> GroupCall::peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
MTPInputGroupCall GroupCall::input() const {
|
||||
return MTP_inputGroupCall(MTP_long(_id), MTP_long(_accessHash));
|
||||
}
|
||||
|
||||
void GroupCall::setPeer(not_null<PeerData*> peer) {
|
||||
Expects(peer->migrateFrom() == _peer);
|
||||
Expects(_peer->migrateTo() == peer);
|
||||
|
||||
_peer = peer;
|
||||
}
|
||||
|
||||
auto GroupCall::participants() const
|
||||
-> const std::vector<Participant> & {
|
||||
return _participants;
|
||||
@@ -70,7 +85,7 @@ void GroupCall::requestParticipants() {
|
||||
)).done([=](const MTPphone_GroupParticipants &result) {
|
||||
result.match([&](const MTPDphone_groupParticipants &data) {
|
||||
_nextOffset = qs(data.vnext_offset());
|
||||
_channel->owner().processUsers(data.vusers());
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::SliceLoaded);
|
||||
@@ -85,30 +100,43 @@ void GroupCall::requestParticipants() {
|
||||
});
|
||||
_participantsSliceAdded.fire({});
|
||||
_participantsRequestId = 0;
|
||||
changeChannelEmptyCallFlag();
|
||||
changePeerEmptyCallFlag();
|
||||
}).fail([=](const RPCError &error) {
|
||||
_fullCount = _participants.size();
|
||||
_allReceived = true;
|
||||
_participantsRequestId = 0;
|
||||
changeChannelEmptyCallFlag();
|
||||
changePeerEmptyCallFlag();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GroupCall::changeChannelEmptyCallFlag() {
|
||||
constexpr auto flag = MTPDchannel::Flag::f_call_not_empty;
|
||||
if (_channel->call() != this) {
|
||||
void GroupCall::changePeerEmptyCallFlag() {
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
constexpr auto chatFlag = MTPDchat::Flag::f_call_not_empty;
|
||||
constexpr auto channelFlag = MTPDchannel::Flag::f_call_not_empty;
|
||||
if (_peer->groupCall() != this) {
|
||||
return;
|
||||
} else if (fullCount() > 0) {
|
||||
if (!(_channel->flags() & flag)) {
|
||||
_channel->addFlags(flag);
|
||||
_channel->session().changes().peerUpdated(
|
||||
_channel,
|
||||
if (chat && !(chat->flags() & chatFlag)) {
|
||||
chat->addFlags(chatFlag);
|
||||
chat->session().changes().peerUpdated(
|
||||
chat,
|
||||
Data::PeerUpdate::Flag::GroupCall);
|
||||
} else if (channel && !(channel->flags() & channelFlag)) {
|
||||
channel->addFlags(channelFlag);
|
||||
channel->session().changes().peerUpdated(
|
||||
channel,
|
||||
Data::PeerUpdate::Flag::GroupCall);
|
||||
}
|
||||
} else if (_channel->flags() & flag) {
|
||||
_channel->removeFlags(flag);
|
||||
_channel->session().changes().peerUpdated(
|
||||
_channel,
|
||||
} else if (chat && (chat->flags() & chatFlag)) {
|
||||
chat->removeFlags(chatFlag);
|
||||
chat->session().changes().peerUpdated(
|
||||
chat,
|
||||
Data::PeerUpdate::Flag::GroupCall);
|
||||
} else if (channel && (channel->flags() & channelFlag)) {
|
||||
channel->removeFlags(channelFlag);
|
||||
channel->session().changes().peerUpdated(
|
||||
channel,
|
||||
Data::PeerUpdate::Flag::GroupCall);
|
||||
}
|
||||
}
|
||||
@@ -159,13 +187,17 @@ void GroupCall::applyCall(const MTPGroupCall &call, bool force) {
|
||||
_canChangeJoinMuted = data.is_can_change_join_muted();
|
||||
_version = data.vversion().v;
|
||||
_fullCount = data.vparticipants_count().v;
|
||||
changeChannelEmptyCallFlag();
|
||||
changePeerEmptyCallFlag();
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
const auto id = _id;
|
||||
const auto channel = _channel;
|
||||
crl::on_main(&channel->session(), [=] {
|
||||
if (channel->call() && channel->call()->id() == id) {
|
||||
channel->clearCall();
|
||||
const auto peer = _peer;
|
||||
crl::on_main(&peer->session(), [=] {
|
||||
if (peer->groupCall() && peer->groupCall()->id() == id) {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->clearGroupCall();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->clearGroupCall();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -182,8 +214,9 @@ void GroupCall::reload() {
|
||||
MTPphone_GetGroupCall(input())
|
||||
).done([=](const MTPphone_GroupCall &result) {
|
||||
result.match([&](const MTPDphone_groupCall &data) {
|
||||
_channel->owner().processUsers(data.vusers());
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
_participants.clear();
|
||||
_speakingByActiveFinishes.clear();
|
||||
_userBySsrc.clear();
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
@@ -201,11 +234,15 @@ void GroupCall::reload() {
|
||||
void GroupCall::applyParticipantsSlice(
|
||||
const QVector<MTPGroupCallParticipant> &list,
|
||||
ApplySliceSource sliceSource) {
|
||||
const auto amInCall = inCall();
|
||||
const auto now = base::unixtime::now();
|
||||
const auto speakingAfterActive = TimeId(kSpeakingAfterActive / 1000);
|
||||
|
||||
auto changedCount = _fullCount.current();
|
||||
for (const auto &participant : list) {
|
||||
participant.match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto userId = data.vuser_id().v;
|
||||
const auto user = _channel->owner().user(userId);
|
||||
const auto user = _peer->owner().user(userId);
|
||||
const auto i = ranges::find(
|
||||
_participants,
|
||||
user,
|
||||
@@ -216,6 +253,7 @@ void GroupCall::applyParticipantsSlice(
|
||||
.was = *i,
|
||||
};
|
||||
_userBySsrc.erase(i->ssrc);
|
||||
_speakingByActiveFinishes.remove(user);
|
||||
_participants.erase(i);
|
||||
if (sliceSource != ApplySliceSource::SliceLoaded) {
|
||||
_participantUpdates.fire(std::move(update));
|
||||
@@ -231,10 +269,16 @@ void GroupCall::applyParticipantsSlice(
|
||||
: std::nullopt;
|
||||
const auto canSelfUnmute = !data.is_muted()
|
||||
|| data.is_can_self_unmute();
|
||||
const auto lastActive = data.vactive_date().value_or(
|
||||
was ? was->lastActive : 0);
|
||||
const auto speaking = canSelfUnmute
|
||||
&& ((was ? was->speaking : false)
|
||||
|| (!amInCall
|
||||
&& (lastActive + speakingAfterActive > now)));
|
||||
const auto value = Participant{
|
||||
.user = user,
|
||||
.date = data.vdate().v,
|
||||
.lastActive = was ? was->lastActive : 0,
|
||||
.lastActive = lastActive,
|
||||
.ssrc = uint32(data.vsource().v),
|
||||
.speaking = canSelfUnmute && (was ? was->speaking : false),
|
||||
.muted = data.is_muted(),
|
||||
@@ -243,8 +287,7 @@ void GroupCall::applyParticipantsSlice(
|
||||
if (i == end(_participants)) {
|
||||
_userBySsrc.emplace(value.ssrc, user);
|
||||
_participants.push_back(value);
|
||||
_channel->owner().unregisterInvitedToCallUser(_id, user);
|
||||
++changedCount;
|
||||
_peer->owner().unregisterInvitedToCallUser(_id, user);
|
||||
} else {
|
||||
if (i->ssrc != value.ssrc) {
|
||||
_userBySsrc.erase(i->ssrc);
|
||||
@@ -252,6 +295,9 @@ void GroupCall::applyParticipantsSlice(
|
||||
}
|
||||
*i = value;
|
||||
}
|
||||
if (data.is_just_joined()) {
|
||||
++changedCount;
|
||||
}
|
||||
if (sliceSource != ApplySliceSource::SliceLoaded) {
|
||||
_participantUpdates.fire({
|
||||
.was = was,
|
||||
@@ -262,53 +308,31 @@ void GroupCall::applyParticipantsSlice(
|
||||
}
|
||||
if (sliceSource == ApplySliceSource::UpdateReceived) {
|
||||
_fullCount = changedCount;
|
||||
changeChannelEmptyCallFlag();
|
||||
changePeerEmptyCallFlag();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::applyParticipantsMutes(
|
||||
const MTPDupdateGroupCallParticipants &update) {
|
||||
for (const auto &participant : update.vparticipants().v) {
|
||||
participant.match([&](const MTPDgroupCallParticipant &data) {
|
||||
if (data.is_left()) {
|
||||
return;
|
||||
}
|
||||
const auto userId = data.vuser_id().v;
|
||||
const auto user = _channel->owner().user(userId);
|
||||
const auto i = ranges::find(
|
||||
_participants,
|
||||
user,
|
||||
&Participant::user);
|
||||
if (i != end(_participants)) {
|
||||
const auto was = *i;
|
||||
i->muted = data.is_muted();
|
||||
i->canSelfUnmute = !i->muted || data.is_can_self_unmute();
|
||||
if (!i->canSelfUnmute) {
|
||||
i->speaking = false;
|
||||
}
|
||||
_participantUpdates.fire({
|
||||
.was = was,
|
||||
.now = *i,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::applyLastSpoke(uint32 ssrc, crl::time when, crl::time now) {
|
||||
void GroupCall::applyLastSpoke(
|
||||
uint32 ssrc,
|
||||
LastSpokeTimes when,
|
||||
crl::time now) {
|
||||
const auto i = _userBySsrc.find(ssrc);
|
||||
if (i == end(_userBySsrc)) {
|
||||
_unknownSpokenSsrcs.emplace(ssrc, when);
|
||||
requestUnknownSsrcs();
|
||||
_unknownSpokenSsrcs[ssrc] = when;
|
||||
requestUnknownParticipants();
|
||||
return;
|
||||
}
|
||||
const auto j = ranges::find(_participants, i->second, &Participant::user);
|
||||
Assert(j != end(_participants));
|
||||
|
||||
const auto speaking = (when + kSpeakStatusKeptFor >= now)
|
||||
_speakingByActiveFinishes.remove(j->user);
|
||||
const auto sounding = (when.anything + kSoundStatusKeptFor >= now)
|
||||
&& j->canSelfUnmute;
|
||||
if (j->speaking != speaking) {
|
||||
const auto speaking = sounding
|
||||
&& (when.voice + kSoundStatusKeptFor >= now);
|
||||
if (j->sounding != sounding || j->speaking != speaking) {
|
||||
const auto was = *j;
|
||||
j->sounding = sounding;
|
||||
j->speaking = speaking;
|
||||
_participantUpdates.fire({
|
||||
.was = was,
|
||||
@@ -317,15 +341,92 @@ void GroupCall::applyLastSpoke(uint32 ssrc, crl::time when, crl::time now) {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::requestUnknownSsrcs() {
|
||||
if (_unknownSsrcsRequestId || _unknownSpokenSsrcs.empty()) {
|
||||
void GroupCall::applyActiveUpdate(
|
||||
UserId userId,
|
||||
LastSpokeTimes when,
|
||||
UserData *userLoaded) {
|
||||
if (inCall()) {
|
||||
return;
|
||||
}
|
||||
const auto i = userLoaded
|
||||
? ranges::find(
|
||||
_participants,
|
||||
not_null{ userLoaded },
|
||||
&Participant::user)
|
||||
: _participants.end();
|
||||
if (i == end(_participants)) {
|
||||
_unknownSpokenUids[userId] = when;
|
||||
requestUnknownParticipants();
|
||||
return;
|
||||
} else if (!i->canSelfUnmute) {
|
||||
return;
|
||||
}
|
||||
const auto was = std::make_optional(*i);
|
||||
const auto now = crl::now();
|
||||
const auto elapsed = TimeId((now - when.anything) / crl::time(1000));
|
||||
const auto lastActive = base::unixtime::now() - elapsed;
|
||||
const auto finishes = when.anything + kSpeakingAfterActive;
|
||||
if (lastActive <= i->lastActive || finishes <= now) {
|
||||
return;
|
||||
}
|
||||
_speakingByActiveFinishes[i->user] = finishes;
|
||||
if (!_speakingByActiveFinishTimer.isActive()) {
|
||||
_speakingByActiveFinishTimer.callOnce(finishes - now);
|
||||
}
|
||||
|
||||
i->lastActive = lastActive;
|
||||
i->speaking = true;
|
||||
i->canSelfUnmute = true;
|
||||
if (!was->speaking || !was->canSelfUnmute) {
|
||||
_participantUpdates.fire({
|
||||
.was = was,
|
||||
.now = *i,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::checkFinishSpeakingByActive() {
|
||||
const auto now = crl::now();
|
||||
auto nearest = 0;
|
||||
auto stop = std::vector<not_null<UserData*>>();
|
||||
for (auto i = begin(_speakingByActiveFinishes); i != end(_speakingByActiveFinishes);) {
|
||||
const auto when = i->second;
|
||||
if (now >= when) {
|
||||
stop.push_back(i->first);
|
||||
i = _speakingByActiveFinishes.erase(i);
|
||||
} else {
|
||||
if (!nearest || nearest > when) {
|
||||
nearest = when;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
for (const auto user : stop) {
|
||||
const auto i = ranges::find(_participants, user, &Participant::user);
|
||||
if (i->speaking) {
|
||||
const auto was = *i;
|
||||
i->speaking = false;
|
||||
_participantUpdates.fire({
|
||||
.was = was,
|
||||
.now = *i,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (nearest) {
|
||||
_speakingByActiveFinishTimer.callOnce(nearest - now);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::requestUnknownParticipants() {
|
||||
if (_unknownUsersRequestId
|
||||
|| (_unknownSpokenSsrcs.empty() && _unknownSpokenUids.empty())) {
|
||||
return;
|
||||
}
|
||||
const auto ssrcs = [&] {
|
||||
if (_unknownSpokenSsrcs.size() < kRequestPerPage) {
|
||||
return base::take(_unknownSpokenSsrcs);
|
||||
}
|
||||
auto result = base::flat_map<uint32, crl::time>();
|
||||
auto result = base::flat_map<uint32, LastSpokeTimes>();
|
||||
result.reserve(kRequestPerPage);
|
||||
while (result.size() < kRequestPerPage) {
|
||||
const auto [ssrc, when] = _unknownSpokenSsrcs.back();
|
||||
@@ -334,53 +435,127 @@ void GroupCall::requestUnknownSsrcs() {
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
auto inputs = QVector<MTPint>();
|
||||
inputs.reserve(ssrcs.size());
|
||||
const auto uids = [&] {
|
||||
if (_unknownSpokenUids.size() + ssrcs.size() < kRequestPerPage) {
|
||||
return base::take(_unknownSpokenUids);
|
||||
}
|
||||
auto result = base::flat_map<UserId, LastSpokeTimes>();
|
||||
const auto available = (kRequestPerPage - int(ssrcs.size()));
|
||||
if (available > 0) {
|
||||
result.reserve(available);
|
||||
while (result.size() < available) {
|
||||
const auto [userId, when] = _unknownSpokenUids.back();
|
||||
result.emplace(userId, when);
|
||||
_unknownSpokenUids.erase(_unknownSpokenUids.end() - 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
auto ssrcInputs = QVector<MTPint>();
|
||||
ssrcInputs.reserve(ssrcs.size());
|
||||
for (const auto [ssrc, when] : ssrcs) {
|
||||
inputs.push_back(MTP_int(ssrc));
|
||||
ssrcInputs.push_back(MTP_int(ssrc));
|
||||
}
|
||||
_unknownSsrcsRequestId = api().request(MTPphone_GetGroupParticipants(
|
||||
auto uidInputs = QVector<MTPint>();
|
||||
uidInputs.reserve(uids.size());
|
||||
for (const auto [userId, when] : uids) {
|
||||
uidInputs.push_back(MTP_int(userId));
|
||||
}
|
||||
_unknownUsersRequestId = api().request(MTPphone_GetGroupParticipants(
|
||||
input(),
|
||||
MTP_vector<MTPint>(), // ids
|
||||
MTP_vector<MTPint>(inputs),
|
||||
MTP_vector<MTPint>(uidInputs),
|
||||
MTP_vector<MTPint>(ssrcInputs),
|
||||
MTP_string(QString()),
|
||||
MTP_int(kRequestPerPage)
|
||||
)).done([=](const MTPphone_GroupParticipants &result) {
|
||||
result.match([&](const MTPDphone_groupParticipants &data) {
|
||||
_channel->owner().processUsers(data.vusers());
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
applyParticipantsSlice(
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::UnknownLoaded);
|
||||
});
|
||||
_unknownSsrcsRequestId = 0;
|
||||
_unknownUsersRequestId = 0;
|
||||
const auto now = crl::now();
|
||||
for (const auto [ssrc, when] : ssrcs) {
|
||||
applyLastSpoke(ssrc, when, now);
|
||||
_unknownSpokenSsrcs.remove(ssrc);
|
||||
}
|
||||
requestUnknownSsrcs();
|
||||
for (const auto [userId, when] : uids) {
|
||||
if (const auto user = _peer->owner().userLoaded(userId)) {
|
||||
const auto isParticipant = ranges::contains(
|
||||
_participants,
|
||||
not_null{ user },
|
||||
&Participant::user);
|
||||
if (isParticipant) {
|
||||
applyActiveUpdate(userId, when, user);
|
||||
}
|
||||
}
|
||||
_unknownSpokenUids.remove(userId);
|
||||
}
|
||||
requestUnknownParticipants();
|
||||
}).fail([=](const RPCError &error) {
|
||||
_unknownSsrcsRequestId = 0;
|
||||
_unknownUsersRequestId = 0;
|
||||
for (const auto [ssrc, when] : ssrcs) {
|
||||
_unknownSpokenSsrcs.remove(ssrc);
|
||||
}
|
||||
requestUnknownSsrcs();
|
||||
for (const auto [userId, when] : uids) {
|
||||
_unknownSpokenUids.remove(userId);
|
||||
}
|
||||
requestUnknownParticipants();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GroupCall::setInCall() {
|
||||
_unknownSpokenUids.clear();
|
||||
if (_speakingByActiveFinishes.empty()) {
|
||||
return;
|
||||
}
|
||||
auto restartTimer = true;
|
||||
const auto latest = crl::now() + kActiveAfterJoined;
|
||||
for (auto &[user, when] : _speakingByActiveFinishes) {
|
||||
if (when > latest) {
|
||||
when = latest;
|
||||
} else {
|
||||
restartTimer = false;
|
||||
}
|
||||
}
|
||||
if (restartTimer) {
|
||||
_speakingByActiveFinishTimer.callOnce(kActiveAfterJoined);
|
||||
}
|
||||
}
|
||||
|
||||
bool GroupCall::inCall() const {
|
||||
const auto current = Core::App().calls().currentGroupCall();
|
||||
return (current != nullptr)
|
||||
&& (current->id() == _id)
|
||||
&& (current->state() == Calls::GroupCall::State::Joined);
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) {
|
||||
const auto version = update.vversion().v;
|
||||
if (version < _version) {
|
||||
return;
|
||||
} else if (version == _version) {
|
||||
applyParticipantsMutes(update);
|
||||
return;
|
||||
} else if (version != _version + 1) {
|
||||
applyParticipantsMutes(update);
|
||||
reload();
|
||||
const auto applyUpdate = [&] {
|
||||
if (version < _version) {
|
||||
return false;
|
||||
}
|
||||
auto versionShouldIncrement = false;
|
||||
for (const auto &participant : update.vparticipants().v) {
|
||||
const auto versioned = participant.match([&](
|
||||
const MTPDgroupCallParticipant &data) {
|
||||
return data.is_versioned();
|
||||
});
|
||||
if (versioned) {
|
||||
versionShouldIncrement = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return versionShouldIncrement
|
||||
? (version == _version + 1)
|
||||
: (version == _version);
|
||||
}();
|
||||
if (!applyUpdate) {
|
||||
return;
|
||||
}
|
||||
_version = update.vversion().v;
|
||||
_version = version;
|
||||
applyUpdateChecked(update);
|
||||
}
|
||||
|
||||
@@ -404,7 +579,7 @@ bool GroupCall::canChangeJoinMuted() const {
|
||||
}
|
||||
|
||||
ApiWrap &GroupCall::api() const {
|
||||
return _channel->session().api();
|
||||
return _peer->session().api();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -7,27 +7,37 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
class UserData;
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
|
||||
class ApiWrap;
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct LastSpokeTimes {
|
||||
crl::time anything = 0;
|
||||
crl::time voice = 0;
|
||||
};
|
||||
|
||||
class GroupCall final {
|
||||
public:
|
||||
GroupCall(not_null<ChannelData*> channel, uint64 id, uint64 accessHash);
|
||||
GroupCall(not_null<PeerData*> peer, uint64 id, uint64 accessHash);
|
||||
~GroupCall();
|
||||
|
||||
[[nodiscard]] uint64 id() const;
|
||||
[[nodiscard]] not_null<ChannelData*> channel() const;
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
[[nodiscard]] MTPInputGroupCall input() const;
|
||||
|
||||
void setPeer(not_null<PeerData*> peer);
|
||||
|
||||
struct Participant {
|
||||
not_null<UserData*> user;
|
||||
TimeId date = 0;
|
||||
TimeId lastActive = 0;
|
||||
uint32 ssrc = 0;
|
||||
bool sounding = false;
|
||||
bool speaking = false;
|
||||
bool muted = false;
|
||||
bool canSelfUnmute = false;
|
||||
@@ -37,7 +47,7 @@ public:
|
||||
std::optional<Participant> now;
|
||||
};
|
||||
|
||||
static constexpr auto kSpeakStatusKeptFor = crl::time(350);
|
||||
static constexpr auto kSoundStatusKeptFor = crl::time(350);
|
||||
|
||||
[[nodiscard]] auto participants() const
|
||||
-> const std::vector<Participant> &;
|
||||
@@ -52,11 +62,16 @@ public:
|
||||
void applyUpdate(const MTPDupdateGroupCallParticipants &update);
|
||||
void applyUpdateChecked(
|
||||
const MTPDupdateGroupCallParticipants &update);
|
||||
void applyLastSpoke(uint32 ssrc, crl::time when, crl::time now);
|
||||
void applyLastSpoke(uint32 ssrc, LastSpokeTimes when, crl::time now);
|
||||
void applyActiveUpdate(
|
||||
UserId userId,
|
||||
LastSpokeTimes when,
|
||||
UserData *userLoaded);
|
||||
|
||||
[[nodiscard]] int fullCount() const;
|
||||
[[nodiscard]] rpl::producer<int> fullCountValue() const;
|
||||
|
||||
void setInCall();
|
||||
void reload();
|
||||
|
||||
void setJoinMutedLocally(bool muted);
|
||||
@@ -71,30 +86,33 @@ private:
|
||||
};
|
||||
[[nodiscard]] ApiWrap &api() const;
|
||||
|
||||
[[nodiscard]] bool inCall() const;
|
||||
void applyCall(const MTPGroupCall &call, bool force);
|
||||
void applyParticipantsSlice(
|
||||
const QVector<MTPGroupCallParticipant> &list,
|
||||
ApplySliceSource sliceSource);
|
||||
void applyParticipantsMutes(
|
||||
const MTPDupdateGroupCallParticipants &update);
|
||||
void requestUnknownSsrcs();
|
||||
void changeChannelEmptyCallFlag();
|
||||
void requestUnknownParticipants();
|
||||
void changePeerEmptyCallFlag();
|
||||
void checkFinishSpeakingByActive();
|
||||
|
||||
const not_null<ChannelData*> _channel;
|
||||
const uint64 _id = 0;
|
||||
const uint64 _accessHash = 0;
|
||||
|
||||
not_null<PeerData*> _peer;
|
||||
int _version = 0;
|
||||
mtpRequestId _participantsRequestId = 0;
|
||||
mtpRequestId _reloadRequestId = 0;
|
||||
|
||||
std::vector<Participant> _participants;
|
||||
base::flat_map<uint32, not_null<UserData*>> _userBySsrc;
|
||||
base::flat_map<not_null<UserData*>, crl::time> _speakingByActiveFinishes;
|
||||
base::Timer _speakingByActiveFinishTimer;
|
||||
QString _nextOffset;
|
||||
rpl::variable<int> _fullCount = 0;
|
||||
|
||||
base::flat_map<uint32, crl::time> _unknownSpokenSsrcs;
|
||||
mtpRequestId _unknownSsrcsRequestId = 0;
|
||||
base::flat_map<uint32, LastSpokeTimes> _unknownSpokenSsrcs;
|
||||
base::flat_map<UserId, LastSpokeTimes> _unknownSpokenUids;
|
||||
mtpRequestId _unknownUsersRequestId = 0;
|
||||
|
||||
rpl::event_stream<ParticipantUpdate> _participantUpdates;
|
||||
rpl::event_stream<> _participantsSliceAdded;
|
||||
|
||||
@@ -888,6 +888,26 @@ bool PeerData::canSendPolls() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::canManageGroupCall() const {
|
||||
if (const auto chat = asChat()) {
|
||||
return chat->amCreator()
|
||||
|| (chat->adminRights() & ChatAdminRight::f_manage_call);
|
||||
} else if (const auto group = asMegagroup()) {
|
||||
return group->amCreator()
|
||||
|| (group->adminRights() & ChatAdminRight::f_manage_call);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Data::GroupCall *PeerData::groupCall() const {
|
||||
if (const auto chat = asChat()) {
|
||||
return chat->groupCall();
|
||||
} else if (const auto group = asMegagroup()) {
|
||||
return group->groupCall();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PeerData::setIsBlocked(bool is) {
|
||||
const auto status = is
|
||||
? BlockStatus::Blocked
|
||||
|
||||
@@ -29,6 +29,7 @@ class Session;
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
class GroupCall;
|
||||
|
||||
int PeerColorIndex(PeerId peerId);
|
||||
int PeerColorIndex(int32 bareId);
|
||||
@@ -202,6 +203,7 @@ public:
|
||||
[[nodiscard]] rpl::producer<bool> slowmodeAppliedValue() const;
|
||||
[[nodiscard]] int slowmodeSecondsLeft() const;
|
||||
[[nodiscard]] bool canSendPolls() const;
|
||||
[[nodiscard]] bool canManageGroupCall() const;
|
||||
|
||||
[[nodiscard]] UserData *asUser();
|
||||
[[nodiscard]] const UserData *asUser() const;
|
||||
@@ -383,6 +385,8 @@ public:
|
||||
}
|
||||
void setLoadedStatus(LoadedStatus status);
|
||||
|
||||
[[nodiscard]] Data::GroupCall *groupCall() const;
|
||||
|
||||
const PeerId id;
|
||||
QString name;
|
||||
MTPinputPeer input = MTP_inputPeerEmpty();
|
||||
|
||||
@@ -604,7 +604,12 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
});
|
||||
}
|
||||
|
||||
chat->setFlags(data.vflags().v);
|
||||
const auto callFlag = MTPDchat::Flag::f_call_not_empty;
|
||||
const auto callNotEmpty = (data.vflags().v & callFlag)
|
||||
|| (chat->groupCall()
|
||||
&& chat->groupCall()->fullCount() > 0);
|
||||
chat->setFlags(data.vflags().v
|
||||
| (callNotEmpty ? callFlag : MTPDchat::Flag(0)));
|
||||
chat->count = data.vparticipants_count().v;
|
||||
|
||||
if (canAddMembers != chat->canAddMembers()) {
|
||||
@@ -651,13 +656,21 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
channel->setDefaultRestrictions(
|
||||
MTP_chatBannedRights(MTP_flags(0), MTP_int(0)));
|
||||
}
|
||||
const auto callFlag = MTPDchannel::Flag::f_call_not_empty;
|
||||
const auto callNotEmpty = (data.vflags().v & callFlag)
|
||||
|| (channel->groupCall()
|
||||
&& channel->groupCall()->fullCount() > 0);
|
||||
if (minimal) {
|
||||
auto mask = 0
|
||||
| MTPDchannel::Flag::f_broadcast
|
||||
| MTPDchannel::Flag::f_verified
|
||||
| MTPDchannel::Flag::f_megagroup
|
||||
| MTPDchannel::Flag::f_call_active
|
||||
| MTPDchannel::Flag::f_call_not_empty
|
||||
| MTPDchannel_ClientFlag::f_forbidden;
|
||||
channel->setFlags((channel->flags() & ~mask) | (data.vflags().v & mask));
|
||||
channel->setFlags((channel->flags() & ~mask)
|
||||
| (data.vflags().v & mask)
|
||||
| (callNotEmpty ? callFlag : MTPDchannel::Flag(0)));
|
||||
if (channel->input.type() == mtpc_inputPeerEmpty
|
||||
|| channel->inputChannel.type() == mtpc_inputChannelEmpty) {
|
||||
channel->setAccessHash(data.vaccess_hash().value_or_empty());
|
||||
@@ -686,9 +699,6 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
} else {
|
||||
channel->setUnavailableReasons({});
|
||||
}
|
||||
const auto callFlag = MTPDchannel::Flag::f_call_not_empty;
|
||||
const auto callNotEmpty = (data.vflags().v & callFlag)
|
||||
|| (channel->call() && channel->call()->fullCount() > 0);
|
||||
channel->setFlags(data.vflags().v
|
||||
| (callNotEmpty ? callFlag : MTPDchannel::Flag(0)));
|
||||
//if (const auto feedId = data.vfeed_id()) { // #feed
|
||||
@@ -804,14 +814,6 @@ void Session::unregisterGroupCall(not_null<GroupCall*> call) {
|
||||
_groupCalls.remove(call->id());
|
||||
}
|
||||
|
||||
rpl::producer<Session::GroupCallDiscard> Session::groupCallDiscards() const {
|
||||
return _groupCallDiscarded.events();
|
||||
}
|
||||
|
||||
void Session::groupCallDiscarded(uint64 id, int duration) {
|
||||
_groupCallDiscarded.fire({ id, duration });
|
||||
}
|
||||
|
||||
GroupCall *Session::groupCall(uint64 callId) const {
|
||||
const auto i = _groupCalls.find(callId);
|
||||
return (i != end(_groupCalls)) ? i->second.get() : nullptr;
|
||||
@@ -826,9 +828,9 @@ auto Session::invitedToCallUsers(uint64 callId) const
|
||||
|
||||
void Session::registerInvitedToCallUser(
|
||||
uint64 callId,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user) {
|
||||
const auto call = channel->call();
|
||||
const auto call = peer->groupCall();
|
||||
if (call && call->id() == callId) {
|
||||
const auto inCall = ranges::contains(
|
||||
call->participants(),
|
||||
@@ -839,6 +841,7 @@ void Session::registerInvitedToCallUser(
|
||||
}
|
||||
}
|
||||
_invitedToCallUsers[callId].emplace(user);
|
||||
_invitesToCalls.fire({ callId, user });
|
||||
}
|
||||
|
||||
void Session::unregisterInvitedToCallUser(
|
||||
@@ -1164,6 +1167,7 @@ void Session::setupMigrationViewer() {
|
||||
return;
|
||||
}
|
||||
|
||||
chat->clearGroupCall();
|
||||
if (const auto from = historyLoaded(chat)) {
|
||||
if (const auto to = historyLoaded(channel)) {
|
||||
if (to->inChatList() && from->inChatList()) {
|
||||
|
||||
@@ -158,21 +158,22 @@ public:
|
||||
void unregisterGroupCall(not_null<GroupCall*> call);
|
||||
GroupCall *groupCall(uint64 callId) const;
|
||||
|
||||
struct GroupCallDiscard {
|
||||
uint64 id = 0;
|
||||
int duration = 0;
|
||||
};
|
||||
rpl::producer<GroupCallDiscard> groupCallDiscards() const;
|
||||
void groupCallDiscarded(uint64 id, int duration);
|
||||
|
||||
[[nodiscard]] auto invitedToCallUsers(uint64 callId) const
|
||||
-> const base::flat_set<not_null<UserData*>> &;
|
||||
void registerInvitedToCallUser(
|
||||
uint64 callId,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user);
|
||||
void unregisterInvitedToCallUser(uint64 callId, not_null<UserData*> user);
|
||||
|
||||
struct InviteToCall {
|
||||
uint64 id = 0;
|
||||
not_null<UserData*> user;
|
||||
};
|
||||
[[nodiscard]] rpl::producer<InviteToCall> invitesToCalls() const {
|
||||
return _invitesToCalls.events();
|
||||
}
|
||||
|
||||
void enumerateUsers(Fn<void(not_null<UserData*>)> action) const;
|
||||
void enumerateGroups(Fn<void(not_null<PeerData*>)> action) const;
|
||||
void enumerateChannels(Fn<void(not_null<ChannelData*>)> action) const;
|
||||
@@ -930,8 +931,8 @@ private:
|
||||
base::flat_set<not_null<ViewElement*>> _heavyViewParts;
|
||||
|
||||
base::flat_map<uint64, not_null<GroupCall*>> _groupCalls;
|
||||
rpl::event_stream<InviteToCall> _invitesToCalls;
|
||||
base::flat_map<uint64, base::flat_set<not_null<UserData*>>> _invitedToCallUsers;
|
||||
rpl::event_stream<GroupCallDiscard> _groupCallDiscarded;
|
||||
|
||||
History *_topPromoted = nullptr;
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ void SharedMediaShowOverview(
|
||||
return;
|
||||
}
|
||||
}
|
||||
windows.front()->showSection(Info::Memento(
|
||||
windows.front()->showSection(std::make_shared<Info::Memento>(
|
||||
history->peer,
|
||||
Info::Section(type)));
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ bool Stickers::applyArchivedResultFake() {
|
||||
MTP_long(raw->access),
|
||||
MTP_string(raw->title),
|
||||
MTP_string(raw->shortName),
|
||||
MTP_photoSizeEmpty(MTP_string()),
|
||||
MTP_vector<MTPPhotoSize>(),
|
||||
MTP_int(0),
|
||||
MTP_int(raw->count),
|
||||
MTP_int(raw->hash));
|
||||
@@ -814,10 +814,20 @@ void Stickers::featuredSetsReceived(
|
||||
auto it = sets.find(data->vid().v);
|
||||
const auto title = getSetTitle(*data);
|
||||
const auto installDate = data->vinstalled_date().value_or_empty();
|
||||
const auto thumb = data->vthumb();
|
||||
const auto thumbnail = thumb
|
||||
? Images::FromPhotoSize(&session(), *data, *thumb)
|
||||
: ImageWithLocation();
|
||||
const auto thumbnail = [&] {
|
||||
if (const auto thumbs = data->vthumbs()) {
|
||||
for (const auto &thumb : thumbs->v) {
|
||||
const auto result = Images::FromPhotoSize(
|
||||
&session(),
|
||||
*data,
|
||||
thumb);
|
||||
if (result.location.valid()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImageWithLocation();
|
||||
}();
|
||||
if (it == sets.cend()) {
|
||||
auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured
|
||||
| MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
@@ -1126,10 +1136,20 @@ StickersSet *Stickers::feedSet(const MTPDstickerSet &data) {
|
||||
auto it = sets.find(data.vid().v);
|
||||
auto title = getSetTitle(data);
|
||||
auto flags = MTPDstickerSet::Flags(0);
|
||||
const auto thumb = data.vthumb();
|
||||
const auto thumbnail = thumb
|
||||
? Images::FromPhotoSize(&session(), data, *thumb)
|
||||
: ImageWithLocation();
|
||||
const auto thumbnail = [&] {
|
||||
if (const auto thumbs = data.vthumbs()) {
|
||||
for (const auto &thumb : thumbs->v) {
|
||||
const auto result = Images::FromPhotoSize(
|
||||
&session(),
|
||||
data,
|
||||
thumb);
|
||||
if (result.location.valid()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImageWithLocation();
|
||||
}();
|
||||
if (it == sets.cend()) {
|
||||
it = sets.emplace(data.vid().v, std::make_unique<StickersSet>(
|
||||
&owner(),
|
||||
|
||||
@@ -1776,7 +1776,7 @@ bool Widget::onCancelSearch() {
|
||||
if (const auto peer = _searchInChat.peer()) {
|
||||
Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
|
||||
//} else if (const auto feed = _searchInChat.feed()) { // #feed
|
||||
// controller()->showSection(HistoryFeed::Memento(feed));
|
||||
// controller()->showSection(std::make_shared<HistoryFeed::Memento>(feed));
|
||||
} else {
|
||||
Unexpected("Empty key in onCancelSearch().");
|
||||
}
|
||||
@@ -1800,7 +1800,7 @@ void Widget::onCancelSearchInChat() {
|
||||
if (const auto peer = _searchInChat.peer()) {
|
||||
Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
|
||||
//} else if (const auto feed = _searchInChat.feed()) { // #feed
|
||||
// controller()->showSection(HistoryFeed::Memento(feed));
|
||||
// controller()->showSection(std::make_shared<HistoryFeed::Memento>(feed));
|
||||
} else {
|
||||
Unexpected("Empty key in onCancelSearchInPeer().");
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
#include "core/application.h"
|
||||
@@ -557,7 +556,7 @@ bool InnerWidget::elementUnderCursor(
|
||||
}
|
||||
|
||||
crl::time InnerWidget::elementHighlightTime(
|
||||
not_null<const HistoryView::Element*> element) {
|
||||
not_null<const HistoryItem*> item) {
|
||||
return crl::time(0);
|
||||
}
|
||||
|
||||
@@ -1220,9 +1219,7 @@ void InnerWidget::copyContextImage(not_null<PhotoData*> photo) {
|
||||
}
|
||||
|
||||
const auto image = media->image(Data::PhotoSize::Large)->original();
|
||||
if (!Platform::SetClipboardImage(image)) {
|
||||
QGuiApplication::clipboard()->setImage(image);
|
||||
}
|
||||
QGuiApplication::clipboard()->setImage(image);
|
||||
}
|
||||
|
||||
void InnerWidget::copySelectedText() {
|
||||
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const HistoryView::Element*> element) override;
|
||||
not_null<const HistoryItem*> item) override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
|
||||
@@ -838,7 +838,7 @@ void GenerateItems(
|
||||
data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto user = history->owner().user(data.vuser_id().v);
|
||||
const auto userLink = user->createOpenLink();
|
||||
const auto userLinkText = textcmdLink(1, user->name);
|
||||
const auto userLinkText = textcmdLink(2, user->name);
|
||||
auto text = tr::lng_admin_log_muted_participant(
|
||||
tr::now,
|
||||
lt_from,
|
||||
@@ -862,7 +862,7 @@ void GenerateItems(
|
||||
data.vparticipant().match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto user = history->owner().user(data.vuser_id().v);
|
||||
const auto userLink = user->createOpenLink();
|
||||
const auto userLinkText = textcmdLink(1, user->name);
|
||||
const auto userLinkText = textcmdLink(2, user->name);
|
||||
auto text = tr::lng_admin_log_unmuted_participant(
|
||||
tr::now,
|
||||
lt_from,
|
||||
|
||||
@@ -371,8 +371,8 @@ void Widget::setupShortcuts() {
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
std::unique_ptr<Window::SectionMemento> Widget::createMemento() {
|
||||
auto result = std::make_unique<SectionMemento>(channel());
|
||||
std::shared_ptr<Window::SectionMemento> Widget::createMemento() {
|
||||
auto result = std::make_shared<SectionMemento>(channel());
|
||||
saveState(result.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
bool showInternal(
|
||||
not_null<Window::SectionMemento*> memento,
|
||||
const Window::SectionShow ¶ms) override;
|
||||
std::unique_ptr<Window::SectionMemento> createMemento() override;
|
||||
std::shared_ptr<Window::SectionMemento> createMemento() override;
|
||||
|
||||
void setInternalState(const QRect &geometry, not_null<SectionMemento*> memento);
|
||||
|
||||
|
||||
@@ -1053,9 +1053,11 @@ void History::applyServiceChanges(
|
||||
} break;
|
||||
|
||||
case mtpc_messageActionGroupCall: {
|
||||
const auto &d = action.c_messageActionGroupCall();
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
const auto &d = action.c_messageActionGroupCall();
|
||||
channel->setCall(d.vcall());
|
||||
channel->setGroupCall(d.vcall());
|
||||
} else if (const auto chat = peer->asChat()) {
|
||||
chat->setGroupCall(d.vcall());
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "history/history_widget.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -1648,7 +1647,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
addDocumentActions(lnkDocument->document());
|
||||
}
|
||||
if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
|
||||
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
|
||||
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_message_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
|
||||
HistoryView::CopyPostLink(session, itemId, HistoryView::Context::History);
|
||||
});
|
||||
}
|
||||
@@ -1752,18 +1751,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
}
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto poll = media->poll()) {
|
||||
if (!poll->closed()) {
|
||||
if (poll->voted() && !poll->quiz()) {
|
||||
_menu->addAction(tr::lng_polls_retract(tr::now), [=] {
|
||||
session->api().sendPollVotes(itemId, {});
|
||||
});
|
||||
}
|
||||
if (item->canStopPoll()) {
|
||||
_menu->addAction(tr::lng_polls_stop(tr::now), [=] {
|
||||
HistoryView::StopPoll(session, itemId);
|
||||
});
|
||||
}
|
||||
}
|
||||
HistoryView::AddPollActions(
|
||||
_menu,
|
||||
poll,
|
||||
item,
|
||||
HistoryView::Context::History);
|
||||
} else if (const auto contact = media->sharedContact()) {
|
||||
const auto phone = contact->phoneNumber;
|
||||
_menu->addAction(tr::lng_profile_copy_phone(tr::now), [=] {
|
||||
@@ -1789,7 +1781,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
});
|
||||
} else if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
|
||||
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
|
||||
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_message_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
|
||||
HistoryView::CopyPostLink(session, itemId, HistoryView::Context::History);
|
||||
});
|
||||
}
|
||||
@@ -1900,9 +1892,7 @@ void HistoryInner::copyContextImage(not_null<PhotoData*> photo) {
|
||||
}
|
||||
|
||||
const auto image = media->image(Data::PhotoSize::Large)->original();
|
||||
if (!Platform::SetClipboardImage(image)) {
|
||||
QGuiApplication::clipboard()->setImage(image);
|
||||
}
|
||||
QGuiApplication::clipboard()->setImage(image);
|
||||
}
|
||||
|
||||
void HistoryInner::showStickerPackInfo(not_null<DocumentData*> document) {
|
||||
@@ -2523,9 +2513,9 @@ void HistoryInner::elementStartStickerLoop(
|
||||
_animatedStickersPlayed.emplace(view->data());
|
||||
}
|
||||
|
||||
crl::time HistoryInner::elementHighlightTime(not_null<const Element*> view) {
|
||||
const auto fullAnimMs = _controller->content()->highlightStartTime(
|
||||
view->data());
|
||||
crl::time HistoryInner::elementHighlightTime(
|
||||
not_null<const HistoryItem*> item) {
|
||||
const auto fullAnimMs = _controller->content()->highlightStartTime(item);
|
||||
if (fullAnimMs > 0) {
|
||||
const auto now = crl::now();
|
||||
if (fullAnimMs < now) {
|
||||
@@ -3424,8 +3414,8 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
|
||||
return (App::hoveredItem() == view);
|
||||
}
|
||||
crl::time elementHighlightTime(
|
||||
not_null<const Element*> view) override {
|
||||
return Instance ? Instance->elementHighlightTime(view) : 0;
|
||||
not_null<const HistoryItem*> item) override {
|
||||
return Instance ? Instance->elementHighlightTime(item) : 0;
|
||||
}
|
||||
bool elementInSelectionMode() override {
|
||||
return Instance ? Instance->inSelectionMode() : false;
|
||||
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
int till) const;
|
||||
void elementStartStickerLoop(not_null<const Element*> view);
|
||||
[[nodiscard]] crl::time elementHighlightTime(
|
||||
not_null<const Element*> view);
|
||||
not_null<const HistoryItem*> item);
|
||||
void elementShowPollResults(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context);
|
||||
|
||||
@@ -776,7 +776,8 @@ bool HistoryItem::canBeEditedFromHistory() const {
|
||||
}
|
||||
if ((IsServerMsgId(id) || isScheduled())
|
||||
&& !serviceMsg()
|
||||
&& (out() || history()->peer->isSelf())) {
|
||||
&& (out() || history()->peer->isSelf())
|
||||
&& !Has<HistoryMessageForwarded>()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_group_call.h" // Data::GroupCall::id().
|
||||
#include "core/application.h"
|
||||
@@ -38,18 +39,27 @@ namespace {
|
||||
|
||||
constexpr auto kPinnedMessageTextLimit = 16;
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> ChannelHasThisCallValue(
|
||||
not_null<ChannelData*> channel,
|
||||
[[nodiscard]] bool PeerCallKnown(not_null<PeerData*> peer) {
|
||||
if (peer->groupCall() != nullptr) {
|
||||
return true;
|
||||
} else if (const auto chat = peer->asChat()) {
|
||||
return !(chat->flags() & MTPDchat::Flag::f_call_active);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return !(channel->flags() & MTPDchannel::Flag::f_call_active);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> PeerHasThisCallValue(
|
||||
not_null<PeerData*> peer,
|
||||
uint64 id) {
|
||||
return channel->session().changes().peerFlagsValue(
|
||||
channel,
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::filter([=] {
|
||||
return (channel->call() != nullptr)
|
||||
|| !(channel->flags()
|
||||
& MTPDchannel::Flag::f_call_active);
|
||||
return PeerCallKnown(peer);
|
||||
}) | rpl::map([=] {
|
||||
const auto call = channel->call();
|
||||
const auto call = peer->groupCall();
|
||||
return (call && call->id() == id);
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::take_while([=](bool hasThisCall) {
|
||||
@@ -59,15 +69,15 @@ constexpr auto kPinnedMessageTextLimit = 16;
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<bool> ChannelHasThisCall(
|
||||
not_null<ChannelData*> channel,
|
||||
[[nodiscard]] std::optional<bool> PeerHasThisCall(
|
||||
not_null<PeerData*> peer,
|
||||
uint64 id) {
|
||||
const auto call = channel->call();
|
||||
const auto call = peer->groupCall();
|
||||
return call
|
||||
? std::make_optional(call->id() == id)
|
||||
: (channel->flags() & MTPDchannel::Flag::f_call_active)
|
||||
? std::nullopt
|
||||
: std::make_optional(false);
|
||||
: PeerCallKnown(peer)
|
||||
? std::make_optional(false)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 CallIdFromInput(const MTPInputGroupCall &data) {
|
||||
@@ -76,20 +86,20 @@ constexpr auto kPinnedMessageTextLimit = 16;
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] ClickHandlerPtr ChannelCallClickHandler(
|
||||
not_null<ChannelData*> megagroup,
|
||||
[[nodiscard]] ClickHandlerPtr GroupCallClickHandler(
|
||||
not_null<PeerData*> peer,
|
||||
uint64 callId) {
|
||||
return std::make_shared<LambdaClickHandler>([=] {
|
||||
const auto call = megagroup->call();
|
||||
const auto call = peer->groupCall();
|
||||
if (call && call->id() == callId) {
|
||||
const auto &windows = megagroup->session().windows();
|
||||
const auto &windows = peer->session().windows();
|
||||
if (windows.empty()) {
|
||||
Core::App().domain().activate(&megagroup->session().account());
|
||||
Core::App().domain().activate(&peer->session().account());
|
||||
if (windows.empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
windows.front()->startOrJoinGroupCall(megagroup);
|
||||
windows.front()->startOrJoinGroupCall(peer);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -338,10 +348,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
||||
return prepareDiscardedCallText(duration->v);
|
||||
}
|
||||
const auto callId = CallIdFromInput(action.vcall());
|
||||
const auto channel = history()->peer->asChannel();
|
||||
const auto linkCallId = !channel
|
||||
? 0
|
||||
: ChannelHasThisCall(channel, callId).value_or(false)
|
||||
const auto peer = history()->peer;
|
||||
const auto linkCallId = PeerHasThisCall(peer, callId).value_or(false)
|
||||
? callId
|
||||
: 0;
|
||||
return prepareStartedCallText(linkCallId);
|
||||
@@ -350,16 +358,14 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
||||
auto prepareInviteToGroupCall = [this](const MTPDmessageActionInviteToGroupCall &action) {
|
||||
const auto callId = CallIdFromInput(action.vcall());
|
||||
const auto owner = &history()->owner();
|
||||
const auto channel = history()->peer->asChannel();
|
||||
const auto peer = history()->peer;
|
||||
for (const auto id : action.vusers().v) {
|
||||
const auto user = owner->user(id.v);
|
||||
if (channel && callId) {
|
||||
owner->registerInvitedToCallUser(callId, channel, user);
|
||||
if (callId) {
|
||||
owner->registerInvitedToCallUser(callId, peer, user);
|
||||
}
|
||||
};
|
||||
const auto linkCallId = !channel
|
||||
? 0
|
||||
: ChannelHasThisCall(channel, callId).value_or(false)
|
||||
const auto linkCallId = PeerHasThisCall(peer, callId).value_or(false)
|
||||
? callId
|
||||
: 0;
|
||||
return prepareInvitedToCallText(action.vusers().v, linkCallId);
|
||||
@@ -534,10 +540,10 @@ HistoryService::PreparedText HistoryService::prepareStartedCallText(
|
||||
uint64 linkCallId) {
|
||||
auto result = PreparedText{};
|
||||
result.links.push_back(fromLink());
|
||||
const auto channel = history()->peer->asChannel();
|
||||
auto chatText = tr::lng_action_group_call_started_chat(tr::now);
|
||||
if (channel && linkCallId) {
|
||||
result.links.push_back(ChannelCallClickHandler(channel, linkCallId));
|
||||
if (linkCallId) {
|
||||
const auto peer = history()->peer;
|
||||
result.links.push_back(GroupCallClickHandler(peer, linkCallId));
|
||||
chatText = textcmdLink(2, chatText);
|
||||
}
|
||||
result.text = tr::lng_action_group_call_started(
|
||||
@@ -552,14 +558,14 @@ HistoryService::PreparedText HistoryService::prepareStartedCallText(
|
||||
HistoryService::PreparedText HistoryService::prepareInvitedToCallText(
|
||||
const QVector<MTPint> &users,
|
||||
uint64 linkCallId) {
|
||||
const auto channel = history()->peer->asChannel();
|
||||
const auto owner = &channel->owner();
|
||||
const auto owner = &history()->owner();
|
||||
auto chatText = tr::lng_action_invite_user_chat(tr::now);
|
||||
auto result = PreparedText{};
|
||||
result.links.push_back(fromLink());
|
||||
auto linkIndex = 1;
|
||||
if (channel && linkCallId) {
|
||||
result.links.push_back(ChannelCallClickHandler(channel, linkCallId));
|
||||
if (linkCallId) {
|
||||
const auto peer = history()->peer;
|
||||
result.links.push_back(GroupCallClickHandler(peer, linkCallId));
|
||||
chatText = textcmdLink(++linkIndex, chatText);
|
||||
}
|
||||
if (users.size() == 1) {
|
||||
@@ -929,47 +935,35 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
|
||||
const auto id = CallIdFromInput(data.vcall());
|
||||
call->lifetime.destroy();
|
||||
|
||||
history()->owner().groupCallDiscards(
|
||||
) | rpl::filter([=](Data::Session::GroupCallDiscard discard) {
|
||||
return (discard.id == id);
|
||||
}) | rpl::start_with_next([=](
|
||||
Data::Session::GroupCallDiscard discard) {
|
||||
RemoveComponents(HistoryServiceOngoingCall::Bit());
|
||||
updateText(prepareDiscardedCallText(discard.duration));
|
||||
}, call->lifetime);
|
||||
|
||||
if (const auto channel = history()->peer->asChannel()) {
|
||||
const auto has = ChannelHasThisCall(channel, id);
|
||||
if (!has.has_value()) {
|
||||
ChannelHasThisCallValue(
|
||||
channel,
|
||||
id
|
||||
) | rpl::start_with_next([=](bool has) {
|
||||
updateText(prepareStartedCallText(has ? id : 0));
|
||||
}, call->lifetime);
|
||||
} else if (*has) {
|
||||
ChannelHasThisCallValue(
|
||||
channel,
|
||||
id
|
||||
) | rpl::skip(1) | rpl::start_with_next([=](bool has) {
|
||||
Assert(!has);
|
||||
updateText(prepareStartedCallText(0));
|
||||
}, call->lifetime);
|
||||
}
|
||||
const auto peer = history()->peer;
|
||||
const auto has = PeerHasThisCall(peer, id);
|
||||
if (!has.has_value()) {
|
||||
PeerHasThisCallValue(
|
||||
peer,
|
||||
id
|
||||
) | rpl::start_with_next([=](bool has) {
|
||||
updateText(prepareStartedCallText(has ? id : 0));
|
||||
}, call->lifetime);
|
||||
} else if (*has) {
|
||||
PeerHasThisCallValue(
|
||||
peer,
|
||||
id
|
||||
) | rpl::skip(1) | rpl::start_with_next([=](bool has) {
|
||||
Assert(!has);
|
||||
updateText(prepareStartedCallText(0));
|
||||
}, call->lifetime);
|
||||
}
|
||||
}
|
||||
} else if (message.vaction().type() == mtpc_messageActionInviteToGroupCall) {
|
||||
const auto &data = message.vaction().c_messageActionInviteToGroupCall();
|
||||
const auto id = CallIdFromInput(data.vcall());
|
||||
const auto channel = history()->peer->asChannel();
|
||||
const auto has = channel
|
||||
? ChannelHasThisCall(channel, id)
|
||||
: std::make_optional(false);
|
||||
const auto peer = history()->peer;
|
||||
const auto has = PeerHasThisCall(peer, id);
|
||||
auto hasLink = !has.has_value()
|
||||
? ChannelHasThisCallValue(channel, id)
|
||||
? PeerHasThisCallValue(peer, id)
|
||||
: (*has)
|
||||
? ChannelHasThisCallValue(
|
||||
channel,
|
||||
? PeerHasThisCallValue(
|
||||
peer,
|
||||
id) | rpl::skip(1) | rpl::type_erased()
|
||||
: rpl::producer<bool>();
|
||||
if (!hasLink) {
|
||||
|
||||
@@ -687,7 +687,7 @@ HistoryWidget::HistoryWidget(
|
||||
cancelReply(lastKeyboardUsed);
|
||||
crl::on_main(this, [=, history = action.history]{
|
||||
controller->showSection(
|
||||
HistoryView::ScheduledMemento(history));
|
||||
std::make_shared<HistoryView::ScheduledMemento>(history));
|
||||
});
|
||||
} else {
|
||||
fastShowAtEnd(action.history);
|
||||
@@ -757,9 +757,6 @@ void HistoryWidget::initVoiceRecordBar() {
|
||||
});
|
||||
_voiceRecordBar->setLockBottom(std::move(scrollHeight));
|
||||
}
|
||||
_voiceRecordBar->setEscFilter([=]() -> bool {
|
||||
return _replyToId || (_nonEmptySelection && _list);
|
||||
});
|
||||
|
||||
_voiceRecordBar->setSendButtonGeometryValue(_send->geometryValue());
|
||||
|
||||
@@ -776,6 +773,12 @@ void HistoryWidget::initVoiceRecordBar() {
|
||||
return false;
|
||||
});
|
||||
|
||||
const auto applyLocalDraft = [=] {
|
||||
if (_history && _history->localDraft()) {
|
||||
applyDraft();
|
||||
}
|
||||
};
|
||||
|
||||
_voiceRecordBar->sendActionUpdates(
|
||||
) | rpl::start_with_next([=](const auto &data) {
|
||||
if (!_history) {
|
||||
@@ -802,8 +805,12 @@ void HistoryWidget::initVoiceRecordBar() {
|
||||
data.duration,
|
||||
action);
|
||||
_voiceRecordBar->clearListenState();
|
||||
applyLocalDraft();
|
||||
}, lifetime());
|
||||
|
||||
_voiceRecordBar->cancelRequests(
|
||||
) | rpl::start_with_next(applyLocalDraft, lifetime());
|
||||
|
||||
_voiceRecordBar->lockShowStarts(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateHistoryDownVisibility();
|
||||
@@ -839,6 +846,11 @@ void HistoryWidget::initTabbedSelector() {
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
auto filter = rpl::filter([=] {
|
||||
return !isHidden();
|
||||
});
|
||||
using Selector = TabbedSelector;
|
||||
|
||||
selector->emojiChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden() && !_field->isHidden();
|
||||
@@ -847,27 +859,24 @@ void HistoryWidget::initTabbedSelector() {
|
||||
}, lifetime());
|
||||
|
||||
selector->fileChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::FileChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::FileChosen data) {
|
||||
sendExistingDocument(data.document, data.options);
|
||||
}, lifetime());
|
||||
|
||||
selector->photoChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::PhotoChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::PhotoChosen data) {
|
||||
sendExistingPhoto(data.photo, data.options);
|
||||
}, lifetime());
|
||||
|
||||
selector->inlineResultChosen(
|
||||
) | rpl::filter([=] {
|
||||
return !isHidden();
|
||||
}) | rpl::start_with_next([=](TabbedSelector::InlineChosen data) {
|
||||
) | filter | rpl::start_with_next([=](Selector::InlineChosen data) {
|
||||
sendInlineResult(data);
|
||||
}, lifetime());
|
||||
|
||||
selector->setSendMenuType([=] { return sendMenuType(); });
|
||||
selector->contextMenuRequested(
|
||||
) | filter | rpl::start_with_next([=] {
|
||||
selector->showMenuWithType(sendMenuType());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void HistoryWidget::supportInitAutocomplete() {
|
||||
@@ -1038,11 +1047,6 @@ void HistoryWidget::scrollToAnimationCallback(
|
||||
|
||||
void HistoryWidget::enqueueMessageHighlight(
|
||||
not_null<HistoryView::Element*> view) {
|
||||
if (const auto group = session().data().groups().find(view->data())) {
|
||||
if (const auto leader = group->items.front()->mainView()) {
|
||||
view = leader;
|
||||
}
|
||||
}
|
||||
auto enqueueMessageId = [this](MsgId universalId) {
|
||||
if (_highlightQueue.empty() && !_highlightTimer.isActive()) {
|
||||
highlightMessage(universalId);
|
||||
@@ -1089,7 +1093,7 @@ void HistoryWidget::checkNextHighlight() {
|
||||
|
||||
void HistoryWidget::updateHighlightedMessage() {
|
||||
const auto item = getItemFromHistoryOrMigrated(_highlightedMessageId);
|
||||
const auto view = item ? item->mainView() : nullptr;
|
||||
auto view = item ? item->mainView() : nullptr;
|
||||
if (!view) {
|
||||
return stopMessageHighlight();
|
||||
}
|
||||
@@ -1098,6 +1102,11 @@ void HistoryWidget::updateHighlightedMessage() {
|
||||
return stopMessageHighlight();
|
||||
}
|
||||
|
||||
if (const auto group = session().data().groups().find(view->data())) {
|
||||
if (const auto leader = group->items.front()->mainView()) {
|
||||
view = leader;
|
||||
}
|
||||
}
|
||||
session().data().requestViewRepaint(view);
|
||||
}
|
||||
|
||||
@@ -1442,7 +1451,7 @@ void HistoryWidget::activate() {
|
||||
updateHistoryGeometry();
|
||||
}
|
||||
}
|
||||
if (App::wnd()) App::wnd()->setInnerFocus();
|
||||
controller()->widget()->setInnerFocus();
|
||||
}
|
||||
|
||||
void HistoryWidget::setInnerFocus() {
|
||||
@@ -1498,7 +1507,7 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U
|
||||
} else if (to.section == Section::Scheduled) {
|
||||
history->setDraft(Data::DraftKey::Scheduled(), std::move(draft));
|
||||
controller()->showSection(
|
||||
HistoryView::ScheduledMemento(history));
|
||||
std::make_shared<HistoryView::ScheduledMemento>(history));
|
||||
} else {
|
||||
history->setLocalDraft(std::move(draft));
|
||||
if (history == _history) {
|
||||
@@ -1616,6 +1625,10 @@ void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
|
||||
void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||
InvokeQueued(this, [=] { updateStickersByEmoji(); });
|
||||
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto draft = !_history
|
||||
? nullptr
|
||||
: _history->localEditDraft()
|
||||
@@ -1650,6 +1663,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||
_editMsgId = 0;
|
||||
_replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
|
||||
}
|
||||
updateCmdStartShown();
|
||||
updateControlsVisibility();
|
||||
updateControlsGeometry();
|
||||
refreshTopBarActiveChat();
|
||||
@@ -2065,7 +2079,7 @@ void HistoryWidget::refreshScheduledToggle() {
|
||||
_scheduled->show();
|
||||
_scheduled->addClickHandler([=] {
|
||||
controller()->showSection(
|
||||
HistoryView::ScheduledMemento(_history));
|
||||
std::make_shared<HistoryView::ScheduledMemento>(_history));
|
||||
});
|
||||
orderWidgets(); // Raise drag areas to the top.
|
||||
} else if (_scheduled && !has) {
|
||||
@@ -2217,11 +2231,7 @@ void HistoryWidget::updateControlsVisibility() {
|
||||
_botCommandStart->hide();
|
||||
} else {
|
||||
_botKeyboardShow->hide();
|
||||
if (_cmdStartShown) {
|
||||
_botCommandStart->show();
|
||||
} else {
|
||||
_botCommandStart->hide();
|
||||
}
|
||||
_botCommandStart->setVisible(_cmdStartShown);
|
||||
}
|
||||
}
|
||||
_attachToggle->show();
|
||||
@@ -3091,7 +3101,7 @@ void HistoryWidget::send(Api::SendOptions options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_voiceRecordBar && _voiceRecordBar->isListenState()) {
|
||||
if (_voiceRecordBar->isListenState()) {
|
||||
_voiceRecordBar->requestToSendWithOptions(options);
|
||||
return;
|
||||
}
|
||||
@@ -3336,7 +3346,7 @@ void HistoryWidget::doneShow() {
|
||||
_groupCallBar->finishAnimating();
|
||||
}
|
||||
checkHistoryActivation();
|
||||
App::wnd()->setInnerFocus();
|
||||
controller()->widget()->setInnerFocus();
|
||||
_preserveScrollTop = false;
|
||||
}
|
||||
|
||||
@@ -3727,7 +3737,7 @@ void HistoryWidget::updateSendButtonType() {
|
||||
bool HistoryWidget::updateCmdStartShown() {
|
||||
bool cmdStartShown = false;
|
||||
if (_history && _peer && ((_peer->isChat() && _peer->asChat()->botStatus > 0) || (_peer->isMegagroup() && _peer->asChannel()->mgInfo->botStatus > 0) || (_peer->isUser() && _peer->asUser()->isBot()))) {
|
||||
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply()) {
|
||||
if (!isBotStart() && !isBlocked() && !_keyboard->hasMarkup() && !_keyboard->forceReply() && !_editMsgId) {
|
||||
if (!HasSendText(_field)) {
|
||||
cmdStartShown = true;
|
||||
}
|
||||
@@ -3875,7 +3885,7 @@ bool HistoryWidget::pushTabbedSelectorToThirdSection(
|
||||
Core::App().settings().setTabbedReplacedWithInfo(false);
|
||||
controller()->resizeForThirdSection();
|
||||
controller()->showSection(
|
||||
ChatHelpers::TabbedMemento(),
|
||||
std::make_shared<ChatHelpers::TabbedMemento>(),
|
||||
params.withThirdColumn());
|
||||
return true;
|
||||
}
|
||||
@@ -3906,6 +3916,14 @@ void HistoryWidget::setTabbedPanel(std::unique_ptr<TabbedPanel> panel) {
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryWidget::preventsClose(Fn<void()> &&continueCallback) const {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
_voiceRecordBar->showDiscardBox(std::move(continueCallback));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HistoryWidget::toggleTabbedSelectorMode() {
|
||||
if (!_peer) {
|
||||
return;
|
||||
@@ -4062,8 +4080,8 @@ void HistoryWidget::checkFieldAutocomplete() {
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '/'
|
||||
&& _peer->isUser()
|
||||
&& !_peer->asUser()->isBot()) {
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot())
|
||||
|| _editMsgId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4321,7 +4339,7 @@ bool HistoryWidget::confirmSendingFiles(
|
||||
}
|
||||
|
||||
if (hasImage) {
|
||||
auto image = Platform::GetClipboardImage();
|
||||
auto image = Platform::GetImageFromClipboard();
|
||||
if (image.isNull()) {
|
||||
image = qvariant_cast<QImage>(data->imageData());
|
||||
}
|
||||
@@ -4826,7 +4844,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
|
||||
_tabbedSelectorToggle->show();
|
||||
_botKeyboardHide->hide();
|
||||
_botKeyboardShow->hide();
|
||||
_botCommandStart->show();
|
||||
_botCommandStart->setVisible(!_editMsgId);
|
||||
}
|
||||
_field->setMaxHeight(computeMaxFieldHeight());
|
||||
_kbShown = false;
|
||||
@@ -5374,7 +5392,7 @@ void HistoryWidget::refreshPinnedBarButton(bool many) {
|
||||
const auto id = _pinnedTracker->currentMessageId();
|
||||
if (id.message) {
|
||||
controller()->showSection(
|
||||
HistoryView::PinnedMemento(
|
||||
std::make_shared<HistoryView::PinnedMemento>(
|
||||
_history,
|
||||
((!_migrated || id.message.channel)
|
||||
? id.message.msg
|
||||
@@ -5388,17 +5406,18 @@ void HistoryWidget::refreshPinnedBarButton(bool many) {
|
||||
void HistoryWidget::setupGroupCallTracker() {
|
||||
Expects(_history != nullptr);
|
||||
|
||||
const auto channel = _history->peer->asChannel();
|
||||
if (!channel) {
|
||||
const auto peer = _history->peer;
|
||||
if (!peer->asMegagroup() && !peer->asChat()) {
|
||||
_groupCallTracker = nullptr;
|
||||
_groupCallBar = nullptr;
|
||||
return;
|
||||
}
|
||||
_groupCallTracker = std::make_unique<HistoryView::GroupCallTracker>(
|
||||
channel);
|
||||
peer);
|
||||
_groupCallBar = std::make_unique<Ui::GroupCallBar>(
|
||||
this,
|
||||
_groupCallTracker->content());
|
||||
_groupCallTracker->content(),
|
||||
Core::App().appDeactivatedValue());
|
||||
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
@@ -5419,16 +5438,15 @@ void HistoryWidget::setupGroupCallTracker() {
|
||||
_groupCallBar->barClicks(),
|
||||
_groupCallBar->joinClicks()
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto channel = _history->peer->asChannel();
|
||||
if (!channel) {
|
||||
return;
|
||||
} else if (channel->amAnonymous()) {
|
||||
const auto peer = _history->peer;
|
||||
const auto channel = peer->asChannel();
|
||||
if (channel && channel->amAnonymous()) {
|
||||
Ui::ShowMultilineToast({
|
||||
.text = tr::lng_group_call_no_anonymous(tr::now),
|
||||
});
|
||||
return;
|
||||
} else if (channel->call()) {
|
||||
controller()->startOrJoinGroupCall(channel);
|
||||
} else if (peer->groupCall()) {
|
||||
controller()->startOrJoinGroupCall(peer);
|
||||
}
|
||||
}, _groupCallBar->lifetime());
|
||||
|
||||
@@ -5633,7 +5651,7 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
|
||||
}
|
||||
|
||||
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
|
||||
if (_voiceRecordBar && _voiceRecordBar->isListenState()) {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
Ui::show(Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
@@ -6107,6 +6125,8 @@ void HistoryWidget::escape() {
|
||||
_fieldAutocomplete->hideAnimated();
|
||||
} else if (_replyToId && _field->getTextWithTags().text.isEmpty()) {
|
||||
cancelReply();
|
||||
} else if (auto &voice = _voiceRecordBar; voice->isActive()) {
|
||||
voice->showDiscardBox(nullptr, anim::type::normal);
|
||||
} else {
|
||||
_cancelRequests.fire({});
|
||||
}
|
||||
|
||||
@@ -119,6 +119,8 @@ public:
|
||||
|
||||
void historyLoaded();
|
||||
|
||||
[[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const;
|
||||
|
||||
// When resizing the widget with top edge moved up or down and we
|
||||
// want to add this top movement to the scroll position, so inner
|
||||
// content will not move.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user