Compare commits

...

149 Commits

Author SHA1 Message Date
John Preston
f9bf6dbc1e Beta version 3.4.5: Fix build with GCC. 2022-01-16 23:25:08 +03:00
John Preston
64b5269648 Beta version 3.4.5.
- Fix crash in monospace blocks processing.
- Fix reaction animations stopping after an hour uptime.
2022-01-16 22:57:01 +03:00
John Preston
f394cecf55 Fix crash in monospace blocks processing.
Fixes #23905.
2022-01-16 14:13:41 +03:00
23rd
8b56676c23 Fixed formatting of some internal links. 2022-01-16 14:13:12 +03:00
23rd
e2713ea627 Added ability to create formatted internal links. 2022-01-16 14:13:10 +03:00
John Preston
f5e50409d3 Add initial reaction bubble appear animation. 2022-01-16 14:11:50 +03:00
John Preston
050916a56a Fix userpics in more than one recent reaction. 2022-01-15 22:02:10 +03:00
John Preston
cdf36cc387 Fix reaction animations stopping after an hour uptime. 2022-01-15 12:38:47 +03:00
John Preston
f909a36cbd Beta version 3.4.4: Fix incorrect changelog metadata. 2022-01-15 06:29:19 +03:00
John Preston
1d36255ca5 Beta version 3.4.4: Fix build on Windows. 2022-01-14 23:41:27 +03:00
John Preston
acfdae2d72 Beta version 3.4.4: Fix build with GCC. 2022-01-14 22:57:13 +03:00
John Preston
ab59e97b92 Beta version 3.4.4.
- Nice animations in reactions.
2022-01-14 22:06:03 +03:00
John Preston
1060b04b1e Reacted users list on inline reaction right click. 2022-01-14 21:55:48 +03:00
John Preston
df044dbd83 Show local notifications about contact reactions. 2022-01-14 21:55:48 +03:00
John Preston
5eb210ec12 "Set As Quick" context menu in reactions dropdown. 2022-01-14 21:55:48 +03:00
John Preston
f24f78c0cc Use click handler property instead of dynamic cast. 2022-01-14 21:55:48 +03:00
John Preston
8a071fe1fe Respect reactions_default from appconfig. 2022-01-14 21:55:48 +03:00
John Preston
f3e84de5fb Implement recent reaction userpics in groups. 2022-01-14 21:55:48 +03:00
John Preston
2dec1b72f7 Rename reactionBottom* to reactionInline* styles. 2022-01-14 21:55:48 +03:00
John Preston
604a827a52 Allow fast reaction revoke in private chats. 2022-01-14 21:55:48 +03:00
John Preston
3d8b303ab7 Implement parabolic reaction drop. 2022-01-14 21:55:48 +03:00
John Preston
2c599e60c3 Improve first reaction animation in group. 2022-01-14 21:55:48 +03:00
John Preston
928d8feb21 Improve clearing of reaction animations. 2022-01-14 21:55:48 +03:00
John Preston
490e688a91 Add reaction animations to comments. 2022-01-14 21:55:48 +03:00
John Preston
34c36d77c3 Paint reaction animations above everything. 2022-01-14 21:55:47 +03:00
John Preston
0ab26f0c82 Initial reaction effects implementation. 2022-01-14 21:55:47 +03:00
John Preston
db453ab7ae Allow slowing down Animations::Simple in debug build. 2022-01-14 21:55:47 +03:00
John Preston
e032dbf383 Cache reaction lottie in Window::SessionController. 2022-01-14 21:55:47 +03:00
John Preston
3b4ed03105 Image coords should be multiply devicePixelRatio.
I hope this fixes #17277.
2022-01-14 21:55:47 +03:00
John Preston
963694330d Correctly apply reaction restrictions. 2022-01-14 21:55:47 +03:00
John Preston
2733b12cff Improve popup menu dimensions. 2022-01-14 21:55:47 +03:00
John Preston
a377364621 Use correct sizes for lottie frames. 2022-01-14 21:55:47 +03:00
John Preston
58f4884deb Optimize dropdown overlay painting. 2022-01-14 21:55:47 +03:00
John Preston
c2c7a25487 Keep reaction media in memory. 2022-01-14 21:55:47 +03:00
John Preston
f98c08f4c6 Improve reaction scale animation. 2022-01-14 21:55:47 +03:00
John Preston
cfc2a959cf Implement nice dropdown collapse animation. 2022-01-14 21:55:47 +03:00
John Preston
6a1630a84c Paint gradients inside dropdown. 2022-01-14 21:55:47 +03:00
John Preston
a51be85199 Improve button and dropdown layout. 2022-01-14 21:55:47 +03:00
John Preston
e0fd5d8795 Fix reactions dropdown on Retina screen. 2022-01-14 21:55:47 +03:00
John Preston
8659f60b46 Good dropdown rounding and shadow. 2022-01-14 21:55:47 +03:00
John Preston
c43699fb43 Prepare for better clipping. 2022-01-14 21:55:47 +03:00
John Preston
c56a22c8d5 Play select animations in reactions. 2022-01-14 21:55:47 +03:00
John Preston
7f27ce6dee Scale reactions on mouse over. 2022-01-14 21:55:47 +03:00
John Preston
508ba4750c Show appear animations in reactions dropdown. 2022-01-14 21:55:47 +03:00
John Preston
c0b19000d6 Use lottie instead of webp in reactions dropdown. 2022-01-14 21:55:47 +03:00
John Preston
409a3357da Use lottie instead of webp in bottom info reactions. 2022-01-14 21:55:47 +03:00
John Preston
82523978c9 Use lottie instead of webp in Edit Chat Reactions. 2022-01-14 21:55:47 +03:00
John Preston
718ba2d0e3 Update API scheme to layer 137. 2022-01-14 21:55:47 +03:00
John Preston
2317dd8820 Update cmake_helpers submodule. 2022-01-14 21:55:47 +03:00
Hans Gaiser
df06f55c7f Use QT_QPA_PLATFORM if provided. 2022-01-14 21:58:43 +04:00
John Preston
d43853460e Update plasma-wayland-protocols to 1.6.0. 2022-01-13 19:37:22 +03:00
John Preston
28fee318d7 Update submodules. 2022-01-13 13:17:55 +03:00
23rd
1ec2ecac11 Fixed render of scene from photo editor for grayscaled images.
Fixed #23889.
2022-01-13 04:20:15 +03:00
23rd
7aa3956792 Fixed skip blocks for web pages and games. 2022-01-13 04:06:50 +03:00
Ilya Fedin
d349759618 Update cmake_helpers 2022-01-12 20:34:51 +03:00
Ilya Fedin
ac3e4fb42f Add missing openssl dependency for non-legacy tgcalls 2022-01-12 20:34:51 +03:00
Ilya Fedin
eccb01e5b5 Use ninja generator in snap 2022-01-12 20:34:51 +03:00
Ilya Fedin
7c8d10022f Use return to decrease indentation in cmake files 2022-01-12 20:34:51 +03:00
John Preston
f1244e19a1 Fix build for Windows. 2022-01-12 13:07:00 +03:00
John Preston
e17143dd8b Update lib_ui submodule. 2022-01-12 12:24:20 +03:00
23rd
39d5d3a1cf Moved some photo editor files to td_ui. 2022-01-12 11:54:25 +03:00
23rd
f8be5731a5 Moved out extracting of attached stickers from Scene to FileLoadTask. 2022-01-12 11:54:25 +03:00
23rd
ab248febcd Added ability to select text of question from polls.
Fixed #8713.
Fixed #17531.
2022-01-12 11:54:25 +03:00
23rd
d4afba3a24 Added ability to copy monospace text via click. 2022-01-12 11:54:25 +03:00
23rd
4ee9751feb Added ability to cancel selection in calendar box with Esc key. 2022-01-12 11:54:24 +03:00
23rd
46fb5ee1d2 Added Page Up / Down keys to calendar box. 2022-01-12 11:54:24 +03:00
23rd
749f837df5 Fixed Home and End keys in calendar box. 2022-01-12 11:54:24 +03:00
23rd
e11904e05b Removed TextParseRichText. 2022-01-12 11:54:24 +03:00
23rd
e1aa08b985 Removed text commands. 2022-01-12 11:54:24 +03:00
23rd
2af3770b29 Moved special text command for lang tags to td_lang. 2022-01-12 11:54:24 +03:00
23rd
74f9d0935b Removed text commands from skip blocks. 2022-01-12 11:54:24 +03:00
23rd
f9c50fdc06 Removed text commands from theme preview. 2022-01-12 11:54:24 +03:00
23rd
1fa825321d Removed text commands from poll box. 2022-01-12 11:54:24 +03:00
23rd
d9147562e5 Removed text commands from profile cover. 2022-01-12 11:54:24 +03:00
23rd
5b569718ec Removed text commands from main menu. 2022-01-12 11:54:24 +03:00
23rd
a5d4746202 Removed text commands from overview layout for voices. 2022-01-12 11:54:24 +03:00
23rd
50d150302d Removed text commands from web pages. 2022-01-12 11:54:24 +03:00
23rd
e451eb5126 Removed text commands from dialogs list. 2022-01-12 11:54:24 +03:00
23rd
a626364430 Removed text commands from connection box. 2022-01-12 11:54:24 +03:00
23rd
b55ed7214a Removed text commands from history item components. 2022-01-12 11:54:24 +03:00
23rd
d6801517bb Removed text commands from url auth box. 2022-01-12 11:54:24 +03:00
23rd
97dde7eb56 Removed text commands from Export::View::TopBar. 2022-01-12 11:54:24 +03:00
23rd
10df3dce7c Removed text commands from dialogs row. 2022-01-12 11:54:24 +03:00
23rd
889d7c0c15 Added undo and redo shortcuts to photo editor. 2022-01-12 11:54:24 +03:00
Ilya Fedin
799155279f Update kwayland and move its dependencies to ThirdParty to keep them in sync 2022-01-11 16:51:01 +03:00
Vitaly Zaitsev
10e7bd0d6e Updated XDG SPEC version to 1.5.
SingleMainWindow is a part of XDG SPEC version 1.5 and bogus on 1.0.

Co-authored-by: gasinvein <gasinvein@gmail.com>
Signed-off-by: Vitaly Zaitsev <vitaly@easycoding.org>
2022-01-11 10:37:44 +03:00
TheEvilSkeleton
0a99487091 Re-add X-GNOME-SingleWindow
`X-GNOME-SingleWindow` is best to be used for backward compatibility. Since `SingleMainWindow` was recently added, many Desktop Environments and Window Managers may not have it implemented yet.

This MR also moves `SingleMainWindow` before the `X-GNOME-*` keys, as requested in this comment: https://github.com/telegramdesktop/tdesktop/pull/23875#issuecomment-1008453759
2022-01-10 09:34:49 +03:00
Felipe Kinoshita
4965c19314 Change X-GNOME-SingleWindow key to SingleMainWindow
X-GNOME-SingleWindow was upstreamed to be an XDG spec with the name
"SingleMainWindow" in
https://gitlab.freedesktop.org/xdg/xdg-specs/-/merge_requests/53
2022-01-09 09:50:50 +03:00
Ilya Fedin
30810e95f4 Log when DE is unknown 2022-01-08 10:59:47 +03:00
Ilya Fedin
a3d84f69ea fixup! Use more sources for DE detection 2022-01-08 10:45:13 +03:00
Ilya Fedin
b3bb1a537c Use more sources for DE detection 2022-01-08 09:55:34 +03:00
Ilya Fedin
726aa3316d Rework DE detection
Variables can point to a mixed environment, make DE detection non-exclusive.
Remove unused methods.
2022-01-07 19:14:59 +03:00
John Preston
ba6c3eaf73 Add dummy Platform::Integration on Linux. 2022-01-06 15:44:02 +03:00
John Preston
ebe45f3fa1 Fix selecting in expanded down dropdown. 2022-01-06 15:42:32 +03:00
John Preston
3f0fed19d8 Fix build for macOS. 2022-01-06 15:41:17 +03:00
John Preston
609cab6e2f Fix build with Xcode. 2022-01-06 15:41:12 +03:00
John Preston
7b3cb0c3dd Allow non-colored and .tgs Lottie::Icon-s. 2022-01-05 15:14:39 +03:00
John Preston
43559fb6b7 Fix crash in history switching. 2022-01-05 15:14:39 +03:00
John Preston
8788692fb3 Fix crash in sending a reaction. 2022-01-05 15:14:39 +03:00
Ilya Fedin
072e346324 Move libdl/libpthread handling to common_options 2022-01-05 13:18:08 +03:00
23rd
99f65ab5ec Migrated dependencies in Github CI for Windows. 2022-01-05 10:48:06 +03:00
John Preston
fe7b120003 Fix possible crash in dictionaries download.
Fixes #17258.
2022-01-05 01:04:38 +03:00
John Preston
cb8f86bc8d Attach main views to correct HistoryInner-s. 2022-01-05 00:11:29 +03:00
John Preston
18e6e2da9e Open specific chat only in one window. 2022-01-04 19:36:33 +03:00
John Preston
54247cd11b Create dialogs widget only in the primary window. 2022-01-04 16:44:53 +03:00
John Preston
8b0725650d Move global event filter to Platform::Integration on Windows. 2022-01-04 15:29:40 +03:00
John Preston
20411be9bd Allow creating separate windows for peers. 2022-01-04 14:18:13 +03:00
github-actions[bot]
f4f36d85b9 Update copyright year to 2022. 2022-01-04 12:49:50 +04:00
John Preston
9f887237eb Remove a couple of unused lang keys. 2022-01-04 11:15:09 +03:00
GitHub Action
4e3f917a2c Update User-Agent for DNS to Chrome 96.0.4664.110. 2022-01-04 00:12:00 +03:00
John Preston
5c9c836857 Version 3.4.3: Fix build with GCC. 2022-01-04 00:01:56 +03:00
John Preston
31b7fe6ba0 Version 3.4.3.
- Bug fixes and other minor improvements.
2022-01-03 20:04:53 +03:00
John Preston
102c0a96ed Re-enable XWayland by default on GNOME.
Should fix #17457, fix #17468, fix #17476, fix #17477, fix #1747,
fix #17481, fix #17498.
2022-01-03 20:03:06 +03:00
John Preston
9a0be43ef5 Align reactions outside of the bubble. 2022-01-03 20:00:18 +03:00
John Preston
c1d948ef63 Reshuffle chat menus. 2022-01-03 18:16:19 +03:00
John Preston
9df229a230 Add pinned message icon.
Regression was introduced in 1af2cfe143.

Fixes #17489.
2022-01-03 18:15:36 +03:00
John Preston
a1c342c822 Leave only one style of the reaction button. 2022-01-03 15:30:17 +03:00
John Preston
c313cfb4ec Don't show empty context menu. 2022-01-03 14:47:05 +03:00
John Preston
8d4a658d0b Use mirrors for freedesktop.org repositories. 2022-01-03 14:14:26 +03:00
John Preston
86f53d3eff Fix crash after comments button destruction.
Regression was introduced in df66162bca.
2022-01-03 11:40:42 +03:00
John Preston
3cb89339c8 Version 3.4.2: Fix build for Mac App Store. 2021-12-31 23:40:36 +03:00
John Preston
ba98a8df32 Version 3.4.2: Fix build with GCC. 2021-12-31 23:32:53 +03:00
John Preston
f3faa52bc7 Version 3.4.2.
- Bug fixes and other minor improvements.
2021-12-31 18:03:25 +03:00
John Preston
3dac08d34f Move reaction button from fast share button. 2021-12-31 17:52:41 +03:00
John Preston
deba090cbd Allow smaller popup menus. 2021-12-31 17:52:17 +03:00
John Preston
5b01f9530b Fix reaction images loading. 2021-12-31 17:49:52 +03:00
John Preston
df66162bca Destroy comments button when switched off. 2021-12-31 17:31:03 +03:00
John Preston
d413080f83 Rebuild sets without restarting thumbnails. 2021-12-31 17:08:34 +03:00
John Preston
38ee57f852 Don't jump to top in StickersBox on stickersUpdated. 2021-12-31 16:50:43 +03:00
John Preston
c632316ad7 Fix updated sticker set thumbnail loading. 2021-12-31 16:40:01 +03:00
John Preston
bba7010e74 Show "View Message" button in sponsored. 2021-12-31 16:07:36 +03:00
John Preston
edf93b0031 Use different color for sponsored sender name. 2021-12-31 16:07:36 +03:00
John Preston
611be90880 Rewrite sponsored to use fake sender names. 2021-12-31 16:07:36 +03:00
John Preston
68886e1b61 Fix channel post views with replies counters. 2021-12-31 14:48:56 +03:00
John Preston
67319c1612 Version 3.4.1: Fix build with GCC. 2021-12-31 12:33:23 +03:00
John Preston
da8db0157f Version 3.4.1: Fix build without DBus. 2021-12-31 10:37:54 +03:00
John Preston
6188268afd Version 3.4.1.
- Bug fixes and other minor improvements.
2021-12-31 02:42:44 +03:00
John Preston
cd0db53bac For non-bubble messages reaction to the left of info. 2021-12-31 02:40:03 +03:00
John Preston
5bb90679a8 Attempt to fix a weird assertion violation. 2021-12-31 01:20:28 +03:00
John Preston
72df3a8f91 Don't show reaction button while selecting text. 2021-12-31 01:03:45 +03:00
John Preston
5fe2e649fb Attempt to fix a crash in reactions list view. 2021-12-31 00:59:29 +03:00
John Preston
9eba8ccc73 Always show reaction emoji in reactions view box. 2021-12-31 00:58:59 +03:00
John Preston
bb3c91aa44 Scale reaction images explicitly.
Fixes #17459.
2021-12-31 00:28:44 +03:00
John Preston
9f1268b6c8 Move kwayland-qt6 patch to kwayland build rule folder.
Fixes #17460.
2021-12-31 00:25:31 +03:00
John Preston
a6f1a1bd62 Fix bottom info with author signature.
Fixes #17464.
2021-12-30 23:57:12 +03:00
John Preston
1b2642b017 Fix spoilers with disabled animations.
Fixes #17458.
2021-12-30 23:38:28 +03:00
John Preston
e722645e7c Try to show the reaction button outside of the bubble. 2021-12-30 23:38:06 +03:00
John Preston
9486c266b5 Use context menu background for sticker reaction dropdown. 2021-12-30 23:36:52 +03:00
John Preston
dc21491099 Fix reactions icon in Manage Group / Channel. 2021-12-30 18:24:12 +03:00
224 changed files with 7537 additions and 3829 deletions

View File

@@ -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: |

View File

@@ -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
View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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";
@@ -440,7 +457,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_phone_label" = "Phone number";
"lng_settings_username_add" = "Add username";
"lng_settings_close_sure" = "Are you sure you want to close this page? You didn't save your changes.";
//"lng_settings_peer_to_peer" = "Peer-to-Peer";
"lng_settings_peer_to_peer_about" = "Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but may slightly decrease audio quality.";
"lng_settings_advanced" = "Advanced";
"lng_settings_stickers_emoji" = "Stickers and emoji";
@@ -917,8 +933,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_files_header" = "Files";
"lng_profile_audios#one" = "{count} voice message";
"lng_profile_audios#other" = "{count} voice messages";
//"lng_profile_rounds#one" = "{count} video message";
//"lng_profile_rounds#other" = "{count} video messages";
"lng_profile_audios_header" = "Voice messages";
"lng_profile_shared_links#one" = "{count} shared link";
"lng_profile_shared_links#other" = "{count} shared links";
@@ -978,8 +992,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_media_selected_file#other" = "{count} Files";
"lng_media_selected_audio#one" = "{count} Voice message";
"lng_media_selected_audio#other" = "{count} Voice messages";
//"lng_media_selected_round#one" = "{count} Video message";
//"lng_media_selected_round#other" = "{count} Video messages";
"lng_media_selected_link#one" = "{count} Shared link";
"lng_media_selected_link#other" = "{count} Shared links";
"lng_media_photo_empty" = "No photos here yet";
@@ -1259,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";
@@ -1729,7 +1739,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_send_message" = "Send message";
"lng_context_view_group" = "View group info";
"lng_context_view_channel" = "View channel info";
//"lng_context_view_feed_info" = "View feed info";
"lng_context_hide_psa" = "Hide this announcement";
"lng_context_pin_to_top" = "Pin to top";
"lng_context_unpin_from_top" = "Unpin from top";
@@ -1745,6 +1754,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_archive_to_list" = "Move to chats list";
"lng_context_archive_to_menu_info" = "Archive moved to the main menu!\nYou can return it from the context menu of the archive button.";
"lng_context_mute" = "Mute notifications";
"lng_context_unmute" = "Unmute";
"lng_context_promote_admin" = "Promote to admin";
"lng_context_edit_permissions" = "Edit permissions";
"lng_context_restrict_user" = "Restrict user";
@@ -1807,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}";
@@ -1829,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";
@@ -1977,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";

View File

@@ -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

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="3.4.0.0" />
Version="3.4.5.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,4,0,0
PRODUCTVERSION 3,4,0,0
FILEVERSION 3,4,5,0
PRODUCTVERSION 3,4,5,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "3.4.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "FileVersion", "3.4.5.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.4.0.0"
VALUE "ProductVersion", "3.4.5.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,4,0,0
PRODUCTVERSION 3,4,0,0
FILEVERSION 3,4,5,0
PRODUCTVERSION 3,4,5,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "3.4.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "FileVersion", "3.4.5.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.4.0.0"
VALUE "ProductVersion", "3.4.5.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -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

