Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc89262461 | ||
|
|
a5425042cf | ||
|
|
d6e03c3e48 | ||
|
|
e91eecf34f | ||
|
|
1a8cc87e60 | ||
|
|
0863941642 | ||
|
|
b331aee599 | ||
|
|
e19180cc86 | ||
|
|
10cb891f48 | ||
|
|
c8f7a8c795 | ||
|
|
74a28ffdf7 | ||
|
|
ecedce0c2f | ||
|
|
bd4f993292 | ||
|
|
4934b026d3 | ||
|
|
11f183a79f | ||
|
|
ae426a41e0 | ||
|
|
d6edc3728d | ||
|
|
e121487170 | ||
|
|
72a093ec77 | ||
|
|
4996d90782 | ||
|
|
9a451a1423 | ||
|
|
4d11ad45db | ||
|
|
1657c2c7f2 | ||
|
|
c5e7048a3d | ||
|
|
1f194da2f0 | ||
|
|
0954b04f24 | ||
|
|
4659499340 | ||
|
|
6eb4584408 | ||
|
|
4ba4b77b95 | ||
|
|
f9bf6dbc1e | ||
|
|
64b5269648 | ||
|
|
f394cecf55 | ||
|
|
8b56676c23 | ||
|
|
e2713ea627 | ||
|
|
f5e50409d3 | ||
|
|
050916a56a | ||
|
|
cdf36cc387 | ||
|
|
f909a36cbd | ||
|
|
1d36255ca5 | ||
|
|
acfdae2d72 | ||
|
|
ab59e97b92 | ||
|
|
1060b04b1e | ||
|
|
df044dbd83 | ||
|
|
5eb210ec12 | ||
|
|
f24f78c0cc | ||
|
|
8a071fe1fe | ||
|
|
f3e84de5fb | ||
|
|
2dec1b72f7 | ||
|
|
604a827a52 | ||
|
|
3d8b303ab7 | ||
|
|
2c599e60c3 | ||
|
|
928d8feb21 | ||
|
|
490e688a91 | ||
|
|
34c36d77c3 | ||
|
|
0ab26f0c82 | ||
|
|
db453ab7ae | ||
|
|
e032dbf383 | ||
|
|
3b4ed03105 | ||
|
|
963694330d | ||
|
|
2733b12cff | ||
|
|
a377364621 | ||
|
|
58f4884deb | ||
|
|
c2c7a25487 | ||
|
|
f98c08f4c6 | ||
|
|
cfc2a959cf | ||
|
|
6a1630a84c | ||
|
|
a51be85199 | ||
|
|
e0fd5d8795 | ||
|
|
8659f60b46 | ||
|
|
c43699fb43 | ||
|
|
c56a22c8d5 | ||
|
|
7f27ce6dee | ||
|
|
508ba4750c | ||
|
|
c0b19000d6 | ||
|
|
409a3357da | ||
|
|
82523978c9 | ||
|
|
718ba2d0e3 | ||
|
|
2317dd8820 | ||
|
|
df06f55c7f | ||
|
|
d43853460e | ||
|
|
28fee318d7 | ||
|
|
1ec2ecac11 | ||
|
|
7aa3956792 | ||
|
|
d349759618 | ||
|
|
ac3e4fb42f | ||
|
|
eccb01e5b5 | ||
|
|
7c8d10022f | ||
|
|
f1244e19a1 | ||
|
|
e17143dd8b | ||
|
|
39d5d3a1cf | ||
|
|
f8be5731a5 | ||
|
|
ab248febcd | ||
|
|
d4afba3a24 | ||
|
|
4ee9751feb | ||
|
|
46fb5ee1d2 | ||
|
|
749f837df5 | ||
|
|
e11904e05b | ||
|
|
e1aa08b985 | ||
|
|
2af3770b29 | ||
|
|
74f9d0935b | ||
|
|
f9c50fdc06 | ||
|
|
1fa825321d | ||
|
|
d9147562e5 | ||
|
|
5b569718ec | ||
|
|
a5d4746202 | ||
|
|
50d150302d | ||
|
|
e451eb5126 | ||
|
|
a626364430 | ||
|
|
b55ed7214a | ||
|
|
d6801517bb | ||
|
|
97dde7eb56 | ||
|
|
10df3dce7c | ||
|
|
889d7c0c15 | ||
|
|
799155279f | ||
|
|
10e7bd0d6e | ||
|
|
0a99487091 | ||
|
|
4965c19314 | ||
|
|
30810e95f4 | ||
|
|
a3d84f69ea | ||
|
|
b3bb1a537c | ||
|
|
726aa3316d | ||
|
|
ba6c3eaf73 | ||
|
|
ebe45f3fa1 | ||
|
|
3f0fed19d8 | ||
|
|
609cab6e2f | ||
|
|
7b3cb0c3dd | ||
|
|
43559fb6b7 | ||
|
|
8788692fb3 | ||
|
|
072e346324 | ||
|
|
99f65ab5ec | ||
|
|
fe7b120003 | ||
|
|
cb8f86bc8d | ||
|
|
18e6e2da9e | ||
|
|
54247cd11b | ||
|
|
8b0725650d | ||
|
|
20411be9bd | ||
|
|
f4f36d85b9 | ||
|
|
9f887237eb | ||
|
|
4e3f917a2c |
5
.github/workflows/mac.yml
vendored
@@ -91,6 +91,11 @@ jobs:
|
||||
run: |
|
||||
./$REPO_NAME/Telegram/build/prepare/mac.sh skip-release silent
|
||||
|
||||
- name: Free up some disk space.
|
||||
run: |
|
||||
cd Libraries
|
||||
find . -iname "*.dir" -exec rm -rf {} || true \;
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: |
|
||||
|
||||
407
.github/workflows/win.yml
vendored
@@ -44,385 +44,84 @@ jobs:
|
||||
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2022
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
defines:
|
||||
- ""
|
||||
arch: [Win32, x64]
|
||||
|
||||
env:
|
||||
SDK: "10.0.18362.0"
|
||||
GIT: "https://github.com"
|
||||
QT_VER: "5.15.2"
|
||||
OPENSSL_VER: "1_1_1"
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "0"
|
||||
PREPARE_PATH: "Telegram/build/prepare/prepare.py"
|
||||
AUTO_CACHING: "1"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: cmd
|
||||
working-directory: Libraries
|
||||
working-directory: ${{ github.workspace }}
|
||||
|
||||
steps:
|
||||
- name: Prepare directories.
|
||||
run: |
|
||||
mkdir %userprofile%\TBuild\Libraries
|
||||
echo TBUILD=%userprofile%\TBuild>>%GITHUB_ENV%
|
||||
|
||||
- name: Get repository name.
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1.9.0
|
||||
name: x86 Native Tools Command Prompt.
|
||||
- uses: ilammy/msvc-dev-cmd@v1.10.0
|
||||
name: Native Tools Command Prompt.
|
||||
with:
|
||||
arch: win32
|
||||
|
||||
- name: Set up environment paths.
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
echo "C:\\Strawberry\\perl\\bin\\" >> $GITHUB_PATH
|
||||
echo "C:\\Program Files\\NASM\\" >> $GITHUB_PATH
|
||||
echo "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Auxiliary\\Build\\" >> $GITHUB_PATH
|
||||
|
||||
mkdir Libraries && cd Libraries
|
||||
echo "Convert unix path to win path."
|
||||
p=`pwd | sed 's#^/[d]#d:#g' |sed 's#/#\\\\#g'`
|
||||
echo "LibrariesPath=$p" >> $GITHUB_ENV
|
||||
|
||||
echo "QT=${QT_VER//./_}" >> $GITHUB_ENV
|
||||
|
||||
- name: Save msbuild version.
|
||||
run: |
|
||||
call vcvars32.bat
|
||||
msbuild -version > CACHE_KEY.txt
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
uses: LebedevRI/checkout@issue197
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
|
||||
- name: Generate cache key.
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
curl -o $LibrariesPath/tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
|
||||
curl -o $LibrariesPath/tg_angle-version.json https://api.github.com/repos/desktop-app/tg_angle/git/refs/heads/master
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
if [ "$AUTO_CACHING" == "1" ]; then
|
||||
thisFile=$REPO_NAME/.github/workflows/win.yml
|
||||
echo `md5sum $thisFile | awk '{ print $1 }'` >> CACHE_KEY.txt
|
||||
fi
|
||||
echo "CACHE_KEY=`md5sum CACHE_KEY.txt | awk '{ print $1 }'`" >> $GITHUB_ENV
|
||||
path: ${{ env.TBUILD }}\${{ env.REPO_NAME }}
|
||||
|
||||
- name: Choco installs.
|
||||
run: |
|
||||
choco install --allow-empty-checksums --no-progress -y yasm
|
||||
choco install --no-progress -y nasm jom ninja
|
||||
python -m pip install pywin32
|
||||
choco install --no-progress -y nasm strawberryperl yasm jom ninja
|
||||
py -m pip install pywin32
|
||||
|
||||
- name: Install msys64.
|
||||
run: |
|
||||
mkdir %TBUILD%\ThirdParty
|
||||
xcopy /E /I C:\msys64 %TBUILD%\ThirdParty\msys64
|
||||
|
||||
- name: Set up environment paths.
|
||||
shell: bash
|
||||
run: |
|
||||
echo "C:\\Strawberry\\perl\\bin\\" >> $GITHUB_PATH
|
||||
echo "C:\\Program Files\\NASM\\" >> $GITHUB_PATH
|
||||
echo "C:\\ProgramData\\chocolatey\\lib\\ninja\\tools\\" >> $GITHUB_PATH
|
||||
|
||||
echo "Configurate git for cherry-picks."
|
||||
git config --global user.email "you@example.com"
|
||||
git config --global user.name "Sample"
|
||||
|
||||
- name: NuGet sources.
|
||||
run: |
|
||||
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
|
||||
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
|
||||
|
||||
- name: Patches.
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
echo "Find necessary commit from doc."
|
||||
checkoutCommit=$(grep -A 1 "cd patches" $REPO_NAME/$PREPARE_PATH | sed -n 2p)
|
||||
cd $LibrariesPath
|
||||
git clone $GIT/desktop-app/patches.git
|
||||
cd patches
|
||||
eval $checkoutCommit
|
||||
|
||||
- name: LZMA.
|
||||
run: |
|
||||
git clone %GIT%/telegramdesktop/lzma.git
|
||||
cd lzma
|
||||
cd C\Util\LzmaLib
|
||||
msbuild -m LzmaLib.sln /property:Configuration=Debug
|
||||
|
||||
- name: OpenSSL cache.
|
||||
id: cache-openssl
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/openssl
|
||||
key: ${{ runner.OS }}-${{ env.CACHE_KEY }}-${{ env.OPENSSL_VER }}
|
||||
- name: OpenSSL.
|
||||
if: steps.cache-openssl.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone -b OpenSSL_%OPENSSL_VER%-stable %GIT%/openssl/openssl.git
|
||||
cd openssl
|
||||
perl Configure no-shared no-tests debug-VC-WIN32
|
||||
nmake
|
||||
mkdir out.dbg
|
||||
move libcrypto.lib out.dbg
|
||||
move libssl.lib out.dbg
|
||||
move ossl_static.pdb out.dbg\ossl_static
|
||||
nmake clean
|
||||
move out.dbg\ossl_static out.dbg\ossl_static.pdb
|
||||
perl Configure no-shared no-tests VC-WIN32
|
||||
nmake
|
||||
mkdir out
|
||||
move libcrypto.lib out
|
||||
move libssl.lib out
|
||||
move ossl_static.pdb out
|
||||
path: ${{ env.TBUILD }}/Libraries
|
||||
key: ${{ runner.OS }}-libs
|
||||
|
||||
rmdir /S /Q test
|
||||
rmdir /S /Q .git
|
||||
|
||||
- name: Zlib.
|
||||
run: |
|
||||
git clone %GIT%/telegramdesktop/zlib.git
|
||||
cd zlib
|
||||
git checkout tdesktop
|
||||
cd contrib\vstudio\vc14
|
||||
msbuild -m zlibstat.vcxproj /property:Configuration=Debug
|
||||
|
||||
- name: MozJPEG.
|
||||
shell: cmd
|
||||
run: |
|
||||
git clone -b v4.0.3 %GIT%/mozilla/mozjpeg.git
|
||||
cd mozjpeg
|
||||
cmake . ^
|
||||
-G "Visual Studio 16 2019" ^
|
||||
-A Win32 ^
|
||||
-DWITH_JPEG8=ON ^
|
||||
-DPNG_SUPPORTED=OFF
|
||||
cmake --build . --config Debug
|
||||
|
||||
- name: OpenAL Soft cache.
|
||||
id: cache-openal
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/openal-soft
|
||||
key: ${{ runner.OS }}-openal-soft-${{ env.CACHE_KEY }}
|
||||
- name: OpenAL Soft.
|
||||
if: steps.cache-openal.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone -b openal-soft-1.21.0 --depth=1 %GIT%/kcat/openal-soft.git
|
||||
cd openal-soft\build
|
||||
cmake .. ^
|
||||
-G "Visual Studio 16 2019" ^
|
||||
-A Win32 ^
|
||||
-D LIBTYPE:STRING=STATIC ^
|
||||
-D FORCE_STATIC_VCRT=ON ^
|
||||
-D ALSOFT_BACKEND_DSOUND=OFF
|
||||
|
||||
msbuild -m OpenAL.vcxproj /property:Configuration=Debug
|
||||
|
||||
- name: Breakpad cache.
|
||||
id: cache-breakpad
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/breakpad
|
||||
key: ${{ runner.OS }}-breakpad-${{ env.CACHE_KEY }}-${{ hashFiles('**/breakpad.diff') }}
|
||||
- name: Breakpad.
|
||||
- name: Libraries.
|
||||
env:
|
||||
GYP_MSVS_OVERRIDE_PATH: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\'
|
||||
GYP_MSVS_VERSION: 2019
|
||||
if: steps.cache-breakpad.outputs.cache-hit != 'true'
|
||||
GYP_MSVS_OVERRIDE_PATH: 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\'
|
||||
GYP_MSVS_VERSION: 2022
|
||||
run: |
|
||||
git clone https://chromium.googlesource.com/external/gyp
|
||||
cd gyp
|
||||
SET PATH=%cd%;%PATH%
|
||||
git checkout d6c5dd51dc
|
||||
git apply ../patches/gyp.diff
|
||||
|
||||
cd %LibrariesPath%
|
||||
|
||||
git clone https://chromium.googlesource.com/breakpad/breakpad
|
||||
cd breakpad
|
||||
git checkout dfcb7b6799
|
||||
git apply ../patches/breakpad.diff
|
||||
cd src
|
||||
git clone -b release-1.11.0 %GIT%/google/googletest testing
|
||||
cd client\windows
|
||||
call gyp --no-circular-check breakpad_client.gyp --format=ninja
|
||||
cd ..\..
|
||||
ninja -C out/Debug common crash_generation_client exception_handler
|
||||
ninja -C out/Release common crash_generation_client exception_handler
|
||||
cd tools\windows\dump_syms
|
||||
call gyp dump_syms.gyp --format=ninja
|
||||
cd ..\..\..
|
||||
ninja -C out/Release dump_syms
|
||||
|
||||
- name: Opus cache.
|
||||
id: cache-opus
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/opus
|
||||
key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }}
|
||||
- name: Opus.
|
||||
if: steps.cache-opus.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone -b v1.3.1 %GIT%/xiph/opus.git
|
||||
cd opus
|
||||
git cherry-pick 927de8453c
|
||||
cmake -B out . ^
|
||||
-A Win32 ^
|
||||
-DCMAKE_INSTALL_PREFIX=%LibrariesPath%/local/opus ^
|
||||
-DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^
|
||||
-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"
|
||||
cmake --build out --config Debug
|
||||
cmake --build out --config Release
|
||||
cmake --install out --config Release
|
||||
|
||||
- name: Rnnoise.
|
||||
run: |
|
||||
git clone %GIT%/desktop-app/rnnoise.git
|
||||
mkdir rnnoise\out
|
||||
cd rnnoise\out
|
||||
cmake -A Win32 ..
|
||||
cmake --build . --config Debug
|
||||
|
||||
- name: FFmpeg cache.
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/ffmpeg
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}-2-${{ hashFiles('**/build_ffmpeg_win.sh') }}
|
||||
- name: FFmpeg.
|
||||
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
choco install --no-progress -y msys2
|
||||
|
||||
git clone %GIT%/FFmpeg/FFmpeg.git ffmpeg
|
||||
cd ffmpeg
|
||||
git checkout release/4.4
|
||||
set CHERE_INVOKING=enabled_from_arguments
|
||||
set MSYS2_PATH_TYPE=inherit
|
||||
call c:\tools\msys64\usr\bin\bash --login ../patches/build_ffmpeg_win.sh
|
||||
|
||||
rmdir /S /Q .git
|
||||
|
||||
- name: Angle cache.
|
||||
id: cache-angle
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_angle
|
||||
key: ${{ runner.OS }}-angle-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_angle-version.json') }}
|
||||
- name: Angle.
|
||||
if: steps.cache-angle.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone --recursive %GIT%/desktop-app/tg_angle.git
|
||||
mkdir tg_angle\out\Debug
|
||||
cd tg_angle\out\Debug
|
||||
cmake -G Ninja ^
|
||||
-DCMAKE_BUILD_TYPE=Debug ^
|
||||
-DTG_ANGLE_SPECIAL_TARGET=win64 ^
|
||||
-DTG_ANGLE_ZLIB_INCLUDE_PATH=%cd%/../../../zlib ../..
|
||||
ninja
|
||||
|
||||
:: Cleanup.
|
||||
cd %LibrariesPath%\tg_angle
|
||||
move out\Debug\tg_angle.lib tg_angle.lib
|
||||
rmdir /S /Q out
|
||||
mkdir out\Debug
|
||||
move tg_angle.lib out\Debug\tg_angle.lib
|
||||
|
||||
- name: Qt 5.15.2 cache.
|
||||
id: cache-qt
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/Qt-${{ env.QT_VER }}
|
||||
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_15_2/*') }}
|
||||
- name: Configure Qt 5.15.2.
|
||||
if: steps.cache-qt.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone git://code.qt.io/qt/qt5.git qt_%QT%
|
||||
cd qt_%QT%
|
||||
perl init-repository --module-subset=qtbase,qtimageformats,qtsvg
|
||||
git checkout v%QT_VER%
|
||||
git submodule update qtbase
|
||||
git submodule update qtimageformats
|
||||
git submodule update qtsvg
|
||||
cd qtbase
|
||||
for /r %%i in (..\..\patches\qtbase_%QT%\*) do git apply %%i
|
||||
cd ..
|
||||
|
||||
SET SSL=%LibrariesPath%\openssl
|
||||
SET SSL_LIBS=libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib
|
||||
|
||||
SET ANGLE=%LibrariesPath%\tg_angle
|
||||
SET ANGLE_LIBS=d3d9.lib dxgi.lib dxguid.lib
|
||||
|
||||
SET ZLIB=%LibrariesPath%\zlib\contrib\vstudio\vc14\x86
|
||||
|
||||
configure ^
|
||||
-prefix "%LibrariesPath%\Qt-%QT_VER%" ^
|
||||
-debug ^
|
||||
-force-debug-info ^
|
||||
-opensource ^
|
||||
-confirm-license ^
|
||||
-static ^
|
||||
-static-runtime ^
|
||||
-opengl es2 -no-angle ^
|
||||
-I "%ANGLE%\include" ^
|
||||
-D "GL_APICALL=" ^
|
||||
QMAKE_LIBS_OPENGL_ES2_DEBUG="%ANGLE%\out\Debug\tg_angle.lib %ZLIB%\ZlibStatDebug\zlibstat.lib %ANGLE_LIBS%" ^
|
||||
QMAKE_LIBS_OPENGL_ES2_RELEASE="%ANGLE%\out\Release\tg_angle.lib %ZLIB%\ZlibStatReleaseWithoutAsm\zlibstat.lib %ANGLE_LIBS%" ^
|
||||
-egl ^
|
||||
-D "EGLAPI=" ^
|
||||
-D "DESKTOP_APP_QT_STATIC_ANGLE=" ^
|
||||
QMAKE_LIBS_EGL_DEBUG="%ANGLE%\out\Debug\tg_angle.lib %ZLIB%\ZlibStatDebug\zlibstat.lib %ANGLE_LIBS% Gdi32.lib User32.lib" ^
|
||||
QMAKE_LIBS_EGL_RELEASE="%ANGLE%\out\Release\tg_angle.lib %ZLIB%\ZlibStatReleaseWithoutAsm\zlibstat.lib %ANGLE_LIBS% Gdi32.lib User32.lib" ^
|
||||
-openssl-linked ^
|
||||
-I "%SSL%\include" ^
|
||||
OPENSSL_LIBS_DEBUG="%SSL%\out.dbg\libssl.lib %SSL%\out.dbg\%SSL_LIBS%" ^
|
||||
OPENSSL_LIBS_RELEASE="%SSL%\out\libssl.lib %SSL%\out\%SSL_LIBS%" ^
|
||||
-I "%LibrariesPath%\mozjpeg" ^
|
||||
LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" ^
|
||||
LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib" ^
|
||||
-mp ^
|
||||
-nomake examples ^
|
||||
-nomake tests ^
|
||||
-platform win32-msvc
|
||||
|
||||
- name: Qt 5.15.2 build.
|
||||
if: steps.cache-qt.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd qt_%QT%
|
||||
|
||||
jom -j%NUMBER_OF_PROCESSORS%
|
||||
jom -j%NUMBER_OF_PROCESSORS% install
|
||||
|
||||
cd ..
|
||||
rmdir /S /Q qt_%QT%
|
||||
|
||||
- name: WebRTC cache.
|
||||
id: cache-webrtc
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_owt
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
|
||||
- name: WebRTC.
|
||||
if: steps.cache-webrtc.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone --recursive %GIT%/desktop-app/tg_owt.git
|
||||
mkdir tg_owt\out\Debug
|
||||
cd tg_owt\out\Debug
|
||||
cmake -G Ninja ^
|
||||
-DCMAKE_BUILD_TYPE=Debug ^
|
||||
-DTG_OWT_SPECIAL_TARGET=win ^
|
||||
-DTG_OWT_BUILD_AUDIO_BACKENDS=OFF ^
|
||||
-DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^
|
||||
-DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl/include ^
|
||||
-DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^
|
||||
-DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ^
|
||||
../..
|
||||
|
||||
ninja
|
||||
|
||||
:: Cleanup.
|
||||
cd %LibrariesPath%\tg_owt
|
||||
move out\Debug\tg_owt.lib tg_owt.lib
|
||||
rmdir /S /Q out
|
||||
mkdir out\Debug
|
||||
move tg_owt.lib out\Debug\tg_owt.lib
|
||||
C:
|
||||
cd %TBUILD%
|
||||
%REPO_NAME%/Telegram/build/prepare/win.bat skip-release silent
|
||||
|
||||
- name: Read defines.
|
||||
shell: bash
|
||||
@@ -438,16 +137,21 @@ jobs:
|
||||
echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV
|
||||
|
||||
- name: Free up some disk space.
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: del /S *.pdb
|
||||
run: |
|
||||
C:
|
||||
cd %TBUILD%
|
||||
del /S Libraries\*.pdb
|
||||
del /S Libraries\*.pch
|
||||
del /S Libraries\*.obj
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
cd %REPO_NAME%\Telegram
|
||||
C:
|
||||
cd %TBUILD%\%REPO_NAME%\Telegram
|
||||
|
||||
call configure.bat ^
|
||||
${{ matrix.arch }} ^
|
||||
-D TDESKTOP_API_TEST=ON ^
|
||||
-D DESKTOP_APP_USE_PACKAGED=OFF ^
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
||||
@@ -455,20 +159,17 @@ jobs:
|
||||
%TDESKTOP_BUILD_DEFINE% ^
|
||||
-DCMAKE_SYSTEM_VERSION=%SDK%
|
||||
|
||||
call vcvars32.bat
|
||||
cd ..\out
|
||||
msbuild -m Telegram.sln /nologo /p:Configuration=Debug,Platform=Win32
|
||||
msbuild -m Telegram.sln /p:Configuration=Debug,Platform=${{ matrix.arch }},DebugSymbols=false,DebugType=none
|
||||
|
||||
- name: Move artifact.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
cd %REPO_NAME%\out\Debug
|
||||
mkdir artifact
|
||||
move Telegram.exe artifact/
|
||||
move %TBUILD%\%REPO_NAME%\out\Debug\Telegram.exe artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload artifact.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: ${{ env.REPO_NAME }}\out\Debug\artifact\
|
||||
path: artifact\
|
||||
|
||||
9
.gitmodules
vendored
@@ -94,3 +94,12 @@
|
||||
[submodule "Telegram/ThirdParty/dispatch"]
|
||||
path = Telegram/ThirdParty/dispatch
|
||||
url = https://github.com/apple/swift-corelibs-libdispatch
|
||||
[submodule "Telegram/ThirdParty/extra-cmake-modules"]
|
||||
path = Telegram/ThirdParty/extra-cmake-modules
|
||||
url = https://github.com/KDE/extra-cmake-modules.git
|
||||
[submodule "Telegram/ThirdParty/plasma-wayland-protocols"]
|
||||
path = Telegram/ThirdParty/plasma-wayland-protocols
|
||||
url = https://github.com/KDE/plasma-wayland-protocols.git
|
||||
[submodule "Telegram/ThirdParty/wayland-protocols"]
|
||||
path = Telegram/ThirdParty/wayland-protocols
|
||||
url = https://github.com/gitlab-freedesktop-mirrors/wayland-protocols.git
|
||||
|
||||
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-2021 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2022 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
|
||||
|
||||
@@ -525,33 +525,16 @@ PRIVATE
|
||||
editor/controllers/controllers.h
|
||||
editor/controllers/stickers_panel_controller.cpp
|
||||
editor/controllers/stickers_panel_controller.h
|
||||
editor/controllers/undo_controller.cpp
|
||||
editor/controllers/undo_controller.h
|
||||
editor/editor_crop.cpp
|
||||
editor/editor_crop.h
|
||||
editor/editor_paint.cpp
|
||||
editor/editor_paint.h
|
||||
editor/photo_editor.cpp
|
||||
editor/photo_editor.h
|
||||
editor/photo_editor_common.cpp
|
||||
editor/photo_editor_common.h
|
||||
editor/photo_editor_content.cpp
|
||||
editor/photo_editor_content.h
|
||||
editor/photo_editor_controls.cpp
|
||||
editor/photo_editor_controls.h
|
||||
editor/photo_editor_inner_common.h
|
||||
editor/photo_editor_layer_widget.cpp
|
||||
editor/photo_editor_layer_widget.h
|
||||
editor/scene/scene.cpp
|
||||
editor/scene/scene.h
|
||||
editor/scene/scene_item_base.cpp
|
||||
editor/scene/scene_item_base.h
|
||||
editor/scene/scene_item_canvas.cpp
|
||||
editor/scene/scene_item_canvas.h
|
||||
editor/scene/scene_item_image.cpp
|
||||
editor/scene/scene_item_image.h
|
||||
editor/scene/scene_item_line.cpp
|
||||
editor/scene/scene_item_line.h
|
||||
editor/scene/scene_item_sticker.cpp
|
||||
editor/scene/scene_item_sticker.h
|
||||
export/export_manager.cpp
|
||||
@@ -655,6 +638,8 @@ PRIVATE
|
||||
history/view/history_view_pinned_section.h
|
||||
history/view/history_view_pinned_tracker.cpp
|
||||
history/view/history_view_pinned_tracker.h
|
||||
history/view/history_view_react_animation.cpp
|
||||
history/view/history_view_react_animation.h
|
||||
history/view/history_view_react_button.cpp
|
||||
history/view/history_view_react_button.h
|
||||
history/view/history_view_reactions.cpp
|
||||
@@ -948,6 +933,8 @@ PRIVATE
|
||||
platform/linux/file_utilities_linux.h
|
||||
platform/linux/launcher_linux.cpp
|
||||
platform/linux/launcher_linux.h
|
||||
platform/linux/integration_linux.cpp
|
||||
platform/linux/integration_linux.h
|
||||
platform/linux/main_window_linux.cpp
|
||||
platform/linux/main_window_linux.h
|
||||
platform/linux/notifications_manager_linux_dummy.cpp
|
||||
@@ -959,6 +946,8 @@ PRIVATE
|
||||
platform/mac/file_utilities_mac.h
|
||||
platform/mac/launcher_mac.mm
|
||||
platform/mac/launcher_mac.h
|
||||
platform/mac/integration_mac.mm
|
||||
platform/mac/integration_mac.h
|
||||
platform/mac/mac_iconv_helper.c
|
||||
platform/mac/main_window_mac.mm
|
||||
platform/mac/main_window_mac.h
|
||||
@@ -993,6 +982,8 @@ PRIVATE
|
||||
platform/win/file_utilities_win.h
|
||||
platform/win/launcher_win.cpp
|
||||
platform/win/launcher_win.h
|
||||
platform/win/integration_win.cpp
|
||||
platform/win/integration_win.h
|
||||
platform/win/main_window_win.cpp
|
||||
platform/win/main_window_win.h
|
||||
platform/win/notifications_manager_win.cpp
|
||||
@@ -1003,8 +994,6 @@ PRIVATE
|
||||
platform/win/windows_app_user_model_id.h
|
||||
platform/win/windows_dlls.cpp
|
||||
platform/win/windows_dlls.h
|
||||
platform/win/windows_event_filter.cpp
|
||||
platform/win/windows_event_filter.h
|
||||
platform/win/windows_autostart_task.cpp
|
||||
platform/win/windows_autostart_task.h
|
||||
platform/win/windows_toast_activator.cpp
|
||||
@@ -1012,6 +1001,8 @@ PRIVATE
|
||||
platform/platform_audio.h
|
||||
platform/platform_file_utilities.h
|
||||
platform/platform_launcher.h
|
||||
platform/platform_integration.cpp
|
||||
platform/platform_integration.h
|
||||
platform/platform_main_window.h
|
||||
platform/platform_notifications_manager.h
|
||||
platform/platform_specific.h
|
||||
|
||||
|
Before Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 524 B |
|
Before Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 873 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 393 B |
|
Before Width: | Height: | Size: 616 B |
|
Before Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 889 B |
|
Before Width: | Height: | Size: 173 B |
|
Before Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 545 B |
|
Before Width: | Height: | Size: 228 B |
|
Before Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 661 B |
|
Before Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 715 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 11 KiB |
@@ -348,6 +348,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_notification_sample" = "This is a sample notification";
|
||||
"lng_notification_reminder" = "Reminder";
|
||||
|
||||
"lng_reaction_text" = "{reaction} to your «{text}»";
|
||||
"lng_reaction_notext" = "{reaction} to your message";
|
||||
"lng_reaction_photo" = "{reaction} to your photo";
|
||||
"lng_reaction_video" = "{reaction} to your video";
|
||||
"lng_reaction_video_message" = "{reaction} to your video message";
|
||||
"lng_reaction_document" = "{reaction} to your file";
|
||||
"lng_reaction_sticker" = "{reaction} to your {emoji}sticker";
|
||||
"lng_reaction_voice_message" = "{reaction} to your voice message";
|
||||
"lng_reaction_contact" = "{reaction} to your contact {name}";
|
||||
"lng_reaction_location" = "{reaction} to your map";
|
||||
"lng_reaction_live_location" = "{reaction} to your live location";
|
||||
"lng_reaction_poll" = "{reaction} to your poll {title}";
|
||||
"lng_reaction_quiz" = "{reaction} to your quiz {title}";
|
||||
"lng_reaction_game" = "{reaction} to your game";
|
||||
"lng_reaction_invoice" = "{reaction} to your invoice";
|
||||
"lng_reaction_gif" = "{reaction} to your GIF";
|
||||
|
||||
"lng_settings_section_general" = "General";
|
||||
"lng_settings_change_lang" = "Change language";
|
||||
"lng_languages" = "Languages";
|
||||
@@ -1254,10 +1271,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_you_proximity_reached" = "You are now within {distance} from {user}";
|
||||
"lng_action_you_theme_changed" = "You changed chat theme to {emoji}";
|
||||
"lng_action_theme_changed" = "{from} changed chat theme to {emoji}";
|
||||
"lng_action_theme_changed_channel" = "Channel theme changed to {emoji}";
|
||||
"lng_action_you_theme_disabled" = "You disabled chat theme";
|
||||
"lng_action_theme_disabled" = "{from} disabled chat theme";
|
||||
"lng_action_theme_disabled_channel" = "Channel theme disabled";
|
||||
"lng_action_proximity_distance_m#one" = "{count} meter";
|
||||
"lng_action_proximity_distance_m#other" = "{count} metres";
|
||||
"lng_action_proximity_distance_km#one" = "{count} km";
|
||||
@@ -1804,6 +1819,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_seen_reacted#other" = "{count} Reacted";
|
||||
"lng_context_seen_reacted_none" = "Nobody Reacted";
|
||||
"lng_context_seen_reacted_all" = "Show All Reactions";
|
||||
"lng_context_set_as_quick" = "Set As Quick";
|
||||
|
||||
"lng_send_image_empty" = "Could not send an empty file: {name}";
|
||||
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1500 MB: {name}";
|
||||
@@ -1826,7 +1842,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forward_share_contact" = "Share contact to {recipient}?";
|
||||
"lng_forward_share_cant" = "Sorry, no way to share contact here :(";
|
||||
|
||||
|
||||
"lng_forward_send_files_cant" = "Sorry, no way to send media here :(";
|
||||
"lng_forward_send" = "Send";
|
||||
"lng_forward_messages#one" = "{count} forwarded message";
|
||||
@@ -1974,6 +1989,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_formatting_link_url" = "URL";
|
||||
"lng_formatting_link_create" = "Create";
|
||||
|
||||
"lng_text_copied" = "Text copied to clipboard.";
|
||||
|
||||
"lng_spellchecker_submenu" = "Spelling";
|
||||
"lng_spellchecker_add" = "Add to Dictionary";
|
||||
"lng_spellchecker_remove" = "Remove from Dictionary";
|
||||
|
||||
@@ -1313,7 +1313,7 @@ messageUserReaction#932844fa user_id:long reaction:string = MessageUserReaction;
|
||||
|
||||
messages.messageReactionsList#a366923c flags:# count:int reactions:Vector<MessageUserReaction> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;
|
||||
|
||||
availableReaction#21d7c4b flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document = AvailableReaction;
|
||||
availableReaction#c077ec01 flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction;
|
||||
|
||||
messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions;
|
||||
messages.availableReactions#768e3aad hash:int reactions:Vector<AvailableReaction> = messages.AvailableReactions;
|
||||
@@ -1750,4 +1750,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 136
|
||||
// LAYER 137
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.4.3.0" />
|
||||
Version="3.4.8.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,4,3,0
|
||||
PRODUCTVERSION 3,4,3,0
|
||||
FILEVERSION 3,4,8,0
|
||||
PRODUCTVERSION 3,4,8,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "FileVersion", "3.4.8.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.4.3.0"
|
||||
VALUE "ProductVersion", "3.4.8.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,4,3,0
|
||||
PRODUCTVERSION 3,4,3,0
|
||||
FILEVERSION 3,4,8,0
|
||||
PRODUCTVERSION 3,4,8,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "3.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "FileVersion", "3.4.8.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.4.3.0"
|
||||
VALUE "ProductVersion", "3.4.8.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -27,7 +27,7 @@ EntitiesInText EntitiesFromMTP(
|
||||
for (const auto &entity : entities) {
|
||||
switch (entity.type()) {
|
||||
case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back({ EntityType::Url, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back({ EntityType::CustomUrl, d.voffset().v, d.vlength().v, Clean(qs(d.vurl())) }); } break;
|
||||
case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back({ EntityType::CustomUrl, d.voffset().v, d.vlength().v, qs(d.vurl()) }); } break;
|
||||
case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back({ EntityType::Email, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back({ EntityType::Hashtag, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset().v, d.vlength().v }); } break;
|
||||
@@ -71,7 +71,7 @@ EntitiesInText EntitiesFromMTP(
|
||||
case mtpc_messageEntityUnderline: { auto &d = entity.c_messageEntityUnderline(); result.push_back({ EntityType::Underline, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, Clean(qs(d.vlanguage())) }); } break;
|
||||
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
|
||||
case mtpc_messageEntityBankCard: break; // Skipping cards.
|
||||
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
|
||||
// #TODO entities
|
||||
|
||||
@@ -51,6 +51,7 @@ inline bool operator==(
|
||||
|
||||
struct PeersWithReactions {
|
||||
std::vector<PeerWithReaction> list;
|
||||
std::vector<PeerId> read;
|
||||
int fullReactionsCount = 0;
|
||||
bool unknown = false;
|
||||
};
|
||||
@@ -59,6 +60,7 @@ inline bool operator==(
|
||||
const PeersWithReactions &b) noexcept {
|
||||
return (a.fullReactionsCount == b.fullReactionsCount)
|
||||
&& (a.list == b.list)
|
||||
&& (a.read == b.read)
|
||||
&& (a.unknown == b.unknown);
|
||||
}
|
||||
|
||||
@@ -80,7 +82,9 @@ struct CachedReacted {
|
||||
|
||||
struct Context {
|
||||
base::flat_map<not_null<HistoryItem*>, CachedRead> cachedRead;
|
||||
base::flat_map<not_null<HistoryItem*>, CachedReacted> cachedReacted;
|
||||
base::flat_map<
|
||||
not_null<HistoryItem*>,
|
||||
base::flat_map<QString, CachedReacted>> cachedReacted;
|
||||
base::flat_map<not_null<Main::Session*>, rpl::lifetime> subscriptions;
|
||||
|
||||
[[nodiscard]] CachedRead &cacheRead(not_null<HistoryItem*> item) {
|
||||
@@ -91,12 +95,15 @@ struct Context {
|
||||
return cachedRead.emplace(item, CachedRead()).first->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] CachedReacted &cacheReacted(not_null<HistoryItem*> item) {
|
||||
const auto i = cachedReacted.find(item);
|
||||
if (i != end(cachedReacted)) {
|
||||
[[nodiscard]] CachedReacted &cacheReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction) {
|
||||
auto &map = cachedReacted[item];
|
||||
const auto i = map.find(reaction);
|
||||
if (i != end(map)) {
|
||||
return i->second;
|
||||
}
|
||||
return cachedReacted.emplace(item, CachedReacted()).first->second;
|
||||
return map.emplace(reaction, CachedReacted()).first->second;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -140,9 +147,11 @@ struct State {
|
||||
item->history()->session().api().request(requestId).cancel();
|
||||
}
|
||||
}
|
||||
for (auto &[item, entry] : i->second->cachedReacted) {
|
||||
if (const auto requestId = entry.requestId) {
|
||||
item->history()->session().api().request(requestId).cancel();
|
||||
for (auto &[item, map] : i->second->cachedReacted) {
|
||||
for (auto &[reaction, entry] : map) {
|
||||
if (const auto requestId = entry.requestId) {
|
||||
item->history()->session().api().request(requestId).cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
contexts.erase(i);
|
||||
@@ -150,7 +159,9 @@ struct State {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Context*> PreparedContextAt(not_null<QWidget*> key, not_null<Main::Session*> session) {
|
||||
[[nodiscard]] not_null<Context*> PreparedContextAt(
|
||||
not_null<QWidget*> key,
|
||||
not_null<Main::Session*> session) {
|
||||
const auto context = ContextAt(key);
|
||||
if (context->subscriptions.contains(session)) {
|
||||
return context;
|
||||
@@ -165,7 +176,9 @@ struct State {
|
||||
}
|
||||
const auto j = context->cachedReacted.find(update.item);
|
||||
if (j != end(context->cachedReacted)) {
|
||||
session->api().request(j->second.requestId).cancel();
|
||||
for (auto &[reaction, entry] : j->second) {
|
||||
session->api().request(entry.requestId).cancel();
|
||||
}
|
||||
context->cachedReacted.erase(j);
|
||||
}
|
||||
}, context->subscriptions[session]);
|
||||
@@ -233,17 +246,20 @@ struct State {
|
||||
}
|
||||
|
||||
[[nodiscard]] PeersWithReactions WithEmptyReactions(
|
||||
const Peers &peers) {
|
||||
return PeersWithReactions{
|
||||
Peers &&peers) {
|
||||
auto result = PeersWithReactions{
|
||||
.list = peers.list | ranges::views::transform([](PeerId peer) {
|
||||
return PeerWithReaction{.peer = peer };
|
||||
}) | ranges::to_vector,
|
||||
.unknown = peers.unknown,
|
||||
};
|
||||
result.read = std::move(peers.list);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context) {
|
||||
auto weak = QPointer<QWidget>(context.get());
|
||||
const auto session = &item->history()->session();
|
||||
@@ -252,19 +268,22 @@ struct State {
|
||||
return rpl::lifetime();
|
||||
}
|
||||
const auto context = PreparedContextAt(weak.data(), session);
|
||||
auto &entry = context->cacheReacted(item);
|
||||
auto &entry = context->cacheReacted(item, reaction);
|
||||
if (!entry.requestId) {
|
||||
using Flag = MTPmessages_GetMessageReactionsList::Flag;
|
||||
entry.requestId = session->api().request(
|
||||
MTPmessages_GetMessageReactionsList(
|
||||
MTP_flags(0),
|
||||
MTP_flags(reaction.isEmpty()
|
||||
? Flag(0)
|
||||
: Flag::f_reaction),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTPstring(), // reaction
|
||||
MTP_string(reaction),
|
||||
MTPstring(), // offset
|
||||
MTP_int(kContextReactionsLimit)
|
||||
)
|
||||
).done([=](const MTPmessages_MessageReactionsList &result) {
|
||||
auto &entry = context->cacheReacted(item);
|
||||
auto &entry = context->cacheReacted(item, reaction);
|
||||
entry.requestId = 0;
|
||||
|
||||
result.match([&](
|
||||
@@ -286,7 +305,7 @@ struct State {
|
||||
entry.data = std::move(parsed);
|
||||
});
|
||||
}).fail([=] {
|
||||
auto &entry = context->cacheReacted(item);
|
||||
auto &entry = context->cacheReacted(item, reaction);
|
||||
entry.requestId = 0;
|
||||
if (entry.data.current().unknown) {
|
||||
entry.data = PeersWithReactions();
|
||||
@@ -302,9 +321,9 @@ struct State {
|
||||
not_null<QWidget*> context)
|
||||
-> rpl::producer<PeersWithReactions> {
|
||||
return rpl::combine(
|
||||
WhoReactedIds(item, context),
|
||||
WhoReactedIds(item, QString(), context),
|
||||
WhoReadIds(item, context)
|
||||
) | rpl::map([=](PeersWithReactions reacted, Peers read) {
|
||||
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
|
||||
if (reacted.unknown || read.unknown) {
|
||||
return PeersWithReactions{ .unknown = true };
|
||||
}
|
||||
@@ -314,7 +333,8 @@ struct State {
|
||||
list.push_back({ .peer = peer });
|
||||
}
|
||||
}
|
||||
return reacted;
|
||||
reacted.read = std::move(read.list);
|
||||
return std::move(reacted);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -423,6 +443,104 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||
RegenerateUserpics(state, small, large);
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||
const auto small = st.userpics.size;
|
||||
const auto large = st.photoSize;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto resolveWhoRead = reaction.isEmpty()
|
||||
&& WhoReadExists(item);
|
||||
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto pushNext = [=] {
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
|
||||
const auto resolveWhoReacted = !reaction.isEmpty()
|
||||
|| item->canViewReactions();
|
||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||
? WhoReadOrReactedIds(item, context)
|
||||
: resolveWhoRead
|
||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||
: WhoReactedIds(item, reaction, context);
|
||||
state->current.type = resolveWhoRead
|
||||
? DetectSeenType(item)
|
||||
: Ui::WhoReadType::Reacted;
|
||||
if (resolveWhoReacted) {
|
||||
const auto &list = item->reactions();
|
||||
state->current.fullReactionsCount = reaction.isEmpty()
|
||||
? ranges::accumulate(
|
||||
list,
|
||||
0,
|
||||
ranges::plus{},
|
||||
[](const auto &pair) { return pair.second; })
|
||||
: list.contains(reaction)
|
||||
? list.find(reaction)->second
|
||||
: 0;
|
||||
|
||||
// #TODO reactions
|
||||
state->current.singleReaction = !reaction.isEmpty()
|
||||
? reaction
|
||||
: (list.size() == 1)
|
||||
? list.front().first
|
||||
: QString();
|
||||
}
|
||||
std::move(
|
||||
idsWithReactions
|
||||
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
|
||||
if (peers.unknown) {
|
||||
state->userpics.clear();
|
||||
consumer.put_next(Ui::WhoReadContent{
|
||||
.type = state->current.type,
|
||||
.fullReactionsCount = state->current.fullReactionsCount,
|
||||
.fullReadCount = state->current.fullReadCount,
|
||||
.unknown = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
state->current.fullReadCount = int(peers.read.size());
|
||||
state->current.fullReactionsCount = peers.fullReactionsCount;
|
||||
if (whoReadIds) {
|
||||
whoReadIds->list = (peers.read.size() > peers.list.size())
|
||||
? std::move(peers.read)
|
||||
: std::vector<PeerId>();
|
||||
}
|
||||
if (UpdateUserpics(state, item, peers.list)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.list.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||
}) | rpl::start_with_next([=] {
|
||||
for (const auto &userpic : state->userpics) {
|
||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||
!= userpic.uniqueKey) {
|
||||
state->scheduled = true;
|
||||
crl::on_main(&state->guard, [=] {
|
||||
state->scheduled = false;
|
||||
RegenerateUserpics(state, small, large);
|
||||
pushNext();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||
@@ -467,81 +585,17 @@ bool WhoReactedExists(not_null<HistoryItem*> item) {
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st) {
|
||||
const auto small = st.userpics.size;
|
||||
const auto large = st.photoSize;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto resolveWhoRead = WhoReadExists(item);
|
||||
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto pushNext = [=] {
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
|
||||
const auto resolveWhoReacted = item->canViewReactions();
|
||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||
? WhoReadOrReactedIds(item, context)
|
||||
: resolveWhoRead
|
||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||
: WhoReactedIds(item, context);
|
||||
state->current.type = resolveWhoRead
|
||||
? DetectSeenType(item)
|
||||
: Ui::WhoReadType::Reacted;
|
||||
if (resolveWhoReacted) {
|
||||
const auto &list = item->reactions();
|
||||
state->current.fullReactionsCount = ranges::accumulate(
|
||||
list,
|
||||
0,
|
||||
ranges::plus{},
|
||||
[](const auto &pair) { return pair.second; });
|
||||
|
||||
// #TODO reactions
|
||||
state->current.singleReaction = (list.size() == 1)
|
||||
? list.front().first
|
||||
: QString();
|
||||
}
|
||||
std::move(
|
||||
idsWithReactions
|
||||
) | rpl::start_with_next([=](const PeersWithReactions &peers) {
|
||||
if (peers.unknown) {
|
||||
state->userpics.clear();
|
||||
consumer.put_next(Ui::WhoReadContent{
|
||||
.type = state->current.type,
|
||||
.unknown = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
state->current.fullReactionsCount = peers.fullReactionsCount;
|
||||
if (UpdateUserpics(state, item, peers.list)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.list.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||
}) | rpl::start_with_next([=] {
|
||||
for (const auto &userpic : state->userpics) {
|
||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||
!= userpic.uniqueKey) {
|
||||
state->scheduled = true;
|
||||
crl::on_main(&state->guard, [=] {
|
||||
state->scheduled = false;
|
||||
RegenerateUserpics(state, small, large);
|
||||
pushNext();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
return WhoReacted(item, reaction, context, st, nullptr);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -15,6 +15,7 @@ struct WhoRead;
|
||||
|
||||
namespace Ui {
|
||||
struct WhoReadContent;
|
||||
enum class WhoReadType;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
@@ -22,10 +23,21 @@ namespace Api {
|
||||
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
|
||||
|
||||
struct WhoReadList {
|
||||
std::vector<PeerId> list;
|
||||
Ui::WhoReadType type = {};
|
||||
};
|
||||
|
||||
// The context must be destroyed before the session holding this item.
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st); // Cache results for this lifetime.
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -73,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "ui/item_text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
@@ -470,13 +471,14 @@ void ApiWrap::sendMessageFail(
|
||||
Ui::show(Box<Ui::InformBox>(
|
||||
PeerFloodErrorText(&session(), PeerFloodType::Send)));
|
||||
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
const auto link = textcmdLink(
|
||||
session().createInternalLinkFull(qsl("spambot")),
|
||||
tr::lng_cant_more_info(tr::now));
|
||||
const auto link = Ui::Text::Link(
|
||||
tr::lng_cant_more_info(tr::now),
|
||||
session().createInternalLinkFull(qsl("spambot")));
|
||||
Ui::show(Box<Ui::InformBox>(tr::lng_error_public_groups_denied(
|
||||
tr::now,
|
||||
lt_more_info,
|
||||
link)));
|
||||
link,
|
||||
Ui::Text::WithEntities)));
|
||||
} else if (error.type().startsWith(qstr("SLOWMODE_WAIT_"))) {
|
||||
const auto chop = qstr("SLOWMODE_WAIT_").size();
|
||||
const auto left = base::StringViewMid(error.type(), chop).toInt();
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/special_fields.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -112,16 +113,19 @@ style::InputField CreateBioFieldStyle() {
|
||||
return result;
|
||||
}
|
||||
|
||||
QString PeerFloodErrorText(
|
||||
TextWithEntities PeerFloodErrorText(
|
||||
not_null<Main::Session*> session,
|
||||
PeerFloodType type) {
|
||||
const auto link = textcmdLink(
|
||||
session->createInternalLinkFull(qsl("spambot")),
|
||||
tr::lng_cant_more_info(tr::now));
|
||||
if (type == PeerFloodType::InviteGroup) {
|
||||
return tr::lng_cant_invite_not_contact(tr::now, lt_more_info, link);
|
||||
}
|
||||
return tr::lng_cant_send_to_not_contact(tr::now, lt_more_info, link);
|
||||
const auto link = Ui::Text::Link(
|
||||
tr::lng_cant_more_info(tr::now),
|
||||
session->createInternalLinkFull(qsl("spambot")));
|
||||
return ((type == PeerFloodType::InviteGroup)
|
||||
? tr::lng_cant_invite_not_contact
|
||||
: tr::lng_cant_send_to_not_contact)(
|
||||
tr::now,
|
||||
lt_more_info,
|
||||
link,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
void ShowAddParticipantsError(
|
||||
@@ -167,6 +171,14 @@ void ShowAddParticipantsError(
|
||||
}
|
||||
}
|
||||
const auto hasBot = ranges::any_of(users, &UserData::isBot);
|
||||
if (error == u"PEER_FLOOD"_q) {
|
||||
const auto type = (chat->isChat() || chat->isMegagroup())
|
||||
? PeerFloodType::InviteGroup
|
||||
: PeerFloodType::InviteChannel;
|
||||
const auto text = PeerFloodErrorText(&chat->session(), type);
|
||||
Ui::show(Box<Ui::InformBox>(text), Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
const auto text = [&] {
|
||||
if (error == u"USER_BOT"_q) {
|
||||
return tr::lng_cant_invite_bot_to_channel(tr::now);
|
||||
@@ -184,11 +196,6 @@ void ShowAddParticipantsError(
|
||||
return tr::lng_bot_already_in_group(tr::now);
|
||||
} else if (error == u"BOT_GROUPS_BLOCKED"_q) {
|
||||
return tr::lng_error_cant_add_bot(tr::now);
|
||||
} else if (error == u"PEER_FLOOD"_q) {
|
||||
const auto type = (chat->isChat() || chat->isMegagroup())
|
||||
? PeerFloodType::InviteGroup
|
||||
: PeerFloodType::InviteChannel;
|
||||
return PeerFloodErrorText(&chat->session(), type);
|
||||
} else if (error == u"ADMINS_TOO_MUCH"_q) {
|
||||
return ((chat->isChat() || chat->isMegagroup())
|
||||
? tr::lng_error_admin_limit
|
||||
@@ -1521,11 +1528,10 @@ RevokePublicLinkBox::Inner::Inner(
|
||||
st::contactsNameStyle,
|
||||
peer->name,
|
||||
Ui::NameTextOptions());
|
||||
row.status.setText(
|
||||
row.status.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
_session->createInternalLink(
|
||||
textcmdLink(1, peer->userName())),
|
||||
Ui::DialogTextOptions());
|
||||
Ui::Text::Link(peer->userName())));
|
||||
_rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ enum class PeerFloodType {
|
||||
|
||||
[[nodiscard]] style::InputField CreateBioFieldStyle();
|
||||
|
||||
[[nodiscard]] QString PeerFloodErrorText(
|
||||
[[nodiscard]] TextWithEntities PeerFloodErrorText(
|
||||
not_null<Main::Session*> session,
|
||||
PeerFloodType type);
|
||||
void ShowAddParticipantsError(
|
||||
|
||||
@@ -302,7 +302,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
base::unixtime::now(),
|
||||
out ? history->session().userId() : peerToUser(history->peer->id),
|
||||
QString(),
|
||||
TextWithEntities{ TextUtilities::Clean(text) },
|
||||
TextWithEntities{ text },
|
||||
MTP_messageMediaEmpty(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupedId);
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -312,9 +313,12 @@ void ProxyRow::updateFields(View &&view) {
|
||||
}
|
||||
_view = std::move(view);
|
||||
const auto endpoint = _view.host + ':' + QString::number(_view.port);
|
||||
_title.setText(
|
||||
_title.setMarkedText(
|
||||
st::proxyRowTitleStyle,
|
||||
_view.type + ' ' + textcmdLink(1, endpoint),
|
||||
TextWithEntities()
|
||||
.append(_view.type)
|
||||
.append(' ')
|
||||
.append(Ui::Text::Link(endpoint, {})),
|
||||
Ui::ItemTextDefaultOptions());
|
||||
|
||||
const auto state = _view.state;
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "main/main_session.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
@@ -190,9 +191,12 @@ not_null<Ui::FlatLabel*> CreateWarningLabel(
|
||||
const auto value = valueLimit - length;
|
||||
const auto shown = (value < warnLimit)
|
||||
&& (field->height() > st::createPollOptionField.heightMin);
|
||||
result->setRichText((value >= 0)
|
||||
? QString::number(value)
|
||||
: textcmdLink(1, QString::number(value)));
|
||||
if (value >= 0) {
|
||||
result->setText(QString::number(value));
|
||||
} else {
|
||||
result->setMarkedText(Ui::Text::PlainLink(
|
||||
QString::number(value)));
|
||||
}
|
||||
result->setVisible(shown);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -28,8 +28,7 @@ namespace {
|
||||
TextParseOptions kInformBoxTextOptions = {
|
||||
(TextParseLinks
|
||||
| TextParseMultiline
|
||||
| TextParseMarkdown
|
||||
| TextParseRichText), // flags
|
||||
| TextParseMarkdown), // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -1060,7 +1061,7 @@ void Controller::fillManageSection() {
|
||||
!_peer->isBroadcast(),
|
||||
session->data().reactions().list(
|
||||
Data::Reactions::Type::Active),
|
||||
session->data().reactions().list(_peer),
|
||||
*Data::PeerAllowedReactions(_peer),
|
||||
done));
|
||||
},
|
||||
st::infoIconReactions);
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
@@ -32,6 +33,7 @@ void AddReactionIcon(
|
||||
not_null<DocumentData*> document) {
|
||||
struct State {
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::unique_ptr<Lottie::Icon> icon;
|
||||
QImage image;
|
||||
};
|
||||
|
||||
@@ -45,39 +47,42 @@ void AddReactionIcon(
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
icon->moveToLeft(
|
||||
st::settingsSectionIconLeft,
|
||||
st::editPeerReactionsIconLeft,
|
||||
(size.height() - icon->height()) / 2,
|
||||
size.width());
|
||||
}, icon->lifetime());
|
||||
|
||||
const auto setImage = [=](not_null<Image*> image) {
|
||||
state->image = Images::prepare(
|
||||
image->original(),
|
||||
size * style::DevicePixelRatio(),
|
||||
size * style::DevicePixelRatio(),
|
||||
Images::Option::Smooth | Images::Option::TransparentBackground,
|
||||
size,
|
||||
size);
|
||||
icon->update();
|
||||
const auto initLottie = [=] {
|
||||
state->icon = std::make_unique<Lottie::Icon>(Lottie::IconDescriptor{
|
||||
.path = state->media->owner()->filepath(true),
|
||||
.json = state->media->bytes(),
|
||||
.sizeOverride = QSize(size, size),
|
||||
.frame = -1,
|
||||
});
|
||||
state->media = nullptr;
|
||||
};
|
||||
if (const auto image = state->media->getStickerLarge()) {
|
||||
setImage(image);
|
||||
state->media->checkStickerLarge();
|
||||
if (state->media->loaded()) {
|
||||
initLottie();
|
||||
} else {
|
||||
document->session().downloaderTaskFinished(
|
||||
) | rpl::map([=] {
|
||||
return state->media->getStickerLarge();
|
||||
}) | rpl::filter_nullptr() | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](not_null<Image*> image) {
|
||||
setImage(image);
|
||||
}, button->lifetime());
|
||||
) | rpl::filter([=] {
|
||||
return state->media->loaded();
|
||||
}) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
initLottie();
|
||||
icon->update();
|
||||
}, icon->lifetime());
|
||||
}
|
||||
|
||||
icon->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
Painter p(icon);
|
||||
QPainter p(icon);
|
||||
if (state->image.isNull() && state->icon) {
|
||||
state->image = state->icon->frame();
|
||||
crl::async([icon = std::move(state->icon)]{});
|
||||
}
|
||||
if (!state->image.isNull()) {
|
||||
p.drawImage(0, 0, state->image);
|
||||
p.drawImage(QRect(0, 0, size, size), state->image);
|
||||
}
|
||||
}, icon->lifetime());
|
||||
}
|
||||
@@ -88,7 +93,7 @@ void EditAllowedReactionsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
bool isGroup,
|
||||
const std::vector<Reaction> &list,
|
||||
const std::vector<Reaction> &selected,
|
||||
const base::flat_set<QString> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback) {
|
||||
box->setTitle(tr::lng_manage_peer_reactions());
|
||||
|
||||
@@ -141,14 +146,16 @@ void EditAllowedReactionsBox(
|
||||
tr::lng_manage_peer_reactions_available());
|
||||
|
||||
const auto active = [&](const Data::Reaction &entry) {
|
||||
return ranges::contains(selected, entry.emoji, &Reaction::emoji);
|
||||
return selected.contains(entry.emoji);
|
||||
};
|
||||
const auto add = [&](const Data::Reaction &entry) {
|
||||
const auto button = Settings::AddButton(
|
||||
container,
|
||||
rpl::single(entry.title),
|
||||
st::manageGroupButton.button);
|
||||
AddReactionIcon(button, entry.staticIcon);
|
||||
AddReactionIcon(button, entry.centerIcon
|
||||
? entry.centerIcon
|
||||
: entry.appearAnimation.get());
|
||||
state->toggles.emplace(entry.emoji, button);
|
||||
button->toggleOn(rpl::single(
|
||||
active(entry)
|
||||
@@ -191,9 +198,9 @@ void SaveAllowedReactions(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->setAllowedReactions(allowed);
|
||||
chat->setAllowedReactions({ begin(allowed), end(allowed) });
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setAllowedReactions(allowed);
|
||||
channel->setAllowedReactions({ begin(allowed), end(allowed) });
|
||||
} else {
|
||||
Unexpected("Invalid peer type in SaveAllowedReactions.");
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ void EditAllowedReactionsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
bool isGroup,
|
||||
const std::vector<Data::Reaction> &list,
|
||||
const std::vector<Data::Reaction> &selected,
|
||||
const base::flat_set<QString> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback);
|
||||
|
||||
void SaveAllowedReactions(
|
||||
|
||||
@@ -289,12 +289,9 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
|
||||
Unexpected("Type in LottieForType.");
|
||||
}();
|
||||
const auto size = st::sessionBigLottieSize;
|
||||
static const auto kWhite = style::owned_color(Qt::white);
|
||||
return std::make_unique<Lottie::Icon>(Lottie::IconDescriptor{
|
||||
.path = u":/icons/settings/devices/"_q + path + u".lottie"_q,
|
||||
.color = kWhite.color(),
|
||||
.sizeOverride = QSize(size, size),
|
||||
.frame = 1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,7 +357,7 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
|
||||
state->lottie->animate(
|
||||
[=] { result->update(); },
|
||||
0,
|
||||
state->lottie->framesCount());
|
||||
state->lottie->framesCount() - 1);
|
||||
}, result->lifetime());
|
||||
}
|
||||
|
||||
|
||||
@@ -2186,7 +2186,7 @@ void StickersBox::Inner::rebuildAppendSet(
|
||||
raw->yadd = {};
|
||||
auto oldStickerMedia = std::move(raw->stickerMedia);
|
||||
auto oldThumbnailMedia = std::move(raw->thumbnailMedia);
|
||||
raw->stickerMedia = sticker->activeMediaView();
|
||||
raw->stickerMedia = sticker ? sticker->activeMediaView() : nullptr;
|
||||
raw->thumbnailMedia = set->activeThumbnailView();
|
||||
if (raw->thumbnailMedia != oldThumbnailMedia
|
||||
|| (!raw->thumbnailMedia && raw->stickerMedia != oldStickerMedia)) {
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
@@ -255,11 +256,11 @@ not_null<Ui::RpWidget*> UrlAuthBox::setupContent(
|
||||
tr::lng_url_auth_open_confirm(tr::now, lt_link, url),
|
||||
st::boxLabel),
|
||||
st::boxPadding);
|
||||
const auto addCheckbox = [&](const QString &text) {
|
||||
const auto addCheckbox = [&](const TextWithEntities &text) {
|
||||
const auto checkbox = result->add(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
result,
|
||||
QString(),
|
||||
text,
|
||||
true,
|
||||
st::urlAuthCheckbox),
|
||||
style::margins(
|
||||
@@ -268,23 +269,22 @@ not_null<Ui::RpWidget*> UrlAuthBox::setupContent(
|
||||
st::boxPadding.right(),
|
||||
st::boxPadding.bottom()));
|
||||
checkbox->setAllowTextLines();
|
||||
checkbox->setText(text, true);
|
||||
return checkbox;
|
||||
};
|
||||
const auto auth = addCheckbox(
|
||||
tr::lng_url_auth_login_option(
|
||||
tr::now,
|
||||
lt_domain,
|
||||
textcmdStartSemibold() + domain + textcmdStopSemibold(),
|
||||
Ui::Text::Bold(domain),
|
||||
lt_user,
|
||||
(textcmdStartSemibold()
|
||||
+ session->user()->name
|
||||
+ textcmdStopSemibold())));
|
||||
Ui::Text::Bold(session->user()->name),
|
||||
Ui::Text::WithEntities));
|
||||
const auto allow = bot
|
||||
? addCheckbox(tr::lng_url_auth_allow_messages(
|
||||
tr::now,
|
||||
lt_bot,
|
||||
textcmdStartSemibold() + bot->firstName + textcmdStopSemibold()))
|
||||
Ui::Text::Bold(bot->firstName),
|
||||
Ui::Text::WithEntities))
|
||||
: nullptr;
|
||||
if (allow) {
|
||||
rpl::single(
|
||||
|
||||
@@ -411,49 +411,45 @@ callBarSignalBars: CallSignalBars(callPanelSignalBars) {
|
||||
color: callBarFg;
|
||||
}
|
||||
|
||||
callTitleButton: IconButton {
|
||||
width: 34px;
|
||||
height: 30px;
|
||||
iconPosition: point(0px, 0px);
|
||||
}
|
||||
callTitleButton: windowTitleButton;
|
||||
callTitleMinimizeIcon: icon {
|
||||
{ "calls/calls_minimize_shadow", windowShadowFg },
|
||||
{ "calls/calls_minimize_main", callNameFg },
|
||||
{ "title_shadow_minimize", windowShadowFg },
|
||||
{ "title_button_minimize", callNameFg },
|
||||
};
|
||||
callTitleMinimizeIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_minimize_shadow", windowShadowFg },
|
||||
{ "calls/calls_minimize_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_minimize", windowShadowFg },
|
||||
{ "title_button_minimize", callNameFg },
|
||||
};
|
||||
callTitleMaximizeIcon: icon {
|
||||
{ "calls/calls_maximize_shadow", windowShadowFg },
|
||||
{ "calls/calls_maximize_main", callNameFg },
|
||||
{ "title_shadow_maximize", windowShadowFg },
|
||||
{ "title_button_maximize", callNameFg },
|
||||
};
|
||||
callTitleMaximizeIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_maximize_shadow", windowShadowFg },
|
||||
{ "calls/calls_maximize_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_maximize", windowShadowFg },
|
||||
{ "title_button_maximize", callNameFg },
|
||||
};
|
||||
callTitleRestoreIcon: icon {
|
||||
{ "calls/calls_restore_shadow", windowShadowFg },
|
||||
{ "calls/calls_restore_main", callNameFg },
|
||||
{ "title_shadow_restore", windowShadowFg },
|
||||
{ "title_button_restore", callNameFg },
|
||||
};
|
||||
callTitleRestoreIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_restore_shadow", windowShadowFg },
|
||||
{ "calls/calls_restore_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_restore", windowShadowFg },
|
||||
{ "title_button_restore", callNameFg },
|
||||
};
|
||||
callTitleCloseIcon: icon {
|
||||
{ "calls/calls_close_shadow", windowShadowFg },
|
||||
{ "calls/calls_close_main", callNameFg },
|
||||
{ "title_shadow_close", windowShadowFg },
|
||||
{ "title_button_close", callNameFg },
|
||||
};
|
||||
callTitleCloseIconOver: icon {
|
||||
{ size(34px, 30px), titleButtonCloseBgOver },
|
||||
{ "calls/calls_close_shadow", windowShadowFg },
|
||||
{ "calls/calls_close_main", titleButtonCloseFgOver },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgOver },
|
||||
{ "title_shadow_close", windowShadowFg },
|
||||
{ "title_button_close", titleButtonCloseFgOver },
|
||||
};
|
||||
callTitle: WindowTitle(defaultWindowTitle) {
|
||||
height: 0px;
|
||||
@@ -1055,37 +1051,37 @@ groupCallDelaySlider: MediaSlider(defaultContinuousSlider) {
|
||||
groupCallDelayMargin: margins(22px, 5px, 20px, 10px);
|
||||
|
||||
groupCallTitleButton: IconButton {
|
||||
width: 24px;
|
||||
height: 21px;
|
||||
width: windowTitleButtonWidth;
|
||||
height: windowTitleHeight;
|
||||
iconPosition: point(0px, 0px);
|
||||
}
|
||||
groupCallTitleMinimizeIcon: icon {
|
||||
{ "title_button_minimize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_minimize", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleMinimizeIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_minimize", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_minimize", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleMaximizeIcon: icon {
|
||||
{ "title_button_maximize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_maximize", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleMaximizeIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_maximize", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_maximize", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleRestoreIcon: icon {
|
||||
{ "title_button_restore", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_restore", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleRestoreIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_restore", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_restore", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleCloseIcon: icon {
|
||||
{ "title_button_close", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_close", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleCloseIconOver: icon {
|
||||
{ size(24px, 21px), titleButtonCloseBgOver },
|
||||
{ "title_button_close", titleButtonCloseFgOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgOver },
|
||||
{ "title_button_close", titleButtonCloseFgOver },
|
||||
};
|
||||
groupCallTitle: WindowTitle(defaultWindowTitle) {
|
||||
height: 0px;
|
||||
@@ -1199,7 +1195,7 @@ desktopCaptureSourceSkips: size(2px, 10px);
|
||||
desktopCaptureSourceTitle: WindowTitle(groupCallTitle) {
|
||||
bg: groupCallMembersBgOver;
|
||||
bgActive: groupCallMembersBgOver;
|
||||
height: 21px;
|
||||
height: windowTitleHeight;
|
||||
}
|
||||
desktopCapturePadding: margins(7px, 7px, 7px, 33px);
|
||||
desktopCaptureLabelBottom: 7px;
|
||||
|
||||
@@ -61,8 +61,8 @@ Panel::Panel(not_null<Call*> call)
|
||||
, _user(call->user())
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
widget(),
|
||||
, _controls(Ui::Platform::SetupSeparateTitleControls(
|
||||
window(),
|
||||
st::callTitle,
|
||||
[=](bool maximized) { toggleFullScreen(maximized); }))
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -144,7 +144,7 @@ void Panel::initWindow() {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#ifndef Q_OS_MAC
|
||||
if (_controls->geometry().contains(widgetPoint)) {
|
||||
if (_controls->controls.geometry().contains(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -550,7 +550,7 @@ void Panel::initLayout() {
|
||||
}, widget()->lifetime());
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
_controls->raise();
|
||||
_controls->wrap.raise();
|
||||
#endif // !Q_OS_MAC
|
||||
}
|
||||
|
||||
@@ -628,7 +628,7 @@ void Panel::updateControlsGeometry() {
|
||||
}
|
||||
if (_fingerprint) {
|
||||
#ifndef Q_OS_MAC
|
||||
const auto controlsGeometry = _controls->geometry();
|
||||
const auto controlsGeometry = _controls->controls.geometry();
|
||||
const auto halfWidth = widget()->width() / 2;
|
||||
const auto minLeft = (controlsGeometry.center().x() < halfWidth)
|
||||
? (controlsGeometry.width() + st::callFingerprintTop)
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/group/ui/desktop_capture_choose_source.h"
|
||||
#include "ui/effects/animations.h"
|
||||
@@ -37,7 +38,7 @@ namespace GL {
|
||||
enum class Backend;
|
||||
} // namespace GL
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
struct SeparateTitleControls;
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
||||
@@ -126,7 +127,7 @@ private:
|
||||
std::unique_ptr<Incoming> _incoming;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
||||
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
|
||||
#endif // !Q_OS_MAC
|
||||
|
||||
QSize _incomingFrameSize;
|
||||
|
||||
@@ -1195,7 +1195,10 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
|
||||
const auto session = &_peer->session();
|
||||
const auto getCurrentWindow = [=]() -> Window::SessionController* {
|
||||
if (const auto window = Core::App().activeWindow()) {
|
||||
if (const auto window = Core::App().separateWindowForPeer(
|
||||
participantPeer)) {
|
||||
return window->sessionController();
|
||||
} else if (const auto window = Core::App().activeWindow()) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
if (&controller->session() == session) {
|
||||
return controller;
|
||||
@@ -1221,7 +1224,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
? st::groupCallPopupMenuWithVolume
|
||||
: st::groupCallPopupMenu));
|
||||
const auto weakMenu = Ui::MakeWeak(result.get());
|
||||
const auto performOnMainWindow = [=](auto callback) {
|
||||
const auto withActiveWindow = [=](auto callback) {
|
||||
if (const auto window = getWindow()) {
|
||||
if (const auto menu = weakMenu.data()) {
|
||||
menu->discardParentReActivate();
|
||||
@@ -1236,12 +1239,12 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
}
|
||||
};
|
||||
const auto showProfile = [=] {
|
||||
performOnMainWindow([=](not_null<Window::SessionController*> window) {
|
||||
withActiveWindow([=](not_null<Window::SessionController*> window) {
|
||||
window->showPeerInfo(participantPeer);
|
||||
});
|
||||
};
|
||||
const auto showHistory = [=] {
|
||||
performOnMainWindow([=](not_null<Window::SessionController*> window) {
|
||||
withActiveWindow([=](not_null<Window::SessionController*> window) {
|
||||
window->showPeerHistory(
|
||||
participantPeer,
|
||||
Window::SectionShow::Way::Forward);
|
||||
|
||||
@@ -108,7 +108,7 @@ private:
|
||||
};
|
||||
|
||||
TextParseOptions MenuTextOptions = {
|
||||
TextParseLinks | TextParseRichText, // flags
|
||||
TextParseLinks, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
||||
@@ -88,8 +88,8 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||
, _peer(call->peer())
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
widget(),
|
||||
, _controls(Ui::Platform::SetupSeparateTitleControls(
|
||||
window(),
|
||||
st::groupCallTitle))
|
||||
#endif // !Q_OS_MAC
|
||||
, _viewport(
|
||||
@@ -302,7 +302,7 @@ void Panel::initWidget() {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
// title geometry depends on _controls->geometry,
|
||||
// title geometry depends on _controls->controls.geometry,
|
||||
// which is not updated here yet.
|
||||
crl::on_main(widget(), [=] { refreshTitle(); });
|
||||
}, lifetime());
|
||||
@@ -1368,7 +1368,7 @@ void Panel::initLayout() {
|
||||
initGeometry();
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
_controls->raise();
|
||||
_controls->wrap.raise();
|
||||
|
||||
Ui::Platform::TitleControlsLayoutChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -1413,7 +1413,7 @@ QRect Panel::computeTitleRect() const {
|
||||
#ifdef Q_OS_MAC
|
||||
return QRect(70, 0, width - remove - 70, 28);
|
||||
#else // Q_OS_MAC
|
||||
const auto controls = _controls->geometry();
|
||||
const auto controls = _controls->controls.geometry();
|
||||
const auto right = controls.x() + controls.width() + skip;
|
||||
return (controls.center().x() < width / 2)
|
||||
? QRect(right, 0, width - right - remove, controls.height())
|
||||
@@ -1884,7 +1884,8 @@ void Panel::updateControlsGeometry() {
|
||||
#ifdef Q_OS_MAC
|
||||
const auto controlsOnTheLeft = true;
|
||||
#else // Q_OS_MAC
|
||||
const auto controlsOnTheLeft = _controls->geometry().center().x()
|
||||
const auto center = _controls->controls.geometry().center();
|
||||
const auto controlsOnTheLeft = center.x()
|
||||
< widget()->width() / 2;
|
||||
#endif // Q_OS_MAC
|
||||
const auto menux = st::groupCallMenuTogglePosition.x();
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/flags.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
@@ -51,7 +52,7 @@ namespace Toast {
|
||||
class Instance;
|
||||
} // namespace Toast
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
struct SeparateTitleControls;
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
||||
@@ -194,7 +195,7 @@ private:
|
||||
rpl::variable<PanelMode> _mode;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
||||
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
|
||||
#endif // !Q_OS_MAC
|
||||
|
||||
rpl::lifetime _callLifetime;
|
||||
|
||||
@@ -139,6 +139,9 @@ void DownloadDictionaryInBackground(
|
||||
not_null<Main::Session*> session,
|
||||
int counter,
|
||||
std::vector<int> langs) {
|
||||
if (counter >= langs.size()) {
|
||||
return;
|
||||
}
|
||||
const auto id = langs[counter];
|
||||
counter++;
|
||||
const auto destroyer = [=] {
|
||||
@@ -154,9 +157,6 @@ void DownloadDictionaryInBackground(
|
||||
}
|
||||
}
|
||||
|
||||
if (counter >= langs.size()) {
|
||||
return;
|
||||
}
|
||||
DownloadDictionaryInBackground(session, counter, langs);
|
||||
};
|
||||
if (DictionaryExists(id)) {
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_last_input.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "platform/platform_integration.h"
|
||||
#include "mainwindow.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "history/history.h"
|
||||
@@ -123,12 +124,14 @@ Application *Application::Instance = nullptr;
|
||||
struct Application::Private {
|
||||
base::Timer quitTimer;
|
||||
UiIntegration uiIntegration;
|
||||
Settings settings;
|
||||
};
|
||||
|
||||
Application::Application(not_null<Launcher*> launcher)
|
||||
: QObject()
|
||||
, _launcher(launcher)
|
||||
, _private(std::make_unique<Private>())
|
||||
, _platformIntegration(Platform::Integration::Create())
|
||||
, _databases(std::make_unique<Storage::Databases>())
|
||||
, _animationsManager(std::make_unique<Ui::Animations::Manager>())
|
||||
, _clearEmojiImageLoaderTimer([=] { clearEmojiSourceImages(); })
|
||||
@@ -144,6 +147,8 @@ Application::Application(not_null<Launcher*> launcher)
|
||||
, _autoLockTimer([=] { checkAutoLock(); }) {
|
||||
Ui::Integration::Set(&_private->uiIntegration);
|
||||
|
||||
_platformIntegration->init();
|
||||
|
||||
passcodeLockChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
_shouldLockAt = 0;
|
||||
@@ -167,10 +172,11 @@ Application::~Application() {
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
// Depend on activeWindow() for now :(
|
||||
// Depend on primaryWindow() for now :(
|
||||
Shortcuts::Finish();
|
||||
|
||||
_window = nullptr;
|
||||
_secondaryWindows.clear();
|
||||
_primaryWindow = nullptr;
|
||||
_mediaView = nullptr;
|
||||
_notifications->clearAllFast();
|
||||
|
||||
@@ -263,12 +269,13 @@ void Application::run() {
|
||||
// Create mime database, so it won't be slow later.
|
||||
QMimeDatabase().mimeTypeForName(qsl("text/plain"));
|
||||
|
||||
_window = std::make_unique<Window::Controller>();
|
||||
_primaryWindow = std::make_unique<Window::Controller>();
|
||||
_lastActiveWindow = _primaryWindow.get();
|
||||
|
||||
_domain->activeChanges(
|
||||
) | rpl::start_with_next([=](not_null<Main::Account*> account) {
|
||||
_window->showAccount(account);
|
||||
}, _window->widget()->lifetime());
|
||||
_primaryWindow->showAccount(account);
|
||||
}, _primaryWindow->widget()->lifetime());
|
||||
|
||||
QCoreApplication::instance()->installEventFilter(this);
|
||||
|
||||
@@ -287,20 +294,20 @@ void Application::run() {
|
||||
startShortcuts();
|
||||
startDomain();
|
||||
|
||||
_window->widget()->show();
|
||||
_primaryWindow->widget()->show();
|
||||
|
||||
const auto currentGeometry = _window->widget()->geometry();
|
||||
const auto currentGeometry = _primaryWindow->widget()->geometry();
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
_window->widget()->Ui::RpWidget::setGeometry(currentGeometry);
|
||||
_primaryWindow->widget()->Ui::RpWidget::setGeometry(currentGeometry);
|
||||
|
||||
DEBUG_LOG(("Application Info: showing."));
|
||||
_window->finishFirstShow();
|
||||
_primaryWindow->finishFirstShow();
|
||||
|
||||
if (!_window->locked() && cStartToSettings()) {
|
||||
_window->showSettings();
|
||||
if (!_primaryWindow->locked() && cStartToSettings()) {
|
||||
_primaryWindow->showSettings();
|
||||
}
|
||||
|
||||
_window->updateIsActiveFocus();
|
||||
_primaryWindow->updateIsActiveFocus();
|
||||
|
||||
for (const auto &error : Shortcuts::Errors()) {
|
||||
LOG(("Shortcuts Error: %1").arg(error));
|
||||
@@ -311,12 +318,12 @@ void Application::run() {
|
||||
showOpenGLCrashNotification();
|
||||
}
|
||||
|
||||
_window->openInMediaViewRequests(
|
||||
_primaryWindow->openInMediaViewRequests(
|
||||
) | rpl::start_with_next([=](Media::View::OpenRequest &&request) {
|
||||
if (_mediaView) {
|
||||
_mediaView->show(std::move(request));
|
||||
}
|
||||
}, _window->lifetime());
|
||||
}, _primaryWindow->lifetime());
|
||||
|
||||
{
|
||||
const auto countries = std::make_shared<Countries::Manager>(
|
||||
@@ -341,7 +348,7 @@ void Application::showOpenGLCrashNotification() {
|
||||
Core::App().settings().setDisableOpenGL(true);
|
||||
Local::writeSettings();
|
||||
};
|
||||
_window->show(Box<Ui::ConfirmBox>(
|
||||
_primaryWindow->show(Box<Ui::ConfirmBox>(
|
||||
"There may be a problem with your graphics drivers and OpenGL. "
|
||||
"Try updating your drivers.\n\n"
|
||||
"OpenGL has been disabled. You can try to enable it again "
|
||||
@@ -371,8 +378,8 @@ void Application::startSettingsAndBackground() {
|
||||
}
|
||||
|
||||
void Application::checkSystemDarkMode() {
|
||||
const auto maybeDarkMode = _settings.systemDarkMode();
|
||||
const auto darkModeEnabled = _settings.systemDarkModeEnabled();
|
||||
const auto maybeDarkMode = settings().systemDarkMode();
|
||||
const auto darkModeEnabled = settings().systemDarkModeEnabled();
|
||||
const auto needToSwitch = darkModeEnabled
|
||||
&& maybeDarkMode
|
||||
&& (*maybeDarkMode != Window::Theme::IsNightMode());
|
||||
@@ -384,11 +391,11 @@ void Application::checkSystemDarkMode() {
|
||||
|
||||
void Application::startSystemDarkModeViewer() {
|
||||
if (Window::Theme::Background()->editingTheme()) {
|
||||
_settings.setSystemDarkModeEnabled(false);
|
||||
settings().setSystemDarkModeEnabled(false);
|
||||
}
|
||||
rpl::merge(
|
||||
_settings.systemDarkModeChanges() | rpl::to_empty,
|
||||
_settings.systemDarkModeEnabledChanges() | rpl::to_empty
|
||||
settings().systemDarkModeChanges() | rpl::to_empty,
|
||||
settings().systemDarkModeEnabledChanges() | rpl::to_empty
|
||||
) | rpl::start_with_next([=] {
|
||||
checkSystemDarkMode();
|
||||
}, _lifetime);
|
||||
@@ -397,7 +404,7 @@ void Application::startSystemDarkModeViewer() {
|
||||
auto Application::prepareEmojiSourceImages()
|
||||
-> std::shared_ptr<Ui::Emoji::UniversalImages> {
|
||||
const auto &images = Ui::Emoji::SourceImages();
|
||||
if (_settings.largeEmoji()) {
|
||||
if (settings().largeEmoji()) {
|
||||
return images;
|
||||
}
|
||||
Ui::Emoji::ClearSourceImages(images);
|
||||
@@ -462,7 +469,7 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
|
||||
checkStartUrl();
|
||||
}
|
||||
if (StartUrlRequiresActivate(url)) {
|
||||
_window->activate();
|
||||
_primaryWindow->activate();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@@ -471,6 +478,10 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
|
||||
return QObject::eventFilter(object, e);
|
||||
}
|
||||
|
||||
Settings &Application::settings() {
|
||||
return _private->settings;
|
||||
}
|
||||
|
||||
void Application::saveSettingsDelayed(crl::time delay) {
|
||||
if (_saveSettingsTimer) {
|
||||
_saveSettingsTimer->callOnce(delay);
|
||||
@@ -508,18 +519,17 @@ void Application::constructFallbackProductionConfig(
|
||||
void Application::setCurrentProxy(
|
||||
const MTP::ProxyData &proxy,
|
||||
MTP::ProxyData::Settings settings) {
|
||||
auto &my = _private->settings.proxy();
|
||||
const auto current = [&] {
|
||||
return _settings.proxy().isEnabled()
|
||||
? _settings.proxy().selected()
|
||||
: MTP::ProxyData();
|
||||
return my.isEnabled() ? my.selected() : MTP::ProxyData();
|
||||
};
|
||||
const auto was = current();
|
||||
_settings.proxy().setSelected(proxy);
|
||||
_settings.proxy().setSettings(settings);
|
||||
my.setSelected(proxy);
|
||||
my.setSettings(settings);
|
||||
const auto now = current();
|
||||
refreshGlobalProxy();
|
||||
_proxyChanges.fire({ was, now });
|
||||
_settings.proxy().connectionTypeChangesNotify();
|
||||
my.connectionTypeChangesNotify();
|
||||
}
|
||||
|
||||
auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
|
||||
@@ -527,10 +537,10 @@ auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
|
||||
}
|
||||
|
||||
void Application::badMtprotoConfigurationError() {
|
||||
if (_settings.proxy().isEnabled() && !_badProxyDisableBox) {
|
||||
if (settings().proxy().isEnabled() && !_badProxyDisableBox) {
|
||||
const auto disableCallback = [=] {
|
||||
setCurrentProxy(
|
||||
_settings.proxy().selected(),
|
||||
settings().proxy().selected(),
|
||||
MTP::ProxyData::Settings::System);
|
||||
};
|
||||
_badProxyDisableBox = Ui::show(Box<Ui::InformBox>(
|
||||
@@ -542,7 +552,7 @@ void Application::badMtprotoConfigurationError() {
|
||||
void Application::startLocalStorage() {
|
||||
Local::start();
|
||||
_saveSettingsTimer.emplace([=] { saveSettings(); });
|
||||
_settings.saveDelayedRequests() | rpl::start_with_next([=] {
|
||||
settings().saveDelayedRequests() | rpl::start_with_next([=] {
|
||||
saveSettingsDelayed();
|
||||
}, _lifetime);
|
||||
}
|
||||
@@ -550,12 +560,12 @@ void Application::startLocalStorage() {
|
||||
void Application::startEmojiImageLoader() {
|
||||
_emojiImageLoader.with([
|
||||
source = prepareEmojiSourceImages(),
|
||||
large = _settings.largeEmoji()
|
||||
large = settings().largeEmoji()
|
||||
](Stickers::EmojiImageLoader &loader) mutable {
|
||||
loader.init(std::move(source), large);
|
||||
});
|
||||
|
||||
_settings.largeEmojiChanges(
|
||||
settings().largeEmojiChanges(
|
||||
) | rpl::start_with_next([=](bool large) {
|
||||
if (large) {
|
||||
_clearEmojiImageLoaderTimer.cancel();
|
||||
@@ -659,14 +669,14 @@ void Application::checkLocalTime() {
|
||||
|
||||
void Application::handleAppActivated() {
|
||||
checkLocalTime();
|
||||
if (_window) {
|
||||
_window->updateIsActiveFocus();
|
||||
if (_primaryWindow) {
|
||||
_primaryWindow->updateIsActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handleAppDeactivated() {
|
||||
if (_window) {
|
||||
_window->updateIsActiveBlur();
|
||||
if (_primaryWindow) {
|
||||
_primaryWindow->updateIsActiveBlur();
|
||||
}
|
||||
Ui::Tooltip::Hide();
|
||||
}
|
||||
@@ -766,7 +776,7 @@ bool Application::canApplyLangPackWithoutRestart() const {
|
||||
}
|
||||
|
||||
void Application::checkStartUrl() {
|
||||
if (!cStartUrl().isEmpty() && _window && !_window->locked()) {
|
||||
if (!cStartUrl().isEmpty() && _primaryWindow && !_primaryWindow->locked()) {
|
||||
const auto url = cStartUrl();
|
||||
cSetStartUrl(QString());
|
||||
if (!openLocalUrl(url, {})) {
|
||||
@@ -827,7 +837,9 @@ bool Application::openCustomUrl(
|
||||
return false;
|
||||
}
|
||||
const auto command = base::StringViewMid(urlTrimmed, protocol.size(), 8192);
|
||||
const auto controller = _window ? _window->sessionController() : nullptr;
|
||||
const auto controller = _primaryWindow
|
||||
? _primaryWindow->sessionController()
|
||||
: nullptr;
|
||||
|
||||
using namespace qthelp;
|
||||
const auto options = RegExOption::CaseInsensitive;
|
||||
@@ -842,22 +854,22 @@ bool Application::openCustomUrl(
|
||||
}
|
||||
|
||||
void Application::preventOrInvoke(Fn<void()> &&callback) {
|
||||
_window->preventOrInvoke(std::move(callback));
|
||||
_primaryWindow->preventOrInvoke(std::move(callback));
|
||||
}
|
||||
|
||||
void Application::lockByPasscode() {
|
||||
preventOrInvoke([=] {
|
||||
if (_window) {
|
||||
if (_primaryWindow) {
|
||||
_passcodeLock = true;
|
||||
_window->setupPasscodeLock();
|
||||
_primaryWindow->setupPasscodeLock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Application::unlockPasscode() {
|
||||
clearPasscodeLock();
|
||||
if (_window) {
|
||||
_window->clearPasscodeLock();
|
||||
if (_primaryWindow) {
|
||||
_primaryWindow->clearPasscodeLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,9 +925,11 @@ void Application::checkAutoLock(crl::time lastNonIdleTime) {
|
||||
|
||||
checkLocalTime();
|
||||
const auto now = crl::now();
|
||||
const auto shouldLockInMs = _settings.autoLock() * 1000LL;
|
||||
const auto shouldLockInMs = settings().autoLock() * 1000LL;
|
||||
const auto checkTimeMs = now - lastNonIdleTime;
|
||||
if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
|
||||
if (checkTimeMs >= shouldLockInMs
|
||||
|| (_shouldLockAt > 0
|
||||
&& now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
|
||||
_shouldLockAt = 0;
|
||||
_autoLockTimer.cancel();
|
||||
lockByPasscode();
|
||||
@@ -940,13 +954,13 @@ void Application::localPasscodeChanged() {
|
||||
}
|
||||
|
||||
bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||
if (App::quitting() || !_window) {
|
||||
if (App::quitting() || !_primaryWindow) {
|
||||
return false;
|
||||
} else if (_calls->hasActivePanel(session)) {
|
||||
return true;
|
||||
} else if (const auto controller = _window->sessionController()) {
|
||||
} else if (const auto controller = _primaryWindow->sessionController()) {
|
||||
if (&controller->session() == session
|
||||
&& _window->widget()->isActive()) {
|
||||
&& _primaryWindow->widget()->isActive()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -954,15 +968,53 @@ bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||
}
|
||||
|
||||
void Application::saveCurrentDraftsToHistories() {
|
||||
if (!_window) {
|
||||
if (!_primaryWindow) {
|
||||
return;
|
||||
} else if (const auto controller = _window->sessionController()) {
|
||||
} else if (const auto controller = _primaryWindow->sessionController()) {
|
||||
controller->content()->saveFieldToHistoryLocalDraft();
|
||||
}
|
||||
}
|
||||
|
||||
Window::Controller *Application::primaryWindow() const {
|
||||
return _primaryWindow.get();
|
||||
}
|
||||
|
||||
Window::Controller *Application::separateWindowForPeer(
|
||||
not_null<PeerData*> peer) const {
|
||||
for (const auto &[history, window] : _secondaryWindows) {
|
||||
if (history->peer == peer) {
|
||||
return window.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Window::Controller *Application::ensureSeparateWindowForPeer(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId showAtMsgId) {
|
||||
const auto activate = [&](not_null<Window::Controller*> window) {
|
||||
window->activate();
|
||||
return window;
|
||||
};
|
||||
|
||||
if (const auto existing = separateWindowForPeer(peer)) {
|
||||
existing->sessionController()->showPeerHistory(
|
||||
peer,
|
||||
Window::SectionShow::Way::ClearStack,
|
||||
showAtMsgId);
|
||||
return activate(existing);
|
||||
}
|
||||
const auto result = _secondaryWindows.emplace(
|
||||
peer->owner().history(peer),
|
||||
std::make_unique<Window::Controller>(peer, showAtMsgId)
|
||||
).first->second.get();
|
||||
result->widget()->show();
|
||||
result->finishFirstShow();
|
||||
return activate(result);
|
||||
}
|
||||
|
||||
Window::Controller *Application::activeWindow() const {
|
||||
return _window.get();
|
||||
return _lastActiveWindow;
|
||||
}
|
||||
|
||||
bool Application::closeActiveWindow() {
|
||||
|
||||
@@ -17,6 +17,10 @@ class MainWidget;
|
||||
class FileUploader;
|
||||
class Translator;
|
||||
|
||||
namespace Platform {
|
||||
class Integration;
|
||||
} // namespace Platform
|
||||
|
||||
namespace Storage {
|
||||
class Databases;
|
||||
} // namespace Storage
|
||||
@@ -115,8 +119,11 @@ public:
|
||||
Application &operator=(const Application &other) = delete;
|
||||
~Application();
|
||||
|
||||
[[nodiscard]] not_null<Launcher*> launcher() const {
|
||||
return _launcher;
|
||||
[[nodiscard]] Launcher &launcher() const {
|
||||
return *_launcher;
|
||||
}
|
||||
[[nodiscard]] Platform::Integration &platformIntegration() const {
|
||||
return *_platformIntegration;
|
||||
}
|
||||
|
||||
void run();
|
||||
@@ -133,7 +140,13 @@ public:
|
||||
// Windows interface.
|
||||
bool hasActiveWindow(not_null<Main::Session*> session) const;
|
||||
void saveCurrentDraftsToHistories();
|
||||
[[nodiscard]] Window::Controller *primaryWindow() const;
|
||||
[[nodiscard]] Window::Controller *activeWindow() const;
|
||||
[[nodiscard]] Window::Controller *separateWindowForPeer(
|
||||
not_null<PeerData*> peer) const;
|
||||
Window::Controller *ensureSeparateWindowForPeer(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId showAtMsgId);
|
||||
bool closeActiveWindow();
|
||||
bool minimizeActiveWindow();
|
||||
[[nodiscard]] QWidget *getFileDialogParent();
|
||||
@@ -147,9 +160,7 @@ public:
|
||||
[[nodiscard]] QPoint getPointForCallPanelCenter() const;
|
||||
|
||||
void startSettingsAndBackground();
|
||||
[[nodiscard]] Settings &settings() {
|
||||
return _settings;
|
||||
}
|
||||
[[nodiscard]] Settings &settings();
|
||||
void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay);
|
||||
void saveSettings();
|
||||
|
||||
@@ -314,13 +325,13 @@ private:
|
||||
};
|
||||
InstanceSetter _setter = { this };
|
||||
|
||||
not_null<Launcher*> _launcher;
|
||||
const not_null<Launcher*> _launcher;
|
||||
rpl::event_stream<ProxyChange> _proxyChanges;
|
||||
|
||||
// Some fields are just moved from the declaration.
|
||||
struct Private;
|
||||
const std::unique_ptr<Private> _private;
|
||||
Settings _settings;
|
||||
const std::unique_ptr<Platform::Integration> _platformIntegration;
|
||||
|
||||
const std::unique_ptr<Storage::Databases> _databases;
|
||||
const std::unique_ptr<Ui::Animations::Manager> _animationsManager;
|
||||
@@ -336,7 +347,12 @@ private:
|
||||
const std::unique_ptr<Main::Domain> _domain;
|
||||
const std::unique_ptr<Export::Manager> _exportManager;
|
||||
const std::unique_ptr<Calls::Instance> _calls;
|
||||
std::unique_ptr<Window::Controller> _window;
|
||||
std::unique_ptr<Window::Controller> _primaryWindow;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
std::unique_ptr<Window::Controller>> _secondaryWindows;
|
||||
Window::Controller *_lastActiveWindow = nullptr;
|
||||
|
||||
std::unique_ptr<Media::View::OverlayWidget> _mediaView;
|
||||
const std::unique_ptr<Lang::Instance> _langpack;
|
||||
const std::unique_ptr<Lang::CloudManager> _langCloudManager;
|
||||
|
||||
@@ -66,6 +66,18 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- Spoiler formatting hides text in chat, "
|
||||
"as well as in the chat list and notifications.\n"
|
||||
},
|
||||
{
|
||||
3004005,
|
||||
"- Fix crash in monospace blocks processing.\n"
|
||||
|
||||
"- Fix reaction animations stopping after an hour uptime.\n"
|
||||
},
|
||||
{
|
||||
3004006,
|
||||
"- Add snap layouts support on Windows 11.\n"
|
||||
|
||||
"- Fix crash in drafts after accounts switching.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mainwindow.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "history/history.h"
|
||||
@@ -266,3 +268,41 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
|
||||
auto BotCommandClickHandler::getTextEntity() const -> TextEntity {
|
||||
return { EntityType::BotCommand };
|
||||
}
|
||||
|
||||
MonospaceClickHandler::MonospaceClickHandler(
|
||||
const QString &text,
|
||||
EntityType type)
|
||||
: _text(text)
|
||||
, _entity({ type }) {
|
||||
}
|
||||
|
||||
void MonospaceClickHandler::onClick(ClickContext context) const {
|
||||
const auto button = context.button;
|
||||
if (button != Qt::LeftButton && button != Qt::MiddleButton) {
|
||||
return;
|
||||
}
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
auto &data = controller->session().data();
|
||||
const auto item = data.message(my.itemId);
|
||||
const auto hasCopyRestriction = item
|
||||
&& (!item->history()->peer->allowsForwarding()
|
||||
|| item->forbidsForward());
|
||||
if (hasCopyRestriction) {
|
||||
Ui::Toast::Show(item->history()->peer->isBroadcast()
|
||||
? tr::lng_error_nocopy_channel(tr::now)
|
||||
: tr::lng_error_nocopy_group(tr::now));
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ui::Toast::Show(tr::lng_text_copied(tr::now));
|
||||
TextUtilities::SetClipboardText(TextForMimeData::Simple(_text.trimmed()));
|
||||
}
|
||||
|
||||
auto MonospaceClickHandler::getTextEntity() const -> TextEntity {
|
||||
return _entity;
|
||||
}
|
||||
|
||||
QString MonospaceClickHandler::url() const {
|
||||
return _text;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/basic_click_handlers.h"
|
||||
|
||||
constexpr auto kPeerLinkPeerIdProperty = 0x01;
|
||||
constexpr auto kPhotoLinkMediaProperty = 0x02;
|
||||
constexpr auto kDocumentLinkMediaProperty = 0x03;
|
||||
constexpr auto kSendReactionEmojiProperty = 0x04;
|
||||
constexpr auto kReactionsCountEmojiProperty = 0x05;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
@@ -195,3 +199,20 @@ private:
|
||||
QString _cmd;
|
||||
|
||||
};
|
||||
|
||||
class MonospaceClickHandler : public TextClickHandler {
|
||||
public:
|
||||
MonospaceClickHandler(const QString &text, EntityType type);
|
||||
|
||||
void onClick(ClickContext context) const override;
|
||||
|
||||
TextEntity getTextEntity() const override;
|
||||
|
||||
protected:
|
||||
QString url() const override;
|
||||
|
||||
private:
|
||||
const QString _text;
|
||||
const TextEntity _entity;
|
||||
|
||||
};
|
||||
|
||||
@@ -61,7 +61,7 @@ FilteredCommandLineArguments::FilteredCommandLineArguments(
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
#elif defined Q_OS_UNIX
|
||||
if (Platform::DesktopEnvironment::IsGnome()) {
|
||||
if (Platform::DesktopEnvironment::IsGnome() && qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) {
|
||||
pushArgument("-platform");
|
||||
pushArgument("xcb;wayland");
|
||||
}
|
||||
|
||||
@@ -441,7 +441,7 @@ void Manager::set(const QString &keys, Command command, bool replace) {
|
||||
}
|
||||
auto shortcut = base::make_unique_q<QShortcut>(
|
||||
result,
|
||||
Core::App().activeWindow()->widget().get(),
|
||||
Core::App().primaryWindow()->widget().get(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
Qt::ApplicationShortcut);
|
||||
|
||||
@@ -202,6 +202,11 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
||||
LOG(("Bad mention name: %1").arg(data.data));
|
||||
}
|
||||
} break;
|
||||
|
||||
case EntityType::Code:
|
||||
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
||||
case EntityType::Pre:
|
||||
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
||||
}
|
||||
return Integration::createLinkHandler(data, context);
|
||||
}
|
||||
|
||||
@@ -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 = 3004003;
|
||||
constexpr auto AppVersionStr = "3.4.3";
|
||||
constexpr auto AppVersion = 3004008;
|
||||
constexpr auto AppVersionStr = "3.4.8";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -761,7 +761,7 @@ PeerId ChannelData::groupCallDefaultJoinAs() const {
|
||||
return _callDefaultJoinAs;
|
||||
}
|
||||
|
||||
void ChannelData::setAllowedReactions(std::vector<QString> list) {
|
||||
void ChannelData::setAllowedReactions(base::flat_set<QString> list) {
|
||||
if (_allowedReactions != list) {
|
||||
const auto toggled = (_allowedReactions.empty() != list.empty());
|
||||
_allowedReactions = std::move(list);
|
||||
@@ -774,7 +774,7 @@ void ChannelData::setAllowedReactions(std::vector<QString> list) {
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<QString> &ChannelData::allowedReactions() const {
|
||||
const base::flat_set<QString> &ChannelData::allowedReactions() const {
|
||||
return _allowedReactions;
|
||||
}
|
||||
|
||||
|
||||
@@ -410,8 +410,8 @@ public:
|
||||
void setGroupCallDefaultJoinAs(PeerId peerId);
|
||||
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
|
||||
|
||||
void setAllowedReactions(std::vector<QString> list);
|
||||
[[nodiscard]] const std::vector<QString> &allowedReactions() const;
|
||||
void setAllowedReactions(base::flat_set<QString> list);
|
||||
[[nodiscard]] const base::flat_set<QString> &allowedReactions() const;
|
||||
|
||||
// Still public data members.
|
||||
uint64 access = 0;
|
||||
@@ -460,7 +460,7 @@ private:
|
||||
QString _inviteLink;
|
||||
std::optional<ChannelData*> _linkedChat;
|
||||
|
||||
std::vector<QString> _allowedReactions;
|
||||
base::flat_set<QString> _allowedReactions;
|
||||
|
||||
std::unique_ptr<Data::GroupCall> _call;
|
||||
PeerId _callDefaultJoinAs = 0;
|
||||
|
||||
@@ -287,7 +287,7 @@ void ChatData::setPendingRequestsCount(
|
||||
}
|
||||
}
|
||||
|
||||
void ChatData::setAllowedReactions(std::vector<QString> list) {
|
||||
void ChatData::setAllowedReactions(base::flat_set<QString> list) {
|
||||
if (_allowedReactions != list) {
|
||||
const auto toggled = (_allowedReactions.empty() != list.empty());
|
||||
_allowedReactions = std::move(list);
|
||||
@@ -300,7 +300,7 @@ void ChatData::setAllowedReactions(std::vector<QString> list) {
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<QString> &ChatData::allowedReactions() const {
|
||||
const base::flat_set<QString> &ChatData::allowedReactions() const {
|
||||
return _allowedReactions;
|
||||
}
|
||||
|
||||
|
||||
@@ -175,8 +175,8 @@ public:
|
||||
int count,
|
||||
std::vector<UserId> recentRequesters);
|
||||
|
||||
void setAllowedReactions(std::vector<QString> list);
|
||||
[[nodiscard]] const std::vector<QString> &allowedReactions() const;
|
||||
void setAllowedReactions(base::flat_set<QString> list);
|
||||
[[nodiscard]] const base::flat_set<QString> &allowedReactions() const;
|
||||
|
||||
// Still public data members.
|
||||
const MTPlong inputChat;
|
||||
@@ -202,7 +202,7 @@ private:
|
||||
int _pendingRequestsCount = 0;
|
||||
std::vector<UserId> _recentRequesters;
|
||||
|
||||
std::vector<QString> _allowedReactions;
|
||||
base::flat_set<QString> _allowedReactions;
|
||||
|
||||
std::unique_ptr<Data::GroupCall> _call;
|
||||
PeerId _callDefaultJoinAs = 0;
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_file_click_handler.h"
|
||||
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_photo.h"
|
||||
@@ -44,6 +45,9 @@ DocumentClickHandler::DocumentClickHandler(
|
||||
FullMsgId context)
|
||||
: FileClickHandler(context)
|
||||
, _document(document) {
|
||||
setProperty(
|
||||
kDocumentLinkMediaProperty,
|
||||
reinterpret_cast<qulonglong>(_document.get()));
|
||||
}
|
||||
|
||||
DocumentOpenClickHandler::DocumentOpenClickHandler(
|
||||
@@ -146,6 +150,9 @@ PhotoClickHandler::PhotoClickHandler(
|
||||
: FileClickHandler(context)
|
||||
, _photo(photo)
|
||||
, _peer(peer) {
|
||||
setProperty(
|
||||
kPhotoLinkMediaProperty,
|
||||
reinterpret_cast<qulonglong>(_photo.get()));
|
||||
}
|
||||
|
||||
not_null<PhotoData*> PhotoClickHandler::photo() const {
|
||||
|
||||
@@ -66,44 +66,27 @@ constexpr auto kMaxPreviewImages = 3;
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
|
||||
[[nodiscard]] QString WithCaptionDialogsText(
|
||||
[[nodiscard]] TextWithEntities WithCaptionNotificationText(
|
||||
const QString &attachType,
|
||||
const QString &caption,
|
||||
bool hasMiniImages,
|
||||
const HistoryView::ToPreviewOptions &options) {
|
||||
if (caption.isEmpty()) {
|
||||
return textcmdLink(1, TextUtilities::Clean(attachType));
|
||||
const TextWithEntities &caption,
|
||||
bool hasMiniImages = false) {
|
||||
if (caption.text.isEmpty()) {
|
||||
return Ui::Text::PlainLink(attachType);
|
||||
}
|
||||
|
||||
return hasMiniImages
|
||||
? TextUtilities::Clean(caption, !options.ignoreSpoilers)
|
||||
? caption
|
||||
: tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media_part,
|
||||
textcmdLink(1, tr::lng_dialogs_text_media_wrapped(
|
||||
tr::lng_dialogs_text_media_wrapped(
|
||||
tr::now,
|
||||
lt_media,
|
||||
TextUtilities::Clean(attachType))),
|
||||
Ui::Text::PlainLink(attachType),
|
||||
Ui::Text::WithEntities),
|
||||
lt_caption,
|
||||
TextUtilities::Clean(caption, !options.ignoreSpoilers));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString WithCaptionNotificationText(
|
||||
const QString &attachType,
|
||||
const QString &caption) {
|
||||
if (caption.isEmpty()) {
|
||||
return attachType;
|
||||
}
|
||||
|
||||
return tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media_part,
|
||||
tr::lng_dialogs_text_media_wrapped(
|
||||
tr::now,
|
||||
lt_media,
|
||||
attachType),
|
||||
lt_caption,
|
||||
caption);
|
||||
caption,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PreparePreviewImage(
|
||||
@@ -352,15 +335,7 @@ bool Media::canBeGrouped() const {
|
||||
}
|
||||
|
||||
ItemPreview Media::toPreview(ToPreviewOptions options) const {
|
||||
auto result = notificationText();
|
||||
auto text = result.isEmpty()
|
||||
? QString()
|
||||
: textcmdLink(
|
||||
1,
|
||||
TextUtilities::Clean(
|
||||
std::move(result),
|
||||
!options.ignoreSpoilers));
|
||||
return { .text = std::move(text) };
|
||||
return { .text = notificationText() };
|
||||
}
|
||||
|
||||
bool Media::hasReplyPreview() const {
|
||||
@@ -428,9 +403,8 @@ std::unique_ptr<HistoryView::Media> Media::createView(
|
||||
ItemPreview Media::toGroupPreview(
|
||||
const HistoryItemsList &items,
|
||||
ToPreviewOptions options) const {
|
||||
const auto genericText = textcmdLink(
|
||||
1,
|
||||
TextUtilities::Clean(tr::lng_in_dlg_album(tr::now)));
|
||||
const auto genericText = Ui::Text::PlainLink(
|
||||
tr::lng_in_dlg_album(tr::now));
|
||||
auto result = ItemPreview();
|
||||
auto loadingContext = std::vector<std::any>();
|
||||
for (const auto &item : items) {
|
||||
@@ -452,17 +426,17 @@ ItemPreview Media::toGroupPreview(
|
||||
if (single.loadingContext.has_value()) {
|
||||
loadingContext.push_back(std::move(single.loadingContext));
|
||||
}
|
||||
const auto original = item->originalText().text;
|
||||
if (!original.isEmpty()) {
|
||||
if (result.text.isEmpty()) {
|
||||
result.text = TextUtilities::Clean(original);
|
||||
const auto original = item->originalText();
|
||||
if (!original.text.isEmpty()) {
|
||||
if (result.text.text.isEmpty()) {
|
||||
result.text = original;
|
||||
} else {
|
||||
result.text = genericText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.text.isEmpty()) {
|
||||
if (result.text.text.isEmpty()) {
|
||||
result.text = genericText;
|
||||
}
|
||||
if (!loadingContext.empty()) {
|
||||
@@ -536,10 +510,10 @@ bool MediaPhoto::replyPreviewLoaded() const {
|
||||
return _photo->replyPreviewLoaded();
|
||||
}
|
||||
|
||||
QString MediaPhoto::notificationText() const {
|
||||
TextWithEntities MediaPhoto::notificationText() const {
|
||||
return WithCaptionNotificationText(
|
||||
tr::lng_in_dlg_photo(tr::now),
|
||||
TextUtilities::TextWithSpoilerCommands(parent()->originalText()));
|
||||
parent()->originalText());
|
||||
}
|
||||
|
||||
ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
||||
@@ -569,13 +543,11 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
||||
}
|
||||
const auto type = tr::lng_in_dlg_photo(tr::now);
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: options.ignoreSpoilers
|
||||
? parent()->originalText().text
|
||||
: TextUtilities::TextWithSpoilerCommands(parent()->originalText());
|
||||
? TextWithEntities()
|
||||
: parent()->originalText();
|
||||
const auto hasMiniImages = !images.empty();
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption, hasMiniImages, options),
|
||||
.text = WithCaptionNotificationText(type, caption, hasMiniImages),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
@@ -791,23 +763,22 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||
return tr::lng_in_dlg_file(tr::now);
|
||||
}();
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: options.ignoreSpoilers
|
||||
? parent()->originalText().text
|
||||
: TextUtilities::TextWithSpoilerCommands(parent()->originalText());
|
||||
? TextWithEntities()
|
||||
: parent()->originalText();
|
||||
const auto hasMiniImages = !images.empty();
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption, hasMiniImages, options),
|
||||
.text = WithCaptionNotificationText(type, caption, hasMiniImages),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaFile::notificationText() const {
|
||||
TextWithEntities MediaFile::notificationText() const {
|
||||
if (const auto sticker = _document->sticker()) {
|
||||
return _emoji.isEmpty()
|
||||
const auto text = _emoji.isEmpty()
|
||||
? tr::lng_in_dlg_sticker(tr::now)
|
||||
: tr::lng_in_dlg_sticker_emoji(tr::now, lt_emoji, _emoji);
|
||||
return Ui::Text::PlainLink(text);
|
||||
}
|
||||
const auto type = [&] {
|
||||
if (_document->isVideoMessage()) {
|
||||
@@ -825,9 +796,7 @@ QString MediaFile::notificationText() const {
|
||||
}
|
||||
return tr::lng_in_dlg_file(tr::now);
|
||||
}();
|
||||
return WithCaptionNotificationText(
|
||||
type,
|
||||
TextUtilities::TextWithSpoilerCommands(parent()->originalText()));
|
||||
return WithCaptionNotificationText(type, parent()->originalText());
|
||||
}
|
||||
|
||||
QString MediaFile::pinnedTextSubstring() const {
|
||||
@@ -1035,8 +1004,8 @@ const SharedContact *MediaContact::sharedContact() const {
|
||||
return &_contact;
|
||||
}
|
||||
|
||||
QString MediaContact::notificationText() const {
|
||||
return tr::lng_in_dlg_contact(tr::now);
|
||||
TextWithEntities MediaContact::notificationText() const {
|
||||
return tr::lng_in_dlg_contact(tr::now, Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
QString MediaContact::pinnedTextSubstring() const {
|
||||
@@ -1124,13 +1093,16 @@ Data::CloudImage *MediaLocation::location() const {
|
||||
ItemPreview MediaLocation::toPreview(ToPreviewOptions options) const {
|
||||
const auto type = tr::lng_maps_point(tr::now);
|
||||
const auto hasMiniImages = false;
|
||||
const auto text = TextWithEntities{ .text = _title };
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, _title, hasMiniImages, options),
|
||||
.text = WithCaptionNotificationText(type, text, hasMiniImages),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaLocation::notificationText() const {
|
||||
return WithCaptionNotificationText(tr::lng_maps_point(tr::now), _title);
|
||||
TextWithEntities MediaLocation::notificationText() const {
|
||||
return WithCaptionNotificationText(
|
||||
tr::lng_maps_point(tr::now),
|
||||
{ .text = _title});
|
||||
}
|
||||
|
||||
QString MediaLocation::pinnedTextSubstring() const {
|
||||
@@ -1141,11 +1113,11 @@ TextForMimeData MediaLocation::clipboardText() const {
|
||||
auto result = TextForMimeData::Simple(
|
||||
qstr("[ ") + tr::lng_maps_point(tr::now) + qstr(" ]\n"));
|
||||
auto titleResult = TextUtilities::ParseEntities(
|
||||
TextUtilities::Clean(_title),
|
||||
_title,
|
||||
Ui::WebpageTextTitleOptions().flags);
|
||||
auto descriptionResult = TextUtilities::ParseEntities(
|
||||
TextUtilities::Clean(_description),
|
||||
TextParseLinks | TextParseMultiline | TextParseRichText);
|
||||
_description,
|
||||
TextParseLinks | TextParseMultiline);
|
||||
if (!titleResult.empty()) {
|
||||
result.append(std::move(titleResult));
|
||||
}
|
||||
@@ -1194,7 +1166,7 @@ const Call *MediaCall::call() const {
|
||||
return &_call;
|
||||
}
|
||||
|
||||
QString MediaCall::notificationText() const {
|
||||
TextWithEntities MediaCall::notificationText() const {
|
||||
auto result = Text(parent(), _call.finishReason, _call.video);
|
||||
if (_call.duration > 0) {
|
||||
result = tr::lng_call_type_and_duration(
|
||||
@@ -1204,7 +1176,7 @@ QString MediaCall::notificationText() const {
|
||||
lt_duration,
|
||||
Ui::FormatDurationWords(_call.duration));
|
||||
}
|
||||
return result;
|
||||
return { .text = result };
|
||||
}
|
||||
|
||||
QString MediaCall::pinnedTextSubstring() const {
|
||||
@@ -1212,8 +1184,7 @@ QString MediaCall::pinnedTextSubstring() const {
|
||||
}
|
||||
|
||||
TextForMimeData MediaCall::clipboardText() const {
|
||||
return TextForMimeData::Simple(
|
||||
qstr("[ ") + notificationText() + qstr(" ]"));
|
||||
return { .rich = notificationText() };
|
||||
}
|
||||
|
||||
bool MediaCall::allowsForward() const {
|
||||
@@ -1321,8 +1292,8 @@ ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
|
||||
return { .text = notificationText() };
|
||||
}
|
||||
|
||||
QString MediaWebPage::notificationText() const {
|
||||
return TextUtilities::TextWithSpoilerCommands(parent()->originalText());
|
||||
TextWithEntities MediaWebPage::notificationText() const {
|
||||
return parent()->originalText();
|
||||
}
|
||||
|
||||
QString MediaWebPage::pinnedTextSubstring() const {
|
||||
@@ -1390,7 +1361,7 @@ bool MediaGame::replyPreviewLoaded() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MediaGame::notificationText() const {
|
||||
TextWithEntities MediaGame::notificationText() const {
|
||||
// Add a game controller emoji before game title.
|
||||
auto result = QString();
|
||||
result.reserve(_game->title.size() + 3);
|
||||
@@ -1401,7 +1372,7 @@ QString MediaGame::notificationText() const {
|
||||
).append(
|
||||
QChar(' ')
|
||||
).append(_game->title);
|
||||
return result;
|
||||
return { .text = result };
|
||||
}
|
||||
|
||||
GameData *MediaGame::game() const {
|
||||
@@ -1496,8 +1467,8 @@ bool MediaInvoice::replyPreviewLoaded() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MediaInvoice::notificationText() const {
|
||||
return _invoice.title;
|
||||
TextWithEntities MediaInvoice::notificationText() const {
|
||||
return { .text = _invoice.title };
|
||||
}
|
||||
|
||||
QString MediaInvoice::pinnedTextSubstring() const {
|
||||
@@ -1541,8 +1512,8 @@ PollData *MediaPoll::poll() const {
|
||||
return _poll;
|
||||
}
|
||||
|
||||
QString MediaPoll::notificationText() const {
|
||||
return _poll->question;
|
||||
TextWithEntities MediaPoll::notificationText() const {
|
||||
return Ui::Text::PlainLink(_poll->question);
|
||||
}
|
||||
|
||||
QString MediaPoll::pinnedTextSubstring() const {
|
||||
@@ -1616,16 +1587,16 @@ bool MediaDice::allowsRevoke(TimeId now) const {
|
||||
return (now >= parent()->date() + kFastRevokeRestriction);
|
||||
}
|
||||
|
||||
QString MediaDice::notificationText() const {
|
||||
return _emoji;
|
||||
TextWithEntities MediaDice::notificationText() const {
|
||||
return { .text = _emoji };
|
||||
}
|
||||
|
||||
QString MediaDice::pinnedTextSubstring() const {
|
||||
return QChar(171) + notificationText() + QChar(187);
|
||||
return QChar(171) + notificationText().text + QChar(187);
|
||||
}
|
||||
|
||||
TextForMimeData MediaDice::clipboardText() const {
|
||||
return { notificationText() };
|
||||
return { .rich = notificationText() };
|
||||
}
|
||||
|
||||
bool MediaDice::forceForwardedInfo() const {
|
||||
|
||||
@@ -100,7 +100,7 @@ public:
|
||||
// Returns text with link-start and link-end commands for service-color highlighting.
|
||||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||
virtual ItemPreview toPreview(ToPreviewOptions way) const;
|
||||
virtual QString notificationText() const = 0;
|
||||
virtual TextWithEntities notificationText() const = 0;
|
||||
virtual QString pinnedTextSubstring() const = 0;
|
||||
virtual TextForMimeData clipboardText() const = 0;
|
||||
virtual bool allowsForward() const;
|
||||
@@ -161,7 +161,7 @@ public:
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
bool allowsEditCaption() const override;
|
||||
@@ -199,7 +199,7 @@ public:
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
bool allowsEditCaption() const override;
|
||||
@@ -234,7 +234,7 @@ public:
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
const SharedContact *sharedContact() const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
|
||||
@@ -265,7 +265,7 @@ public:
|
||||
|
||||
Data::CloudImage *location() const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
|
||||
@@ -292,7 +292,7 @@ public:
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
const Call *call() const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
bool allowsForward() const override;
|
||||
@@ -331,7 +331,7 @@ public:
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
bool allowsEdit() const override;
|
||||
@@ -361,7 +361,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
QString errorTextForForward(not_null<PeerData*> peer) const override;
|
||||
@@ -396,7 +396,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
|
||||
@@ -423,7 +423,7 @@ public:
|
||||
|
||||
PollData *poll() const override;
|
||||
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
QString errorTextForForward(not_null<PeerData*> peer) const override;
|
||||
@@ -450,7 +450,7 @@ public:
|
||||
[[nodiscard]] int value() const;
|
||||
|
||||
bool allowsRevoke(TimeId now) const override;
|
||||
QString notificationText() const override;
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
bool forceForwardedInfo() const override;
|
||||
|
||||
@@ -10,12 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -25,6 +26,7 @@ namespace {
|
||||
|
||||
constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000);
|
||||
constexpr auto kPollEach = 20 * crl::time(1000);
|
||||
constexpr auto kSizeForDownscale = 64;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -47,8 +49,22 @@ Reactions::Reactions(not_null<Session*> owner)
|
||||
_pollItems.remove(item);
|
||||
_repaintItems.remove(item);
|
||||
}, _lifetime);
|
||||
|
||||
const auto appConfig = &_owner->session().account().appConfig();
|
||||
appConfig->value(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto favorite = appConfig->get<QString>(
|
||||
u"reactions_default"_q,
|
||||
QString::fromUtf8("\xf0\x9f\x91\x8d"));
|
||||
if (_favorite != favorite && !_saveFaveRequestId) {
|
||||
_favorite = favorite;
|
||||
_updated.fire({});
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
Reactions::~Reactions() = default;
|
||||
|
||||
void Reactions::refresh() {
|
||||
request();
|
||||
}
|
||||
@@ -61,13 +77,26 @@ const std::vector<Reaction> &Reactions::list(Type type) const {
|
||||
Unexpected("Type in Reactions::list.");
|
||||
}
|
||||
|
||||
std::vector<Reaction> Reactions::list(not_null<PeerData*> peer) const {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
return filtered(chat->allowedReactions());
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return filtered(channel->allowedReactions());
|
||||
} else {
|
||||
return list(Type::Active);
|
||||
QString Reactions::favorite() const {
|
||||
return _favorite;
|
||||
}
|
||||
|
||||
void Reactions::setFavorite(const QString &emoji) {
|
||||
const auto api = &_owner->session().api();
|
||||
if (_saveFaveRequestId) {
|
||||
api->request(_saveFaveRequestId).cancel();
|
||||
}
|
||||
_saveFaveRequestId = api->request(MTPmessages_SetDefaultReaction(
|
||||
MTP_string(emoji)
|
||||
)).done([=] {
|
||||
_saveFaveRequestId = 0;
|
||||
}).fail([=] {
|
||||
_saveFaveRequestId = 0;
|
||||
}).send();
|
||||
|
||||
if (_favorite != emoji) {
|
||||
_favorite = emoji;
|
||||
_updated.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,17 +110,37 @@ void Reactions::preloadImageFor(const QString &emoji) {
|
||||
}
|
||||
auto &set = _images.emplace(emoji).first->second;
|
||||
const auto i = ranges::find(_available, emoji, &Reaction::emoji);
|
||||
const auto document = (i != end(_available))
|
||||
? i->staticIcon.get()
|
||||
: nullptr;
|
||||
const auto document = (i == end(_available))
|
||||
? nullptr
|
||||
: i->centerIcon
|
||||
? i->centerIcon
|
||||
: i->appearAnimation.get();
|
||||
if (document) {
|
||||
loadImage(set, document);
|
||||
loadImage(set, document, !i->centerIcon);
|
||||
} else if (!_waitingForList) {
|
||||
_waitingForList = true;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void Reactions::preloadAnimationsFor(const QString &emoji) {
|
||||
const auto i = ranges::find(_available, emoji, &Reaction::emoji);
|
||||
if (i == end(_available)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto preload = [&](DocumentData *document) {
|
||||
const auto view = document
|
||||
? document->activeMediaView()
|
||||
: nullptr;
|
||||
if (view) {
|
||||
view->checkStickerLarge();
|
||||
}
|
||||
};
|
||||
preload(i->centerIcon);
|
||||
preload(i->aroundAnimation);
|
||||
}
|
||||
|
||||
QImage Reactions::resolveImageFor(
|
||||
const QString &emoji,
|
||||
ImageSize size) {
|
||||
@@ -100,6 +149,39 @@ QImage Reactions::resolveImageFor(
|
||||
preloadImageFor(emoji);
|
||||
}
|
||||
auto &set = (i != end(_images)) ? i->second : _images[emoji];
|
||||
const auto resolve = [&](QImage &image, int size) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto frameSize = set.fromAppearAnimation
|
||||
? (size / 2)
|
||||
: size;
|
||||
image = set.icon->frame().scaled(
|
||||
frameSize * factor,
|
||||
frameSize * factor,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
if (set.fromAppearAnimation) {
|
||||
auto result = QImage(
|
||||
size * factor,
|
||||
size * factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(Qt::transparent);
|
||||
|
||||
auto p = QPainter(&result);
|
||||
p.drawImage(
|
||||
(size - frameSize) * factor / 2,
|
||||
(size - frameSize) * factor / 2,
|
||||
image);
|
||||
p.end();
|
||||
|
||||
std::swap(result, image);
|
||||
}
|
||||
image.setDevicePixelRatio(factor);
|
||||
};
|
||||
if (set.bottomInfo.isNull() && set.icon) {
|
||||
resolve(set.bottomInfo, st::reactionInfoImage);
|
||||
resolve(set.inlineList, st::reactionInlineImage);
|
||||
crl::async([icon = std::move(set.icon)]{});
|
||||
}
|
||||
switch (size) {
|
||||
case ImageSize::BottomInfo: return set.bottomInfo;
|
||||
case ImageSize::InlineList: return set.inlineList;
|
||||
@@ -109,15 +191,17 @@ QImage Reactions::resolveImageFor(
|
||||
|
||||
void Reactions::resolveImages() {
|
||||
for (auto &[emoji, set] : _images) {
|
||||
if (!set.bottomInfo.isNull() || set.media) {
|
||||
if (!set.bottomInfo.isNull() || set.icon || set.media) {
|
||||
continue;
|
||||
}
|
||||
const auto i = ranges::find(_available, emoji, &Reaction::emoji);
|
||||
const auto document = (i != end(_available))
|
||||
? i->staticIcon.get()
|
||||
: nullptr;
|
||||
const auto document = (i == end(_available))
|
||||
? nullptr
|
||||
: i->centerIcon
|
||||
? i->centerIcon
|
||||
: i->appearAnimation.get();
|
||||
if (document) {
|
||||
loadImage(set, document);
|
||||
loadImage(set, document, !i->centerIcon);
|
||||
} else {
|
||||
LOG(("API Error: Reaction for emoji '%1' not found!"
|
||||
).arg(emoji));
|
||||
@@ -127,14 +211,17 @@ void Reactions::resolveImages() {
|
||||
|
||||
void Reactions::loadImage(
|
||||
ImageSet &set,
|
||||
not_null<DocumentData*> document) {
|
||||
if (!set.bottomInfo.isNull()) {
|
||||
not_null<DocumentData*> document,
|
||||
bool fromAppearAnimation) {
|
||||
if (!set.bottomInfo.isNull() || set.icon) {
|
||||
return;
|
||||
} else if (!set.media) {
|
||||
set.fromAppearAnimation = fromAppearAnimation;
|
||||
set.media = document->createMediaView();
|
||||
set.media->checkStickerLarge();
|
||||
}
|
||||
if (const auto image = set.media->getStickerLarge()) {
|
||||
setImage(set, image->original());
|
||||
if (set.media->loaded()) {
|
||||
setLottie(set);
|
||||
} else if (!_imagesLoadLifetime) {
|
||||
document->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -143,20 +230,15 @@ void Reactions::loadImage(
|
||||
}
|
||||
}
|
||||
|
||||
void Reactions::setImage(ImageSet &set, QImage large) {
|
||||
void Reactions::setLottie(ImageSet &set) {
|
||||
const auto size = style::ConvertScale(kSizeForDownscale);
|
||||
set.icon = std::make_unique<Lottie::Icon>(Lottie::IconDescriptor{
|
||||
.path = set.media->owner()->filepath(true),
|
||||
.json = set.media->bytes(),
|
||||
.sizeOverride = QSize(size, size),
|
||||
.frame = -1,
|
||||
});
|
||||
set.media = nullptr;
|
||||
const auto scale = [&](int size) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
return Images::prepare(
|
||||
large,
|
||||
size * factor,
|
||||
size * factor,
|
||||
Images::Option::Smooth,
|
||||
size,
|
||||
size);
|
||||
};
|
||||
set.bottomInfo = scale(st::reactionInfoSize);
|
||||
set.inlineList = scale(st::reactionBottomSize);
|
||||
}
|
||||
|
||||
void Reactions::downloadTaskFinished() {
|
||||
@@ -164,8 +246,8 @@ void Reactions::downloadTaskFinished() {
|
||||
for (auto &[emoji, set] : _images) {
|
||||
if (!set.media) {
|
||||
continue;
|
||||
} else if (const auto image = set.media->getStickerLarge()) {
|
||||
setImage(set, image->original());
|
||||
} else if (set.media->loaded()) {
|
||||
setLottie(set);
|
||||
} else {
|
||||
hasOne = true;
|
||||
}
|
||||
@@ -175,33 +257,17 @@ void Reactions::downloadTaskFinished() {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Reaction> Reactions::Filtered(
|
||||
const std::vector<Reaction> &reactions,
|
||||
const std::vector<QString> &emoji) {
|
||||
auto result = std::vector<Reaction>();
|
||||
result.reserve(emoji.size());
|
||||
for (const auto &single : emoji) {
|
||||
const auto i = ranges::find(reactions, single, &Reaction::emoji);
|
||||
if (i != end(reactions)) {
|
||||
result.push_back(*i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Reaction> Reactions::filtered(
|
||||
const std::vector<QString> &emoji) const {
|
||||
return Filtered(list(Type::Active), emoji);
|
||||
}
|
||||
|
||||
std::vector<QString> Reactions::ParseAllowed(
|
||||
base::flat_set<QString> Reactions::ParseAllowed(
|
||||
const MTPVector<MTPstring> *list) {
|
||||
if (!list) {
|
||||
return {};
|
||||
}
|
||||
return list->v | ranges::view::transform([](const MTPstring &string) {
|
||||
const auto parsed = ranges::views::all(
|
||||
list->v
|
||||
) | ranges::views::transform([](const MTPstring &string) {
|
||||
return qs(string);
|
||||
}) | ranges::to_vector;
|
||||
return { begin(parsed), end(parsed) };
|
||||
}
|
||||
|
||||
void Reactions::request() {
|
||||
@@ -214,26 +280,7 @@ void Reactions::request() {
|
||||
)).done([=](const MTPmessages_AvailableReactions &result) {
|
||||
_requestId = 0;
|
||||
result.match([&](const MTPDmessages_availableReactions &data) {
|
||||
_hash = data.vhash().v;
|
||||
|
||||
const auto &list = data.vreactions().v;
|
||||
_active.clear();
|
||||
_available.clear();
|
||||
_active.reserve(list.size());
|
||||
_available.reserve(list.size());
|
||||
for (const auto &reaction : list) {
|
||||
if (const auto parsed = parse(reaction)) {
|
||||
_available.push_back(*parsed);
|
||||
if (parsed->active) {
|
||||
_active.push_back(*parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_waitingForList) {
|
||||
_waitingForList = false;
|
||||
resolveImages();
|
||||
}
|
||||
_updated.fire({});
|
||||
updateFromData(data);
|
||||
}, [&](const MTPDmessages_availableReactionsNotModified &) {
|
||||
});
|
||||
}).fail([=] {
|
||||
@@ -242,6 +289,40 @@ void Reactions::request() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Reactions::updateFromData(const MTPDmessages_availableReactions &data) {
|
||||
_hash = data.vhash().v;
|
||||
|
||||
const auto &list = data.vreactions().v;
|
||||
const auto oldCache = base::take(_iconsCache);
|
||||
const auto toCache = [&](DocumentData *document) {
|
||||
if (document) {
|
||||
_iconsCache.emplace(document, document->createMediaView());
|
||||
}
|
||||
};
|
||||
_active.clear();
|
||||
_available.clear();
|
||||
_active.reserve(list.size());
|
||||
_available.reserve(list.size());
|
||||
_iconsCache.reserve(list.size() * 4);
|
||||
for (const auto &reaction : list) {
|
||||
if (const auto parsed = parse(reaction)) {
|
||||
_available.push_back(*parsed);
|
||||
if (parsed->active) {
|
||||
_active.push_back(*parsed);
|
||||
toCache(parsed->appearAnimation);
|
||||
toCache(parsed->selectAnimation);
|
||||
toCache(parsed->centerIcon);
|
||||
toCache(parsed->aroundAnimation);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_waitingForList) {
|
||||
_waitingForList = false;
|
||||
resolveImages();
|
||||
}
|
||||
_updated.fire({});
|
||||
}
|
||||
|
||||
std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
||||
return entry.match([&](const MTPDavailableReaction &data) {
|
||||
const auto emoji = qs(data.vreaction());
|
||||
@@ -249,6 +330,8 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
||||
if (!known) {
|
||||
LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji));
|
||||
}
|
||||
const auto selectAnimation = _owner->processDocument(
|
||||
data.vselect_animation());
|
||||
return known
|
||||
? std::make_optional(Reaction{
|
||||
.emoji = emoji,
|
||||
@@ -256,12 +339,18 @@ std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
|
||||
.staticIcon = _owner->processDocument(data.vstatic_icon()),
|
||||
.appearAnimation = _owner->processDocument(
|
||||
data.vappear_animation()),
|
||||
.selectAnimation = _owner->processDocument(
|
||||
data.vselect_animation()),
|
||||
.activateAnimation = _owner->processDocument(
|
||||
data.vactivate_animation()),
|
||||
.activateEffects = _owner->processDocument(
|
||||
data.veffect_animation()),
|
||||
.selectAnimation = selectAnimation,
|
||||
//.activateAnimation = _owner->processDocument(
|
||||
// data.vactivate_animation()),
|
||||
//.activateEffects = _owner->processDocument(
|
||||
// data.veffect_animation()),
|
||||
.centerIcon = (data.vcenter_icon()
|
||||
? _owner->processDocument(*data.vcenter_icon()).get()
|
||||
: nullptr),
|
||||
.aroundAnimation = (data.varound_animation()
|
||||
? _owner->processDocument(
|
||||
*data.varound_animation()).get()
|
||||
: nullptr),
|
||||
.active = !data.is_inactive(),
|
||||
})
|
||||
: std::nullopt;
|
||||
@@ -326,7 +415,7 @@ void Reactions::updateAllInHistory(not_null<PeerData*> peer, bool enabled) {
|
||||
|
||||
void Reactions::repaintCollected() {
|
||||
const auto now = crl::now();
|
||||
auto closest = 0;
|
||||
auto closest = crl::time();
|
||||
for (auto i = begin(_repaintItems); i != end(_repaintItems);) {
|
||||
if (i->second <= now) {
|
||||
_owner->requestItemRepaint(i->first);
|
||||
@@ -390,19 +479,33 @@ void MessageReactions::add(const QString &reaction) {
|
||||
if (_chosen == reaction) {
|
||||
return;
|
||||
}
|
||||
const auto history = _item->history();
|
||||
const auto self = history->session().user();
|
||||
if (!_chosen.isEmpty()) {
|
||||
const auto i = _list.find(_chosen);
|
||||
Assert(i != end(_list));
|
||||
--i->second;
|
||||
if (!i->second) {
|
||||
const auto removed = !i->second;
|
||||
if (removed) {
|
||||
_list.erase(i);
|
||||
}
|
||||
const auto j = _recent.find(_chosen);
|
||||
if (j != end(_recent)) {
|
||||
j->second.erase(ranges::remove(j->second, self), end(j->second));
|
||||
if (j->second.empty() || removed) {
|
||||
_recent.erase(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
_chosen = reaction;
|
||||
if (!reaction.isEmpty()) {
|
||||
if (_item->canViewReactions()) {
|
||||
auto &list = _recent[reaction];
|
||||
list.insert(begin(list), self);
|
||||
}
|
||||
++_list[reaction];
|
||||
}
|
||||
auto &owner = _item->history()->owner();
|
||||
auto &owner = history->owner();
|
||||
owner.reactions().send(_item, _chosen);
|
||||
owner.notifyItemDataChange(_item);
|
||||
}
|
||||
@@ -413,8 +516,10 @@ void MessageReactions::remove() {
|
||||
|
||||
void MessageReactions::set(
|
||||
const QVector<MTPReactionCount> &list,
|
||||
const QVector<MTPMessageUserReaction> &recent,
|
||||
bool ignoreChosen) {
|
||||
if (_item->history()->owner().reactions().sending(_item)) {
|
||||
auto &owner = _item->history()->owner();
|
||||
if (owner.reactions().sending(_item)) {
|
||||
// We'll apply non-stale data from the request response.
|
||||
return;
|
||||
}
|
||||
@@ -451,8 +556,24 @@ void MessageReactions::set(
|
||||
_chosen = QString();
|
||||
}
|
||||
}
|
||||
auto parsed = base::flat_map<
|
||||
QString,
|
||||
std::vector<not_null<UserData*>>>();
|
||||
for (const auto &reaction : recent) {
|
||||
reaction.match([&](const MTPDmessageUserReaction &data) {
|
||||
const auto emoji = qs(data.vreaction());
|
||||
if (_list.contains(emoji)) {
|
||||
parsed[emoji].push_back(owner.user(data.vuser_id()));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (_recent != parsed) {
|
||||
_recent = std::move(parsed);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
_item->history()->owner().notifyItemDataChange(_item);
|
||||
owner.notifyItemDataChange(_item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,6 +581,11 @@ const base::flat_map<QString, int> &MessageReactions::list() const {
|
||||
return _list;
|
||||
}
|
||||
|
||||
auto MessageReactions::recent() const
|
||||
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> & {
|
||||
return _recent;
|
||||
}
|
||||
|
||||
bool MessageReactions::empty() const {
|
||||
return _list.empty();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace Lottie {
|
||||
class Icon;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Data {
|
||||
|
||||
class DocumentMedia;
|
||||
@@ -20,14 +24,17 @@ struct Reaction {
|
||||
not_null<DocumentData*> staticIcon;
|
||||
not_null<DocumentData*> appearAnimation;
|
||||
not_null<DocumentData*> selectAnimation;
|
||||
not_null<DocumentData*> activateAnimation;
|
||||
not_null<DocumentData*> activateEffects;
|
||||
//not_null<DocumentData*> activateAnimation;
|
||||
//not_null<DocumentData*> activateEffects;
|
||||
DocumentData *centerIcon = nullptr;
|
||||
DocumentData *aroundAnimation = nullptr;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
class Reactions final {
|
||||
public:
|
||||
explicit Reactions(not_null<Session*> owner);
|
||||
~Reactions();
|
||||
|
||||
void refresh();
|
||||
|
||||
@@ -36,15 +43,10 @@ public:
|
||||
All,
|
||||
};
|
||||
[[nodiscard]] const std::vector<Reaction> &list(Type type) const;
|
||||
[[nodiscard]] std::vector<Reaction> list(not_null<PeerData*> peer) const;
|
||||
[[nodiscard]] QString favorite() const;
|
||||
void setFavorite(const QString &emoji);
|
||||
|
||||
[[nodiscard]] static std::vector<Reaction> Filtered(
|
||||
const std::vector<Reaction> &reactions,
|
||||
const std::vector<QString> &emoji);
|
||||
[[nodiscard]] std::vector<Reaction> filtered(
|
||||
const std::vector<QString> &emoji) const;
|
||||
|
||||
[[nodiscard]] static std::vector<QString> ParseAllowed(
|
||||
[[nodiscard]] static base::flat_set<QString> ParseAllowed(
|
||||
const MTPVector<MTPstring> *list);
|
||||
|
||||
[[nodiscard]] rpl::producer<> updates() const;
|
||||
@@ -54,6 +56,7 @@ public:
|
||||
InlineList,
|
||||
};
|
||||
void preloadImageFor(const QString &emoji);
|
||||
void preloadAnimationsFor(const QString &emoji);
|
||||
[[nodiscard]] QImage resolveImageFor(
|
||||
const QString &emoji,
|
||||
ImageSize size);
|
||||
@@ -70,15 +73,21 @@ private:
|
||||
QImage bottomInfo;
|
||||
QImage inlineList;
|
||||
std::shared_ptr<DocumentMedia> media;
|
||||
std::unique_ptr<Lottie::Icon> icon;
|
||||
bool fromAppearAnimation = false;
|
||||
};
|
||||
|
||||
void request();
|
||||
void updateFromData(const MTPDmessages_availableReactions &data);
|
||||
|
||||
[[nodiscard]] std::optional<Reaction> parse(
|
||||
const MTPAvailableReaction &entry);
|
||||
|
||||
void loadImage(ImageSet &set, not_null<DocumentData*> document);
|
||||
void setImage(ImageSet &set, QImage large);
|
||||
void loadImage(
|
||||
ImageSet &set,
|
||||
not_null<DocumentData*> document,
|
||||
bool fromAppearAnimation);
|
||||
void setLottie(ImageSet &set);
|
||||
void resolveImages();
|
||||
void downloadTaskFinished();
|
||||
|
||||
@@ -89,6 +98,10 @@ private:
|
||||
|
||||
std::vector<Reaction> _active;
|
||||
std::vector<Reaction> _available;
|
||||
QString _favorite;
|
||||
base::flat_map<
|
||||
not_null<DocumentData*>,
|
||||
std::shared_ptr<Data::DocumentMedia>> _iconsCache;
|
||||
rpl::event_stream<> _updated;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
@@ -106,6 +119,8 @@ private:
|
||||
base::flat_set<not_null<HistoryItem*>> _pollingItems;
|
||||
mtpRequestId _pollRequestId = 0;
|
||||
|
||||
mtpRequestId _saveFaveRequestId = 0;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
@@ -116,8 +131,13 @@ public:
|
||||
|
||||
void add(const QString &reaction);
|
||||
void remove();
|
||||
void set(const QVector<MTPReactionCount> &list, bool ignoreChosen);
|
||||
void set(
|
||||
const QVector<MTPReactionCount> &list,
|
||||
const QVector<MTPMessageUserReaction> &recent,
|
||||
bool ignoreChosen);
|
||||
[[nodiscard]] const base::flat_map<QString, int> &list() const;
|
||||
[[nodiscard]] auto recent() const
|
||||
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> &;
|
||||
[[nodiscard]] QString chosen() const;
|
||||
[[nodiscard]] bool empty() const;
|
||||
|
||||
@@ -126,6 +146,7 @@ private:
|
||||
|
||||
QString _chosen;
|
||||
base::flat_map<QString, int> _list;
|
||||
base::flat_map<QString, std::vector<not_null<UserData*>>> _recent;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "base/unixtime.h"
|
||||
|
||||
@@ -497,18 +499,36 @@ rpl::producer<QImage> PeerUserpicImageValue(
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<std::vector<Data::Reaction>> PeerAllowedReactionsValue(
|
||||
std::optional<base::flat_set<QString>> PeerAllowedReactions(
|
||||
not_null<PeerData*> peer) {
|
||||
return rpl::combine(
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(peer->owner().reactions().updates()),
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Reactions)
|
||||
) | rpl::map([=] {
|
||||
return peer->owner().reactions().list(peer);
|
||||
if (const auto chat = peer->asChat()) {
|
||||
return chat->allowedReactions();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return channel->allowedReactions();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto PeerAllowedReactionsValue(
|
||||
not_null<PeerData*> peer)
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>> {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Reactions
|
||||
) | rpl::map([=]{
|
||||
return PeerAllowedReactions(peer);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> UniqueReactionsLimitValue(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto config = &session->account().appConfig();
|
||||
return config->value(
|
||||
) | rpl::map([=] {
|
||||
return int(base::SafeRound(
|
||||
config->get<double>("reactions_uniq_max", 11)));
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
enum class ImageRoundRadius;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct Reaction;
|
||||
@@ -122,7 +126,12 @@ inline auto PeerFullFlagValue(
|
||||
int size,
|
||||
ImageRoundRadius radius);
|
||||
|
||||
[[nodiscard]] std::optional<base::flat_set<QString>> PeerAllowedReactions(
|
||||
not_null<PeerData*> peer);
|
||||
[[nodiscard]] auto PeerAllowedReactionsValue(not_null<PeerData*> peer)
|
||||
-> rpl::producer<std::vector<Data::Reaction>>;
|
||||
-> rpl::producer<std::optional<base::flat_set<QString>>>;
|
||||
|
||||
[[nodiscard]] rpl::producer<int> UniqueReactionsLimitValue(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -32,7 +32,7 @@ constexpr auto kMessagesPerPage = 50;
|
||||
history->nextNonHistoryEntryId(),
|
||||
MessageFlag::FakeHistoryItem,
|
||||
date,
|
||||
HistoryService::PreparedText{ text });
|
||||
HistoryService::PreparedText{ { .text = text } });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -287,7 +287,7 @@ void RepliesList::injectRootDivider(
|
||||
text());
|
||||
} else if (_dividerWithComments != withComments) {
|
||||
_dividerWithComments = withComments;
|
||||
_divider->setServiceText(HistoryService::PreparedText{ text() });
|
||||
_divider->setServiceText(HistoryService::PreparedText{ { text() } });
|
||||
}
|
||||
slice->ids.push_back(_divider->fullId());
|
||||
}
|
||||
|
||||
@@ -3015,11 +3015,11 @@ not_null<WebPageData*> Session::webpage(
|
||||
void Session::webpageApplyFields(
|
||||
not_null<WebPageData*> page,
|
||||
const MTPDwebPage &data) {
|
||||
auto description = TextWithEntities {
|
||||
TextUtilities::Clean(qs(data.vdescription().value_or_empty()))
|
||||
auto description = TextWithEntities{
|
||||
qs(data.vdescription().value_or_empty())
|
||||
};
|
||||
const auto siteName = qs(data.vsite_name().value_or_empty());
|
||||
auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
|
||||
auto parseFlags = TextParseLinks | TextParseMultiline;
|
||||
if (siteName == qstr("Twitter") || siteName == qstr("Instagram")) {
|
||||
parseFlags |= TextParseHashtags | TextParseMentions;
|
||||
}
|
||||
@@ -3191,9 +3191,9 @@ void Session::gameApplyFields(
|
||||
return;
|
||||
}
|
||||
game->accessHash = accessHash;
|
||||
game->shortName = TextUtilities::Clean(shortName);
|
||||
game->shortName = shortName;
|
||||
game->title = TextUtilities::SingleLine(title);
|
||||
game->description = TextUtilities::Clean(description);
|
||||
game->description = description;
|
||||
game->photo = photo;
|
||||
game->document = document;
|
||||
notifyGameUpdateDelayed(game);
|
||||
|
||||
@@ -221,14 +221,11 @@ bool WebPageData::applyChanges(
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto resultUrl = TextUtilities::Clean(newUrl);
|
||||
const auto resultDisplayUrl = TextUtilities::Clean(
|
||||
newDisplayUrl);
|
||||
const auto possibleSiteName = TextUtilities::Clean(
|
||||
newSiteName);
|
||||
const auto resultTitle = TextUtilities::SingleLine(
|
||||
newTitle);
|
||||
const auto resultAuthor = TextUtilities::Clean(newAuthor);
|
||||
const auto resultUrl = newUrl;
|
||||
const auto resultDisplayUrl = newDisplayUrl;
|
||||
const auto possibleSiteName = newSiteName;
|
||||
const auto resultTitle = TextUtilities::SingleLine(newTitle);
|
||||
const auto resultAuthor = newAuthor;
|
||||
|
||||
const auto viewTitleText = resultTitle.isEmpty()
|
||||
? TextUtilities::SingleLine(resultAuthor)
|
||||
|
||||
@@ -1052,7 +1052,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
|
||||
}
|
||||
if (anim::Disabled()
|
||||
&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
|
||||
mousePressReleased(e->globalPos(), e->button());
|
||||
mousePressReleased(e->globalPos(), e->button(), e->modifiers());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,12 +1274,13 @@ bool InnerWidget::pinnedShiftAnimationCallback(crl::time now) {
|
||||
}
|
||||
|
||||
void InnerWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
mousePressReleased(e->globalPos(), e->button());
|
||||
mousePressReleased(e->globalPos(), e->button(), e->modifiers());
|
||||
}
|
||||
|
||||
void InnerWidget::mousePressReleased(
|
||||
QPoint globalPosition,
|
||||
Qt::MouseButton button) {
|
||||
Qt::MouseButton button,
|
||||
Qt::KeyboardModifiers modifiers) {
|
||||
auto wasDragging = (_dragging != nullptr);
|
||||
if (wasDragging) {
|
||||
updateReorderIndexGetCount();
|
||||
@@ -1322,7 +1323,7 @@ void InnerWidget::mousePressReleased(
|
||||
&& peerSearchPressed == _peerSearchSelected)
|
||||
|| (searchedPressed >= 0
|
||||
&& searchedPressed == _searchedSelected)) {
|
||||
chooseRow();
|
||||
chooseRow(modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1758,7 +1759,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
_menuRow = row;
|
||||
if (_pressButton != Qt::LeftButton) {
|
||||
mousePressReleased(e->globalPos(), _pressButton);
|
||||
mousePressReleased(e->globalPos(), _pressButton, e->modifiers());
|
||||
}
|
||||
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
@@ -2347,8 +2348,9 @@ void InnerWidget::refreshSearchInChatLabel() {
|
||||
const auto fromUserText = tr::lng_dlg_search_from(
|
||||
tr::now,
|
||||
lt_user,
|
||||
textcmdLink(1, from));
|
||||
_searchFromUserText.setText(
|
||||
Ui::Text::Link(from),
|
||||
Ui::Text::WithEntities);
|
||||
_searchFromUserText.setMarkedText(
|
||||
st::dialogsSearchFromStyle,
|
||||
fromUserText,
|
||||
Ui::DialogTextOptions());
|
||||
@@ -2689,13 +2691,21 @@ ChosenRow InnerWidget::computeChosenRow() const {
|
||||
return ChosenRow();
|
||||
}
|
||||
|
||||
bool InnerWidget::chooseRow() {
|
||||
bool InnerWidget::chooseRow(Qt::KeyboardModifiers modifiers) {
|
||||
if (chooseCollapsedRow()) {
|
||||
return true;
|
||||
} else if (chooseHashtag()) {
|
||||
return true;
|
||||
}
|
||||
const auto chosen = computeChosenRow();
|
||||
const auto modifyChosenRow = [](
|
||||
ChosenRow row,
|
||||
Qt::KeyboardModifiers modifiers) {
|
||||
#ifdef _DEBUG
|
||||
row.newWindow = (modifiers & Qt::ControlModifier);
|
||||
#endif
|
||||
return row;
|
||||
};
|
||||
const auto chosen = modifyChosenRow(computeChosenRow(), modifiers);
|
||||
if (chosen.key) {
|
||||
if (IsServerMsgId(chosen.message.fullId.msg)) {
|
||||
session().local().saveRecentSearchHashtags(_filter);
|
||||
|
||||
@@ -46,6 +46,7 @@ struct ChosenRow {
|
||||
Key key;
|
||||
Data::MessagePosition message;
|
||||
bool filteredRow = false;
|
||||
bool newWindow = false;
|
||||
};
|
||||
|
||||
enum class SearchRequestType {
|
||||
@@ -95,7 +96,7 @@ public:
|
||||
void refreshEmptyLabel();
|
||||
void resizeEmptyLabel();
|
||||
|
||||
bool chooseRow();
|
||||
bool chooseRow(Qt::KeyboardModifiers modifiers = {});
|
||||
|
||||
void scrollToEntry(const RowDescriptor &entry);
|
||||
|
||||
@@ -192,7 +193,10 @@ private:
|
||||
void refreshDialogRow(RowDescriptor row);
|
||||
|
||||
void clearMouseSelection(bool clearSelection = false);
|
||||
void mousePressReleased(QPoint globalPosition, Qt::MouseButton button);
|
||||
void mousePressReleased(
|
||||
QPoint globalPosition,
|
||||
Qt::MouseButton button,
|
||||
Qt::KeyboardModifiers modifiers);
|
||||
void clearIrrelevantState();
|
||||
void selectByMouse(QPoint globalPosition);
|
||||
void loadPeerPhotos();
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_peer_values.h"
|
||||
@@ -20,10 +21,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Dialogs {
|
||||
namespace {
|
||||
|
||||
QString ComposeFolderListEntryText(not_null<Data::Folder*> folder) {
|
||||
[[nodiscard]] TextWithEntities ComposeFolderListEntryText(
|
||||
not_null<Data::Folder*> folder) {
|
||||
const auto &list = folder->lastHistories();
|
||||
if (list.empty()) {
|
||||
return QString();
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto count = std::max(
|
||||
@@ -38,12 +40,16 @@ QString ComposeFolderListEntryText(not_null<Data::Folder*> folder) {
|
||||
list.size() - (throwAwayLastName ? 1 : 0)
|
||||
);
|
||||
const auto wrapName = [](not_null<History*> history) {
|
||||
const auto name = TextUtilities::Clean(history->peer->name);
|
||||
return (history->unreadCount() > 0)
|
||||
? (textcmdStartSemibold()
|
||||
+ textcmdLink(1, name)
|
||||
+ textcmdStopSemibold())
|
||||
: name;
|
||||
const auto name = history->peer->name;
|
||||
return TextWithEntities{
|
||||
.text = name,
|
||||
.entities = (history->unreadCount() > 0)
|
||||
? EntitiesInText{
|
||||
{ EntityType::Semibold, 0, int(name.size()), QString() },
|
||||
{ EntityType::PlainLink, 0, int(name.size()), QString() },
|
||||
}
|
||||
: EntitiesInText{}
|
||||
};
|
||||
};
|
||||
const auto shown = int(peers.size());
|
||||
const auto accumulated = [&] {
|
||||
@@ -57,12 +63,19 @@ QString ComposeFolderListEntryText(not_null<Data::Folder*> folder) {
|
||||
lt_accumulated,
|
||||
result,
|
||||
lt_chat,
|
||||
wrapName(*i));
|
||||
wrapName(*i),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
return (shown < count)
|
||||
? tr::lng_archived_last(tr::now, lt_count, (count - shown), lt_chats, accumulated)
|
||||
? tr::lng_archived_last(
|
||||
tr::now,
|
||||
lt_count,
|
||||
(count - shown),
|
||||
lt_chats,
|
||||
accumulated,
|
||||
Ui::Text::WithEntities)
|
||||
: accumulated;
|
||||
}
|
||||
|
||||
@@ -277,10 +290,11 @@ void Row::validateListEntryCache() const {
|
||||
return;
|
||||
}
|
||||
_listEntryCacheVersion = version;
|
||||
_listEntryCache.setText(
|
||||
_listEntryCache.setMarkedText(
|
||||
st::dialogsTextStyle,
|
||||
ComposeFolderListEntryText(folder),
|
||||
Ui::DialogTextOptions());
|
||||
// Use rich options as long as the entry text does not have user text.
|
||||
Ui::ItemTextDefaultOptions());
|
||||
}
|
||||
|
||||
FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
|
||||
@@ -288,4 +302,4 @@ FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
|
||||
, _item(item) {
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
} // namespace Dialogs
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_slide_animation.h"
|
||||
#include "window/window_connecting_widget.h"
|
||||
@@ -226,11 +227,24 @@ Widget::Widget(
|
||||
const auto openSearchResult = !controller->selectingPeer()
|
||||
&& row.filteredRow;
|
||||
if (const auto history = row.key.history()) {
|
||||
controller->content()->choosePeer(
|
||||
history->peer->id,
|
||||
(controller->uniqueChatsInSearchResults()
|
||||
? ShowAtUnreadMsgId
|
||||
: row.message.fullId.msg));
|
||||
const auto peer = history->peer;
|
||||
const auto showAtMsgId = controller->uniqueChatsInSearchResults()
|
||||
? ShowAtUnreadMsgId
|
||||
: row.message.fullId.msg;
|
||||
if (row.newWindow) {
|
||||
const auto active = controller->activeChatCurrent();
|
||||
if (const auto history = active.history()) {
|
||||
if (history->peer == peer) {
|
||||
controller->content()->ui_showPeerHistory(
|
||||
0,
|
||||
Window::SectionShow::Way::ClearStack,
|
||||
0);
|
||||
}
|
||||
}
|
||||
Core::App().ensureSeparateWindowForPeer(peer, showAtMsgId);
|
||||
} else {
|
||||
controller->content()->choosePeer(peer->id, showAtMsgId);
|
||||
}
|
||||
} else if (const auto folder = row.key.folder()) {
|
||||
controller->openFolder(folder);
|
||||
}
|
||||
@@ -581,7 +595,9 @@ void Widget::checkUpdateStatus() {
|
||||
|
||||
using Checker = Core::UpdateChecker;
|
||||
if (Checker().state() == Checker::State::Ready) {
|
||||
if (_updateTelegram) return;
|
||||
if (_updateTelegram) {
|
||||
return;
|
||||
}
|
||||
_updateTelegram.create(
|
||||
this,
|
||||
tr::lng_update_telegram(tr::now),
|
||||
@@ -593,9 +609,13 @@ void Widget::checkUpdateStatus() {
|
||||
Core::checkReadyUpdate();
|
||||
App::restart();
|
||||
});
|
||||
_connecting->raise();
|
||||
if (_connecting) {
|
||||
_connecting->raise();
|
||||
}
|
||||
} else {
|
||||
if (!_updateTelegram) return;
|
||||
if (!_updateTelegram) {
|
||||
return;
|
||||
}
|
||||
_updateTelegram.destroy();
|
||||
}
|
||||
updateControlsGeometry();
|
||||
@@ -744,13 +764,13 @@ void Widget::escape() {
|
||||
controller()->closeFolder();
|
||||
} else if (!onCancelSearch()) {
|
||||
if (controller()->activeChatEntryCurrent().key) {
|
||||
cancelled();
|
||||
controller()->content()->dialogsCancelled();
|
||||
} else if (controller()->activeChatsFilterCurrent()) {
|
||||
controller()->setActiveChatsFilter(FilterId(0));
|
||||
}
|
||||
} else if (!_searchInChat && !controller()->selectingPeer()) {
|
||||
if (controller()->activeChatEntryCurrent().key) {
|
||||
cancelled();
|
||||
controller()->content()->dialogsCancelled();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1803,7 +1823,7 @@ void Widget::onCancelSearchInChat() {
|
||||
}
|
||||
applyFilterUpdate(true);
|
||||
if (!isOneColumn && !controller()->selectingPeer()) {
|
||||
cancelled();
|
||||
controller()->content()->dialogsCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,9 +97,6 @@ public:
|
||||
|
||||
~Widget();
|
||||
|
||||
Q_SIGNALS:
|
||||
void cancelled();
|
||||
|
||||
public Q_SLOTS:
|
||||
void onDraggingScrollDelta(int delta);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/localstorage.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -367,11 +368,25 @@ void paintRow(
|
||||
if (!ShowSendActionInDialogs(history)
|
||||
|| !history->sendActionPainter()->paint(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) {
|
||||
if (history->cloudDraftTextCache.isEmpty()) {
|
||||
auto draftWrapped = textcmdLink(1, tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, tr::lng_from_draft(tr::now)));
|
||||
auto draftWrapped = Ui::Text::PlainLink(
|
||||
tr::lng_dialogs_text_from_wrapped(
|
||||
tr::now,
|
||||
lt_from,
|
||||
tr::lng_from_draft(tr::now)));
|
||||
auto draftText = supportMode
|
||||
? textcmdLink(1, Support::ChatOccupiedString(history))
|
||||
: tr::lng_dialogs_text_with_from(tr::now, lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text));
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, DialogTextOptions());
|
||||
? Ui::Text::PlainLink(
|
||||
Support::ChatOccupiedString(history))
|
||||
: tr::lng_dialogs_text_with_from(
|
||||
tr::now,
|
||||
lt_from_part,
|
||||
draftWrapped,
|
||||
lt_message,
|
||||
{ .text = draft->textWithTags.text },
|
||||
Ui::Text::WithEntities);
|
||||
history->cloudDraftTextCache.setMarkedText(
|
||||
st::dialogsTextStyle,
|
||||
draftText,
|
||||
DialogTextOptions());
|
||||
}
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
if (supportMode) {
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_item_preview.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
@@ -109,17 +110,25 @@ void MessageView::paint(
|
||||
options.existing = &_imagesCache;
|
||||
auto preview = item->toPreview(options);
|
||||
if (!preview.images.empty() && preview.imagesInTextPosition > 0) {
|
||||
_senderCache.setText(
|
||||
auto sender = ::Ui::Text::Mid(
|
||||
preview.text,
|
||||
0,
|
||||
preview.imagesInTextPosition);
|
||||
TextUtilities::Trim(sender);
|
||||
_senderCache.setMarkedText(
|
||||
st::dialogsTextStyle,
|
||||
preview.text.mid(0, preview.imagesInTextPosition).trimmed(),
|
||||
std::move(sender),
|
||||
DialogTextOptions());
|
||||
preview.text = preview.text.mid(preview.imagesInTextPosition);
|
||||
preview.text = ::Ui::Text::Mid(
|
||||
preview.text,
|
||||
preview.imagesInTextPosition);
|
||||
} else {
|
||||
_senderCache = { st::dialogsTextWidthMin };
|
||||
}
|
||||
_textCache.setText(
|
||||
TextUtilities::Trim(preview.text);
|
||||
_textCache.setMarkedText(
|
||||
st::dialogsTextStyle,
|
||||
preview.text.trimmed(),
|
||||
preview.text,
|
||||
DialogTextOptions());
|
||||
_textCachedFor = item;
|
||||
_imagesCache = std::move(preview.images);
|
||||
@@ -191,18 +200,24 @@ void MessageView::paint(
|
||||
|
||||
HistoryView::ItemPreview PreviewWithSender(
|
||||
HistoryView::ItemPreview &&preview,
|
||||
const QString &sender) {
|
||||
auto textWithOffset = tr::lng_dialogs_text_with_from(
|
||||
const TextWithEntities &sender) {
|
||||
const auto textWithOffset = tr::lng_dialogs_text_with_from(
|
||||
tr::now,
|
||||
lt_from_part,
|
||||
sender.text,
|
||||
lt_message,
|
||||
preview.text.text,
|
||||
TextWithTagOffset<lt_from_part>::FromString);
|
||||
preview.text = tr::lng_dialogs_text_with_from(
|
||||
tr::now,
|
||||
lt_from_part,
|
||||
sender,
|
||||
lt_message,
|
||||
std::move(preview.text),
|
||||
TextWithTagOffset<lt_from_part>::FromString);
|
||||
preview.text = std::move(textWithOffset.text);
|
||||
Ui::Text::WithEntities);
|
||||
preview.imagesInTextPosition = (textWithOffset.offset < 0)
|
||||
? 0
|
||||
: textWithOffset.offset + sender.size();
|
||||
: textWithOffset.offset + sender.text.size();
|
||||
return std::move(preview);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,6 @@ private:
|
||||
|
||||
[[nodiscard]] HistoryView::ItemPreview PreviewWithSender(
|
||||
HistoryView::ItemPreview &&preview,
|
||||
const QString &sender);
|
||||
const TextWithEntities &sender);
|
||||
|
||||
} // namespace Dialogs::Ui
|
||||
|
||||
@@ -18,6 +18,11 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) {
|
||||
return image;
|
||||
}
|
||||
if (mods.paint) {
|
||||
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
|
||||
image = image.convertToFormat(
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
|
||||
Painter p(&image);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
|
||||
@@ -293,7 +293,23 @@ PhotoEditorControls::PhotoEditorControls(
|
||||
|
||||
controllers->undoController->setPerformRequestChanges(rpl::merge(
|
||||
_undoButton->clicks() | rpl::map_to(Undo::Undo),
|
||||
_redoButton->clicks() | rpl::map_to(Undo::Redo)));
|
||||
_redoButton->clicks() | rpl::map_to(Undo::Redo),
|
||||
_keyPresses.events(
|
||||
) | rpl::filter([=](not_null<QKeyEvent*> e) {
|
||||
using Mode = PhotoEditorMode::Mode;
|
||||
return (e->matches(QKeySequence::Undo)
|
||||
&& !_undoButton->isHidden()
|
||||
&& !_undoButton->testAttribute(
|
||||
Qt::WA_TransparentForMouseEvents)
|
||||
&& (_mode.current().mode == Mode::Paint))
|
||||
|| (e->matches(QKeySequence::Redo)
|
||||
&& !_redoButton->isHidden()
|
||||
&& !_redoButton->testAttribute(
|
||||
Qt::WA_TransparentForMouseEvents)
|
||||
&& (_mode.current().mode == Mode::Paint));
|
||||
}) | rpl::map([=](not_null<QKeyEvent*> e) {
|
||||
return e->matches(QKeySequence::Undo) ? Undo::Undo : Undo::Redo;
|
||||
})));
|
||||
|
||||
controllers->undoController->canPerformChanges(
|
||||
) | rpl::start_with_next([=](const UndoController::EnableRequest &r) {
|
||||
@@ -368,7 +384,8 @@ rpl::producer<> PhotoEditorControls::doneRequests() const {
|
||||
_transformDone->clicks() | rpl::to_empty,
|
||||
_paintDone->clicks() | rpl::to_empty,
|
||||
_keyPresses.events(
|
||||
) | rpl::filter([=](int key) {
|
||||
) | rpl::filter([=](not_null<QKeyEvent*> e) {
|
||||
const auto key = e->key();
|
||||
return ((key == Qt::Key_Enter) || (key == Qt::Key_Return))
|
||||
&& !_toggledBarAnimation.animating();
|
||||
}) | rpl::to_empty);
|
||||
@@ -379,7 +396,8 @@ rpl::producer<> PhotoEditorControls::cancelRequests() const {
|
||||
_transformCancel->clicks() | rpl::to_empty,
|
||||
_paintCancel->clicks() | rpl::to_empty,
|
||||
_keyPresses.events(
|
||||
) | rpl::filter([=](int key) {
|
||||
) | rpl::filter([=](not_null<QKeyEvent*> e) {
|
||||
const auto key = e->key();
|
||||
return (key == Qt::Key_Escape)
|
||||
&& !_toggledBarAnimation.animating();
|
||||
}) | rpl::to_empty);
|
||||
@@ -478,7 +496,7 @@ rpl::producer<bool> PhotoEditorControls::colorLineShownValue() const {
|
||||
}
|
||||
|
||||
bool PhotoEditorControls::handleKeyPress(not_null<QKeyEvent*> e) const {
|
||||
_keyPresses.fire(e->key());
|
||||
_keyPresses.fire(std::move(e));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ private:
|
||||
Ui::Animations::Simple _toggledBarAnimation;
|
||||
|
||||
rpl::variable<PhotoEditorMode> _mode;
|
||||
rpl::event_stream<int> _keyPresses;
|
||||
rpl::event_stream<not_null<QKeyEvent*>> _keyPresses;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -120,18 +120,6 @@ std::vector<ItemPtr> Scene::items(
|
||||
return copyItems;
|
||||
}
|
||||
|
||||
std::vector<not_null<DocumentData*>> Scene::attachedStickers() const {
|
||||
const auto allItems = items();
|
||||
|
||||
return ranges::views::all(
|
||||
allItems
|
||||
) | ranges::views::filter([](const ItemPtr &i) {
|
||||
return i->isVisible() && (i->type() == ItemSticker::Type);
|
||||
}) | ranges::views::transform([](const ItemPtr &i) {
|
||||
return static_cast<ItemSticker*>(i.get())->sticker();
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
std::shared_ptr<float64> Scene::lastZ() const {
|
||||
return _lastZ;
|
||||
}
|
||||
|
||||
@@ -39,9 +39,6 @@ public:
|
||||
[[nodiscard]] rpl::producer<> addsItem() const;
|
||||
[[nodiscard]] rpl::producer<> removesItem() const;
|
||||
|
||||
[[nodiscard]] auto attachedStickers() const
|
||||
-> std::vector<not_null<DocumentData*>>;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<float64> lastZ() const;
|
||||
|
||||
void updateZoom(float64 zoom);
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QGraphicsSceneHoverEvent>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QtMath>
|
||||
|
||||
namespace Editor {
|
||||
namespace {
|
||||
|
||||
@@ -86,8 +86,8 @@ void ItemCanvas::clearPixmap() {
|
||||
_p = nullptr;
|
||||
|
||||
_pixmap = QPixmap(
|
||||
(scene()->sceneRect().size() * cIntRetinaFactor()).toSize());
|
||||
_pixmap.setDevicePixelRatio(cRetinaFactor());
|
||||
(scene()->sceneRect().size() * style::DevicePixelRatio()).toSize());
|
||||
_pixmap.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
_pixmap.fill(Qt::transparent);
|
||||
|
||||
_p = std::make_unique<Painter>(&_pixmap);
|
||||
@@ -170,10 +170,10 @@ void ItemCanvas::handleMouseReleaseEvent(
|
||||
|
||||
if (_contentRect.isValid()) {
|
||||
const auto scaledContentRect = QRectF(
|
||||
_contentRect.x() * cRetinaFactor(),
|
||||
_contentRect.y() * cRetinaFactor(),
|
||||
_contentRect.width() * cRetinaFactor(),
|
||||
_contentRect.height() * cRetinaFactor());
|
||||
_contentRect.x() * style::DevicePixelRatio(),
|
||||
_contentRect.y() * style::DevicePixelRatio(),
|
||||
_contentRect.width() * style::DevicePixelRatio(),
|
||||
_contentRect.height() * style::DevicePixelRatio());
|
||||
|
||||
_grabContentRequests.fire({
|
||||
.pixmap = _pixmap.copy(scaledContentRect.toRect()),
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Editor {
|
||||
|
||||
ItemLine::ItemLine(const QPixmap &&pixmap)
|
||||
: _pixmap(std::move(pixmap))
|
||||
, _rect(QPointF(), _pixmap.size() / cRetinaFactor()) {
|
||||
, _rect(QPointF(), _pixmap.size() / float64(style::DevicePixelRatio())) {
|
||||
}
|
||||
|
||||
QRectF ItemLine::boundingRect() const {
|
||||
|
||||