Compare commits
230 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57aebdd300 | |||
| 25821ba338 | |||
| 7dda5461ad | |||
| ee0238bad8 | |||
| 451940e6a7 | |||
| 6163cc8a5a | |||
| ef859d77e9 | |||
| 0fd752657a | |||
| f1451a1de3 | |||
| fa96f25683 | |||
| 9e447383df | |||
| 5e762be32b | |||
| 75de81a3ab | |||
| d26b64a5bb | |||
| cb8d40eaf2 | |||
| ded0936bc4 | |||
| 16830a410c | |||
| 9f79dda463 | |||
| 15dc7c74d7 | |||
| f9abef9e05 | |||
| ba84499f00 | |||
| 79ce24222a | |||
| 6cb9264864 | |||
| 247a070405 | |||
| e05bb75b8a | |||
| 6a415cf232 | |||
| e8034189df | |||
| 107f329b4f | |||
| 999a13358e | |||
| 2077f51084 | |||
| 1e77a3df20 | |||
| 141a291523 | |||
| cb03d5a9d3 | |||
| b618d1e56a | |||
| 23ae638512 | |||
| eda749d7cb | |||
| 08d0186e53 | |||
| 443981ba31 | |||
| f3ed7c5e19 | |||
| dd2378b591 | |||
| b885779365 | |||
| 32b95f0d9a | |||
| 2b8eec8666 | |||
| 3f24627f54 | |||
| 0c4bca312e | |||
| e36afc675e | |||
| d754014321 | |||
| 9f73242cc5 | |||
| fae9649773 | |||
| 4b43f4cbec | |||
| b61befa210 | |||
| 5e2bc337bc | |||
| af1608cbfa | |||
| 9aa80976ff | |||
| c9cfe9e90f | |||
| 482e337762 | |||
| a0821f5a01 | |||
| 5ebdf3ed39 | |||
| 86096db02d | |||
| a93e01b896 | |||
| 0585e72c35 | |||
| c82fbefcfc | |||
| 23542a1db1 | |||
| 1bf50d60d8 | |||
| 8596b0309e | |||
| 7f6e871b26 | |||
| 8912d4d55a | |||
| 7ac849ab12 | |||
| 4cab699b04 | |||
| 03aa05e4d2 | |||
| 428a3cf0ce | |||
| 1c8b165a64 | |||
| 1869071ef7 | |||
| 33ca5ee39f | |||
| e46d5a86d3 | |||
| 2729bcac3b | |||
| d60ce41fa9 | |||
| 958db945f3 | |||
| 057f906ca4 | |||
| 8df7a45e29 | |||
| e4af1570cb | |||
| 049ebf9027 | |||
| f2f0c7df92 | |||
| bee4118513 | |||
| a1096f7ecb | |||
| dac0a6ec20 | |||
| 4327fd4c58 | |||
| 3243f57fd5 | |||
| 383c488268 | |||
| 53392d6e5d | |||
| bf48025d12 | |||
| 5e624605cf | |||
| 026490acc6 | |||
| 709ce3adb8 | |||
| a8d23489c4 | |||
| 296df113e3 | |||
| 292de83c35 | |||
| 7fe66c07e4 | |||
| 9aa8f30e2b | |||
| e3ebb30518 | |||
| 51122ab3ba | |||
| 5d96eab7b4 | |||
| 97c4e79e96 | |||
| f89bac7781 | |||
| 1d8a7f8fd3 | |||
| ef9f7ab27a | |||
| a676138745 | |||
| 7442ea7a16 | |||
| 0089cad740 | |||
| cf1fa718a8 | |||
| d6ba6ac41e | |||
| bbdd5feaa4 | |||
| 678527254b | |||
| a43943ec3d | |||
| ab3a61794b | |||
| 46bf7781aa | |||
| 99c547b625 | |||
| 6976e97de3 | |||
| f8e3e70273 | |||
| 23c9f7a957 | |||
| 2ca763cc77 | |||
| 2adc811351 | |||
| be18be4a86 | |||
| 8d7abb1b8a | |||
| 1cc5988c40 | |||
| da426ae03b | |||
| 3cebd6d923 | |||
| f1019c8ca4 | |||
| edfb7b6b24 | |||
| 157a928f5a | |||
| 5f5a2a3ef2 | |||
| 8534cf3756 | |||
| 39b90092ff | |||
| d3142ebe6d | |||
| d914c6be2e | |||
| 05c3e968df | |||
| 7756cce123 | |||
| 8287d717f8 | |||
| db9e60b4b5 | |||
| 28a79bfccb | |||
| 321490e528 | |||
| 6f752357d7 | |||
| e1f71baed6 | |||
| df377cd5bb | |||
| 691a0acdab | |||
| 5e2eda6af3 | |||
| 17cdc2b585 | |||
| c08266f81b | |||
| f90a4db569 | |||
| 22ec7a6d75 | |||
| 1b16a84810 | |||
| d778276f5d | |||
| e3030a168f | |||
| dfd07a4f4f | |||
| f9fc65d7de | |||
| 46cf7db242 | |||
| 2b94cffe7e | |||
| 2e74ad6fbe | |||
| df7dc1583d | |||
| 88e80b4fae | |||
| aea90f4b65 | |||
| 80db076f38 | |||
| 27bba8250a | |||
| 3fb0fa6892 | |||
| c3195cfcbe | |||
| 51661a872c | |||
| 64706ea103 | |||
| ec69d557dc | |||
| addd37fb1f | |||
| 9dc947ecb6 | |||
| 7d74d3da3a | |||
| aa0c56876c | |||
| 37c7b0c6d1 | |||
| 0d07d238bc | |||
| fa4e74ffef | |||
| c22d76e5be | |||
| 18850ebd83 | |||
| 17abef95eb | |||
| d135151477 | |||
| 07fd9b3074 | |||
| 0523ae705a | |||
| 9db2502cd0 | |||
| a174119877 | |||
| 569dd19932 | |||
| 530e2a1feb | |||
| de732ba692 | |||
| c6649e84a6 | |||
| e3517aceab | |||
| 6d7abd1718 | |||
| e9e493707b | |||
| c25adf8b57 | |||
| d2be10cd4e | |||
| 006ecf9a56 | |||
| a53cc52241 | |||
| 961d283325 | |||
| 4e46529eb6 | |||
| 81001e04e9 | |||
| 2fd174ab9c | |||
| 6ff5e221ea | |||
| 232077b919 | |||
| fecddb5203 | |||
| d0132c0f7b | |||
| 37d32b32f8 | |||
| 51213b499f | |||
| dcb98ce0fb | |||
| 428e90a844 | |||
| 017535cf7b | |||
| 409389a994 | |||
| ba34d92cd3 | |||
| b412ee258d | |||
| f1ffe2a641 | |||
| a13ca95894 | |||
| 0989a80a57 | |||
| d72c15e9d3 | |||
| 7084cf9526 | |||
| 67cc0ef75c | |||
| 75714cc358 | |||
| 75e454f3fd | |||
| 97afb4e01a | |||
| 8fcbf43410 | |||
| 15a834b883 | |||
| 3d8396e586 | |||
| 3257fd364a | |||
| d690af99fc | |||
| 1c28495162 | |||
| ec82d5674f | |||
| 4a22e76bdb | |||
| 527be95618 | |||
| 41985c0a5f | |||
| 522e45ce92 |
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "CentOS",
|
||||
"image": "tdesktop:centos_env",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"C_Cpp.intelliSenseEngine": "disabled",
|
||||
"clangd.arguments": [
|
||||
"--compile-commands-dir=${workspaceFolder}/out"
|
||||
],
|
||||
"cmake.generator": "Ninja Multi-Config",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/out",
|
||||
"cmake.configureSettings": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
"extensions": [
|
||||
"ms-vscode.cpptools-extension-pack",
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"TheQtCompany.qt",
|
||||
"ms-python.python",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
},
|
||||
"capAdd": [
|
||||
"SYS_PTRACE"
|
||||
],
|
||||
"securityOpt": [
|
||||
"seccomp=unconfined"
|
||||
],
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/usr/src/tdesktop,type=bind,consistency=cached",
|
||||
"workspaceFolder": "/usr/src/tdesktop"
|
||||
}
|
||||
@@ -83,6 +83,7 @@ jobs:
|
||||
fi
|
||||
|
||||
docker run --rm \
|
||||
-u $(id -u) \
|
||||
-v $PWD:/usr/src/tdesktop \
|
||||
-e CONFIG=Debug \
|
||||
tdesktop:centos_env \
|
||||
@@ -114,8 +115,8 @@ jobs:
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
cd $REPO_NAME/out/Debug
|
||||
sudo mkdir artifact
|
||||
sudo mv {Telegram,Updater} artifact/
|
||||
mkdir artifact
|
||||
mv {Telegram,Updater} artifact/
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
|
||||
@@ -19,6 +19,7 @@ Release/
|
||||
ipch/
|
||||
.vs/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
/Telegram/log.txt
|
||||
/Telegram/data
|
||||
|
||||
@@ -1041,6 +1041,7 @@ PRIVATE
|
||||
info/global_media/info_global_media_inner_widget.h
|
||||
info/global_media/info_global_media_provider.cpp
|
||||
info/global_media/info_global_media_provider.h
|
||||
info/media/info_media_buttons.cpp
|
||||
info/media/info_media_buttons.h
|
||||
info/media/info_media_common.cpp
|
||||
info/media/info_media_common.h
|
||||
@@ -1096,8 +1097,8 @@ PRIVATE
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
info/settings/info_settings_widget.h
|
||||
info/similar_channels/info_similar_channels_widget.cpp
|
||||
info/similar_channels/info_similar_channels_widget.h
|
||||
info/similar_peers/info_similar_peers_widget.cpp
|
||||
info/similar_peers/info_similar_peers_widget.h
|
||||
info/statistics/info_statistics_common.h
|
||||
info/statistics/info_statistics_inner_widget.cpp
|
||||
info/statistics/info_statistics_inner_widget.h
|
||||
@@ -1168,8 +1169,6 @@ PRIVATE
|
||||
intro/intro_qr.h
|
||||
intro/intro_signup.cpp
|
||||
intro/intro_signup.h
|
||||
intro/intro_start.cpp
|
||||
intro/intro_start.h
|
||||
intro/intro_step.cpp
|
||||
intro/intro_step.h
|
||||
intro/intro_widget.cpp
|
||||
@@ -1217,6 +1216,8 @@ PRIVATE
|
||||
media/audio/media_audio_loader.h
|
||||
media/audio/media_audio_loaders.cpp
|
||||
media/audio/media_audio_loaders.h
|
||||
media/audio/media_audio_local_cache.cpp
|
||||
media/audio/media_audio_local_cache.h
|
||||
media/audio/media_audio_track.cpp
|
||||
media/audio/media_audio_track.h
|
||||
media/audio/media_child_ffmpeg_loader.cpp
|
||||
@@ -1285,6 +1286,8 @@ PRIVATE
|
||||
media/streaming/media_streaming_video_track.h
|
||||
media/view/media_view_group_thumbs.cpp
|
||||
media/view/media_view_group_thumbs.h
|
||||
media/view/media_view_open_common.cpp
|
||||
media/view/media_view_open_common.h
|
||||
media/view/media_view_overlay_opengl.cpp
|
||||
media/view/media_view_overlay_opengl.h
|
||||
media/view/media_view_overlay_raster.cpp
|
||||
@@ -1303,7 +1306,6 @@ PRIVATE
|
||||
media/view/media_view_playback_controls.h
|
||||
media/view/media_view_playback_progress.cpp
|
||||
media/view/media_view_playback_progress.h
|
||||
media/view/media_view_open_common.h
|
||||
media/system_media_controls_manager.h
|
||||
media/system_media_controls_manager.cpp
|
||||
menu/menu_antispam_validator.cpp
|
||||
@@ -1553,6 +1555,8 @@ PRIVATE
|
||||
settings/settings_privacy_security.h
|
||||
settings/settings_scale_preview.cpp
|
||||
settings/settings_scale_preview.h
|
||||
settings/settings_shortcuts.cpp
|
||||
settings/settings_shortcuts.h
|
||||
settings/settings_type.h
|
||||
settings/settings_websites.cpp
|
||||
settings/settings_websites.h
|
||||
|
||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 21 KiB |
@@ -1,6 +1,7 @@
|
||||
// This is a list of your own shortcuts for Telegram Desktop
|
||||
// You can see full list of commands in the 'shortcuts-default.json' file
|
||||
// Place a null value instead of a command string to switch the shortcut off
|
||||
// You can also edit them in Settings > Chat Settings > Keyboard Shortcuts.
|
||||
|
||||
[
|
||||
// {
|
||||
|
||||
|
After Width: | Height: | Size: 401 B |
|
After Width: | Height: | Size: 712 B |
|
After Width: | Height: | Size: 957 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 568 B |
|
After Width: | Height: | Size: 663 B |
|
After Width: | Height: | Size: 761 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 730 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 716 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 447 B |
|
After Width: | Height: | Size: 671 B |
|
After Width: | Height: | Size: 1004 B |
@@ -604,6 +604,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_update_fail" = "Update check failed :(";
|
||||
"lng_settings_workmode_tray" = "Show tray icon";
|
||||
"lng_settings_workmode_window" = "Show taskbar icon";
|
||||
"lng_settings_window_close" = "When window closed";
|
||||
"lng_settings_run_in_background" = "Run in the background";
|
||||
"lng_settings_quit_on_close" = "Quit the application";
|
||||
"lng_settings_close_to_taskbar" = "Close to taskbar";
|
||||
"lng_settings_monochrome_icon" = "Use monochrome icon";
|
||||
"lng_settings_window_system" = "Window title bar";
|
||||
@@ -639,6 +642,46 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_chat_quick_action_react" = "Send reaction with double click";
|
||||
"lng_settings_chat_corner_reaction" = "Reaction button on messages";
|
||||
|
||||
"lng_settings_shortcuts" = "Keyboard shortcuts";
|
||||
|
||||
"lng_shortcuts_reset" = "Reset to default";
|
||||
"lng_shortcuts_recording" = "Recording...";
|
||||
"lng_shortcuts_add_another" = "Add another";
|
||||
|
||||
"lng_shortcuts_close" = "Close the window";
|
||||
"lng_shortcuts_lock" = "Lock the application";
|
||||
"lng_shortcuts_minimize" = "Minimize the window";
|
||||
"lng_shortcuts_quit" = "Quit the application";
|
||||
"lng_shortcuts_media_play" = "Play the media";
|
||||
"lng_shortcuts_media_pause" = "Pause the media";
|
||||
"lng_shortcuts_media_play_pause" = "Toggle media playback";
|
||||
"lng_shortcuts_media_stop" = "Stop media playback";
|
||||
"lng_shortcuts_media_previous" = "Previous track";
|
||||
"lng_shortcuts_media_next" = "Next track";
|
||||
"lng_shortcuts_search" = "Search messages";
|
||||
"lng_shortcuts_chat_previous" = "Previous chat";
|
||||
"lng_shortcuts_chat_next" = "Next chat";
|
||||
"lng_shortcuts_chat_first" = "First chat";
|
||||
"lng_shortcuts_chat_last" = "Last chat";
|
||||
"lng_shortcuts_chat_self" = "Saved Messages";
|
||||
"lng_shortcuts_chat_pinned_n" = "Pinned chat #{index}";
|
||||
"lng_shortcuts_show_account_n" = "Account #{index}";
|
||||
"lng_shortcuts_show_all_chats" = "All Chats folder";
|
||||
"lng_shortcuts_show_folder_n" = "Folder #{index}";
|
||||
"lng_shortcuts_show_folder_last" = "Last folder";
|
||||
"lng_shortcuts_folder_next" = "Next folder";
|
||||
"lng_shortcuts_folder_previous" = "Previous folder";
|
||||
"lng_shortcuts_scheduled" = "Scheduled messages";
|
||||
"lng_shortcuts_archive" = "Archived chats";
|
||||
"lng_shortcuts_contacts" = "Contacts list";
|
||||
"lng_shortcuts_just_send" = "Just send";
|
||||
"lng_shortcuts_silent_send" = "Silent send";
|
||||
"lng_shortcuts_schedule" = "Schedule";
|
||||
"lng_shortcuts_read_chat" = "Mark chat as read";
|
||||
"lng_shortcuts_archive_chat" = "Archive chat";
|
||||
"lng_shortcuts_media_fullscreen" = "Toggle video fullscreen";
|
||||
"lng_shortcuts_show_chat_menu" = "Show chat menu";
|
||||
|
||||
"lng_settings_chat_reactions_title" = "Quick Reaction";
|
||||
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
|
||||
"lng_settings_chat_message_reply_from" = "Bob Harris";
|
||||
@@ -1346,6 +1389,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_common_groups#other" = "{count} groups in common";
|
||||
"lng_profile_similar_channels#one" = "{count} similar channel";
|
||||
"lng_profile_similar_channels#other" = "{count} similar channels";
|
||||
"lng_profile_similar_bots#one" = "{count} similar bot";
|
||||
"lng_profile_similar_bots#other" = "{count} similar bots";
|
||||
"lng_profile_saved_messages#one" = "{count} saved message";
|
||||
"lng_profile_saved_messages#other" = "{count} saved messages";
|
||||
"lng_profile_peer_gifts#one" = "{count} gift";
|
||||
@@ -2020,16 +2065,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_gift_sent" = "You sent a gift for {cost}";
|
||||
"lng_action_gift_unique_sent" = "You sent a unique collectible item";
|
||||
"lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible";
|
||||
"lng_action_gift_upgraded_channel" = "{user} turned this gift to {channel} into a unique collectible";
|
||||
"lng_action_gift_upgraded_self_channel" = "You turned this gift to {channel} into a unique collectible";
|
||||
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible";
|
||||
"lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible";
|
||||
"lng_action_gift_transferred" = "{user} transferred you a gift";
|
||||
"lng_action_gift_transferred_channel" = "{user} transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_unknown" = "Someone transferred you a gift";
|
||||
"lng_action_gift_transferred_unknown_channel" = "Someone transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_self" = "You transferred a unique collectible";
|
||||
"lng_action_gift_transferred_self_channel" = "You transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_mine" = "You transferred a gift to {user}";
|
||||
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
|
||||
"lng_action_gift_sent_channel" = "{user} sent a gift to {name} for {cost}";
|
||||
"lng_action_gift_sent_self_channel" = "You sent a gift to {name} for {cost}";
|
||||
"lng_action_gift_self_bought" = "You bought a gift for {cost}";
|
||||
"lng_action_gift_self_subtitle" = "Saved Gift";
|
||||
"lng_action_gift_self_about#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_self_about#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_self_about_unique" = "You can display this gift on your page or turn it into unique collectible and send to others.";
|
||||
"lng_action_gift_channel_about#one" = "Display this gift in channel's Gifts or convert it to **{count}** Star.";
|
||||
"lng_action_gift_channel_about#other" = "Display this gift in channel's Gifts or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_channel_about_unique" = "You can display this gift in channel's Gifts or turn it into unique collectible.";
|
||||
"lng_action_gift_for_stars#one" = "{count} Star";
|
||||
"lng_action_gift_for_stars#other" = "{count} Stars";
|
||||
"lng_action_gift_got_subtitle" = "Gift from {user}";
|
||||
@@ -2038,6 +2095,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_gift_got_upgradable_text" = "Upgrade this gift to a unique collectible.";
|
||||
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
|
||||
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
|
||||
"lng_action_gift_got_gift_channel" = "You can keep this gift in channel's Gifts.";
|
||||
"lng_action_gift_can_remove_channel" = "You can remove this gift from channel's Gifts.";
|
||||
"lng_action_gift_sent_subtitle" = "Gift for {user}";
|
||||
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
|
||||
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
|
||||
@@ -2108,9 +2167,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_similar_channels_premium_all_link" = "Telegram Premium";
|
||||
"lng_similar_channels_show_more" = "Show more channels";
|
||||
|
||||
"lng_similar_bots_title" = "Similar bots";
|
||||
"lng_similar_bots_premium_all#one" = "Subscribe to {link} to unlock up to **{count}** similar bot.";
|
||||
"lng_similar_bots_premium_all#other" = "Subscribe to {link} to unlock up to **{count}** similar bots.";
|
||||
"lng_similar_bots_show_more" = "Show more bots";
|
||||
|
||||
"lng_peer_gifts_title" = "Gifts";
|
||||
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users.";
|
||||
"lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings.";
|
||||
"lng_peer_gifts_notify" = "Notify About New Gifts";
|
||||
"lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift.";
|
||||
"lng_peer_gifts_filter_by_value" = "Sort by Value";
|
||||
"lng_peer_gifts_filter_by_date" = "Sort by Date";
|
||||
"lng_peer_gifts_filter_unlimited" = "Unlimited";
|
||||
"lng_peer_gifts_filter_limited" = "Limited";
|
||||
"lng_peer_gifts_filter_unique" = "Unique";
|
||||
"lng_peer_gifts_filter_saved" = "Displayed";
|
||||
"lng_peer_gifts_filter_unsaved" = "Hidden";
|
||||
|
||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||
@@ -2373,6 +2446,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_stickers_add" = "Choose sticker set";
|
||||
"lng_group_emoji" = "Select Emoji Pack";
|
||||
"lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group.";
|
||||
"lng_collectible_emoji" = "Collectibles";
|
||||
|
||||
"lng_premium" = "Premium";
|
||||
"lng_premium_free" = "Free";
|
||||
@@ -2517,6 +2591,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_double_limits_subtitle_accounts" = "Connected Accounts";
|
||||
"lng_premium_double_limits_about_accounts#one" = "Connect {count} account with different mobile numbers";
|
||||
"lng_premium_double_limits_about_accounts#other" = "Connect {count} accounts with different mobile numbers";
|
||||
|
||||
"lng_premium_double_limits_subtitle_similar_channels" = "Similar Channel";
|
||||
"lng_premium_double_limits_about_similar_channels#one" = "View up to {count} similar channel";
|
||||
"lng_premium_double_limits_about_similar_channels#other" = "View up to {count} similar channels";
|
||||
//
|
||||
|
||||
"lng_premium_gift_title" = "Gift Telegram Premium";
|
||||
@@ -2960,11 +3038,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack.";
|
||||
"lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack.";
|
||||
|
||||
"lng_boost_channel_title_wear" = "Wear Item";
|
||||
"lng_boost_channel_needs_level_wear#one" = "Your channel needs **Level {count}** to wear collectibles.";
|
||||
"lng_boost_channel_needs_level_wear#other" = "Your channel needs **Level {count}** to wear collectibles.";
|
||||
|
||||
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:";
|
||||
"lng_boost_channel_ask_button" = "Copy Link";
|
||||
"lng_boost_channel_or" = "or";
|
||||
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||
"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||
//"lng_boost_channel_or" = "or";
|
||||
//"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||
//"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||
"lng_boost_group_ask" = "Ask your **Premium** members to boost your group with this link:";
|
||||
//"lng_boost_group_gifting" = "Boost your group by gifting your members Telegram Premium. {link}";
|
||||
|
||||
"lng_feature_stories#one" = "**{count}** Story Per Day";
|
||||
"lng_feature_stories#other" = "**{count}** Stories Per Day";
|
||||
@@ -3239,10 +3323,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile.";
|
||||
"lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message.";
|
||||
"lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins.";
|
||||
"lng_gift_send_unique" = "Make Unique for {price}";
|
||||
"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
|
||||
"lng_gift_send_unique_about_channel" = "Enable this to let the admins of {name} turn your gift into a unique collectible. {link}";
|
||||
"lng_gift_send_unique_link" = "Learn More >";
|
||||
"lng_gift_send_premium_about" = "Only {user} will see your message.";
|
||||
"lng_gift_send_limited_sold#one" = "{count} sold";
|
||||
"lng_gift_send_limited_sold#other" = "{count} sold";
|
||||
"lng_gift_send_limited_left#one" = "{count} left";
|
||||
"lng_gift_send_limited_left#other" = "{count} left";
|
||||
"lng_gift_send_button" = "Send a Gift for {cost}";
|
||||
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
|
||||
"lng_gift_sent_title" = "Gift Sent!";
|
||||
@@ -3254,20 +3344,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_price_unique" = "Unique";
|
||||
"lng_gift_view_unpack" = "Unpack";
|
||||
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
|
||||
"lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name.";
|
||||
"lng_gift_hidden_hint" = "This gift is hidden. Only you can see it.";
|
||||
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
|
||||
"lng_gift_hidden_unique" = "This gift is not displayed on your page.";
|
||||
"lng_gift_visible_hint" = "This gift is visible on your page.";
|
||||
"lng_gift_hidden_hint_channel" = "This gift is hidden from visitors of your channel.";
|
||||
"lng_gift_visible_hint_channel" = "This gift is visible in your channel's Gifts.";
|
||||
"lng_gift_in_blockchain" = "This gift is in TON blockchain. {link}";
|
||||
"lng_gift_in_blockchain_link" = "View >";
|
||||
"lng_gift_visible_hide" = "Hide >";
|
||||
"lng_gift_show_on_page" = "Display on my Page";
|
||||
"lng_gift_show_on_channel" = "Display in channel's Gifts";
|
||||
"lng_gift_availability" = "Availability";
|
||||
"lng_gift_from_hidden" = "Hidden User";
|
||||
"lng_gift_visibility" = "Visibility";
|
||||
"lng_gift_visibility_shown" = "Visible on your page";
|
||||
"lng_gift_visibility_hidden" = "Not visible on your page";
|
||||
"lng_gift_visibility_show" = "show";
|
||||
"lng_gift_visibility_hide" = "hide";
|
||||
"lng_gift_self_status" = "buy yourself a gift";
|
||||
"lng_gift_self_title" = "Buy a Gift";
|
||||
"lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later.";
|
||||
"lng_gift_channel_title" = "Send a Gift";
|
||||
"lng_gift_channel_about" = "Select a gift to show appreciation for {name}.";
|
||||
"lng_gift_unique_owner" = "Owner";
|
||||
"lng_gift_unique_owner_change" = "change";
|
||||
"lng_gift_unique_address_copied" = "Address copied to clipboard.";
|
||||
"lng_gift_unique_status" = "Status";
|
||||
"lng_gift_unique_status_non" = "Non-Unique";
|
||||
"lng_gift_unique_status_upgrade" = "upgrade";
|
||||
@@ -3276,6 +3372,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_unique_backdrop" = "Backdrop";
|
||||
"lng_gift_unique_symbol" = "Symbol";
|
||||
"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute.";
|
||||
"lng_gift_unique_availability_label" = "Quantity";
|
||||
"lng_gift_unique_availability#one" = "{count} of {amount} issued";
|
||||
"lng_gift_unique_availability#other" = "{count} of {amount} issued";
|
||||
"lng_gift_unique_info" = "Gifted to {recipient} on {date}.";
|
||||
@@ -3291,14 +3388,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
|
||||
"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
|
||||
"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?";
|
||||
"lng_gift_convert_sure_confirm_channel#one" = "Do you want to convert this gift to {channel} to **{count} Star**?";
|
||||
"lng_gift_convert_sure_confirm_channel#other" = "Do you want to convert this gift to {channel} to **{count} Stars**?";
|
||||
"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**.";
|
||||
"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**.";
|
||||
"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift.";
|
||||
"lng_gift_convert_sure" = "Convert";
|
||||
"lng_gift_display_done" = "The gift is now shown on your profile page.";
|
||||
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
|
||||
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
||||
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
|
||||
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
|
||||
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
|
||||
"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift.";
|
||||
"lng_gift_channel_got#other" = "Channel got **{count} Stars** for this gift.";
|
||||
"lng_gift_sold_out_title" = "Sold Out!";
|
||||
"lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
|
||||
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold.";
|
||||
@@ -3309,6 +3412,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction.";
|
||||
"lng_gift_upgrade_preview_title" = "Make Unique";
|
||||
"lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible.";
|
||||
"lng_gift_upgrade_preview_about_channel" = "Let the admins of {name} turn your gift into a unique collectible.";
|
||||
"lng_gift_upgrade_unique_title" = "Unique";
|
||||
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
|
||||
"lng_gift_upgrade_transferable_title" = "Transferable";
|
||||
@@ -3328,6 +3432,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}.";
|
||||
"lng_gift_transfer_title" = "Transfer {name}";
|
||||
"lng_gift_transfer_via_blockchain" = "Send via Blockchain";
|
||||
"lng_gift_transfer_password_title" = "Two-step verification";
|
||||
"lng_gift_transfer_password_description" = "Please enter your password to transfer.";
|
||||
"lng_gift_transfer_password_about" = "You can withdraw only if you have:";
|
||||
"lng_gift_transfer_confirm_title" = "Manage with Fragment";
|
||||
"lng_gift_transfer_confirm_text" = "You can use Fragment, a third-party service, to transfer {name} to your TON account. After that, you can manage it as an NFT with any TON wallet outside Telegram.\n\nYou can also move such NFTs back to your Telegram account via Fragment.";
|
||||
"lng_gift_transfer_confirm_button" = "Open Fragment";
|
||||
"lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day";
|
||||
"lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days";
|
||||
"lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour";
|
||||
@@ -3344,6 +3454,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?";
|
||||
"lng_gift_transfer_button" = "Transfer";
|
||||
"lng_gift_transfer_button_for" = "Transfer for {price}";
|
||||
"lng_gift_transfer_wear" = "Wear";
|
||||
"lng_gift_transfer_take_off" = "Take Off";
|
||||
"lng_gift_wear_title" = "Wear {name}";
|
||||
"lng_gift_wear_about" = "and get these benefits:";
|
||||
"lng_gift_wear_badge_title" = "Radiant Badge";
|
||||
"lng_gift_wear_badge_about" = "The glittering icon of this item will be displayed next to your name.";
|
||||
"lng_gift_wear_badge_about_channel" = "The glittering icon of this item will be displayed next to channel's name.";
|
||||
"lng_gift_wear_proof_title" = "Proof of Ownership";
|
||||
"lng_gift_wear_proof_about" = "Clicking the icon of this item next to your name will show its info and owner.";
|
||||
"lng_gift_wear_proof_about_channel" = "Clicking the icon of this item next to channel's name will show its info and owner.";
|
||||
"lng_gift_wear_start" = "Start Wearing";
|
||||
"lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles.";
|
||||
"lng_gift_wear_start_toast" = "You put on {name}";
|
||||
"lng_gift_wear_end_toast" = "You took off {name}";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
@@ -3759,6 +3883,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_choose_image" = "Choose an image";
|
||||
"lng_choose_file" = "Choose a file";
|
||||
"lng_choose_files" = "Choose Files";
|
||||
"lng_choose_cover" = "Choose video cover";
|
||||
"lng_choose_cover_bad" = "Can't use this file as a caption.";
|
||||
"lng_game_tag" = "Game";
|
||||
|
||||
"lng_context_new_window" = "Open in new window";
|
||||
@@ -3875,6 +4001,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_read_show" = "show when";
|
||||
"lng_context_edit_shortcut" = "Edit Shortcut";
|
||||
"lng_context_delete_shortcut" = "Delete Quick Reply";
|
||||
"lng_context_gift_send" = "Send Another Gift";
|
||||
|
||||
"lng_add_tag_about" = "Tag this message with an emoji for quick search.";
|
||||
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
|
||||
@@ -3903,6 +4030,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
"lng_context_edit_cover" = "Edit Cover";
|
||||
"lng_context_clear_cover" = "Clear Cover";
|
||||
|
||||
"lng_context_mention" = "Mention";
|
||||
"lng_context_search_from" = "Search messages";
|
||||
@@ -4032,6 +4161,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
|
||||
|
||||
"lng_share_title" = "Share to";
|
||||
"lng_share_at_time_title" = "Share at {time} to";
|
||||
"lng_share_copy_link" = "Copy share link";
|
||||
"lng_share_confirm" = "Send";
|
||||
"lng_share_wrong_user" = "This game was opened from a different user.";
|
||||
@@ -4155,7 +4285,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_media_save_progress" = "{ready} of {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Save As...";
|
||||
"lng_mediaview_copy" = "Copy";
|
||||
"lng_mediaview_copy_frame" = "Copy Frame";
|
||||
"lng_mediaview_forward" = "Forward";
|
||||
"lng_mediaview_share_at_time" = "Share at {time}";
|
||||
"lng_mediaview_delete" = "Delete";
|
||||
"lng_mediaview_save_to_profile" = "Post to Profile";
|
||||
"lng_mediaview_pin_story_done" = "Story pinned";
|
||||
@@ -5520,6 +5652,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_view_button_iv" = "Instant View";
|
||||
"lng_view_button_stickerset" = "View stickers";
|
||||
"lng_view_button_emojipack" = "View emoji";
|
||||
"lng_view_button_collectible" = "View collectible";
|
||||
|
||||
"lng_sponsored_hide_ads" = "Hide";
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
@@ -5836,6 +5969,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boosts_list_tab_gifts#other" = "{count} Gifts";
|
||||
|
||||
"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways";
|
||||
"lng_boosts_prepaid_giveaway_title_subtext" = "Select a giveaway you already paid for to set it up.";
|
||||
"lng_boosts_prepaid_giveaway_single" = "Prepaid giveaway";
|
||||
"lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium";
|
||||
"lng_boosts_prepaid_giveaway_quantity#other" = "{count} Telegram Premium";
|
||||
@@ -5895,6 +6029,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channel_earn_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels";
|
||||
"lng_channel_earn_chart_top_hours" = "Ad impressions";
|
||||
"lng_channel_earn_chart_revenue" = "Ad rewards";
|
||||
"lng_channel_earn_chart_overriden_detail_credits" = "Rewards in Stars";
|
||||
"lng_channel_earn_chart_overriden_detail_currency" = "Rewards in TON";
|
||||
"lng_channel_earn_chart_overriden_detail_usd" = "Rewards in USD";
|
||||
"lng_channel_earn_currency_history" = "TON Transactions";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.10.3.0" />
|
||||
Version="5.11.1.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 5,10,3,0
|
||||
PRODUCTVERSION 5,10,3,0
|
||||
FILEVERSION 5,11,1,0
|
||||
PRODUCTVERSION 5,11,1,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.10.3.0"
|
||||
VALUE "FileVersion", "5.11.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.10.3.0"
|
||||
VALUE "ProductVersion", "5.11.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,10,3,0
|
||||
PRODUCTVERSION 5,10,3,0
|
||||
FILEVERSION 5,11,1,0
|
||||
PRODUCTVERSION 5,11,1,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "5.10.3.0"
|
||||
VALUE "FileVersion", "5.11.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.10.3.0"
|
||||
VALUE "ProductVersion", "5.11.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -217,7 +217,11 @@ void Authorizations::toggleCallsDisabled(uint64 hash, bool disabled) {
|
||||
MTP_bool(disabled)
|
||||
)).done([=] {
|
||||
_toggleCallsDisabledRequests.remove(hash);
|
||||
}).fail([=] {
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: toggle calls %1. Hash: %2. %3.")
|
||||
.arg(disabled ? u"disabled"_q : u"enabled"_q)
|
||||
.arg(hash)
|
||||
.arg(error.type()));
|
||||
_toggleCallsDisabledRequests.remove(hash);
|
||||
}).send();
|
||||
_toggleCallsDisabledRequests.emplace(hash, id);
|
||||
|
||||
@@ -211,11 +211,10 @@ void ApplyBotsList(
|
||||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_Chats &chats) {
|
||||
auto result = ChatParticipants::Channels();
|
||||
std::vector<not_null<ChannelData*>>();
|
||||
auto result = ChatParticipants::Peers();
|
||||
chats.match([&](const auto &data) {
|
||||
const auto &list = data.vchats().v;
|
||||
result.list.reserve(list.size());
|
||||
@@ -234,10 +233,29 @@ void ApplyBotsList(
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPmessages_Chats &chats) {
|
||||
return ParseSimilar(&channel->session(), chats);
|
||||
return ParseSimilarChannels(&channel->session(), chats);
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarBots(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPusers_Users &users) {
|
||||
auto result = ChatParticipants::Peers();
|
||||
users.match([&](const auto &data) {
|
||||
const auto &list = data.vusers().v;
|
||||
result.list.reserve(list.size());
|
||||
for (const auto &user : list) {
|
||||
result.list.push_back(session->data().processUser(user));
|
||||
}
|
||||
if constexpr (MTPDusers_usersSlice::Is<decltype(data)>()) {
|
||||
if (session->premiumPossible()) {
|
||||
result.more = data.vcount().v - data.vusers().v.size();
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -782,52 +800,65 @@ void ChatParticipants::unblock(
|
||||
_kickRequests.emplace(kick, requestId);
|
||||
}
|
||||
|
||||
void ChatParticipants::loadSimilarChannels(not_null<ChannelData*> channel) {
|
||||
if (!channel->isBroadcast()) {
|
||||
return;
|
||||
} else if (const auto i = _similar.find(channel); i != end(_similar)) {
|
||||
void ChatParticipants::loadSimilarPeers(not_null<PeerData*> peer) {
|
||||
if (const auto i = _similar.find(peer); i != end(_similar)) {
|
||||
if (i->second.requestId
|
||||
|| !i->second.channels.more
|
||||
|| !channel->session().premium()) {
|
||||
|| !i->second.peers.more
|
||||
|| !peer->session().premium()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
using Flag = MTPchannels_GetChannelRecommendations::Flag;
|
||||
_similar[channel].requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(
|
||||
MTP_flags(Flag::f_channel),
|
||||
channel->inputChannel)
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
auto &similar = _similar[channel];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilar(channel, result);
|
||||
if (similar.channels == parsed) {
|
||||
return;
|
||||
}
|
||||
similar.channels = std::move(parsed);
|
||||
if (const auto history = channel->owner().historyLoaded(channel)) {
|
||||
if (const auto item = history->joinedMessageInstance()) {
|
||||
history->owner().requestItemResize(item);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
using Flag = MTPchannels_GetChannelRecommendations::Flag;
|
||||
_similar[peer].requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(
|
||||
MTP_flags(Flag::f_channel),
|
||||
channel->inputChannel)
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
auto &similar = _similar[channel];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilarChannels(channel, result);
|
||||
if (similar.peers == parsed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_similarLoaded.fire_copy(channel);
|
||||
}).send();
|
||||
similar.peers = std::move(parsed);
|
||||
if (const auto history = channel->owner().historyLoaded(channel)) {
|
||||
if (const auto item = history->joinedMessageInstance()) {
|
||||
history->owner().requestItemResize(item);
|
||||
}
|
||||
}
|
||||
_similarLoaded.fire_copy(channel);
|
||||
}).send();
|
||||
} else if (const auto bot = peer->asBot()) {
|
||||
_similar[peer].requestId = _api.request(
|
||||
MTPbots_GetBotRecommendations(bot->inputUser)
|
||||
).done([=](const MTPusers_Users &result) {
|
||||
auto &similar = _similar[peer];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilarBots(&peer->session(), result);
|
||||
if (similar.peers == parsed) {
|
||||
return;
|
||||
}
|
||||
similar.peers = std::move(parsed);
|
||||
_similarLoaded.fire_copy(peer);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
auto ChatParticipants::similar(not_null<ChannelData*> channel)
|
||||
-> const Channels & {
|
||||
const auto i = channel->isBroadcast()
|
||||
? _similar.find(channel)
|
||||
auto ChatParticipants::similar(not_null<PeerData*> peer)
|
||||
-> const Peers & {
|
||||
const auto i = (peer->isBroadcast() || peer->isBot())
|
||||
? _similar.find(peer)
|
||||
: end(_similar);
|
||||
if (i != end(_similar)) {
|
||||
return i->second.channels;
|
||||
return i->second.peers;
|
||||
}
|
||||
static const auto empty = Channels();
|
||||
static const auto empty = Peers();
|
||||
return empty;
|
||||
}
|
||||
|
||||
auto ChatParticipants::similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>> {
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _similarLoaded.events();
|
||||
}
|
||||
|
||||
@@ -841,15 +872,15 @@ void ChatParticipants::loadRecommendations() {
|
||||
MTP_inputChannelEmpty())
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
_recommendations.requestId = 0;
|
||||
auto parsed = ParseSimilar(_session, result);
|
||||
_recommendations.channels = std::move(parsed);
|
||||
_recommendations.channels.more = 0;
|
||||
auto parsed = ParseSimilarChannels(_session, result);
|
||||
_recommendations.peers = std::move(parsed);
|
||||
_recommendations.peers.more = 0;
|
||||
_recommendationsLoaded = true;
|
||||
}).send();
|
||||
}
|
||||
|
||||
const ChatParticipants::Channels &ChatParticipants::recommendations() const {
|
||||
return _recommendations.channels;
|
||||
const ChatParticipants::Peers &ChatParticipants::recommendations() const {
|
||||
return _recommendations.peers;
|
||||
}
|
||||
|
||||
rpl::producer<> ChatParticipants::recommendationsLoaded() const {
|
||||
|
||||
@@ -138,27 +138,27 @@ public:
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> participant);
|
||||
|
||||
void loadSimilarChannels(not_null<ChannelData*> channel);
|
||||
void loadSimilarPeers(not_null<PeerData*> peer);
|
||||
|
||||
struct Channels {
|
||||
std::vector<not_null<ChannelData*>> list;
|
||||
struct Peers {
|
||||
std::vector<not_null<PeerData*>> list;
|
||||
int more = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const Channels &,
|
||||
const Channels &) = default;
|
||||
const Peers &,
|
||||
const Peers &) = default;
|
||||
};
|
||||
[[nodiscard]] const Channels &similar(not_null<ChannelData*> channel);
|
||||
[[nodiscard]] const Peers &similar(not_null<PeerData*> peer);
|
||||
[[nodiscard]] auto similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>>;
|
||||
-> rpl::producer<not_null<PeerData*>>;
|
||||
|
||||
void loadRecommendations();
|
||||
[[nodiscard]] const Channels &recommendations() const;
|
||||
[[nodiscard]] const Peers &recommendations() const;
|
||||
[[nodiscard]] rpl::producer<> recommendationsLoaded() const;
|
||||
|
||||
private:
|
||||
struct SimilarChannels {
|
||||
Channels channels;
|
||||
struct SimilarPeers {
|
||||
Peers peers;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
@@ -186,10 +186,10 @@ private:
|
||||
not_null<PeerData*>>;
|
||||
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
|
||||
|
||||
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar;
|
||||
rpl::event_stream<not_null<ChannelData*>> _similarLoaded;
|
||||
base::flat_map<not_null<PeerData*>, SimilarPeers> _similar;
|
||||
rpl::event_stream<not_null<PeerData*>> _similarLoaded;
|
||||
|
||||
SimilarChannels _recommendations;
|
||||
SimilarPeers _recommendations;
|
||||
rpl::variable<bool> _recommendationsLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -74,6 +74,7 @@ struct MessageToSend {
|
||||
struct RemoteFileInfo {
|
||||
MTPInputFile file;
|
||||
std::optional<MTPInputFile> thumb;
|
||||
std::optional<MTPInputPhoto> videoCover;
|
||||
std::vector<MTPInputDocument> attachedStickers;
|
||||
};
|
||||
|
||||
|
||||
@@ -513,4 +513,12 @@ void EditCreditsSubscription(
|
||||
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
|
||||
}
|
||||
|
||||
MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
|
||||
return id.isUser()
|
||||
? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
|
||||
: MTP_inputSavedStarGiftChat(
|
||||
id.chat()->input,
|
||||
MTP_long(id.chatSavedId()));
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_credits_earn.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Data {
|
||||
class SavedStarGiftId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -116,4 +120,7 @@ void EditCreditsSubscription(
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
|
||||
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
|
||||
const Data::SavedStarGiftId &id);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -276,14 +276,22 @@ mtpRequestId EditTextMessage(
|
||||
takeFileReference = [=] { return photo->fileReference(); };
|
||||
} else if (const auto document = media->document()) {
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto videoCover = media->videoCover();
|
||||
const auto videoTimestamp = media->videoTimestamp();
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoilered ? Flag::f_spoiler : Flag());
|
||||
| (spoilered ? Flag::f_spoiler : Flag())
|
||||
| (videoTimestamp ? Flag::f_video_timestamp : Flag())
|
||||
| (videoCover ? Flag::f_video_cover : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
document->mtpInput(),
|
||||
(videoCover
|
||||
? videoCover->mtpInput()
|
||||
: MTPInputPhoto()),
|
||||
MTP_int(media->ttlSeconds()),
|
||||
MTP_int(videoTimestamp),
|
||||
MTPstring()); // query
|
||||
};
|
||||
takeFileReference = [=] { return document->fileReference(); };
|
||||
|
||||
@@ -13,6 +13,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Api {
|
||||
|
||||
PeerId ParsePaidReactionShownPeer(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPPaidReactionPrivacy &value) {
|
||||
return value.match([&](const MTPDpaidReactionPrivacyDefault &) {
|
||||
return session->userPeerId();
|
||||
}, [](const MTPDpaidReactionPrivacyAnonymous &) {
|
||||
return PeerId();
|
||||
}, [&](const MTPDpaidReactionPrivacyPeer &data) {
|
||||
return data.vpeer().match([&](const MTPDinputPeerSelf &) {
|
||||
return session->userPeerId();
|
||||
}, [](const MTPDinputPeerUser &data) {
|
||||
return peerFromUser(data.vuser_id());
|
||||
}, [](const MTPDinputPeerChat &data) {
|
||||
return peerFromChat(data.vchat_id());
|
||||
}, [](const MTPDinputPeerChannel &data) {
|
||||
return peerFromChannel(data.vchannel_id());
|
||||
}, [](const MTPDinputPeerUserFromMessage &data) -> PeerId {
|
||||
Unexpected("From message peer in ParsePaidReactionShownPeer.");
|
||||
}, [](const MTPDinputPeerChannelFromMessage &data) -> PeerId {
|
||||
Unexpected("From message peer in ParsePaidReactionShownPeer.");
|
||||
}, [](const MTPDinputPeerEmpty &) -> PeerId {
|
||||
Unexpected("Empty peer in ParsePaidReactionShownPeer.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
GlobalPrivacy::GlobalPrivacy(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
@@ -115,27 +141,27 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
||||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionAnonymous() {
|
||||
if (_paidReactionAnonymousLoaded) {
|
||||
void GlobalPrivacy::loadPaidReactionShownPeer() {
|
||||
if (_paidReactionShownPeerLoaded) {
|
||||
return;
|
||||
}
|
||||
_paidReactionAnonymousLoaded = true;
|
||||
_paidReactionShownPeerLoaded = true;
|
||||
_api.request(MTPmessages_GetPaidReactionPrivacy(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
|
||||
_paidReactionAnonymous = value;
|
||||
void GlobalPrivacy::updatePaidReactionShownPeer(PeerId shownPeer) {
|
||||
_paidReactionShownPeer = shownPeer;
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
|
||||
return _paidReactionAnonymous.current();
|
||||
PeerId GlobalPrivacy::paidReactionShownPeerCurrent() const {
|
||||
return _paidReactionShownPeer.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
|
||||
return _paidReactionAnonymous.value();
|
||||
rpl::producer<PeerId> GlobalPrivacy::paidReactionShownPeer() const {
|
||||
return _paidReactionShownPeer.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
|
||||
@@ -23,6 +23,10 @@ enum class UnarchiveOnNewMessage {
|
||||
AnyUnmuted,
|
||||
};
|
||||
|
||||
[[nodiscard]] PeerId ParsePaidReactionShownPeer(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPPaidReactionPrivacy &value);
|
||||
|
||||
class GlobalPrivacy final {
|
||||
public:
|
||||
explicit GlobalPrivacy(not_null<ApiWrap*> api);
|
||||
@@ -49,10 +53,10 @@ public:
|
||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
void loadPaidReactionAnonymous();
|
||||
void updatePaidReactionAnonymous(bool value);
|
||||
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
|
||||
void loadPaidReactionShownPeer();
|
||||
void updatePaidReactionShownPeer(PeerId shownPeer);
|
||||
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
|
||||
[[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
@@ -72,9 +76,9 @@ private:
|
||||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<bool> _paidReactionAnonymous = false;
|
||||
rpl::variable<PeerId> _paidReactionShownPeer = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionAnonymousLoaded = false;
|
||||
bool _paidReactionShownPeerLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -111,7 +111,8 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
| (info.thumb ? Flag::f_thumb : Flag())
|
||||
| (item->groupId() ? Flag::f_nosound_video : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (info.videoCover ? Flag::f_video_cover : Flag());
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
@@ -121,6 +122,8 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
info.videoCover.value_or(MTPInputPhoto()),
|
||||
MTP_int(0), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ void MessagesSearch::searchRequest() {
|
||||
_requestId = _history->session().api().request(MTPmessages_Search(
|
||||
MTP_flags((fromPeer ? Flag::f_from_id : Flag())
|
||||
| (savedPeer ? Flag::f_saved_peer_id : Flag())
|
||||
| (_request.topMsgId ? Flag::f_top_msg_id : Flag())
|
||||
| (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)),
|
||||
_history->peer->input,
|
||||
MTP_string(_request.query),
|
||||
@@ -111,7 +112,7 @@ void MessagesSearch::searchRequest() {
|
||||
MTP_vector_from_range(_request.tags | ranges::views::transform(
|
||||
Data::ReactionToMTP
|
||||
)),
|
||||
MTPint(), // top_msg_id
|
||||
MTP_int(_request.topMsgId), // top_msg_id
|
||||
MTP_inputMessagesFilterEmpty(),
|
||||
MTP_int(0), // min_date
|
||||
MTP_int(0), // max_date
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
QString query;
|
||||
PeerData *from = nullptr;
|
||||
std::vector<Data::ReactionId> tags;
|
||||
MsgId topMsgId;
|
||||
|
||||
friend inline bool operator==(
|
||||
const Request &,
|
||||
|
||||
@@ -64,6 +64,10 @@ MessagesSearchMerged::MessagesSearchMerged(not_null<History*> history)
|
||||
}
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::disableMigrated() {
|
||||
_migratedSearch = std::nullopt;
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::addFound(const FoundMessages &data) {
|
||||
for (const auto &message : data.messages) {
|
||||
_concatedFound.messages.push_back(message);
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
void clear();
|
||||
void search(const Request &search);
|
||||
void searchMore();
|
||||
void disableMigrated();
|
||||
|
||||
[[nodiscard]] const FoundMessages &messages() const;
|
||||
[[nodiscard]] const Request &request() const;
|
||||
|
||||
@@ -805,8 +805,14 @@ std::optional<Data::StarGift> FromTL(
|
||||
auto result = Data::StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
|
||||
.id = data.vid().v,
|
||||
.slug = qs(data.vslug()),
|
||||
.title = qs(data.vtitle()),
|
||||
.ownerId = peerFromUser(UserId(data.vowner_id().v)),
|
||||
.ownerAddress = qs(data.vowner_address().value_or_empty()),
|
||||
.ownerName = qs(data.vowner_name().value_or_empty()),
|
||||
.ownerId = (data.vowner_id()
|
||||
? peerFromMTP(*data.vowner_id())
|
||||
: PeerId()),
|
||||
.number = data.vnum().v,
|
||||
.model = *model,
|
||||
.pattern = *pattern,
|
||||
@@ -829,9 +835,9 @@ std::optional<Data::StarGift> FromTL(
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<Data::UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift) {
|
||||
std::optional<Data::SavedStarGift> FromTL(
|
||||
not_null<PeerData*> to,
|
||||
const MTPsavedStarGift &gift) {
|
||||
const auto session = &to->session();
|
||||
const auto &data = gift.data();
|
||||
auto parsed = FromTL(session, data.vgift());
|
||||
@@ -841,8 +847,12 @@ std::optional<Data::UserStarGift> FromTL(
|
||||
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
|
||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
||||
}
|
||||
return Data::UserStarGift{
|
||||
using Id = Data::SavedStarGiftId;
|
||||
return Data::SavedStarGift{
|
||||
.info = std::move(*parsed),
|
||||
.manageId = (to->isUser()
|
||||
? Id::User(data.vmsg_id().value_or_empty())
|
||||
: Id::Chat(to, data.vsaved_id().value_or_empty())),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
@@ -855,9 +865,8 @@ std::optional<Data::UserStarGift> FromTL(
|
||||
.starsUpgradedBySender = int64(
|
||||
data.vupgrade_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
? peerFromMTP(*data.vfrom_id())
|
||||
: PeerId()),
|
||||
.messageId = data.vmsg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.upgradable = data.is_can_upgrade(),
|
||||
.anonymous = data.is_name_hidden(),
|
||||
@@ -910,11 +919,9 @@ Data::UniqueGiftOriginalDetails FromTL(
|
||||
auto result = Data::UniqueGiftOriginalDetails();
|
||||
result.date = data.vdate().v;
|
||||
result.senderId = data.vsender_id()
|
||||
? peerFromUser(
|
||||
UserId(data.vsender_id().value_or_empty()))
|
||||
? peerFromMTP(*data.vsender_id())
|
||||
: PeerId();
|
||||
result.recipientId = peerFromUser(
|
||||
UserId(data.vrecipient_id().v));
|
||||
result.recipientId = peerFromMTP(data.vrecipient_id());
|
||||
result.message = data.vmessage()
|
||||
? ParseTextWithEntities(session, *data.vmessage())
|
||||
: TextWithEntities();
|
||||
|
||||
@@ -259,9 +259,9 @@ enum class RequirePremiumState {
|
||||
[[nodiscard]] std::optional<Data::StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<Data::UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift);
|
||||
[[nodiscard]] std::optional<Data::SavedStarGift> FromTL(
|
||||
not_null<PeerData*> to,
|
||||
const MTPsavedStarGift &gift);
|
||||
|
||||
[[nodiscard]] Data::UniqueGiftModel FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
|
||||
@@ -272,7 +272,9 @@ void SendExistingDocument(
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(0),
|
||||
document->mtpInput(),
|
||||
MTPInputPhoto(), // video_cover
|
||||
MTPint(), // ttl_seconds
|
||||
MTPint(), // video_timestamp
|
||||
MTPstring()); // query
|
||||
};
|
||||
SendExistingMedia(
|
||||
@@ -547,9 +549,12 @@ void SendConfirmedFile(
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())
|
||||
| (file->videoCover ? Flag::f_video_cover : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
file->videoCover ? file->videoCover->photo : MTPPhoto(),
|
||||
MTPint(), // video_timestamp
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
@@ -557,9 +562,12 @@ void SendConfirmedFile(
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| Flag::f_voice
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (file->videoCover ? Flag::f_video_cover : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
file->videoCover ? file->videoCover->photo : MTPPhoto(),
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
@@ -571,6 +579,8 @@ void SendConfirmedFile(
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
|
||||
@@ -2717,8 +2717,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updatePaidReactionPrivacy: {
|
||||
const auto &data = update.c_updatePaidReactionPrivacy();
|
||||
_session->api().globalPrivacy().updatePaidReactionAnonymous(
|
||||
mtpIsTrue(data.vprivate()));
|
||||
_session->api().globalPrivacy().updatePaidReactionShownPeer(
|
||||
Api::ParsePaidReactionShownPeer(_session, data.vprivate()));
|
||||
} break;
|
||||
|
||||
}
|
||||
|
||||
@@ -148,6 +148,16 @@ void ShowChannelsLimitBox(not_null<PeerData*> peer) {
|
||||
action.replaceMediaOf);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString FormatVideoTimestamp(TimeId seconds) {
|
||||
const auto minutes = seconds / 60;
|
||||
const auto hours = minutes / 60;
|
||||
return hours
|
||||
? u"%1h%2m%3s"_q.arg(hours).arg(minutes % 60).arg(seconds % 60)
|
||||
: minutes
|
||||
? u"%1m%2s"_q.arg(minutes).arg(seconds % 60)
|
||||
: QString::number(seconds);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
@@ -740,7 +750,8 @@ void ApiWrap::finalizeMessageDataRequest(
|
||||
QString ApiWrap::exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink) {
|
||||
bool forceNonPublicLink,
|
||||
std::optional<TimeId> videoTimestamp) {
|
||||
Expects(item->history()->peer->isChannel());
|
||||
|
||||
const auto itemId = item->fullId();
|
||||
@@ -792,19 +803,6 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
: linkThreadId
|
||||
? (QString::number(linkThreadId.bare) + '/' + post)
|
||||
: post);
|
||||
if (linkChannel->hasUsername()
|
||||
&& !forceNonPublicLink
|
||||
&& !linkChannel->isMegagroup()
|
||||
&& !linkCommentId
|
||||
&& !linkThreadId) {
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto document = media->document()) {
|
||||
if (document->isVideoMessage()) {
|
||||
return u"https://telesco.pe/"_q + query;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return session().createInternalLinkFull(query);
|
||||
};
|
||||
if (forceNonPublicLink) {
|
||||
@@ -826,7 +824,14 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
_unlikelyMessageLinks.emplace_or_assign(itemId, link);
|
||||
}
|
||||
}).send();
|
||||
return current;
|
||||
const auto addTimestamp = channel->hasUsername()
|
||||
&& !inRepliesContext
|
||||
&& videoTimestamp.has_value();
|
||||
const auto addedSeparator = (current.indexOf('?') >= 0) ? '&' : '?';
|
||||
const auto addedTimestamp = addTimestamp
|
||||
? (addedSeparator + u"t="_q + FormatVideoTimestamp(*videoTimestamp))
|
||||
: QString();
|
||||
return current + addedTimestamp;
|
||||
}
|
||||
|
||||
QString ApiWrap::exportDirectStoryLink(not_null<Data::Story*> story) {
|
||||
@@ -1812,7 +1817,7 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
|
||||
_channelAmInRequests.emplace(channel, requestId);
|
||||
|
||||
using Flag = ChannelDataFlag;
|
||||
chatParticipants().loadSimilarChannels(channel);
|
||||
chatParticipants().loadSimilarPeers(channel);
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->collapseSimilarChannels) {
|
||||
@@ -3422,7 +3427,8 @@ void ApiWrap::forwardMessages(
|
||||
MTP_int(topMsgId),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTPint() // video_timestamp
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (!scheduled) {
|
||||
this->updates().checkForSentToScheduled(result);
|
||||
@@ -3623,6 +3629,18 @@ void ApiWrap::editMedia(
|
||||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
(file.videoCover
|
||||
? std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
file.videoCover->path,
|
||||
file.videoCover->content,
|
||||
std::move(file.videoCover->information),
|
||||
nullptr,
|
||||
SendMediaType::Photo,
|
||||
to,
|
||||
TextWithTags(),
|
||||
false)
|
||||
: nullptr),
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
@@ -3664,6 +3682,19 @@ void ApiWrap::sendFiles(
|
||||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
(file.videoCover
|
||||
? std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
file.videoCover->path,
|
||||
file.videoCover->content,
|
||||
std::move(file.videoCover->information),
|
||||
nullptr,
|
||||
SendMediaType::Photo,
|
||||
to,
|
||||
TextWithTags(),
|
||||
false,
|
||||
nullptr)
|
||||
: nullptr),
|
||||
uploadWithType,
|
||||
to,
|
||||
caption,
|
||||
@@ -3689,11 +3720,13 @@ void ApiWrap::sendFile(
|
||||
auto caption = TextWithTags();
|
||||
const auto spoiler = false;
|
||||
const auto information = nullptr;
|
||||
const auto videoCover = nullptr;
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
QString(),
|
||||
fileContent,
|
||||
information,
|
||||
videoCover,
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
@@ -4208,16 +4241,29 @@ void ApiWrap::uploadAlbumMedia(
|
||||
return;
|
||||
}
|
||||
const auto &fields = document->c_document();
|
||||
const auto mtpCover = data.vvideo_cover();
|
||||
const auto cover = (mtpCover && mtpCover->type() == mtpc_photo)
|
||||
? &(mtpCover->c_photo())
|
||||
: (const MTPDphoto*)nullptr;
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto flags = Flag()
|
||||
| (data.vttl_seconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoiler ? Flag::f_spoiler : Flag());
|
||||
| (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (data.vvideo_timestamp() ? Flag::f_video_timestamp : Flag())
|
||||
| (cover ? Flag::f_video_cover : Flag());
|
||||
const auto media = MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
MTP_inputDocument(
|
||||
fields.vid(),
|
||||
fields.vaccess_hash(),
|
||||
fields.vfile_reference()),
|
||||
(cover
|
||||
? MTP_inputPhoto(
|
||||
cover->vid(),
|
||||
cover->vaccess_hash(),
|
||||
cover->vfile_reference())
|
||||
: MTPInputPhoto()),
|
||||
MTP_int(data.vvideo_timestamp().value_or_empty()),
|
||||
MTP_int(data.vttl_seconds().value_or_empty()),
|
||||
MTPstring()); // query
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
|
||||
@@ -166,7 +166,8 @@ public:
|
||||
QString exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink = false);
|
||||
bool forceNonPublicLink = false,
|
||||
std::optional<TimeId> videoTimestamp = {});
|
||||
QString exportDirectStoryLink(not_null<Data::Story*> item);
|
||||
|
||||
void requestContacts();
|
||||
|
||||
@@ -240,7 +240,7 @@ AyuGramSettings::AyuGramSettings() {
|
||||
#endif
|
||||
;
|
||||
simpleQuotesAndReplies = true;
|
||||
replaceBottomInfoWithIcons = false;
|
||||
replaceBottomInfoWithIcons = true;
|
||||
deletedMark = "🧹";
|
||||
editedMark = Core::IsAppLaunched() ? tr::lng_edited(tr::now) : QString("edited");
|
||||
recentStickersCount = 100;
|
||||
|
||||
@@ -44,6 +44,8 @@ void map(not_null<HistoryItem*> item, AyuMessageBase &message) {
|
||||
message.fromId = item->from()->id.value & PeerId::kChatTypeMask;
|
||||
if (item->topic()) {
|
||||
message.topicId = item->topicRootId().bare;
|
||||
} else {
|
||||
message.topicId = 0;
|
||||
}
|
||||
message.messageId = item->id.bare;
|
||||
message.date = item->date();
|
||||
|
||||
@@ -247,8 +247,8 @@ QImage addPadding(const QImage &original) {
|
||||
}
|
||||
|
||||
QImage paddedImage(
|
||||
original.width() + 2 * st::messageShotPadding,
|
||||
original.height() + 2 * st::messageShotPadding,
|
||||
original.width() + 2 * st::messageShotPadding * style::DevicePixelRatio(),
|
||||
original.height() + 2 * st::messageShotPadding * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied
|
||||
);
|
||||
paddedImage.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
@@ -352,6 +352,7 @@ QImage Make(not_null<QWidget*> box, const ShotConfig &config) {
|
||||
height += view->resizeGetHeight(width);
|
||||
}
|
||||
|
||||
width *= style::DevicePixelRatio();
|
||||
height *= style::DevicePixelRatio();
|
||||
|
||||
// create the image
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
using "ui/colors.palette";
|
||||
using "ui/widgets/widgets.style";
|
||||
using "dialogs/dialogs.style";
|
||||
|
||||
ayuGhostIcon: icon {{ "ayu/ghost", menuIconColor }};
|
||||
ayuMenuIcon: icon {{ "ayu/ayu_menu", menuIconColor }};
|
||||
@@ -40,3 +41,14 @@ editedIconPadding: margins(0px, 0px, 0px, 0px);
|
||||
|
||||
deletedIcon: icon {{ "ayu/trash_bin", windowFg }};
|
||||
deletedIconPadding: margins(0px, 0px, 0px, 0px);
|
||||
|
||||
dialogsExteraOfficialIcon: ThreeStateIcon {
|
||||
icon: icon {{ "ayu/dialogs_extera_official", dialogsVerifiedIconBg }};
|
||||
over: icon {{ "ayu/dialogs_extera_official", dialogsVerifiedIconBgOver }};
|
||||
active: icon {{ "ayu/dialogs_extera_official", dialogsVerifiedIconBgActive }};
|
||||
}
|
||||
dialogsExteraSupporterIcon: ThreeStateIcon {
|
||||
icon: icon {{ "ayu/dialogs_extera_supporter", dialogsVerifiedIconBg }};
|
||||
over: icon {{ "ayu/dialogs_extera_supporter", dialogsVerifiedIconBgOver }};
|
||||
active: icon {{ "ayu/dialogs_extera_supporter", dialogsVerifiedIconBgActive }};
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ void MessageShotBox::setupContent() {
|
||||
|
||||
updatePreview();
|
||||
|
||||
const auto boxWidth = imageView->getImage().width() + (st::boxPadding.left() + st::boxPadding.right()) * 4;
|
||||
const auto boxWidth = imageView->getImage().width() / style::DevicePixelRatio() + (st::boxPadding.left() + st::boxPadding.right()) * 4;
|
||||
|
||||
boxClosing() | rpl::start_with_next(
|
||||
[=]
|
||||
|
||||
@@ -27,7 +27,8 @@ void ImageView::setImage(const QImage &image) {
|
||||
this->prevImage = this->image;
|
||||
this->image = image;
|
||||
|
||||
setMinimumSize(image.size().grownBy(st::imageViewInnerPadding));
|
||||
const auto size = image.size() / style::DevicePixelRatio();
|
||||
setMinimumSize(size.grownBy(st::imageViewInnerPadding));
|
||||
|
||||
if (this->animation.animating()) {
|
||||
this->animation.stop();
|
||||
@@ -67,7 +68,7 @@ void ImageView::paintEvent(QPaintEvent *e) {
|
||||
const auto brush = QBrush(AyuFeatures::MessageShot::makeDefaultBackgroundColor());
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(rect(), st::bubbleRadiusLarge, st::bubbleRadiusLarge);
|
||||
path.addRoundedRect(rect(), st::roundRadiusLarge, st::roundRadiusLarge);
|
||||
|
||||
p.fillPath(path, brush);
|
||||
|
||||
@@ -75,10 +76,10 @@ void ImageView::paintEvent(QPaintEvent *e) {
|
||||
const auto realRect = rect().marginsRemoved(st::imageViewInnerPadding);
|
||||
|
||||
const auto resizedRect = QRect(
|
||||
(realRect.width() - prevImage.width()) / 2 + st::imageViewInnerPadding.left(),
|
||||
(realRect.height() - prevImage.height()) / 2 + st::imageViewInnerPadding.top(),
|
||||
prevImage.width(),
|
||||
prevImage.height());
|
||||
(realRect.width() - prevImage.width() / style::DevicePixelRatio()) / 2 + st::imageViewInnerPadding.left(),
|
||||
(realRect.height() - prevImage.height() / style::DevicePixelRatio()) / 2 + st::imageViewInnerPadding.top(),
|
||||
prevImage.width() / style::DevicePixelRatio(),
|
||||
prevImage.height() / style::DevicePixelRatio());
|
||||
|
||||
const auto opacity = 1.0 - animation.value(1.0);
|
||||
p.setOpacity(opacity);
|
||||
@@ -90,10 +91,10 @@ void ImageView::paintEvent(QPaintEvent *e) {
|
||||
const auto realRect = rect().marginsRemoved(st::imageViewInnerPadding);
|
||||
|
||||
const auto resizedRect = QRect(
|
||||
(realRect.width() - image.width()) / 2 + st::imageViewInnerPadding.left(),
|
||||
(realRect.height() - image.height()) / 2 + st::imageViewInnerPadding.top(),
|
||||
image.width(),
|
||||
image.height());
|
||||
(realRect.width() - image.width() / style::DevicePixelRatio()) / 2 + st::imageViewInnerPadding.left(),
|
||||
(realRect.height() - image.height() / style::DevicePixelRatio()) / 2 + st::imageViewInnerPadding.top(),
|
||||
image.width() / style::DevicePixelRatio(),
|
||||
image.height() / style::DevicePixelRatio());
|
||||
|
||||
const auto opacity = animation.value(1.0);
|
||||
p.setOpacity(opacity);
|
||||
|
||||
@@ -702,7 +702,7 @@ void InnerWidget::addMessages(Direction direction, const std::vector<AyuMessageB
|
||||
? message.fakeId // viewing edited history
|
||||
: message.messageId; // viewing deleted messages
|
||||
if (_messageIds.find(id) != _messageIds.end()) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
auto count = 0;
|
||||
const auto addOne = [&](
|
||||
|
||||
@@ -48,6 +48,13 @@
|
||||
|
||||
class PainterHighQualityEnabler;
|
||||
|
||||
rpl::producer<QString> asBeta(rpl::producer<QString> text) {
|
||||
return std::move(text) | rpl::map([=](const QString &val)
|
||||
{
|
||||
return val + " β";
|
||||
});
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> AddInnerToggle(not_null<Ui::VerticalLayout*> container,
|
||||
const style::SettingsButton &st,
|
||||
std::vector<not_null<Ui::AbstractCheckView*>> innerCheckViews,
|
||||
@@ -1277,7 +1284,7 @@ void SetupRecentStickersLimitSlider(not_null<Ui::VerticalLayout*> container) {
|
||||
updateLabel(settings->recentStickersCount);
|
||||
|
||||
slider->setPseudoDiscrete(
|
||||
100 + 1,
|
||||
200 + 1,
|
||||
// thx tg
|
||||
[=](int amount)
|
||||
{
|
||||
@@ -1461,10 +1468,7 @@ void SetupMarks(not_null<Ui::VerticalLayout*> container) {
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_ReplaceMarksWithIcons() | rpl::map([=](QString val)
|
||||
{
|
||||
return val + " β";
|
||||
}),
|
||||
tr::ayu_ReplaceMarksWithIcons(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->replaceBottomInfoWithIcons)
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/ayu_state.h"
|
||||
#include "ayu/data/messages_storage.h"
|
||||
|
||||
Main::Session *getSession(ID userId) {
|
||||
for (const auto &[index, account] : Core::App().domain().accounts()) {
|
||||
@@ -85,6 +86,15 @@ bool isSupporterPeer(ID peerId) {
|
||||
return RCManager::getInstance().supporters().contains(peerId);
|
||||
}
|
||||
|
||||
rpl::producer<Info::Profile::Badge::Content> ExteraBadgeTypeFromPeer(not_null<PeerData*> peer) {
|
||||
if (isExteraPeer(getBareID(peer))) {
|
||||
return rpl::single(Info::Profile::Badge::Content{Info::Profile::BadgeType::Extera });
|
||||
} else if (isSupporterPeer(getBareID(peer))) {
|
||||
return rpl::single(Info::Profile::Badge::Content{Info::Profile::BadgeType::ExteraSupporter });
|
||||
}
|
||||
return rpl::single(Info::Profile::Badge::Content{Info::Profile::BadgeType::None });
|
||||
}
|
||||
|
||||
bool isMessageHidden(const not_null<HistoryItem*> item) {
|
||||
if (AyuState::isHidden(item)) {
|
||||
return true;
|
||||
@@ -494,6 +504,28 @@ int getScheduleTime(int64 sumSize) {
|
||||
return time;
|
||||
}
|
||||
|
||||
bool isMessageSavable(const not_null<HistoryItem *> item) {
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
|
||||
if (!settings->saveDeletedMessages) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto possiblyBot = item->history()->peer->asUser()) {
|
||||
return !possiblyBot->isBot() || (settings->saveForBots && possiblyBot->isBot());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void processMessageDelete(not_null<HistoryItem*> item) {
|
||||
if (!isMessageSavable(item)) {
|
||||
item->destroy();
|
||||
} else {
|
||||
item->setDeleted();
|
||||
AyuMessages::addDeletedMessage(item);
|
||||
}
|
||||
}
|
||||
|
||||
void resolveUser(ID userId, const QString &username, Main::Session *session, const Callback &callback) {
|
||||
auto normalized = username.trimmed().toLower();
|
||||
if (normalized.isEmpty()) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "core/application.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "main/main_domain.h"
|
||||
|
||||
using Callback = Fn<void(const QString &, UserData *)>;
|
||||
@@ -23,6 +24,8 @@ ID getBareID(not_null<PeerData*> peer);
|
||||
bool isExteraPeer(ID peerId);
|
||||
bool isSupporterPeer(ID peerId);
|
||||
|
||||
rpl::producer<Info::Profile::Badge::Content> ExteraBadgeTypeFromPeer(not_null<PeerData*> peer);
|
||||
|
||||
bool isMessageHidden(not_null<HistoryItem*> item);
|
||||
|
||||
void MarkAsReadChatList(not_null<Dialogs::MainList*> list);
|
||||
@@ -45,6 +48,9 @@ QString getPeerDC(not_null<PeerData*> peer);
|
||||
|
||||
int getScheduleTime(int64 sumSize);
|
||||
|
||||
bool isMessageSavable(not_null<HistoryItem *> item);
|
||||
void processMessageDelete(not_null<HistoryItem *> item);
|
||||
|
||||
void searchById(ID userId, Main::Session *session, bool retry, const Callback &callback);
|
||||
void searchById(ID userId, Main::Session *session, const Callback &callback);
|
||||
|
||||
|
||||
@@ -25,17 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_session_controller_link_info.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
rpl::producer<TextWithEntities> Text1() {
|
||||
return tr::lng_about_text1(
|
||||
lt_api_link,
|
||||
tr::lng_about_text1_api(
|
||||
) | Ui::Text::ToLink("https://core.telegram.org/api"),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> Text2() {
|
||||
rpl::producer<TextWithEntities> Text() {
|
||||
return tr::lng_about_text2(
|
||||
lt_gpl_link,
|
||||
rpl::single(Ui::Text::Link(
|
||||
@@ -48,34 +45,33 @@ rpl::producer<TextWithEntities> Text2() {
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> Text3() {
|
||||
return tr::lng_about_text3(
|
||||
lt_faq_link,
|
||||
tr::lng_about_text3_faq() | Ui::Text::ToLink(telegramFaqLink()),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AboutBox::AboutBox(QWidget *parent)
|
||||
AboutBox::AboutBox(QWidget *parent, Window::SessionController* controller)
|
||||
: _version(this, tr::lng_about_version(tr::now, lt_version, currentVersionText()), st::aboutVersionLink)
|
||||
, _text1(this, Text1(), st::aboutLabel)
|
||||
, _text2(this, Text2(), st::aboutLabel)
|
||||
, _text3(this, Text3(), st::aboutLabel) {
|
||||
, _text(this, Text(), st::aboutLabel)
|
||||
, _controller(controller) {
|
||||
}
|
||||
|
||||
void AboutBox::prepare() {
|
||||
setTitle(rpl::single(u"AyuGram Desktop"_q));
|
||||
|
||||
addButton(tr::lng_close(), [this] { closeBox(); });
|
||||
addLeftButton(
|
||||
rpl::single(QString("@ayugramchat")),
|
||||
[this, controller = _controller]
|
||||
{
|
||||
closeBox();
|
||||
controller->showPeerByLink(Window::PeerByLinkInfo{
|
||||
.usernameOrId = QString("ayugramchat"),
|
||||
});
|
||||
});
|
||||
|
||||
_text1->setLinksTrusted();
|
||||
_text2->setLinksTrusted();
|
||||
_text3->setLinksTrusted();
|
||||
_text->setLinksTrusted();
|
||||
|
||||
_version->setClickedCallback([this] { showVersionHistory(); });
|
||||
|
||||
setDimensions(st::aboutWidth, st::aboutTextTop + _text1->height() + st::aboutSkip + _text2->height() + st::aboutSkip + _text3->height());
|
||||
setDimensions(st::aboutWidth, st::aboutTextTop + _text->height());
|
||||
}
|
||||
|
||||
void AboutBox::resizeEvent(QResizeEvent *e) {
|
||||
@@ -85,42 +81,12 @@ void AboutBox::resizeEvent(QResizeEvent *e) {
|
||||
- st::boxPadding.left()
|
||||
- st::boxPadding.right();
|
||||
_version->moveToLeft(st::boxPadding.left(), st::aboutVersionTop);
|
||||
_text1->resizeToWidth(available);
|
||||
_text1->moveToLeft(st::boxPadding.left(), st::aboutTextTop);
|
||||
_text2->resizeToWidth(available);
|
||||
_text2->moveToLeft(st::boxPadding.left(), _text1->y() + _text1->height() + st::aboutSkip);
|
||||
_text3->resizeToWidth(available);
|
||||
_text3->moveToLeft(st::boxPadding.left(), _text2->y() + _text2->height() + st::aboutSkip);
|
||||
_text->resizeToWidth(available);
|
||||
_text->moveToLeft(st::boxPadding.left(), st::aboutTextTop);
|
||||
}
|
||||
|
||||
void AboutBox::showVersionHistory() {
|
||||
if (cRealAlphaVersion()) {
|
||||
auto url = u"https://tdesktop.com/"_q;
|
||||
if (Platform::IsWindows32Bit()) {
|
||||
url += u"win/%1.zip"_q;
|
||||
} else if (Platform::IsWindows64Bit()) {
|
||||
url += u"win64/%1.zip"_q;
|
||||
} else if (Platform::IsWindowsARM64()) {
|
||||
url += u"winarm/%1.zip"_q;
|
||||
} else if (Platform::IsMac()) {
|
||||
url += u"mac/%1.zip"_q;
|
||||
} else if (Platform::IsLinux()) {
|
||||
url += u"linux/%1.tar.xz"_q;
|
||||
} else {
|
||||
Unexpected("Platform value.");
|
||||
}
|
||||
url = url.arg(u"talpha%1_%2"_q.arg(cRealAlphaVersion()).arg(Core::countAlphaVersionSignature(cRealAlphaVersion())));
|
||||
|
||||
QGuiApplication::clipboard()->setText(url);
|
||||
|
||||
getDelegate()->show(
|
||||
Ui::MakeInformBox(
|
||||
"The link to the current private alpha "
|
||||
"version of Telegram Desktop was copied to the clipboard."),
|
||||
Ui::LayerOption::CloseOther);
|
||||
} else {
|
||||
File::OpenUrl(Core::App().changelogLink());
|
||||
}
|
||||
File::OpenUrl(Core::App().changelogLink());
|
||||
}
|
||||
|
||||
void AboutBox::keyPressEvent(QKeyEvent *e) {
|
||||
@@ -131,23 +97,6 @@ void AboutBox::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
QString telegramFaqLink() {
|
||||
const auto result = u"https://telegram.org/faq"_q;
|
||||
const auto langpacked = [&](const char *language) {
|
||||
return result + '/' + language;
|
||||
};
|
||||
const auto current = Lang::Id();
|
||||
for (const auto language : { "de", "es", "it", "ko" }) {
|
||||
if (current.startsWith(QLatin1String(language))) {
|
||||
return langpacked(language);
|
||||
}
|
||||
}
|
||||
if (current.startsWith(u"pt-br"_q)) {
|
||||
return langpacked("br");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString currentVersionText() {
|
||||
auto result = QString::fromLatin1(AppVersionStr);
|
||||
if (cAlphaVersion()) {
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "ui/layers/box_content.h"
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
}
|
||||
|
||||
namespace Ui {
|
||||
class LinkButton;
|
||||
class FlatLabel;
|
||||
@@ -16,7 +20,7 @@ class FlatLabel;
|
||||
|
||||
class AboutBox : public Ui::BoxContent {
|
||||
public:
|
||||
AboutBox(QWidget*);
|
||||
AboutBox(QWidget*, Window::SessionController* controller);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@@ -28,9 +32,8 @@ private:
|
||||
void showVersionHistory();
|
||||
|
||||
object_ptr<Ui::LinkButton> _version;
|
||||
object_ptr<Ui::FlatLabel> _text1;
|
||||
object_ptr<Ui::FlatLabel> _text2;
|
||||
object_ptr<Ui::FlatLabel> _text3;
|
||||
object_ptr<Ui::FlatLabel> _text;
|
||||
Window::SessionController* _controller;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -467,13 +467,16 @@ void EditCaptionBox::rebuildPreview() {
|
||||
}
|
||||
} else {
|
||||
const auto &file = _preparedList.files.front();
|
||||
|
||||
const auto isVideoFile = file.isVideoFile();
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
this,
|
||||
st::defaultComposeControls,
|
||||
gifPaused,
|
||||
file,
|
||||
[] { return true; },
|
||||
[=](Ui::AttachActionType type) {
|
||||
return (type != Ui::AttachActionType::EditCover)
|
||||
|| isVideoFile;
|
||||
},
|
||||
Ui::AttachControls::Type::EditOnly);
|
||||
_isPhoto = (media && media->isPhoto());
|
||||
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
|
||||
@@ -719,7 +722,7 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
|
||||
controller->uiShow(),
|
||||
&_preparedList.files.front(),
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { rebuildPreview(); });
|
||||
[=](bool ok) { if (ok) rebuildPreview(); });
|
||||
} else {
|
||||
EditPhotoImage(_controller, _photoMedia, hasSpoiler(), [=](
|
||||
Ui::PreparedList &&list) {
|
||||
|
||||
@@ -13,6 +13,10 @@ namespace Api {
|
||||
struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct Boost;
|
||||
struct CreditsHistoryEntry;
|
||||
@@ -21,6 +25,14 @@ struct GiveawayResults;
|
||||
struct SubscriptionEntry;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Settings {
|
||||
struct CreditsEntryBoxStyleOverrides;
|
||||
} // namespace Settings
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
@@ -54,29 +66,37 @@ void ResolveGiveawayInfo(
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
[[nodiscard]] QString TonAddressUrl(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &address);
|
||||
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void(bool)> toggleVisibility,
|
||||
Fn<void()> convertToStars,
|
||||
Fn<void()> startUpgrade);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
|
||||
void AddSubscriptionEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::SubscriptionEntry &s);
|
||||
void AddSubscriberEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date);
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::Boost &boost);
|
||||
|
||||
@@ -72,6 +72,16 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
|
||||
if (peer != item->history()->peer) {
|
||||
return {};
|
||||
}
|
||||
{
|
||||
const auto author = item->author();
|
||||
if (author == peer) {
|
||||
return {};
|
||||
} else if (const auto channel = author->asChannel()) {
|
||||
if (channel->linkedChat() == peer) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item->suggestBanReport()) {
|
||||
result.allCanBan = false;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <xxhash.h> // XXH64.
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
// AyuGram includes
|
||||
#include "styles/style_ayu_icons.h"
|
||||
|
||||
|
||||
[[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d) {
|
||||
return XXH64(d.data(), d.size() * sizeof(ushort), 0);
|
||||
}
|
||||
@@ -800,6 +804,12 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
.exteraOfficial = &(selected
|
||||
? st::dialogsExteraOfficialIcon.over
|
||||
: st::dialogsExteraOfficialIcon.icon),
|
||||
.exteraSupporter = &(selected
|
||||
? st::dialogsExteraSupporterIcon.over
|
||||
: st::dialogsExteraSupporterIcon.icon),
|
||||
.premium = &(selected
|
||||
? st::dialogsPremiumIcon.over
|
||||
: st::dialogsPremiumIcon.icon),
|
||||
@@ -912,7 +922,13 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
|
||||
p.setPen(userpicBorderPen);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(userpicEllipse);
|
||||
if (peer()->forum()) {
|
||||
const auto radius = userpicDiameter
|
||||
* Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(userpicEllipse, radius, radius);
|
||||
} else {
|
||||
p.drawEllipse(userpicEllipse);
|
||||
}
|
||||
|
||||
p.setPen(iconBorderPen);
|
||||
p.setBrush(st.disabledCheckFg);
|
||||
|
||||
@@ -265,7 +265,7 @@ struct IconSelector {
|
||||
const auto manager = &controller->session().data().customEmojiManager();
|
||||
|
||||
auto factory = [=](DocumentId id, Fn<void()> repaint)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
|
||||
if (id == kDefaultIconId) {
|
||||
return std::make_unique<DefaultIconEmoji>(
|
||||
@@ -288,7 +288,7 @@ struct IconSelector {
|
||||
.show = controller->uiShow(),
|
||||
.mode = EmojiListWidget::Mode::TopicIcon,
|
||||
.paused = Window::PausedIn(controller, PauseReason::Layer),
|
||||
.customRecentList = recent(),
|
||||
.customRecentList = DocumentListToRecent(recent()),
|
||||
.customRecentFactory = std::move(factory),
|
||||
.st = &st::reactPanelEmojiPan,
|
||||
}),
|
||||
@@ -297,7 +297,7 @@ struct IconSelector {
|
||||
icons->requestDefaultIfUnknown();
|
||||
icons->defaultUpdates(
|
||||
) | rpl::start_with_next([=] {
|
||||
selector->provideRecent(recent());
|
||||
selector->provideRecent(DocumentListToRecent(recent()));
|
||||
}, selector->lifetime());
|
||||
|
||||
placeFooter(selector->createFooter());
|
||||
|
||||
@@ -313,9 +313,11 @@ PreviewWrap::PreviewWrap(
|
||||
WebPageCollage(),
|
||||
nullptr, // iv
|
||||
nullptr, // stickerSet
|
||||
nullptr, // uniqueGift
|
||||
0, // duration
|
||||
QString(), // author
|
||||
false, // hasLargeMedia
|
||||
false, // photoIsVideoCover
|
||||
0)) // pendingTill
|
||||
, _theme(theme)
|
||||
, _style(style)
|
||||
@@ -525,7 +527,7 @@ void LevelBadge::paintEvent(QPaintEvent *e) {
|
||||
struct SetValues {
|
||||
uint8 colorIndex = 0;
|
||||
DocumentId backgroundEmojiId = 0;
|
||||
DocumentId statusId = 0;
|
||||
EmojiStatusId statusId;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
};
|
||||
@@ -807,7 +809,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.someCustomChosen(
|
||||
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
|
||||
emojiIdChosen(chosen.id);
|
||||
emojiIdChosen(chosen.id.documentId);
|
||||
}, raw->lifetime());
|
||||
|
||||
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
|
||||
@@ -871,13 +873,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
const auto customTextColor = [=] {
|
||||
return style->coloredValues(false, state->index).name;
|
||||
};
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto controller = show->resolveWindow();
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.ensureAddedEmojiId = state->emojiId,
|
||||
.ensureAddedEmojiId = { state->emojiId },
|
||||
.customTextColor = customTextColor,
|
||||
.backgroundEmojiMode = true,
|
||||
});
|
||||
@@ -901,8 +902,8 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<ChannelData*> channel,
|
||||
rpl::producer<DocumentId> statusIdValue,
|
||||
Fn<void(DocumentId,TimeId)> statusIdChosen,
|
||||
rpl::producer<EmojiStatusId> statusIdValue,
|
||||
Fn<void(EmojiStatusId,TimeId)> statusIdChosen,
|
||||
bool group) {
|
||||
const auto button = ButtonStyleWithRightEmoji(
|
||||
parent,
|
||||
@@ -924,20 +925,20 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
struct State {
|
||||
EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId statusId = 0;
|
||||
EmojiStatusId statusId;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.someCustomChosen(
|
||||
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
|
||||
statusIdChosen(chosen.id, chosen.until);
|
||||
statusIdChosen({ chosen.id }, chosen.until);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto session = &show->session();
|
||||
std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
|
||||
std::move(statusIdValue) | rpl::start_with_next([=](EmojiStatusId id) {
|
||||
state->statusId = id;
|
||||
state->emoji = id
|
||||
? session->data().customEmojiManager().create(
|
||||
id,
|
||||
Data::EmojiStatusCustomId(id),
|
||||
[=] { right->update(); })
|
||||
: nullptr;
|
||||
right->resize(
|
||||
@@ -985,13 +986,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
}, right->lifetime());
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto controller = show->resolveWindow();
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.ensureAddedEmojiId = state->statusId,
|
||||
.ensureAddedEmojiId = { state->statusId },
|
||||
.channelStatusMode = true,
|
||||
});
|
||||
}
|
||||
@@ -1183,7 +1183,7 @@ void EditPeerColorBox(
|
||||
struct State {
|
||||
rpl::variable<uint8> index;
|
||||
rpl::variable<DocumentId> emojiId;
|
||||
rpl::variable<DocumentId> statusId;
|
||||
rpl::variable<EmojiStatusId> statusId;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
bool changing = false;
|
||||
@@ -1263,8 +1263,7 @@ void EditPeerColorBox(
|
||||
{ &st::menuBlueIconWallpaper }
|
||||
);
|
||||
button->setClickedCallback([=] {
|
||||
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
|
||||
if (const auto strong = show->resolveWindow(usage)) {
|
||||
if (const auto strong = show->resolveWindow()) {
|
||||
show->show(Box<BackgroundBox>(strong, channel));
|
||||
}
|
||||
});
|
||||
@@ -1321,7 +1320,7 @@ void EditPeerColorBox(
|
||||
show,
|
||||
channel,
|
||||
state->statusId.value(),
|
||||
[=](DocumentId id, TimeId until) {
|
||||
[=](EmojiStatusId id, TimeId until) {
|
||||
state->statusId = id;
|
||||
state->statusUntil = until;
|
||||
state->statusChanged = true;
|
||||
@@ -1471,8 +1470,7 @@ void CheckBoostLevel(
|
||||
return;
|
||||
}
|
||||
const auto openStatistics = [=] {
|
||||
if (const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo)) {
|
||||
if (const auto controller = show->resolveWindow()) {
|
||||
controller->showSection(Info::Boosts::Make(peer));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1242,7 +1242,9 @@ void Controller::fillManageSection() {
|
||||
? channel->canViewMembers()
|
||||
: chat->amIn();
|
||||
const auto canViewKicked = isChannel
|
||||
&& (channel->isBroadcast() || channel->isGigagroup());
|
||||
&& (channel->isMegagroup()
|
||||
? (channel->isBroadcast() || channel->isGigagroup())
|
||||
: true);
|
||||
const auto hasRecentActions = isChannel
|
||||
&& (channel->hasAdminRights() || channel->amCreator());
|
||||
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)
|
||||
|
||||
@@ -1019,7 +1019,8 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
AddSubscriberEntryTable(controller, content, row->peer(), data.date);
|
||||
const auto show = controller->uiShow();
|
||||
AddSubscriberEntryTable(show, content, {}, row->peer(), data.date);
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_config.h" // megagroupSizeMax
|
||||
#include "apiwrap.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -49,7 +50,6 @@ namespace {
|
||||
|
||||
constexpr auto kSlowmodeValues = 7;
|
||||
constexpr auto kBoostsUnrestrictValues = 5;
|
||||
constexpr auto kSuggestGigagroupThreshold = 199000;
|
||||
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] auto Dependencies(PowerSaving::Flags)
|
||||
@@ -1191,8 +1191,11 @@ void ShowEditPeerPermissionsBox(
|
||||
});
|
||||
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
constexpr auto kThresholdOffset = int(1000);
|
||||
const auto threshold = -kThresholdOffset
|
||||
+ channel->session().serverConfig().megagroupSizeMax;
|
||||
if (channel->amCreator()
|
||||
&& channel->membersCount() >= kSuggestGigagroupThreshold) {
|
||||
&& channel->membersCount() >= threshold) {
|
||||
AddSuggestGigagroup(
|
||||
inner,
|
||||
AboutGigagroupCallback(
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "chat_helpers/emoji_list_widget.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
@@ -491,7 +492,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
panelList.erase(
|
||||
ranges::remove(panelList, paid->selectAnimation->id),
|
||||
end(panelList));
|
||||
panel->selector()->provideRecentEmoji(panelList);
|
||||
panel->selector()->provideRecentEmoji(
|
||||
ChatHelpers::DocumentListToRecent(panelList));
|
||||
panel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
|
||||
@@ -106,6 +106,8 @@ PeerShortInfoCover::PeerShortInfoCover(
|
||||
, _statusStyle(std::make_unique<CustomLabelStyle>(_st.status))
|
||||
, _status(_widget.get(), std::move(status), _statusStyle->st)
|
||||
, _roundMask(Images::CornersMask(_st.radius))
|
||||
, _roundMaskRetina(
|
||||
Images::CornersMask(_st.radius / style::DevicePixelRatio()))
|
||||
, _videoPaused(std::move(videoPaused)) {
|
||||
_widget->setCursor(_cursor);
|
||||
|
||||
@@ -190,7 +192,7 @@ void PeerShortInfoCover::paint(QPainter &p) {
|
||||
if (!frame.isNull()) {
|
||||
frame = Images::Round(
|
||||
std::move(frame),
|
||||
_roundMask,
|
||||
_roundMaskRetina,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
} else if (_userpicImage.isNull()) {
|
||||
auto image = QImage(
|
||||
@@ -226,10 +228,11 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
|
||||
const auto top = _widget->height() - fill;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
if (fill > 0) {
|
||||
const auto t = roundedHeight + _scrollTop;
|
||||
p.drawImage(
|
||||
QRect(0, top, roundedWidth, fill),
|
||||
QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor),
|
||||
image,
|
||||
QRect(0, top * factor, roundedWidth * factor, fill * factor));
|
||||
QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor));
|
||||
}
|
||||
if (covered <= 0) {
|
||||
return;
|
||||
@@ -238,9 +241,9 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
|
||||
const auto from = top - rounded;
|
||||
auto q = QPainter(&_roundedTopImage);
|
||||
q.drawImage(
|
||||
QRect(0, 0, roundedWidth, rounded),
|
||||
QRect(0, 0, roundedWidth * factor, rounded * factor),
|
||||
image,
|
||||
QRect(0, from * factor, roundedWidth * factor, rounded * factor));
|
||||
QRect(0, _scrollTop, roundedWidth * factor, rounded * factor));
|
||||
q.end();
|
||||
_roundedTopImage = Images::Round(
|
||||
std::move(_roundedTopImage),
|
||||
|
||||
@@ -123,6 +123,7 @@ private:
|
||||
object_ptr<Ui::FlatLabel> _additionalStatus = { nullptr };
|
||||
|
||||
std::array<QImage, 4> _roundMask;
|
||||
std::array<QImage, 4> _roundMaskRetina;
|
||||
QImage _userpicImage;
|
||||
QImage _roundedTopImage;
|
||||
QImage _barSmall;
|
||||
|
||||
@@ -486,20 +486,23 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const style::ShortInfoBox *stOverride) {
|
||||
const auto open = [=] { navigation->showPeerHistory(peer); };
|
||||
const auto open = [=] {
|
||||
if (const auto window = show->resolveWindow()) {
|
||||
window->showPeerHistory(peer);
|
||||
}
|
||||
};
|
||||
const auto videoIsPaused = [=] {
|
||||
return navigation->parentController()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
return show->paused(Window::GifPauseReason::Layer);
|
||||
};
|
||||
auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
|
||||
const auto controller = navigation->parentController();
|
||||
const auto peerSeparateId = Window::SeparateId(peer);
|
||||
if (controller->windowId() != peerSeparateId) {
|
||||
const auto window = show->resolveWindow();
|
||||
if (window && window->windowId() != peerSeparateId) {
|
||||
addAction(tr::lng_context_new_window(tr::now), [=] {
|
||||
Ui::PreventDelayedActivation();
|
||||
controller->showInNewWindow(peer);
|
||||
window->showInNewWindow(peer);
|
||||
}, &st::menuIconNewWindow);
|
||||
}
|
||||
};
|
||||
@@ -511,6 +514,13 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
stOverride);
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
const style::ShortInfoBox *stOverride) {
|
||||
return PrepareShortInfoBox(peer, navigation->uiShow(), stOverride);
|
||||
}
|
||||
|
||||
rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
|
||||
return StatusValue(peer);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ struct ShortInfoCover;
|
||||
struct ShortInfoBox;
|
||||
} // namespace style
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui::Menu {
|
||||
struct MenuCallback;
|
||||
} // namespace Ui::Menu
|
||||
@@ -42,6 +46,11 @@ struct PreparedShortInfoUserpic {
|
||||
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
|
||||
const style::ShortInfoBox *stOverride = nullptr);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const style::ShortInfoBox *stOverride = nullptr);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
|
||||
@@ -1139,8 +1139,7 @@ void PreviewBox(
|
||||
button->resizeToWidth(width);
|
||||
if (!descriptor.fromSettings) {
|
||||
button->setClickedCallback([=] {
|
||||
const auto window = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto window = show->resolveWindow();
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
@@ -1527,6 +1526,18 @@ void DoubledLimitsPreviewBox(
|
||||
Main::Domain::kPremiumMaxAccounts,
|
||||
till,
|
||||
});
|
||||
{
|
||||
const auto premium = limits.similarChannelsPremium();
|
||||
entries.push_back({
|
||||
tr::lng_premium_double_limits_subtitle_similar_channels(),
|
||||
tr::lng_premium_double_limits_about_similar_channels(
|
||||
lt_count,
|
||||
rpl::single(float64(premium)),
|
||||
Ui::Text::RichLangValue),
|
||||
limits.similarChannelsDefault(),
|
||||
premium,
|
||||
});
|
||||
}
|
||||
Ui::Premium::ShowListBox(
|
||||
box,
|
||||
st::defaultPremiumLimits,
|
||||
|
||||
@@ -250,7 +250,7 @@ SendFilesBox::Block::Block(
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
|
||||
: _items(items)
|
||||
, _from(from)
|
||||
, _till(till) {
|
||||
@@ -268,7 +268,9 @@ SendFilesBox::Block::Block(
|
||||
st,
|
||||
my,
|
||||
way,
|
||||
std::move(canToggleSpoiler));
|
||||
[=](int index, Ui::AttachActionType type) {
|
||||
return actionAllowed((*_items)[from + index], type);
|
||||
});
|
||||
_preview.reset(preview);
|
||||
} else {
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
@@ -276,7 +278,9 @@ SendFilesBox::Block::Block(
|
||||
st,
|
||||
gifPaused,
|
||||
first,
|
||||
std::move(canToggleSpoiler));
|
||||
[=](Ui::AttachActionType type) {
|
||||
return actionAllowed((*_items)[from], type);
|
||||
});
|
||||
if (media) {
|
||||
_isSingleMedia = true;
|
||||
_preview.reset(media);
|
||||
@@ -352,6 +356,38 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SendFilesBox::Block::itemEditCoverRequest() 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->thumbEditCoverRequested() | rpl::map(_1 + from);
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->editCoverRequests() | rpl::map_to(from);
|
||||
} else {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SendFilesBox::Block::itemClearCoverRequest() 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->thumbClearCoverRequested() | rpl::map(_1 + from);
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->clearCoverRequests() | rpl::map_to(from);
|
||||
} else {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
@@ -1046,7 +1082,16 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
till,
|
||||
gifPaused,
|
||||
_sendWay.current(),
|
||||
[=] { return !hasPrice(); });
|
||||
[=](const Ui::PreparedFile &file, Ui::AttachActionType type) {
|
||||
return (type == Ui::AttachActionType::ToggleSpoiler)
|
||||
? !hasPrice()
|
||||
: (type == Ui::AttachActionType::EditCover)
|
||||
? (file.isVideoFile()
|
||||
&& _captionToPeer
|
||||
&& (_captionToPeer->isBroadcast()
|
||||
|| _captionToPeer->isSelf()))
|
||||
: (file.videoCover != nullptr);
|
||||
});
|
||||
auto &block = _blocks.back();
|
||||
const auto widget = _inner->add(
|
||||
block.takeWidget(),
|
||||
@@ -1167,7 +1212,79 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
show,
|
||||
&_list.files[index],
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { refreshAllAfterChanges(from); });
|
||||
[=](bool ok) { if (ok) refreshAllAfterChanges(from); });
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemEditCoverRequest(
|
||||
) | rpl::start_with_next([=, show = _show](int index) {
|
||||
applyBlockChanges();
|
||||
|
||||
const auto replace = [=](Ui::PreparedList list) {
|
||||
if (list.files.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &entry = _list.files[index];
|
||||
const auto video = entry.information
|
||||
? std::get_if<Ui::PreparedFileInformation::Video>(
|
||||
&entry.information->media)
|
||||
: nullptr;
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
auto old = std::shared_ptr<Ui::PreparedFile>(
|
||||
std::move(entry.videoCover));
|
||||
entry.videoCover = std::make_unique<Ui::PreparedFile>(
|
||||
std::move(list.files.front()));
|
||||
Editor::OpenWithPreparedFile(
|
||||
this,
|
||||
show,
|
||||
entry.videoCover.get(),
|
||||
st::sendMediaPreviewSize,
|
||||
crl::guard(this, [=](bool ok) {
|
||||
if (!ok) {
|
||||
_list.files[index].videoCover = old
|
||||
? std::make_unique<Ui::PreparedFile>(
|
||||
std::move(*old))
|
||||
: nullptr;
|
||||
}
|
||||
refreshAllAfterChanges(from);
|
||||
}),
|
||||
video->thumbnail.size());
|
||||
};
|
||||
const auto checkResult = [=](const Ui::PreparedList &list) {
|
||||
if (list.files.empty()) {
|
||||
return true;
|
||||
}
|
||||
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
|
||||
show->showToast(tr::lng_choose_cover_bad(tr::now));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||
const auto premium = _show->session().premium();
|
||||
FileDialogCallback(
|
||||
std::move(result),
|
||||
checkResult,
|
||||
replace,
|
||||
premium,
|
||||
show);
|
||||
};
|
||||
|
||||
FileDialog::GetOpenPath(
|
||||
this,
|
||||
tr::lng_choose_cover(tr::now),
|
||||
FileDialog::ImagesFilter(),
|
||||
crl::guard(this, callback));
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemClearCoverRequest(
|
||||
) | rpl::start_with_next([=](int index) {
|
||||
applyBlockChanges();
|
||||
refreshAllAfterChanges(from, [&] {
|
||||
auto &entry = _list.files[index];
|
||||
entry.videoCover = nullptr;
|
||||
});
|
||||
}, widget->lifetime());
|
||||
|
||||
block.orderUpdated() | rpl::start_with_next([=]{
|
||||
|
||||
@@ -153,7 +153,9 @@ private:
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
Ui::SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Fn<bool(
|
||||
const Ui::PreparedFile &,
|
||||
Ui::AttachActionType)> actionAllowed);
|
||||
Block(Block &&other) = default;
|
||||
Block &operator=(Block &&other) = default;
|
||||
|
||||
@@ -164,6 +166,8 @@ private:
|
||||
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemEditCoverRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemClearCoverRequest() const;
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
|
||||
@@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
@@ -202,16 +203,16 @@ ShareBox::ShareBox(QWidget*, Descriptor &&descriptor)
|
||||
, _api(&_descriptor.session->mtp())
|
||||
, _select(
|
||||
this,
|
||||
(_descriptor.stMultiSelect
|
||||
? *_descriptor.stMultiSelect
|
||||
(_descriptor.st.multiSelect
|
||||
? *_descriptor.st.multiSelect
|
||||
: st::defaultMultiSelect),
|
||||
tr::lng_participant_filter())
|
||||
, _comment(
|
||||
this,
|
||||
object_ptr<Ui::InputField>(
|
||||
this,
|
||||
(_descriptor.stComment
|
||||
? *_descriptor.stComment
|
||||
(_descriptor.st.comment
|
||||
? *_descriptor.st.comment
|
||||
: st::shareComment),
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_photos_comment()),
|
||||
@@ -256,7 +257,7 @@ void ShareBox::prepareCommentField() {
|
||||
.session = _descriptor.session,
|
||||
.show = Main::MakeSessionShow(show, _descriptor.session),
|
||||
.field = field,
|
||||
.fieldStyle = _descriptor.stLabel,
|
||||
.fieldStyle = _descriptor.st.label,
|
||||
});
|
||||
}
|
||||
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
@@ -281,7 +282,9 @@ void ShareBox::prepare() {
|
||||
_select->resizeToWidth(st::boxWideWidth);
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
|
||||
setTitle(tr::lng_share_title());
|
||||
setTitle(_descriptor.titleOverride
|
||||
? std::move(_descriptor.titleOverride)
|
||||
: tr::lng_share_title());
|
||||
|
||||
_inner = setInnerWidget(
|
||||
object_ptr<Inner>(this, _descriptor, uiShow()),
|
||||
@@ -566,6 +569,9 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
submit(action.options);
|
||||
return;
|
||||
}
|
||||
const auto st = _descriptor.st.scheduleBox
|
||||
? *_descriptor.st.scheduleBox
|
||||
: HistoryView::ScheduleBoxStyleArgs();
|
||||
uiShow()->showBox(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
@@ -574,7 +580,7 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
[=](Api::SendOptions options) { submit(options); },
|
||||
action.options,
|
||||
HistoryView::DefaultScheduleTime(),
|
||||
_descriptor.scheduleBoxStyle));
|
||||
st));
|
||||
});
|
||||
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
const auto result = FillSendMenu(
|
||||
@@ -709,7 +715,9 @@ ShareBox::Inner::Inner(
|
||||
: RpWidget(parent)
|
||||
, _descriptor(descriptor)
|
||||
, _show(std::move(show))
|
||||
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
|
||||
, _st(_descriptor.st.peerList
|
||||
? *_descriptor.st.peerList
|
||||
: st::shareBoxList)
|
||||
, _defaultChatsIndexed(
|
||||
std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add))
|
||||
@@ -1494,7 +1502,8 @@ ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
|
||||
ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds) {
|
||||
MessageIdsList msgIds,
|
||||
std::optional<TimeId> videoTimestamp) {
|
||||
struct State final {
|
||||
base::flat_set<mtpRequestId> requests;
|
||||
};
|
||||
@@ -1530,6 +1539,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
: Flag(0))
|
||||
| ((forwardOptions == Data::ForwardOptions::NoNamesAndCaptions)
|
||||
? Flag::f_drop_media_captions
|
||||
: Flag(0))
|
||||
| (videoTimestamp.has_value()
|
||||
? Flag::f_video_timestamp
|
||||
: Flag(0));
|
||||
auto mtpMsgIds = QVector<MTPint>();
|
||||
mtpMsgIds.reserve(existingIds.size());
|
||||
@@ -1586,7 +1598,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
MTP_int(topMsgId),
|
||||
MTP_int(options.scheduled),
|
||||
MTP_inputPeerEmpty(), // send_as
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId)
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||
MTP_int(videoTimestamp.value_or(0))
|
||||
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
|
||||
threadHistory->session().api().applyUpdates(updates);
|
||||
state->requests.remove(reqId);
|
||||
@@ -1624,9 +1637,37 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
};
|
||||
}
|
||||
|
||||
ShareBoxStyleOverrides DarkShareBoxStyle() {
|
||||
using namespace HistoryView;
|
||||
|
||||
const auto schedule = [&] {
|
||||
auto date = Ui::ChooseDateTimeStyleArgs();
|
||||
date.labelStyle = &st::groupCallBoxLabel;
|
||||
date.dateFieldStyle = &st::groupCallScheduleDateField;
|
||||
date.timeFieldStyle = &st::groupCallScheduleTimeField;
|
||||
date.separatorStyle = &st::callMuteButtonLabel;
|
||||
date.atStyle = &st::callMuteButtonLabel;
|
||||
date.calendarStyle = &st::groupCallCalendarColors;
|
||||
|
||||
auto st = ScheduleBoxStyleArgs();
|
||||
st.topButtonStyle = &st::groupCallMenuToggle;
|
||||
st.popupMenuStyle = &st::groupCallPopupMenu;
|
||||
st.chooseDateTimeArgs = std::move(date);
|
||||
return st;
|
||||
};
|
||||
return {
|
||||
.multiSelect = &st::groupCallMultiSelect,
|
||||
.comment = &st::groupCallShareBoxComment,
|
||||
.peerList = &st::groupCallShareBoxList,
|
||||
.label = &st::groupCallField,
|
||||
.scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
|
||||
};
|
||||
}
|
||||
|
||||
void FastShareMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item) {
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st) {
|
||||
const auto history = item->history();
|
||||
const auto owner = &history->owner();
|
||||
const auto session = &history->session();
|
||||
@@ -1695,6 +1736,7 @@ void FastShareMessage(
|
||||
history,
|
||||
msgIds),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.st = st,
|
||||
.forwardOptions = {
|
||||
.sendersCount = ItemsForwardSendersCount(items),
|
||||
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||
@@ -1706,19 +1748,22 @@ void FastShareMessage(
|
||||
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
FastShareMessage(controller->uiShow(), item);
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st) {
|
||||
FastShareMessage(controller->uiShow(), item, st);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url) {
|
||||
FastShareLink(controller->uiShow(), url);
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st) {
|
||||
FastShareLink(controller->uiShow(), url, st);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url) {
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st) {
|
||||
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
const auto sending = std::make_shared<bool>();
|
||||
auto copyCallback = [=] {
|
||||
@@ -1782,6 +1827,7 @@ void FastShareLink(
|
||||
.copyCallback = std::move(copyCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.st = st,
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
}),
|
||||
Ui::LayerOption::KeepOther,
|
||||
|
||||
@@ -61,18 +61,31 @@ class PopupMenu;
|
||||
|
||||
class ShareBox;
|
||||
|
||||
struct ShareBoxStyleOverrides {
|
||||
const style::MultiSelect *multiSelect = nullptr;
|
||||
const style::InputField *comment = nullptr;
|
||||
const style::PeerList *peerList = nullptr;
|
||||
const style::InputField *label = nullptr;
|
||||
std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox;
|
||||
};
|
||||
[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle();
|
||||
|
||||
void FastShareMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item);
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url);
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url);
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
|
||||
struct RecipientPremiumRequiredError;
|
||||
[[nodiscard]] auto SharePremiumRequiredError()
|
||||
@@ -91,7 +104,8 @@ public:
|
||||
[[nodiscard]] static SubmitCallback DefaultForwardCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds);
|
||||
MessageIdsList msgIds,
|
||||
std::optional<TimeId> videoTimestamp = {});
|
||||
|
||||
struct Descriptor {
|
||||
not_null<Main::Session*> session;
|
||||
@@ -100,16 +114,14 @@ public:
|
||||
FilterCallback filterCallback;
|
||||
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
|
||||
rpl::producer<QString> copyLinkText;
|
||||
const style::MultiSelect *stMultiSelect = nullptr;
|
||||
const style::InputField *stComment = nullptr;
|
||||
const style::PeerList *st = nullptr;
|
||||
const style::InputField *stLabel = nullptr;
|
||||
rpl::producer<QString> titleOverride;
|
||||
ShareBoxStyleOverrides st;
|
||||
std::optional<TimeId> videoTimestamp;
|
||||
struct {
|
||||
int sendersCount = 0;
|
||||
int captionsCount = 0;
|
||||
bool show = false;
|
||||
} forwardOptions;
|
||||
HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
|
||||
|
||||
using PremiumRequiredError = RecipientPremiumRequiredError;
|
||||
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
|
||||
|
||||
@@ -8,14 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/star_gift_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/random.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "boxes/filters/edit_filter_chats_list.h"
|
||||
#include "boxes/peers/edit_peer_color_box.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/send_credits_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
@@ -24,10 +27,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
@@ -51,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_credits.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
@@ -70,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/section_widget.h"
|
||||
@@ -82,6 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
@@ -89,8 +99,8 @@ namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPriceTabAll = 0;
|
||||
constexpr auto kPriceTabLimited = -1;
|
||||
constexpr auto kPriceTabInStock = -2;
|
||||
constexpr auto kPriceTabLimited = -2;
|
||||
constexpr auto kPriceTabInStock = -1;
|
||||
constexpr auto kGiftMessageLimit = 255;
|
||||
constexpr auto kSentToastDuration = 3 * crl::time(1000);
|
||||
constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
|
||||
@@ -206,8 +216,12 @@ auto GenerateGiftMedia(
|
||||
Element *replacing,
|
||||
not_null<PeerData*> recipient,
|
||||
const GiftDetails &data)
|
||||
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
|
||||
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
|
||||
-> Fn<void(
|
||||
not_null<MediaGeneric*>,
|
||||
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
|
||||
return [=](
|
||||
not_null<MediaGeneric*> media,
|
||||
Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
|
||||
const auto &descriptor = data.descriptor;
|
||||
auto pushText = [&](
|
||||
TextWithEntities text,
|
||||
@@ -241,18 +255,26 @@ auto GenerateGiftMedia(
|
||||
replacing,
|
||||
sticker,
|
||||
st::giftBoxPreviewStickerPadding));
|
||||
const auto title = v::match(descriptor, [&](GiftTypePremium gift) {
|
||||
auto title = v::match(descriptor, [&](GiftTypePremium gift) {
|
||||
return tr::lng_action_gift_premium_months(
|
||||
tr::now,
|
||||
lt_count,
|
||||
gift.months);
|
||||
gift.months,
|
||||
Text::Bold);
|
||||
}, [&](const GiftTypeStars &gift) {
|
||||
return recipient->isSelf()
|
||||
? tr::lng_action_gift_self_subtitle(tr::now)
|
||||
? tr::lng_action_gift_self_subtitle(tr::now, Text::Bold)
|
||||
: tr::lng_action_gift_got_subtitle(
|
||||
tr::now,
|
||||
lt_user,
|
||||
recipient->session().user()->shortName());
|
||||
TextWithEntities()
|
||||
.append(Text::SingleCustomEmoji(
|
||||
recipient->owner().customEmojiManager(
|
||||
).peerUserpicEmojiData(
|
||||
recipient->session().user())))
|
||||
.append(' ')
|
||||
.append(recipient->session().user()->shortName()),
|
||||
Text::Bold);
|
||||
});
|
||||
auto textFallback = v::match(descriptor, [&](GiftTypePremium gift) {
|
||||
return tr::lng_action_gift_premium_about(
|
||||
@@ -267,8 +289,14 @@ auto GenerateGiftMedia(
|
||||
? tr::lng_action_gift_self_about_unique(
|
||||
tr::now,
|
||||
Text::RichLangValue)
|
||||
: (recipient->isBroadcast() && gift.info.starsToUpgrade)
|
||||
? tr::lng_action_gift_channel_about_unique(
|
||||
tr::now,
|
||||
Text::RichLangValue)
|
||||
: (recipient->isSelf()
|
||||
? tr::lng_action_gift_self_about
|
||||
: recipient->isBroadcast()
|
||||
? tr::lng_action_gift_channel_about
|
||||
: tr::lng_action_gift_got_stars_text)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
@@ -278,15 +306,20 @@ auto GenerateGiftMedia(
|
||||
auto description = data.text.empty()
|
||||
? std::move(textFallback)
|
||||
: data.text;
|
||||
pushText(Text::Bold(title), st::giftBoxPreviewTitlePadding);
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = &parent->history()->session(),
|
||||
.customEmojiRepaint = [parent] { parent->repaint(); },
|
||||
};
|
||||
pushText(
|
||||
std::move(title),
|
||||
st::giftBoxPreviewTitlePadding,
|
||||
{},
|
||||
context);
|
||||
pushText(
|
||||
std::move(description),
|
||||
st::giftBoxPreviewTextPadding,
|
||||
{},
|
||||
Core::MarkedTextContext{
|
||||
.session = &parent->history()->session(),
|
||||
.customEmojiRepaint = [parent] { parent->repaint(); },
|
||||
});
|
||||
context);
|
||||
|
||||
push(HistoryView::MakeGenericButtonPart(
|
||||
(data.upgraded
|
||||
@@ -475,6 +508,15 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
|
||||
? tr::lng_action_gift_unique_received(tr::now, lt_user, name)
|
||||
: _recipient->isSelf()
|
||||
? tr::lng_action_gift_self_bought(tr::now, lt_cost, cost)
|
||||
: _recipient->isBroadcast()
|
||||
? tr::lng_action_gift_sent_channel(
|
||||
tr::now,
|
||||
lt_user,
|
||||
name,
|
||||
lt_name,
|
||||
_recipient->name(),
|
||||
lt_cost,
|
||||
cost)
|
||||
: tr::lng_action_gift_received(
|
||||
tr::now,
|
||||
lt_user,
|
||||
@@ -652,6 +694,21 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
||||
for (auto &gift : gifts) {
|
||||
list.push_back({ .info = gift });
|
||||
}
|
||||
ranges::sort(list, [](
|
||||
const GiftTypeStars &a,
|
||||
const GiftTypeStars &b) {
|
||||
if (!a.info.limitedCount && !b.info.limitedCount) {
|
||||
return a.info.stars <= b.info.stars;
|
||||
} else if (!a.info.limitedCount) {
|
||||
return true;
|
||||
} else if (!b.info.limitedCount) {
|
||||
return false;
|
||||
} else if (a.info.limitedLeft != b.info.limitedLeft) {
|
||||
return a.info.limitedLeft > b.info.limitedLeft;
|
||||
}
|
||||
return a.info.stars <= b.info.stars;
|
||||
});
|
||||
|
||||
auto &map = Map[session];
|
||||
if (map.last != list) {
|
||||
map.last = list;
|
||||
@@ -1060,7 +1117,7 @@ void SendGift(
|
||||
.giftId = gift.info.id,
|
||||
.randomId = details.randomId,
|
||||
.message = details.text,
|
||||
.user = peer->asUser(),
|
||||
.recipient = peer,
|
||||
.limitedCount = gift.info.limitedCount,
|
||||
.anonymous = details.anonymous,
|
||||
.upgraded = details.upgraded,
|
||||
@@ -1145,7 +1202,7 @@ void SendStarsFormRequest(
|
||||
|
||||
void UpgradeGift(
|
||||
not_null<Window::SessionController*> window,
|
||||
MsgId messageId,
|
||||
Data::SavedStarGiftId savedId,
|
||||
bool keepDetails,
|
||||
int stars,
|
||||
Fn<void(Payments::CheckoutResult)> done) {
|
||||
@@ -1165,7 +1222,7 @@ void UpgradeGift(
|
||||
using Flag = MTPpayments_UpgradeStarGift::Flag;
|
||||
session->api().request(MTPpayments_UpgradeStarGift(
|
||||
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
|
||||
MTP_int(messageId.bare)
|
||||
Api::InputSavedStarGiftId(savedId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
session->api().applyUpdates(result);
|
||||
formDone(Payments::CheckoutResult::Paid, &result);
|
||||
@@ -1182,7 +1239,7 @@ void UpgradeGift(
|
||||
window,
|
||||
MTP_inputInvoiceStarGiftUpgrade(
|
||||
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
|
||||
MTP_int(messageId.bare)),
|
||||
Api::InputSavedStarGiftId(savedId)),
|
||||
std::move(formDone));
|
||||
}
|
||||
|
||||
@@ -1211,7 +1268,7 @@ void AddUpgradeButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
int cost,
|
||||
QString name,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(bool)> toggled,
|
||||
Fn<void()> preview) {
|
||||
const auto button = container->add(
|
||||
@@ -1258,25 +1315,105 @@ void AddUpgradeButton(
|
||||
AddSkip(container);
|
||||
const auto about = AddDividerText(
|
||||
container,
|
||||
tr::lng_gift_send_unique_about(
|
||||
lt_user,
|
||||
rpl::single(TextWithEntities{ name }),
|
||||
lt_link,
|
||||
tr::lng_gift_send_unique_link() | Text::ToLink(),
|
||||
Text::WithEntities));
|
||||
(peer->isBroadcast()
|
||||
? tr::lng_gift_send_unique_about_channel(
|
||||
lt_name,
|
||||
rpl::single(TextWithEntities{ peer->name() }),
|
||||
lt_link,
|
||||
tr::lng_gift_send_unique_link() | Text::ToLink(),
|
||||
Text::WithEntities)
|
||||
: tr::lng_gift_send_unique_about(
|
||||
lt_user,
|
||||
rpl::single(TextWithEntities{ peer->shortName() }),
|
||||
lt_link,
|
||||
tr::lng_gift_send_unique_link() | Text::ToLink(),
|
||||
Text::WithEntities)));
|
||||
about->setClickHandlerFilter([=](const auto &...) {
|
||||
preview();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void AddSoldLeftSlider(
|
||||
not_null<RoundButton*> button,
|
||||
const GiftTypeStars &gift) {
|
||||
const auto still = gift.info.limitedLeft;
|
||||
const auto total = gift.info.limitedCount;
|
||||
const auto slider = CreateChild<RpWidget>(button->parentWidget());
|
||||
struct State {
|
||||
Text::String still;
|
||||
Text::String sold;
|
||||
int height = 0;
|
||||
};
|
||||
const auto state = slider->lifetime().make_state<State>();
|
||||
const auto sold = total - still;
|
||||
state->still.setText(
|
||||
st::semiboldTextStyle,
|
||||
tr::lng_gift_send_limited_left(tr::now, lt_count_decimal, still));
|
||||
state->sold.setText(
|
||||
st::semiboldTextStyle,
|
||||
tr::lng_gift_send_limited_sold(tr::now, lt_count_decimal, sold));
|
||||
state->height = st::giftLimitedPadding.top()
|
||||
+ st::semiboldFont->height
|
||||
+ st::giftLimitedPadding.bottom();
|
||||
button->geometryValue() | rpl::start_with_next([=](QRect geometry) {
|
||||
const auto space = st::giftLimitedBox.buttonPadding.top();
|
||||
const auto skip = (space - state->height) / 2;
|
||||
slider->setGeometry(
|
||||
geometry.x(),
|
||||
geometry.y() - skip - state->height,
|
||||
geometry.width(),
|
||||
state->height);
|
||||
}, slider->lifetime());
|
||||
slider->paintRequest() | rpl::start_with_next([=] {
|
||||
const auto &padding = st::giftLimitedPadding;
|
||||
const auto left = (padding.left() * 2) + state->still.maxWidth();
|
||||
const auto right = (padding.right() * 2) + state->sold.maxWidth();
|
||||
const auto space = slider->width() - left - right;
|
||||
if (space <= 0) {
|
||||
return;
|
||||
}
|
||||
const auto edge = left + ((space * still) / total);
|
||||
|
||||
const auto radius = st::buttonRadius;
|
||||
auto p = QPainter(slider);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBgOver);
|
||||
p.drawRoundedRect(
|
||||
edge - (radius * 3),
|
||||
0,
|
||||
slider->width() - (edge - (radius * 3)),
|
||||
state->height,
|
||||
radius,
|
||||
radius);
|
||||
p.setBrush(st::windowBgActive);
|
||||
p.drawRoundedRect(0, 0, edge, state->height, radius, radius);
|
||||
|
||||
p.setPen(st::windowFgActive);
|
||||
state->still.draw(p, {
|
||||
.position = { padding.left(), padding.top() },
|
||||
.availableWidth = left,
|
||||
});
|
||||
p.setPen(st::windowSubTextFg);
|
||||
state->sold.draw(p, {
|
||||
.position = { left + space + padding.right(), padding.top() },
|
||||
.availableWidth = right,
|
||||
});
|
||||
}, slider->lifetime());
|
||||
}
|
||||
|
||||
void SendGiftBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
|
||||
const GiftDescriptor &descriptor) {
|
||||
box->setStyle(st::giftBox);
|
||||
const auto stars = std::get_if<GiftTypeStars>(&descriptor);
|
||||
const auto limited = stars
|
||||
&& (stars->info.limitedCount > stars->info.limitedLeft)
|
||||
&& (stars->info.limitedLeft > 0);
|
||||
box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setTitle(tr::lng_gift_send_title());
|
||||
box->addTopButton(st::boxTitleClose, [=] {
|
||||
@@ -1373,18 +1510,14 @@ void SendGiftBox(
|
||||
session,
|
||||
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
|
||||
|
||||
if (const auto stars = std::get_if<GiftTypeStars>(&descriptor)) {
|
||||
if (stars) {
|
||||
const auto cost = stars->info.starsToUpgrade;
|
||||
if (cost > 0 && !peer->isSelf()) {
|
||||
const auto user = peer->asUser();
|
||||
Assert(user != nullptr);
|
||||
|
||||
const auto id = stars->info.id;
|
||||
const auto name = user->shortName();
|
||||
const auto showing = std::make_shared<bool>();
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
AddUpgradeButton(container, session, cost, name, [=](bool on) {
|
||||
AddUpgradeButton(container, session, cost, peer, [=](bool on) {
|
||||
auto now = state->details.current();
|
||||
now.upgraded = on;
|
||||
state->details = std::move(now);
|
||||
@@ -1397,7 +1530,7 @@ void SendGiftBox(
|
||||
.controller = window,
|
||||
.stargiftId = id,
|
||||
.ready = [=](bool) { *showing = false; },
|
||||
.user = user,
|
||||
.peer = peer,
|
||||
.cost = int(cost),
|
||||
});
|
||||
});
|
||||
@@ -1425,6 +1558,8 @@ void SendGiftBox(
|
||||
}, [&](const GiftTypeStars &) {
|
||||
AddDividerText(container, peer->isSelf()
|
||||
? tr::lng_gift_send_anonymous_self()
|
||||
: peer->isBroadcast()
|
||||
? tr::lng_gift_send_anonymous_about_channel()
|
||||
: tr::lng_gift_send_anonymous_about(
|
||||
lt_user,
|
||||
rpl::single(peer->shortName()),
|
||||
@@ -1454,6 +1589,9 @@ void SendGiftBox(
|
||||
};
|
||||
SendGift(window, peer, api, details, done);
|
||||
});
|
||||
if (limited) {
|
||||
AddSoldLeftSlider(button, *stars);
|
||||
}
|
||||
SetButtonMarkedLabel(
|
||||
button,
|
||||
(peer->isSelf()
|
||||
@@ -1704,7 +1842,7 @@ void GiftBox(
|
||||
window->showSettings(Settings::CreditsId());
|
||||
return false;
|
||||
};
|
||||
if (!peer->isSelf()) {
|
||||
if (peer->isUser() && !peer->isSelf()) {
|
||||
const auto premiumClickHandlerFilter = [=](const auto &...) {
|
||||
Settings::ShowPremium(window, u"gift_send"_q);
|
||||
return false;
|
||||
@@ -1724,9 +1862,16 @@ void GiftBox(
|
||||
AddBlock(content, window, {
|
||||
.subtitle = (peer->isSelf()
|
||||
? tr::lng_gift_self_title()
|
||||
: peer->isBroadcast()
|
||||
? tr::lng_gift_channel_title()
|
||||
: tr::lng_gift_stars_subtitle()),
|
||||
.about = (peer->isSelf()
|
||||
? tr::lng_gift_self_about(Text::WithEntities)
|
||||
: peer->isBroadcast()
|
||||
? tr::lng_gift_channel_about(
|
||||
lt_name,
|
||||
rpl::single(Text::Bold(peer->name())),
|
||||
Text::WithEntities)
|
||||
: tr::lng_gift_stars_about(
|
||||
lt_name,
|
||||
rpl::single(Text::Bold(peer->shortName())),
|
||||
@@ -2112,6 +2257,242 @@ void AddUniqueGiftCover(
|
||||
}, cover->lifetime());
|
||||
}
|
||||
|
||||
void AddWearGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
const Data::UniqueGift &data,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto cover = container->add(object_ptr<RpWidget>(container));
|
||||
|
||||
const auto title = CreateChild<FlatLabel>(
|
||||
cover,
|
||||
rpl::single(peer->name()),
|
||||
st::uniqueGiftTitle);
|
||||
title->setTextColorOverride(QColor(255, 255, 255));
|
||||
const auto subtitle = CreateChild<FlatLabel>(
|
||||
cover,
|
||||
(peer->isChannel()
|
||||
? tr::lng_chat_status_subscribers(
|
||||
lt_count,
|
||||
rpl::single(peer->asChannel()->membersCount() * 1.))
|
||||
: tr::lng_status_online()),
|
||||
st::uniqueGiftSubtitle);
|
||||
subtitle->setTextColorOverride(data.backdrop.textColor);
|
||||
|
||||
struct State {
|
||||
QImage gradient;
|
||||
Data::UniqueGift gift;
|
||||
Ui::PeerUserpicView view;
|
||||
std::unique_ptr<Text::CustomEmoji> emoji;
|
||||
base::flat_map<float64, QImage> emojis;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
const auto state = cover->lifetime().make_state<State>(State{
|
||||
.gift = data,
|
||||
});
|
||||
state->emoji = peer->owner().customEmojiManager().create(
|
||||
state->gift.pattern.document,
|
||||
[=] { cover->update(); },
|
||||
Data::CustomEmojiSizeTag::Large);
|
||||
|
||||
cover->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto skip = st::uniqueGiftBottom;
|
||||
if (width <= 3 * skip) {
|
||||
return;
|
||||
}
|
||||
const auto available = width - 2 * skip;
|
||||
title->resizeToWidth(available);
|
||||
title->moveToLeft(skip, st::uniqueGiftTitleTop);
|
||||
|
||||
subtitle->resizeToWidth(available);
|
||||
subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
|
||||
|
||||
cover->resize(width, subtitle->y() + subtitle->height() + skip);
|
||||
}, cover->lifetime());
|
||||
|
||||
cover->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = Painter(cover);
|
||||
|
||||
const auto width = cover->width();
|
||||
const auto pointsHeight = st::uniqueGiftSubtitleTop;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (state->gradient.size() != cover->size() * ratio) {
|
||||
state->gradient = CreateGradient(cover->size(), state->gift);
|
||||
}
|
||||
p.drawImage(0, 0, state->gradient);
|
||||
|
||||
PaintPoints(
|
||||
p,
|
||||
PatternPoints(),
|
||||
state->emojis,
|
||||
state->emoji.get(),
|
||||
state->gift,
|
||||
QRect(0, 0, width, pointsHeight),
|
||||
1.);
|
||||
|
||||
peer->paintUserpic(
|
||||
p,
|
||||
state->view,
|
||||
(width - st::uniqueGiftUserpicSize) / 2,
|
||||
st::uniqueGiftUserpicTop,
|
||||
st::uniqueGiftUserpicSize);
|
||||
}, cover->lifetime());
|
||||
}
|
||||
|
||||
void ShowUniqueGiftWearBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const Data::UniqueGift &gift,
|
||||
Settings::GiftWearBoxStyleOverride st) {
|
||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
box->setWidth((st::boxWidth + st::boxWideWidth) / 2); // =)
|
||||
box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
|
||||
|
||||
const auto channel = peer->isChannel();
|
||||
const auto content = box->verticalLayout();
|
||||
AddWearGiftCover(content, gift, peer);
|
||||
|
||||
AddSkip(content, st::defaultVerticalListSkip * 2);
|
||||
|
||||
const auto infoRow = [&](
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> text,
|
||||
not_null<const style::icon*> icon) {
|
||||
auto raw = content->add(
|
||||
object_ptr<Ui::VerticalLayout>(content));
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(title) | Ui::Text::ToBold(),
|
||||
st.infoTitle ? *st.infoTitle : st::defaultFlatLabel),
|
||||
st::settingsPremiumRowTitlePadding);
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st.infoAbout ? *st.infoAbout : st::upgradeGiftSubtext),
|
||||
st::settingsPremiumRowAboutPadding);
|
||||
object_ptr<Info::Profile::FloatingIcon>(
|
||||
raw,
|
||||
*icon,
|
||||
st::starrefInfoIconPosition);
|
||||
};
|
||||
|
||||
content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_gift_wear_title(
|
||||
lt_name,
|
||||
rpl::single(UniqueGiftName(gift))),
|
||||
st.title ? *st.title : st::uniqueGiftTitle),
|
||||
st::settingsPremiumRowTitlePadding);
|
||||
content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_gift_wear_about(),
|
||||
st.subtitle ? *st.subtitle : st::uniqueGiftSubtitle),
|
||||
st::settingsPremiumRowAboutPadding);
|
||||
infoRow(
|
||||
tr::lng_gift_wear_badge_title(),
|
||||
(channel
|
||||
? tr::lng_gift_wear_badge_about_channel()
|
||||
: tr::lng_gift_wear_badge_about()),
|
||||
st.radiantIcon ? st.radiantIcon : &st::menuIconUnique);
|
||||
//infoRow(
|
||||
// tr::lng_gift_wear_design_title(),
|
||||
// tr::lng_gift_wear_design_about(),
|
||||
// &st::menuIconUniqueProfile);
|
||||
infoRow(
|
||||
tr::lng_gift_wear_proof_title(),
|
||||
(channel
|
||||
? tr::lng_gift_wear_proof_about_channel()
|
||||
: tr::lng_gift_wear_proof_about()),
|
||||
st.proofIcon ? st.proofIcon : &st::menuIconFactcheck);
|
||||
|
||||
const auto session = &show->session();
|
||||
const auto checking = std::make_shared<bool>();
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
const auto emojiStatuses = &session->data().emojiStatuses();
|
||||
const auto id = emojiStatuses->fromUniqueGift(gift);
|
||||
if (!peer->isSelf()) {
|
||||
if (*checking) {
|
||||
return;
|
||||
}
|
||||
*checking = true;
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
CheckBoostLevel(show, peer, [=](int level) {
|
||||
const auto limits = Data::LevelLimits(&peer->session());
|
||||
const auto wanted = limits.channelEmojiStatusLevelMin();
|
||||
if (level >= wanted) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
emojiStatuses->set(peer, id);
|
||||
return std::optional<Ui::AskBoostReason>();
|
||||
}
|
||||
const auto reason = [&]() -> Ui::AskBoostReason {
|
||||
return { Ui::AskBoostWearCollectible{
|
||||
wanted
|
||||
} };
|
||||
}();
|
||||
return std::make_optional(reason);
|
||||
}, [=] { *checking = false; });
|
||||
} else if (session->premium()) {
|
||||
box->closeBox();
|
||||
emojiStatuses->set(peer, id);
|
||||
} else {
|
||||
const auto link = Ui::Text::Bold(
|
||||
tr::lng_send_as_premium_required_link(tr::now));
|
||||
Settings::ShowPremiumPromoToast(
|
||||
show,
|
||||
tr::lng_gift_wear_subscribe(
|
||||
tr::now,
|
||||
lt_link,
|
||||
Ui::Text::Link(link),
|
||||
Ui::Text::WithEntities),
|
||||
u"wear_collectibles"_q);
|
||||
}
|
||||
});
|
||||
const auto lock = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::historySendDisabledIcon,
|
||||
st::giftBoxLockMargins,
|
||||
true));
|
||||
auto label = rpl::combine(
|
||||
tr::lng_gift_wear_start(),
|
||||
Data::AmPremiumValue(&show->session())
|
||||
) | rpl::map([=](const QString &text, bool premium) {
|
||||
auto result = TextWithEntities();
|
||||
if (!premium && peer->isSelf()) {
|
||||
result.append(lock);
|
||||
}
|
||||
result.append(text);
|
||||
return result;
|
||||
});
|
||||
SetButtonMarkedLabel(
|
||||
button,
|
||||
std::move(label),
|
||||
session,
|
||||
st::creditsBoxButtonLabel,
|
||||
&st::giftBox.button.textFg);
|
||||
|
||||
rpl::combine(
|
||||
box->widthValue(),
|
||||
button->widthValue()
|
||||
) | rpl::start_with_next([=](int outer, int inner) {
|
||||
const auto padding = st::giftBox.buttonPadding;
|
||||
const auto wanted = outer - padding.left() - padding.right();
|
||||
if (inner != wanted) {
|
||||
button->resizeToWidth(wanted);
|
||||
button->moveToLeft(padding.left(), padding.top());
|
||||
}
|
||||
}, box->lifetime());
|
||||
|
||||
AddUniqueCloseButton(box, {});
|
||||
}));
|
||||
}
|
||||
|
||||
struct UpgradeArgs : StarGiftUpgradeArgs {
|
||||
std::vector<Data::UniqueGiftModel> models;
|
||||
std::vector<Data::UniqueGiftPattern> patterns;
|
||||
@@ -2162,7 +2543,7 @@ struct UpgradeArgs : StarGiftUpgradeArgs {
|
||||
auto &patterns = state->data.patterns;
|
||||
auto &backdrops = state->data.backdrops;
|
||||
consumer.put_next(Data::UniqueGift{
|
||||
.title = (state->data.itemId
|
||||
.title = (state->data.savedId
|
||||
? tr::lng_gift_upgrade_title(tr::now)
|
||||
: tr::lng_gift_upgrade_preview_title(tr::now)),
|
||||
.model = models[index(state->modelIndices, models)],
|
||||
@@ -2186,11 +2567,13 @@ void AddUpgradeGiftCover(
|
||||
AddUniqueGiftCover(
|
||||
container,
|
||||
MakeUpgradeGiftStream(args),
|
||||
(args.itemId
|
||||
(args.savedId
|
||||
? tr::lng_gift_upgrade_about()
|
||||
: tr::lng_gift_upgrade_preview_about(
|
||||
lt_name,
|
||||
rpl::single(args.user->shortName()))));
|
||||
: (args.peer->isBroadcast()
|
||||
? tr::lng_gift_upgrade_preview_about_channel
|
||||
: tr::lng_gift_upgrade_preview_about)(
|
||||
lt_name,
|
||||
rpl::single(args.peer->shortName()))));
|
||||
}
|
||||
|
||||
void UpgradeBox(
|
||||
@@ -2207,31 +2590,20 @@ void UpgradeBox(
|
||||
const auto infoRow = [&](
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> text,
|
||||
not_null<const style::icon*> icon,
|
||||
bool newBadge = false) {
|
||||
not_null<const style::icon*> icon) {
|
||||
auto raw = container->add(
|
||||
object_ptr<Ui::VerticalLayout>(container));
|
||||
const auto widget = raw->add(
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(title) | Ui::Text::ToBold(),
|
||||
st::defaultFlatLabel),
|
||||
st::settingsPremiumRowTitlePadding);
|
||||
if (newBadge) {
|
||||
const auto badge = NewBadge::CreateNewBadge(
|
||||
raw,
|
||||
tr::lng_soon_badge(Ui::Text::Upper));
|
||||
widget->geometryValue(
|
||||
) | rpl::start_with_next([=](QRect geometry) {
|
||||
badge->move(st::settingsPremiumNewBadgePosition
|
||||
+ QPoint(widget->x() + widget->width(), widget->y()));
|
||||
}, badge->lifetime());
|
||||
}
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st::boxDividerLabel),
|
||||
st::upgradeGiftSubtext),
|
||||
st::settingsPremiumRowAboutPadding);
|
||||
object_ptr<Info::Profile::FloatingIcon>(
|
||||
raw,
|
||||
@@ -2250,15 +2622,14 @@ void UpgradeBox(
|
||||
infoRow(
|
||||
tr::lng_gift_upgrade_tradable_title(),
|
||||
tr::lng_gift_upgrade_tradable_about(),
|
||||
&st::menuIconTradable,
|
||||
true);
|
||||
&st::menuIconTradable);
|
||||
|
||||
struct State {
|
||||
bool sent = false;
|
||||
bool preserveDetails = false;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto preview = !args.itemId;
|
||||
const auto preview = !args.savedId;
|
||||
|
||||
if (!preview) {
|
||||
const auto skip = st::defaultVerticalListSkip;
|
||||
@@ -2303,13 +2674,13 @@ void UpgradeBox(
|
||||
if (result != Payments::CheckoutResult::Paid) {
|
||||
state->sent = false;
|
||||
} else {
|
||||
controller->showPeerHistory(args.user);
|
||||
controller->showPeerHistory(args.peer);
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}
|
||||
};
|
||||
UpgradeGift(controller, args.itemId, keepDetails, cost, done);
|
||||
UpgradeGift(controller, args.savedId, keepDetails, cost, done);
|
||||
});
|
||||
if (!preview) {
|
||||
auto star = session->data().customEmojiManager().creditsEmoji();
|
||||
@@ -2339,7 +2710,7 @@ void UpgradeBox(
|
||||
}
|
||||
}, box->lifetime());
|
||||
|
||||
AddUniqueCloseButton(box);
|
||||
AddUniqueCloseButton(box, {});
|
||||
}
|
||||
|
||||
const std::vector<PatternPoint> &PatternPoints() {
|
||||
@@ -2458,7 +2829,7 @@ void PaintPoints(
|
||||
|
||||
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
|
||||
const auto weak = base::make_weak(args.controller);
|
||||
const auto session = &args.user->session();
|
||||
const auto session = &args.peer->session();
|
||||
session->api().request(MTPpayments_GetStarGiftUpgradePreview(
|
||||
MTP_long(args.stargiftId)
|
||||
)).done([=](const MTPpayments_StarGiftUpgradePreview &result) {
|
||||
@@ -2494,19 +2865,51 @@ void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void AddUniqueCloseButton(not_null<GenericBox*> box) {
|
||||
const auto button = Ui::CreateChild<IconButton>(
|
||||
void AddUniqueCloseButton(
|
||||
not_null<GenericBox*> box,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> fillMenu) {
|
||||
const auto close = Ui::CreateChild<IconButton>(
|
||||
box,
|
||||
st::uniqueCloseButton);
|
||||
button->show();
|
||||
button->raise();
|
||||
const auto menu = fillMenu
|
||||
? Ui::CreateChild<IconButton>(box, st::uniqueMenuButton)
|
||||
: nullptr;
|
||||
close->show();
|
||||
close->raise();
|
||||
if (menu) {
|
||||
menu->show();
|
||||
menu->raise();
|
||||
}
|
||||
box->widthValue() | rpl::start_with_next([=](int width) {
|
||||
button->moveToRight(0, 0, width);
|
||||
button->raise();
|
||||
}, button->lifetime());
|
||||
button->setClickedCallback([=] {
|
||||
close->moveToRight(0, 0, width);
|
||||
close->raise();
|
||||
if (menu) {
|
||||
menu->moveToRight(close->width(), 0, width);
|
||||
menu->raise();
|
||||
}
|
||||
}, close->lifetime());
|
||||
close->setClickedCallback([=] {
|
||||
box->closeBox();
|
||||
});
|
||||
if (menu) {
|
||||
const auto state = menu->lifetime().make_state<
|
||||
base::unique_qptr<Ui::PopupMenu>
|
||||
>();
|
||||
menu->setClickedCallback([=] {
|
||||
if (*state) {
|
||||
*state = nullptr;
|
||||
return;
|
||||
}
|
||||
*state = base::make_unique_q<Ui::PopupMenu>(
|
||||
menu,
|
||||
st.menu ? *st.menu : st::popupMenuWithIcons);
|
||||
fillMenu(state->get());
|
||||
if (!(*state)->empty()) {
|
||||
(*state)->popup(QCursor::pos());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RequestStarsFormAndSubmit(
|
||||
@@ -2539,25 +2942,29 @@ void RequestStarsFormAndSubmit(
|
||||
done(Payments::CheckoutResult::Failed, nullptr);
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast(error.type());
|
||||
const auto type = error.type();
|
||||
if (type == u"STARGIFT_EXPORT_IN_PROGRESS"_q) {
|
||||
done(Payments::CheckoutResult::Cancelled, nullptr);
|
||||
} else {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast(type);
|
||||
}
|
||||
done(Payments::CheckoutResult::Failed, nullptr);
|
||||
}
|
||||
done(Payments::CheckoutResult::Failed, nullptr);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ShowGiftTransferredToast(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
not_null<PeerData*> to,
|
||||
const MTPUpdates &result) {
|
||||
const auto gift = FindUniqueGift(&to->session(), result);
|
||||
if (const auto strong = gift ? weak.get() : nullptr) {
|
||||
const Data::UniqueGift &gift) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast({
|
||||
.title = tr::lng_gift_transferred_title(tr::now),
|
||||
.text = tr::lng_gift_transferred_about(
|
||||
tr::now,
|
||||
lt_name,
|
||||
Text::Bold(Data::UniqueGiftName(*gift)),
|
||||
Text::Bold(Data::UniqueGiftName(gift)),
|
||||
lt_recipient,
|
||||
Text::Bold(to->shortName()),
|
||||
Ui::Text::WithEntities),
|
||||
|
||||
@@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_star_gift.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
struct GiftCode;
|
||||
@@ -17,6 +23,11 @@ namespace Payments {
|
||||
enum class CheckoutResult;
|
||||
} // namespace Payments
|
||||
|
||||
namespace Settings {
|
||||
struct GiftWearBoxStyleOverride;
|
||||
struct CreditsEntryBoxStyleOverrides;
|
||||
} // namespace Settings
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
@@ -27,6 +38,7 @@ class CustomEmoji;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class PopupMenu;
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
|
||||
@@ -41,6 +53,16 @@ void AddUniqueGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<Data::UniqueGift> data,
|
||||
rpl::producer<QString> subtitleOverride = nullptr);
|
||||
void AddWearGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
const Data::UniqueGift &data,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void ShowUniqueGiftWearBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const Data::UniqueGift &gift,
|
||||
Settings::GiftWearBoxStyleOverride st);
|
||||
|
||||
struct PatternPoint {
|
||||
QPointF position;
|
||||
@@ -63,8 +85,8 @@ struct StarGiftUpgradeArgs {
|
||||
not_null<Window::SessionController*> controller;
|
||||
base::required<uint64> stargiftId;
|
||||
Fn<void(bool)> ready;
|
||||
not_null<UserData*> user;
|
||||
MsgId itemId = 0;
|
||||
not_null<PeerData*> peer;
|
||||
Data::SavedStarGiftId savedId;
|
||||
int cost = 0;
|
||||
bool canAddSender = false;
|
||||
bool canAddComment = false;
|
||||
@@ -73,7 +95,10 @@ struct StarGiftUpgradeArgs {
|
||||
};
|
||||
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args);
|
||||
|
||||
void AddUniqueCloseButton(not_null<GenericBox*> box);
|
||||
void AddUniqueCloseButton(
|
||||
not_null<GenericBox*> box,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
Fn<void(not_null<PopupMenu*>)> fillMenu = nullptr);
|
||||
|
||||
void RequestStarsFormAndSubmit(
|
||||
not_null<Window::SessionController*> window,
|
||||
@@ -83,6 +108,6 @@ void RequestStarsFormAndSubmit(
|
||||
void ShowGiftTransferredToast(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
not_null<PeerData*> to,
|
||||
const MTPUpdates &result);
|
||||
const Data::UniqueGift &gift);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -817,9 +817,7 @@ void StickerSetBox::updateButtons() {
|
||||
- st.buttonPadding.left()
|
||||
- st.buttonPadding.left());
|
||||
button->setClickedCallback([=] {
|
||||
using namespace ChatHelpers;
|
||||
const auto usage = WindowUsage::PremiumPromo;
|
||||
if (const auto window = _show->resolveWindow(usage)) {
|
||||
if (const auto window = _show->resolveWindow()) {
|
||||
Settings::ShowPremium(window, u"animated_emoji"_q);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/transfer_gift_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "data/data_user.h"
|
||||
#include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle.
|
||||
@@ -21,12 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h" // peerListSingleRow.
|
||||
#include "styles/style_dialogs.h" // recentPeersSpecialName.
|
||||
#include "styles/style_layers.h" // boxLabel.
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -41,13 +47,15 @@ public:
|
||||
Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Fn<void(not_null<PeerData*>)> choose);
|
||||
Data::SavedStarGiftId savedId,
|
||||
Fn<void(not_null<PeerData*>, Fn<void()>)> choose);
|
||||
|
||||
void init(not_null<PeerListBox*> box);
|
||||
|
||||
void noSearchSubmit();
|
||||
|
||||
private:
|
||||
void prepareViewHook() override;
|
||||
void setupExportOption();
|
||||
|
||||
bool overrideKeyboardNavigation(
|
||||
int direction,
|
||||
@@ -58,34 +66,150 @@ private:
|
||||
not_null<UserData*> user) override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
const not_null<Window::SessionController*> _window;
|
||||
const std::shared_ptr<Data::UniqueGift> _gift;
|
||||
const Fn<void(not_null<PeerData*>)> _choose;
|
||||
const Data::SavedStarGiftId _giftId;
|
||||
const Fn<void(not_null<PeerData*>, Fn<void()>)> _choose;
|
||||
ExportOption _exportOption;
|
||||
QPointer<PeerListBox> _box;
|
||||
|
||||
};
|
||||
|
||||
void ConfirmExportBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Fn<void(Fn<void()> close)> confirmed) {
|
||||
box->setTitle(tr::lng_gift_transfer_confirm_title());
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_gift_transfer_confirm_text(
|
||||
lt_name,
|
||||
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
|
||||
Ui::Text::WithEntities),
|
||||
st::boxLabel));
|
||||
box->addButton(tr::lng_gift_transfer_confirm_button(), [=] {
|
||||
confirmed([weak = Ui::MakeWeak(box)] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
});
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
void ExportOnBlockchain(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Data::SavedStarGiftId giftId,
|
||||
Fn<void()> boxShown,
|
||||
Fn<void()> wentToUrl) {
|
||||
struct State {
|
||||
bool loading = false;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto session = &window->session();
|
||||
const auto show = window->uiShow();
|
||||
session->api().cloudPassword().reload();
|
||||
session->api().request(
|
||||
MTPpayments_GetStarGiftWithdrawalUrl(
|
||||
Api::InputSavedStarGiftId(giftId),
|
||||
MTP_inputCheckPasswordEmpty())
|
||||
).fail([=](const MTP::Error &error) {
|
||||
auto box = PrePasswordErrorBox(
|
||||
error.type(),
|
||||
session,
|
||||
TextWithEntities{
|
||||
tr::lng_gift_transfer_password_about(tr::now),
|
||||
});
|
||||
if (box) {
|
||||
show->show(std::move(box));
|
||||
boxShown();
|
||||
return;
|
||||
}
|
||||
state->lifetime = session->api().cloudPassword().state(
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const Core::CloudPasswordState &pass) {
|
||||
auto fields = PasscodeBox::CloudFields::From(pass);
|
||||
fields.customTitle = tr::lng_gift_transfer_password_title();
|
||||
fields.customDescription
|
||||
= tr::lng_gift_transfer_password_description(tr::now);
|
||||
fields.customSubmitButton = tr::lng_passcode_submit();
|
||||
fields.customCheckCallback = crl::guard(parent, [=](
|
||||
const Core::CloudPasswordResult &result,
|
||||
QPointer<PasscodeBox> box) {
|
||||
using ExportUrl = MTPpayments_StarGiftWithdrawalUrl;
|
||||
session->api().request(
|
||||
MTPpayments_GetStarGiftWithdrawalUrl(
|
||||
Api::InputSavedStarGiftId(giftId),
|
||||
result.result)
|
||||
).done([=](const ExportUrl &result) {
|
||||
UrlClickHandler::Open(qs(result.data().vurl()));
|
||||
wentToUrl();
|
||||
if (box) {
|
||||
box->closeBox();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto message = error.type();
|
||||
if (box && !box->handleCustomCheckError(message)) {
|
||||
show->showToast(message);
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
show->show(Box<PasscodeBox>(session, fields));
|
||||
boxShown();
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
[[nodiscard]] ExportOption MakeExportOption(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerListBox*> box,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Data::SavedStarGiftId giftId,
|
||||
TimeId when) {
|
||||
struct State {
|
||||
bool exporting = false;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto activate = [=] {
|
||||
const auto now = base::unixtime::now();
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
const auto left = (when > now) ? (when - now) : 0;
|
||||
const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0;
|
||||
if (!hours) {
|
||||
window->show(Box(ConfirmExportBox, gift, [=](Fn<void()> close) {
|
||||
if (state->exporting) {
|
||||
return;
|
||||
}
|
||||
state->exporting = true;
|
||||
ExportOnBlockchain(window, box, gift, giftId, [=] {
|
||||
state->exporting = false;
|
||||
close();
|
||||
}, [=] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
close();
|
||||
});
|
||||
}));
|
||||
return;
|
||||
}
|
||||
window->show(Ui::MakeInformBox({
|
||||
.text = (!hours
|
||||
? tr::lng_gift_transfer_unlocks_update_about()
|
||||
: tr::lng_gift_transfer_unlocks_about(
|
||||
lt_when,
|
||||
((hours >= 24)
|
||||
? tr::lng_gift_transfer_unlocks_when_days(
|
||||
lt_count,
|
||||
rpl::single((hours / 24) * 1.))
|
||||
: tr::lng_gift_transfer_unlocks_when_hours(
|
||||
lt_count,
|
||||
rpl::single(hours * 1.))))),
|
||||
.title = (!hours
|
||||
? tr::lng_gift_transfer_unlocks_update_title()
|
||||
: tr::lng_gift_transfer_unlocks_title()),
|
||||
.text = tr::lng_gift_transfer_unlocks_about(
|
||||
lt_when,
|
||||
((hours >= 24)
|
||||
? tr::lng_gift_transfer_unlocks_when_days(
|
||||
lt_count,
|
||||
rpl::single((hours / 24) * 1.))
|
||||
: tr::lng_gift_transfer_unlocks_when_hours(
|
||||
lt_count,
|
||||
rpl::single(hours * 1.)))),
|
||||
.title = tr::lng_gift_transfer_unlocks_title(),
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -130,10 +254,14 @@ private:
|
||||
|
||||
const style::PeerListItem &computeSt(
|
||||
const style::PeerListItem &st) const override {
|
||||
return _available ? st::recentPeersSpecialName : st;
|
||||
_st = st;
|
||||
_st.namePosition.setY(
|
||||
st::recentPeersSpecialName.namePosition.y());
|
||||
return _available ? _st : st;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable style::PeerListItem _st;
|
||||
bool _available = false;
|
||||
|
||||
};
|
||||
@@ -228,18 +356,27 @@ private:
|
||||
Controller::Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Fn<void(not_null<PeerData*>)> choose)
|
||||
Data::SavedStarGiftId giftId,
|
||||
Fn<void(not_null<PeerData*>, Fn<void()>)> choose)
|
||||
: ContactsBoxController(&window->session())
|
||||
, _window(window)
|
||||
, _gift(std::move(gift))
|
||||
, _giftId(giftId)
|
||||
, _choose(std::move(choose)) {
|
||||
if (const auto when = _gift->exportAt) {
|
||||
_exportOption = MakeExportOption(window, when);
|
||||
}
|
||||
if (_exportOption.content) {
|
||||
if (_gift->exportAt) {
|
||||
setStyleOverrides(&st::peerListSmallSkips);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::init(not_null<PeerListBox*> box) {
|
||||
_box = box;
|
||||
if (const auto when = _gift->exportAt) {
|
||||
_exportOption = MakeExportOption(_window, box, _gift, _giftId, when);
|
||||
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::noSearchSubmit() {
|
||||
if (const auto onstack = _exportOption.activate) {
|
||||
onstack();
|
||||
@@ -258,11 +395,6 @@ void Controller::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
|
||||
lt_name,
|
||||
rpl::single(UniqueGiftName(*_gift))));
|
||||
setupExportOption();
|
||||
}
|
||||
|
||||
void Controller::setupExportOption() {
|
||||
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> Controller::createRow(
|
||||
@@ -277,14 +409,18 @@ std::unique_ptr<PeerListRow> Controller::createRow(
|
||||
}
|
||||
|
||||
void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
_choose(row->peer());
|
||||
_choose(row->peer(), [parentBox = _box] {
|
||||
if (const auto strong = parentBox.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void TransferGift(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> to,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId messageId,
|
||||
Data::SavedStarGiftId savedId,
|
||||
Fn<void(Payments::CheckoutResult)> done) {
|
||||
Expects(to->isUser());
|
||||
|
||||
@@ -293,33 +429,37 @@ void TransferGift(
|
||||
auto formDone = [=](
|
||||
Payments::CheckoutResult result,
|
||||
const MTPUpdates *updates) {
|
||||
if (result == Payments::CheckoutResult::Paid && updates) {
|
||||
done(result);
|
||||
if (result == Payments::CheckoutResult::Paid) {
|
||||
if (const auto strong = weak.get()) {
|
||||
Ui::ShowGiftTransferredToast(strong, to, *updates);
|
||||
strong->session().data().notifyGiftUpdate({
|
||||
.id = savedId,
|
||||
.action = Data::GiftUpdate::Action::Transfer,
|
||||
});
|
||||
Ui::ShowGiftTransferredToast(strong, to, *gift);
|
||||
}
|
||||
}
|
||||
done(result);
|
||||
};
|
||||
if (gift->starsForTransfer <= 0) {
|
||||
session->api().request(MTPpayments_TransferStarGift(
|
||||
MTP_int(messageId.bare),
|
||||
to->asUser()->inputUser
|
||||
Api::InputSavedStarGiftId(savedId),
|
||||
to->input
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
session->api().applyUpdates(result);
|
||||
formDone(Payments::CheckoutResult::Paid, &result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
formDone(Payments::CheckoutResult::Failed, nullptr);
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast(error.type());
|
||||
}
|
||||
formDone(Payments::CheckoutResult::Failed, nullptr);
|
||||
}).send();
|
||||
return;
|
||||
}
|
||||
Ui::RequestStarsFormAndSubmit(
|
||||
window,
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
MTP_int(messageId.bare),
|
||||
to->asUser()->inputUser),
|
||||
Api::InputSavedStarGiftId(savedId),
|
||||
to->input),
|
||||
std::move(formDone));
|
||||
}
|
||||
|
||||
@@ -327,7 +467,8 @@ void ShowTransferToBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId) {
|
||||
Data::SavedStarGiftId savedId,
|
||||
Fn<void()> closeParentBox) {
|
||||
const auto stars = gift->starsForTransfer;
|
||||
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_gift_transfer_title(
|
||||
@@ -353,16 +494,24 @@ void ShowTransferToBox(
|
||||
state->sent = true;
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
const auto done = [=](Payments::CheckoutResult result) {
|
||||
if (result != Payments::CheckoutResult::Paid) {
|
||||
if (result == Payments::CheckoutResult::Cancelled) {
|
||||
closeParentBox();
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
} else if (result != Payments::CheckoutResult::Paid) {
|
||||
state->sent = false;
|
||||
} else {
|
||||
controller->showPeerHistory(peer);
|
||||
if (savedId.isUser()) {
|
||||
controller->showPeerHistory(peer);
|
||||
}
|
||||
closeParentBox();
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}
|
||||
};
|
||||
TransferGift(controller, peer, gift, msgId, done);
|
||||
TransferGift(controller, peer, gift, savedId, done);
|
||||
};
|
||||
|
||||
Ui::ConfirmBox(box, {
|
||||
@@ -395,15 +544,18 @@ void ShowTransferToBox(
|
||||
void ShowTransferGiftBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId) {
|
||||
Data::SavedStarGiftId savedId) {
|
||||
auto controller = std::make_unique<Controller>(
|
||||
window,
|
||||
gift,
|
||||
[=](not_null<PeerData*> peer) {
|
||||
ShowTransferToBox(window, peer, gift, msgId);
|
||||
savedId,
|
||||
[=](not_null<PeerData*> peer, Fn<void()> done) {
|
||||
ShowTransferToBox(window, peer, gift, savedId, done);
|
||||
});
|
||||
const auto controllerRaw = controller.get();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
controllerRaw->init(box);
|
||||
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
|
||||
box->noSearchSubmits() | rpl::start_with_next([=] {
|
||||
|
||||
@@ -13,9 +13,10 @@ class SessionController;
|
||||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
class SavedStarGiftId;
|
||||
} // namespace Data
|
||||
|
||||
void ShowTransferGiftBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId);
|
||||
Data::SavedStarGiftId savedId);
|
||||
|
||||
@@ -1441,6 +1441,19 @@ groupCallScheduleTimeField: InputField(groupCallScheduleDateField) {
|
||||
placeholderFont: font(14px);
|
||||
}
|
||||
|
||||
groupCallVolumeSettings: Menu(groupCallPopupVolumeMenu) {
|
||||
widthMin: 210px;
|
||||
itemBg: groupCallMembersBg;
|
||||
itemBgOver: groupCallMembersBgOver;
|
||||
}
|
||||
groupCallVolumeSettingsPadding: margins(24px, 8px, 24px, 6px);
|
||||
groupCallVolumeSettingsSlider: MediaSlider(groupCallMenuVolumeSlider) {
|
||||
activeFg: groupCallMenuBg;
|
||||
inactiveFg: groupCallMenuBg;
|
||||
activeFgOver: groupCallMenuBg;
|
||||
inactiveFgOver: groupCallMenuBg;
|
||||
}
|
||||
|
||||
//
|
||||
groupCallCalendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", groupCallMemberNotJoinedStatus }};
|
||||
groupCallCalendarNextDisabled: icon {{ "calendar_down", groupCallMemberNotJoinedStatus }};
|
||||
|
||||
@@ -578,8 +578,7 @@ void Instance::handleCallUpdate(
|
||||
if (inCall()
|
||||
&& _currentCall->type() == Call::Type::Outgoing
|
||||
&& _currentCall->user()->id == session->userPeerId()
|
||||
&& (peerFromUser(phoneCall.vparticipant_id())
|
||||
== _currentCall->user()->session().userPeerId())) {
|
||||
&& (user->id == _currentCall->user()->session().userPeerId())) {
|
||||
// Ignore call from the same running app, other account.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/integration.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -65,6 +66,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
|
||||
constexpr auto kHideControlsQuickTimeout = 2 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] QByteArray BatterySvg(
|
||||
const QSize &s,
|
||||
const QColor &c) {
|
||||
@@ -118,7 +122,9 @@ Panel::Panel(not_null<Call*> call)
|
||||
st::callMicrophoneMute,
|
||||
&st::callMicrophoneUnmute))
|
||||
, _name(widget(), st::callName)
|
||||
, _status(widget(), st::callStatus) {
|
||||
, _status(widget(), st::callStatus)
|
||||
, _hideControlsTimer([=] { requestControlsHidden(true); })
|
||||
, _controlsShownForceTimer([=] { controlsShownForce(false); }) {
|
||||
_layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
|
||||
_layerBg->setHideByBackgroundClick(true);
|
||||
|
||||
@@ -188,6 +194,25 @@ void Panel::initWindow() {
|
||||
&& window()->isFullScreen()) {
|
||||
window()->showNormal();
|
||||
}
|
||||
} else if (e->type() == QEvent::WindowStateChange) {
|
||||
const auto state = window()->windowState();
|
||||
_fullScreenOrMaximized = (state & Qt::WindowFullScreen)
|
||||
|| (state & Qt::WindowMaximized);
|
||||
} else if (e->type() == QEvent::Enter) {
|
||||
_mouseInside = true;
|
||||
Ui::Integration::Instance().registerLeaveSubscription(
|
||||
window().get());
|
||||
if (!_fullScreenOrMaximized.current()) {
|
||||
requestControlsHidden(false);
|
||||
_hideControlsTimer.cancel();
|
||||
}
|
||||
} else if (e->type() == QEvent::Leave) {
|
||||
_mouseInside = false;
|
||||
Ui::Integration::Instance().unregisterLeaveSubscription(
|
||||
window().get());
|
||||
if (!_fullScreenOrMaximized.current()) {
|
||||
_hideControlsTimer.callOnce(kHideControlsQuickTimeout);
|
||||
}
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
@@ -415,7 +440,11 @@ void Panel::refreshIncomingGeometry() {
|
||||
void Panel::reinitWithCall(Call *call) {
|
||||
_callLifetime.destroy();
|
||||
_call = call;
|
||||
const auto guard = gsl::finally([&] {
|
||||
updateControlsShown();
|
||||
});
|
||||
if (!_call) {
|
||||
_fingerprint.destroy();
|
||||
_incoming = nullptr;
|
||||
_outgoingVideoBubble = nullptr;
|
||||
_powerSaveBlocker = nullptr;
|
||||
@@ -457,6 +486,51 @@ void Panel::reinitWithCall(Call *call) {
|
||||
_window.backend());
|
||||
_incoming->widget()->hide();
|
||||
|
||||
_incoming->rp()->shownValue() | rpl::start_with_next([=] {
|
||||
updateControlsShown();
|
||||
}, _incoming->rp()->lifetime());
|
||||
|
||||
_hideControlsFilter = nullptr;
|
||||
_fullScreenOrMaximized.value(
|
||||
) | rpl::start_with_next([=](bool fullScreenOrMaximized) {
|
||||
if (fullScreenOrMaximized) {
|
||||
class Filter final : public QObject {
|
||||
public:
|
||||
explicit Filter(Fn<void(QObject*)> moved) : _moved(moved) {
|
||||
qApp->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool eventFilter(QObject *watched, QEvent *event) {
|
||||
if (event->type() == QEvent::MouseMove) {
|
||||
_moved(watched);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
Fn<void(QObject*)> _moved;
|
||||
|
||||
};
|
||||
_hideControlsFilter.reset(new Filter([=](QObject *what) {
|
||||
_mouseInside = true;
|
||||
if (what->isWidgetType()
|
||||
&& window()->isAncestorOf(static_cast<QWidget*>(what))) {
|
||||
_hideControlsTimer.callOnce(kHideControlsTimeout);
|
||||
requestControlsHidden(false);
|
||||
updateControlsShown();
|
||||
}
|
||||
}));
|
||||
_hideControlsTimer.callOnce(kHideControlsTimeout);
|
||||
} else {
|
||||
_hideControlsFilter = nullptr;
|
||||
_hideControlsTimer.cancel();
|
||||
if (_mouseInside) {
|
||||
requestControlsHidden(false);
|
||||
updateControlsShown();
|
||||
}
|
||||
}
|
||||
}, _incoming->rp()->lifetime());
|
||||
|
||||
_call->mutedValue(
|
||||
) | rpl::start_with_next([=](bool mute) {
|
||||
_mute->entity()->setProgress(mute ? 1. : 0.);
|
||||
@@ -603,6 +677,8 @@ void Panel::createRemoteAudioMute() {
|
||||
const auto r = _remoteAudioMute->rect();
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setOpacity(_controlsShownAnimation.value(
|
||||
_controlsShown ? 1. : 0.));
|
||||
p.setBrush(st::videoPlayIconBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(r, r.height() / 2, r.height() / 2);
|
||||
@@ -661,6 +737,8 @@ void Panel::createRemoteLowBattery() {
|
||||
const auto r = _remoteLowBattery->rect();
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setOpacity(_controlsShownAnimation.value(
|
||||
_controlsShown ? 1. : 0.));
|
||||
p.setBrush(st::videoPlayIconBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(r, r.height() / 2, r.height() / 2);
|
||||
@@ -782,6 +860,9 @@ void Panel::showDevicesMenu(
|
||||
}
|
||||
Core::App().saveSettingsDelayed();
|
||||
};
|
||||
controlsShownForce(true);
|
||||
updateControlsShown();
|
||||
|
||||
_devicesMenu = MakeDeviceSelectionMenu(
|
||||
widget(),
|
||||
&Core::App().mediaDevices(),
|
||||
@@ -791,6 +872,9 @@ void Panel::showDevicesMenu(
|
||||
Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
_devicesMenu->popup(button->mapToGlobal(QPoint())
|
||||
- QPoint(st::callDeviceSelectionMenu.menu.widthMin / 2, 0));
|
||||
QObject::connect(_devicesMenu.get(), &QObject::destroyed, window(), [=] {
|
||||
_controlsShownForceTimer.callOnce(kHideControlsQuickTimeout);
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::refreshOutgoingPreviewInBody(State state) {
|
||||
@@ -823,6 +907,33 @@ QRect Panel::outgoingFrameGeometry() const {
|
||||
return _outgoingVideoBubble->geometry();
|
||||
}
|
||||
|
||||
void Panel::requestControlsHidden(bool hidden) {
|
||||
_hideControlsRequested = hidden;
|
||||
updateControlsShown();
|
||||
}
|
||||
|
||||
void Panel::controlsShownForce(bool shown) {
|
||||
_controlsShownForce = shown;
|
||||
if (shown) {
|
||||
_controlsShownForceTimer.cancel();
|
||||
}
|
||||
updateControlsShown();
|
||||
}
|
||||
|
||||
void Panel::updateControlsShown() {
|
||||
const auto shown = !_incoming
|
||||
|| _incoming->widget()->isHidden()
|
||||
|| _controlsShownForce
|
||||
|| !_hideControlsRequested;
|
||||
if (_controlsShown != shown) {
|
||||
_controlsShown = shown;
|
||||
_controlsShownAnimation.start([=] {
|
||||
updateControlsGeometry();
|
||||
}, shown ? 0. : 1., shown ? 1. : 0., st::slideDuration);
|
||||
updateControlsGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::updateControlsGeometry() {
|
||||
if (widget()->size().isEmpty()) {
|
||||
return;
|
||||
@@ -830,6 +941,8 @@ void Panel::updateControlsGeometry() {
|
||||
if (_incoming) {
|
||||
refreshIncomingGeometry();
|
||||
}
|
||||
const auto shown = _controlsShownAnimation.value(
|
||||
_controlsShown ? 1. : 0.);
|
||||
if (_fingerprint) {
|
||||
#ifndef Q_OS_MAC
|
||||
const auto controlsGeometry = _controls->controls.geometry();
|
||||
@@ -848,14 +961,14 @@ void Panel::updateControlsGeometry() {
|
||||
const auto minRight = 0;
|
||||
#endif // _controls
|
||||
const auto desired = (widget()->width() - _fingerprint->width()) / 2;
|
||||
const auto top = anim::interpolate(
|
||||
-_fingerprint->height(),
|
||||
st::callFingerprintTop,
|
||||
shown);
|
||||
if (minLeft) {
|
||||
_fingerprint->moveToLeft(
|
||||
std::max(desired, minLeft),
|
||||
st::callFingerprintTop);
|
||||
_fingerprint->moveToLeft(std::max(desired, minLeft), top);
|
||||
} else {
|
||||
_fingerprint->moveToRight(
|
||||
std::max(desired, minRight),
|
||||
st::callFingerprintTop);
|
||||
_fingerprint->moveToRight(std::max(desired, minRight), top);
|
||||
}
|
||||
}
|
||||
const auto innerHeight = std::max(widget()->height(), st::callHeightMin);
|
||||
@@ -885,7 +998,11 @@ void Panel::updateControlsGeometry() {
|
||||
/ (_outgoingPreviewInBody ? 3 : 2);
|
||||
|
||||
_bodyTop = availableTop + skipHeight;
|
||||
_buttonsTop = availableTop + available;
|
||||
_buttonsTopShown = availableTop + available;
|
||||
_buttonsTop = anim::interpolate(
|
||||
widget()->height(),
|
||||
_buttonsTopShown,
|
||||
shown);
|
||||
const auto previewTop = _bodyTop + _bodySt->height + skipHeight;
|
||||
|
||||
_userpic->setGeometry(
|
||||
@@ -908,6 +1025,8 @@ void Panel::updateControlsGeometry() {
|
||||
(_buttonsTop
|
||||
- st::callRemoteAudioMuteSkip
|
||||
- _remoteAudioMute->height()));
|
||||
_remoteAudioMute->update();
|
||||
_remoteAudioMute->entity()->setOpacity(shown);
|
||||
}
|
||||
if (_remoteLowBattery) {
|
||||
_remoteLowBattery->moveToLeft(
|
||||
@@ -915,6 +1034,8 @@ void Panel::updateControlsGeometry() {
|
||||
(_buttonsTop
|
||||
- st::callRemoteAudioMuteSkip
|
||||
- _remoteLowBattery->height()));
|
||||
_remoteLowBattery->update();
|
||||
_remoteLowBattery->entity()->setOpacity(shown);
|
||||
}
|
||||
|
||||
if (_outgoingPreviewInBody) {
|
||||
@@ -925,7 +1046,7 @@ void Panel::updateControlsGeometry() {
|
||||
previewTop,
|
||||
bodyPreviewSize.width(),
|
||||
bodyPreviewSize.height()));
|
||||
} else {
|
||||
} else if (_outgoingVideoBubble) {
|
||||
updateOutgoingVideoBubbleGeometry();
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,9 @@ private:
|
||||
|
||||
[[nodiscard]] bool handleClose() const;
|
||||
|
||||
void requestControlsHidden(bool hidden);
|
||||
void controlsShownForce(bool shown);
|
||||
void updateControlsShown();
|
||||
void updateControlsGeometry();
|
||||
void updateHangupGeometry();
|
||||
void updateStatusGeometry();
|
||||
@@ -177,8 +180,19 @@ private:
|
||||
std::unique_ptr<VideoBubble> _outgoingVideoBubble;
|
||||
QPixmap _bottomShadow;
|
||||
int _bodyTop = 0;
|
||||
int _buttonsTopShown = 0;
|
||||
int _buttonsTop = 0;
|
||||
|
||||
base::Timer _hideControlsTimer;
|
||||
base::Timer _controlsShownForceTimer;
|
||||
std::unique_ptr<QObject> _hideControlsFilter;
|
||||
bool _hideControlsRequested = false;
|
||||
rpl::variable<bool> _fullScreenOrMaximized;
|
||||
Ui::Animations::Simple _controlsShownAnimation;
|
||||
bool _controlsShownForce = false;
|
||||
bool _controlsShown = true;
|
||||
bool _mouseInside = false;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _devicesMenu;
|
||||
|
||||
base::Timer _updateDurationTimer;
|
||||
|
||||
@@ -1431,10 +1431,12 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
|
||||
menu->menu(),
|
||||
st::groupCallPopupVolumeMenu,
|
||||
st::groupCallMenuVolumeSlider,
|
||||
otherParticipantStateValue,
|
||||
_call->rtmp() ? _call->rtmpVolume() : row->volume(),
|
||||
Group::kMaxVolume,
|
||||
muted);
|
||||
muted,
|
||||
st::groupCallMenuVolumePadding);
|
||||
|
||||
mutesFromVolume = volumeItem->toggleMuteRequests();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/group/calls_group_menu.h" // LeaveBox.
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
#include "calls/group/calls_volume_item.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "ui/widgets/level_meter.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
@@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "webrtc/webrtc_audio_input_tester.h"
|
||||
#include "webrtc/webrtc_device_resolver.h"
|
||||
#include "settings/settings_calls.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_invite_links.h"
|
||||
@@ -183,22 +185,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||
return Data::CanSend(thread, ChatRestriction::SendOther);
|
||||
};
|
||||
|
||||
const auto scheduleStyle = [&] {
|
||||
auto date = Ui::ChooseDateTimeStyleArgs();
|
||||
date.labelStyle = &st::groupCallBoxLabel;
|
||||
date.dateFieldStyle = &st::groupCallScheduleDateField;
|
||||
date.timeFieldStyle = &st::groupCallScheduleTimeField;
|
||||
date.separatorStyle = &st::callMuteButtonLabel;
|
||||
date.atStyle = &st::callMuteButtonLabel;
|
||||
date.calendarStyle = &st::groupCallCalendarColors;
|
||||
|
||||
auto st = HistoryView::ScheduleBoxStyleArgs();
|
||||
st.topButtonStyle = &st::groupCallMenuToggle;
|
||||
st.popupMenuStyle = &st::groupCallPopupMenu;
|
||||
st.chooseDateTimeArgs = std::move(date);
|
||||
return st;
|
||||
};
|
||||
|
||||
const auto st = ::Settings::DarkCreditsEntryBoxStyle();
|
||||
auto result = Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = &peer->session(),
|
||||
.copyCallback = std::move(copyCallback),
|
||||
@@ -211,11 +198,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||
: rpl::single(false)),
|
||||
tr::lng_group_call_copy_speaker_link(),
|
||||
tr::lng_group_call_copy_listener_link()),
|
||||
.stMultiSelect = &st::groupCallMultiSelect,
|
||||
.stComment = &st::groupCallShareBoxComment,
|
||||
.st = &st::groupCallShareBoxList,
|
||||
.stLabel = &st::groupCallField,
|
||||
.scheduleBoxStyle = scheduleStyle(),
|
||||
.st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
});
|
||||
*box = result.data();
|
||||
@@ -731,6 +714,50 @@ void SettingsBox(
|
||||
addDivider();
|
||||
Ui::AddSkip(layout);
|
||||
}
|
||||
if (rtmp) {
|
||||
const auto volumeItem = layout->add(
|
||||
object_ptr<MenuVolumeItem>(
|
||||
layout,
|
||||
st::groupCallVolumeSettings,
|
||||
st::groupCallVolumeSettingsSlider,
|
||||
call->otherParticipantStateValue(
|
||||
) | rpl::filter([=](const Group::ParticipantState &data) {
|
||||
return data.peer == peer;
|
||||
}),
|
||||
call->rtmpVolume(),
|
||||
Group::kMaxVolume,
|
||||
false,
|
||||
st::groupCallVolumeSettingsPadding));
|
||||
|
||||
const auto toggleMute = crl::guard(layout, [=](bool m, bool local) {
|
||||
if (call) {
|
||||
call->toggleMute({
|
||||
.peer = peer,
|
||||
.mute = m,
|
||||
.locallyOnly = local,
|
||||
});
|
||||
}
|
||||
});
|
||||
const auto changeVolume = crl::guard(layout, [=](int v, bool local) {
|
||||
if (call) {
|
||||
call->changeVolume({
|
||||
.peer = peer,
|
||||
.volume = std::clamp(v, 1, Group::kMaxVolume),
|
||||
.locallyOnly = local,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
volumeItem->toggleMuteLocallyRequests(
|
||||
) | rpl::start_with_next([=](bool muted) {
|
||||
toggleMute(muted, true);
|
||||
}, volumeItem->lifetime());
|
||||
|
||||
volumeItem->changeVolumeLocallyRequests(
|
||||
) | rpl::start_with_next([=](int volume) {
|
||||
changeVolume(volume, true);
|
||||
}, volumeItem->lifetime());
|
||||
}
|
||||
|
||||
if (peer->canManageGroupCall()) {
|
||||
layout->add(object_ptr<Ui::SettingsButton>(
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/effects/cross_line.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/rect.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include "ui/paint/arcs.h"
|
||||
@@ -41,20 +42,21 @@ constexpr auto kVolumeStickedValues
|
||||
MenuVolumeItem::MenuVolumeItem(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const style::MediaSlider &stSlider,
|
||||
rpl::producer<Group::ParticipantState> participantState,
|
||||
int startVolume,
|
||||
int maxVolume,
|
||||
bool muted)
|
||||
bool muted,
|
||||
const QMargins &padding)
|
||||
: Ui::Menu::ItemBase(parent, st)
|
||||
, _maxVolume(maxVolume)
|
||||
, _cloudMuted(muted)
|
||||
, _localMuted(muted)
|
||||
, _slider(base::make_unique_q<Ui::MediaSlider>(
|
||||
this,
|
||||
st::groupCallMenuVolumeSlider))
|
||||
, _slider(base::make_unique_q<Ui::MediaSlider>(this, stSlider))
|
||||
, _dummyAction(new QAction(parent))
|
||||
, _st(st)
|
||||
, _stCross(st::groupCallMuteCrossLine)
|
||||
, _padding(padding)
|
||||
, _crossLineMute(std::make_unique<Ui::CrossLineAnimation>(_stCross, true))
|
||||
, _arcs(std::make_unique<Ui::Paint::ArcsAnimation>(
|
||||
st::groupCallSpeakerArcsAnimation,
|
||||
@@ -71,7 +73,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
const auto geometry = QRect(QPoint(), size);
|
||||
_itemRect = geometry - st::groupCallMenuVolumePadding;
|
||||
_itemRect = geometry - _padding;
|
||||
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
|
||||
_arcPosition = _speakerRect.center()
|
||||
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip);
|
||||
@@ -280,9 +282,7 @@ bool MenuVolumeItem::isEnabled() const {
|
||||
}
|
||||
|
||||
int MenuVolumeItem::contentHeight() const {
|
||||
return st::groupCallMenuVolumePadding.top()
|
||||
+ st::groupCallMenuVolumePadding.bottom()
|
||||
+ _stCross.icon.height();
|
||||
return rect::m::sum::v(_padding) + _stCross.icon.height();
|
||||
}
|
||||
|
||||
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {
|
||||
|
||||
@@ -31,10 +31,12 @@ public:
|
||||
MenuVolumeItem(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const style::MediaSlider &stSlider,
|
||||
rpl::producer<Group::ParticipantState> participantState,
|
||||
int startVolume,
|
||||
int maxVolume,
|
||||
bool muted);
|
||||
bool muted,
|
||||
const QMargins &padding);
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
@@ -71,6 +73,7 @@ private:
|
||||
const not_null<QAction*> _dummyAction;
|
||||
const style::Menu &_st;
|
||||
const style::CrossLineAnimation &_stCross;
|
||||
const QMargins &_padding;
|
||||
|
||||
const std::unique_ptr<Ui::CrossLineAnimation> _crossLineMute;
|
||||
Ui::Animations::Simple _crossLineAnimation;
|
||||
|
||||
@@ -40,6 +40,7 @@ TabbedSearch {
|
||||
|
||||
ComposeIcons {
|
||||
settings: icon;
|
||||
collectibles: icon;
|
||||
|
||||
recent: icon;
|
||||
recentActive: icon;
|
||||
@@ -587,6 +588,7 @@ sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_delete", roun
|
||||
|
||||
defaultComposeIcons: ComposeIcons {
|
||||
settings: icon {{ "emoji/emoji_settings", emojiIconFg }};
|
||||
collectibles: icon {{ "menu/unique", emojiIconFg }};
|
||||
|
||||
recent: icon {{ "emoji/emoji_recent", emojiIconFg }};
|
||||
recentActive: icon {{ "emoji/emoji_recent", emojiSubIconFgActive }};
|
||||
@@ -853,6 +855,17 @@ historyComposeButton: FlatButton {
|
||||
color: historyComposeButtonBgRipple;
|
||||
}
|
||||
}
|
||||
historyGiftToChannel: IconButton(defaultIconButton) {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
|
||||
icon: icon{{ "menu/gift_premium", windowActiveTextFg }};
|
||||
iconOver: icon{{ "menu/gift_premium", windowActiveTextFg }};
|
||||
|
||||
rippleAreaPosition: point(3px, 3px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
historyUnblock: FlatButton(historyComposeButton) {
|
||||
color: attentionButtonFg;
|
||||
overColor: attentionButtonFgOver;
|
||||
|
||||
@@ -18,6 +18,7 @@ struct ComposeFeatures {
|
||||
bool attachBotsMenu : 1 = true;
|
||||
bool inlineBots : 1 = true;
|
||||
bool megagroupSet : 1 = true;
|
||||
bool collectibleStatus : 1 = false;
|
||||
bool stickersSettings : 1 = true;
|
||||
bool openStickerSets : 1 = true;
|
||||
bool autocompleteHashtags : 1 = true;
|
||||
|
||||
@@ -19,7 +19,7 @@ rpl::producer<bool> Show::adjustShadowLeft() const {
|
||||
}
|
||||
|
||||
ResolveWindow ResolveWindowDefault() {
|
||||
return [](not_null<Main::Session*> session, WindowUsage usage)
|
||||
return [](not_null<Main::Session*> session)
|
||||
-> Window::SessionController* {
|
||||
const auto check = [&](Window::Controller *window) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
@@ -45,8 +45,8 @@ ResolveWindow ResolveWindowDefault() {
|
||||
};
|
||||
}
|
||||
|
||||
Window::SessionController *Show::resolveWindow(WindowUsage usage) const {
|
||||
return ResolveWindowDefault()(&session(), usage);
|
||||
Window::SessionController *Show::resolveWindow() const {
|
||||
return ResolveWindowDefault()(&session());
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -40,13 +40,8 @@ enum class PauseReason {
|
||||
using PauseReasons = base::flags<PauseReason>;
|
||||
inline constexpr bool is_flag_type(PauseReason) { return true; };
|
||||
|
||||
enum class WindowUsage {
|
||||
PremiumPromo,
|
||||
};
|
||||
|
||||
using ResolveWindow = Fn<Window::SessionController*(
|
||||
not_null<Main::Session*>,
|
||||
WindowUsage)>;
|
||||
not_null<Main::Session*>)>;
|
||||
[[nodiscard]] ResolveWindow ResolveWindowDefault();
|
||||
|
||||
class Show : public Main::SessionShow {
|
||||
@@ -68,8 +63,7 @@ public:
|
||||
|
||||
virtual void processChosenSticker(FileChosen &&chosen) const = 0;
|
||||
|
||||
[[nodiscard]] virtual Window::SessionController *resolveWindow(
|
||||
WindowUsage) const;
|
||||
[[nodiscard]] virtual Window::SessionController *resolveWindow() const;
|
||||
};
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "layout/layout_position.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -134,6 +135,7 @@ struct EmojiListWidget::CustomEmojiInstance {
|
||||
};
|
||||
|
||||
struct EmojiListWidget::RecentOne {
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
|
||||
Ui::Text::CustomEmoji *custom = nullptr;
|
||||
RecentEmojiId id;
|
||||
mutable QImage premiumLock;
|
||||
@@ -447,6 +449,13 @@ void EmojiColorPicker::drawVariant(QPainter &p, int variant) {
|
||||
w.y() + _innerPosition.y());
|
||||
}
|
||||
|
||||
std::vector<EmojiStatusId> DocumentListToRecent(
|
||||
const std::vector<DocumentId> &documents) {
|
||||
return documents | ranges::views::transform([](DocumentId id) {
|
||||
return EmojiStatusId{ .documentId = id };
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
EmojiListWidget::EmojiListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -510,6 +519,11 @@ EmojiListWidget::EmojiListWidget(
|
||||
refreshCustom();
|
||||
}
|
||||
}, lifetime());
|
||||
} else if (_mode == Mode::EmojiStatus && _features.collectibleStatus) {
|
||||
session().data().emojiStatuses().collectiblesUpdates(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshCustom();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
_customSingleSize = Data::FrameSizeFromTag(
|
||||
@@ -614,6 +628,13 @@ void EmojiListWidget::applyNextSearchQuery() {
|
||||
const auto modeChanged = (_searchMode != searching);
|
||||
clearSelection();
|
||||
if (modeChanged) {
|
||||
if (_picker) {
|
||||
_picker->hideAnimated();
|
||||
}
|
||||
_colorAllRipple = nullptr;
|
||||
for (auto &set : _custom) {
|
||||
set.ripple = nullptr;
|
||||
}
|
||||
_searchMode = searching;
|
||||
}
|
||||
if (!searching) {
|
||||
@@ -656,7 +677,8 @@ void EmojiListWidget::applyNextSearchQuery() {
|
||||
void EmojiListWidget::showPreview() {
|
||||
if (const auto over = std::get_if<OverEmoji>(&_pressed)) {
|
||||
if (const auto custom = lookupCustomEmoji(over)) {
|
||||
_show->showMediaPreview(custom->stickerSetOrigin(), custom);
|
||||
const auto document = custom.document;
|
||||
_show->showMediaPreview(document->stickerSetOrigin(), document);
|
||||
_previewShown = true;
|
||||
}
|
||||
}
|
||||
@@ -706,7 +728,7 @@ void EmojiListWidget::appendPremiumSearchResults() {
|
||||
}
|
||||
|
||||
void EmojiListWidget::provideRecent(
|
||||
const std::vector<DocumentId> &customRecentList) {
|
||||
const std::vector<EmojiStatusId> &customRecentList) {
|
||||
clearSelection();
|
||||
fillRecentFrom(customRecentList);
|
||||
resizeToWidth(width());
|
||||
@@ -1094,7 +1116,8 @@ void EmojiListWidget::fillRecent() {
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
|
||||
void EmojiListWidget::fillRecentFrom(
|
||||
const std::vector<EmojiStatusId> &list) {
|
||||
const auto test = session().isTestMode();
|
||||
_recent.clear();
|
||||
_recent.reserve(list.size());
|
||||
@@ -1114,10 +1137,15 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
|
||||
_recentCustomIds.emplace(fakeId);
|
||||
} else {
|
||||
_recent.push_back({
|
||||
.collectible = id.collectible,
|
||||
.custom = resolveCustomRecent(id),
|
||||
.id = { RecentEmojiDocument{ .id = id, .test = test } },
|
||||
.id = {
|
||||
RecentEmojiDocument{ .id = id.documentId, .test = test },
|
||||
},
|
||||
});
|
||||
_recentCustomIds.emplace(id);
|
||||
_recentCustomIds.emplace(id.collectible
|
||||
? id.collectible->documentId
|
||||
: id.documentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1158,8 +1186,12 @@ void EmojiListWidget::fillRecentMenu(
|
||||
const auto over = OverEmoji{ section, index };
|
||||
const auto emoji = lookupOverEmoji(&over);
|
||||
const auto custom = lookupCustomEmoji(&over);
|
||||
if (custom && custom->sticker()) {
|
||||
const auto sticker = custom->sticker();
|
||||
if (custom.collectible) {
|
||||
return;
|
||||
}
|
||||
const auto document = custom.document;
|
||||
if (document && document->sticker()) {
|
||||
const auto sticker = document->sticker();
|
||||
const auto emoji = sticker->alt;
|
||||
const auto setId = sticker->set.id;
|
||||
if (!emoji.isEmpty()) {
|
||||
@@ -1168,7 +1200,7 @@ void EmojiListWidget::fillRecentMenu(
|
||||
EntityType::CustomEmoji,
|
||||
0,
|
||||
int(emoji.size()),
|
||||
Data::SerializeCustomEmojiId(custom)
|
||||
Data::SerializeCustomEmojiId(document)
|
||||
});
|
||||
addAction(tr::lng_emoji_copy(tr::now), [=] {
|
||||
TextUtilities::SetClipboardText(data);
|
||||
@@ -1192,8 +1224,8 @@ void EmojiListWidget::fillRecentMenu(
|
||||
auto id = RecentEmojiId{ emoji };
|
||||
if (custom) {
|
||||
id.data = RecentEmojiDocument{
|
||||
.id = custom->id,
|
||||
.test = custom->session().isTestMode(),
|
||||
.id = custom.document->id,
|
||||
.test = custom.document->session().isTestMode(),
|
||||
};
|
||||
}
|
||||
addAction(tr::lng_emoji_remove_recent(tr::now), crl::guard(this, [=] {
|
||||
@@ -1229,7 +1261,7 @@ void EmojiListWidget::fillEmojiStatusMenu(
|
||||
int section,
|
||||
int index) {
|
||||
const auto chosen = lookupCustomEmoji(index, section);
|
||||
if (!chosen) {
|
||||
if (!chosen || chosen.collectible) {
|
||||
return;
|
||||
}
|
||||
const auto selectWith = [=](TimeId scheduled) {
|
||||
@@ -1573,12 +1605,14 @@ bool EmojiListWidget::checkPickerHide() {
|
||||
return false;
|
||||
}
|
||||
|
||||
DocumentData *EmojiListWidget::lookupCustomEmoji(
|
||||
EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
|
||||
const OverEmoji *over) const {
|
||||
return over ? lookupCustomEmoji(over->index, over->section) : nullptr;
|
||||
return over
|
||||
? lookupCustomEmoji(over->index, over->section)
|
||||
: ResolvedCustom();
|
||||
}
|
||||
|
||||
DocumentData *EmojiListWidget::lookupCustomEmoji(
|
||||
EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
|
||||
int index,
|
||||
int section) const {
|
||||
if (_searchMode) {
|
||||
@@ -1586,22 +1620,30 @@ DocumentData *EmojiListWidget::lookupCustomEmoji(
|
||||
const auto document = std::get_if<RecentEmojiDocument>(
|
||||
&_searchResults[index].id.data);
|
||||
if (document) {
|
||||
return session().data().document(document->id);
|
||||
return { session().data().document(document->id) };
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return {};
|
||||
} else if (section == int(Section::Recent) && index < _recent.size()) {
|
||||
const auto &recent = _recent[index];
|
||||
if (recent.collectible) {
|
||||
return {
|
||||
session().data().document(recent.collectible->documentId),
|
||||
recent.collectible,
|
||||
};
|
||||
}
|
||||
const auto document = std::get_if<RecentEmojiDocument>(
|
||||
&_recent[index].id.data);
|
||||
&recent.id.data);
|
||||
if (document) {
|
||||
return session().data().document(document->id);
|
||||
return { session().data().document(document->id) };
|
||||
}
|
||||
} else if (section >= _staticCount
|
||||
&& index < _custom[section - _staticCount].list.size()) {
|
||||
auto &set = _custom[section - _staticCount];
|
||||
return set.list[index].document;
|
||||
auto &entry = set.list[index];
|
||||
return { entry.document, entry.collectible };
|
||||
}
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const {
|
||||
@@ -1643,9 +1685,11 @@ EmojiChosen EmojiListWidget::lookupChosen(
|
||||
}
|
||||
|
||||
FileChosen EmojiListWidget::lookupChosen(
|
||||
not_null<DocumentData*> custom,
|
||||
ResolvedCustom custom,
|
||||
const OverEmoji *over,
|
||||
Api::SendOptions options) {
|
||||
Expects(custom.document != nullptr);
|
||||
|
||||
_grabbingChosen = true;
|
||||
const auto guard = gsl::finally([&] { _grabbingChosen = false; });
|
||||
const auto rect = over ? emojiRect(over->section, over->index) : QRect();
|
||||
@@ -1655,13 +1699,14 @@ FileChosen EmojiListWidget::lookupChosen(
|
||||
) : QRect();
|
||||
|
||||
return {
|
||||
.document = custom,
|
||||
.document = custom.document,
|
||||
.options = options,
|
||||
.messageSendingFrom = {
|
||||
.type = Ui::MessageSendingAnimationFrom::Type::Emoji,
|
||||
.globalStartGeometry = over ? mapToGlobal(emoji) : QRect(),
|
||||
.frame = over ? Ui::GrabWidgetToImage(this, emoji) : QImage(),
|
||||
},
|
||||
.collectible = custom.collectible,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1751,7 +1796,6 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
const auto id = hasColorButton(button->section)
|
||||
? 0
|
||||
: _custom[button->section - _staticCount].id;
|
||||
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
|
||||
if (hasColorButton(button->section)) {
|
||||
_pickerSelected = pressed;
|
||||
showPicker();
|
||||
@@ -1759,7 +1803,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
removeSet(id);
|
||||
} else if (hasAddButton(button->section)) {
|
||||
_localSetsManager->install(id);
|
||||
} else if (const auto resolved = _show->resolveWindow(usage)) {
|
||||
} else if (const auto resolved = _show->resolveWindow()) {
|
||||
_jumpedToPremium.fire({});
|
||||
switch (_mode) {
|
||||
case Mode::Full:
|
||||
@@ -1792,6 +1836,8 @@ void EmojiListWidget::displaySet(uint64 setId) {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (setId == Data::Stickers::CollectibleSetId) {
|
||||
return;
|
||||
}
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
auto it = sets.find(setId);
|
||||
@@ -1830,6 +1876,7 @@ void EmojiListWidget::removeSet(uint64 setId) {
|
||||
Assert(i != end(_custom));
|
||||
const auto removeLocally = !_megagroupSet->canEditEmoji();
|
||||
removeMegagroupSet(removeLocally);
|
||||
} else if (setId == Data::Stickers::CollectibleSetId) {
|
||||
} else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) {
|
||||
checkHideWithBox(std::move(box));
|
||||
}
|
||||
@@ -1933,6 +1980,8 @@ bool EmojiListWidget::hasRemoveButton(int index) const {
|
||||
return true;
|
||||
}
|
||||
return !set.list.empty() && _megagroupSet->canEditEmoji();
|
||||
} else if (set.id == Data::Stickers::CollectibleSetId) {
|
||||
return false;
|
||||
}
|
||||
return set.canRemove && !set.premiumRequired;
|
||||
}
|
||||
@@ -1962,7 +2011,8 @@ bool EmojiListWidget::hasAddButton(int index) const {
|
||||
const auto &set = _custom[index - _staticCount];
|
||||
return !set.canRemove
|
||||
&& !set.premiumRequired
|
||||
&& set.id != Data::Stickers::MegagroupSetId;
|
||||
&& set.id != Data::Stickers::MegagroupSetId
|
||||
&& set.id != Data::Stickers::CollectibleSetId;
|
||||
}
|
||||
|
||||
QRect EmojiListWidget::addButtonRect(int index) const {
|
||||
@@ -1991,8 +2041,9 @@ bool EmojiListWidget::hasButton(int index) const {
|
||||
} else if (index >= _staticCount
|
||||
&& index < _staticCount + _custom.size()) {
|
||||
const auto &custom = _custom[index - _staticCount];
|
||||
return (custom.id != Data::Stickers::MegagroupSetId)
|
||||
|| custom.canRemove;
|
||||
return (custom.id != Data::Stickers::CollectibleSetId)
|
||||
&& ((custom.id != Data::Stickers::MegagroupSetId)
|
||||
|| custom.canRemove);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -2020,6 +2071,7 @@ QRect EmojiListWidget::buttonRect(
|
||||
auto EmojiListWidget::rightButton(int index) const -> const RightButton & {
|
||||
Expects(index >= _staticCount
|
||||
&& index < _staticCount + _custom.size());
|
||||
|
||||
return hasAddButton(index)
|
||||
? _add
|
||||
: _custom[index - _staticCount].canRemove
|
||||
@@ -2279,11 +2331,12 @@ void EmojiListWidget::refreshCustom() {
|
||||
auto set = std::vector<CustomOne>();
|
||||
set.reserve(list.size());
|
||||
for (const auto document : list) {
|
||||
if (_restrictedCustomList.contains(document->id)) {
|
||||
const auto id = EmojiStatusId{ document->id };
|
||||
if (_restrictedCustomList.contains(id.documentId)) {
|
||||
continue;
|
||||
} else if (const auto sticker = document->sticker()) {
|
||||
set.push_back({
|
||||
.custom = resolveCustomEmoji(document, lookupId),
|
||||
.custom = resolveCustomEmoji(id, document, lookupId),
|
||||
.document = document,
|
||||
.emoji = Ui::Emoji::Find(sticker->alt),
|
||||
});
|
||||
@@ -2305,6 +2358,7 @@ void EmojiListWidget::refreshCustom() {
|
||||
.premiumRequired = premium && premiumMayBeBought,
|
||||
});
|
||||
};
|
||||
refreshEmojiStatusCollectibles();
|
||||
refreshMegagroupStickers(push, GroupStickersPlace::Visible);
|
||||
for (const auto setId : owner->stickers().emojiSetsOrder()) {
|
||||
push(setId, true);
|
||||
@@ -2337,18 +2391,17 @@ Fn<void()> EmojiListWidget::repaintCallback(
|
||||
}
|
||||
|
||||
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
|
||||
EmojiStatusId id,
|
||||
not_null<DocumentData*> document,
|
||||
uint64 setId) {
|
||||
Expects(document->sticker() != nullptr);
|
||||
|
||||
const auto documentId = document->id;
|
||||
const auto i = _customEmoji.find(documentId);
|
||||
const auto i = _customEmoji.find(id);
|
||||
const auto recentOnly = (i != end(_customEmoji)) && i->second.recentOnly;
|
||||
if (i != end(_customEmoji) && !recentOnly) {
|
||||
return i->second.emoji.get();
|
||||
}
|
||||
auto instance = document->owner().customEmojiManager().create(
|
||||
document,
|
||||
Data::EmojiStatusCustomId(id),
|
||||
repaintCallback(documentId, setId),
|
||||
Data::CustomEmojiManager::SizeTag::Large);
|
||||
if (recentOnly) {
|
||||
@@ -2362,7 +2415,7 @@ not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
|
||||
return i->second.emoji.get();
|
||||
}
|
||||
return _customEmoji.emplace(
|
||||
documentId,
|
||||
id,
|
||||
CustomEmojiInstance{ .emoji = std::move(instance) }
|
||||
).first->second.emoji.get();
|
||||
}
|
||||
@@ -2380,31 +2433,78 @@ Ui::Text::CustomEmoji *EmojiListWidget::resolveCustomRecent(
|
||||
|
||||
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
|
||||
DocumentId documentId) {
|
||||
const auto i = _customRecent.find(documentId);
|
||||
return resolveCustomRecent(EmojiStatusId{ documentId });
|
||||
}
|
||||
|
||||
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
|
||||
EmojiStatusId id) {
|
||||
const auto i = id.collectible
|
||||
? end(_customRecent)
|
||||
: _customRecent.find(id.documentId);
|
||||
if (i != end(_customRecent)) {
|
||||
return i->second.get();
|
||||
}
|
||||
const auto j = _customEmoji.find(documentId);
|
||||
const auto j = _customEmoji.find(id);
|
||||
if (j != end(_customEmoji)) {
|
||||
return j->second.emoji.get();
|
||||
}
|
||||
const auto documentId = id.collectible
|
||||
? id.collectible->documentId
|
||||
: id.documentId;
|
||||
auto repaint = repaintCallback(documentId, RecentEmojiSectionSetId());
|
||||
if (_customRecentFactory) {
|
||||
if (_customRecentFactory && !id.collectible) {
|
||||
return _customRecent.emplace(
|
||||
documentId,
|
||||
_customRecentFactory(documentId, std::move(repaint))
|
||||
id.documentId,
|
||||
_customRecentFactory(id.documentId, std::move(repaint))
|
||||
).first->second.get();
|
||||
}
|
||||
auto custom = session().data().customEmojiManager().create(
|
||||
documentId,
|
||||
Data::EmojiStatusCustomId(id),
|
||||
std::move(repaint),
|
||||
Data::CustomEmojiManager::SizeTag::Large);
|
||||
return _customEmoji.emplace(
|
||||
documentId,
|
||||
id,
|
||||
CustomEmojiInstance{ .emoji = std::move(custom), .recentOnly = true }
|
||||
).first->second.emoji.get();
|
||||
}
|
||||
|
||||
void EmojiListWidget::refreshEmojiStatusCollectibles() {
|
||||
if (_mode != Mode::EmojiStatus || !_features.collectibleStatus) {
|
||||
return;
|
||||
}
|
||||
const auto type = Data::EmojiStatuses::Type::Collectibles;
|
||||
const auto &list = session().data().emojiStatuses().list(type);
|
||||
const auto setId = Data::Stickers::CollectibleSetId;
|
||||
auto set = std::vector<CustomOne>();
|
||||
set.reserve(list.size());
|
||||
for (const auto &status : list) {
|
||||
const auto documentId = status.collectible
|
||||
? status.collectible->documentId
|
||||
: status.documentId;
|
||||
const auto document = session().data().document(documentId);
|
||||
const auto sticker = document->sticker();
|
||||
set.push_back({
|
||||
.collectible = status.collectible,
|
||||
.custom = resolveCustomEmoji(status, document, setId),
|
||||
.document = document,
|
||||
.emoji = sticker ? Ui::Emoji::Find(sticker->alt) : nullptr,
|
||||
});
|
||||
}
|
||||
if (set.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto collectibles = session().data().stickers().collectibleSet();
|
||||
_custom.push_back({
|
||||
.id = setId,
|
||||
.set = collectibles,
|
||||
.thumbnailDocument = nullptr,
|
||||
.title = collectibles->title,
|
||||
.list = std::move(set),
|
||||
.canRemove = false,
|
||||
.premiumRequired = !session().premium(),
|
||||
});
|
||||
}
|
||||
|
||||
void EmojiListWidget::refreshMegagroupStickers(
|
||||
Fn<void(uint64 setId, bool installed)> push,
|
||||
GroupStickersPlace place) {
|
||||
@@ -2639,8 +2739,11 @@ void EmojiListWidget::setSelected(OverState newSelected) {
|
||||
} else if (_previewShown && _pressed != _selected) {
|
||||
if (const auto over = std::get_if<OverEmoji>(&_selected)) {
|
||||
if (const auto custom = lookupCustomEmoji(over)) {
|
||||
const auto document = custom.document;
|
||||
_pressed = _selected;
|
||||
_show->showMediaPreview(custom->stickerSetOrigin(), custom);
|
||||
_show->showMediaPreview(
|
||||
document->stickerSetOrigin(),
|
||||
document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +83,15 @@ enum class EmojiListMode {
|
||||
MessageEffects,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<EmojiStatusId> DocumentListToRecent(
|
||||
const std::vector<DocumentId> &documents);
|
||||
|
||||
struct EmojiListDescriptor {
|
||||
std::shared_ptr<Show> show;
|
||||
EmojiListMode mode = EmojiListMode::Full;
|
||||
Fn<QColor()> customTextColor;
|
||||
Fn<bool()> paused;
|
||||
std::vector<DocumentId> customRecentList;
|
||||
std::vector<EmojiStatusId> customRecentList;
|
||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||
DocumentId,
|
||||
Fn<void()>)> customRecentFactory;
|
||||
@@ -137,7 +140,7 @@ public:
|
||||
[[nodiscard]] rpl::producer<> jumpedToPremium() const;
|
||||
[[nodiscard]] rpl::producer<> escapes() const;
|
||||
|
||||
void provideRecent(const std::vector<DocumentId> &customRecentList);
|
||||
void provideRecent(const std::vector<EmojiStatusId> &customRecentList);
|
||||
|
||||
void prepareExpanding();
|
||||
void paintExpanding(
|
||||
@@ -186,6 +189,7 @@ private:
|
||||
bool collapsed = false;
|
||||
};
|
||||
struct CustomOne {
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
|
||||
not_null<Ui::Text::CustomEmoji*> custom;
|
||||
not_null<DocumentData*> document;
|
||||
EmojiPtr emoji = nullptr;
|
||||
@@ -253,6 +257,14 @@ private:
|
||||
int finalHeight = 0;
|
||||
bool expanding = false;
|
||||
};
|
||||
struct ResolvedCustom {
|
||||
DocumentData *document = nullptr;
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
|
||||
|
||||
explicit operator bool() const {
|
||||
return document != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Callback>
|
||||
bool enumerateSections(Callback callback) const;
|
||||
@@ -271,6 +283,7 @@ private:
|
||||
Visible,
|
||||
Hidden,
|
||||
};
|
||||
void refreshEmojiStatusCollectibles();
|
||||
void refreshMegagroupStickers(
|
||||
Fn<void(uint64 setId, bool installed)> push,
|
||||
GroupStickersPlace place);
|
||||
@@ -296,16 +309,16 @@ private:
|
||||
int index);
|
||||
|
||||
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
|
||||
[[nodiscard]] DocumentData *lookupCustomEmoji(
|
||||
[[nodiscard]] ResolvedCustom lookupCustomEmoji(
|
||||
const OverEmoji *over) const;
|
||||
[[nodiscard]] DocumentData *lookupCustomEmoji(
|
||||
[[nodiscard]] ResolvedCustom lookupCustomEmoji(
|
||||
int index,
|
||||
int section) const;
|
||||
[[nodiscard]] EmojiChosen lookupChosen(
|
||||
EmojiPtr emoji,
|
||||
not_null<const OverEmoji*> over);
|
||||
[[nodiscard]] FileChosen lookupChosen(
|
||||
not_null<DocumentData*> custom,
|
||||
ResolvedCustom custom,
|
||||
const OverEmoji *over,
|
||||
Api::SendOptions options = Api::SendOptions());
|
||||
void selectEmoji(EmojiChosen data);
|
||||
@@ -370,14 +383,17 @@ private:
|
||||
void repaintCustom(uint64 setId);
|
||||
|
||||
void fillRecent();
|
||||
void fillRecentFrom(const std::vector<DocumentId> &list);
|
||||
void fillRecentFrom(const std::vector<EmojiStatusId> &list);
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji(
|
||||
EmojiStatusId id,
|
||||
not_null<DocumentData*> document,
|
||||
uint64 setId);
|
||||
[[nodiscard]] Ui::Text::CustomEmoji *resolveCustomRecent(
|
||||
Core::RecentEmojiId customId);
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
|
||||
DocumentId documentId);
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
|
||||
EmojiStatusId id);
|
||||
[[nodiscard]] Fn<void()> repaintCallback(
|
||||
DocumentId documentId,
|
||||
uint64 setId);
|
||||
@@ -415,7 +431,7 @@ private:
|
||||
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
|
||||
std::vector<CustomSet> _custom;
|
||||
base::flat_set<DocumentId> _restrictedCustomList;
|
||||
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji;
|
||||
base::flat_map<EmojiStatusId, CustomEmojiInstance> _customEmoji;
|
||||
base::flat_map<
|
||||
DocumentId,
|
||||
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
|
||||
|
||||