Compare commits
242 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cdd115317 | ||
|
|
bdd1d2484c | ||
|
|
d5a416d5ea | ||
|
|
46d393ea0f | ||
|
|
68e351b7c8 | ||
|
|
6f9c911faa | ||
|
|
dd381d9b56 | ||
|
|
8eedc7b2ba | ||
|
|
9ba1af2eb9 | ||
|
|
f567328a60 | ||
|
|
953fa52490 | ||
|
|
b1477260f0 | ||
|
|
038de9ef15 | ||
|
|
4701c5d6e3 | ||
|
|
8420b7dc17 | ||
|
|
9dacf69d41 | ||
|
|
a91efd9164 | ||
|
|
a631a28092 | ||
|
|
7bcb1fc8b2 | ||
|
|
cdafd8f171 | ||
|
|
116aa01e51 | ||
|
|
96b40f43e9 | ||
|
|
a93ec9c2c2 | ||
|
|
3ee3919d50 | ||
|
|
b4410c49b9 | ||
|
|
82bf6ca94f | ||
|
|
785ebfee34 | ||
|
|
a60f8d75a0 | ||
|
|
5976a7ed19 | ||
|
|
df7026b59c | ||
|
|
edfd9bedc1 | ||
|
|
f52c6a6daa | ||
|
|
af10b6d487 | ||
|
|
e30eacff41 | ||
|
|
18154e403a | ||
|
|
6975b04e6b | ||
|
|
948302cf02 | ||
|
|
e4cff8cb4b | ||
|
|
5bd17ae1b2 | ||
|
|
22213a71c1 | ||
|
|
e926e5f882 | ||
|
|
221d45b500 | ||
|
|
6bb7e2c2eb | ||
|
|
2a86ce596d | ||
|
|
f936e484cc | ||
|
|
b2a1c10036 | ||
|
|
2a58d01927 | ||
|
|
7cd6b821b3 | ||
|
|
de108c8efe | ||
|
|
e7104b5ebe | ||
|
|
2d17bd02a3 | ||
|
|
2a3115f461 | ||
|
|
8d62800e77 | ||
|
|
7e04bf9533 | ||
|
|
2bd3a8aaff | ||
|
|
70f92a7817 | ||
|
|
8e08f69508 | ||
|
|
abe62475cb | ||
|
|
1cdb83462e | ||
|
|
d9a29b6f15 | ||
|
|
1504f92a64 | ||
|
|
36e5056b59 | ||
|
|
c5c707f0fd | ||
|
|
832dd8d50c | ||
|
|
7d2b20e624 | ||
|
|
049945a9b9 | ||
|
|
808c9e3d2c | ||
|
|
fde7cef9c8 | ||
|
|
2791f89f30 | ||
|
|
858b5831e8 | ||
|
|
9166423598 | ||
|
|
184d984336 | ||
|
|
0b5044f064 | ||
|
|
274b66f74b | ||
|
|
e05343d721 | ||
|
|
bc316a2536 | ||
|
|
a6904be81d | ||
|
|
690a7d1608 | ||
|
|
a3e54fcd7c | ||
|
|
23c67bb2a2 | ||
|
|
75367f0488 | ||
|
|
216ffad80e | ||
|
|
c312607ff8 | ||
|
|
812d616f66 | ||
|
|
183408cb2d | ||
|
|
1a7d5b7c95 | ||
|
|
17465e1082 | ||
|
|
a996b14291 | ||
|
|
2045252cfd | ||
|
|
a2e674bdb6 | ||
|
|
cc4055a5e3 | ||
|
|
d1b6cf1fae | ||
|
|
671a06c407 | ||
|
|
3ce315111f | ||
|
|
09768ce28a | ||
|
|
c9affe0da5 | ||
|
|
4909ba5a1e | ||
|
|
e322733e20 | ||
|
|
dc7f440902 | ||
|
|
4849376347 | ||
|
|
8eca57f419 | ||
|
|
0adcd37030 | ||
|
|
2bdb9af146 | ||
|
|
5b6bddd7fc | ||
|
|
e1ea833ad6 | ||
|
|
85c21ba0e4 | ||
|
|
4d72d20398 | ||
|
|
9d3d16a725 | ||
|
|
99deaf6005 | ||
|
|
d8921c7cf5 | ||
|
|
f7fa36ca1d | ||
|
|
45f8e68203 | ||
|
|
2b47d6d63f | ||
|
|
aaefeed3f1 | ||
|
|
7f00065bd8 | ||
|
|
6f031a715e | ||
|
|
6981ae605a | ||
|
|
d91c21fb26 | ||
|
|
c95f052e60 | ||
|
|
c33be27b3c | ||
|
|
6be9b25e99 | ||
|
|
0bb391937e | ||
|
|
75ff7a6637 | ||
|
|
aece7c1096 | ||
|
|
d2e6e7adf2 | ||
|
|
b930bc0e6d | ||
|
|
93d99d6173 | ||
|
|
a8df3dcf91 | ||
|
|
b22e2ffe1d | ||
|
|
22d23c8be1 | ||
|
|
b335741f99 | ||
|
|
1261c775d4 | ||
|
|
01b4a24ac7 | ||
|
|
4124c2eb57 | ||
|
|
f09b91ebb5 | ||
|
|
57b147e0c8 | ||
|
|
551ea7d879 | ||
|
|
d3c9bb0bc6 | ||
|
|
c711b1f1df | ||
|
|
af7ea90246 | ||
|
|
348cf4829c | ||
|
|
4753a57091 | ||
|
|
1a93f4fa4c | ||
|
|
118fd187e3 | ||
|
|
baa47bde7f | ||
|
|
18b48df9ce | ||
|
|
e71fc60d22 | ||
|
|
148af59615 | ||
|
|
5b2db4112f | ||
|
|
7cedc1f7a5 | ||
|
|
6cea7d4a52 | ||
|
|
bd93aed393 | ||
|
|
348666de6d | ||
|
|
47e32bebe4 | ||
|
|
0b21c04489 | ||
|
|
85f013ebdb | ||
|
|
832cc6ac69 | ||
|
|
30ce049f51 | ||
|
|
d42fb6d1b9 | ||
|
|
cade53aa0a | ||
|
|
2fdcda7536 | ||
|
|
7e6439e4f8 | ||
|
|
f07ee7f590 | ||
|
|
02db4e01fa | ||
|
|
8d75078a42 | ||
|
|
0e25ef7524 | ||
|
|
8608d8aa4d | ||
|
|
c1a7332a5e | ||
|
|
c3fb392906 | ||
|
|
a59bfdb2f8 | ||
|
|
79f96480c2 | ||
|
|
60cbd96d91 | ||
|
|
ee0400f1ac | ||
|
|
b8a3746558 | ||
|
|
14f25fc997 | ||
|
|
27da6ee9eb | ||
|
|
48d482006a | ||
|
|
9afee2620a | ||
|
|
baca3047d4 | ||
|
|
43a5265e0c | ||
|
|
5519bb3523 | ||
|
|
ff213d1386 | ||
|
|
55b3f99653 | ||
|
|
8a6ff3f414 | ||
|
|
feb8624d05 | ||
|
|
a2c33545d4 | ||
|
|
7decf68122 | ||
|
|
6b62ec97c6 | ||
|
|
ea3dab4a06 | ||
|
|
5c8f08fc92 | ||
|
|
00a0b2c8b6 | ||
|
|
007218cc13 | ||
|
|
8afe495a4f | ||
|
|
257f2086d1 | ||
|
|
f011c84ce8 | ||
|
|
8b839f46b2 | ||
|
|
c1067d8fe1 | ||
|
|
bb76818cc8 | ||
|
|
5dcc219f1c | ||
|
|
4ff9e90153 | ||
|
|
28fe98af80 | ||
|
|
5eba65aaa0 | ||
|
|
d1e3e7d240 | ||
|
|
90ff8ecd0f | ||
|
|
468e75a572 | ||
|
|
ae3e5487d7 | ||
|
|
03147a5426 | ||
|
|
14a2b10989 | ||
|
|
b29f8aa1e6 | ||
|
|
f9bb932cd8 | ||
|
|
a38cbbf7e8 | ||
|
|
d5bb1717e0 | ||
|
|
520ff8f2ce | ||
|
|
b3848f6a84 | ||
|
|
518f387e0c | ||
|
|
635f76a312 | ||
|
|
ff14ac68ee | ||
|
|
948c5d50cb | ||
|
|
6b520ecc05 | ||
|
|
bb474686eb | ||
|
|
659ddae9a8 | ||
|
|
b70276912e | ||
|
|
858c575782 | ||
|
|
62fe14d592 | ||
|
|
6ad037e556 | ||
|
|
a55b41faa1 | ||
|
|
a26d769304 | ||
|
|
e1120d1cb5 | ||
|
|
8897f9e46a | ||
|
|
7a588be54f | ||
|
|
1cb1f1cbc1 | ||
|
|
5827d6ffdb | ||
|
|
766bc90921 | ||
|
|
eb228eb744 | ||
|
|
7c02d67665 | ||
|
|
23c54896e5 | ||
|
|
460baa54d8 | ||
|
|
6c56fad180 | ||
|
|
3fd772ce17 | ||
|
|
8834ec8bf2 | ||
|
|
003fb52fb9 | ||
|
|
ec234cdc43 |
3
.github/workflows/linux.yml
vendored
@@ -93,6 +93,9 @@ jobs:
|
||||
DEFINE=""
|
||||
if [ -n "${{ matrix.defines }}" ]; then
|
||||
DEFINE="-D ${{ matrix.defines }}=ON"
|
||||
if [ "${{ matrix.defines }}" == "DESKTOP_APP_DISABLE_DBUS_INTEGRATION" ]; then
|
||||
DEFINE="$DEFINE -D DESKTOP_APP_DISABLE_GTK_INTEGRATION=ON"
|
||||
fi
|
||||
echo Define from matrix: $DEFINE
|
||||
echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV
|
||||
else
|
||||
|
||||
6
.gitmodules
vendored
@@ -76,9 +76,6 @@
|
||||
[submodule "Telegram/ThirdParty/hime"]
|
||||
path = Telegram/ThirdParty/hime
|
||||
url = https://github.com/hime-ime/hime.git
|
||||
[submodule "Telegram/ThirdParty/qt5ct"]
|
||||
path = Telegram/ThirdParty/qt5ct
|
||||
url = https://github.com/desktop-app/qt5ct.git
|
||||
[submodule "Telegram/ThirdParty/fcitx5-qt"]
|
||||
path = Telegram/ThirdParty/fcitx5-qt
|
||||
url = https://github.com/fcitx/fcitx5-qt.git
|
||||
@@ -91,9 +88,6 @@
|
||||
[submodule "Telegram/lib_webview"]
|
||||
path = Telegram/lib_webview
|
||||
url = https://github.com/desktop-app/lib_webview.git
|
||||
[submodule "Telegram/ThirdParty/mallocng"]
|
||||
path = Telegram/ThirdParty/mallocng
|
||||
url = https://github.com/desktop-app/mallocng.git
|
||||
[submodule "Telegram/lib_waylandshells"]
|
||||
path = Telegram/lib_waylandshells
|
||||
url = https://github.com/desktop-app/lib_waylandshells.git
|
||||
|
||||
@@ -31,6 +31,7 @@ include(cmake/target_link_static_libraries.cmake)
|
||||
include(cmake/target_link_frameworks.cmake)
|
||||
include(cmake/init_target.cmake)
|
||||
include(cmake/generate_target.cmake)
|
||||
include(cmake/nuget.cmake)
|
||||
|
||||
include(cmake/options.cmake)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ Version **1.8.15** was the last that supports older systems
|
||||
* xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE))
|
||||
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))
|
||||
* CMake ([New BSD License](https://github.com/Kitware/CMake/blob/master/Copyright.txt))
|
||||
* Hunspell ([GPL](https://github.com/hunspell/hunspell/blob/master/COPYING))
|
||||
* Hunspell ([LGPL](https://github.com/hunspell/hunspell/blob/master/COPYING.LESSER))
|
||||
|
||||
## Build instructions
|
||||
|
||||
|
||||
@@ -82,17 +82,11 @@ PRIVATE
|
||||
desktop-app::external_xxhash
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::lib_webview_winrt
|
||||
)
|
||||
elseif (LINUX)
|
||||
if (LINUX)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_glibmm
|
||||
desktop-app::external_glib
|
||||
desktop-app::external_mallocng
|
||||
)
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
@@ -119,6 +113,7 @@ elseif (LINUX)
|
||||
endif()
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION)
|
||||
target_link_libraries(Telegram PRIVATE rt)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED AND NOT DESKTOP_APP_USE_PACKAGED_LAZY)
|
||||
@@ -130,7 +125,7 @@ elseif (LINUX)
|
||||
target_link_libraries(Telegram PRIVATE PkgConfig::X11)
|
||||
endif()
|
||||
else()
|
||||
pkg_search_module(GTK REQUIRED gtk+-3.0 gtk+-2.0)
|
||||
pkg_check_modules(GTK REQUIRED gtk+-3.0)
|
||||
target_include_directories(Telegram PRIVATE ${GTK_INCLUDE_DIRS})
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
|
||||
@@ -256,8 +251,6 @@ PRIVATE
|
||||
boxes/peer_lists_box.h
|
||||
boxes/passcode_box.cpp
|
||||
boxes/passcode_box.h
|
||||
boxes/photo_crop_box.cpp
|
||||
boxes/photo_crop_box.h
|
||||
boxes/rate_call_box.cpp
|
||||
boxes/rate_call_box.h
|
||||
boxes/self_destruction_box.cpp
|
||||
@@ -400,6 +393,7 @@ PRIVATE
|
||||
data/stickers/data_stickers_set.h
|
||||
data/stickers/data_stickers.cpp
|
||||
data/stickers/data_stickers.h
|
||||
data/data_abstract_sparse_ids.h
|
||||
data/data_abstract_structure.cpp
|
||||
data/data_abstract_structure.h
|
||||
data/data_auto_download.cpp
|
||||
@@ -428,6 +422,8 @@ PRIVATE
|
||||
data/data_drafts.h
|
||||
data/data_folder.cpp
|
||||
data/data_folder.h
|
||||
data/data_file_click_handler.cpp
|
||||
data/data_file_click_handler.h
|
||||
data/data_file_origin.cpp
|
||||
data/data_file_origin.h
|
||||
data/data_flags.h
|
||||
@@ -510,6 +506,39 @@ PRIVATE
|
||||
dialogs/dialogs_search_from_controllers.h
|
||||
dialogs/dialogs_widget.cpp
|
||||
dialogs/dialogs_widget.h
|
||||
editor/color_picker.cpp
|
||||
editor/color_picker.h
|
||||
editor/controllers/controllers.h
|
||||
editor/controllers/stickers_panel_controller.cpp
|
||||
editor/controllers/stickers_panel_controller.h
|
||||
editor/controllers/undo_controller.cpp
|
||||
editor/controllers/undo_controller.h
|
||||
editor/editor_crop.cpp
|
||||
editor/editor_crop.h
|
||||
editor/editor_paint.cpp
|
||||
editor/editor_paint.h
|
||||
editor/photo_editor.cpp
|
||||
editor/photo_editor.h
|
||||
editor/photo_editor_common.cpp
|
||||
editor/photo_editor_common.h
|
||||
editor/photo_editor_content.cpp
|
||||
editor/photo_editor_content.h
|
||||
editor/photo_editor_controls.cpp
|
||||
editor/photo_editor_controls.h
|
||||
editor/photo_editor_layer_widget.cpp
|
||||
editor/photo_editor_layer_widget.h
|
||||
editor/scene/scene.cpp
|
||||
editor/scene/scene.h
|
||||
editor/scene/scene_item_base.cpp
|
||||
editor/scene/scene_item_base.h
|
||||
editor/scene/scene_item_canvas.cpp
|
||||
editor/scene/scene_item_canvas.h
|
||||
editor/scene/scene_item_image.cpp
|
||||
editor/scene/scene_item_image.h
|
||||
editor/scene/scene_item_line.cpp
|
||||
editor/scene/scene_item_line.h
|
||||
editor/scene/scene_item_sticker.cpp
|
||||
editor/scene/scene_item_sticker.h
|
||||
export/export_manager.cpp
|
||||
export/export_manager.h
|
||||
export/view/export_view_content.cpp
|
||||
@@ -866,8 +895,6 @@ PRIVATE
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_dummy.cpp
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
@@ -1199,8 +1226,6 @@ if (DESKTOP_APP_DISABLE_GTK_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
platform/linux/linux_gtk_open_with_dialog.cpp
|
||||
@@ -1365,6 +1390,32 @@ endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32)
|
||||
target_link_options(Telegram
|
||||
PRIVATE
|
||||
/DELAYLOAD:secur32.dll
|
||||
/DELAYLOAD:winmm.dll
|
||||
/DELAYLOAD:ws2_32.dll
|
||||
/DELAYLOAD:user32.dll
|
||||
/DELAYLOAD:gdi32.dll
|
||||
/DELAYLOAD:advapi32.dll
|
||||
/DELAYLOAD:shell32.dll
|
||||
/DELAYLOAD:ole32.dll
|
||||
/DELAYLOAD:oleaut32.dll
|
||||
/DELAYLOAD:shlwapi.dll
|
||||
/DELAYLOAD:iphlpapi.dll
|
||||
/DELAYLOAD:gdiplus.dll
|
||||
/DELAYLOAD:version.dll
|
||||
/DELAYLOAD:dwmapi.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore)
|
||||
add_executable(Updater WIN32)
|
||||
init_target(Updater)
|
||||
@@ -1381,8 +1432,26 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
|
||||
|
||||
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32 AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_link_options(Updater PRIVATE -municode)
|
||||
if (WIN32)
|
||||
get_filename_component(lib_base_loc lib_base REALPATH)
|
||||
nice_target_sources(Updater ${lib_base_loc}
|
||||
PRIVATE
|
||||
base/platform/win/base_windows_safe_library.cpp
|
||||
base/platform/win/base_windows_safe_library.h
|
||||
)
|
||||
target_include_directories(Updater PRIVATE ${lib_base_loc})
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_link_options(Updater
|
||||
PRIVATE
|
||||
/DELAYLOAD:user32.dll
|
||||
/DELAYLOAD:advapi32.dll
|
||||
/DELAYLOAD:shell32.dll
|
||||
/DELAYLOAD:ole32.dll
|
||||
/DELAYLOAD:shlwapi.dll
|
||||
)
|
||||
else()
|
||||
target_link_options(Updater PRIVATE -municode)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
|
||||
BIN
Telegram/Resources/icons/calls/video_tooltip.png
Normal file
|
After Width: | Height: | Size: 360 B |
BIN
Telegram/Resources/icons/calls/video_tooltip@2x.png
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
Telegram/Resources/icons/calls/video_tooltip@3x.png
Normal file
|
After Width: | Height: | Size: 967 B |
BIN
Telegram/Resources/icons/photo_editor/flip.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
Telegram/Resources/icons/photo_editor/flip@2x.png
Normal file
|
After Width: | Height: | Size: 762 B |
BIN
Telegram/Resources/icons/photo_editor/flip@3x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/photo_editor/paint.png
Normal file
|
After Width: | Height: | Size: 947 B |
BIN
Telegram/Resources/icons/photo_editor/paint@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/photo_editor/paint@3x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Telegram/Resources/icons/photo_editor/rotate.png
Normal file
|
After Width: | Height: | Size: 971 B |
BIN
Telegram/Resources/icons/photo_editor/rotate@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/photo_editor/rotate@3x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Telegram/Resources/icons/photo_editor/undo.png
Normal file
|
After Width: | Height: | Size: 554 B |
BIN
Telegram/Resources/icons/photo_editor/undo@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/photo_editor/undo@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/send_media/send_media_delete.png
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
Telegram/Resources/icons/send_media/send_media_delete@2x.png
Normal file
|
After Width: | Height: | Size: 463 B |
BIN
Telegram/Resources/icons/send_media/send_media_delete@3x.png
Normal file
|
After Width: | Height: | Size: 689 B |
BIN
Telegram/Resources/icons/send_media/send_media_replace.png
Normal file
|
After Width: | Height: | Size: 452 B |
BIN
Telegram/Resources/icons/send_media/send_media_replace@2x.png
Normal file
|
After Width: | Height: | Size: 769 B |
BIN
Telegram/Resources/icons/send_media/send_media_replace@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -184,6 +184,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_edit_media_album_error" = "This file cannot be saved as a part of an album.";
|
||||
"lng_edit_media_invalid_file" = "Sorry, no way to use this file.";
|
||||
"lng_edit_photo_editor_hint" = "Left-click on the photo to edit.";
|
||||
"lng_edit_caption_attach" = "Sorry, you can't attach a new media while you're editing your message.";
|
||||
"lng_edit_caption_voice" = "Sorry, you can't edit your message while you're having an unsent voice message.";
|
||||
|
||||
@@ -452,6 +453,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_performance" = "Performance";
|
||||
"lng_settings_enable_animations" = "Enable animations";
|
||||
"lng_settings_enable_opengl" = "Enable OpenGL rendering for media";
|
||||
"lng_settings_angle_backend" = "ANGLE graphics backend";
|
||||
"lng_settings_angle_backend_auto" = "Auto";
|
||||
"lng_settings_angle_backend_d3d9" = "Direct3D 9";
|
||||
"lng_settings_angle_backend_d3d11" = "Direct3D 11";
|
||||
"lng_settings_angle_backend_d3d11on12" = "D3D11on12";
|
||||
"lng_settings_angle_backend_opengl" = "OpenGL";
|
||||
"lng_settings_angle_backend_disabled" = "Disabled";
|
||||
"lng_settings_sensitive_title" = "Sensitive content";
|
||||
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
|
||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||
@@ -1007,6 +1015,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_report_group_title" = "Report group";
|
||||
"lng_report_bot_title" = "Report bot";
|
||||
"lng_report_message_title" = "Report message";
|
||||
"lng_report_please_select_messages" = "Please select messages to report.";
|
||||
"lng_report_select_messages" = "Select messages";
|
||||
"lng_report_messages_none" = "Select Messages";
|
||||
"lng_report_messages_count#one" = "Report {count} Message";
|
||||
@@ -1371,6 +1380,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_switch_stickers" = "Stickers";
|
||||
"lng_switch_emoji" = "Emoji";
|
||||
"lng_switch_gifs" = "GIFs";
|
||||
"lng_switch_masks" = "Masks";
|
||||
"lng_stickers_featured_add" = "Add";
|
||||
"lng_gifs_search" = "Search GIFs";
|
||||
"lng_gifs_no_saved" = "You have no saved GIFs yet.";
|
||||
@@ -1381,11 +1391,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_box_remove" = "Remove";
|
||||
|
||||
"lng_stickers_installed_tab" = "Stickers";
|
||||
"lng_stickers_masks_tab" = "Masks";
|
||||
"lng_stickers_featured_tab" = "Trending";
|
||||
"lng_stickers_archived_tab" = "Archived";
|
||||
"lng_stickers_remove_pack" = "Remove «{sticker_pack}»?";
|
||||
"lng_stickers_add_pack" = "Add stickers";
|
||||
"lng_stickers_add_masks" = "Add masks";
|
||||
"lng_stickers_share_pack" = "Share Stickers";
|
||||
"lng_stickers_share_masks" = "Share Masks";
|
||||
"lng_stickers_not_found" = "Sticker pack not found.";
|
||||
"lng_stickers_packs_archived" = "Some of your unused stickers have been archived to make room for the sets you've activated.";
|
||||
"lng_stickers_copied" = "Sticker pack link copied to clipboard.";
|
||||
@@ -1394,7 +1407,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_stickers_return" = "Undo";
|
||||
"lng_stickers_count#one" = "{count} sticker";
|
||||
"lng_stickers_count#other" = "{count} stickers";
|
||||
"lng_stickers_masks_pack" = "This is a pack of mask stickers. You can use them in the photo editor on our mobile apps.";
|
||||
"lng_masks_count#one" = "{count} mask";
|
||||
"lng_masks_count#other" = "{count} masks";
|
||||
"lng_stickers_attached_sets" = "Sets of attached stickers";
|
||||
"lng_stickers_group_set" = "Group sticker set";
|
||||
"lng_stickers_remove_group_set" = "Remove group sticker set?";
|
||||
@@ -1405,6 +1419,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_stickers_remove_pack_confirm" = "Remove";
|
||||
"lng_stickers_archive_pack" = "Archive Stickers";
|
||||
"lng_stickers_has_been_archived" = "Sticker pack has been archived.";
|
||||
"lng_masks_archive_pack" = "Archive Masks";
|
||||
"lng_masks_has_been_archived" = "Mask pack has been archived.";
|
||||
"lng_masks_installed" = "Mask pack has been installed.";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
"lng_in_dlg_album" = "Album";
|
||||
@@ -2030,6 +2047,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_chat_no_camera" = "You can't turn on video in this chat.";
|
||||
"lng_group_call_chat_no_screen" = "You can't share your screen in this chat.";
|
||||
"lng_group_call_failed_screen" = "An error occured. Screencast has stopped.";
|
||||
"lng_group_call_failed_camera" = "Could not enable camera. Perhaps another app is using the camera already. Try closing other apps.";
|
||||
"lng_group_call_tooltip_screen" = "Share screen";
|
||||
"lng_group_call_tooltip_camera" = "Your camera is off. Click here to enable camera.";
|
||||
"lng_group_call_tooltip_microphone" = "You are on mute. Click here to speak.";
|
||||
@@ -2758,6 +2776,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
|
||||
"lng_filters_remove_yes" = "Remove";
|
||||
|
||||
"lng_photo_editor_menu_delete" = "Delete";
|
||||
"lng_photo_editor_menu_flip" = "Flip";
|
||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
||||
@@ -579,8 +579,8 @@ keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = Keybo
|
||||
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
|
||||
|
||||
replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
|
||||
replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup;
|
||||
replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> = ReplyMarkup;
|
||||
replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup;
|
||||
replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> placeholder:flags.3?string = ReplyMarkup;
|
||||
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
|
||||
|
||||
messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity;
|
||||
@@ -1250,6 +1250,16 @@ groupCallParticipantVideoSourceGroup#dcb118b7 semantics:string sources:Vector<in
|
||||
|
||||
groupCallParticipantVideo#78e41663 flags:# paused:flags.0?true endpoint:string source_groups:Vector<GroupCallParticipantVideoSourceGroup> = GroupCallParticipantVideo;
|
||||
|
||||
stickers.suggestedShortName#85fea03f short_name:string = stickers.SuggestedShortName;
|
||||
|
||||
botCommandScopeDefault#2f6cb2ab = BotCommandScope;
|
||||
botCommandScopeUsers#3c4f04d8 = BotCommandScope;
|
||||
botCommandScopeChats#6fe1a881 = BotCommandScope;
|
||||
botCommandScopeChatAdmins#b9aa606a = BotCommandScope;
|
||||
botCommandScopePeer#db9d897d peer:InputPeer = BotCommandScope;
|
||||
botCommandScopePeerAdmins#3fd863d1 peer:InputPeer = BotCommandScope;
|
||||
botCommandScopePeerUser#a1321f3 peer:InputPeer user_id:InputUser = BotCommandScope;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -1596,7 +1606,9 @@ channels.convertToGigagroup#b290c69 channel:InputChannel = Updates;
|
||||
|
||||
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
|
||||
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
|
||||
bots.setBotCommands#805d46f6 commands:Vector<BotCommand> = Bool;
|
||||
bots.setBotCommands#517165a scope:BotCommandScope lang_code:string commands:Vector<BotCommand> = Bool;
|
||||
bots.resetBotCommands#3d8de0f9 scope:BotCommandScope lang_code:string = Bool;
|
||||
bots.getBotCommands#e34c0dd6 scope:BotCommandScope lang_code:string = Vector<BotCommand>;
|
||||
|
||||
payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||
@@ -1606,11 +1618,13 @@ payments.getSavedInfo#227d824b = payments.SavedInfo;
|
||||
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
|
||||
payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
|
||||
|
||||
stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> = messages.StickerSet;
|
||||
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
|
||||
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
|
||||
stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
|
||||
stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
|
||||
stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet;
|
||||
stickers.checkShortName#284b3639 short_name:string = Bool;
|
||||
stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName;
|
||||
|
||||
phone.getCallConfig#55451fa9 = DataJSON;
|
||||
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||
@@ -1656,4 +1670,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 129
|
||||
// LAYER 130
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.7.9.0" />
|
||||
Version="2.8.6.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,7,9,0
|
||||
PRODUCTVERSION 2,7,9,0
|
||||
FILEVERSION 2,8,6,0
|
||||
PRODUCTVERSION 2,8,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.7.9.0"
|
||||
VALUE "FileVersion", "2.8.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.9.0"
|
||||
VALUE "ProductVersion", "2.8.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,7,9,0
|
||||
PRODUCTVERSION 2,7,9,0
|
||||
FILEVERSION 2,8,6,0
|
||||
PRODUCTVERSION 2,8,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.7.9.0"
|
||||
VALUE "FileVersion", "2.8.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.7.9.0"
|
||||
VALUE "ProductVersion", "2.8.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "updater.h"
|
||||
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
|
||||
bool _debug = false;
|
||||
|
||||
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
|
||||
@@ -329,6 +331,8 @@ void updateRegistry() {
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) {
|
||||
base::Platform::InitDynamicLibraries();
|
||||
|
||||
openLog();
|
||||
|
||||
_oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter);
|
||||
|
||||
@@ -52,6 +52,10 @@ mtpRequestId EditMessage(
|
||||
ConvertOption::SkipLocal);
|
||||
const auto media = item->media();
|
||||
|
||||
const auto updateRecentStickers = inputMedia.has_value()
|
||||
? Api::HasAttachedStickers(*inputMedia)
|
||||
: false;
|
||||
|
||||
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
|
||||
const auto flags = emptyFlag
|
||||
| (!text.isEmpty() || media
|
||||
@@ -97,6 +101,10 @@ mtpRequestId EditMessage(
|
||||
} else {
|
||||
apply();
|
||||
}
|
||||
|
||||
if (updateRecentStickers) {
|
||||
api->requestRecentStickersForce(true);
|
||||
}
|
||||
}).fail(
|
||||
fail
|
||||
).send();
|
||||
@@ -165,22 +173,30 @@ void EditMessageWithUploadedDocument(
|
||||
HistoryItem *item,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
SendOptions options) {
|
||||
SendOptions options,
|
||||
std::vector<MTPInputDocument> attachedStickers) {
|
||||
if (!item || !item->media() || !item->media()->document()) {
|
||||
return;
|
||||
}
|
||||
const auto media = PrepareUploadedDocument(item, file, thumb);
|
||||
const auto media = PrepareUploadedDocument(
|
||||
item,
|
||||
file,
|
||||
thumb,
|
||||
std::move(attachedStickers));
|
||||
EditMessageWithUploadedMedia(item, options, media);
|
||||
}
|
||||
|
||||
void EditMessageWithUploadedPhoto(
|
||||
HistoryItem *item,
|
||||
const MTPInputFile &file,
|
||||
SendOptions options) {
|
||||
SendOptions options,
|
||||
std::vector<MTPInputDocument> attachedStickers) {
|
||||
if (!item || !item->media() || !item->media()->photo()) {
|
||||
return;
|
||||
}
|
||||
const auto media = PrepareUploadedPhoto(file);
|
||||
const auto media = PrepareUploadedPhoto(
|
||||
file,
|
||||
std::move(attachedStickers));
|
||||
EditMessageWithUploadedMedia(item, options, media);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,14 @@ void EditMessageWithUploadedDocument(
|
||||
HistoryItem *item,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
SendOptions options);
|
||||
SendOptions options,
|
||||
std::vector<MTPInputDocument> attachedStickers);
|
||||
|
||||
void EditMessageWithUploadedPhoto(
|
||||
HistoryItem *item,
|
||||
const MTPInputFile &file,
|
||||
SendOptions options);
|
||||
SendOptions options,
|
||||
std::vector<MTPInputDocument> attachedStickers);
|
||||
|
||||
mtpRequestId EditCaption(
|
||||
not_null<HistoryItem*> item,
|
||||
|
||||
@@ -61,10 +61,14 @@ int32 CountStickersHash(
|
||||
: 0;
|
||||
}
|
||||
|
||||
int32 CountRecentStickersHash(not_null<Main::Session*> session) {
|
||||
int32 CountRecentStickersHash(
|
||||
not_null<Main::Session*> session,
|
||||
bool attached) {
|
||||
return CountSpecialStickerSetHash(
|
||||
session,
|
||||
Data::Stickers::CloudRecentSetId);
|
||||
attached
|
||||
? Data::Stickers::CloudRecentAttachedSetId
|
||||
: Data::Stickers::CloudRecentSetId);
|
||||
}
|
||||
|
||||
int32 CountFavedStickersHash(not_null<Main::Session*> session) {
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace Api {
|
||||
not_null<Main::Session*> session,
|
||||
bool checkOutdatedInfo = false);
|
||||
[[nodiscard]] int32 CountRecentStickersHash(
|
||||
not_null<Main::Session*> session);
|
||||
not_null<Main::Session*> session,
|
||||
bool attached = false);
|
||||
[[nodiscard]] int32 CountFavedStickersHash(not_null<Main::Session*> session);
|
||||
[[nodiscard]] int32 CountFeaturedStickersHash(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
@@ -73,18 +73,24 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
|
||||
} // namespace
|
||||
|
||||
MTPInputMedia PrepareUploadedPhoto(const MTPInputFile &file) {
|
||||
MTPInputMedia PrepareUploadedPhoto(
|
||||
const MTPInputFile &file,
|
||||
std::vector<MTPInputDocument> attachedStickers) {
|
||||
const auto flags = attachedStickers.empty()
|
||||
? MTPDinputMediaUploadedPhoto::Flags(0)
|
||||
: MTPDinputMediaUploadedPhoto::Flag::f_stickers;
|
||||
return MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(0),
|
||||
MTP_flags(flags),
|
||||
file,
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_vector<MTPInputDocument>(ranges::to<QVector>(attachedStickers)),
|
||||
MTP_int(0));
|
||||
}
|
||||
|
||||
MTPInputMedia PrepareUploadedDocument(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb) {
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
std::vector<MTPInputDocument> attachedStickers) {
|
||||
if (!item || !item->media() || !item->media()->document()) {
|
||||
return MTP_inputMediaEmpty();
|
||||
}
|
||||
@@ -92,7 +98,8 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
using DocFlags = MTPDinputMediaUploadedDocument::Flag;
|
||||
const auto flags = emptyFlag
|
||||
| (thumb ? DocFlags::f_thumb : emptyFlag)
|
||||
| (item->groupId() ? DocFlags::f_nosound_video : emptyFlag);
|
||||
| (item->groupId() ? DocFlags::f_nosound_video : emptyFlag)
|
||||
| (attachedStickers.empty() ? DocFlags::f_stickers : emptyFlag);
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
@@ -100,8 +107,20 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
thumb.value_or(MTPInputFile()),
|
||||
MTP_string(document->mimeString()),
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTPVector<MTPInputDocument>(),
|
||||
MTP_vector<MTPInputDocument>(ranges::to<QVector>(attachedStickers)),
|
||||
MTP_int(0));
|
||||
}
|
||||
|
||||
bool HasAttachedStickers(MTPInputMedia media) {
|
||||
return media.match([&](const MTPDinputMediaUploadedPhoto &photo) -> bool {
|
||||
return (photo.vflags().v
|
||||
& MTPDinputMediaUploadedPhoto::Flag::f_stickers);
|
||||
}, [&](const MTPDinputMediaUploadedDocument &document) -> bool {
|
||||
return (document.vflags().v
|
||||
& MTPDinputMediaUploadedDocument::Flag::f_stickers);
|
||||
}, [](const auto &d) {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -11,11 +11,16 @@ class HistoryItem;
|
||||
|
||||
namespace Api {
|
||||
|
||||
MTPInputMedia PrepareUploadedPhoto(const MTPInputFile &file);
|
||||
MTPInputMedia PrepareUploadedPhoto(
|
||||
const MTPInputFile &file,
|
||||
std::vector<MTPInputDocument> attachedStickers);
|
||||
|
||||
MTPInputMedia PrepareUploadedDocument(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb);
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
std::vector<MTPInputDocument> attachedStickers);
|
||||
|
||||
bool HasAttachedStickers(MTPInputMedia media);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -862,8 +862,8 @@ int32 Updates::pts() const {
|
||||
return _ptsWaiter.current();
|
||||
}
|
||||
|
||||
void Updates::updateOnline() {
|
||||
updateOnline(false);
|
||||
void Updates::updateOnline(crl::time lastNonIdleTime) {
|
||||
updateOnline(lastNonIdleTime, false);
|
||||
}
|
||||
|
||||
bool Updates::isIdle() const {
|
||||
@@ -874,15 +874,20 @@ rpl::producer<bool> Updates::isIdleValue() const {
|
||||
return _isIdle.value();
|
||||
}
|
||||
|
||||
void Updates::updateOnline(bool gotOtherOffline) {
|
||||
crl::on_main(&session(), [] { Core::App().checkAutoLock(); });
|
||||
void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) {
|
||||
if (!lastNonIdleTime) {
|
||||
lastNonIdleTime = Core::App().lastNonIdleTime();
|
||||
}
|
||||
crl::on_main(&session(), [=] {
|
||||
Core::App().checkAutoLock(lastNonIdleTime);
|
||||
});
|
||||
|
||||
const auto &config = _session->serverConfig();
|
||||
bool isOnline = Core::App().hasActiveWindow(&session());
|
||||
int updateIn = config.onlineUpdatePeriod;
|
||||
Assert(updateIn >= 0);
|
||||
if (isOnline) {
|
||||
const auto idle = crl::now() - Core::App().lastNonIdleTime();
|
||||
const auto idle = crl::now() - lastNonIdleTime;
|
||||
if (idle >= config.offlineIdleTimeout) {
|
||||
isOnline = false;
|
||||
if (!isIdle()) {
|
||||
@@ -933,10 +938,13 @@ void Updates::updateOnline(bool gotOtherOffline) {
|
||||
_onlineTimer.callOnce(updateIn);
|
||||
}
|
||||
|
||||
void Updates::checkIdleFinish() {
|
||||
if (crl::now() - Core::App().lastNonIdleTime()
|
||||
void Updates::checkIdleFinish(crl::time lastNonIdleTime) {
|
||||
if (!lastNonIdleTime) {
|
||||
lastNonIdleTime = Core::App().lastNonIdleTime();
|
||||
}
|
||||
if (crl::now() - lastNonIdleTime
|
||||
< _session->serverConfig().offlineIdleTimeout) {
|
||||
updateOnline();
|
||||
updateOnline(lastNonIdleTime);
|
||||
_idleFinishTimer.cancel();
|
||||
_isIdle = false;
|
||||
} else {
|
||||
@@ -957,9 +965,10 @@ bool Updates::isQuitPrevent() {
|
||||
return false;
|
||||
}
|
||||
LOG(("Api::Updates prevents quit, sending offline status..."));
|
||||
updateOnline();
|
||||
updateOnline(crl::now());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Updates::handleSendActionUpdate(
|
||||
PeerId peerId,
|
||||
MsgId rootId,
|
||||
@@ -1750,7 +1759,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
if (UserId(d.vuser_id()) == session().userId()) {
|
||||
if (d.vstatus().type() == mtpc_userStatusOffline
|
||||
|| d.vstatus().type() == mtpc_userStatusEmpty) {
|
||||
updateOnline(true);
|
||||
updateOnline(Core::App().lastNonIdleTime(), true);
|
||||
if (d.vstatus().type() == mtpc_userStatusOffline) {
|
||||
cSetOtherOnline(
|
||||
d.vstatus().c_userStatusOffline().vwas_online().v);
|
||||
@@ -2132,31 +2141,46 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateStickerSetsOrder: {
|
||||
auto &d = update.c_updateStickerSetsOrder();
|
||||
if (!d.is_masks()) {
|
||||
const auto &order = d.vorder().v;
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
Data::StickersSetsOrder result;
|
||||
for (const auto &item : order) {
|
||||
if (sets.find(item.v) == sets.cend()) {
|
||||
break;
|
||||
}
|
||||
result.push_back(item.v);
|
||||
auto &stickers = session().data().stickers();
|
||||
const auto isMasks = d.is_masks();
|
||||
const auto &order = d.vorder().v;
|
||||
const auto &sets = stickers.sets();
|
||||
Data::StickersSetsOrder result;
|
||||
for (const auto &item : order) {
|
||||
if (sets.find(item.v) == sets.cend()) {
|
||||
break;
|
||||
}
|
||||
if (result.size() != session().data().stickers().setsOrder().size()
|
||||
|| result.size() != order.size()) {
|
||||
session().data().stickers().setLastUpdate(0);
|
||||
session().api().updateStickers();
|
||||
result.push_back(item.v);
|
||||
}
|
||||
const auto localSize = isMasks
|
||||
? stickers.maskSetsOrder().size()
|
||||
: stickers.setsOrder().size();
|
||||
if ((result.size() != localSize) || (result.size() != order.size())) {
|
||||
if (isMasks) {
|
||||
stickers.setLastMasksUpdate(0);
|
||||
session().api().updateMasks();
|
||||
} else {
|
||||
session().data().stickers().setsOrderRef() = std::move(result);
|
||||
session().local().writeInstalledStickers();
|
||||
session().data().stickers().notifyUpdated();
|
||||
stickers.setLastUpdate(0);
|
||||
session().api().updateStickers();
|
||||
}
|
||||
} else {
|
||||
if (isMasks) {
|
||||
stickers.maskSetsOrderRef() = std::move(result);
|
||||
session().local().writeInstalledMasks();
|
||||
} else {
|
||||
stickers.setsOrderRef() = std::move(result);
|
||||
session().local().writeInstalledStickers();
|
||||
}
|
||||
stickers.notifyUpdated();
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateStickerSets: {
|
||||
// Can't determine is it masks or stickers, so update both.
|
||||
session().data().stickers().setLastUpdate(0);
|
||||
session().api().updateStickers();
|
||||
session().data().stickers().setLastMasksUpdate(0);
|
||||
session().api().updateMasks();
|
||||
} break;
|
||||
|
||||
case mtpc_updateRecentStickers: {
|
||||
|
||||
@@ -38,10 +38,10 @@ public:
|
||||
|
||||
[[nodiscard]] int32 pts() const;
|
||||
|
||||
void updateOnline();
|
||||
void updateOnline(crl::time lastNonIdleTime = 0);
|
||||
[[nodiscard]] bool isIdle() const;
|
||||
[[nodiscard]] rpl::producer<bool> isIdleValue() const;
|
||||
void checkIdleFinish();
|
||||
void checkIdleFinish(crl::time lastNonIdleTime = 0);
|
||||
bool lastWasOnline() const;
|
||||
crl::time lastSetOnline() const;
|
||||
bool isQuitPrevent();
|
||||
@@ -87,7 +87,7 @@ private:
|
||||
MsgRange range,
|
||||
const MTPupdates_ChannelDifference &result);
|
||||
|
||||
void updateOnline(bool gotOtherOffline);
|
||||
void updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline);
|
||||
void sendPing();
|
||||
void getDifferenceByPts();
|
||||
void getDifferenceAfterFail();
|
||||
|
||||
@@ -1857,9 +1857,14 @@ void ApiWrap::requestStickerSets() {
|
||||
if (i.value().second) continue;
|
||||
|
||||
auto waitMs = (j == e) ? 0 : kSmallDelayMs;
|
||||
i.value().second = request(MTPmessages_GetStickerSet(MTP_inputStickerSetID(MTP_long(i.key()), MTP_long(i.value().first)))).done([this, setId = i.key()](const MTPmessages_StickerSet &result) {
|
||||
const auto id = MTP_inputStickerSetID(
|
||||
MTP_long(i.key()),
|
||||
MTP_long(i.value().first));
|
||||
i.value().second = request(MTPmessages_GetStickerSet(
|
||||
id
|
||||
)).done([=, setId = i.key()](const MTPmessages_StickerSet &result) {
|
||||
gotStickerSet(setId, result);
|
||||
}).fail([this, setId = i.key()](const MTP::Error &error) {
|
||||
}).fail([=, setId = i.key()](const MTP::Error &error) {
|
||||
_stickerSetRequests.remove(setId);
|
||||
}).afterDelay(waitMs).send();
|
||||
}
|
||||
@@ -1867,23 +1872,91 @@ void ApiWrap::requestStickerSets() {
|
||||
|
||||
void ApiWrap::saveStickerSets(
|
||||
const Data::StickersSetsOrder &localOrder,
|
||||
const Data::StickersSetsOrder &localRemoved) {
|
||||
for (auto requestId : base::take(_stickerSetDisenableRequests)) {
|
||||
const Data::StickersSetsOrder &localRemoved,
|
||||
bool setsMasks) {
|
||||
auto &setDisenableRequests = setsMasks
|
||||
? _maskSetDisenableRequests
|
||||
: _stickerSetDisenableRequests;
|
||||
const auto reorderRequestId = [=]() -> mtpRequestId & {
|
||||
return setsMasks
|
||||
? _masksReorderRequestId
|
||||
: _stickersReorderRequestId;
|
||||
};
|
||||
for (auto requestId : base::take(setDisenableRequests)) {
|
||||
request(requestId).cancel();
|
||||
}
|
||||
request(base::take(_stickersReorderRequestId)).cancel();
|
||||
request(base::take(reorderRequestId())).cancel();
|
||||
request(base::take(_stickersClearRecentRequestId)).cancel();
|
||||
request(base::take(_stickersClearRecentAttachedRequestId)).cancel();
|
||||
|
||||
auto writeInstalled = true, writeRecent = false, writeCloudRecent = false, writeFaved = false, writeArchived = false;
|
||||
const auto stickersSaveOrder = [=] {
|
||||
if (localOrder.size() < 2) {
|
||||
return;
|
||||
}
|
||||
QVector<MTPlong> mtpOrder;
|
||||
mtpOrder.reserve(localOrder.size());
|
||||
for (const auto setId : std::as_const(localOrder)) {
|
||||
mtpOrder.push_back(MTP_long(setId));
|
||||
}
|
||||
|
||||
const auto flags = setsMasks
|
||||
? MTPmessages_ReorderStickerSets::Flag::f_masks
|
||||
: MTPmessages_ReorderStickerSets::Flags(0);
|
||||
reorderRequestId() = request(MTPmessages_ReorderStickerSets(
|
||||
MTP_flags(flags),
|
||||
MTP_vector<MTPlong>(mtpOrder)
|
||||
)).done([=](const MTPBool &result) {
|
||||
reorderRequestId() = 0;
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
reorderRequestId() = 0;
|
||||
if (setsMasks) {
|
||||
_session->data().stickers().setLastMasksUpdate(0);
|
||||
updateMasks();
|
||||
} else {
|
||||
_session->data().stickers().setLastUpdate(0);
|
||||
updateStickers();
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
|
||||
const auto stickerSetDisenabled = [=](mtpRequestId requestId) {
|
||||
auto &setDisenableRequests = setsMasks
|
||||
? _maskSetDisenableRequests
|
||||
: _stickerSetDisenableRequests;
|
||||
setDisenableRequests.remove(requestId);
|
||||
if (setDisenableRequests.empty()) {
|
||||
stickersSaveOrder();
|
||||
}
|
||||
};
|
||||
|
||||
auto writeInstalled = true,
|
||||
writeRecent = false,
|
||||
writeCloudRecent = false,
|
||||
writeCloudRecentAttached = false,
|
||||
writeFaved = false,
|
||||
writeArchived = false;
|
||||
auto &recent = _session->data().stickers().getRecentPack();
|
||||
auto &sets = _session->data().stickers().setsRef();
|
||||
|
||||
_stickersOrder = localOrder;
|
||||
auto &order = setsMasks
|
||||
? _session->data().stickers().maskSetsOrder()
|
||||
: _session->data().stickers().setsOrder();
|
||||
auto &orderRef = setsMasks
|
||||
? _session->data().stickers().maskSetsOrderRef()
|
||||
: _session->data().stickers().setsOrderRef();
|
||||
|
||||
using Flag = MTPDstickerSet::Flag;
|
||||
using ClientFlag = MTPDstickerSet_ClientFlag;
|
||||
|
||||
for (const auto removedSetId : localRemoved) {
|
||||
if (removedSetId == Data::Stickers::CloudRecentSetId) {
|
||||
if ((removedSetId == Data::Stickers::CloudRecentSetId)
|
||||
|| (removedSetId == Data::Stickers::CloudRecentAttachedSetId)) {
|
||||
if (sets.remove(Data::Stickers::CloudRecentSetId) != 0) {
|
||||
writeCloudRecent = true;
|
||||
}
|
||||
if (sets.remove(Data::Stickers::CloudRecentAttachedSetId) != 0) {
|
||||
writeCloudRecentAttached = true;
|
||||
}
|
||||
if (sets.remove(Data::Stickers::CustomSetId)) {
|
||||
writeInstalled = true;
|
||||
}
|
||||
@@ -1892,12 +1965,25 @@ void ApiWrap::saveStickerSets(
|
||||
writeRecent = true;
|
||||
}
|
||||
|
||||
_stickersClearRecentRequestId = request(MTPmessages_ClearRecentStickers(
|
||||
MTP_flags(0)
|
||||
)).done([this](const MTPBool &result) {
|
||||
_stickersClearRecentRequestId = 0;
|
||||
}).fail([this](const MTP::Error &error) {
|
||||
_stickersClearRecentRequestId = 0;
|
||||
const auto isAttached =
|
||||
(removedSetId == Data::Stickers::CloudRecentAttachedSetId);
|
||||
const auto flags = isAttached
|
||||
? MTPmessages_ClearRecentStickers::Flag::f_attached
|
||||
: MTPmessages_ClearRecentStickers::Flags(0);
|
||||
auto &requestId = isAttached
|
||||
? _stickersClearRecentAttachedRequestId
|
||||
: _stickersClearRecentRequestId;
|
||||
const auto finish = [=] {
|
||||
(isAttached
|
||||
? _stickersClearRecentAttachedRequestId
|
||||
: _stickersClearRecentRequestId) = 0;
|
||||
};
|
||||
requestId = request(MTPmessages_ClearRecentStickers(
|
||||
MTP_flags(flags)
|
||||
)).done([=](const MTPBool &result) {
|
||||
finish();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
finish();
|
||||
}).send();
|
||||
continue;
|
||||
}
|
||||
@@ -1913,27 +1999,34 @@ void ApiWrap::saveStickerSets(
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
const auto archived = !!(set->flags & Flag::f_archived);
|
||||
if (!archived) {
|
||||
const auto featured = !!(set->flags & ClientFlag::f_featured);
|
||||
const auto special = !!(set->flags & ClientFlag::f_special);
|
||||
const auto setId = set->mtpInput();
|
||||
|
||||
auto requestId = request(MTPmessages_UninstallStickerSet(setId)).done([this](const MTPBool &result, mtpRequestId requestId) {
|
||||
auto requestId = request(MTPmessages_UninstallStickerSet(
|
||||
setId
|
||||
)).done([=](const MTPBool &result, mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
}).fail([this](const MTP::Error &error, mtpRequestId requestId) {
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
|
||||
_stickerSetDisenableRequests.insert(requestId);
|
||||
setDisenableRequests.insert(requestId);
|
||||
|
||||
int removeIndex = _session->data().stickers().setsOrder().indexOf(set->id);
|
||||
if (removeIndex >= 0) _session->data().stickers().setsOrderRef().removeAt(removeIndex);
|
||||
if (!(set->flags & MTPDstickerSet_ClientFlag::f_featured)
|
||||
&& !(set->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
const auto removeIndex = order.indexOf(set->id);
|
||||
if (removeIndex >= 0) {
|
||||
orderRef.removeAt(removeIndex);
|
||||
}
|
||||
if (!featured && !special) {
|
||||
sets.erase(it);
|
||||
} else {
|
||||
if (set->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
if (archived) {
|
||||
writeArchived = true;
|
||||
}
|
||||
set->flags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet::Flag::f_archived);
|
||||
set->flags &= ~(Flag::f_installed_date
|
||||
| Flag::f_archived);
|
||||
set->installDate = TimeId(0);
|
||||
}
|
||||
}
|
||||
@@ -1942,51 +2035,55 @@ void ApiWrap::saveStickerSets(
|
||||
|
||||
// Clear all installed flags, set only for sets from order.
|
||||
for (auto &[id, set] : sets) {
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
const auto archived = !!(set->flags & Flag::f_archived);
|
||||
const auto masks = !!(set->flags & MTPDstickerSet::Flag::f_masks);
|
||||
if (!archived && (setsMasks == masks)) {
|
||||
set->flags &= ~Flag::f_installed_date;
|
||||
}
|
||||
}
|
||||
|
||||
auto &order = _session->data().stickers().setsOrderRef();
|
||||
order.clear();
|
||||
for (const auto setId : std::as_const(_stickersOrder)) {
|
||||
orderRef.clear();
|
||||
for (const auto setId : std::as_const(localOrder)) {
|
||||
auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
const auto set = it->second.get();
|
||||
if ((set->flags & MTPDstickerSet::Flag::f_archived) && !localRemoved.contains(set->id)) {
|
||||
const auto mtpSetId = set->mtpInput();
|
||||
if (it == sets.cend()) {
|
||||
continue;
|
||||
}
|
||||
const auto set = it->second.get();
|
||||
const auto archived = !!(set->flags & Flag::f_archived);
|
||||
if (archived && !localRemoved.contains(set->id)) {
|
||||
const auto mtpSetId = set->mtpInput();
|
||||
|
||||
const auto requestId = request(MTPmessages_InstallStickerSet(
|
||||
mtpSetId,
|
||||
MTP_boolFalse()
|
||||
)).done([=](
|
||||
const MTPmessages_StickerSetInstallResult &result,
|
||||
mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
}).fail([=](
|
||||
const MTP::Error &error,
|
||||
mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
const auto requestId = request(MTPmessages_InstallStickerSet(
|
||||
mtpSetId,
|
||||
MTP_boolFalse()
|
||||
)).done([=](
|
||||
const MTPmessages_StickerSetInstallResult &result,
|
||||
mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
}).fail([=](
|
||||
const MTP::Error &error,
|
||||
mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
|
||||
_stickerSetDisenableRequests.insert(requestId);
|
||||
setDisenableRequests.insert(requestId);
|
||||
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_archived;
|
||||
writeArchived = true;
|
||||
}
|
||||
order.push_back(setId);
|
||||
set->flags |= MTPDstickerSet::Flag::f_installed_date;
|
||||
if (!set->installDate) {
|
||||
set->installDate = base::unixtime::now();
|
||||
}
|
||||
set->flags &= ~Flag::f_archived;
|
||||
writeArchived = true;
|
||||
}
|
||||
orderRef.push_back(setId);
|
||||
set->flags |= Flag::f_installed_date;
|
||||
if (!set->installDate) {
|
||||
set->installDate = base::unixtime::now();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = sets.begin(); it != sets.cend();) {
|
||||
const auto set = it->second.get();
|
||||
if ((set->flags & MTPDstickerSet_ClientFlag::f_featured)
|
||||
|| (set->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
|| (set->flags & MTPDstickerSet::Flag::f_archived)
|
||||
|| (set->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
if ((set->flags & ClientFlag::f_featured)
|
||||
|| (set->flags & Flag::f_installed_date)
|
||||
|| (set->flags & Flag::f_archived)
|
||||
|| (set->flags & ClientFlag::f_special)) {
|
||||
++it;
|
||||
} else {
|
||||
it = sets.erase(it);
|
||||
@@ -1994,27 +2091,40 @@ void ApiWrap::saveStickerSets(
|
||||
}
|
||||
|
||||
auto &storage = local();
|
||||
if (writeInstalled) storage.writeInstalledStickers();
|
||||
if (writeRecent) session().saveSettings();
|
||||
if (writeArchived) storage.writeArchivedStickers();
|
||||
if (writeCloudRecent) storage.writeRecentStickers();
|
||||
if (writeFaved) storage.writeFavedStickers();
|
||||
if (writeInstalled && !setsMasks) {
|
||||
storage.writeInstalledStickers();
|
||||
}
|
||||
if (writeInstalled && setsMasks) {
|
||||
storage.writeInstalledMasks();
|
||||
}
|
||||
if (writeRecent) {
|
||||
session().saveSettings();
|
||||
}
|
||||
if (writeArchived) {
|
||||
if (setsMasks) {
|
||||
storage.writeArchivedMasks();
|
||||
} else {
|
||||
storage.writeArchivedStickers();
|
||||
}
|
||||
}
|
||||
if (writeCloudRecent) {
|
||||
storage.writeRecentStickers();
|
||||
}
|
||||
if (writeCloudRecentAttached) {
|
||||
storage.writeRecentMasks();
|
||||
}
|
||||
if (writeFaved) {
|
||||
storage.writeFavedStickers();
|
||||
}
|
||||
_session->data().stickers().notifyUpdated();
|
||||
|
||||
if (_stickerSetDisenableRequests.empty()) {
|
||||
if (setDisenableRequests.empty()) {
|
||||
stickersSaveOrder();
|
||||
} else {
|
||||
requestSendDelayed();
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::stickerSetDisenabled(mtpRequestId requestId) {
|
||||
_stickerSetDisenableRequests.remove(requestId);
|
||||
if (_stickerSetDisenableRequests.empty()) {
|
||||
stickersSaveOrder();
|
||||
}
|
||||
};
|
||||
|
||||
void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
|
||||
if (channel->amIn()) {
|
||||
session().changes().peerUpdated(
|
||||
@@ -2825,12 +2935,24 @@ void ApiWrap::refreshFileReference(
|
||||
}, [&](Data::FileOriginPeerPhoto data) {
|
||||
fail();
|
||||
}, [&](Data::FileOriginStickerSet data) {
|
||||
const auto isRecentAttached =
|
||||
(data.setId == Data::Stickers::CloudRecentAttachedSetId);
|
||||
if (data.setId == Data::Stickers::CloudRecentSetId
|
||||
|| data.setId == Data::Stickers::RecentSetId) {
|
||||
|| data.setId == Data::Stickers::RecentSetId
|
||||
|| isRecentAttached) {
|
||||
auto done = [=] { crl::on_main(_session, [=] {
|
||||
if (isRecentAttached) {
|
||||
local().writeRecentMasks();
|
||||
} else {
|
||||
local().writeRecentStickers();
|
||||
}
|
||||
}); };
|
||||
request(MTPmessages_GetRecentStickers(
|
||||
MTP_flags(0),
|
||||
MTP_flags(isRecentAttached
|
||||
? MTPmessages_GetRecentStickers::Flag::f_attached
|
||||
: MTPmessages_GetRecentStickers::Flags(0)),
|
||||
MTP_int(0)),
|
||||
[=] { crl::on_main(_session, [=] { local().writeRecentStickers(); }); });
|
||||
std::move(done));
|
||||
} else if (data.setId == Data::Stickers::FavedSetId) {
|
||||
request(MTPmessages_GetFavedStickers(MTP_int(0)),
|
||||
[=] { crl::on_main(_session, [=] { local().writeFavedStickers(); }); });
|
||||
@@ -2886,30 +3008,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
|
||||
_session->data().sendWebPageGamePollNotifications();
|
||||
}
|
||||
|
||||
void ApiWrap::stickersSaveOrder() {
|
||||
if (_stickersOrder.size() < 2) {
|
||||
return;
|
||||
}
|
||||
QVector<MTPlong> mtpOrder;
|
||||
mtpOrder.reserve(_stickersOrder.size());
|
||||
for (const auto setId : std::as_const(_stickersOrder)) {
|
||||
mtpOrder.push_back(MTP_long(setId));
|
||||
}
|
||||
|
||||
_stickersReorderRequestId = request(MTPmessages_ReorderStickerSets(
|
||||
MTP_flags(0),
|
||||
MTP_vector<MTPlong>(mtpOrder)
|
||||
)).done([=](const MTPBool &result) {
|
||||
_stickersReorderRequestId = 0;
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_stickersReorderRequestId = 0;
|
||||
_session->data().stickers().setLastUpdate(0);
|
||||
updateStickers();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::updateStickers() {
|
||||
auto now = crl::now();
|
||||
const auto now = crl::now();
|
||||
requestStickers(now);
|
||||
requestRecentStickers(now);
|
||||
requestFavedStickers(now);
|
||||
@@ -2917,8 +3017,14 @@ void ApiWrap::updateStickers() {
|
||||
requestSavedGifs(now);
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickersForce() {
|
||||
requestRecentStickersWithHash(0);
|
||||
void ApiWrap::updateMasks() {
|
||||
const auto now = crl::now();
|
||||
requestStickers(now, true);
|
||||
requestRecentStickers(now, true);
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickersForce(bool attached) {
|
||||
requestRecentStickersWithHash(0, attached);
|
||||
}
|
||||
|
||||
void ApiWrap::setGroupStickerSet(not_null<ChannelData*> megagroup, const MTPInputStickerSet &set) {
|
||||
@@ -2977,17 +3083,29 @@ std::vector<not_null<DocumentData*>> *ApiWrap::stickersByEmoji(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ApiWrap::requestStickers(TimeId now) {
|
||||
if (!_session->data().stickers().updateNeeded(now)
|
||||
|| _stickersUpdateRequest) {
|
||||
void ApiWrap::requestStickers(TimeId now, bool masks) {
|
||||
const auto requestId = [=]() -> mtpRequestId & {
|
||||
return masks
|
||||
? _masksUpdateRequest
|
||||
: _stickersUpdateRequest;
|
||||
};
|
||||
const auto needed = masks
|
||||
? _session->data().stickers().masksUpdateNeeded(now)
|
||||
: _session->data().stickers().updateNeeded(now);
|
||||
if (!needed || requestId()) {
|
||||
return;
|
||||
}
|
||||
auto onDone = [this](const MTPmessages_AllStickers &result) {
|
||||
_session->data().stickers().setLastUpdate(crl::now());
|
||||
_stickersUpdateRequest = 0;
|
||||
const auto onDone = [=](const MTPmessages_AllStickers &result) {
|
||||
if (masks) {
|
||||
_session->data().stickers().setLastMasksUpdate(crl::now());
|
||||
} else {
|
||||
_session->data().stickers().setLastUpdate(crl::now());
|
||||
}
|
||||
requestId() = 0;
|
||||
|
||||
switch (result.type()) {
|
||||
case mtpc_messages_allStickersNotModified: return;
|
||||
case mtpc_messages_getMaskStickers:
|
||||
case mtpc_messages_allStickers: {
|
||||
auto &d = result.c_messages_allStickers();
|
||||
_session->data().stickers().setsReceived(
|
||||
@@ -2997,39 +3115,67 @@ void ApiWrap::requestStickers(TimeId now) {
|
||||
default: Unexpected("Type in ApiWrap::stickersDone()");
|
||||
}
|
||||
};
|
||||
_stickersUpdateRequest = request(MTPmessages_GetAllStickers(
|
||||
MTP_int(Api::CountStickersHash(_session, true))
|
||||
)).done(onDone).fail([=](const MTP::Error &error) {
|
||||
LOG(("App Fail: Failed to get stickers!"));
|
||||
const auto onFail = [=](const MTP::Error &error) {
|
||||
LOG(("App Fail: Failed to get %1!"
|
||||
).arg(masks ? "masks" : "stickers"));
|
||||
onDone(MTP_messages_allStickersNotModified());
|
||||
}).send();
|
||||
};
|
||||
auto hash = MTP_int(Api::CountStickersHash(_session, true));
|
||||
requestId() = masks
|
||||
? request(MTPmessages_GetMaskStickers(
|
||||
std::move(hash))
|
||||
).done(onDone).fail(onFail).send()
|
||||
: request(MTPmessages_GetAllStickers(
|
||||
std::move(hash))
|
||||
).done(onDone).fail(onFail).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickers(TimeId now) {
|
||||
if (!_session->data().stickers().recentUpdateNeeded(now)) {
|
||||
void ApiWrap::requestRecentStickers(TimeId now, bool attached) {
|
||||
const auto needed = attached
|
||||
? _session->data().stickers().recentAttachedUpdateNeeded(now)
|
||||
: _session->data().stickers().recentUpdateNeeded(now);
|
||||
if (!needed) {
|
||||
return;
|
||||
}
|
||||
requestRecentStickersWithHash(
|
||||
Api::CountRecentStickersHash(_session));
|
||||
Api::CountRecentStickersHash(_session, attached), attached);
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickersWithHash(int32 hash) {
|
||||
if (_recentStickersUpdateRequest) {
|
||||
void ApiWrap::requestRecentStickersWithHash(int32 hash, bool attached) {
|
||||
const auto requestId = [=]() -> mtpRequestId & {
|
||||
return attached
|
||||
? _recentAttachedStickersUpdateRequest
|
||||
: _recentStickersUpdateRequest;
|
||||
};
|
||||
if (requestId()) {
|
||||
return;
|
||||
}
|
||||
_recentStickersUpdateRequest = request(MTPmessages_GetRecentStickers(
|
||||
MTP_flags(0),
|
||||
const auto finish = [=] {
|
||||
auto &stickers = _session->data().stickers();
|
||||
if (attached) {
|
||||
stickers.setLastRecentAttachedUpdate(crl::now());
|
||||
} else {
|
||||
stickers.setLastRecentUpdate(crl::now());
|
||||
}
|
||||
requestId() = 0;
|
||||
};
|
||||
const auto flags = attached
|
||||
? MTPmessages_getRecentStickers::Flag::f_attached
|
||||
: MTPmessages_getRecentStickers::Flags(0);
|
||||
requestId() = request(MTPmessages_GetRecentStickers(
|
||||
MTP_flags(flags),
|
||||
MTP_int(hash)
|
||||
)).done([=](const MTPmessages_RecentStickers &result) {
|
||||
_session->data().stickers().setLastRecentUpdate(crl::now());
|
||||
_recentStickersUpdateRequest = 0;
|
||||
finish();
|
||||
|
||||
switch (result.type()) {
|
||||
case mtpc_messages_recentStickersNotModified: return;
|
||||
case mtpc_messages_recentStickers: {
|
||||
auto &d = result.c_messages_recentStickers();
|
||||
_session->data().stickers().specialSetReceived(
|
||||
Data::Stickers::CloudRecentSetId,
|
||||
attached
|
||||
? Data::Stickers::CloudRecentAttachedSetId
|
||||
: Data::Stickers::CloudRecentSetId,
|
||||
tr::lng_recent_stickers(tr::now),
|
||||
d.vstickers().v,
|
||||
d.vhash().v,
|
||||
@@ -3039,8 +3185,7 @@ void ApiWrap::requestRecentStickersWithHash(int32 hash) {
|
||||
default: Unexpected("Type in ApiWrap::recentStickersDone()");
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_session->data().stickers().setLastRecentUpdate(crl::now());
|
||||
_recentStickersUpdateRequest = 0;
|
||||
finish();
|
||||
|
||||
LOG(("App Fail: Failed to get recent stickers!"));
|
||||
}).send();
|
||||
@@ -3947,9 +4092,12 @@ void ApiWrap::sendFile(
|
||||
void ApiWrap::sendUploadedPhoto(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
Api::SendOptions options) {
|
||||
Api::SendOptions options,
|
||||
std::vector<MTPInputDocument> attachedStickers) {
|
||||
if (const auto item = _session->data().message(localId)) {
|
||||
const auto media = Api::PrepareUploadedPhoto(file);
|
||||
const auto media = Api::PrepareUploadedPhoto(
|
||||
file,
|
||||
std::move(attachedStickers));
|
||||
if (const auto groupId = item->groupId()) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
@@ -3962,12 +4110,17 @@ void ApiWrap::sendUploadedDocument(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
Api::SendOptions options) {
|
||||
Api::SendOptions options,
|
||||
std::vector<MTPInputDocument> attachedStickers) {
|
||||
if (const auto item = _session->data().message(localId)) {
|
||||
if (!item->media() || !item->media()->document()) {
|
||||
return;
|
||||
}
|
||||
const auto media = Api::PrepareUploadedDocument(item, file, thumb);
|
||||
const auto media = Api::PrepareUploadedDocument(
|
||||
item,
|
||||
file,
|
||||
thumb,
|
||||
std::move(attachedStickers));
|
||||
const auto groupId = item->groupId();
|
||||
if (groupId) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
@@ -4368,6 +4521,8 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
caption.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
|
||||
const auto updateRecentStickers = Api::HasAttachedStickers(media);
|
||||
|
||||
const auto flags = MTPmessages_SendMedia::Flags(0)
|
||||
| (replyTo
|
||||
? MTPmessages_SendMedia::Flag::f_reply_to_msg_id
|
||||
@@ -4400,6 +4555,10 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
finish();
|
||||
|
||||
if (updateRecentStickers) {
|
||||
requestRecentStickersForce(true);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
sendMessageFail(error, peer, randomId, itemId);
|
||||
finish();
|
||||
|
||||
@@ -280,9 +280,11 @@ public:
|
||||
void requestStickerSets();
|
||||
void saveStickerSets(
|
||||
const Data::StickersSetsOrder &localOrder,
|
||||
const Data::StickersSetsOrder &localRemoved);
|
||||
const Data::StickersSetsOrder &localRemoved,
|
||||
bool setsMasks);
|
||||
void updateStickers();
|
||||
void requestRecentStickersForce();
|
||||
void updateMasks();
|
||||
void requestRecentStickersForce(bool attached = false);
|
||||
void setGroupStickerSet(
|
||||
not_null<ChannelData*> megagroup,
|
||||
const MTPInputStickerSet &set);
|
||||
@@ -339,12 +341,6 @@ public:
|
||||
not_null<UserData*> user,
|
||||
PhotoId afterId);
|
||||
|
||||
void stickerSetInstalled(uint64 setId) {
|
||||
_stickerSetInstalled.fire_copy(setId);
|
||||
}
|
||||
auto stickerSetInstalled() const {
|
||||
return _stickerSetInstalled.events();
|
||||
}
|
||||
void readFeaturedSetDelayed(uint64 setId);
|
||||
|
||||
void parseChannelParticipants(
|
||||
@@ -410,12 +406,14 @@ public:
|
||||
void sendUploadedPhoto(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
Api::SendOptions options);
|
||||
Api::SendOptions options,
|
||||
std::vector<MTPInputDocument> attachedStickers);
|
||||
void sendUploadedDocument(
|
||||
FullMsgId localId,
|
||||
const MTPInputFile &file,
|
||||
const std::optional<MTPInputFile> &thumb,
|
||||
Api::SendOptions options);
|
||||
Api::SendOptions options,
|
||||
std::vector<MTPInputDocument> attachedStickers);
|
||||
|
||||
void cancelLocalItem(not_null<HistoryItem*> item);
|
||||
|
||||
@@ -542,12 +540,9 @@ private:
|
||||
mtpRequestId req);
|
||||
void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result);
|
||||
|
||||
void stickerSetDisenabled(mtpRequestId requestId);
|
||||
void stickersSaveOrder();
|
||||
|
||||
void requestStickers(TimeId now);
|
||||
void requestRecentStickers(TimeId now);
|
||||
void requestRecentStickersWithHash(int32 hash);
|
||||
void requestStickers(TimeId now, bool masks = false);
|
||||
void requestRecentStickers(TimeId now, bool attached = false);
|
||||
void requestRecentStickersWithHash(int32 hash, bool attached = false);
|
||||
void requestFavedStickers(TimeId now);
|
||||
void requestFeaturedStickers(TimeId now);
|
||||
void requestSavedGifs(TimeId now);
|
||||
@@ -685,12 +680,16 @@ private:
|
||||
base::Timer _draftsSaveTimer;
|
||||
|
||||
base::flat_set<mtpRequestId> _stickerSetDisenableRequests;
|
||||
Data::StickersSetsOrder _stickersOrder;
|
||||
base::flat_set<mtpRequestId> _maskSetDisenableRequests;
|
||||
mtpRequestId _masksReorderRequestId = 0;
|
||||
mtpRequestId _stickersReorderRequestId = 0;
|
||||
mtpRequestId _stickersClearRecentRequestId = 0;
|
||||
mtpRequestId _stickersClearRecentAttachedRequestId = 0;
|
||||
|
||||
mtpRequestId _stickersUpdateRequest = 0;
|
||||
mtpRequestId _masksUpdateRequest = 0;
|
||||
mtpRequestId _recentStickersUpdateRequest = 0;
|
||||
mtpRequestId _recentAttachedStickersUpdateRequest = 0;
|
||||
mtpRequestId _favedStickersUpdateRequest = 0;
|
||||
mtpRequestId _featuredStickersUpdateRequest = 0;
|
||||
mtpRequestId _savedGifsUpdateRequest = 0;
|
||||
@@ -731,8 +730,6 @@ private:
|
||||
|
||||
base::Observable<PeerData*> _fullPeerUpdated;
|
||||
|
||||
rpl::event_stream<uint64> _stickerSetInstalled;
|
||||
|
||||
mtpRequestId _topPromotionRequestId = 0;
|
||||
std::pair<QString, uint32> _topPromotionKey;
|
||||
TimeId _topPromotionNextRequestTime = TimeId(0);
|
||||
|
||||
@@ -282,8 +282,4 @@ namespace App {
|
||||
return result;
|
||||
}
|
||||
|
||||
QPixmap pixmapFromImageInPlace(QImage &&image) {
|
||||
return QPixmap::fromImage(std::move(image), Qt::ColorOnly);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,5 @@ namespace App {
|
||||
constexpr auto kImageSizeLimit = 64 * 1024 * 1024; // Open images up to 64mb jpg/png/gif
|
||||
QImage readImage(QByteArray data, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr);
|
||||
QImage readImage(const QString &file, QByteArray *format = nullptr, bool opaque = true, bool *animated = nullptr, QByteArray *content = 0);
|
||||
QPixmap pixmapFromImageInPlace(QImage &&image);
|
||||
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/openssl_help.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/confirm_phone_box.h" // ExtractPhonePrefix.
|
||||
#include "boxes/photo_crop_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/add_participants_box.h"
|
||||
#include "boxes/peers/edit_participant_box.h"
|
||||
@@ -466,6 +465,7 @@ void GroupInfoBox::prepare() {
|
||||
|
||||
_photo.create(
|
||||
this,
|
||||
&_navigation->parentController()->window(),
|
||||
((_type == Type::Channel)
|
||||
? tr::lng_create_channel_crop
|
||||
: tr::lng_create_group_crop)(tr::now),
|
||||
|
||||
@@ -45,6 +45,6 @@ void AutoLockBox::durationChanged(int seconds) {
|
||||
Core::App().settings().setAutoLock(seconds);
|
||||
Core::App().saveSettingsDelayed();
|
||||
|
||||
Core::App().checkAutoLock();
|
||||
Core::App().checkAutoLock(crl::now());
|
||||
closeBox();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_overview.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -337,7 +336,7 @@ void BackgroundBox::Inner::validatePaperThumbnail(
|
||||
Data::PatternColor(color),
|
||||
paper.data.patternIntensity());
|
||||
}
|
||||
paper.thumbnail = App::pixmapFromImageInPlace(TakeMiddleSample(
|
||||
paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample(
|
||||
original,
|
||||
st::backgroundSize));
|
||||
paper.thumbnail.setDevicePixelRatio(cRetinaFactor());
|
||||
|
||||
@@ -29,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -399,7 +398,7 @@ BackgroundPreviewBox::BackgroundPreviewBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::WallPaper &paper)
|
||||
: SimpleElementDelegate(controller)
|
||||
: SimpleElementDelegate(controller, [=] { update(); })
|
||||
, _controller(controller)
|
||||
, _text1(GenerateTextItem(
|
||||
delegate(),
|
||||
@@ -688,8 +687,8 @@ void BackgroundPreviewBox::setScaledFromImage(
|
||||
if (!_full.isNull()) {
|
||||
startFadeInFrom(std::move(_scaled));
|
||||
}
|
||||
_scaled = App::pixmapFromImageInPlace(std::move(image));
|
||||
_blurred = App::pixmapFromImageInPlace(std::move(blurred));
|
||||
_scaled = Ui::PixmapFromImage(std::move(image));
|
||||
_blurred = Ui::PixmapFromImage(std::move(blurred));
|
||||
if (_blur && (!_paper.document() || !_full.isNull())) {
|
||||
_blur->setDisabled(false);
|
||||
}
|
||||
|
||||
@@ -76,10 +76,6 @@ defaultFeedUserpicButton: FeedUserpicButton {
|
||||
innerPart: defaultUserpicButton;
|
||||
}
|
||||
|
||||
cropPointSize: 10px;
|
||||
cropSkip: 13px;
|
||||
cropMinSize: 20px;
|
||||
|
||||
confirmInviteTitle: FlatLabel(defaultFlatLabel) {
|
||||
align: align(center);
|
||||
minWidth: 320px;
|
||||
@@ -386,6 +382,7 @@ confirmCaptionArea: InputField(defaultInputField) {
|
||||
textMargins: margins(1px, 26px, 31px, 4px);
|
||||
heightMax: 158px;
|
||||
}
|
||||
confirmEditCaptionAreaHeightMax: 78px;
|
||||
confirmBg: windowBgOver;
|
||||
confirmMaxHeight: 245px;
|
||||
confirmMaxHeightSkip: 25px;
|
||||
@@ -427,33 +424,42 @@ backgroundScroll: ScrollArea(boxScroll) {
|
||||
deltab: 10px;
|
||||
}
|
||||
|
||||
editMediaButtonSize: 28px;
|
||||
editMediaButtonSkip: 8px;
|
||||
sendMediaPreviewSize: 308px;
|
||||
sendMediaPreviewHeightMax: 1280;
|
||||
sendMediaRowSkip: 10px;
|
||||
|
||||
editMediaButtonSize: 32px;
|
||||
editMediaButtonSkip: 5px;
|
||||
editMediaButtonFileSkipRight: 1px;
|
||||
editMediaButtonFileSkipTop: 7px;
|
||||
|
||||
editMediaButtonIconFile: icon {{ "settings_edit", menuIconFg }};
|
||||
editMediaButtonIconPhoto: icon {{ "settings_edit", roundedFg }};
|
||||
editMediaButton: IconButton {
|
||||
editMediaButtonIconFile: icon {{ "send_media/send_media_replace", menuIconFg }};
|
||||
editMediaButton: IconButton(defaultIconButton) {
|
||||
width: editMediaButtonSize;
|
||||
height: editMediaButtonSize;
|
||||
|
||||
icon: editMediaButtonIconPhoto;
|
||||
icon: editMediaButtonIconFile;
|
||||
|
||||
rippleAreaSize: editMediaButtonSize;
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
|
||||
editMediaHintLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
minWidth: sendMediaPreviewSize;
|
||||
}
|
||||
|
||||
// SendFilesBox
|
||||
|
||||
sendBoxAlbumGroupEditInternalSkip: 8px;
|
||||
sendBoxAlbumGroupSkipRight: 6px;
|
||||
sendBoxAlbumGroupSkipTop: 6px;
|
||||
sendBoxAlbumGroupRadius: 13px;
|
||||
sendBoxAlbumGroupHeight: 26px;
|
||||
sendBoxAlbumGroupSkipRight: 5px;
|
||||
sendBoxAlbumGroupSkipTop: 5px;
|
||||
sendBoxAlbumGroupRadius: 4px;
|
||||
sendBoxAlbumGroupSize: size(62px, 25px);
|
||||
sendBoxAlbumSmallGroupSize: size(30px, 25px);
|
||||
|
||||
sendBoxFileGroupSkipTop: 2px;
|
||||
sendBoxFileGroupSkipRight: 8px;
|
||||
sendBoxFileGroupSkipRight: 5px;
|
||||
sendBoxFileGroupEditInternalSkip: -1px;
|
||||
|
||||
sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
|
||||
@@ -462,16 +468,11 @@ sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
|
||||
}
|
||||
}
|
||||
sendBoxAlbumGroupEditButtonIconFile: editMediaButtonIconFile;
|
||||
sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "history_file_cancel", menuIconFg, point(6px, 6px) }};
|
||||
sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "send_media/send_media_delete", menuIconFg }};
|
||||
|
||||
sendBoxAlbumGroupButtonMediaEdit: icon {{ "settings_edit", roundedFg, point(3px, -2px) }};
|
||||
sendBoxAlbumGroupButtonMediaDelete: icon {{ "history_file_cancel", roundedFg, point(2px, 5px) }};
|
||||
sendBoxAlbumGroupButtonMedia: IconButton {
|
||||
width: sendBoxAlbumGroupHeight;
|
||||
height: sendBoxAlbumGroupHeight;
|
||||
|
||||
icon: sendBoxAlbumGroupButtonMediaEdit;
|
||||
}
|
||||
sendBoxAlbumButtonMediaEdit: icon {{ "send_media/send_media_replace", roundedFg }};
|
||||
sendBoxAlbumGroupButtonMediaEdit: icon {{ "send_media/send_media_replace", roundedFg, point(4px, 1px) }};
|
||||
sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_delete", roundedFg }};
|
||||
|
||||
// End of SendFilesBox
|
||||
|
||||
@@ -523,6 +524,7 @@ usernameTextStyle: TextStyle(boxTextStyle, passcodeTextStyle) {
|
||||
}
|
||||
usernameDefaultFg: windowSubTextFg;
|
||||
|
||||
editMediaLabelMargins: margins(0px, 11px, 0px, 0px);
|
||||
editMediaCheckboxMargins: margins(0px, 15px, 23px, 15px);
|
||||
|
||||
downloadPathSkip: 10px;
|
||||
@@ -649,10 +651,6 @@ groupStickersField: InputField(defaultMultiSelectSearchField) {
|
||||
}
|
||||
groupStickersSubTitleHeight: 36px;
|
||||
|
||||
sendMediaPreviewSize: 308px;
|
||||
sendMediaPreviewHeightMax: 1280;
|
||||
sendMediaRowSkip: 10px;
|
||||
|
||||
proxyUsePadding: margins(22px, 6px, 22px, 5px);
|
||||
proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
|
||||
proxyRowPadding: margins(22px, 8px, 8px, 8px);
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_editing.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
@@ -50,20 +51,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/format_song_document_name.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/chat/attach/attach_controls.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/abstract_button.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "app.h" // App::pixmapFromImageInPlace.
|
||||
#include "facades.h" // App::LambdaDelayed.
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
#include "editor/photo_editor_layer_widget.h"
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
namespace {
|
||||
@@ -204,7 +209,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
| Images::Option::RoundedTopRight
|
||||
| Images::Option::RoundedBottomLeft
|
||||
| Images::Option::RoundedBottomRight;
|
||||
_thumb = App::pixmapFromImageInPlace(Images::prepare(
|
||||
_thumb = Ui::PixmapFromImage(Images::prepare(
|
||||
image->original(),
|
||||
_thumbw * cIntRetinaFactor(),
|
||||
0,
|
||||
@@ -334,7 +339,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
|
||||
const auto prepareBasicThumb = _refreshThumbnail;
|
||||
const auto scaleThumbDown = [=] {
|
||||
_thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(
|
||||
_thumb = Ui::PixmapFromImage(_thumb.toImage().scaled(
|
||||
_thumbw * cIntRetinaFactor(),
|
||||
_thumbh * cIntRetinaFactor(),
|
||||
Qt::KeepAspectRatio,
|
||||
@@ -381,6 +386,20 @@ EditCaptionBox::EditCaptionBox(
|
||||
|
||||
InitSpellchecker(_controller, _field);
|
||||
|
||||
auto label = object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
|
||||
this,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
this,
|
||||
tr::lng_edit_photo_editor_hint(tr::now),
|
||||
st::editMediaHintLabel),
|
||||
st::editMediaLabelMargins);
|
||||
_hintLabel = label.data();
|
||||
_hintLabel->toggle(
|
||||
_controller->session().settings().photoEditorHintShown()
|
||||
? _photo
|
||||
: false,
|
||||
anim::type::instant);
|
||||
|
||||
auto r = object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
|
||||
this,
|
||||
object_ptr<Ui::Checkbox>(
|
||||
@@ -402,6 +421,48 @@ EditCaptionBox::EditCaptionBox(
|
||||
) | rpl::start_with_next([=] {
|
||||
closeBox();
|
||||
}, lifetime());
|
||||
|
||||
_photoEditorOpens.events(
|
||||
) | rpl::start_with_next([=, controller = _controller] {
|
||||
const auto previewWidth = st::sendMediaPreviewSize;
|
||||
if (!_preparedList.files.empty()) {
|
||||
Editor::OpenWithPreparedFile(
|
||||
this,
|
||||
controller,
|
||||
&_preparedList.files.front(),
|
||||
previewWidth,
|
||||
[=] { updateEditPreview(); });
|
||||
} else {
|
||||
auto callback = [=](const Editor::PhotoModifications &mods) {
|
||||
if (!mods) {
|
||||
return;
|
||||
}
|
||||
auto copy = computeImage()->original();
|
||||
_preparedList = Storage::PrepareMediaFromImage(
|
||||
std::move(copy),
|
||||
QByteArray(),
|
||||
previewWidth);
|
||||
|
||||
using ImageInfo = Ui::PreparedFileInformation::Image;
|
||||
auto &file = _preparedList.files.front();
|
||||
const auto image = std::get_if<ImageInfo>(
|
||||
&file.information->media);
|
||||
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(file, previewWidth);
|
||||
updateEditPreview();
|
||||
};
|
||||
const auto fileImage = std::make_shared<Image>(*computeImage());
|
||||
controller->showLayer(
|
||||
std::make_unique<Editor::LayerWidget>(
|
||||
this,
|
||||
&controller->window(),
|
||||
fileImage,
|
||||
Editor::PhotoModifications(),
|
||||
std::move(callback)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
EditCaptionBox::~EditCaptionBox() = default;
|
||||
@@ -594,9 +655,13 @@ void EditCaptionBox::updateEditPreview() {
|
||||
|
||||
const auto showCheckbox = _photo && (_albumType == Ui::AlbumType::None);
|
||||
_wayWrap->toggle(showCheckbox, anim::type::instant);
|
||||
if (_controller->session().settings().photoEditorHintShown()) {
|
||||
_hintLabel->toggle(_photo, anim::type::instant);
|
||||
}
|
||||
_photoEditorButton->setVisible(_photo);
|
||||
|
||||
if (!_doc) {
|
||||
_thumb = App::pixmapFromImageInPlace(
|
||||
_thumb = Ui::PixmapFromImage(
|
||||
file->preview.scaled(
|
||||
st::sendMediaPreviewSize * cIntRetinaFactor(),
|
||||
(st::confirmMaxHeight - (showCheckbox
|
||||
@@ -614,17 +679,13 @@ void EditCaptionBox::updateEditPreview() {
|
||||
}
|
||||
}
|
||||
updateEditMediaButton();
|
||||
updateCaptionMaxHeight();
|
||||
captionResized();
|
||||
}
|
||||
|
||||
void EditCaptionBox::updateEditMediaButton() {
|
||||
const auto icon = _doc
|
||||
? &st::editMediaButtonIconFile
|
||||
: &st::editMediaButtonIconPhoto;
|
||||
const auto color = _doc ? &st::windowBgRipple : &st::roundedBg;
|
||||
_editMedia->setIconOverride(icon);
|
||||
_editMedia->setRippleColorOverride(color);
|
||||
_editMedia->setForceRippled(!_doc, anim::type::instant);
|
||||
_editMedia->setVisible(!_doc);
|
||||
_editFile->setVisible(_doc);
|
||||
}
|
||||
|
||||
void EditCaptionBox::createEditMediaButton() {
|
||||
@@ -677,13 +738,26 @@ void EditCaptionBox::createEditMediaButton() {
|
||||
lifetime());
|
||||
|
||||
// Create edit media button.
|
||||
_editMedia.create(this, st::editMediaButton);
|
||||
_editMedia.create(this, Ui::AttachControls::Type::EditOnly);
|
||||
_editFile.create(this, st::editMediaButton);
|
||||
updateEditMediaButton();
|
||||
_editMedia->setClickedCallback(
|
||||
_editFile->setClickedCallback(
|
||||
App::LambdaDelayed(
|
||||
st::historyAttach.ripple.hideDuration,
|
||||
this,
|
||||
buttonCallback));
|
||||
|
||||
_editMedia->editRequests(
|
||||
) | rpl::start_with_next(buttonCallback, _editMedia->lifetime());
|
||||
|
||||
_photoEditorButton = base::make_unique_q<Ui::AbstractButton>(this);
|
||||
_photoEditorButton->clicks(
|
||||
) | rpl::to_empty | rpl::start_to_stream(
|
||||
_photoEditorOpens,
|
||||
_photoEditorButton->lifetime());
|
||||
|
||||
_photoEditorButton->raise();
|
||||
_editMedia->raise();
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepare() {
|
||||
@@ -728,6 +802,7 @@ void EditCaptionBox::prepare() {
|
||||
auto cursor = _field->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
_field->setTextCursor(cursor);
|
||||
updateCaptionMaxHeight();
|
||||
|
||||
setupDragArea();
|
||||
}
|
||||
@@ -771,6 +846,28 @@ void EditCaptionBox::captionResized() {
|
||||
update();
|
||||
}
|
||||
|
||||
void EditCaptionBox::updateCaptionMaxHeight() {
|
||||
// Save.
|
||||
const auto wasCursor = _field->textCursor();
|
||||
const auto position = wasCursor.position();
|
||||
const auto anchor = wasCursor.anchor();
|
||||
const auto text = _field->getTextWithAppliedMarkdown();
|
||||
_field->setTextWithTags({});
|
||||
|
||||
_field->setMaxHeight(_doc
|
||||
? st::confirmCaptionArea.heightMax
|
||||
: (st::confirmEditCaptionAreaHeightMax - (_hintLabel->height() / 2)));
|
||||
|
||||
// Restore.
|
||||
_field->setTextWithTags(text);
|
||||
auto cursor = _field->textCursor();
|
||||
cursor.setPosition(anchor);
|
||||
if (position != anchor) {
|
||||
cursor.setPosition(position, QTextCursor::KeepAnchor);
|
||||
}
|
||||
_field->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void EditCaptionBox::setupEmojiPanel() {
|
||||
const auto container = getDelegate()->outerContainer();
|
||||
_emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||
@@ -843,6 +940,9 @@ void EditCaptionBox::updateBoxSize() {
|
||||
if (_photo) {
|
||||
newHeight += _wayWrap->height() / 2;
|
||||
}
|
||||
if (_hintLabel->toggled()) {
|
||||
newHeight += _hintLabel->height();
|
||||
}
|
||||
const auto &st = isThumbedLayout()
|
||||
? st::msgFileThumbLayout
|
||||
: st::msgFileLayout;
|
||||
@@ -887,39 +987,56 @@ void EditCaptionBox::startStreamedPlayer() {
|
||||
void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
const auto &padding = st::boxPhotoPadding;
|
||||
|
||||
Painter p(this);
|
||||
|
||||
if (_photo || _animated) {
|
||||
const auto th = std::max(_gifh, _thumbh);
|
||||
if (_thumbx > st::boxPhotoPadding.left()) {
|
||||
p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _thumbx - st::boxPhotoPadding.left(), th, st::confirmBg);
|
||||
if (_thumbx > padding.left()) {
|
||||
p.fillRect(
|
||||
padding.left(),
|
||||
padding.top(),
|
||||
_thumbx - padding.left(),
|
||||
th,
|
||||
st::confirmBg);
|
||||
}
|
||||
if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) {
|
||||
p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, th, st::confirmBg);
|
||||
if (_thumbx + _thumbw < width() - padding.right()) {
|
||||
p.fillRect(
|
||||
_thumbx + _thumbw,
|
||||
padding.top(),
|
||||
width() - padding.right() - _thumbx - _thumbw,
|
||||
th,
|
||||
st::confirmBg);
|
||||
}
|
||||
checkStreamedIsStarted();
|
||||
if (_streamed
|
||||
&& _streamed->player().ready()
|
||||
&& !_streamed->player().videoSize().isEmpty()) {
|
||||
const auto s = QSize(_gifw, _gifh);
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
|
||||
auto request = ::Media::Streaming::FrameRequest();
|
||||
request.outer = s * cIntRetinaFactor();
|
||||
request.resize = s * cIntRetinaFactor();
|
||||
p.drawImage(
|
||||
QRect(_gifx, st::boxPhotoPadding.top(), _gifw, _gifh),
|
||||
QRect(_gifx, padding.top(), _gifw, _gifh),
|
||||
_streamed->frame(request));
|
||||
if (!paused) {
|
||||
_streamed->markFrameShown();
|
||||
}
|
||||
} else {
|
||||
const auto offset = _gifh ? ((_gifh - _thumbh) / 2) : 0;
|
||||
p.drawPixmap(_thumbx, st::boxPhotoPadding.top() + offset, _thumb);
|
||||
p.drawPixmap(_thumbx, padding.top() + offset, _thumb);
|
||||
}
|
||||
if (_animated && !_streamed) {
|
||||
const auto &st = st::msgFileLayout;
|
||||
QRect inner(_thumbx + (_thumbw - st.thumbSize) / 2, st::boxPhotoPadding.top() + (th - st.thumbSize) / 2, st.thumbSize, st.thumbSize);
|
||||
QRect inner(
|
||||
_thumbx + (_thumbw - st.thumbSize) / 2,
|
||||
padding.top() + (th - st.thumbSize) / 2,
|
||||
st.thumbSize,
|
||||
st.thumbSize);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgDateImgBg);
|
||||
|
||||
@@ -935,21 +1052,26 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
const auto &st = isThumbedLayout()
|
||||
? st::msgFileThumbLayout
|
||||
: st::msgFileLayout;
|
||||
const auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
const auto w = width() - padding.left() - padding.right();
|
||||
const auto h = 0 + st.thumbSize + 0;
|
||||
const auto nameleft = 0 + st.thumbSize + st.padding.right();
|
||||
const auto nametop = st.nameTop - st.padding.top();
|
||||
const auto nameright = 0;
|
||||
const auto statustop = st.statusTop - st.padding.top();
|
||||
const auto editButton = _isAllowedEditMedia
|
||||
? _editMedia->width() + st::editMediaButtonSkip
|
||||
? _editFile->width() + st::editMediaButtonSkip
|
||||
: 0;
|
||||
const auto namewidth = w - nameleft - editButton;
|
||||
const auto x = (width() - w) / 2, y = st::boxPhotoPadding.top();
|
||||
const auto x = (width() - w) / 2, y = padding.top();
|
||||
|
||||
// Ui::FillRoundCorner(p, x, y, w, h, st::msgInBg, Ui::MessageInCorners, &st::msgInShadow);
|
||||
|
||||
const auto rthumb = style::rtlrect(x + 0, y + 0, st.thumbSize, st.thumbSize, width());
|
||||
const auto rthumb = style::rtlrect(
|
||||
x + 0,
|
||||
y + 0,
|
||||
st.thumbSize,
|
||||
st.thumbSize,
|
||||
width());
|
||||
if (isThumbedLayout()) {
|
||||
p.drawPixmap(rthumb.topLeft(), _thumb);
|
||||
} else {
|
||||
@@ -972,7 +1094,12 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
p.setFont(st::semiboldFont);
|
||||
p.setPen(st::historyFileNameInFg);
|
||||
_name.drawLeftElided(p, x + nameleft, y + nametop, namewidth, width());
|
||||
_name.drawLeftElided(
|
||||
p,
|
||||
x + nameleft,
|
||||
y + nametop,
|
||||
namewidth,
|
||||
width());
|
||||
|
||||
const auto &status = st::mediaInFg;
|
||||
p.setFont(st::normalFont);
|
||||
@@ -981,38 +1108,67 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
} else {
|
||||
p.setFont(st::boxTitleFont);
|
||||
p.setPen(st::boxTextFg);
|
||||
p.drawTextLeft(_field->x(), st::boxPhotoPadding.top(), width(), tr::lng_edit_message(tr::now));
|
||||
p.drawTextLeft(
|
||||
_field->x(),
|
||||
padding.top(),
|
||||
width(),
|
||||
tr::lng_edit_message(tr::now));
|
||||
}
|
||||
|
||||
if (!_error.isEmpty()) {
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawTextLeft(_field->x(), _field->y() + _field->height() + errorTopSkip(), width(), _error);
|
||||
p.drawTextLeft(
|
||||
_field->x(),
|
||||
_field->y() + _field->height() + errorTopSkip(),
|
||||
width(),
|
||||
_error);
|
||||
}
|
||||
|
||||
if (_isAllowedEditMedia) {
|
||||
_editMedia->moveToRight(
|
||||
st::boxPhotoPadding.right() + (_doc
|
||||
? st::editMediaButtonFileSkipRight
|
||||
: st::editMediaButtonSkip),
|
||||
st::boxPhotoPadding.top() + (_doc
|
||||
? st::editMediaButtonFileSkipTop
|
||||
: st::editMediaButtonSkip));
|
||||
if (_doc) {
|
||||
_editFile->moveToRight(
|
||||
padding.right() + st::editMediaButtonFileSkipRight,
|
||||
padding.top() + st::editMediaButtonFileSkipTop);
|
||||
} else {
|
||||
_editMedia->moveToRight(
|
||||
padding.right() + st::editMediaButtonSkip,
|
||||
padding.top() + st::editMediaButtonSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
const auto previewBottom = st::boxPhotoPadding.top() + _thumbh;
|
||||
const auto hintToggled = _hintLabel->toggled();
|
||||
|
||||
if (hintToggled) {
|
||||
_hintLabel->resize(st::sendMediaPreviewSize, _hintLabel->height());
|
||||
_hintLabel->moveToLeft(st::boxPhotoPadding.left(), previewBottom);
|
||||
}
|
||||
|
||||
if (_photo) {
|
||||
_wayWrap->resize(st::sendMediaPreviewSize, _wayWrap->height());
|
||||
_wayWrap->moveToLeft(
|
||||
st::boxPhotoPadding.left(),
|
||||
st::boxPhotoPadding.top() + _thumbh);
|
||||
hintToggled
|
||||
? _hintLabel->y() + _hintLabel->height()
|
||||
: previewBottom);
|
||||
|
||||
_photoEditorButton->resize(_thumbw, _thumbh);
|
||||
_photoEditorButton->moveToLeft(_thumbx, st::boxPhotoPadding.top());
|
||||
}
|
||||
|
||||
_field->resize(st::sendMediaPreviewSize, _field->height());
|
||||
_field->moveToLeft(st::boxPhotoPadding.left(), height() - st::normalFont->height - errorTopSkip() - _field->height());
|
||||
_field->moveToLeft(
|
||||
st::boxPhotoPadding.left(),
|
||||
height()
|
||||
- st::normalFont->height
|
||||
- errorTopSkip()
|
||||
- _field->height());
|
||||
|
||||
_emojiToggle->moveToLeft(
|
||||
(st::boxPhotoPadding.left()
|
||||
+ st::sendMediaPreviewSize
|
||||
@@ -1048,6 +1204,11 @@ void EditCaptionBox::save() {
|
||||
action.options = options;
|
||||
action.replaceMediaOf = item->fullId().msg;
|
||||
|
||||
if (Storage::ApplyModifications(_preparedList)) {
|
||||
_controller->session().settings().incrementPhotoEditorHintShown();
|
||||
_controller->session().saveSettings();
|
||||
}
|
||||
|
||||
_controller->session().api().editMedia(
|
||||
std::move(_preparedList),
|
||||
(!_asFile && _photo) ? SendMediaType::Photo : SendMediaType::File,
|
||||
@@ -1099,8 +1260,10 @@ void EditCaptionBox::setName(QString nameString, qint64 size) {
|
||||
}
|
||||
|
||||
void EditCaptionBox::keyPressEvent(QKeyEvent *e) {
|
||||
if ((e->key() == Qt::Key_E || e->key() == Qt::Key_O)
|
||||
&& e->modifiers() == Qt::ControlModifier) {
|
||||
const auto ctrl = e->modifiers().testFlag(Qt::ControlModifier);
|
||||
if ((e->key() == Qt::Key_E) && ctrl) {
|
||||
_photoEditorOpens.fire({});
|
||||
} else if ((e->key() == Qt::Key_O) && ctrl) {
|
||||
_editMediaClicks.fire({});
|
||||
} else {
|
||||
e->ignore();
|
||||
|
||||
@@ -29,11 +29,13 @@ class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class AbstractButton;
|
||||
class InputField;
|
||||
class EmojiButton;
|
||||
class IconButton;
|
||||
class Checkbox;
|
||||
enum class AlbumType;
|
||||
class AttachControlsWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
@@ -90,6 +92,7 @@ private:
|
||||
bool fileFromClipboard(not_null<const QMimeData*> data);
|
||||
void updateEditPreview();
|
||||
void updateEditMediaButton();
|
||||
void updateCaptionMaxHeight();
|
||||
|
||||
int errorTopSkip() const;
|
||||
|
||||
@@ -122,6 +125,7 @@ private:
|
||||
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
base::unique_qptr<QObject> _emojiFilter;
|
||||
base::unique_qptr<Ui::AbstractButton> _photoEditorButton;
|
||||
|
||||
int _thumbx = 0;
|
||||
int _thumbw = 0;
|
||||
@@ -139,13 +143,16 @@ private:
|
||||
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
|
||||
object_ptr<Ui::IconButton> _editMedia = nullptr;
|
||||
object_ptr<Ui::AttachControlsWidget> _editMedia = nullptr;
|
||||
object_ptr<Ui::IconButton> _editFile = nullptr;
|
||||
Ui::SlideWrap<Ui::RpWidget> *_wayWrap = nullptr;
|
||||
Ui::SlideWrap<Ui::RpWidget> *_hintLabel = nullptr;
|
||||
QString _newMediaPath;
|
||||
Ui::AlbumType _albumType = Ui::AlbumType();
|
||||
bool _isAllowedEditMedia = false;
|
||||
bool _asFile = false;
|
||||
rpl::event_stream<> _editMediaClicks;
|
||||
rpl::event_stream<> _photoEditorOpens;
|
||||
|
||||
QString _error;
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_media_view.h"
|
||||
|
||||
@@ -423,7 +422,7 @@ void EditColorBox::Slider::generatePixmap() {
|
||||
if (!isHorizontal()) {
|
||||
image = std::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0));
|
||||
}
|
||||
_pixmap = App::pixmapFromImageInPlace(std::move(image));
|
||||
_pixmap = Ui::PixmapFromImage(std::move(image));
|
||||
} else if (_type == Type::Opacity) {
|
||||
auto color = anim::shifted(QColor(255, 255, 255, 255));
|
||||
auto transparent = anim::shifted(QColor(255, 255, 255, 0));
|
||||
@@ -459,7 +458,7 @@ void EditColorBox::Slider::generatePixmap() {
|
||||
if (!isHorizontal()) {
|
||||
image = std::move(image).transformed(QTransform(0, -1, 1, 0, 0, 0));
|
||||
}
|
||||
_pixmap = App::pixmapFromImageInPlace(std::move(image));
|
||||
_pixmap = Ui::PixmapFromImage(std::move(image));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +529,7 @@ void EditColorBox::Slider::setLightnessLimits(int min, int max) {
|
||||
}
|
||||
|
||||
void EditColorBox::Slider::updatePixmapFromMask() {
|
||||
_pixmap = App::pixmapFromImageInPlace(style::colorizeImage(_mask, _color));
|
||||
_pixmap = Ui::PixmapFromImage(style::colorizeImage(_mask, _color));
|
||||
}
|
||||
|
||||
void EditColorBox::Slider::updateCurrentPoint(QPoint localPosition) {
|
||||
|
||||
@@ -495,6 +495,7 @@ object_ptr<Ui::RpWidget> Controller::createPhotoEdit() {
|
||||
_wrap,
|
||||
object_ptr<Ui::UserpicButton>(
|
||||
_wrap,
|
||||
&_navigation->parentController()->window(),
|
||||
_peer,
|
||||
Ui::UserpicButton::Role::ChangePhoto,
|
||||
st::defaultUserpicButton),
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/photo_crop_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
PhotoCropBox::PhotoCropBox(
|
||||
QWidget*,
|
||||
const QImage &img,
|
||||
const QString &title)
|
||||
: _title(title)
|
||||
, _img(img) {
|
||||
}
|
||||
|
||||
void PhotoCropBox::prepare() {
|
||||
addButton(tr::lng_settings_save(), [this] { sendPhoto(); });
|
||||
addButton(tr::lng_cancel(), [this] { closeBox(); });
|
||||
|
||||
int32 s = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
_thumb = App::pixmapFromImageInPlace(_img.scaled(s * cIntRetinaFactor(), s * cIntRetinaFactor(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
_thumb.setDevicePixelRatio(cRetinaFactor());
|
||||
_mask = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
_mask.setDevicePixelRatio(cRetinaFactor());
|
||||
_fade = QImage(_thumb.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
_fade.setDevicePixelRatio(cRetinaFactor());
|
||||
_thumbw = _thumb.width() / cIntRetinaFactor();
|
||||
_thumbh = _thumb.height() / cIntRetinaFactor();
|
||||
if (_thumbw > _thumbh) {
|
||||
_cropw = _thumbh - 20;
|
||||
} else {
|
||||
_cropw = _thumbw - 20;
|
||||
}
|
||||
_cropx = (_thumbw - _cropw) / 2;
|
||||
_cropy = (_thumbh - _cropw) / 2;
|
||||
|
||||
_thumbx = (st::boxWideWidth - _thumbw) / 2;
|
||||
_thumby = st::boxPhotoPadding.top();
|
||||
setMouseTracking(true);
|
||||
|
||||
setDimensions(st::boxWideWidth, st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom() + st::boxTextFont->height + st::cropSkip);
|
||||
}
|
||||
|
||||
void PhotoCropBox::mousePressEvent(QMouseEvent *e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
_downState = mouseState(e->pos());
|
||||
_fromposx = e->pos().x();
|
||||
_fromposy = e->pos().y();
|
||||
_fromcropx = _cropx;
|
||||
_fromcropy = _cropy;
|
||||
_fromcropw = _cropw;
|
||||
}
|
||||
return BoxContent::mousePressEvent(e);
|
||||
}
|
||||
|
||||
int PhotoCropBox::mouseState(QPoint p) {
|
||||
p -= QPoint(_thumbx, _thumby);
|
||||
int32 delta = st::cropPointSize, mdelta(-delta / 2);
|
||||
if (QRect(_cropx + mdelta, _cropy + mdelta, delta, delta).contains(p)) {
|
||||
return 1;
|
||||
} else if (QRect(_cropx + _cropw + mdelta, _cropy + mdelta, delta, delta).contains(p)) {
|
||||
return 2;
|
||||
} else if (QRect(_cropx + _cropw + mdelta, _cropy + _cropw + mdelta, delta, delta).contains(p)) {
|
||||
return 3;
|
||||
} else if (QRect(_cropx + mdelta, _cropy + _cropw + mdelta, delta, delta).contains(p)) {
|
||||
return 4;
|
||||
} else if (QRect(_cropx, _cropy, _cropw, _cropw).contains(p)) {
|
||||
return 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
rpl::producer<QImage> PhotoCropBox::ready() const {
|
||||
return _readyImages.events();
|
||||
}
|
||||
|
||||
void PhotoCropBox::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (_downState) {
|
||||
_downState = 0;
|
||||
mouseMoveEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoCropBox::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_downState && !(e->buttons() & Qt::LeftButton)) {
|
||||
mouseReleaseEvent(e);
|
||||
}
|
||||
if (_downState) {
|
||||
if (_downState == 1) {
|
||||
int32 dx = e->pos().x() - _fromposx, dy = e->pos().y() - _fromposy, d = (dx < dy) ? dx : dy;
|
||||
if (_fromcropx + d < 0) {
|
||||
d = -_fromcropx;
|
||||
}
|
||||
if (_fromcropy + d < 0) {
|
||||
d = -_fromcropy;
|
||||
}
|
||||
if (_fromcropw - d < st::cropMinSize) {
|
||||
d = _fromcropw - st::cropMinSize;
|
||||
}
|
||||
if (_cropx != _fromcropx + d || _cropy != _fromcropy + d || _cropw != _fromcropw - d) {
|
||||
_cropx = _fromcropx + d;
|
||||
_cropy = _fromcropy + d;
|
||||
_cropw = _fromcropw - d;
|
||||
update();
|
||||
}
|
||||
} else if (_downState == 2) {
|
||||
int32 dx = _fromposx - e->pos().x(), dy = e->pos().y() - _fromposy, d = (dx < dy) ? dx : dy;
|
||||
if (_fromcropx + _fromcropw - d > _thumbw) {
|
||||
d = _fromcropx + _fromcropw - _thumbw;
|
||||
}
|
||||
if (_fromcropy + d < 0) {
|
||||
d = -_fromcropy;
|
||||
}
|
||||
if (_fromcropw - d < st::cropMinSize) {
|
||||
d = _fromcropw - st::cropMinSize;
|
||||
}
|
||||
if (_cropy != _fromcropy + d || _cropw != _fromcropw - d) {
|
||||
_cropy = _fromcropy + d;
|
||||
_cropw = _fromcropw - d;
|
||||
update();
|
||||
}
|
||||
} else if (_downState == 3) {
|
||||
int32 dx = _fromposx - e->pos().x(), dy = _fromposy - e->pos().y(), d = (dx < dy) ? dx : dy;
|
||||
if (_fromcropx + _fromcropw - d > _thumbw) {
|
||||
d = _fromcropx + _fromcropw - _thumbw;
|
||||
}
|
||||
if (_fromcropy + _fromcropw - d > _thumbh) {
|
||||
d = _fromcropy + _fromcropw - _thumbh;
|
||||
}
|
||||
if (_fromcropw - d < st::cropMinSize) {
|
||||
d = _fromcropw - st::cropMinSize;
|
||||
}
|
||||
if (_cropw != _fromcropw - d) {
|
||||
_cropw = _fromcropw - d;
|
||||
update();
|
||||
}
|
||||
} else if (_downState == 4) {
|
||||
int32 dx = e->pos().x() - _fromposx, dy = _fromposy - e->pos().y(), d = (dx < dy) ? dx : dy;
|
||||
if (_fromcropx + d < 0) {
|
||||
d = -_fromcropx;
|
||||
}
|
||||
if (_fromcropy + _fromcropw - d > _thumbh) {
|
||||
d = _fromcropy + _fromcropw - _thumbh;
|
||||
}
|
||||
if (_fromcropw - d < st::cropMinSize) {
|
||||
d = _fromcropw - st::cropMinSize;
|
||||
}
|
||||
if (_cropx != _fromcropx + d || _cropw != _fromcropw - d) {
|
||||
_cropx = _fromcropx + d;
|
||||
_cropw = _fromcropw - d;
|
||||
update();
|
||||
}
|
||||
} else if (_downState == 5) {
|
||||
int32 dx = e->pos().x() - _fromposx, dy = e->pos().y() - _fromposy;
|
||||
if (_fromcropx + dx < 0) {
|
||||
dx = -_fromcropx;
|
||||
} else if (_fromcropx + _fromcropw + dx > _thumbw) {
|
||||
dx = _thumbw - _fromcropx - _fromcropw;
|
||||
}
|
||||
if (_fromcropy + dy < 0) {
|
||||
dy = -_fromcropy;
|
||||
} else if (_fromcropy + _fromcropw + dy > _thumbh) {
|
||||
dy = _thumbh - _fromcropy - _fromcropw;
|
||||
}
|
||||
if (_cropx != _fromcropx + dx || _cropy != _fromcropy + dy) {
|
||||
_cropx = _fromcropx + dx;
|
||||
_cropy = _fromcropy + dy;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
int32 cursorState = _downState ? _downState : mouseState(e->pos());
|
||||
QCursor cur(style::cur_default);
|
||||
if (cursorState == 1 || cursorState == 3) {
|
||||
cur = style::cur_sizefdiag;
|
||||
} else if (cursorState == 2 || cursorState == 4) {
|
||||
cur = style::cur_sizebdiag;
|
||||
} else if (cursorState == 5) {
|
||||
cur = style::cur_sizeall;
|
||||
}
|
||||
setCursor(cur);
|
||||
}
|
||||
|
||||
void PhotoCropBox::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
sendPhoto();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoCropBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
p.setFont(st::boxTextFont);
|
||||
p.setPen(st::boxPhotoTextFg);
|
||||
p.drawText(QRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top() + _thumbh + st::boxPhotoPadding.bottom(), width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), st::boxTextFont->height), _title, style::al_top);
|
||||
|
||||
p.translate(_thumbx, _thumby);
|
||||
p.drawPixmap(0, 0, _thumb);
|
||||
_mask.fill(Qt::white);
|
||||
{
|
||||
Painter p(&_mask);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(Qt::black);
|
||||
p.drawEllipse(_cropx, _cropy, _cropw, _cropw);
|
||||
}
|
||||
style::colorizeImage(_mask, st::photoCropFadeBg->c, &_fade);
|
||||
p.drawImage(0, 0, _fade);
|
||||
|
||||
int delta = st::cropPointSize;
|
||||
int mdelta = -delta / 2;
|
||||
p.fillRect(QRect(_cropx + mdelta, _cropy + mdelta, delta, delta), st::photoCropPointFg);
|
||||
p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + mdelta, delta, delta), st::photoCropPointFg);
|
||||
p.fillRect(QRect(_cropx + _cropw + mdelta, _cropy + _cropw + mdelta, delta, delta), st::photoCropPointFg);
|
||||
p.fillRect(QRect(_cropx + mdelta, _cropy + _cropw + mdelta, delta, delta), st::photoCropPointFg);
|
||||
}
|
||||
|
||||
void PhotoCropBox::sendPhoto() {
|
||||
auto from = _img;
|
||||
if (_img.width() < _thumb.width()) {
|
||||
from = _thumb.toImage();
|
||||
}
|
||||
float64 x = float64(_cropx) / _thumbw, y = float64(_cropy) / _thumbh, w = float64(_cropw) / _thumbw;
|
||||
int32 ix = int32(x * from.width()), iy = int32(y * from.height()), iw = int32(w * from.width());
|
||||
if (ix < 0) {
|
||||
ix = 0;
|
||||
}
|
||||
if (ix + iw > from.width()) {
|
||||
iw = from.width() - ix;
|
||||
}
|
||||
if (iy < 0) {
|
||||
iy = 0;
|
||||
}
|
||||
if (iy + iw > from.height()) {
|
||||
iw = from.height() - iy;
|
||||
}
|
||||
int32 offset = ix * from.depth() / 8 + iy * from.bytesPerLine();
|
||||
QImage cropped(from.constBits() + offset, iw, iw, from.bytesPerLine(), from.format()), tosend;
|
||||
if (from.format() == QImage::Format_Indexed8) {
|
||||
cropped.setColorCount(from.colorCount());
|
||||
cropped.setColorTable(from.colorTable());
|
||||
}
|
||||
if (cropped.width() > 1280) {
|
||||
tosend = cropped.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
} else if (cropped.width() < 320) {
|
||||
tosend = cropped.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
} else {
|
||||
tosend = cropped.copy();
|
||||
}
|
||||
|
||||
auto weak = Ui::MakeWeak(this);
|
||||
_readyImages.fire(std::move(tosend));
|
||||
if (weak) {
|
||||
closeBox();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
class PhotoCropBox : public Ui::BoxContent {
|
||||
public:
|
||||
PhotoCropBox(QWidget*, const QImage &img, const QString &title);
|
||||
|
||||
int32 mouseState(QPoint p);
|
||||
|
||||
rpl::producer<QImage> ready() const;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
|
||||
private:
|
||||
void sendPhoto();
|
||||
|
||||
QString _title;
|
||||
int32 _downState = 0;
|
||||
int32 _thumbx, _thumby, _thumbw, _thumbh;
|
||||
int32 _cropx, _cropy, _cropw;
|
||||
int32 _fromposx, _fromposy, _fromcropx, _fromcropy, _fromcropw;
|
||||
QImage _img;
|
||||
QPixmap _thumb;
|
||||
QImage _mask, _fade;
|
||||
rpl::event_stream<QImage> _readyImages;
|
||||
|
||||
};
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/send_context_menu.h"
|
||||
@@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "confirm_box.h"
|
||||
#include "editor/photo_editor_layer_widget.h"
|
||||
#include "history/history_drag_area.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "core/file_utilities.h"
|
||||
@@ -187,6 +189,22 @@ rpl::producer<int> SendFilesBox::Block::itemReplaceRequest() const {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto preview = _preview.get();
|
||||
const auto from = _from;
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(preview);
|
||||
return album->thumbModified() | rpl::map(_1 + from);
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->modifyRequests() | rpl::map_to(from);
|
||||
} else {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
|
||||
if (!_isAlbum) {
|
||||
return;
|
||||
@@ -601,6 +619,16 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
FileDialog::AllOrImagesFilter(),
|
||||
crl::guard(this, callback));
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemModifyRequest(
|
||||
) | rpl::start_with_next([=, controller = _controller](int index) {
|
||||
Editor::OpenWithPreparedFile(
|
||||
this,
|
||||
controller,
|
||||
&_list.files[index],
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { refreshAllAfterChanges(from); });
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshControls() {
|
||||
@@ -640,6 +668,11 @@ void SendFilesBox::setupSendWayControls() {
|
||||
sendWay.setSendImagesAsPhotos(_sendImagesAsPhotos->checked());
|
||||
_sendWay = sendWay;
|
||||
}, lifetime());
|
||||
|
||||
_hintLabel.create(
|
||||
this,
|
||||
tr::lng_edit_photo_editor_hint(tr::now),
|
||||
st::editMediaHintLabel);
|
||||
}
|
||||
|
||||
void SendFilesBox::updateSendWayControlsVisibility() {
|
||||
@@ -647,6 +680,11 @@ void SendFilesBox::updateSendWayControlsVisibility() {
|
||||
_groupFiles->setVisible(_list.hasGroupOption(onlyOne));
|
||||
_sendImagesAsPhotos->setVisible(
|
||||
_list.hasSendImagesAsPhotosOption(onlyOne));
|
||||
|
||||
_hintLabel->setVisible(
|
||||
_controller->session().settings().photoEditorHintShown()
|
||||
? _list.hasSendImagesAsPhotosOption(false)
|
||||
: false);
|
||||
}
|
||||
|
||||
void SendFilesBox::setupCaption() {
|
||||
@@ -850,14 +888,15 @@ void SendFilesBox::updateBoxSize() {
|
||||
if (_caption) {
|
||||
footerHeight += st::boxPhotoCaptionSkip + _caption->height();
|
||||
}
|
||||
const auto pointers = {
|
||||
_groupFiles.data(),
|
||||
_sendImagesAsPhotos.data(),
|
||||
};
|
||||
for (auto pointer : pointers) {
|
||||
const auto pairs = std::array<std::pair<RpWidget*, int>, 3>{ {
|
||||
{ _groupFiles.data(), st::boxPhotoCompressedSkip },
|
||||
{ _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
|
||||
{ _hintLabel.data(), st::editMediaLabelMargins.top() },
|
||||
} };
|
||||
for (const auto &pair : pairs) {
|
||||
const auto pointer = pair.first;
|
||||
if (pointer && !pointer->isHidden()) {
|
||||
footerHeight += st::boxPhotoCompressedSkip
|
||||
+ pointer->heightNoMargins();
|
||||
footerHeight += pair.second + pointer->heightNoMargins();
|
||||
}
|
||||
}
|
||||
_footerHeight = footerHeight;
|
||||
@@ -916,16 +955,18 @@ void SendFilesBox::updateControlsGeometry() {
|
||||
_emojiToggle->update();
|
||||
}
|
||||
}
|
||||
const auto pointers = {
|
||||
_groupFiles.data(),
|
||||
_sendImagesAsPhotos.data(),
|
||||
};
|
||||
for (const auto pointer : ranges::views::reverse(pointers)) {
|
||||
const auto pairs = std::array<std::pair<RpWidget*, int>, 3>{ {
|
||||
{ _hintLabel.data(), st::editMediaLabelMargins.top() },
|
||||
{ _groupFiles.data(), st::boxPhotoCompressedSkip },
|
||||
{ _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
|
||||
} };
|
||||
for (const auto &pair : ranges::views::reverse(pairs)) {
|
||||
const auto pointer = pair.first;
|
||||
if (pointer && !pointer->isHidden()) {
|
||||
pointer->moveToLeft(
|
||||
st::boxPhotoPadding.left(),
|
||||
bottom - pointer->heightNoMargins());
|
||||
bottom -= st::boxPhotoCompressedSkip + pointer->heightNoMargins();
|
||||
bottom -= pair.second + pointer->heightNoMargins();
|
||||
}
|
||||
}
|
||||
_scroll->resize(width(), bottom - _titleHeight.current());
|
||||
@@ -976,6 +1017,12 @@ void SendFilesBox::send(
|
||||
for (auto &block : _blocks) {
|
||||
block.applyAlbumOrder();
|
||||
}
|
||||
|
||||
if (Storage::ApplyModifications(_list)) {
|
||||
_controller->session().settings().incrementPhotoEditorHintShown();
|
||||
_controller->session().saveSettings();
|
||||
}
|
||||
|
||||
_confirmed = true;
|
||||
if (_confirmedCallback) {
|
||||
auto caption = (_caption && !_caption->isHidden())
|
||||
|
||||
@@ -35,6 +35,7 @@ struct GroupMediaLayout;
|
||||
class EmojiButton;
|
||||
class AlbumPreview;
|
||||
class VerticalLayout;
|
||||
class FlatLabel;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
@@ -102,6 +103,7 @@ private:
|
||||
|
||||
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
void applyAlbumOrder();
|
||||
@@ -183,6 +185,7 @@ private:
|
||||
|
||||
object_ptr<Ui::Checkbox> _groupFiles = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _sendImagesAsPhotos = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _hintLabel = { nullptr };
|
||||
rpl::variable<Ui::SendFilesWay> _sendWay = Ui::SendFilesWay();
|
||||
|
||||
rpl::variable<int> _footerHeight = 0;
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
@@ -68,10 +69,17 @@ public:
|
||||
|
||||
void install();
|
||||
[[nodiscard]] rpl::producer<uint64> setInstalled() const;
|
||||
[[nodiscard]] rpl::producer<uint64> setArchived() const;
|
||||
[[nodiscard]] rpl::producer<> updateControls() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<Error> errors() const;
|
||||
|
||||
void archiveStickers();
|
||||
|
||||
bool isMasksSet() const {
|
||||
return (_setFlags & MTPDstickerSet::Flag::f_masks);
|
||||
}
|
||||
|
||||
~Inner();
|
||||
|
||||
protected:
|
||||
@@ -104,10 +112,6 @@ private:
|
||||
void gotSet(const MTPmessages_StickerSet &set);
|
||||
void installDone(const MTPmessages_StickerSetInstallResult &result);
|
||||
|
||||
bool isMasksSet() const {
|
||||
return (_setFlags & MTPDstickerSet::Flag::f_masks);
|
||||
}
|
||||
|
||||
not_null<Lottie::MultiPlayer*> getLottiePlayer();
|
||||
|
||||
void showPreview();
|
||||
@@ -128,6 +132,8 @@ private:
|
||||
TimeId _setInstallDate = TimeId(0);
|
||||
ImageWithLocation _setThumbnail;
|
||||
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
|
||||
MTPInputStickerSet _input;
|
||||
|
||||
mtpRequestId _installRequest = 0;
|
||||
@@ -138,6 +144,7 @@ private:
|
||||
int _previewShown = -1;
|
||||
|
||||
rpl::event_stream<uint64> _setInstalled;
|
||||
rpl::event_stream<uint64> _setArchived;
|
||||
rpl::event_stream<> _updateControls;
|
||||
rpl::event_stream<Error> _errors;
|
||||
|
||||
@@ -186,7 +193,12 @@ void StickerSetBox::prepare() {
|
||||
|
||||
_inner->setInstalled(
|
||||
) | rpl::start_with_next([=](uint64 setId) {
|
||||
_controller->session().api().stickerSetInstalled(setId);
|
||||
if (_inner->isMasksSet()) {
|
||||
Ui::Toast::Show(tr::lng_masks_installed(tr::now));
|
||||
} else {
|
||||
auto &stickers = _controller->session().data().stickers();
|
||||
stickers.notifyStickerSetInstalled(setId);
|
||||
}
|
||||
closeBox();
|
||||
}, lifetime());
|
||||
|
||||
@@ -194,6 +206,36 @@ void StickerSetBox::prepare() {
|
||||
) | rpl::start_with_next([=](Error error) {
|
||||
handleError(error);
|
||||
}, lifetime());
|
||||
|
||||
_inner->setArchived(
|
||||
) | rpl::start_with_next([=](uint64 setId) {
|
||||
const auto isMasks = _inner->isMasksSet();
|
||||
|
||||
Ui::Toast::Show(isMasks
|
||||
? tr::lng_masks_has_been_archived(tr::now)
|
||||
: tr::lng_stickers_has_been_archived(tr::now));
|
||||
|
||||
auto &order = isMasks
|
||||
? _controller->session().data().stickers().maskSetsOrderRef()
|
||||
: _controller->session().data().stickers().setsOrderRef();
|
||||
const auto index = order.indexOf(setId);
|
||||
if (index != -1) {
|
||||
order.removeAt(index);
|
||||
|
||||
auto &local = _controller->session().local();
|
||||
if (isMasks) {
|
||||
local.writeInstalledMasks();
|
||||
local.writeArchivedMasks();
|
||||
} else {
|
||||
local.writeInstalledStickers();
|
||||
local.writeArchivedStickers();
|
||||
}
|
||||
}
|
||||
|
||||
_controller->session().data().stickers().notifyUpdated();
|
||||
|
||||
closeBox();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickerSetBox::addStickers() {
|
||||
@@ -220,38 +262,6 @@ void StickerSetBox::handleError(Error error) {
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::archiveStickers() {
|
||||
const auto weak = base::make_weak(_controller.get());
|
||||
const auto setId = _set.c_inputStickerSetID().vid().v;
|
||||
_controller->session().api().request(MTPmessages_InstallStickerSet(
|
||||
_set,
|
||||
MTP_boolTrue()
|
||||
)).done([=](const MTPmessages_StickerSetInstallResult &result) {
|
||||
const auto controller = weak.get();
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) {
|
||||
Ui::Toast::Show(tr::lng_stickers_has_been_archived(tr::now));
|
||||
|
||||
const auto &session = controller->session();
|
||||
auto &order = session.data().stickers().setsOrderRef();
|
||||
const auto index = order.indexOf(setId);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
order.removeAt(index);
|
||||
|
||||
session.local().writeInstalledStickers();
|
||||
session.local().writeArchivedStickers();
|
||||
|
||||
session.data().stickers().notifyUpdated();
|
||||
}
|
||||
}).fail([](const MTP::Error &error) {
|
||||
Ui::Toast::Show(Lang::Hard::ServerError());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void StickerSetBox::updateTitleAndButtons() {
|
||||
setTitle(_inner->title());
|
||||
updateButtons();
|
||||
@@ -260,8 +270,12 @@ void StickerSetBox::updateTitleAndButtons() {
|
||||
void StickerSetBox::updateButtons() {
|
||||
clearButtons();
|
||||
if (_inner->loaded()) {
|
||||
const auto isMasks = _inner->isMasksSet();
|
||||
if (_inner->notInstalled()) {
|
||||
addButton(tr::lng_stickers_add_pack(), [=] { addStickers(); });
|
||||
auto addText = isMasks
|
||||
? tr::lng_stickers_add_masks()
|
||||
: tr::lng_stickers_add_pack();
|
||||
addButton(std::move(addText), [=] { addStickers(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
if (!_inner->shortName().isEmpty()) {
|
||||
@@ -276,7 +290,9 @@ void StickerSetBox::updateButtons() {
|
||||
top->setClickedCallback([=] {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(top);
|
||||
(*menu)->addAction(
|
||||
tr::lng_stickers_share_pack(tr::now),
|
||||
(isMasks
|
||||
? tr::lng_stickers_share_masks
|
||||
: tr::lng_stickers_share_pack)(tr::now),
|
||||
share);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
@@ -289,21 +305,25 @@ void StickerSetBox::updateButtons() {
|
||||
copyStickersLink();
|
||||
Ui::Toast::Show(tr::lng_stickers_copied(tr::now));
|
||||
};
|
||||
addButton(tr::lng_stickers_share_pack(), std::move(share));
|
||||
auto shareText = isMasks
|
||||
? tr::lng_stickers_share_masks()
|
||||
: tr::lng_stickers_share_pack();
|
||||
addButton(std::move(shareText), std::move(share));
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
if (!_inner->shortName().isEmpty()) {
|
||||
const auto top = addTopButton(st::infoTopBarMenu);
|
||||
const auto archive = [=] {
|
||||
archiveStickers();
|
||||
closeBox();
|
||||
_inner->archiveStickers();
|
||||
};
|
||||
const auto menu =
|
||||
std::make_shared<base::unique_qptr<Ui::PopupMenu>>();
|
||||
top->setClickedCallback([=] {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(top);
|
||||
(*menu)->addAction(
|
||||
tr::lng_stickers_archive_pack(tr::now),
|
||||
isMasks
|
||||
? tr::lng_masks_archive_pack(tr::now)
|
||||
: tr::lng_stickers_archive_pack(tr::now),
|
||||
archive);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
@@ -328,6 +348,10 @@ StickerSetBox::Inner::Inner(
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _api(&_controller->session().mtp())
|
||||
, _pathGradient(std::make_unique<Ui::PathShiftGradient>(
|
||||
st::windowBgRipple,
|
||||
st::windowBgOver,
|
||||
[=] { update(); }))
|
||||
, _input(set)
|
||||
, _previewTimer([=] { showPreview(); }) {
|
||||
set.match([&](const MTPDinputStickerSetID &data) {
|
||||
@@ -457,6 +481,10 @@ rpl::producer<uint64> StickerSetBox::Inner::setInstalled() const {
|
||||
return _setInstalled.events();
|
||||
}
|
||||
|
||||
rpl::producer<uint64> StickerSetBox::Inner::setArchived() const {
|
||||
return _setArchived.events();
|
||||
}
|
||||
|
||||
rpl::producer<> StickerSetBox::Inner::updateControls() const {
|
||||
return _updateControls.events();
|
||||
}
|
||||
@@ -467,13 +495,19 @@ rpl::producer<StickerSetBox::Error> StickerSetBox::Inner::errors() const {
|
||||
|
||||
void StickerSetBox::Inner::installDone(
|
||||
const MTPmessages_StickerSetInstallResult &result) {
|
||||
auto &sets = _controller->session().data().stickers().setsRef();
|
||||
auto &stickers = _controller->session().data().stickers();
|
||||
auto &sets = stickers.setsRef();
|
||||
const auto isMasks = isMasksSet();
|
||||
|
||||
bool wasArchived = (_setFlags & MTPDstickerSet::Flag::f_archived);
|
||||
const bool wasArchived = (_setFlags & MTPDstickerSet::Flag::f_archived);
|
||||
if (wasArchived) {
|
||||
auto index = _controller->session().data().stickers().archivedSetsOrderRef().indexOf(_setId);
|
||||
const auto index = (isMasks
|
||||
? stickers.archivedMaskSetsOrderRef()
|
||||
: stickers.archivedSetsOrderRef()).indexOf(_setId);
|
||||
if (index >= 0) {
|
||||
_controller->session().data().stickers().archivedSetsOrderRef().removeAt(index);
|
||||
(isMasks
|
||||
? stickers.archivedMaskSetsOrderRef()
|
||||
: stickers.archivedSetsOrderRef()).removeAt(index);
|
||||
}
|
||||
}
|
||||
_setInstallDate = base::unixtime::now();
|
||||
@@ -502,8 +536,10 @@ void StickerSetBox::Inner::installDone(
|
||||
set->stickers = _pack;
|
||||
set->emoji = _emoji;
|
||||
|
||||
auto &order = _controller->session().data().stickers().setsOrderRef();
|
||||
int insertAtIndex = 0, currentIndex = order.indexOf(_setId);
|
||||
auto &order = isMasks
|
||||
? stickers.maskSetsOrderRef()
|
||||
: stickers.setsOrderRef();
|
||||
const auto insertAtIndex = 0, currentIndex = order.indexOf(_setId);
|
||||
if (currentIndex != insertAtIndex) {
|
||||
if (currentIndex > 0) {
|
||||
order.removeAt(currentIndex);
|
||||
@@ -515,8 +551,10 @@ void StickerSetBox::Inner::installDone(
|
||||
if (customIt != sets.cend()) {
|
||||
const auto custom = customIt->second.get();
|
||||
for (const auto sticker : std::as_const(_pack)) {
|
||||
int removeIndex = custom->stickers.indexOf(sticker);
|
||||
if (removeIndex >= 0) custom->stickers.removeAt(removeIndex);
|
||||
const int removeIndex = custom->stickers.indexOf(sticker);
|
||||
if (removeIndex >= 0) {
|
||||
custom->stickers.removeAt(removeIndex);
|
||||
}
|
||||
}
|
||||
if (custom->stickers.isEmpty()) {
|
||||
sets.erase(customIt);
|
||||
@@ -524,14 +562,23 @@ void StickerSetBox::Inner::installDone(
|
||||
}
|
||||
|
||||
if (result.type() == mtpc_messages_stickerSetInstallResultArchive) {
|
||||
_controller->session().data().stickers().applyArchivedResult(
|
||||
stickers.applyArchivedResult(
|
||||
result.c_messages_stickerSetInstallResultArchive());
|
||||
} else {
|
||||
auto &storage = _controller->session().local();
|
||||
if (wasArchived) {
|
||||
_controller->session().local().writeArchivedStickers();
|
||||
if (isMasks) {
|
||||
storage.writeArchivedMasks();
|
||||
} else {
|
||||
storage.writeArchivedStickers();
|
||||
}
|
||||
}
|
||||
_controller->session().local().writeInstalledStickers();
|
||||
_controller->session().data().stickers().notifyUpdated();
|
||||
if (isMasks) {
|
||||
storage.writeInstalledMasks();
|
||||
} else {
|
||||
storage.writeInstalledStickers();
|
||||
}
|
||||
stickers.notifyUpdated();
|
||||
}
|
||||
_setInstalled.fire_copy(_setId);
|
||||
}
|
||||
@@ -652,6 +699,8 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
+ ((_elements.size() % kStickersPanelPerRow) ? 1 : 0);
|
||||
int32 from = qFloor(e->rect().top() / st::stickersSize.height()), to = qFloor(e->rect().bottom() / st::stickersSize.height()) + 1;
|
||||
|
||||
_pathGradient->startFrame(0, width(), width() / 2);
|
||||
|
||||
for (int32 i = from; i < to; ++i) {
|
||||
for (int32 j = 0; j < kStickersPanelPerRow; ++j) {
|
||||
int32 index = i * kStickersPanelPerRow + j;
|
||||
@@ -747,7 +796,8 @@ void StickerSetBox::Inner::paintSticker(
|
||||
const auto &media = element.documentMedia;
|
||||
media->checkStickerSmall();
|
||||
|
||||
if (document->sticker()->animated
|
||||
const auto isAnimated = document->sticker()->animated;
|
||||
if (isAnimated
|
||||
&& !element.animated
|
||||
&& media->loaded()) {
|
||||
const_cast<Inner*>(this)->setupLottie(index);
|
||||
@@ -755,7 +805,7 @@ void StickerSetBox::Inner::paintSticker(
|
||||
|
||||
auto w = 1;
|
||||
auto h = 1;
|
||||
if (element.animated && !document->dimensions.isEmpty()) {
|
||||
if (isAnimated && !document->dimensions.isEmpty()) {
|
||||
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
|
||||
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
|
||||
w = std::max(size.width(), 1);
|
||||
@@ -767,6 +817,7 @@ void StickerSetBox::Inner::paintSticker(
|
||||
h = std::max(qRound(coef * document->dimensions.height()), 1);
|
||||
}
|
||||
QPoint ppos = position + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2);
|
||||
|
||||
if (element.animated && element.animated->ready()) {
|
||||
const auto frame = element.animated->frame();
|
||||
p.drawImage(
|
||||
@@ -779,6 +830,12 @@ void StickerSetBox::Inner::paintSticker(
|
||||
ppos,
|
||||
width(),
|
||||
image->pix(w, h));
|
||||
} else {
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, QSize(w, h)),
|
||||
_pathGradient.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,12 +877,7 @@ QString StickerSetBox::Inner::shortName() const {
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::install() {
|
||||
if (isMasksSet()) {
|
||||
_controller->show(
|
||||
Box<InformBox>(tr::lng_stickers_masks_pack(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
} else if (_installRequest) {
|
||||
if (_installRequest) {
|
||||
return;
|
||||
}
|
||||
_installRequest = _api.request(MTPmessages_InstallStickerSet(
|
||||
@@ -838,4 +890,17 @@ void StickerSetBox::Inner::install() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::archiveStickers() {
|
||||
_api.request(MTPmessages_InstallStickerSet(
|
||||
_input,
|
||||
MTP_boolTrue()
|
||||
)).done([=](const MTPmessages_StickerSetInstallResult &result) {
|
||||
if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) {
|
||||
_setArchived.fire_copy(_setId);
|
||||
}
|
||||
}).fail([](const MTP::Error &error) {
|
||||
Ui::Toast::Show(Lang::Hard::ServerError());
|
||||
}).send();
|
||||
}
|
||||
|
||||
StickerSetBox::Inner::~Inner() = default;
|
||||
|
||||
@@ -46,7 +46,6 @@ private:
|
||||
void updateButtons();
|
||||
void addStickers();
|
||||
void copyStickersLink();
|
||||
void archiveStickers();
|
||||
void handleError(Error error);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
|
||||
void saveGroupSet();
|
||||
|
||||
void rebuild();
|
||||
void rebuild(bool masks);
|
||||
void updateSize(int newWidth = 0);
|
||||
void updateRows(); // refresh only pack cover stickers
|
||||
bool appendSet(not_null<StickersSet*> set);
|
||||
@@ -152,6 +152,7 @@ private:
|
||||
~Row();
|
||||
|
||||
bool isRecentSet() const;
|
||||
bool isMasksSet() const;
|
||||
|
||||
const not_null<StickersSet*> set;
|
||||
DocumentData *sticker = nullptr;
|
||||
@@ -239,7 +240,8 @@ private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
|
||||
Section _section;
|
||||
const Section _section;
|
||||
const bool _isInstalled;
|
||||
|
||||
int32 _rowHeight;
|
||||
|
||||
@@ -351,7 +353,7 @@ void StickersBox::Tab::returnWidget(object_ptr<Inner> widget) {
|
||||
Assert(_widget == _weak);
|
||||
}
|
||||
|
||||
StickersBox::Inner *StickersBox::Tab::widget() {
|
||||
StickersBox::Inner *StickersBox::Tab::widget() const {
|
||||
return _weak;
|
||||
}
|
||||
|
||||
@@ -366,7 +368,8 @@ void StickersBox::Tab::saveScrollTop() {
|
||||
StickersBox::StickersBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Section section)
|
||||
Section section,
|
||||
bool masks)
|
||||
: _controller(controller)
|
||||
, _api(&controller->session().mtp())
|
||||
, _tabs(this, st::stickersTabs)
|
||||
@@ -374,9 +377,11 @@ StickersBox::StickersBox(
|
||||
this,
|
||||
controller->session().data().stickers().featuredSetsUnreadCountValue())
|
||||
, _section(section)
|
||||
, _installed(0, this, controller, Section::Installed)
|
||||
, _featured(1, this, controller, Section::Featured)
|
||||
, _archived(2, this, controller, Section::Archived) {
|
||||
, _isMasks(masks)
|
||||
, _installed(_isMasks ? Tab() : Tab(0, this, controller, Section::Installed))
|
||||
, _masks(_isMasks ? Tab(0, this, controller, Section::Masks) : Tab())
|
||||
, _featured(_isMasks ? Tab() : Tab(1, this, controller, Section::Featured))
|
||||
, _archived((_isMasks ? 1 : 2), this, controller, Section::Archived) {
|
||||
_tabs->setRippleTopRoundRadius(st::boxRadius);
|
||||
}
|
||||
|
||||
@@ -387,6 +392,7 @@ StickersBox::StickersBox(
|
||||
: _controller(controller)
|
||||
, _api(&controller->session().mtp())
|
||||
, _section(Section::Installed)
|
||||
, _isMasks(false)
|
||||
, _installed(0, this, controller, megagroup)
|
||||
, _megagroupSet(megagroup) {
|
||||
_installed.widget()->scrollsToY(
|
||||
@@ -402,6 +408,7 @@ StickersBox::StickersBox(
|
||||
: _controller(controller)
|
||||
, _api(&controller->session().mtp())
|
||||
, _section(Section::Attached)
|
||||
, _isMasks(false)
|
||||
, _attached(0, this, controller, Section::Attached)
|
||||
, _attachedSets(attachedSets) {
|
||||
}
|
||||
@@ -447,7 +454,7 @@ void StickersBox::getArchivedDone(
|
||||
}
|
||||
|
||||
auto &stickers = result.c_messages_archivedStickers();
|
||||
auto &archived = session().data().stickers().archivedSetsOrderRef();
|
||||
auto &archived = archivedSetsOrderRef();
|
||||
if (offsetId) {
|
||||
auto index = archived.indexOf(offsetId);
|
||||
if (index >= 0) {
|
||||
@@ -514,7 +521,11 @@ void StickersBox::getArchivedDone(
|
||||
void StickersBox::prepare() {
|
||||
if (_section == Section::Installed) {
|
||||
if (_tabs) {
|
||||
session().local().readArchivedStickers();
|
||||
if (_isMasks) {
|
||||
session().local().readArchivedMasks();
|
||||
} else {
|
||||
session().local().readArchivedStickers();
|
||||
}
|
||||
} else {
|
||||
setTitle(tr::lng_stickers_group_set());
|
||||
}
|
||||
@@ -524,7 +535,7 @@ void StickersBox::prepare() {
|
||||
setTitle(tr::lng_stickers_attached_sets());
|
||||
}
|
||||
if (_tabs) {
|
||||
if (session().data().stickers().archivedSetsOrder().isEmpty()) {
|
||||
if (archivedSetsOrder().isEmpty()) {
|
||||
preloadArchivedSets();
|
||||
}
|
||||
setNoContentMargin(true);
|
||||
@@ -534,21 +545,33 @@ void StickersBox::prepare() {
|
||||
lifetime());
|
||||
refreshTabs();
|
||||
}
|
||||
if (_installed.widget() && _section != Section::Installed) _installed.widget()->hide();
|
||||
if (_featured.widget() && _section != Section::Featured) _featured.widget()->hide();
|
||||
if (_archived.widget() && _section != Section::Archived) _archived.widget()->hide();
|
||||
if (_attached.widget() && _section != Section::Attached) _attached.widget()->hide();
|
||||
if (_installed.widget() && _section != Section::Installed) {
|
||||
_installed.widget()->hide();
|
||||
}
|
||||
if (_masks.widget() && _section != Section::Masks) {
|
||||
_masks.widget()->hide();
|
||||
}
|
||||
if (_featured.widget() && _section != Section::Featured) {
|
||||
_featured.widget()->hide();
|
||||
}
|
||||
if (_archived.widget() && _section != Section::Archived) {
|
||||
_archived.widget()->hide();
|
||||
}
|
||||
if (_attached.widget() && _section != Section::Attached) {
|
||||
_attached.widget()->hide();
|
||||
}
|
||||
|
||||
const auto installCallback = [=](uint64 setId) { installSet(setId); };
|
||||
if (_featured.widget()) {
|
||||
_featured.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); });
|
||||
_featured.widget()->setInstallSetCallback(installCallback);
|
||||
}
|
||||
if (_archived.widget()) {
|
||||
_archived.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); });
|
||||
_archived.widget()->setLoadMoreCallback([this] { loadMoreArchived(); });
|
||||
_archived.widget()->setInstallSetCallback(installCallback);
|
||||
_archived.widget()->setLoadMoreCallback([=] { loadMoreArchived(); });
|
||||
}
|
||||
if (_attached.widget()) {
|
||||
_attached.widget()->setInstallSetCallback([this](uint64 setId) { installSet(setId); });
|
||||
_attached.widget()->setLoadMoreCallback([this] { showAttachedStickers(); });
|
||||
_attached.widget()->setInstallSetCallback(installCallback);
|
||||
_attached.widget()->setLoadMoreCallback([=] { showAttachedStickers(); });
|
||||
}
|
||||
|
||||
if (_megagroupSet) {
|
||||
@@ -565,6 +588,8 @@ void StickersBox::prepare() {
|
||||
|
||||
if (_section == Section::Installed) {
|
||||
_tab = &_installed;
|
||||
} else if (_section == Section::Masks) {
|
||||
_tab = &_masks;
|
||||
} else if (_section == Section::Archived) {
|
||||
_tab = &_archived;
|
||||
} else if (_section == Section::Attached) {
|
||||
@@ -580,18 +605,21 @@ void StickersBox::prepare() {
|
||||
[this] { handleStickersUpdated(); },
|
||||
lifetime());
|
||||
session().api().updateStickers();
|
||||
session().api().updateMasks();
|
||||
|
||||
if (_installed.widget()) {
|
||||
_installed.widget()->draggingScrollDelta(
|
||||
) | rpl::start_with_next([=](int delta) {
|
||||
scrollByDraggingDelta(delta);
|
||||
}, _installed.widget()->lifetime());
|
||||
if (!_megagroupSet) {
|
||||
boxClosing() | rpl::start_with_next([=] {
|
||||
saveChanges();
|
||||
}, lifetime());
|
||||
for (const auto &widget : { _installed.widget(), _masks.widget() }) {
|
||||
if (widget) {
|
||||
widget->draggingScrollDelta(
|
||||
) | rpl::start_with_next([=](int delta) {
|
||||
scrollByDraggingDelta(delta);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
}
|
||||
if (!_megagroupSet) {
|
||||
boxClosing() | rpl::start_with_next([=] {
|
||||
saveChanges();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
if (_tabs) {
|
||||
_tabs->raise();
|
||||
@@ -601,28 +629,41 @@ void StickersBox::prepare() {
|
||||
}
|
||||
|
||||
void StickersBox::refreshTabs() {
|
||||
if (!_tabs) return;
|
||||
if (!_tabs) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &stickers = session().data().stickers();
|
||||
|
||||
_tabIndices.clear();
|
||||
auto sections = QStringList();
|
||||
sections.push_back(tr::lng_stickers_installed_tab(tr::now).toUpper());
|
||||
_tabIndices.push_back(Section::Installed);
|
||||
if (!session().data().stickers().featuredSetsOrder().isEmpty()) {
|
||||
auto sections = std::vector<QString>();
|
||||
if (_installed.widget()) {
|
||||
sections.push_back(tr::lng_stickers_installed_tab(tr::now).toUpper());
|
||||
_tabIndices.push_back(Section::Installed);
|
||||
}
|
||||
if (_masks.widget()) {
|
||||
sections.push_back(tr::lng_stickers_masks_tab(tr::now).toUpper());
|
||||
_tabIndices.push_back(Section::Masks);
|
||||
}
|
||||
if (!stickers.featuredSetsOrder().isEmpty() && _featured.widget()) {
|
||||
sections.push_back(tr::lng_stickers_featured_tab(tr::now).toUpper());
|
||||
_tabIndices.push_back(Section::Featured);
|
||||
}
|
||||
if (!session().data().stickers().archivedSetsOrder().isEmpty()) {
|
||||
if (!archivedSetsOrder().isEmpty() && _archived.widget()) {
|
||||
sections.push_back(tr::lng_stickers_archived_tab(tr::now).toUpper());
|
||||
_tabIndices.push_back(Section::Archived);
|
||||
}
|
||||
_tabs->setSections(sections);
|
||||
if ((_tab == &_archived && !_tabIndices.contains(Section::Archived))
|
||||
|| (_tab == &_featured && !_tabIndices.contains(Section::Featured))) {
|
||||
|| (_tab == &_featured && !_tabIndices.contains(Section::Featured))
|
||||
|| (_tab == &_masks && !_tabIndices.contains(Section::Masks))) {
|
||||
switchTab();
|
||||
} else if (_tab == &_archived) {
|
||||
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Archived));
|
||||
} else if (_tab == &_featured) {
|
||||
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Featured));
|
||||
} else if (_tab == &_masks) {
|
||||
_tabs->setActiveSectionFast(_tabIndices.indexOf(Section::Masks));
|
||||
}
|
||||
updateTabsGeometry();
|
||||
}
|
||||
@@ -635,7 +676,7 @@ void StickersBox::loadMoreArchived() {
|
||||
}
|
||||
|
||||
uint64 lastId = 0;
|
||||
const auto &order = session().data().stickers().archivedSetsOrder();
|
||||
const auto &order = archivedSetsOrder();
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
for (auto setIt = order.cend(), e = order.cbegin(); setIt != e;) {
|
||||
--setIt;
|
||||
@@ -647,8 +688,11 @@ void StickersBox::loadMoreArchived() {
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto flags = _isMasks
|
||||
? MTPmessages_GetArchivedStickers::Flag::f_masks
|
||||
: MTPmessages_GetArchivedStickers::Flags(0);
|
||||
_archivedRequestId = _api.request(MTPmessages_GetArchivedStickers(
|
||||
MTP_flags(0),
|
||||
MTP_flags(flags),
|
||||
MTP_long(lastId),
|
||||
MTP_int(kArchivedLimitPerPage)
|
||||
)).done([=](const MTPmessages_ArchivedStickers &result) {
|
||||
@@ -674,13 +718,15 @@ void StickersBox::paintEvent(QPaintEvent *e) {
|
||||
void StickersBox::updateTabsGeometry() {
|
||||
if (!_tabs) return;
|
||||
|
||||
_tabs->resizeToWidth(_tabIndices.size() * width() / 3);
|
||||
const auto maxTabs = _isMasks ? 2 : 3;
|
||||
|
||||
_tabs->resizeToWidth(_tabIndices.size() * width() / maxTabs);
|
||||
_unreadBadge->setVisible(_tabIndices.contains(Section::Featured));
|
||||
|
||||
setInnerTopSkip(getTopSkip());
|
||||
|
||||
auto featuredLeft = width() / 3;
|
||||
auto featuredRight = 2 * width() / 3;
|
||||
auto featuredLeft = width() / maxTabs;
|
||||
auto featuredRight = 2 * width() / maxTabs;
|
||||
auto featuredTextWidth = st::stickersTabs.labelStyle.font->width(tr::lng_stickers_featured_tab(tr::now).toUpper());
|
||||
auto featuredTextRight = featuredLeft + (featuredRight - featuredLeft - featuredTextWidth) / 2 + featuredTextWidth;
|
||||
auto unreadBadgeLeft = featuredTextRight - st::stickersFeaturedBadgeSkip;
|
||||
@@ -712,6 +758,9 @@ void StickersBox::switchTab() {
|
||||
} else if (newSection == Section::Archived) {
|
||||
newTab = &_archived;
|
||||
requestArchivedSets();
|
||||
} else if (newSection == Section::Masks) {
|
||||
newTab = &_masks;
|
||||
session().api().updateMasks();
|
||||
}
|
||||
if (_tab == newTab) {
|
||||
onScrollToY(0);
|
||||
@@ -744,7 +793,7 @@ void StickersBox::switchTab() {
|
||||
_slideAnimation = std::make_unique<Ui::SlideAnimation>();
|
||||
_slideAnimation->setSnapshots(std::move(wasCache), std::move(nowCache));
|
||||
auto slideLeft = wasIndex > nowIndex;
|
||||
_slideAnimation->start(slideLeft, [this] { update(); }, st::slideDuration);
|
||||
_slideAnimation->start(slideLeft, [=] { update(); }, st::slideDuration);
|
||||
setInnerVisible(false);
|
||||
|
||||
setFocus();
|
||||
@@ -758,6 +807,16 @@ QPixmap StickersBox::grabContentCache() {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<StickersBox::Inner*, 5> StickersBox::widgets() const {
|
||||
return {
|
||||
_installed.widget(),
|
||||
_featured.widget(),
|
||||
_archived.widget(),
|
||||
_attached.widget(),
|
||||
_masks.widget()
|
||||
};
|
||||
}
|
||||
|
||||
void StickersBox::installSet(uint64 setId) {
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
const auto it = sets.find(setId);
|
||||
@@ -769,10 +828,11 @@ void StickersBox::installSet(uint64 setId) {
|
||||
const auto set = it->second.get();
|
||||
if (_localRemoved.contains(setId)) {
|
||||
_localRemoved.removeOne(setId);
|
||||
if (_installed.widget()) _installed.widget()->setRemovedSets(_localRemoved);
|
||||
if (_featured.widget()) _featured.widget()->setRemovedSets(_localRemoved);
|
||||
if (_archived.widget()) _archived.widget()->setRemovedSets(_localRemoved);
|
||||
if (_attached.widget()) _attached.widget()->setRemovedSets(_localRemoved);
|
||||
for (const auto &widget : widgets()) {
|
||||
if (widget) {
|
||||
widget->setRemovedSets(_localRemoved);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
|| (set->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
@@ -811,8 +871,11 @@ void StickersBox::preloadArchivedSets() {
|
||||
return;
|
||||
}
|
||||
if (!_archivedRequestId) {
|
||||
const auto flags = _isMasks
|
||||
? MTPmessages_GetArchivedStickers::Flag::f_masks
|
||||
: MTPmessages_GetArchivedStickers::Flags(0);
|
||||
_archivedRequestId = _api.request(MTPmessages_GetArchivedStickers(
|
||||
MTP_flags(0),
|
||||
MTP_flags(flags),
|
||||
MTP_long(0),
|
||||
MTP_int(kArchivedLimitFirstRequest)
|
||||
)).done([=](const MTPmessages_ArchivedStickers &result) {
|
||||
@@ -828,7 +891,7 @@ void StickersBox::requestArchivedSets() {
|
||||
}
|
||||
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
const auto &order = session().data().stickers().archivedSetsOrder();
|
||||
const auto &order = archivedSetsOrder();
|
||||
for (const auto setId : order) {
|
||||
auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
@@ -850,19 +913,22 @@ void StickersBox::resizeEvent(QResizeEvent *e) {
|
||||
if (_titleShadow) {
|
||||
_titleShadow->setGeometry(0, 0, width(), st::lineWidth);
|
||||
}
|
||||
if (_installed.widget()) _installed.widget()->resize(width(), _installed.widget()->height());
|
||||
if (_featured.widget()) _featured.widget()->resize(width(), _featured.widget()->height());
|
||||
if (_archived.widget()) _archived.widget()->resize(width(), _archived.widget()->height());
|
||||
if (_attached.widget()) _attached.widget()->resize(width(), _attached.widget()->height());
|
||||
for (const auto &widget : widgets()) {
|
||||
if (widget) {
|
||||
widget->resize(width(), widget->height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StickersBox::handleStickersUpdated() {
|
||||
if (_section == Section::Installed || _section == Section::Featured) {
|
||||
if (_section == Section::Installed
|
||||
|| _section == Section::Featured
|
||||
|| _section == Section::Masks) {
|
||||
rebuildList();
|
||||
} else {
|
||||
_tab->widget()->updateRows();
|
||||
}
|
||||
if (session().data().stickers().archivedSetsOrder().isEmpty()) {
|
||||
if (archivedSetsOrder().isEmpty()) {
|
||||
preloadArchivedSets();
|
||||
} else {
|
||||
refreshTabs();
|
||||
@@ -870,28 +936,55 @@ void StickersBox::handleStickersUpdated() {
|
||||
}
|
||||
|
||||
void StickersBox::rebuildList(Tab *tab) {
|
||||
if (_section == Section::Attached) return;
|
||||
if (!tab) tab = _tab;
|
||||
if (_section == Section::Attached) {
|
||||
return;
|
||||
}
|
||||
if (!tab) {
|
||||
tab = _tab;
|
||||
}
|
||||
|
||||
if (tab == &_installed) {
|
||||
if ((tab == &_installed) || (tab == &_masks)) {
|
||||
_localOrder = tab->widget()->getFullOrder();
|
||||
_localRemoved = tab->widget()->getRemovedSets();
|
||||
}
|
||||
tab->widget()->rebuild();
|
||||
if (tab == &_installed) {
|
||||
tab->widget()->rebuild(_isMasks);
|
||||
if ((tab == &_installed) || (tab == &_masks)) {
|
||||
tab->widget()->setFullOrder(_localOrder);
|
||||
}
|
||||
tab->widget()->setRemovedSets(_localRemoved);
|
||||
}
|
||||
|
||||
void StickersBox::saveChanges() {
|
||||
const auto installed = _installed.widget();
|
||||
const auto masks = _masks.widget();
|
||||
|
||||
// Make sure that our changes in other tabs are applied in the Installed tab.
|
||||
rebuildList(&_installed);
|
||||
if (installed) {
|
||||
rebuildList(&_installed);
|
||||
}
|
||||
if (masks) {
|
||||
rebuildList(&_masks);
|
||||
}
|
||||
|
||||
if (_someArchivedLoaded) {
|
||||
session().local().writeArchivedStickers();
|
||||
if (_isMasks) {
|
||||
session().local().writeArchivedMasks();
|
||||
} else {
|
||||
session().local().writeArchivedStickers();
|
||||
}
|
||||
}
|
||||
if (installed) {
|
||||
session().api().saveStickerSets(
|
||||
installed->getOrder(),
|
||||
installed->getRemovedSets(),
|
||||
false);
|
||||
}
|
||||
if (masks) {
|
||||
session().api().saveStickerSets(
|
||||
masks->getOrder(),
|
||||
masks->getRemovedSets(),
|
||||
true);
|
||||
}
|
||||
session().api().saveStickerSets(_installed.widget()->getOrder(), _installed.widget()->getRemovedSets());
|
||||
}
|
||||
|
||||
void StickersBox::setInnerFocus() {
|
||||
@@ -900,6 +993,18 @@ void StickersBox::setInnerFocus() {
|
||||
}
|
||||
}
|
||||
|
||||
const Data::StickersSetsOrder &StickersBox::archivedSetsOrder() const {
|
||||
return !_isMasks
|
||||
? session().data().stickers().archivedSetsOrder()
|
||||
: session().data().stickers().archivedMaskSetsOrder();
|
||||
}
|
||||
|
||||
Data::StickersSetsOrder &StickersBox::archivedSetsOrderRef() {
|
||||
return !_isMasks
|
||||
? session().data().stickers().archivedSetsOrderRef()
|
||||
: session().data().stickers().archivedMaskSetsOrderRef();
|
||||
}
|
||||
|
||||
StickersBox::~StickersBox() = default;
|
||||
|
||||
StickersBox::Inner::Row::Row(
|
||||
@@ -932,7 +1037,12 @@ StickersBox::Inner::Row::Row(
|
||||
StickersBox::Inner::Row::~Row() = default;
|
||||
|
||||
bool StickersBox::Inner::Row::isRecentSet() const {
|
||||
return (set->id == Data::Stickers::CloudRecentSetId);
|
||||
return (set->id == Data::Stickers::CloudRecentSetId)
|
||||
|| (set->id == Data::Stickers::CloudRecentAttachedSetId);
|
||||
}
|
||||
|
||||
bool StickersBox::Inner::Row::isMasksSet() const {
|
||||
return (set->flags & MTPDstickerSet::Flag::f_masks);
|
||||
}
|
||||
|
||||
StickersBox::Inner::Inner(
|
||||
@@ -943,6 +1053,7 @@ StickersBox::Inner::Inner(
|
||||
, _controller(controller)
|
||||
, _api(&_controller->session().mtp())
|
||||
, _section(section)
|
||||
, _isInstalled(_section == Section::Installed || _section == Section::Masks)
|
||||
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
|
||||
, _shiftingAnimation([=](crl::time now) {
|
||||
return shiftingAnimationCallback(now);
|
||||
@@ -963,6 +1074,7 @@ StickersBox::Inner::Inner(
|
||||
, _controller(controller)
|
||||
, _api(&_controller->session().mtp())
|
||||
, _section(StickersBox::Section::Installed)
|
||||
, _isInstalled(_section == Section::Installed || _section == Section::Masks)
|
||||
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
|
||||
, _shiftingAnimation([=](crl::time now) {
|
||||
return shiftingAnimationCallback(now);
|
||||
@@ -1087,8 +1199,10 @@ QRect StickersBox::Inner::relativeButtonRect(bool removeButton) const {
|
||||
auto buttonh = st::stickersRemove.height;
|
||||
auto buttonshift = st::stickersRemoveSkip;
|
||||
if (!removeButton) {
|
||||
auto &st = (_section == Section::Installed) ? st::stickersUndoRemove : st::stickersTrendingAdd;
|
||||
auto textWidth = (_section == Section::Installed) ? _undoWidth : _addWidth;
|
||||
const auto &st = _isInstalled
|
||||
? st::stickersUndoRemove
|
||||
: st::stickersTrendingAdd;
|
||||
const auto textWidth = _isInstalled ? _undoWidth : _addWidth;
|
||||
buttonw = textWidth - st.width;
|
||||
buttonh = st.height;
|
||||
buttonshift = 0;
|
||||
@@ -1117,7 +1231,7 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_section == Section::Installed) {
|
||||
if (_isInstalled) {
|
||||
if (index >= 0 && index == _above) {
|
||||
auto current = _aboveShadowFadeOpacity.current();
|
||||
if (_started >= 0) {
|
||||
@@ -1144,13 +1258,13 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
|
||||
paintFakeButton(p, row, index);
|
||||
}
|
||||
|
||||
if (row->removed && _section == Section::Installed) {
|
||||
if (row->removed && _isInstalled) {
|
||||
p.setOpacity(st::stickersRowDisabledOpacity);
|
||||
}
|
||||
|
||||
auto stickerx = st::contactsPadding.left();
|
||||
|
||||
if (!_megagroupSet && _section == Section::Installed) {
|
||||
if (!_megagroupSet && _isInstalled) {
|
||||
stickerx += st::stickersReorderIcon.width() + st::stickersReorderSkip;
|
||||
if (!row->isRecentSet()) {
|
||||
st::stickersReorderIcon.paint(p, st::contactsPadding.left(), (_rowHeight - st::stickersReorderIcon.height()) / 2, width());
|
||||
@@ -1181,7 +1295,11 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
|
||||
}
|
||||
}
|
||||
|
||||
auto statusText = (row->count > 0) ? tr::lng_stickers_count(tr::now, lt_count, row->count) : tr::lng_contacts_loading(tr::now);
|
||||
const auto statusText = (row->count == 0)
|
||||
? tr::lng_contacts_loading(tr::now)
|
||||
: row->isMasksSet()
|
||||
? tr::lng_masks_count(tr::now, lt_count, row->count)
|
||||
: tr::lng_stickers_count(tr::now, lt_count, row->count);
|
||||
|
||||
p.setFont(st::contactsStatusFont);
|
||||
p.setPen(st::contactsStatusFg);
|
||||
@@ -1281,7 +1399,7 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
|
||||
Unexpected("StickersBox::Inner::updateRowThumbnail: row not found");
|
||||
}();
|
||||
const auto left = st::contactsPadding.left()
|
||||
+ ((!_megagroupSet && _section == Section::Installed)
|
||||
+ ((!_megagroupSet && _isInstalled)
|
||||
? st::stickersReorderIcon.width() + st::stickersReorderSkip
|
||||
: 0);
|
||||
update(
|
||||
@@ -1292,9 +1410,9 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
|
||||
}
|
||||
|
||||
void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int index) {
|
||||
auto removeButton = (_section == Section::Installed && !row->removed);
|
||||
auto removeButton = (_isInstalled && !row->removed);
|
||||
auto rect = relativeButtonRect(removeButton);
|
||||
if (_section != Section::Installed && row->installed && !row->archived && !row->removed) {
|
||||
if (!_isInstalled && row->installed && !row->archived && !row->removed) {
|
||||
// Checkbox after installed from Trending or Archived.
|
||||
int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (rect.width() + st::stickersFeaturedInstalled.width()) / 2);
|
||||
int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2;
|
||||
@@ -1317,10 +1435,12 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
|
||||
} else {
|
||||
// Round button ADD when not installed from Trending or Archived.
|
||||
// Or round button UNDO after disabled from Installed.
|
||||
auto &st = (_section == Section::Installed) ? st::stickersUndoRemove : st::stickersTrendingAdd;
|
||||
auto textWidth = (_section == Section::Installed) ? _undoWidth : _addWidth;
|
||||
auto &text = (_section == Section::Installed) ? _undoText : _addText;
|
||||
auto &textBg = selected ? st.textBgOver : st.textBg;
|
||||
const auto &st = _isInstalled
|
||||
? st::stickersUndoRemove
|
||||
: st::stickersTrendingAdd;
|
||||
const auto textWidth = _isInstalled ? _undoWidth : _addWidth;
|
||||
const auto &text = _isInstalled ? _undoText : _addText;
|
||||
const auto &textBg = selected ? st.textBgOver : st.textBg;
|
||||
Ui::FillRoundRect(p, myrtlrect(rect), textBg, ImageRoundRadius::Small);
|
||||
if (row->ripple) {
|
||||
row->ripple->paint(p, rect.x(), rect.y(), width());
|
||||
@@ -1345,7 +1465,7 @@ void StickersBox::Inner::mousePressEvent(QMouseEvent *e) {
|
||||
setActionDown(_actionSel);
|
||||
update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
|
||||
} else if (auto selectedIndex = std::get_if<int>(&_selected)) {
|
||||
if (_section == Section::Installed && !_rows[*selectedIndex]->isRecentSet() && _inDragArea) {
|
||||
if (_isInstalled && !_rows[*selectedIndex]->isRecentSet() && _inDragArea) {
|
||||
_above = _dragging = _started = *selectedIndex;
|
||||
_dragStart = mapFromGlobal(_mouse);
|
||||
}
|
||||
@@ -1367,9 +1487,9 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
|
||||
if (_actionDown >= 0 && _actionDown < _rows.size()) {
|
||||
update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
|
||||
const auto row = _rows[_actionDown].get();
|
||||
auto removeButton = (_section == Section::Installed && !row->removed);
|
||||
auto removeButton = (_isInstalled && !row->removed);
|
||||
if (!row->ripple) {
|
||||
if (_section == Section::Installed) {
|
||||
if (_isInstalled) {
|
||||
if (row->removed) {
|
||||
auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height);
|
||||
auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::roundRadiusSmall);
|
||||
@@ -1512,14 +1632,14 @@ void StickersBox::Inner::updateSelected() {
|
||||
selected = selectedIndex;
|
||||
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
|
||||
const auto row = _rows[selectedIndex].get();
|
||||
if (!_megagroupSet && (_section == Section::Installed || !row->installed || row->archived || row->removed)) {
|
||||
auto removeButton = (_section == Section::Installed && !row->removed);
|
||||
if (!_megagroupSet && (_isInstalled || !row->installed || row->archived || row->removed)) {
|
||||
auto removeButton = (_isInstalled && !row->removed);
|
||||
auto rect = myrtlrect(relativeButtonRect(removeButton));
|
||||
actionSel = rect.contains(local) ? selectedIndex : -1;
|
||||
} else {
|
||||
actionSel = -1;
|
||||
}
|
||||
if (!_megagroupSet && _section == Section::Installed && !row->isRecentSet()) {
|
||||
if (!_megagroupSet && _isInstalled && !row->isRecentSet()) {
|
||||
auto dragAreaWidth = st::contactsPadding.left() + st::stickersReorderIcon.width() + st::stickersReorderSkip;
|
||||
auto dragArea = myrtlrect(0, 0, dragAreaWidth, _rowHeight);
|
||||
inDragArea = dragArea.contains(local);
|
||||
@@ -1543,7 +1663,7 @@ void StickersBox::Inner::updateSelected() {
|
||||
void StickersBox::Inner::updateCursor() {
|
||||
setCursor(_inDragArea
|
||||
? style::cur_sizeall
|
||||
: (!_megagroupSet && _section == Section::Installed)
|
||||
: (!_megagroupSet && _isInstalled)
|
||||
? ((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel))
|
||||
? style::cur_pointer
|
||||
: style::cur_default)
|
||||
@@ -1568,7 +1688,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_mouse = e->globalPos();
|
||||
updateSelected();
|
||||
if (_actionDown == _actionSel && _actionSel >= 0) {
|
||||
if (_section == Section::Installed) {
|
||||
if (_isInstalled) {
|
||||
setRowRemoved(_actionDown, !_rows[_actionDown]->removed);
|
||||
} else if (_installSetCallback) {
|
||||
_installSetCallback(_rows[_actionDown]->set->id);
|
||||
@@ -1623,7 +1743,8 @@ void StickersBox::Inner::saveGroupSet() {
|
||||
: 0;
|
||||
if (newId != oldId) {
|
||||
session().api().setGroupStickerSet(_megagroupSet, _megagroupSetInput);
|
||||
session().api().stickerSetInstalled(Data::Stickers::MegagroupSetId);
|
||||
session().data().stickers().notifyStickerSetInstalled(
|
||||
Data::Stickers::MegagroupSetId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1840,7 +1961,7 @@ void StickersBox::Inner::rebuildMegagroupSet() {
|
||||
}
|
||||
}
|
||||
|
||||
void StickersBox::Inner::rebuild() {
|
||||
void StickersBox::Inner::rebuild(bool masks) {
|
||||
_itemsTop = st::membersMarginTop;
|
||||
|
||||
if (_megagroupSet) {
|
||||
@@ -1859,10 +1980,14 @@ void StickersBox::Inner::rebuild() {
|
||||
return session().data().stickers().featuredSetsOrder();
|
||||
}
|
||||
return result;
|
||||
} else if (_section == Section::Masks) {
|
||||
return session().data().stickers().maskSetsOrder();
|
||||
} else if (_section == Section::Featured) {
|
||||
return session().data().stickers().featuredSetsOrder();
|
||||
}
|
||||
return session().data().stickers().archivedSetsOrder();
|
||||
return masks
|
||||
? session().data().stickers().archivedMaskSetsOrder()
|
||||
: session().data().stickers().archivedSetsOrder();
|
||||
})();
|
||||
_rows.reserve(order.size() + 1);
|
||||
_shiftingStartTimes.reserve(order.size() + 1);
|
||||
@@ -1874,8 +1999,10 @@ void StickersBox::Inner::rebuild() {
|
||||
? tr::lng_stickers_group_from_featured(tr::now)
|
||||
: tr::lng_stickers_group_from_your(tr::now));
|
||||
updateControlsGeometry();
|
||||
} else if (_section == Section::Installed) {
|
||||
auto cloudIt = sets.find(Data::Stickers::CloudRecentSetId);
|
||||
} else if (_isInstalled) {
|
||||
const auto cloudIt = sets.find((_section == Section::Masks)
|
||||
? Data::Stickers::CloudRecentAttachedSetId
|
||||
: Data::Stickers::CloudRecentSetId); // Section::Installed.
|
||||
if (cloudIt != sets.cend() && !cloudIt->second->stickers.isEmpty()) {
|
||||
rebuildAppendSet(cloudIt->second.get(), maxNameWidth);
|
||||
}
|
||||
@@ -1900,7 +2027,7 @@ void StickersBox::Inner::rebuild() {
|
||||
|
||||
void StickersBox::Inner::setMegagroupSelectedSet(const MTPInputStickerSet &set) {
|
||||
_megagroupSetInput = set;
|
||||
rebuild();
|
||||
rebuild(false);
|
||||
_scrollsToY.fire(0);
|
||||
updateSelected();
|
||||
}
|
||||
@@ -1944,7 +2071,7 @@ void StickersBox::Inner::updateRows() {
|
||||
auto wasInstalled = row->installed;
|
||||
auto wasArchived = row->archived;
|
||||
fillSetFlags(set, &row->installed, &row->official, &row->unread, &row->archived);
|
||||
if (_section == Section::Installed) {
|
||||
if (_isInstalled) {
|
||||
row->archived = false;
|
||||
}
|
||||
if (row->installed != wasInstalled || row->archived != wasArchived) {
|
||||
@@ -1969,11 +2096,11 @@ bool StickersBox::Inner::appendSet(not_null<StickersSet*> set) {
|
||||
|
||||
int StickersBox::Inner::countMaxNameWidth() const {
|
||||
int namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
|
||||
if (!_megagroupSet && _section == Section::Installed) {
|
||||
if (!_megagroupSet && _isInstalled) {
|
||||
namex += st::stickersReorderIcon.width() + st::stickersReorderSkip;
|
||||
}
|
||||
int namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x();
|
||||
if (_section == Section::Installed) {
|
||||
if (_isInstalled) {
|
||||
if (!_megagroupSet) {
|
||||
namew -= _undoWidth - st::stickersUndoRemove.width;
|
||||
}
|
||||
@@ -1993,7 +2120,7 @@ void StickersBox::Inner::rebuildAppendSet(
|
||||
if (set->id != Data::Stickers::CloudRecentSetId) {
|
||||
fillSetFlags(set, &installed, &official, &unread, &archived);
|
||||
}
|
||||
if (_section == Section::Installed && archived) {
|
||||
if (_isInstalled && archived) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,12 +56,14 @@ public:
|
||||
Featured,
|
||||
Archived,
|
||||
Attached,
|
||||
Masks,
|
||||
};
|
||||
|
||||
StickersBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Section section);
|
||||
Section section,
|
||||
bool masks = false);
|
||||
StickersBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -94,7 +96,7 @@ private:
|
||||
object_ptr<Inner> takeWidget();
|
||||
void returnWidget(object_ptr<Inner> widget);
|
||||
|
||||
[[nodiscard]] Inner *widget();
|
||||
[[nodiscard]] Inner *widget() const;
|
||||
[[nodiscard]] int index() const;
|
||||
|
||||
void saveScrollTop();
|
||||
@@ -103,7 +105,7 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
int _index = 0;
|
||||
const int _index = 0;
|
||||
object_ptr<Inner> _widget = { nullptr };
|
||||
QPointer<Inner> _weak;
|
||||
int _scrollTop = 0;
|
||||
@@ -132,6 +134,11 @@ private:
|
||||
uint64 offsetId);
|
||||
void showAttachedStickers();
|
||||
|
||||
const Data::StickersSetsOrder &archivedSetsOrder() const;
|
||||
Data::StickersSetsOrder &archivedSetsOrderRef();
|
||||
|
||||
std::array<Inner*, 5> widgets() const;
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
|
||||
@@ -142,8 +149,10 @@ private:
|
||||
object_ptr<CounterWidget> _unreadBadge = { nullptr };
|
||||
|
||||
Section _section;
|
||||
const bool _isMasks;
|
||||
|
||||
Tab _installed;
|
||||
Tab _masks;
|
||||
Tab _featured;
|
||||
Tab _archived;
|
||||
Tab _attached;
|
||||
|
||||
@@ -490,6 +490,7 @@ groupCallMenu: Menu(defaultMenu) {
|
||||
itemFgShortcutDisabled: groupCallMemberNotJoinedStatus;
|
||||
|
||||
separatorFg: groupCallMenuBgOver;
|
||||
separatorPadding: margins(0px, 4px, 0px, 4px);
|
||||
|
||||
arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }};
|
||||
|
||||
@@ -513,6 +514,16 @@ groupCallPopupMenu: PopupMenu(defaultPopupMenu) {
|
||||
menu: groupCallMenu;
|
||||
animation: groupCallPanelAnimation;
|
||||
}
|
||||
groupCallPopupMenuWithVolume: PopupMenu(groupCallPopupMenu) {
|
||||
scrollPadding: margins(0px, 3px, 0px, 8px);
|
||||
menu: Menu(groupCallMenu) {
|
||||
widthMin: 210px;
|
||||
}
|
||||
}
|
||||
groupCallPopupVolumeMenu: Menu(groupCallMenu) {
|
||||
widthMin: 210px;
|
||||
itemBgOver: groupCallMenuBg;
|
||||
}
|
||||
|
||||
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
|
||||
groupCallRecordingTimerFont: font(12px);
|
||||
@@ -1071,13 +1082,17 @@ groupCallMuteCrossLine: CrossLineAnimation {
|
||||
|
||||
groupCallMenuSpeakerArcsSkip: 1px;
|
||||
groupCallMenuVolumeSkip: 5px;
|
||||
groupCallMenuVolumePadding: margins(17px, 6px, 17px, 5px);
|
||||
groupCallMenuVolumeMargin: margins(55px, 0px, 15px, 0px);
|
||||
groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) {
|
||||
activeFg: groupCallMembersFg;
|
||||
inactiveFg: groupCallMemberInactiveIcon;
|
||||
inactiveFg: groupCallMembersBgOver;
|
||||
activeFgOver: groupCallMembersFg;
|
||||
inactiveFgOver: groupCallMemberInactiveIcon;
|
||||
activeFgDisabled: groupCallMemberInactiveIcon;
|
||||
receivedTillFg: groupCallMemberInactiveIcon;
|
||||
inactiveFgOver: groupCallMembersBgOver;
|
||||
activeFgDisabled: groupCallMembersBgOver;
|
||||
receivedTillFg: groupCallMembersBgOver;
|
||||
width: 7px;
|
||||
seekSize: size(7px, 7px);
|
||||
}
|
||||
|
||||
groupCallSpeakerArcsAnimation: ArcsAnimation {
|
||||
@@ -1251,5 +1266,16 @@ groupCallNiceTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
|
||||
linkFontOver: font(11px underline);
|
||||
}
|
||||
}
|
||||
groupCallStickedTooltip: ImportantTooltip(groupCallNiceTooltip) {
|
||||
padding: margins(10px, 1px, 6px, 3px);
|
||||
}
|
||||
groupCallStickedTooltipClose: IconButton(defaultIconButton) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
iconPosition: point(4px, 3px);
|
||||
icon: icon {{ "calls/video_tooltip", importantTooltipFg }};
|
||||
iconOver: icon {{ "calls/video_tooltip", importantTooltipFg }};
|
||||
ripple: emptyRippleAnimation;
|
||||
}
|
||||
groupCallNiceTooltipTop: 4px;
|
||||
groupCallPaused: icon {{ "calls/video_large_paused", groupCallVideoTextFg }};
|
||||
|
||||
@@ -42,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "window/main_window.h"
|
||||
#include "media/view/media_view_pip.h" // Utilities for frame rotation.
|
||||
#include "app.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "styles/style_calls.h"
|
||||
@@ -51,16 +50,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
namespace Calls {
|
||||
|
||||
Panel::Panel(not_null<Call*> call)
|
||||
: _call(call)
|
||||
, _user(call->user())
|
||||
, _window(createWindow())
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
_window->body(),
|
||||
widget(),
|
||||
st::callTitle,
|
||||
[=](bool maximized) { toggleFullScreen(maximized); }))
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -86,46 +85,27 @@ Panel::Panel(not_null<Call*> call)
|
||||
|
||||
Panel::~Panel() = default;
|
||||
|
||||
std::unique_ptr<Ui::Window> Panel::createWindow() {
|
||||
auto result = std::make_unique<Ui::Window>();
|
||||
const auto capabilities = Ui::GL::CheckCapabilities(result.get());
|
||||
const auto use = Platform::IsMac()
|
||||
? true
|
||||
: Platform::IsWindows()
|
||||
? capabilities.supported
|
||||
: capabilities.transparency;
|
||||
LOG(("OpenGL: %1 (Incoming)").arg(Logs::b(use)));
|
||||
_backend = use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster;
|
||||
|
||||
if (use) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We have to create a new window, if OpenGL initialization failed.
|
||||
return std::make_unique<Ui::Window>();
|
||||
}
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return _window->isActiveWindow()
|
||||
&& _window->isVisible()
|
||||
&& !(_window->windowState() & Qt::WindowMinimized);
|
||||
return window()->isActiveWindow()
|
||||
&& window()->isVisible()
|
||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::showAndActivate() {
|
||||
if (_window->isHidden()) {
|
||||
_window->show();
|
||||
if (window()->isHidden()) {
|
||||
window()->show();
|
||||
}
|
||||
const auto state = _window->windowState();
|
||||
const auto state = window()->windowState();
|
||||
if (state & Qt::WindowMinimized) {
|
||||
_window->setWindowState(state & ~Qt::WindowMinimized);
|
||||
window()->setWindowState(state & ~Qt::WindowMinimized);
|
||||
}
|
||||
_window->raise();
|
||||
_window->activateWindow();
|
||||
_window->setFocus();
|
||||
window()->raise();
|
||||
window()->activateWindow();
|
||||
window()->setFocus();
|
||||
}
|
||||
|
||||
void Panel::minimize() {
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
|
||||
window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::replaceCall(not_null<Call*> call) {
|
||||
@@ -134,26 +114,26 @@ void Panel::replaceCall(not_null<Call*> call) {
|
||||
}
|
||||
|
||||
void Panel::initWindow() {
|
||||
_window->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
_window->setAttribute(Qt::WA_NoSystemBackground);
|
||||
_window->setWindowIcon(
|
||||
window()->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
window()->setAttribute(Qt::WA_NoSystemBackground);
|
||||
window()->setWindowIcon(
|
||||
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
|
||||
_window->setTitle(u" "_q);
|
||||
_window->setTitleStyle(st::callTitle);
|
||||
window()->setTitle(u" "_q);
|
||||
window()->setTitleStyle(st::callTitle);
|
||||
|
||||
_window->events(
|
||||
window()->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Close) {
|
||||
handleClose();
|
||||
} else if (e->type() == QEvent::KeyPress) {
|
||||
if ((static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Escape)
|
||||
&& _window->isFullScreen()) {
|
||||
_window->showNormal();
|
||||
&& window()->isFullScreen()) {
|
||||
window()->showNormal();
|
||||
}
|
||||
}
|
||||
}, _window->lifetime());
|
||||
}, window()->lifetime());
|
||||
|
||||
_window->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
window()->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
using Flag = Ui::WindowTitleHitTestFlag;
|
||||
if (!widget()->rect().contains(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
@@ -179,28 +159,31 @@ void Panel::initWindow() {
|
||||
: (Flag::Move | Flag::FullScreen);
|
||||
});
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// On Windows we replace snap-to-top maximizing with fullscreen.
|
||||
//
|
||||
// We have to switch first to showNormal, so that showFullScreen
|
||||
// will remember correct normal window geometry and next showNormal
|
||||
// will show it instead of a moving maximized window.
|
||||
//
|
||||
// We have to do it in InvokeQueued, otherwise it still captures
|
||||
// the maximized window geometry and saves it.
|
||||
//
|
||||
// I couldn't find a less glitchy way to do that *sigh*.
|
||||
const auto object = _window->windowHandle();
|
||||
const auto signal = &QWindow::windowStateChanged;
|
||||
QObject::connect(object, signal, [=](Qt::WindowState state) {
|
||||
if (state == Qt::WindowMaximized) {
|
||||
InvokeQueued(object, [=] {
|
||||
_window->showNormal();
|
||||
_window->showFullScreen();
|
||||
});
|
||||
}
|
||||
});
|
||||
#endif // Q_OS_WIN
|
||||
// Don't do that, it looks awful :(
|
||||
//#ifdef Q_OS_WIN
|
||||
// // On Windows we replace snap-to-top maximizing with fullscreen.
|
||||
// //
|
||||
// // We have to switch first to showNormal, so that showFullScreen
|
||||
// // will remember correct normal window geometry and next showNormal
|
||||
// // will show it instead of a moving maximized window.
|
||||
// //
|
||||
// // We have to do it in InvokeQueued, otherwise it still captures
|
||||
// // the maximized window geometry and saves it.
|
||||
// //
|
||||
// // I couldn't find a less glitchy way to do that *sigh*.
|
||||
// const auto object = window()->windowHandle();
|
||||
// const auto signal = &QWindow::windowStateChanged;
|
||||
// QObject::connect(object, signal, [=](Qt::WindowState state) {
|
||||
// if (state == Qt::WindowMaximized) {
|
||||
// InvokeQueued(object, [=] {
|
||||
// window()->showNormal();
|
||||
// InvokeQueued(object, [=] {
|
||||
// window()->showFullScreen();
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
//#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
void Panel::initWidget() {
|
||||
@@ -339,7 +322,7 @@ void Panel::reinitWithCall(Call *call) {
|
||||
_incoming = std::make_unique<Incoming>(
|
||||
widget(),
|
||||
_call->videoIncoming(),
|
||||
_backend);
|
||||
_window.backend());
|
||||
_incoming->widget()->hide();
|
||||
|
||||
_call->mutedValue(
|
||||
@@ -519,16 +502,20 @@ void Panel::showControls() {
|
||||
}
|
||||
|
||||
void Panel::closeBeforeDestroy() {
|
||||
_window->close();
|
||||
window()->close();
|
||||
reinitWithCall(nullptr);
|
||||
}
|
||||
|
||||
rpl::lifetime &Panel::lifetime() {
|
||||
return window()->lifetime();
|
||||
}
|
||||
|
||||
void Panel::initGeometry() {
|
||||
const auto center = Core::App().getPointForCallPanelCenter();
|
||||
const auto initRect = QRect(0, 0, st::callWidth, st::callHeight);
|
||||
_window->setGeometry(initRect.translated(center - initRect.center()));
|
||||
_window->setMinimumSize({ st::callWidthMin, st::callHeightMin });
|
||||
_window->show();
|
||||
window()->setGeometry(initRect.translated(center - initRect.center()));
|
||||
window()->setMinimumSize({ st::callWidthMin, st::callHeightMin });
|
||||
window()->show();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
@@ -546,9 +533,9 @@ void Panel::refreshOutgoingPreviewInBody(State state) {
|
||||
|
||||
void Panel::toggleFullScreen(bool fullscreen) {
|
||||
if (fullscreen) {
|
||||
_window->showFullScreen();
|
||||
window()->showFullScreen();
|
||||
} else {
|
||||
_window->showNormal();
|
||||
window()->showNormal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,8 +711,12 @@ void Panel::handleClose() {
|
||||
}
|
||||
}
|
||||
|
||||
not_null<Ui::Window*> Panel::window() const {
|
||||
return _window.window();
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> Panel::widget() const {
|
||||
return _window->body();
|
||||
return _window.widget();
|
||||
}
|
||||
|
||||
void Panel::stateChanged(State state) {
|
||||
@@ -741,7 +732,7 @@ void Panel::stateChanged(State state) {
|
||||
auto toggleButton = [&](auto &&button, bool visible) {
|
||||
button->toggle(
|
||||
visible,
|
||||
_window->isHidden()
|
||||
window()->isHidden()
|
||||
? anim::type::instant
|
||||
: anim::type::normal);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/object_ptr.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/gl/gl_window.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class Image;
|
||||
@@ -60,6 +61,8 @@ public:
|
||||
void replaceCall(not_null<Call*> call);
|
||||
void closeBeforeDestroy();
|
||||
|
||||
rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
class Incoming;
|
||||
using State = Call::State;
|
||||
@@ -70,7 +73,7 @@ private:
|
||||
Redial,
|
||||
};
|
||||
|
||||
std::unique_ptr<Ui::Window> createWindow();
|
||||
[[nodiscard]] not_null<Ui::Window*> window() const;
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
|
||||
void paint(QRect clip);
|
||||
@@ -106,8 +109,7 @@ private:
|
||||
Call *_call = nullptr;
|
||||
not_null<UserData*> _user;
|
||||
|
||||
Ui::GL::Backend _backend = Ui::GL::Backend();
|
||||
const std::unique_ptr<Ui::Window> _window;
|
||||
Ui::GL::Window _window;
|
||||
std::unique_ptr<Incoming> _incoming;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
|
||||
@@ -223,6 +223,7 @@ void Panel::Incoming::RendererGL::paint(
|
||||
Assert(data.format == Webrtc::FrameFormat::YUV420);
|
||||
Assert(!data.yuv420->size.isEmpty());
|
||||
const auto yuv = data.yuv420;
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
_textures.bind(f, 1);
|
||||
@@ -230,8 +231,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->size,
|
||||
_lumaSize,
|
||||
yuv->y.stride,
|
||||
@@ -243,8 +244,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->u.stride,
|
||||
@@ -255,8 +256,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->v.stride,
|
||||
|
||||
@@ -49,6 +49,8 @@ constexpr auto kUpdateSendActionEach = crl::time(500);
|
||||
constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000);
|
||||
constexpr auto kFixManualLargeVideoDuration = 5 * crl::time(1000);
|
||||
constexpr auto kFixSpeakingLargeVideoDuration = 3 * crl::time(1000);
|
||||
constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums.
|
||||
constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums.
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Webrtc::MediaDevices> CreateMediaDevices() {
|
||||
const auto &settings = Core::App().settings();
|
||||
@@ -187,6 +189,28 @@ struct GroupCall::SinkPointer {
|
||||
std::weak_ptr<Webrtc::SinkInterface> data;
|
||||
};
|
||||
|
||||
struct GroupCall::VideoTrack {
|
||||
VideoTrack(bool paused, bool requireARGB32, not_null<PeerData*> peer);
|
||||
|
||||
Webrtc::VideoTrack track;
|
||||
rpl::variable<QSize> trackSize;
|
||||
not_null<PeerData*> peer;
|
||||
rpl::lifetime lifetime;
|
||||
Group::VideoQuality quality = Group::VideoQuality();
|
||||
bool shown = false;
|
||||
};
|
||||
|
||||
GroupCall::VideoTrack::VideoTrack(
|
||||
bool paused,
|
||||
bool requireARGB32,
|
||||
not_null<PeerData*> peer)
|
||||
: track((paused
|
||||
? Webrtc::VideoState::Paused
|
||||
: Webrtc::VideoState::Active),
|
||||
requireARGB32)
|
||||
, peer(peer) {
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGroupCallAdmin(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PeerData*> participantPeer) {
|
||||
@@ -451,6 +475,21 @@ void GroupCall::MediaChannelDescriptionsTask::cancel() {
|
||||
}
|
||||
}
|
||||
|
||||
not_null<PeerData*> GroupCall::TrackPeer(
|
||||
const std::unique_ptr<VideoTrack> &track) {
|
||||
return track->peer;
|
||||
}
|
||||
|
||||
not_null<Webrtc::VideoTrack*> GroupCall::TrackPointer(
|
||||
const std::unique_ptr<VideoTrack> &track) {
|
||||
return &track->track;
|
||||
}
|
||||
|
||||
rpl::producer<QSize> GroupCall::TrackSizeValue(
|
||||
const std::unique_ptr<VideoTrack> &track) {
|
||||
return track->trackSize.value();
|
||||
}
|
||||
|
||||
GroupCall::GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
Group::JoinInfo info,
|
||||
@@ -1064,43 +1103,39 @@ void GroupCall::markEndpointActive(
|
||||
if (active) {
|
||||
const auto i = _activeVideoTracks.emplace(
|
||||
endpoint,
|
||||
VideoTrack{
|
||||
.track = std::make_unique<Webrtc::VideoTrack>(
|
||||
(paused
|
||||
? Webrtc::VideoState::Paused
|
||||
: Webrtc::VideoState::Active),
|
||||
_requireARGB32),
|
||||
.peer = endpoint.peer,
|
||||
}).first;
|
||||
const auto track = i->second.track.get();
|
||||
std::make_unique<VideoTrack>(
|
||||
paused,
|
||||
_requireARGB32,
|
||||
endpoint.peer)).first;
|
||||
const auto track = &i->second->track;
|
||||
|
||||
track->renderNextFrame(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto &activeTrack = _activeVideoTracks[endpoint];
|
||||
const auto activeTrack = _activeVideoTracks[endpoint].get();
|
||||
const auto size = track->frameSize();
|
||||
if (size.isEmpty()) {
|
||||
track->markFrameShown();
|
||||
} else if (!activeTrack.shown) {
|
||||
activeTrack.shown = true;
|
||||
} else if (!activeTrack->shown) {
|
||||
activeTrack->shown = true;
|
||||
markTrackShown(endpoint, true);
|
||||
}
|
||||
activeTrack.trackSize = size;
|
||||
}, i->second.lifetime);
|
||||
activeTrack->trackSize = size;
|
||||
}, i->second->lifetime);
|
||||
|
||||
const auto size = track->frameSize();
|
||||
i->second.trackSize = size;
|
||||
i->second->trackSize = size;
|
||||
if (!size.isEmpty() || paused) {
|
||||
i->second.shown = true;
|
||||
i->second->shown = true;
|
||||
shown = true;
|
||||
} else {
|
||||
track->stateValue(
|
||||
) | rpl::filter([=](Webrtc::VideoState state) {
|
||||
return (state == Webrtc::VideoState::Paused)
|
||||
&& !_activeVideoTracks[endpoint].shown;
|
||||
&& !_activeVideoTracks[endpoint]->shown;
|
||||
}) | rpl::start_with_next([=] {
|
||||
_activeVideoTracks[endpoint].shown = true;
|
||||
_activeVideoTracks[endpoint]->shown = true;
|
||||
markTrackShown(endpoint, true);
|
||||
}, i->second.lifetime);
|
||||
}, i->second->lifetime);
|
||||
}
|
||||
addVideoOutput(i->first.id, { track->sink() });
|
||||
} else {
|
||||
@@ -1144,7 +1179,7 @@ void GroupCall::markTrackPaused(const VideoEndpoint &endpoint, bool paused) {
|
||||
const auto i = _activeVideoTracks.find(endpoint);
|
||||
Assert(i != end(_activeVideoTracks));
|
||||
|
||||
i->second.track->setState(paused
|
||||
i->second->track.setState(paused
|
||||
? Webrtc::VideoState::Paused
|
||||
: Webrtc::VideoState::Active);
|
||||
}
|
||||
@@ -1954,6 +1989,10 @@ bool GroupCall::emitShareCameraError() {
|
||||
|
||||
void GroupCall::emitShareCameraError(Error error) {
|
||||
_cameraState = Webrtc::VideoState::Inactive;
|
||||
if (error == Error::CameraFailed
|
||||
&& Webrtc::GetVideoInputList().empty()) {
|
||||
error = Error::NoCamera;
|
||||
}
|
||||
_errors.fire_copy(error);
|
||||
}
|
||||
|
||||
@@ -2007,8 +2046,14 @@ void GroupCall::setupOutgoingVideo() {
|
||||
_cameraCapture = _delegate->groupCallGetVideoCapture(
|
||||
_cameraInputId);
|
||||
if (!_cameraCapture) {
|
||||
return emitShareCameraError(Error::NoCamera);
|
||||
return emitShareCameraError(Error::CameraFailed);
|
||||
}
|
||||
const auto weak = base::make_weak(this);
|
||||
_cameraCapture->setOnFatalError([=] {
|
||||
crl::on_main(weak, [=] {
|
||||
emitShareCameraError(Error::CameraFailed);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_cameraCapture->switchToDevice(_cameraInputId.toStdString());
|
||||
}
|
||||
@@ -2402,6 +2447,9 @@ void GroupCall::updateRequestedVideoChannels() {
|
||||
channels.reserve(_activeVideoTracks.size());
|
||||
const auto &camera = cameraSharingEndpoint();
|
||||
const auto &screen = screenSharingEndpoint();
|
||||
auto mediums = 0;
|
||||
auto fullcameras = 0;
|
||||
auto fullscreencasts = 0;
|
||||
for (const auto &[endpoint, video] : _activeVideoTracks) {
|
||||
const auto &endpointId = endpoint.id;
|
||||
if (endpointId == camera || endpointId == screen) {
|
||||
@@ -2414,24 +2462,74 @@ void GroupCall::updateRequestedVideoChannels() {
|
||||
if (!params) {
|
||||
continue;
|
||||
}
|
||||
const auto min = (video->quality == Group::VideoQuality::Full
|
||||
&& endpoint.type == VideoEndpointType::Screen)
|
||||
? Quality::Full
|
||||
: Quality::Thumbnail;
|
||||
const auto max = (video->quality == Group::VideoQuality::Full)
|
||||
? Quality::Full
|
||||
: (video->quality == Group::VideoQuality::Medium
|
||||
&& endpoint.type != VideoEndpointType::Screen)
|
||||
? Quality::Medium
|
||||
: Quality::Thumbnail;
|
||||
if (max == Quality::Full) {
|
||||
if (endpoint.type == VideoEndpointType::Screen) {
|
||||
++fullscreencasts;
|
||||
} else {
|
||||
++fullcameras;
|
||||
}
|
||||
} else if (max == Quality::Medium) {
|
||||
++mediums;
|
||||
}
|
||||
channels.push_back({
|
||||
.audioSsrc = participant->ssrc,
|
||||
.endpointId = endpointId,
|
||||
.ssrcGroups = (params->camera.endpointId == endpointId
|
||||
? params->camera.ssrcGroups
|
||||
: params->screen.ssrcGroups),
|
||||
.minQuality = ((video.quality == Group::VideoQuality::Full
|
||||
&& endpoint.type == VideoEndpointType::Screen)
|
||||
? Quality::Full
|
||||
: Quality::Thumbnail),
|
||||
.maxQuality = ((video.quality == Group::VideoQuality::Full)
|
||||
? Quality::Full
|
||||
: (video.quality == Group::VideoQuality::Medium
|
||||
&& endpoint.type != VideoEndpointType::Screen)
|
||||
? Quality::Medium
|
||||
: Quality::Thumbnail),
|
||||
.minQuality = min,
|
||||
.maxQuality = max,
|
||||
});
|
||||
}
|
||||
|
||||
// We limit `count(Full) * kFullAsMediumsCount + count(medium)`.
|
||||
//
|
||||
// Try to preserve all qualities; If not
|
||||
// Try to preserve all screencasts as Full and cameras as Medium; If not
|
||||
// Try to preserve all screencasts as Full; If not
|
||||
// Try to preserve all cameras as Medium;
|
||||
const auto mediumsCount = mediums
|
||||
+ (fullcameras + fullscreencasts) * kFullAsMediumsCount;
|
||||
const auto downgradeSome = (mediumsCount > kMaxMediumQualities);
|
||||
const auto downgradeAll = (fullscreencasts * kFullAsMediumsCount)
|
||||
> kMaxMediumQualities;
|
||||
if (downgradeSome) {
|
||||
for (auto &channel : channels) {
|
||||
if (channel.maxQuality == Quality::Full) {
|
||||
const auto camera = (channel.minQuality != Quality::Full);
|
||||
if (camera) {
|
||||
channel.maxQuality = Quality::Medium;
|
||||
} else if (downgradeAll) {
|
||||
channel.maxQuality
|
||||
= channel.minQuality
|
||||
= Quality::Thumbnail;
|
||||
--fullscreencasts;
|
||||
}
|
||||
}
|
||||
}
|
||||
mediums += fullcameras;
|
||||
fullcameras = 0;
|
||||
if (downgradeAll) {
|
||||
fullscreencasts = 0;
|
||||
}
|
||||
}
|
||||
if (mediums > kMaxMediumQualities) {
|
||||
for (auto &channel : channels) {
|
||||
if (channel.maxQuality == Quality::Medium) {
|
||||
channel.maxQuality = Quality::Thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
_instance->setRequestedVideoChannels(std::move(channels));
|
||||
}
|
||||
|
||||
@@ -2911,10 +3009,10 @@ void GroupCall::requestVideoQuality(
|
||||
return;
|
||||
}
|
||||
const auto i = _activeVideoTracks.find(endpoint);
|
||||
if (i == end(_activeVideoTracks) || i->second.quality == quality) {
|
||||
if (i == end(_activeVideoTracks) || i->second->quality == quality) {
|
||||
return;
|
||||
}
|
||||
i->second.quality = quality;
|
||||
i->second->quality = quality;
|
||||
updateRequestedVideoChannelsDelayed();
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ struct VideoEndpoint {
|
||||
std::string id;
|
||||
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
Expects(id.empty() || peer != nullptr);
|
||||
|
||||
return id.empty();
|
||||
}
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
@@ -194,6 +196,15 @@ public:
|
||||
|
||||
using GlobalShortcutManager = base::GlobalShortcutManager;
|
||||
|
||||
struct VideoTrack;
|
||||
|
||||
[[nodiscard]] static not_null<PeerData*> TrackPeer(
|
||||
const std::unique_ptr<VideoTrack> &track);
|
||||
[[nodiscard]] static not_null<Webrtc::VideoTrack*> TrackPointer(
|
||||
const std::unique_ptr<VideoTrack> &track);
|
||||
[[nodiscard]] static rpl::producer<QSize> TrackSizeValue(
|
||||
const std::unique_ptr<VideoTrack> &track);
|
||||
|
||||
GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
Group::JoinInfo info,
|
||||
@@ -321,27 +332,8 @@ public:
|
||||
-> rpl::producer<VideoEndpoint> {
|
||||
return _videoEndpointLarge.value();
|
||||
}
|
||||
|
||||
struct VideoTrack {
|
||||
std::unique_ptr<Webrtc::VideoTrack> track;
|
||||
rpl::variable<QSize> trackSize;
|
||||
PeerData *peer = nullptr;
|
||||
rpl::lifetime lifetime;
|
||||
Group::VideoQuality quality = Group::VideoQuality();
|
||||
bool shown = false;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return (track != nullptr);
|
||||
}
|
||||
[[nodiscard]] bool operator==(const VideoTrack &other) const {
|
||||
return (track == other.track) && (peer == other.peer);
|
||||
}
|
||||
[[nodiscard]] bool operator!=(const VideoTrack &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
[[nodiscard]] auto activeVideoTracks() const
|
||||
-> const base::flat_map<VideoEndpoint, VideoTrack> & {
|
||||
-> const base::flat_map<VideoEndpoint, std::unique_ptr<VideoTrack>> & {
|
||||
return _activeVideoTracks;
|
||||
}
|
||||
[[nodiscard]] auto shownVideoTracks() const
|
||||
@@ -625,7 +617,9 @@ private:
|
||||
rpl::event_stream<VideoStateToggle> _videoStreamActiveUpdates;
|
||||
rpl::event_stream<VideoStateToggle> _videoStreamPausedUpdates;
|
||||
rpl::event_stream<VideoStateToggle> _videoStreamShownUpdates;
|
||||
base::flat_map<VideoEndpoint, VideoTrack> _activeVideoTracks;
|
||||
base::flat_map<
|
||||
VideoEndpoint,
|
||||
std::unique_ptr<VideoTrack>> _activeVideoTracks;
|
||||
base::flat_set<VideoEndpoint> _shownVideoTracks;
|
||||
rpl::variable<VideoEndpoint> _videoEndpointLarge;
|
||||
rpl::variable<bool> _videoEndpointPinned = false;
|
||||
|
||||
@@ -61,6 +61,7 @@ enum class VideoQuality {
|
||||
|
||||
enum class Error {
|
||||
NoCamera,
|
||||
CameraFailed,
|
||||
ScreenFailed,
|
||||
MutedNoCamera,
|
||||
MutedNoScreen,
|
||||
@@ -68,4 +69,13 @@ enum class Error {
|
||||
DisabledNoScreen,
|
||||
};
|
||||
|
||||
enum class StickedTooltip {
|
||||
Camera = 0x01,
|
||||
Microphone = 0x02,
|
||||
};
|
||||
constexpr inline bool is_flag_type(StickedTooltip) {
|
||||
return true;
|
||||
}
|
||||
using StickedTooltips = base::flags<StickedTooltip>;
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -34,7 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
|
||||
#include "window/window_controller.h" // Controller::sessionController.
|
||||
#include "window/window_session_controller.h"
|
||||
#include "media/view/media_view_pip.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
@@ -1236,12 +1235,10 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
not_null<PeerListRow*> row) {
|
||||
const auto participantPeer = row->peer();
|
||||
const auto real = static_cast<Row*>(row.get());
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::groupCallPopupMenu);
|
||||
|
||||
const auto muteState = real->state();
|
||||
const auto muted = (muteState == Row::State::Muted)
|
||||
|| (muteState == Row::State::RaisedHand);
|
||||
const auto addVolumeItem = !muted || isMe(participantPeer);
|
||||
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
|
||||
const auto session = &_peer->session();
|
||||
const auto getCurrentWindow = [=]() -> Window::SessionController* {
|
||||
@@ -1262,6 +1259,12 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
}
|
||||
return getCurrentWindow();
|
||||
};
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
(addVolumeItem
|
||||
? st::groupCallPopupMenuWithVolume
|
||||
: st::groupCallPopupMenu));
|
||||
const auto weakMenu = Ui::MakeWeak(result.get());
|
||||
const auto performOnMainWindow = [=](auto callback) {
|
||||
if (const auto window = getWindow()) {
|
||||
@@ -1442,7 +1445,8 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
|
||||
auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
|
||||
|
||||
if (!muted || _call->joinAs() == participantPeer) {
|
||||
const auto addVolumeItem = !muted || isMe(participantPeer);
|
||||
if (addVolumeItem) {
|
||||
auto otherParticipantStateValue
|
||||
= _call->otherParticipantStateValue(
|
||||
) | rpl::filter([=](const Group::ParticipantState &data) {
|
||||
@@ -1451,7 +1455,7 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
|
||||
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
|
||||
menu->menu(),
|
||||
st::groupCallPopupMenu.menu,
|
||||
st::groupCallPopupVolumeMenu,
|
||||
otherParticipantStateValue,
|
||||
row->volume(),
|
||||
Group::kMaxVolume,
|
||||
@@ -1490,7 +1494,15 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
}
|
||||
}, volumeItem->lifetime());
|
||||
|
||||
if (!menu->empty()) {
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
menu->addAction(std::move(volumeItem));
|
||||
|
||||
if (!isMe(participantPeer)) {
|
||||
menu->addSeparator();
|
||||
}
|
||||
};
|
||||
|
||||
const auto muteAction = [&]() -> QAction* {
|
||||
|
||||
@@ -48,11 +48,13 @@ void EditGroupCallTitleBox(
|
||||
box->setFocusCallback([=] {
|
||||
input->setFocusFast();
|
||||
});
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
const auto submit = [=] {
|
||||
const auto result = input->getLastText().trimmed();
|
||||
box->closeBox();
|
||||
done(result);
|
||||
});
|
||||
};
|
||||
QObject::connect(input, &Ui::InputField::submitted, submit);
|
||||
box->addButton(tr::lng_settings_save(), submit);
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
@@ -76,11 +78,13 @@ void StartGroupCallRecordingBox(
|
||||
box->setFocusCallback([=] {
|
||||
input->setFocusFast();
|
||||
});
|
||||
box->addButton(tr::lng_group_call_recording_start_button(), [=] {
|
||||
const auto submit = [=] {
|
||||
const auto result = input->getLastText().trimmed();
|
||||
box->closeBox();
|
||||
done(result);
|
||||
});
|
||||
};
|
||||
QObject::connect(input, &Ui::InputField::submitted, submit);
|
||||
box->addButton(tr::lng_group_call_recording_start_button(), submit);
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/controls/call_mute_button.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/window.h"
|
||||
#include "ui/widgets/call_button.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "ui/widgets/window.h"
|
||||
#include "ui/chat/group_call_bar.h"
|
||||
#include "ui/layers/layer_manager.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
@@ -47,13 +46,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "app.h"
|
||||
#include "apiwrap.h" // api().kickParticipant.
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "webrtc/webrtc_media_devices.h" // UniqueDesktopCaptureSource.
|
||||
#include "webrtc/webrtc_audio_input_tester.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
@@ -71,6 +70,10 @@ constexpr auto kRecordingOpacity = 0.6;
|
||||
constexpr auto kStartNoConfirmation = TimeId(10);
|
||||
constexpr auto kControlsBackgroundOpacity = 0.8;
|
||||
constexpr auto kOverrideActiveColorBgAlpha = 172;
|
||||
constexpr auto kMicrophoneTooltipAfterLoudCount = 3;
|
||||
constexpr auto kDropLoudAfterQuietCount = 5;
|
||||
constexpr auto kMicrophoneTooltipLevelThreshold = 0.2;
|
||||
constexpr auto kMicrophoneTooltipCheckInterval = crl::time(500);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -84,17 +87,60 @@ struct Panel::ControlsBackgroundNarrow {
|
||||
Ui::RpWidget blocker;
|
||||
};
|
||||
|
||||
class Panel::MicLevelTester final {
|
||||
public:
|
||||
explicit MicLevelTester(Fn<void()> show);
|
||||
|
||||
[[nodiscard]] bool showTooltip() const;
|
||||
|
||||
private:
|
||||
void check();
|
||||
|
||||
Fn<void()> _show;
|
||||
base::Timer _timer;
|
||||
Webrtc::AudioInputTester _tester;
|
||||
int _loudCount = 0;
|
||||
int _quietCount = 0;
|
||||
|
||||
};
|
||||
|
||||
Panel::MicLevelTester::MicLevelTester(Fn<void()> show)
|
||||
: _show(std::move(show))
|
||||
, _timer([=] { check(); })
|
||||
, _tester(
|
||||
Core::App().settings().callAudioBackend(),
|
||||
Core::App().settings().callInputDeviceId()) {
|
||||
_timer.callEach(kMicrophoneTooltipCheckInterval);
|
||||
}
|
||||
|
||||
bool Panel::MicLevelTester::showTooltip() const {
|
||||
return (_loudCount >= kMicrophoneTooltipAfterLoudCount);
|
||||
}
|
||||
|
||||
void Panel::MicLevelTester::check() {
|
||||
const auto level = _tester.getAndResetLevel();
|
||||
if (level >= kMicrophoneTooltipLevelThreshold) {
|
||||
_quietCount = 0;
|
||||
if (++_loudCount >= kMicrophoneTooltipAfterLoudCount) {
|
||||
_show();
|
||||
}
|
||||
} else if (_loudCount > 0 && ++_quietCount >= kDropLoudAfterQuietCount) {
|
||||
_quietCount = 0;
|
||||
_loudCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Panel::Panel(not_null<GroupCall*> call)
|
||||
: _call(call)
|
||||
, _peer(call->peer())
|
||||
, _window(createWindow())
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(_window->body()))
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
_window->body(),
|
||||
widget(),
|
||||
st::groupCallTitle))
|
||||
#endif // !Q_OS_MAC
|
||||
, _viewport(std::make_unique<Viewport>(widget(), PanelMode::Wide, _backend))
|
||||
, _viewport(
|
||||
std::make_unique<Viewport>(widget(), PanelMode::Wide, _window.backend()))
|
||||
, _mute(std::make_unique<Ui::CallMuteButton>(
|
||||
widget(),
|
||||
st::callMuteButton,
|
||||
@@ -112,6 +158,8 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||
: Ui::CallMuteButtonType::ScheduledSilent),
|
||||
}))
|
||||
, _hangup(widget(), st::groupCallHangup)
|
||||
, _stickedTooltipsShown(Core::App().settings().hiddenGroupCallTooltips()
|
||||
& ~StickedTooltip::Microphone) // Always show tooltip about mic.
|
||||
, _toasts(std::make_unique<Toasts>(this)) {
|
||||
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
|
||||
_layerBg->setHideByBackgroundClick(true);
|
||||
@@ -123,7 +171,7 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||
|
||||
SubscribeToMigration(
|
||||
_peer,
|
||||
_window->lifetime(),
|
||||
lifetime(),
|
||||
[=](not_null<ChannelData*> channel) { migrate(channel); });
|
||||
setupRealCallViewers();
|
||||
|
||||
@@ -139,30 +187,11 @@ Panel::~Panel() {
|
||||
_viewport = nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::Window> Panel::createWindow() {
|
||||
auto result = std::make_unique<Ui::Window>();
|
||||
const auto capabilities = Ui::GL::CheckCapabilities(result.get());
|
||||
const auto use = Platform::IsMac()
|
||||
? true
|
||||
: Platform::IsWindows()
|
||||
? capabilities.supported
|
||||
: capabilities.transparency;
|
||||
LOG(("OpenGL: %1 (Calls::Group::Viewport)").arg(Logs::b(use)));
|
||||
_backend = use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster;
|
||||
|
||||
if (use) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We have to create a new window, if OpenGL initialization failed.
|
||||
return std::make_unique<Ui::Window>();
|
||||
}
|
||||
|
||||
void Panel::setupRealCallViewers() {
|
||||
_call->real(
|
||||
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
|
||||
subscribeToChanges(real);
|
||||
}, _window->lifetime());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<GroupCall*> Panel::call() const {
|
||||
@@ -170,9 +199,9 @@ not_null<GroupCall*> Panel::call() const {
|
||||
}
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return _window->isActiveWindow()
|
||||
&& _window->isVisible()
|
||||
&& !(_window->windowState() & Qt::WindowMinimized);
|
||||
return window()->isActiveWindow()
|
||||
&& window()->isVisible()
|
||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::showToast(TextWithEntities &&text, crl::time duration) {
|
||||
@@ -187,24 +216,24 @@ void Panel::showToast(TextWithEntities &&text, crl::time duration) {
|
||||
}
|
||||
|
||||
void Panel::minimize() {
|
||||
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
|
||||
window()->setWindowState(window()->windowState() | Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
void Panel::close() {
|
||||
_window->close();
|
||||
window()->close();
|
||||
}
|
||||
|
||||
void Panel::showAndActivate() {
|
||||
if (_window->isHidden()) {
|
||||
_window->show();
|
||||
if (window()->isHidden()) {
|
||||
window()->show();
|
||||
}
|
||||
const auto state = _window->windowState();
|
||||
const auto state = window()->windowState();
|
||||
if (state & Qt::WindowMinimized) {
|
||||
_window->setWindowState(state & ~Qt::WindowMinimized);
|
||||
window()->setWindowState(state & ~Qt::WindowMinimized);
|
||||
}
|
||||
_window->raise();
|
||||
_window->activateWindow();
|
||||
_window->setFocus();
|
||||
window()->raise();
|
||||
window()->activateWindow();
|
||||
window()->setFocus();
|
||||
}
|
||||
|
||||
void Panel::migrate(not_null<ChannelData*> channel) {
|
||||
@@ -219,12 +248,12 @@ void Panel::subscribeToPeerChanges() {
|
||||
Info::Profile::NameValue(
|
||||
_peer
|
||||
) | rpl::start_with_next([=](const TextWithEntities &name) {
|
||||
_window->setTitle(name.text);
|
||||
window()->setTitle(name.text);
|
||||
}, _peerLifetime);
|
||||
}
|
||||
|
||||
QWidget *Panel::chooseSourceParent() {
|
||||
return _window.get();
|
||||
return window().get();
|
||||
}
|
||||
|
||||
QString Panel::chooseSourceActiveDeviceId() {
|
||||
@@ -232,7 +261,7 @@ QString Panel::chooseSourceActiveDeviceId() {
|
||||
}
|
||||
|
||||
rpl::lifetime &Panel::chooseSourceInstanceLifetime() {
|
||||
return _window->lifetime();
|
||||
return lifetime();
|
||||
}
|
||||
|
||||
void Panel::chooseSourceAccepted(const QString &deviceId) {
|
||||
@@ -244,15 +273,15 @@ void Panel::chooseSourceStop() {
|
||||
}
|
||||
|
||||
void Panel::initWindow() {
|
||||
_window->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
_window->setAttribute(Qt::WA_NoSystemBackground);
|
||||
_window->setWindowIcon(
|
||||
window()->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
window()->setAttribute(Qt::WA_NoSystemBackground);
|
||||
window()->setWindowIcon(
|
||||
QIcon(QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly)));
|
||||
_window->setTitleStyle(st::groupCallTitle);
|
||||
window()->setTitleStyle(st::groupCallTitle);
|
||||
|
||||
subscribeToPeerChanges();
|
||||
|
||||
base::install_event_filter(_window.get(), [=](not_null<QEvent*> e) {
|
||||
base::install_event_filter(window().get(), [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Close && handleClose()) {
|
||||
e->ignore();
|
||||
return base::EventFilterResult::Cancel;
|
||||
@@ -267,7 +296,7 @@ void Panel::initWindow() {
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
_window->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
window()->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
using Flag = Ui::WindowTitleHitTestFlag;
|
||||
const auto titleRect = QRect(
|
||||
0,
|
||||
@@ -286,7 +315,7 @@ void Panel::initWindow() {
|
||||
_call->hasVideoWithFramesValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateMode();
|
||||
}, _window->lifetime());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void Panel::initWidget() {
|
||||
@@ -295,7 +324,7 @@ void Panel::initWidget() {
|
||||
widget()->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
paint(clip);
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
|
||||
widget()->sizeValue(
|
||||
) | rpl::skip(1) | rpl::start_with_next([=](QSize size) {
|
||||
@@ -306,7 +335,7 @@ void Panel::initWidget() {
|
||||
// title geometry depends on _controls->geometry,
|
||||
// which is not updated here yet.
|
||||
crl::on_main(widget(), [=] { refreshTitle(); });
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void Panel::endCall() {
|
||||
@@ -380,7 +409,7 @@ void Panel::initControls() {
|
||||
_call->canManageValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshTopButton();
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
|
||||
_hangup->setClickedCallback([=] { endCall(); });
|
||||
|
||||
@@ -418,7 +447,9 @@ void Panel::initControls() {
|
||||
}
|
||||
|
||||
_call->stateValue(
|
||||
) | rpl::filter([](State state) {
|
||||
) | rpl::before_next([=] {
|
||||
showStickedTooltip();
|
||||
}) | rpl::filter([](State state) {
|
||||
return (state == State::HangingUp)
|
||||
|| (state == State::Ended)
|
||||
|| (state == State::FailedHangingUp)
|
||||
@@ -499,18 +530,22 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
|
||||
&st::groupCallVideoActiveSmall);
|
||||
_video->show();
|
||||
_video->setClickedCallback([=] {
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Camera,
|
||||
StickedTooltipHide::Activated);
|
||||
_call->toggleVideo(!_call->isSharingCamera());
|
||||
});
|
||||
_video->setColorOverrides(
|
||||
toggleableOverrides(_call->isSharingCameraValue()));
|
||||
_call->isSharingCameraValue(
|
||||
) | rpl::start_with_next([=](bool sharing) {
|
||||
if (sharing) {
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Camera,
|
||||
StickedTooltipHide::Activated);
|
||||
}
|
||||
_video->setProgress(sharing ? 1. : 0.);
|
||||
}, _video->lifetime());
|
||||
_call->mutedValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateButtonsGeometry();
|
||||
}, _video->lifetime());
|
||||
}
|
||||
if (!_screenShare) {
|
||||
_screenShare.create(widget(), st::groupCallScreenShareSmall);
|
||||
@@ -536,6 +571,46 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
|
||||
updateButtonsGeometry();
|
||||
}
|
||||
|
||||
void Panel::hideStickedTooltip(StickedTooltipHide hide) {
|
||||
if (!_stickedTooltipClose || !_niceTooltipControl) {
|
||||
return;
|
||||
}
|
||||
if (_niceTooltipControl.data() == _video.data()) {
|
||||
hideStickedTooltip(StickedTooltip::Camera, hide);
|
||||
} else if (_niceTooltipControl.data() == _mute->outer().get()) {
|
||||
hideStickedTooltip(StickedTooltip::Microphone, hide);
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::hideStickedTooltip(
|
||||
StickedTooltip type,
|
||||
StickedTooltipHide hide) {
|
||||
if (hide != StickedTooltipHide::Unavailable) {
|
||||
_stickedTooltipsShown |= type;
|
||||
if (hide == StickedTooltipHide::Discarded) {
|
||||
Core::App().settings().setHiddenGroupCallTooltip(type);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}
|
||||
}
|
||||
const auto control = (type == StickedTooltip::Camera)
|
||||
? _video.data()
|
||||
: (type == StickedTooltip::Microphone)
|
||||
? _mute->outer().get()
|
||||
: nullptr;
|
||||
if (_niceTooltipControl.data() == control) {
|
||||
hideNiceTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::hideNiceTooltip() {
|
||||
if (!_niceTooltip) {
|
||||
return;
|
||||
}
|
||||
_stickedTooltipClose = nullptr;
|
||||
_niceTooltip.release()->toggleAnimated(false);
|
||||
_niceTooltipControl = nullptr;
|
||||
}
|
||||
|
||||
void Panel::initShareAction() {
|
||||
const auto showBoxCallback = [=](object_ptr<Ui::BoxContent> next) {
|
||||
_layerBg->showBox(std::move(next));
|
||||
@@ -552,7 +627,7 @@ void Panel::initShareAction() {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
widget()->lifetime().add(std::move(shareLinkLifetime));
|
||||
lifetime().add(std::move(shareLinkLifetime));
|
||||
}
|
||||
|
||||
void Panel::setupRealMuteButtonState(not_null<Data::GroupCall*> real) {
|
||||
@@ -628,7 +703,7 @@ void Panel::setupScheduledLabels(rpl::producer<TimeId> date) {
|
||||
) | rpl::map([=](TimeId date) {
|
||||
_countdownData = std::make_shared<Ui::GroupCallScheduledLeft>(date);
|
||||
return rpl::empty_value();
|
||||
}) | rpl::start_spawning(widget()->lifetime());
|
||||
}) | rpl::start_spawning(lifetime());
|
||||
|
||||
_countdown = Ui::CreateGradientLabel(widget(), rpl::duplicate(
|
||||
countdownCreated
|
||||
@@ -696,7 +771,7 @@ void Panel::setupMembers() {
|
||||
_countdown.destroy();
|
||||
_startsWhen.destroy();
|
||||
|
||||
_members.create(widget(), _call, mode(), _backend);
|
||||
_members.create(widget(), _call, mode(), _window.backend());
|
||||
|
||||
setupVideo(_viewport.get());
|
||||
setupVideo(_members->viewport());
|
||||
@@ -753,40 +828,40 @@ void Panel::setupMembers() {
|
||||
}
|
||||
|
||||
void Panel::enlargeVideo() {
|
||||
_lastSmallGeometry = _window->geometry();
|
||||
_lastSmallGeometry = window()->geometry();
|
||||
|
||||
const auto available = _window->screen()->availableGeometry();
|
||||
const auto available = window()->screen()->availableGeometry();
|
||||
const auto width = std::max(
|
||||
_window->width(),
|
||||
window()->width(),
|
||||
std::max(
|
||||
std::min(available.width(), st::groupCallWideModeSize.width()),
|
||||
st::groupCallWideModeWidthMin));
|
||||
const auto height = std::max(
|
||||
_window->height(),
|
||||
window()->height(),
|
||||
std::min(available.height(), st::groupCallWideModeSize.height()));
|
||||
auto geometry = QRect(_window->pos(), QSize(width, height));
|
||||
auto geometry = QRect(window()->pos(), QSize(width, height));
|
||||
if (geometry.x() < available.x()) {
|
||||
geometry.setX(std::min(available.x(), _window->x()));
|
||||
geometry.moveLeft(std::min(available.x(), window()->x()));
|
||||
}
|
||||
if (geometry.x() + geometry.width()
|
||||
> available.x() + available.width()) {
|
||||
geometry.setX(std::max(
|
||||
geometry.moveLeft(std::max(
|
||||
available.x() + available.width(),
|
||||
_window->x() + _window->width()) - geometry.width());
|
||||
window()->x() + window()->width()) - geometry.width());
|
||||
}
|
||||
if (geometry.y() < available.y()) {
|
||||
geometry.setY(std::min(available.y(), _window->y()));
|
||||
geometry.moveTop(std::min(available.y(), window()->y()));
|
||||
}
|
||||
if (geometry.y() + geometry.height() > available.y() + available.height()) {
|
||||
geometry.setY(std::max(
|
||||
geometry.moveTop(std::max(
|
||||
available.y() + available.height(),
|
||||
_window->y() + _window->height()) - geometry.height());
|
||||
window()->y() + window()->height()) - geometry.height());
|
||||
}
|
||||
if (_lastLargeMaximized) {
|
||||
_window->setWindowState(
|
||||
_window->windowState() | Qt::WindowMaximized);
|
||||
window()->setWindowState(
|
||||
window()->windowState() | Qt::WindowMaximized);
|
||||
} else {
|
||||
_window->setGeometry((_lastLargeGeometry
|
||||
window()->setGeometry((_lastLargeGeometry
|
||||
&& available.intersects(*_lastLargeGeometry))
|
||||
? *_lastLargeGeometry
|
||||
: geometry);
|
||||
@@ -794,23 +869,23 @@ void Panel::enlargeVideo() {
|
||||
}
|
||||
|
||||
void Panel::minimizeVideo() {
|
||||
if (_window->windowState() & Qt::WindowMaximized) {
|
||||
if (window()->windowState() & Qt::WindowMaximized) {
|
||||
_lastLargeMaximized = true;
|
||||
_window->setWindowState(
|
||||
_window->windowState() & ~Qt::WindowMaximized);
|
||||
window()->setWindowState(
|
||||
window()->windowState() & ~Qt::WindowMaximized);
|
||||
} else {
|
||||
_lastLargeMaximized = false;
|
||||
_lastLargeGeometry = _window->geometry();
|
||||
_lastLargeGeometry = window()->geometry();
|
||||
}
|
||||
const auto available = _window->screen()->availableGeometry();
|
||||
const auto available = window()->screen()->availableGeometry();
|
||||
const auto width = st::groupCallWidth;
|
||||
const auto height = st::groupCallHeight;
|
||||
auto geometry = QRect(
|
||||
_window->x() + (_window->width() - width) / 2,
|
||||
_window->y() + (_window->height() - height) / 2,
|
||||
window()->x() + (window()->width() - width) / 2,
|
||||
window()->y() + (window()->height() - height) / 2,
|
||||
width,
|
||||
height);
|
||||
_window->setGeometry((_lastSmallGeometry
|
||||
window()->setGeometry((_lastSmallGeometry
|
||||
&& available.intersects(*_lastSmallGeometry))
|
||||
? *_lastSmallGeometry
|
||||
: geometry);
|
||||
@@ -838,14 +913,17 @@ void Panel::raiseControls() {
|
||||
}
|
||||
}
|
||||
_mute->raise();
|
||||
if (_niceTooltip) {
|
||||
_niceTooltip->raise();
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::setupVideo(not_null<Viewport*> viewport) {
|
||||
const auto setupTile = [=](
|
||||
const VideoEndpoint &endpoint,
|
||||
const GroupCall::VideoTrack &track) {
|
||||
const std::unique_ptr<GroupCall::VideoTrack> &track) {
|
||||
using namespace rpl::mappers;
|
||||
const auto row = _members->lookupRow(track.peer);
|
||||
const auto row = _members->lookupRow(GroupCall::TrackPeer(track));
|
||||
Assert(row != nullptr);
|
||||
auto pinned = rpl::combine(
|
||||
_call->videoEndpointLargeValue(),
|
||||
@@ -853,8 +931,8 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
|
||||
) | rpl::map(_1 == endpoint && _2);
|
||||
viewport->add(
|
||||
endpoint,
|
||||
VideoTileTrack{ track.track.get(), row },
|
||||
track.trackSize.value(),
|
||||
VideoTileTrack{ GroupCall::TrackPointer(track), row },
|
||||
GroupCall::TrackSizeValue(track),
|
||||
std::move(pinned));
|
||||
};
|
||||
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
|
||||
@@ -908,18 +986,24 @@ void Panel::toggleWideControls(bool shown) {
|
||||
}
|
||||
_showWideControls = shown;
|
||||
crl::on_main(widget(), [=] {
|
||||
if (_wideControlsShown == _showWideControls) {
|
||||
return;
|
||||
}
|
||||
_wideControlsShown = _showWideControls;
|
||||
_wideControlsAnimation.start(
|
||||
[=] { updateButtonsGeometry(); },
|
||||
_wideControlsShown ? 0. : 1.,
|
||||
_wideControlsShown ? 1. : 0.,
|
||||
st::slideWrapDuration);
|
||||
updateWideControlsVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::updateWideControlsVisibility() {
|
||||
const auto shown = _showWideControls
|
||||
|| (_stickedTooltipClose != nullptr);
|
||||
if (_wideControlsShown == shown) {
|
||||
return;
|
||||
}
|
||||
_wideControlsShown = shown;
|
||||
_wideControlsAnimation.start(
|
||||
[=] { updateButtonsGeometry(); },
|
||||
_wideControlsShown ? 0. : 1.,
|
||||
_wideControlsShown ? 1. : 0.,
|
||||
st::slideWrapDuration);
|
||||
}
|
||||
|
||||
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
const auto validateRecordingMark = [=](bool recording) {
|
||||
if (!recording && _recordingMark) {
|
||||
@@ -980,7 +1064,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
: tr::lng_group_call_recording_stopped)(
|
||||
tr::now,
|
||||
Ui::Text::RichLangValue));
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
validateRecordingMark(real->recordStartDate() != 0);
|
||||
|
||||
rpl::combine(
|
||||
@@ -988,14 +1072,27 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_call->isSharingCameraValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshVideoButtons();
|
||||
}, widget()->lifetime());
|
||||
showStickedTooltip();
|
||||
}, lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_call->videoIsWorkingValue(),
|
||||
_call->isSharingScreenValue()
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshTopButton();
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
|
||||
_call->mutedValue(
|
||||
) | rpl::skip(1) | rpl::start_with_next([=](MuteState state) {
|
||||
updateButtonsGeometry();
|
||||
if (state == MuteState::Active
|
||||
|| state == MuteState::PushToTalk) {
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Microphone,
|
||||
StickedTooltipHide::Activated);
|
||||
}
|
||||
showStickedTooltip();
|
||||
}, lifetime());
|
||||
|
||||
updateControlsGeometry();
|
||||
}
|
||||
@@ -1041,7 +1138,7 @@ void Panel::refreshTopButton() {
|
||||
chooseJoinAs();
|
||||
});
|
||||
updateControlsGeometry();
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
} else {
|
||||
_menuToggle.destroy();
|
||||
_joinAsToggle.destroy();
|
||||
@@ -1295,7 +1392,7 @@ void Panel::initLayout() {
|
||||
) | rpl::start_with_next([=] {
|
||||
// _menuToggle geometry depends on _controls arrangement.
|
||||
crl::on_main(widget(), [=] { updateControlsGeometry(); });
|
||||
}, widget()->lifetime());
|
||||
}, lifetime());
|
||||
|
||||
#endif // !Q_OS_MAC
|
||||
}
|
||||
@@ -1307,16 +1404,20 @@ void Panel::showControls() {
|
||||
}
|
||||
|
||||
void Panel::closeBeforeDestroy() {
|
||||
_window->close();
|
||||
window()->close();
|
||||
_callLifetime.destroy();
|
||||
}
|
||||
|
||||
rpl::lifetime &Panel::lifetime() {
|
||||
return window()->lifetime();
|
||||
}
|
||||
|
||||
void Panel::initGeometry() {
|
||||
const auto center = Core::App().getPointForCallPanelCenter();
|
||||
const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight);
|
||||
_window->setGeometry(rect.translated(center - rect.center()));
|
||||
_window->setMinimumSize(rect.size());
|
||||
_window->show();
|
||||
window()->setGeometry(rect.translated(center - rect.center()));
|
||||
window()->setMinimumSize(rect.size());
|
||||
window()->show();
|
||||
}
|
||||
|
||||
QRect Panel::computeTitleRect() const {
|
||||
@@ -1352,7 +1453,10 @@ bool Panel::updateMode() {
|
||||
_call->showVideoEndpointLarge({});
|
||||
}
|
||||
refreshVideoButtons(wide);
|
||||
_niceTooltip.destroy();
|
||||
if (!_stickedTooltipClose
|
||||
|| _niceTooltipControl.data() != _mute->outer().get()) {
|
||||
_niceTooltip.destroy();
|
||||
}
|
||||
_mode = mode;
|
||||
if (_title) {
|
||||
_title->setTextColorOverride(wide
|
||||
@@ -1373,6 +1477,7 @@ bool Panel::updateMode() {
|
||||
updateButtonsStyles();
|
||||
refreshControlsBackground();
|
||||
updateControlsGeometry();
|
||||
showStickedTooltip();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1557,8 +1662,12 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
|
||||
}
|
||||
|
||||
void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
|
||||
if (_niceTooltip) {
|
||||
_niceTooltip.release()->toggleAnimated(false);
|
||||
if (_stickedTooltipClose) {
|
||||
if (!over) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
hideNiceTooltip();
|
||||
}
|
||||
if (over) {
|
||||
Ui::Integration::Instance().registerLeaveSubscription(control);
|
||||
@@ -1569,7 +1678,52 @@ void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
|
||||
toggleWideControls(over);
|
||||
}
|
||||
|
||||
void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
|
||||
void Panel::showStickedTooltip() {
|
||||
static const auto kHasCamera = !Webrtc::GetVideoInputList().empty();
|
||||
const auto callReady = (_call->state() == State::Joined
|
||||
|| _call->state() == State::Connecting);
|
||||
if (!(_stickedTooltipsShown & StickedTooltip::Camera)
|
||||
&& callReady
|
||||
&& (_mode.current() == PanelMode::Wide)
|
||||
&& _video
|
||||
&& _call->videoIsWorking()
|
||||
&& !_call->mutedByAdmin()
|
||||
&& kHasCamera) { // Don't recount this every time for now.
|
||||
showNiceTooltip(_video, NiceTooltipType::Sticked);
|
||||
return;
|
||||
}
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Camera,
|
||||
StickedTooltipHide::Unavailable);
|
||||
|
||||
if (!(_stickedTooltipsShown & StickedTooltip::Microphone)
|
||||
&& callReady
|
||||
&& _mute
|
||||
&& !_call->mutedByAdmin()) {
|
||||
if (_stickedTooltipClose) {
|
||||
// Showing already.
|
||||
return;
|
||||
} else if (!_micLevelTester) {
|
||||
// Check if there is incoming sound.
|
||||
_micLevelTester = std::make_unique<MicLevelTester>([=] {
|
||||
showStickedTooltip();
|
||||
});
|
||||
}
|
||||
if (_micLevelTester->showTooltip()) {
|
||||
_micLevelTester = nullptr;
|
||||
showNiceTooltip(_mute->outer(), NiceTooltipType::Sticked);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_micLevelTester = nullptr;
|
||||
hideStickedTooltip(
|
||||
StickedTooltip::Microphone,
|
||||
StickedTooltipHide::Unavailable);
|
||||
}
|
||||
|
||||
void Panel::showNiceTooltip(
|
||||
not_null<Ui::RpWidget*> control,
|
||||
NiceTooltipType type) {
|
||||
auto text = [&]() -> rpl::producer<QString> {
|
||||
if (control == _screenShare.data()) {
|
||||
if (_call->mutedByAdmin()) {
|
||||
@@ -1595,40 +1749,98 @@ void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
|
||||
}
|
||||
return rpl::producer<QString>();
|
||||
}();
|
||||
if (!text
|
||||
|| _wideControlsAnimation.animating()
|
||||
|| !_wideControlsShown) {
|
||||
if (!text || _stickedTooltipClose) {
|
||||
return;
|
||||
} else if (_wideControlsAnimation.animating() || !_wideControlsShown) {
|
||||
if (type == NiceTooltipType::Normal) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto inner = [&]() -> Ui::RpWidget* {
|
||||
const auto normal = (type == NiceTooltipType::Normal);
|
||||
auto container = normal
|
||||
? nullptr
|
||||
: Ui::CreateChild<Ui::RpWidget>(widget().get());
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
(normal ? widget().get() : container),
|
||||
std::move(text),
|
||||
st::groupCallNiceTooltipLabel);
|
||||
if (normal) {
|
||||
return label;
|
||||
}
|
||||
const auto button = Ui::CreateChild<Ui::IconButton>(
|
||||
container,
|
||||
st::groupCallStickedTooltipClose);
|
||||
rpl::combine(
|
||||
label->sizeValue(),
|
||||
button->sizeValue()
|
||||
) | rpl::start_with_next([=](QSize text, QSize close) {
|
||||
const auto height = std::max(text.height(), close.height());
|
||||
container->resize(text.width() + close.width(), height);
|
||||
label->move(0, (height - text.height()) / 2);
|
||||
button->move(text.width(), (height - close.height()) / 2);
|
||||
}, container->lifetime());
|
||||
button->setClickedCallback([=] {
|
||||
hideStickedTooltip(StickedTooltipHide::Discarded);
|
||||
});
|
||||
_stickedTooltipClose = button;
|
||||
updateWideControlsVisibility();
|
||||
return container;
|
||||
}();
|
||||
_niceTooltip.create(
|
||||
widget().get(),
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
widget().get(),
|
||||
std::move(text),
|
||||
st::groupCallNiceTooltipLabel),
|
||||
st::groupCallNiceTooltip);
|
||||
object_ptr<Ui::RpWidget>::fromRaw(inner),
|
||||
(type == NiceTooltipType::Sticked
|
||||
? st::groupCallStickedTooltip
|
||||
: st::groupCallNiceTooltip));
|
||||
const auto tooltip = _niceTooltip.data();
|
||||
const auto weak = QPointer<QWidget>(tooltip);
|
||||
const auto destroy = [=] {
|
||||
delete weak.data();
|
||||
};
|
||||
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (type != NiceTooltipType::Sticked) {
|
||||
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
tooltip->setHiddenCallback(destroy);
|
||||
base::qt_signal_producer(
|
||||
control.get(),
|
||||
&QObject::destroyed
|
||||
) | rpl::start_with_next(destroy, tooltip->lifetime());
|
||||
|
||||
const auto geometry = control->geometry();
|
||||
_niceTooltipControl = control;
|
||||
updateTooltipGeometry();
|
||||
tooltip->toggleAnimated(true);
|
||||
}
|
||||
|
||||
void Panel::updateTooltipGeometry() {
|
||||
if (!_niceTooltip) {
|
||||
return;
|
||||
} else if (!_niceTooltipControl) {
|
||||
hideNiceTooltip();
|
||||
return;
|
||||
}
|
||||
const auto geometry = _niceTooltipControl->geometry();
|
||||
const auto weak = QPointer<QWidget>(_niceTooltip);
|
||||
const auto countPosition = [=](QSize size) {
|
||||
const auto strong = weak.data();
|
||||
if (!strong) {
|
||||
return QPoint();
|
||||
}
|
||||
const auto wide = (_mode.current() == PanelMode::Wide);
|
||||
const auto top = geometry.y()
|
||||
- st::groupCallNiceTooltipTop
|
||||
- (wide ? st::groupCallNiceTooltipTop : 0)
|
||||
- size.height();
|
||||
const auto middle = geometry.center().x();
|
||||
if (!strong) {
|
||||
return QPoint();
|
||||
} else if (!wide) {
|
||||
return QPoint(
|
||||
std::max(
|
||||
std::min(
|
||||
middle - size.width() / 2,
|
||||
(widget()->width()
|
||||
- st::groupCallMembersMargin.right()
|
||||
- size.width())),
|
||||
st::groupCallMembersMargin.left()),
|
||||
top);
|
||||
}
|
||||
const auto back = _controlsBackgroundWide.data();
|
||||
if (size.width() >= _viewport->widget()->width()) {
|
||||
return QPoint(_viewport->widget()->x(), top);
|
||||
@@ -1645,8 +1857,7 @@ void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
|
||||
return QPoint(middle - size.width() / 2, top);
|
||||
}
|
||||
};
|
||||
tooltip->pointAt(geometry, RectPart::Top, countPosition);
|
||||
tooltip->toggleAnimated(true);
|
||||
_niceTooltip->pointAt(geometry, RectPart::Top, countPosition);
|
||||
}
|
||||
|
||||
void Panel::trackControls(bool track) {
|
||||
@@ -1832,6 +2043,7 @@ void Panel::updateButtonsGeometry() {
|
||||
width,
|
||||
st::groupCallMembersBottomSkip);
|
||||
}
|
||||
updateTooltipGeometry();
|
||||
}
|
||||
|
||||
bool Panel::videoButtonInNarrowMode() const {
|
||||
@@ -1999,14 +2211,18 @@ void Panel::paint(QRect clip) {
|
||||
|
||||
bool Panel::handleClose() {
|
||||
if (_call) {
|
||||
_window->hide();
|
||||
window()->hide();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
not_null<Ui::Window*> Panel::window() const {
|
||||
return _window.window();
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> Panel::widget() const {
|
||||
return _window->body();
|
||||
return _window.widget();
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -11,9 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
#include "calls/group/ui/desktop_capture_choose_source.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/gl/gl_window.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class Image;
|
||||
@@ -37,14 +39,10 @@ template <typename Widget>
|
||||
class FadeWrap;
|
||||
template <typename Widget>
|
||||
class PaddingWrap;
|
||||
class Window;
|
||||
class ScrollArea;
|
||||
class GenericBox;
|
||||
class LayerManager;
|
||||
class GroupCallScheduledLeft;
|
||||
namespace GL {
|
||||
enum class Backend;
|
||||
} // namespace GL
|
||||
namespace Toast {
|
||||
class Instance;
|
||||
} // namespace Toast
|
||||
@@ -64,6 +62,7 @@ class Toasts;
|
||||
class Members;
|
||||
class Viewport;
|
||||
enum class PanelMode;
|
||||
enum class StickedTooltip;
|
||||
|
||||
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
|
||||
public:
|
||||
@@ -80,11 +79,24 @@ public:
|
||||
void showAndActivate();
|
||||
void closeBeforeDestroy();
|
||||
|
||||
rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
using State = GroupCall::State;
|
||||
struct ControlsBackgroundNarrow;
|
||||
|
||||
std::unique_ptr<Ui::Window> createWindow();
|
||||
enum class NiceTooltipType {
|
||||
Normal,
|
||||
Sticked,
|
||||
};
|
||||
enum class StickedTooltipHide {
|
||||
Unavailable,
|
||||
Activated,
|
||||
Discarded,
|
||||
};
|
||||
class MicLevelTester;
|
||||
|
||||
[[nodiscard]] not_null<Ui::Window*> window() const;
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
|
||||
[[nodiscard]] PanelMode mode() const;
|
||||
@@ -111,11 +123,18 @@ private:
|
||||
|
||||
void trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime);
|
||||
void trackControlOver(not_null<Ui::RpWidget*> control, bool over);
|
||||
void showNiceTooltip(not_null<Ui::RpWidget*> control);
|
||||
void showNiceTooltip(
|
||||
not_null<Ui::RpWidget*> control,
|
||||
NiceTooltipType type = NiceTooltipType::Normal);
|
||||
void showStickedTooltip();
|
||||
void hideStickedTooltip(StickedTooltipHide hide);
|
||||
void hideStickedTooltip(StickedTooltip type, StickedTooltipHide hide);
|
||||
void hideNiceTooltip();
|
||||
|
||||
bool updateMode();
|
||||
void updateControlsGeometry();
|
||||
void updateButtonsGeometry();
|
||||
void updateTooltipGeometry();
|
||||
void updateButtonsStyles();
|
||||
void updateMembersGeometry();
|
||||
void refreshControlsBackground();
|
||||
@@ -127,6 +146,7 @@ private:
|
||||
std::optional<bool> overrideWideMode = std::nullopt);
|
||||
void refreshTopButton();
|
||||
void toggleWideControls(bool shown);
|
||||
void updateWideControlsVisibility();
|
||||
[[nodiscard]] bool videoButtonInNarrowMode() const;
|
||||
|
||||
void endCall();
|
||||
@@ -156,8 +176,7 @@ private:
|
||||
const not_null<GroupCall*> _call;
|
||||
not_null<PeerData*> _peer;
|
||||
|
||||
Ui::GL::Backend _backend = Ui::GL::Backend();
|
||||
const std::unique_ptr<Ui::Window> _window;
|
||||
Ui::GL::Window _window;
|
||||
const std::unique_ptr<Ui::LayerManager> _layerBg;
|
||||
rpl::variable<PanelMode> _mode;
|
||||
|
||||
@@ -202,11 +221,16 @@ private:
|
||||
std::unique_ptr<Ui::CallMuteButton> _mute;
|
||||
object_ptr<Ui::CallButton> _hangup;
|
||||
object_ptr<Ui::ImportantTooltip> _niceTooltip = { nullptr };
|
||||
QPointer<Ui::IconButton> _stickedTooltipClose;
|
||||
QPointer<Ui::RpWidget> _niceTooltipControl;
|
||||
StickedTooltips _stickedTooltipsShown;
|
||||
Fn<void()> _callShareLinkCallback;
|
||||
|
||||
const std::unique_ptr<Toasts> _toasts;
|
||||
base::weak_ptr<Ui::Toast::Instance> _lastToast;
|
||||
|
||||
std::unique_ptr<MicLevelTester> _micLevelTester;
|
||||
|
||||
rpl::lifetime _peerLifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -493,8 +493,10 @@ void SettingsBox(
|
||||
tr::now,
|
||||
lt_delay,
|
||||
FormatDelay(delay)));
|
||||
Core::App().settings().setGroupCallPushToTalkDelay(delay);
|
||||
applyAndSave();
|
||||
if (Core::App().settings().groupCallPushToTalkDelay() != delay) {
|
||||
Core::App().settings().setGroupCallPushToTalkDelay(delay);
|
||||
applyAndSave();
|
||||
}
|
||||
};
|
||||
callback(value);
|
||||
const auto slider = pushToTalkInner->add(
|
||||
|
||||
@@ -105,7 +105,9 @@ void Toasts::setupPinnedVideo() {
|
||||
? _call->videoEndpointLargeValue()
|
||||
: rpl::single(_call->videoEndpointLarge());
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
||||
) | rpl::filter([=] {
|
||||
return (_call->shownVideoTracks().size() > 1);
|
||||
}) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
||||
const auto pinned = _call->videoEndpointPinned();
|
||||
const auto peer = endpoint.peer;
|
||||
if (!peer) {
|
||||
@@ -155,6 +157,8 @@ void Toasts::setupError() {
|
||||
const auto key = [&] {
|
||||
switch (error) {
|
||||
case Error::NoCamera: return tr::lng_call_error_no_camera;
|
||||
case Error::CameraFailed:
|
||||
return tr::lng_group_call_failed_camera;
|
||||
case Error::ScreenFailed:
|
||||
return tr::lng_group_call_failed_screen;
|
||||
case Error::MutedNoCamera:
|
||||
|
||||
@@ -191,7 +191,7 @@ void Viewport::updateSelected(QPoint position) {
|
||||
return;
|
||||
}
|
||||
for (const auto &tile : _tiles) {
|
||||
const auto geometry = tile->shown()
|
||||
const auto geometry = tile->visible()
|
||||
? tile->geometry()
|
||||
: QRect();
|
||||
if (geometry.contains(position)) {
|
||||
@@ -763,7 +763,12 @@ void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
|
||||
const auto kMedium = style::ConvertScale(540);
|
||||
const auto kSmall = style::ConvertScale(240);
|
||||
const auto &endpoint = tile->endpoint();
|
||||
const auto quality = (min >= kMedium)
|
||||
const auto forceThumbnailQuality = !wide()
|
||||
&& (ranges::count(_tiles, false, &VideoTile::hidden) > 1);
|
||||
const auto forceFullQuality = wide() && (tile.get() == _large);
|
||||
const auto quality = forceThumbnailQuality
|
||||
? VideoQuality::Thumbnail
|
||||
: (forceFullQuality || min >= kMedium)
|
||||
? VideoQuality::Full
|
||||
: (min >= kSmall)
|
||||
? VideoQuality::Medium
|
||||
|
||||
@@ -439,7 +439,7 @@ void Viewport::RendererGL::paint(
|
||||
validateDatas();
|
||||
auto index = 0;
|
||||
for (const auto &tile : _owner->_tiles) {
|
||||
if (!tile->shown()) {
|
||||
if (!tile->visible()) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
@@ -988,6 +988,7 @@ void Viewport::RendererGL::bindFrame(
|
||||
program.argb32->setUniformValue("s_texture", GLint(0));
|
||||
} else {
|
||||
const auto yuv = data.yuv420;
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
program.yuv420->bind();
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
tileData.textures.bind(f, 0);
|
||||
@@ -995,8 +996,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->size,
|
||||
tileData.textureSize,
|
||||
yuv->y.stride,
|
||||
@@ -1009,8 +1010,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
tileData.textureChromaSize,
|
||||
yuv->u.stride,
|
||||
@@ -1021,8 +1022,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
tileData.textureChromaSize,
|
||||
yuv->v.stride,
|
||||
@@ -1368,16 +1369,17 @@ void Viewport::RendererGL::validateNoiseTexture(
|
||||
if (_noiseTexture.created()) {
|
||||
return;
|
||||
}
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
_noiseTexture.ensureCreated(f, GL_NEAREST, GL_REPEAT);
|
||||
_noiseTexture.bind(f, 0);
|
||||
f.glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RED,
|
||||
format,
|
||||
kNoiseTextureSize,
|
||||
kNoiseTextureSize,
|
||||
0,
|
||||
GL_RED,
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ void Viewport::RendererSW::paintFallback(
|
||||
tileData.stale = true;
|
||||
}
|
||||
for (const auto &tile : _owner->_tiles) {
|
||||
if (!tile->shown()) {
|
||||
if (!tile->visible()) {
|
||||
continue;
|
||||
}
|
||||
paintTile(p, tile.get(), bounding, bg);
|
||||
|
||||
@@ -82,14 +82,14 @@ bool Viewport::VideoTile::screencast() const {
|
||||
void Viewport::VideoTile::setGeometry(
|
||||
QRect geometry,
|
||||
TileAnimation animation) {
|
||||
_shown = true;
|
||||
_hidden = false;
|
||||
_geometry = geometry;
|
||||
_animation = animation;
|
||||
updateTopControlsPosition();
|
||||
}
|
||||
|
||||
void Viewport::VideoTile::hide() {
|
||||
_shown = false;
|
||||
_hidden = true;
|
||||
_quality = std::nullopt;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ void Viewport::VideoTile::toggleTopControlsShown(bool shown) {
|
||||
}
|
||||
|
||||
bool Viewport::VideoTile::updateRequestedQuality(VideoQuality quality) {
|
||||
if (!_shown) {
|
||||
if (_hidden) {
|
||||
_quality = std::nullopt;
|
||||
return false;
|
||||
} else if (_quality && *_quality == quality) {
|
||||
@@ -249,7 +249,7 @@ void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
|
||||
}) | rpl::start_with_next([=](bool pinned) {
|
||||
_pinned = pinned;
|
||||
updateTopControlsSize();
|
||||
if (_shown) {
|
||||
if (!_hidden) {
|
||||
updateTopControlsPosition();
|
||||
_update();
|
||||
}
|
||||
|
||||
@@ -45,8 +45,11 @@ public:
|
||||
[[nodiscard]] bool pinned() const {
|
||||
return _pinned;
|
||||
}
|
||||
[[nodiscard]] bool shown() const {
|
||||
return _shown && !_geometry.isEmpty();
|
||||
[[nodiscard]] bool hidden() const {
|
||||
return _hidden;
|
||||
}
|
||||
[[nodiscard]] bool visible() const {
|
||||
return !_hidden && !_geometry.isEmpty();
|
||||
}
|
||||
[[nodiscard]] QRect pinOuter() const;
|
||||
[[nodiscard]] QRect pinInner() const;
|
||||
@@ -115,7 +118,7 @@ private:
|
||||
Ui::Animations::Simple _topControlsShownAnimation;
|
||||
bool _topControlsShown = false;
|
||||
bool _pinned = false;
|
||||
bool _shown = false;
|
||||
bool _hidden = true;
|
||||
std::optional<VideoQuality> _quality;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
@@ -30,16 +30,12 @@ constexpr auto kVolumeStickedValues =
|
||||
{ 25. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 50. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 75. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 100. / kMaxVolumePercent, 5. / kMaxVolumePercent },
|
||||
{ 100. / kMaxVolumePercent, 10. / kMaxVolumePercent },
|
||||
{ 125. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 150. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 175. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
}};
|
||||
|
||||
QString VolumeString(int volumePercent) {
|
||||
return u"%1%"_q.arg(volumePercent);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MenuVolumeItem::MenuVolumeItem(
|
||||
@@ -75,20 +71,21 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
const auto geometry = QRect(QPoint(), size);
|
||||
_itemRect = geometry - _st.itemPadding;
|
||||
_itemRect = geometry - st::groupCallMenuVolumePadding;
|
||||
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
|
||||
_arcPosition = _speakerRect.center()
|
||||
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip);
|
||||
_volumeRect = QRect(
|
||||
_arcPosition.x()
|
||||
+ st::groupCallMenuVolumeSkip
|
||||
+ _arcs->finishedWidth(),
|
||||
const auto sliderLeft = _arcPosition.x()
|
||||
+ st::groupCallMenuVolumeSkip
|
||||
+ _arcs->maxWidth()
|
||||
+ st::groupCallMenuVolumeSkip;
|
||||
_slider->setGeometry(
|
||||
st::groupCallMenuVolumeMargin.left(),
|
||||
_speakerRect.y(),
|
||||
_st.itemStyle.font->width(VolumeString(kMaxVolumePercent)),
|
||||
(geometry.width()
|
||||
- st::groupCallMenuVolumeMargin.left()
|
||||
- st::groupCallMenuVolumeMargin.right()),
|
||||
_speakerRect.height());
|
||||
|
||||
_slider->setGeometry(_itemRect
|
||||
- style::margins(0, contentHeight() / 2, 0, 0));
|
||||
}, lifetime());
|
||||
|
||||
setCloudVolume(startVolume);
|
||||
@@ -110,15 +107,12 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
unmuteColor(),
|
||||
muteColor(),
|
||||
muteProgress);
|
||||
p.setPen(mutePen);
|
||||
p.setFont(_st.itemStyle.font);
|
||||
p.drawText(_volumeRect, VolumeString(volume), style::al_left);
|
||||
|
||||
_crossLineMute->paint(
|
||||
p,
|
||||
_speakerRect.topLeft(),
|
||||
muteProgress,
|
||||
(!muteProgress) ? std::nullopt : std::optional<QColor>(mutePen));
|
||||
(muteProgress > 0) ? std::make_optional(mutePen) : std::nullopt);
|
||||
|
||||
{
|
||||
p.translate(_arcPosition);
|
||||
@@ -133,7 +127,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
_toggleMuteLocallyRequests.fire_copy(newMuted);
|
||||
|
||||
_crossLineAnimation.start(
|
||||
[=] { update(_speakerRect.united(_volumeRect)); },
|
||||
[=] { update(_speakerRect); },
|
||||
_localMuted ? 0. : 1.,
|
||||
_localMuted ? 1. : 0.,
|
||||
st::callPanelDuration);
|
||||
@@ -141,8 +135,8 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
if (value > 0) {
|
||||
_changeVolumeLocallyRequests.fire(value * _maxVolume);
|
||||
}
|
||||
update(_volumeRect);
|
||||
_arcs->setValue(value);
|
||||
updateSliderColor(value);
|
||||
});
|
||||
|
||||
const auto returnVolume = [=] {
|
||||
@@ -169,6 +163,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
if (!_cloudMuted && !muted) {
|
||||
_changeVolumeRequests.fire_copy(newVolume);
|
||||
}
|
||||
updateSliderColor(value);
|
||||
});
|
||||
|
||||
std::move(
|
||||
@@ -209,30 +204,15 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
}
|
||||
|
||||
void MenuVolumeItem::initArcsAnimation() {
|
||||
const auto volumeLeftWas = lifetime().make_state<int>(0);
|
||||
const auto lastTime = lifetime().make_state<int>(0);
|
||||
_arcsAnimation.init([=](crl::time now) {
|
||||
_arcs->update(now);
|
||||
update(_speakerRect);
|
||||
|
||||
const auto wasRect = _volumeRect;
|
||||
_volumeRect.moveLeft(anim::interpolate(
|
||||
*volumeLeftWas,
|
||||
_arcPosition.x()
|
||||
+ st::groupCallMenuVolumeSkip
|
||||
+ _arcs->finishedWidth(),
|
||||
std::clamp(
|
||||
(now - (*lastTime))
|
||||
/ float64(st::groupCallSpeakerArcsAnimation.duration),
|
||||
0.,
|
||||
1.)));
|
||||
update(_speakerRect.united(wasRect.united(_volumeRect)));
|
||||
});
|
||||
|
||||
_arcs->startUpdateRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!_arcsAnimation.animating()) {
|
||||
*volumeLeftWas = _volumeRect.left();
|
||||
*lastTime = crl::now();
|
||||
_arcsAnimation.start();
|
||||
}
|
||||
@@ -269,8 +249,30 @@ void MenuVolumeItem::setCloudVolume(int volume) {
|
||||
}
|
||||
|
||||
void MenuVolumeItem::setSliderVolume(int volume) {
|
||||
_slider->setValue(float64(volume) / _maxVolume);
|
||||
update(_volumeRect);
|
||||
const auto value = float64(volume) / _maxVolume;
|
||||
_slider->setValue(value);
|
||||
updateSliderColor(value);
|
||||
}
|
||||
|
||||
void MenuVolumeItem::updateSliderColor(float64 value) {
|
||||
value = std::clamp(value, 0., 1.);
|
||||
const auto color = [](int rgb) {
|
||||
return QColor(
|
||||
int((rgb & 0xFF0000) >> 16),
|
||||
int((rgb & 0x00FF00) >> 8),
|
||||
int(rgb & 0x0000FF));
|
||||
};
|
||||
const auto colors = std::array<QColor, 4>{ {
|
||||
color(0xF66464),
|
||||
color(0xD0B738),
|
||||
color(0x24CD80),
|
||||
color(0x3BBCEC),
|
||||
} };
|
||||
_slider->setActiveFgOverride((value < 0.25)
|
||||
? anim::color(colors[0], colors[1], value / 0.25)
|
||||
: (value < 0.5)
|
||||
? anim::color(colors[1], colors[2], (value - 0.25) / 0.25)
|
||||
: anim::color(colors[2], colors[3], (value - 0.5) / 0.5));
|
||||
}
|
||||
|
||||
not_null<QAction*> MenuVolumeItem::action() const {
|
||||
@@ -282,9 +284,9 @@ bool MenuVolumeItem::isEnabled() const {
|
||||
}
|
||||
|
||||
int MenuVolumeItem::contentHeight() const {
|
||||
return _st.itemPadding.top()
|
||||
+ _st.itemPadding.bottom()
|
||||
+ _stCross.icon.height() * 2;
|
||||
return st::groupCallMenuVolumePadding.top()
|
||||
+ st::groupCallMenuVolumePadding.bottom()
|
||||
+ _stCross.icon.height();
|
||||
}
|
||||
|
||||
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {
|
||||
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
|
||||
void setCloudVolume(int volume);
|
||||
void setSliderVolume(int volume);
|
||||
void updateSliderColor(float64 value);
|
||||
|
||||
QColor unmuteColor() const;
|
||||
QColor muteColor() const;
|
||||
@@ -64,7 +65,6 @@ private:
|
||||
|
||||
QRect _itemRect;
|
||||
QRect _speakerRect;
|
||||
QRect _volumeRect;
|
||||
QPoint _arcPosition;
|
||||
|
||||
const base::unique_qptr<Ui::MediaSlider> _slider;
|
||||
|
||||
@@ -171,11 +171,11 @@ bool BotKeyboard::moderateKeyActivate(int key) {
|
||||
App::sendBotCommand(user, user, qsl("/pattern"));
|
||||
} else if (key == Qt::Key_4) {
|
||||
App::sendBotCommand(user, user, qsl("/abuse"));
|
||||
} else if (key == Qt::Key_0 || key == Qt::Key_E) {
|
||||
} else if (key == Qt::Key_0) {
|
||||
App::sendBotCommand(user, user, qsl("/undo"));
|
||||
} else if (key == Qt::Key_Plus || key == Qt::Key_QuoteLeft) {
|
||||
} else if (key == Qt::Key_7) {
|
||||
App::sendBotCommand(user, user, qsl("/next"));
|
||||
} else if (key == Qt::Key_Period || key == Qt::Key_S) {
|
||||
} else if (key == Qt::Key_8) {
|
||||
App::sendBotCommand(user, user, qsl("/stats"));
|
||||
}
|
||||
return true;
|
||||
@@ -201,6 +201,7 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
|
||||
if (_wasForMsgId.msg) {
|
||||
_maximizeSize = _singleUse = _forceReply = false;
|
||||
_wasForMsgId = FullMsgId();
|
||||
_placeholder = QString();
|
||||
_impl = nullptr;
|
||||
return true;
|
||||
}
|
||||
@@ -218,6 +219,12 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
|
||||
_maximizeSize = !(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_resize);
|
||||
_singleUse = _forceReply || (markupFlags & MTPDreplyKeyboardMarkup::Flag::f_single_use);
|
||||
|
||||
if (const auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
_placeholder = markup->placeholder;
|
||||
} else {
|
||||
_placeholder = QString();
|
||||
}
|
||||
|
||||
_impl = nullptr;
|
||||
if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (!markup->rows.empty()) {
|
||||
|
||||
@@ -31,8 +31,12 @@ public:
|
||||
// With force=true the markup is updated even if it is
|
||||
// already shown for the passed history item.
|
||||
bool updateMarkup(HistoryItem *last, bool force = false);
|
||||
bool hasMarkup() const;
|
||||
bool forceReply() const;
|
||||
[[nodiscard]] bool hasMarkup() const;
|
||||
[[nodiscard]] bool forceReply() const;
|
||||
|
||||
[[nodiscard]] QString placeholder() const {
|
||||
return _placeholder;
|
||||
}
|
||||
|
||||
void step_selected(crl::time ms, bool timer);
|
||||
void resizeToWidth(int newWidth, int maxOuterHeight) {
|
||||
@@ -40,10 +44,10 @@ public:
|
||||
return TWidget::resizeToWidth(newWidth);
|
||||
}
|
||||
|
||||
bool maximizeSize() const;
|
||||
bool singleUse() const;
|
||||
[[nodiscard]] bool maximizeSize() const;
|
||||
[[nodiscard]] bool singleUse() const;
|
||||
|
||||
FullMsgId forMsgId() const {
|
||||
[[nodiscard]] FullMsgId forMsgId() const {
|
||||
return _wasForMsgId;
|
||||
}
|
||||
|
||||
@@ -76,6 +80,7 @@ private:
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
FullMsgId _wasForMsgId;
|
||||
QString _placeholder;
|
||||
int _height = 0;
|
||||
int _maxOuterHeight = 0;
|
||||
bool _maximizeSize = false;
|
||||
|
||||
@@ -15,10 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_account.h"
|
||||
#include "mainwidget.h"
|
||||
#include "app.h"
|
||||
#include "storage/storage_cloud_blob.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -456,7 +456,7 @@ void Row::setupPreview(const Set &set) {
|
||||
const auto full = original.height();
|
||||
auto &&preview = ranges::views::zip(_preview, ranges::views::ints(0, int(_preview.size())));
|
||||
for (auto &&[pixmap, index] : preview) {
|
||||
pixmap = App::pixmapFromImageInPlace(original.copy(
|
||||
pixmap = Ui::PixmapFromImage(original.copy(
|
||||
{ full * index, 0, full, full }
|
||||
).scaledToWidth(size, Qt::SmoothTransformation));
|
||||
pixmap.setDevicePixelRatio(cRetinaFactor());
|
||||
|
||||
@@ -29,7 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "base/unixtime.h"
|
||||
@@ -125,6 +127,8 @@ private:
|
||||
|
||||
bool _isOneColumn = false;
|
||||
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
|
||||
Fn<SendMenu::Type()> _sendMenuType;
|
||||
|
||||
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
|
||||
@@ -438,23 +442,24 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
} else if (_type == Type::BotCommands) {
|
||||
bool listAllSuggestions = _filter.isEmpty();
|
||||
bool hasUsername = _filter.indexOf('@') > 0;
|
||||
base::flat_map<UserData*, bool> bots;
|
||||
base::flat_map<
|
||||
not_null<UserData*>,
|
||||
not_null<const std::vector<BotCommand>*>> bots;
|
||||
int32 cnt = 0;
|
||||
if (_chat) {
|
||||
if (_chat->noParticipantInfo()) {
|
||||
_chat->session().api().requestFullPeer(_chat);
|
||||
} else if (!_chat->participants.empty()) {
|
||||
const auto &commands = _chat->botCommands();
|
||||
for (const auto user : _chat->participants) {
|
||||
if (!user->isBot()) {
|
||||
continue;
|
||||
} else if (!user->botInfo->inited) {
|
||||
user->session().api().requestFullPeer(user);
|
||||
}
|
||||
if (user->botInfo->commands.isEmpty()) {
|
||||
continue;
|
||||
const auto i = commands.find(peerToUser(user->id));
|
||||
if (i != end(commands)) {
|
||||
bots.emplace(user, &i->second);
|
||||
cnt += i->second.size();
|
||||
}
|
||||
bots.emplace(user, true);
|
||||
cnt += user->botInfo->commands.size();
|
||||
}
|
||||
}
|
||||
} else if (_user && _user->isBot()) {
|
||||
@@ -462,65 +467,71 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
_user->session().api().requestFullPeer(_user);
|
||||
}
|
||||
cnt = _user->botInfo->commands.size();
|
||||
bots.emplace(_user, true);
|
||||
bots.emplace(_user, &_user->botInfo->commands);
|
||||
} else if (_channel && _channel->isMegagroup()) {
|
||||
if (_channel->mgInfo->bots.empty()) {
|
||||
if (!_channel->mgInfo->botStatus) {
|
||||
_channel->session().api().requestBots(_channel);
|
||||
}
|
||||
} else {
|
||||
const auto &commands = _channel->mgInfo->botCommands();
|
||||
for (const auto user : _channel->mgInfo->bots) {
|
||||
if (!user->isBot()) {
|
||||
continue;
|
||||
} else if (!user->botInfo->inited) {
|
||||
user->session().api().requestFullPeer(user);
|
||||
}
|
||||
if (user->botInfo->commands.isEmpty()) {
|
||||
continue;
|
||||
const auto i = commands.find(peerToUser(user->id));
|
||||
if (i != end(commands)) {
|
||||
bots.emplace(user, &i->second);
|
||||
cnt += i->second.size();
|
||||
}
|
||||
bots.emplace(user, true);
|
||||
cnt += user->botInfo->commands.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cnt) {
|
||||
const auto make = [&](
|
||||
not_null<UserData*> user,
|
||||
const BotCommand &command) {
|
||||
return BotCommandRow{
|
||||
user,
|
||||
command.command,
|
||||
command.description,
|
||||
user->activeUserpicView()
|
||||
};
|
||||
};
|
||||
brows.reserve(cnt);
|
||||
int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1);
|
||||
if (_chat) {
|
||||
for (const auto &user : _chat->lastAuthors) {
|
||||
if (!user->isBot()) {
|
||||
continue;
|
||||
} else if (!bots.contains(user)) {
|
||||
continue;
|
||||
} else if (!user->botInfo->inited) {
|
||||
user->session().api().requestFullPeer(user);
|
||||
}
|
||||
if (user->botInfo->commands.isEmpty()) {
|
||||
const auto i = bots.find(user);
|
||||
if (i == end(bots)) {
|
||||
continue;
|
||||
}
|
||||
bots.remove(user);
|
||||
for (auto j = 0, l = user->botInfo->commands.size(); j != l; ++j) {
|
||||
for (const auto &command : *i->second) {
|
||||
if (!listAllSuggestions) {
|
||||
auto toFilter = (hasUsername || botStatus == 0 || botStatus == 2)
|
||||
? user->botInfo->commands.at(j).command + '@' + user->username
|
||||
: user->botInfo->commands.at(j).command;
|
||||
? command.command + '@' + user->username
|
||||
: command.command;
|
||||
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
brows.push_back({ user, &user->botInfo->commands.at(j) });
|
||||
brows.push_back(make(user, command));
|
||||
}
|
||||
bots.erase(i);
|
||||
}
|
||||
}
|
||||
if (!bots.empty()) {
|
||||
for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
|
||||
UserData *user = i->first;
|
||||
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
|
||||
const auto user = i->first;
|
||||
for (const auto &command : *i->second) {
|
||||
if (!listAllSuggestions) {
|
||||
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
|
||||
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? command.command + '@' + user->username : command.command;
|
||||
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue;
|
||||
}
|
||||
brows.push_back({ user, &user->botInfo->commands.at(j) });
|
||||
brows.push_back(make(user, command));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -738,13 +749,17 @@ FieldAutocomplete::Inner::Inner(
|
||||
, _hrows(hrows)
|
||||
, _brows(brows)
|
||||
, _srows(srows)
|
||||
, _pathGradient(std::make_unique<Ui::PathShiftGradient>(
|
||||
st::windowBgRipple,
|
||||
st::windowBgOver,
|
||||
[=] { update(); }))
|
||||
, _previewTimer([=] { showPreview(); }) {
|
||||
controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
controller->adaptive().changed(
|
||||
controller->adaptive().value(
|
||||
) | rpl::start_with_next([=] {
|
||||
_isOneColumn = controller->adaptive().isOneColumn();
|
||||
update();
|
||||
@@ -770,6 +785,11 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
- st::defaultScrollArea.width;
|
||||
|
||||
if (!_srows->empty()) {
|
||||
_pathGradient->startFrame(
|
||||
0,
|
||||
width(),
|
||||
std::min(st::msgMaxWidth / 2, width() / 2));
|
||||
|
||||
int32 rows = rowscount(_srows->size(), _stickersPerRow);
|
||||
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
|
||||
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
|
||||
@@ -815,6 +835,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
w = std::max(qRound(coef * document->dimensions.width()), 1);
|
||||
h = std::max(qRound(coef * document->dimensions.height()), 1);
|
||||
}
|
||||
|
||||
if (sticker.animated && sticker.animated->ready()) {
|
||||
const auto frame = sticker.animated->frame();
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
@@ -832,6 +853,13 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
} else if (const auto image = media->getStickerSmall()) {
|
||||
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
|
||||
p.drawPixmapLeft(ppos, width(), image->pix(w, h));
|
||||
} else {
|
||||
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, QSize(w, h)),
|
||||
_pathGradient.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -920,8 +948,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
auto &row = _brows->at(i);
|
||||
const auto user = row.user;
|
||||
|
||||
const auto command = row.command;
|
||||
auto toHighlight = command->command;
|
||||
auto toHighlight = row.command;
|
||||
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
|
||||
if (hasUsername || botStatus == 0 || botStatus == 2) {
|
||||
toHighlight += '@' + user->username;
|
||||
@@ -939,9 +966,16 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
auto addleft = commandTextWidth + st::mentionPadding.left();
|
||||
auto widthleft = mentionwidth - addleft;
|
||||
|
||||
if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) {
|
||||
if (!row.description.isEmpty()
|
||||
&& row.descriptionText.isEmpty()) {
|
||||
row.descriptionText.setText(
|
||||
st::defaultTextStyle,
|
||||
row.description,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
if (widthleft > st::mentionFont->elidew && !row.descriptionText.isEmpty()) {
|
||||
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
|
||||
command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft);
|
||||
row.descriptionText.drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1043,7 +1077,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex(
|
||||
} else if (!_brows->empty()) {
|
||||
if (index < _brows->size()) {
|
||||
const auto user = _brows->at(index).user;
|
||||
const auto command = _brows->at(index).command;
|
||||
const auto &command = _brows->at(index).command;
|
||||
const auto botStatus = _parent->chat()
|
||||
? _parent->chat()->botStatus
|
||||
: ((_parent->channel() && _parent->channel()->isMegagroup())
|
||||
@@ -1054,7 +1088,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex(
|
||||
|| botStatus == 2
|
||||
|| _parent->filter().indexOf('@') > 0);
|
||||
const auto commandString = QString("/%1%2").arg(
|
||||
command->command,
|
||||
command,
|
||||
insertUsername ? ('@' + user->username) : QString());
|
||||
|
||||
_botCommandChosen.fire({ commandString, method });
|
||||
|
||||
@@ -136,8 +136,10 @@ private:
|
||||
|
||||
struct BotCommandRow {
|
||||
not_null<UserData*> user;
|
||||
not_null<const BotCommand*> command;
|
||||
QString command;
|
||||
QString description;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
Ui::Text::String descriptionText;
|
||||
};
|
||||
|
||||
using HashtagRows = std::vector<QString>;
|
||||
|
||||
@@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
@@ -110,7 +111,9 @@ struct StickerIcon {
|
||||
|
||||
class StickersListWidget::Footer : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
explicit Footer(not_null<StickersListWidget*> parent);
|
||||
explicit Footer(
|
||||
not_null<StickersListWidget*> parent,
|
||||
bool searchButtonVisible);
|
||||
|
||||
void preloadImages();
|
||||
void validateSelectedIcon(
|
||||
@@ -127,6 +130,8 @@ public:
|
||||
|
||||
void clearHeavyData();
|
||||
|
||||
rpl::producer<> openSettingsRequests() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
@@ -145,8 +150,7 @@ private:
|
||||
};
|
||||
using OverState = std::variant<SpecialOver, int>;
|
||||
|
||||
template <typename Callback>
|
||||
void enumerateVisibleIcons(Callback callback);
|
||||
void enumerateVisibleIcons(Fn<void(const StickerIcon &, int)> callback);
|
||||
|
||||
bool iconsAnimationCallback(crl::time now);
|
||||
void setSelectedIcon(
|
||||
@@ -170,6 +174,7 @@ private:
|
||||
void scrollByWheelEvent(not_null<QWheelEvent*> e);
|
||||
|
||||
const not_null<StickersListWidget*> _pan;
|
||||
const bool _searchButtonVisible = true;
|
||||
|
||||
static constexpr auto kVisibleIconsCount = 8;
|
||||
|
||||
@@ -196,6 +201,8 @@ private:
|
||||
object_ptr<Ui::CrossButton> _searchCancel = { nullptr };
|
||||
QPointer<QWidget> _focusTakenFrom;
|
||||
|
||||
rpl::event_stream<> _openSettingsRequests;
|
||||
|
||||
};
|
||||
|
||||
auto StickersListWidget::PrepareStickers(
|
||||
@@ -240,15 +247,21 @@ void StickersListWidget::Sticker::ensureMediaCreated() {
|
||||
documentMedia = document->createMediaView();
|
||||
}
|
||||
|
||||
StickersListWidget::Footer::Footer(not_null<StickersListWidget*> parent)
|
||||
StickersListWidget::Footer::Footer(
|
||||
not_null<StickersListWidget*> parent,
|
||||
bool searchButtonVisible)
|
||||
: InnerFooter(parent)
|
||||
, _pan(parent)
|
||||
, _searchButtonVisible(searchButtonVisible)
|
||||
, _iconsAnimation([=](crl::time now) {
|
||||
return iconsAnimationCallback(now);
|
||||
}) {
|
||||
setMouseTracking(true);
|
||||
|
||||
_iconsLeft = _iconsRight = st::emojiCategorySkip + st::stickerIconWidth;
|
||||
_iconsLeft = st::emojiCategorySkip + (_searchButtonVisible
|
||||
? st::stickerIconWidth
|
||||
: 0);
|
||||
_iconsRight = st::emojiCategorySkip + st::stickerIconWidth;
|
||||
|
||||
_pan->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -340,13 +353,17 @@ void StickersListWidget::Footer::returnFocus() {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void StickersListWidget::Footer::enumerateVisibleIcons(Callback callback) {
|
||||
void StickersListWidget::Footer::enumerateVisibleIcons(
|
||||
Fn<void(const StickerIcon &, int)> callback) {
|
||||
auto iconsX = qRound(_iconsX.current());
|
||||
auto index = iconsX / st::stickerIconWidth;
|
||||
auto x = _iconsLeft - (iconsX % st::stickerIconWidth);
|
||||
auto first = floorclamp(iconsX, st::stickerIconWidth, 0, _icons.size());
|
||||
auto last = ceilclamp(iconsX + width(), st::stickerIconWidth, 0, _icons.size());
|
||||
auto last = ceilclamp(
|
||||
iconsX + width(),
|
||||
st::stickerIconWidth,
|
||||
0,
|
||||
_icons.size());
|
||||
for (auto index = first; index != last; ++index) {
|
||||
callback(_icons[index], x);
|
||||
x += st::stickerIconWidth;
|
||||
@@ -526,6 +543,10 @@ void StickersListWidget::Footer::resizeSearchControls() {
|
||||
_searchCancel->moveToRight(st::gifsSearchCancelPosition.x(), st::gifsSearchCancelPosition.y());
|
||||
}
|
||||
|
||||
rpl::producer<> StickersListWidget::Footer::openSettingsRequests() const {
|
||||
return _openSettingsRequests.events();
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::mousePressEvent(QMouseEvent *e) {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
@@ -534,11 +555,7 @@ void StickersListWidget::Footer::mousePressEvent(QMouseEvent *e) {
|
||||
updateSelected();
|
||||
|
||||
if (_iconOver == SpecialOver::Settings) {
|
||||
_pan->controller()->show(Box<StickersBox>(
|
||||
_pan->controller(),
|
||||
(hasOnlyFeaturedSets()
|
||||
? StickersBox::Section::Featured
|
||||
: StickersBox::Section::Installed)));
|
||||
_openSettingsRequests.fire({});
|
||||
} else if (_iconOver == SpecialOver::Search) {
|
||||
toggleSearch(true);
|
||||
} else {
|
||||
@@ -668,7 +685,8 @@ void StickersListWidget::Footer::updateSelected() {
|
||||
const auto settingsLeft = width() - _iconsRight;
|
||||
const auto searchLeft = _iconsLeft - st::stickerIconWidth;
|
||||
auto newOver = OverState(SpecialOver::None);
|
||||
if (x >= searchLeft
|
||||
if (_searchButtonVisible
|
||||
&& x >= searchLeft
|
||||
&& x < searchLeft + st::stickerIconWidth
|
||||
&& y >= _iconsTop
|
||||
&& y < _iconsTop + st::emojiFooterHeight) {
|
||||
@@ -752,12 +770,21 @@ bool StickersListWidget::Footer::hasOnlyFeaturedSets() const {
|
||||
|
||||
void StickersListWidget::Footer::paintStickerSettingsIcon(Painter &p) const {
|
||||
const auto settingsLeft = width() - _iconsRight;
|
||||
st::stickersSettings.paint(p, settingsLeft + (st::stickerIconWidth - st::stickersSettings.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width());
|
||||
st::stickersSettings.paint(
|
||||
p,
|
||||
settingsLeft
|
||||
+ (st::stickerIconWidth - st::stickersSettings.width()) / 2,
|
||||
_iconsTop + st::emojiCategory.iconPosition.y(),
|
||||
width());
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::paintSearchIcon(Painter &p) const {
|
||||
const auto searchLeft = _iconsLeft - st::stickerIconWidth;
|
||||
st::stickersSearch.paint(p, searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width());
|
||||
st::stickersSearch.paint(
|
||||
p,
|
||||
searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2,
|
||||
_iconsTop + st::emojiCategory.iconPosition.y(),
|
||||
width());
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::validateIconLottieAnimation(
|
||||
@@ -895,10 +922,16 @@ bool StickersListWidget::Footer::iconsAnimationCallback(crl::time now) {
|
||||
|
||||
StickersListWidget::StickersListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
not_null<Window::SessionController*> controller,
|
||||
bool masks)
|
||||
: Inner(parent, controller)
|
||||
, _api(&controller->session().mtp())
|
||||
, _section(Section::Stickers)
|
||||
, _isMasks(masks)
|
||||
, _pathGradient(std::make_unique<Ui::PathShiftGradient>(
|
||||
st::windowBgRipple,
|
||||
st::windowBgOver,
|
||||
[=] { update(); }))
|
||||
, _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st::emojiPanHeaderLeft)
|
||||
, _addText(tr::lng_stickers_featured_add(tr::now).toUpper())
|
||||
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
|
||||
@@ -909,8 +942,10 @@ StickersListWidget::StickersListWidget(
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
_settings->addClickHandler([=] {
|
||||
using Section = StickersBox::Section;
|
||||
controller->show(
|
||||
Box<StickersBox>(controller, StickersBox::Section::Installed));
|
||||
Box<StickersBox>(controller, Section::Installed, _isMasks),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
|
||||
session().downloaderTaskFinished(
|
||||
@@ -930,7 +965,11 @@ StickersListWidget::StickersListWidget(
|
||||
}, lifetime());
|
||||
|
||||
session().data().stickers().recentUpdated(
|
||||
) | rpl::start_with_next([=] {
|
||||
) | rpl::start_with_next([=](Data::Stickers::Recent recent) {
|
||||
const auto attached = (recent == Data::Stickers::Recent::Attached);
|
||||
if (attached != _isMasks) {
|
||||
return;
|
||||
}
|
||||
refreshRecent();
|
||||
}, lifetime());
|
||||
}
|
||||
@@ -954,8 +993,22 @@ rpl::producer<> StickersListWidget::checkForHide() const {
|
||||
object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
|
||||
Expects(_footer == nullptr);
|
||||
|
||||
auto result = object_ptr<Footer>(this);
|
||||
auto result = object_ptr<Footer>(this, !_isMasks);
|
||||
_footer = result;
|
||||
|
||||
_footer->openSettingsRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto onlyFeatured = _footer->hasOnlyFeaturedSets();
|
||||
Ui::show(Box<StickersBox>(
|
||||
controller(),
|
||||
(onlyFeatured
|
||||
? StickersBox::Section::Featured
|
||||
: _isMasks
|
||||
? StickersBox::Section::Masks
|
||||
: StickersBox::Section::Installed),
|
||||
onlyFeatured ? false : _isMasks),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}, _footer->lifetime());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1531,6 +1584,8 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
toColumn = _columnCount - toColumn;
|
||||
}
|
||||
|
||||
_pathGradient->startFrame(0, width(), width() / 2);
|
||||
|
||||
auto &sets = shownSets();
|
||||
auto selectedSticker = std::get_if<OverSticker>(&_selected);
|
||||
auto selectedButton = std::get_if<OverButton>(!v::is_null(_pressed)
|
||||
@@ -1889,6 +1944,7 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||
h = std::max(qRound(coef * document->dimensions.height()), 1);
|
||||
}
|
||||
auto ppos = pos + QPoint((_singleSize.width() - w) / 2, (_singleSize.height() - h) / 2);
|
||||
|
||||
if (sticker.animated && sticker.animated->ready()) {
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = boundingBoxSize() * cIntRetinaFactor();
|
||||
@@ -1918,6 +1974,12 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||
if (sticker.savedFrame.isNull()) {
|
||||
sticker.savedFrame = pixmap;
|
||||
}
|
||||
} else {
|
||||
PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, QSize{ w, h }),
|
||||
_pathGradient.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2363,12 +2425,12 @@ void StickersListWidget::refreshStickers() {
|
||||
void StickersListWidget::refreshMySets() {
|
||||
auto wasSets = base::take(_mySets);
|
||||
_favedStickersMap.clear();
|
||||
_mySets.reserve(session().data().stickers().setsOrder().size() + 3);
|
||||
_mySets.reserve(defaultSetsOrder().size() + 3);
|
||||
|
||||
refreshFavedStickers();
|
||||
refreshRecentStickers(false);
|
||||
refreshMegagroupStickers(GroupStickersPlace::Visible);
|
||||
for (const auto setId : session().data().stickers().setsOrder()) {
|
||||
for (const auto setId : defaultSetsOrder()) {
|
||||
const auto externalLayout = false;
|
||||
appendSet(_mySets, setId, externalLayout, AppendSkip::Archived);
|
||||
}
|
||||
@@ -2441,7 +2503,9 @@ void StickersListWidget::refreshSearchIndex() {
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshSettingsVisibility() {
|
||||
const auto visible = (_section == Section::Stickers) && _mySets.empty();
|
||||
const auto visible = (_section == Section::Stickers)
|
||||
&& _mySets.empty()
|
||||
&& !_isMasks;
|
||||
_settings->setVisible(visible);
|
||||
}
|
||||
|
||||
@@ -2519,9 +2583,15 @@ auto StickersListWidget::collectRecentStickers() -> std::vector<Sticker> {
|
||||
auto result = std::vector<Sticker>();
|
||||
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
const auto &recent = session().data().stickers().getRecentPack();
|
||||
const auto customIt = sets.find(Data::Stickers::CustomSetId);
|
||||
const auto cloudIt = sets.find(Data::Stickers::CloudRecentSetId);
|
||||
const auto &recent = _isMasks
|
||||
? RecentStickerPack()
|
||||
: session().data().stickers().getRecentPack();
|
||||
const auto customIt = _isMasks
|
||||
? sets.cend()
|
||||
: sets.find(Data::Stickers::CustomSetId);
|
||||
const auto cloudIt = sets.find(_isMasks
|
||||
? Data::Stickers::CloudRecentAttachedSetId
|
||||
: Data::Stickers::CloudRecentSetId);
|
||||
const auto customCount = (customIt != sets.cend())
|
||||
? customIt->second->stickers.size()
|
||||
: 0;
|
||||
@@ -2607,6 +2677,9 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshFavedStickers() {
|
||||
if (_isMasks) {
|
||||
return;
|
||||
}
|
||||
clearSelection();
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
const auto it = sets.find(Data::Stickers::FavedSetId);
|
||||
@@ -2634,7 +2707,7 @@ void StickersListWidget::refreshFavedStickers() {
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
|
||||
if (!_megagroupSet) {
|
||||
if (!_megagroupSet || _isMasks) {
|
||||
return;
|
||||
}
|
||||
auto canEdit = _megagroupSet->canEditStickers();
|
||||
@@ -2720,7 +2793,7 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
|
||||
std::vector<StickerIcon> StickersListWidget::fillIcons() {
|
||||
auto result = std::vector<StickerIcon>();
|
||||
result.reserve(_mySets.size() + 1);
|
||||
if (!_officialSets.empty()) {
|
||||
if (!_officialSets.empty() && !_isMasks) {
|
||||
result.emplace_back(Data::Stickers::FeaturedSetId);
|
||||
}
|
||||
|
||||
@@ -2837,7 +2910,8 @@ bool StickersListWidget::setHasTitle(const Set &set) const {
|
||||
if (set.id == Data::Stickers::FavedSetId) {
|
||||
return false;
|
||||
} else if (set.id == Data::Stickers::RecentSetId) {
|
||||
return !_mySets.empty() && _mySets[0].id == Data::Stickers::FavedSetId;
|
||||
return !_mySets.empty()
|
||||
&& (_isMasks || (_mySets[0].id == Data::Stickers::FavedSetId));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -3101,53 +3175,92 @@ void StickersListWidget::removeMegagroupSet(bool locally) {
|
||||
void StickersListWidget::removeSet(uint64 setId) {
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
const auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
const auto set = it->second.get();
|
||||
_removingSetId = set->id;
|
||||
auto text = tr::lng_stickers_remove_pack(tr::now, lt_sticker_pack, set->title);
|
||||
controller()->show(Box<ConfirmBox>(text, tr::lng_stickers_remove_pack_confirm(tr::now), crl::guard(this, [=] {
|
||||
Ui::hideLayer();
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
const auto it = sets.find(_removingSetId);
|
||||
if (it != sets.cend()) {
|
||||
const auto set = it->second.get();
|
||||
if (set->id && set->access) {
|
||||
_api.request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(set->id), MTP_long(set->access)))).send();
|
||||
} else if (!set->shortName.isEmpty()) {
|
||||
_api.request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetShortName(MTP_string(set->shortName)))).send();
|
||||
}
|
||||
auto writeRecent = false;
|
||||
auto &recent = session().data().stickers().getRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (set->stickers.indexOf(i->first) >= 0) {
|
||||
i = recent.erase(i);
|
||||
writeRecent = true;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
set->installDate = TimeId(0);
|
||||
//
|
||||
// Set can be in search results.
|
||||
//
|
||||
//if (!(set->flags & MTPDstickerSet_ClientFlag::f_featured)
|
||||
// && !(set->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
// sets.erase(it);
|
||||
//}
|
||||
int removeIndex = session().data().stickers().setsOrder().indexOf(_removingSetId);
|
||||
if (removeIndex >= 0) session().data().stickers().setsOrderRef().removeAt(removeIndex);
|
||||
refreshStickers();
|
||||
session().local().writeInstalledStickers();
|
||||
if (writeRecent) session().saveSettings();
|
||||
}
|
||||
_removingSetId = 0;
|
||||
_checkForHide.fire({});
|
||||
}), crl::guard(this, [=] {
|
||||
_removingSetId = 0;
|
||||
_checkForHide.fire({});
|
||||
})));
|
||||
if (it == sets.cend()) {
|
||||
return;
|
||||
}
|
||||
const auto set = it->second.get();
|
||||
_removingSetId = set->id;
|
||||
const auto text = tr::lng_stickers_remove_pack(
|
||||
tr::now,
|
||||
lt_sticker_pack,
|
||||
set->title);
|
||||
const auto confirm = tr::lng_stickers_remove_pack_confirm(tr::now);
|
||||
controller()->show(Box<ConfirmBox>(text, confirm, crl::guard(this, [=](
|
||||
Fn<void()> &&close) {
|
||||
close();
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
const auto it = sets.find(_removingSetId);
|
||||
if (it != sets.cend()) {
|
||||
const auto set = it->second.get();
|
||||
if (set->id && set->access) {
|
||||
_api.request(MTPmessages_UninstallStickerSet(
|
||||
MTP_inputStickerSetID(
|
||||
MTP_long(set->id),
|
||||
MTP_long(set->access)))
|
||||
).send();
|
||||
} else if (!set->shortName.isEmpty()) {
|
||||
_api.request(MTPmessages_UninstallStickerSet(
|
||||
MTP_inputStickerSetShortName(
|
||||
MTP_string(set->shortName)))
|
||||
).send();
|
||||
}
|
||||
auto writeRecent = false;
|
||||
auto &recent = session().data().stickers().getRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (set->stickers.indexOf(i->first) >= 0) {
|
||||
i = recent.erase(i);
|
||||
writeRecent = true;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
set->installDate = TimeId(0);
|
||||
//
|
||||
// Set can be in search results.
|
||||
//
|
||||
//if (!(set->flags & MTPDstickerSet_ClientFlag::f_featured)
|
||||
// && !(set->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
// sets.erase(it);
|
||||
//}
|
||||
const auto removeIndex = defaultSetsOrder().indexOf(
|
||||
_removingSetId);
|
||||
if (removeIndex >= 0) {
|
||||
defaultSetsOrderRef().removeAt(removeIndex);
|
||||
}
|
||||
refreshStickers();
|
||||
if (set->flags & MTPDstickerSet::Flag::f_masks) {
|
||||
session().local().writeInstalledMasks();
|
||||
} else {
|
||||
session().local().writeInstalledStickers();
|
||||
}
|
||||
if (writeRecent) {
|
||||
session().saveSettings();
|
||||
}
|
||||
session().data().stickers().notifyUpdated();
|
||||
}
|
||||
_removingSetId = 0;
|
||||
_checkForHide.fire({});
|
||||
}), crl::guard(this, [=] {
|
||||
_removingSetId = 0;
|
||||
_checkForHide.fire({});
|
||||
})), Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
const Data::StickersSetsOrder &StickersListWidget::defaultSetsOrder() const {
|
||||
return !_isMasks
|
||||
? session().data().stickers().setsOrder()
|
||||
: session().data().stickers().maskSetsOrder();
|
||||
}
|
||||
|
||||
Data::StickersSetsOrder &StickersListWidget::defaultSetsOrderRef() {
|
||||
return !_isMasks
|
||||
? session().data().stickers().setsOrderRef()
|
||||
: session().data().stickers().maskSetsOrderRef();
|
||||
}
|
||||
|
||||
bool StickersListWidget::mySetsEmpty() const {
|
||||
return _mySets.empty();
|
||||
}
|
||||
|
||||
StickersListWidget::~StickersListWidget() = default;
|
||||
|
||||
@@ -25,6 +25,7 @@ class LinkButton;
|
||||
class PopupMenu;
|
||||
class RippleAnimation;
|
||||
class BoxContent;
|
||||
class PathShiftGradient;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Lottie {
|
||||
@@ -48,7 +49,8 @@ class StickersListWidget
|
||||
public:
|
||||
StickersListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
not_null<Window::SessionController*> controller,
|
||||
bool masks = false);
|
||||
|
||||
Main::Session &session() const;
|
||||
|
||||
@@ -87,6 +89,8 @@ public:
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
SendMenu::Type type) override;
|
||||
|
||||
bool mySetsEmpty() const;
|
||||
|
||||
~StickersListWidget();
|
||||
|
||||
protected:
|
||||
@@ -299,6 +303,9 @@ private:
|
||||
void refreshMegagroupSetGeometry();
|
||||
QRect megagroupSetButtonRectFinal() const;
|
||||
|
||||
const Data::StickersSetsOrder &defaultSetsOrder() const;
|
||||
Data::StickersSetsOrder &defaultSetsOrderRef();
|
||||
|
||||
enum class AppendSkip {
|
||||
None,
|
||||
Archived,
|
||||
@@ -348,6 +355,7 @@ private:
|
||||
int _officialOffset = 0;
|
||||
|
||||
Section _section = Section::Stickers;
|
||||
const bool _isMasks;
|
||||
|
||||
bool _displayingSet = false;
|
||||
uint64 _removingSetId = 0;
|
||||
@@ -361,6 +369,8 @@ private:
|
||||
OverState _pressed;
|
||||
QPoint _lastMousePosition;
|
||||
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
|
||||
Ui::Text::String _megagroupSetAbout;
|
||||
QString _megagroupSetButtonText;
|
||||
int _megagroupSetButtonTextWidth = 0;
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
@@ -188,4 +189,50 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
||||
box);
|
||||
}
|
||||
|
||||
bool PaintStickerThumbnailPath(
|
||||
QPainter &p,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
QRect target,
|
||||
QLinearGradient *gradient) {
|
||||
const auto &path = media->thumbnailPath();
|
||||
const auto dimensions = media->owner()->dimensions;
|
||||
if (path.isEmpty() || dimensions.isEmpty() || target.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
p.save();
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.translate(target.topLeft());
|
||||
if (gradient) {
|
||||
const auto scale = dimensions.width() / float64(target.width());
|
||||
const auto shift = p.worldTransform().dx();
|
||||
gradient->setStart((gradient->start().x() - shift) * scale, 0);
|
||||
gradient->setFinalStop(
|
||||
(gradient->finalStop().x() - shift) * scale,
|
||||
0);
|
||||
p.setBrush(*gradient);
|
||||
}
|
||||
p.scale(
|
||||
target.width() / float64(dimensions.width()),
|
||||
target.height() / float64(dimensions.height()));
|
||||
p.drawPath(path);
|
||||
p.restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PaintStickerThumbnailPath(
|
||||
QPainter &p,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
QRect target,
|
||||
not_null<Ui::PathShiftGradient*> gradient) {
|
||||
return gradient->paint([&](const Ui::PathShiftGradient::Background &bg) {
|
||||
if (const auto color = std::get_if<style::color>(&bg)) {
|
||||
p.setBrush(*color);
|
||||
return PaintStickerThumbnailPath(p, media, target);
|
||||
}
|
||||
const auto gradient = v::get<QLinearGradient*>(bg);
|
||||
return PaintStickerThumbnailPath(p, media, target, gradient);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -26,6 +26,10 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class PathShiftGradient;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
class StickersSetThumbnailView;
|
||||
@@ -71,4 +75,16 @@ enum class StickerLottieSize : uchar {
|
||||
QSize box,
|
||||
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
|
||||
|
||||
bool PaintStickerThumbnailPath(
|
||||
QPainter &p,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
QRect target,
|
||||
QLinearGradient *gradient = nullptr);
|
||||
|
||||
bool PaintStickerThumbnailPath(
|
||||
QPainter &p,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
QRect target,
|
||||
not_null<Ui::PathShiftGradient*> gradient);
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -256,8 +256,12 @@ void TabbedSelector::SlideAnimation::paintFrame(QPainter &p, float64 dt, float64
|
||||
p.drawImage(outerLeft / cIntRetinaFactor(), outerTop / cIntRetinaFactor(), _frame, outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop);
|
||||
}
|
||||
|
||||
TabbedSelector::Tab::Tab(SelectorTab type, object_ptr<Inner> widget)
|
||||
TabbedSelector::Tab::Tab(
|
||||
SelectorTab type,
|
||||
int index,
|
||||
object_ptr<Inner> widget)
|
||||
: _type(type)
|
||||
, _index(index)
|
||||
, _widget(std::move(widget))
|
||||
, _weak(_widget)
|
||||
, _footer(_widget ? _widget->createFooter() : nullptr) {
|
||||
@@ -292,33 +296,52 @@ TabbedSelector::TabbedSelector(
|
||||
, _topShadow(full() ? object_ptr<Ui::PlainShadow>(this) : nullptr)
|
||||
, _bottomShadow(this)
|
||||
, _scroll(this, st::emojiScroll)
|
||||
, _tabs { {
|
||||
createTab(SelectorTab::Emoji),
|
||||
createTab(SelectorTab::Stickers),
|
||||
createTab(SelectorTab::Gifs),
|
||||
} }
|
||||
, _tabs([&] {
|
||||
std::vector<Tab> tabs;
|
||||
if (full()) {
|
||||
tabs.reserve(3);
|
||||
tabs.push_back(createTab(SelectorTab::Emoji, 0));
|
||||
tabs.push_back(createTab(SelectorTab::Stickers, 1));
|
||||
tabs.push_back(createTab(SelectorTab::Gifs, 2));
|
||||
} else if (mediaEditor()) {
|
||||
tabs.reserve(2);
|
||||
tabs.push_back(createTab(SelectorTab::Stickers, 0));
|
||||
tabs.push_back(createTab(SelectorTab::Masks, 1));
|
||||
} else {
|
||||
tabs.reserve(1);
|
||||
tabs.push_back(createTab(SelectorTab::Emoji, 0));
|
||||
}
|
||||
return tabs;
|
||||
}())
|
||||
, _currentTabType(full()
|
||||
? session().settings().selectorTab()
|
||||
: SelectorTab::Emoji) {
|
||||
: mediaEditor()
|
||||
? SelectorTab::Stickers
|
||||
: SelectorTab::Emoji)
|
||||
, _hasEmojiTab(ranges::contains(_tabs, SelectorTab::Emoji, &Tab::type))
|
||||
, _hasStickersTab(ranges::contains(_tabs, SelectorTab::Stickers, &Tab::type))
|
||||
, _hasGifsTab(ranges::contains(_tabs, SelectorTab::Gifs, &Tab::type))
|
||||
, _hasMasksTab(ranges::contains(_tabs, SelectorTab::Masks, &Tab::type))
|
||||
, _tabbed(_tabs.size() > 1) {
|
||||
resize(st::emojiPanWidth, st::emojiPanMaxHeight);
|
||||
|
||||
for (auto &tab : _tabs) {
|
||||
if (!tab.widget()) {
|
||||
continue;
|
||||
}
|
||||
tab.footer()->hide();
|
||||
tab.widget()->hide();
|
||||
}
|
||||
createTabsSlider();
|
||||
if (tabbed()) {
|
||||
createTabsSlider();
|
||||
}
|
||||
setWidgetToScrollArea();
|
||||
|
||||
_bottomShadow->setGeometry(0, _scroll->y() + _scroll->height() - st::lineWidth, width(), st::lineWidth);
|
||||
_bottomShadow->setGeometry(
|
||||
0,
|
||||
_scroll->y() + _scroll->height() - st::lineWidth,
|
||||
width(),
|
||||
st::lineWidth);
|
||||
|
||||
for (auto &tab : _tabs) {
|
||||
const auto widget = tab.widget();
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
widget->scrollToRequests(
|
||||
) | rpl::start_with_next([=, tab = &tab](int y) {
|
||||
@@ -338,7 +361,7 @@ TabbedSelector::TabbedSelector(
|
||||
}
|
||||
|
||||
rpl::merge(
|
||||
(full()
|
||||
(hasStickersTab()
|
||||
? stickers()->scrollUpdated() | rpl::map_to(0)
|
||||
: rpl::never<int>() | rpl::type_erased()),
|
||||
_scroll->scrollTopChanges()
|
||||
@@ -346,13 +369,15 @@ TabbedSelector::TabbedSelector(
|
||||
handleScroll();
|
||||
}, lifetime());
|
||||
|
||||
if (full()) {
|
||||
if (_topShadow) {
|
||||
_topShadow->raise();
|
||||
}
|
||||
_bottomShadow->raise();
|
||||
if (full()) {
|
||||
if (_tabsSlider) {
|
||||
_tabsSlider->raise();
|
||||
}
|
||||
|
||||
if (hasStickersTab() || hasGifsTab()) {
|
||||
session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::Rights
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
@@ -360,11 +385,12 @@ TabbedSelector::TabbedSelector(
|
||||
}) | rpl::start_with_next([=] {
|
||||
checkRestrictedPeer();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
session().api().stickerSetInstalled(
|
||||
) | rpl::start_with_next([this](uint64 setId) {
|
||||
_tabsSlider->setActiveSection(
|
||||
static_cast<int>(SelectorTab::Stickers));
|
||||
if (hasStickersTab()) {
|
||||
session().data().stickers().stickerSetInstalled(
|
||||
) | rpl::start_with_next([=](uint64 setId) {
|
||||
_tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
|
||||
stickers()->showStickerSet(setId);
|
||||
_showRequests.fire({});
|
||||
}, lifetime());
|
||||
@@ -387,11 +413,8 @@ Main::Session &TabbedSelector::session() const {
|
||||
return _controller->session();
|
||||
}
|
||||
|
||||
TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type) {
|
||||
TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
|
||||
auto createWidget = [&]() -> object_ptr<Inner> {
|
||||
if (!full() && type != SelectorTab::Emoji) {
|
||||
return { nullptr };
|
||||
}
|
||||
switch (type) {
|
||||
case SelectorTab::Emoji:
|
||||
return object_ptr<EmojiListWidget>(this, _controller);
|
||||
@@ -399,52 +422,94 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type) {
|
||||
return object_ptr<StickersListWidget>(this, _controller);
|
||||
case SelectorTab::Gifs:
|
||||
return object_ptr<GifsListWidget>(this, _controller);
|
||||
case SelectorTab::Masks:
|
||||
return object_ptr<StickersListWidget>(this, _controller, true);
|
||||
}
|
||||
Unexpected("Type in TabbedSelector::createTab.");
|
||||
};
|
||||
return Tab{ type, createWidget() };
|
||||
return Tab{ type, index, createWidget() };
|
||||
}
|
||||
|
||||
bool TabbedSelector::full() const {
|
||||
return (_mode == Mode::Full);
|
||||
}
|
||||
|
||||
bool TabbedSelector::mediaEditor() const {
|
||||
return (_mode == Mode::MediaEditor);
|
||||
}
|
||||
|
||||
bool TabbedSelector::tabbed() const {
|
||||
return _tabbed;
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasEmojiTab() const {
|
||||
return _hasEmojiTab;
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasStickersTab() const {
|
||||
return _hasStickersTab;
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasGifsTab() const {
|
||||
return _hasGifsTab;
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasMasksTab() const {
|
||||
return _hasMasksTab;
|
||||
}
|
||||
|
||||
rpl::producer<EmojiPtr> TabbedSelector::emojiChosen() const {
|
||||
return emoji()->chosen();
|
||||
}
|
||||
|
||||
rpl::producer<TabbedSelector::FileChosen> TabbedSelector::fileChosen() const {
|
||||
return full()
|
||||
? rpl::merge(stickers()->chosen(), gifs()->fileChosen())
|
||||
: rpl::never<TabbedSelector::FileChosen>() | rpl::type_erased();
|
||||
auto never = rpl::never<TabbedSelector::FileChosen>(
|
||||
) | rpl::type_erased();
|
||||
return rpl::merge(
|
||||
hasStickersTab() ? stickers()->chosen() : never,
|
||||
hasGifsTab() ? gifs()->fileChosen() : never,
|
||||
hasMasksTab() ? masks()->chosen() : never);
|
||||
}
|
||||
|
||||
auto TabbedSelector::photoChosen() const
|
||||
-> rpl::producer<TabbedSelector::PhotoChosen>{
|
||||
return full() ? gifs()->photoChosen() : nullptr;
|
||||
return hasGifsTab() ? gifs()->photoChosen() : nullptr;
|
||||
}
|
||||
|
||||
auto TabbedSelector::inlineResultChosen() const
|
||||
-> rpl::producer<InlineChosen> {
|
||||
return full() ? gifs()->inlineResultChosen() : nullptr;
|
||||
return hasGifsTab() ? gifs()->inlineResultChosen() : nullptr;
|
||||
}
|
||||
|
||||
rpl::producer<> TabbedSelector::cancelled() const {
|
||||
return full() ? gifs()->cancelRequests() : nullptr;
|
||||
return hasGifsTab() ? gifs()->cancelRequests() : nullptr;
|
||||
}
|
||||
|
||||
rpl::producer<> TabbedSelector::checkForHide() const {
|
||||
return full() ? stickers()->checkForHide() : nullptr;
|
||||
auto never = rpl::never<>();
|
||||
return rpl::merge(
|
||||
hasStickersTab() ? stickers()->checkForHide() : never,
|
||||
hasMasksTab() ? masks()->checkForHide() : never);
|
||||
}
|
||||
|
||||
rpl::producer<> TabbedSelector::slideFinished() const {
|
||||
return _slideFinished.events();
|
||||
}
|
||||
|
||||
void TabbedSelector::updateTabsSliderGeometry() {
|
||||
if (!_tabsSlider) {
|
||||
return;
|
||||
}
|
||||
const auto w = mediaEditor() && hasMasksTab() && masks()->mySetsEmpty()
|
||||
? width() / 2
|
||||
: width();
|
||||
_tabsSlider->resizeToWidth(w);
|
||||
_tabsSlider->moveToLeft(0, 0);
|
||||
}
|
||||
|
||||
void TabbedSelector::resizeEvent(QResizeEvent *e) {
|
||||
if (full()) {
|
||||
_tabsSlider->resizeToWidth(width());
|
||||
_tabsSlider->moveToLeft(0, 0);
|
||||
updateTabsSliderGeometry();
|
||||
if (_topShadow && _tabsSlider) {
|
||||
_topShadow->setGeometry(
|
||||
_tabsSlider->x(),
|
||||
_tabsSlider->bottomNoMargins() - st::lineWidth,
|
||||
@@ -476,14 +541,15 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) {
|
||||
updateInnerGeometry();
|
||||
updateScrollGeometry();
|
||||
}
|
||||
_bottomShadow->setGeometry(0, _scroll->y() + _scroll->height() - st::lineWidth, width(), st::lineWidth);
|
||||
_bottomShadow->setGeometry(
|
||||
0,
|
||||
_scroll->y() + _scroll->height() - st::lineWidth,
|
||||
width(),
|
||||
st::lineWidth);
|
||||
updateRestrictedLabelGeometry();
|
||||
|
||||
_footerTop = height() - st::emojiFooterHeight;
|
||||
for (auto &tab : _tabs) {
|
||||
if (!tab.widget()) {
|
||||
continue;
|
||||
}
|
||||
tab.footer()->resizeToWidth(width());
|
||||
tab.footer()->moveToLeft(0, _footerTop);
|
||||
}
|
||||
@@ -521,14 +587,22 @@ void TabbedSelector::paintEvent(QPaintEvent *e) {
|
||||
|
||||
void TabbedSelector::paintSlideFrame(Painter &p) {
|
||||
if (_roundRadius > 0) {
|
||||
if (full()) {
|
||||
auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius);
|
||||
Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom);
|
||||
} else {
|
||||
auto topPart = QRect(0, 0, width(), 3 * _roundRadius);
|
||||
Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop);
|
||||
}
|
||||
} else if (full()) {
|
||||
const auto topPart = QRect(
|
||||
0,
|
||||
0,
|
||||
width(),
|
||||
_tabsSlider
|
||||
? _tabsSlider->height() + _roundRadius
|
||||
: 3 * _roundRadius);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
topPart,
|
||||
st::emojiPanBg,
|
||||
ImageRoundRadius::Small,
|
||||
tabbed()
|
||||
? RectPart::FullTop | RectPart::NoTopBottom
|
||||
: RectPart::FullTop);
|
||||
} else if (_tabsSlider) {
|
||||
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
|
||||
}
|
||||
auto slideDt = _a_slide.value(1.);
|
||||
@@ -536,21 +610,39 @@ void TabbedSelector::paintSlideFrame(Painter &p) {
|
||||
}
|
||||
|
||||
void TabbedSelector::paintContent(Painter &p) {
|
||||
auto &bottomBg = hasSectionIcons() ? st::emojiPanCategories : st::emojiPanBg;
|
||||
auto &bottomBg = hasSectionIcons()
|
||||
? st::emojiPanCategories
|
||||
: st::emojiPanBg;
|
||||
if (_roundRadius > 0) {
|
||||
if (full()) {
|
||||
auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius);
|
||||
Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop | RectPart::NoTopBottom);
|
||||
} else {
|
||||
auto topPart = QRect(0, 0, width(), 3 * _roundRadius);
|
||||
Ui::FillRoundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, RectPart::FullTop);
|
||||
}
|
||||
const auto topPart = QRect(
|
||||
0,
|
||||
0,
|
||||
width(),
|
||||
_tabsSlider
|
||||
? _tabsSlider->height() + _roundRadius
|
||||
: 3 * _roundRadius);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
topPart,
|
||||
st::emojiPanBg,
|
||||
ImageRoundRadius::Small,
|
||||
tabbed()
|
||||
? RectPart::FullTop | RectPart::NoTopBottom
|
||||
: RectPart::FullTop);
|
||||
|
||||
auto bottomPart = QRect(0, _footerTop - _roundRadius, width(), st::emojiFooterHeight + _roundRadius);
|
||||
auto bottomParts = RectPart::NoTopBottom | RectPart::FullBottom;
|
||||
Ui::FillRoundRect(p, bottomPart, bottomBg, ImageRoundRadius::Small, bottomParts);
|
||||
const auto bottomPart = QRect(
|
||||
0,
|
||||
_footerTop - _roundRadius,
|
||||
width(),
|
||||
st::emojiFooterHeight + _roundRadius);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
bottomPart,
|
||||
bottomBg,
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::NoTopBottom | RectPart::FullBottom);
|
||||
} else {
|
||||
if (full()) {
|
||||
if (_tabsSlider) {
|
||||
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
|
||||
}
|
||||
p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, bottomBg);
|
||||
@@ -561,17 +653,27 @@ void TabbedSelector::paintContent(Painter &p) {
|
||||
if (_restrictedLabel) {
|
||||
p.fillRect(0, sidesTop, width(), sidesHeight, st::emojiPanBg);
|
||||
} else {
|
||||
p.fillRect(myrtlrect(width() - st::emojiScroll.width, sidesTop, st::emojiScroll.width, sidesHeight), st::emojiPanBg);
|
||||
p.fillRect(myrtlrect(0, sidesTop, st::roundRadiusSmall, sidesHeight), st::emojiPanBg);
|
||||
p.fillRect(
|
||||
myrtlrect(
|
||||
width() - st::emojiScroll.width,
|
||||
sidesTop,
|
||||
st::emojiScroll.width,
|
||||
sidesHeight),
|
||||
st::emojiPanBg);
|
||||
p.fillRect(
|
||||
myrtlrect(0, sidesTop, st::roundRadiusSmall, sidesHeight),
|
||||
st::emojiPanBg);
|
||||
}
|
||||
}
|
||||
|
||||
int TabbedSelector::marginTop() const {
|
||||
return full() ? (_tabsSlider->height() - st::lineWidth) : _roundRadius;
|
||||
return _tabsSlider
|
||||
? (_tabsSlider->height() - st::lineWidth)
|
||||
: _roundRadius;
|
||||
}
|
||||
|
||||
int TabbedSelector::scrollTop() const {
|
||||
return full() ? marginTop() : 0;
|
||||
return tabbed() ? marginTop() : 0;
|
||||
}
|
||||
|
||||
int TabbedSelector::marginBottom() const {
|
||||
@@ -579,19 +681,31 @@ int TabbedSelector::marginBottom() const {
|
||||
}
|
||||
|
||||
void TabbedSelector::refreshStickers() {
|
||||
if (!full()) {
|
||||
return;
|
||||
if (hasStickersTab()) {
|
||||
stickers()->refreshStickers();
|
||||
if (isHidden() || _currentTabType != SelectorTab::Stickers) {
|
||||
stickers()->preloadImages();
|
||||
}
|
||||
}
|
||||
stickers()->refreshStickers();
|
||||
if (isHidden() || _currentTabType != SelectorTab::Stickers) {
|
||||
stickers()->preloadImages();
|
||||
if (hasMasksTab()) {
|
||||
const auto masksList = masks();
|
||||
masksList->refreshStickers();
|
||||
if (isHidden() || _currentTabType != SelectorTab::Masks) {
|
||||
masksList->preloadImages();
|
||||
}
|
||||
|
||||
fillTabsSliderSections();
|
||||
updateTabsSliderGeometry();
|
||||
if (hasStickersTab() && masksList->mySetsEmpty()) {
|
||||
_tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TabbedSelector::preventAutoHide() const {
|
||||
return full()
|
||||
? (stickers()->preventAutoHide() || hasMenu())
|
||||
: false;
|
||||
return (hasStickersTab() ? stickers()->preventAutoHide() : false)
|
||||
|| (hasMasksTab() ? masks()->preventAutoHide() : false)
|
||||
|| hasMenu();
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasMenu() const {
|
||||
@@ -603,13 +717,17 @@ QImage TabbedSelector::grabForAnimation() {
|
||||
auto slideAnimation = base::take(_a_slide);
|
||||
|
||||
showAll();
|
||||
if (full()) {
|
||||
if (_topShadow) {
|
||||
_topShadow->hide();
|
||||
}
|
||||
if (_tabsSlider) {
|
||||
_tabsSlider->hide();
|
||||
}
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
|
||||
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
auto result = QImage(
|
||||
size() * cIntRetinaFactor(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
result.fill(Qt::transparent);
|
||||
render(&result);
|
||||
@@ -630,9 +748,6 @@ QRect TabbedSelector::floatPlayerAvailableRect() const {
|
||||
|
||||
void TabbedSelector::hideFinished() {
|
||||
for (auto &tab : _tabs) {
|
||||
if (!tab.widget()) {
|
||||
continue;
|
||||
}
|
||||
tab.widget()->panelHideFinished();
|
||||
}
|
||||
_a_slide.stop();
|
||||
@@ -640,9 +755,12 @@ void TabbedSelector::hideFinished() {
|
||||
}
|
||||
|
||||
void TabbedSelector::showStarted() {
|
||||
if (full()) {
|
||||
if (hasStickersTab()) {
|
||||
session().api().updateStickers();
|
||||
}
|
||||
if (hasMasksTab()) {
|
||||
session().api().updateMasks();
|
||||
}
|
||||
currentTab()->widget()->refreshRecent();
|
||||
currentTab()->widget()->preloadImages();
|
||||
_a_slide.stop();
|
||||
@@ -670,13 +788,14 @@ void TabbedSelector::afterShown() {
|
||||
}
|
||||
|
||||
void TabbedSelector::setCurrentPeer(PeerData *peer) {
|
||||
if (!full()) {
|
||||
return;
|
||||
if (hasGifsTab()) {
|
||||
gifs()->setInlineQueryPeer(peer);
|
||||
}
|
||||
gifs()->setInlineQueryPeer(peer);
|
||||
_currentPeer = peer;
|
||||
checkRestrictedPeer();
|
||||
stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
|
||||
if (hasStickersTab()) {
|
||||
stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::checkRestrictedPeer() {
|
||||
@@ -730,16 +849,20 @@ void TabbedSelector::showAll() {
|
||||
_scroll->show();
|
||||
_bottomShadow->setVisible(_currentTabType == SelectorTab::Gifs);
|
||||
}
|
||||
if (full()) {
|
||||
if (_topShadow) {
|
||||
_topShadow->show();
|
||||
}
|
||||
if (_tabsSlider) {
|
||||
_tabsSlider->show();
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::hideForSliding() {
|
||||
hideChildren();
|
||||
if (full()) {
|
||||
if (_topShadow) {
|
||||
_topShadow->show();
|
||||
}
|
||||
if (_tabsSlider) {
|
||||
_tabsSlider->show();
|
||||
}
|
||||
currentTab()->widget()->clearSelection();
|
||||
@@ -753,48 +876,70 @@ void TabbedSelector::handleScroll() {
|
||||
|
||||
void TabbedSelector::setRoundRadius(int radius) {
|
||||
_roundRadius = radius;
|
||||
if (full()) {
|
||||
if (_tabsSlider) {
|
||||
_tabsSlider->setRippleTopRoundRadius(_roundRadius);
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::createTabsSlider() {
|
||||
if (!full()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_tabsSlider.create(this, st::emojiTabs);
|
||||
|
||||
auto sections = QStringList();
|
||||
sections.push_back(tr::lng_switch_emoji(tr::now).toUpper());
|
||||
sections.push_back(tr::lng_switch_stickers(tr::now).toUpper());
|
||||
sections.push_back(tr::lng_switch_gifs(tr::now).toUpper());
|
||||
_tabsSlider->setSections(sections);
|
||||
fillTabsSliderSections();
|
||||
|
||||
_tabsSlider->setActiveSectionFast(static_cast<int>(_currentTabType));
|
||||
_tabsSlider->setActiveSectionFast(indexByType(_currentTabType));
|
||||
_tabsSlider->sectionActivated(
|
||||
) | rpl::start_with_next([=] {
|
||||
switchTab();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void TabbedSelector::fillTabsSliderSections() {
|
||||
if (!_tabsSlider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sections = ranges::views::all(
|
||||
_tabs
|
||||
) | ranges::views::filter([&](const Tab &tab) {
|
||||
return (tab.type() == SelectorTab::Masks)
|
||||
? !masks()->mySetsEmpty()
|
||||
: true;
|
||||
}) | ranges::views::transform([&](const Tab &tab) {
|
||||
return [&] {
|
||||
switch (tab.type()) {
|
||||
case SelectorTab::Emoji:
|
||||
return tr::lng_switch_emoji;
|
||||
case SelectorTab::Stickers:
|
||||
return tr::lng_switch_stickers;
|
||||
case SelectorTab::Gifs:
|
||||
return tr::lng_switch_gifs;
|
||||
case SelectorTab::Masks:
|
||||
return tr::lng_switch_masks;
|
||||
}
|
||||
Unexpected("SelectorTab value in fillTabsSliderSections.");
|
||||
}()(tr::now).toUpper();
|
||||
}) | ranges::to_vector;
|
||||
_tabsSlider->setSections(sections);
|
||||
}
|
||||
|
||||
bool TabbedSelector::hasSectionIcons() const {
|
||||
return (_currentTabType != SelectorTab::Gifs) && !_restrictedLabel;
|
||||
}
|
||||
|
||||
void TabbedSelector::switchTab() {
|
||||
Expects(full());
|
||||
Expects(tabbed());
|
||||
|
||||
auto tab = _tabsSlider->activeSection();
|
||||
Assert(tab >= 0 && tab < Tab::kCount);
|
||||
auto newTabType = static_cast<SelectorTab>(tab);
|
||||
const auto tab = _tabsSlider->activeSection();
|
||||
Assert(tab >= 0 && tab < _tabs.size());
|
||||
const auto newTabType = typeByIndex(tab);
|
||||
if (_currentTabType == newTabType) {
|
||||
_scroll->scrollToY(0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto wasSectionIcons = hasSectionIcons();
|
||||
auto wasTab = _currentTabType;
|
||||
const auto wasSectionIcons = hasSectionIcons();
|
||||
const auto wasTab = _currentTabType;
|
||||
const auto wasIndex = indexByType(_currentTabType);
|
||||
currentTab()->saveScrollTop();
|
||||
|
||||
beforeHiding();
|
||||
@@ -817,21 +962,38 @@ void TabbedSelector::switchTab() {
|
||||
|
||||
auto nowCache = grabForAnimation();
|
||||
|
||||
auto direction = (wasTab > _currentTabType) ? SlideAnimation::Direction::LeftToRight : SlideAnimation::Direction::RightToLeft;
|
||||
auto direction = (wasIndex > indexByType(_currentTabType))
|
||||
? SlideAnimation::Direction::LeftToRight
|
||||
: SlideAnimation::Direction::RightToLeft;
|
||||
if (direction == SlideAnimation::Direction::LeftToRight) {
|
||||
std::swap(wasCache, nowCache);
|
||||
}
|
||||
_slideAnimation = std::make_unique<SlideAnimation>();
|
||||
auto slidingRect = QRect(0, _scroll->y() * cIntRetinaFactor(), width() * cIntRetinaFactor(), (height() - _scroll->y()) * cIntRetinaFactor());
|
||||
_slideAnimation->setFinalImages(direction, std::move(wasCache), std::move(nowCache), slidingRect, wasSectionIcons);
|
||||
_slideAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small));
|
||||
const auto slidingRect = QRect(
|
||||
0,
|
||||
_scroll->y() * cIntRetinaFactor(),
|
||||
width() * cIntRetinaFactor(),
|
||||
(height() - _scroll->y()) * cIntRetinaFactor());
|
||||
_slideAnimation->setFinalImages(
|
||||
direction,
|
||||
std::move(wasCache),
|
||||
std::move(nowCache),
|
||||
slidingRect,
|
||||
wasSectionIcons);
|
||||
_slideAnimation->setCornerMasks(
|
||||
Images::CornersMask(ImageRoundRadius::Small));
|
||||
_slideAnimation->start();
|
||||
|
||||
hideForSliding();
|
||||
|
||||
getTab(wasTab)->widget()->hideFinished();
|
||||
getTab(wasIndex)->widget()->hideFinished();
|
||||
|
||||
_a_slide.start([this] { update(); }, 0., 1., st::emojiPanSlideDuration, anim::linear);
|
||||
_a_slide.start(
|
||||
[=] { update(); },
|
||||
0.,
|
||||
1.,
|
||||
st::emojiPanSlideDuration,
|
||||
anim::linear);
|
||||
update();
|
||||
|
||||
if (full()) {
|
||||
@@ -841,19 +1003,31 @@ void TabbedSelector::switchTab() {
|
||||
}
|
||||
|
||||
not_null<EmojiListWidget*> TabbedSelector::emoji() const {
|
||||
return static_cast<EmojiListWidget*>(getTab(SelectorTab::Emoji)->widget());
|
||||
Expects(hasEmojiTab());
|
||||
|
||||
return static_cast<EmojiListWidget*>(
|
||||
getTab(indexByType(SelectorTab::Emoji))->widget());
|
||||
}
|
||||
|
||||
not_null<StickersListWidget*> TabbedSelector::stickers() const {
|
||||
Expects(full());
|
||||
Expects(hasStickersTab());
|
||||
|
||||
return static_cast<StickersListWidget*>(getTab(SelectorTab::Stickers)->widget());
|
||||
return static_cast<StickersListWidget*>(
|
||||
getTab(indexByType(SelectorTab::Stickers))->widget());
|
||||
}
|
||||
|
||||
not_null<GifsListWidget*> TabbedSelector::gifs() const {
|
||||
Expects(full());
|
||||
Expects(hasGifsTab());
|
||||
|
||||
return static_cast<GifsListWidget*>(getTab(SelectorTab::Gifs)->widget());
|
||||
return static_cast<GifsListWidget*>(
|
||||
getTab(indexByType(SelectorTab::Gifs))->widget());
|
||||
}
|
||||
|
||||
not_null<StickersListWidget*> TabbedSelector::masks() const {
|
||||
Expects(hasMasksTab());
|
||||
|
||||
return static_cast<StickersListWidget*>(
|
||||
getTab(indexByType(SelectorTab::Masks))->widget());
|
||||
}
|
||||
|
||||
void TabbedSelector::setWidgetToScrollArea() {
|
||||
@@ -873,7 +1047,7 @@ void TabbedSelector::scrollToY(int y) {
|
||||
_scroll->scrollToY(y);
|
||||
|
||||
// Qt render glitch workaround, shadow sometimes disappears if we just scroll to y.
|
||||
if (full()) {
|
||||
if (_topShadow) {
|
||||
_topShadow->update();
|
||||
}
|
||||
}
|
||||
@@ -894,6 +1068,40 @@ rpl::producer<> TabbedSelector::contextMenuRequested() const {
|
||||
}) | rpl::to_empty;
|
||||
}
|
||||
|
||||
SelectorTab TabbedSelector::typeByIndex(int index) const {
|
||||
for (const auto &tab : _tabs) {
|
||||
if (tab.index() == index) {
|
||||
return tab.type();
|
||||
}
|
||||
}
|
||||
Unexpected("Type in TabbedSelector::typeByIndex.");
|
||||
}
|
||||
|
||||
int TabbedSelector::indexByType(SelectorTab type) const {
|
||||
for (const auto &tab : _tabs) {
|
||||
if (tab.type() == type) {
|
||||
return tab.index();
|
||||
}
|
||||
}
|
||||
Unexpected("Index in TabbedSelector::indexByType.");
|
||||
}
|
||||
|
||||
not_null<TabbedSelector::Tab*> TabbedSelector::getTab(int index) {
|
||||
return &(_tabs[index]);
|
||||
}
|
||||
|
||||
not_null<const TabbedSelector::Tab*> TabbedSelector::getTab(int index) const {
|
||||
return &_tabs[index];
|
||||
}
|
||||
|
||||
not_null<TabbedSelector::Tab*> TabbedSelector::currentTab() {
|
||||
return &_tabs[indexByType(_currentTabType)];
|
||||
}
|
||||
|
||||
not_null<const TabbedSelector::Tab*> TabbedSelector::currentTab() const {
|
||||
return &_tabs[indexByType(_currentTabType)];
|
||||
}
|
||||
|
||||
TabbedSelector::Inner::Inner(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
@@ -917,7 +1125,9 @@ void TabbedSelector::Inner::disableScroll(bool disabled) {
|
||||
_disableScrollRequests.fire_copy(disabled);
|
||||
}
|
||||
|
||||
void TabbedSelector::Inner::visibleTopBottomUpdated(int visibleTop, int visibleBottom) {
|
||||
void TabbedSelector::Inner::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
_visibleTop = visibleTop;
|
||||
_visibleBottom = visibleBottom;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ enum class SelectorTab {
|
||||
Emoji,
|
||||
Stickers,
|
||||
Gifs,
|
||||
Masks,
|
||||
};
|
||||
|
||||
class EmojiListWidget;
|
||||
@@ -63,7 +64,8 @@ public:
|
||||
using InlineChosen = InlineBots::ResultSelected;
|
||||
enum class Mode {
|
||||
Full,
|
||||
EmojiOnly
|
||||
EmojiOnly,
|
||||
MediaEditor,
|
||||
};
|
||||
|
||||
TabbedSelector(
|
||||
@@ -130,9 +132,7 @@ protected:
|
||||
private:
|
||||
class Tab {
|
||||
public:
|
||||
static constexpr auto kCount = 3;
|
||||
|
||||
Tab(SelectorTab type, object_ptr<Inner> widget);
|
||||
Tab(SelectorTab type, int index, object_ptr<Inner> widget);
|
||||
|
||||
object_ptr<Inner> takeWidget();
|
||||
void returnWidget(object_ptr<Inner> widget);
|
||||
@@ -140,6 +140,9 @@ private:
|
||||
SelectorTab type() const {
|
||||
return _type;
|
||||
}
|
||||
int index() const {
|
||||
return _index;
|
||||
}
|
||||
Inner *widget() const {
|
||||
return _weak;
|
||||
}
|
||||
@@ -156,7 +159,8 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
SelectorTab _type = SelectorTab::Emoji;
|
||||
const SelectorTab _type;
|
||||
const int _index;
|
||||
object_ptr<Inner> _widget = { nullptr };
|
||||
QPointer<Inner> _weak;
|
||||
object_ptr<InnerFooter> _footer;
|
||||
@@ -165,7 +169,13 @@ private:
|
||||
};
|
||||
|
||||
bool full() const;
|
||||
Tab createTab(SelectorTab type);
|
||||
bool mediaEditor() const;
|
||||
bool tabbed() const;
|
||||
bool hasEmojiTab() const;
|
||||
bool hasStickersTab() const;
|
||||
bool hasGifsTab() const;
|
||||
bool hasMasksTab() const;
|
||||
Tab createTab(SelectorTab type, int index);
|
||||
|
||||
void paintSlideFrame(Painter &p);
|
||||
void paintContent(Painter &p);
|
||||
@@ -182,25 +192,25 @@ private:
|
||||
void showAll();
|
||||
void hideForSliding();
|
||||
|
||||
SelectorTab typeByIndex(int index) const;
|
||||
int indexByType(SelectorTab type) const;
|
||||
|
||||
bool hasSectionIcons() const;
|
||||
void setWidgetToScrollArea();
|
||||
void createTabsSlider();
|
||||
void fillTabsSliderSections();
|
||||
void updateTabsSliderGeometry();
|
||||
void switchTab();
|
||||
not_null<Tab*> getTab(SelectorTab type) {
|
||||
return &_tabs[static_cast<int>(type)];
|
||||
}
|
||||
not_null<const Tab*> getTab(SelectorTab type) const {
|
||||
return &_tabs[static_cast<int>(type)];
|
||||
}
|
||||
not_null<Tab*> currentTab() {
|
||||
return getTab(_currentTabType);
|
||||
}
|
||||
not_null<const Tab*> currentTab() const {
|
||||
return getTab(_currentTabType);
|
||||
}
|
||||
|
||||
not_null<Tab*> getTab(int index);
|
||||
not_null<const Tab*> getTab(int index) const;
|
||||
not_null<Tab*> currentTab();
|
||||
not_null<const Tab*> currentTab() const;
|
||||
|
||||
not_null<EmojiListWidget*> emoji() const;
|
||||
not_null<StickersListWidget*> stickers() const;
|
||||
not_null<GifsListWidget*> gifs() const;
|
||||
not_null<StickersListWidget*> masks() const;
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
@@ -218,9 +228,15 @@ private:
|
||||
object_ptr<Ui::PlainShadow> _bottomShadow;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr };
|
||||
std::array<Tab, Tab::kCount> _tabs;
|
||||
std::vector<Tab> _tabs;
|
||||
SelectorTab _currentTabType = SelectorTab::Emoji;
|
||||
|
||||
const bool _hasEmojiTab;
|
||||
const bool _hasStickersTab;
|
||||
const bool _hasGifsTab;
|
||||
const bool _hasMasksTab;
|
||||
const bool _tabbed;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
Fn<void(SelectorTab)> _afterShownCallback;
|
||||
|
||||
@@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_lock_widgets.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/emoji_config.h"
|
||||
@@ -281,8 +282,7 @@ void Application::run() {
|
||||
LOG(("Shortcuts Error: %1").arg(error));
|
||||
}
|
||||
|
||||
if (!Platform::IsMac()
|
||||
&& Ui::Integration::Instance().openglLastCheckFailed()) {
|
||||
if (!Platform::IsMac() && Ui::GL::LastCrashCheckFailed()) {
|
||||
showOpenGLCrashNotification();
|
||||
}
|
||||
|
||||
@@ -297,14 +297,14 @@ void Application::run() {
|
||||
void Application::showOpenGLCrashNotification() {
|
||||
const auto enable = [=] {
|
||||
Ui::GL::ForceDisable(false);
|
||||
Ui::Integration::Instance().openglCheckFinish();
|
||||
Ui::GL::CrashCheckFinish();
|
||||
Core::App().settings().setDisableOpenGL(false);
|
||||
Local::writeSettings();
|
||||
App::restart();
|
||||
};
|
||||
const auto keepDisabled = [=] {
|
||||
Ui::GL::ForceDisable(true);
|
||||
Ui::Integration::Instance().openglCheckFinish();
|
||||
Ui::GL::CrashCheckFinish();
|
||||
Core::App().settings().setDisableOpenGL(true);
|
||||
Local::writeSettings();
|
||||
};
|
||||
@@ -849,7 +849,7 @@ bool Application::passcodeLocked() const {
|
||||
void Application::updateNonIdle() {
|
||||
_lastNonIdleTime = crl::now();
|
||||
if (const auto session = maybeActiveSession()) {
|
||||
session->updates().checkIdleFinish();
|
||||
session->updates().checkIdleFinish(_lastNonIdleTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,19 +876,21 @@ bool Application::someSessionExists() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Application::checkAutoLock() {
|
||||
void Application::checkAutoLock(crl::time lastNonIdleTime) {
|
||||
if (!_domain->local().hasLocalPasscode()
|
||||
|| passcodeLocked()
|
||||
|| !someSessionExists()) {
|
||||
_shouldLockAt = 0;
|
||||
_autoLockTimer.cancel();
|
||||
return;
|
||||
} else if (!lastNonIdleTime) {
|
||||
lastNonIdleTime = this->lastNonIdleTime();
|
||||
}
|
||||
|
||||
checkLocalTime();
|
||||
const auto now = crl::now();
|
||||
const auto shouldLockInMs = _settings.autoLock() * 1000LL;
|
||||
const auto checkTimeMs = now - lastNonIdleTime();
|
||||
const auto checkTimeMs = now - lastNonIdleTime;
|
||||
if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
|
||||
_shouldLockAt = 0;
|
||||
_autoLockTimer.cancel();
|
||||
@@ -910,7 +912,7 @@ void Application::checkAutoLockIn(crl::time time) {
|
||||
void Application::localPasscodeChanged() {
|
||||
_shouldLockAt = 0;
|
||||
_autoLockTimer.cancel();
|
||||
checkAutoLock();
|
||||
checkAutoLock(crl::now());
|
||||
}
|
||||
|
||||
bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||
|
||||
@@ -245,7 +245,7 @@ public:
|
||||
rpl::producer<bool> passcodeLockChanges() const;
|
||||
rpl::producer<bool> passcodeLockValue() const;
|
||||
|
||||
void checkAutoLock();
|
||||
void checkAutoLock(crl::time lastNonIdleTime = 0);
|
||||
void checkAutoLockIn(crl::time time);
|
||||
void localPasscodeChanged();
|
||||
|
||||
|
||||
@@ -127,6 +127,25 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- Several bug and crash fixes.\n"
|
||||
},
|
||||
{
|
||||
2007010,
|
||||
"- Added ability to mix together bold, italic and other formatting.\n"
|
||||
|
||||
"- Fix voice chats and video calls OpenGL with some drivers on Windows.\n"
|
||||
|
||||
"- Several bug fixes.\n"
|
||||
},
|
||||
{
|
||||
2008006,
|
||||
"- Added a simple image editor. "
|
||||
"Crop photos or highlight parts of screenshots before sending.\n"
|
||||
|
||||
"- Use Direct3D 9 backend in ANGLE by default (Windows).\n"
|
||||
|
||||
"- Fix \"Show in Finder\" not focusing the Finder window (macOS).\n"
|
||||
|
||||
"- Use GTK from a child process (Linux).\n"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "facades.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -94,23 +95,36 @@ QByteArray Settings::serialize() const {
|
||||
+ sizeof(qint32) * 5
|
||||
+ Serialize::stringSize(_downloadPath.current())
|
||||
+ Serialize::bytearraySize(_downloadPathBookmark)
|
||||
+ sizeof(qint32) * 12
|
||||
+ sizeof(qint32) * 9
|
||||
+ Serialize::stringSize(_callOutputDeviceId)
|
||||
+ Serialize::stringSize(_callInputDeviceId)
|
||||
+ Serialize::stringSize(_callVideoInputDeviceId)
|
||||
+ sizeof(qint32) * 5
|
||||
+ Serialize::bytearraySize(proxy);
|
||||
+ sizeof(qint32) * 5;
|
||||
for (const auto &[key, value] : _soundOverrides) {
|
||||
size += Serialize::stringSize(key) + Serialize::stringSize(value);
|
||||
}
|
||||
size += sizeof(qint32) * 13
|
||||
+ Serialize::bytearraySize(_videoPipGeometry)
|
||||
+ sizeof(qint32)
|
||||
+ (_dictionariesEnabled.current().size() * sizeof(quint64))
|
||||
+ sizeof(qint32) * 12
|
||||
+ Serialize::stringSize(_callVideoInputDeviceId)
|
||||
+ sizeof(qint32) * 2
|
||||
+ Serialize::bytearraySize(_groupCallPushToTalkShortcut)
|
||||
+ sizeof(qint64)
|
||||
+ sizeof(qint32) * 2
|
||||
+ Serialize::bytearraySize(windowPosition)
|
||||
+ sizeof(qint32)
|
||||
+ Serialize::bytearraySize(_photoEditorBrush);
|
||||
for (const auto &[id, rating] : recentEmojiPreloadData) {
|
||||
size += Serialize::stringSize(id) + sizeof(quint16);
|
||||
}
|
||||
size += sizeof(qint32);
|
||||
for (const auto &[id, variant] : _emojiVariants) {
|
||||
size += Serialize::stringSize(id) + sizeof(quint8);
|
||||
}
|
||||
size += Serialize::bytearraySize(_videoPipGeometry);
|
||||
size += Serialize::bytearraySize(windowPosition);
|
||||
size += sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(proxy)
|
||||
+ sizeof(qint32) * 2;
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -198,10 +212,13 @@ QByteArray Settings::serialize() const {
|
||||
stream << id << quint8(variant);
|
||||
}
|
||||
stream
|
||||
<< qint32(_disableOpenGL ? 1 : 0)
|
||||
<< qint32(0) // Old Disable OpenGL
|
||||
<< qint32(_groupCallNoiseSuppression ? 1 : 0)
|
||||
<< _workMode.current()
|
||||
<< proxy;
|
||||
<< qint32(_workMode.current())
|
||||
<< proxy
|
||||
<< qint32(_hiddenGroupCallTooltips.value())
|
||||
<< qint32(_disableOpenGL ? 1 : 0)
|
||||
<< _photoEditorBrush;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -281,6 +298,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 groupCallNoiseSuppression = _groupCallNoiseSuppression ? 1 : 0;
|
||||
qint32 workMode = static_cast<qint32>(_workMode.current());
|
||||
QByteArray proxy;
|
||||
qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value());
|
||||
QByteArray photoEditorBrush = _photoEditorBrush;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -410,7 +429,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
}
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> disableOpenGL;
|
||||
qint32 disableOpenGLOld;
|
||||
stream >> disableOpenGLOld;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> groupCallNoiseSuppression;
|
||||
@@ -421,6 +441,15 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> proxy;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> hiddenGroupCallTooltips;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> disableOpenGL;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> photoEditorBrush;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -531,7 +560,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_disableOpenGL = (disableOpenGL == 1);
|
||||
if (!Platform::IsMac()) {
|
||||
Ui::GL::ForceDisable(_disableOpenGL
|
||||
|| Ui::Integration::Instance().openglLastCheckFailed());
|
||||
|| Ui::GL::LastCrashCheckFailed());
|
||||
}
|
||||
_groupCallNoiseSuppression = (groupCallNoiseSuppression == 1);
|
||||
const auto uncheckedWorkMode = static_cast<WorkMode>(workMode);
|
||||
@@ -540,6 +569,17 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
case WorkMode::TrayOnly:
|
||||
case WorkMode::WindowOnly: _workMode = uncheckedWorkMode; break;
|
||||
}
|
||||
_hiddenGroupCallTooltips = [&] {
|
||||
using Tooltip = Calls::Group::StickedTooltip;
|
||||
return Tooltip(0)
|
||||
| ((hiddenGroupCallTooltips & int(Tooltip::Camera))
|
||||
? Tooltip::Camera
|
||||
: Tooltip(0))
|
||||
| ((hiddenGroupCallTooltips & int(Tooltip::Microphone))
|
||||
? Tooltip::Microphone
|
||||
: Tooltip(0));
|
||||
}();
|
||||
_photoEditorBrush = photoEditorBrush;
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
@@ -795,6 +835,7 @@ void Settings::resetOnLastLogout() {
|
||||
_notifyFromAll = true;
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/themes/window_themes_embedded.h"
|
||||
#include "ui/chat/attach/attach_send_files_way.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "base/flags.h"
|
||||
#include "emoji.h"
|
||||
|
||||
enum class RectPart;
|
||||
@@ -27,6 +28,10 @@ namespace Webrtc {
|
||||
enum class Backend;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Calls::Group {
|
||||
enum class StickedTooltip;
|
||||
} // namespace Calls::Group
|
||||
|
||||
namespace Core {
|
||||
|
||||
struct WindowPosition {
|
||||
@@ -90,6 +95,9 @@ public:
|
||||
[[nodiscard]] rpl::producer<bool> adaptiveForWideValue() const {
|
||||
return _adaptiveForWide.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> adaptiveForWideChanges() const {
|
||||
return _adaptiveForWide.changes();
|
||||
}
|
||||
void setAdaptiveForWide(bool value) {
|
||||
_adaptiveForWide = value;
|
||||
}
|
||||
@@ -426,6 +434,13 @@ public:
|
||||
_videoPipGeometry = geometry;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray photoEditorBrush() const {
|
||||
return _photoEditorBrush;
|
||||
}
|
||||
void setPhotoEditorBrush(QByteArray brush) {
|
||||
_photoEditorBrush = brush;
|
||||
}
|
||||
|
||||
[[nodiscard]] float64 rememberedSongVolume() const {
|
||||
return _rememberedSongVolume;
|
||||
}
|
||||
@@ -574,6 +589,13 @@ public:
|
||||
_disableOpenGL = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] base::flags<Calls::Group::StickedTooltip> hiddenGroupCallTooltips() const {
|
||||
return _hiddenGroupCallTooltips;
|
||||
}
|
||||
void setHiddenGroupCallTooltip(Calls::Group::StickedTooltip value) {
|
||||
_hiddenGroupCallTooltips |= value;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
|
||||
@@ -671,6 +693,7 @@ private:
|
||||
WindowPosition _windowPosition; // per-window
|
||||
bool _disableOpenGL = false;
|
||||
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
|
||||
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
@@ -680,6 +703,8 @@ private:
|
||||
bool _rememberedSoundNotifyFromTray = false;
|
||||
bool _rememberedFlashBounceNotifyFromTray = false;
|
||||
|
||||
QByteArray _photoEditorBrush;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "ui/main_queue_processor.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "core/sandbox.h"
|
||||
@@ -334,7 +333,6 @@ int Launcher::exec() {
|
||||
|
||||
// Must be started before Sandbox is created.
|
||||
Platform::start();
|
||||
Ui::DisableCustomScaling();
|
||||
|
||||
auto result = executeApplication();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
|
||||
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
|
||||
|
||||
int exec();
|
||||
virtual int exec();
|
||||
|
||||
QString argumentsString() const;
|
||||
bool customWorkingDir() const;
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "app.h"
|
||||
|
||||
@@ -312,6 +313,7 @@ void Sandbox::singleInstanceChecked() {
|
||||
Logs::multipleInstances();
|
||||
}
|
||||
|
||||
Ui::DisableCustomScaling();
|
||||
refreshGlobalProxy();
|
||||
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
|
||||
new NotStartedWindow();
|
||||
|
||||