View File

@@ -31,34 +31,58 @@ namespace {
constexpr auto kContextReactionsLimit = 50;
struct Peers {
std::vector<PeerId> list;
bool unknown = false;
};
inline bool operator==(const Peers &a, const Peers &b) noexcept {
return (a.list == b.list) && (a.unknown == b.unknown);
}
struct PeerWithReaction {
PeerId peer = 0;
QString reaction;
};
bool operator==(const PeerWithReaction &a, const PeerWithReaction &b) {
inline bool operator==(
const PeerWithReaction &a,
const PeerWithReaction &b) noexcept {
return (a.peer == b.peer) && (a.reaction == b.reaction);
}
struct PeersWithReactions {
std::vector<PeerWithReaction> list;
int fullReactionsCount = 0;
bool unknown = false;
};
inline bool operator==(
const PeersWithReactions &a,
const PeersWithReactions &b) noexcept {
return (a.fullReactionsCount == b.fullReactionsCount)
&& (a.list == b.list)
&& (a.unknown == b.unknown);
}
struct CachedRead {
explicit CachedRead(PeerId unknownFlag)
: list(std::vector<PeerId>{ unknownFlag }) {
CachedRead()
: data(Peers{ .unknown = true }) {
}
rpl::variable<std::vector<PeerId>> list;
rpl::variable<Peers> data;
mtpRequestId requestId = 0;
};
struct CachedReacted {
explicit CachedReacted(PeerId unknownFlag)
: list(
std::vector<PeerWithReaction>{ PeerWithReaction{ unknownFlag } }) {
CachedReacted()
: data(PeersWithReactions{ .unknown = true }) {
}
rpl::variable<std::vector<PeerWithReaction>> list;
rpl::variable<PeersWithReactions> data;
mtpRequestId requestId = 0;
};
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) {
@@ -66,21 +90,18 @@ struct Context {
if (i != end(cachedRead)) {
return i->second;
}
return cachedRead.emplace(
item,
CachedRead(item->history()->session().userPeerId())
).first->second;
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(item->history()->session().userPeerId())
).first->second;
return map.emplace(reaction, CachedReacted()).first->second;
}
};
@@ -124,9 +145,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);
@@ -134,7 +157,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;
@@ -149,7 +174,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]);
@@ -163,21 +190,6 @@ struct State {
return result;
}
[[nodiscard]] bool ListUnknown(
const std::vector<PeerId> &list,
not_null<HistoryItem*> item) {
return (list.size() == 1)
&& (list.front() == item->history()->session().userPeerId());
}
[[nodiscard]] bool ListUnknown(
const std::vector<PeerWithReaction> &list,
not_null<HistoryItem*> item) {
return (list.size() == 1)
&& list.front().reaction.isEmpty()
&& (list.front().peer == item->history()->session().userPeerId());
}
[[nodiscard]] Ui::WhoReadType DetectSeenType(not_null<HistoryItem*> item) {
if (const auto media = item->media()) {
if (!media->webpage()) {
@@ -193,7 +205,7 @@ struct State {
return Ui::WhoReadType::Seen;
}
[[nodiscard]] rpl::producer<std::vector<PeerId>> WhoReadIds(
[[nodiscard]] rpl::producer<Peers> WhoReadIds(
not_null<HistoryItem*> item,
not_null<QWidget*> context) {
auto weak = QPointer<QWidget>(context.get());
@@ -213,33 +225,37 @@ struct State {
).done([=](const MTPVector<MTPlong> &result) {
auto &entry = context->cacheRead(item);
entry.requestId = 0;
auto peers = std::vector<PeerId>();
peers.reserve(std::max(int(result.v.size()), 1));
auto parsed = Peers();
parsed.list.reserve(result.v.size());
for (const auto &id : result.v) {
peers.push_back(UserId(id));
parsed.list.push_back(UserId(id));
}
entry.list = std::move(peers);
entry.data = std::move(parsed);
}).fail([=] {
auto &entry = context->cacheRead(item);
entry.requestId = 0;
if (ListUnknown(entry.list.current(), item)) {
entry.list = std::vector<PeerId>();
if (entry.data.current().unknown) {
entry.data = Peers();
}
}).send();
}
return entry.list.value().start_existing(consumer);
return entry.data.value().start_existing(consumer);
};
}
[[nodiscard]] std::vector < PeerWithReaction> WithEmptyReactions(
const std::vector<PeerId> &peers) {
return peers | ranges::views::transform([](PeerId peer) {
return PeerWithReaction{ .peer = peer };
}) | ranges::to_vector;
[[nodiscard]] PeersWithReactions WithEmptyReactions(
const Peers &peers) {
return PeersWithReactions{
.list = peers.list | ranges::views::transform([](PeerId peer) {
return PeerWithReaction{.peer = peer };
}) | ranges::to_vector,
.unknown = peers.unknown,
};
}
[[nodiscard]] rpl::producer<std::vector<PeerWithReaction>> WhoReactedIds(
[[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();
@@ -248,65 +264,69 @@ 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([&](
const MTPDmessages_messageReactionsList &data) {
session->data().processUsers(data.vusers());
auto peers = std::vector<PeerWithReaction>();
peers.reserve(data.vreactions().v.size());
auto parsed = PeersWithReactions{
.fullReactionsCount = data.vcount().v,
};
parsed.list.reserve(data.vreactions().v.size());
for (const auto &vote : data.vreactions().v) {
vote.match([&](const auto &data) {
peers.push_back(PeerWithReaction{
parsed.list.push_back(PeerWithReaction{
.peer = peerFromUser(data.vuser_id()),
.reaction = qs(data.vreaction()),
});
});
}
entry.list = std::move(peers);
entry.data = std::move(parsed);
});
}).fail([=] {
auto &entry = context->cacheReacted(item);
auto &entry = context->cacheReacted(item, reaction);
entry.requestId = 0;
if (ListUnknown(entry.list.current(), item)) {
entry.list = std::vector<PeerWithReaction>();
if (entry.data.current().unknown) {
entry.data = PeersWithReactions();
}
}).send();
}
return entry.list.value().start_existing(consumer);
return entry.data.value().start_existing(consumer);
};
}
[[nodiscard]] auto WhoReadOrReactedIds(
not_null<HistoryItem*> item,
not_null<QWidget*> context)
-> rpl::producer<std::vector<PeerWithReaction>> {
-> rpl::producer<PeersWithReactions> {
return rpl::combine(
WhoReactedIds(item, context),
WhoReactedIds(item, QString(), context),
WhoReadIds(item, context)
) | rpl::map([=](
std::vector<PeerWithReaction> reacted,
std::vector<PeerId> read) {
if (ListUnknown(reacted, item) || ListUnknown(read, item)) {
return reacted;
) | rpl::map([=](PeersWithReactions reacted, Peers read) {
if (reacted.unknown || read.unknown) {
return PeersWithReactions{ .unknown = true };
}
for (const auto &peer : read) {
if (!ranges::contains(reacted, peer, &PeerWithReaction::peer)) {
reacted.push_back({ .peer = peer });
auto &list = reacted.list;
for (const auto &peer : read.list) {
if (!ranges::contains(list, peer, &PeerWithReaction::peer)) {
list.push_back({ .peer = peer });
}
}
return reacted;
@@ -463,55 +483,72 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
not_null<QWidget*> context,
const style::WhoRead &st) {
return WhoReacted(item, QString(), context, st);
}
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 resolveWhoRead = reaction.isEmpty() && WhoReadExists(item);
const auto state = lifetime.make_state<State>();
const auto pushNext = [=] {
consumer.put_next_copy(state->current);
};
const auto resolveWhoReacted = item->canViewReactions();
const auto resolveWhoReacted = !reaction.isEmpty()
|| item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
: resolveWhoRead
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
: WhoReactedIds(item, context);
: WhoReactedIds(item, reaction, 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; });
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 = (list.size() == 1)
state->current.singleReaction = !reaction.isEmpty()
? reaction
: (list.size() == 1)
? list.front().first
: QString();
}
std::move(
idsWithReactions
) | rpl::start_with_next([=](
const std::vector<PeerWithReaction> &peers) {
if (ListUnknown(peers, item)) {
) | rpl::start_with_next([=](const PeersWithReactions &peers) {
if (peers.unknown) {
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.fullReactionsCount = state->current.fullReactionsCount,
.unknown = true,
});
return;
} else if (UpdateUserpics(state, item, peers)) {
}
state->current.fullReactionsCount = peers.fullReactionsCount;
if (UpdateUserpics(state, item, peers.list)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.empty()) {
} else if (peers.list.empty()) {
pushNext();
}
}, lifetime);

View File

@@ -27,5 +27,10 @@ namespace Api {
not_null<HistoryItem*> item,
not_null<QWidget*> context,
const style::WhoRead &st); // Cache results for this lifetime.
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context,
const style::WhoRead &st); // Cache results for this lifetime.
} // namespace Api

View File

@@ -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();

View File

@@ -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));
}
}

View File

@@ -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(

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}));
});

View File

@@ -28,8 +28,7 @@ namespace {
TextParseOptions kInformBoxTextOptions = {
(TextParseLinks
| TextParseMultiline
| TextParseMarkdown
| TextParseRichText), // flags
| TextParseMarkdown), // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir

View File

@@ -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);

View File

@@ -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,37 +47,40 @@ 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);
}
@@ -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.");
}

View File

@@ -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(

View File

@@ -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());
}

View File

@@ -247,6 +247,7 @@ private:
int32 _rowHeight;
std::vector<std::unique_ptr<Row>> _rows;
std::vector<std::unique_ptr<Row>> _oldRows;
std::vector<crl::time> _shiftingStartTimes;
crl::time _aboveShadowFadeStart = 0;
anim::value _aboveShadowFadeOpacity;
@@ -547,7 +548,9 @@ void StickersBox::prepare() {
}
setNoContentMargin(true);
_tabs->sectionActivated(
) | rpl::start_with_next(
) | rpl::filter([=] {
return !_ignoreTabActivation;
}) | rpl::start_with_next(
[this] { switchTab(); },
lifetime());
refreshTabs();
@@ -665,12 +668,16 @@ void StickersBox::refreshTabs() {
|| (_tab == &_featured && !_tabIndices.contains(Section::Featured))
|| (_tab == &_masks && !_tabIndices.contains(Section::Masks))) {
switchTab();
} else if (_tab == &_archived) {
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Archived));
} else if (_tab == &_featured) {
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Featured));
} else if (_tab == &_masks) {
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Masks));
} else {
_ignoreTabActivation = true;
_tabs->setActiveSectionFast(_tabIndices.indexOf((_tab == &_archived)
? Section::Archived
: (_tab == &_featured)
? Section::Featured
: (_tab == &_masks)
? Section::Masks
: Section::Installed));
_ignoreTabActivation = false;
}
updateTabsGeometry();
}
@@ -1987,6 +1994,7 @@ void StickersBox::Inner::rebuild(bool masks) {
auto maxNameWidth = countMaxNameWidth();
_oldRows = std::move(_rows);
clear();
const auto &order = ([&]() -> const StickersSetsOrder & {
if (_section == Section::Installed) {
@@ -2038,6 +2046,7 @@ void StickersBox::Inner::rebuild(bool masks) {
set->accessHash);
}
}
_oldRows.clear();
session().api().requestStickerSets();
updateSize();
}
@@ -2150,19 +2159,55 @@ void StickersBox::Inner::rebuildAppendSet(
QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
int count = fillSetCount(set);
_rows.push_back(std::make_unique<Row>(
set,
sticker,
count,
title,
titleWidth,
installed,
official,
unread,
archived,
removed,
pixw,
pixh));
const auto existing = [&]{
const auto now = int(_rows.size());
const auto setProj = [](const std::unique_ptr<Row> &row) {
return row ? row->set.get() : nullptr;
};
if (_oldRows.size() > now
&& setProj(_oldRows[now]) == set.get()) {
return _oldRows.begin() + now;
}
return ranges::find(_oldRows, set.get(), setProj);
}();
if (existing != end(_oldRows)) {
const auto raw = existing->get();
raw->sticker = sticker;
raw->count = count;
raw->title = title;
raw->titleWidth = titleWidth;
raw->installed = installed;
raw->official = official;
raw->unread = unread;
raw->archived = archived;
raw->removed = removed;
raw->pixw = pixw;
raw->pixh = pixh;
raw->yadd = {};
auto oldStickerMedia = std::move(raw->stickerMedia);
auto oldThumbnailMedia = std::move(raw->thumbnailMedia);
raw->stickerMedia = sticker->activeMediaView();
raw->thumbnailMedia = set->activeThumbnailView();
if (raw->thumbnailMedia != oldThumbnailMedia
|| (!raw->thumbnailMedia && raw->stickerMedia != oldStickerMedia)) {
raw->lottie = nullptr;
}
_rows.push_back(std::move(*existing));
} else {
_rows.push_back(std::make_unique<Row>(
set,
sticker,
count,
title,
titleWidth,
installed,
official,
unread,
archived,
removed,
pixw,
pixh));
}
_shiftingStartTimes.push_back(0);
}

View File

@@ -143,6 +143,7 @@ private:
object_ptr<Ui::SettingsSlider> _tabs = { nullptr };
QList<Section> _tabIndices;
bool _ignoreTabActivation = false;
class CounterWidget;
object_ptr<CounterWidget> _unreadBadge = { nullptr };

View File

@@ -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(

View File

@@ -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);

View File

@@ -108,7 +108,7 @@ private:
};
TextParseOptions MenuTextOptions = {
TextParseLinks | TextParseRichText, // flags
TextParseLinks, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir

View File

@@ -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)) {

View File

@@ -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() {

View File

@@ -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;

View File

@@ -66,6 +66,12 @@ 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"
}
};
};

View File

@@ -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;
}

View File

@@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/basic_click_handlers.h"
constexpr auto kPeerLinkPeerIdProperty = 0x01;
constexpr auto kPhotoLinkMediaIdProperty = 0x02;
constexpr auto kDocumentLinkMediaIdProperty = 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;
};

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_launcher.h"
#include "platform/platform_specific.h"
#include "platform/linux/linux_desktop_environment.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/base_platform_file_utilities.h"
#include "ui/main_queue_processor.h"
@@ -59,7 +60,12 @@ FilteredCommandLineArguments::FilteredCommandLineArguments(
pushArgument("cocoa:fontengine=freetype");
#endif // !Q_OS_WIN
}
#endif // Q_OS_WIN || Q_OS_MAC
#elif defined Q_OS_UNIX
if (Platform::DesktopEnvironment::IsGnome() && qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) {
pushArgument("-platform");
pushArgument("xcb;wayland");
}
#endif // Q_OS_WIN || Q_OS_MAC || Q_OS_UNIX
pushArgument(nullptr);
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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 = 3004000;
constexpr auto AppVersionStr = "3.4";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 3004005;
constexpr auto AppVersionStr = "3.4.5";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -172,11 +172,25 @@ void UpdateCloudFile(
return;
}
const auto needStickerThumbnailUpdate = [&] {
const auto was = std::get_if<StorageFileLocation>(
&file.location.file().data);
const auto now = std::get_if<StorageFileLocation>(
&data.location.file().data);
using Type = StorageFileLocation::Type;
if (!was || !now || was->type() != Type::StickerSetThumb) {
return false;
}
return now->valid()
&& (now->type() != Type::StickerSetThumb
|| now->cacheKey() != was->cacheKey());
};
const auto update = !file.location.valid()
|| (data.location.file().cacheKey()
&& (!file.location.file().cacheKey()
|| (file.location.width() < data.location.width())
|| (file.location.height() < data.location.height())));
|| (file.location.height() < data.location.height())
|| needStickerThumbnailUpdate()));
if (!update) {
return;
}
@@ -204,6 +218,8 @@ void UpdateCloudFile(
} else if (file.loader) {
const auto origin = base::take(file.loader)->fileOrigin();
restartLoader(origin);
} else if (file.flags & CloudFile::Flag::Failed) {
file.flags &= ~CloudFile::Flag::Failed;
}
}

View File

@@ -1336,7 +1336,7 @@ bool DocumentData::isSongWithCover() const {
}
bool DocumentData::isAudioFile() const {
if (isVoiceMessage()) {
if (isVoiceMessage() || isVideoFile()) {
return false;
} else if (isSong()) {
return true;

View File

@@ -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(
kDocumentLinkMediaIdProperty,
QVariant(qulonglong(_document->id)));
}
DocumentOpenClickHandler::DocumentOpenClickHandler(
@@ -146,6 +150,7 @@ PhotoClickHandler::PhotoClickHandler(
: FileClickHandler(context)
, _photo(photo)
, _peer(peer) {
setProperty(kPhotoLinkMediaIdProperty, QVariant(qulonglong(_photo->id)));
}
not_null<PhotoData*> PhotoClickHandler::photo() const {

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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,16 +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) {
@@ -99,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;
@@ -108,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));
@@ -126,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([=] {
@@ -142,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() {
@@ -163,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;
}
@@ -174,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() {
@@ -213,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([=] {
@@ -241,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());
@@ -248,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,
@@ -255,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;
@@ -389,19 +479,32 @@ 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()) {
_recent[reaction].push_back(self);
}
++_list[reaction];
}
auto &owner = _item->history()->owner();
auto &owner = history->owner();
owner.reactions().send(_item, _chosen);
owner.notifyItemDataChange(_item);
}
@@ -412,8 +515,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;
}
@@ -450,8 +555,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);
}
}
@@ -459,6 +580,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();
}

View File

@@ -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;
};

View File

@@ -42,6 +42,8 @@ class CloudImageView;
int PeerColorIndex(PeerId peerId);
int PeerColorIndex(BareId bareId);
style::color PeerUserpicColor(PeerId peerId);
// Must be used only for PeerColor-s.
PeerId FakePeerIdForJustName(const QString &name);
class RestrictionCheckResult {

View File

@@ -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

View File

@@ -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

View File

@@ -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());
}

View File

