Compare commits
172 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 |
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
|
||||
|
||||
@@ -87,7 +87,6 @@ if (LINUX)
|
||||
PRIVATE
|
||||
desktop-app::external_glibmm
|
||||
desktop-app::external_glib
|
||||
desktop-app::external_mallocng
|
||||
)
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
@@ -114,6 +113,7 @@ if (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)
|
||||
@@ -251,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
|
||||
@@ -395,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
|
||||
@@ -507,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
|
||||
@@ -863,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
|
||||
@@ -1196,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
|
||||
@@ -1362,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)
|
||||
@@ -1378,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/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.";
|
||||
@@ -1372,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.";
|
||||
@@ -1382,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.";
|
||||
@@ -1395,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?";
|
||||
@@ -1406,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";
|
||||
@@ -2760,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.8.2.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,8,2,0
|
||||
PRODUCTVERSION 2,8,2,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.8.2.0"
|
||||
VALUE "FileVersion", "2.8.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.8.2.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,8,2,0
|
||||
PRODUCTVERSION 2,8,2,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.8.2.0"
|
||||
VALUE "FileVersion", "2.8.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.8.2.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
|
||||
|
||||
@@ -2141,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: {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(); });
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,6 +749,10 @@ 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([=] {
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -135,6 +135,17 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- 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"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -113,7 +113,8 @@ QByteArray Settings::serialize() const {
|
||||
+ sizeof(qint64)
|
||||
+ sizeof(qint32) * 2
|
||||
+ Serialize::bytearraySize(windowPosition)
|
||||
+ sizeof(qint32);
|
||||
+ sizeof(qint32)
|
||||
+ Serialize::bytearraySize(_photoEditorBrush);
|
||||
for (const auto &[id, rating] : recentEmojiPreloadData) {
|
||||
size += Serialize::stringSize(id) + sizeof(quint16);
|
||||
}
|
||||
@@ -123,7 +124,7 @@ QByteArray Settings::serialize() const {
|
||||
}
|
||||
size += sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(proxy)
|
||||
+ sizeof(qint32);
|
||||
+ sizeof(qint32) * 2;
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -211,11 +212,13 @@ QByteArray Settings::serialize() const {
|
||||
stream << id << quint8(variant);
|
||||
}
|
||||
stream
|
||||
<< qint32(_disableOpenGL ? 1 : 0)
|
||||
<< qint32(0) // Old Disable OpenGL
|
||||
<< qint32(_groupCallNoiseSuppression ? 1 : 0)
|
||||
<< qint32(_workMode.current())
|
||||
<< proxy
|
||||
<< qint32(_hiddenGroupCallTooltips.value());
|
||||
<< qint32(_hiddenGroupCallTooltips.value())
|
||||
<< qint32(_disableOpenGL ? 1 : 0)
|
||||
<< _photoEditorBrush;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -296,6 +299,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 workMode = static_cast<qint32>(_workMode.current());
|
||||
QByteArray proxy;
|
||||
qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value());
|
||||
QByteArray photoEditorBrush = _photoEditorBrush;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -425,7 +429,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
}
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> disableOpenGL;
|
||||
qint32 disableOpenGLOld;
|
||||
stream >> disableOpenGLOld;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> groupCallNoiseSuppression;
|
||||
@@ -439,6 +444,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
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()"));
|
||||
@@ -549,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);
|
||||
@@ -568,6 +579,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
? Tooltip::Microphone
|
||||
: Tooltip(0));
|
||||
}();
|
||||
_photoEditorBrush = photoEditorBrush;
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
||||
@@ -434,6 +434,13 @@ public:
|
||||
_videoPipGeometry = geometry;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray photoEditorBrush() const {
|
||||
return _photoEditorBrush;
|
||||
}
|
||||
void setPhotoEditorBrush(QByteArray brush) {
|
||||
_photoEditorBrush = brush;
|
||||
}
|
||||
|
||||
[[nodiscard]] float64 rememberedSongVolume() const {
|
||||
return _rememberedSongVolume;
|
||||
}
|
||||
@@ -696,6 +703,8 @@ private:
|
||||
bool _rememberedSoundNotifyFromTray = false;
|
||||
bool _rememberedFlashBounceNotifyFromTray = false;
|
||||
|
||||
QByteArray _photoEditorBrush;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -88,6 +88,10 @@ const auto kBadPrefix = u"http://"_q;
|
||||
return cWorkingDir() + "tdata/opengl_crash_check";
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ANGLEBackendFilePath() {
|
||||
return cWorkingDir() + "tdata/angle_backend";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void UiIntegration::postponeCall(FnMut<void()> &&callable) {
|
||||
@@ -106,20 +110,12 @@ QString UiIntegration::emojiCacheFolder() {
|
||||
return cWorkingDir() + "tdata/emoji";
|
||||
}
|
||||
|
||||
void UiIntegration::openglCheckStart() {
|
||||
auto f = QFile(OpenGLCheckFilePath());
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
f.write("1", 1);
|
||||
f.close();
|
||||
}
|
||||
QString UiIntegration::openglCheckFilePath() {
|
||||
return OpenGLCheckFilePath();
|
||||
}
|
||||
|
||||
void UiIntegration::openglCheckFinish() {
|
||||
QFile::remove(OpenGLCheckFilePath());
|
||||
}
|
||||
|
||||
bool UiIntegration::openglLastCheckFailed() {
|
||||
return OpenGLLastCheckFailed();
|
||||
QString UiIntegration::angleBackendFilePath() {
|
||||
return ANGLEBackendFilePath();
|
||||
}
|
||||
|
||||
void UiIntegration::textActionsUpdated() {
|
||||
|
||||
@@ -37,10 +37,8 @@ public:
|
||||
void unregisterLeaveSubscription(not_null<QWidget*> widget) override;
|
||||
|
||||
QString emojiCacheFolder() override;
|
||||
|
||||
void openglCheckStart() override;
|
||||
void openglCheckFinish() override;
|
||||
bool openglLastCheckFailed() override;
|
||||
QString openglCheckFilePath() override;
|
||||
QString angleBackendFilePath() override;
|
||||
|
||||
void textActionsUpdated() override;
|
||||
void activationFromTopPanel() override;
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 2008002;
|
||||
constexpr auto AppVersionStr = "2.8.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 2008006;
|
||||
constexpr auto AppVersionStr = "2.8.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
78
Telegram/SourceFiles/data/data_abstract_sparse_ids.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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
|
||||
|
||||
template <typename IdsContainer>
|
||||
class AbstractSparseIds {
|
||||
public:
|
||||
using Id = typename IdsContainer::value_type;
|
||||
|
||||
AbstractSparseIds() = default;
|
||||
AbstractSparseIds(
|
||||
const IdsContainer &ids,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter)
|
||||
: _ids(ids)
|
||||
, _fullCount(fullCount)
|
||||
, _skippedBefore(skippedBefore)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
}
|
||||
|
||||
std::optional<int> fullCount() const {
|
||||
return _fullCount;
|
||||
}
|
||||
std::optional<int> skippedBefore() const {
|
||||
return _skippedBefore;
|
||||
}
|
||||
std::optional<int> skippedAfter() const {
|
||||
return _skippedAfter;
|
||||
}
|
||||
std::optional<int> indexOf(Id id) const {
|
||||
const auto it = ranges::find(_ids, id);
|
||||
if (it != _ids.end()) {
|
||||
return (it - _ids.begin());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
int size() const {
|
||||
return _ids.size();
|
||||
}
|
||||
Id operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return *(_ids.begin() + index);
|
||||
}
|
||||
std::optional<int> distance(Id a, Id b) const {
|
||||
if (const auto i = indexOf(a)) {
|
||||
if (const auto j = indexOf(b)) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<Id> nearest(Id id) const {
|
||||
if (const auto it = ranges::lower_bound(_ids, id); it != _ids.end()) {
|
||||
return *it;
|
||||
} else if (_ids.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return _ids.back();
|
||||
}
|
||||
void reverse() {
|
||||
ranges::reverse(_ids);
|
||||
std::swap(_skippedBefore, _skippedAfter);
|
||||
}
|
||||
|
||||
private:
|
||||
IdsContainer _ids;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
|
||||
};
|
||||
@@ -46,6 +46,10 @@ void MegagroupInfo::setLocation(const ChannelLocation &location) {
|
||||
_location = location;
|
||||
}
|
||||
|
||||
bool MegagroupInfo::updateBotCommands(const MTPVector<MTPBotInfo> &data) {
|
||||
return Data::UpdateBotCommands(_botCommands, data);
|
||||
}
|
||||
|
||||
ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
|
||||
: PeerData(owner, id)
|
||||
, inputChannel(
|
||||
@@ -803,15 +807,6 @@ void ApplyChannelUpdate(
|
||||
const auto chat = channel->owner().chat(migratedFrom->v);
|
||||
Data::ApplyMigration(chat, channel);
|
||||
}
|
||||
for (const auto &item : update.vbot_info().v) {
|
||||
auto &owner = channel->owner();
|
||||
item.match([&](const MTPDbotInfo &info) {
|
||||
if (const auto user = owner.userLoaded(info.vuser_id().v)) {
|
||||
user->setBotInfo(item);
|
||||
session->api().fullPeerUpdated().notify(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
channel->setAbout(qs(update.vabout()));
|
||||
channel->setMembersCount(update.vparticipants_count().value_or_empty());
|
||||
channel->setAdminsCount(update.vadmins_count().value_or_empty());
|
||||
@@ -867,6 +862,9 @@ void ApplyChannelUpdate(
|
||||
SetTopPinnedMessageId(channel, pinned->v);
|
||||
}
|
||||
if (channel->isMegagroup()) {
|
||||
if (channel->mgInfo->updateBotCommands(update.vbot_info())) {
|
||||
channel->owner().botCommandsChanged(channel);
|
||||
}
|
||||
const auto stickerSet = update.vstickerset();
|
||||
const auto set = stickerSet ? &stickerSet->c_stickerSet() : nullptr;
|
||||
const auto newSetId = (set ? set->vid().v : 0);
|
||||
|
||||
@@ -56,6 +56,12 @@ public:
|
||||
const ChannelLocation *getLocation() const;
|
||||
void setLocation(const ChannelLocation &location);
|
||||
|
||||
bool updateBotCommands(const MTPVector<MTPBotInfo> &data);
|
||||
[[nodiscard]] auto botCommands() const
|
||||
-> const base::flat_map<UserId, std::vector<BotCommand>> & {
|
||||
return _botCommands;
|
||||
}
|
||||
|
||||
std::deque<not_null<UserData*>> lastParticipants;
|
||||
base::flat_map<not_null<UserData*>, Admin> lastAdmins;
|
||||
base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
|
||||
@@ -82,6 +88,7 @@ public:
|
||||
private:
|
||||
ChatData *_migratedFrom = nullptr;
|
||||
ChannelLocation _location;
|
||||
base::flat_map<UserId, std::vector<BotCommand>> _botCommands;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -246,6 +246,12 @@ PeerId ChatData::groupCallDefaultJoinAs() const {
|
||||
return _callDefaultJoinAs;
|
||||
}
|
||||
|
||||
void ChatData::setBotCommands(const MTPVector<MTPBotInfo> &data) {
|
||||
if (Data::UpdateBotCommands(_botCommands, data)) {
|
||||
owner().botCommandsChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Data {
|
||||
|
||||
void ApplyChatUpdate(
|
||||
@@ -393,15 +399,9 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
|
||||
|
||||
chat->setMessagesTTL(update.vttl_period().value_or_empty());
|
||||
if (const auto info = update.vbot_info()) {
|
||||
for (const auto &item : info->v) {
|
||||
item.match([&](const MTPDbotInfo &data) {
|
||||
const auto userId = data.vuser_id().v;
|
||||
if (const auto bot = chat->owner().userLoaded(userId)) {
|
||||
bot->setBotInfo(item);
|
||||
chat->session().api().fullPeerUpdated().notify(bot);
|
||||
}
|
||||
});
|
||||
}
|
||||
chat->setBotCommands(*info);
|
||||
} else {
|
||||
chat->setBotCommands(MTP_vector<MTPBotInfo>());
|
||||
}
|
||||
chat->setFullFlags(update.vflags().v);
|
||||
if (const auto photo = update.vchat_photo()) {
|
||||
|
||||
@@ -173,6 +173,12 @@ public:
|
||||
void setGroupCallDefaultJoinAs(PeerId peerId);
|
||||
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
|
||||
|
||||
void setBotCommands(const MTPVector<MTPBotInfo> &data);
|
||||
[[nodiscard]] auto botCommands() const
|
||||
-> const base::flat_map<UserId, std::vector<BotCommand>> & {
|
||||
return _botCommands;
|
||||
}
|
||||
|
||||
// Still public data members.
|
||||
const MTPint inputChat;
|
||||
|
||||
@@ -198,6 +204,7 @@ private:
|
||||
|
||||
std::unique_ptr<Data::GroupCall> _call;
|
||||
PeerId _callDefaultJoinAs = 0;
|
||||
base::flat_map<UserId, std::vector<BotCommand>> _botCommands;
|
||||
|
||||
ChannelData *_migratedTo = nullptr;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
@@ -442,12 +442,17 @@ bool DocumentData::checkWallPaperProperties() {
|
||||
}
|
||||
|
||||
void DocumentData::updateThumbnails(
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const InlineImageLocation &inlineThumbnail,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &videoThumbnail) {
|
||||
if (!inlineThumbnailBytes.isEmpty()
|
||||
if (!inlineThumbnail.bytes.isEmpty()
|
||||
&& _inlineThumbnailBytes.isEmpty()) {
|
||||
_inlineThumbnailBytes = inlineThumbnailBytes;
|
||||
_inlineThumbnailBytes = inlineThumbnail.bytes;
|
||||
if (inlineThumbnail.isPath) {
|
||||
_flags |= Flag::InlineThumbnailIsPath;
|
||||
} else {
|
||||
_flags &= ~Flag::InlineThumbnailIsPath;
|
||||
}
|
||||
}
|
||||
Data::UpdateCloudFile(
|
||||
_thumbnail,
|
||||
|
||||
@@ -171,13 +171,16 @@ public:
|
||||
[[nodiscard]] int videoThumbnailByteSize() const;
|
||||
|
||||
void updateThumbnails(
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const InlineImageLocation &inlineThumbnail,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &videoThumbnail);
|
||||
|
||||
[[nodiscard]] QByteArray inlineThumbnailBytes() const {
|
||||
return _inlineThumbnailBytes;
|
||||
}
|
||||
[[nodiscard]] bool inlineThumbnailIsPath() const {
|
||||
return (_flags & Flag::InlineThumbnailIsPath);
|
||||
}
|
||||
void clearInlineThumbnailBytes() {
|
||||
_inlineThumbnailBytes = QByteArray();
|
||||
}
|
||||
@@ -257,6 +260,7 @@ private:
|
||||
DownloadCancelled = 0x10,
|
||||
LoadedInMediaCache = 0x20,
|
||||
HasAttachedStickers = 0x40,
|
||||
InlineThumbnailIsPath = 0x80,
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend constexpr bool is_flag_type(Flag) { return true; };
|
||||
|
||||
@@ -172,7 +172,7 @@ void DocumentMedia::setGoodThumbnail(QImage thumbnail) {
|
||||
}
|
||||
|
||||
Image *DocumentMedia::thumbnailInline() const {
|
||||
if (!_inlineThumbnail) {
|
||||
if (!_inlineThumbnail && !_owner->inlineThumbnailIsPath()) {
|
||||
const auto bytes = _owner->inlineThumbnailBytes();
|
||||
if (!bytes.isEmpty()) {
|
||||
auto image = Images::FromInlineBytes(bytes);
|
||||
@@ -186,6 +186,19 @@ Image *DocumentMedia::thumbnailInline() const {
|
||||
return _inlineThumbnail.get();
|
||||
}
|
||||
|
||||
const QPainterPath &DocumentMedia::thumbnailPath() const {
|
||||
if (_pathThumbnail.isEmpty() && _owner->inlineThumbnailIsPath()) {
|
||||
const auto bytes = _owner->inlineThumbnailBytes();
|
||||
if (!bytes.isEmpty()) {
|
||||
_pathThumbnail = Images::PathFromInlineBytes(bytes);
|
||||
if (_pathThumbnail.isEmpty()) {
|
||||
_owner->clearInlineThumbnailBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _pathThumbnail;
|
||||
}
|
||||
|
||||
Image *DocumentMedia::thumbnail() const {
|
||||
return _thumbnail.get();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
void setGoodThumbnail(QImage thumbnail);
|
||||
|
||||
[[nodiscard]] Image *thumbnailInline() const;
|
||||
[[nodiscard]] const QPainterPath &thumbnailPath() const;
|
||||
|
||||
[[nodiscard]] Image *thumbnail() const;
|
||||
[[nodiscard]] QSize thumbnailSize() const;
|
||||
@@ -102,6 +103,7 @@ private:
|
||||
const not_null<DocumentData*> _owner;
|
||||
std::unique_ptr<Image> _goodThumbnail;
|
||||
mutable std::unique_ptr<Image> _inlineThumbnail;
|
||||
mutable QPainterPath _pathThumbnail;
|
||||
std::unique_ptr<Image> _thumbnail;
|
||||
std::unique_ptr<Image> _sticker;
|
||||
QByteArray _bytes;
|
||||
|
||||
@@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -41,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "facades.h" // Ui::showPeerProfile
|
||||
#include "app.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -84,6 +84,75 @@ PeerId FakePeerIdForJustName(const QString &name) {
|
||||
: base::crc32(name.constData(), name.size() * sizeof(QChar)));
|
||||
}
|
||||
|
||||
bool UpdateBotCommands(
|
||||
std::vector<BotCommand> &commands,
|
||||
const MTPVector<MTPBotCommand> &data) {
|
||||
const auto &v = data.v;
|
||||
commands.reserve(v.size());
|
||||
auto result = false;
|
||||
auto index = 0;
|
||||
for (const auto &command : v) {
|
||||
command.match([&](const MTPDbotCommand &data) {
|
||||
const auto command = qs(data.vcommand());
|
||||
const auto description = qs(data.vdescription());
|
||||
if (commands.size() <= index) {
|
||||
commands.push_back({
|
||||
.command = command,
|
||||
.description = description,
|
||||
});
|
||||
result = true;
|
||||
} else {
|
||||
auto &entry = commands[index];
|
||||
if (entry.command != command
|
||||
|| entry.description != description) {
|
||||
entry.command = command;
|
||||
entry.description = description;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
++index;
|
||||
});
|
||||
}
|
||||
if (index < commands.size()) {
|
||||
result = true;
|
||||
}
|
||||
commands.resize(index);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool UpdateBotCommands(
|
||||
base::flat_map<UserId, std::vector<BotCommand>> &commands,
|
||||
const MTPVector<MTPBotInfo> &data) {
|
||||
auto result = false;
|
||||
auto filled = base::flat_set<UserId>();
|
||||
filled.reserve(data.v.size());
|
||||
for (const auto &item : data.v) {
|
||||
item.match([&](const MTPDbotInfo &data) {
|
||||
const auto id = UserId(data.vuser_id().v);
|
||||
if (!filled.emplace(id).second) {
|
||||
LOG(("API Error: Two BotInfo for a single bot."));
|
||||
return;
|
||||
}
|
||||
if (data.vcommands().v.isEmpty()) {
|
||||
if (commands.remove(id)) {
|
||||
result = true;
|
||||
}
|
||||
} else if (UpdateBotCommands(commands[id], data.vcommands())) {
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto i = begin(commands); i != end(commands);) {
|
||||
if (filled.contains(i->first)) {
|
||||
++i;
|
||||
} else {
|
||||
i = commands.erase(i);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
PeerClickHandler::PeerClickHandler(not_null<PeerData*> peer)
|
||||
@@ -348,7 +417,7 @@ QPixmap PeerData::genUserpic(
|
||||
Painter p(&result);
|
||||
paintUserpic(p, view, 0, 0, size);
|
||||
}
|
||||
return App::pixmapFromImageInPlace(std::move(result));
|
||||
return Ui::PixmapFromImage(std::move(result));
|
||||
}
|
||||
|
||||
QPixmap PeerData::genUserpicRounded(
|
||||
@@ -364,7 +433,7 @@ QPixmap PeerData::genUserpicRounded(
|
||||
Painter p(&result);
|
||||
paintUserpicRounded(p, view, 0, 0, size);
|
||||
}
|
||||
return App::pixmapFromImageInPlace(std::move(result));
|
||||
return Ui::PixmapFromImage(std::move(result));
|
||||
}
|
||||
|
||||
Data::FileOrigin PeerData::userpicOrigin() const {
|
||||
|
||||
@@ -22,6 +22,11 @@ using ChatRestriction = MTPDchatBannedRights::Flag;
|
||||
using ChatAdminRights = MTPDchatAdminRights::Flags;
|
||||
using ChatRestrictions = MTPDchatBannedRights::Flags;
|
||||
|
||||
struct BotCommand {
|
||||
QString command;
|
||||
QString description;
|
||||
};
|
||||
|
||||
namespace Ui {
|
||||
class EmptyUserpic;
|
||||
} // namespace Ui
|
||||
@@ -100,6 +105,13 @@ struct UnavailableReason {
|
||||
[[nodiscard]] TimeId ChatBannedRightsUntilDate(
|
||||
const MTPChatBannedRights &rights);
|
||||
|
||||
bool UpdateBotCommands(
|
||||
std::vector<BotCommand> &commands,
|
||||
const MTPVector<MTPBotCommand> &data);
|
||||
bool UpdateBotCommands(
|
||||
base::flat_map<UserId, std::vector<BotCommand>> &commands,
|
||||
const MTPVector<MTPBotInfo> &data);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
class PeerClickHandler : public ClickHandler {
|
||||
|
||||
@@ -134,18 +134,25 @@ std::vector<UnavailableReason> ExtractUnavailableReasons(
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray FindInlineThumbnail(
|
||||
[[nodiscard]] InlineImageLocation FindInlineThumbnail(
|
||||
const QVector<MTPPhotoSize> &sizes) {
|
||||
const auto i = ranges::find(
|
||||
sizes,
|
||||
mtpc_photoStrippedSize,
|
||||
&MTPPhotoSize::type);
|
||||
const auto j = ranges::find(
|
||||
sizes,
|
||||
mtpc_photoPathSize,
|
||||
&MTPPhotoSize::type);
|
||||
return (i != sizes.end())
|
||||
? i->c_photoStrippedSize().vbytes().v
|
||||
: QByteArray();
|
||||
? InlineImageLocation{ i->c_photoStrippedSize().vbytes().v, false }
|
||||
: (j != sizes.end())
|
||||
? InlineImageLocation{ j->c_photoPathSize().vbytes().v, true }
|
||||
: InlineImageLocation();
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray FindDocumentInlineThumbnail(const MTPDdocument &data) {
|
||||
[[nodiscard]] InlineImageLocation FindDocumentInlineThumbnail(
|
||||
const MTPDdocument &data) {
|
||||
return FindInlineThumbnail(data.vthumbs().value_or_empty());
|
||||
}
|
||||
|
||||
@@ -193,7 +200,8 @@ std::vector<UnavailableReason> ExtractUnavailableReasons(
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray FindPhotoInlineThumbnail(const MTPDphoto &data) {
|
||||
return FindInlineThumbnail(data.vsizes().v);
|
||||
const auto thumbnail = FindInlineThumbnail(data.vsizes().v);
|
||||
return !thumbnail.isPath ? thumbnail.bytes : QByteArray();
|
||||
}
|
||||
|
||||
[[nodiscard]] int VideoStartTime(const MTPDvideoSize &data) {
|
||||
@@ -1080,11 +1088,11 @@ rpl::producer<not_null<UserData*>> Session::userIsBotChanges() const {
|
||||
return _userIsBotChanges.events();
|
||||
}
|
||||
|
||||
void Session::botCommandsChanged(not_null<UserData*> user) {
|
||||
_botCommandsChanges.fire_copy(user);
|
||||
void Session::botCommandsChanged(not_null<PeerData*> peer) {
|
||||
_botCommandsChanges.fire_copy(peer);
|
||||
}
|
||||
|
||||
rpl::producer<not_null<UserData*>> Session::botCommandsChanges() const {
|
||||
rpl::producer<not_null<PeerData*>> Session::botCommandsChanges() const {
|
||||
return _botCommandsChanges.events();
|
||||
}
|
||||
|
||||
@@ -2656,7 +2664,7 @@ not_null<DocumentData*> Session::processDocument(
|
||||
data.vdate().v,
|
||||
data.vattributes().v,
|
||||
qs(data.vmime_type()),
|
||||
QByteArray(),
|
||||
InlineImageLocation(),
|
||||
thumbnail,
|
||||
ImageWithLocation(),
|
||||
data.vdc_id().v,
|
||||
@@ -2673,7 +2681,7 @@ not_null<DocumentData*> Session::document(
|
||||
TimeId date,
|
||||
const QVector<MTPDocumentAttribute> &attributes,
|
||||
const QString &mime,
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const InlineImageLocation &inlineThumbnail,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &videoThumbnail,
|
||||
int32 dc,
|
||||
@@ -2686,7 +2694,7 @@ not_null<DocumentData*> Session::document(
|
||||
date,
|
||||
attributes,
|
||||
mime,
|
||||
inlineThumbnailBytes,
|
||||
inlineThumbnail,
|
||||
thumbnail,
|
||||
videoThumbnail,
|
||||
dc,
|
||||
@@ -2754,7 +2762,7 @@ DocumentData *Session::documentFromWeb(
|
||||
base::unixtime::now(),
|
||||
data.vattributes().v,
|
||||
data.vmime_type().v,
|
||||
QByteArray(),
|
||||
InlineImageLocation(),
|
||||
ImageWithLocation{ .location = thumbnailLocation },
|
||||
ImageWithLocation{ .location = videoThumbnailLocation },
|
||||
session().mainDcId(),
|
||||
@@ -2776,7 +2784,7 @@ DocumentData *Session::documentFromWeb(
|
||||
base::unixtime::now(),
|
||||
data.vattributes().v,
|
||||
data.vmime_type().v,
|
||||
QByteArray(),
|
||||
InlineImageLocation(),
|
||||
ImageWithLocation{ .location = thumbnailLocation },
|
||||
ImageWithLocation{ .location = videoThumbnailLocation },
|
||||
session().mainDcId(),
|
||||
@@ -2796,7 +2804,7 @@ void Session::documentApplyFields(
|
||||
void Session::documentApplyFields(
|
||||
not_null<DocumentData*> document,
|
||||
const MTPDdocument &data) {
|
||||
const auto inlineThumbnailBytes = FindDocumentInlineThumbnail(data);
|
||||
const auto inlineThumbnail = FindDocumentInlineThumbnail(data);
|
||||
const auto thumbnailSize = FindDocumentThumbnail(data);
|
||||
const auto videoThumbnailSize = FindDocumentVideoThumbnail(data);
|
||||
const auto prepared = Images::FromPhotoSize(
|
||||
@@ -2813,7 +2821,7 @@ void Session::documentApplyFields(
|
||||
data.vdate().v,
|
||||
data.vattributes().v,
|
||||
qs(data.vmime_type()),
|
||||
inlineThumbnailBytes,
|
||||
inlineThumbnail,
|
||||
prepared,
|
||||
videoThumbnail,
|
||||
data.vdc_id().v,
|
||||
@@ -2827,7 +2835,7 @@ void Session::documentApplyFields(
|
||||
TimeId date,
|
||||
const QVector<MTPDocumentAttribute> &attributes,
|
||||
const QString &mime,
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const InlineImageLocation &inlineThumbnail,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &videoThumbnail,
|
||||
int32 dc,
|
||||
@@ -2838,7 +2846,7 @@ void Session::documentApplyFields(
|
||||
document->date = date;
|
||||
document->setMimeString(mime);
|
||||
document->updateThumbnails(
|
||||
inlineThumbnailBytes,
|
||||
inlineThumbnail,
|
||||
thumbnail,
|
||||
videoThumbnail);
|
||||
document->size = size;
|
||||
|
||||
@@ -215,8 +215,8 @@ public:
|
||||
|
||||
void userIsBotChanged(not_null<UserData*> user);
|
||||
[[nodiscard]] rpl::producer<not_null<UserData*>> userIsBotChanges() const;
|
||||
void botCommandsChanged(not_null<UserData*> user);
|
||||
[[nodiscard]] rpl::producer<not_null<UserData*>> botCommandsChanges() const;
|
||||
void botCommandsChanged(not_null<PeerData*> peer);
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> botCommandsChanges() const;
|
||||
|
||||
struct ItemVisibilityQuery {
|
||||
not_null<HistoryItem*> item;
|
||||
@@ -476,7 +476,7 @@ public:
|
||||
TimeId date,
|
||||
const QVector<MTPDocumentAttribute> &attributes,
|
||||
const QString &mime,
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const InlineImageLocation &inlineThumbnail,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &videoThumbnail,
|
||||
int32 dc,
|
||||
@@ -743,7 +743,7 @@ private:
|
||||
TimeId date,
|
||||
const QVector<MTPDocumentAttribute> &attributes,
|
||||
const QString &mime,
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const InlineImageLocation &inlineThumbnail,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &videoThumbnail,
|
||||
int32 dc,
|
||||
@@ -831,7 +831,7 @@ private:
|
||||
rpl::event_stream<Data::Folder*> _chatsListLoadedEvents;
|
||||
rpl::event_stream<Data::Folder*> _chatsListChanged;
|
||||
rpl::event_stream<not_null<UserData*>> _userIsBotChanges;
|
||||
rpl::event_stream<not_null<UserData*>> _botCommandsChanges;
|
||||
rpl::event_stream<not_null<PeerData*>> _botCommandsChanges;
|
||||
base::Observable<ItemVisibilityQuery> _queryItemVisibility;
|
||||
rpl::event_stream<IdChange> _itemIdChanges;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges;
|
||||
|
||||
@@ -16,8 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_sparse_ids.h"
|
||||
#include "data/data_session.h"
|
||||
#include "info/info_memento.h"
|
||||
@@ -30,6 +32,51 @@ namespace {
|
||||
|
||||
using Type = Storage::SharedMediaType;
|
||||
|
||||
bool IsItemGoodForType(const not_null<HistoryItem*> item, Type type) {
|
||||
const auto media = item->media();
|
||||
if (!media || media->webpage()) {
|
||||
return false;
|
||||
}
|
||||
const auto photo = media->photo();
|
||||
const auto photoType = (type == Type::Photo);
|
||||
const auto photoVideoType = (type == Type::PhotoVideo);
|
||||
if ((photoType || photoVideoType) && photo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto document = media->document();
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
const auto voiceType = (type == Type::VoiceFile);
|
||||
const auto voiceDoc = document->isVoiceMessage();
|
||||
|
||||
const auto roundType = (type == Type::RoundFile);
|
||||
const auto roundDoc = document->isVideoMessage();
|
||||
|
||||
const auto audioType = (type == Type::MusicFile);
|
||||
const auto audioDoc = document->isAudioFile();
|
||||
|
||||
const auto gifType = (type == Type::GIF);
|
||||
const auto gifDoc = document->isGifv();
|
||||
|
||||
const auto videoType = (type == Type::Video);
|
||||
const auto videoDoc = document->isVideoFile();
|
||||
|
||||
const auto voiceRoundType = (type == Type::RoundVoiceFile);
|
||||
const auto fileType = (type == Type::File);
|
||||
|
||||
return (audioType && audioDoc)
|
||||
|| (voiceType && voiceDoc)
|
||||
|| (roundType && roundDoc)
|
||||
|| (voiceRoundType && (roundDoc || voiceDoc))
|
||||
|| (gifType && gifDoc)
|
||||
|| ((videoType || photoVideoType) && videoDoc)
|
||||
|| (fileType && (document->isTheme()
|
||||
|| document->isImage()
|
||||
|| !document->canBeStreamed()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<Storage::SharedMediaType> SharedMediaOverviewType(
|
||||
@@ -153,6 +200,55 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
Expects(!IsServerMsgId(key.mergedKey.universalId));
|
||||
Expects((key.mergedKey.universalId != 0)
|
||||
|| (limitBefore == 0 && limitAfter == 0));
|
||||
|
||||
const auto history = session->data().history(key.mergedKey.peerId);
|
||||
|
||||
return rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
session->data().scheduledMessages().updates(history)
|
||||
) | rpl::map([=] {
|
||||
const auto list = session->data().scheduledMessages().list(history);
|
||||
|
||||
auto items = ranges::views::all(
|
||||
list.ids
|
||||
) | ranges::views::transform([=](const FullMsgId &fullId) {
|
||||
return session->data().message(fullId);
|
||||
}) | ranges::views::filter([=](HistoryItem *item) {
|
||||
return item
|
||||
? IsItemGoodForType(item, key.type)
|
||||
: false;
|
||||
}) | ranges::to_vector;
|
||||
|
||||
ranges::sort(items, ranges::less(), &HistoryItem::position);
|
||||
|
||||
auto finishMsgIds = ranges::views::all(
|
||||
items
|
||||
) | ranges::views::transform([=](not_null<HistoryItem*> item) {
|
||||
return item->fullId().msg;
|
||||
}) | ranges::to_vector;
|
||||
|
||||
const auto fullCount = finishMsgIds.size();
|
||||
|
||||
auto unsorted = SparseUnsortedIdsSlice(
|
||||
std::move(finishMsgIds),
|
||||
fullCount,
|
||||
list.skippedBefore,
|
||||
list.skippedAfter);
|
||||
return SparseIdsMergedSlice(
|
||||
key.mergedKey,
|
||||
std::move(unsorted));
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
@@ -374,12 +470,29 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer(
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return [=](auto consumer) {
|
||||
auto viewerKey = SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type);
|
||||
|
||||
if (std::get_if<not_null<PhotoData*>>(&key.universalId)) {
|
||||
return SharedMediaMergedViewer(
|
||||
session,
|
||||
SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type),
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
|
||||
consumer.put_next(SharedMediaWithLastSlice(
|
||||
session,
|
||||
key,
|
||||
std::move(update),
|
||||
std::nullopt));
|
||||
});
|
||||
}
|
||||
|
||||
if (key.scheduled) {
|
||||
return SharedScheduledMediaViewer(
|
||||
session,
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
|
||||
@@ -393,9 +506,7 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer(
|
||||
return rpl::combine(
|
||||
SharedMediaMergedViewer(
|
||||
session,
|
||||
SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type),
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter),
|
||||
SharedMediaMergedViewer(
|
||||
|
||||
@@ -50,6 +50,12 @@ struct SharedMediaMergedKey {
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
@@ -71,11 +77,13 @@ public:
|
||||
PeerId peerId,
|
||||
PeerId migratedPeerId,
|
||||
Type type,
|
||||
UniversalMsgId universalId)
|
||||
UniversalMsgId universalId,
|
||||
bool scheduled = false)
|
||||
: peerId(peerId)
|
||||
, migratedPeerId(migratedPeerId)
|
||||
, type(type)
|
||||
, universalId(universalId) {
|
||||
, universalId(universalId)
|
||||
, scheduled(scheduled) {
|
||||
Expects(v::is<MessageId>(universalId) || type == Type::ChatPhoto);
|
||||
}
|
||||
|
||||
@@ -93,6 +101,7 @@ public:
|
||||
PeerId migratedPeerId = 0;
|
||||
Type type = Type::kCount;
|
||||
UniversalMsgId universalId;
|
||||
bool scheduled = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -10,53 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <rpl/combine.h>
|
||||
#include "storage/storage_sparse_ids_list.h"
|
||||
|
||||
SparseIdsSlice::SparseIdsSlice(
|
||||
const base::flat_set<MsgId> &ids,
|
||||
MsgRange range,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter)
|
||||
: _ids(ids)
|
||||
, _range(range)
|
||||
, _fullCount(fullCount)
|
||||
, _skippedBefore(skippedBefore)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsSlice::indexOf(MsgId msgId) const {
|
||||
auto it = _ids.find(msgId);
|
||||
if (it != _ids.end()) {
|
||||
return (it - _ids.begin());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MsgId SparseIdsSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return *(_ids.begin() + index);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsSlice::distance(
|
||||
MsgId a,
|
||||
MsgId b) const {
|
||||
if (auto i = indexOf(a)) {
|
||||
if (auto j = indexOf(b)) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<MsgId> SparseIdsSlice::nearest(MsgId msgId) const {
|
||||
if (auto it = ranges::lower_bound(_ids, msgId); it != _ids.end()) {
|
||||
return *it;
|
||||
} else if (_ids.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return _ids.back();
|
||||
}
|
||||
|
||||
SparseIdsMergedSlice::SparseIdsMergedSlice(Key key)
|
||||
: SparseIdsMergedSlice(
|
||||
key,
|
||||
@@ -73,33 +26,48 @@ SparseIdsMergedSlice::SparseIdsMergedSlice(
|
||||
, _migrated(std::move(migrated)) {
|
||||
}
|
||||
|
||||
SparseIdsMergedSlice::SparseIdsMergedSlice(
|
||||
Key key,
|
||||
SparseUnsortedIdsSlice scheduled)
|
||||
: _key(key)
|
||||
, _scheduled(std::move(scheduled)) {
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::fullCount() const {
|
||||
return Add(
|
||||
_part.fullCount(),
|
||||
_migrated ? _migrated->fullCount() : 0);
|
||||
return _scheduled
|
||||
? _scheduled->fullCount()
|
||||
: Add(
|
||||
_part.fullCount(),
|
||||
_migrated ? _migrated->fullCount() : 0);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::skippedBefore() const {
|
||||
return Add(
|
||||
isolatedInMigrated() ? 0 : _part.skippedBefore(),
|
||||
_migrated
|
||||
? (isolatedInPart()
|
||||
? _migrated->fullCount()
|
||||
: _migrated->skippedBefore())
|
||||
: 0
|
||||
);
|
||||
return _scheduled
|
||||
? _scheduled->skippedBefore()
|
||||
: Add(
|
||||
isolatedInMigrated() ? 0 : _part.skippedBefore(),
|
||||
_migrated
|
||||
? (isolatedInPart()
|
||||
? _migrated->fullCount()
|
||||
: _migrated->skippedBefore())
|
||||
: 0
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::skippedAfter() const {
|
||||
return Add(
|
||||
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
|
||||
isolatedInPart() ? 0 : _migrated->skippedAfter()
|
||||
);
|
||||
return _scheduled
|
||||
? _scheduled->skippedAfter()
|
||||
: Add(
|
||||
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
|
||||
isolatedInPart() ? 0 : _migrated->skippedAfter()
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::indexOf(
|
||||
FullMsgId fullId) const {
|
||||
return isFromPart(fullId)
|
||||
return _scheduled
|
||||
? _scheduled->indexOf(fullId.msg)
|
||||
: isFromPart(fullId)
|
||||
? (_part.indexOf(fullId.msg) | func::add(migratedSize()))
|
||||
: isolatedInPart()
|
||||
? std::nullopt
|
||||
@@ -109,14 +77,20 @@ std::optional<int> SparseIdsMergedSlice::indexOf(
|
||||
}
|
||||
|
||||
int SparseIdsMergedSlice::size() const {
|
||||
return (isolatedInPart() ? 0 : migratedSize())
|
||||
+ (isolatedInMigrated() ? 0 : _part.size());
|
||||
return _scheduled
|
||||
? _scheduled->size()
|
||||
: (isolatedInPart() ? 0 : migratedSize())
|
||||
+ (isolatedInMigrated() ? 0 : _part.size());
|
||||
}
|
||||
|
||||
FullMsgId SparseIdsMergedSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
if (auto size = migratedSize()) {
|
||||
if (_scheduled) {
|
||||
return ComputeId(_key.peerId, (*_scheduled)[index]);
|
||||
}
|
||||
|
||||
if (const auto size = migratedSize()) {
|
||||
if (index < size) {
|
||||
return ComputeId(_key.migratedPeerId, (*_migrated)[index]);
|
||||
}
|
||||
@@ -128,8 +102,8 @@ FullMsgId SparseIdsMergedSlice::operator[](int index) const {
|
||||
std::optional<int> SparseIdsMergedSlice::distance(
|
||||
const Key &a,
|
||||
const Key &b) const {
|
||||
if (auto i = indexOf(ComputeId(a))) {
|
||||
if (auto j = indexOf(ComputeId(b))) {
|
||||
if (const auto i = indexOf(ComputeId(a))) {
|
||||
if (const auto j = indexOf(ComputeId(b))) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
@@ -138,10 +112,15 @@ std::optional<int> SparseIdsMergedSlice::distance(
|
||||
|
||||
auto SparseIdsMergedSlice::nearest(
|
||||
UniversalMsgId id) const -> std::optional<FullMsgId> {
|
||||
auto convertFromPartNearest = [&](MsgId result) {
|
||||
if (_scheduled) {
|
||||
if (const auto nearestId = _scheduled->nearest(id)) {
|
||||
return ComputeId(_key.peerId, *nearestId);
|
||||
}
|
||||
}
|
||||
const auto convertFromPartNearest = [&](MsgId result) {
|
||||
return ComputeId(_key.peerId, result);
|
||||
};
|
||||
auto convertFromMigratedNearest = [&](MsgId result) {
|
||||
const auto convertFromMigratedNearest = [&](MsgId result) {
|
||||
return ComputeId(_key.migratedPeerId, result);
|
||||
};
|
||||
if (IsServerMsgId(id)) {
|
||||
@@ -245,7 +224,6 @@ bool SparseIdsSliceBuilder::removeOne(MsgId messageId) {
|
||||
|
||||
bool SparseIdsSliceBuilder::removeAll() {
|
||||
_ids = {};
|
||||
_range = { 0, ServerMaxMsgId };
|
||||
_fullCount = 0;
|
||||
_skippedBefore = 0;
|
||||
_skippedAfter = 0;
|
||||
@@ -254,9 +232,6 @@ bool SparseIdsSliceBuilder::removeAll() {
|
||||
|
||||
bool SparseIdsSliceBuilder::invalidateBottom() {
|
||||
_fullCount = _skippedAfter = std::nullopt;
|
||||
if (_range.till == ServerMaxMsgId) {
|
||||
_range.till = _ids.empty() ? _range.from : _ids.back();
|
||||
}
|
||||
checkInsufficient();
|
||||
return true;
|
||||
}
|
||||
@@ -389,7 +364,6 @@ void SparseIdsSliceBuilder::requestMessagesCount() {
|
||||
SparseIdsSlice SparseIdsSliceBuilder::snapshot() const {
|
||||
return SparseIdsSlice(
|
||||
_ids,
|
||||
_range,
|
||||
_fullCount,
|
||||
_skippedBefore,
|
||||
_skippedAfter);
|
||||
@@ -440,4 +414,4 @@ rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
std::move(migrated)));
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_abstract_sparse_ids.h"
|
||||
#include "data/data_messages.h"
|
||||
|
||||
namespace Storage {
|
||||
@@ -14,36 +15,15 @@ struct SparseIdsListResult;
|
||||
struct SparseIdsSliceUpdate;
|
||||
} // namespace Storage
|
||||
|
||||
class SparseIdsSlice {
|
||||
class SparseIdsSlice final : public AbstractSparseIds<base::flat_set<MsgId>> {
|
||||
public:
|
||||
using Key = MsgId;
|
||||
|
||||
SparseIdsSlice() = default;
|
||||
SparseIdsSlice(
|
||||
const base::flat_set<MsgId> &ids,
|
||||
MsgRange range,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter);
|
||||
|
||||
std::optional<int> fullCount() const { return _fullCount; }
|
||||
std::optional<int> skippedBefore() const { return _skippedBefore; }
|
||||
std::optional<int> skippedAfter() const { return _skippedAfter; }
|
||||
std::optional<int> indexOf(MsgId msgId) const;
|
||||
int size() const { return _ids.size(); }
|
||||
MsgId operator[](int index) const;
|
||||
std::optional<int> distance(MsgId a, MsgId b) const;
|
||||
std::optional<MsgId> nearest(MsgId msgId) const;
|
||||
|
||||
private:
|
||||
base::flat_set<MsgId> _ids;
|
||||
MsgRange _range;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
using AbstractSparseIds<base::flat_set<MsgId>>::AbstractSparseIds;
|
||||
|
||||
};
|
||||
|
||||
using SparseUnsortedIdsSlice = AbstractSparseIds<std::vector<MsgId>>;
|
||||
|
||||
class SparseIdsMergedSlice {
|
||||
public:
|
||||
using UniversalMsgId = MsgId;
|
||||
@@ -51,9 +31,11 @@ public:
|
||||
Key(
|
||||
PeerId peerId,
|
||||
PeerId migratedPeerId,
|
||||
UniversalMsgId universalId)
|
||||
UniversalMsgId universalId,
|
||||
bool scheduled = false)
|
||||
: peerId(peerId)
|
||||
, migratedPeerId(migratedPeerId)
|
||||
, scheduled(scheduled)
|
||||
, migratedPeerId(scheduled ? 0 : migratedPeerId)
|
||||
, universalId(universalId) {
|
||||
}
|
||||
|
||||
@@ -67,6 +49,7 @@ public:
|
||||
}
|
||||
|
||||
PeerId peerId = 0;
|
||||
bool scheduled = false;
|
||||
PeerId migratedPeerId = 0;
|
||||
UniversalMsgId universalId = 0;
|
||||
|
||||
@@ -77,6 +60,9 @@ public:
|
||||
Key key,
|
||||
SparseIdsSlice part,
|
||||
std::optional<SparseIdsSlice> migrated);
|
||||
SparseIdsMergedSlice(
|
||||
Key key,
|
||||
SparseUnsortedIdsSlice scheduled);
|
||||
|
||||
std::optional<int> fullCount() const;
|
||||
std::optional<int> skippedBefore() const;
|
||||
@@ -155,6 +141,7 @@ private:
|
||||
Key _key;
|
||||
SparseIdsSlice _part;
|
||||
std::optional<SparseIdsSlice> _migrated;
|
||||
std::optional<SparseUnsortedIdsSlice> _scheduled;
|
||||
|
||||
};
|
||||
|
||||
@@ -205,7 +192,6 @@ private:
|
||||
|
||||
Key _key;
|
||||
base::flat_set<MsgId> _ids;
|
||||
MsgRange _range;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
|
||||
@@ -95,7 +95,7 @@ class PeerData;
|
||||
class UserData;
|
||||
class ChatData;
|
||||
class ChannelData;
|
||||
class BotCommand;
|
||||
struct BotCommand;
|
||||
struct BotInfo;
|
||||
|
||||
namespace Data {
|
||||
|
||||
@@ -24,32 +24,6 @@ using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
|
||||
} // namespace
|
||||
|
||||
BotCommand::BotCommand(
|
||||
const QString &command,
|
||||
const QString &description)
|
||||
: command(command)
|
||||
, _description(description) {
|
||||
}
|
||||
|
||||
bool BotCommand::setDescription(const QString &description) {
|
||||
if (_description != description) {
|
||||
_description = description;
|
||||
_descriptionText = Ui::Text::String();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Ui::Text::String &BotCommand::descriptionText() const {
|
||||
if (_descriptionText.isEmpty() && !_description.isEmpty()) {
|
||||
_descriptionText.setText(
|
||||
st::defaultTextStyle,
|
||||
_description,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
return _descriptionText;
|
||||
}
|
||||
|
||||
UserData::UserData(not_null<Data::Session*> owner, PeerId id)
|
||||
: PeerData(owner, id) {
|
||||
}
|
||||
@@ -132,7 +106,7 @@ void UserData::setBotInfoVersion(int version) {
|
||||
botInfo->version = version;
|
||||
owner().userIsBotChanged(this);
|
||||
} else if (botInfo->version < version) {
|
||||
if (!botInfo->commands.isEmpty()) {
|
||||
if (!botInfo->commands.empty()) {
|
||||
botInfo->commands.clear();
|
||||
owner().botCommandsChanged(this);
|
||||
}
|
||||
@@ -155,34 +129,9 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
|
||||
botInfo->description = desc;
|
||||
botInfo->text = Ui::Text::String(st::msgMinWidth);
|
||||
}
|
||||
|
||||
auto &v = d.vcommands().v;
|
||||
botInfo->commands.reserve(v.size());
|
||||
auto changedCommands = false;
|
||||
int32 j = 0;
|
||||
for (const auto &command : v) {
|
||||
command.match([&](const MTPDbotCommand &data) {
|
||||
const auto cmd = qs(data.vcommand());
|
||||
const auto desc = qs(data.vdescription());
|
||||
if (botInfo->commands.size() <= j) {
|
||||
botInfo->commands.push_back(BotCommand(cmd, desc));
|
||||
changedCommands = true;
|
||||
} else {
|
||||
if (botInfo->commands[j].command != cmd) {
|
||||
botInfo->commands[j].command = cmd;
|
||||
changedCommands = true;
|
||||
}
|
||||
if (botInfo->commands[j].setDescription(desc)) {
|
||||
changedCommands = true;
|
||||
}
|
||||
}
|
||||
++j;
|
||||
});
|
||||
}
|
||||
while (j < botInfo->commands.size()) {
|
||||
botInfo->commands.pop_back();
|
||||
changedCommands = true;
|
||||
}
|
||||
const auto changedCommands = Data::UpdateBotCommands(
|
||||
botInfo->commands,
|
||||
d.vcommands());
|
||||
|
||||
botInfo->inited = true;
|
||||
|
||||
|
||||
@@ -10,28 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
|
||||
class BotCommand {
|
||||
public:
|
||||
BotCommand(const QString &command, const QString &description);
|
||||
|
||||
bool setDescription(const QString &description);
|
||||
const Ui::Text::String &descriptionText() const;
|
||||
|
||||
QString command;
|
||||
|
||||
private:
|
||||
QString _description;
|
||||
mutable Ui::Text::String _descriptionText;
|
||||
|
||||
};
|
||||
|
||||
struct BotInfo {
|
||||
bool inited = false;
|
||||
bool readsAllHistory = false;
|
||||
bool cantJoinGroups = false;
|
||||
int version = 0;
|
||||
QString description, inlinePlaceholder;
|
||||
QList<BotCommand> commands;
|
||||
std::vector<BotCommand> commands;
|
||||
Ui::Text::String text = { int(st::msgMinWidth) }; // description
|
||||
|
||||
QString startToken, startGroupToken, shareGameShortName;
|
||||
|
||||
@@ -64,39 +64,23 @@ UserPhotosSlice::UserPhotosSlice(
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter)
|
||||
: _key(key)
|
||||
, _ids(std::move(ids))
|
||||
, _fullCount(fullCount)
|
||||
, _skippedBefore(skippedBefore)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
: AbstractSparseIds<std::deque<PhotoId>>(
|
||||
ids,
|
||||
fullCount,
|
||||
skippedBefore,
|
||||
skippedAfter)
|
||||
, _key(key) {
|
||||
}
|
||||
|
||||
void UserPhotosSlice::reverse() {
|
||||
ranges::reverse(_ids);
|
||||
std::swap(_skippedBefore, _skippedAfter);
|
||||
}
|
||||
|
||||
std::optional<int> UserPhotosSlice::indexOf(PhotoId photoId) const {
|
||||
auto it = ranges::find(_ids, photoId);
|
||||
if (it != _ids.end()) {
|
||||
return (it - _ids.begin());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
PhotoId UserPhotosSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return *(_ids.begin() + index);
|
||||
}
|
||||
|
||||
std::optional<int> UserPhotosSlice::distance(const Key &a, const Key &b) const {
|
||||
std::optional<int> UserPhotosSlice::distance(
|
||||
const Key &a,
|
||||
const Key &b) const {
|
||||
if (a.userId != _key.userId
|
||||
|| b.userId != _key.userId) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (auto i = indexOf(a.photoId)) {
|
||||
if (auto j = indexOf(b.photoId)) {
|
||||
if (const auto i = indexOf(a.photoId)) {
|
||||
if (const auto j = indexOf(b.photoId)) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
@@ -248,4 +232,4 @@ rpl::producer<UserPhotosSlice> UserPhotosReversedViewer(
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_abstract_sparse_ids.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
@@ -14,7 +15,7 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
class UserPhotosSlice {
|
||||
class UserPhotosSlice final : public AbstractSparseIds<std::deque<PhotoId>> {
|
||||
public:
|
||||
using Key = Storage::UserPhotosKey;
|
||||
|
||||
@@ -26,24 +27,11 @@ public:
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter);
|
||||
|
||||
void reverse();
|
||||
|
||||
const Key &key() const { return _key; }
|
||||
|
||||
std::optional<int> fullCount() const { return _fullCount; }
|
||||
std::optional<int> skippedBefore() const { return _skippedBefore; }
|
||||
std::optional<int> skippedAfter() const { return _skippedAfter; }
|
||||
std::optional<int> indexOf(PhotoId msgId) const;
|
||||
int size() const { return _ids.size(); }
|
||||
PhotoId operator[](int index) const;
|
||||
std::optional<int> distance(const Key &a, const Key &b) const;
|
||||
const Key &key() const { return _key; }
|
||||
|
||||
private:
|
||||
Key _key;
|
||||
std::deque<PhotoId> _ids;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
|
||||
friend class UserPhotosSliceBuilder;
|
||||
|
||||
|
||||
@@ -86,11 +86,11 @@ rpl::producer<> Stickers::updated() const {
|
||||
return _updated.events();
|
||||
}
|
||||
|
||||
void Stickers::notifyRecentUpdated() {
|
||||
_recentUpdated.fire({});
|
||||
void Stickers::notifyRecentUpdated(Recent recent) {
|
||||
_recentUpdated.fire(std::move(recent));
|
||||
}
|
||||
|
||||
rpl::producer<> Stickers::recentUpdated() const {
|
||||
rpl::producer<Stickers::Recent> Stickers::recentUpdated() const {
|
||||
return _recentUpdated.events();
|
||||
}
|
||||
|
||||
@@ -102,6 +102,14 @@ rpl::producer<> Stickers::savedGifsUpdated() const {
|
||||
return _savedGifsUpdated.events();
|
||||
}
|
||||
|
||||
void Stickers::notifyStickerSetInstalled(uint64 setId) {
|
||||
_stickerSetInstalled.fire(std::move(setId));
|
||||
}
|
||||
|
||||
rpl::producer<uint64> Stickers::stickerSetInstalled() const {
|
||||
return _stickerSetInstalled.events();
|
||||
}
|
||||
|
||||
void Stickers::incrementSticker(not_null<DocumentData*> document) {
|
||||
if (!document->sticker()
|
||||
|| document->sticker()->set.type() == mtpc_inputStickerSetEmpty) {
|
||||
@@ -254,10 +262,12 @@ void Stickers::checkSavedGif(not_null<HistoryItem*> item) {
|
||||
void Stickers::applyArchivedResult(
|
||||
const MTPDmessages_stickerSetInstallResultArchive &d) {
|
||||
auto &v = d.vsets().v;
|
||||
auto &order = setsOrderRef();
|
||||
StickersSetsOrder archived;
|
||||
archived.reserve(v.size());
|
||||
QMap<uint64, uint64> setsToRequest;
|
||||
|
||||
auto masksCount = 0;
|
||||
auto stickersCount = 0;
|
||||
for (const auto &stickerSet : v) {
|
||||
const MTPDstickerSet *setData = nullptr;
|
||||
switch (stickerSet.type()) {
|
||||
@@ -279,7 +289,10 @@ void Stickers::applyArchivedResult(
|
||||
if (set->stickers.isEmpty()) {
|
||||
setsToRequest.insert(set->id, set->access);
|
||||
}
|
||||
auto index = order.indexOf(set->id);
|
||||
const auto masks = !!(set->flags & MTPDstickerSet::Flag::f_masks);
|
||||
(masks ? masksCount : stickersCount)++;
|
||||
auto &order = masks ? maskSetsOrderRef() : setsOrderRef();
|
||||
const auto index = order.indexOf(set->id);
|
||||
if (index >= 0) {
|
||||
order.removeAt(index);
|
||||
}
|
||||
@@ -292,8 +305,14 @@ void Stickers::applyArchivedResult(
|
||||
}
|
||||
session().api().requestStickerSets();
|
||||
}
|
||||
session().local().writeInstalledStickers();
|
||||
session().local().writeArchivedStickers();
|
||||
if (stickersCount) {
|
||||
session().local().writeInstalledStickers();
|
||||
session().local().writeArchivedStickers();
|
||||
}
|
||||
if (masksCount) {
|
||||
session().local().writeInstalledMasks();
|
||||
session().local().writeArchivedMasks();
|
||||
}
|
||||
|
||||
Ui::Toast::Show(Ui::Toast::Config{
|
||||
.text = { tr::lng_stickers_packs_archived(tr::now) },
|
||||
@@ -351,12 +370,14 @@ void Stickers::installLocally(uint64 setId) {
|
||||
|
||||
const auto set = it->second.get();
|
||||
auto flags = set->flags;
|
||||
set->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread);
|
||||
set->flags &= ~(MTPDstickerSet::Flag::f_archived
|
||||
| MTPDstickerSet_ClientFlag::f_unread);
|
||||
set->flags |= MTPDstickerSet::Flag::f_installed_date;
|
||||
set->installDate = base::unixtime::now();
|
||||
auto changedFlags = flags ^ set->flags;
|
||||
|
||||
auto &order = setsOrderRef();
|
||||
const auto masks = !!(flags & MTPDstickerSet::Flag::f_masks);
|
||||
auto &order = masks ? maskSetsOrderRef() : setsOrderRef();
|
||||
int insertAtIndex = 0, currentIndex = order.indexOf(setId);
|
||||
if (currentIndex != insertAtIndex) {
|
||||
if (currentIndex > 0) {
|
||||
@@ -381,10 +402,17 @@ void Stickers::installLocally(uint64 setId) {
|
||||
session().local().writeFeaturedStickers();
|
||||
}
|
||||
if (changedFlags & MTPDstickerSet::Flag::f_archived) {
|
||||
auto index = archivedSetsOrderRef().indexOf(setId);
|
||||
auto &archivedOrder = masks
|
||||
? archivedMaskSetsOrderRef()
|
||||
: archivedSetsOrderRef();
|
||||
const auto index = archivedOrder.indexOf(setId);
|
||||
if (index >= 0) {
|
||||
archivedSetsOrderRef().removeAt(index);
|
||||
session().local().writeArchivedStickers();
|
||||
archivedOrder.removeAt(index);
|
||||
if (masks) {
|
||||
session().local().writeArchivedMasks();
|
||||
} else {
|
||||
session().local().writeArchivedStickers();
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyUpdated();
|
||||
@@ -512,7 +540,7 @@ void Stickers::setIsFaved(
|
||||
}
|
||||
session().local().writeFavedStickers();
|
||||
notifyUpdated();
|
||||
session().api().stickerSetInstalled(FavedSetId);
|
||||
notifyStickerSetInstalled(FavedSetId);
|
||||
}
|
||||
|
||||
void Stickers::requestSetToPushFaved(not_null<DocumentData*> document) {
|
||||
@@ -573,26 +601,41 @@ void Stickers::setFaved(not_null<DocumentData*> document, bool faved) {
|
||||
}
|
||||
|
||||
void Stickers::setsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
|
||||
auto &setsOrder = setsOrderRef();
|
||||
const auto masksReceived = ranges::all_of(
|
||||
data,
|
||||
[](const MTPStickerSet &set) {
|
||||
return set.c_stickerSet().is_masks();
|
||||
});
|
||||
auto &setsOrder = masksReceived
|
||||
? maskSetsOrderRef()
|
||||
: setsOrderRef();
|
||||
setsOrder.clear();
|
||||
|
||||
using Flag = MTPDstickerSet::Flag;
|
||||
using ClientFlag = MTPDstickerSet_ClientFlag;
|
||||
|
||||
auto &sets = setsRef();
|
||||
QMap<uint64, uint64> setsToRequest;
|
||||
for (auto &[id, set] : sets) {
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
const auto archived = !!(set->flags & Flag::f_archived);
|
||||
const auto masks = !!(set->flags & MTPDstickerSet::Flag::f_masks);
|
||||
if (!archived && (masksReceived == masks)) {
|
||||
// Mark for removing.
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
set->flags &= ~Flag::f_installed_date;
|
||||
set->installDate = 0;
|
||||
}
|
||||
}
|
||||
for (const auto &setData : data) {
|
||||
if (setData.type() == mtpc_stickerSet) {
|
||||
auto set = feedSet(setData.c_stickerSet());
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived) || (set->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
setsOrder.push_back(set->id);
|
||||
if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
setsToRequest.insert(set->id, set->access);
|
||||
}
|
||||
if (setData.type() != mtpc_stickerSet) {
|
||||
continue;
|
||||
}
|
||||
const auto set = feedSet(setData.c_stickerSet());
|
||||
if (!(set->flags & Flag::f_archived)
|
||||
|| (set->flags & Flag::f_official)) {
|
||||
setsOrder.push_back(set->id);
|
||||
if (set->stickers.isEmpty()
|
||||
|| (set->flags & ClientFlag::f_not_loaded)) {
|
||||
setsToRequest.insert(set->id, set->access);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -600,10 +643,10 @@ void Stickers::setsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
|
||||
auto &recent = getRecentPack();
|
||||
for (auto it = sets.begin(); it != sets.end();) {
|
||||
const auto set = it->second.get();
|
||||
bool installed = (set->flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
bool featured = (set->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
bool special = (set->flags & MTPDstickerSet_ClientFlag::f_special);
|
||||
bool archived = (set->flags & MTPDstickerSet::Flag::f_archived);
|
||||
const auto installed = !!(set->flags & Flag::f_installed_date);
|
||||
const auto featured = !!(set->flags & ClientFlag::f_featured);
|
||||
const auto special = !!(set->flags & ClientFlag::f_special);
|
||||
const auto archived = !!(set->flags & Flag::f_archived);
|
||||
if (!installed) { // remove not mine sets from recent stickers
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (set->stickers.indexOf(i->first) >= 0) {
|
||||
@@ -629,8 +672,14 @@ void Stickers::setsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
|
||||
api.requestStickerSets();
|
||||
}
|
||||
|
||||
session().local().writeInstalledStickers();
|
||||
if (writeRecent) session().saveSettings();
|
||||
if (masksReceived) {
|
||||
session().local().writeInstalledMasks();
|
||||
} else {
|
||||
session().local().writeInstalledStickers();
|
||||
}
|
||||
if (writeRecent) {
|
||||
session().saveSettings();
|
||||
}
|
||||
|
||||
const auto counted = Api::CountStickersHash(&session());
|
||||
if (counted != hash) {
|
||||
@@ -705,7 +754,8 @@ void Stickers::specialSetReceived(
|
||||
auto dates = std::vector<TimeId>();
|
||||
auto dateIndex = 0;
|
||||
auto datesAvailable = (items.size() == usageDates.size())
|
||||
&& (setId == CloudRecentSetId);
|
||||
&& ((setId == CloudRecentSetId)
|
||||
|| (setId == CloudRecentAttachedSetId));
|
||||
|
||||
auto customIt = sets.find(CustomSetId);
|
||||
auto pack = StickersPack();
|
||||
@@ -768,6 +818,16 @@ void Stickers::specialSetReceived(
|
||||
}
|
||||
session().local().writeRecentStickers();
|
||||
} break;
|
||||
case CloudRecentAttachedSetId: {
|
||||
const auto counted = Api::CountRecentStickersHash(&session(), true);
|
||||
if (counted != hash) {
|
||||
LOG(("API Error: "
|
||||
"received recent attached stickers hash %1 "
|
||||
"while counted hash is %2"
|
||||
).arg(hash, counted));
|
||||
}
|
||||
session().local().writeRecentMasks();
|
||||
} break;
|
||||
case FavedSetId: {
|
||||
const auto counted = Api::CountFavedStickersHash(&session());
|
||||
if (counted != hash) {
|
||||
@@ -1193,13 +1253,17 @@ StickersSet *Stickers::feedSet(const MTPDstickerSet &data) {
|
||||
const auto set = it->second.get();
|
||||
auto changedFlags = (flags ^ set->flags);
|
||||
if (changedFlags & MTPDstickerSet::Flag::f_archived) {
|
||||
auto index = archivedSetsOrder().indexOf(set->id);
|
||||
const auto masks = !!(set->flags & MTPDstickerSet::Flag::f_masks);
|
||||
auto &archivedOrder = masks
|
||||
? archivedMaskSetsOrderRef()
|
||||
: archivedSetsOrderRef();
|
||||
const auto index = archivedOrder.indexOf(set->id);
|
||||
if (set->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
if (index < 0) {
|
||||
archivedSetsOrderRef().push_front(set->id);
|
||||
archivedOrder.push_front(set->id);
|
||||
}
|
||||
} else if (index >= 0) {
|
||||
archivedSetsOrderRef().removeAt(index);
|
||||
archivedOrder.removeAt(index);
|
||||
}
|
||||
}
|
||||
return it->second.get();
|
||||
@@ -1263,9 +1327,16 @@ StickersSet *Stickers::feedSetFull(const MTPmessages_StickerSet &data) {
|
||||
}
|
||||
}
|
||||
|
||||
const auto isMasks = !!(set->flags & MTPDstickerSet::Flag::f_masks);
|
||||
if (pack.isEmpty()) {
|
||||
int removeIndex = setsOrder().indexOf(set->id);
|
||||
if (removeIndex >= 0) setsOrderRef().removeAt(removeIndex);
|
||||
const auto removeIndex = (isMasks
|
||||
? maskSetsOrder()
|
||||
: setsOrder()).indexOf(set->id);
|
||||
if (removeIndex >= 0) {
|
||||
(isMasks
|
||||
? maskSetsOrderRef()
|
||||
: setsOrderRef()).removeAt(removeIndex);
|
||||
}
|
||||
sets.remove(set->id);
|
||||
set = nullptr;
|
||||
} else {
|
||||
@@ -1299,7 +1370,9 @@ StickersSet *Stickers::feedSetFull(const MTPmessages_StickerSet &data) {
|
||||
|
||||
if (set) {
|
||||
const auto isArchived = !!(set->flags & MTPDstickerSet::Flag::f_archived);
|
||||
if (set->flags & MTPDstickerSet::Flag::f_installed_date) {
|
||||
if (isMasks) {
|
||||
session().local().writeInstalledMasks();
|
||||
} else if (set->flags & MTPDstickerSet::Flag::f_installed_date) {
|
||||
if (!isArchived) {
|
||||
session().local().writeInstalledStickers();
|
||||
}
|
||||
@@ -1308,7 +1381,11 @@ StickersSet *Stickers::feedSetFull(const MTPmessages_StickerSet &data) {
|
||||
session().local().writeFeaturedStickers();
|
||||
}
|
||||
if (wasArchived != isArchived) {
|
||||
session().local().writeArchivedStickers();
|
||||
if (isMasks) {
|
||||
session().local().writeArchivedMasks();
|
||||
} else {
|
||||
session().local().writeArchivedStickers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1329,10 +1406,8 @@ void Stickers::newSetReceived(const MTPmessages_StickerSet &data) {
|
||||
LOG(("API Error: "
|
||||
"updateNewStickerSet with archived flag."));
|
||||
return;
|
||||
} else if (s.is_masks()) {
|
||||
return;
|
||||
}
|
||||
auto &order = setsOrderRef();
|
||||
auto &order = s.is_masks() ? maskSetsOrderRef() : setsOrderRef();
|
||||
int32 insertAtIndex = 0, currentIndex = order.indexOf(s.vid().v);
|
||||
if (currentIndex != insertAtIndex) {
|
||||
if (currentIndex > 0) {
|
||||
|
||||
@@ -41,6 +41,7 @@ public:
|
||||
|
||||
// For cloud-stored recent stickers.
|
||||
static constexpr auto CloudRecentSetId = 0xFFFFFFFFFFFFFFFCULL;
|
||||
static constexpr auto CloudRecentAttachedSetId = 0xFFFFFFFFFFFFFFF9ULL;
|
||||
|
||||
// For cloud-stored faved stickers.
|
||||
static constexpr auto FavedSetId = 0xFFFFFFFFFFFFFFFAULL;
|
||||
@@ -48,12 +49,19 @@ public:
|
||||
// For setting up megagroup sticker set.
|
||||
static constexpr auto MegagroupSetId = 0xFFFFFFFFFFFFFFEFULL;
|
||||
|
||||
enum Recent {
|
||||
Regular,
|
||||
Attached,
|
||||
};
|
||||
|
||||
void notifyUpdated();
|
||||
[[nodiscard]] rpl::producer<> updated() const;
|
||||
void notifyRecentUpdated();
|
||||
[[nodiscard]] rpl::producer<> recentUpdated() const;
|
||||
void notifyRecentUpdated(Recent recent = Recent::Regular);
|
||||
[[nodiscard]] rpl::producer<Recent> recentUpdated() const;
|
||||
void notifySavedGifsUpdated();
|
||||
[[nodiscard]] rpl::producer<> savedGifsUpdated() const;
|
||||
void notifyStickerSetInstalled(uint64 setId);
|
||||
[[nodiscard]] rpl::producer<uint64> stickerSetInstalled() const;
|
||||
|
||||
void incrementSticker(not_null<DocumentData*> document);
|
||||
|
||||
@@ -72,6 +80,21 @@ public:
|
||||
}
|
||||
_lastRecentUpdate = update;
|
||||
}
|
||||
bool masksUpdateNeeded(crl::time now) const {
|
||||
return updateNeeded(_lastMasksUpdate, now);
|
||||
}
|
||||
void setLastMasksUpdate(crl::time update) {
|
||||
_lastMasksUpdate = update;
|
||||
}
|
||||
bool recentAttachedUpdateNeeded(crl::time now) const {
|
||||
return updateNeeded(_lastRecentAttachedUpdate, now);
|
||||
}
|
||||
void setLastRecentAttachedUpdate(crl::time update) {
|
||||
if (update) {
|
||||
notifyRecentUpdated(Recent::Attached);
|
||||
}
|
||||
_lastRecentAttachedUpdate = update;
|
||||
}
|
||||
bool favedUpdateNeeded(crl::time now) const {
|
||||
return updateNeeded(_lastFavedUpdate, now);
|
||||
}
|
||||
@@ -111,6 +134,12 @@ public:
|
||||
StickersSetsOrder &setsOrderRef() {
|
||||
return _setsOrder;
|
||||
}
|
||||
const StickersSetsOrder &maskSetsOrder() const {
|
||||
return _maskSetsOrder;
|
||||
}
|
||||
StickersSetsOrder &maskSetsOrderRef() {
|
||||
return _maskSetsOrder;
|
||||
}
|
||||
const StickersSetsOrder &featuredSetsOrder() const {
|
||||
return _featuredSetsOrder;
|
||||
}
|
||||
@@ -123,6 +152,12 @@ public:
|
||||
StickersSetsOrder &archivedSetsOrderRef() {
|
||||
return _archivedSetsOrder;
|
||||
}
|
||||
const StickersSetsOrder &archivedMaskSetsOrder() const {
|
||||
return _archivedMaskSetsOrder;
|
||||
}
|
||||
StickersSetsOrder &archivedMaskSetsOrderRef() {
|
||||
return _archivedMaskSetsOrder;
|
||||
}
|
||||
const SavedGifs &savedGifs() const {
|
||||
return _savedGifs;
|
||||
}
|
||||
@@ -196,18 +231,23 @@ private:
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
rpl::event_stream<> _updated;
|
||||
rpl::event_stream<> _recentUpdated;
|
||||
rpl::event_stream<Recent> _recentUpdated;
|
||||
rpl::event_stream<> _savedGifsUpdated;
|
||||
rpl::event_stream<uint64> _stickerSetInstalled;
|
||||
crl::time _lastUpdate = 0;
|
||||
crl::time _lastRecentUpdate = 0;
|
||||
crl::time _lastFavedUpdate = 0;
|
||||
crl::time _lastFeaturedUpdate = 0;
|
||||
crl::time _lastSavedGifsUpdate = 0;
|
||||
crl::time _lastMasksUpdate = 0;
|
||||
crl::time _lastRecentAttachedUpdate = 0;
|
||||
rpl::variable<int> _featuredSetsUnreadCount = 0;
|
||||
StickersSets _sets;
|
||||
StickersSetsOrder _setsOrder;
|
||||
StickersSetsOrder _maskSetsOrder;
|
||||
StickersSetsOrder _featuredSetsOrder;
|
||||
StickersSetsOrder _archivedSetsOrder;
|
||||
StickersSetsOrder _archivedMaskSetsOrder;
|
||||
SavedGifs _savedGifs;
|
||||
|
||||
};
|
||||
|
||||