Compare commits

..

173 Commits

Author SHA1 Message Date
John Preston
0863941642 Beta version 3.4.7.
- Fix a crash on launch on Windows.
2022-01-19 09:34:17 +03:00
John Preston
b331aee599 Fix a crash on Windows < 11. 2022-01-19 09:33:34 +03:00
John Preston
e19180cc86 Beta version 3.4.6: Fix build with Xcode. 2022-01-18 21:56:20 +03:00
John Preston
10cb891f48 Beta version 3.4.6.
- Add snap layouts support on Windows 11.
- Fix crash in drafts after accounts switching.
2022-01-18 21:47:13 +03:00
John Preston
c8f7a8c795 Add a tab with "Who Seen" to "Who Reacted" box. 2022-01-18 21:44:59 +03:00
John Preston
74a28ffdf7 Use correct string for reacted / seen item. 2022-01-18 19:55:24 +03:00
John Preston
ecedce0c2f Fix crash in drafts saving.
Regression was introduced in 43559fb6b7.
2022-01-18 19:05:31 +03:00
John Preston
bd4f993292 Add Windows 11 snap layouts to call / voicechat. 2022-01-18 18:39:55 +03:00
John Preston
4934b026d3 Improve call / voicechat title controls on Windows 11. 2022-01-18 15:53:04 +03:00
John Preston
11f183a79f Better animate sent reaction in flipped context. 2022-01-18 14:45:28 +03:00
John Preston
ae426a41e0 Better reaction layout outside of a bubble. 2022-01-18 14:37:14 +03:00
John Preston
d6edc3728d Workaround selection glitches on macOS. 2022-01-18 14:11:15 +03:00
John Preston
e121487170 Fix appear animation when sending a reaction in a group. 2022-01-18 13:09:42 +03:00
John Preston
72a093ec77 Support Windows 11 snap layouts in the main window. 2022-01-18 12:59:54 +03:00
John Preston
4996d90782 Fix first my reaction userpic in groups. 2022-01-17 21:29:28 +03:00
John Preston
9a451a1423 Don't suggest "Set As Quick" on already quick. 2022-01-17 19:21:34 +03:00
John Preston
4d11ad45db Use common title bar buttons in Call Panel. 2022-01-17 19:21:34 +03:00
John Preston
1657c2c7f2 Fix context menu on sent images / documents. 2022-01-17 19:21:34 +03:00
John Preston
c5e7048a3d Don't copy-on-click "pre" entities. 2022-01-17 16:49:14 +03:00
John Preston
1f194da2f0 Improve macOS title bar font and rounding. 2022-01-17 16:48:32 +03:00
John Preston
0954b04f24 Fix title controls on Windows 11. 2022-01-17 13:39:14 +03:00
John Preston
4659499340 Update window title icons. 2022-01-17 11:18:12 +03:00
John Preston
6eb4584408 Fix links before monospace formatting. 2022-01-17 10:51:35 +03:00
23rd
4ba4b77b95 Fixed formatting of text in entry of archived folder.
Fixed #23906.
2022-01-17 10:12:18 +03:00
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
260 changed files with 7988 additions and 4008 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

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.7.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,7,0
PRODUCTVERSION 3,4,7,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.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.4.0.0"
VALUE "ProductVersion", "3.4.7.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,7,0
PRODUCTVERSION 3,4,7,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.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.4.0.0"
VALUE "ProductVersion", "3.4.7.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,60 @@ 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;
std::vector<PeerId> read;
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.read == b.read)
&& (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 +92,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 +147,11 @@ struct State {
item->history()->session().api().request(requestId).cancel();
}
}
for (auto &[item, entry] : i->second->cachedReacted) {
if (const auto requestId = entry.requestId) {
item->history()->session().api().request(requestId).cancel();
for (auto &[item, map] : i->second->cachedReacted) {
for (auto &[reaction, entry] : map) {
if (const auto requestId = entry.requestId) {
item->history()->session().api().request(requestId).cancel();
}
}
}
contexts.erase(i);
@@ -134,7 +159,9 @@ struct State {
return result;
}
[[nodiscard]] not_null<Context*> PreparedContextAt(not_null<QWidget*> key, not_null<Main::Session*> session) {
[[nodiscard]] not_null<Context*> PreparedContextAt(
not_null<QWidget*> key,
not_null<Main::Session*> session) {
const auto context = ContextAt(key);
if (context->subscriptions.contains(session)) {
return context;
@@ -149,7 +176,9 @@ struct State {
}
const auto j = context->cachedReacted.find(update.item);
if (j != end(context->cachedReacted)) {
session->api().request(j->second.requestId).cancel();
for (auto &[reaction, entry] : j->second) {
session->api().request(entry.requestId).cancel();
}
context->cachedReacted.erase(j);
}
}, context->subscriptions[session]);
@@ -163,21 +192,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 +207,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 +227,39 @@ 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(
Peers &&peers) {
auto result = PeersWithReactions{
.list = peers.list | ranges::views::transform([](PeerId peer) {
return PeerWithReaction{.peer = peer };
}) | ranges::to_vector,
.unknown = peers.unknown,
};
result.read = std::move(peers.list);
return result;
}
[[nodiscard]] rpl::producer<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,68 +268,73 @@ 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;
reacted.read = std::move(read.list);
return std::move(reacted);
});
}
@@ -418,6 +443,104 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
RegenerateUserpics(state, small, large);
}
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context,
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds) {
const auto small = st.userpics.size;
const auto large = st.photoSize;
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto resolveWhoRead = reaction.isEmpty()
&& WhoReadExists(item);
const auto state = lifetime.make_state<State>();
const auto pushNext = [=] {
consumer.put_next_copy(state->current);
};
const auto resolveWhoReacted = !reaction.isEmpty()
|| item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
: resolveWhoRead
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
: WhoReactedIds(item, reaction, context);
state->current.type = resolveWhoRead
? DetectSeenType(item)
: Ui::WhoReadType::Reacted;
if (resolveWhoReacted) {
const auto &list = item->reactions();
state->current.fullReactionsCount = reaction.isEmpty()
? ranges::accumulate(
list,
0,
ranges::plus{},
[](const auto &pair) { return pair.second; })
: list.contains(reaction)
? list.find(reaction)->second
: 0;
// #TODO reactions
state->current.singleReaction = !reaction.isEmpty()
? reaction
: (list.size() == 1)
? list.front().first
: QString();
}
std::move(
idsWithReactions
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
if (peers.unknown) {
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.fullReactionsCount = state->current.fullReactionsCount,
.fullReadCount = state->current.fullReadCount,
.unknown = true,
});
return;
}
state->current.fullReadCount = int(peers.read.size());
state->current.fullReactionsCount = peers.fullReactionsCount;
if (whoReadIds) {
whoReadIds->list = (peers.read.size() > peers.list.size())
? std::move(peers.read)
: std::vector<PeerId>();
}
if (UpdateUserpics(state, item, peers.list)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.list.empty()) {
pushNext();
}
}, lifetime);
item->history()->session().downloaderTaskFinished(
) | rpl::filter([=] {
return state->someUserpicsNotLoaded && !state->scheduled;
}) | rpl::start_with_next([=] {
for (const auto &userpic : state->userpics) {
if (userpic.peer->userpicUniqueKey(userpic.view)
!= userpic.uniqueKey) {
state->scheduled = true;
crl::on_main(&state->guard, [=] {
state->scheduled = false;
RegenerateUserpics(state, small, large);
pushNext();
});
return;
}
}
}, lifetime);
return lifetime;
};
}
} // namespace
bool WhoReadExists(not_null<HistoryItem*> item) {
@@ -462,80 +585,17 @@ bool WhoReactedExists(not_null<HistoryItem*> item) {
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
not_null<QWidget*> context,
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds) {
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
}
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context,
const style::WhoRead &st) {
const auto small = st.userpics.size;
const auto large = st.photoSize;
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto resolveWhoRead = WhoReadExists(item);
const auto state = lifetime.make_state<State>();
const auto pushNext = [=] {
consumer.put_next_copy(state->current);
};
const auto resolveWhoReacted = item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
: resolveWhoRead
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
: WhoReactedIds(item, context);
state->current.type = resolveWhoRead
? DetectSeenType(item)
: Ui::WhoReadType::Reacted;
if (resolveWhoReacted) {
const auto &list = item->reactions();
state->current.fullReactionsCount = ranges::accumulate(
list,
0,
ranges::plus{},
[](const auto &pair) { return pair.second; });
// #TODO reactions
state->current.singleReaction = (list.size() == 1)
? list.front().first
: QString();
}
std::move(
idsWithReactions
) | rpl::start_with_next([=](
const std::vector<PeerWithReaction> &peers) {
if (ListUnknown(peers, item)) {
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.unknown = true,
});
return;
} else if (UpdateUserpics(state, item, peers)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.empty()) {
pushNext();
}
}, lifetime);
item->history()->session().downloaderTaskFinished(
) | rpl::filter([=] {
return state->someUserpicsNotLoaded && !state->scheduled;
}) | rpl::start_with_next([=] {
for (const auto &userpic : state->userpics) {
if (userpic.peer->userpicUniqueKey(userpic.view)
!= userpic.uniqueKey) {
state->scheduled = true;
crl::on_main(&state->guard, [=] {
state->scheduled = false;
RegenerateUserpics(state, small, large);
pushNext();
});
return;
}
}
}, lifetime);
return lifetime;
};
return WhoReacted(item, reaction, context, st, nullptr);
}
} // namespace Api