@@ -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);

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_peer_id.h"
#include "data/data_session.h"
@@ -71,23 +72,10 @@ bool SponsoredMessages::append(not_null<History*> history) {
return false;
}
const auto flags = MessageFlags(0)
| (history->peer->isChannel() ? MessageFlag::Post : MessageFlags(0))
| MessageFlag::HasFromId
| MessageFlag::IsSponsored
| MessageFlag::Local;
auto local = history->addNewLocalMessage(
entryIt->item.reset(history->addNewLocalMessage(
_session->data().nextLocalMessageId(),
flags,
UserId(0),
MsgId(0),
HistoryItem::NewMessageDate(0),
entryIt->sponsored.fromId,
QString(),
entryIt->sponsored.textWithEntities,
MTP_messageMediaEmpty(),
HistoryMessageMarkupData());
entryIt->item.reset(std::move(local));
entryIt->sponsored.from,
entryIt->sponsored.textWithEntities));
return true;
}
@@ -163,36 +151,53 @@ void SponsoredMessages::append(
});
const auto randomId = data.vrandom_id().v;
const auto hash = qs(data.vchat_invite_hash().value_or_empty());
const auto fromId = [&] {
const auto makeFrom = [](
not_null<PeerData*> peer,
bool exactPost = false) {
const auto channel = peer->asChannel();
return SponsoredFrom{
.peer = peer,
.title = peer->name,
.isBroadcast = (channel && channel->isBroadcast()),
.isMegagroup = (channel && channel->isMegagroup()),
.isChannel = (channel != nullptr),
.isPublic = (channel && channel->isPublic()),
.isBot = (peer->isUser() && peer->asUser()->isBot()),
.isExactPost = exactPost,
};
};
const auto from = [&]() -> SponsoredFrom {
if (data.vfrom_id()) {
return peerFromMTP(*data.vfrom_id());
return makeFrom(
_session->data().peer(peerFromMTP(*data.vfrom_id())),
(data.vchannel_post() != nullptr));
}
Assert(data.vchat_invite());
return data.vchat_invite()->match([](const MTPDchatInvite &data) {
return Data::FakePeerIdForJustName(qs(data.vtitle()));
return SponsoredFrom{
.title = qs(data.vtitle()),
.isBroadcast = data.is_broadcast(),
.isMegagroup = data.is_megagroup(),
.isChannel = data.is_channel(),
.isPublic = data.is_public(),
};
}, [&](const MTPDchatInviteAlready &data) {
const auto chat = _session->data().processChat(data.vchat());
if (!chat) {
return PeerId(0);
}
if (const auto channel = chat->asChannel()) {
channel->clearInvitePeek();
}
return chat->id;
return makeFrom(chat);
}, [&](const MTPDchatInvitePeek &data) {
const auto chat = _session->data().processChat(data.vchat());
if (!chat) {
return PeerId(0);
}
if (const auto channel = chat->asChannel()) {
channel->setInvitePeek(hash, data.vexpires().v);
}
return chat->id;
return makeFrom(chat);
});
}();
auto sharedMessage = SponsoredMessage{
.randomId = randomId,
.fromId = fromId,
.from = from,
.textWithEntities = {
.text = qs(data.vmessage()),
.entities = Api::EntitiesFromMTP(
@@ -263,17 +268,17 @@ void SponsoredMessages::view(const FullMsgId &fullId) {
}).send();
}
SponsoredMessages::ChannelPost SponsoredMessages::channelPost(
SponsoredMessages::Details SponsoredMessages::lookupDetails(
const FullMsgId &fullId) const {
const auto entryPtr = find(fullId);
if (!entryPtr) {
return { .msgId = ShowAtUnreadMsgId, .hash = std::nullopt };
return {};
}
const auto msgId = entryPtr->sponsored.msgId;
const auto hash = entryPtr->sponsored.chatInviteHash;
const auto &hash = entryPtr->sponsored.chatInviteHash;
return {
.msgId = msgId ? msgId : ShowAtUnreadMsgId,
.hash = hash.isEmpty() ? std::nullopt : std::make_optional(hash),
.peer = entryPtr->sponsored.from.peer,
.msgId = entryPtr->sponsored.msgId,
};
}

View File

@@ -20,9 +20,20 @@ namespace Data {
class Session;
struct SponsoredMessage final {
struct SponsoredFrom {
PeerData *peer = nullptr;
QString title;
bool isBroadcast = false;
bool isMegagroup = false;
bool isChannel = false;
bool isPublic = false;
bool isBot = false;
bool isExactPost = false;
};
struct SponsoredMessage {
QByteArray randomId;
PeerId fromId;
SponsoredFrom from;
TextWithEntities textWithEntities;
History *history = nullptr;
MsgId msgId;
@@ -31,9 +42,10 @@ struct SponsoredMessage final {
class SponsoredMessages final {
public:
struct ChannelPost {
MsgId msgId;
struct Details {
std::optional<QString> hash;
PeerData *peer = nullptr;
MsgId msgId;
};
using RandomId = QByteArray;
explicit SponsoredMessages(not_null<Session*> owner);
@@ -45,7 +57,7 @@ public:
void request(not_null<History*> history);
[[nodiscard]] bool append(not_null<History*> history);
void clearItems(not_null<History*> history);
[[nodiscard]] ChannelPost channelPost(const FullMsgId &fullId) const;
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
void view(const FullMsgId &fullId);

View File

@@ -280,9 +280,6 @@ enum class MessageFlag : uint32 {
// Contact sign-up message, notification should be skipped for Silent.
IsContactSignUp = (1U << 30),
// In channels.
IsSponsored = (1U << 31),
};
inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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();

View File

@@ -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::CustomUrl, 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,7 +290,7 @@ void Row::validateListEntryCache() const {
return;
}
_listEntryCacheVersion = version;
_listEntryCache.setText(
_listEntryCache.setMarkedText(
st::dialogsTextStyle,
ComposeFolderListEntryText(folder),
Ui::DialogTextOptions());
@@ -288,4 +301,4 @@ FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
, _item(item) {
}
} // namespace Dialogs
} // namespace Dialogs

View File

@@ -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);
}
@@ -593,6 +607,7 @@ void Widget::checkUpdateStatus() {
Core::checkReadyUpdate();
App::restart();
});
_connecting->raise();
} else {
if (!_updateTelegram) return;
_updateTelegram.destroy();
@@ -743,13 +758,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();
}
}
}
@@ -1802,7 +1817,7 @@ void Widget::onCancelSearchInChat() {
}
applyFilterUpdate(true);
if (!isOneColumn && !controller()->selectingPeer()) {
cancelled();
controller()->content()->dialogsCancelled();
}
}

View File

@@ -97,9 +97,6 @@ public:
~Widget();
Q_SIGNALS:
void cancelled();
public Q_SLOTS:
void onDraggingScrollDelta(int delta);

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -59,6 +59,6 @@ private:
[[nodiscard]] HistoryView::ItemPreview PreviewWithSender(
HistoryView::ItemPreview &&preview,
const QString &sender);
const TextWithEntities &sender);
} // namespace Dialogs::Ui

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>
#include <QStyleOptionGraphicsItem>
#include <QtMath>
namespace Editor {
namespace {

View File

@@ -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()),

View File

@@ -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 {

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/view/export_view_top_bar.h"
#include "export/view/export_view_content.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
@@ -39,13 +40,12 @@ void TopBar::updateData(Content &&content) {
return;
}
const auto &row = content.rows[0];
_info->setRichText(textcmdStartSemibold()
+ TextUtilities::Clean(tr::lng_export_progress_title(tr::now))
+ textcmdStopSemibold()
+ QString::fromUtf8(" \xe2\x80\x93 ")
+ TextUtilities::Clean(row.label)
+ ' '
+ textcmdLink(1, TextUtilities::Clean(row.info)));
_info->setMarkedText(
Ui::Text::Bold(tr::lng_export_progress_title(tr::now))
.append(" \xe2\x80\x93 ")
.append(row.label)
.append(' ')
.append(Ui::Text::PlainLink(row.info)));
_progress->setValue(row.progress);
}

View File

@@ -515,7 +515,7 @@ void InnerWidget::updateEmptyText() {
? tr::lng_admin_log_no_results_search_text(
tr::now,
lt_query,
TextUtilities::Clean(_searchQuery))
_searchQuery)
: hasFilter
? tr::lng_admin_log_no_results_text(tr::now)
: _channel->isMegagroup()
@@ -1169,11 +1169,21 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
auto view = App::hoveredItem()
? App::hoveredItem()
: App::hoveredLinkItem();
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(link.get());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(link.get());
auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false;
auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false;
auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false;
const auto lnkPhotoId = PhotoId(link
? link->property(kPhotoLinkMediaIdProperty).toULongLong()
: 0);
const auto lnkDocumentId = DocumentId(link
? link->property(kDocumentLinkMediaIdProperty).toULongLong()
: 0);
const auto lnkPhoto = lnkPhotoId
? session().data().photo(lnkPhotoId).get()
: nullptr;
const auto lnkDocument = lnkDocumentId
? session().data().document(lnkDocumentId).get()
: nullptr;
auto lnkIsVideo = lnkDocument ? lnkDocument->isVideoFile() : false;
auto lnkIsVoice = lnkDocument ? lnkDocument->isVoiceMessage() : false;
auto lnkIsAudio = lnkDocument ? lnkDocument->isAudioFile() : false;
const auto fromId = PeerId(link
? link->property(kPeerLinkPeerIdProperty).toULongLong()
: 0);
@@ -1184,21 +1194,20 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}, &st::menuIconCopy);
}
if (lnkPhoto) {
const auto photo = lnkPhoto->photo();
const auto media = photo->activeMediaView();
if (!photo->isNull() && media && media->loaded()) {
const auto media = lnkPhoto->activeMediaView();
if (!lnkPhoto->isNull() && media && media->loaded()) {
_menu->addAction(tr::lng_context_save_image(tr::now), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] {
savePhotoToFile(photo);
savePhotoToFile(lnkPhoto);
}), &st::menuIconSaveImage);
_menu->addAction(tr::lng_context_copy_image(tr::now), [=] {
copyContextImage(photo);
copyContextImage(lnkPhoto);
}, &st::menuIconCopy);
}
if (photo->hasAttachedStickers()) {
if (lnkPhoto->hasAttachedStickers()) {
const auto controller = _controller;
auto callback = [=] {
auto &attached = session().api().attachedStickers();
attached.requestAttachedStickerSets(controller, photo);
attached.requestAttachedStickerSets(controller, lnkPhoto);
};
_menu->addAction(
tr::lng_context_attached_stickers(tr::now),
@@ -1206,22 +1215,21 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
&st::menuIconStickers);
}
} else {
auto document = lnkDocument->document();
if (document->loading()) {
if (lnkDocument->loading()) {
_menu->addAction(tr::lng_context_cancel_download(tr::now), [=] {
cancelContextDownload(document);
cancelContextDownload(lnkDocument);
}, &st::menuIconCancel);
} else {
const auto itemId = view
? view->data()->fullId()
: FullMsgId();
if (const auto item = document->session().data().message(itemId)) {
if (const auto item = session().data().message(itemId)) {
const auto notAutoplayedGif = [&] {
return document->isGifv()
return lnkDocument->isGifv()
&& !Data::AutoDownload::ShouldAutoPlay(
document->session().settings().autoDownload(),
session().settings().autoDownload(),
item->history()->peer,
document);
lnkDocument);
}();
if (notAutoplayedGif) {
_menu->addAction(tr::lng_context_open_gif(tr::now), [=] {
@@ -1229,19 +1237,19 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}, &st::menuIconShowInChat);
}
}
if (!document->filepath(true).isEmpty()) {
if (!lnkDocument->filepath(true).isEmpty()) {
_menu->addAction(Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) : tr::lng_context_show_in_folder(tr::now), [=] {
showContextInFolder(document);
showContextInFolder(lnkDocument);
}, &st::menuIconShowInFolder);
}
_menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] {
saveDocumentToFile(document);
_menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, lnkDocument] {
saveDocumentToFile(lnkDocument);
}), &st::menuIconDownload);
if (document->hasAttachedStickers()) {
if (lnkDocument->hasAttachedStickers()) {
const auto controller = _controller;
auto callback = [=, doc = document] {
auto callback = [=] {
auto &attached = session().api().attachedStickers();
attached.requestAttachedStickerSets(controller, doc);
attached.requestAttachedStickerSets(controller, lnkDocument);
};
_menu->addAction(
tr::lng_context_attached_stickers(tr::now),

View File

@@ -35,7 +35,7 @@ namespace {
TextWithEntities PrepareText(
const QString &value,
const QString &emptyValue) {
auto result = TextWithEntities { TextUtilities::Clean(value) };
auto result = TextWithEntities{ value };
if (result.text.isEmpty()) {
result.text = emptyValue;
if (!emptyValue.isEmpty()) {
@@ -143,7 +143,7 @@ TextWithEntities ExtractEditedText(
}
const auto &data = message.c_message();
return {
TextUtilities::Clean(qs(data.vmessage())),
qs(data.vmessage()),
Api::EntitiesFromMTP(session, data.ventities().value_or_empty())
};
}
@@ -319,11 +319,11 @@ QString GenerateInviteLinkText(const MTPExportedChatInvite &data) {
) : label;
}
QString GenerateInviteLinkLink(const MTPExportedChatInvite &data) {
TextWithEntities GenerateInviteLinkLink(const MTPExportedChatInvite &data) {
const auto text = GenerateInviteLinkText(data);
return text.endsWith(Ui::kQEllipsis)
? text
: textcmdLink(InternalInviteLinkUrl(data), text);
? TextWithEntities{ .text = text }
: Ui::Text::Link(text, InternalInviteLinkUrl(data));
}
TextWithEntities GenerateInviteLinkChangeText(
@@ -669,12 +669,12 @@ void GenerateItems(
const auto fromName = from->name;
const auto fromLink = from->createOpenLink();
const auto fromLinkText = textcmdLink(1, fromName);
const auto fromLinkText = Ui::Text::Link(fromName, {});
const auto addSimpleServiceMessage = [&](
const QString &text,
const TextWithEntities &text,
PhotoData *photo = nullptr) {
auto message = HistoryService::PreparedText { text };
auto message = HistoryService::PreparedText{ text };
message.links.push_back(fromLink);
addPart(history->makeServiceMessage(
history->nextNonHistoryEntryId(),
@@ -693,8 +693,9 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_title,
qs(action.vnew_value()));
addSimpleServiceMessage(text);
{ .text = qs(action.vnew_value()) },
Ui::Text::WithEntities);
addSimpleServiceMessage(std::move(text));
};
const auto makeSimpleTextMessage = [&](TextWithEntities &&text) {
@@ -731,7 +732,7 @@ void GenerateItems(
: (newValue.isEmpty()
? tr::lng_admin_log_removed_description_channel
: tr::lng_admin_log_changed_description_channel)
)(tr::now, lt_from, fromLinkText);
)(tr::now, lt_from, fromLinkText, Ui::Text::WithEntities);
addSimpleServiceMessage(text);
const auto body = makeSimpleTextMessage(
@@ -756,7 +757,7 @@ void GenerateItems(
: (newValue.isEmpty()
? tr::lng_admin_log_removed_link_channel
: tr::lng_admin_log_changed_link_channel)
)(tr::now, lt_from, fromLinkText);
)(tr::now, lt_from, fromLinkText, Ui::Text::WithEntities);
addSimpleServiceMessage(text);
const auto body = makeSimpleTextMessage(newValue.isEmpty()
@@ -784,7 +785,8 @@ void GenerateItems(
: tr::lng_admin_log_changed_photo_channel)(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text, photo);
}, [&](const MTPDphotoEmpty &data) {
const auto text = (channel->isMegagroup()
@@ -792,7 +794,8 @@ void GenerateItems(
: tr::lng_admin_log_removed_photo_channel)(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
});
};
@@ -801,16 +804,24 @@ void GenerateItems(
const auto enabled = (action.vnew_value().type() == mtpc_boolTrue);
const auto text = (enabled
? tr::lng_admin_log_invites_enabled
: tr::lng_admin_log_invites_disabled);
addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText));
: tr::lng_admin_log_invites_disabled)(
tr::now,
lt_from,
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
const auto createToggleSignatures = [&](const LogSign &action) {
const auto enabled = (action.vnew_value().type() == mtpc_boolTrue);
const auto text = (enabled
? tr::lng_admin_log_signatures_enabled
: tr::lng_admin_log_signatures_disabled);
addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText));
: tr::lng_admin_log_signatures_disabled)(
tr::now,
lt_from,
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
const auto createUpdatePinned = [&](const LogPin &action) {
@@ -821,7 +832,8 @@ void GenerateItems(
: tr::lng_admin_log_unpinned_message)(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
const auto detachExistingItem = false;
@@ -836,7 +848,8 @@ void GenerateItems(
const auto text = tr::lng_admin_log_unpinned_message(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
});
};
@@ -854,7 +867,8 @@ void GenerateItems(
: tr::lng_admin_log_edited_caption)(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
auto oldValue = ExtractEditedText(
@@ -885,7 +899,8 @@ void GenerateItems(
const auto text = tr::lng_admin_log_deleted_message(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
const auto detachExistingItem = false;
@@ -901,15 +916,23 @@ void GenerateItems(
const auto createParticipantJoin = [&]() {
const auto text = (channel->isMegagroup()
? tr::lng_admin_log_participant_joined
: tr::lng_admin_log_participant_joined_channel);
addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText));
: tr::lng_admin_log_participant_joined_channel)(
tr::now,
lt_from,
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
const auto createParticipantLeave = [&]() {
const auto text = (channel->isMegagroup()
? tr::lng_admin_log_participant_left
: tr::lng_admin_log_participant_left_channel);
addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText));
: tr::lng_admin_log_participant_left_channel)(
tr::now,
lt_from,
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
const auto createParticipantInvite = [&](const LogInvite &action) {
@@ -947,7 +970,8 @@ void GenerateItems(
const auto text = tr::lng_admin_log_removed_stickers_group(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
} else {
const auto text = tr::lng_admin_log_changed_stickers_group(
@@ -955,9 +979,10 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_sticker_set,
textcmdLink(
2,
tr::lng_admin_log_changed_stickers_set(tr::now)));
Ui::Text::Link(
tr::lng_admin_log_changed_stickers_set(tr::now),
{}),
Ui::Text::WithEntities);
const auto setLink = std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
@@ -986,8 +1011,12 @@ void GenerateItems(
const auto hidden = (action.vnew_value().type() == mtpc_boolTrue);
const auto text = (hidden
? tr::lng_admin_log_history_made_hidden
: tr::lng_admin_log_history_made_visible);
addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText));
: tr::lng_admin_log_history_made_visible)(
tr::now,
lt_from,
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
const auto createDefaultBannedRights = [&](
@@ -1003,7 +1032,8 @@ void GenerateItems(
const auto text = tr::lng_admin_log_stopped_poll(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
const auto detachExistingItem = false;
@@ -1025,7 +1055,8 @@ void GenerateItems(
: tr::lng_admin_log_removed_linked_channel)(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
} else {
const auto text = (broadcast
@@ -1035,7 +1066,8 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_chat,
textcmdLink(2, now->name));
Ui::Text::Link(now->name, {}),
Ui::Text::WithEntities);
const auto chatLink = std::make_shared<LambdaClickHandler>([=] {
Ui::showPeerHistory(now, ShowAtUnreadMsgId);
});
@@ -1056,24 +1088,26 @@ void GenerateItems(
const auto address = qs(data.vaddress());
const auto link = data.vgeo_point().match([&](
const MTPDgeoPoint &data) {
return textcmdLink(
LocationClickHandler::Url(Data::LocationPoint(data)),
address);
return Ui::Text::Link(
address,
LocationClickHandler::Url(Data::LocationPoint(data)));
}, [&](const MTPDgeoPointEmpty &) {
return address;
return TextWithEntities{ .text = address };
});
const auto text = tr::lng_admin_log_changed_location_chat(
tr::now,
lt_from,
fromLinkText,
lt_address,
link);
link,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
}, [&](const MTPDchannelLocationEmpty &) {
const auto text = tr::lng_admin_log_removed_location_chat(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
});
};
@@ -1094,13 +1128,15 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_duration,
duration);
{ .text = duration },
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
} else {
const auto text = tr::lng_admin_log_removed_slow_mode(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
}
};
@@ -1111,7 +1147,8 @@ void GenerateItems(
: tr::lng_admin_log_started_group_call)(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
@@ -1121,7 +1158,8 @@ void GenerateItems(
: tr::lng_admin_log_discarded_group_call)(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
@@ -1133,7 +1171,7 @@ void GenerateItems(
};
const auto addServiceMessageWithLink = [&](
const QString &text,
const TextWithEntities &text,
const ClickHandlerPtr &link) {
auto message = HistoryService::PreparedText{ text };
message.links.push_back(fromLink);
@@ -1150,9 +1188,9 @@ void GenerateItems(
const auto participantPeer = groupCallParticipantPeer(
data.vparticipant());
const auto participantPeerLink = participantPeer->createOpenLink();
const auto participantPeerLinkText = textcmdLink(
2,
participantPeer->name);
const auto participantPeerLinkText = Ui::Text::Link(
participantPeer->name,
{});
const auto text = (broadcast
? tr::lng_admin_log_muted_participant_channel
: tr::lng_admin_log_muted_participant)(
@@ -1160,7 +1198,8 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_user,
participantPeerLinkText);
participantPeerLinkText,
Ui::Text::WithEntities);
addServiceMessageWithLink(text, participantPeerLink);
};
@@ -1168,9 +1207,9 @@ void GenerateItems(
const auto participantPeer = groupCallParticipantPeer(
data.vparticipant());
const auto participantPeerLink = participantPeer->createOpenLink();
const auto participantPeerLinkText = textcmdLink(
2,
participantPeer->name);
const auto participantPeerLinkText = Ui::Text::Link(
participantPeer->name,
{});
const auto text = (broadcast
? tr::lng_admin_log_unmuted_participant_channel
: tr::lng_admin_log_unmuted_participant)(
@@ -1178,7 +1217,8 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_user,
participantPeerLinkText);
participantPeerLinkText,
Ui::Text::WithEntities);
addServiceMessageWithLink(text, participantPeerLink);
};
@@ -1193,12 +1233,13 @@ void GenerateItems(
: tr::lng_admin_log_allowed_unmute_self))(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
const auto addInviteLinkServiceMessage = [&](
const QString &text,
const TextWithEntities &text,
const MTPExportedChatInvite &data,
ClickHandlerPtr additional = nullptr) {
auto message = HistoryService::PreparedText{ text };
@@ -1230,7 +1271,8 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_link,
GenerateInviteLinkLink(data.vinvite())),
GenerateInviteLinkLink(data.vinvite()),
Ui::Text::WithEntities),
data.vinvite());
};
@@ -1241,7 +1283,8 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_link,
GenerateInviteLinkLink(data.vinvite())),
GenerateInviteLinkLink(data.vinvite()),
Ui::Text::WithEntities),
data.vinvite());
};
@@ -1252,7 +1295,8 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_link,
GenerateInviteLinkLink(data.vinvite())),
GenerateInviteLinkLink(data.vinvite()),
Ui::Text::WithEntities),
data.vinvite());
};
@@ -1267,9 +1311,9 @@ void GenerateItems(
const auto participantPeer = groupCallParticipantPeer(
data.vparticipant());
const auto participantPeerLink = participantPeer->createOpenLink();
const auto participantPeerLinkText = textcmdLink(
2,
participantPeer->name);
const auto participantPeerLinkText = Ui::Text::Link(
participantPeer->name,
{});
const auto volume = data.vparticipant().match([&](
const MTPDgroupCallParticipant &data) {
return data.vvolume().value_or(10000);
@@ -1278,27 +1322,29 @@ void GenerateItems(
auto text = (broadcast
? tr::lng_admin_log_participant_volume_channel
: tr::lng_admin_log_participant_volume)(
tr::now,
lt_from,
fromLinkText,
lt_user,
participantPeerLinkText,
lt_percent,
volumeText);
tr::now,
lt_from,
fromLinkText,
lt_user,
participantPeerLinkText,
lt_percent,
{ .text = volumeText },
Ui::Text::WithEntities);
addServiceMessageWithLink(text, participantPeerLink);
};
const auto createChangeHistoryTTL = [&](const LogTTL &data) {
const auto was = data.vprev_value().v;
const auto now = data.vnew_value().v;
const auto wrap = [](int duration) {
return (duration == 5)
const auto wrap = [](int duration) -> TextWithEntities {
const auto text = (duration == 5)
? u"5 seconds"_q
: (duration < 2 * 86400)
? tr::lng_manage_messages_ttl_after1(tr::now)
: (duration < 8 * 86400)
? tr::lng_manage_messages_ttl_after2(tr::now)
: tr::lng_manage_messages_ttl_after3(tr::now);
return { .text = text };
};
const auto text = !was
? tr::lng_admin_log_messages_ttl_set(
@@ -1306,14 +1352,16 @@ void GenerateItems(
lt_from,
fromLinkText,
lt_duration,
wrap(now))
wrap(now),
Ui::Text::WithEntities)
: !now
? tr::lng_admin_log_messages_ttl_removed(
tr::now,
lt_from,
fromLinkText,
lt_duration,
wrap(was))
wrap(was),
Ui::Text::WithEntities)
: tr::lng_admin_log_messages_ttl_changed(
tr::now,
lt_from,
@@ -1321,7 +1369,8 @@ void GenerateItems(
lt_previous,
wrap(was),
lt_duration,
wrap(now));
wrap(now),
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
@@ -1332,7 +1381,6 @@ void GenerateItems(
? tr::lng_admin_log_participant_approved_by_link
: tr::lng_admin_log_participant_approved_by_link_channel);
const auto linkText = GenerateInviteLinkLink(data.vinvite());
const auto adminIndex = linkText.endsWith(Ui::kQEllipsis) ? 2 : 3;
addInviteLinkServiceMessage(
text(
tr::now,
@@ -1341,7 +1389,8 @@ void GenerateItems(
lt_link,
linkText,
lt_user,
textcmdLink(adminIndex, user->name)),
Ui::Text::Link(user->name, {}),
Ui::Text::WithEntities),
data.vinvite(),
user->createOpenLink());
};
@@ -1350,15 +1399,20 @@ void GenerateItems(
const auto disabled = (data.vnew_value().type() == mtpc_boolTrue);
const auto text = (disabled
? tr::lng_admin_log_forwards_disabled
: tr::lng_admin_log_forwards_enabled);
addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText));
: tr::lng_admin_log_forwards_enabled)(
tr::now,
lt_from,
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
const auto createSendMessage = [&](const LogActionSendMessage &data) {
const auto text = tr::lng_admin_log_sent_message(
tr::now,
lt_from,
fromLinkText);
fromLinkText,
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
const auto detachExistingItem = false;
@@ -1371,14 +1425,25 @@ void GenerateItems(
ExtractSentDate(data.vmessage()));
};
const auto createChangeAvailableReactions = [&](const LogEventActionChangeAvailableReactions &data) {
const auto createChangeAvailableReactions = [&](
const LogEventActionChangeAvailableReactions &data) {
auto list = QStringList();
for (const auto &emoji : data.vnew_value().v) {
list.append(qs(emoji));
}
const auto text = list.isEmpty()
? tr::lng_admin_log_reactions_disabled(tr::now, lt_from, fromLinkText)
: tr::lng_admin_log_reactions_updated(tr::now, lt_from, fromLinkText, lt_emoji, list.join(", "));
? tr::lng_admin_log_reactions_disabled(
tr::now,
lt_from,
fromLinkText,
Ui::Text::WithEntities)
: tr::lng_admin_log_reactions_updated(
tr::now,
lt_from,
fromLinkText,
lt_emoji,
{ .text = list.join(", ") },
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_chat_filters.h"
#include "data/data_scheduled_messages.h"
#include "data/data_sponsored_messages.h"
#include "data/data_send_action.h"
#include "data/data_folder.h"
#include "data/data_photo.h"
@@ -67,6 +68,7 @@ History::History(not_null<Data::Session*> owner, PeerId peerId)
: Entry(owner, Type::History)
, peer(owner->peer(peerId))
, cloudDraftTextCache(st::dialogsTextWidthMin)
, _delegateMixin(HistoryInner::DelegateMixin())
, _mute(owner->notifyIsMuted(peer))
, _chatListNameSortKey(owner->nameSortKey(peer->name))
, _sendActionPainter(this) {
@@ -95,14 +97,14 @@ int History::height() const {
void History::removeNotification(not_null<HistoryItem*> item) {
_notifications.erase(
ranges::remove(_notifications, item),
ranges::remove(_notifications, item, &ItemNotification::item),
end(_notifications));
}
HistoryItem *History::currentNotification() {
auto History::currentNotification() const -> std::optional<ItemNotification> {
return empty(_notifications)
? nullptr
: _notifications.front().get();
? std::nullopt
: std::make_optional(_notifications.front());
}
bool History::hasNotification() const {
@@ -115,8 +117,12 @@ void History::skipNotification() {
}
}
void History::popNotification(HistoryItem *item) {
if (!empty(_notifications) && (_notifications.back() == item)) {
void History::pushNotification(ItemNotification notification) {
_notifications.push_back(notification);
}
void History::popNotification(ItemNotification notification) {
if (!empty(_notifications) && (_notifications.back() == notification)) {
_notifications.pop_back();
}
}
@@ -673,6 +679,18 @@ not_null<HistoryItem*> History::addNewLocalMessage(
true);
}
not_null<HistoryItem*> History::addNewLocalMessage(
MsgId id,
Data::SponsoredFrom from,
const TextWithEntities &textWithEntities) {
return addNewItem(
makeMessage(
id,
from,
textWithEntities),
true);
}
void History::setUnreadMentionsCount(int count) {
const auto had = _unreadMentionsCount && (*_unreadMentionsCount > 0);
if (_unreadMentions.size() > count) {
@@ -1132,13 +1150,17 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
from->madeAction(item->date());
}
item->contributeToSlowmode();
auto notification = ItemNotification{
item,
ItemNotificationType::Message,
};
if (item->showNotification()) {
_notifications.push_back(item);
pushNotification(notification);
}
owner().notifyNewItemAdded(item);
const auto stillShow = item->showNotification(); // Could be read already.
if (stillShow) {
Core::App().notifications().schedule(item);
Core::App().notifications().schedule(notification);
if (!item->out() && item->unread()) {
if (unreadCountKnown()) {
setUnreadCount(unreadCount() + 1);
@@ -1231,8 +1253,7 @@ void History::addItemToBlock(not_null<HistoryItem*> item) {
auto block = prepareBlockForAddingItem();
block->messages.push_back(item->createView(
HistoryInner::ElementDelegate()));
block->messages.push_back(item->createView(_delegateMixin->delegate()));
const auto view = block->messages.back().get();
view->attachToBlock(block, block->messages.size() - 1);
@@ -1998,8 +2019,7 @@ not_null<HistoryItem*> History::addNewInTheMiddle(
const auto it = block->messages.insert(
block->messages.begin() + itemIndex,
item->createView(
HistoryInner::ElementDelegate()));
item->createView(_delegateMixin->delegate()));
(*it)->attachToBlock(block.get(), itemIndex);
if (itemIndex + 1 < block->messages.size()) {
for (auto i = itemIndex + 1, l = int(block->messages.size()); i != l; ++i) {
@@ -2135,8 +2155,11 @@ void History::clearNotifications() {
void History::clearIncomingNotifications() {
if (!peer->isSelf()) {
const auto proj = [](ItemNotification notification) {
return notification.item->out();
};
_notifications.erase(
ranges::remove(_notifications, false, &HistoryItem::out),
ranges::remove(_notifications, false, proj),
end(_notifications));
}
}
@@ -2971,7 +2994,9 @@ void History::reactionsEnabledChanged(bool enabled) {
item->updateReactions(nullptr);
}
} else {
for (const auto &item : _messages) {
item->updateReactionsUnknown();
}
}
}
@@ -3274,7 +3299,7 @@ void HistoryBlock::refreshView(not_null<Element*> view) {
const auto item = view->data();
auto refreshed = item->createView(
HistoryInner::ElementDelegate(),
_history->delegateMixin()->delegate(),
view);
auto blockIndex = indexInHistory();

View File

@@ -25,6 +25,7 @@ class HistoryItem;
class HistoryMessage;
class HistoryService;
struct HistoryMessageMarkupData;
class HistoryMainElementDelegateMixin;
namespace Main {
class Session;
@@ -35,6 +36,7 @@ struct Draft;
class Session;
class Folder;
class ChatFilter;
struct SponsoredFrom;
enum class ForwardOptions {
PreserveInfo,
@@ -74,6 +76,19 @@ enum class UnreadMentionType {
Existing, // when some messages slice was received
};
enum class ItemNotificationType {
Message,
Reaction,
};
struct ItemNotification {
not_null<HistoryItem*> item;
ItemNotificationType type = ItemNotificationType::Message;
friend inline bool operator==(ItemNotification a, ItemNotification b) {
return (a.item == b.item) && (a.type == b.type);
}
};
class History final : public Dialogs::Entry {
public:
using Element = HistoryView::Element;
@@ -83,6 +98,11 @@ public:
History &operator=(const History &) = delete;
~History();
[[nodiscard]] auto delegateMixin() const
-> not_null<HistoryMainElementDelegateMixin*> {
return _delegateMixin.get();
}
not_null<History*> migrateToOrMe() const;
History *migrateFrom() const;
MsgRange rangeForDifferenceRequest() const;
@@ -190,6 +210,10 @@ public:
const QString &postAuthor,
not_null<GameData*> game,
HistoryMessageMarkupData &&markup);
not_null<HistoryItem*> addNewLocalMessage(
MsgId id,
Data::SponsoredFrom from,
const TextWithEntities &textWithEntities); // sponsored
// Used only internally and for channel admin log.
not_null<HistoryItem*> createItem(
@@ -293,10 +317,11 @@ public:
void itemRemoved(not_null<HistoryItem*> item);
void itemVanished(not_null<HistoryItem*> item);
HistoryItem *currentNotification();
[[nodiscard]] std::optional<ItemNotification> currentNotification() const;
bool hasNotification() const;
void skipNotification();
void popNotification(HistoryItem *item);
void pushNotification(ItemNotification notification);
void popNotification(ItemNotification notification);
bool hasPendingResizedItems() const;
void setHasPendingResizedItems();
@@ -580,6 +605,8 @@ private:
void setFolderPointer(Data::Folder *folder);
const std::unique_ptr<HistoryMainElementDelegateMixin> _delegateMixin;
Flags _flags = 0;
bool _mute = false;
int _width = 0;
@@ -632,7 +659,7 @@ private:
HistoryView::SendActionPainter _sendActionPainter;
std::deque<not_null<HistoryItem*>> _notifications;
std::deque<ItemNotification> _notifications;
};

View File

@@ -120,7 +120,148 @@ int BinarySearchBlocksOrItems(const T &list, int edge) {
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
HistoryInner *HistoryInner::Instance = nullptr;
HistoryMainElementDelegateMixin::HistoryMainElementDelegateMixin() = default;
HistoryMainElementDelegateMixin::~HistoryMainElementDelegateMixin()
= default;
class HistoryMainElementDelegate final
: public HistoryView::ElementDelegate
, public HistoryMainElementDelegateMixin {
public:
using Element = HistoryView::Element;
HistoryView::Context elementContext() override {
return HistoryView::Context::History;
}
std::unique_ptr<Element> elementCreate(
not_null<HistoryMessage*> message,
Element *replacing = nullptr) override {
return std::make_unique<HistoryView::Message>(
this,
message,
replacing);
}
std::unique_ptr<HistoryView::Element> elementCreate(
not_null<HistoryService*> message,
Element *replacing = nullptr) override {
return std::make_unique<HistoryView::Service>(
this,
message,
replacing);
}
bool elementUnderCursor(
not_null<const Element*> view) override {
return (App::mousedItem() == view);
}
crl::time elementHighlightTime(
not_null<const HistoryItem*> item) override {
return _widget ? _widget->elementHighlightTime(item) : 0;
}
bool elementInSelectionMode() override {
return _widget ? _widget->inSelectionMode() : false;
}
bool elementIntersectsRange(
not_null<const Element*> view,
int from,
int till) override {
return _widget
? _widget->elementIntersectsRange(view, from, till)
: false;
}
void elementStartStickerLoop(
not_null<const Element*> view) override {
if (_widget) {
_widget->elementStartStickerLoop(view);
}
}
void elementShowPollResults(
not_null<PollData*> poll,
FullMsgId context) override {
if (_widget) {
_widget->elementShowPollResults(poll, context);
}
}
void elementOpenPhoto(
not_null<PhotoData*> photo,
FullMsgId context) override {
if (_widget) {
_widget->elementOpenPhoto(photo, context);
}
}
void elementOpenDocument(
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView = false) override {
if (_widget) {
_widget->elementOpenDocument(
document,
context,
showInMediaView);
}
}
void elementCancelUpload(const FullMsgId &context) override {
if (_widget) {
_widget->elementCancelUpload(context);
}
}
void elementShowTooltip(
const TextWithEntities &text,
Fn<void()> hiddenCallback) override {
if (_widget) {
_widget->elementShowTooltip(text, hiddenCallback);
}
}
bool elementIsGifPaused() override {
return _widget ? _widget->elementIsGifPaused() : false;
}
bool elementHideReply(not_null<const Element*> view) override {
return false;
}
bool elementShownUnread(not_null<const Element*> view) override {
return view->data()->unread();
}
void elementSendBotCommand(
const QString &command,
const FullMsgId &context) override {
if (_widget) {
_widget->elementSendBotCommand(command, context);
}
}
void elementHandleViaClick(not_null<UserData*> bot) override {
if (_widget) {
_widget->elementHandleViaClick(bot);
}
}
bool elementIsChatWide() override {
return _widget ? _widget->elementIsChatWide() : false;
}
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override {
Expects(_widget != nullptr);
return _widget->elementPathShiftGradient();
}
void elementReplyTo(const FullMsgId &to) override {
if (_widget) {
_widget->elementReplyTo(to);
}
}
void elementStartInteraction(not_null<const Element*> view) override {
if (_widget) {
_widget->elementStartInteraction(view);
}
}
void elementShowSpoilerAnimation() override {
if (_widget) {
_widget->elementShowSpoilerAnimation();
}
}
not_null<HistoryView::ElementDelegate*> delegate() override {
return this;
}
};
class HistoryInner::BotAbout : public ClickHandlerHost {
public:
@@ -170,6 +311,7 @@ HistoryInner::HistoryInner(
, _controller(controller)
, _peer(history->peer)
, _history(history)
, _elementDelegate(_history->delegateMixin()->delegate())
, _emojiInteractions(std::make_unique<HistoryView::EmojiInteractions>(
&controller->session()))
, _migrated(history->migrateFrom())
@@ -180,12 +322,17 @@ HistoryInner::HistoryInner(
, _reactionsManager(
std::make_unique<HistoryView::Reactions::Manager>(
this,
[=](QRect updated) { update(updated); }))
Data::UniqueReactionsLimitValue(&controller->session()),
[=](QRect updated) { update(updated); },
controller->cachedReactionIconFactory().createMethod()))
, _touchSelectTimer([=] { onTouchSelect(); })
, _touchScrollTimer([=] { onTouchScrollTimer(); })
, _scrollDateCheck([this] { scrollDateCheck(); })
, _scrollDateHideTimer([this] { scrollDateHideByTimer(); }) {
Instance = this;
_history->delegateMixin()->setCurrent(this);
if (_migrated) {
_migrated->delegateMixin()->setCurrent(this);
}
Window::ChatThemeValueFromPeer(
controller,
@@ -230,8 +377,21 @@ HistoryInner::HistoryInner(
using ChosenReaction = HistoryView::Reactions::Manager::Chosen;
_reactionsManager->chosen(
) | rpl::start_with_next([=](ChosenReaction reaction) {
if (const auto item = session().data().message(reaction.context)) {
item->toggleReaction(reaction.emoji);
const auto item = session().data().message(reaction.context);
if (!item) {
return;
}
item->toggleReaction(reaction.emoji);
if (item->chosenReaction() != reaction.emoji) {
return;
} else if (const auto view = item->mainView()) {
if (const auto top = itemTop(view); top >= 0) {
view->animateSendReaction({
.emoji = reaction.emoji,
.flyIcon = reaction.icon,
.flyFrom = reaction.geometry.translated(0, -top),
});
}
}
}, lifetime());
@@ -267,6 +427,7 @@ HistoryInner::HistoryInner(
return item->mainView() != nullptr;
}) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
item->mainView()->itemDataChanged();
_reactionsManager->updateUniqueLimit(item);
}, lifetime());
session().changes().historyUpdates(
@@ -276,11 +437,10 @@ HistoryInner::HistoryInner(
update();
}, lifetime());
Data::PeerAllowedReactionsValue(
_peer
) | rpl::start_with_next([=](std::vector<Data::Reaction> &&list) {
_reactionsManager->applyList(std::move(list));
}, lifetime());
HistoryView::Reactions::SetupManagerList(
_reactionsManager.get(),
&session(),
Data::PeerAllowedReactionsValue(_peer));
controller->adaptive().chatWideValue(
) | rpl::start_with_next([=](bool wide) {
@@ -394,6 +554,10 @@ void HistoryInner::repaintItem(const Element *view) {
if (top >= 0) {
const auto range = view->verticalRepaintRange();
update(0, top + range.top, width(), range.height);
const auto id = view->data()->fullId();
if (const auto area = _reactionsManager->lookupEffectArea(id)) {
update(*area);
}
}
}
@@ -679,6 +843,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
}
if (hasPendingResizedItems()) {
return;
} else if (_recountedAfterPendingResizedItems) {
_recountedAfterPendingResizedItems = false;
mouseActionUpdate();
}
const auto guard = gsl::finally([&] {
@@ -731,6 +898,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
} else {
_emptyPainter = nullptr;
}
_reactionsManager->startEffectsCollection();
if (!noHistoryDisplayed) {
auto readMentions = base::flat_set<not_null<HistoryItem*>>();
@@ -760,12 +929,17 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
context.translate(0, -top);
p.translate(0, top);
if (context.clip.y() < view->height()) while (top < drawToY) {
context.reactionEffects
= _reactionsManager->currentReactionEffect();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(
view,
selfromy - mtop,
seltoy - mtop);
view->draw(p, context);
_reactionsManager->recordCurrentReactionEffect(
item->fullId(),
QPoint(0, top));
const auto height = view->height();
const auto middle = top + height / 2;
@@ -815,14 +989,18 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
while (top < drawToY) {
const auto height = view->height();
if (context.clip.y() < height && hdrawtop < top + height) {
context.reactionEffects
= _reactionsManager->currentReactionEffect();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(
view,
selfromy - htop,
seltoy - htop);
view->draw(p, context);
_reactionsManager->recordCurrentReactionEffect(
item->fullId(),
QPoint(0, top));
const auto item = view->data();
const auto middle = top + height / 2;
const auto bottom = top + height;
if (_visibleAreaBottom >= bottom) {
@@ -893,7 +1071,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
userpicTop,
width(),
st::msgPhotoSize);
} else if (const auto info = view->data()->hiddenForwardedInfo()) {
} else if (const auto info = view->data()->hiddenSenderInfo()) {
info->userpic.paint(
p,
st::historyPhotoLeft,
@@ -962,7 +1140,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
});
p.setOpacity(1.);
_reactionsManager->paintButtons(p, context);
_reactionsManager->paint(p, context);
p.translate(0, _historyPaddingTop);
_emojiInteractions->paint(p);
@@ -1528,16 +1706,22 @@ void HistoryInner::mouseActionFinish(
const auto pressedItemId = pressedItemView
? pressedItemView->data()->fullId()
: FullMsgId();
const auto weak = base::make_weak(_controller.get());
ActivateClickHandler(window(), activated, {
button,
QVariant::fromValue(ClickHandlerContext{
.itemId = pressedItemId,
.elementDelegate = [weak = Ui::MakeWeak(this)] {
return weak
? HistoryInner::ElementDelegate().get()
: nullptr;
.elementDelegate = [=]() -> HistoryView::ElementDelegate* {
if (const auto strong = weak.get()) {
auto &data = strong->session().data();
if (const auto item = data.message(pressedItemId)) {
const auto history = item->history();
return history->delegateMixin()->delegate();
}
}
return nullptr;
},
.sessionWindow = base::make_weak(_controller.get()),
.sessionWindow = weak,
})
});
return;
@@ -1665,6 +1849,12 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
mouseActionUpdate(e->globalPos());
}
const auto link = ClickHandler::getActive();
if (link
&& !link->property(kSendReactionEmojiProperty).toString().isEmpty()
&& _reactionsManager->showContextMenu(this, e)) {
return;
}
auto selectedState = getSelectionState();
auto canSendMessages = _peer->canWrite();
@@ -1702,6 +1892,22 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto hasWhoReactedItem = _dragStateItem
&& Api::WhoReactedExists(_dragStateItem);
const auto clickedEmoji = link
? link->property(kReactionsCountEmojiProperty).toString()
: QString();
_whoReactedMenuLifetime.destroy();
if (hasWhoReactedItem && !clickedEmoji.isEmpty()) {
HistoryView::ShowWhoReactedMenu(
&_menu,
e->globalPos(),
this,
_dragStateItem,
clickedEmoji,
_controller,
_whoReactedMenuLifetime);
e->accept();
return;
}
_menu = base::make_unique_q<Ui::PopupMenu>(
this,
hasWhoReactedItem ? st::whoReadMenu : st::popupMenuWithIcons);
@@ -1860,10 +2066,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
};
const auto link = ClickHandler::getActive();
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(link.get());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(link.get());
if (lnkPhoto || lnkDocument) {
const auto lnkPhotoId = PhotoId(link
? link->property(kPhotoLinkMediaIdProperty).toULongLong()
: 0);
const auto lnkDocumentId = DocumentId(link
? link->property(kDocumentLinkMediaIdProperty).toULongLong()
: 0);
if (lnkPhotoId || lnkDocumentId) {
const auto item = _dragStateItem;
const auto itemId = item ? item->fullId() : FullMsgId();
if (isUponSelected > 0 && !hasCopyRestrictionForSelected()) {
@@ -1875,10 +2084,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
&st::menuIconCopy);
}
addItemActions(item, item);
if (lnkPhoto) {
addPhotoActions(lnkPhoto->photo(), item);
if (lnkPhotoId) {
addPhotoActions(session->data().photo(lnkPhotoId), item);
} else {
addDocumentActions(lnkDocument->document(), item);
addDocumentActions(session->data().document(lnkDocumentId), item);
}
if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
_menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_message_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] {
@@ -2370,6 +2579,11 @@ void HistoryInner::checkHistoryActivation() {
void HistoryInner::recountHistoryGeometry() {
_contentWidth = _scroll->width();
if (_history->hasPendingResizedItems()
|| (_migrated && _migrated->hasPendingResizedItems())) {
_recountedAfterPendingResizedItems = true;
}
const auto visibleHeight = _scroll->height();
int oldHistoryPaddingTop = qMax(visibleHeight - historyHeight() - st::historyPaddingBottom, 0);
if (_botAbout && !_botAbout->info->text.isEmpty()) {
@@ -2543,7 +2757,13 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) {
const auto pages = kUnloadHeavyPartsPages;
const auto from = _visibleAreaTop - pages * visibleAreaHeight;
const auto till = _visibleAreaBottom + pages * visibleAreaHeight;
session().data().unloadHeavyViewParts(ElementDelegate(), from, till);
session().data().unloadHeavyViewParts(_elementDelegate, from, till);
if (_migratedElementDelegate) {
session().data().unloadHeavyViewParts(
_migratedElementDelegate,
from,
till);
}
checkHistoryActivation();
_emojiInteractions->visibleAreaUpdated(
@@ -2671,7 +2891,7 @@ void HistoryInner::enterEventHook(QEnterEvent *e) {
}
void HistoryInner::leaveEventHook(QEvent *e) {
_reactionsManager->updateButton({});
_reactionsManager->updateButton({ .cursorLeft = true });
if (auto item = App::hoveredItem()) {
repaintItem(item);
App::hoveredItem(nullptr);
@@ -2693,8 +2913,9 @@ HistoryInner::~HistoryInner() {
}
}
}
if (Instance == this) {
Instance = nullptr;
_history->delegateMixin()->setCurrent(nullptr);
if (_migrated) {
_migrated->delegateMixin()->setCurrent(nullptr);
}
delete _menu;
_mouseAction = MouseAction::None;
@@ -2966,6 +3187,7 @@ auto HistoryInner::reactionButtonParameters(
if (top < 0
|| !view->data()->canReact()
|| _mouseAction == MouseAction::Dragging
|| _mouseAction == MouseAction::Selecting
|| inSelectionMode()) {
return {};
}
@@ -3002,12 +3224,20 @@ void HistoryInner::mouseActionUpdate() {
: nullptr;
const auto item = view ? view->data().get() : nullptr;
if (view) {
App::mousedItem(view);
const auto changed = (App::mousedItem() != view);
if (changed) {
repaintItem(App::mousedItem());
App::mousedItem(view);
repaintItem(App::mousedItem());
}
m = mapPointToItem(point, view);
_reactionsManager->updateButton(reactionButtonParameters(
view,
m,
reactionState));
if (changed) {
_reactionsManager->updateUniqueLimit(item);
}
if (view->pointState(m) != PointState::Outside) {
if (App::hoveredItem() != view) {
repaintItem(App::hoveredItem());
@@ -3019,6 +3249,10 @@ void HistoryInner::mouseActionUpdate() {
App::hoveredItem(nullptr);
}
} else {
if (App::mousedItem()) {
repaintItem(App::mousedItem());
App::mousedItem(nullptr);
}
_reactionsManager->updateButton({});
}
if (_mouseActionItem && !_mouseActionItem->mainView()) {
@@ -3720,139 +3954,7 @@ void HistoryInner::onParentGeometryChanged() {
}
}
not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
class Result final : public HistoryView::ElementDelegate {
public:
HistoryView::Context elementContext() override {
return HistoryView::Context::History;
}
std::unique_ptr<HistoryView::Element> elementCreate(
not_null<HistoryMessage*> message,
Element *replacing = nullptr) override {
return std::make_unique<HistoryView::Message>(
this,
message,
replacing);
}
std::unique_ptr<HistoryView::Element> elementCreate(
not_null<HistoryService*> message,
Element *replacing = nullptr) override {
return std::make_unique<HistoryView::Service>(
this,
message,
replacing);
}
bool elementUnderCursor(
not_null<const Element*> view) override {
return (App::hoveredItem() == view);
}
crl::time elementHighlightTime(
not_null<const HistoryItem*> item) override {
return Instance ? Instance->elementHighlightTime(item) : 0;
}
bool elementInSelectionMode() override {
return Instance ? Instance->inSelectionMode() : false;
}
bool elementIntersectsRange(
not_null<const Element*> view,
int from,
int till) override {
return Instance
? Instance->elementIntersectsRange(view, from, till)
: false;
}
void elementStartStickerLoop(
not_null<const Element*> view) override {
if (Instance) {
Instance->elementStartStickerLoop(view);
}
}
void elementShowPollResults(
not_null<PollData*> poll,
FullMsgId context) override {
if (Instance) {
Instance->elementShowPollResults(poll, context);
}
}
void elementOpenPhoto(
not_null<PhotoData*> photo,
FullMsgId context) override {
if (Instance) {
Instance->elementOpenPhoto(photo, context);
}
}
void elementOpenDocument(
not_null<DocumentData*> document,
FullMsgId context,
bool showInMediaView = false) override {
if (Instance) {
Instance->elementOpenDocument(
document,
context,
showInMediaView);
}
}
void elementCancelUpload(const FullMsgId &context) override {
if (Instance) {
Instance->elementCancelUpload(context);
}
}
void elementShowTooltip(
const TextWithEntities &text,
Fn<void()> hiddenCallback) override {
if (Instance) {
Instance->elementShowTooltip(text, hiddenCallback);
}
}
bool elementIsGifPaused() override {
return Instance ? Instance->elementIsGifPaused() : false;
}
bool elementHideReply(not_null<const Element*> view) override {
return false;
}
bool elementShownUnread(not_null<const Element*> view) override {
return view->data()->unread();
}
void elementSendBotCommand(
const QString &command,
const FullMsgId &context) override {
if (Instance) {
Instance->elementSendBotCommand(command, context);
}
}
void elementHandleViaClick(not_null<UserData*> bot) override {
if (Instance) {
Instance->elementHandleViaClick(bot);
}
}
bool elementIsChatWide() override {
return Instance
? Instance->elementIsChatWide()
: false;
}
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override {
Expects(Instance != nullptr);
return Instance->elementPathShiftGradient();
}
void elementReplyTo(const FullMsgId &to) override {
if (Instance) {
Instance->elementReplyTo(to);
}
}
void elementStartInteraction(not_null<const Element*> view) override {
if (Instance) {
Instance->elementStartInteraction(view);
}
}
void elementShowSpoilerAnimation() override {
if (Instance) {
Instance->elementShowSpoilerAnimation();
}
}
};
static Result result;
return &result;
auto HistoryInner::DelegateMixin()
-> std::unique_ptr<HistoryMainElementDelegateMixin> {
return std::make_unique<HistoryMainElementDelegate>();
}

View File

@@ -47,6 +47,26 @@ enum class ReportReason;
class PathShiftGradient;
} // namespace Ui
class HistoryInner;
class HistoryMainElementDelegate;
class HistoryMainElementDelegateMixin {
public:
void setCurrent(HistoryInner *widget) {
_widget = widget;
}
virtual not_null<HistoryView::ElementDelegate*> delegate() = 0;
virtual ~HistoryMainElementDelegateMixin();
private:
friend class HistoryMainElementDelegate;
HistoryMainElementDelegateMixin();
HistoryInner *_widget = nullptr;
};
class HistoryWidget;
class HistoryInner
: public Ui::RpWidget
@@ -160,8 +180,8 @@ public:
void onParentGeometryChanged();
// HistoryView::ElementDelegate interface.
static not_null<HistoryView::ElementDelegate*> ElementDelegate();
[[nodiscard]] static auto DelegateMixin()
-> std::unique_ptr<HistoryMainElementDelegateMixin>;
protected:
bool focusNextPrevChild(bool next) override;
@@ -364,17 +384,17 @@ private:
// Does any of the shown histories has this flag set.
bool hasPendingResizedItems() const;
static HistoryInner *Instance;
const not_null<HistoryWidget*> _widget;
const not_null<Ui::ScrollArea*> _scroll;
const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
const not_null<History*> _history;
const not_null<HistoryView::ElementDelegate*> _elementDelegate;
const std::unique_ptr<HistoryView::EmojiInteractions> _emojiInteractions;
std::shared_ptr<Ui::ChatTheme> _theme;
History *_migrated = nullptr;
HistoryView::ElementDelegate *_migratedElementDelegate = nullptr;
int _contentWidth = 0;
int _historyPaddingTop = 0;
int _revealHeight = 0;
@@ -417,6 +437,7 @@ private:
CursorState _mouseCursorState = CursorState();
uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false;
bool _recountedAfterPendingResizedItems = false;
QPoint _trippleClickPoint;
base::Timer _trippleClickTimer;
@@ -448,6 +469,8 @@ private:
Ui::Animations::Simple _spoilerOpacity;
// _menu must be destroyed before _whoReactedMenuLifetime.
rpl::lifetime _whoReactedMenuLifetime;
base::unique_qptr<Ui::PopupMenu> _menu;
bool _scrollDateShown = false;

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "storage/file_upload.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
@@ -45,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_sponsored_messages.h"
#include "styles/style_dialogs.h"
#include "styles/style_chat.h"
@@ -294,6 +296,10 @@ HistoryItem *HistoryItem::lookupDiscussionPostOriginal() const {
PeerData *HistoryItem::displayFrom() const {
if (const auto sender = discussionPostOriginalSender()) {
return sender;
} else if (const auto sponsored = Get<HistoryMessageSponsored>()) {
if (sponsored->sender) {
return nullptr;
}
} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
if (history()->peer->isSelf() || history()->peer->isRepliesChat() || forwarded->imported) {
return forwarded->originalSender;
@@ -377,7 +383,7 @@ void HistoryItem::setIsPinned(bool pinned) {
id));
}
if (changed) {
history()->owner().requestItemResize(this);
history()->owner().notifyItemDataChange(this);
}
}
@@ -484,7 +490,7 @@ bool HistoryItem::isScheduled() const {
}
bool HistoryItem::isSponsored() const {
return (_flags & MessageFlag::IsSponsored);
return Has<HistoryMessageSponsored>();
}
bool HistoryItem::skipNotification() const {
@@ -802,6 +808,26 @@ void HistoryItem::toggleReaction(const QString &reaction) {
}
void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
const auto history = this->history();
const auto toUser = (reactions && out())
? history->peer->asUser()
: nullptr;
const auto toContact = toUser && toUser->isContact();
const auto maybeNotify = toContact && lookupHisReaction().isEmpty();
setReactions(reactions);
if (maybeNotify) {
if (const auto reaction = lookupHisReaction(); !reaction.isEmpty()) {
const auto notification = ItemNotification{
this,
ItemNotificationType::Reaction,
};
history->pushNotification(notification);
Core::App().notifications().schedule(notification);
}
}
}
void HistoryItem::setReactions(const MTPMessageReactions *reactions) {
if (reactions || _reactionsLastRefreshed) {
_reactionsLastRefreshed = crl::now();
}
@@ -828,7 +854,10 @@ void HistoryItem::updateReactions(const MTPMessageReactions *reactions) {
} else if (!_reactions) {
_reactions = std::make_unique<Data::MessageReactions>(this);
}
_reactions->set(data.vresults().v, data.is_min());
_reactions->set(
data.vresults().v,
data.vrecent_reactons().value_or_empty(),
data.is_min());
});
}
@@ -841,6 +870,14 @@ const base::flat_map<QString, int> &HistoryItem::reactions() const {
return _reactions ? _reactions->list() : kEmpty;
}
auto HistoryItem::recentReactions() const
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> & {
static const auto kEmpty = base::flat_map<
QString,
std::vector<not_null<UserData*>>>();
return _reactions ? _reactions->recent() : kEmpty;
}
bool HistoryItem::canViewReactions() const {
return (_flags & MessageFlag::CanViewReactions)
&& _reactions
@@ -851,6 +888,24 @@ QString HistoryItem::chosenReaction() const {
return _reactions ? _reactions->chosen() : QString();
}
QString HistoryItem::lookupHisReaction() const {
if (!_reactions) {
return QString();
}
const auto &list = _reactions->list();
if (list.empty()) {
return QString();
}
const auto chosen = _reactions->chosen();
const auto &[first, count] = list.front();
if (chosen.isEmpty() || first != chosen || count > 1) {
return first;
} else if (list.size() == 1) {
return QString();
}
return list.back().first;
}
crl::time HistoryItem::lastReactionsRefreshTime() const {
return _reactionsLastRefreshed;
}
@@ -900,8 +955,10 @@ PeerData *HistoryItem::senderOriginal() const {
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
}
const HiddenSenderInfo *HistoryItem::hiddenForwardedInfo() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
const HiddenSenderInfo *HistoryItem::hiddenSenderInfo() const {
if (const auto sponsored = Get<HistoryMessageSponsored>()) {
return sponsored->sender.get();
} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->hiddenSenderInfo.get();
}
return nullptr;
@@ -1067,23 +1124,20 @@ bool HistoryItem::isEmpty() const {
&& !Has<HistoryMessageLogEntryOriginal>();
}
QString HistoryItem::notificationText() const {
TextWithEntities HistoryItem::notificationText() const {
const auto result = [&] {
if (_media && !isService()) {
return _media->notificationText();
} else if (!emptyText()) {
return TextUtilities::TextWithSpoilerCommands(
_text.toTextWithEntities());
return _text.toTextWithEntities();
}
return QString();
return TextWithEntities();
}();
return (result.size() <= kNotificationTextLimit)
? result
: TextUtilities::CutTextWithCommands(
result,
kNotificationTextLimit,
textcmdStartSpoiler(),
textcmdStopSpoiler());
if (result.text.size() <= kNotificationTextLimit) {
return result;
}
return Ui::Text::Mid(result, 0, kNotificationTextLimit).append(
Ui::kQEllipsis);
}
ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
@@ -1092,12 +1146,7 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
return _media->toPreview(options);
} else if (!emptyText()) {
return {
.text = TextUtilities::Clean(
options.ignoreSpoilers
? _text.toString()
: TextUtilities::TextWithSpoilerCommands(
_text.toTextWithEntities()),
!options.ignoreSpoilers),
.text = _text.toTextWithEntities()
};
}
return {};
@@ -1118,6 +1167,8 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
const auto sender = [&]() -> std::optional<QString> {
if (options.hideSender || isPost() || isEmpty()) {
return {};
} else if (const auto sponsored = Get<HistoryMessageSponsored>()) {
return sponsored->sender->name;
} else if (!_history->peer->isUser()) {
if (const auto from = displayFrom()) {
return fromSender(from);
@@ -1131,16 +1182,12 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
if (!sender) {
return result;
}
const auto fromWrapped = textcmdLink(
1,
tr::lng_dialogs_text_from_wrapped(
tr::now,
lt_from,
TextUtilities::Clean(*sender)));
const auto fromWrapped = Ui::Text::PlainLink(
tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, *sender));
return Dialogs::Ui::PreviewWithSender(std::move(result), fromWrapped);
}
QString HistoryItem::inReplyText() const {
TextWithEntities HistoryItem::inReplyText() const {
return toPreview({
.hideSender = true,
.generateImages = false,
@@ -1265,7 +1312,7 @@ not_null<HistoryItem*> HistoryItem::Create(
data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
} else if (checked == MediaCheckResult::Empty) {
const auto text = HistoryService::PreparedText{
tr::lng_message_empty(tr::now)
tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
};
return history->makeServiceMessage(
id,
@@ -1284,7 +1331,7 @@ not_null<HistoryItem*> HistoryItem::Create(
return history->makeServiceMessage(id, data, localFlags);
}, [&](const MTPDmessageEmpty &data) -> HistoryItem* {
const auto text = HistoryService::PreparedText{
tr::lng_message_empty(tr::now)
tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
};
return history->makeServiceMessage(id, localFlags, TimeId(0), text);
});

View File

@@ -290,7 +290,7 @@ public:
[[nodiscard]] virtual QString notificationHeader() const {
return QString();
}
[[nodiscard]] virtual QString notificationText() const;
[[nodiscard]] virtual TextWithEntities notificationText() const;
using ToPreviewOptions = HistoryView::ToPreviewOptions;
using ItemPreview = HistoryView::ItemPreview;
@@ -299,7 +299,7 @@ public:
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
[[nodiscard]] virtual ItemPreview toPreview(
ToPreviewOptions options) const;
[[nodiscard]] virtual QString inReplyText() const;
[[nodiscard]] virtual TextWithEntities inReplyText() const;
[[nodiscard]] virtual Ui::Text::IsolatedEmoji isolatedEmoji() const;
[[nodiscard]] virtual TextWithEntities originalText() const {
return TextWithEntities();
@@ -359,8 +359,11 @@ public:
void updateReactions(const MTPMessageReactions *reactions);
void updateReactionsUnknown();
[[nodiscard]] const base::flat_map<QString, int> &reactions() const;
[[nodiscard]] auto recentReactions() const
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> &;
[[nodiscard]] bool canViewReactions() const;
[[nodiscard]] QString chosenReaction() const;
[[nodiscard]] QString lookupHisReaction() const;
[[nodiscard]] crl::time lastReactionsRefreshTime() const;
[[nodiscard]] bool hasDirectLink() const;
@@ -387,7 +390,7 @@ public:
[[nodiscard]] TimeId dateOriginal() const;
[[nodiscard]] PeerData *senderOriginal() const;
[[nodiscard]] const HiddenSenderInfo *hiddenForwardedInfo() const;
[[nodiscard]] const HiddenSenderInfo *hiddenSenderInfo() const;
[[nodiscard]] not_null<PeerData*> fromOriginal() const;
[[nodiscard]] QString authorOriginal() const;
[[nodiscard]] MsgId idOriginal() const;
@@ -440,6 +443,8 @@ protected:
void finishEdition(int oldKeyboardTop);
void finishEditionToEmpty();
void setReactions(const MTPMessageReactions *reactions);
const not_null<History*> _history;
const not_null<PeerData*> _from;
MessageFlags _flags = 0;
@@ -467,7 +472,6 @@ protected:
crl::time _reactionsLastRefreshed = 0;
private:
TimeId _date = 0;
TimeId _ttlDestroyAt = 0;

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image.h"
#include "ui/toast/toast.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "history/history.h"
@@ -106,20 +107,21 @@ HiddenSenderInfo::HiddenSenderInfo(const QString &name, bool external)
}
void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
auto phrase = QString();
auto phrase = TextWithEntities();
const auto fromChannel = originalSender
&& originalSender->isChannel()
&& !originalSender->isMegagroup();
const auto name = originalSender
? originalSender->name
: hiddenSenderInfo->name;
const auto name = TextWithEntities{
.text = originalSender ? originalSender->name : hiddenSenderInfo->name
};
if (!originalAuthor.isEmpty()) {
phrase = tr::lng_forwarded_signed(
tr::now,
lt_channel,
name,
lt_user,
originalAuthor);
{ .text = originalAuthor },
Ui::Text::WithEntities);
} else {
phrase = name;
}
@@ -128,16 +130,18 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
phrase = tr::lng_forwarded_channel_via(
tr::now,
lt_channel,
textcmdLink(1, phrase),
Ui::Text::Link(phrase.text, QString()), // Link 1.
lt_inline_bot,
textcmdLink(2, '@' + via->bot->username));
Ui::Text::Link('@' + via->bot->username, {}), // Link 2.
Ui::Text::WithEntities);
} else {
phrase = tr::lng_forwarded_via(
tr::now,
lt_user,
textcmdLink(1, phrase),
Ui::Text::Link(phrase.text, QString()), // Link 1.
lt_inline_bot,
textcmdLink(2, '@' + via->bot->username));
Ui::Text::Link('@' + via->bot->username, {}), // Link 2.
Ui::Text::WithEntities);
}
} else {
if (fromChannel || !psaType.isEmpty()) {
@@ -145,28 +149,32 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
? QString()
: Lang::GetNonDefaultValue(
kPsaForwardedPrefix + psaType.toUtf8());
phrase = !custom.isEmpty()
? custom.replace("{channel}", textcmdLink(1, phrase))
: (psaType.isEmpty()
if (!custom.isEmpty()) {
custom = custom.replace("{channel}", phrase.text);
const auto index = int(custom.indexOf(phrase.text));
const auto size = int(phrase.text.size());
phrase = TextWithEntities{
.text = custom,
.entities = {{ EntityType::CustomUrl, index, size, {} }},
};
} else {
phrase = (psaType.isEmpty()
? tr::lng_forwarded_channel
: tr::lng_forwarded_psa_default)(
tr::now,
lt_channel,
textcmdLink(1, phrase));
Ui::Text::Link(phrase.text, QString()), // Link 1.
Ui::Text::WithEntities);
}
} else {
phrase = tr::lng_forwarded(
tr::now,
lt_user,
textcmdLink(1, phrase));
Ui::Text::Link(phrase.text, QString()), // Link 1.
Ui::Text::WithEntities);
}
}
TextParseOptions opts = {
TextParseRichText,
0,
0,
Qt::LayoutDirectionAuto
};
text.setText(st::fwdTextStyle, phrase, opts);
text.setMarkedText(st::fwdTextStyle, phrase);
static const auto hidden = std::make_shared<LambdaClickHandler>([] {
Ui::Toast::Show(tr::lng_forwarded_hidden(tr::now));
});
@@ -210,7 +218,7 @@ bool HistoryMessageReply::updateData(
}
if (replyToMsg) {
replyToText.setText(
replyToText.setMarkedText(
st::messageTextStyle,
replyToMsg->inReplyText(),
Ui::DialogTextOptions());

View File

@@ -102,6 +102,18 @@ struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded
bool imported = false;
};
struct HistoryMessageSponsored : public RuntimeComponent<HistoryMessageSponsored, HistoryItem> {
enum class Type : uchar {
User,
Group,
Broadcast,
Post,
Bot,
};
std::unique_ptr<HiddenSenderInfo> sender;
Type type = Type::User;
};
struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, HistoryItem> {
HistoryMessageReply() = default;
HistoryMessageReply(const HistoryMessageReply &other) = delete;

View File

@@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_histories.h"
#include "data/data_web_page.h"
#include "data/data_sponsored_messages.h"
#include "styles/style_dialogs.h"
#include "styles/style_widgets.h"
#include "styles/style_chat.h"
@@ -500,7 +501,7 @@ HistoryMessage::HistoryMessage(
setMedia(*media);
}
const auto textWithEntities = TextWithEntities{
TextUtilities::Clean(qs(data.vmessage())),
qs(data.vmessage()),
Api::EntitiesFromMTP(
&history->session(),
data.ventities().value_or_empty())
@@ -511,7 +512,7 @@ HistoryMessage::HistoryMessage(
MessageGroupId::FromRaw(history->peer->id, groupedId->v));
}
if (const auto reactions = data.vreactions()) {
updateReactions(reactions);
setReactions(reactions);
}
applyTTL(data);
@@ -582,7 +583,7 @@ HistoryMessage::HistoryMessage(
&& (!originalMedia || !originalMedia->forceForwardedInfo()));
if (!dropForwardInfo) {
config.originalDate = original->dateOriginal();
if (const auto info = original->hiddenForwardedInfo()) {
if (const auto info = original->hiddenSenderInfo()) {
config.senderNameOriginal = info->name;
} else if (const auto senderOriginal = original->senderOriginal()) {
config.senderOriginal = senderOriginal->id;
@@ -768,6 +769,29 @@ HistoryMessage::HistoryMessage(
setEmptyText();
}
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId id,
Data::SponsoredFrom from,
const TextWithEntities &textWithEntities)
: HistoryItem(
history,
id,
((history->peer->isChannel() ? MessageFlag::Post : MessageFlag(0))
//| (from.peer ? MessageFlag::HasFromId : MessageFlag(0))
| MessageFlag::Local),
HistoryItem::NewMessageDate(0),
/*from.peer ? from.peer->id : */PeerId(0)) {
createComponentsHelper(
_flags,
MsgId(0), // replyTo
UserId(0), // viaBotId
QString(), // postAuthor
HistoryMessageMarkupData());
setText(textWithEntities);
setSponsoredFrom(from);
}
void HistoryMessage::createComponentsHelper(
MessageFlags flags,
MsgId replyTo,
@@ -1915,6 +1939,25 @@ void HistoryMessage::setUnreadRepliesCount(
Data::MessageUpdate::Flag::RepliesUnreadCount);
}
void HistoryMessage::setSponsoredFrom(const Data::SponsoredFrom &from) {
AddComponents(HistoryMessageSponsored::Bit());
const auto sponsored = Get<HistoryMessageSponsored>();
sponsored->sender = std::make_unique<HiddenSenderInfo>(
from.title,
false);
using Type = HistoryMessageSponsored::Type;
sponsored->type = from.isExactPost
? Type::Post
: from.isBot
? Type::Bot
: from.isBroadcast
? Type::Broadcast
: (from.peer && from.peer->isUser())
? Type::User
: Type::Group;
}
void HistoryMessage::setReplyToTop(MsgId replyToTop) {
const auto reply = Get<HistoryMessageReply>();
if (!reply

View File

@@ -14,6 +14,10 @@ struct SendAction;
struct SendOptions;
} // namespace Api
namespace Data {
struct SponsoredFrom;
} // namespace Data
namespace HistoryView {
class Message;
} // namespace HistoryView
@@ -115,6 +119,11 @@ public:
const QString &postAuthor,
not_null<GameData*> game,
HistoryMessageMarkupData &&markup); // local game
HistoryMessage(
not_null<History*> history,
MsgId id,
Data::SponsoredFrom from,
const TextWithEntities &textWithEntities); // sponsored
void refreshMedia(const MTPMessageMedia *media);
void refreshSentMedia(const MTPMessageMedia *media);
@@ -251,6 +260,7 @@ private:
void setUnreadRepliesCount(
not_null<HistoryMessageViews*> views,
int count);
void setSponsoredFrom(const Data::SponsoredFrom &from);
static void FillForwardedInfo(
CreateConfig &config,

View File

@@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
#include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
namespace {
@@ -111,31 +112,65 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto u = history()->owner().user(users[0].v);
if (u == _from) {
result.links.push_back(fromLink());
result.text = tr::lng_action_user_joined(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_user_joined(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.links.push_back(u->createOpenLink());
result.text = tr::lng_action_add_user(tr::now, lt_from, fromLinkText(), lt_user, textcmdLink(2, u->name));
result.text = tr::lng_action_add_user(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_user,
Ui::Text::Link(u->name, {}), // Link 2.
Ui::Text::WithEntities);
}
} else if (users.isEmpty()) {
result.links.push_back(fromLink());
result.text = tr::lng_action_add_user(tr::now, lt_from, fromLinkText(), lt_user, qsl("somebody"));
result.text = tr::lng_action_add_user(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_user,
{ .text = qsl("somebody") },
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
for (auto i = 0, l = int(users.size()); i != l; ++i) {
auto user = history()->owner().user(users[i].v);
result.links.push_back(user->createOpenLink());
auto linkText = textcmdLink(i + 2, user->name);
auto linkText = Ui::Text::Link(user->name, {});
if (i == 0) {
result.text = linkText;
} else if (i + 1 == l) {
result.text = tr::lng_action_add_users_and_last(tr::now, lt_accumulated, result.text, lt_user, linkText);
result.text = tr::lng_action_add_users_and_last(
tr::now,
lt_accumulated,
result.text,
lt_user,
linkText,
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_add_users_and_one(tr::now, lt_accumulated, result.text, lt_user, linkText);
result.text = tr::lng_action_add_users_and_one(
tr::now,
lt_accumulated,
result.text,
lt_user,
linkText,
Ui::Text::WithEntities);
}
}
result.text = tr::lng_action_add_users_many(tr::now, lt_from, fromLinkText(), lt_users, result.text);
result.text = tr::lng_action_add_users_many(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_users,
result.text,
Ui::Text::WithEntities);
}
return result;
};
@@ -143,24 +178,42 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) {
auto result = PreparedText{};
result.links.push_back(fromLink());
result.text = tr::lng_action_user_joined_by_link(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_user_joined_by_link(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
return result;
};
auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) {
auto result = PreparedText{};
result.links.push_back(fromLink());
result.text = tr::lng_action_created_chat(tr::now, lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle())));
result.text = tr::lng_action_created_chat(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_title,
{ .text = qs(action.vtitle()) },
Ui::Text::WithEntities);
return result;
};
auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) {
auto result = PreparedText {};
if (isPost()) {
result.text = tr::lng_action_created_channel(tr::now);
result.text = tr::lng_action_created_channel(
tr::now,
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_created_chat(tr::now, lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle())));
result.text = tr::lng_action_created_chat(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_title,
{ .text = qs(action.vtitle()) },
Ui::Text::WithEntities);
}
return result;
};
@@ -168,10 +221,16 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto prepareChatDeletePhoto = [this] {
auto result = PreparedText{};
if (isPost()) {
result.text = tr::lng_action_removed_photo_channel(tr::now);
result.text = tr::lng_action_removed_photo_channel(
tr::now,
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_removed_photo(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_removed_photo(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
}
return result;
};
@@ -180,12 +239,22 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto result = PreparedText{};
if (peerFromUser(action.vuser_id()) == _from->id) {
result.links.push_back(fromLink());
result.text = tr::lng_action_user_left(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_user_left(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
} else {
auto user = history()->owner().user(action.vuser_id().v);
result.links.push_back(fromLink());
result.links.push_back(user->createOpenLink());
result.text = tr::lng_action_kick_user(tr::now, lt_from, fromLinkText(), lt_user, textcmdLink(2, user->name));
result.text = tr::lng_action_kick_user(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_user,
Ui::Text::Link(user->name, {}), // Link 2.
Ui::Text::WithEntities);
}
return result;
};
@@ -193,10 +262,16 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) {
auto result = PreparedText{};
if (isPost()) {
result.text = tr::lng_action_changed_photo_channel(tr::now);
result.text = tr::lng_action_changed_photo_channel(
tr::now,
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_changed_photo(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_changed_photo(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
}
return result;
};
@@ -204,10 +279,20 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) {
auto result = PreparedText{};
if (isPost()) {
result.text = tr::lng_action_changed_title_channel(tr::now, lt_title, TextUtilities::Clean(qs(action.vtitle())));
result.text = tr::lng_action_changed_title_channel(
tr::now,
lt_title,
{ .text = (qs(action.vtitle())) },
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_changed_title(tr::now, lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle())));
result.text = tr::lng_action_changed_title(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_title,
{ .text = qs(action.vtitle()) },
Ui::Text::WithEntities);
}
return result;
};
@@ -215,17 +300,23 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto prepareScreenshotTaken = [this] {
auto result = PreparedText{};
if (out()) {
result.text = tr::lng_action_you_took_screenshot(tr::now);
result.text = tr::lng_action_you_took_screenshot(
tr::now,
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_took_screenshot(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_took_screenshot(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
}
return result;
};
auto prepareCustomAction = [&](const MTPDmessageActionCustomAction &action) {
auto result = PreparedText{};
result.text = qs(action.vmessage());
result.text = { .text = qs(action.vmessage()) };
return result;
};
@@ -235,7 +326,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
result.text = tr::lng_action_bot_allowed_from_domain(
tr::now,
lt_domain,
textcmdLink(qstr("http://") + domain, domain));
Ui::Text::Link(domain, qstr("http://") + domain),
Ui::Text::WithEntities);
return result;
};
@@ -272,16 +364,21 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
result.text = tr::lng_action_secure_values_sent(
tr::now,
lt_user,
textcmdLink(1, history()->peer->name),
Ui::Text::Link(history()->peer->name, {}), // Link 1.
lt_documents,
documents.join(", "));
{ .text = documents.join(", ") },
Ui::Text::WithEntities);
return result;
};
auto prepareContactSignUp = [this] {
auto result = PreparedText{};
result.links.push_back(fromLink());
result.text = tr::lng_action_user_registered(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_user_registered(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
return result;
};
@@ -313,28 +410,31 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return tr::lng_action_you_proximity_reached(
tr::now,
lt_distance,
distance,
{ .text = distance },
lt_user,
textcmdLink(1, toPeer->name));
Ui::Text::Link(toPeer->name, {}), // Link 1.
Ui::Text::WithEntities);
} else if (toId == selfId) {
result.links.push_back(fromPeer->createOpenLink());
return tr::lng_action_proximity_reached_you(
tr::now,
lt_from,
textcmdLink(1, fromPeer->name),
Ui::Text::Link(fromPeer->name, {}), // Link 1.
lt_distance,
distance);
{ .text = distance },
Ui::Text::WithEntities);
} else {
result.links.push_back(fromPeer->createOpenLink());
result.links.push_back(toPeer->createOpenLink());
return tr::lng_action_proximity_reached(
tr::now,
lt_from,
textcmdLink(1, fromPeer->name),
Ui::Text::Link(fromPeer->name, {}), // Link 1.
lt_distance,
distance,
{ .text = distance },
lt_user,
textcmdLink(2, toPeer->name));
Ui::Text::Link(toPeer->name, {}), // Link 2.
Ui::Text::WithEntities);
}
}();
return result;
@@ -358,26 +458,31 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
result.text = tr::lng_action_group_call_finished(
tr::now,
lt_duration,
text);
{ .text = text },
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_group_call_finished_group(
tr::now,
lt_from,
fromLinkText(),
fromLinkText(), // Link 1.
lt_duration,
text);
{ .text = text },
Ui::Text::WithEntities);
}
return result;
}
if (history()->peer->isBroadcast()) {
result.text = tr::lng_action_group_call_started_channel(tr::now);
result.text = tr::lng_action_group_call_started_channel(
tr::now,
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_group_call_started_group(
tr::now,
lt_from,
fromLinkText());
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
}
return result;
};
@@ -410,22 +515,44 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
: tr::lng_ttl_about_duration3(tr::now);
if (isPost()) {
if (!period) {
result.text = tr::lng_action_ttl_removed_channel(tr::now);
result.text = tr::lng_action_ttl_removed_channel(
tr::now,
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_ttl_changed_channel(tr::now, lt_duration, duration);
result.text = tr::lng_action_ttl_changed_channel(
tr::now,
lt_duration,
{ .text = duration },
Ui::Text::WithEntities);
}
} else if (_from->isSelf()) {
if (!period) {
result.text = tr::lng_action_ttl_removed_you(tr::now);
result.text = tr::lng_action_ttl_removed_you(
tr::now,
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_ttl_changed_you(tr::now, lt_duration, duration);
result.text = tr::lng_action_ttl_changed_you(
tr::now,
lt_duration,
{ .text = duration },
Ui::Text::WithEntities);
}
} else {
result.links.push_back(fromLink());
if (!period) {
result.text = tr::lng_action_ttl_removed(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_ttl_removed(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_ttl_changed(tr::now, lt_from, fromLinkText(), lt_duration, duration);
result.text = tr::lng_action_ttl_changed(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_duration,
{ .text = duration },
Ui::Text::WithEntities);
}
}
return result;
@@ -435,22 +562,34 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto result = PreparedText{};
const auto text = qs(action.vemoticon());
if (!text.isEmpty()) {
if (isPost()) {
result.text = tr::lng_action_theme_changed_channel(tr::now, lt_emoji, text);
} else if (_from->isSelf()) {
result.text = tr::lng_action_you_theme_changed(tr::now, lt_emoji, text);
if (_from->isSelf()) {
result.text = tr::lng_action_you_theme_changed(
tr::now,
lt_emoji,
{ .text = text },
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_theme_changed(tr::now, lt_from, fromLinkText(), lt_emoji, text);
result.text = tr::lng_action_theme_changed(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_emoji,
{ .text = text },
Ui::Text::WithEntities);
}
} else {
if (isPost()) {
result.text = tr::lng_action_theme_disabled_channel(tr::now);
} else if (_from->isSelf()) {
result.text = tr::lng_action_you_theme_disabled(tr::now);
if (_from->isSelf()) {
result.text = tr::lng_action_you_theme_disabled(
tr::now,
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_theme_disabled(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_theme_disabled(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
}
}
return result;
@@ -459,7 +598,11 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto prepareChatJoinedByRequest = [this](const MTPDmessageActionChatJoinedByRequest &action) {
auto result = PreparedText{};
result.links.push_back(fromLink());
result.text = tr::lng_action_user_joined_by_request(tr::now, lt_from, fromLinkText());
result.text = tr::lng_action_user_joined_by_request(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
return result;
};
@@ -508,10 +651,14 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return prepareProximityReached(data);
}, [](const MTPDmessageActionPaymentSentMe &) {
LOG(("API Error: messageActionPaymentSentMe received."));
return PreparedText{ tr::lng_message_empty(tr::now) };
return PreparedText{
tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
};
}, [](const MTPDmessageActionSecureValuesSentMe &) {
LOG(("API Error: messageActionSecureValuesSentMe received."));
return PreparedText{ tr::lng_message_empty(tr::now) };
return PreparedText{
tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
};
}, [&](const MTPDmessageActionGroupCall &data) {
return prepareGroupCall(data);
}, [&](const MTPDmessageActionInviteToGroupCall &data) {
@@ -525,7 +672,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
}, [&](const MTPDmessageActionChatJoinedByRequest &data) {
return prepareChatJoinedByRequest(data);
}, [](const MTPDmessageActionEmpty &) {
return PreparedText{ tr::lng_message_empty(tr::now) };
return PreparedText{
tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
};
});
setServiceText(messageText);
@@ -642,36 +791,73 @@ HistoryService::PreparedText HistoryService::prepareInvitedToCallText(
const QVector<MTPlong> &users,
CallId linkCallId) {
const auto owner = &history()->owner();
auto chatText = tr::lng_action_invite_user_chat(tr::now);
auto chatText = tr::lng_action_invite_user_chat(
tr::now,
Ui::Text::WithEntities);
auto result = PreparedText{};
result.links.push_back(fromLink());
auto linkIndex = 1;
if (linkCallId) {
const auto peer = history()->peer;
result.links.push_back(GroupCallClickHandler(peer, linkCallId));
chatText = textcmdLink(++linkIndex, chatText);
chatText = Ui::Text::Link(chatText.text, {});
}
if (users.size() == 1) {
auto user = owner->user(users[0].v);
result.links.push_back(user->createOpenLink());
result.text = tr::lng_action_invite_user(tr::now, lt_from, fromLinkText(), lt_user, textcmdLink(++linkIndex, user->name), lt_chat, chatText);
result.text = tr::lng_action_invite_user(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_user,
Ui::Text::Link(user->name, {}), // Link N.
lt_chat,
chatText,
Ui::Text::WithEntities);
} else if (users.isEmpty()) {
result.text = tr::lng_action_invite_user(tr::now, lt_from, fromLinkText(), lt_user, qsl("somebody"), lt_chat, chatText);
result.text = tr::lng_action_invite_user(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_user,
{ .text = qsl("somebody") },
lt_chat,
chatText,
Ui::Text::WithEntities);
} else {
for (auto i = 0, l = int(users.size()); i != l; ++i) {
auto user = owner->user(users[i].v);
result.links.push_back(user->createOpenLink());
auto linkText = textcmdLink(++linkIndex, user->name);
auto linkText = Ui::Text::Link(user->name, {});
if (i == 0) {
result.text = linkText;
} else if (i + 1 == l) {
result.text = tr::lng_action_invite_users_and_last(tr::now, lt_accumulated, result.text, lt_user, linkText);
result.text = tr::lng_action_invite_users_and_last(
tr::now,
lt_accumulated,
result.text,
lt_user,
linkText,
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_invite_users_and_one(tr::now, lt_accumulated, result.text, lt_user, linkText);
result.text = tr::lng_action_invite_users_and_one(
tr::now,
lt_accumulated,
result.text,
lt_user,
linkText,
Ui::Text::WithEntities);
}
}
result.text = tr::lng_action_invite_users_many(tr::now, lt_from, fromLinkText(), lt_users, result.text, lt_chat, chatText);
result.text = tr::lng_action_invite_users_many(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_users,
result.text,
lt_chat,
chatText,
Ui::Text::WithEntities);
}
return result;
}
@@ -696,38 +882,63 @@ HistoryService::PreparedText HistoryService::preparePinnedText() {
result.links.push_back(fromLink());
result.links.push_back(pinned->lnk);
if (mediaText.isEmpty()) {
auto original = TextUtilities::TextWithSpoilerCommands(
pinned->msg->originalText());
auto original = pinned->msg->originalText();
auto cutAt = 0;
auto limit = kPinnedMessageTextLimit;
auto size = original.size();
auto size = original.text.size();
for (; limit != 0;) {
--limit;
if (cutAt >= size) break;
if (original.at(cutAt).isLowSurrogate() && cutAt + 1 < size && original.at(cutAt + 1).isHighSurrogate()) {
if (original.text.at(cutAt).isLowSurrogate()
&& (cutAt + 1 < size)
&& original.text.at(cutAt + 1).isHighSurrogate()) {
cutAt += 2;
} else {
++cutAt;
}
}
if (!limit && cutAt + 5 < size) {
original = TextUtilities::CutTextWithCommands(
std::move(original),
cutAt,
textcmdStartSpoiler(),
textcmdStopSpoiler());
original = Ui::Text::Mid(original, 0, cutAt).append(
Ui::kQEllipsis);
}
result.text = tr::lng_action_pinned_message(tr::now, lt_from, fromLinkText(), lt_text, textcmdLink(2, original));
original = Ui::Text::Wrapped(
std::move(original),
EntityType::CustomUrl);
result.text = tr::lng_action_pinned_message(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_text,
std::move(original), // Link 2.
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_pinned_media(tr::now, lt_from, fromLinkText(), lt_media, textcmdLink(2, mediaText));
result.text = tr::lng_action_pinned_media(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_media,
Ui::Text::Link(mediaText, {}), // Link 2.
Ui::Text::WithEntities);
}
} else if (pinned && pinned->msgId) {
result.links.push_back(fromLink());
result.links.push_back(pinned->lnk);
result.text = tr::lng_action_pinned_media(tr::now, lt_from, fromLinkText(), lt_media, textcmdLink(2, tr::lng_contacts_loading(tr::now)));
result.text = tr::lng_action_pinned_media(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_media,
Ui::Text::Link(tr::lng_contacts_loading(tr::now), {}), // Link 2.
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_pinned_media(tr::now, lt_from, fromLinkText(), lt_media, tr::lng_deleted_message(tr::now));
result.text = tr::lng_action_pinned_media(
tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_media,
{ .text = tr::lng_deleted_message(tr::now) },
Ui::Text::WithEntities);
}
return result;
}
@@ -736,7 +947,7 @@ HistoryService::PreparedText HistoryService::prepareGameScoreText() {
auto result = PreparedText {};
auto gamescore = Get<HistoryServiceGameScore>();
auto computeGameTitle = [&]() -> QString {
auto computeGameTitle = [&]() -> TextWithEntities {
if (gamescore && gamescore->msg) {
if (const auto media = gamescore->msg->media()) {
if (const auto game = media->game()) {
@@ -749,51 +960,55 @@ HistoryService::PreparedText HistoryService::prepareGameScoreText() {
column,
gamescore->msg->fullId()));
auto titleText = game->title;
return textcmdLink(result.links.size(), titleText);
return Ui::Text::Link(titleText, {});
}
}
return tr::lng_deleted_message(tr::now);
return tr::lng_deleted_message(tr::now, Ui::Text::WithEntities);
} else if (gamescore && gamescore->msgId) {
return tr::lng_contacts_loading(tr::now);
return tr::lng_contacts_loading(tr::now, Ui::Text::WithEntities);
}
return QString();
return {};
};
const auto scoreNumber = gamescore ? gamescore->score : 0;
if (_from->isSelf()) {
auto gameTitle = computeGameTitle();
if (gameTitle.isEmpty()) {
if (gameTitle.text.isEmpty()) {
result.text = tr::lng_action_game_you_scored_no_game(
tr::now,
lt_count,
scoreNumber);
scoreNumber,
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_game_you_scored(
tr::now,
lt_count,
scoreNumber,
lt_game,
gameTitle);
gameTitle,
Ui::Text::WithEntities);
}
} else {
result.links.push_back(fromLink());
auto gameTitle = computeGameTitle();
if (gameTitle.isEmpty()) {
if (gameTitle.text.isEmpty()) {
result.text = tr::lng_action_game_score_no_game(
tr::now,
lt_count,
scoreNumber,
lt_from,
fromLinkText());
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_game_score(
tr::now,
lt_count,
scoreNumber,
lt_from,
fromLinkText(),
fromLinkText(), // Link 1.
lt_game,
gameTitle);
gameTitle,
Ui::Text::WithEntities);
}
}
return result;
@@ -808,17 +1023,31 @@ HistoryService::PreparedText HistoryService::preparePaymentSentText() {
if (payment->msg) {
if (const auto media = payment->msg->media()) {
if (const auto invoice = media->invoice()) {
return textcmdLink(1, invoice->title);
return Ui::Text::Link(invoice->title, {});
}
}
}
return QString();
return TextWithEntities();
}();
if (invoiceTitle.isEmpty()) {
result.text = tr::lng_action_payment_done(tr::now, lt_amount, payment->amount, lt_user, history()->peer->name);
if (invoiceTitle.text.isEmpty()) {
result.text = tr::lng_action_payment_done(
tr::now,
lt_amount,
{ .text = payment->amount },
lt_user,
{ .text = history()->peer->name },
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_payment_done_for(tr::now, lt_amount, payment->amount, lt_user, history()->peer->name, lt_invoice, invoiceTitle);
result.text = tr::lng_action_payment_done_for(
tr::now,
lt_amount,
{ .text = payment->amount },
lt_user,
{ .text = history()->peer->name },
lt_invoice,
invoiceTitle,
Ui::Text::WithEntities);
if (payment->msg) {
result.links.push_back(payment->lnk);
}
@@ -843,15 +1072,17 @@ HistoryService::PreparedText HistoryService::prepareCallScheduledText(
result.text = tr::lng_action_group_call_scheduled_channel(
tr::now,
lt_date,
date);
{ .text = date },
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
result.text = tr::lng_action_group_call_scheduled_group(
tr::now,
lt_from,
fromLinkText(),
fromLinkText(), // Link 1.
lt_date,
date);
{ .text = date },
Ui::Text::WithEntities);
}
};
const auto time = scheduled.time().toString(cTimeFormat());
@@ -952,21 +1183,21 @@ ItemPreview HistoryService::toPreview(ToPreviewOptions options) const {
// Because larger version is shown exactly to the left of the preview.
//auto media = _media ? _media->toPreview(options) : ItemPreview();
return {
.text = textcmdLink(
1,
TextUtilities::Clean(notificationText(), true)),
.text = Ui::Text::Wrapped(notificationText(), EntityType::PlainLink),
//.images = std::move(media.images),
//.loadingContext = std::move(media.loadingContext),
};
}
QString HistoryService::inReplyText() const {
const auto result = HistoryService::notificationText();
TextWithEntities HistoryService::inReplyText() const {
auto result = HistoryService::notificationText();
const auto &name = author()->name;
const auto text = result.trimmed().startsWith(name)
? result.trimmed().mid(name.size()).trimmed()
: result;
return textcmdLink(1, text);
TextUtilities::Trim(result);
if (result.text.startsWith(name)) {
result = Ui::Text::Mid(result, name.size());
TextUtilities::Trim(result);
}
return Ui::Text::Wrapped(result, EntityType::PlainLink);
}
std::unique_ptr<HistoryView::Element> HistoryService::createView(
@@ -975,8 +1206,8 @@ std::unique_ptr<HistoryView::Element> HistoryService::createView(
return delegate->elementCreate(this, replacing);
}
QString HistoryService::fromLinkText() const {
return textcmdLink(1, _from->name);
TextWithEntities HistoryService::fromLinkText() const {
return Ui::Text::Link(_from->name, {});
}
ClickHandlerPtr HistoryService::fromLink() const {
@@ -984,7 +1215,7 @@ ClickHandlerPtr HistoryService::fromLink() const {
}
void HistoryService::setServiceText(const PreparedText &prepared) {
_text.setText(
_text.setMarkedText(
st::serviceTextStyle,
prepared.text,
Ui::ItemTextServiceOptions());
@@ -1022,7 +1253,7 @@ crl::time HistoryService::getSelfDestructIn(crl::time now) {
}
Unexpected("Type in HistoryServiceSelfDestruct::Type");
};
setServiceText({ text() });
setServiceText({ TextWithEntities{ .text = text() } });
return 0;
}
return selfdestruct->destructAt - now;
@@ -1045,15 +1276,23 @@ void HistoryService::createFromMtp(const MTPDmessage &message) {
setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, ttl->v);
if (out()) {
setServiceText({ tr::lng_ttl_photo_sent(tr::now) });
setServiceText({
tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities)
});
} else {
auto result = PreparedText();
result.links.push_back(fromLink());
result.text = tr::lng_ttl_photo_received(tr::now, lt_from, fromLinkText());
result.text = tr::lng_ttl_photo_received(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
setServiceText(std::move(result));
}
} else {
setServiceText({ tr::lng_ttl_photo_expired(tr::now) });
setServiceText({
tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities)
});
}
} break;
case mtpc_messageMediaDocument: {
@@ -1064,15 +1303,23 @@ void HistoryService::createFromMtp(const MTPDmessage &message) {
setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, ttl->v);
if (out()) {
setServiceText({ tr::lng_ttl_video_sent(tr::now) });
setServiceText({
tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities)
});
} else {
auto result = PreparedText();
result.links.push_back(fromLink());
result.text = tr::lng_ttl_video_received(tr::now, lt_from, fromLinkText());
result.text = tr::lng_ttl_video_received(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
setServiceText(std::move(result));
}
} else {
setServiceText({ tr::lng_ttl_video_expired(tr::now) });
setServiceText({
tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities)
});
}
} break;
@@ -1268,11 +1515,14 @@ HistoryService::PreparedText GenerateJoinedText(
: tr::lng_action_add_you)(
tr::now,
lt_from,
textcmdLink(1, inviter->name));
Ui::Text::Link(inviter->name, {}),
Ui::Text::WithEntities);
return result;
} else if (history->peer->isMegagroup()) {
if (viaRequest) {
return { tr::lng_action_you_joined_by_request(tr::now) };
return { tr::lng_action_you_joined_by_request(
tr::now,
Ui::Text::WithEntities) };
}
auto self = history->session().user();
auto result = HistoryService::PreparedText{};
@@ -1280,12 +1530,15 @@ HistoryService::PreparedText GenerateJoinedText(
result.text = tr::lng_action_user_joined(
tr::now,
lt_from,
textcmdLink(1, self->name));
Ui::Text::Link(self->name, {}),
Ui::Text::WithEntities);
return result;
}
return { viaRequest
? tr::lng_action_you_joined_by_request_channel(tr::now)
: tr::lng_action_you_joined(tr::now) };
? tr::lng_action_you_joined_by_request_channel(
tr::now,
Ui::Text::WithEntities)
: tr::lng_action_you_joined(tr::now, Ui::Text::WithEntities) };
}
not_null<HistoryService*> GenerateJoinedMessage(

View File

@@ -63,7 +63,7 @@ class ServiceMessagePainter;
class HistoryService : public HistoryItem {
public:
struct PreparedText {
QString text;
TextWithEntities text;
QList<ClickHandlerPtr> links;
};
@@ -112,7 +112,7 @@ public:
return true;
}
ItemPreview toPreview(ToPreviewOptions options) const override;
QString inReplyText() const override;
TextWithEntities inReplyText() const override;
std::unique_ptr<HistoryView::Element> createView(
not_null<HistoryView::ElementDelegate*> delegate,
@@ -129,7 +129,7 @@ protected:
void markMediaAsReadHook() override;
QString fromLinkText() const;
TextWithEntities fromLinkText() const;
ClickHandlerPtr fromLink() const;
void removeMedia();

View File

@@ -479,18 +479,6 @@ HistoryWidget::HistoryWidget(
Window::ActivateWindow(controller);
});
controller->adaptive().changes(
) | rpl::start_with_next([=] {
if (_history) {
_history->forceFullResize();
if (_migrated) {
_migrated->forceFullResize();
}
updateHistoryGeometry();
update();
}
}, lifetime());
session().data().newItemAdded(
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
newItemAdded(item);
@@ -503,7 +491,10 @@ HistoryWidget::HistoryWidget(
session().data().viewResizeRequest(
) | rpl::start_with_next([=](not_null<HistoryView::Element*> view) {
if (view->data()->mainView() == view) {
const auto item = view->data();
const auto history = item->history();
if (item->mainView() == view
&& (history == _history || history == _migrated)) {
updateHistoryGeometry();
}
}, lifetime());
@@ -2068,9 +2059,6 @@ void HistoryWidget::showHistory(
_historyInited = false;
_contactStatus = nullptr;
// Unload lottie animations.
session().data().unloadHeavyViewParts(HistoryInner::ElementDelegate());
if (peerId) {
_peer = session().data().peer(peerId);
_canSendMessages = _peer->canWrite();
@@ -2151,6 +2139,16 @@ void HistoryWidget::showHistory(
object_ptr<HistoryInner>(this, _scroll, controller(), _history));
_list->show();
controller()->adaptive().changes(
) | rpl::start_with_next([=] {
_history->forceFullResize();
if (_migrated) {
_migrated->forceFullResize();
}
updateHistoryGeometry();
update();
}, _list->lifetime());
if (_chooseForReport && _chooseForReport->active) {
_list->setChooseReportReason(_chooseForReport->reason);
}
@@ -2242,6 +2240,21 @@ void HistoryWidget::setHistory(History *history) {
if (_history == history) {
return;
}
const auto wasHistory = base::take(_history);
const auto wasMigrated = base::take(_migrated);
// Unload lottie animations.
const auto unloadHeavyViewParts = [](History *history) {
if (history) {
history->owner().unloadHeavyViewParts(
history->delegateMixin()->delegate());
history->forceFullResize();
}
};
unloadHeavyViewParts(wasHistory);
unloadHeavyViewParts(wasMigrated);
unregisterDraftSources();
_history = history;
_migrated = _history ? _history->migrateFrom() : nullptr;
@@ -6693,7 +6706,7 @@ void HistoryWidget::updatePreview() {
auto linkText = QStringView(_previewLinks).split(' ').at(0).toString();
_previewDescription.setText(
st::messageTextStyle,
TextUtilities::Clean(linkText),
linkText,
Ui::DialogTextOptions());
const auto timeout = (_previewData->pendingTill - base::unixtime::now());
@@ -6714,7 +6727,7 @@ void HistoryWidget::updatePreview() {
Ui::NameTextOptions());
_previewDescription.setText(
st::messageTextStyle,
TextUtilities::Clean(preview.description),
preview.description,
Ui::DialogTextOptions());
}
} else if (!readyToForward() && !replyToId() && !_editMsgId) {
@@ -6939,7 +6952,7 @@ void HistoryWidget::messageDataReceived(
}
void HistoryWidget::updateReplyEditText(not_null<HistoryItem*> item) {
_replyEditMsgText.setText(
_replyEditMsgText.setMarkedText(
st::messageTextStyle,
item->inReplyText(),
Ui::DialogTextOptions());
@@ -6987,7 +7000,8 @@ void HistoryWidget::updateForwarding() {
void HistoryWidget::updateForwardingTexts() {
int32 version = 0;
QString from, text;
QString from;
TextWithEntities text;
const auto keepNames = (_toForward.options
== Data::ForwardOptions::PreserveInfo);
const auto keepCaptions = (_toForward.options
@@ -7006,7 +7020,7 @@ void HistoryWidget::updateForwardingTexts() {
fullname = from->name;
}
version += from->nameVersion;
} else if (const auto info = item->hiddenForwardedInfo()) {
} else if (const auto info = item->hiddenSenderInfo()) {
if (!insertedNames.contains(info->name)) {
insertedNames.emplace(info->name);
names.push_back(info->firstName);
@@ -7034,13 +7048,12 @@ void HistoryWidget::updateForwardingTexts() {
.generateImages = false,
}).text;
} else {
text = textcmdLink(
1,
text = Ui::Text::PlainLink(
tr::lng_forward_messages(tr::now, lt_count, count));
}
}
_toForwardFrom.setText(st::msgNameStyle, from, Ui::NameTextOptions());
_toForwardText.setText(
_toForwardText.setMarkedText(
st::messageTextStyle,
text,
Ui::DialogTextOptions());
@@ -7058,7 +7071,7 @@ void HistoryWidget::checkForwardingInfo() {
for (const auto item : _toForward.items) {
if (const auto from = item->senderOriginal()) {
version += from->nameVersion;
} else if (const auto info = item->hiddenForwardedInfo()) {
} else if (const auto info = item->hiddenSenderInfo()) {
++version;
} else {
Unexpected("Corrupt forwarded information in message.");

View File

@@ -276,7 +276,7 @@ void FieldHeader::init() {
) | rpl::start_with_next([=](const auto &d) {
_preview.description.setText(
st::messageTextStyle,
TextUtilities::Clean(d),
d,
Ui::DialogTextOptions());
}, lifetime());
@@ -325,7 +325,7 @@ void FieldHeader::init() {
void FieldHeader::updateShownMessageText() {
Expects(_shownMessage != nullptr);
_shownMessageText.setText(
_shownMessageText.setMarkedText(
st::messageTextStyle,
_shownMessage->inReplyText(),
Ui::DialogTextOptions());

View File

@@ -17,7 +17,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/view/history_view_message.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_react_animation.h"
#include "core/click_handler_types.h"
#include "main/main_session.h"
#include "lottie/lottie_icon.h"
#include "data/data_session.h"
#include "data/data_message_reactions.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h"
@@ -31,6 +37,8 @@ BottomInfo::BottomInfo(
layout();
}
BottomInfo::~BottomInfo() = default;
void BottomInfo::update(Data &&data, int availableWidth) {
_data = std::move(data);
layout();
@@ -103,6 +111,10 @@ TextState BottomInfo::textState(
not_null<const HistoryItem*> item,
QPoint position) const {
auto result = TextState(item);
if (const auto link = revokeReactionLink(item, position)) {
result.link = link;
return result;
}
const auto inTime = QRect(
width() - _dateWidth,
0,
@@ -115,6 +127,75 @@ TextState BottomInfo::textState(
return result;
}
ClickHandlerPtr BottomInfo::revokeReactionLink(
not_null<const HistoryItem*> item,
QPoint position) const {
if (_reactions.empty()) {
return nullptr;
}
auto left = 0;
auto top = 0;
auto available = width();
if (height() != minHeight()) {
available = std::min(available, _reactionsMaxWidth);
left += width() - available;
top += st::msgDateFont->height;
}
auto x = left;
auto y = top;
auto widthLeft = available;
for (const auto &reaction : _reactions) {
const auto chosen = (reaction.emoji == _data.chosenReaction);
const auto add = (reaction.countTextWidth > 0)
? st::reactionInfoDigitSkip
: st::reactionInfoBetween;
const auto width = st::reactionInfoSize
+ (reaction.countTextWidth > 0
? (st::reactionInfoSkip + reaction.countTextWidth)
: 0);
if (x > left && widthLeft < width) {
x = left;
y += st::msgDateFont->height;
widthLeft = available;
}
const auto image = QRect(
x,
y,
st::reactionInfoSize,
st::msgDateFont->height);
if (chosen && image.contains(position)) {
if (!_revokeLink) {
_revokeLink = revokeReactionLink(item);
}
return _revokeLink;
}
x += width + add;
widthLeft -= width + add;
}
return nullptr;
}
ClickHandlerPtr BottomInfo::revokeReactionLink(
not_null<const HistoryItem*> item) const {
const auto itemId = item->fullId();
const auto sessionId = item->history()->session().uniqueId();
return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
if (controller->session().uniqueId() == sessionId) {
auto &owner = controller->session().data();
if (const auto item = owner.message(itemId)) {
const auto chosen = item->chosenReaction();
if (!chosen.isEmpty()) {
item->toggleReaction(chosen);
}
}
}
}
});
}
bool BottomInfo::isSignedAuthorElided() const {
return _authorElided;
}
@@ -159,6 +240,17 @@ void BottomInfo::paint(
authorEditedWidth,
outerWidth);
if (_data.flags & Data::Flag::Pinned) {
const auto &icon = inverted
? st->historyPinInvertedIcon()
: stm->historyPinIcon;
right -= st::historyPinWidth;
icon.paint(
p,
right,
firstLineBottom + st::historyPinTop,
outerWidth);
}
if (!_views.isEmpty()) {
const auto viewsWidth = _views.maxWidth();
right -= st::historyViewsSpace + viewsWidth;
@@ -167,9 +259,10 @@ void BottomInfo::paint(
const auto &icon = inverted
? st->historyViewsInvertedIcon()
: stm->historyViewsIcon;
right -= st::historyViewsWidth;
icon.paint(
p,
right - st::historyViewsWidth,
right,
firstLineBottom + st::historyViewsTop,
outerWidth);
}
@@ -181,9 +274,10 @@ void BottomInfo::paint(
const auto &icon = inverted
? st->historyRepliesInvertedIcon()
: stm->historyRepliesIcon;
right -= st::historyViewsWidth;
icon.paint(
p,
right - st::historyViewsWidth,
right,
firstLineBottom + st::historyViewsTop,
outerWidth);
}
@@ -208,19 +302,30 @@ void BottomInfo::paint(
left += width() - available;
top += st::msgDateFont->height;
}
paintReactions(p, left, top, available);
paintReactions(p, position, left, top, available, context);
}
}
void BottomInfo::paintReactions(
Painter &p,
QPoint origin,
int left,
int top,
int availableWidth) const {
int availableWidth,
const PaintContext &context) const {
auto x = left;
auto y = top;
auto widthLeft = availableWidth;
const auto animated = _reactionAnimation
? _reactionAnimation->playingAroundEmoji()
: QString();
if (_reactionAnimation
&& context.reactionEffects
&& animated.isEmpty()) {
_reactionAnimation = nullptr;
}
for (const auto &reaction : _reactions) {
const auto animating = (reaction.emoji == animated);
const auto add = (reaction.countTextWidth > 0)
? st::reactionInfoDigitSkip
: st::reactionInfoBetween;
@@ -238,11 +343,20 @@ void BottomInfo::paintReactions(
reaction.emoji,
::Data::Reactions::ImageSize::BottomInfo);
}
if (!reaction.image.isNull()) {
p.drawImage(
x,
y + (st::msgDateFont->height - st::reactionInfoSize) / 2,
reaction.image);
const auto image = QRect(
x + (st::reactionInfoSize - st::reactionInfoImage) / 2,
y + (st::msgDateFont->height - st::reactionInfoImage) / 2,
st::reactionInfoImage,
st::reactionInfoImage);
const auto skipImage = animating
&& (reaction.count < 2 || !_reactionAnimation->flying());
if (!reaction.image.isNull() && !skipImage) {
p.drawImage(image.topLeft(), reaction.image);
}
if (animating) {
context.reactionEffects->paint = [=](QPainter &p) {
return _reactionAnimation->paintGetArea(p, origin, image);
};
}
if (reaction.countTextWidth > 0) {
p.drawText(
@@ -279,7 +393,7 @@ void BottomInfo::layoutDateText() {
? (tr::lng_edited(tr::now) + ' ')
: QString();
const auto author = _data.author;
const auto prefix = author.isEmpty() ? qsl(", ") : QString();
const auto prefix = !author.isEmpty() ? qsl(", ") : QString();
const auto date = edited + _data.date.toString(cTimeFormat());
_dateWidth = st::msgDateFont->width(date);
const auto afterAuthor = prefix + date;
@@ -326,6 +440,11 @@ void BottomInfo::layoutRepliesText() {
}
void BottomInfo::layoutReactionsText() {
if (_reactionAnimation
&& !_data.reactions.contains(
_reactionAnimation->playingAroundEmoji())) {
_reactionAnimation = nullptr;
}
if (_data.reactions.empty()) {
_reactions.clear();
return;
@@ -365,6 +484,9 @@ QSize BottomInfo::countOptimalSize() {
+ _replies.maxWidth()
+ st::historyViewsWidth;
}
if (_data.flags & Data::Flag::Pinned) {
width += st::historyPinWidth;
}
_reactionsMaxWidth = countReactionsMaxWidth();
width += _reactionsMaxWidth;
return QSize(width, st::msgDateFont->height);
@@ -390,6 +512,26 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) {
: 0;
}
void BottomInfo::animateReactionSend(
SendReactionAnimationArgs &&args,
Fn<void()> repaint) {
_reactionAnimation = std::make_unique<Reactions::SendAnimation>(
_reactionsOwner,
args.translated(QPoint(width(), height())),
std::move(repaint),
st::reactionInfoImage);
}
auto BottomInfo::takeSendReactionAnimation()
-> std::unique_ptr<Reactions::SendAnimation> {
return std::move(_reactionAnimation);
}
void BottomInfo::continueSendReactionAnimation(
std::unique_ptr<Reactions::SendAnimation> animation) {
_reactionAnimation = std::move(animation);
}
BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
using Flag = BottomInfo::Data::Flag;
const auto item = message->message();
@@ -398,6 +540,7 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
result.date = message->dateTime();
if (message->embedReactionsInBottomInfo()) {
result.reactions = item->reactions();
result.chosenReaction = item->chosenReaction();
}
if (message->hasOutLayout()) {
result.flags |= Flag::OutLayout;
@@ -408,6 +551,9 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
if (item->isSponsored()) {
result.flags |= Flag::Sponsored;
}
if (item->isPinned() && message->context() != Context::Pinned) {
result.flags |= Flag::Pinned;
}
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
if (!msgsigned->isAnonymousRank) {
result.author = msgsigned->author;

View File

@@ -20,11 +20,15 @@ class Reactions;
} // namespace Data
namespace HistoryView {
namespace Reactions {
class SendAnimation;
} // namespace Reactions
using PaintContext = Ui::ChatPaintContext;
class Message;
struct TextState;
struct SendReactionAnimationArgs;
class BottomInfo final : public Object {
public:
@@ -35,6 +39,7 @@ public:
Sending = 0x04,
RepliesContext = 0x08,
Sponsored = 0x10,
Pinned = 0x20,
//Unread, // We don't want to pass and update it in Date for now.
};
friend inline constexpr bool is_flag_type(Flag) { return true; };
@@ -43,11 +48,13 @@ public:
QDateTime date;
QString author;
base::flat_map<QString, int> reactions;
QString chosenReaction;
std::optional<int> views;
std::optional<int> replies;
Flags flags;
};
BottomInfo(not_null<::Data::Reactions*> reactionsOwner, Data &&data);
~BottomInfo();
void update(Data &&data, int availableWidth);
@@ -66,6 +73,14 @@ public:
bool inverted,
const PaintContext &context) const;
void animateReactionSend(
SendReactionAnimationArgs &&args,
Fn<void()> repaint);
[[nodiscard]] auto takeSendReactionAnimation()
-> std::unique_ptr<Reactions::SendAnimation>;
void continueSendReactionAnimation(
std::unique_ptr<Reactions::SendAnimation> animation);
private:
struct Reaction {
mutable QImage image;
@@ -85,15 +100,22 @@ private:
[[nodiscard]] int countReactionsHeight(int newWidth) const;
void paintReactions(
Painter &p,
QPoint origin,
int left,
int top,
int availableWidth) const;
int availableWidth,
const PaintContext &context) const;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
void setReactionCount(Reaction &reaction, int count);
[[nodiscard]] Reaction prepareReactionWithEmoji(const QString &emoji);
[[nodiscard]] ClickHandlerPtr revokeReactionLink(
not_null<const HistoryItem*> item,
QPoint position) const;
[[nodiscard]] ClickHandlerPtr revokeReactionLink(
not_null<const HistoryItem*> item) const;
const not_null<::Data::Reactions*> _reactionsOwner;
Data _data;
@@ -101,6 +123,8 @@ private:
Ui::Text::String _views;
Ui::Text::String _replies;
std::vector<Reaction> _reactions;
mutable ClickHandlerPtr _revokeLink;
mutable std::unique_ptr<Reactions::SendAnimation> _reactionAnimation;
int _reactionsMaxWidth = 0;
int _dateWidth = 0;
bool _authorElided = false;

View File

@@ -44,7 +44,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "data/data_scheduled_messages.h"
#include "data/data_message_reactions.h"
#include "core/file_utilities.h"
#include "core/click_handler_types.h"
#include "base/platform/base_platform_info.h"
#include "window/window_peer_menu.h"
#include "window/window_controller.h"
@@ -921,12 +923,17 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
const auto view = request.view;
const auto item = request.item;
const auto itemId = item ? item->fullId() : FullMsgId();
const auto rawLink = link.get();
const auto linkPhoto = dynamic_cast<PhotoClickHandler*>(rawLink);
const auto linkDocument = dynamic_cast<DocumentClickHandler*>(rawLink);
const auto photo = linkPhoto ? linkPhoto->photo().get() : nullptr;
const auto document = linkDocument
? linkDocument->document().get()
const auto lnkPhotoId = PhotoId(link
? link->property(kPhotoLinkMediaIdProperty).toULongLong()
: 0);
const auto lnkDocumentId = DocumentId(link
? link->property(kDocumentLinkMediaIdProperty).toULongLong()
: 0);
const auto photo = lnkPhotoId
? list->session().data().photo(lnkPhotoId).get()
: nullptr;
const auto document = lnkDocumentId
? list->session().data().document(lnkDocumentId).get()
: nullptr;
const auto poll = item
? (item->media() ? item->media()->poll() : nullptr)
@@ -951,9 +958,9 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
}
AddTopMessageActions(result, request, list);
if (linkPhoto) {
if (photo) {
AddPhotoActions(result, photo, item, list);
} else if (linkDocument) {
} else if (document) {
AddDocumentActions(result, document, item, list);
} else if (poll) {
AddPollActions(result, poll, item, list->elementContext());
@@ -1101,6 +1108,67 @@ void AddWhoReactedAction(
showAllChosen));
}
void ShowWhoReactedMenu(
not_null<base::unique_qptr<Ui::PopupMenu>*> menu,
QPoint position,
not_null<QWidget*> context,
not_null<HistoryItem*> item,
const QString &emoji,
not_null<Window::SessionController*> controller,
rpl::lifetime &lifetime) {
const auto participantChosen = [=](uint64 id) {
controller->showPeerInfo(PeerId(id));
};
const auto showAllChosen = [=, itemId = item->fullId()]{
if (const auto item = controller->session().data().message(itemId)) {
controller->window().show(ReactionsListBox(
controller,
item,
emoji));
}
};
const auto reactions = &controller->session().data().reactions();
const auto &list = reactions->list(
Data::Reactions::Type::Active);
const auto active = ranges::contains(
list,
emoji,
&Data::Reaction::emoji);
const auto filler = lifetime.make_state<Ui::WhoReactedListMenu>(
participantChosen,
showAllChosen);
Api::WhoReacted(
item,
emoji,
context,
st::defaultWhoRead
) | rpl::filter([=](const Ui::WhoReadContent &content) {
return !content.unknown;
}) | rpl::start_with_next([=, &lifetime](Ui::WhoReadContent &&content) {
const auto creating = !*menu;
const auto refill = [=] {
if (active) {
(*menu)->addAction(tr::lng_context_set_as_quick(tr::now), [=] {
reactions->setFavorite(emoji);
}, &st::menuIconFave);
(*menu)->addSeparator();
}
};
if (creating) {
*menu = base::make_unique_q<Ui::PopupMenu>(
context,
st::whoReadMenu);
(*menu)->lifetime().add(base::take(lifetime));
refill();
}
filler->populate(menu->get(), content);
if (creating) {
(*menu)->popup(position);
}
}, lifetime);
}
void ShowReportItemsBox(not_null<PeerData*> peer, MessageIdsList ids) {
const auto chosen = [=](Ui::ReportReason reason) {
Ui::show(Box(Ui::ReportDetailsBox, [=](const QString &text) {

View File

@@ -65,6 +65,14 @@ void AddWhoReactedAction(
not_null<QWidget*> context,
not_null<HistoryItem*> item,
not_null<Window::SessionController*> controller);
void ShowWhoReactedMenu(
not_null<base::unique_qptr<Ui::PopupMenu>*> menu,
QPoint position,
not_null<QWidget*> context,
not_null<HistoryItem*> item,
const QString &emoji,
not_null<Window::SessionController*> controller,
rpl::lifetime &lifetime);
void ShowReportItemsBox(not_null<PeerData*> peer, MessageIdsList ids);
void ShowReportPeerBox(

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media_grouped.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_large_emoji.h"
#include "history/view/history_view_react_animation.h"
#include "history/view/history_view_react_button.h"
#include "history/view/history_view_cursor_state.h"
#include "history/history.h"
@@ -45,7 +46,7 @@ namespace {
// A new message from the same sender is attached to previous within 15 minutes.
constexpr int kAttachMessageToPreviousSecondsDelta = 900;
bool IsAttachedToPreviousInSavedMessages(
[[nodiscard]] bool IsAttachedToPreviousInSavedMessages(
not_null<HistoryItem*> previous,
HistoryMessageForwarded *prevForwarded,
not_null<HistoryItem*> item,
@@ -65,6 +66,22 @@ bool IsAttachedToPreviousInSavedMessages(
return (*previousInfo == *itemInfo);
}
[[nodiscard]] Window::SessionController *ContextOrSessionWindow(
const ClickHandlerContext &context,
not_null<Main::Session*> session) {
if (const auto controller = context.sessionWindow.get()) {
return controller;
}
const auto &windows = session->windows();
if (windows.empty()) {
session->domain().activate(&session->account());
if (windows.empty()) {
return nullptr;
}
}
return windows.front();
}
} // namespace
std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
@@ -325,6 +342,15 @@ void DateBadge::paint(
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
}
SendReactionAnimationArgs SendReactionAnimationArgs::translated(
QPoint point) const {
return {
.emoji = emoji,
.flyIcon = flyIcon,
.flyFrom = flyFrom.translated(point),
};
}
Element::Element(
not_null<ElementDelegate*> delegate,
not_null<HistoryItem*> data,
@@ -376,6 +402,10 @@ void Element::setY(int y) {
void Element::refreshDataIdHook() {
}
void Element::repaint() const {
history()->owner().requestViewRepaint(this);
}
void Element::paintHighlight(
Painter &p,
const PaintContext &context,
@@ -464,10 +494,6 @@ int Element::skipBlockHeight() const {
return st::msgDateFont->height - st::msgDateDelta.y();
}
QString Element::skipBlock() const {
return textcmdSkipBlock(skipBlockWidth(), skipBlockHeight());
}
int Element::infoWidth() const {
return 0;
}
@@ -599,7 +625,26 @@ ClickHandlerPtr Element::fromLink() const {
return _fromLink;
}
const auto item = data();
if (const auto from = item->displayFrom()) {
if (item->isSponsored()) {
const auto session = &item->history()->session();
_fromLink = std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
if (context.button != Qt::LeftButton) {
return;
}
const auto my = context.other.value<ClickHandlerContext>();
if (const auto window = ContextOrSessionWindow(my, session)) {
auto &sponsored = session->data().sponsoredMessages();
const auto details = sponsored.lookupDetails(my.itemId);
if (const auto &hash = details.hash) {
Api::CheckChatInvite(window, *hash);
} else if (const auto peer = details.peer) {
window->showPeerInfo(peer);
}
}
});
return _fromLink;
} else if (const auto from = item->displayFrom()) {
_fromLink = std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
if (context.button != Qt::LeftButton) {
@@ -607,29 +652,8 @@ ClickHandlerPtr Element::fromLink() const {
}
const auto my = context.other.value<ClickHandlerContext>();
const auto session = &from->session();
const auto window = [&]() -> Window::SessionController* {
if (const auto controller = my.sessionWindow.get()) {
return controller;
}
const auto &windows = session->windows();
if (windows.empty()) {
session->domain().activate(&session->account());
if (windows.empty()) {
return nullptr;
}
}
return windows.front();
}();
if (window) {
const auto inviteHash = item->isSponsored()
? session->data().sponsoredMessages().channelPost(
my.itemId).hash
: std::nullopt;
if (inviteHash) {
Api::CheckChatInvite(window, *inviteHash);
} else {
window->showPeerInfo(from);
}
if (const auto window = ContextOrSessionWindow(my, session)) {
window->showPeerInfo(from);
}
});
_fromLink->setProperty(kPeerLinkPeerIdProperty, from->id.value);
@@ -1010,7 +1034,7 @@ void Element::clickHandlerActiveChanged(
}
}
App::hoveredLinkItem(active ? this : nullptr);
history()->owner().requestViewRepaint(this);
repaint();
if (const auto media = this->media()) {
media->clickHandlerActiveChanged(handler, active);
}
@@ -1025,12 +1049,20 @@ void Element::clickHandlerPressedChanged(
}
}
App::pressedLinkItem(pressed ? this : nullptr);
history()->owner().requestViewRepaint(this);
repaint();
if (const auto media = this->media()) {
media->clickHandlerPressedChanged(handler, pressed);
}
}
void Element::animateSendReaction(SendReactionAnimationArgs &&args) {
}
auto Element::takeSendReactionAnimation()
-> std::unique_ptr<Reactions::SendAnimation> {
return nullptr;
}
Element::~Element() {
// Delete media while owner still exists.
base::take(_media);

View File

@@ -33,6 +33,10 @@ struct ChatPaintContext;
class ChatStyle;
} // namespace Ui
namespace Lottie {
class Icon;
} // namespace Lottie
namespace HistoryView {
enum class PointState : char;
@@ -45,6 +49,7 @@ using PaintContext = Ui::ChatPaintContext;
namespace Reactions {
struct ButtonParameters;
class SendAnimation;
} // namespace Reactions
enum class Context : char {
@@ -224,6 +229,14 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
};
struct SendReactionAnimationArgs {
QString emoji;
std::shared_ptr<Lottie::Icon> flyIcon;
QRect flyFrom;
[[nodiscard]] SendReactionAnimationArgs translated(QPoint point) const;
};
class Element
: public Object
, public RuntimeComposer<Element>
@@ -269,7 +282,6 @@ public:
int skipBlockWidth() const;
int skipBlockHeight() const;
QString skipBlock() const;
virtual int infoWidth() const;
virtual int bottomInfoFirstLineWidth() const;
virtual bool bottomInfoIsWide() const;
@@ -408,9 +420,15 @@ public:
[[nodiscard]] bool markSponsoredViewed(int shownFromTop) const;
virtual void animateSendReaction(SendReactionAnimationArgs &&args);
[[nodiscard]] virtual auto takeSendReactionAnimation()
-> std::unique_ptr<Reactions::SendAnimation>;
virtual ~Element();
protected:
void repaint() const;
void paintHighlight(
Painter &p,
const PaintContext &context,

View File

@@ -19,7 +19,7 @@ struct ItemPreviewImage {
};
struct ItemPreview {
QString text;
TextWithEntities text;
std::vector<ItemPreviewImage> images;
int imagesInTextPosition = 0;
std::any loadingContext;

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "core/click_handler_types.h"
#include "apiwrap.h"
#include "api/api_who_reacted.h"
#include "layout/layout_selection.h"
#include "window/window_adaptive.h"
#include "window/window_session_controller.h"
@@ -49,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_file_click_handler.h"
#include "data/data_message_reactions.h"
#include "data/data_peer_values.h"
#include "facades.h"
#include "styles/style_chat.h"
@@ -267,7 +269,9 @@ ListWidget::ListWidget(
, _reactionsManager(
std::make_unique<Reactions::Manager>(
this,
[=](QRect updated) { update(updated); }))
Data::UniqueReactionsLimitValue(&controller->session()),
[=](QRect updated) { update(updated); },
controller->cachedReactionIconFactory().createMethod()))
, _scrollDateCheck([this] { scrollDateCheck(); })
, _applyUpdatedScrollState([this] { applyUpdatedScrollState(); })
, _selectEnabled(_delegate->listAllowsMultiSelect())
@@ -339,15 +343,28 @@ ListWidget::ListWidget(
using ChosenReaction = Reactions::Manager::Chosen;
_reactionsManager->chosen(
) | rpl::start_with_next([=](ChosenReaction reaction) {
if (const auto item = session().data().message(reaction.context)) {
item->toggleReaction(reaction.emoji);
const auto item = session().data().message(reaction.context);
if (!item) {
return;
}
item->toggleReaction(reaction.emoji);
if (item->chosenReaction() != reaction.emoji) {
return;
} else if (const auto view = viewForItem(item)) {
if (const auto top = itemTop(view); top >= 0) {
view->animateSendReaction({
.emoji = reaction.emoji,
.flyIcon = reaction.icon,
.flyFrom = reaction.geometry.translated(0, -top),
});
}
}
}, lifetime());
_delegate->listAllowedReactionsValue(
) | rpl::start_with_next([=](std::vector<Data::Reaction> &&list) {
_reactionsManager->applyList(std::move(list));
}, lifetime());
Reactions::SetupManagerList(
_reactionsManager.get(),
&session(),
_delegate->listAllowedReactionsValue());
controller->adaptive().chatWideValue(
) | rpl::start_with_next([=](bool wide) {
@@ -1715,6 +1732,8 @@ void ListWidget::paintEvent(QPaintEvent *e) {
});
if (from != end(_items)) {
_reactionsManager->startEffectsCollection();
auto top = itemTop(from->get());
auto context = controller()->preparePaintContext({
.theme = _delegate->listChatTheme(),
@@ -1726,9 +1745,14 @@ void ListWidget::paintEvent(QPaintEvent *e) {
p.translate(0, top);
for (auto i = from; i != to; ++i) {
const auto view = *i;
context.reactionEffects
= _reactionsManager->currentReactionEffect();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(view);
view->draw(p, context);
_reactionsManager->recordCurrentReactionEffect(
view->data()->fullId(),
QPoint(0, top));
const auto height = view->height();
top += height;
context.translate(0, -height);
@@ -1753,7 +1777,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
userpicTop,
view->width(),
st::msgPhotoSize);
} else if (const auto info = view->data()->hiddenForwardedInfo()) {
} else if (const auto info = view->data()->hiddenSenderInfo()) {
info->userpic.paint(
p,
st::historyPhotoLeft,
@@ -1813,7 +1837,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
return true;
});
_reactionsManager->paintButtons(p, context);
_reactionsManager->paint(p, context);
}
}
@@ -2069,15 +2093,41 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
mouseActionUpdate(e->globalPos());
}
auto request = ContextMenuRequest(_controller);
request.link = ClickHandler::getActive();
request.view = _overElement;
request.item = _overItemExact
const auto link = ClickHandler::getActive();
if (link
&& !link->property(kSendReactionEmojiProperty).toString().isEmpty()
&& _reactionsManager->showContextMenu(this, e)) {
return;
}
const auto overItem = _overItemExact
? _overItemExact
: _overElement
? _overElement->data().get()
: nullptr;
const auto hasWhoReactedItem = overItem
&& Api::WhoReactedExists(overItem);
const auto clickedEmoji = link
? link->property(kReactionsCountEmojiProperty).toString()
: QString();
_whoReactedMenuLifetime.destroy();
if (hasWhoReactedItem && !clickedEmoji.isEmpty()) {
HistoryView::ShowWhoReactedMenu(
&_menu,
e->globalPos(),
this,
overItem,
clickedEmoji,
_controller,
_whoReactedMenuLifetime);
e->accept();
return;
}
auto request = ContextMenuRequest(_controller);
request.link = link;
request.view = _overElement;
request.item = overItem;
request.pointState = _overState.pointState;
request.selectedText = _selectedText;
request.selectedItems = collectSelectedItems();
@@ -2135,7 +2185,7 @@ void ListWidget::enterEventHook(QEnterEvent *e) {
}
void ListWidget::leaveEventHook(QEvent *e) {
_reactionsManager->updateButton({});
_reactionsManager->updateButton({ .cursorLeft = true });
if (const auto view = _overElement) {
if (_overState.pointState != PointState::Outside) {
repaintItem(view);
@@ -2563,7 +2613,8 @@ void ListWidget::mouseActionUpdate() {
view ? view->height() : 0,
itemPoint,
view ? view->pointState(itemPoint) : PointState::Outside);
if (_overElement != view) {
const auto viewChanged = (_overElement != view);
if (viewChanged) {
repaintItem(_overElement);
_overElement = view;
repaintItem(_overElement);
@@ -2574,6 +2625,9 @@ void ListWidget::mouseActionUpdate() {
itemPoint,
reactionState)
: Reactions::ButtonParameters());
if (viewChanged && view) {
_reactionsManager->updateUniqueLimit(item);
}
TextState dragState;
ClickHandlerHost *lnkhost = nullptr;
@@ -2875,6 +2929,10 @@ void ListWidget::repaintItem(const Element *view) {
const auto top = itemTop(view);
const auto range = view->verticalRepaintRange();
update(0, top + range.top, width(), range.height);
const auto id = view->data()->fullId();
if (const auto area = _reactionsManager->lookupEffectArea(id)) {
update(*area);
}
}
void ListWidget::repaintItem(FullMsgId itemId) {

Some files were not shown because too many files have changed in this diff Show More