Compare commits

...

144 Commits

Author SHA1 Message Date
John Preston
4274f9d3f3 Version 4.14.2.
- Show original senders name in reply to forward information.
- Use original senders color / emoji pattern in forwards.
- Highlight active saved messages chat in list.
- Fix chats list scrolling on X11 (Linux).
2024-01-02 15:05:06 +04:00
John Preston
c8dd94601b Don't add original name in forward of reply. 2024-01-02 15:05:06 +04:00
John Preston
382dab4ecb Don't push sublists to stack endlessly. 2024-01-02 15:05:06 +04:00
23rd
bdf67645bb Improved phrase of terms in gifts box from settings. 2024-01-02 13:40:21 +03:00
John Preston
5c29cc59c8 Show forward original sender in reply bar. 2024-01-02 14:11:09 +04:00
John Preston
ca9caa36da Inside message use original senders colors. 2024-01-02 13:52:55 +04:00
John Preston
0b5f05c7d4 Skip service accounts in gift premiums box. 2024-01-02 13:52:55 +04:00
John Preston
676e85983d Don't cut bio value, show what server returned. 2024-01-02 13:52:55 +04:00
Eric Kotato
7638f4cc3d Fix warning C4805 2024-01-02 13:27:37 +04:00
John Preston
e2e55312b8 Fix viewer hide workaround for software renderer. 2024-01-02 12:59:41 +04:00
John Preston
c51a8816eb Show selected sublist as active in list. 2024-01-02 12:59:22 +04:00
John Preston
175914f02b Show "My Notes", "Author Hidden" in title. 2024-01-02 12:12:16 +04:00
John Preston
fbd6b5b640 Fix possible build issue on Windows. 2024-01-02 12:01:03 +04:00
kukuruzka
1b11731d6b Fix crash on some videos 2024-01-02 11:43:15 +04:00
Ilya Fedin
8ecb49f132 Multiple by the magic multiplier only on Wayland 2024-01-02 06:57:09 +00:00
John Preston
0ae537478f Version 4.14.1.
- Fix crash in "Author Hidden" chat in "Saved Messages".
- Improve jump-to-original button layout in "Saved Messages".
- Show my own chat as "My Notes" in "Saved Messages".
- In screen sharing source window select first screen by default.
2024-01-02 00:22:46 +04:00
John Preston
4aa432ecbe Fix build with GCC. 2024-01-02 00:22:22 +04:00
John Preston
d5a0f4890d Choose first screen for sharing by default. 2024-01-02 00:17:15 +04:00
John Preston
5e255e56eb Don't allow gifting premium if can't buy. 2024-01-02 00:12:51 +04:00
John Preston
a4d7309209 Support tg://premium_multigift links. 2024-01-02 00:10:52 +04:00
John Preston
a30d0eccda Avoid unnecessary saved dialogs load requests. 2024-01-01 23:42:18 +04:00
John Preston
a4f4e4564a Improve jump-to-message in saved messages. 2024-01-01 23:33:19 +04:00
John Preston
bfe7683cdb Show myself as "My Notes" in Saved Messages sublists. 2024-01-01 09:50:24 +04:00
John Preston
fbc600a978 Fix crash in pending gift payment finish. 2024-01-01 09:23:37 +04:00
GitHub Action
70eb452a09 Update User-Agent for DNS to Chrome 120.0.6099.109. 2024-01-01 05:18:11 +00:00
GitHub Action
9f7c74ae72 Update copyright year to 2024. 2024-01-01 05:17:53 +00:00
Ilya Fedin
65a3cf136b Update submodules 2023-12-31 22:03:57 +00:00
Ilya Fedin
2d86ec1e84 Update hunspell for warning fixes 2023-12-31 22:03:57 +00:00
Ilya Fedin
fdef19a009 Ensure temporaries don't detach with range loop 2023-12-31 22:03:57 +00:00
Ilya Fedin
26df482b54 Fix prototype for fill_fopen_filefunc 2023-12-31 22:03:57 +00:00
Ilya Fedin
ee5b7a5100 Fix the whitespaces for KeyFormat enum 2023-12-31 22:03:57 +00:00
Ilya Fedin
bd67bc4433 Ignore some unused variables 2023-12-31 22:03:57 +00:00
Ilya Fedin
9d582040e6 Fix detaching temporaries 2023-12-31 22:03:57 +00:00
Ilya Fedin
f3bda59019 Replace QString::mid with base::StringViewMid where QStringView is accepted 2023-12-31 22:03:57 +00:00
Ilya Fedin
0d72d47318 Normalize signal connections 2023-12-31 22:03:57 +00:00
Ilya Fedin
29646707a1 QString::arg usage optimization 2023-12-31 22:03:57 +00:00
Ilya Fedin
e6b9a07163 Instantiate QRegularExpression instances statically 2023-12-31 22:03:57 +00:00
Ilya Fedin
4b297bfa09 Make use of wrongly unused variables 2023-12-31 22:03:57 +00:00
Ilya Fedin
00e785a3af Remove unused variables 2023-12-31 22:03:57 +00:00
Ilya Fedin
78e6b3e13f Try to fix circular dependency between external_scudo and common_options 2023-12-31 21:36:32 +00:00
John Preston
ad84750130 Fix crash in Saved Messages from Author Hidden.
Fixes #27293.
2024-01-01 01:18:35 +04:00
John Preston
686310489b Version 4.14.
- Improved saved messages.
- One-time voice messages.
2023-12-31 19:42:24 +04:00
John Preston
84c5310262 Revert "Update GCC to 13 in Docker"
This reverts commit 3adbfb1fb5.

There is some problem with static libstdc++ linking in Release
configuration, the Updater utility fails with unresolved externals.
2023-12-31 19:42:24 +04:00
23rd
f53397e26a Fixed possible crash from voice messages with ttl. 2023-12-31 19:42:24 +04:00
John Preston
2a8a74b5b1 Display correctly forwards of forwards in sublists. 2023-12-31 19:42:24 +04:00
John Preston
9392550c01 Support pinned saved sublists. 2023-12-31 19:42:24 +04:00
23rd
d2565dc944 Added badge corner to voice messages with ttl. 2023-12-31 19:42:24 +04:00
23rd
22f68b430d Disabled transcribe button for voice and round messages with ttl. 2023-12-31 19:42:24 +04:00
23rd
3962e5a680 Added animation to voice messages with ttl. 2023-12-31 19:42:24 +04:00
23rd
a1c7a48958 Improved processing of out expired voice messages. 2023-12-31 19:42:23 +04:00
23rd
85286684e3 Added initial support for voice messages with TTL. 2023-12-31 19:42:23 +04:00
23rd
c2712b0104 Removed redundant semicolons from code. 2023-12-31 19:42:23 +04:00
John Preston
1cb5ef7476 Show information about hidden author. 2023-12-31 19:42:23 +04:00
John Preston
9f0b42bbbd Show correct titles in sublists / sublist. 2023-12-31 19:42:23 +04:00
John Preston
634687881a Fix loading of saved sublist histories. 2023-12-31 19:42:23 +04:00
John Preston
878b4bb5af Update API scheme on layer 170. 2023-12-31 19:42:23 +04:00
John Preston
452257dcd5 Show special name/userpic for "Author Hidden". 2023-12-31 19:42:23 +04:00
John Preston
4e6d8f06d9 Show saved messages entry point from profiles. 2023-12-31 19:42:23 +04:00
John Preston
fd417024fb Initial saved sublist section implementation. 2023-12-31 19:42:23 +04:00
John Preston
18c4d210e5 Show saved messages sublists in profile. 2023-12-31 19:42:23 +04:00
John Preston
ead40c759e Update API scheme to layer 170. 2023-12-31 19:42:23 +04:00
Ilya Fedin
be9aa3a097 Test the build of updater on the CI 2023-12-31 15:35:14 +00:00
23rd
bdcb146d06 Improved style of button in group call bar for consistency. 2023-12-30 09:28:03 +04:00
Ilya Fedin
70115a24bb Fix some webview crashes 2023-12-28 10:05:55 +00:00
Ilya Fedin
ea37e83b13 Revert "Force enable fractional-scale-v1 experimental option"
This reverts commit 4696f731da.
2023-12-27 22:44:58 +00:00
Ilya Fedin
931c17418d Update to the new cppgir API 2023-12-26 15:33:33 +00:00
John Preston
5a47ed268c Fix build for Windows Store. 2023-12-25 23:21:47 +00:00
Ilya Fedin
d63ebbe62c Handle webview crash 2023-12-23 19:12:17 +00:00
John Preston
cb4fce251e Version 4.13.1: Fix build with GCC. 2023-12-23 14:18:12 -04:00
John Preston
4aa8a41119 Version 4.13.1.
- Fix crash in chat history right click.
- Fix user emoji status display in main menu, profile and settings.
2023-12-23 14:15:29 -04:00
John Preston
13cba72945 Fix emoji status display for users.
Regression was introduced in 805a5d73b6.
2023-12-23 14:14:03 -04:00
John Preston
cf63b0138e Fix crash in context menu.
Regression was introduced in 4e3c1460f6.

Fixes #27254.
2023-12-23 14:12:50 -04:00
John Preston
3cbe0aae4a Version 4.13.
- Support setting channel wallpaper.
- Support setting channel emoji status.
- Allow gifting premium to several recipients at once.
2023-12-22 20:55:24 -04:00
John Preston
5ab8e68366 Fix possible crash in colorizeImage. 2023-12-22 20:52:11 -04:00
John Preston
1d345299f5 Allow smartglocal to customize tokenize url. 2023-12-22 20:52:11 -04:00
23rd
fc50d5c30f Improved code style in HistoryView::WebPage. 2023-12-23 03:16:58 +03:00
23rd
4e3c1460f6 Fixed display of menu items from sponsored message with selected text. 2023-12-23 01:50:12 +03:00
23rd
5bc954396c Fixed display of long title or long description in sponsored messages. 2023-12-23 01:50:12 +03:00
John Preston
b24290b019 Fix text-colored emoji in long-press preview. 2023-12-22 13:46:17 -04:00
John Preston
23cce64d00 Channel message reposts to stories like reposts. 2023-12-22 13:16:14 -04:00
John Preston
73690d14f7 Allow clipboard access for attach menu bots. 2023-12-22 12:42:23 -04:00
John Preston
2a5698cf34 Admin log events about channel emoji status. 2023-12-22 07:35:02 -04:00
John Preston
fd64718502 Allow setting channel wallpaper. 2023-12-22 07:35:02 -04:00
John Preston
941126ad69 Allow setting channel emoji status. 2023-12-22 07:35:02 -04:00
John Preston
0e8058adb1 Update tg_owt to support custom reflectors. 2023-12-22 07:35:02 -04:00
23rd
01906c1161 Fixed display of archiving toasts on wrong window. 2023-12-22 07:35:02 -04:00
John Preston
9201cf24f1 Respect correct min-level for colors. 2023-12-22 07:35:02 -04:00
John Preston
d5a1c354d0 Support by-emoji background resolve in preview. 2023-12-22 07:35:02 -04:00
John Preston
41ae1f56ed Update API scheme to layer 169. Multigifts. 2023-12-22 07:35:02 -04:00
23rd
ed7212f864 Added mini icon of boosts to description in gifts box from settings. 2023-12-19 04:06:54 +03:00
23rd
8bcb784f12 Moved out child centering within widget to single function. 2023-12-19 01:52:33 +03:00
23rd
431549c81a Improved processing of successful payment in gift box from settings. 2023-12-19 01:43:58 +03:00
23rd
12110e17a2 Slightly improved style of userpics from top in gift box from settings. 2023-12-18 05:50:14 +03:00
23rd
db8338156a Slightly improved style of terms label in gift box from settings. 2023-12-18 05:50:14 +03:00
John Preston
de8b09d7fc Use correct phrases for outgoing giftcodes. 2023-12-17 21:20:21 +00:00
23rd
fddbce5dce Added premium summary to gift box from settings. 2023-12-17 13:05:21 +03:00
23rd
081817f62a Moved out making of premium summary to separated function. 2023-12-17 13:01:36 +03:00
23rd
8efbd7a1cb Added ability to hide subscription button in preview premium boxes. 2023-12-17 13:01:36 +03:00
23rd
bce310d5c8 Added complex description to top of gift box from settings. 2023-12-17 13:01:36 +03:00
23rd
ed9ecbd235 Added circle badge to userpics in gift box from settings. 2023-12-17 13:01:17 +03:00
23rd
1e756dd380 Added userpics to top of gift box from settings. 2023-12-17 13:00:40 +03:00
23rd
b9b6226692 Added initial ability to gift premium to contacts from settings. 2023-12-17 09:50:50 +03:00
23rd
82d73e2396 Moved out making of "new" badges to single place. 2023-12-17 09:50:50 +03:00
John Preston
cd5a6025f6 Support pre-defined channel wallpapers resolving. 2023-12-16 23:52:55 +00:00
John Preston
b6c679449e Support custom channel backgrounds display. 2023-12-16 23:23:24 +00:00
John Preston
ac744b957a Show emoji pattern also on link preview bubbles. 2023-12-16 22:52:30 +00:00
John Preston
805a5d73b6 Show emoji statuses in channels. 2023-12-16 22:43:35 +00:00
John Preston
6aaf841a73 Ignore Cmd+Up/Down if field isn't empty. 2023-12-16 22:00:41 +00:00
John Preston
60e72768e1 Update API scheme. Shared posts in stories. 2023-12-16 21:26:40 +00:00
John Preston
94e8f2a791 Update API scheme. Anonymous gift links. 2023-12-16 20:25:02 +00:00
John Preston
1fb4a2f4ba Update API scheme. Show interactions in channel stories. 2023-12-16 20:25:02 +00:00
John Preston
28d68acfe6 Use forward declaration for ripple animation. 2023-12-16 20:25:02 +00:00
John Preston
d87a0a2d25 Show reposts / forwards in story viewers. 2023-12-16 20:25:02 +00:00
23rd
8e92778b62 Fixed API support of public forwards in statistics info. 2023-12-16 20:25:02 +00:00
John Preston
f7e2c7977b Update API scheme. TODO public forwards stats. 2023-12-16 20:25:02 +00:00
John Preston
4337f0b509 Fix "Keep Disabled" in OpenGL crash check. 2023-12-16 20:25:02 +00:00
John Preston
2b960a1f21 Update API scheme. 2023-12-16 20:25:02 +00:00
John Preston
4b9648d8d9 Update API scheme. Giveaway winners. 2023-12-16 20:25:02 +00:00
John Preston
62f9f3c94b Update API scheme. HistoryView::Giveaway->MediaInBubble. 2023-12-16 20:25:02 +00:00
John Preston
e854f0b60c Show additional prize in giveaway message. 2023-12-16 20:25:02 +00:00
John Preston
19f38f3c6f Optimize empty->non-empty userpics repainting. 2023-12-16 20:25:02 +00:00
John Preston
d56724f290 Add additional prize info to giveaway details. 2023-12-16 20:25:02 +00:00
John Preston
8abc35ca86 Make giveaway end message clickable. 2023-12-16 20:25:02 +00:00
John Preston
e135f8954f Update API scheme to layer 168. Giveaways. 2023-12-16 20:25:02 +00:00
John Preston
f5b59c9456 Remove old test code giveaway creation. 2023-12-16 20:25:02 +00:00
23rd
6471d43c71 Added mini preview for some types of webpages. 2023-12-16 20:25:02 +00:00
23rd
563b8d1468 Added support of inline markup reply to JSON export. 2023-12-16 20:25:01 +00:00
23rd
f41a3fe01f Fixed display of non-stack bar chart view with empty values in chart. 2023-12-16 20:25:01 +00:00
23rd
29c9266ef5 Fixed display of bottom solid line in chart view for some scroll states. 2023-12-16 20:25:01 +00:00
23rd
1a856e359f Fixed display of bottom captions in chart view with long date strings. 2023-12-16 20:25:01 +00:00
23rd
59099a8d46 Added ability to open profile info through menu from public forwards. 2023-12-16 20:25:01 +00:00
23rd
98c6a3ff79 Added support of stories in list of public forwards in statistics info. 2023-12-16 20:25:01 +00:00
23rd
cccc2ce0f1 Improved processing of access to boosts info. 2023-12-16 20:25:01 +00:00
23rd
88b20f6700 Fixed indents in some files. 2023-12-16 20:25:01 +00:00
Ilya Fedin
3adbfb1fb5 Update GCC to 13 in Docker 2023-12-16 09:32:50 +04:00
Ilya Fedin
0ee0ffa7f1 Update breakpad in Docker 2023-12-16 09:32:50 +04:00
Ilya Fedin
4c82620677 Disable building libstdc++ tests 2023-12-16 09:32:50 +04:00
Ilya Fedin
73294bfabf Make Linux action to use the pre-set entrypoint 2023-12-16 09:32:50 +04:00
Ilya Fedin
6c42095108 Move the GHCR authentication to the first set up step 2023-12-16 09:32:50 +04:00
John Preston
fbe93b0afc Fix webpage layouts with thumbnails. 2023-12-15 14:15:35 +00:00
Ilya Fedin
e173c727f7 Fix remaining known xdg-output fractional scaling issues on Wayland
And enable it by default
2023-12-07 10:24:19 +04:00
Ilya Fedin
85f56217a8 Fix docker & snap build 2023-12-01 17:20:46 +04:00
Ilya Fedin
06564efe0e Build GLib manually in Docker and Snap 2023-12-01 15:39:42 +04:00
Ilya Fedin
664ebe4ed0 Use xdg-output for Wayland fractional scaling
That's way more stable
2023-12-01 15:39:14 +04:00
330 changed files with 9036 additions and 2440 deletions

View File

@@ -28,6 +28,7 @@ jobs:
run: |
sudo apt update
curl -sSL https://install.python-poetry.org | python3 -
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
- name: Free up some disk space.
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
@@ -40,6 +41,4 @@ jobs:
- name: Push the Docker image.
if: ${{ github.ref_name == github.event.repository.default_branch }}
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
docker push $IMAGE_TAG
run: docker push $IMAGE_TAG

View File

@@ -43,15 +43,6 @@ jobs:
linux:
name: Rocky Linux 8
runs-on: ubuntu-latest
container:
image: ghcr.io/${{ github.repository }}/centos_env
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: scl enable gcc-toolset-12 -- bash --noprofile --norc -eo pipefail {0}
strategy:
matrix:
@@ -75,12 +66,13 @@ jobs:
- name: First set up.
run: |
gcc --version
ln -s /usr/src/Libraries
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env
docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env
- name: Telegram Desktop build.
run: |
cd $REPO_NAME/Telegram
cd $REPO_NAME
DEFINE=""
if [ -n "${{ matrix.defines }}" ]; then
@@ -91,18 +83,21 @@ jobs:
echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV
fi
./configure.sh \
docker run --rm \
-v $PWD:/usr/src/tdesktop \
-e DEBUG=1 \
tdesktop:centos_env \
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
-D CMAKE_C_FLAGS_DEBUG="" \
-D CMAKE_CXX_FLAGS_DEBUG="" \
-D CMAKE_C_FLAGS="-Werror" \
-D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_EXE_LINKER_FLAGS="-s" \
-D TDESKTOP_API_TEST=ON \
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE
cmake --build ../out --config Debug --parallel
- name: Check.
run: |
filePath="$REPO_NAME/out/Debug/Telegram"
@@ -121,7 +116,7 @@ jobs:
run: |
cd $REPO_NAME/out/Debug
mkdir artifact
mv Telegram artifact/
mv {Telegram,Updater} artifact/
- uses: actions/upload-artifact@master
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.

View File

@@ -115,6 +115,7 @@ jobs:
-D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \
-D TDESKTOP_API_TEST=ON \
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE

View File

@@ -169,6 +169,7 @@ jobs:
%TDESKTOP_BUILD_GENERATOR% ^
%TDESKTOP_BUILD_ARCH% ^
%TDESKTOP_BUILD_API% ^
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
-D DESKTOP_APP_NO_PDB=ON ^
%TDESKTOP_BUILD_DEFINE%
@@ -178,8 +179,10 @@ jobs:
- name: Move artifact.
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
run: |
set OUT=%TBUILD%\%REPO_NAME%\out\Debug
mkdir artifact
move %TBUILD%\%REPO_NAME%\out\Debug\Telegram.exe artifact/
move %OUT%\Telegram.exe artifact/
move %OUT%\Updater.exe artifact/
- uses: actions/upload-artifact@master
name: Upload artifact.
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')

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-2023 The Telegram Desktop Authors.
Copyright (c) 2014-2024 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

@@ -550,6 +550,10 @@ PRIVATE
data/data_replies_list.h
data/data_reply_preview.cpp
data/data_reply_preview.h
data/data_saved_messages.cpp
data/data_saved_messages.h
data/data_saved_sublist.cpp
data/data_saved_sublist.h
data/data_search_controller.cpp
data/data_search_controller.h
data/data_send_action.cpp
@@ -796,6 +800,8 @@ PRIVATE
history/view/history_view_sponsored_click_handler.h
history/view/history_view_sticker_toast.cpp
history/view/history_view_sticker_toast.h
history/view/history_view_sublist_section.cpp
history/view/history_view_sublist_section.h
history/view/history_view_transcribe_button.cpp
history/view/history_view_transcribe_button.h
history/view/history_view_translate_bar.cpp
@@ -897,6 +903,8 @@ PRIVATE
info/profile/info_profile_values.h
info/profile/info_profile_widget.cpp
info/profile/info_profile_widget.h
info/saved/info_saved_sublists_widget.cpp
info/saved/info_saved_sublists_widget.h
info/settings/info_settings_widget.cpp
info/settings/info_settings_widget.h
info/similar_channels/info_similar_channels_widget.cpp

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

View File