View File

@@ -15,6 +15,7 @@ struct WhoRead;
namespace Ui {
struct WhoReadContent;
enum class WhoReadType;
} // namespace Ui
namespace Api {
@@ -22,10 +23,21 @@ namespace Api {
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
struct WhoReadList {
std::vector<PeerId> list;
Ui::WhoReadType type = {};
};
// The context must be destroyed before the session holding this item.
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
not_null<QWidget*> context,
const style::WhoRead &st); // Cache results for this lifetime.
not_null<QWidget*> context, // Cache results for this lifetime.
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context, // Cache results for this lifetime.
const style::WhoRead &st);
} // namespace Api

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

@@ -411,49 +411,45 @@ callBarSignalBars: CallSignalBars(callPanelSignalBars) {
color: callBarFg;
}
callTitleButton: IconButton {
width: 34px;
height: 30px;
iconPosition: point(0px, 0px);
}
callTitleButton: windowTitleButton;
callTitleMinimizeIcon: icon {
{ "calls/calls_minimize_shadow", windowShadowFg },
{ "calls/calls_minimize_main", callNameFg },
{ "title_shadow_minimize", windowShadowFg },
{ "title_button_minimize", callNameFg },
};
callTitleMinimizeIconOver: icon {
{ size(34px, 30px), callBgButton },
{ size(34px, 30px), callMuteRipple },
{ "calls/calls_minimize_shadow", windowShadowFg },
{ "calls/calls_minimize_main", callNameFg },
{ windowTitleButtonSize, callBgButton },
{ windowTitleButtonSize, callMuteRipple },
{ "title_shadow_minimize", windowShadowFg },
{ "title_button_minimize", callNameFg },
};
callTitleMaximizeIcon: icon {
{ "calls/calls_maximize_shadow", windowShadowFg },
{ "calls/calls_maximize_main", callNameFg },
{ "title_shadow_maximize", windowShadowFg },
{ "title_button_maximize", callNameFg },
};
callTitleMaximizeIconOver: icon {
{ size(34px, 30px), callBgButton },
{ size(34px, 30px), callMuteRipple },
{ "calls/calls_maximize_shadow", windowShadowFg },
{ "calls/calls_maximize_main", callNameFg },
{ windowTitleButtonSize, callBgButton },
{ windowTitleButtonSize, callMuteRipple },
{ "title_shadow_maximize", windowShadowFg },
{ "title_button_maximize", callNameFg },
};
callTitleRestoreIcon: icon {
{ "calls/calls_restore_shadow", windowShadowFg },
{ "calls/calls_restore_main", callNameFg },
{ "title_shadow_restore", windowShadowFg },
{ "title_button_restore", callNameFg },
};
callTitleRestoreIconOver: icon {
{ size(34px, 30px), callBgButton },
{ size(34px, 30px), callMuteRipple },
{ "calls/calls_restore_shadow", windowShadowFg },
{ "calls/calls_restore_main", callNameFg },
{ windowTitleButtonSize, callBgButton },
{ windowTitleButtonSize, callMuteRipple },
{ "title_shadow_restore", windowShadowFg },
{ "title_button_restore", callNameFg },
};
callTitleCloseIcon: icon {
{ "calls/calls_close_shadow", windowShadowFg },
{ "calls/calls_close_main", callNameFg },
{ "title_shadow_close", windowShadowFg },
{ "title_button_close", callNameFg },
};
callTitleCloseIconOver: icon {
{ size(34px, 30px), titleButtonCloseBgOver },
{ "calls/calls_close_shadow", windowShadowFg },
{ "calls/calls_close_main", titleButtonCloseFgOver },
{ windowTitleButtonSize, titleButtonCloseBgOver },
{ "title_shadow_close", windowShadowFg },
{ "title_button_close", titleButtonCloseFgOver },
};
callTitle: WindowTitle(defaultWindowTitle) {
height: 0px;
@@ -1055,37 +1051,37 @@ groupCallDelaySlider: MediaSlider(defaultContinuousSlider) {
groupCallDelayMargin: margins(22px, 5px, 20px, 10px);
groupCallTitleButton: IconButton {
width: 24px;
height: 21px;
width: windowTitleButtonWidth;
height: windowTitleHeight;
iconPosition: point(0px, 0px);
}
groupCallTitleMinimizeIcon: icon {
{ "title_button_minimize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
{ "title_button_minimize", groupCallMemberNotJoinedStatus },
};
groupCallTitleMinimizeIconOver: icon {
{ size(24px, 21px), groupCallMembersBgOver },
{ "title_button_minimize", groupCallMembersFg, point(4px, 4px) },
{ windowTitleButtonSize, groupCallMembersBgOver },
{ "title_button_minimize", groupCallMembersFg },
};
groupCallTitleMaximizeIcon: icon {
{ "title_button_maximize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
{ "title_button_maximize", groupCallMemberNotJoinedStatus },
};
groupCallTitleMaximizeIconOver: icon {
{ size(24px, 21px), groupCallMembersBgOver },
{ "title_button_maximize", groupCallMembersFg, point(4px, 4px) },
{ windowTitleButtonSize, groupCallMembersBgOver },
{ "title_button_maximize", groupCallMembersFg },
};
groupCallTitleRestoreIcon: icon {
{ "title_button_restore", groupCallMemberNotJoinedStatus, point(4px, 4px) },
{ "title_button_restore", groupCallMemberNotJoinedStatus },
};
groupCallTitleRestoreIconOver: icon {
{ size(24px, 21px), groupCallMembersBgOver },
{ "title_button_restore", groupCallMembersFg, point(4px, 4px) },
{ windowTitleButtonSize, groupCallMembersBgOver },
{ "title_button_restore", groupCallMembersFg },
};
groupCallTitleCloseIcon: icon {
{ "title_button_close", groupCallMemberNotJoinedStatus, point(4px, 4px) },
{ "title_button_close", groupCallMemberNotJoinedStatus },
};
groupCallTitleCloseIconOver: icon {
{ size(24px, 21px), titleButtonCloseBgOver },
{ "title_button_close", titleButtonCloseFgOver, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonCloseBgOver },
{ "title_button_close", titleButtonCloseFgOver },
};
groupCallTitle: WindowTitle(defaultWindowTitle) {
height: 0px;
@@ -1199,7 +1195,7 @@ desktopCaptureSourceSkips: size(2px, 10px);
desktopCaptureSourceTitle: WindowTitle(groupCallTitle) {
bg: groupCallMembersBgOver;
bgActive: groupCallMembersBgOver;
height: 21px;
height: windowTitleHeight;
}
desktopCapturePadding: margins(7px, 7px, 7px, 33px);
desktopCaptureLabelBottom: 7px;

View File

@@ -61,8 +61,8 @@ Panel::Panel(not_null<Call*> call)
, _user(call->user())
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
#ifndef Q_OS_MAC
, _controls(std::make_unique<Ui::Platform::TitleControls>(
widget(),
, _controls(Ui::Platform::SetupSeparateTitleControls(
window(),
st::callTitle,
[=](bool maximized) { toggleFullScreen(maximized); }))
#endif // !Q_OS_MAC
@@ -144,7 +144,7 @@ void Panel::initWindow() {
return Flag::None | Flag(0);
}
#ifndef Q_OS_MAC
if (_controls->geometry().contains(widgetPoint)) {
if (_controls->controls.geometry().contains(widgetPoint)) {
return Flag::None | Flag(0);
}
#endif // !Q_OS_MAC
@@ -550,7 +550,7 @@ void Panel::initLayout() {
}, widget()->lifetime());
#ifndef Q_OS_MAC
_controls->raise();
_controls->wrap.raise();
#endif // !Q_OS_MAC
}
@@ -628,7 +628,7 @@ void Panel::updateControlsGeometry() {
}
if (_fingerprint) {
#ifndef Q_OS_MAC
const auto controlsGeometry = _controls->geometry();
const auto controlsGeometry = _controls->controls.geometry();
const auto halfWidth = widget()->width() / 2;
const auto minLeft = (controlsGeometry.center().x() < halfWidth)
? (controlsGeometry.width() + st::callFingerprintTop)

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/weak_ptr.h"
#include "base/timer.h"
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
#include "calls/calls_call.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/effects/animations.h"
@@ -37,7 +38,7 @@ namespace GL {
enum class Backend;
} // namespace GL
namespace Platform {
class TitleControls;
struct SeparateTitleControls;
} // namespace Platform
} // namespace Ui
@@ -126,7 +127,7 @@ private:
std::unique_ptr<Incoming> _incoming;
#ifndef Q_OS_MAC
std::unique_ptr<Ui::Platform::TitleControls> _controls;
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
#endif // !Q_OS_MAC
QSize _incomingFrameSize;

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

@@ -88,8 +88,8 @@ Panel::Panel(not_null<GroupCall*> call)
, _peer(call->peer())
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
#ifndef Q_OS_MAC
, _controls(std::make_unique<Ui::Platform::TitleControls>(
widget(),
, _controls(Ui::Platform::SetupSeparateTitleControls(
window(),
st::groupCallTitle))
#endif // !Q_OS_MAC
, _viewport(
@@ -302,7 +302,7 @@ void Panel::initWidget() {
updateControlsGeometry();
}
// title geometry depends on _controls->geometry,
// title geometry depends on _controls->controls.geometry,
// which is not updated here yet.
crl::on_main(widget(), [=] { refreshTitle(); });
}, lifetime());
@@ -1368,7 +1368,7 @@ void Panel::initLayout() {
initGeometry();
#ifndef Q_OS_MAC
_controls->raise();
_controls->wrap.raise();
Ui::Platform::TitleControlsLayoutChanged(
) | rpl::start_with_next([=] {
@@ -1413,7 +1413,7 @@ QRect Panel::computeTitleRect() const {
#ifdef Q_OS_MAC
return QRect(70, 0, width - remove - 70, 28);
#else // Q_OS_MAC
const auto controls = _controls->geometry();
const auto controls = _controls->controls.geometry();
const auto right = controls.x() + controls.width() + skip;
return (controls.center().x() < width / 2)
? QRect(right, 0, width - right - remove, controls.height())
@@ -1884,7 +1884,8 @@ void Panel::updateControlsGeometry() {
#ifdef Q_OS_MAC
const auto controlsOnTheLeft = true;
#else // Q_OS_MAC
const auto controlsOnTheLeft = _controls->geometry().center().x()
const auto center = _controls->controls.geometry().center();
const auto controlsOnTheLeft = center.x()
< widget()->width() / 2;
#endif // Q_OS_MAC
const auto menux = st::groupCallMenuTogglePosition.x();

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/flags.h"
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_choose_join_as.h"
@@ -51,7 +52,7 @@ namespace Toast {
class Instance;
} // namespace Toast
namespace Platform {
class TitleControls;
struct SeparateTitleControls;
} // namespace Platform
} // namespace Ui
@@ -194,7 +195,7 @@ private:
rpl::variable<PanelMode> _mode;
#ifndef Q_OS_MAC
std::unique_ptr<Ui::Platform::TitleControls> _controls;
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
#endif // !Q_OS_MAC
rpl::lifetime _callLifetime;

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,18 @@ std::map<int, const char*> BetaLogs() {
"- Spoiler formatting hides text in chat, "
"as well as in the chat list and notifications.\n"
},
{
3004005,
"- Fix crash in monospace blocks processing.\n"
"- Fix reaction animations stopping after an hour uptime.\n"
},
{
3004006,
"- Add snap layouts support on Windows 11.\n"
"- Fix crash in drafts after accounts switching.\n"
}
};
};

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 kPhotoLinkMediaProperty = 0x02;
constexpr auto kDocumentLinkMediaProperty = 0x03;
constexpr auto kSendReactionEmojiProperty = 0x04;
constexpr auto kReactionsCountEmojiProperty = 0x05;
namespace Main {
class Session;
@@ -195,3 +199,20 @@ private:
QString _cmd;
};
class MonospaceClickHandler : public TextClickHandler {
public:
MonospaceClickHandler(const QString &text, EntityType type);
void onClick(ClickContext context) const override;
TextEntity getTextEntity() const override;
protected:
QString url() const override;
private:
const QString _text;
const TextEntity _entity;
};

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 = 3004007;
constexpr auto AppVersionStr = "3.4.7";
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(
kDocumentLinkMediaProperty,
reinterpret_cast<qulonglong>(_document.get()));
}
DocumentOpenClickHandler::DocumentOpenClickHandler(
@@ -146,6 +150,9 @@ PhotoClickHandler::PhotoClickHandler(
: FileClickHandler(context)
, _photo(photo)
, _peer(peer) {
setProperty(
kPhotoLinkMediaProperty,
reinterpret_cast<qulonglong>(_photo.get()));
}
not_null<PhotoData*> PhotoClickHandler::photo() const {

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,33 @@ void MessageReactions::add(const QString &reaction) {
if (_chosen == reaction) {
return;
}
const auto history = _item->history();
const auto self = history->session().user();
if (!_chosen.isEmpty()) {
const auto i = _list.find(_chosen);
Assert(i != end(_list));
--i->second;
if (!i->second) {
const auto removed = !i->second;
if (removed) {
_list.erase(i);
}
const auto j = _recent.find(_chosen);
if (j != end(_recent)) {
j->second.erase(ranges::remove(j->second, self), end(j->second));
if (j->second.empty() || removed) {
_recent.erase(j);
}
}
}
_chosen = reaction;
if (!reaction.isEmpty()) {
if (_item->canViewReactions()) {
auto &list = _recent[reaction];
list.insert(begin(list), self);
}
++_list[reaction];
}
auto &owner = _item->history()->owner();
auto &owner = history->owner();
owner.reactions().send(_item, _chosen);
owner.notifyItemDataChange(_item);
}
@@ -412,8 +516,10 @@ void MessageReactions::remove() {
void MessageReactions::set(
const QVector<MTPReactionCount> &list,
const QVector<MTPMessageUserReaction> &recent,
bool ignoreChosen) {
if (_item->history()->owner().reactions().sending(_item)) {
auto &owner = _item->history()->owner();
if (owner.reactions().sending(_item)) {
// We'll apply non-stale data from the request response.
return;
}
@@ -450,8 +556,24 @@ void MessageReactions::set(
_chosen = QString();
}
}
auto parsed = base::flat_map<
QString,
std::vector<not_null<UserData*>>>();
for (const auto &reaction : recent) {
reaction.match([&](const MTPDmessageUserReaction &data) {
const auto emoji = qs(data.vreaction());
if (_list.contains(emoji)) {
parsed[emoji].push_back(owner.user(data.vuser_id()));
}
});
}
if (_recent != parsed) {
_recent = std::move(parsed);
changed = true;
}
if (changed) {
_item->history()->owner().notifyItemDataChange(_item);
owner.notifyItemDataChange(_item);
}
}
@@ -459,6 +581,11 @@ const base::flat_map<QString, int> &MessageReactions::list() const {
return _list;
}
auto MessageReactions::recent() const
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> & {
return _recent;
}
bool MessageReactions::empty() const {
return _list.empty();
}

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::PlainLink, 0, int(name.size()), QString() },
}
: EntitiesInText{}
};
};
const auto shown = int(peers.size());
const auto accumulated = [&] {
@@ -57,12 +63,19 @@ QString ComposeFolderListEntryText(not_null<Data::Folder*> folder) {
lt_accumulated,
result,
lt_chat,
wrapName(*i));
wrapName(*i),
Ui::Text::WithEntities);
}
return result;
}();
return (shown < count)
? tr::lng_archived_last(tr::now, lt_count, (count - shown), lt_chats, accumulated)
? tr::lng_archived_last(
tr::now,
lt_count,
(count - shown),
lt_chats,
accumulated,
Ui::Text::WithEntities)
: accumulated;
}
@@ -277,10 +290,11 @@ void Row::validateListEntryCache() const {
return;
}
_listEntryCacheVersion = version;
_listEntryCache.setText(
_listEntryCache.setMarkedText(
st::dialogsTextStyle,
ComposeFolderListEntryText(folder),
Ui::DialogTextOptions());
// Use rich options as long as the entry text does not have user text.
Ui::ItemTextDefaultOptions());
}
FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
@@ -288,4 +302,4 @@ FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
, _item(item) {
}
} // namespace Dialogs
} // namespace Dialogs

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

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