@@ -395,6 +395,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dlg_new_bot_name" = "Bot name";
"lng_no_chats" = "Your chats will be here";
"lng_no_chats_filter" = "No chats currently belong to this folder.";
"lng_no_saved_sublists" = "You can save messages from other chats here.";
"lng_contacts_loading" = "Loading...";
"lng_contacts_not_found" = "No contacts found";
"lng_topics_not_found" = "No topics found.";
@@ -591,6 +592,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_section_background" = "Chat background";
"lng_settings_bg_from_gallery" = "Choose from gallery";
"lng_settings_bg_from_file" = "Choose from file";
"lng_settings_bg_remove" = "Remove wallpaper";
"lng_settings_bg_theme_edit" = "Edit theme";
"lng_settings_bg_theme_create" = "Create new theme";
"lng_settings_bg_cloud_themes" = "Custom themes";
@@ -813,6 +815,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_manage_enabled_dictionary" = "Dictionary is enabled";
"lng_settings_manage_remove_dictionary" = "Remove Dictionary";
"lng_settings_gift_premium" = "Premium Gifting";
"lng_settings_gift_premium_users_confirm" = "Proceed";
"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user.";
"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users.";
"lng_backgrounds_header" = "Choose Wallpaper";
"lng_theme_sure_keep" = "Keep this theme?";
"lng_theme_reverting#one" = "Reverting to the old theme in {count} second.";
@@ -833,6 +840,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_blur" = "Blurred";
"lng_background_sure_delete" = "Are you sure you want to delete this background?";
"lng_background_other_info" = "{user} will be able to apply this wallpaper";
"lng_background_other_channel" = "All subscribers will see this wallpaper";
"lng_background_apply1" = "Apply the wallpaper in this chat.";
"lng_background_apply2" = "Enjoy the view.";
"lng_background_apply_button" = "Apply For This Chat";
@@ -841,6 +849,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_reset_default" = "Reset";
"lng_background_apply_me" = "Apply for me";
"lng_background_apply_both" = "Apply for me and {user}";
"lng_background_apply_channel" = "Apply For Channel";
"lng_download_path_ask" = "Ask download path for each file";
"lng_download_path" = "Download path";
@@ -1179,6 +1188,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_common_groups#other" = "{count} groups in common";
"lng_profile_similar_channels#one" = "{count} similar channel";
"lng_profile_similar_channels#other" = "{count} similar channels";
"lng_profile_saved_messages#one" = "{count} saved message";
"lng_profile_saved_messages#other" = "{count} saved messages";
"lng_profile_participants_section" = "Members";
"lng_profile_subscribers_section" = "Subscribers";
"lng_profile_add_contact" = "Add Contact";
@@ -1682,7 +1693,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
"lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results_some" = "Some winners of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
"lng_similar_channels_title" = "Similar channels";
@@ -1704,6 +1715,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile.";
"lng_ttl_video_sent" = "You sent a self-destructing video.";
"lng_ttl_video_expired" = "Video has expired";
"lng_ttl_voice_sent" = "You sent a self-destructing voice messsage.";
"lng_ttl_voice_expired" = "Voice message expired";
"lng_ttl_round_sent" = "You sent a self-destructing video message.";
"lng_ttl_round_expired" = "Round message expired";
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
"lng_profile_camera_title" = "Capture yourself";
@@ -1836,6 +1851,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_message_title" = "Sponsored";
"lng_recommended_message_title" = "Recommended";
"lng_edited" = "edited";
"lng_commented" = "commented";
"lng_edited_date" = "Edited: {date}";
"lng_sent_date" = "Sent: {date}";
"lng_views_tooltip#one" = "Views: {count}";
@@ -2055,6 +2071,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_gift_per" = "{cost} / month";
"lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}.";
"lng_premium_gift_terms_link" = "here";
"lng_premium_gifts_about_user1" = "Give **{user}** access to exclusive features.";
"lng_premium_gifts_about_user2" = "Give **{user}** and **{second_user}** access to exclusive features.";
"lng_premium_gifts_about_user3" = "Give **{user}**, **{second_user}** and **{name}** access to exclusive features.";
"lng_premium_gifts_about_user_more#one" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friend access to exclusive features.";
"lng_premium_gifts_about_user_more#other" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friends access to exclusive features.";
"lng_premium_gifts_about_reward#one" = "You will receive {emoji}**{count}** boost.";
"lng_premium_gifts_about_reward#other" = "You will receive {emoji}**{count}** boosts.";
"lng_premium_gifts_about_paid_title" = "Gifts Sent!";
"lng_premium_gifts_about_paid1" = "**{user}** has been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid2" = "**{user}** and **{second_user}** have been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid3" = "**{user}**, **{second_user}** and **{name}** have been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid_more#one" = "**{user}**, **{second_user}**, **{name}** and **{count}** other have been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid_more#other" = "**{user}**, **{second_user}**, **{name}** and **{count}** others have been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid_below#one" = "They now have access to additional features.";
"lng_premium_gifts_about_paid_below#other" = "They now have access to additional features.";
"lng_premium_gifts_summary_subtitle" = "What's Included";
"lng_premium_gifts_terms" = "By gifting Telegram Premium, you agree to the Telegram {link} and {policy}.";
"lng_premium_gifts_terms_policy" = "Privacy Policy";
"lng_boost_channel_button" = "Boost Channel";
"lng_boost_again_button" = "Boost Again";
@@ -2111,6 +2145,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color.";
"lng_boost_channel_title_wallpaper" = "Enable wallpapers";
"lng_boost_channel_needs_level_wallpaper#one" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
"lng_boost_channel_needs_level_wallpaper#other" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
"lng_boost_channel_title_status" = "Enable emoji status";
"lng_boost_channel_needs_level_status#one" = "Your channel needs to reach **Level {count}** to set emoji status.";
"lng_boost_channel_needs_level_status#other" = "Your channel needs to reach **Level {count}** to set emoji status.";
"lng_boost_channel_title_reactions" = "Custom reactions";
"lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction.";
"lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions.";
@@ -2170,6 +2212,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_giveaway_maximum_users_error#other" = "You can select maximum {count} users.";
"lng_giveaway_channels_confirm_title" = "Channel is Private";
"lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link.";
"lng_giveaway_additional_prizes" = "Additional prizes";
"lng_giveaway_additional_about" = "Turn this on if you want to give the winners your own prizes in addition to Premium subscriptions.";
"lng_giveaway_additional_prizes_ph" = "Enter your prize";
"lng_giveaway_prizes_just_premium#one" = "All prizes: **{count}** Telegram Premium subscription {duration}.";
"lng_giveaway_prizes_just_premium#other" = "All prizes: **{count}** Telegram Premium subscriptions {duration}.";
"lng_giveaway_prizes_additional#one" = "All prizes: **{count}** {prize} with Telegram Premium subscription {duration}.";
"lng_giveaway_prizes_additional#other" = "All prizes: **{count}** {prize} with Telegram Premium subscriptions {duration}.";
"lng_giveaway_show_winners" = "Show winners";
"lng_giveaway_show_winners_about" = "Choose whether to make the list of winners public when the giveaway ends.";
"lng_giveaway_created_title" = "Giveaway created";
"lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel.";
@@ -2189,6 +2240,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_title#one" = "Giveaway Prize";
"lng_prizes_title#other" = "Giveaway Prizes";
"lng_prizes_additional#one" = "**{count}** {prize}";
"lng_prizes_additional#other" = "**{count}** {prize}";
"lng_prizes_additional_with" = "with";
"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}.";
"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}.";
"lng_prizes_participants" = "Participants";
@@ -2207,6 +2261,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_end_text" = "This giveaway was sponsored by {admins}.";
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
"lng_prizes_additional_added#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the channel are responsible for delivering this prize.";
"lng_prizes_additional_added#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the channel are responsible for delivering these prizes.";
"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}.";
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
@@ -2232,6 +2288,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
"lng_prizes_badge" = "x{amount}";
"lng_prizes_results_title" = "Winners Selected!";
"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram.";
"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram.";
"lng_prizes_results_link" = "Giveaway";
"lng_prizes_results_winners" = "Winners";
"lng_prizes_results_more#one" = "and {count} more!";
"lng_prizes_results_more#other" = "and {count} more!";
"lng_prizes_results_all" = "All winners received gift links in private messages.";
"lng_prizes_results_some" = "Some winners couldn't be selected.";
"lng_gift_link_title" = "Gift Link";
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
"lng_gift_link_label_from" = "From";
@@ -2435,6 +2501,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_saved_short" = "Save";
"lng_saved_forward_here" = "Forward messages here for quick access";
"lng_saved_quote_here" = "Quote here to save";
"lng_saved_open_chat" = "Open Chat";
"lng_saved_open_channel" = "Open Channel";
"lng_saved_open_group" = "Open Group";
"lng_saved_about_hidden" = "Senders of this messages restricted to link their name when forwarding.";
"lng_scheduled_messages" = "Scheduled Messages";
"lng_scheduled_messages_empty" = "No scheduled messages here yet...";
@@ -2461,6 +2531,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_comments_open_none" = "Leave a comment";
"lng_replies_view_original" = "View in chat";
"lng_replies_messages" = "Replies";
"lng_hidden_author_messages" = "Author Hidden";
"lng_my_notes" = "My Notes";
"lng_replies_discussion_started" = "Discussion started";
"lng_replies_no_comments" = "No comments here yet...";
@@ -2801,6 +2873,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_sign_messages" = "Sign messages";
"lng_edit_group" = "Edit group";
"lng_edit_channel_color" = "Change name color";
"lng_edit_channel_level_min" = "Level 1+";
"lng_edit_channel_wallpaper" = "Channel wallpaper";
"lng_edit_channel_wallpaper_about" = "Set a wallpaper that will be visible for everyone reading your channel.";
"lng_edit_channel_status" = "Channel emoji status";
"lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name.";
"lng_edit_self_title" = "Edit your name";
"lng_confirm_contact_data" = "New Contact";
"lng_add_contact" = "Create";
@@ -3615,6 +3692,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}";
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}";
"lng_admin_log_change_profile_color" = "{from} changed channel profile color from {previous} to {color}";
"lng_admin_log_set_profile_background_emoji" = "{from} set channel profile background emoji to {emoji}";
"lng_admin_log_change_profile_background_emoji" = "{from} changed channel profile background emoji from {previous} to {emoji}";
"lng_admin_log_removed_profile_background_emoji" = "{from} removed channel profile background emoji {emoji}";
"lng_admin_log_change_wallpaper" = "{from} changed channel wallpaper";
"lng_admin_log_set_status" = "{from} set channel emoji status to {emoji}";
"lng_admin_log_change_status" = "{from} changed channel emoji status from {previous} to {emoji}";
"lng_admin_log_removed_status" = "{from} removed channel emoji status {emoji}";
"lng_admin_log_set_status_until" = "{from} set channel emoji status to {emoji} until {date}";
"lng_admin_log_change_status_until" = "{from} changed channel emoji status from {previous} to {emoji} until {date}";
"lng_admin_log_user_with_username" = "{name} ({mention})";
"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
@@ -4207,6 +4294,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_request_peer_confirm_rights" = "This will also add {bot} to {chat} with the following rights: {rights}.";
"lng_request_peer_confirm_send" = "Send";
"lng_request_user_title" = "Choose User";
"lng_request_users_title" = "Choose Users";
"lng_request_user_premium_yes" = "The user should have a Premium subscription.";
"lng_request_user_premium_no" = "The user shouldn't have a Premium subscription.";
"lng_request_user_no" = "No such users";

View File

@@ -11,5 +11,7 @@
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
<file alias="stats.tgs">../../animations/stats.tgs</file>
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
</qresource>
</RCC>

View File

@@ -15,6 +15,7 @@
<file alias="art/slot_2_idle.tgs">../../art/slot_2_idle.tgs</file>
<file alias="art/slot_back.tgs">../../art/slot_back.tgs</file>
<file alias="art/slot_pull.tgs">../../art/slot_pull.tgs</file>
<file alias="art/winners.tgs">../../art/winners.tgs</file>
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="4.12.2.0" />
Version="4.14.2.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 4,12,2,0
PRODUCTVERSION 4,12,2,0
FILEVERSION 4,14,2,0
PRODUCTVERSION 4,14,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "4.12.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "FileVersion", "4.14.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "4.12.2.0"
VALUE "ProductVersion", "4.14.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,12,2,0
PRODUCTVERSION 4,12,2,0
FILEVERSION 4,14,2,0
PRODUCTVERSION 4,14,2,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", "4.12.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "FileVersion", "4.14.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "4.12.2.0"
VALUE "ProductVersion", "4.14.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -6,6 +6,7 @@ For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include <windows.h>
#include <shellapi.h>
#include <array>
#include <string>

View File

@@ -537,11 +537,12 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) {
GetLocalTime(&stLocalTime);
wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
szPath, szExeName, updaterVersionStr,
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
GetCurrentProcessId(), GetCurrentThreadId());
wsprintf(
szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
szPath, szExeName, updaterVersionStr,
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
GetCurrentProcessId(), GetCurrentThreadId());
return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
}
@@ -562,7 +563,7 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) {
DWORD len = GetModuleFileName(GetModuleHandle(0), szPath, maxFileLen);
if (!len) return;
WCHAR *pathEnd = szPath + len;
WCHAR *pathEnd = szPath + len;
if (!_wcsicmp(pathEnd - wcslen(_exeName), _exeName)) {
wsprintf(pathEnd - wcslen(_exeName), L"");

View File

@@ -415,12 +415,16 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
const auto peer = item->history()->peer;
const auto itemId = item->id;
const auto id = int32(button->buttonId);
const auto chosen = [=](not_null<PeerData*> result) {
const auto chosen = [=](std::vector<not_null<PeerData*>> result) {
peer->session().api().request(MTPmessages_SendBotRequestedPeer(
peer->input,
MTP_int(itemId),
MTP_int(id),
result->input
MTP_vector_from_range(
result
| ranges::views::transform([](
not_null<PeerData*> peer) {
return MTPInputPeer(peer->input); }))
)).done([=](const MTPUpdates &result) {
peer->session().api().applyUpdates(result);
}).send();

View File

@@ -90,6 +90,7 @@ void MessagesSearch::searchRequest() {
(_from
? _from->input
: MTP_inputPeerEmpty()),
MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_peer_colors.h"
#include "apiwrap.h"
#include "data/data_peer.h"
#include "ui/chat/chat_style.h"
namespace Api {
@@ -62,6 +63,16 @@ auto PeerColors::indicesValue() const
}));
}
int PeerColors::requiredLevelFor(PeerId channel, uint8 index) const {
if (Data::DecideColorIndex(channel) == index) {
return 0;
} else if (const auto i = _requiredLevels.find(index)
; i != end(_requiredLevels)) {
return i->second;
}
return 1;
}
void PeerColors::apply(const MTPDhelp_peerColors &data) {
auto suggested = std::vector<uint8>();
auto colors = std::make_shared<
@@ -89,6 +100,7 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
};
const auto &list = data.vcolors().v;
_requiredLevels.clear();
suggested.reserve(list.size());
for (const auto &color : list) {
const auto &data = color.data();
@@ -98,6 +110,9 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
continue;
}
const auto colorIndex = uint8(colorIndexBare);
if (const auto min = data.vchannel_min_level()) {
_requiredLevels[colorIndex] = min->v;
}
if (!data.is_hidden()) {
suggested.push_back(colorIndex);
}

View File

@@ -28,6 +28,10 @@ public:
[[nodiscard]] auto indicesValue() const
-> rpl::producer<Ui::ColorIndicesCompressed>;
[[nodiscard]] int requiredLevelFor(
PeerId channel,
uint8 index) const;
private:
void request();
void apply(const MTPDhelp_peerColors &data);
@@ -38,6 +42,7 @@ private:
mtpRequestId _requestId = 0;
base::Timer _timer;
rpl::variable<std::vector<uint8>> _suggested;
base::flat_map<uint8, int> _requiredLevels;
rpl::event_stream<> _colorIndicesChanged;
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;

View File

@@ -515,6 +515,7 @@ auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
case EmojiListType::Profile: return _profileEmojiList;
case EmojiListType::Group: return _groupEmojiList;
case EmojiListType::Background: return _backgroundEmojiList;
case EmojiListType::NoChannelStatus: return _noChannelStatusEmojiList;
}
Unexpected("Type in PeerPhoto::emojiList.");
}
@@ -551,6 +552,8 @@ void PeerPhoto::requestEmojiList(EmojiListType type) {
? send(MTPaccount_GetDefaultProfilePhotoEmojis())
: (type == EmojiListType::Group)
? send(MTPaccount_GetDefaultGroupPhotoEmojis())
: (type == EmojiListType::NoChannelStatus)
? send(MTPaccount_GetChannelRestrictedStatusEmojis())
: send(MTPaccount_GetDefaultBackgroundEmojis());
}

View File

@@ -32,6 +32,7 @@ public:
Profile,
Group,
Background,
NoChannelStatus,
};
struct UserPhoto {
@@ -112,6 +113,7 @@ private:
EmojiListData _profileEmojiList;
EmojiListData _groupEmojiList;
EmojiListData _backgroundEmojiList;
EmojiListData _noChannelStatusEmojiList;
};

View File

@@ -26,7 +26,7 @@ namespace {
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
return {
.from = peerFromMTP(data.vfrom_id()),
.from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(),
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
.giveawayId = data.vgiveaway_msg_id().value_or_empty(),
.date = data.vdate().v,
@@ -342,15 +342,12 @@ PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null<PeerData*> peer)
rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto channel = _peer->asChannel();
if (!channel) {
return lifetime;
}
using TLOption = MTPPremiumGiftCodeOption;
_api.request(MTPpayments_GetPremiumGiftCodeOptions(
MTP_flags(
MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer),
MTP_flags(_peer->isChannel()
? MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer
: MTPpayments_GetPremiumGiftCodeOptions::Flag(0)),
_peer->input
)).done([=](const MTPVector<TLOption> &result) {
auto tlMapOptions = base::flat_map<Amount, QVector<TLOption>>();
@@ -420,6 +417,8 @@ const std::vector<int> &PremiumGiftCodeOptions::availablePresets() const {
}
[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) {
Expects(monthsIndex >= 0 && monthsIndex < _availablePresets.size());
return _optionsForOnePerson.months[monthsIndex];
}

View File

@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_story.h"
#include "history/history.h"
#include "main/main_session.h"
#include "statistics/statistics_data_deserialize.h"
@@ -359,161 +361,54 @@ PublicForwards::PublicForwards(
void PublicForwards::request(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done) {
if (!_requestId) {
if (_fullId.messageId) {
requestMessage(token, std::move(done));
} else if (_fullId.storyId) {
requestStory(token, std::move(done));
}
if (_requestId) {
return;
}
}
void PublicForwards::requestMessage(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done) {
Expects(_fullId.messageId);
const auto offsetPeer = channel()->owner().peer(token.fullId.peer);
const auto tlOffsetPeer = offsetPeer
? offsetPeer->input
: MTP_inputPeerEmpty();
constexpr auto kLimit = tl::make_int(100);
_requestId = makeRequest(MTPstats_GetMessagePublicForwards(
channel()->inputChannel,
MTP_int(_fullId.messageId.msg),
MTP_int(token.rate),
tlOffsetPeer,
MTP_int(token.fullId.msg),
kLimit
)).done([=, channel = channel()](const MTPmessages_Messages &result) {
const auto channel = StatisticsRequestSender::channel();
const auto processResult = [=](const MTPstats_PublicForwards &tl) {
using Messages = QVector<Data::RecentPostId>;
_requestId = 0;
auto nextToken = Data::PublicForwardsSlice::OffsetToken();
const auto process = [&](const MTPVector<MTPMessage> &messages) {
auto result = Messages();
for (const auto &message : messages.v) {
const auto msgId = IdFromMessage(message);
const auto peerId = PeerFromMessage(message);
const auto lastDate = DateFromMessage(message);
if (const auto peer = channel->owner().peerLoaded(peerId)) {
if (lastDate) {
channel->owner().addNewMessage(
message,
MessageFlags(),
NewMessageType::Existing);
nextToken.fullId = { peerId, msgId };
result.push_back({ .messageId = nextToken.fullId });
}
}
}
return result;
};
const auto &data = tl.data();
auto &owner = channel->owner();
auto allLoaded = false;
auto fullCount = 0;
auto messages = result.match([&](const MTPDmessages_messages &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
allLoaded = true;
fullCount = list.size();
return list;
}, [&](const MTPDmessages_messagesSlice &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
owner.processUsers(data.vusers());
owner.processChats(data.vchats());
if (const auto nextRate = data.vnext_rate()) {
const auto rateUpdated = (nextRate->v != token.rate);
if (rateUpdated) {
nextToken.rate = nextRate->v;
} else {
allLoaded = true;
}
}
fullCount = data.vcount().v;
return list;
}, [&](const MTPDmessages_channelMessages &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
allLoaded = true;
fullCount = data.vcount().v;
return list;
}, [&](const MTPDmessages_messagesNotModified &) {
allLoaded = true;
return Messages();
});
const auto nextToken = data.vnext_offset()
? qs(*data.vnext_offset())
: Data::PublicForwardsSlice::OffsetToken();
_lastTotal = std::max(_lastTotal, fullCount);
done({
.list = std::move(messages),
.total = _lastTotal,
.allLoaded = allLoaded,
.token = nextToken,
});
}).fail([=] {
_requestId = 0;
}).send();
}
void PublicForwards::requestStory(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done) {
Expects(_fullId.storyId);
constexpr auto kLimit = tl::make_int(100);
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
channel()->input,
MTP_int(_fullId.storyId.story),
MTP_string(token.storyOffset),
kLimit
)).done([=, channel = channel()](
const MTPstats_PublicForwards &tlForwards) {
using Messages = QVector<Data::RecentPostId>;
_requestId = 0;
const auto &data = tlForwards.data();
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
const auto nextToken = Data::PublicForwardsSlice::OffsetToken({
.storyOffset = data.vnext_offset().value_or_empty(),
});
const auto allLoaded = nextToken.storyOffset.isEmpty()
|| (nextToken.storyOffset == token.storyOffset);
const auto fullCount = data.vcount().v;
auto recentList = Messages();
auto recentList = Messages(data.vforwards().v.size());
for (const auto &tlForward : data.vforwards().v) {
tlForward.match([&](const MTPDpublicForwardMessage &data) {
const auto &message = data.vmessage();
const auto msgId = IdFromMessage(message);
const auto peerId = PeerFromMessage(message);
const auto lastDate = DateFromMessage(message);
if (const auto peer = channel->owner().peerLoaded(peerId)) {
if (const auto peer = owner.peerLoaded(peerId)) {
if (!lastDate) {
return;
}
channel->owner().addNewMessage(
owner.addNewMessage(
message,
MessageFlags(),
NewMessageType::Existing);
recentList.push_back({ .messageId = { peerId, msgId } });
}
}, [&](const MTPDpublicForwardStory &data) {
data.vstory().match([&](const MTPDstoryItem &d) {
recentList.push_back({
.storyId = { peerFromMTP(data.vpeer()), d.vid().v }
});
}, [](const auto &) {
});
const auto story = owner.stories().applySingle(
peerFromMTP(data.vpeer()),
data.vstory());
if (story) {
recentList.push_back({ .storyId = story->fullId() });
}
});
}
const auto allLoaded = nextToken.isEmpty() || (nextToken == token);
_lastTotal = std::max(_lastTotal, fullCount);
done({
.list = std::move(recentList),
@@ -521,9 +416,24 @@ void PublicForwards::requestStory(
.allLoaded = allLoaded,
.token = nextToken,
});
}).fail([=] {
_requestId = 0;
}).send();
};
constexpr auto kLimit = tl::make_int(100);
if (_fullId.messageId) {
_requestId = makeRequest(MTPstats_GetMessagePublicForwards(
channel->inputChannel,
MTP_int(_fullId.messageId.msg),
MTP_string(token),
kLimit
)).done(processResult).fail([=] { _requestId = 0; }).send();
} else if (_fullId.storyId) {
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
channel->input,
MTP_int(_fullId.storyId.story),
MTP_string(token),
kLimit
)).done(processResult).fail([=] { _requestId = 0; }).send();
}
}
MessageStatistics::MessageStatistics(
@@ -702,6 +612,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
_peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
channel->updateLevelHint(data.vlevel().v);
const auto hasPremium = !!data.vpremium_audience();
const auto premiumMemberCount = hasPremium
? std::max(0, int(data.vpremium_audience()->data().vpart().v))

View File

@@ -77,13 +77,6 @@ public:
Fn<void(Data::PublicForwardsSlice)> done);
private:
void requestMessage(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done);
void requestStory(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done);
const Data::RecentPostId _fullId;
mtpRequestId _requestId = 0;
int _lastTotal = 0;

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_dc_options.h"
#include "data/notify/data_notify_settings.h"
#include "data/stickers/data_stickers.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_chat.h"
@@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_cloud_manager.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/history_unread_things.h"
#include "core/application.h"
#include "storage/storage_account.h"
@@ -1111,6 +1113,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
? peerToMTP(_session->userPeerId())
: MTP_peerUser(d.vuser_id())),
MTP_peerUser(d.vuser_id()),
MTPPeer(), // saved_peer_id
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(d.vvia_bot_id().value_or_empty()),
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
@@ -1142,6 +1145,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
d.vid(),
MTP_peerUser(d.vfrom_id()),
MTP_peerChat(d.vchat_id()),
MTPPeer(), // saved_peer_id
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(d.vvia_bot_id().value_or_empty()),
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
@@ -1202,11 +1206,12 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
item->markMediaAndMentionRead();
_session->data().requestItemRepaint(item);
if (item->out()
&& item->history()->peer->isUser()
&& !requestingDifference()) {
item->history()->peer->asUser()->madeAction(
base::unixtime::now());
if (item->out()) {
const auto user = item->history()->peer->asUser();
if (user && !requestingDifference()) {
user->madeAction(base::unixtime::now());
}
ClearMediaAsExpired(item);
}
}
} else {
@@ -2204,6 +2209,16 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
} break;
case mtpc_updatePinnedSavedDialogs: {
session().data().savedMessages().apply(
update.c_updatePinnedSavedDialogs());
} break;
case mtpc_updateSavedDialogPinned: {
session().data().savedMessages().apply(
update.c_updateSavedDialogPinned());
} break;
case mtpc_updateChannel: {
auto &d = update.c_updateChannel();
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {

View File

@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_forum.h"
#include "data/data_saved_sublist.h"
#include "data/data_search_controller.h"
#include "data/data_scheduled_messages.h"
#include "data/data_session.h"
@@ -221,11 +222,11 @@ void ApiWrap::setupSupportMode() {
void ApiWrap::requestChangelog(
const QString &sinceVersion,
Fn<void(const MTPUpdates &result)> callback) {
request(MTPhelp_GetAppChangelog(
MTP_string(sinceVersion)
)).done(
callback
).send();
//request(MTPhelp_GetAppChangelog(
// MTP_string(sinceVersion)
//)).done(
// callback
//).send();
}
void ApiWrap::refreshTopPromotion() {
@@ -440,6 +441,26 @@ void ApiWrap::savePinnedOrder(not_null<Data::Forum*> forum) {
}).send();
}
void ApiWrap::savePinnedOrder(not_null<Data::SavedMessages*> saved) {
const auto &order = _session->data().pinnedChatsOrder(saved);
const auto input = [](Dialogs::Key key) {
if (const auto sublist = key.sublist()) {
return MTP_inputDialogPeer(sublist->peer()->input);
}
Unexpected("Key type in pinnedDialogsOrder().");
};
auto peers = QVector<MTPInputDialogPeer>();
peers.reserve(order.size());
ranges::transform(
order,
ranges::back_inserter(peers),
input);
request(MTPmessages_ReorderPinnedSavedDialogs(
MTP_flags(MTPmessages_ReorderPinnedSavedDialogs::Flag::f_force),
MTP_vector(peers)
)).send();
}
void ApiWrap::toggleHistoryArchived(
not_null<History*> history,
bool archived,

View File

@@ -34,6 +34,7 @@ class Forum;
class ForumTopic;
class Thread;
class Story;
class SavedMessages;
} // namespace Data
namespace InlineBots {
@@ -152,6 +153,7 @@ public:
void savePinnedOrder(Data::Folder *folder);
void savePinnedOrder(not_null<Data::Forum*> forum);
void savePinnedOrder(not_null<Data::SavedMessages*> saved);
void toggleHistoryArchived(
not_null<History*> history,
bool archived,

View File

@@ -129,6 +129,8 @@ private:
int row) const;
void validatePaperThumbnail(const Paper &paper) const;
[[nodiscard]] bool forChannel() const;
const not_null<Main::Session*> _session;
PeerData * const _forPeer = nullptr;
@@ -176,6 +178,23 @@ void BackgroundBox::prepare() {
st::infoIconMediaPhoto,
st::infoSharedMediaButtonIconPosition);
if (forChannel() && _forPeer->wallPaper()) {
const auto remove = container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_settings_bg_remove(),
st::infoBlockButton));
object_ptr<Info::Profile::FloatingIcon>(
remove,
st::infoIconDeleteRed,
st::infoSharedMediaButtonIconPosition);
remove->setClickedCallback([=] {
if (const auto resolved = _inner->resolveResetCustomPaper()) {
chosen(*resolved);
}
});
}
button->setClickedCallback([=] {
chooseFromFile();
});
@@ -290,6 +309,23 @@ void BackgroundBox::chosen(const Data::WallPaper &paper) {
closeBox();
}
return;
} else if (forChannel()) {
if (_forPeer->wallPaper() && _forPeer->wallPaper()->equals(paper)) {
closeBox();
return;
}
const auto &themes = _forPeer->owner().cloudThemes();
for (const auto &theme : themes.chatThemes()) {
for (const auto &[type, themed] : theme.settings) {
if (themed.paper && themed.paper->equals(paper)) {
_controller->show(Box<BackgroundPreviewBox>(
_controller,
Data::WallPaper::FromEmojiId(theme.emoticon),
BackgroundPreviewArgs{ _forPeer }));
return;
}
}
}
}
_controller->show(Box<BackgroundPreviewBox>(
_controller,
@@ -316,6 +352,10 @@ void BackgroundBox::resetForPeer() {
}
}
bool BackgroundBox::forChannel() const {
return _forPeer && _forPeer->isChannel();
}
void BackgroundBox::removePaper(const Data::WallPaper &paper) {
const auto session = &_controller->session();
const auto remove = [=, weak = Ui::MakeWeak(this)](Fn<void()> &&close) {
@@ -345,9 +385,16 @@ BackgroundBox::Inner::Inner(
, _session(session)
, _forPeer(forPeer)
, _api(&_session->mtp())
, _check(std::make_unique<Ui::RoundCheckbox>(st::overviewCheck, [=] { update(); })) {
, _check(
std::make_unique<Ui::RoundCheckbox>(
st::overviewCheck,
[=] { update(); })) {
_check->setChecked(true, anim::type::instant);
resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
resize(
st::boxWideWidth,
(2 * (st::backgroundSize.height() + st::backgroundPadding)
+ st::backgroundPadding));
Window::Theme::IsNightModeValue(
) | rpl::start_with_next([=] {
updatePapers();
@@ -364,21 +411,31 @@ BackgroundBox::Inner::Inner(
_check->invalidateCache();
}, lifetime());
using Update = Window::Theme::BackgroundUpdate;
Window::Theme::Background()->updates(
) | rpl::start_with_next([=](const Update &update) {
if (update.type == Update::Type::New) {
sortPapers();
requestPapers();
this->update();
}
}, lifetime());
if (forChannel()) {
_session->data().cloudThemes().chatThemesUpdated(
) | rpl::start_with_next([=] {
updatePapers();
}, lifetime());
} else {
using Update = Window::Theme::BackgroundUpdate;
Window::Theme::Background()->updates(
) | rpl::start_with_next([=](const Update &update) {
if (update.type == Update::Type::New) {
sortPapers();
requestPapers();
this->update();
}
}, lifetime());
}
setMouseTracking(true);
}
void BackgroundBox::Inner::requestPapers() {
if (forChannel()) {
_session->data().cloudThemes().refreshChatThemes();
return;
}
_api.request(MTPaccount_GetWallPapers(
MTP_long(_session->data().wallpapersHash())
)).done([=](const MTPaccount_WallPapers &result) {
@@ -395,7 +452,7 @@ auto BackgroundBox::Inner::resolveResetCustomPaper() const
}
const auto nonCustom = Window::Theme::Background()->paper();
const auto themeEmoji = _forPeer->themeEmoji();
if (themeEmoji.isEmpty()) {
if (forChannel() || themeEmoji.isEmpty()) {
return nonCustom;
}
const auto &themes = _forPeer->owner().cloudThemes();
@@ -443,6 +500,8 @@ void BackgroundBox::Inner::pushCustomPapers() {
}
void BackgroundBox::Inner::sortPapers() {
Expects(!forChannel());
const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr;
_currentId = currentCustom
? currentCustom->id()
@@ -472,23 +531,60 @@ void BackgroundBox::Inner::sortPapers() {
}
void BackgroundBox::Inner::updatePapers() {
if (_session->data().wallpapers().empty()) {
return;
if (forChannel()) {
if (_session->data().cloudThemes().chatThemes().empty()) {
return;
}
} else {
if (_session->data().wallpapers().empty()) {
return;
}
}
_over = _overDown = Selection();
_papers = _session->data().wallpapers(
) | ranges::views::filter([&](const Data::WallPaper &paper) {
return (!paper.isPattern() || !paper.backgroundColors().empty())
&& (!_forPeer
|| (!Data::IsDefaultWallPaper(paper)
&& (Data::IsCloudWallPaper(paper)
|| Data::IsCustomWallPaper(paper))));
}) | ranges::views::transform([](const Data::WallPaper &paper) {
return Paper{ paper };
}) | ranges::to_vector;
pushCustomPapers();
sortPapers();
const auto was = base::take(_papers);
if (forChannel()) {
const auto now = _forPeer->wallPaper();
const auto &list = _session->data().cloudThemes().chatThemes();
if (list.empty()) {
return;
}
using Type = Data::CloudThemeType;
const auto type = Window::Theme::IsNightMode()
? Type::Dark
: Type::Light;
_papers.reserve(list.size() + 1);
const auto nowEmojiId = now ? now->emojiId() : QString();
if (!now || !now->emojiId().isEmpty()) {
_papers.push_back({ Window::Theme::Background()->paper() });
_currentId = _papers.back().data.id();
} else {
_papers.push_back({ *now });
_currentId = now->id();
}
for (const auto &theme : list) {
const auto i = theme.settings.find(type);
if (i != end(theme.settings) && i->second.paper) {
_papers.push_back({ *i->second.paper });
if (nowEmojiId == theme.emoticon) {
_currentId = _papers.back().data.id();
}
}
}
} else {
_papers = _session->data().wallpapers(
) | ranges::views::filter([&](const Data::WallPaper &paper) {
return (!paper.isPattern() || !paper.backgroundColors().empty())
&& (!_forPeer
|| (!Data::IsDefaultWallPaper(paper)
&& (Data::IsCloudWallPaper(paper)
|| Data::IsCustomWallPaper(paper))));
}) | ranges::views::transform([](const Data::WallPaper &paper) {
return Paper{ paper };
}) | ranges::to_vector;
pushCustomPapers();
sortPapers();
}
resizeToContentAndPreload();
}
@@ -587,6 +683,10 @@ void BackgroundBox::Inner::validatePaperThumbnail(
paper.thumbnail.setDevicePixelRatio(cRetinaFactor());
}
bool BackgroundBox::Inner::forChannel() const {
return _forPeer && _forPeer->isChannel();
}
void BackgroundBox::Inner::paintPaper(
QPainter &p,
const Paper &paper,
@@ -604,7 +704,8 @@ void BackgroundBox::Inner::paintPaper(
const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size;
const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size;
_check->paint(p, checkLeft, checkTop, width());
} else if (Data::IsCloudWallPaper(paper.data)
} else if (!forChannel()
&& Data::IsCloudWallPaper(paper.data)
&& !Data::IsDefaultWallPaper(paper.data)
&& !Data::IsLegacy2DefaultWallPaper(paper.data)
&& !Data::IsLegacy3DefaultWallPaper(paper.data)
@@ -642,7 +743,8 @@ void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) {
- st::stickerPanDeleteIconBg.width();
const auto deleteBottom = row * (height + skip) + skip
+ st::stickerPanDeleteIconBg.height();
const auto inDelete = (x >= deleteLeft)
const auto inDelete = !forChannel()
&& (x >= deleteLeft)
&& (y < deleteBottom)
&& Data::IsCloudWallPaper(data)
&& !Data::IsDefaultWallPaper(data)

View File

@@ -38,6 +38,7 @@ private:
const Data::WallPaper &paper) const;
void removePaper(const Data::WallPaper &paper);
void resetForPeer();
[[nodiscard]] bool forChannel() const;
void chooseFromFile();

View File

@@ -8,11 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/background_preview_box.h"
#include "base/unixtime.h"
#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/premium_preview_box.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "window/themes/window_theme.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/boost_box.h"
#include "ui/controls/chat_service_checkbox.h"
#include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h"
@@ -29,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/view/history_view_message.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "data/data_session.h"
@@ -53,7 +57,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kMaxWallPaperSlugLength = 255;
constexpr auto kDefaultDimming = 50;
[[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) {
if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) {
@@ -159,6 +162,25 @@ constexpr auto kDefaultDimming = 50;
return result;
}
[[nodiscard]] Data::WallPaper Resolve(
not_null<Main::Session*> session,
const Data::WallPaper &paper,
bool dark) {
if (paper.emojiId().isEmpty()) {
return paper;
}
const auto &themes = session->data().cloudThemes();
if (const auto theme = themes.themeForEmoji(paper.emojiId())) {
using Type = Data::CloudThemeType;
const auto type = dark ? Type::Dark : Type::Light;
const auto i = theme->settings.find(type);
if (i != end(theme->settings) && i->second.paper) {
return *i->second.paper;
}
}
return paper;
}
} // namespace
struct BackgroundPreviewBox::OverridenStyle {
@@ -196,15 +218,17 @@ BackgroundPreviewBox::BackgroundPreviewBox(
? tr::lng_background_apply2(tr::now)
: tr::lng_background_text2(tr::now)),
true))
, _paper(paper)
, _paperEmojiId(paper.emojiId())
, _paper(
Resolve(&controller->session(), paper, Window::Theme::IsNightMode()))
, _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
, _radial([=](crl::time now) { radialAnimationCallback(now); })
, _appNightMode(Window::Theme::IsNightModeValue())
, _boxDarkMode(_appNightMode.current())
, _dimmingIntensity(std::clamp(paper.patternIntensity(), 0, 100))
, _dimmingIntensity(std::clamp(_paper.patternIntensity(), 0, 100))
, _dimmed(_forPeer
&& (paper.document() || paper.localThumbnail())
&& !paper.isPattern()) {
&& (_paper.document() || _paper.localThumbnail())
&& !_paper.isPattern()) {
if (_media) {
_media->thumbnailWanted(_paper.fileOrigin());
}
@@ -244,7 +268,36 @@ BackgroundPreviewBox::BackgroundPreviewBox(
BackgroundPreviewBox::~BackgroundPreviewBox() = default;
void BackgroundPreviewBox::recreate(bool dark) {
_paper = Resolve(
&_controller->session(),
Data::WallPaper::FromEmojiId(_paperEmojiId),
dark);
_media = _paper.document()
? _paper.document()->createMediaView()
: nullptr;
if (_media) {
_media->thumbnailWanted(_paper.fileOrigin());
}
_full = QImage();
_generated = _scaled = _blurred = _fadeOutThumbnail = QPixmap();
_generating = {};
generateBackground();
_paper.loadDocument();
if (const auto document = _paper.document()) {
if (document->loading()) {
_radial.start(_media->progress());
}
}
checkLoadedDocument();
updateServiceBg(_paper.backgroundColors());
update();
}
void BackgroundPreviewBox::applyDarkMode(bool dark) {
if (!_paperEmojiId.isEmpty()) {
recreate(dark);
}
const auto equals = (dark == Window::Theme::IsNightMode());
const auto &palette = (dark ? _darkPalette : _lightPalette);
if (!equals && !palette) {
@@ -410,6 +463,10 @@ auto BackgroundPreviewBox::prepareOverridenStyle(bool dark)
return result;
}
bool BackgroundPreviewBox::forChannel() const {
return _forPeer && _forPeer->isChannel();
}
void BackgroundPreviewBox::generateBackground() {
if (_paper.backgroundColors().empty()) {
return;
@@ -435,7 +492,9 @@ void BackgroundPreviewBox::resetTitle() {
void BackgroundPreviewBox::rebuildButtons(bool dark) {
clearButtons();
addButton(_forPeer
addButton(forChannel()
? tr::lng_background_apply_channel()
: _forPeer
? tr::lng_background_apply_button()
: tr::lng_settings_apply(), [=] { apply(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
@@ -624,6 +683,36 @@ void BackgroundPreviewBox::setExistingForPeer(
_controller->finishChatThemeEdit(_forPeer);
}
void BackgroundPreviewBox::checkLevelForChannel() {
Expects(forChannel());
const auto show = _controller->uiShow();
_forPeerLevelCheck = true;
const auto weak = Ui::MakeWeak(this);
CheckBoostLevel(show, _forPeer, [=](int level) {
if (!weak) {
return std::optional<Ui::AskBoostReason>();
}
const auto appConfig = &_forPeer->session().account().appConfig();
const auto defaultRequired = appConfig->get<int>(
"channel_wallpaper_level_min",
9);
const auto customRequired = appConfig->get<int>(
"channel_custom_wallpaper_level_min",
10);
const auto required = _paperEmojiId.isEmpty()
? customRequired
: defaultRequired;
if (level >= required) {
applyForPeer(false);
return std::optional<Ui::AskBoostReason>();
}
return std::make_optional(Ui::AskBoostReason{
Ui::AskBoostWallpaper{ required }
});
}, [=] { _forPeerLevelCheck = false; });
}
void BackgroundPreviewBox::applyForPeer() {
Expects(_forPeer != nullptr);
@@ -636,105 +725,110 @@ void BackgroundPreviewBox::applyForPeer() {
}
}
if (!_fromMessageId && _forPeer->session().premiumPossible()) {
if (_forBothOverlay) {
return;
}
const auto size = this->size() * style::DevicePixelRatio();
const auto bg = Images::DitherImage(
Images::BlurLargeImage(
Ui::GrabWidgetToImage(this).scaled(
size / style::ConvertScale(4),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation),
24).scaled(
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
_forBothOverlay = std::make_unique<Ui::FadeWrap<>>(
this,
object_ptr<Ui::RpWidget>(this));
const auto overlay = _forBothOverlay->entity();
sizeValue() | rpl::start_with_next([=](QSize size) {
_forBothOverlay->setGeometry({ QPoint(), size });
overlay->setGeometry({ QPoint(), size });
}, _forBothOverlay->lifetime());
overlay->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(overlay);
p.drawImage(0, 0, bg);
p.fillRect(clip, QColor(0, 0, 0, 64));
}, overlay->lifetime());
using namespace Ui;
const auto forMe = CreateChild<RoundButton>(
overlay,
tr::lng_background_apply_me(),
st::backgroundConfirm);
forMe->setClickedCallback([=] {
applyForPeer(false);
});
using namespace rpl::mappers;
const auto forBoth = ::Settings::CreateLockedButton(
overlay,
tr::lng_background_apply_both(
lt_user,
rpl::single(_forPeer->shortName())),
st::backgroundConfirm,
Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
forBoth->setClickedCallback([=] {
if (_forPeer->session().premium()) {
applyForPeer(true);
} else {
ShowPremiumPreviewBox(
_controller->uiShow(),
PremiumPreview::Wallpapers);
}
});
const auto cancel = CreateChild<RoundButton>(
overlay,
tr::lng_cancel(),
st::backgroundConfirmCancel);
cancel->setClickedCallback([=] {
const auto raw = _forBothOverlay.release();
raw->shownValue() | rpl::filter(
!rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
delete raw;
}), raw->lifetime());
raw->toggle(false, anim::type::normal);
});
forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
overlay->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto padding = st::backgroundConfirmPadding;
const auto width = size.width()
- padding.left()
- padding.right();
const auto height = cancel->height();
auto top = size.height() - padding.bottom() - height;
cancel->setGeometry(padding.left(), top, width, height);
top -= height + padding.top();
forBoth->setGeometry(padding.left(), top, width, height);
top -= height + padding.top();
forMe->setGeometry(padding.left(), top, width, height);
}, _forBothOverlay->lifetime());
_forBothOverlay->hide(anim::type::instant);
_forBothOverlay->show(anim::type::normal);
} else {
if (forChannel()) {
checkLevelForChannel();
return;
} else if (_fromMessageId || !_forPeer->session().premiumPossible()) {
applyForPeer(false);
return;
} else if (_forBothOverlay) {
return;
}
const auto size = this->size() * style::DevicePixelRatio();
const auto bg = Images::DitherImage(
Images::BlurLargeImage(
Ui::GrabWidgetToImage(this).scaled(
size / style::ConvertScale(4),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation),
24).scaled(
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
_forBothOverlay = std::make_unique<Ui::FadeWrap<>>(
this,
object_ptr<Ui::RpWidget>(this));
const auto overlay = _forBothOverlay->entity();
sizeValue() | rpl::start_with_next([=](QSize size) {
_forBothOverlay->setGeometry({ QPoint(), size });
overlay->setGeometry({ QPoint(), size });
}, _forBothOverlay->lifetime());
overlay->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(overlay);
p.drawImage(0, 0, bg);
p.fillRect(clip, QColor(0, 0, 0, 64));
}, overlay->lifetime());
using namespace Ui;
const auto forMe = CreateChild<RoundButton>(
overlay,
tr::lng_background_apply_me(),
st::backgroundConfirm);
forMe->setClickedCallback([=] {
applyForPeer(false);
});
using namespace rpl::mappers;
const auto forBoth = ::Settings::CreateLockedButton(
overlay,
tr::lng_background_apply_both(
lt_user,
rpl::single(_forPeer->shortName())),
st::backgroundConfirm,
Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
forBoth->setClickedCallback([=] {
if (_forPeer->session().premium()) {
applyForPeer(true);
} else {
ShowPremiumPreviewBox(
_controller->uiShow(),
PremiumPreview::Wallpapers);
}
});
const auto cancel = CreateChild<RoundButton>(
overlay,
tr::lng_cancel(),
st::backgroundConfirmCancel);
cancel->setClickedCallback([=] {
const auto raw = _forBothOverlay.release();
raw->shownValue() | rpl::filter(
!rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
delete raw;
}), raw->lifetime());
raw->toggle(false, anim::type::normal);
});
forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
overlay->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto padding = st::backgroundConfirmPadding;
const auto width = size.width()
- padding.left()
- padding.right();
const auto height = cancel->height();
auto top = size.height() - padding.bottom() - height;
cancel->setGeometry(padding.left(), top, width, height);
top -= height + padding.top();
forBoth->setGeometry(padding.left(), top, width, height);
top -= height + padding.top();
forMe->setGeometry(padding.left(), top, width, height);
}, _forBothOverlay->lifetime());
_forBothOverlay->hide(anim::type::instant);
_forBothOverlay->show(anim::type::normal);
}
void BackgroundPreviewBox::applyForPeer(bool both) {
if (Data::IsCustomWallPaper(_paper)) {
using namespace Data;
if (forChannel() && !_paperEmojiId.isEmpty()) {
setExistingForPeer(WallPaper::FromEmojiId(_paperEmojiId), both);
} else if (IsCustomWallPaper(_paper)) {
uploadForPeer(both);
} else {
setExistingForPeer(_paper, both);
@@ -855,7 +949,7 @@ int BackgroundPreviewBox::textsTop() const {
- st::historyPaddingBottom
- (_service ? _service->height() : 0)
- _text1->height()
- _text2->height();
- (forChannel() ? _text2->height() : 0);
}
QRect BackgroundPreviewBox::radialRect() const {
@@ -885,10 +979,11 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
context.outbg = _text1->hasOutLayout();
_text1->draw(p, context);
p.translate(0, height1);
context.outbg = _text2->hasOutLayout();
_text2->draw(p, context);
p.translate(0, height2);
if (!forChannel()) {
context.outbg = _text2->hasOutLayout();
_text2->draw(p, context);
p.translate(0, height2);
}
}
void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
@@ -988,7 +1083,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
_service = GenerateServiceItem(
delegate(),
_serviceHistory,
((_forPeer && !_fromMessageId)
(forChannel()
? tr::lng_background_other_channel(tr::now)
: (_forPeer && !_fromMessageId)
? tr::lng_background_other_info(
tr::now,
lt_user,

View File

@@ -94,18 +94,24 @@ private:
void applyDarkMode(bool dark);
[[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark);
[[nodiscard]] bool forChannel() const;
void checkLevelForChannel();
void recreate(bool dark);
void resetTitle();
void rebuildButtons(bool dark);
void createDimmingSlider(bool dark);
const not_null<Window::SessionController*> _controller;
PeerData * const _forPeer = nullptr;
bool _forPeerLevelCheck = false;
FullMsgId _fromMessageId;
std::unique_ptr<Ui::ChatStyle> _chatStyle;
const not_null<History*> _serviceHistory;
AdminLog::OwnedItem _service;
AdminLog::OwnedItem _text1;
AdminLog::OwnedItem _text2;
QString _paperEmojiId;
Data::WallPaper _paper;
std::shared_ptr<Data::DocumentMedia> _media;
QImage _full;

View File

@@ -113,7 +113,8 @@ Base64UrlInput::Base64UrlInput(
rpl::producer<QString> placeholder,
const QString &val)
: MaskedInputField(parent, st, std::move(placeholder), val) {
if (!QRegularExpression("^[a-zA-Z0-9_\\-]+$").match(val).hasMatch()) {
static const auto RegExp = QRegularExpression("^[a-zA-Z0-9_\\-]+$");
if (!RegExp.match(val).hasMatch()) {
setText(QString());
}
}
@@ -831,8 +832,9 @@ void ProxyBox::prepare() {
connect(_host.data(), &HostInput::changed, [=] {
Ui::PostponeCall(_host, [=] {
const auto host = _host->getLastText().trimmed();
static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q;
const auto match = QRegularExpression(mask).match(host);
static const auto mask = QRegularExpression(
u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q);
const auto match = mask.match(host);
if (_host->cursorPosition() == host.size()
&& match.hasMatch()) {
const auto port = match.captured(1);
@@ -1107,6 +1109,10 @@ void ProxiesBoxController::ShowApplyConfirmation(
proxy.password = fields.value(u"secret"_q);
}
if (proxy) {
static const auto UrlStartRegExp = QRegularExpression(
"^https://",
QRegularExpression::CaseInsensitiveOption);
static const auto UrlEndRegExp = QRegularExpression("/$");
const auto displayed = "https://" + server + "/";
const auto parsed = QUrl::fromUserInput(displayed);
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
@@ -1117,11 +1123,9 @@ void ProxiesBoxController::ShowApplyConfirmation(
const auto displayServer = QString(
displayUrl
).replace(
QRegularExpression(
"^https://",
QRegularExpression::CaseInsensitiveOption),
UrlStartRegExp,
QString()
).replace(QRegularExpression("/$"), QString());
).replace(UrlEndRegExp, QString());
const auto text = tr::lng_sure_enable_socks(
tr::now,
lt_server,

View File

@@ -12,18 +12,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h"
#include "boxes/peer_list_controllers.h" // ContactsBoxController.
#include "boxes/peers/prepare_short_info_box.h"
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/data_boosts.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_media_types.h" // Data::Giveaway
#include "data/data_media_types.h" // Data::GiveawayStart.
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
#include "data/data_session.h"
#include "data/data_subscription_option.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "info/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "payments/payments_checkout_process.h"
#include "payments/payments_form.h"
#include "settings/settings_premium.h"
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
#include "ui/boxes/boost_box.h" // StartFireworks.
@@ -33,11 +41,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/premium_top_bar.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/table_layout.h"
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
#include "window/window_session_controller.h"
@@ -51,7 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kDiscountDivider = 5.;
constexpr auto kUserpicsMax = size_t(3);
using GiftOption = Data::SubscriptionOption;
using GiftOptions = Data::SubscriptionOptions;
@@ -72,6 +83,137 @@ GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
return result;
}
using TagUser1 = lngtag_user;
using TagUser2 = lngtag_second_user;
using TagUser3 = lngtag_name;
[[nodiscard]] rpl::producer<TextWithEntities> ComplexAboutLabel(
const std::vector<not_null<UserData*>> &users,
tr::phrase<TagUser1> phrase1,
tr::phrase<TagUser1, TagUser2> phrase2,
tr::phrase<TagUser1, TagUser2, TagUser3> phrase3,
tr::phrase<lngtag_count, TagUser1, TagUser2, TagUser3> phraseMore) {
Expects(!users.empty());
const auto count = users.size();
const auto nameValue = [&](not_null<UserData*> user) {
return user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::Name
) | rpl::map([=] { return TextWithEntities{ user->firstName }; });
};
if (count == 1) {
return phrase1(
lt_user,
nameValue(users.front()),
Ui::Text::RichLangValue);
} else if (count == 2) {
return phrase2(
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
Ui::Text::RichLangValue);
} else if (count == 3) {
return phrase3(
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
lt_name,
nameValue(users[2]),
Ui::Text::RichLangValue);
} else {
return phraseMore(
lt_count,
rpl::single(count - kUserpicsMax) | tr::to_count(),
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
lt_name,
nameValue(users[2]),
Ui::Text::RichLangValue);
}
}
[[nodiscard]] not_null<Ui::RpWidget*> CircleBadge(
not_null<Ui::RpWidget*> parent,
const QString &text) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto full = Rect(st::premiumGiftsUserpicBadgeSize);
const auto inner = full - Margins(st::premiumGiftsUserpicBadgeInner);
auto gradient = QLinearGradient(
QPointF(0, full.height()),
QPointF(full.width(), 0));
gradient.setStops(Ui::Premium::GiftGradientStops());
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::boxBg);
p.drawEllipse(full);
p.setPen(Qt::NoPen);
p.setBrush(gradient);
p.drawEllipse(inner);
p.setFont(st::premiumGiftsUserpicBadgeFont);
p.setPen(st::premiumButtonFg);
p.drawText(full, text, style::al_center);
}, widget->lifetime());
widget->resize(full.size());
return widget;
}
[[nodiscard]] not_null<Ui::RpWidget*> UserpicsContainer(
not_null<Ui::RpWidget*> parent,
std::vector<not_null<UserData*>> users) {
Expects(!users.empty());
if (users.size() == 1) {
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
parent.get(),
users.front(),
st::defaultUserpicButton);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
return userpic;
}
const auto &singleSize = st::defaultUserpicButton.size;
const auto container = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto single = singleSize.width();
const auto shift = single - st::boostReplaceUserpicsShift;
const auto maxWidth = users.size() * (single - shift) + shift;
container->resize(maxWidth, singleSize.height());
container->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto diff = (single - st::premiumGiftsUserpicButton.size.width())
/ 2;
for (auto i = 0; i < users.size(); i++) {
const auto bg = Ui::CreateChild<Ui::RpWidget>(container);
bg->resize(singleSize);
bg->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::boxBg);
p.drawEllipse(bg->rect());
}, bg->lifetime());
bg->moveToLeft(std::max(0, i * (single - shift)), 0);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
bg,
users[i],
st::premiumGiftsUserpicButton);
userpic->moveToLeft(diff, diff);
}
return container;
}
void GiftBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
@@ -95,12 +237,11 @@ void GiftBox(
+ st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = box->lifetime().make_state<ColoredMiniStars>(top, true);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
top,
user,
st::defaultUserpicButton);
true);
const auto userpic = UserpicsContainer(top, { user });
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
top->widthValue(
) | rpl::start_with_next([=](int width) {
@@ -211,7 +352,7 @@ void GiftBox(
auto raw = Settings::CreateSubscribeButton({
controller,
box,
[] { return QString("gift"); },
[] { return u"gift"_q; },
state->buttonText.events(),
Ui::Premium::GiftGradientStops(),
[=] {
@@ -222,10 +363,8 @@ void GiftBox(
},
});
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
button->resizeToWidth(boxWidth
- stButton.buttonPadding.left()
- stButton.buttonPadding.right());
box->setShowFinishedCallback([raw = button.data()]{
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
box->setShowFinishedCallback([raw = button.data()] {
raw->startGlareAnimation();
});
box->addButton(std::move(button));
@@ -239,6 +378,316 @@ void GiftBox(
}, box->lifetime());
}
void GiftsBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
std::vector<not_null<UserData*>> users,
not_null<Api::PremiumGiftCodeOptions*> api,
const QString &ref) {
Expects(!users.empty());
const auto boxWidth = st::boxWideWidth;
box->setWidth(boxWidth);
box->setNoContentMargin(true);
const auto buttonsParent = box->verticalLayout().get();
const auto session = &users.front()->session();
struct State {
rpl::event_stream<QString> buttonText;
rpl::variable<bool> confirmButtonBusy = false;
rpl::variable<bool> isPaymentComplete = false;
};
const auto state = box->lifetime().make_state<State>();
const auto userpicPadding = st::premiumGiftUserpicPadding;
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
buttonsParent,
userpicPadding.top()
+ userpicPadding.bottom()
+ st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
top,
true);
const auto maxWithUserpic = std::min(users.size(), kUserpicsMax);
const auto userpics = UserpicsContainer(
top,
{ users.begin(), users.begin() + maxWithUserpic });
top->widthValue(
) | rpl::start_with_next([=](int width) {
userpics->moveToLeft(
(width - userpics->width()) / 2,
userpicPadding.top());
const auto center = top->rect().center();
const auto size = QSize(
userpics->width() * Ui::Premium::MiniStars::kSizeFactor,
userpics->height());
const auto ministarsRect = QRect(
QPoint(center.x() - size.width(), center.y() - size.height()),
QPoint(center.x() + size.width(), center.y() + size.height()));
stars->setPosition(ministarsRect.topLeft());
stars->setSize(ministarsRect.size());
}, userpics->lifetime());
if (const auto rest = users.size() - maxWithUserpic; rest > 0) {
const auto badge = CircleBadge(
userpics,
QChar('+') + QString::number(rest));
badge->moveToRight(0, userpics->height() - badge->height());
}
top->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(top);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, top->lifetime());
const auto close = Ui::CreateChild<Ui::IconButton>(
buttonsParent,
st::infoTopBarClose);
close->setClickedCallback([=] { box->closeBox(); });
buttonsParent->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0, width);
}, close->lifetime());
// Header.
const auto &padding = st::premiumGiftAboutPadding;
const auto available = boxWidth - padding.left() - padding.right();
const auto &stTitle = st::premiumPreviewAboutTitle;
auto titleLabel = object_ptr<Ui::FlatLabel>(
box,
rpl::conditional(
state->isPaymentComplete.value(),
tr::lng_premium_gifts_about_paid_title(),
tr::lng_premium_gift_title()),
stTitle);
titleLabel->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
std::move(titleLabel)),
st::premiumGiftTitlePadding);
// About.
{
const auto emoji = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::premiumGiftsBoostIcon,
QMargins(0, st::premiumGiftsUserpicBadgeInner, 0, 0),
false));
auto text = rpl::conditional(
state->isPaymentComplete.value(),
ComplexAboutLabel(
users,
tr::lng_premium_gifts_about_paid1,
tr::lng_premium_gifts_about_paid2,
tr::lng_premium_gifts_about_paid3,
tr::lng_premium_gifts_about_paid_more
) | rpl::map([count = users.size()](TextWithEntities text) {
text.append('\n');
text.append('\n');
text.append(tr::lng_premium_gifts_about_paid_below(
tr::now,
lt_count,
float64(count),
Ui::Text::RichLangValue));
return text;
}),
ComplexAboutLabel(
users,
tr::lng_premium_gifts_about_user1,
tr::lng_premium_gifts_about_user2,
tr::lng_premium_gifts_about_user3,
tr::lng_premium_gifts_about_user_more
) | rpl::map([=, count = users.size()](TextWithEntities text) {
text.append('\n');
text.append('\n');
text.append(tr::lng_premium_gifts_about_reward(
tr::now,
lt_count,
count * BoostsForGift(session),
lt_emoji,
emoji,
Ui::Text::RichLangValue));
return text;
})
);
const auto label = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(box, st::premiumPreviewAbout)),
padding)->entity();
std::move(
text
) | rpl::start_with_next([=](const TextWithEntities &t) {
using namespace Core;
label->setMarkedText(t, MarkedTextContext{ .session = session });
}, label->lifetime());
label->setTextColorOverride(stTitle.textFg->c);
label->resizeToWidth(available);
}
// List.
const auto optionsContainer = buttonsParent->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
buttonsParent,
object_ptr<Ui::VerticalLayout>(buttonsParent)));
const auto options = api->options(users.size());
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
const auto groupValueChangedCallback = [=](int value) {
Expects(value < options.size() && value >= 0);
auto text = tr::lng_premium_gift_button(
tr::now,
lt_cost,
options[value].costTotal);
state->buttonText.fire(std::move(text));
};
group->setChangedCallback(groupValueChangedCallback);
Ui::Premium::AddGiftOptions(
optionsContainer->entity(),
group,
options,
st::premiumGiftOption);
optionsContainer->toggleOn(
state->isPaymentComplete.value() | rpl::map(!rpl::mappers::_1),
anim::type::instant);
// Summary.
{
{
// Will be hidden after payment.
const auto content = optionsContainer->entity();
Ui::AddSkip(content);
Ui::AddDivider(content);
Ui::AddSkip(content);
Ui::AddSubsectionTitle(
content,
tr::lng_premium_gifts_summary_subtitle());
}
const auto content = box->addRow(
object_ptr<Ui::VerticalLayout>(box),
{});
auto buttonCallback = [=](PremiumPreview section) {
stars->setPaused(true);
const auto previewBoxShown = [=](
not_null<Ui::BoxContent*> previewBox) {
previewBox->boxClosing(
) | rpl::start_with_next(crl::guard(box, [=] {
stars->setPaused(false);
}), previewBox->lifetime());
};
ShowPremiumPreviewBox(
controller->uiShow(),
section,
previewBoxShown,
true);
};
Settings::AddSummaryPremium(
content,
controller,
ref,
std::move(buttonCallback));
}
// Footer.
{
box->addRow(
object_ptr<Ui::DividerLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_premium_gifts_terms(
lt_link,
tr::lng_payments_terms_link(
) | rpl::map([](const QString &t) {
using namespace Ui::Text;
return Link(t, u"https://telegram.org/tos"_q);
}),
lt_policy,
tr::lng_premium_gifts_terms_policy(
) | rpl::map([](const QString &t) {
using namespace Ui::Text;
return Link(t, u"https://telegram.org/privacy"_q);
}),
Ui::Text::RichLangValue),
st::premiumGiftTerms),
st::defaultBoxDividerLabelPadding),
{});
}
// Button.
const auto &stButton = st::premiumGiftBox;
box->setStyle(stButton);
auto raw = Settings::CreateSubscribeButton({
controller,
box,
[=] { return ref; },
rpl::combine(
state->buttonText.events(),
state->confirmButtonBusy.value(),
state->isPaymentComplete.value()
) | rpl::map([](const QString &text, bool busy, bool paid) {
return busy
? QString()
: paid
? tr::lng_close(tr::now)
: text;
}),
Ui::Premium::GiftGradientStops(),
});
raw->setClickedCallback([=] {
if (state->confirmButtonBusy.current()) {
return;
}
if (state->isPaymentComplete.current()) {
return box->closeBox();
}
auto invoice = api->invoice(
users.size(),
api->monthsFromPreset(group->value()));
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ users };
state->confirmButtonBusy = true;
const auto show = box->uiShow();
const auto weak = Ui::MakeWeak(box.get());
const auto done = [=](Payments::CheckoutResult result) {
if (const auto strong = weak.data()) {
strong->window()->setFocus();
state->confirmButtonBusy = false;
if (result == Payments::CheckoutResult::Paid) {
state->isPaymentComplete = true;
Ui::StartFireworks(box->parentWidget());
}
}
};
Payments::CheckoutProcess::Start(std::move(invoice), done);
});
{
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
raw,
raw->height() / 2);
AddChildToWidgetCenter(raw, loadingAnimation);
loadingAnimation->showOn(state->confirmButtonBusy.value());
}
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
box->setShowFinishedCallback([raw = button.data()] {
raw->startGlareAnimation();
});
box->addButton(std::move(button));
groupValueChangedCallback(0);
}
[[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink(
not_null<Main::Session*> session,
const QString &slug) {
@@ -370,18 +819,20 @@ void AddTable(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
AddTableRow(
table,
tr::lng_gift_link_label_from(),
controller,
current.from);
if (current.to) {
if (current.from) {
AddTableRow(
table,
tr::lng_gift_link_label_from(),
controller,
current.from);
}
if (current.from && current.to) {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
controller,
current.to);
} else {
} else if (current.from) {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
@@ -394,7 +845,7 @@ void AddTable(
lt_duration,
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
Ui::Text::WithEntities));
if (!skipReason) {
if (!skipReason && current.from) {
const auto reason = AddTableRow(
table,
tr::lng_gift_link_label_reason(),
@@ -439,6 +890,116 @@ void GiftPremiumValidator::cancel() {
_requestId = 0;
}
void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
if (_manyGiftsLifetime) {
return;
}
using namespace Api;
const auto api = _manyGiftsLifetime.make_state<PremiumGiftCodeOptions>(
_controller->session().user());
const auto show = _controller->uiShow();
api->request(
) | rpl::start_with_error_done([=](const QString &error) {
show->showToast(error);
}, [=] {
const auto maxAmount = *ranges::max_element(api->availablePresets());
class Controller final : public ContactsBoxController {
public:
Controller(
not_null<Main::Session*> session,
Fn<bool(int)> checkErrorCallback)
: ContactsBoxController(session)
, _checkErrorCallback(std::move(checkErrorCallback)) {
}
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
if (user->isSelf()
|| user->isBot()
|| user->isServiceUser()
|| user->isInaccessible()) {
return nullptr;
}
return ContactsBoxController::createRow(user);
}
void rowClicked(not_null<PeerListRow*> row) override {
const auto checked = !row->checked();
if (checked
&& _checkErrorCallback
&& _checkErrorCallback(
delegate()->peerListSelectedRowsCount())) {
return;
}
delegate()->peerListSetRowChecked(row, checked);
}
private:
const Fn<bool(int)> _checkErrorCallback;
};
auto initBox = [=](not_null<PeerListBox*> peersBox) {
const auto ignoreClose = peersBox->lifetime().make_state<bool>(0);
auto process = [=] {
const auto selected = peersBox->collectSelectedRows();
const auto users = ranges::views::all(
selected
) | ranges::views::transform([](not_null<PeerData*> p) {
return p->asUser();
}) | ranges::views::filter([](UserData *u) -> bool {
return u;
}) | ranges::to<std::vector<not_null<UserData*>>>();
if (!users.empty()) {
const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api, ref));
giftBox->boxClosing(
) | rpl::start_with_next([=] {
_manyGiftsLifetime.destroy();
}, giftBox->lifetime());
}
(*ignoreClose) = true;
peersBox->closeBox();
};
peersBox->setTitle(tr::lng_premium_gift_title());
peersBox->addButton(
tr::lng_settings_gift_premium_users_confirm(),
std::move(process));
peersBox->addButton(tr::lng_cancel(), [=] {
peersBox->closeBox();
});
peersBox->boxClosing(
) | rpl::start_with_next([=] {
if (!(*ignoreClose)) {
_manyGiftsLifetime.destroy();
}
}, peersBox->lifetime());
};
auto listController = std::make_unique<Controller>(
&_controller->session(),
[=](int count) {
if (count <= maxAmount) {
return false;
}
show->showToast(tr::lng_settings_gift_premium_users_error(
tr::now,
lt_count,
maxAmount));
return true;
});
show->showBox(
Box<PeerListBox>(
std::move(listController),
std::move(initBox)),
Ui::LayerOption::KeepOther);
}, _manyGiftsLifetime);
}
void GiftPremiumValidator::showBox(not_null<UserData*> user) {
if (_requestId) {
return;
@@ -493,7 +1054,7 @@ void GiftCodeBox(
state->data = session->api().premium().giftCodeValue(slug);
state->used = state->data.value(
) | rpl::map([=](const Api::GiftCode &data) {
return data.used;
return data.used != 0;
});
box->setWidth(st::boxWideWidth);
@@ -723,10 +1284,22 @@ void GiftCodePendingBox(
void ResolveGiftCode(
not_null<Window::SessionNavigation*> controller,
const QString &slug) {
const QString &slug,
PeerId fromId,
PeerId toId) {
const auto done = [=](Api::GiftCode code) {
const auto session = &controller->session();
const auto selfId = session->userPeerId();
if (!code) {
controller->showToast(tr::lng_gift_link_expired(tr::now));
} else if (!code.from && fromId == selfId) {
code.from = fromId;
code.to = toId;
const auto self = (fromId == selfId);
const auto peer = session->data().peer(self ? toId : fromId);
const auto months = code.months;
const auto parent = controller->parentController();
Settings::ShowGiftPremium(parent, peer, months, self);
} else {
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
}
@@ -739,8 +1312,11 @@ void ResolveGiftCode(
void GiveawayInfoBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> controller,
Data::Giveaway giveaway,
std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results,
Api::GiveawayInfo info) {
Expects(start || results);
using State = Api::GiveawayState;
const auto finished = (info.state == State::Finished)
|| (info.state == State::Refunded);
@@ -749,10 +1325,31 @@ void GiveawayInfoBox(
? tr::lng_prizes_end_title
: tr::lng_prizes_how_title)());
const auto first = !giveaway.channels.empty()
? giveaway.channels.front()->name()
const auto first = results
? results->channel->name()
: !start->channels.empty()
? start->channels.front()->name()
: u"channel"_q;
auto text = (finished
auto text = TextWithEntities();
if (!info.giftCode.isEmpty()) {
text.append("\n\n");
text.append(Ui::Text::Bold(tr::lng_prizes_you_won(
tr::now,
lt_cup,
QString::fromUtf8("\xf0\x9f\x8f\x86"))));
text.append("\n\n");
} else if (info.state == State::Finished) {
text.append("\n\n");
text.append(Ui::Text::Bold(tr::lng_prizes_you_didnt(tr::now)));
text.append("\n\n");
}
const auto quantity = start
? start->quantity
: (results->winnersCount + results->unclaimedCount);
const auto months = start ? start->months : results->months;
text.append((finished
? tr::lng_prizes_end_text
: tr::lng_prizes_how_text)(
tr::now,
@@ -760,18 +1357,21 @@ void GiveawayInfoBox(
tr::lng_prizes_admins(
tr::now,
lt_count,
giveaway.quantity,
quantity,
lt_channel,
Ui::Text::Bold(first),
lt_duration,
TextWithEntities{ GiftDuration(giveaway.months) },
TextWithEntities{ GiftDuration(months) },
Ui::Text::RichLangValue),
Ui::Text::RichLangValue);
const auto many = (giveaway.channels.size() > 1);
Ui::Text::RichLangValue));
const auto many = start
? (start->channels.size() > 1)
: (results->additionalPeersCount > 0);
const auto count = info.winnersCount
? info.winnersCount
: giveaway.quantity;
auto winners = giveaway.all
: quantity;
const auto all = start ? start->all : results->all;
auto winners = all
? (many
? tr::lng_prizes_winners_all_of_many
: tr::lng_prizes_winners_all_of_one)(
@@ -793,13 +1393,30 @@ void GiveawayInfoBox(
Ui::Text::Bold(
langDateTime(base::unixtime::parse(info.startDate))),
Ui::Text::RichLangValue);
const auto additionalPrize = results
? results->additionalPrize
: start->additionalPrize;
if (!additionalPrize.isEmpty()) {
text.append("\n\n").append(tr::lng_prizes_additional_added(
tr::now,
lt_count,
count,
lt_channel,
Ui::Text::Bold(first),
lt_prize,
TextWithEntities{ additionalPrize },
Ui::Text::RichLangValue));
}
const auto untilDate = start
? start->untilDate
: results->untilDate;
text.append("\n\n").append((finished
? tr::lng_prizes_end_when_finish
: tr::lng_prizes_how_when_finish)(
tr::now,
lt_date,
Ui::Text::Bold(langDayOfMonthFull(
base::unixtime::parse(giveaway.untilDate).date())),
base::unixtime::parse(untilDate).date())),
lt_winners,
winners,
Ui::Text::RichLangValue));
@@ -810,17 +1427,9 @@ void GiveawayInfoBox(
info.activatedCount,
Ui::Text::RichLangValue));
}
if (!info.giftCode.isEmpty()) {
text.append("\n\n");
text.append(tr::lng_prizes_you_won(
tr::now,
lt_cup,
QString::fromUtf8("\xf0\x9f\x8f\x86")));
} else if (info.state == State::Finished) {
text.append("\n\n");
text.append(tr::lng_prizes_you_didnt(tr::now));
} else if (info.state == State::Preparing) {
if (!info.giftCode.isEmpty()
|| info.state == State::Finished
|| info.state == State::Preparing) {
} else if (info.state != State::Refunded) {
if (info.adminChannelId) {
const auto channel = controller->session().data().channel(
@@ -858,7 +1467,7 @@ void GiveawayInfoBox(
Ui::Text::Bold(first),
lt_date,
Ui::Text::Bold(langDayOfMonthFull(
base::unixtime::parse(giveaway.untilDate).date())),
base::unixtime::parse(untilDate).date())),
Ui::Text::RichLangValue));
}
}
@@ -902,14 +1511,15 @@ void ResolveGiveawayInfo(
not_null<Window::SessionNavigation*> controller,
not_null<PeerData*> peer,
MsgId messageId,
Data::Giveaway giveaway) {
std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results) {
const auto show = [=](Api::GiveawayInfo info) {
if (!info) {
controller->showToast(
tr::lng_confirm_phone_link_invalid(tr::now));
} else {
controller->uiShow()->showBox(
Box(GiveawayInfoBox, controller, giveaway, info));
Box(GiveawayInfoBox, controller, start, results, info));
}
};
controller->session().api().premium().resolveGiveawayInfo(

View File

@@ -16,7 +16,8 @@ struct GiftCode;
} // namespace Api
namespace Data {
struct Giveaway;
struct GiveawayStart;
struct GiveawayResults;
} // namespace Data
namespace Ui {
@@ -33,6 +34,7 @@ public:
GiftPremiumValidator(not_null<Window::SessionController*> controller);
void showBox(not_null<UserData*> user);
void showChoosePeerBox(const QString &ref);
void cancel();
private:
@@ -41,6 +43,8 @@ private:
mtpRequestId _requestId = 0;
rpl::lifetime _manyGiftsLifetime;
};
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
@@ -56,10 +60,13 @@ void GiftCodePendingBox(
const Api::GiftCode &data);
void ResolveGiftCode(
not_null<Window::SessionNavigation*> controller,
const QString &slug);
const QString &slug,
PeerId fromId = 0,
PeerId toId = 0);
void ResolveGiveawayInfo(
not_null<Window::SessionNavigation*> controller,
not_null<PeerData*> peer,
MsgId messageId,
Data::Giveaway giveaway);
std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results);

View File

@@ -40,11 +40,14 @@ public:
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> callback);
Fn<void(std::vector<not_null<PeerData*>>)> callback);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
[[nodiscard]] rpl::producer<int> selectedCountValue() const;
void submit();
QString savedMessagesChatStatus() const override {
return tr::lng_saved_forward_here(tr::now);
}
@@ -60,7 +63,9 @@ private:
not_null<UserData*> _bot;
RequestPeerQuery _query;
base::flat_set<not_null<PeerData*>> _commonGroups;
Fn<void(not_null<PeerData*>)> _callback;
base::flat_set<not_null<PeerData*>> _selected;
rpl::variable<int> _selectedCount;
Fn<void(std::vector<not_null<PeerData*>>)> _callback;
};
@@ -253,10 +258,10 @@ object_ptr<Ui::BoxContent> CreatePeerByQueryBox(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> done) {
Fn<void(std::vector<not_null<PeerData*>>)> done) {
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto callback = [=](not_null<PeerData*> peer) {
done(peer);
done({ peer });
if (const auto strong = weak->data()) {
strong->closeBox();
}
@@ -332,7 +337,7 @@ ChoosePeerBoxController::ChoosePeerBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> callback)
Fn<void(std::vector<not_null<PeerData*>>)> callback)
: ChatsListBoxController(&navigation->session())
, _navigation(navigation)
, _bot(bot)
@@ -415,6 +420,8 @@ void ChoosePeerBoxController::prepareViewHook() {
switch (_query.type) {
case Type::User: return (_query.userIsBot == Restriction::Yes)
? tr::lng_request_bot_title()
: (_query.maxQuantity > 1)
? tr::lng_request_users_title()
: tr::lng_request_user_title();
case Type::Group: return tr::lng_request_group_title();
case Type::Broadcast: return tr::lng_request_channel_title();
@@ -425,10 +432,24 @@ void ChoosePeerBoxController::prepareViewHook() {
}
void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto limit = _query.maxQuantity;
const auto multiselect = (limit > 1);
const auto peer = row->peer();
if (multiselect) {
if (_selected.contains(peer) || _selected.size() < limit) {
delegate()->peerListSetRowChecked(row, !row->checked());
if (row->checked()) {
_selected.emplace(peer);
} else {
_selected.remove(peer);
}
_selectedCount = int(_selected.size());
}
return;
}
const auto done = [callback = _callback, peer] {
const auto onstack = callback;
onstack(peer);
onstack({ peer });
};
if (const auto user = peer->asUser()) {
done();
@@ -438,6 +459,15 @@ void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
}
}
rpl::producer<int> ChoosePeerBoxController::selectedCountValue() const {
return _selectedCount.value();
}
void ChoosePeerBoxController::submit() {
const auto onstack = _callback;
onstack(ranges::to_vector(_selected));
}
auto ChoosePeerBoxController::createRow(not_null<History*> history)
-> std::unique_ptr<Row> {
return FilterPeerByQuery(history->peer, _query, _commonGroups)
@@ -474,7 +504,7 @@ void ShowChoosePeerBox(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> chosen) {
Fn<void(std::vector<not_null<PeerData*>>)> chosen) {
const auto needCommonGroups = query.isBotParticipant
&& (query.type == RequestPeerQuery::Type::Group)
&& !query.myRights;
@@ -488,22 +518,39 @@ void ShowChoosePeerBox(
return;
}
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [box] {
box->closeBox();
});
};
auto callback = [=, done = std::move(chosen)](not_null<PeerData*> peer) {
done(peer);
auto callback = [=, done = std::move(chosen)](
std::vector<not_null<PeerData*>> peers) {
done(std::move(peers));
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
const auto limit = query.maxQuantity;
auto controller = std::make_unique<ChoosePeerBoxController>(
navigation,
bot,
query,
std::move(callback));
auto initBox = [=, ptr = controller.get()](not_null<PeerListBox*> box) {
ptr->selectedCountValue() | rpl::start_with_next([=](int count) {
box->clearButtons();
if (limit > 1) {
box->setAdditionalTitle(rpl::single(u"%1 / %2"_q.arg(count).arg(limit)));
}
if (count > 0) {
box->addButton(tr::lng_intro_submit(), [=] {
ptr->submit();
if (*weak) {
(*weak)->closeBox();
}
});
}
box->addButton(tr::lng_cancel(), [box] {
box->closeBox();
});
}, box->lifetime());
};
*weak = navigation->parentController()->show(Box<PeerListBox>(
std::make_unique<ChoosePeerBoxController>(
navigation,
bot,
query,
std::move(callback)),
std::move(controller),
std::move(initBox)));
}

View File

@@ -21,4 +21,4 @@ void ShowChoosePeerBox(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> chosen);
Fn<void(std::vector<not_null<PeerData*>>)> chosen);

View File

@@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_peer_colors.h"
#include "api/api_peer_photo.h"
#include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h"
#include "boxes/background_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_emoji_statuses.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
@@ -428,11 +431,17 @@ HistoryView::Context PreviewDelegate::elementContext() {
return HistoryView::Context::AdminLog;
}
struct SetValues {
uint8 colorIndex = 0;
DocumentId backgroundEmojiId = 0;
DocumentId statusId = 0;
TimeId statusUntil = 0;
bool statusChanged = false;
};
void Set(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
DocumentId backgroundEmojiId) {
SetValues values) {
const auto wasIndex = peer->colorIndex();
const auto wasEmojiId = peer->backgroundEmojiId();
@@ -444,7 +453,7 @@ void Set(
peer,
UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
};
setLocal(colorIndex, backgroundEmojiId);
setLocal(values.colorIndex, values.backgroundEmojiId);
const auto done = [=] {
show->showToast(peer->isSelf()
@@ -452,8 +461,11 @@ void Set(
: tr::lng_settings_color_changed_channel(tr::now));
};
const auto fail = [=](const MTP::Error &error) {
setLocal(wasIndex, wasEmojiId);
show->showToast(error.type());
const auto type = error.type();
if (type != u"CHAT_NOT_MODIFIED"_q) {
setLocal(wasIndex, wasEmojiId);
show->showToast(type);
}
};
const auto send = [&](auto &&request) {
peer->session().api().request(
@@ -464,15 +476,23 @@ void Set(
using Flag = MTPaccount_UpdateColor::Flag;
send(MTPaccount_UpdateColor(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
MTP_int(colorIndex),
MTP_long(backgroundEmojiId)));
MTP_int(values.colorIndex),
MTP_long(values.backgroundEmojiId)));
} else if (const auto channel = peer->asChannel()) {
using Flag = MTPchannels_UpdateColor::Flag;
send(MTPchannels_UpdateColor(
MTP_flags(Flag::f_background_emoji_id),
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
channel->inputChannel,
MTP_int(colorIndex),
MTP_long(backgroundEmojiId)));
MTP_int(values.colorIndex),
MTP_long(values.backgroundEmojiId)));
if (values.statusChanged
&& (values.statusId || peer->emojiStatusId())) {
peer->owner().emojiStatuses().set(
channel,
values.statusId,
values.statusUntil);
}
} else {
Unexpected("Invalid peer type in Set(colorIndex).");
}
@@ -481,13 +501,13 @@ void Set(
void Apply(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
DocumentId backgroundEmojiId,
SetValues values,
Fn<void()> close,
Fn<void()> cancel) {
const auto session = &peer->session();
if (peer->colorIndex() == colorIndex
&& peer->backgroundEmojiId() == backgroundEmojiId) {
if (peer->colorIndex() == values.colorIndex
&& peer->backgroundEmojiId() == values.backgroundEmojiId
&& !values.statusChanged) {
close();
} else if (peer->isSelf() && !session->premium()) {
Settings::ShowPremiumPromoToast(
@@ -502,39 +522,45 @@ void Apply(
u"name_color"_q);
cancel();
} else if (peer->isSelf()) {
Set(show, peer, colorIndex, backgroundEmojiId);
Set(show, peer, values);
close();
} else {
session->api().request(MTPpremium_GetBoostsStatus(
peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
const auto required = session->account().appConfig().get<int>(
"channel_color_level_min",
5);
if (data.vlevel().v >= required) {
Set(show, peer, colorIndex, backgroundEmojiId);
CheckBoostLevel(show, peer, [=](int level) {
const auto peerColors = &peer->session().api().peerColors();
const auto colorRequired = peerColors->requiredLevelFor(
peer->id,
values.colorIndex);
const auto iconRequired = values.backgroundEmojiId
? session->account().appConfig().get<int>(
"channel_bg_icon_level_min",
5)
: 0;
const auto statusRequired = (values.statusChanged
&& values.statusId)
? session->account().appConfig().get<int>(
"channel_emoji_status_level_min",
8)
: 0;
const auto required = std::max({
colorRequired,
iconRequired,
statusRequired,
});
if (level >= required) {
Set(show, peer, values);
close();
return;
return std::optional<Ui::AskBoostReason>();
}
const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer));
const auto reason = [&]() -> Ui::AskBoostReason {
if (level < statusRequired) {
return { Ui::AskBoostEmojiStatus{ statusRequired } };
} else if (level < iconRequired) {
return { Ui::AskBoostChannelColor{ iconRequired } };
}
};
auto counters = ParseBoostCounters(result);
counters.mine = 0; // Don't show current level as just-reached.
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()),
.boost = counters,
.reason = { Ui::AskBoostChannelColor{ required } },
}, openStatistics, nullptr));
cancel();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
cancel();
}).send();
return { Ui::AskBoostChannelColor{ colorRequired } };
}();
return std::make_optional(reason);
}, cancel);
}
}
@@ -672,15 +698,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
using namespace Info::Profile;
struct State {
Info::Profile::EmojiStatusPanel panel;
EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId emojiId = 0;
uint8 index = 0;
};
const auto state = right->lifetime().make_state<State>();
state->panel.backgroundEmojiChosen(
) | rpl::start_with_next(emojiIdChosen, raw->lifetime());
state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
emojiIdChosen(chosen.id);
}, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
state->index = index;
@@ -748,7 +777,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
state->panel.show({
.controller = controller,
.button = right,
.currentBackgroundEmojiId = state->emojiId,
.ensureAddedEmojiId = state->emojiId,
.customTextColor = customTextColor,
.backgroundEmojiMode = true,
});
@@ -758,6 +787,108 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return result;
}
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiStatusButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
rpl::producer<DocumentId> statusIdValue,
Fn<void(DocumentId,TimeId)> statusIdChosen) {
const auto &basicSt = st::settingsButtonNoIcon;
const auto ratio = style::DevicePixelRatio();
const auto added = st::normalFont->spacew;
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = added
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
const auto emojiWidth = added + emojiSize;
const auto rightPadding = std::max(noneWidth, emojiWidth)
+ basicSt.padding.right();
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding.setRight(rightPadding);
auto result = object_ptr<Ui::SettingsButton>(
parent,
tr::lng_edit_channel_status(),
*st);
const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
using namespace Info::Profile;
struct State {
EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId statusId = 0;
};
const auto state = right->lifetime().make_state<State>();
state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
statusIdChosen(chosen.id, chosen.until);
}, raw->lifetime());
const auto session = &show->session();
std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
state->statusId = id;
state->emoji = id
? session->data().customEmojiManager().create(
id,
[=] { right->update(); })
: nullptr;
right->resize(
(id ? emojiWidth : noneWidth) + added,
right->height());
right->update();
}, right->lifetime());
rpl::combine(
raw->sizeValue(),
right->widthValue()
) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
) | rpl::start_with_next([=] {
if (state->panel.paintBadgeFrame(right)) {
return;
}
auto p = QPainter(right);
const auto height = right->height();
if (state->emoji) {
state->emoji->paint(p, {
.textColor = anim::color(
st::stickerPanPremium1,
st::stickerPanPremium2,
0.5),
.position = QPoint(added, (height - emojiSize) / 2),
});
} else {
const auto &font = st::normalFont;
p.setFont(font);
p.setPen(st::windowActiveTextFg);
p.drawText(
QPoint(added, (height - font->height) / 2 + font->ascent),
tr::lng_settings_color_emoji_off(tr::now));
}
}, right->lifetime());
raw->setClickedCallback([=] {
const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
if (controller) {
state->panel.show({
.controller = controller,
.button = right,
.ensureAddedEmojiId = state->statusId,
.channelStatusMode = true,
});
}
});
return result;
}
} // namespace
void EditPeerColorBox(
@@ -772,12 +903,16 @@ void EditPeerColorBox(
struct State {
rpl::variable<uint8> index;
rpl::variable<DocumentId> emojiId;
rpl::variable<DocumentId> statusId;
TimeId statusUntil = 0;
bool statusChanged = false;
bool changing = false;
bool applying = false;
};
const auto state = box->lifetime().make_state<State>();
state->index = peer->colorIndex();
state->emojiId = peer->backgroundEmojiId();
state->statusId = peer->emojiStatusId();
box->addRow(object_ptr<PreviewWrap>(
box,
@@ -820,14 +955,61 @@ void EditPeerColorBox(
? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel());
if (const auto channel = peer->asChannel()) {
Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_edit_channel_wallpaper(),
st::settingsButtonNoIcon)
)->setClickedCallback([=] {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
if (const auto strong = show->resolveWindow(usage)) {
show->show(Box<BackgroundBox>(strong, channel));
}
});
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(
container,
tr::lng_edit_channel_wallpaper_about());
// Preload exceptions list.
const auto peerPhoto = &channel->session().api().peerPhoto();
[[maybe_unused]] auto list = peerPhoto->emojiListValue(
Api::PeerPhoto::EmojiListType::NoChannelStatus
);
const auto statuses = &channel->owner().emojiStatuses();
statuses->refreshChannelDefault();
statuses->refreshChannelColored();
Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(CreateEmojiStatusButton(
container,
show,
state->statusId.value(),
[=](DocumentId id, TimeId until) {
state->statusId = id;
state->statusUntil = until;
state->statusChanged = true;
}));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(container, tr::lng_edit_channel_status_about());
}
box->addButton(tr::lng_settings_apply(), [=] {
if (state->applying) {
return;
}
state->applying = true;
const auto index = state->index.current();
const auto emojiId = state->emojiId.current();
Apply(show, peer, index, emojiId, crl::guard(box, [=] {
Apply(show, peer, {
state->index.current(),
state->emojiId.current(),
state->statusId.current(),
state->statusUntil,
state->statusChanged,
}, crl::guard(box, [=] {
box->closeBox();
}), crl::guard(box, [=] {
state->applying = false;
@@ -842,11 +1024,12 @@ void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer) {
auto label = peer->isSelf()
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color();
const auto button = AddButtonWithIcon(
container,
(peer->isSelf()
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color()),
rpl::duplicate(label),
st::settingsColorButton,
{ &st::menuIconChangeColors });
@@ -873,7 +1056,7 @@ void AddPeerColorButton(
rpl::combine(
button->widthValue(),
tr::lng_settings_theme_name_color(),
rpl::duplicate(label),
rpl::duplicate(colorIndexValue)
) | rpl::start_with_next([=](
int width,
@@ -920,3 +1103,39 @@ void AddPeerColorButton(
show->show(Box(EditPeerColorBox, show, peer, style, theme));
});
}
void CheckBoostLevel(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
Fn<void()> cancel) {
peer->session().api().request(MTPpremium_GetBoostsStatus(
peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
if (const auto channel = peer->asChannel()) {
channel->updateLevelHint(data.vlevel().v);
}
const auto reason = askMore(data.vlevel().v);
if (!reason) {
return;
}
const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer));
}
};
auto counters = ParseBoostCounters(result);
counters.mine = 0; // Don't show current level as just-reached.
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()),
.boost = counters,
.reason = *reason,
}, openStatistics, nullptr));
cancel();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
cancel();
}).send();
}

View File

@@ -16,6 +16,7 @@ class GenericBox;
class ChatStyle;
class ChatTheme;
class VerticalLayout;
struct AskBoostReason;
} // namespace Ui
void EditPeerColorBox(
@@ -29,3 +30,9 @@ void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer);
void CheckBoostLevel(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
Fn<void()> cancel);

View File

@@ -1294,6 +1294,9 @@ void Controller::editReactions() {
_peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
_controls.levelRequested = false;
if (const auto channel = _peer->asChannel()) {
channel->updateLevelHint(result.data().vlevel().v);
}
const auto link = qs(result.data().vboost_url());
const auto weak = base::make_weak(_navigation->parentController());
auto counters = ParseBoostCounters(result);

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_forum.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_folder.h"
#include "data/data_premium_limits.h"
@@ -882,6 +883,18 @@ void PinsLimitBox(
limits.dialogsPinnedPremium(),
PinsCount(session->data().chatsList()));
}
void SublistsPinsLimitBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session) {
const auto limits = Data::PremiumLimits(session);
SimplePinsLimitBox(
box,
session,
"saved_dialog_pinned",
limits.savedSublistsPinnedDefault(),
limits.savedSublistsPinnedPremium(),
PinsCount(session->data().savedMessages().chatsList()));
}
void ForumPinsLimitBox(
not_null<Ui::GenericBox*> box,

View File

@@ -60,6 +60,9 @@ void PinsLimitBox(
void ForumPinsLimitBox(
not_null<Ui::GenericBox*> box,
not_null<Data::Forum*> forum);
void SublistsPinsLimitBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session);
void CaptionLimitBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session,

View File

@@ -51,8 +51,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kPremiumShift = 21. / 240;
constexpr auto kReactionsPerRow = 5;
constexpr auto kDisabledOpacity = 0.5;
constexpr auto kToggleStickerTimeout = 2 * crl::time(1000);
constexpr auto kStarOpacityOff = 0.1;
constexpr auto kStarOpacityOn = 1.;
@@ -66,6 +64,7 @@ struct Descriptor {
bool fromSettings = false;
Fn<void()> hiddenCallback;
Fn<void(not_null<Ui::BoxContent*>)> shownCallback;
bool hideSubscriptionButton = false;
};
bool operator==(const Descriptor &a, const Descriptor &b) {
@@ -1025,7 +1024,8 @@ void PreviewBox(
state->preload();
}
};
if (descriptor.fromSettings && show->session().premium()) {
if ((descriptor.fromSettings && show->session().premium())
|| descriptor.hideSubscriptionButton) {
box->setShowFinishedCallback(showFinished);
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
} else {
@@ -1151,7 +1151,7 @@ void DecorateListPromoBox(
box->boxClosing() | rpl::start_with_next(hidden, box->lifetime());
}
if (session->premium()) {
if (session->premium() || descriptor.hideSubscriptionButton) {
box->addButton(tr::lng_close(), [=] {
box->closeBox();
});
@@ -1286,10 +1286,12 @@ void ShowPremiumPreviewBox(
void ShowPremiumPreviewBox(
std::shared_ptr<ChatHelpers::Show> show,
PremiumPreview section,
Fn<void(not_null<Ui::BoxContent*>)> shown) {
Fn<void(not_null<Ui::BoxContent*>)> shown,
bool hideSubscriptionButton) {
Show(std::move(show), Descriptor{
.section = section,
.shownCallback = std::move(shown),
.hideSubscriptionButton = hideSubscriptionButton,
});
}

View File

@@ -73,7 +73,8 @@ void ShowPremiumPreviewBox(
void ShowPremiumPreviewBox(
std::shared_ptr<ChatHelpers::Show> show,
PremiumPreview section,
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr);
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr,
bool hideSubscriptionButton = false);
void ShowPremiumPreviewToBuy(
not_null<Window::SessionController*> controller,

View File

@@ -211,7 +211,7 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
return Type::Other;
} else if (const auto browser = detectBrowser()) {
return *browser;
} else if (device.contains("iphone")) {
} else if (device.contains("iphone")) {
return Type::iPhone;
} else if (device.contains("ipad")) {
return Type::iPad;
@@ -221,9 +221,9 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
return *desktop;
} else if (platform.contains("android") || system.contains("android")) {
return Type::Android;
} else if (platform.contains("ios") || system.contains("ios")) {
} else if (platform.contains("ios") || system.contains("ios")) {
return Type::iPhone;
}
}
return Type::Other;
}

View File

@@ -848,7 +848,7 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
if (!_chatsIndexed->empty()) {
const auto index = yFrom / _rowHeight;
auto i = _chatsIndexed->begin()
+ std::min(index, _chatsIndexed->size());;
+ std::min(index, _chatsIndexed->size());
for (auto end = _chatsIndexed->cend(); i != end; ++i) {
if (((*i)->index() * _rowHeight) >= yTo) {
break;

View File

@@ -305,7 +305,7 @@ bool SkipTranslate(TextWithEntities textWithEntities) {
const auto skip = Core::App().settings().skipTranslationLanguages();
return result.known() && ranges::contains(skip, result);
#else
return false;
return false;
#endif
}

View File

@@ -520,6 +520,7 @@ void BoxController::loadMoreRows() {
MTP_inputPeerEmpty(),
MTP_string(), // q
MTP_inputPeerEmpty(),
MTPInputPeer(), // saved_peer_id
MTPint(), // top_msg_id
MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)),
MTP_int(0), // min_date

View File

@@ -321,9 +321,6 @@ void Viewport::RendererGL::init(
_frameBuffer->bind();
_frameBuffer->allocate(kValues * sizeof(GLfloat));
_downscaleProgram.yuv420.emplace();
const auto downscaleVertexSource = VertexShader({
VertexPassTextureCoord(),
});
_downscaleVertexShader = LinkProgram(
&*_downscaleProgram.yuv420,
VertexShader({

View File

@@ -454,8 +454,10 @@ void ChooseSourceProcess::fillSources() {
auto screenIndex = 0;
auto windowIndex = 0;
auto firstScreenSelected = false;
const auto active = _delegate->chooseSourceActiveDeviceId();
const auto append = [&](const tgcalls::DesktopCaptureSource &source) {
const auto firstScreen = !source.isWindow() && !screenIndex;
const auto title = !source.isWindow()
? tr::lng_group_call_screen_title(
tr::now,
@@ -471,6 +473,10 @@ void ChooseSourceProcess::fillSources() {
if (!active.isEmpty() && active.toStdString() == id) {
_selected = raw;
raw->setActive(true);
} else if (active.isEmpty() && firstScreen) {
_selected = raw;
raw->setActive(true);
firstScreenSelected = true;
}
_sources.back()->activations(
) | rpl::filter([=] {
@@ -489,6 +495,9 @@ void ChooseSourceProcess::fillSources() {
for (const auto &source : windowsManager.sources()) {
append(source);
}
if (firstScreenSelected) {
updateButtonsVisibility();
}
}
void ChooseSourceProcess::updateButtonsVisibility() {

View File

@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "chat_helpers/emoji_list_widget.h"
#include "api/api_peer_photo.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/tabbed_search.h"
@@ -477,10 +479,23 @@ EmojiListWidget::EmojiListWidget(
setAttribute(Qt::WA_OpaquePaintEvent);
}
if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) {
if (_mode != Mode::RecentReactions
&& _mode != Mode::BackgroundEmoji
&& _mode != Mode::ChannelStatus) {
setupSearch();
}
if (_mode == Mode::ChannelStatus) {
session().api().peerPhoto().emojiListValue(
Api::PeerPhoto::EmojiListType::NoChannelStatus
) | rpl::start_with_next([=](const std::vector<DocumentId> &list) {
_restrictedCustomList = { begin(list), end(list) };
if (!_custom.empty()) {
refreshCustom();
}
}, lifetime());
}
_customSingleSize = Data::FrameSizeFromTag(
Data::CustomEmojiManager::SizeTag::Large
) / style::DevicePixelRatio();
@@ -1034,7 +1049,9 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
if (!id && _mode == Mode::EmojiStatus) {
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
} else if (!id && _mode == Mode::BackgroundEmoji) {
} else if (!id
&& (_mode == Mode::BackgroundEmoji
|| _mode == Mode::ChannelStatus)) {
const auto fakeId = DocumentId(5246772116543512028ULL);
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
_recent.push_back({
@@ -1070,7 +1087,7 @@ base::unique_qptr<Ui::PopupMenu> EmojiListWidget::fillContextMenu(
: st::defaultPopupMenu));
if (_mode == Mode::Full) {
fillRecentMenu(menu, section, index);
} else if (_mode == Mode::EmojiStatus) {
} else if (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus) {
fillEmojiStatusMenu(menu, section, index);
}
if (menu->empty()) {
@@ -1205,7 +1222,7 @@ void EmojiListWidget::validateEmojiPaintContext(
auto value = Ui::Text::CustomEmojiPaintContext{
.textColor = (_customTextColor
? _customTextColor()
: (_mode == Mode::EmojiStatus)
: (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus)
? anim::color(
st::stickerPanPremium1,
st::stickerPanPremium2,
@@ -1402,6 +1419,10 @@ void EmojiListWidget::drawRecent(
_emojiPaintContext->position = position
+ _innerPosition
+ _customPosition;
if (_mode == Mode::ChannelStatus) {
_emojiPaintContext->internal.forceFirstFrame
= (recent.id == _recent.front().id);
}
custom->paint(p, *_emojiPaintContext);
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
if (_mode == Mode::EmojiStatus) {
@@ -1642,6 +1663,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
Settings::ShowPremium(resolved, u"infinite_reactions"_q);
break;
case Mode::EmojiStatus:
case Mode::ChannelStatus:
Settings::ShowPremium(resolved, u"emoji_status"_q);
break;
case Mode::TopicIcon:
@@ -2018,8 +2040,9 @@ void EmojiListWidget::refreshCustom() {
auto it = sets.find(setId);
if (it == sets.cend()
|| it->second->stickers.isEmpty()
|| (_mode == Mode::BackgroundEmoji
&& !it->second->textColor())) {
|| (_mode == Mode::BackgroundEmoji && !it->second->textColor())
|| (_mode == Mode::ChannelStatus
&& !it->second->channelStatus())) {
return;
}
const auto canRemove = !!(it->second->flags
@@ -2070,7 +2093,9 @@ void EmojiListWidget::refreshCustom() {
auto set = std::vector<CustomOne>();
set.reserve(list.size());
for (const auto document : list) {
if (const auto sticker = document->sticker()) {
if (_restrictedCustomList.contains(document->id)) {
continue;
} else if (const auto sticker = document->sticker()) {
set.push_back({
.custom = resolveCustomEmoji(document, setId),
.document = document,

View File

@@ -71,6 +71,7 @@ enum class EmojiListMode {
Full,
TopicIcon,
EmojiStatus,
ChannelStatus,
FullReactions,
RecentReactions,
UserpicBuilder,
@@ -384,6 +385,7 @@ private:
bool _grabbingChosen = false;
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
std::vector<CustomSet> _custom;
base::flat_set<DocumentId> _restrictedCustomList;
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji;
base::flat_map<
DocumentId,

View File

@@ -26,6 +26,7 @@ const QString DicePacks::kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF");
const QString DicePacks::kSlotString = QString::fromUtf8("\xF0\x9F\x8E\xB0");
const QString DicePacks::kFballString = QString::fromUtf8("\xE2\x9A\xBD");
const QString DicePacks::kBballString = QString::fromUtf8("\xF0\x9F\x8F\x80");
const QString DicePacks::kPartyPopper = QString::fromUtf8("\xf0\x9f\x8e\x89");
DicePack::DicePack(not_null<Main::Session*> session, const QString &emoji)
: _session(session)
@@ -35,7 +36,7 @@ DicePack::DicePack(not_null<Main::Session*> session, const QString &emoji)
DicePack::~DicePack() = default;
DocumentData *DicePack::lookup(int value) {
if (!_requestId) {
if (!_requestId && _emoji != DicePacks::kPartyPopper) {
load();
}
tryGenerateLocalZero();
@@ -117,6 +118,8 @@ void DicePack::tryGenerateLocalZero() {
generateLocal(8, u"slot_0_idle"_q);
generateLocal(14, u"slot_1_idle"_q);
generateLocal(20, u"slot_2_idle"_q);
} else if (_emoji == DicePacks::kPartyPopper) {
generateLocal(0, u"winners"_q);
}
}

View File

@@ -44,6 +44,7 @@ public:
static const QString kSlotString;
static const QString kFballString;
static const QString kBballString;
static const QString kPartyPopper;
[[nodiscard]] static bool IsSlot(const QString &emoji) {
return (emoji == kSlotString);

View File

@@ -332,6 +332,7 @@ TabbedSelector::TabbedSelector(
: TabbedSelector(parent, {
.show = std::move(show),
.st = ((mode == Mode::EmojiStatus
|| mode == Mode::ChannelStatus
|| mode == Mode::BackgroundEmoji
|| mode == Mode::FullReactions)
? st::statusEmojiPan
@@ -521,6 +522,8 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
.show = _show,
.mode = (_mode == Mode::EmojiStatus
? EmojiMode::EmojiStatus
: _mode == Mode::ChannelStatus
? EmojiMode::ChannelStatus
: _mode == Mode::BackgroundEmoji
? EmojiMode::BackgroundEmoji
: _mode == Mode::FullReactions

View File

@@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
EmojiOnly,
MediaEditor,
EmojiStatus,
ChannelStatus,
BackgroundEmoji,
FullReactions,
RecentReactions,

View File

@@ -428,11 +428,12 @@ void Application::showOpenGLCrashNotification() {
Local::writeSettings();
Restart();
};
const auto keepDisabled = [=] {
const auto keepDisabled = [=](Fn<void()> close) {
Ui::GL::ForceDisable(true);
Ui::GL::CrashCheckFinish();
settings().setDisableOpenGL(true);
Local::writeSettings();
close();
};
_lastActivePrimaryWindow->show(Ui::MakeConfirmBox({
.text = ""
@@ -682,7 +683,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
} break;
case QEvent::ThemeChange: {
if (Platform::IsLinux() && object == QGuiApplication::allWindows().first()) {
if (Platform::IsLinux()
&& object == QGuiApplication::allWindows().constFirst()) {
Core::App().refreshApplicationIcon();
Core::App().tray().updateIconCounters();
}

View File

@@ -1258,7 +1258,7 @@ void Settings::resetOnLastLogout() {
_tabbedReplacedWithInfo = false; // per-window
_systemDarkModeEnabled = false;
_hiddenGroupCallTooltips = 0;
_storiesClickTooltipHidden = 0;
_storiesClickTooltipHidden = false;
_recentEmojiPreload.clear();
_recentEmoji.clear();

View File

@@ -499,7 +499,7 @@ uint64 Launcher::installationTag() const {
}
void Launcher::processArguments() {
enum class KeyFormat {
enum class KeyFormat {
NoValues,
OneValue,
AllLeftValues,
@@ -542,9 +542,13 @@ void Launcher::processArguments() {
}
}
static const auto RegExp = QRegularExpression("[^a-z0-9\\-_]");
gDebugMode = parseResult.contains("-debug");
gKeyFile = parseResult.value("-key", {}).join(QString()).toLower();
gKeyFile = gKeyFile.replace(QRegularExpression("[^a-z0-9\\-_]"), {});
gKeyFile = parseResult
.value("-key", {})
.join(QString())
.toLower()
.replace(RegExp, {});
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
: parseResult.contains("-cleanup") ? LaunchModeCleanup

View File

@@ -344,9 +344,19 @@ bool ResolveUsernameOrPhone(
qthelp::UrlParamNameTransform::ToLower);
const auto domainParam = params.value(u"domain"_q);
const auto appnameParam = params.value(u"appname"_q);
const auto myContext = context.value<ClickHandlerContext>();
if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
ResolveGiftCode(controller, appnameParam);
const auto itemId = myContext.itemId;
const auto item = controller->session().data().message(itemId);
const auto fromId = item ? item->from()->id : PeerId();
const auto selfId = controller->session().userPeerId();
const auto toId = !item
? PeerId()
: (fromId == selfId)
? item->history()->peer->id
: selfId;
ResolveGiftCode(controller, appnameParam, fromId, toId);
return true;
}
@@ -413,7 +423,6 @@ bool ResolveUsernameOrPhone(
startToken = params.value(u"startapp"_q);
}
}
const auto myContext = context.value<ClickHandlerContext>();
controller->window().activate();
controller->showPeerByLink(Window::PeerByLinkInfo{
.usernameOrId = domain,
@@ -732,7 +741,8 @@ void ExportTestChatTheme(
MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)),
MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)),
MTP_int(fields.paper->patternIntensity()),
MTP_int(0)));
MTP_int(0), // rotation
MTPstring())); // emoticon
};
const auto light = inputSettings(Data::CloudThemeType::Light);
if (!light) {
@@ -835,6 +845,21 @@ bool ResolvePremiumOffer(
return true;
}
bool ResolvePremiumMultigift(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto params = url_parse_params(
match->captured(1).mid(1),
qthelp::UrlParamNameTransform::ToLower);
controller->showGiftPremiumsBox(params.value(u"ref"_q, u"gift_url"_q));
controller->window().activate();
return true;
}
bool ResolveLoginCode(
Window::SessionController *controller,
const Match &match,
@@ -958,6 +983,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
u"premium_offer/?(\\?.+)?(#|$)"_q,
ResolvePremiumOffer,
},
{
u"^premium_multigift/?\\?(.+)(#|$)"_q,
ResolvePremiumMultigift,
},
{
u"^login/?(\\?code=([0-9]+))(&|$)"_q,
ResolveLoginCode
@@ -1082,7 +1111,7 @@ QString TryConvertUrlToLocal(QString url) {
const auto base = u"tg://privatepost?channel="_q + channel;
auto added = QString();
if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2));
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
} else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
added = u"&post="_q + postMatch->captured(1);
}
@@ -1112,7 +1141,7 @@ QString TryConvertUrlToLocal(QString url) {
const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1));
auto added = QString();
if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2));
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
} else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&post="_q + postMatch->captured(1);
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {

View File

@@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/local_url_handlers.h"
#include "core/update_checker.h"
#include "core/deadlock_detector.h"
#include "base/options.h"
#include "base/timer.h"
#include "base/concurrent_timer.h"
#include "base/invoke_queued.h"
@@ -38,22 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Core {
namespace {
base::options::toggle OptionForceWaylandFractionalScaling({
.id = kOptionForceWaylandFractionalScaling,
.name = "Force enable fractional-scale-v1",
.description = "Enable fractional-scale-v1 on Wayland without "
"precise High DPI scaling. "
"Requires Qt with Desktop App Toolkit patches.",
.scope = [] {
#ifdef DESKTOP_APP_QT_PATCHED
return Platform::IsWayland();
#else // DESKTOP_APP_QT_PATCHED
return false;
#endif // !DESKTOP_APP_QT_PATCHED
},
.restartRequired = true,
});
QChar _toHex(ushort v) {
v = v & 0x000F;
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
@@ -94,17 +77,12 @@ QString _escapeFrom7bit(const QString &str) {
} // namespace
const char kOptionForceWaylandFractionalScaling[] = "force-wayland-fractional-scaling";
bool Sandbox::QuitOnStartRequested = false;
Sandbox::Sandbox(int &argc, char **argv)
: QApplication(argc, argv)
, _mainThreadId(QThread::currentThreadId()) {
setQuitOnLastWindowClosed(false);
if (OptionForceWaylandFractionalScaling.value()) {
setProperty("_q_force_wayland_fractional_scale", true);
}
}
int Sandbox::start() {
@@ -238,7 +216,7 @@ void Sandbox::setupScreenScale() {
const auto logEnv = [](const char *name) {
const auto value = qEnvironmentVariable(name);
if (!value.isEmpty()) {
LOG(("%1: %2").arg(name).arg(value));
LOG(("%1: %2").arg(name, value));
}
};
logEnv("QT_DEVICE_PIXEL_RATIO");
@@ -251,12 +229,7 @@ void Sandbox::setupScreenScale() {
logEnv("QT_USE_PHYSICAL_DPI");
logEnv("QT_FONT_DPI");
// Like Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor.
// Round up for .75 and higher. This favors "small UI" over "large UI".
const auto roundedRatio = ((ratio - qFloor(ratio)) < 0.75)
? qFloor(ratio)
: qCeil(ratio);
const auto useRatio = std::clamp(roundedRatio, 1, 3);
const auto useRatio = std::clamp(qCeil(ratio), 1, 3);
style::SetDevicePixelRatio(useRatio);
const auto screen = Sandbox::primaryScreen();

View File

@@ -19,8 +19,6 @@ class QLockFile;
namespace Core {
extern const char kOptionForceWaylandFractionalScaling[];
class UpdateChecker;
class Application;

View File

@@ -241,7 +241,7 @@ QString FindUpdateFile() {
}
const auto list = updates.entryInfoList(QDir::Files);
for (const auto &info : list) {
if (QRegularExpression(
static const auto RegExp = QRegularExpression(
"^("
"tupdate|"
"tx64upd|"
@@ -250,7 +250,8 @@ QString FindUpdateFile() {
"tlinuxupd|"
")\\d+(_[a-z\\d]+)?$",
QRegularExpression::CaseInsensitiveOption
).match(info.fileName()).hasMatch()) {
);
if (RegExp.match(info.fileName()).hasMatch()) {
return info.absoluteFilePath();
}
}

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 = 4012002;
constexpr auto AppVersionStr = "4.12.2";
constexpr auto AppVersion = 4014002;
constexpr auto AppVersionStr = "4.14.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h"
#include "data/data_group_call.h"
#include "data/data_message_reactions.h"
#include "data/data_wall_paper.h"
#include "data/notify/data_notify_settings.h"
#include "main/main_session.h"
#include "main/session/send_as_peers.h"
@@ -948,6 +949,14 @@ void ChannelData::processTopics(const MTPVector<MTPForumTopic> &topics) {
}
}
int ChannelData::levelHint() const {
return _levelHint;
}
void ChannelData::updateLevelHint(int levelHint) {
_levelHint = levelHint;
}
namespace Data {
void ApplyMigration(
@@ -1142,6 +1151,13 @@ void ApplyChannelUpdate(
session->sendAsPeers().setChosen(channel, PeerId());
}
if (const auto paper = update.vwallpaper()) {
channel->setWallPaper(
Data::WallPaper::Create(&channel->session(), *paper));
} else {
channel->setWallPaper({});
}
// For clearUpTill() call.
channel->owner().sendHistoryChangeNotifications();
}

View File

@@ -463,6 +463,9 @@ public:
void processTopics(const MTPVector<MTPForumTopic> &topics);
[[nodiscard]] int levelHint() const;
void updateLevelHint(int levelHint);
// Still public data members.
uint64 access = 0;
@@ -497,6 +500,7 @@ private:
int _restrictedCount = 0;
int _kickedCount = 0;
int _pendingRequestsCount = 0;
int _levelHint = 0;
std::vector<UserId> _recentRequesters;
MsgId _availableMinId = 0;

View File

@@ -889,7 +889,6 @@ not_null<HistoryItem*> DownloadManager::generateItem(
const auto replyTo = FullReplyTo();
const auto viaBotId = UserId();
const auto date = base::unixtime::now();
const auto postAuthor = QString();
const auto caption = TextWithEntities();
const auto make = [&](const auto media) {
return history->makeMessage(

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_emoji_statuses.h"
#include "main/main_session.h"
#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_document.h"
@@ -53,6 +54,7 @@ EmojiStatuses::EmojiStatuses(not_null<Session*> owner)
kRefreshDefaultListEach
) | rpl::start_with_next([=] {
refreshDefault();
refreshChannelDefault();
}, _lifetime);
}
@@ -74,6 +76,14 @@ void EmojiStatuses::refreshColored() {
requestColored();
}
void EmojiStatuses::refreshChannelDefault() {
requestChannelDefault();
}
void EmojiStatuses::refreshChannelColored() {
requestChannelColored();
}
void EmojiStatuses::refreshRecentDelayed() {
if (_recentRequestId || _recentRequestScheduled) {
return;
@@ -91,6 +101,8 @@ const std::vector<DocumentId> &EmojiStatuses::list(Type type) const {
case Type::Recent: return _recent;
case Type::Default: return _default;
case Type::Colored: return _colored;
case Type::ChannelDefault: return _channelDefault;
case Type::ChannelColored: return _channelColored;
}
Unexpected("Type in EmojiStatuses::list.");
}
@@ -103,20 +115,24 @@ rpl::producer<> EmojiStatuses::defaultUpdates() const {
return _defaultUpdated.events();
}
rpl::producer<> EmojiStatuses::channelDefaultUpdates() const {
return _channelDefaultUpdated.events();
}
void EmojiStatuses::registerAutomaticClear(
not_null<UserData*> user,
not_null<PeerData*> peer,
TimeId until) {
if (!until) {
_clearing.remove(user);
_clearing.remove(peer);
if (_clearing.empty()) {
_clearingTimer.cancel();
}
} else if (auto &already = _clearing[user]; already != until) {
} else if (auto &already = _clearing[peer]; already != until) {
already = until;
const auto i = ranges::min_element(_clearing, {}, [](auto &&pair) {
return pair.second;
});
if (i->first == user) {
if (i->first == peer) {
const auto now = base::unixtime::now();
if (now < until) {
processClearingIn(until - now);
@@ -266,7 +282,7 @@ void EmojiStatuses::requestDefault() {
}
auto &api = _owner->session().api();
_defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
MTP_long(_recentHash)
MTP_long(_defaultHash)
)).done([=](const MTPaccount_EmojiStatuses &result) {
_defaultRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) {
@@ -299,6 +315,45 @@ void EmojiStatuses::requestColored() {
}).send();
}
void EmojiStatuses::requestChannelDefault() {
if (_channelDefaultRequestId) {
return;
}
auto &api = _owner->session().api();
_channelDefaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
MTP_long(_channelDefaultHash)
)).done([=](const MTPaccount_EmojiStatuses &result) {
_channelDefaultRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) {
updateChannelDefault(data);
}, [&](const MTPDaccount_emojiStatusesNotModified &) {
});
}).fail([=] {
_channelDefaultRequestId = 0;
_channelDefaultHash = 0;
}).send();
}
void EmojiStatuses::requestChannelColored() {
if (_channelColoredRequestId) {
return;
}
auto &api = _owner->session().api();
_channelColoredRequestId = api.request(MTPmessages_GetStickerSet(
MTP_inputStickerSetEmojiChannelDefaultStatuses(),
MTP_int(0) // hash
)).done([=](const MTPmessages_StickerSet &result) {
_channelColoredRequestId = 0;
result.match([&](const MTPDmessages_stickerSet &data) {
updateChannelColored(data);
}, [](const MTPDmessages_stickerSetNotModified &) {
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
});
}).fail([=] {
_channelColoredRequestId = 0;
}).send();
}
void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) {
_recentHash = data.vhash().v;
_recent = ListFromMTP(data);
@@ -321,27 +376,57 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) {
_coloredUpdated.fire({});
}
void EmojiStatuses::set(DocumentId id, TimeId until) {
auto &api = _owner->session().api();
if (_sentRequestId) {
api.request(base::take(_sentRequestId)).cancel();
void EmojiStatuses::updateChannelDefault(
const MTPDaccount_emojiStatuses &data) {
_channelDefaultHash = data.vhash().v;
_channelDefault = ListFromMTP(data);
_channelDefaultUpdated.fire({});
}
void EmojiStatuses::updateChannelColored(
const MTPDmessages_stickerSet &data) {
const auto &list = data.vdocuments().v;
_channelColored.clear();
_channelColored.reserve(list.size());
for (const auto &sticker : data.vdocuments().v) {
_channelColored.push_back(_owner->processDocument(sticker)->id);
}
_owner->session().user()->setEmojiStatus(id, until);
_sentRequestId = api.request(MTPaccount_UpdateEmojiStatus(
!id
_channelColoredUpdated.fire({});
}
void EmojiStatuses::set(DocumentId id, TimeId until) {
set(_owner->session().user(), id, until);
}
void EmojiStatuses::set(
not_null<PeerData*> peer,
DocumentId id,
TimeId until) {
auto &api = _owner->session().api();
auto &requestId = _sentRequests[peer];
if (requestId) {
api.request(base::take(requestId)).cancel();
}
peer->setEmojiStatus(id, until);
const auto send = [&](auto &&request) {
requestId = api.request(
std::move(request)
).done([=] {
_sentRequests.remove(peer);
}).fail([=] {
_sentRequests.remove(peer);
}).send();
};
const auto status = !id
? MTP_emojiStatusEmpty()
: !until
? MTP_emojiStatus(MTP_long(id))
: MTP_emojiStatusUntil(MTP_long(id), MTP_int(until))
)).done([=] {
_sentRequestId = 0;
}).fail([=] {
_sentRequestId = 0;
}).send();
}
bool EmojiStatuses::setting() const {
return _sentRequestId != 0;;
: MTP_emojiStatusUntil(MTP_long(id), MTP_int(until));
if (peer->isSelf()) {
send(MTPaccount_UpdateEmojiStatus(status));
} else if (const auto channel = peer->asChannel()) {
send(MTPchannels_UpdateEmojiStatus(channel->inputChannel, status));
}
}
EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) {

View File

@@ -36,22 +36,26 @@ public:
void refreshRecentDelayed();
void refreshDefault();
void refreshColored();
void refreshChannelDefault();
void refreshChannelColored();
enum class Type {
Recent,
Default,
Colored,
ChannelDefault,
ChannelColored,
};
[[nodiscard]] const std::vector<DocumentId> &list(Type type) const;
[[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const;
[[nodiscard]] rpl::producer<> coloredUpdates() const;
[[nodiscard]] rpl::producer<> channelDefaultUpdates() const;
void set(DocumentId id, TimeId until = 0);
[[nodiscard]] bool setting() const;
void set(not_null<PeerData*> peer, DocumentId id, TimeId until = 0);
void registerAutomaticClear(not_null<UserData*> user, TimeId until);
void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);
using Groups = std::vector<Ui::EmojiGroup>;
[[nodiscard]] rpl::producer<Groups> emojiGroupsValue() const;
@@ -71,10 +75,14 @@ private:
void requestRecent();
void requestDefault();
void requestColored();
void requestChannelDefault();
void requestChannelColored();
void updateRecent(const MTPDaccount_emojiStatuses &data);
void updateDefault(const MTPDaccount_emojiStatuses &data);
void updateColored(const MTPDmessages_stickerSet &data);
void updateChannelDefault(const MTPDaccount_emojiStatuses &data);
void updateChannelColored(const MTPDmessages_stickerSet &data);
void processClearingIn(TimeId wait);
void processClearing();
@@ -87,9 +95,13 @@ private:
std::vector<DocumentId> _recent;
std::vector<DocumentId> _default;
std::vector<DocumentId> _colored;
std::vector<DocumentId> _channelDefault;
std::vector<DocumentId> _channelColored;
rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated;
rpl::event_stream<> _coloredUpdated;
rpl::event_stream<> _channelDefaultUpdated;
rpl::event_stream<> _channelColoredUpdated;
mtpRequestId _recentRequestId = 0;
bool _recentRequestScheduled = false;
@@ -100,9 +112,14 @@ private:
mtpRequestId _coloredRequestId = 0;
mtpRequestId _sentRequestId = 0;
mtpRequestId _channelDefaultRequestId = 0;
uint64 _channelDefaultHash = 0;
base::flat_map<not_null<UserData*>, TimeId> _clearing;
mtpRequestId _channelColoredRequestId = 0;
base::flat_map<not_null<PeerData*>, mtpRequestId> _sentRequests;
base::flat_map<not_null<PeerData*>, TimeId> _clearing;
base::Timer _clearingTimer;
GroupsType _emojiGroups;

View File

@@ -343,12 +343,6 @@ int Folder::storiesUnreadCount() const {
return _storiesUnreadCount;
}
void Folder::requestChatListMessage() {
if (!chatListMessageKnown()) {
owner().histories().requestDialogEntry(this);
}
}
TimeId Folder::adjustedChatListTimeId() const {
return chatListTimeId();
}

View File

@@ -49,9 +49,9 @@ public:
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
void requestChatListMessage() override;
const QString &chatListName() const override;
const QString &chatListNameSortKey() const override;
int chatListNameVersion() const override;
const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override;
@@ -82,8 +82,6 @@ public:
private:
void indexNameParts();
int chatListNameVersion() const override;
void reorderLastHistories();
void paintUserpic(

View File

@@ -15,13 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
namespace Data {
namespace {
constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000);
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000);
} // namespace
ForumIcons::ForumIcons(not_null<Session*> owner)
: _owner(owner)

View File

@@ -98,6 +98,7 @@ public:
void setRealRootId(MsgId realId);
void readTillEnd();
void requestChatListMessage();
void applyTopic(const MTPDforumTopic &data);
@@ -109,9 +110,9 @@ public:
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
void requestChatListMessage() override;
const QString &chatListName() const override;
const QString &chatListNameSortKey() const override;
int chatListNameVersion() const override;
const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override;
@@ -187,8 +188,6 @@ private:
void allowChatListMessageResolve();
void resolveChatListMessageGroup();
int chatListNameVersion() const override;
void subscribeToUnreadChanges();
[[nodiscard]] Dialogs::UnreadState unreadStateFor(
int count,

View File

@@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_stories.h"
#include "data/data_story.h"
#include "data/data_user.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "core/application.h"
@@ -363,10 +364,10 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
return result;
}
Giveaway ComputeGiveawayData(
GiveawayStart ComputeGiveawayStartData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveaway &data) {
auto result = Giveaway{
auto result = GiveawayStart{
.untilDate = data.vuntil_date().v,
.quantity = data.vquantity().v,
.months = data.vmonths().v,
@@ -383,6 +384,35 @@ Giveaway ComputeGiveawayData(
result.countries.push_back(qs(country));
}
}
if (const auto additional = data.vprize_description()) {
result.additionalPrize = qs(*additional);
}
return result;
}
GiveawayResults ComputeGiveawayResultsData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveawayResults &data) {
const auto additional = data.vadditional_peers_count();
auto result = GiveawayResults{
.channel = item->history()->owner().channel(data.vchannel_id()),
.untilDate = data.vuntil_date().v,
.launchId = data.vlaunch_msg_id().v,
.additionalPeersCount = additional.value_or_empty(),
.winnersCount = data.vwinners_count().v,
.unclaimedCount = data.vunclaimed_count().v,
.months = data.vmonths().v,
.refunded = data.is_refunded(),
.all = !data.is_only_new_subscribers(),
};
result.winners.reserve(data.vwinners().v.size());
const auto owner = &item->history()->owner();
for (const auto &id : data.vwinners().v) {
result.winners.push_back(owner->user(UserId(id)));
}
if (const auto additional = data.vprize_description()) {
result.additionalPrize = qs(*additional);
}
return result;
}
@@ -453,7 +483,11 @@ bool Media::storyMention() const {
return false;
}
const Giveaway *Media::giveaway() const {
const GiveawayStart *Media::giveawayStart() const {
return nullptr;
}
const GiveawayResults *Media::giveawayResults() const {
return nullptr;
}
@@ -521,6 +555,10 @@ bool Media::hasSpoiler() const {
return false;
}
crl::time Media::ttlSeconds() const {
return 0;
}
bool Media::consumeMessageText(const TextWithEntities &text) {
return false;
}
@@ -815,12 +853,14 @@ MediaFile::MediaFile(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
bool skipPremiumEffect,
bool spoiler)
bool spoiler,
crl::time ttlSeconds)
: Media(parent)
, _document(document)
, _emoji(document->sticker() ? document->sticker()->alt : QString())
, _skipPremiumEffect(skipPremiumEffect)
, _spoiler(spoiler) {
, _spoiler(spoiler)
, _ttlSeconds(ttlSeconds) {
parent->history()->owner().registerDocumentItem(_document, parent);
if (!_emoji.isEmpty()) {
@@ -848,7 +888,8 @@ std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
parent,
_document,
!_document->session().premium(),
_spoiler);
_spoiler,
_ttlSeconds);
}
DocumentData *MediaFile::document() const {
@@ -1095,6 +1136,14 @@ bool MediaFile::hasSpoiler() const {
return _spoiler;
}
crl::time MediaFile::ttlSeconds() const {
return _ttlSeconds;
}
bool MediaFile::allowsForward() const {
return !ttlSeconds();
}
bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) {
if (media.type() != mtpc_messageMediaDocument) {
return false;
@@ -1506,15 +1555,57 @@ bool MediaWebPage::replyPreviewLoaded() const {
}
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
auto text = options.ignoreMessageText
? TextWithEntities()
: options.translated
? parent()->translatedText()
: parent()->originalText();
if (text.empty()) {
text = Ui::Text::Colorized(_page->url);
const auto caption = [&] {
const auto text = options.ignoreMessageText
? TextWithEntities()
: options.translated
? parent()->translatedText()
: parent()->originalText();
return text.empty() ? Ui::Text::Colorized(_page->url) : text;
}();
const auto pageTypeWithPreview = _page->type == WebPageType::Photo
|| _page->type == WebPageType::Video
|| _page->type == WebPageType::Document;
if (pageTypeWithPreview || !_page->collage.items.empty()) {
if (auto found = FindCachedPreview(options.existing, _page, false)) {
return { .text = caption, .images = { std::move(found) } };
}
auto context = std::any();
auto images = std::vector<ItemPreviewImage>();
auto prepared = ItemPreviewImage();
const auto radius = ImageRoundRadius::Small;
if (const auto photo = MediaWebPage::photo()) {
const auto media = photo->createMediaView();
prepared = PreparePhotoPreview(parent(), media, radius, false);
if (prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context = media;
}
}
} else {
const auto document = MediaWebPage::document();
if (document
&& document->hasThumbnail()
&& (document->isGifv() || document->isVideoFile())) {
const auto media = document->createMediaView();
prepared = PrepareFilePreview(parent(), media, radius, false);
if (prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context = media;
}
}
}
}
return {
.text = caption,
.images = std::move(images),
.loadingContext = std::move(context),
};
} else {
return { .text = caption };
}
return { .text = text };
}
TextWithEntities MediaWebPage::notificationText() const {
@@ -2196,51 +2287,103 @@ std::unique_ptr<HistoryView::Media> MediaStory::createView(
}
}
MediaGiveaway::MediaGiveaway(
MediaGiveawayStart::MediaGiveawayStart(
not_null<HistoryItem*> parent,
const Giveaway &data)
const GiveawayStart &data)
: Media(parent)
, _giveaway(data) {
, _data(data) {
parent->history()->session().giftBoxStickersPacks().load();
}
std::unique_ptr<Media> MediaGiveaway::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaGiveaway>(parent, _giveaway);
std::unique_ptr<Media> MediaGiveawayStart::clone(
not_null<HistoryItem*> parent) {
return std::make_unique<MediaGiveawayStart>(parent, _data);
}
const Giveaway *MediaGiveaway::giveaway() const {
return &_giveaway;
const GiveawayStart *MediaGiveawayStart::giveawayStart() const {
return &_data;
}
TextWithEntities MediaGiveaway::notificationText() const {
TextWithEntities MediaGiveawayStart::notificationText() const {
return {
.text = tr::lng_prizes_title(tr::now, lt_count, _giveaway.quantity),
.text = tr::lng_prizes_title(tr::now, lt_count, _data.quantity),
};
}
QString MediaGiveaway::pinnedTextSubstring() const {
QString MediaGiveawayStart::pinnedTextSubstring() const {
return QString::fromUtf8("\xC2\xAB")
+ notificationText().text
+ QString::fromUtf8("\xC2\xBB");
}
TextForMimeData MediaGiveaway::clipboardText() const {
TextForMimeData MediaGiveawayStart::clipboardText() const {
return TextForMimeData();
}
bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) {
bool MediaGiveawayStart::updateInlineResultMedia(const MTPMessageMedia &media) {
return true;
}
bool MediaGiveaway::updateSentMedia(const MTPMessageMedia &media) {
bool MediaGiveawayStart::updateSentMedia(const MTPMessageMedia &media) {
return true;
}
std::unique_ptr<HistoryView::Media> MediaGiveaway::createView(
std::unique_ptr<HistoryView::Media> MediaGiveawayStart::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::Giveaway>(message, &_giveaway);
return std::make_unique<HistoryView::MediaInBubble>(
message,
HistoryView::GenerateGiveawayStart(message, &_data));
}
MediaGiveawayResults::MediaGiveawayResults(
not_null<HistoryItem*> parent,
const GiveawayResults &data)
: Media(parent)
, _data(data) {
}
std::unique_ptr<Media> MediaGiveawayResults::clone(
not_null<HistoryItem*> parent) {
return std::make_unique<MediaGiveawayResults>(parent, _data);
}
const GiveawayResults *MediaGiveawayResults::giveawayResults() const {
return &_data;
}
TextWithEntities MediaGiveawayResults::notificationText() const {
return {
.text = tr::lng_prizes_results_title(tr::now),
};
}
QString MediaGiveawayResults::pinnedTextSubstring() const {
return QString::fromUtf8("\xC2\xAB")
+ notificationText().text
+ QString::fromUtf8("\xC2\xBB");
}
TextForMimeData MediaGiveawayResults::clipboardText() const {
return TextForMimeData();
}
bool MediaGiveawayResults::updateInlineResultMedia(const MTPMessageMedia &media) {
return true;
}
bool MediaGiveawayResults::updateSentMedia(const MTPMessageMedia &media) {
return true;
}
std::unique_ptr<HistoryView::Media> MediaGiveawayResults::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::MediaInBubble>(
message,
HistoryView::GenerateGiveawayResults(message, &_data));
}
} // namespace Data

View File

@@ -90,15 +90,30 @@ struct Invoice {
bool isTest = false;
};
struct Giveaway {
struct GiveawayStart {
std::vector<not_null<ChannelData*>> channels;
std::vector<QString> countries;
QString additionalPrize;
TimeId untilDate = 0;
int quantity = 0;
int months = 0;
bool all = false;
};
struct GiveawayResults {
not_null<ChannelData*> channel;
std::vector<not_null<PeerData*>> winners;
QString additionalPrize;
TimeId untilDate = 0;
MsgId launchId = 0;
int additionalPeersCount = 0;
int winnersCount = 0;
int unclaimedCount = 0;
int months = 0;
bool refunded = false;
bool all = false;
};
struct GiftCode {
QString slug;
ChannelData *channel = nullptr;
@@ -135,7 +150,8 @@ public:
virtual FullStoryId storyId() const;
virtual bool storyExpired(bool revalidate = false);
virtual bool storyMention() const;
virtual const Giveaway *giveaway() const;
virtual const GiveawayStart *giveawayStart() const;
virtual const GiveawayResults *giveawayResults() const;
virtual bool uploading() const;
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
@@ -158,6 +174,7 @@ public:
virtual bool dropForwardedInfo() const;
virtual bool forceForwardedInfo() const;
[[nodiscard]] virtual bool hasSpoiler() const;
[[nodiscard]] virtual crl::time ttlSeconds() const;
[[nodiscard]] virtual bool consumeMessageText(
const TextWithEntities &text);
@@ -240,7 +257,8 @@ public:
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
bool skipPremiumEffect,
bool spoiler);
bool spoiler,
crl::time ttlSeconds);
~MediaFile();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
@@ -262,6 +280,8 @@ public:
bool forwardedBecomesUnread() const override;
bool dropForwardedInfo() const override;
bool hasSpoiler() const override;
crl::time ttlSeconds() const override;
bool allowsForward() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
@@ -276,6 +296,9 @@ private:
bool _skipPremiumEffect = false;
bool _spoiler = false;
// Video (unsupported) / Voice / Round.
crl::time _ttlSeconds = 0;
};
class MediaContact final : public Media {
@@ -634,15 +657,15 @@ private:
};
class MediaGiveaway final : public Media {
class MediaGiveawayStart final : public Media {
public:
MediaGiveaway(
MediaGiveawayStart(
not_null<HistoryItem*> parent,
const Giveaway &data);
const GiveawayStart &data);
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
const Giveaway *giveaway() const override;
const GiveawayStart *giveawayStart() const override;
TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override;
@@ -656,7 +679,33 @@ public:
HistoryView::Element *replacing = nullptr) override;
private:
Giveaway _giveaway;
GiveawayStart _data;
};
class MediaGiveawayResults final : public Media {
public:
MediaGiveawayResults(
not_null<HistoryItem*> parent,
const GiveawayResults &data);
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
const GiveawayResults *giveawayResults() const override;
TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
std::unique_ptr<HistoryView::Media> createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing = nullptr) override;
private:
GiveawayResults _data;
};
@@ -670,8 +719,12 @@ private:
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
[[nodiscard]] Giveaway ComputeGiveawayData(
[[nodiscard]] GiveawayStart ComputeGiveawayStartData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveaway &data);
[[nodiscard]] GiveawayResults ComputeGiveawayResultsData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveawayResults &data);
} // namespace Data

View File

@@ -12,11 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_participant_status.h"
#include "data/data_channel.h"
#include "data/data_changes.h"
#include "data/data_emoji_statuses.h"
#include "data/data_message_reaction_id.h"
#include "data/data_photo.h"
#include "data/data_folder.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
@@ -926,6 +928,24 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) {
_backgroundEmojiId = id;
return true;
}
void PeerData::setEmojiStatus(const MTPEmojiStatus &status) {
const auto parsed = Data::ParseEmojiStatus(status);
setEmojiStatus(parsed.id, parsed.until);
}
void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
if (_emojiStatusId != emojiStatusId) {
_emojiStatusId = emojiStatusId;
session().changes().peerUpdated(this, UpdateFlag::EmojiStatus);
}
owner().emojiStatuses().registerAutomaticClear(this, until);
}
DocumentId PeerData::emojiStatusId() const {
return _emojiStatusId;
}
bool PeerData::isSelf() const {
if (const auto user = asUser()) {
return (user->flags() & UserDataFlag::Self);
@@ -1010,6 +1030,10 @@ bool PeerData::sharedMediaInfo() const {
return isSelf() || isRepliesChat();
}
bool PeerData::savedSublistsInfo() const {
return isSelf() && owner().savedMessages().supported();
}
bool PeerData::hasStoriesHidden() const {
if (const auto user = asUser()) {
return user->hasStoriesHidden();

View File

@@ -165,6 +165,7 @@ public:
virtual ~PeerData();
static constexpr auto kServiceNotificationsId = peerFromUser(777000);
static constexpr auto kSavedHiddenAuthorId = peerFromUser(2666000);
[[nodiscard]] Data::Session &owner() const;
[[nodiscard]] Main::Session &session() const;
@@ -178,6 +179,10 @@ public:
[[nodiscard]] DocumentId backgroundEmojiId() const;
bool changeBackgroundEmojiId(DocumentId id);
void setEmojiStatus(const MTPEmojiStatus &status);
void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0);
[[nodiscard]] DocumentId emojiStatusId() const;
[[nodiscard]] bool isUser() const {
return peerIsUser(id);
}
@@ -198,6 +203,7 @@ public:
[[nodiscard]] bool isGigagroup() const;
[[nodiscard]] bool isRepliesChat() const;
[[nodiscard]] bool sharedMediaInfo() const;
[[nodiscard]] bool savedSublistsInfo() const;
[[nodiscard]] bool hasStoriesHidden() const;
void setStoriesHidden(bool hidden);
@@ -208,6 +214,9 @@ public:
[[nodiscard]] bool isServiceUser() const {
return isUser() && !(id.value % 1000);
}
[[nodiscard]] bool isSavedHiddenAuthor() const {
return (id == kSavedHiddenAuthorId);
}
[[nodiscard]] Data::Forum *forum() const;
[[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const;
@@ -466,6 +475,7 @@ private:
base::flat_set<QString> _nameWords; // for filtering
base::flat_set<QChar> _nameFirstLetters;
DocumentId _emojiStatusId = 0;
uint64 _backgroundEmojiId = 0;
crl::time _lastFullUpdate = 0;

View File

@@ -226,7 +226,6 @@ bool PhotoMedia::setToClipboard() {
if (fallback.isNull()) {
return false;
}
const auto bytes = imageBytes(large);
auto mime = std::make_unique<QMimeData>();
mime->setImageData(std::move(fallback));
if (auto bytes = imageBytes(large); !bytes.isEmpty()) {

View File

@@ -64,7 +64,7 @@ private:
// In case this is a problem the ~Gif code should be rewritten.
const not_null<PhotoData*> _owner;
mutable std::unique_ptr<Image> _inlineThumbnail;
std::array<PhotoImage, kPhotoSizeCount> _images;
std::array<PhotoImage, kPhotoSizeCount> _images;
QByteArray _videoBytesSmall;
QByteArray _videoBytesLarge;

View File

@@ -141,6 +141,18 @@ int PremiumLimits::topicsPinnedCurrent() const {
return appConfigLimit("topics_pinned_limit", 5);
}
int PremiumLimits::savedSublistsPinnedDefault() const {
return appConfigLimit("saved_dialogs_pinned_limit_default", 5);
}
int PremiumLimits::savedSublistsPinnedPremium() const {
return appConfigLimit("saved_dialogs_pinned_limit_premium", 100);
}
int PremiumLimits::savedSublistsPinnedCurrent() const {
return isPremium()
? savedSublistsPinnedPremium()
: savedSublistsPinnedDefault();
}
int PremiumLimits::channelsPublicDefault() const {
return appConfigLimit("channels_public_limit_default", 10);
}

View File

@@ -59,6 +59,10 @@ public:
[[nodiscard]] int topicsPinnedCurrent() const;
[[nodiscard]] int savedSublistsPinnedDefault() const;
[[nodiscard]] int savedSublistsPinnedPremium() const;
[[nodiscard]] int savedSublistsPinnedCurrent() const;
[[nodiscard]] int channelsPublicDefault() const;
[[nodiscard]] int channelsPublicPremium() const;
[[nodiscard]] int channelsPublicCurrent() const;

View File

@@ -0,0 +1,296 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_saved_messages.h"
#include "apiwrap.h"
#include "data/data_peer.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_session.h"
namespace Data {
namespace {
constexpr auto kPerPage = 50;
constexpr auto kFirstPerPage = 10;
} // namespace
SavedMessages::SavedMessages(not_null<Session*> owner)
: _owner(owner)
, _chatsList(
&owner->session(),
FilterId(),
owner->maxPinnedChatsLimitValue(this))
, _loadMore([=] { sendLoadMoreRequests(); }) {
}
SavedMessages::~SavedMessages() = default;
bool SavedMessages::supported() const {
return !_unsupported;
}
Session &SavedMessages::owner() const {
return *_owner;
}
Main::Session &SavedMessages::session() const {
return _owner->session();
}
not_null<Dialogs::MainList*> SavedMessages::chatsList() {
return &_chatsList;
}
not_null<SavedSublist*> SavedMessages::sublist(not_null<PeerData*> peer) {
const auto i = _sublists.find(peer);
if (i != end(_sublists)) {
return i->second.get();
}
return _sublists.emplace(
peer,
std::make_unique<SavedSublist>(peer)).first->second.get();
}
void SavedMessages::loadMore() {
_loadMoreScheduled = true;
_loadMore.call();
}
void SavedMessages::loadMore(not_null<SavedSublist*> sublist) {
_loadMoreSublistsScheduled.emplace(sublist);
_loadMore.call();
}
void SavedMessages::sendLoadMore() {
if (_loadMoreRequestId || _chatsList.loaded()) {
return;
} else if (!_pinnedLoaded) {
loadPinned();
}
_loadMoreRequestId = _owner->session().api().request(
MTPmessages_GetSavedDialogs(
MTP_flags(MTPmessages_GetSavedDialogs::Flag::f_exclude_pinned),
MTP_int(_offsetDate),
MTP_int(_offsetId),
_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
MTP_int(kPerPage),
MTP_long(0)) // hash
).done([=](const MTPmessages_SavedDialogs &result) {
apply(result, false);
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
}
_chatsList.setLoaded();
_loadMoreRequestId = 0;
}).send();
}
void SavedMessages::loadPinned() {
if (_pinnedRequestId) {
return;
}
_pinnedRequestId = _owner->session().api().request(
MTPmessages_GetPinnedSavedDialogs()
).done([=](const MTPmessages_SavedDialogs &result) {
apply(result, true);
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
} else {
_pinnedLoaded = true;
}
_pinnedRequestId = 0;
}).send();
}
void SavedMessages::sendLoadMore(not_null<SavedSublist*> sublist) {
if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) {
return;
}
const auto &list = sublist->messages();
const auto offsetId = list.empty() ? MsgId(0) : list.back()->id;
const auto offsetDate = list.empty() ? MsgId(0) : list.back()->date();
const auto limit = offsetId ? kPerPage : kFirstPerPage;
const auto requestId = _owner->session().api().request(
MTPmessages_GetSavedHistory(
sublist->peer()->input,
MTP_int(offsetId),
MTP_int(offsetDate),
MTP_int(0), // add_offset
MTP_int(limit),
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_long(0)) // hash
).done([=](const MTPmessages_Messages &result) {
auto count = 0;
auto list = (const QVector<MTPMessage>*)nullptr;
result.match([](const MTPDmessages_channelMessages &) {
LOG(("API Error: messages.channelMessages in sublist."));
}, [](const MTPDmessages_messagesNotModified &) {
LOG(("API Error: messages.messagesNotModified in sublist."));
}, [&](const auto &data) {
owner().processUsers(data.vusers());
owner().processChats(data.vchats());
list = &data.vmessages().v;
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
count = int(list->size());
} else {
count = data.vcount().v;
}
});
_loadMoreRequests.remove(sublist);
if (!list) {
sublist->setFullLoaded();
return;
}
auto items = std::vector<not_null<HistoryItem*>>();
items.reserve(list->size());
for (const auto &message : *list) {
const auto item = owner().addNewMessage(
message,
{},
NewMessageType::Existing);
if (item) {
items.push_back(item);
}
}
sublist->append(std::move(items), count);
if (result.type() == mtpc_messages_messages) {
sublist->setFullLoaded();
}
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
}
sublist->setFullLoaded();
_loadMoreRequests.remove(sublist);
}).send();
_loadMoreRequests[sublist] = requestId;
}
void SavedMessages::apply(
const MTPmessages_SavedDialogs &result,
bool pinned) {
auto list = (const QVector<MTPSavedDialog>*)nullptr;
result.match([](const MTPDmessages_savedDialogsNotModified &) {
LOG(("API Error: messages.savedDialogsNotModified."));
}, [&](const auto &data) {
_owner->processUsers(data.vusers());
_owner->processChats(data.vchats());
_owner->processMessages(
data.vmessages(),
NewMessageType::Existing);
list = &data.vdialogs().v;
});
if (pinned) {
_pinnedRequestId = 0;
_pinnedLoaded = true;
} else {
_loadMoreRequestId = 0;
}
if (!list) {
if (!pinned) {
_chatsList.setLoaded();
}
return;
}
auto lastValid = false;
auto offsetDate = TimeId();
auto offsetId = MsgId();
auto offsetPeer = (PeerData*)nullptr;
const auto selfId = _owner->session().userPeerId();
for (const auto &dialog : *list) {
const auto &data = dialog.data();
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
const auto topId = MsgId(data.vtop_message().v);
if (const auto item = _owner->message(selfId, topId)) {
offsetPeer = peer;
offsetDate = item->date();
offsetId = topId;
lastValid = true;
const auto entry = sublist(peer);
const auto entryPinned = pinned || data.is_pinned();
entry->applyMaybeLast(item);
_owner->setPinnedFromEntryList(entry, entryPinned);
} else {
lastValid = false;
}
}
if (pinned) {
} else if (!lastValid) {
LOG(("API Error: Unknown message in the end of a slice."));
_chatsList.setLoaded();
} else if (result.type() == mtpc_messages_savedDialogs) {
_chatsList.setLoaded();
} else if (offsetDate < _offsetDate
|| (offsetDate == _offsetDate && offsetId == _offsetId && offsetPeer == _offsetPeer)) {
LOG(("API Error: Bad order in messages.savedDialogs."));
_chatsList.setLoaded();
} else {
_offsetDate = offsetDate;
_offsetId = offsetId;
_offsetPeer = offsetPeer;
}
}
void SavedMessages::sendLoadMoreRequests() {
if (_loadMoreScheduled) {
sendLoadMore();
}
for (const auto sublist : base::take(_loadMoreSublistsScheduled)) {
sendLoadMore(sublist);
}
}
void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) {
const auto list = update.vorder();
if (!list) {
loadPinned();
return;
}
const auto &order = list->v;
const auto notLoaded = [&](const MTPDialogPeer &peer) {
return peer.match([&](const MTPDdialogPeer &data) {
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
return !_sublists.contains(peer);
}, [&](const MTPDdialogPeerFolder &data) {
LOG(("API Error: "
"updatePinnedSavedDialogs has folders."));
return false;
});
};
if (!ranges::none_of(order, notLoaded)) {
loadPinned();
} else {
_chatsList.pinned()->applyList(_owner, order);
_owner->notifyPinnedDialogsOrderUpdated();
}
}
void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
update.vpeer().match([&](const MTPDdialogPeer &data) {
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
const auto i = _sublists.find(peer);
if (i != end(_sublists)) {
const auto entry = i->second.get();
_owner->setChatPinned(entry, FilterId(), update.is_pinned());
} else {
loadPinned();
}
}, [&](const MTPDdialogPeerFolder &data) {
DEBUG_LOG(("API Error: Folder in updateSavedDialogPinned."));
});
}
} // namespace Data

View File

@@ -0,0 +1,72 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/dialogs_main_list.h"
namespace Main {
class Session;
} // namespace Main
namespace Data {
class Session;
class SavedSublist;
class SavedMessages final {
public:
explicit SavedMessages(not_null<Session*> owner);
~SavedMessages();
[[nodiscard]] bool supported() const;
[[nodiscard]] Session &owner() const;
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] not_null<Dialogs::MainList*> chatsList();
[[nodiscard]] not_null<SavedSublist*> sublist(not_null<PeerData*> peer);
void loadMore();
void loadMore(not_null<SavedSublist*> sublist);
void apply(const MTPDupdatePinnedSavedDialogs &update);
void apply(const MTPDupdateSavedDialogPinned &update);
private:
void loadPinned();
void apply(const MTPmessages_SavedDialogs &result, bool pinned);
void sendLoadMore();
void sendLoadMore(not_null<SavedSublist*> sublist);
void sendLoadMoreRequests();
const not_null<Session*> _owner;
Dialogs::MainList _chatsList;
base::flat_map<
not_null<PeerData*>,
std::unique_ptr<SavedSublist>> _sublists;
base::flat_map<not_null<SavedSublist*>, mtpRequestId> _loadMoreRequests;
mtpRequestId _loadMoreRequestId = 0;
mtpRequestId _pinnedRequestId = 0;
TimeId _offsetDate = 0;
MsgId _offsetId = 0;
PeerData *_offsetPeer = nullptr;
SingleQueuedInvokation _loadMore;
base::flat_set<not_null<SavedSublist*>> _loadMoreSublistsScheduled;
bool _loadMoreScheduled = false;
bool _pinnedLoaded = false;
bool _unsupported = false;
};
} // namespace Data

View File

@@ -0,0 +1,256 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_saved_sublist.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_saved_messages.h"
#include "data/data_session.h"
#include "history/view/history_view_item_preview.h"
#include "history/history.h"
#include "history/history_item.h"
namespace Data {
SavedSublist::SavedSublist(not_null<PeerData*> peer)
: Entry(&peer->owner(), Dialogs::Entry::Type::SavedSublist)
, _history(peer->owner().history(peer)) {
}
SavedSublist::~SavedSublist() = default;
not_null<History*> SavedSublist::history() const {
return _history;
}
not_null<PeerData*> SavedSublist::peer() const {
return _history->peer;
}
bool SavedSublist::isHiddenAuthor() const {
return peer()->isSavedHiddenAuthor();
}
bool SavedSublist::isFullLoaded() const {
return (_flags & Flag::FullLoaded) != 0;
}
auto SavedSublist::messages() const
-> const std::vector<not_null<HistoryItem*>> & {
return _items;
}
void SavedSublist::applyMaybeLast(not_null<HistoryItem*> item, bool added) {
const auto before = [](
not_null<HistoryItem*> a,
not_null<HistoryItem*> b) {
return IsServerMsgId(a->id)
? (IsServerMsgId(b->id) ? (a->id < b->id) : true)
: (IsServerMsgId(b->id) ? false : (a->id < b->id));
};
if (_items.empty()) {
_items.push_back(item);
} else if (_items.front() == item) {
return;
} else if (!isFullLoaded()
&& _items.size() == 1
&& before(_items.front(), item)) {
_items[0] = item;
} else if (before(_items.back(), item)) {
for (auto i = begin(_items); i != end(_items); ++i) {
if (item == *i) {
return;
} else if (before(*i, item)) {
_items.insert(i, item);
break;
}
}
}
if (added && _fullCount) {
++*_fullCount;
}
if (_items.front() == item) {
setChatListTimeId(item->date());
resolveChatListMessageGroup();
}
_changed.fire({});
}
void SavedSublist::removeOne(not_null<HistoryItem*> item) {
if (_items.empty()) {
return;
}
const auto last = (_items.front() == item);
const auto from = ranges::remove(_items, item);
const auto removed = end(_items) - from;
if (removed) {
_items.erase(from, end(_items));
}
if (_fullCount) {
--*_fullCount;
}
if (last) {
if (_items.empty()) {
if (isFullLoaded()) {
updateChatListExistence();
} else {
updateChatListEntry();
crl::on_main(this, [=] {
owner().savedMessages().loadMore(this);
});
}
} else {
setChatListTimeId(_items.front()->date());
}
}
if (removed || _fullCount) {
_changed.fire({});
}
}
rpl::producer<> SavedSublist::changes() const {
return _changed.events();
}
std::optional<int> SavedSublist::fullCount() const {
return isFullLoaded() ? int(_items.size()) : _fullCount;
}
rpl::producer<int> SavedSublist::fullCountValue() const {
return _changed.events_starting_with({}) | rpl::map([=] {
return fullCount();
}) | rpl::filter_optional();
}
void SavedSublist::append(
std::vector<not_null<HistoryItem*>> &&items,
int fullCount) {
_fullCount = fullCount;
if (items.empty()) {
setFullLoaded();
} else if (_items.empty()) {
_items = std::move(items);
setChatListTimeId(_items.front()->date());
_changed.fire({});
} else if (_items.back()->id > items.front()->id) {
_items.insert(end(_items), begin(items), end(items));
_changed.fire({});
} else {
_items.insert(end(_items), begin(items), end(items));
ranges::stable_sort(
_items,
ranges::greater(),
&HistoryItem::id);
ranges::unique(_items, ranges::greater(), &HistoryItem::id);
_changed.fire({});
}
}
void SavedSublist::setFullLoaded(bool loaded) {
if (loaded != isFullLoaded()) {
if (loaded) {
_flags |= Flag::FullLoaded;
if (_items.empty()) {
updateChatListExistence();
}
} else {
_flags &= ~Flag::FullLoaded;
}
_changed.fire({});
}
}
int SavedSublist::fixedOnTopIndex() const {
return 0;
}
bool SavedSublist::shouldBeInChatList() const {
return isPinnedDialog(FilterId()) || !_items.empty();
}
Dialogs::UnreadState SavedSublist::chatListUnreadState() const {
return {};
}
Dialogs::BadgesState SavedSublist::chatListBadgesState() const {
return {};
}
HistoryItem *SavedSublist::chatListMessage() const {
return _items.empty() ? nullptr : _items.front().get();
}
bool SavedSublist::chatListMessageKnown() const {
return true;
}
const QString &SavedSublist::chatListName() const {
return _history->chatListName();
}
const base::flat_set<QString> &SavedSublist::chatListNameWords() const {
return _history->chatListNameWords();
}
const base::flat_set<QChar> &SavedSublist::chatListFirstLetters() const {
return _history->chatListFirstLetters();
}
const QString &SavedSublist::chatListNameSortKey() const {
return _history->chatListNameSortKey();
}
int SavedSublist::chatListNameVersion() const {
return _history->chatListNameVersion();
}
void SavedSublist::paintUserpic(
Painter &p,
Ui::PeerUserpicView &view,
const Dialogs::Ui::PaintContext &context) const {
_history->paintUserpic(p, view, context);
}
void SavedSublist::chatListPreloadData() {
peer()->loadUserpic();
allowChatListMessageResolve();
}
void SavedSublist::allowChatListMessageResolve() {
if (_flags & Flag::ResolveChatListMessage) {
return;
}
_flags |= Flag::ResolveChatListMessage;
resolveChatListMessageGroup();
}
bool SavedSublist::hasOrphanMediaGroupPart() const {
if (isFullLoaded() || _items.size() != 1) {
return false;
}
return (_items.front()->groupId() != MessageGroupId());
}
void SavedSublist::resolveChatListMessageGroup() {
const auto item = chatListMessage();
if (!(_flags & Flag::ResolveChatListMessage)
|| !item
|| !hasOrphanMediaGroupPart()) {
return;
}
// If we set a single album part, request the full album.
const auto withImages = !item->toPreview({
.hideSender = true,
.hideCaption = true }).images.empty();
if (withImages) {
owner().histories().requestGroupAround(item);
}
}
} // namespace Data

View File

@@ -0,0 +1,85 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/ui/dialogs_message_view.h"
#include "dialogs/dialogs_entry.h"
class PeerData;
class History;
namespace Data {
class Session;
class SavedSublist final : public Dialogs::Entry {
public:
explicit SavedSublist(not_null<PeerData*> peer);
~SavedSublist();
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] bool isHiddenAuthor() const;
[[nodiscard]] bool isFullLoaded() const;
[[nodiscard]] auto messages() const
-> const std::vector<not_null<HistoryItem*>> &;
void applyMaybeLast(not_null<HistoryItem*> item, bool added = false);
void removeOne(not_null<HistoryItem*> item);
void append(std::vector<not_null<HistoryItem*>> &&items, int fullCount);
void setFullLoaded(bool loaded = true);
[[nodiscard]] rpl::producer<> changes() const;
[[nodiscard]] std::optional<int> fullCount() const;
[[nodiscard]] rpl::producer<int> fullCountValue() const;
[[nodiscard]] Dialogs::Ui::MessageView &lastItemDialogsView() {
return _lastItemDialogsView;
}
int fixedOnTopIndex() const override;
bool shouldBeInChatList() const override;
Dialogs::UnreadState chatListUnreadState() const override;
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
const QString &chatListName() const override;
const QString &chatListNameSortKey() const override;
int chatListNameVersion() const override;
const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override;
void chatListPreloadData() override;
void paintUserpic(
Painter &p,
Ui::PeerUserpicView &view,
const Dialogs::Ui::PaintContext &context) const override;
private:
enum class Flag : uchar {
ResolveChatListMessage = (1 << 0),
FullLoaded = (1 << 1),
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags<Flag>;
bool hasOrphanMediaGroupPart() const;
void allowChatListMessageResolve();
void resolveChatListMessageGroup();
const not_null<History*> _history;
std::vector<not_null<HistoryItem*>> _items;
std::optional<int> _fullCount;
rpl::event_stream<> _changed;
Dialogs::Ui::MessageView _lastItemDialogsView;
Flags _flags;
};
} // namespace Data

View File

@@ -68,6 +68,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
data.vpeer_id(),
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
MTP_long(data.vvia_bot_id().value_or_empty()),
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
@@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage(
update.vid(),
peerToMTP(local->from()->id),
peerToMTP(history->peer->id),
MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(),
MTPlong(), // via_bot_id
replyHeader,

View File

@@ -97,6 +97,7 @@ std::optional<SearchRequest> PrepareSearchRequest(
peer->input,
MTP_string(query),
MTP_inputPeerEmpty(),
MTPInputPeer(), // saved_peer_id
MTP_int(topicRootId),
filter,
MTP_int(0), // min_date

View File

@@ -60,6 +60,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_emoji_statuses.h"
#include "data/data_forum_icons.h"
#include "data/data_cloud_themes.h"
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_stories.h"
#include "data/data_streaming.h"
#include "data/data_media_rotation.h"
@@ -261,7 +263,8 @@ Session::Session(not_null<Main::Session*> session)
, _forumIcons(std::make_unique<ForumIcons>(this))
, _notifySettings(std::make_unique<NotifySettings>(this))
, _customEmojiManager(std::make_unique<CustomEmojiManager>(this))
, _stories(std::make_unique<Stories>(this)) {
, _stories(std::make_unique<Stories>(this))
, _savedMessages(std::make_unique<SavedMessages>(this)) {
_cache->open(_session->local().cacheKey());
_bigFileCache->open(_session->local().cacheBigFileKey());
@@ -845,6 +848,7 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
const auto wasCallNotEmpty = Data::ChannelHasActiveCall(channel);
channel->updateLevelHint(data.vlevel().value_or_empty());
if (const auto count = data.vparticipants_count()) {
channel->setMembersCount(count->v);
}
@@ -854,6 +858,11 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
channel->setDefaultRestrictions(ChatRestrictions());
}
if (const auto &status = data.vemoji_status()) {
channel->setEmojiStatus(*status);
} else {
channel->setEmojiStatus(0);
}
if (minimal) {
if (channel->input.type() == mtpc_inputPeerEmpty
|| channel->inputChannel.type() == mtpc_inputChannelEmpty) {
@@ -1706,6 +1715,11 @@ void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
topic->updateChatListEntry();
}
}
if (const auto sublist = item->savedSublist()) {
if (sublist->lastItemDialogsView().dependsOn(item)) {
sublist->updateChatListEntry();
}
}
}
rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
@@ -2093,13 +2107,17 @@ void Session::applyDialog(
setPinnedFromEntryList(folder, data.is_pinned());
}
bool Session::pinnedCanPin(not_null<Data::Thread*> thread) const {
if (const auto topic = thread->asTopic()) {
bool Session::pinnedCanPin(not_null<Dialogs::Entry*> entry) const {
if (const auto sublist = entry->asSublist()) {
const auto saved = &savedMessages();
return pinnedChatsOrder(saved).size() < pinnedChatsLimit(saved);
} else if (const auto topic = entry->asTopic()) {
const auto forum = topic->forum();
return pinnedChatsOrder(forum).size() < pinnedChatsLimit(forum);
} else {
const auto folder = entry->folder();
return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder);
}
const auto folder = thread->folder();
return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder);
}
bool Session::pinnedCanPin(
@@ -2131,6 +2149,11 @@ int Session::pinnedChatsLimit(not_null<Data::Forum*> forum) const {
return limits.topicsPinnedCurrent();
}
int Session::pinnedChatsLimit(not_null<Data::SavedMessages*> saved) const {
const auto limits = Data::PremiumLimits(_session);
return limits.savedSublistsPinnedCurrent();
}
rpl::producer<int> Session::maxPinnedChatsLimitValue(
Data::Folder *folder) const {
// Premium limit from appconfig.
@@ -2171,6 +2194,20 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
});
}
rpl::producer<int> Session::maxPinnedChatsLimitValue(
not_null<SavedMessages*> saved) const {
// Premium limit from appconfig.
// We always use premium limit in the MainList limit producer,
// because it slices the list to that limit. We don't want to slice
// premium-ly added chats from the pinned list because of sync issues.
return rpl::single(rpl::empty_value()) | rpl::then(
_session->account().appConfig().refreshed()
) | rpl::map([=] {
const auto limits = Data::PremiumLimits(_session);
return limits.savedSublistsPinnedPremium();
});
}
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
Data::Folder *folder) const {
return chatsList(folder)->pinned()->order();
@@ -2186,6 +2223,11 @@ const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
return forum->topicsList()->pinned()->order();
}
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
not_null<Data::SavedMessages*> saved) const {
return saved->chatsList()->pinned()->order();
}
void Session::clearPinnedChats(Data::Folder *folder) {
chatsList(folder)->pinned()->clear();
}
@@ -2202,7 +2244,7 @@ void Session::reorderTwoPinnedChats(
? topic->forum()->topicsList()
: filterId
? chatsFilters().chatsList(filterId)
: chatsList(key1.entry()->folder());
: chatsListFor(key1.entry());
list->pinned()->reorder(key1, key2);
notifyPinnedDialogsOrderUpdated();
}
@@ -3416,7 +3458,7 @@ void Session::webpageApplyFields(
data.vid().v,
};
if (const auto embed = data.vstory()) {
story = stories().applyFromWebpage(
story = stories().applySingle(
peerFromMTP(data.vpeer()),
*embed);
} else if (const auto maybe = stories().lookup(storyId)) {
@@ -4192,6 +4234,8 @@ not_null<Dialogs::MainList*> Session::chatsListFor(
const auto topic = entry->asTopic();
return topic
? topic->forum()->topicsList()
: entry->asSublist()
? _savedMessages->chatsList()
: chatsList(entry->folder());
}
@@ -4388,6 +4432,7 @@ void Session::insertCheckedServiceNotification(
MTP_int(0), // Not used (would've been trimmed to 32 bits).
peerToMTP(PeerData::kServiceNotificationsId),
peerToMTP(PeerData::kServiceNotificationsId),
MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(),
MTPlong(), // via_bot_id
MTPMessageReplyHeader(),

View File

@@ -61,6 +61,7 @@ class GroupCall;
class NotifySettings;
class CustomEmojiManager;
class Stories;
class SavedMessages;
struct RepliesReadTillUpdate {
FullMsgId id;
@@ -137,6 +138,9 @@ public:
[[nodiscard]] Stories &stories() const {
return *_stories;
}
[[nodiscard]] SavedMessages &savedMessages() const {
return *_savedMessages;
}
[[nodiscard]] MsgId nextNonHistoryEntryId() {
return ++_nonHistoryEntryId;
@@ -345,25 +349,31 @@ public:
const QVector<MTPDialog> &dialogs,
std::optional<int> count = std::nullopt);
[[nodiscard]] bool pinnedCanPin(not_null<Thread*> thread) const;
[[nodiscard]] bool pinnedCanPin(not_null<Dialogs::Entry*> entry) const;
[[nodiscard]] bool pinnedCanPin(
FilterId filterId,
not_null<History*> history) const;
[[nodiscard]] int pinnedChatsLimit(Folder *folder) const;
[[nodiscard]] int pinnedChatsLimit(FilterId filterId) const;
[[nodiscard]] int pinnedChatsLimit(not_null<Forum*> forum) const;
[[nodiscard]] int pinnedChatsLimit(
not_null<SavedMessages*> saved) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
Folder *folder) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
FilterId filterId) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
not_null<Forum*> forum) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
not_null<SavedMessages*> saved) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
Folder *folder) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
not_null<Forum*> forum) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
FilterId filterId) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
not_null<Data::SavedMessages*> saved) const;
void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned);
void setPinnedFromEntryList(Dialogs::Key key, bool pinned);
void clearPinnedChats(Folder *folder);
@@ -1041,6 +1051,7 @@ private:
const std::unique_ptr<NotifySettings> _notifySettings;
const std::unique_ptr<CustomEmojiManager> _customEmojiManager;
const std::unique_ptr<Stories> _stories;
const std::unique_ptr<SavedMessages> _savedMessages;
MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange;

View File

@@ -148,11 +148,7 @@ struct RecentPostId final {
};
struct PublicForwardsSlice final {
struct OffsetToken final {
int rate = 0;
FullMsgId fullId;
QString storyOffset;
};
using OffsetToken = QString;
QVector<RecentPostId> list;
int total = 0;
bool allLoaded = false;

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