Compare commits
237 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e2e510d8a | ||
|
|
1ed34c6fa0 | ||
|
|
78a0fa55b5 | ||
|
|
d37c040b36 | ||
|
|
e56bbf557d | ||
|
|
5abecec478 | ||
|
|
ccb41f778e | ||
|
|
059a4cf0d8 | ||
|
|
7a535a4554 | ||
|
|
f89167ef94 | ||
|
|
a77777f509 | ||
|
|
4a327ba584 | ||
|
|
a41e9bf67e | ||
|
|
6716973ce0 | ||
|
|
7cc81393d6 | ||
|
|
3e413a036f | ||
|
|
63a8fe7ee8 | ||
|
|
146409844d | ||
|
|
ba0da9f59e | ||
|
|
81aef519d4 | ||
|
|
bcd84518d1 | ||
|
|
f205952ff2 | ||
|
|
1d7622e0b5 | ||
|
|
4d9112283d | ||
|
|
dc49c788a8 | ||
|
|
36741ab780 | ||
|
|
53dffc5e88 | ||
|
|
607c7e7777 | ||
|
|
6f09e1699f | ||
|
|
8c35de48f3 | ||
|
|
b83d943841 | ||
|
|
b11b5caeb3 | ||
|
|
36924da59a | ||
|
|
f0a2c47613 | ||
|
|
5a4449f1a2 | ||
|
|
de3d7a7774 | ||
|
|
b06dbd1c00 | ||
|
|
1fa5e424e9 | ||
|
|
d81c7abf1a | ||
|
|
932215c91d | ||
|
|
7aa1141ba5 | ||
|
|
3699439506 | ||
|
|
76b1288f77 | ||
|
|
8fd9ae4e59 | ||
|
|
53abd2fe38 | ||
|
|
da8a4ba8ab | ||
|
|
9c3990c0c1 | ||
|
|
1eeb46d5fc | ||
|
|
02c01e258c | ||
|
|
b2f912868d | ||
|
|
ecf0eba0a5 | ||
|
|
8e5fec2fa8 | ||
|
|
89c35e8512 | ||
|
|
3fa88fad79 | ||
|
|
5f037462ed | ||
|
|
93c01e5f1e | ||
|
|
52953626a7 | ||
|
|
f77fdc799d | ||
|
|
7bf1f9bd71 | ||
|
|
363ffc1c04 | ||
|
|
4001899bdf | ||
|
|
fabdd89c4a | ||
|
|
70f1675085 | ||
|
|
07e1e2d9d6 | ||
|
|
ebff6c6370 | ||
|
|
233eb6d916 | ||
|
|
d568cab2fc | ||
|
|
8447b95c50 | ||
|
|
e959d1e1b0 | ||
|
|
ead5dbe368 | ||
|
|
f091f2b344 | ||
|
|
267a51e800 | ||
|
|
939f6095ba | ||
|
|
a333615e53 | ||
|
|
e1b33fbc40 | ||
|
|
99c5d994b5 | ||
|
|
0971485367 | ||
|
|
3cfa963f69 | ||
|
|
7096a4231f | ||
|
|
07f0c182e6 | ||
|
|
535b223333 | ||
|
|
ffa8c2be79 | ||
|
|
2873e64ca2 | ||
|
|
8bd28bce69 | ||
|
|
b97ce43fca | ||
|
|
d34c4cc2f2 | ||
|
|
1ca1f0fa7d | ||
|
|
8c3c8f888d | ||
|
|
a75d7f0381 | ||
|
|
7684466acf | ||
|
|
0067245739 | ||
|
|
4a5d8aa217 | ||
|
|
2d786aa02c | ||
|
|
b0933b96ef | ||
|
|
20fb73b626 | ||
|
|
ae7bd7112b | ||
|
|
2365363dcc | ||
|
|
e0e4a7bec6 | ||
|
|
ebd0c3696a | ||
|
|
e0a0d9c039 | ||
|
|
dae9f2ab2b | ||
|
|
7168a00ee4 | ||
|
|
1e2d0ced20 | ||
|
|
d6ac883efa | ||
|
|
a386d70ae4 | ||
|
|
bc8bf672b4 | ||
|
|
e38998214f | ||
|
|
472a2fe802 | ||
|
|
2d6d89b1cf | ||
|
|
66be2ac6ca | ||
|
|
3137c9f3f7 | ||
|
|
b9ebb02e72 | ||
|
|
1e02c475d6 | ||
|
|
4d2cda0692 | ||
|
|
cbd2b8f428 | ||
|
|
93605db690 | ||
|
|
2567096de0 | ||
|
|
6ed25d012f | ||
|
|
c2afef2bde | ||
|
|
0991e7d8a4 | ||
|
|
cf52f2a743 | ||
|
|
37dddda1a0 | ||
|
|
3f2f3ebd51 | ||
|
|
7ba78540ac | ||
|
|
19afb49fce | ||
|
|
46ab553fa5 | ||
|
|
68cc42047e | ||
|
|
e25cf27ba5 | ||
|
|
3895e6d958 | ||
|
|
24cf3984c8 | ||
|
|
6237675744 | ||
|
|
30dae049ff | ||
|
|
1dc30caee9 | ||
|
|
b2d340cbfb | ||
|
|
7d52787e54 | ||
|
|
ae3f16ccbd | ||
|
|
057222757b | ||
|
|
119f109904 | ||
|
|
23a77b1ba4 | ||
|
|
c076daa91f | ||
|
|
b7ef5325ac | ||
|
|
8b535c58fa | ||
|
|
6bc8daaeda | ||
|
|
7a2562e5bb | ||
|
|
9e0c731b32 | ||
|
|
2e0e4006a1 | ||
|
|
187139473d | ||
|
|
dbb0a5ad28 | ||
|
|
24aaed44b9 | ||
|
|
32b8d83c04 | ||
|
|
bf55c325ce | ||
|
|
3af288c74e | ||
|
|
e306d9ba35 | ||
|
|
b23a877d7e | ||
|
|
08fda055fc | ||
|
|
84055ed74e | ||
|
|
2db30690ce | ||
|
|
304bcfd343 | ||
|
|
8a1cf2bb3a | ||
|
|
c857c24a64 | ||
|
|
bbdcb047d0 | ||
|
|
78f2e70956 | ||
|
|
75a75626ce | ||
|
|
cec9688d58 | ||
|
|
81492b7d3a | ||
|
|
9166acbbb9 | ||
|
|
36de2b6ca6 | ||
|
|
21f909dd4b | ||
|
|
f2a92c9122 | ||
|
|
7ee2e3d8bc | ||
|
|
f89aeb6ad4 | ||
|
|
0397006894 | ||
|
|
d6863074b2 | ||
|
|
9c185a30e0 | ||
|
|
a8f492a027 | ||
|
|
0a92b1dc68 | ||
|
|
e6d661f8ee | ||
|
|
f48dfb5d81 | ||
|
|
cd041e8366 | ||
|
|
6787ea883e | ||
|
|
78937d716f | ||
|
|
9713abc002 | ||
|
|
b44b45cca0 | ||
|
|
9e2cf0ed73 | ||
|
|
b01d7ea5b9 | ||
|
|
ae89b65a98 | ||
|
|
9b9c3d788d | ||
|
|
ccc6c6daa5 | ||
|
|
9ce6636c6a | ||
|
|
6287d306c2 | ||
|
|
6cfa053328 | ||
|
|
9514b6eecd | ||
|
|
c8d4818d22 | ||
|
|
4142ada729 | ||
|
|
d7ffdbd78d | ||
|
|
e8d87d37bb | ||
|
|
343ffc23eb | ||
|
|
95e0086eed | ||
|
|
c010ecfe38 | ||
|
|
302e9371c8 | ||
|
|
7060c0e6d7 | ||
|
|
20a4c7f9f4 | ||
|
|
e59e4afd3e | ||
|
|
f74dd3ca1e | ||
|
|
511cfc524f | ||
|
|
4cf6173d25 | ||
|
|
17996757fd | ||
|
|
6bc1049858 | ||
|
|
ff44f626ba | ||
|
|
552343fa37 | ||
|
|
4dc7fd8cd1 | ||
|
|
285c96fd2e | ||
|
|
e6af33367e | ||
|
|
7092fe2242 | ||
|
|
a32ff46579 | ||
|
|
7f20cf59d1 | ||
|
|
a8a1b08127 | ||
|
|
1a1e777b87 | ||
|
|
9e76e64064 | ||
|
|
975ae17ef9 | ||
|
|
ed9dcef66f | ||
|
|
b1e537e54e | ||
|
|
e5886862c3 | ||
|
|
d85b668d4f | ||
|
|
b363d8bfb5 | ||
|
|
754d467440 | ||
|
|
598f08d6c7 | ||
|
|
224fdc1864 | ||
|
|
e646b4dc9a | ||
|
|
9077db2e97 | ||
|
|
7e14277ead | ||
|
|
d351a7d697 | ||
|
|
70ed43b811 | ||
|
|
913083ebc6 | ||
|
|
588a95a7ae | ||
|
|
c0a0ad4ec5 | ||
|
|
5eafe96525 |
2
.gitmodules
vendored
@@ -3,7 +3,7 @@
|
||||
url = https://github.com/telegramdesktop/libtgvoip
|
||||
[submodule "Telegram/ThirdParty/GSL"]
|
||||
path = Telegram/ThirdParty/GSL
|
||||
url = https://github.com/desktop-app/GSL.git
|
||||
url = https://github.com/Microsoft/GSL.git
|
||||
[submodule "Telegram/ThirdParty/xxHash"]
|
||||
path = Telegram/ThirdParty/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash.git
|
||||
|
||||
@@ -112,6 +112,8 @@ PRIVATE
|
||||
api/api_bot.h
|
||||
api/api_chat_filters.cpp
|
||||
api/api_chat_filters.h
|
||||
api/api_chat_filters_remove_manager.cpp
|
||||
api/api_chat_filters_remove_manager.h
|
||||
api/api_chat_invite.cpp
|
||||
api/api_chat_invite.h
|
||||
api/api_chat_links.cpp
|
||||
@@ -636,6 +638,8 @@ PRIVATE
|
||||
data/data_thread.h
|
||||
data/data_types.cpp
|
||||
data/data_types.h
|
||||
data/data_unread_value.cpp
|
||||
data/data_unread_value.h
|
||||
data/data_user.cpp
|
||||
data/data_user.h
|
||||
data/data_user_photos.cpp
|
||||
@@ -995,6 +999,7 @@ PRIVATE
|
||||
info/statistics/info_statistics_list_controllers.h
|
||||
info/statistics/info_statistics_recent_message.cpp
|
||||
info/statistics/info_statistics_recent_message.h
|
||||
info/statistics/info_statistics_tag.h
|
||||
info/statistics/info_statistics_widget.cpp
|
||||
info/statistics/info_statistics_widget.h
|
||||
info/stories/info_stories_inner_widget.cpp
|
||||
@@ -1162,6 +1167,8 @@ PRIVATE
|
||||
media/streaming/media_streaming_player.h
|
||||
media/streaming/media_streaming_reader.cpp
|
||||
media/streaming/media_streaming_reader.h
|
||||
media/streaming/media_streaming_round_preview.cpp
|
||||
media/streaming/media_streaming_round_preview.h
|
||||
media/streaming/media_streaming_utility.cpp
|
||||
media/streaming/media_streaming_utility.h
|
||||
media/streaming/media_streaming_video_track.cpp
|
||||
@@ -1501,6 +1508,8 @@ PRIVATE
|
||||
ui/chat/choose_send_as.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/chat/sponsored_message_bar.cpp
|
||||
ui/chat/sponsored_message_bar.h
|
||||
ui/controls/emoji_button_factory.cpp
|
||||
ui/controls/emoji_button_factory.h
|
||||
ui/controls/location_picker.cpp
|
||||
@@ -1532,6 +1541,8 @@ PRIVATE
|
||||
ui/widgets/expandable_peer_list.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/widgets/chat_filters_tabs_strip.cpp
|
||||
ui/widgets/chat_filters_tabs_strip.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/dynamic_thumbnails.cpp
|
||||
@@ -1849,7 +1860,7 @@ endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
if (MSVC)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
delayimp
|
||||
@@ -1946,7 +1957,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
|
||||
base/platform/win/base_windows_safe_library.h
|
||||
)
|
||||
target_include_directories(Updater PRIVATE ${lib_base_loc})
|
||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
if (MSVC)
|
||||
target_link_libraries(Updater
|
||||
PRIVATE
|
||||
delayimp
|
||||
|
||||
BIN
Telegram/Resources/art/round_placeholder.jpg
Normal file
|
After Width: | Height: | Size: 787 B |
BIN
Telegram/Resources/icons/chat/input_video.png
Normal file
|
After Width: | Height: | Size: 621 B |
BIN
Telegram/Resources/icons/chat/input_video@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/chat/input_video@3x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/menu/edited_status.png
Normal file
|
After Width: | Height: | Size: 586 B |
BIN
Telegram/Resources/icons/menu/edited_status@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/edited_status@3x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/player/player_settings.png
Normal file
|
After Width: | Height: | Size: 997 B |
BIN
Telegram/Resources/icons/player/player_settings@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/player/player_settings@3x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s.png
Normal file
|
After Width: | Height: | Size: 498 B |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
@@ -12,6 +12,7 @@ body {
|
||||
margin: 0;
|
||||
background-color: var(--td-window-bg);
|
||||
color: var(--td-window-fg);
|
||||
zoom: var(--td-zoom-percentage);
|
||||
}
|
||||
|
||||
html.custom_scroll ::-webkit-scrollbar {
|
||||
|
||||
@@ -72,6 +72,9 @@ var IV = {
|
||||
}
|
||||
},
|
||||
frameKeyDown: function (e) {
|
||||
const key0 = (e.key === '0')
|
||||
|| (e.code === 'Key0')
|
||||
|| (e.keyCode === 48);
|
||||
const keyW = (e.key === 'w')
|
||||
|| (e.code === 'KeyW')
|
||||
|| (e.keyCode === 87);
|
||||
@@ -81,12 +84,12 @@ var IV = {
|
||||
const keyM = (e.key === 'm')
|
||||
|| (e.code === 'KeyM')
|
||||
|| (e.keyCode === 77);
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM || key0)) {
|
||||
e.preventDefault();
|
||||
IV.notify({
|
||||
event: 'keydown',
|
||||
modifier: e.ctrlKey ? 'ctrl' : 'cmd',
|
||||
key: keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
key: key0 ? '0' : keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
});
|
||||
} else if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -499,8 +499,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_notify_global" = "Global settings";
|
||||
"lng_settings_notify_title" = "Notifications for chats";
|
||||
"lng_settings_desktop_notify" = "Desktop notifications";
|
||||
"lng_settings_native_title" = "Native notifications";
|
||||
"lng_settings_native_title" = "System integration";
|
||||
"lng_settings_use_windows" = "Use Windows notifications";
|
||||
"lng_settings_skip_in_focus" = "Respect system Focus mode";
|
||||
"lng_settings_use_native_notifications" = "Use native notifications";
|
||||
"lng_settings_notifications_position" = "Location on the screen";
|
||||
"lng_settings_notifications_count" = "Notifications count";
|
||||
@@ -1593,6 +1594,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_bot_public_link" = "Public Link";
|
||||
"lng_manage_peer_bot_public_links" = "Public Links";
|
||||
"lng_manage_peer_bot_balance" = "Balance";
|
||||
"lng_manage_peer_bot_balance_currency" = "Toncoin";
|
||||
"lng_manage_peer_bot_balance_credits" = "Stars";
|
||||
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
|
||||
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
|
||||
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
|
||||
@@ -1870,6 +1873,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_gift_got_subtitle" = "Gift from {user}";
|
||||
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
|
||||
"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.";
|
||||
@@ -2097,6 +2101,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_channel_public_link_copied" = "Link copied to clipboard.";
|
||||
"lng_context_about_private_link" = "This link will only work for members of this chat.";
|
||||
"lng_public_post_private_hint_ctrl" = "Use Ctrl+Click to copy a non-public link.";
|
||||
"lng_public_post_private_hint_cmd" = "Use Cmd+Click to copy a non-public link.";
|
||||
|
||||
"lng_forwarded" = "Forwarded from {user}";
|
||||
"lng_forwarded_story" = "Story from {user}";
|
||||
@@ -2115,8 +2121,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_recommended_message_title" = "Recommended";
|
||||
"lng_edited" = "edited";
|
||||
"lng_commented" = "commented";
|
||||
"lng_approximate" = "appx.";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
"lng_approximate_about" = "Estimated date of video publishing.";
|
||||
"lng_views_tooltip#one" = "Views: {count}";
|
||||
"lng_views_tooltip#other" = "Views: {count}";
|
||||
"lng_forwards_tooltip#one" = "Shares: {count}";
|
||||
@@ -2151,6 +2159,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_media_cancel" = "Cancel";
|
||||
"lng_media_video" = "Video";
|
||||
"lng_media_audio" = "Voice message";
|
||||
"lng_media_round" = "Video message";
|
||||
|
||||
"lng_media_auto_settings" = "Automatic media download";
|
||||
"lng_media_auto_in_private" = "In private chats";
|
||||
@@ -2442,18 +2451,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
|
||||
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
|
||||
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
|
||||
"lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
|
||||
"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
|
||||
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
|
||||
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
|
||||
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
|
||||
"lng_credits_box_history_entry_gift_examples" = "Examples";
|
||||
"lng_credits_box_history_entry_ads" = "Ads Platform";
|
||||
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
|
||||
"lng_credits_box_history_entry_api" = "Paid Broadcast";
|
||||
"lng_credits_box_history_entry_floodskip_about#one" = "{count} Message";
|
||||
"lng_credits_box_history_entry_floodskip_about#other" = "{count} Messages";
|
||||
"lng_credits_box_history_entry_floodskip_row" = "Messages";
|
||||
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
|
||||
"lng_credits_box_history_entry_id" = "Transaction ID";
|
||||
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
|
||||
"lng_credits_box_history_entry_success_date" = "Transaction date";
|
||||
"lng_credits_box_history_entry_success_url" = "Transaction link";
|
||||
"lng_credits_box_history_entry_media" = "Media";
|
||||
"lng_credits_box_history_entry_message" = "Message";
|
||||
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
|
||||
"lng_credits_box_history_entry_about_link" = "here";
|
||||
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
|
||||
@@ -2988,6 +3004,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway";
|
||||
"lng_gift_link_reason_chosen" = "You were selected by the channel";
|
||||
"lng_gift_link_label_date" = "Date";
|
||||
"lng_gift_link_label_first_sale" = "First Sale";
|
||||
"lng_gift_link_label_last_sale" = "Last Sale";
|
||||
"lng_gift_link_label_value" = "Value";
|
||||
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
|
||||
"lng_gift_link_also_send_link" = "send this link";
|
||||
"lng_gift_link_use" = "Use Link";
|
||||
@@ -3044,8 +3063,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
|
||||
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
|
||||
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
|
||||
"lng_gift_convert_sure_text#one" = "Do you want to convert this gift from {user} to **{count} Star**?\n\nThis action cannot be undone.";
|
||||
"lng_gift_convert_sure_text#other" = "Do you want to convert this gift from {user} to **{count} Stars**?\n\nThis action cannot be undone.";
|
||||
"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_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_hide" = "The gift is now hidden from your profile page.";
|
||||
@@ -3054,6 +3076,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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.";
|
||||
"lng_gift_send_small" = "send a gift";
|
||||
"lng_gift_sell_small#one" = "sell for {count} Star";
|
||||
"lng_gift_sell_small#other" = "sell for {count} Stars";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
@@ -3243,11 +3268,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_record_cancel_stories" = "Release outside to cancel";
|
||||
"lng_record_lock_cancel_sure" = "Do you want to stop recording and discard your voice message?";
|
||||
"lng_record_lock_cancel_sure_round" = "Do you want to stop recording and discard your video message?";
|
||||
"lng_record_listen_cancel_sure" = "Do you want to discard your recorded voice message?";
|
||||
"lng_record_listen_cancel_sure_round" = "Do you want to discard your recorded video message?";
|
||||
"lng_record_lock_discard" = "Discard";
|
||||
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
|
||||
"lng_record_voice_tip" = "Hold to record audio. Click to switch to video.";
|
||||
"lng_record_video_tip" = "Hold to record video. Click to switch to audio.";
|
||||
"lng_record_audio_problem" = "Could not start audio recording. Please check your microphone.";
|
||||
"lng_record_video_problem" = "Could not start video recording. Please check your camera.";
|
||||
"lng_record_once_first_tooltip" = "Click to set this message to **Play Once**.";
|
||||
"lng_record_once_active_tooltip" = "The recipient will be able to listen only once.";
|
||||
"lng_record_once_active_video" = "The recipient will be able to watch only once.";
|
||||
"lng_will_be_notified" = "Subscribers will be notified when you post.";
|
||||
"lng_wont_be_notified" = "Subscribers will receive a silent notification.";
|
||||
"lng_willbe_history" = "Select a chat to start messaging";
|
||||
@@ -3276,6 +3308,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_scheduled_send_now" = "Send message now?";
|
||||
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
|
||||
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
|
||||
"lng_scheduled_video_tip_title" = "Improving video...";
|
||||
"lng_scheduled_video_tip_text" = "The video will be published after it's optimized for the best viewing experience.";
|
||||
"lng_scheduled_video_tip" = "Processing video may take a few minutes.";
|
||||
"lng_scheduled_video_published" = "Video Published.";
|
||||
"lng_scheduled_video_view" = "View";
|
||||
|
||||
"lng_replies_view#one" = "View {count} Reply";
|
||||
"lng_replies_view#other" = "View {count} Replies";
|
||||
@@ -3359,6 +3396,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_settings" = "Settings";
|
||||
"lng_bot_open" = "Open Bot";
|
||||
"lng_bot_terms" = "Terms of Use";
|
||||
"lng_bot_privacy" = "Privacy Policy";
|
||||
"lng_bot_reload_page" = "Reload Page";
|
||||
"lng_bot_add_to_menu" = "{bot} asks your permission to be added as an option to your attachment menu so you can access it from any chat.";
|
||||
"lng_bot_add_to_menu_done" = "Bot added to the menu.";
|
||||
@@ -3576,11 +3614,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**.";
|
||||
"lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**.";
|
||||
|
||||
"lng_context_noforwards_info_channel" = "Copying and forwarding is not allowed in this channel.";
|
||||
"lng_context_noforwards_info_group" = "Copying and forwarding is not allowed in this group.";
|
||||
"lng_context_noforwards_info_bot" = "Copying and forwarding is not allowed from this bot.";
|
||||
|
||||
"lng_context_spoiler_effect" = "Hide with Spoiler";
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
|
||||
"lng_context_mention" = "Mention";
|
||||
"lng_context_search_from" = "Search messages";
|
||||
|
||||
"lng_factcheck_title" = "Fact Check";
|
||||
"lng_factcheck_placeholder" = "Add Facts or Context";
|
||||
"lng_factcheck_whats_this" = "what's this?";
|
||||
@@ -3678,6 +3723,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_reply_in_another_title" = "Reply in...";
|
||||
"lng_reply_in_another_chat" = "Reply in Another Chat";
|
||||
"lng_reply_in_author" = "Message author";
|
||||
"lng_reply_in_chats_list" = "Your chats";
|
||||
"lng_reply_show_in_chat" = "Show in Chat";
|
||||
"lng_reply_remove" = "Do Not Reply";
|
||||
"lng_reply_about_quote" = "You can select a specific part to quote.";
|
||||
@@ -3865,6 +3912,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mediaview_downloads" = "Downloads";
|
||||
"lng_mediaview_playback_speed" = "Playback speed: {speed}";
|
||||
"lng_mediaview_rotate_video" = "Rotate video";
|
||||
"lng_mediaview_quality_auto" = "Auto";
|
||||
|
||||
"lng_theme_preview_title" = "Theme Preview";
|
||||
"lng_theme_preview_generating" = "Generating color theme preview...";
|
||||
@@ -3946,7 +3994,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration.";
|
||||
"lng_payments_webview_install_edge" = "Please install {link}.";
|
||||
"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkitgtk-6.0/webkit2gtk-4.1/webkit2gtk-4.0) using your package manager.";
|
||||
"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkit2gtk-4.1/webkit2gtk-4.0) using your package manager.";
|
||||
"lng_payments_webview_enable_opengl" = "Please enable OpenGL in application settings.";
|
||||
"lng_payments_webview_switch_x11" = "Unsupported display server. Please switch to X11.";
|
||||
"lng_payments_webview_update_windows" = "Please update your system to Windows 8.1 or later.";
|
||||
"lng_payments_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost.";
|
||||
"lng_payments_receipt_label" = "Receipt";
|
||||
@@ -5049,6 +5099,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_toast_add" = "{chat} added to {folder} folder";
|
||||
"lng_filters_toast_remove" = "{chat} removed from {folder} folder";
|
||||
"lng_filters_shareable_status" = "shareable folder";
|
||||
"lng_filters_view_subtitle" = "Tabs view";
|
||||
"lng_filters_vertical" = "Tabs on the left";
|
||||
"lng_filters_horizontal" = "Tabs at the top";
|
||||
|
||||
"lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder.";
|
||||
"lng_filters_link" = "Share Folder";
|
||||
@@ -5168,13 +5221,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_sponsored_revenued_subtitle" = "Telegram Ads are very different from ads on other platforms. Ads such as this one:";
|
||||
"lng_sponsored_revenued_info1_title" = "Respect Your Privacy";
|
||||
"lng_sponsored_revenued_info1_description" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them.";
|
||||
"lng_sponsored_revenued_info1_bot_description" = "Ads on Telegram do not use your personal information and are based on the mini app in which you see them.";
|
||||
"lng_sponsored_revenued_info2_title" = "Help the Channel Creator";
|
||||
"lng_sponsored_revenued_info2_bot_title" = "Help the Bot Developer";
|
||||
"lng_sponsored_revenued_info2_description" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.";
|
||||
"lng_sponsored_revenued_info2_bot_description" = "50% of the revenue from Telegram Ads goes to the developer of the mini app where they are displayed.";
|
||||
"lng_sponsored_revenued_info3_title" = "Can Be Removed";
|
||||
"lng_sponsored_revenued_info3_description#one" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
|
||||
"lng_sponsored_revenued_info3_description#other" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
|
||||
"lng_sponsored_revenued_info3_bot_description" = "You can turn off ads in mini apps by subscribing to {link}.";
|
||||
"lng_sponsored_revenued_footer_title" = "Can I Launch an Ad?";
|
||||
"lng_sponsored_revenued_footer_description" = "Anyone can create an ad to display in this channel — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
|
||||
"lng_sponsored_revenued_footer_bot_description" = "Anyone can create an ad to display in this bot — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
|
||||
"lng_sponsored_top_bar_hide" = "remove";
|
||||
|
||||
"lng_telegram_features_url" = "https://t.me/TelegramTips";
|
||||
|
||||
@@ -5478,6 +5537,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_channel_earn_title" = "Monetization";
|
||||
"lng_channel_earn_about" = "Telegram shares 50% of the revenue from ads displayed in your channel as rewards. {link}";
|
||||
"lng_channel_earn_about_bot" = "Telegram shares 50% of the revenue from ads displayed in your bot. {link}";
|
||||
"lng_channel_earn_about_link" = "Learn more {emoji}";
|
||||
"lng_channel_earn_overview_title" = "Rewards overview";
|
||||
"lng_channel_earn_available" = "Rewards available for collection";
|
||||
@@ -5510,8 +5570,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channel_earn_cpm#one" = "{emoji} {count} CPM";
|
||||
"lng_channel_earn_cpm#other" = "{emoji} {count} CPM";
|
||||
"lng_channel_earn_learn_title" = "Earn From Your Channel";
|
||||
"lng_channel_earn_bot_learn_title" = "Earn From Your Bot";
|
||||
"lng_channel_earn_learn_in_subtitle" = "Telegram Ads";
|
||||
"lng_channel_earn_learn_in_about" = "Telegram can display ads in your channel.";
|
||||
"lng_channel_earn_learn_bot_in_about" = "Telegram can display ads in your bot.";
|
||||
"lng_channel_earn_learn_split_subtitle" = "50:50 revenue split";
|
||||
"lng_channel_earn_learn_split_about" = "You can receive 50% of the ad revenue as rewards in TON.";
|
||||
"lng_channel_earn_learn_out_subtitle" = "Flexible withdrawals";
|
||||
@@ -5548,6 +5610,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less than {link}.";
|
||||
"lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
|
||||
"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars";
|
||||
"lng_bot_copy_text_tooltip" = "Copy to Clipboard: {text}";
|
||||
|
||||
"lng_contact_add" = "Add";
|
||||
"lng_contact_send_message" = "Message";
|
||||
@@ -5558,6 +5621,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_iv_window_title" = "Instant View";
|
||||
"lng_iv_wrong_layout" = "Wrong layout?";
|
||||
"lng_iv_not_supported" = "This link appears to be invalid.";
|
||||
"lng_iv_zoom_tooltip_ctrl" = "Hold Ctrl to zoom by 5%.\nHold Alt to zoom by 1%.";
|
||||
"lng_iv_zoom_tooltip_cmd" = "Hold Cmd to zoom by 5%.\nHold Alt to zoom by 1%.";
|
||||
|
||||
"lng_limit_download_title" = "Download speed limited";
|
||||
"lng_limit_download_subscribe" = "Subscribe to {link} to increase download speed {increase}.";
|
||||
@@ -5595,6 +5660,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channels_recommended" = "Similar channels";
|
||||
"lng_bot_apps_your" = "Apps you use";
|
||||
"lng_bot_apps_popular" = "Grossing apps";
|
||||
"lng_bot_apps_which" = "Which apps are included here? {link}";
|
||||
"lng_bot_apps_which_link" = "Learn >";
|
||||
|
||||
"lng_popular_apps_info_title" = "Top Mini Apps";
|
||||
"lng_popular_apps_info_text" = "This catalogue ranks mini apps based on their daily revenue, measured in Stars. To be listed, developers must set their main mini apps in {bot} (as described {link}), have over **1,000** daily users, and earn a daily revenue above **1,000** Stars, based on the weekly average.";
|
||||
"lng_popular_apps_info_bot" = "@botfather";
|
||||
"lng_popular_apps_info_here" = "here";
|
||||
"lng_popular_apps_info_url" = "https://core.telegram.org/bots/webapps#launching-the-main-mini-app";
|
||||
"lng_popular_apps_info_confirm" = "Understood";
|
||||
|
||||
"lng_font_box_title" = "Choose font family";
|
||||
"lng_font_default" = "Default";
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<file alias="art/logo_256.png">../../art/logo_256.png</file>
|
||||
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
|
||||
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>
|
||||
<file alias="art/round_placeholder.jpg">../../art/round_placeholder.jpg</file>
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.6.2.0" />
|
||||
Version="5.7.3.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,6,2,0
|
||||
PRODUCTVERSION 5,6,2,0
|
||||
FILEVERSION 5,7,3,0
|
||||
PRODUCTVERSION 5,7,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.6.2.0"
|
||||
VALUE "FileVersion", "5.7.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.6.2.0"
|
||||
VALUE "ProductVersion", "5.7.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,6,2,0
|
||||
PRODUCTVERSION 5,6,2,0
|
||||
FILEVERSION 5,7,3,0
|
||||
PRODUCTVERSION 5,7,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.6.2.0"
|
||||
VALUE "FileVersion", "5.7.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.6.2.0"
|
||||
VALUE "ProductVersion", "5.7.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -152,6 +153,7 @@ void InitFilterLinkHeader(
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
: rpl::single(0)),
|
||||
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
|
||||
});
|
||||
const auto widget = header.widget;
|
||||
widget->resizeToWidth(st::boxWideWidth);
|
||||
|
||||
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_chat_filters_remove_manager.h"
|
||||
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
void RemoveChatFilter(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId,
|
||||
std::vector<not_null<PeerData*>> leave) {
|
||||
const auto api = &session->api();
|
||||
session->data().chatsFilters().apply(MTP_updateDialogFilter(
|
||||
MTP_flags(MTPDupdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()));
|
||||
if (leave.empty()) {
|
||||
api->request(MTPmessages_UpdateDialogFilter(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()
|
||||
)).send();
|
||||
} else {
|
||||
api->request(MTPchatlists_LeaveChatlist(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(ranges::views::all(
|
||||
leave
|
||||
) | ranges::views::transform([](not_null<PeerData*> peer) {
|
||||
return MTPInputPeer(peer->input);
|
||||
}) | ranges::to<QVector<MTPInputPeer>>())
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RemoveComplexChatFilter::RemoveComplexChatFilter() = default;
|
||||
|
||||
void RemoveComplexChatFilter::request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id) {
|
||||
const auto session = &weak->session();
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
const auto i = ranges::find(list, id, &Data::ChatFilter::id);
|
||||
const auto filter = (i != end(list)) ? *i : Data::ChatFilter();
|
||||
const auto has = filter.hasMyLinks();
|
||||
const auto confirm = [=](Fn<void()> action, bool onlyWhenHas = false) {
|
||||
if (!has && onlyWhenHas) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
weak->window().show(Ui::MakeConfirmBox({
|
||||
.text = (has
|
||||
? tr::lng_filters_delete_sure()
|
||||
: tr::lng_filters_remove_sure()),
|
||||
.confirmed = [=](Fn<void()> &&close) { close(); action(); },
|
||||
.confirmText = (has
|
||||
? tr::lng_box_delete()
|
||||
: tr::lng_filters_remove_yes()),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
const auto simple = [=] {
|
||||
confirm([=] { RemoveChatFilter(session, id, {}); });
|
||||
};
|
||||
const auto suggestRemoving = Api::ExtractSuggestRemoving(filter);
|
||||
if (suggestRemoving.empty()) {
|
||||
simple();
|
||||
return;
|
||||
} else if (_removingRequestId) {
|
||||
if (_removingId == id) {
|
||||
return;
|
||||
}
|
||||
session->api().request(_removingRequestId).cancel();
|
||||
}
|
||||
_removingId = id;
|
||||
_removingRequestId = session->api().request(
|
||||
MTPchatlists_GetLeaveChatlistSuggestions(
|
||||
MTP_inputChatlistDialogFilter(
|
||||
MTP_int(id)))
|
||||
).done(crl::guard(widget, [=, this](const MTPVector<MTPPeer> &result) {
|
||||
_removingRequestId = 0;
|
||||
const auto suggestRemovePeers = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([=](const MTPPeer &peer) {
|
||||
return session->data().peer(peerFromMTP(peer));
|
||||
}) | ranges::to_vector;
|
||||
const auto chosen = crl::guard(widget, [=](
|
||||
std::vector<not_null<PeerData*>> peers) {
|
||||
RemoveChatFilter(session, id, std::move(peers));
|
||||
});
|
||||
confirm(crl::guard(widget, [=] {
|
||||
Api::ProcessFilterRemove(
|
||||
weak,
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
suggestRemoving,
|
||||
suggestRemovePeers,
|
||||
chosen);
|
||||
}), true);
|
||||
})).fail(crl::guard(widget, [=, this] {
|
||||
_removingRequestId = 0;
|
||||
simple();
|
||||
})).send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
|
||||
class RemoveComplexChatFilter final {
|
||||
public:
|
||||
RemoveComplexChatFilter();
|
||||
|
||||
void request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id);
|
||||
|
||||
private:
|
||||
FilterId _removingId = 0;
|
||||
mtpRequestId _removingRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
@@ -129,6 +130,7 @@ void ConfirmSubscriptionBox(
|
||||
struct State final {
|
||||
std::shared_ptr<Data::PhotoMedia> photoMedia;
|
||||
std::unique_ptr<Ui::EmptyUserpic> photoEmpty;
|
||||
QImage frame;
|
||||
|
||||
std::optional<MTP::Sender> api;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
@@ -146,25 +148,45 @@ void ConfirmSubscriptionBox(
|
||||
const auto userpic = userpicWrap->entity();
|
||||
const auto photoSize = st::confirmInvitePhotoSize;
|
||||
userpic->resize(Size(photoSize));
|
||||
const auto creditsIconSize = photoSize / 3;
|
||||
const auto creditsIconCallback =
|
||||
Ui::PaintOutlinedColoredCreditsIconCallback(
|
||||
creditsIconSize,
|
||||
1.5);
|
||||
state->frame = QImage(
|
||||
Size(photoSize * style::DevicePixelRatio()),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
state->frame.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
const auto options = Images::Option::RoundCircle;
|
||||
userpic->paintRequest(
|
||||
) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
|
||||
auto p = QPainter(userpic);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
state->frame.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&state->frame);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
if (creditsIconCallback) {
|
||||
p.translate(
|
||||
photoSize - creditsIconSize,
|
||||
photoSize - creditsIconSize);
|
||||
creditsIconCallback(p);
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
auto p = QPainter(userpic);
|
||||
p.drawImage(0, 0, state->frame);
|
||||
}, userpicWrap->lifetime());
|
||||
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (photo) {
|
||||
|
||||
@@ -39,8 +39,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
if (const auto list = tl.data().vextended_media()) {
|
||||
extended.reserve(list->v.size());
|
||||
for (const auto &media : list->v) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &photo) {
|
||||
if (const auto inner = photo.vphoto()) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &data) {
|
||||
if (const auto inner = data.vphoto()) {
|
||||
const auto photo = owner->processPhoto(*inner);
|
||||
if (!photo->isNull()) {
|
||||
extended.push_back(CreditsHistoryMedia{
|
||||
@@ -49,9 +49,11 @@ constexpr auto kTransactionsLimit = 100;
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDmessageMediaDocument &document) {
|
||||
if (const auto inner = document.vdocument()) {
|
||||
const auto document = owner->processDocument(*inner);
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
if (const auto inner = data.vdocument()) {
|
||||
const auto document = owner->processDocument(
|
||||
*inner,
|
||||
data.valt_documents());
|
||||
if (document->isAnimation()
|
||||
|| document->isVideoFile()
|
||||
|| document->isGifv()) {
|
||||
@@ -71,7 +73,9 @@ constexpr auto kTransactionsLimit = 100;
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto reaction = tl.data().is_reaction();
|
||||
const auto incoming = (int64(tl.data().vstars().v) >= 0);
|
||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
@@ -81,12 +85,13 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.extended = std::move(extended),
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.barePeerId = saveActorId ? peer->id.value : barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.bareGiftStickerId = (stargift
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -101,6 +106,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
|
||||
}, [](const MTPDstarsTransactionPeerAds &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Ads;
|
||||
}, [](const MTPDstarsTransactionPeerAPI &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::API;
|
||||
}),
|
||||
.subscriptionUntil = tl.data().vsubscription_period()
|
||||
? base::unixtime::parse(base::unixtime::now()
|
||||
@@ -113,6 +120,7 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.convertStars = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
: 0),
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
|
||||
@@ -99,8 +99,8 @@ public:
|
||||
[[nodiscard]] Data::CreditsEarnStatistics data() const;
|
||||
|
||||
private:
|
||||
const bool _isUser = false;
|
||||
Data::CreditsEarnStatistics _data;
|
||||
bool _isUser = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
|
||||
@@ -89,12 +89,15 @@ void HandleWithdrawalButton(
|
||||
}
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
const auto message = error.type();
|
||||
if (box && !box->handleCustomCheckError(message)) {
|
||||
show->showToast(message);
|
||||
}
|
||||
};
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
channel->input,
|
||||
result.result
|
||||
)).done([=](const ChannelOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
@@ -134,7 +137,7 @@ void HandleWithdrawalButton(
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
channel->input,
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
} else if (peer) {
|
||||
|
||||
@@ -776,6 +776,8 @@ std::optional<StarGift> FromTL(
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -789,7 +791,7 @@ std::optional<UserStarGift> FromTL(
|
||||
return {};
|
||||
}
|
||||
return UserStarGift{
|
||||
.gift = std::move(*parsed),
|
||||
.info = std::move(*parsed),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
|
||||
@@ -80,10 +80,16 @@ struct StarGift {
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarGift &,
|
||||
const StarGift &) = default;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift gift;
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 convertStars = 0;
|
||||
PeerId fromId = 0;
|
||||
|
||||
@@ -40,29 +40,20 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void SendReport(
|
||||
void SendPhotoReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data) {
|
||||
auto done = [=] {
|
||||
not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
photo->mtpInput(),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done([=] {
|
||||
show->showToast(tr::lng_report_thanks(tr::now));
|
||||
};
|
||||
v::match(data, [&](v::null_t) {
|
||||
peer->session().api().request(MTPaccount_ReportPeer(
|
||||
peer->input,
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
photo->mtpInput(),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
auto CreateReportMessagesOrStoriesCallback(
|
||||
|
||||
@@ -41,12 +41,12 @@ struct ReportResult final {
|
||||
bool successful = false;
|
||||
};
|
||||
|
||||
void SendReport(
|
||||
void SendPhotoReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data);
|
||||
not_null<PhotoData*> photo);
|
||||
|
||||
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
|
||||
@@ -456,6 +456,7 @@ void SendConfirmedFile(
|
||||
not_null<Main::Session*> session,
|
||||
const std::shared_ptr<FilePrepareResult> &file) {
|
||||
const auto isEditing = (file->type != SendMediaType::Audio)
|
||||
&& (file->type != SendMediaType::Round)
|
||||
&& (file->to.replaceMediaOf != 0);
|
||||
const auto newId = FullMsgId(
|
||||
file->to.peer,
|
||||
@@ -525,7 +526,8 @@ void SendConfirmedFile(
|
||||
// Shortcut messages have no 'edited' badge.
|
||||
flags |= MessageFlag::HideEdited;
|
||||
}
|
||||
if (file->type == SendMediaType::Audio) {
|
||||
if (file->type == SendMediaType::Audio
|
||||
|| file->type == SendMediaType::Round) {
|
||||
if (!peer->isChannel() || peer->isMegagroup()) {
|
||||
flags |= MessageFlag::MediaIsUnread;
|
||||
}
|
||||
@@ -551,29 +553,25 @@ void SendConfirmedFile(
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
const auto isVoice = [&] {
|
||||
return file->document.match([](const MTPDdocumentEmpty &d) {
|
||||
return false;
|
||||
}, [](const MTPDdocument &d) {
|
||||
return ranges::any_of(d.vattributes().v, [&](
|
||||
const MTPDocumentAttribute &attribute) {
|
||||
using Att = MTPDdocumentAttributeAudio;
|
||||
return attribute.match([](const Att &data) -> bool {
|
||||
return data.vflags().v & Att::Flag::f_voice;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}();
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| Flag::f_voice
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| Flag::f_round
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_story.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
@@ -341,6 +342,10 @@ void PublicForwards::request(
|
||||
.token = nextToken,
|
||||
});
|
||||
};
|
||||
const auto processFail = [=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
};
|
||||
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
if (_fullId.messageId) {
|
||||
@@ -349,14 +354,14 @@ void PublicForwards::request(
|
||||
MTP_int(_fullId.messageId.msg),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
)).done(processResult).fail(processFail).send();
|
||||
} else if (_fullId.storyId) {
|
||||
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
|
||||
channel->input,
|
||||
MTP_int(_fullId.storyId.story),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
)).done(processResult).fail(processFail).send();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +386,7 @@ Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
|
||||
}
|
||||
|
||||
void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
if (channel()->isMegagroup()) {
|
||||
if (channel()->isMegagroup() && !_storyId) {
|
||||
return;
|
||||
}
|
||||
const auto requestFirstPublicForwards = [=](
|
||||
@@ -681,17 +686,18 @@ Data::BoostStatus Boosts::boostStatus() const {
|
||||
return _boostStatus;
|
||||
}
|
||||
|
||||
ChannelEarnStatistics::ChannelEarnStatistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
EarnStatistics::EarnStatistics(not_null<PeerData*> peer)
|
||||
: StatisticsRequestSender(peer)
|
||||
, _isUser(peer->isUser()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
makeRequest(MTPstats_GetBroadcastRevenueStats(
|
||||
MTP_flags(0),
|
||||
channel()->inputChannel
|
||||
(_isUser ? user()->input : channel()->input)
|
||||
)).done([=](const MTPstats_BroadcastRevenueStats &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &balances = data.vbalances().data();
|
||||
@@ -708,18 +714,22 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
requestHistory({}, [=](Data::EarnHistorySlice &&slice) {
|
||||
_data.firstHistorySlice = std::move(slice);
|
||||
|
||||
api().request(
|
||||
MTPchannels_GetFullChannel(channel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
result.data().vfull_chat().match([&](
|
||||
const MTPDchannelFull &d) {
|
||||
_data.switchedOff = d.is_restricted_sponsored();
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (!_isUser) {
|
||||
api().request(
|
||||
MTPchannels_GetFullChannel(channel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
result.data().vfull_chat().match([&](
|
||||
const MTPDchannelFull &d) {
|
||||
_data.switchedOff = d.is_restricted_sponsored();
|
||||
}, [](const auto &) {
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
} else {
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
@@ -729,7 +739,7 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
};
|
||||
}
|
||||
|
||||
void ChannelEarnStatistics::requestHistory(
|
||||
void EarnStatistics::requestHistory(
|
||||
const Data::EarnHistorySlice::OffsetToken &token,
|
||||
Fn<void(Data::EarnHistorySlice)> done) {
|
||||
if (_requestId) {
|
||||
@@ -738,7 +748,7 @@ void ChannelEarnStatistics::requestHistory(
|
||||
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
||||
constexpr auto kTlLimit = tl::make_int(kLimit);
|
||||
_requestId = api().request(MTPstats_GetBroadcastRevenueTransactions(
|
||||
channel()->inputChannel,
|
||||
(_isUser ? user()->input : channel()->input),
|
||||
MTP_int(token),
|
||||
(!token) ? kTlFirstSlice : kTlLimit
|
||||
)).done([=](const MTPstats_BroadcastRevenueTransactions &result) {
|
||||
@@ -799,7 +809,7 @@ void ChannelEarnStatistics::requestHistory(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::EarnStatistics ChannelEarnStatistics::data() const {
|
||||
Data::EarnStatistics EarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,9 +79,9 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class ChannelEarnStatistics final : public StatisticsRequestSender {
|
||||
class EarnStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit ChannelEarnStatistics(not_null<ChannelData*> channel);
|
||||
explicit EarnStatistics(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
void requestHistory(
|
||||
@@ -94,6 +94,7 @@ public:
|
||||
static constexpr auto kLimit = int(10);
|
||||
|
||||
private:
|
||||
const bool _isUser = false;
|
||||
Data::EarnStatistics _data;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
@@ -316,6 +316,9 @@ void Updates::feedUpdateVector(
|
||||
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
|
||||
return;
|
||||
}
|
||||
if (policy == SkipUpdatePolicy::SkipNone) {
|
||||
applyConvertToScheduledOnSend(updates);
|
||||
}
|
||||
for (const auto &entry : std::as_const(list)) {
|
||||
const auto type = entry.type();
|
||||
if ((policy == SkipUpdatePolicy::SkipMessageIds
|
||||
@@ -329,6 +332,15 @@ void Updates::feedUpdateVector(
|
||||
session().data().sendHistoryChangeNotifications();
|
||||
}
|
||||
|
||||
void Updates::checkForSentToScheduled(const MTPUpdates &updates) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
applyConvertToScheduledOnSend(data.vupdates(), true);
|
||||
}, [&](const MTPDupdatesCombined &data) {
|
||||
applyConvertToScheduledOnSend(data.vupdates(), true);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
void Updates::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
|
||||
for (const auto &update : updates.v) {
|
||||
if (update.type() == mtpc_updateMessageID) {
|
||||
@@ -432,6 +444,7 @@ void Updates::feedChannelDifference(
|
||||
session().data().processChats(data.vchats());
|
||||
|
||||
_handlingChannelDifference = true;
|
||||
applyConvertToScheduledOnSend(data.vother_updates());
|
||||
feedMessageIds(data.vother_updates());
|
||||
session().data().processMessages(
|
||||
data.vnew_messages(),
|
||||
@@ -596,6 +609,7 @@ void Updates::feedDifference(
|
||||
Core::App().checkAutoLock();
|
||||
session().data().processUsers(users);
|
||||
session().data().processChats(chats);
|
||||
applyConvertToScheduledOnSend(other);
|
||||
feedMessageIds(other);
|
||||
session().data().processMessages(msgs, NewMessageType::Unread);
|
||||
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
|
||||
@@ -881,6 +895,51 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::applyConvertToScheduledOnSend(
|
||||
const MTPVector<MTPUpdate> &other,
|
||||
bool skipScheduledCheck) {
|
||||
for (const auto &update : other.v) {
|
||||
update.match([&](const MTPDupdateNewScheduledMessage &data) {
|
||||
const auto &message = data.vmessage();
|
||||
const auto id = IdFromMessage(message);
|
||||
const auto scheduledMessages = &_session->scheduledMessages();
|
||||
const auto scheduledId = scheduledMessages->localMessageId(id);
|
||||
for (const auto &updateId : other.v) {
|
||||
updateId.match([&](const MTPDupdateMessageID &dataId) {
|
||||
if (dataId.vid().v == id) {
|
||||
auto &owner = session().data();
|
||||
if (skipScheduledCheck) {
|
||||
const auto peerId = PeerFromMessage(message);
|
||||
const auto history = owner.historyLoaded(peerId);
|
||||
if (history) {
|
||||
_session->data().sentToScheduled({
|
||||
.history = history,
|
||||
.scheduledId = scheduledId,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto rand = dataId.vrandom_id().v;
|
||||
const auto localId = owner.messageIdByRandomId(rand);
|
||||
if (const auto local = owner.message(localId)) {
|
||||
if (!local->isScheduled()) {
|
||||
_session->data().sentToScheduled({
|
||||
.history = local->history(),
|
||||
.scheduledId = scheduledId,
|
||||
});
|
||||
|
||||
// We've sent a non-scheduled message,
|
||||
// but it was converted to a scheduled.
|
||||
local->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
session().data().processUsers(data.vusers());
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
|
||||
void applyUpdateNoPtsCheck(const MTPUpdate &update);
|
||||
|
||||
void checkForSentToScheduled(const MTPUpdates &updates);
|
||||
|
||||
[[nodiscard]] int32 pts() const;
|
||||
|
||||
void updateOnline(crl::time lastNonIdleTime = 0);
|
||||
@@ -131,6 +133,9 @@ private:
|
||||
// Doesn't call sendHistoryChangeNotifications itself.
|
||||
void feedUpdate(const MTPUpdate &update);
|
||||
|
||||
void applyConvertToScheduledOnSend(
|
||||
const MTPVector<MTPUpdate> &other,
|
||||
bool skipScheduledCheck = false);
|
||||
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
|
||||
|
||||
bool whenGetDiffChanged(
|
||||
|
||||
@@ -756,5 +756,19 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
const style::WhoRead &st) {
|
||||
return WhoReacted(item, reaction, context, st, nullptr);
|
||||
}
|
||||
rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date) {
|
||||
return rpl::single(Ui::WhoReadContent{
|
||||
.participants = { Ui::WhoReadParticipant{
|
||||
.name = author->name(),
|
||||
.date = FormatReadDate(date, QDateTime::currentDateTime()),
|
||||
.id = author->id.value,
|
||||
} },
|
||||
.type = Ui::WhoReadType::Edited,
|
||||
.fullReadCount = 1,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -61,5 +61,8 @@ struct WhoReadList {
|
||||
const Data::ReactionId &reaction,
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -708,7 +708,8 @@ void ApiWrap::finalizeMessageDataRequest(
|
||||
|
||||
QString ApiWrap::exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext) {
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink) {
|
||||
Expects(item->history()->peer->isChannel());
|
||||
|
||||
const auto itemId = item->fullId();
|
||||
@@ -731,7 +732,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
const auto sender = root
|
||||
? root->discussionPostOriginalSender()
|
||||
: nullptr;
|
||||
if (sender && sender->hasUsername()) {
|
||||
if (sender && sender->hasUsername() && !forceNonPublicLink) {
|
||||
// Comment to a public channel.
|
||||
const auto forwarded = root->Get<HistoryMessageForwarded>();
|
||||
linkItemId = forwarded->savedFromMsgId;
|
||||
@@ -747,7 +748,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto base = linkChannel->hasUsername()
|
||||
const auto base = (linkChannel->hasUsername() && !forceNonPublicLink)
|
||||
? linkChannel->username()
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto post = QString::number(linkItemId.bare);
|
||||
@@ -761,6 +762,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
? (QString::number(linkThreadId.bare) + '/' + post)
|
||||
: post);
|
||||
if (linkChannel->hasUsername()
|
||||
&& !forceNonPublicLink
|
||||
&& !linkChannel->isMegagroup()
|
||||
&& !linkCommentId
|
||||
&& !linkThreadId) {
|
||||
@@ -774,6 +776,9 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
}
|
||||
return session().createInternalLinkFull(query);
|
||||
};
|
||||
if (forceNonPublicLink) {
|
||||
return fallback();
|
||||
}
|
||||
const auto i = _unlikelyMessageLinks.find(itemId);
|
||||
const auto current = (i != end(_unlikelyMessageLinks))
|
||||
? i->second
|
||||
@@ -3329,6 +3334,7 @@ void ApiWrap::forwardMessages(
|
||||
}
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
const auto idsCopy = localIds;
|
||||
const auto scheduled = action.options.scheduled;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -3341,6 +3347,9 @@ void ApiWrap::forwardMessages(
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (!scheduled) {
|
||||
this->updates().checkForSentToScheduled(result);
|
||||
}
|
||||
applyUpdates(result);
|
||||
if (shared && !--shared->requestsLeft) {
|
||||
shared->callback();
|
||||
@@ -3502,6 +3511,7 @@ void ApiWrap::sendVoiceMessage(
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
crl::time duration,
|
||||
bool video,
|
||||
const SendAction &action) {
|
||||
const auto caption = TextWithTags();
|
||||
const auto to = FileLoadTaskOptions(action);
|
||||
@@ -3510,6 +3520,7 @@ void ApiWrap::sendVoiceMessage(
|
||||
result,
|
||||
duration,
|
||||
waveform,
|
||||
video,
|
||||
to,
|
||||
caption));
|
||||
}
|
||||
@@ -4225,6 +4236,7 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
@@ -4296,6 +4308,9 @@ void ApiWrap::sendAlbumWithCancelled(
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
if (album->sent) {
|
||||
return;
|
||||
}
|
||||
const auto groupId = album->groupId;
|
||||
if (album->items.empty()) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
@@ -4320,6 +4335,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
return;
|
||||
} else if (medias.size() < 2) {
|
||||
const auto &single = medias.front().data();
|
||||
album->sent = true;
|
||||
sendMediaWithRandomId(
|
||||
sample,
|
||||
single.vmedia(),
|
||||
@@ -4346,6 +4362,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
|
||||
@@ -164,7 +164,8 @@ public:
|
||||
void requestMessageData(PeerData *peer, MsgId msgId, Fn<void()> done);
|
||||
QString exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext);
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink = false);
|
||||
QString exportDirectStoryLink(not_null<Data::Story*> item);
|
||||
|
||||
void requestContacts();
|
||||
@@ -317,6 +318,7 @@ public:
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
crl::time duration,
|
||||
bool video,
|
||||
const SendAction &action);
|
||||
void sendFiles(
|
||||
Ui::PreparedList &&list,
|
||||
|
||||
@@ -154,9 +154,7 @@ contactsSortButton: IconButton(defaultIconButton) {
|
||||
iconPosition: point(10px, -1px);
|
||||
rippleAreaPosition: point(1px, 6px);
|
||||
rippleAreaSize: 42px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }};
|
||||
contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }};
|
||||
@@ -416,9 +414,7 @@ calendarPrevious: IconButton {
|
||||
|
||||
rippleAreaPosition: point(2px, 2px);
|
||||
rippleAreaSize: 44px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }};
|
||||
calendarNext: IconButton(calendarPrevious) {
|
||||
@@ -616,9 +612,7 @@ proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
|
||||
proxyRowPadding: margins(22px, 8px, 8px, 8px);
|
||||
proxyRowIconSkip: 32px;
|
||||
proxyRowSkip: 2px;
|
||||
proxyRowRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
proxyRowRipple: defaultRippleAnimationBgOver;
|
||||
proxyRowTitleFg: windowFg;
|
||||
proxyRowTitlePalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: windowSubTextFg;
|
||||
@@ -683,9 +677,7 @@ themesMenuToggle: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 36px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
themesMenuPosition: point(-2px, 25px);
|
||||
|
||||
@@ -738,9 +730,7 @@ createPollOptionRemove: CrossButton {
|
||||
|
||||
duration: 150;
|
||||
loadingPeriod: 1000;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
createPollOptionRemovePosition: point(11px, 9px);
|
||||
createPollOptionEmojiPositionSkip: 4px;
|
||||
@@ -888,6 +878,13 @@ peerListWithInviteViaLink: PeerList(peerListBox) {
|
||||
peerListSingleRow: PeerList(peerListBox) {
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
}
|
||||
peerListSmallSkips: PeerList(peerListBox) {
|
||||
padding: margins(
|
||||
0px,
|
||||
defaultVerticalListSkip,
|
||||
0px,
|
||||
defaultVerticalListSkip);
|
||||
}
|
||||
|
||||
scheduleHeight: 95px;
|
||||
scheduleDateTop: 38px;
|
||||
@@ -951,9 +948,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
textTop: 7px;
|
||||
style: defaultTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
requestPeerRestriction: FlatLabel(defaultFlatLabel) {
|
||||
|
||||
@@ -238,7 +238,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
Fn<void()> saved)
|
||||
: _controller(controller)
|
||||
, _historyItem(item)
|
||||
, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
|
||||
, _isAllowedEditMedia(item->allowsEditMedia())
|
||||
, _albumType(ComputeAlbumType(item))
|
||||
, _controls(base::make_unique_q<Ui::VerticalLayout>(this))
|
||||
, _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll))
|
||||
@@ -253,8 +253,8 @@ EditCaptionBox::EditCaptionBox(
|
||||
, _initialText(std::move(text))
|
||||
, _initialList(std::move(list))
|
||||
, _saved(std::move(saved)) {
|
||||
Expects(item->media() != nullptr);
|
||||
Expects(item->media()->allowsEditCaption());
|
||||
Expects(!_initialList.files.empty());
|
||||
Expects(!item->media() || item->media()->allowsEditCaption());
|
||||
|
||||
_mediaEditManager.start(item, spoilered, invertCaption);
|
||||
|
||||
@@ -422,7 +422,8 @@ void EditCaptionBox::prepare() {
|
||||
setInitialText();
|
||||
|
||||
if (!setPreparedList(std::move(_initialList))) {
|
||||
rebuildPreview();
|
||||
crl::on_main(this, [=] { closeBox(); });
|
||||
return;
|
||||
}
|
||||
setupEditEventHandler();
|
||||
SetupShadowsToScrollContent(this, _scroll, _contentHeight.events());
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/prepare_short_info_box.h"
|
||||
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
|
||||
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -123,7 +124,8 @@ namespace {
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id) {
|
||||
PeerId id,
|
||||
bool withSendGiftButton = false) {
|
||||
auto result = object_ptr<Ui::AbstractButton>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
@@ -134,15 +136,40 @@ namespace {
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
peer->name(),
|
||||
withSendGiftButton ? peer->shortName() : peer->name(),
|
||||
st::giveawayGiftCodeValue);
|
||||
raw->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto send = withSendGiftButton
|
||||
? Ui::CreateChild<Ui::RoundButton>(
|
||||
raw,
|
||||
tr::lng_gift_send_small(),
|
||||
st::starGiftSmallButton)
|
||||
: nullptr;
|
||||
if (send) {
|
||||
send->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
send->setClickedCallback([=] {
|
||||
Ui::ShowStarGiftBox(controller->parentController(), peer);
|
||||
});
|
||||
}
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
send ? send->widthValue() : rpl::single(0)
|
||||
) | rpl::start_with_next([=](int width, int sendWidth) {
|
||||
const auto position = st::giveawayGiftCodeNamePosition;
|
||||
label->resizeToNaturalWidth(width - position.x());
|
||||
const auto sendSkip = sendWidth
|
||||
? (st::normalFont->spacew + sendWidth)
|
||||
: 0;
|
||||
label->resizeToNaturalWidth(width - position.x() - sendSkip);
|
||||
label->moveToLeft(position.x(), position.y(), width);
|
||||
const auto top = (raw->height() - userpic->height()) / 2;
|
||||
userpic->moveToLeft(0, top, width);
|
||||
if (send) {
|
||||
send->moveToLeft(
|
||||
position.x() + label->width() + st::normalFont->spacew,
|
||||
(position.y()
|
||||
+ st::giveawayGiftCodeValue.style.font->ascent
|
||||
- st::starGiftSmallButton.style.font->ascent),
|
||||
width);
|
||||
}
|
||||
}, label->lifetime());
|
||||
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
@@ -210,14 +237,82 @@ void AddTableRow(
|
||||
valueMargins);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void()> convertToStars) {
|
||||
auto result = object_ptr<Ui::RpWidget>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto session = &controller->session();
|
||||
const auto makeContext = [session](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(update),
|
||||
};
|
||||
};
|
||||
auto star = session->data().customEmojiManager().creditsEmoji();
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
rpl::single(
|
||||
star.append(' ' + Lang::FormatCountDecimal(entry.credits))),
|
||||
st::giveawayGiftCodeValue,
|
||||
st::defaultPopupMenu,
|
||||
std::move(makeContext));
|
||||
|
||||
const auto convert = convertToStars
|
||||
? Ui::CreateChild<Ui::RoundButton>(
|
||||
raw,
|
||||
tr::lng_gift_sell_small(
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.convertStars * 1.)),
|
||||
st::starGiftSmallButton)
|
||||
: nullptr;
|
||||
if (convert) {
|
||||
convert->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
convert->setClickedCallback(std::move(convertToStars));
|
||||
}
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
convert ? convert->widthValue() : rpl::single(0)
|
||||
) | rpl::start_with_next([=](int width, int convertWidth) {
|
||||
const auto convertSkip = convertWidth
|
||||
? (st::normalFont->spacew + convertWidth)
|
||||
: 0;
|
||||
label->resizeToNaturalWidth(width - convertSkip);
|
||||
label->moveToLeft(0, 0, width);
|
||||
if (convert) {
|
||||
convert->moveToLeft(
|
||||
label->width() + st::normalFont->spacew,
|
||||
(st::giveawayGiftCodeValue.style.font->ascent
|
||||
- st::starGiftSmallButton.style.font->ascent),
|
||||
width);
|
||||
}
|
||||
}, label->lifetime());
|
||||
|
||||
label->heightValue() | rpl::start_with_next([=](int height) {
|
||||
raw->resize(
|
||||
raw->width(),
|
||||
height + st::giveawayGiftCodeValueMargin.bottom());
|
||||
}, raw->lifetime());
|
||||
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<Ui::FlatLabel*> AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
rpl::producer<TextWithEntities> value) {
|
||||
rpl::producer<TextWithEntities> value,
|
||||
const Fn<std::any(Fn<void()>)> &makeContext = nullptr) {
|
||||
auto widget = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
std::move(value),
|
||||
st::giveawayGiftCodeValue);
|
||||
st::giveawayGiftCodeValue,
|
||||
st::defaultPopupMenu,
|
||||
std::move(makeContext));
|
||||
const auto result = widget.data();
|
||||
AddTableRow(
|
||||
table,
|
||||
@@ -939,26 +1034,56 @@ void ResolveGiveawayInfo(
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry) {
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void()> convertToStars) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
const auto withSendButton = entry.in;
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
controller,
|
||||
peerId);
|
||||
} else {
|
||||
MakePeerTableValue(table, controller, peerId, withSendButton),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
} else if (!entry.soldOutInfo) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
MakeHiddenPeerTableValue(table, controller),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
if (!entry.firstSaleDate.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_first_sale(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(entry.firstSaleDate))));
|
||||
}
|
||||
if (!entry.lastSaleDate.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_last_sale(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(entry.lastSaleDate))));
|
||||
}
|
||||
{
|
||||
const auto margin = st::giveawayGiftCodeValueMargin
|
||||
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_value(),
|
||||
MakeStarGiftStarsValue(
|
||||
table,
|
||||
controller,
|
||||
entry,
|
||||
std::move(convertToStars)),
|
||||
margin);
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
@@ -967,14 +1092,14 @@ void AddStarGiftTable(
|
||||
}
|
||||
if (entry.limitedCount > 0) {
|
||||
auto amount = rpl::single(TextWithEntities{
|
||||
QString::number(entry.limitedCount)
|
||||
Lang::FormatCountDecimal(entry.limitedCount)
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_availability(),
|
||||
((entry.limitedLeft > 0)
|
||||
? tr::lng_gift_availability_left(
|
||||
lt_count,
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.limitedLeft * 1.),
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
@@ -985,7 +1110,6 @@ void AddStarGiftTable(
|
||||
Ui::Text::WithEntities)));
|
||||
}
|
||||
if (!entry.description.empty()) {
|
||||
const auto session = &controller->session();
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
@@ -1044,7 +1168,9 @@ void AddCreditsHistoryEntryTable(
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_media(),
|
||||
(entry.reaction
|
||||
? tr::lng_credits_box_history_entry_message
|
||||
: tr::lng_credits_box_history_entry_media)(),
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
@@ -1138,6 +1264,14 @@ void AddCreditsHistoryEntryTable(
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
if (entry.floodSkip) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_floodskip_row(),
|
||||
rpl::single(
|
||||
Ui::Text::WithEntities(
|
||||
Lang::FormatCountDecimal(entry.floodSkip))));
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
@@ -1178,6 +1312,16 @@ void AddSubscriptionEntryTable(
|
||||
controller,
|
||||
peerId);
|
||||
if (!s.until.isNull()) {
|
||||
if (s.subscription.period > 0) {
|
||||
const auto subscribed = s.until.addSecs(-s.subscription.period);
|
||||
if (subscribed.isValid()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_group_invite_joined_row_date(),
|
||||
rpl::single(
|
||||
Ui::Text::WithEntities(langDateTime(subscribed))));
|
||||
}
|
||||
}
|
||||
AddTableRow(
|
||||
table,
|
||||
s.expired
|
||||
|
||||
@@ -57,7 +57,8 @@ void ResolveGiveawayInfo(
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void()> convertToStars);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
|
||||
@@ -206,7 +206,9 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
content()->selectSkipPage(height(), 1);
|
||||
} else if (e->key() == Qt::Key_PageUp) {
|
||||
content()->selectSkipPage(height(), -1);
|
||||
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
|
||||
} else if (e->key() == Qt::Key_Escape
|
||||
&& _select
|
||||
&& !_select->entity()->getQuery().isEmpty()) {
|
||||
_select->entity()->clearQuery();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
@@ -215,7 +217,16 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
void PeerListBox::searchQueryChanged(const QString &query) {
|
||||
scrollToY(0);
|
||||
content()->searchQueryChanged(query);
|
||||
const auto isEmpty = content()->searchQueryChanged(query);
|
||||
if (_specialTabsMode.enabled) {
|
||||
_specialTabsMode.searchIsActive = !isEmpty;
|
||||
if (_specialTabsMode.searchIsActive) {
|
||||
_specialTabsMode.topSkip = _addedTopScrollSkip;
|
||||
setAddedTopScrollSkip(0);
|
||||
} else {
|
||||
setAddedTopScrollSkip(_specialTabsMode.topSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::resizeEvent(QResizeEvent *e) {
|
||||
@@ -543,6 +554,19 @@ auto PeerListBox::collectSelectedRows()
|
||||
return result;
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerListBox::multiSelectHeightValue() const {
|
||||
return _select ? _select->heightValue() : rpl::single(0);
|
||||
}
|
||||
|
||||
void PeerListBox::setSpecialTabMode(bool value) {
|
||||
content()->setIgnoreHiddenRowsOnSearch(value);
|
||||
if (value) {
|
||||
_specialTabsMode.enabled = true;
|
||||
} else {
|
||||
_specialTabsMode = {};
|
||||
}
|
||||
}
|
||||
|
||||
PeerListRow::PeerListRow(not_null<PeerData*> peer)
|
||||
: PeerListRow(peer, peer->id.value) {
|
||||
}
|
||||
@@ -1385,10 +1409,12 @@ int PeerListContent::labelHeight() const {
|
||||
|
||||
void PeerListContent::refreshRows() {
|
||||
if (!_hiddenRows.empty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
if (!_ignoreHiddenRowsOnSearch || _normalizedSearchQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1944,6 +1970,13 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_controller->overrideKeyboardNavigation(
|
||||
direction,
|
||||
_selected.index.value,
|
||||
newSelectedIndex)) {
|
||||
return { _selected.index.value, _selected.index.value };
|
||||
}
|
||||
|
||||
_selected.index.value = newSelectedIndex;
|
||||
_selected.element = 0;
|
||||
if (newSelectedIndex >= 0) {
|
||||
@@ -2043,13 +2076,16 @@ void PeerListContent::checkScrollForPreload() {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::searchQueryChanged(QString query) {
|
||||
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
|
||||
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
||||
const auto normalizedQuery = searchWordsList.join(' ');
|
||||
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
}
|
||||
if (_normalizedSearchQuery != normalizedQuery) {
|
||||
setSearchQuery(query, normalizedQuery);
|
||||
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
|
||||
Assert(_hiddenRows.empty());
|
||||
Assert(_hiddenRows.empty() || _ignoreHiddenRowsOnSearch);
|
||||
|
||||
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
|
||||
for (const auto &searchWord : searchWordsList) {
|
||||
@@ -2097,6 +2133,7 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||
}
|
||||
refreshRows();
|
||||
}
|
||||
return _normalizedSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
||||
@@ -2185,6 +2222,10 @@ void PeerListContent::dragLeft() {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
void PeerListContent::setIgnoreHiddenRowsOnSearch(bool value) {
|
||||
_ignoreHiddenRowsOnSearch = value;
|
||||
}
|
||||
|
||||
void PeerListContent::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
||||
@@ -357,6 +357,8 @@ public:
|
||||
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
||||
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
|
||||
|
||||
virtual void peerListSelectSkip(int direction) = 0;
|
||||
|
||||
virtual void peerListPressLeftToContextMenu(bool shown) = 0;
|
||||
virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
|
||||
|
||||
@@ -573,6 +575,13 @@ public:
|
||||
Unexpected("PeerListController::customRowRippleMaskGenerator.");
|
||||
}
|
||||
|
||||
virtual bool overrideKeyboardNavigation(
|
||||
int direction,
|
||||
int fromIndex,
|
||||
int toIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
@@ -643,12 +652,15 @@ public:
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
|
||||
void searchQueryChanged(QString query);
|
||||
using IsEmpty = bool;
|
||||
IsEmpty searchQueryChanged(QString query);
|
||||
bool submitted();
|
||||
|
||||
PeerListRowId updateFromParentDrag(QPoint globalPosition);
|
||||
void dragLeft();
|
||||
|
||||
void setIgnoreHiddenRowsOnSearch(bool value);
|
||||
|
||||
// Interface for the controller.
|
||||
void appendRow(std::unique_ptr<PeerListRow> row);
|
||||
void appendSearchRow(std::unique_ptr<PeerListRow> row);
|
||||
@@ -870,6 +882,7 @@ private:
|
||||
int _aboveHeight = 0;
|
||||
int _belowHeight = 0;
|
||||
bool _hideEmpty = false;
|
||||
bool _ignoreHiddenRowsOnSearch = false;
|
||||
object_ptr<Ui::RpWidget> _aboveWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _belowWidget = { nullptr };
|
||||
@@ -1016,6 +1029,10 @@ public:
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
|
||||
|
||||
void peerListSelectSkip(int direction) override {
|
||||
_content->selectSkip(direction);
|
||||
}
|
||||
|
||||
void peerListPressLeftToContextMenu(bool shown) override {
|
||||
_content->pressLeftToContextMenu(shown);
|
||||
}
|
||||
@@ -1089,6 +1106,9 @@ public:
|
||||
|
||||
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
||||
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
|
||||
|
||||
void setSpecialTabMode(bool value);
|
||||
|
||||
void peerListSetTitle(rpl::producer<QString> title) override {
|
||||
setTitle(std::move(title));
|
||||
@@ -1155,4 +1175,11 @@ private:
|
||||
bool _scrollBottomFixed = false;
|
||||
int _addedTopScrollSkip = 0;
|
||||
|
||||
struct SpecialTabsMode final {
|
||||
bool enabled = false;
|
||||
bool searchIsActive = false;
|
||||
int topSkip = 0;
|
||||
};
|
||||
SpecialTabsMode _specialTabsMode;
|
||||
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
@@ -46,6 +47,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/bot/earn/info_bot_earn_widget.h"
|
||||
#include "info/channel_statistics/boosts/info_boosts_widget.h"
|
||||
#include "info/channel_statistics/earn/earn_format.h"
|
||||
#include "info/channel_statistics/earn/earn_icons.h"
|
||||
#include "info/channel_statistics/earn/info_channel_earn_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -352,7 +356,8 @@ private:
|
||||
void fillPendingRequestsButton();
|
||||
|
||||
void fillBotUsernamesButton();
|
||||
void fillBotBalanceButton();
|
||||
void fillBotCurrencyButton();
|
||||
void fillBotCreditsButton();
|
||||
void fillBotEditIntroButton();
|
||||
void fillBotEditCommandsButton();
|
||||
void fillBotEditSettingsButton();
|
||||
@@ -1174,7 +1179,8 @@ void Controller::fillManageSection() {
|
||||
|
||||
::AddSkip(container, 0);
|
||||
fillBotUsernamesButton();
|
||||
fillBotBalanceButton();
|
||||
fillBotCurrencyButton();
|
||||
fillBotCreditsButton();
|
||||
fillBotEditIntroButton();
|
||||
fillBotEditCommandsButton();
|
||||
fillBotEditSettingsButton();
|
||||
@@ -1583,7 +1589,72 @@ void Controller::fillBotUsernamesButton() {
|
||||
{ &st::menuIconLinks });
|
||||
}
|
||||
|
||||
void Controller::fillBotBalanceButton() {
|
||||
void Controller::fillBotCurrencyButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
struct State final {
|
||||
rpl::variable<QString> balance;
|
||||
};
|
||||
|
||||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto format = [=](uint64 balance) {
|
||||
return Info::ChannelEarn::MajorPart(balance)
|
||||
+ Info::ChannelEarn::MinorPart(balance);
|
||||
};
|
||||
const auto was = _peer->session().credits().balanceCurrency(
|
||||
_peer->id);
|
||||
if (was) {
|
||||
state->balance = format(was);
|
||||
}
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
_controls.buttonsLayout,
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_bot_balance_currency(),
|
||||
state->balance.value(),
|
||||
[controller = _navigation->parentController(), peer = _peer] {
|
||||
controller->showSection(Info::ChannelEarn::Make(peer));
|
||||
},
|
||||
st::manageGroupButton,
|
||||
{})));
|
||||
wrap->toggle(!state->balance.current().isEmpty(), anim::type::instant);
|
||||
|
||||
const auto button = wrap->entity();
|
||||
{
|
||||
const auto currencyLoad
|
||||
= button->lifetime().make_state<Api::EarnStatistics>(_peer);
|
||||
currencyLoad->request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
}, [=] {
|
||||
const auto balance = currencyLoad->data().currentBalance;
|
||||
if (balance) {
|
||||
wrap->toggle(true, anim::type::normal);
|
||||
}
|
||||
state->balance = format(balance);
|
||||
}, button->lifetime());
|
||||
}
|
||||
{
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
icon->resize(st::menuIconLinks.size());
|
||||
const auto image = Ui::Earn::MenuIconCurrency(icon->size());
|
||||
icon->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
p.drawImage(0, 0, image);
|
||||
}, icon->lifetime());
|
||||
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
icon->moveToLeft(
|
||||
button->st().iconLeft,
|
||||
(size.height() - icon->height()) / 2);
|
||||
}, icon->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::fillBotCreditsButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
struct State final {
|
||||
@@ -1593,7 +1664,7 @@ void Controller::fillBotBalanceButton() {
|
||||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
|
||||
state->balance = QString::number(balance);
|
||||
state->balance = Lang::FormatCountDecimal(balance);
|
||||
}
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
@@ -1601,7 +1672,7 @@ void Controller::fillBotBalanceButton() {
|
||||
_controls.buttonsLayout,
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_bot_balance(),
|
||||
tr::lng_manage_peer_bot_balance_credits(),
|
||||
state->balance.value(),
|
||||
[controller = _navigation->parentController(), peer = _peer] {
|
||||
controller->showSection(Info::BotEarn::Make(peer));
|
||||
@@ -1618,46 +1689,22 @@ void Controller::fillBotBalanceButton() {
|
||||
if (data.balance) {
|
||||
wrap->toggle(true, anim::type::normal);
|
||||
}
|
||||
state->balance = QString::number(data.balance);
|
||||
state->balance = Lang::FormatCountDecimal(data.balance);
|
||||
});
|
||||
}
|
||||
{
|
||||
constexpr auto kSizeShift = 3;
|
||||
constexpr auto kStrokeWidth = 5;
|
||||
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
icon->resize(Size(st::menuIconLinks.width() - kSizeShift));
|
||||
|
||||
auto colorized = [&] {
|
||||
auto f = QFile(Ui::Premium::Svg());
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return QString();
|
||||
}
|
||||
return QString::fromUtf8(
|
||||
f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q);
|
||||
}();
|
||||
colorized.replace(
|
||||
u"stroke=\"none\""_q,
|
||||
u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
|
||||
colorized.replace(
|
||||
u"stroke-width=\"1\""_q,
|
||||
u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
|
||||
const auto svg = icon->lifetime().make_state<QSvgRenderer>(
|
||||
colorized.toUtf8());
|
||||
svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
|
||||
|
||||
const auto starSize = Size(icon->height());
|
||||
|
||||
icon->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto image = Ui::Earn::MenuIconCredits();
|
||||
icon->resize(image.size() / style::DevicePixelRatio());
|
||||
icon->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
svg->render(&p, Rect(starSize));
|
||||
p.drawImage(0, 0, image);
|
||||
}, icon->lifetime());
|
||||
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
icon->moveToLeft(
|
||||
button->st().iconLeft + kSizeShift / 2.,
|
||||
button->st().iconLeft,
|
||||
(size.height() - icon->height()) / 2);
|
||||
}, icon->lifetime());
|
||||
}
|
||||
|
||||
@@ -956,12 +956,11 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto &stUser = st::boostReplaceUserpic;
|
||||
const auto photoSize = st::boostReplaceUserpic.photoSize;
|
||||
const auto session = &row->peer()->session();
|
||||
content->add(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(content, channel, stUser))
|
||||
)->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
Settings::SubscriptionUserpic(content, channel, photoSize)));
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
@@ -534,15 +534,16 @@ void PeerShortInfoCover::handleStreamingUpdate(
|
||||
|
||||
v::match(update.data, [&](Information &update) {
|
||||
streamingReady(std::move(update));
|
||||
}, [&](const PreloadedVideo &update) {
|
||||
}, [&](const UpdateVideo &update) {
|
||||
}, [](PreloadedVideo) {
|
||||
}, [&](UpdateVideo update) {
|
||||
_videoPosition = update.position;
|
||||
_widget->update();
|
||||
}, [&](const PreloadedAudio &update) {
|
||||
}, [&](const UpdateAudio &update) {
|
||||
}, [&](const WaitingForData &update) {
|
||||
}, [&](MutedByOther) {
|
||||
}, [&](Finished) {
|
||||
}, [](PreloadedAudio) {
|
||||
}, [](UpdateAudio) {
|
||||
}, [](WaitingForData) {
|
||||
}, [](SpeedEstimate) {
|
||||
}, [](MutedByOther) {
|
||||
}, [](Finished) {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -774,6 +775,10 @@ void PeerShortInfoBox::prepareRows() {
|
||||
result->setContextCopyText(contextCopyText);
|
||||
return result;
|
||||
};
|
||||
addInfoOneLine(
|
||||
tr::lng_settings_channel_label(),
|
||||
channelValue(),
|
||||
tr::lng_context_copy_link(tr::now));
|
||||
addInfoOneLine(
|
||||
tr::lng_info_link_label(),
|
||||
linkValue(),
|
||||
@@ -835,6 +840,13 @@ rpl::producer<QString> PeerShortInfoBox::nameValue() const {
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> PeerShortInfoBox::channelValue() const {
|
||||
return _fields.value(
|
||||
) | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
return Ui::Text::Link(fields.channelName, fields.channelLink);
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> PeerShortInfoBox::linkValue() const {
|
||||
return _fields.value(
|
||||
) | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
|
||||
@@ -37,6 +37,8 @@ enum class PeerShortInfoType {
|
||||
|
||||
struct PeerShortInfoFields {
|
||||
QString name;
|
||||
QString channelName;
|
||||
QString channelLink;
|
||||
QString phone;
|
||||
QString link;
|
||||
TextWithEntities about;
|
||||
@@ -169,6 +171,7 @@ private:
|
||||
int fillRoundedTopHeight();
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> nameValue() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> channelValue() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> linkValue() const;
|
||||
[[nodiscard]] rpl::producer<QString> phoneValue() const;
|
||||
[[nodiscard]] rpl::producer<QString> usernameValue() const;
|
||||
|
||||
@@ -202,6 +202,7 @@ void ProcessFullPhoto(
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
(UpdateFlag::Name
|
||||
| UpdateFlag::PersonalChannel
|
||||
| UpdateFlag::PhoneNumber
|
||||
| UpdateFlag::Username
|
||||
| UpdateFlag::About
|
||||
@@ -209,8 +210,20 @@ void ProcessFullPhoto(
|
||||
) | rpl::map([=] {
|
||||
const auto user = peer->asUser();
|
||||
const auto username = peer->username();
|
||||
const auto channelId = user->personalChannelId();
|
||||
const auto channel = channelId
|
||||
? user->owner().channel(channelId).get()
|
||||
: nullptr;
|
||||
const auto channelUsername = channel
|
||||
? channel->username()
|
||||
: QString();
|
||||
const auto hasChannel = !channelUsername.isEmpty();
|
||||
return PeerShortInfoFields{
|
||||
.name = peer->name(),
|
||||
.channelName = hasChannel ? channel->name() : QString(),
|
||||
.channelLink = (hasChannel
|
||||
? channel->session().createInternalLinkFull(channelUsername)
|
||||
: QString()),
|
||||
.phone = user ? Ui::FormatPhone(user->phone()) : QString(),
|
||||
.link = ((user || username.isEmpty())
|
||||
? QString()
|
||||
|
||||
@@ -27,13 +27,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> Report(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> ReportPhoto(
|
||||
not_null<PeerData*> peer,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data,
|
||||
not_null<PhotoData*> photo,
|
||||
const style::ReportBox *stOverride) {
|
||||
const auto source = v::match(data, [](const MessageIdsList &ids) {
|
||||
return Ui::ReportSource::Message;
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
const auto source = [&] {
|
||||
return peer->isUser()
|
||||
? (photo->hasVideo()
|
||||
? Ui::ReportSource::ProfileVideo
|
||||
@@ -45,19 +43,14 @@ namespace {
|
||||
: (photo->hasVideo()
|
||||
? Ui::ReportSource::ChannelVideo
|
||||
: Ui::ReportSource::ChannelPhoto);
|
||||
}, [&](StoryId id) {
|
||||
return Ui::ReportSource::Story;
|
||||
}, [](v::null_t) {
|
||||
Unexpected("Bad source report.");
|
||||
return Ui::ReportSource::Bot;
|
||||
});
|
||||
}();
|
||||
const auto st = stOverride ? stOverride : &st::defaultReportBox;
|
||||
return Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto show = box->uiShow();
|
||||
Ui::ReportReasonBox(box, *st, source, [=](Ui::ReportReason reason) {
|
||||
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
Ui::ReportDetailsBox(box, *st, [=](const QString &text) {
|
||||
Api::SendReport(show, peer, reason, text, data);
|
||||
Api::SendPhotoReport(show, peer, reason, text, photo);
|
||||
show->hideLayer();
|
||||
});
|
||||
}));
|
||||
@@ -70,7 +63,7 @@ namespace {
|
||||
object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PhotoData*> photo) {
|
||||
return Report(peer, photo, nullptr);
|
||||
return ReportPhoto(peer, photo, nullptr);
|
||||
}
|
||||
|
||||
void ShowReportMessageBox(
|
||||
@@ -86,7 +79,6 @@ void ShowReportMessageBox(
|
||||
auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
Data::ReportInput reportInput) -> void {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
report(reportInput, [=](const Api::ReportResult &result) {
|
||||
if (!result.error.isEmpty()) {
|
||||
if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
|
||||
@@ -206,6 +198,7 @@ void ShowReportMessageBox(
|
||||
}
|
||||
}));
|
||||
} else if (result.successful) {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
show->showToast(
|
||||
tr::lng_report_thanks(tr::now),
|
||||
kToastDuration);
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/widgets/chat_filters_tabs_strip.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
@@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/share_message_phrase_factory.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -81,11 +83,14 @@ public:
|
||||
void activateSkipColumn(int direction);
|
||||
void activateSkipPage(int pageHeight, int direction);
|
||||
void updateFilter(QString filter = QString());
|
||||
[[nodiscard]] bool isFilterEmpty() const;
|
||||
void selectActive();
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<> searchRequests() const;
|
||||
|
||||
void applyChatFilter(FilterId id);
|
||||
|
||||
protected:
|
||||
void visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
@@ -166,7 +171,9 @@ private:
|
||||
int _upon = -1;
|
||||
int _visibleTop = 0;
|
||||
|
||||
std::unique_ptr<Dialogs::IndexedList> _chatsIndexed;
|
||||
std::unique_ptr<Dialogs::IndexedList> _defaultChatsIndexed;
|
||||
std::unique_ptr<Dialogs::IndexedList> _customChatsIndexed;
|
||||
not_null<Dialogs::IndexedList*> _chatsIndexed;
|
||||
QString _filter;
|
||||
std::vector<not_null<Dialogs::Row*>> _filtered;
|
||||
|
||||
@@ -282,6 +289,10 @@ void ShareBox::prepare() {
|
||||
|
||||
_select->setQueryChangedCallback([=](const QString &query) {
|
||||
applyFilterUpdate(query);
|
||||
if (_chatsFilters) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}
|
||||
});
|
||||
_select->setItemRemovedCallback([=](uint64 itemId) {
|
||||
if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) {
|
||||
@@ -337,10 +348,32 @@ void ShareBox::prepare() {
|
||||
{ .suggestCustomEmoji = true });
|
||||
|
||||
_select->raise();
|
||||
|
||||
{
|
||||
const auto chatsFilters = AddChatFiltersTabsStrip(
|
||||
this,
|
||||
_descriptor.session,
|
||||
[this](FilterId id) {
|
||||
_inner->applyChatFilter(id);
|
||||
scrollToY(0);
|
||||
});
|
||||
chatsFilters->lower();
|
||||
chatsFilters->heightValue() | rpl::start_with_next([this](int h) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}, lifetime());
|
||||
_select->heightValue() | rpl::start_with_next([=](int h) {
|
||||
chatsFilters->moveToLeft(0, h);
|
||||
}, chatsFilters->lifetime());
|
||||
_chatsFilters = chatsFilters;
|
||||
}
|
||||
}
|
||||
|
||||
int ShareBox::getTopScrollSkip() const {
|
||||
return _select->isHidden() ? 0 : _select->height();
|
||||
return (_select->isHidden() ? 0 : _select->height())
|
||||
+ ((_chatsFilters && _inner && _inner->isFilterEmpty())
|
||||
? _chatsFilters->height()
|
||||
: 0);
|
||||
}
|
||||
|
||||
int ShareBox::getBottomScrollSkip() const {
|
||||
@@ -671,9 +704,10 @@ ShareBox::Inner::Inner(
|
||||
, _descriptor(descriptor)
|
||||
, _show(std::move(show))
|
||||
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
|
||||
, _chatsIndexed(
|
||||
, _defaultChatsIndexed(
|
||||
std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add)) {
|
||||
Dialogs::SortMode::Add))
|
||||
, _chatsIndexed(_defaultChatsIndexed.get()) {
|
||||
_rowsTop = st::shareRowsTop;
|
||||
_rowHeight = st::shareRowHeight;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
@@ -691,7 +725,7 @@ ShareBox::Inner::Inner(
|
||||
const auto self = _descriptor.session->user();
|
||||
const auto selfHistory = self->owner().history(self);
|
||||
if (_descriptor.filterCallback(selfHistory)) {
|
||||
_chatsIndexed->addToEnd(selfHistory);
|
||||
_defaultChatsIndexed->addToEnd(selfHistory);
|
||||
}
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
@@ -699,7 +733,7 @@ ShareBox::Inner::Inner(
|
||||
if (!history->peer->isSelf()
|
||||
&& (history->asForum()
|
||||
|| _descriptor.filterCallback(history))) {
|
||||
_chatsIndexed->addToEnd(history);
|
||||
_defaultChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -722,7 +756,7 @@ ShareBox::Inner::Inner(
|
||||
|
||||
_descriptor.session->changes().realtimeNameUpdates(
|
||||
) | rpl::start_with_next([=](const Data::NameUpdate &update) {
|
||||
_chatsIndexed->peerNameChanged(
|
||||
_defaultChatsIndexed->peerNameChanged(
|
||||
update.peer,
|
||||
update.oldFirstLetters);
|
||||
}, lifetime());
|
||||
@@ -1331,6 +1365,10 @@ void ShareBox::Inner::updateFilter(QString filter) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ShareBox::Inner::isFilterEmpty() const {
|
||||
return _filter.isEmpty();
|
||||
}
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> ShareBox::Inner::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
@@ -1339,6 +1377,29 @@ rpl::producer<> ShareBox::Inner::searchRequests() const {
|
||||
return _searchRequests.events();
|
||||
}
|
||||
|
||||
void ShareBox::Inner::applyChatFilter(FilterId id) {
|
||||
if (!id) {
|
||||
_chatsIndexed = _defaultChatsIndexed.get();
|
||||
} else {
|
||||
_customChatsIndexed = std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add);
|
||||
_chatsIndexed = _customChatsIndexed.get();
|
||||
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
if (const auto history = row->history()) {
|
||||
if (_descriptor.filterCallback(history)) {
|
||||
_customChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto &data = _descriptor.session->data();
|
||||
addList(data.chatsFilters().chatsList(id)->indexed());
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ShareBox::Inner::peopleReceived(
|
||||
const QString &query,
|
||||
const QVector<MTPPeer> &my,
|
||||
|
||||
@@ -174,6 +174,8 @@ private:
|
||||
bool _peopleFull = false;
|
||||
mtpRequestId _peopleRequest = 0;
|
||||
|
||||
RpWidget *_chatsFilters = nullptr;
|
||||
|
||||
using PeopleCache = QMap<QString, MTPcontacts_Found>;
|
||||
PeopleCache _peopleCache;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/send_credits_box.h"
|
||||
@@ -19,6 +20,7 @@ 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_credits.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -40,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_non_panel_process.h"
|
||||
#include "settings/settings_credits.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
@@ -213,7 +216,7 @@ auto GenerateGiftMedia(
|
||||
return tr::lng_action_gift_got_stars_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
gift.convertStars,
|
||||
gift.info.convertStars,
|
||||
Ui::Text::RichLangValue);
|
||||
});
|
||||
auto description = data.text.empty()
|
||||
@@ -280,7 +283,7 @@ void ShowSentToast(
|
||||
return tr::lng_gift_sent_about(
|
||||
tr::now,
|
||||
lt_count,
|
||||
gift.stars,
|
||||
gift.info.stars,
|
||||
Ui::Text::RichLangValue);
|
||||
});
|
||||
const auto strong = window->showToast({
|
||||
@@ -338,7 +341,10 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
|
||||
const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
|
||||
return FillAmountAndCurrency(data.cost, data.currency, true);
|
||||
}, [&](GiftTypeStars data) {
|
||||
return tr::lng_gift_stars_title(tr::now, lt_count, data.stars);
|
||||
return tr::lng_gift_stars_title(
|
||||
tr::now,
|
||||
lt_count,
|
||||
data.info.stars);
|
||||
});
|
||||
const auto text = tr::lng_action_gift_received(
|
||||
tr::now,
|
||||
@@ -508,14 +514,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
||||
const auto &gifts = api->starGifts();
|
||||
list.reserve(gifts.size());
|
||||
for (auto &gift : gifts) {
|
||||
list.push_back({
|
||||
.id = gift.id,
|
||||
.stars = gift.stars,
|
||||
.convertStars = gift.convertStars,
|
||||
.document = gift.document,
|
||||
.limitedCount = gift.limitedCount,
|
||||
.limitedLeft = gift.limitedLeft,
|
||||
});
|
||||
list.push_back({ .info = gift });
|
||||
}
|
||||
auto &map = Map[session];
|
||||
if (map.last != list) {
|
||||
@@ -587,7 +586,8 @@ struct GiftPriceTabs {
|
||||
auto sameKey = 0;
|
||||
for (const auto &gift : gifts) {
|
||||
if (same) {
|
||||
const auto key = gift.stars * (gift.limitedCount ? -1 : 1);
|
||||
const auto key = gift.info.stars
|
||||
* (gift.info.limitedCount ? -1 : 1);
|
||||
if (!sameKey) {
|
||||
sameKey = key;
|
||||
} else if (sameKey != key) {
|
||||
@@ -595,12 +595,12 @@ struct GiftPriceTabs {
|
||||
}
|
||||
}
|
||||
|
||||
if (gift.limitedCount
|
||||
if (gift.info.limitedCount
|
||||
&& (result.size() < 2 || result[1] != kPriceTabLimited)) {
|
||||
result.insert(begin(result) + 1, kPriceTabLimited);
|
||||
}
|
||||
if (!ranges::contains(result, gift.stars)) {
|
||||
result.push_back(gift.stars);
|
||||
if (!ranges::contains(result, gift.info.stars)) {
|
||||
result.push_back(gift.info.stars);
|
||||
}
|
||||
}
|
||||
if (same) {
|
||||
@@ -838,16 +838,38 @@ void SendGift(
|
||||
const auto processNonPanelPaymentFormFactory
|
||||
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
|
||||
Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
|
||||
.giftId = gift.id,
|
||||
.giftId = gift.info.id,
|
||||
.randomId = details.randomId,
|
||||
.message = details.text,
|
||||
.user = peer->asUser(),
|
||||
.limitedCount = gift.limitedCount,
|
||||
.limitedCount = gift.info.limitedCount,
|
||||
.anonymous = details.anonymous,
|
||||
}, done, processNonPanelPaymentFormFactory);
|
||||
});
|
||||
}
|
||||
|
||||
void SoldOutBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
const GiftTypeStars &gift) {
|
||||
Settings::ReceiptCreditsBox(
|
||||
box,
|
||||
window,
|
||||
Data::CreditsHistoryEntry{
|
||||
.firstSaleDate = base::unixtime::parse(gift.info.firstSaleDate),
|
||||
.lastSaleDate = base::unixtime::parse(gift.info.lastSaleDate),
|
||||
.credits = uint64(gift.info.stars),
|
||||
.bareGiftStickerId = gift.info.document->id,
|
||||
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
|
||||
.limitedCount = gift.info.limitedCount,
|
||||
.limitedLeft = gift.info.limitedLeft,
|
||||
.soldOutInfo = true,
|
||||
.gift = true,
|
||||
},
|
||||
Data::SubscriptionEntry());
|
||||
|
||||
}
|
||||
|
||||
void SendGiftBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
@@ -873,7 +895,7 @@ void SendGiftBox(
|
||||
};
|
||||
}, [&](const GiftTypeStars &data) {
|
||||
return Ui::CreditsEmojiSmall(session).append(
|
||||
Lang::FormatCountDecimal(std::abs(data.stars)));
|
||||
Lang::FormatCountDecimal(std::abs(data.info.stars)));
|
||||
});
|
||||
}());
|
||||
|
||||
@@ -1076,15 +1098,10 @@ void SendGiftBox(
|
||||
|
||||
button->setClickedCallback([=] {
|
||||
const auto star = std::get_if<GiftTypeStars>(&descriptor);
|
||||
if (star && star->limitedCount && !star->limitedLeft) {
|
||||
window->showToast({
|
||||
.title = tr::lng_gift_sold_out_title(tr::now),
|
||||
.text = tr::lng_gift_sold_out_text(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
star->limitedCount,
|
||||
Ui::Text::RichLangValue),
|
||||
});
|
||||
if (star
|
||||
&& star->info.limitedCount
|
||||
&& !star->info.limitedLeft) {
|
||||
window->show(Box(SoldOutBox, window, *star));
|
||||
} else {
|
||||
window->show(
|
||||
Box(SendGiftBox, window, peer, api, descriptor));
|
||||
@@ -1187,8 +1204,8 @@ void AddBlock(
|
||||
) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) {
|
||||
gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) {
|
||||
return (price == kPriceTabLimited)
|
||||
? (!gift.limitedCount)
|
||||
: (price && gift.stars != price);
|
||||
? (!gift.info.limitedCount)
|
||||
: (price && gift.info.stars != price);
|
||||
}), end(gifts));
|
||||
return GiftsDescriptor{
|
||||
gifts | ranges::to<std::vector<GiftDescriptor>>(),
|
||||
|
||||
@@ -409,9 +409,7 @@ callRatingStar: IconButton {
|
||||
icon: icon {{ "calls/call_rating", windowSubTextFg }};
|
||||
iconPosition: point(-1px, -1px);
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 36px;
|
||||
}
|
||||
@@ -1410,9 +1408,7 @@ groupCallRtmpShowButton: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 32px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
groupCallSettingsRtmpShowButton: IconButton(groupCallRtmpShowButton) {
|
||||
ripple: groupCallRipple;
|
||||
|
||||
@@ -73,8 +73,8 @@ private:
|
||||
|
||||
SourceButton _widget;
|
||||
FlatLabel _label;
|
||||
RoundRect _selectedRect;
|
||||
RoundRect _activeRect;
|
||||
Ui::RoundRect _selectedRect;
|
||||
Ui::RoundRect _activeRect;
|
||||
tgcalls::DesktopCaptureSource _source;
|
||||
std::unique_ptr<Preview> _preview;
|
||||
rpl::event_stream<> _activations;
|
||||
|
||||
@@ -150,6 +150,8 @@ SendButton {
|
||||
inner: IconButton;
|
||||
record: icon;
|
||||
recordOver: icon;
|
||||
round: icon;
|
||||
roundOver: icon;
|
||||
sendDisabledFg: color;
|
||||
}
|
||||
|
||||
@@ -331,9 +333,7 @@ stickersRemove: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaSize: 40px;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
stickersUndoRemove: RoundButton(defaultLightButton) {
|
||||
width: -16px;
|
||||
@@ -494,9 +494,7 @@ hashtagClose: IconButton {
|
||||
|
||||
rippleAreaPosition: point(5px, 5px);
|
||||
rippleAreaSize: 20px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
stickerPanWidthMin: 64px;
|
||||
@@ -898,9 +896,7 @@ historyBusinessBotSettings: IconButton(defaultIconButton) {
|
||||
iconPosition: point(-1px, -1px);
|
||||
rippleAreaSize: 40px;
|
||||
rippleAreaPosition: point(4px, 9px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
height: 58px;
|
||||
width: 48px;
|
||||
}
|
||||
@@ -927,9 +923,7 @@ historyReplyCancel: IconButton {
|
||||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
historyPinnedShowAll: IconButton(historyReplyCancel) {
|
||||
icon: icon {{ "pinned_show_all", historyReplyCancelFg }};
|
||||
@@ -1058,9 +1052,7 @@ historyAttach: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(2px, 3px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
historyMessagesTTL: IconButtonWithText {
|
||||
@@ -1082,6 +1074,13 @@ historyReplaceMedia: IconButton(historyAttach) {
|
||||
color: lightButtonBgOver;
|
||||
}
|
||||
}
|
||||
historyAddMedia: IconButton(historyAttach) {
|
||||
icon: icon {{ "chat/input_attach", windowBgActive }};
|
||||
iconOver: icon {{ "chat/input_attach", windowBgActive }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: lightButtonBgOver;
|
||||
}
|
||||
}
|
||||
|
||||
historyAttachEmojiActive: icon {{ "chat/input_smile_face", windowBgActive }};
|
||||
historyEmojiCircle: size(20px, 20px);
|
||||
@@ -1169,6 +1168,10 @@ historyRecordVoiceOnceFg: icon {{ "voice_lock/audio_once_number", windowFgActive
|
||||
historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }};
|
||||
historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }};
|
||||
historyRecordVoiceActive: icon {{ "chat/input_record_filled", historyRecordVoiceFgActiveIcon }};
|
||||
historyRecordRound: icon {{ "chat/input_video", historyRecordVoiceFg }};
|
||||
historyRecordRoundOver: icon {{ "chat/input_video", historyRecordVoiceFgOver }};
|
||||
historyRecordRoundActive: icon {{ "chat/input_video", historyRecordVoiceFgActiveIcon }};
|
||||
historyRecordRoundIconPosition: point(0px, 0px);
|
||||
historyRecordSendIconPosition: point(2px, 0px);
|
||||
historyRecordVoiceRippleBgActive: lightButtonBgOver;
|
||||
historyRecordSignalRadius: 5px;
|
||||
@@ -1214,6 +1217,7 @@ historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}
|
||||
historyRecordLockMargin: margins(4px, 4px, 4px, 4px);
|
||||
historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
|
||||
historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }};
|
||||
historyRecordLockRound: icon {{ "voice_lock/input_round_s", historyToDownFg }};
|
||||
historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
|
||||
|
||||
historyRecordDelete: IconButton(historyAttach) {
|
||||
@@ -1274,6 +1278,8 @@ historySend: SendButton {
|
||||
}
|
||||
record: historyRecordVoice;
|
||||
recordOver: historyRecordVoiceOver;
|
||||
round: historyRecordRound;
|
||||
roundOver: historyRecordRoundOver;
|
||||
sendDisabledFg: historyComposeIconFg;
|
||||
}
|
||||
|
||||
@@ -1287,9 +1293,7 @@ defaultComposeFilesMenu: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(1px, 6px);
|
||||
rippleAreaSize: 42px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
defaultComposeFilesField: InputField(defaultInputField) {
|
||||
textMargins: margins(1px, 26px, 31px, 4px);
|
||||
@@ -1349,9 +1353,7 @@ moreChatsBarClose: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(0px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
reportReasonTopSkip: 8px;
|
||||
@@ -1517,3 +1519,22 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
||||
sendGifBox: Box(defaultBox) {
|
||||
shadowIgnoreBottomSkip: true;
|
||||
}
|
||||
|
||||
processingVideoTipMaxWidth: 364px;
|
||||
processingVideoTipShift: 8px;
|
||||
processingVideoToast: Toast(defaultToast) {
|
||||
minWidth: 32px;
|
||||
maxWidth: 380px;
|
||||
padding: margins(19px, 17px, 19px, 17px);
|
||||
}
|
||||
processingVideoPreviewSkip: 8px;
|
||||
processingVideoView: RoundButton(defaultActiveButton) {
|
||||
width: -24px;
|
||||
height: 52px;
|
||||
textTop: 17px;
|
||||
textFg: mediaviewTextLinkFg;
|
||||
textFgOver: mediaviewTextLinkFg;
|
||||
textBg: transparent;
|
||||
textBgOver: transparent;
|
||||
ripple: emptyRippleAnimation;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ base::options::toggle TabbedPanelShowOnClick({
|
||||
|
||||
const char kOptionTabbedPanelShowOnClick[] = "tabbed-panel-show-on-click";
|
||||
|
||||
bool ShowPanelOnClick() {
|
||||
return TabbedPanelShowOnClick.value();
|
||||
}
|
||||
|
||||
TabbedPanel::TabbedPanel(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace ChatHelpers {
|
||||
class TabbedSelector;
|
||||
|
||||
extern const char kOptionTabbedPanelShowOnClick[];
|
||||
[[nodiscard]] bool ShowPanelOnClick();
|
||||
|
||||
struct TabbedPanelDescriptor {
|
||||
Window::SessionController *regularWindow = nullptr;
|
||||
|
||||
@@ -16,6 +16,7 @@ constexpr auto kDocumentLinkMediaProperty = 0x03;
|
||||
constexpr auto kSendReactionEmojiProperty = 0x04;
|
||||
constexpr auto kReactionsCountEmojiProperty = 0x05;
|
||||
constexpr auto kDocumentFilenameTooltipProperty = 0x06;
|
||||
constexpr auto kPhoneNumberLinkProperty = 0x07;
|
||||
|
||||
namespace Ui {
|
||||
class Show;
|
||||
|
||||
@@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
constexpr auto kInitialVideoQuality = 480; // Start with SD.
|
||||
|
||||
[[nodiscard]] WindowPosition Deserialize(const QByteArray &data) {
|
||||
QDataStream stream(data);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
@@ -88,6 +90,21 @@ void LogPosition(const WindowPosition &position, const QString &name) {
|
||||
return RecentEmojiDocument{ id, (test == '1') };
|
||||
}
|
||||
|
||||
[[nodiscard]] quint32 SerializeVideoQuality(Media::VideoQuality quality) {
|
||||
static_assert(sizeof(Media::VideoQuality) == sizeof(uint32));
|
||||
auto result = uint32();
|
||||
const auto data = static_cast<const void*>(&quality);
|
||||
memcpy(&result, data, sizeof(quality));
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] Media::VideoQuality DeserializeVideoQuality(quint32 value) {
|
||||
auto result = Media::VideoQuality();
|
||||
const auto data = static_cast<void*>(&result);
|
||||
memcpy(data, &value, sizeof(result));
|
||||
return (result.height <= 4320) ? result : Media::VideoQuality();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[nodiscard]] WindowPosition AdjustToScale(
|
||||
@@ -124,7 +141,8 @@ Settings::Settings()
|
||||
, _floatPlayerColumn(Window::Column::Second)
|
||||
, _floatPlayerCorner(RectPart::TopRight)
|
||||
, _dialogsWithChatWidthRatio(DefaultDialogsWidthRatio())
|
||||
, _dialogsNoChatWidthRatio(DefaultDialogsWidthRatio()) {
|
||||
, _dialogsNoChatWidthRatio(DefaultDialogsWidthRatio())
|
||||
, _videoQuality({ .height = kInitialVideoQuality }) {
|
||||
}
|
||||
|
||||
Settings::~Settings() = default;
|
||||
@@ -222,7 +240,7 @@ QByteArray Settings::serialize() const {
|
||||
+ Serialize::stringSize(_customFontFamily)
|
||||
+ sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken)
|
||||
+ sizeof(qint32);
|
||||
+ sizeof(qint32) * 6;
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -377,7 +395,12 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_systemUnlockEnabled ? 1 : 0)
|
||||
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
|
||||
<< _tonsiteStorageToken
|
||||
<< qint32(_includeMutedCounterFolders ? 1 : 0);
|
||||
<< qint32(_includeMutedCounterFolders ? 1 : 0)
|
||||
<< qint32(_chatFiltersHorizontal.current() ? 1 : 0)
|
||||
<< qint32(_skipToastsInFocus ? 1 : 0)
|
||||
<< qint32(_recordVideoMessages ? 1 : 0)
|
||||
<< SerializeVideoQuality(_videoQuality)
|
||||
<< qint32(_ivZoom.current());
|
||||
}
|
||||
|
||||
Ensures(result.size() == size);
|
||||
@@ -501,6 +524,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0;
|
||||
qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2;
|
||||
QByteArray tonsiteStorageToken = _tonsiteStorageToken;
|
||||
qint32 ivZoom = _ivZoom.current();
|
||||
qint32 skipToastsInFocus = _skipToastsInFocus ? 1 : 0;
|
||||
qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0;
|
||||
quint32 videoQuality = SerializeVideoQuality(_videoQuality);
|
||||
quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -810,6 +838,21 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> includeMutedCounterFolders;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> chatFiltersHorizontal;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> skipToastsInFocus;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> recordVideoMessages;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> videoQuality;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> ivZoom;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -1021,6 +1064,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
? std::optional<bool>()
|
||||
: (weatherInCelsius == 1);
|
||||
_tonsiteStorageToken = tonsiteStorageToken;
|
||||
_ivZoom = ivZoom;
|
||||
_skipToastsInFocus = (skipToastsInFocus == 1);
|
||||
_recordVideoMessages = (recordVideoMessages == 1);
|
||||
_videoQuality = DeserializeVideoQuality(videoQuality);
|
||||
_chatFiltersHorizontal = (chatFiltersHorizontal == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
@@ -1347,6 +1395,7 @@ void Settings::resetOnLastLogout() {
|
||||
_flashBounceNotify = true;
|
||||
_notifyView = NotifyView::ShowPreview;
|
||||
//_nativeNotifications = std::nullopt;
|
||||
//_skipToastsInFocus = false;
|
||||
//_notificationsCount = 3;
|
||||
//_notificationsCorner = ScreenCorner::BottomRight;
|
||||
_includeMutedCounter = true;
|
||||
@@ -1408,6 +1457,10 @@ void Settings::resetOnLastLogout() {
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
_storiesClickTooltipHidden = false;
|
||||
_ttlVoiceClickTooltipHidden = false;
|
||||
_ivZoom = 100;
|
||||
_recordVideoMessages = false;
|
||||
_videoQuality = {};
|
||||
_chatFiltersHorizontal = false;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
@@ -1465,6 +1518,14 @@ void Settings::setNativeNotifications(bool value) {
|
||||
: std::make_optional(value);
|
||||
}
|
||||
|
||||
bool Settings::skipToastsInFocus() const {
|
||||
return _skipToastsInFocus;
|
||||
}
|
||||
|
||||
void Settings::setSkipToastsInFocus(bool value) {
|
||||
_skipToastsInFocus = value;
|
||||
}
|
||||
|
||||
void Settings::setTranslateButtonEnabled(bool value) {
|
||||
_translateButtonEnabled = value;
|
||||
}
|
||||
@@ -1543,8 +1604,48 @@ auto Settings::skipTranslationLanguagesValue() const
|
||||
void Settings::setRememberedDeleteMessageOnlyForYou(bool value) {
|
||||
_rememberedDeleteMessageOnlyForYou = value;
|
||||
}
|
||||
|
||||
bool Settings::rememberedDeleteMessageOnlyForYou() const {
|
||||
return _rememberedDeleteMessageOnlyForYou;
|
||||
}
|
||||
|
||||
int Settings::ivZoom() const {
|
||||
return _ivZoom.current();
|
||||
}
|
||||
|
||||
rpl::producer<int> Settings::ivZoomValue() const {
|
||||
return _ivZoom.value();
|
||||
}
|
||||
|
||||
void Settings::setIvZoom(int value) {
|
||||
#ifdef Q_OS_WIN
|
||||
constexpr auto kMin = 25;
|
||||
constexpr auto kMax = 500;
|
||||
#else
|
||||
constexpr auto kMin = 30;
|
||||
constexpr auto kMax = 200;
|
||||
#endif
|
||||
_ivZoom = std::clamp(value, kMin, kMax);
|
||||
}
|
||||
|
||||
Media::VideoQuality Settings::videoQuality() const {
|
||||
return _videoQuality;
|
||||
}
|
||||
|
||||
void Settings::setVideoQuality(Media::VideoQuality value) {
|
||||
_videoQuality = value;
|
||||
}
|
||||
|
||||
bool Settings::chatFiltersHorizontal() const {
|
||||
return _chatFiltersHorizontal.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> Settings::chatFiltersHorizontalChanges() const {
|
||||
return _chatFiltersHorizontal.changes();
|
||||
}
|
||||
|
||||
void Settings::setChatFiltersHorizontal(bool value) {
|
||||
_chatFiltersHorizontal = value;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -220,6 +220,9 @@ public:
|
||||
[[nodiscard]] bool nativeNotifications() const;
|
||||
void setNativeNotifications(bool value);
|
||||
|
||||
[[nodiscard]] bool skipToastsInFocus() const;
|
||||
void setSkipToastsInFocus(bool value);
|
||||
|
||||
[[nodiscard]] int notificationsCount() const {
|
||||
return _notificationsCount;
|
||||
}
|
||||
@@ -625,6 +628,13 @@ public:
|
||||
return _floatPlayerCorner;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool recordVideoMessages() const {
|
||||
return _recordVideoMessages;
|
||||
}
|
||||
void setRecordVideoMessages(bool value) {
|
||||
_recordVideoMessages = value;
|
||||
}
|
||||
|
||||
void updateDialogsWidthRatio(float64 ratio, bool nochat);
|
||||
[[nodiscard]] float64 dialogsWidthRatio(bool nochat) const;
|
||||
|
||||
@@ -915,6 +925,17 @@ public:
|
||||
_tonsiteStorageToken = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] int ivZoom() const;
|
||||
[[nodiscard]] rpl::producer<int> ivZoomValue() const;
|
||||
void setIvZoom(int value);
|
||||
|
||||
[[nodiscard]] bool chatFiltersHorizontal() const;
|
||||
[[nodiscard]] rpl::producer<bool> chatFiltersHorizontalChanges() const;
|
||||
void setChatFiltersHorizontal(bool value);
|
||||
|
||||
[[nodiscard]] Media::VideoQuality videoQuality() const;
|
||||
void setVideoQuality(Media::VideoQuality quality);
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
|
||||
@@ -954,6 +975,7 @@ private:
|
||||
bool _flashBounceNotify = true;
|
||||
NotifyView _notifyView = NotifyView::ShowPreview;
|
||||
std::optional<bool> _nativeNotifications;
|
||||
bool _skipToastsInFocus = false;
|
||||
int _notificationsCount = 3;
|
||||
ScreenCorner _notificationsCorner = ScreenCorner::BottomRight;
|
||||
bool _includeMutedCounter = true;
|
||||
@@ -1050,6 +1072,9 @@ private:
|
||||
bool _systemUnlockEnabled = false;
|
||||
std::optional<bool> _weatherInCelsius;
|
||||
QByteArray _tonsiteStorageToken;
|
||||
rpl::variable<int> _ivZoom = 100;
|
||||
Media::VideoQuality _videoQuality;
|
||||
rpl::variable<bool> _chatFiltersHorizontal = false;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
@@ -1060,6 +1085,8 @@ private:
|
||||
bool _rememberedFlashBounceNotifyFromTray = false;
|
||||
bool _dialogsWidthSetToZeroWithoutChat = false;
|
||||
|
||||
bool _recordVideoMessages = false;
|
||||
|
||||
QByteArray _photoEditorBrush;
|
||||
|
||||
};
|
||||
|
||||
@@ -175,6 +175,34 @@ auto PersonalChannelController::chosen() const
|
||||
return _chosen.events();
|
||||
}
|
||||
|
||||
Window::SessionController *ApplyAccountIndex(
|
||||
not_null<Window::SessionController*> controller,
|
||||
int accountIndex) {
|
||||
if (accountIndex <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto list = Core::App().domain().orderedAccounts();
|
||||
if (accountIndex > int(list.size())) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto account = list[accountIndex - 1];
|
||||
if (account == &controller->session().account()) {
|
||||
return controller;
|
||||
} else if (const auto window = Core::App().windowFor({ account })) {
|
||||
if (&window->account() != account) {
|
||||
Core::App().domain().maybeActivate(account);
|
||||
if (&window->account() != account) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
const auto session = window->sessionController();
|
||||
if (session) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SavePersonalChannel(
|
||||
not_null<Window::SessionController*> window,
|
||||
ChannelData *channel) {
|
||||
@@ -471,6 +499,20 @@ bool ResolveUsernameOrPhone(
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
|
||||
if (params.contains(u"acc"_q)) {
|
||||
const auto switched = ApplyAccountIndex(
|
||||
controller,
|
||||
params.value(u"acc"_q).toInt());
|
||||
if (switched) {
|
||||
controller = switched;
|
||||
} else {
|
||||
controller->showToast(u"Could not activate account %1."_q.arg(
|
||||
params.value(u"acc"_q)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto domainParam = params.value(u"domain"_q);
|
||||
const auto appnameParam = params.value(u"appname"_q);
|
||||
const auto myContext = context.value<ClickHandlerContext>();
|
||||
@@ -965,6 +1007,17 @@ bool ShowStarsExamples(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowPopularAppsAbout(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
controller->show(Dialogs::PopularAppsAboutBox(controller));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
@@ -1431,6 +1484,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||
u"^stars_examples$"_q,
|
||||
ShowStarsExamples,
|
||||
},
|
||||
{
|
||||
u"^about_popular_apps$"_q,
|
||||
ShowPopularAppsAbout,
|
||||
},
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "core/phone_click_handler.h"
|
||||
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -48,6 +49,9 @@ public:
|
||||
|
||||
void handleKeyPress(not_null<QKeyEvent*> e) override;
|
||||
|
||||
[[nodiscard]] QString firstName() const;
|
||||
[[nodiscard]] QString lastName() const;
|
||||
|
||||
protected:
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
QImage prepareRippleMask() const override;
|
||||
@@ -130,6 +134,18 @@ ResolvePhoneAction::ResolvePhoneAction(
|
||||
prepare();
|
||||
}
|
||||
|
||||
QString ResolvePhoneAction::firstName() const {
|
||||
const auto peer = _peer.current();
|
||||
const auto user = peer ? peer->asUser() : nullptr;
|
||||
return user ? user->firstName : QString();
|
||||
}
|
||||
|
||||
QString ResolvePhoneAction::lastName() const {
|
||||
const auto peer = _peer.current();
|
||||
const auto user = peer ? peer->asUser() : nullptr;
|
||||
return user ? user->lastName : QString();
|
||||
}
|
||||
|
||||
void ResolvePhoneAction::paint(Painter &p) {
|
||||
const auto selected = isSelected() && _peer.current();
|
||||
const auto height = contentHeight();
|
||||
@@ -275,6 +291,7 @@ PhoneClickHandler::PhoneClickHandler(
|
||||
QString text)
|
||||
: _session(session)
|
||||
, _text(text) {
|
||||
setProperty(kPhoneNumberLinkProperty, _text);
|
||||
}
|
||||
|
||||
void PhoneClickHandler::onClick(ClickContext context) const {
|
||||
@@ -314,14 +331,29 @@ void PhoneClickHandler::onClick(ClickContext context) const {
|
||||
TextForMimeData::Simple(phone.trimmed()));
|
||||
}, &st::menuIconCopy);
|
||||
|
||||
auto resolvePhoneAction = base::make_unique_q<ResolvePhoneAction>(
|
||||
menu,
|
||||
menu->st().menu,
|
||||
phone,
|
||||
controller);
|
||||
|
||||
if (Trim(phone) != Trim(controller->session().user()->phone())) {
|
||||
menu->addAction(
|
||||
tr::lng_info_add_as_contact(tr::now),
|
||||
[=, raw = resolvePhoneAction.get()] {
|
||||
controller->show(
|
||||
Box<AddContactBox>(
|
||||
_session,
|
||||
raw->firstName(),
|
||||
raw->lastName(),
|
||||
Trim(phone)));
|
||||
},
|
||||
&st::menuIconInvite);
|
||||
}
|
||||
|
||||
menu->addSeparator(&st::popupMenuExpandedSeparator.menu.separator);
|
||||
|
||||
menu->addAction(
|
||||
base::make_unique_q<ResolvePhoneAction>(
|
||||
menu,
|
||||
menu->st().menu,
|
||||
phone,
|
||||
controller));
|
||||
menu->addAction(std::move(resolvePhoneAction));
|
||||
|
||||
menu->popup(pos);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 5006002;
|
||||
constexpr auto AppVersionStr = "5.6.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 5007003;
|
||||
constexpr auto AppVersionStr = "5.7.3";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -74,6 +74,11 @@ uint64 Credits::balance(PeerId peerId) const {
|
||||
return (it != _cachedPeerBalances.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
uint64 Credits::balanceCurrency(PeerId peerId) const {
|
||||
const auto it = _cachedPeerCurrencyBalances.find(peerId);
|
||||
return (it != _cachedPeerCurrencyBalances.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
rpl::producer<uint64> Credits::balanceValue() const {
|
||||
return _nonLockedBalance.value();
|
||||
}
|
||||
@@ -128,4 +133,8 @@ void Credits::apply(PeerId peerId, uint64 balance) {
|
||||
_cachedPeerBalances[peerId] = balance;
|
||||
}
|
||||
|
||||
void Credits::applyCurrency(PeerId peerId, uint64 balance) {
|
||||
_cachedPeerCurrencyBalances[peerId] = balance;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -35,6 +35,9 @@ public:
|
||||
[[nodiscard]] rpl::producer<float64> rateValue(
|
||||
not_null<PeerData*> ownedBotOrChannel);
|
||||
|
||||
void applyCurrency(PeerId peerId, uint64 balance);
|
||||
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
|
||||
|
||||
void lock(int count);
|
||||
void unlock(int count);
|
||||
void withdrawLocked(int count);
|
||||
@@ -50,6 +53,7 @@ private:
|
||||
std::unique_ptr<Api::CreditsStatus> _loader;
|
||||
|
||||
base::flat_map<PeerId, uint64> _cachedPeerBalances;
|
||||
base::flat_map<PeerId, uint64> _cachedPeerCurrencyBalances;
|
||||
|
||||
uint64 _balance = 0;
|
||||
uint64 _locked = 0;
|
||||
|
||||
@@ -112,6 +112,7 @@ void RecentPeers::applyLocal(QByteArray serialized) {
|
||||
).arg(streamAppVersion));
|
||||
_list.reserve(count);
|
||||
for (auto i = 0; i != int(count); ++i) {
|
||||
const auto streamPosition = stream.underlying().device()->pos();
|
||||
const auto peer = Serialize::readPeer(
|
||||
_session,
|
||||
streamAppVersion,
|
||||
@@ -123,6 +124,8 @@ void RecentPeers::applyLocal(QByteArray serialized) {
|
||||
DEBUG_LOG(("Suggestions: Failed RecentPeers reading %1 / %2."
|
||||
).arg(i + 1
|
||||
).arg(count));
|
||||
DEBUG_LOG(("Failed bytes: %1.").arg(
|
||||
QString::fromUtf8(serialized.mid(streamPosition).toHex())));
|
||||
_list.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -343,10 +343,20 @@ void ScheduledMessages::apply(
|
||||
if (i == end(_data)) {
|
||||
return;
|
||||
}
|
||||
for (const auto &id : update.vmessages().v) {
|
||||
const auto sent = update.vsent_messages();
|
||||
const auto &ids = update.vmessages().v;
|
||||
for (auto k = 0, count = int(ids.size()); k != count; ++k) {
|
||||
const auto id = ids[k].v;
|
||||
const auto &list = i->second;
|
||||
const auto j = list.itemById.find(id.v);
|
||||
const auto j = list.itemById.find(id);
|
||||
if (j != end(list.itemById)) {
|
||||
if (sent && k < sent->v.size()) {
|
||||
const auto &sentId = sent->v[k];
|
||||
_session->data().sentFromScheduled({
|
||||
.item = j->second,
|
||||
.sentId = sentId.v,
|
||||
});
|
||||
}
|
||||
j->second->destroy();
|
||||
i = _data.find(history);
|
||||
if (i == end(_data)) {
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/sponsored_message_bar.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::RichLangValue.
|
||||
|
||||
namespace Data {
|
||||
@@ -73,6 +74,9 @@ void SponsoredMessages::clearOldRequests() {
|
||||
|
||||
SponsoredMessages::AppendResult SponsoredMessages::append(
|
||||
not_null<History*> history) {
|
||||
if (isTopBarFor(history)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
@@ -194,7 +198,21 @@ void SponsoredMessages::inject(
|
||||
}
|
||||
|
||||
bool SponsoredMessages::canHaveFor(not_null<History*> history) const {
|
||||
return history->peer->isChannel();
|
||||
if (history->peer->isChannel()) {
|
||||
return true;
|
||||
} else if (const auto user = history->peer->asUser()) {
|
||||
return user->isBot();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SponsoredMessages::isTopBarFor(not_null<History*> history) const {
|
||||
if (peerIsUser(history->peer->id)) {
|
||||
if (const auto user = history->peer->asUser()) {
|
||||
return user->isBot();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) {
|
||||
@@ -218,10 +236,8 @@ void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) {
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto channel = history->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_GetSponsoredMessages(channel->inputChannel)
|
||||
MTPmessages_GetSponsoredMessages(history->peer->input)
|
||||
).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||
parse(history, result);
|
||||
if (done) {
|
||||
@@ -257,12 +273,62 @@ void SponsoredMessages::parse(
|
||||
list.postsBetween = postsBetween->v;
|
||||
list.state = State::InjectToMiddle;
|
||||
} else {
|
||||
list.state = State::AppendToEnd;
|
||||
list.state = history->peer->isChannel()
|
||||
? State::AppendToEnd
|
||||
: State::AppendToTopBar;
|
||||
}
|
||||
}, [](const MTPDmessages_sponsoredMessagesEmpty &) {
|
||||
});
|
||||
}
|
||||
|
||||
FullMsgId SponsoredMessages::fillTopBar(
|
||||
not_null<History*> history,
|
||||
not_null<Ui::RpWidget*> widget) {
|
||||
const auto it = _data.find(history);
|
||||
if (it != end(_data)) {
|
||||
auto &list = it->second;
|
||||
if (!list.entries.empty()) {
|
||||
const auto &entry = list.entries.front();
|
||||
const auto fullId = entry.itemFullId;
|
||||
Ui::FillSponsoredMessageBar(
|
||||
widget,
|
||||
_session,
|
||||
fullId,
|
||||
entry.sponsored.from,
|
||||
entry.sponsored.textWithEntities);
|
||||
return fullId;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
rpl::producer<> SponsoredMessages::itemRemoved(const FullMsgId &fullId) {
|
||||
if (IsServerMsgId(fullId.msg) || !fullId) {
|
||||
return rpl::never<>();
|
||||
}
|
||||
const auto history = _session->data().history(fullId.peer);
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return rpl::never<>();
|
||||
}
|
||||
auto &list = it->second;
|
||||
const auto entryIt = ranges::find_if(list.entries, [&](const Entry &e) {
|
||||
return e.itemFullId == fullId;
|
||||
});
|
||||
if (entryIt == end(list.entries)) {
|
||||
return rpl::never<>();
|
||||
}
|
||||
if (!entryIt->optionalDestructionNotifier) {
|
||||
entryIt->optionalDestructionNotifier
|
||||
= std::make_unique<rpl::lifetime>();
|
||||
entryIt->optionalDestructionNotifier->add([this, fullId] {
|
||||
_itemRemoved.fire_copy(fullId);
|
||||
});
|
||||
}
|
||||
return _itemRemoved.events(
|
||||
) | rpl::filter(rpl::mappers::_1 == fullId) | rpl::to_empty;
|
||||
}
|
||||
|
||||
void SponsoredMessages::append(
|
||||
not_null<History*> history,
|
||||
List &list,
|
||||
@@ -283,7 +349,9 @@ void SponsoredMessages::append(
|
||||
}, [&](const MTPDmessageMediaDocument &media) {
|
||||
if (const auto tlDocument = media.vdocument()) {
|
||||
tlDocument->match([&](const MTPDdocument &data) {
|
||||
const auto d = history->owner().processDocument(data);
|
||||
const auto d = history->owner().processDocument(
|
||||
data,
|
||||
media.valt_documents());
|
||||
if (d->isVideoFile()
|
||||
|| d->isSilentVideo()
|
||||
|| d->isAnimation()
|
||||
@@ -406,7 +474,7 @@ void SponsoredMessages::clearItems(not_null<History*> history) {
|
||||
|
||||
const SponsoredMessages::Entry *SponsoredMessages::find(
|
||||
const FullMsgId &fullId) const {
|
||||
if (!peerIsChannel(fullId.peer)) {
|
||||
if (!peerIsChannel(fullId.peer) && !peerIsUser(fullId.peer)) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto history = _session->data().history(fullId.peer);
|
||||
@@ -434,11 +502,11 @@ void SponsoredMessages::view(const FullMsgId &fullId) {
|
||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
const auto channel = entryPtr->item->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_ViewSponsoredMessage(
|
||||
channel->inputChannel,
|
||||
MTPmessages_ViewSponsoredMessage(
|
||||
entryPtr->item
|
||||
? entryPtr->item->history()->peer->input
|
||||
: _session->data().peer(fullId.peer)->input,
|
||||
MTP_bytes(randomId))
|
||||
).done([=] {
|
||||
auto &request = _viewRequests[randomId];
|
||||
@@ -489,14 +557,14 @@ void SponsoredMessages::clicked(
|
||||
return;
|
||||
}
|
||||
const auto randomId = entryPtr->sponsored.randomId;
|
||||
const auto channel = entryPtr->item->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
using Flag = MTPchannels_ClickSponsoredMessage::Flag;
|
||||
_session->api().request(MTPchannels_ClickSponsoredMessage(
|
||||
using Flag = MTPmessages_ClickSponsoredMessage::Flag;
|
||||
_session->api().request(MTPmessages_ClickSponsoredMessage(
|
||||
MTP_flags(Flag(0)
|
||||
| (isMedia ? Flag::f_media : Flag(0))
|
||||
| (isFullscreen ? Flag::f_fullscreen : Flag(0))),
|
||||
channel->inputChannel,
|
||||
entryPtr->item
|
||||
? entryPtr->item->history()->peer->input
|
||||
: _session->data().peer(fullId.peer)->input,
|
||||
MTP_bytes(randomId)
|
||||
)).send();
|
||||
}
|
||||
@@ -525,11 +593,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto history = entry->item->history();
|
||||
const auto channel = history->peer->asChannel();
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
const auto history = _session->data().history(fullId.peer);
|
||||
|
||||
const auto erase = [=] {
|
||||
const auto it = _data.find(history);
|
||||
@@ -548,8 +612,8 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
|
||||
}
|
||||
|
||||
state->requestId = _session->api().request(
|
||||
MTPchannels_ReportSponsoredMessage(
|
||||
channel->inputChannel,
|
||||
MTPmessages_ReportSponsoredMessage(
|
||||
history->peer->input,
|
||||
MTP_bytes(entry->sponsored.randomId),
|
||||
MTP_bytes(optionId))
|
||||
).done([=](
|
||||
|
||||
@@ -18,6 +18,10 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
|
||||
class MediaPreload;
|
||||
@@ -76,6 +80,7 @@ public:
|
||||
None,
|
||||
AppendToEnd,
|
||||
InjectToMiddle,
|
||||
AppendToTopBar,
|
||||
};
|
||||
struct Details {
|
||||
std::vector<TextWithEntities> info;
|
||||
@@ -94,10 +99,15 @@ public:
|
||||
~SponsoredMessages();
|
||||
|
||||
[[nodiscard]] bool canHaveFor(not_null<History*> history) const;
|
||||
[[nodiscard]] bool isTopBarFor(not_null<History*> history) const;
|
||||
void request(not_null<History*> history, Fn<void()> done);
|
||||
void clearItems(not_null<History*> history);
|
||||
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
|
||||
void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen);
|
||||
[[nodiscard]] FullMsgId fillTopBar(
|
||||
not_null<History*> history,
|
||||
not_null<Ui::RpWidget*> widget);
|
||||
[[nodiscard]] rpl::producer<> itemRemoved(const FullMsgId &);
|
||||
|
||||
[[nodiscard]] AppendResult append(not_null<History*> history);
|
||||
void inject(
|
||||
@@ -122,6 +132,7 @@ private:
|
||||
FullMsgId itemFullId;
|
||||
SponsoredMessage sponsored;
|
||||
std::unique_ptr<MediaPreload> preload;
|
||||
std::unique_ptr<rpl::lifetime> optionalDestructionNotifier;
|
||||
};
|
||||
struct List {
|
||||
std::vector<Entry> entries;
|
||||
@@ -156,6 +167,8 @@ private:
|
||||
base::flat_map<not_null<History*>, Request> _requests;
|
||||
base::flat_map<RandomId, Request> _viewRequests;
|
||||
|
||||
rpl::event_stream<FullMsgId> _itemRemoved;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -300,6 +300,7 @@ void TopPeers::applyLocal(QByteArray serialized) {
|
||||
_list.reserve(count);
|
||||
for (auto i = 0; i != int(count); ++i) {
|
||||
auto rating = quint64();
|
||||
const auto streamPosition = stream.underlying().device()->pos();
|
||||
const auto peer = Serialize::readPeer(
|
||||
_session,
|
||||
streamAppVersion,
|
||||
@@ -313,6 +314,8 @@ void TopPeers::applyLocal(QByteArray serialized) {
|
||||
} else {
|
||||
DEBUG_LOG(("Suggestions: "
|
||||
"Failed TopPeers reading %1 / %2.").arg(i + 1).arg(count));
|
||||
DEBUG_LOG(("Failed bytes: %1.").arg(
|
||||
QString::fromUtf8(serialized.mid(streamPosition).toHex())));
|
||||
_list.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,12 +46,15 @@ struct CreditsHistoryEntry final {
|
||||
Unsupported,
|
||||
PremiumBot,
|
||||
Ads,
|
||||
API,
|
||||
};
|
||||
|
||||
QString id;
|
||||
QString title;
|
||||
TextWithEntities description;
|
||||
QDateTime date;
|
||||
QDateTime firstSaleDate;
|
||||
QDateTime lastSaleDate;
|
||||
PhotoId photoId = 0;
|
||||
std::vector<CreditsHistoryMedia> extended;
|
||||
uint64 credits = 0;
|
||||
@@ -59,6 +62,7 @@ struct CreditsHistoryEntry final {
|
||||
uint64 barePeerId = 0;
|
||||
uint64 bareGiveawayMsgId = 0;
|
||||
uint64 bareGiftStickerId = 0;
|
||||
uint64 bareActorId = 0;
|
||||
PeerType peerType;
|
||||
QDateTime subscriptionUntil;
|
||||
QDateTime successDate;
|
||||
@@ -66,10 +70,12 @@ struct CreditsHistoryEntry final {
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int convertStars = 0;
|
||||
int floodSkip = 0;
|
||||
bool converted = false;
|
||||
bool anonymous = false;
|
||||
bool savedToProfile = false;
|
||||
bool fromGiftsList = false;
|
||||
bool soldOutInfo = false;
|
||||
bool reaction = false;
|
||||
bool refunded = false;
|
||||
bool pending = false;
|
||||
|
||||
@@ -332,6 +332,8 @@ void DocumentData::setattributes(
|
||||
|
||||
validateLottieSticker();
|
||||
|
||||
auto wasVideoData = isVideoFile() ? std::move(_additional) : nullptr;
|
||||
|
||||
_videoPreloadPrefix = 0;
|
||||
for (const auto &attribute : attributes) {
|
||||
attribute.match([&](const MTPDdocumentAttributeImageSize &data) {
|
||||
@@ -388,11 +390,21 @@ void DocumentData::setattributes(
|
||||
: VideoDocument;
|
||||
if (data.is_round_message()) {
|
||||
_additional = std::make_unique<RoundData>();
|
||||
} else if (const auto size = data.vpreload_prefix_size()) {
|
||||
if (size->v > 0 && size->v < kMaxAllowedPreloadPrefix) {
|
||||
_videoPreloadPrefix = size->v;
|
||||
} else {
|
||||
if (const auto size = data.vpreload_prefix_size()) {
|
||||
if (size->v > 0
|
||||
&& size->v < kMaxAllowedPreloadPrefix) {
|
||||
_videoPreloadPrefix = size->v;
|
||||
}
|
||||
}
|
||||
_additional = wasVideoData
|
||||
? std::move(wasVideoData)
|
||||
: std::make_unique<VideoData>();
|
||||
video()->codec = qs(
|
||||
data.vvideo_codec().value_or_empty());
|
||||
}
|
||||
} else if (type == VideoDocument && wasVideoData) {
|
||||
_additional = std::move(wasVideoData);
|
||||
} else if (const auto info = sticker()) {
|
||||
info->type = StickerType::Webm;
|
||||
}
|
||||
@@ -511,6 +523,108 @@ void DocumentData::setattributes(
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentData::setVideoQualities(const QVector<MTPDocument> &list) {
|
||||
auto qualities = std::vector<not_null<DocumentData*>>();
|
||||
qualities.reserve(list.size());
|
||||
for (const auto &document : list) {
|
||||
qualities.push_back(owner().processDocument(document));
|
||||
}
|
||||
setVideoQualities(std::move(qualities));
|
||||
}
|
||||
|
||||
void DocumentData::setVideoQualities(
|
||||
std::vector<not_null<DocumentData*>> qualities) {
|
||||
const auto data = video();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
auto count = int(qualities.size());
|
||||
if (qualities.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto good = [&](not_null<DocumentData*> document) {
|
||||
return document->isVideoFile()
|
||||
&& !document->dimensions.isEmpty()
|
||||
&& !document->inappPlaybackFailed()
|
||||
&& document->useStreamingLoader()
|
||||
&& document->canBeStreamed(nullptr);
|
||||
};
|
||||
ranges::sort(
|
||||
qualities,
|
||||
ranges::greater(),
|
||||
&DocumentData::resolveVideoQuality);
|
||||
for (auto i = 0; i != count - 1;) {
|
||||
const auto my = qualities[i];
|
||||
const auto next = qualities[i + 1];
|
||||
const auto myQuality = my->resolveVideoQuality();
|
||||
const auto nextQuality = next->resolveVideoQuality();
|
||||
const auto myGood = good(my);
|
||||
const auto nextGood = good(next);
|
||||
if (!myGood || !nextGood || myQuality == nextQuality) {
|
||||
const auto removeMe = !myGood
|
||||
|| (nextGood && (my->size > next->size));
|
||||
const auto from = i + (removeMe ? 1 : 2);
|
||||
for (auto j = from; j != count; ++j) {
|
||||
qualities[j - 1] = qualities[j];
|
||||
}
|
||||
--count;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (!qualities[count - 1]->resolveVideoQuality()) {
|
||||
--count;
|
||||
}
|
||||
qualities.erase(qualities.begin() + count, qualities.end());
|
||||
if (!qualities.empty()) {
|
||||
if (const auto mine = resolveVideoQuality()) {
|
||||
if (mine > qualities.front()->resolveVideoQuality()) {
|
||||
qualities.insert(begin(qualities), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
data->qualities = std::move(qualities);
|
||||
}
|
||||
|
||||
int DocumentData::resolveVideoQuality() const {
|
||||
const auto size = isVideoFile() ? dimensions : QSize();
|
||||
return size.isEmpty() ? 0 : std::min(size.width(), size.height());
|
||||
}
|
||||
|
||||
auto DocumentData::resolveQualities(HistoryItem *context) const
|
||||
-> const std::vector<not_null<DocumentData*>> & {
|
||||
static const auto empty = std::vector<not_null<DocumentData*>>();
|
||||
const auto info = video();
|
||||
const auto media = context ? context->media() : nullptr;
|
||||
if (!info || !media || media->document() != this) {
|
||||
return empty;
|
||||
}
|
||||
return media->hasQualitiesList() ? info->qualities : empty;
|
||||
}
|
||||
|
||||
not_null<DocumentData*> DocumentData::chooseQuality(
|
||||
HistoryItem *context,
|
||||
Media::VideoQuality request) {
|
||||
const auto &list = resolveQualities(context);
|
||||
if (list.empty() || !request.height) {
|
||||
return this;
|
||||
}
|
||||
const auto height = int(request.height);
|
||||
auto closest = this;
|
||||
auto closestAbs = std::abs(height - resolveVideoQuality());
|
||||
auto closestSize = size;
|
||||
for (const auto &quality : list) {
|
||||
const auto abs = std::abs(height - quality->resolveVideoQuality());
|
||||
if (abs < closestAbs
|
||||
|| (abs == closestAbs && quality->size < closestSize)) {
|
||||
closest = quality;
|
||||
closestAbs = abs;
|
||||
closestSize = quality->size;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
void DocumentData::validateLottieSticker() {
|
||||
if (type == FileDocument
|
||||
&& hasMimeType(u"application/x-tgsticker"_q)) {
|
||||
@@ -1384,6 +1498,16 @@ const RoundData *DocumentData::round() const {
|
||||
return const_cast<DocumentData*>(this)->round();
|
||||
}
|
||||
|
||||
VideoData *DocumentData::video() {
|
||||
return isVideoFile()
|
||||
? static_cast<VideoData*>(_additional.get())
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const VideoData *DocumentData::video() const {
|
||||
return const_cast<DocumentData*>(this)->video();
|
||||
}
|
||||
|
||||
bool DocumentData::hasRemoteLocation() const {
|
||||
return (_dc != 0 && _access != 0);
|
||||
}
|
||||
|
||||
@@ -31,11 +31,13 @@ struct Key;
|
||||
} // namespace Storage
|
||||
|
||||
namespace Media {
|
||||
namespace Streaming {
|
||||
class Loader;
|
||||
} // namespace Streaming
|
||||
struct VideoQuality;
|
||||
} // namespace Media
|
||||
|
||||
namespace Media::Streaming {
|
||||
class Loader;
|
||||
} // namespace Media::Streaming
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
class DocumentMedia;
|
||||
@@ -92,6 +94,11 @@ struct VoiceData : public DocumentAdditionalData {
|
||||
char wavemax = 0;
|
||||
};
|
||||
|
||||
struct VideoData : public DocumentAdditionalData {
|
||||
QString codec;
|
||||
std::vector<not_null<DocumentData*>> qualities;
|
||||
};
|
||||
|
||||
using RoundData = VoiceData;
|
||||
|
||||
namespace Serialize {
|
||||
@@ -108,8 +115,16 @@ public:
|
||||
|
||||
void setattributes(
|
||||
const QVector<MTPDocumentAttribute> &attributes);
|
||||
void setVideoQualities(const QVector<MTPDocument> &list);
|
||||
|
||||
void automaticLoadSettingsChanged();
|
||||
void setVideoQualities(std::vector<not_null<DocumentData*>> qualities);
|
||||
[[nodiscard]] int resolveVideoQuality() const;
|
||||
[[nodiscard]] auto resolveQualities(HistoryItem *context) const
|
||||
-> const std::vector<not_null<DocumentData*>> &;
|
||||
[[nodiscard]] not_null<DocumentData*> chooseQuality(
|
||||
HistoryItem *context,
|
||||
Media::VideoQuality request);
|
||||
|
||||
[[nodiscard]] bool loading() const;
|
||||
[[nodiscard]] QString loadingFilePath() const;
|
||||
@@ -161,6 +176,8 @@ public:
|
||||
[[nodiscard]] const VoiceData *voice() const;
|
||||
[[nodiscard]] RoundData *round();
|
||||
[[nodiscard]] const RoundData *round() const;
|
||||
[[nodiscard]] VideoData *video();
|
||||
[[nodiscard]] const VideoData *video() const;
|
||||
|
||||
void forceIsStreamedAnimation();
|
||||
[[nodiscard]] bool isVoiceMessage() const;
|
||||
|
||||
@@ -87,6 +87,7 @@ struct FileReferenceAccumulator {
|
||||
push(data.vphoto());
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
push(data.vdocument());
|
||||
push(data.valt_documents());
|
||||
}, [&](const MTPDmessageMediaWebPage &data) {
|
||||
push(data.vwebpage());
|
||||
}, [&](const MTPDmessageMediaGame &data) {
|
||||
|
||||
@@ -112,7 +112,9 @@ private:
|
||||
static constexpr auto kValidAfter = kLifeStartDate + kSpecialValueSkip;
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
return !_available || (_value >= kSpecialValueSkip);
|
||||
constexpr auto kMaxSum = uint32(std::numeric_limits<TimeId>::max());
|
||||
return (kMaxSum - _value > uint32(kLifeStartDate))
|
||||
&& (!_available || (_value >= kSpecialValueSkip));
|
||||
}
|
||||
|
||||
LastseenStatus(uint32 value, bool available, bool hiddenByMe)
|
||||
|
||||
@@ -551,6 +551,10 @@ DocumentData *Media::document() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Media::hasQualitiesList() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
PhotoData *Media::photo() const {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -964,12 +968,14 @@ MediaFile::MediaFile(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> document,
|
||||
bool skipPremiumEffect,
|
||||
bool hasQualitiesList,
|
||||
bool spoiler,
|
||||
crl::time ttlSeconds)
|
||||
: Media(parent)
|
||||
, _document(document)
|
||||
, _emoji(document->sticker() ? document->sticker()->alt : QString())
|
||||
, _skipPremiumEffect(skipPremiumEffect)
|
||||
, _hasQualitiesList(hasQualitiesList)
|
||||
, _spoiler(spoiler)
|
||||
, _ttlSeconds(ttlSeconds) {
|
||||
parent->history()->owner().registerDocumentItem(_document, parent);
|
||||
@@ -999,6 +1005,7 @@ std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
|
||||
parent,
|
||||
_document,
|
||||
!_document->session().premium(),
|
||||
_hasQualitiesList,
|
||||
_spoiler,
|
||||
_ttlSeconds);
|
||||
}
|
||||
@@ -1007,6 +1014,10 @@ DocumentData *MediaFile::document() const {
|
||||
return _document;
|
||||
}
|
||||
|
||||
bool MediaFile::hasQualitiesList() const {
|
||||
return _hasQualitiesList;
|
||||
}
|
||||
|
||||
bool MediaFile::uploading() const {
|
||||
return _document->uploading();
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ public:
|
||||
virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0;
|
||||
|
||||
virtual DocumentData *document() const;
|
||||
virtual bool hasQualitiesList() const;
|
||||
virtual PhotoData *photo() const;
|
||||
virtual WebPageData *webpage() const;
|
||||
virtual MediaWebPageFlags webpageFlags() const;
|
||||
@@ -287,6 +288,7 @@ public:
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> document,
|
||||
bool skipPremiumEffect,
|
||||
bool hasQualitiesList,
|
||||
bool spoiler,
|
||||
crl::time ttlSeconds);
|
||||
~MediaFile();
|
||||
@@ -294,6 +296,7 @@ public:
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
DocumentData *document() const override;
|
||||
bool hasQualitiesList() const override;
|
||||
|
||||
bool uploading() const override;
|
||||
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
|
||||
@@ -324,6 +327,7 @@ private:
|
||||
not_null<DocumentData*> _document;
|
||||
QString _emoji;
|
||||
bool _skipPremiumEffect = false;
|
||||
bool _hasQualitiesList = false;
|
||||
bool _spoiler = false;
|
||||
|
||||
// Video (unsupported) / Voice / Round.
|
||||
|
||||
@@ -40,6 +40,25 @@ std::vector<ReactionId> SearchTagsFromQuery(
|
||||
return result;
|
||||
}
|
||||
|
||||
HashtagWithUsername HashtagWithUsernameFromQuery(QStringView query) {
|
||||
const auto match = TextUtilities::RegExpHashtag(true).match(query);
|
||||
if (match.hasMatch()) {
|
||||
const auto username = match.capturedView(2).mid(1).toString();
|
||||
const auto offset = int(match.capturedLength(1));
|
||||
const auto full = int(query.size());
|
||||
const auto length = full
|
||||
- int(username.size())
|
||||
- 1
|
||||
- offset
|
||||
- int(match.capturedLength(3));
|
||||
if (!username.isEmpty() && length > 0 && offset + length <= full) {
|
||||
const auto hashtag = query.mid(offset, length).toString();
|
||||
return { hashtag, username };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString ReactionEntityData(const ReactionId &id) {
|
||||
if (id.empty()) {
|
||||
return {};
|
||||
|
||||
@@ -65,6 +65,13 @@ struct MessageReaction {
|
||||
[[nodiscard]] std::vector<ReactionId> SearchTagsFromQuery(
|
||||
const QString &query);
|
||||
|
||||
struct HashtagWithUsername {
|
||||
QString hashtag;
|
||||
QString username;
|
||||
};
|
||||
[[nodiscard]] HashtagWithUsername HashtagWithUsernameFromQuery(
|
||||
QStringView query);
|
||||
|
||||
[[nodiscard]] QString ReactionEntityData(const ReactionId &id);
|
||||
|
||||
[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
|
||||
|
||||
@@ -156,6 +156,18 @@ struct FullMsgId {
|
||||
MsgId msg = 0;
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
inline QDebug operator<<(QDebug debug, const FullMsgId &fullMsgId) {
|
||||
debug.nospace()
|
||||
<< "FullMsgId(peer: "
|
||||
<< fullMsgId.peer.value
|
||||
<< ", msg: "
|
||||
<< fullMsgId.msg.bare
|
||||
<< ")";
|
||||
return debug;
|
||||
}
|
||||
#endif // _DEBUG
|
||||
|
||||
Q_DECLARE_METATYPE(FullMsgId);
|
||||
|
||||
struct FullReplyTo {
|
||||
|
||||
@@ -3139,17 +3139,24 @@ not_null<DocumentData*> Session::document(DocumentId id) {
|
||||
return i->second.get();
|
||||
}
|
||||
|
||||
not_null<DocumentData*> Session::processDocument(const MTPDocument &data) {
|
||||
not_null<DocumentData*> Session::processDocument(
|
||||
const MTPDocument &data,
|
||||
const MTPVector<MTPDocument> *qualities) {
|
||||
return data.match([&](const MTPDdocument &data) {
|
||||
return processDocument(data);
|
||||
return processDocument(data, qualities);
|
||||
}, [&](const MTPDdocumentEmpty &data) {
|
||||
return document(data.vid().v);
|
||||
});
|
||||
}
|
||||
|
||||
not_null<DocumentData*> Session::processDocument(const MTPDdocument &data) {
|
||||
not_null<DocumentData*> Session::processDocument(
|
||||
const MTPDdocument &data,
|
||||
const MTPVector<MTPDocument> *qualities) {
|
||||
const auto result = document(data.vid().v);
|
||||
documentApplyFields(result, data);
|
||||
if (qualities) {
|
||||
result->setVideoQualities(qualities->v);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4806,6 +4813,22 @@ void Session::viewTagsChanged(
|
||||
}
|
||||
}
|
||||
|
||||
void Session::sentToScheduled(SentToScheduled value) {
|
||||
_sentToScheduled.fire(std::move(value));
|
||||
}
|
||||
|
||||
rpl::producer<SentToScheduled> Session::sentToScheduled() const {
|
||||
return _sentToScheduled.events();
|
||||
}
|
||||
|
||||
void Session::sentFromScheduled(SentFromScheduled value) {
|
||||
_sentFromScheduled.fire(std::move(value));
|
||||
}
|
||||
|
||||
rpl::producer<SentFromScheduled> Session::sentFromScheduled() const {
|
||||
return _sentFromScheduled.events();
|
||||
}
|
||||
|
||||
void Session::clearLocalStorage() {
|
||||
_cache->close();
|
||||
_cache->clear();
|
||||
|
||||
@@ -89,6 +89,15 @@ struct GiftUpdate {
|
||||
Action action = {};
|
||||
};
|
||||
|
||||
struct SentToScheduled {
|
||||
not_null<History*> history;
|
||||
MsgId scheduledId = 0;
|
||||
};
|
||||
struct SentFromScheduled {
|
||||
not_null<HistoryItem*> item;
|
||||
MsgId sentId = 0;
|
||||
};
|
||||
|
||||
class Session final {
|
||||
public:
|
||||
using ViewElement = HistoryView::Element;
|
||||
@@ -558,8 +567,12 @@ public:
|
||||
const ImageLocation &thumbnailLocation);
|
||||
|
||||
[[nodiscard]] not_null<DocumentData*> document(DocumentId id);
|
||||
not_null<DocumentData*> processDocument(const MTPDocument &data);
|
||||
not_null<DocumentData*> processDocument(const MTPDdocument &data);
|
||||
not_null<DocumentData*> processDocument(
|
||||
const MTPDocument &data,
|
||||
const MTPVector<MTPDocument> *qualities = nullptr);
|
||||
not_null<DocumentData*> processDocument(
|
||||
const MTPDdocument &data,
|
||||
const MTPVector<MTPDocument> *qualities = nullptr);
|
||||
not_null<DocumentData*> processDocument(
|
||||
const MTPdocument &data,
|
||||
const ImageWithLocation &thumbnail);
|
||||
@@ -787,6 +800,11 @@ public:
|
||||
std::vector<ReactionId> &&was,
|
||||
std::vector<ReactionId> &&now);
|
||||
|
||||
void sentToScheduled(SentToScheduled value);
|
||||
[[nodiscard]] rpl::producer<SentToScheduled> sentToScheduled() const;
|
||||
void sentFromScheduled(SentFromScheduled value);
|
||||
[[nodiscard]] rpl::producer<SentFromScheduled> sentFromScheduled() const;
|
||||
|
||||
void clearLocalStorage();
|
||||
|
||||
private:
|
||||
@@ -959,6 +977,8 @@ private:
|
||||
rpl::event_stream<ChatListEntryRefresh> _chatListEntryRefreshes;
|
||||
rpl::event_stream<> _unreadBadgeChanges;
|
||||
rpl::event_stream<RepliesReadTillUpdate> _repliesReadTillUpdates;
|
||||
rpl::event_stream<SentToScheduled> _sentToScheduled;
|
||||
rpl::event_stream<SentFromScheduled> _sentFromScheduled;
|
||||
|
||||
Dialogs::MainList _chatsList;
|
||||
Dialogs::IndexedList _contactsList;
|
||||
|
||||
@@ -62,7 +62,9 @@ using UpdateFlag = StoryUpdate::Flag;
|
||||
}, [&](const MTPDmessageMediaDocument &data)
|
||||
-> std::optional<StoryMedia> {
|
||||
if (const auto document = data.vdocument()) {
|
||||
const auto result = owner->processDocument(*document);
|
||||
const auto result = owner->processDocument(
|
||||
*document,
|
||||
data.valt_documents());
|
||||
if (!result->isNull()
|
||||
&& (result->isGifv() || result->isVideoFile())) {
|
||||
result->setStoryMedia(true);
|
||||
|
||||
@@ -41,6 +41,43 @@ bool PruneDestroyedAndSet(
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto LookupOtherQualities(
|
||||
DocumentData *original,
|
||||
not_null<DocumentData*> quality,
|
||||
HistoryItem *context)
|
||||
-> std::vector<Media::Streaming::QualityDescriptor> {
|
||||
if (!original || !context) {
|
||||
return {};
|
||||
}
|
||||
auto qualities = original->resolveQualities(context);
|
||||
if (qualities.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto result = std::vector<Media::Streaming::QualityDescriptor>();
|
||||
result.reserve(qualities.size());
|
||||
for (const auto &video : qualities) {
|
||||
if (video != quality) {
|
||||
if (const auto height = video->resolveVideoQuality()) {
|
||||
result.push_back({
|
||||
.sizeInBytes = uint32(video->size),
|
||||
.height = uint32(height),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto LookupOtherQualities(
|
||||
DocumentData *original,
|
||||
not_null<PhotoData*> quality,
|
||||
HistoryItem *context)
|
||||
-> std::vector<Media::Streaming::QualityDescriptor> {
|
||||
Expects(!original);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Streaming::Streaming(not_null<Session*> owner)
|
||||
@@ -50,7 +87,6 @@ Streaming::Streaming(not_null<Session*> owner)
|
||||
|
||||
Streaming::~Streaming() = default;
|
||||
|
||||
|
||||
template <typename Data>
|
||||
[[nodiscard]] std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
|
||||
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
|
||||
@@ -84,10 +120,16 @@ template <typename Data>
|
||||
base::flat_map<not_null<Data*>, std::weak_ptr<Document>> &documents,
|
||||
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
|
||||
not_null<Data*> data,
|
||||
DocumentData *original,
|
||||
HistoryItem *context,
|
||||
FileOrigin origin) {
|
||||
auto otherQualities = LookupOtherQualities(original, data, context);
|
||||
const auto i = documents.find(data);
|
||||
if (i != end(documents)) {
|
||||
if (auto result = i->second.lock()) {
|
||||
if (!otherQualities.empty()) {
|
||||
result->setOtherQualities(std::move(otherQualities));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -95,7 +137,10 @@ template <typename Data>
|
||||
if (!reader) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_shared<Document>(data, std::move(reader));
|
||||
auto result = std::make_shared<Document>(
|
||||
data,
|
||||
std::move(reader),
|
||||
std::move(otherQualities));
|
||||
if (!PruneDestroyedAndSet(documents, data, result)) {
|
||||
documents.emplace_or_assign(data, result);
|
||||
}
|
||||
@@ -136,7 +181,27 @@ std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
|
||||
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
|
||||
not_null<DocumentData*> document,
|
||||
FileOrigin origin) {
|
||||
return sharedDocument(_fileDocuments, _fileReaders, document, origin);
|
||||
return sharedDocument(
|
||||
_fileDocuments,
|
||||
_fileReaders,
|
||||
document,
|
||||
nullptr,
|
||||
nullptr,
|
||||
origin);
|
||||
}
|
||||
|
||||
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
|
||||
not_null<DocumentData*> quality,
|
||||
not_null<DocumentData*> original,
|
||||
HistoryItem *context,
|
||||
FileOrigin origin) {
|
||||
return sharedDocument(
|
||||
_fileDocuments,
|
||||
_fileReaders,
|
||||
quality,
|
||||
original,
|
||||
context,
|
||||
origin);
|
||||
}
|
||||
|
||||
std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
|
||||
@@ -149,7 +214,13 @@ std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
|
||||
std::shared_ptr<Streaming::Document> Streaming::sharedDocument(
|
||||
not_null<PhotoData*> photo,
|
||||
FileOrigin origin) {
|
||||
return sharedDocument(_photoDocuments, _photoReaders, photo, origin);
|
||||
return sharedDocument(
|
||||
_photoDocuments,
|
||||
_photoReaders,
|
||||
photo,
|
||||
nullptr,
|
||||
nullptr,
|
||||
origin);
|
||||
}
|
||||
|
||||
void Streaming::keepAlive(not_null<DocumentData*> document) {
|
||||
|
||||
@@ -12,12 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
class PhotoData;
|
||||
class DocumentData;
|
||||
|
||||
namespace Media {
|
||||
namespace Streaming {
|
||||
namespace Media::Streaming {
|
||||
class Reader;
|
||||
class Document;
|
||||
} // namespace Streaming
|
||||
} // namespace Media
|
||||
} // namespace Media::Streaming
|
||||
|
||||
namespace Data {
|
||||
|
||||
@@ -41,6 +39,11 @@ public:
|
||||
[[nodiscard]] std::shared_ptr<Document> sharedDocument(
|
||||
not_null<DocumentData*> document,
|
||||
FileOrigin origin);
|
||||
[[nodiscard]] std::shared_ptr<Document> sharedDocument(
|
||||
not_null<DocumentData*> quality,
|
||||
not_null<DocumentData*> original,
|
||||
HistoryItem *context,
|
||||
FileOrigin origin);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Reader> sharedReader(
|
||||
not_null<PhotoData*> photo,
|
||||
@@ -68,6 +71,8 @@ private:
|
||||
base::flat_map<not_null<Data*>, std::weak_ptr<Document>> &documents,
|
||||
base::flat_map<not_null<Data*>, std::weak_ptr<Reader>> &readers,
|
||||
not_null<Data*> data,
|
||||
DocumentData *original,
|
||||
HistoryItem *context,
|
||||
FileOrigin origin);
|
||||
|
||||
template <typename Data>
|
||||
|
||||
@@ -327,6 +327,8 @@ enum class MessageFlag : uint64 {
|
||||
|
||||
SensitiveContent = (1ULL << 47),
|
||||
HasRestrictions = (1ULL << 48),
|
||||
|
||||
EstimatedDate = (1ULL << 49),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
68
Telegram/SourceFiles/data/data_unread_value.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_unread_value.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
rpl::producer<Dialogs::UnreadState> MainListUnreadState(
|
||||
not_null<Dialogs::MainList*> list) {
|
||||
return rpl::single(rpl::empty) | rpl::then(
|
||||
list->unreadStateChanges() | rpl::to_empty
|
||||
) | rpl::map([=] {
|
||||
return list->unreadState();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state) {
|
||||
const auto folderId = Data::Folder::kId;
|
||||
if (const auto folder = session->data().folderLoaded(folderId)) {
|
||||
return state - folder->chatsList()->unreadState();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId) {
|
||||
if (filterId > 0) {
|
||||
const auto filters = &session->data().chatsFilters();
|
||||
return MainListUnreadState(filters->chatsList(filterId));
|
||||
}
|
||||
return MainListUnreadState(
|
||||
session->data().chatsList()
|
||||
) | rpl::map([=](const Dialogs::UnreadState &state) {
|
||||
return MainListMapUnreadState(session, state);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<bool> IncludeMutedCounterFoldersValue() {
|
||||
using namespace Window::Notifications;
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
Core::App().notifications().settingsChanged(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == ChangeType::IncludeMuted
|
||||
) | rpl::to_empty
|
||||
) | rpl::map([] {
|
||||
return Core::App().settings().includeMutedCounterFolders();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
30
Telegram/SourceFiles/data/data_unread_value.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Dialogs {
|
||||
struct UnreadState;
|
||||
} // namespace Dialogs
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state);
|
||||
|
||||
[[nodiscard]] rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> IncludeMutedCounterFoldersValue();
|
||||
|
||||
} // namespace Data
|
||||
@@ -7,12 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_user.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_sensitive_content.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/business/data_business_common.h"
|
||||
#include "data/business/data_business_info.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer_bot_command.h"
|
||||
@@ -635,6 +638,35 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||
user,
|
||||
Data::PeerUpdate::Flag::Rights);
|
||||
}
|
||||
if (info->canEditInformation) {
|
||||
const auto id = user->id;
|
||||
const auto weak = base::make_weak(&user->session());
|
||||
const auto creditsLoadLifetime
|
||||
= std::make_shared<rpl::lifetime>();
|
||||
const auto creditsLoad
|
||||
= creditsLoadLifetime->make_state<Api::CreditsStatus>(user);
|
||||
creditsLoad->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->credits().apply(id, slice.balance);
|
||||
creditsLoadLifetime->destroy();
|
||||
}
|
||||
});
|
||||
const auto currencyLoadLifetime
|
||||
= std::make_shared<rpl::lifetime>();
|
||||
const auto currencyLoad
|
||||
= currencyLoadLifetime->make_state<Api::EarnStatistics>(user);
|
||||
currencyLoad->request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
currencyLoadLifetime->destroy();
|
||||
}, [=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->credits().applyCurrency(
|
||||
id,
|
||||
currencyLoad->data().currentBalance);
|
||||
currencyLoadLifetime->destroy();
|
||||
}
|
||||
}, *currencyLoadLifetime);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto paper = update.vwallpaper()) {
|
||||
|
||||
@@ -222,9 +222,7 @@ dialogsMenuToggle: IconButton {
|
||||
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
dialogsMenuToggleUnread: icon {
|
||||
{ "dialogs/dialogs_menu_unread", dialogsMenuIconFg },
|
||||
@@ -745,3 +743,7 @@ dialogsSearchTagPromoLeft: 6px;
|
||||
dialogsSearchTagPromoRight: 1px;
|
||||
dialogsSearchTagPromoSkip: 6px;
|
||||
|
||||
dialogsPopularAppsPadding: margins(10px, 8px, 10px, 12px);
|
||||
dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) {
|
||||
minWidth: 128px;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/ui/chat_search_empty.h"
|
||||
#include "dialogs/ui/chat_search_in.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
#include "dialogs/ui/dialogs_video_userpic.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/dialogs_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "dialogs/dialogs_search_tags.h"
|
||||
#include "history/view/history_view_chat_preview.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -44,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
@@ -68,7 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/loading_element.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "boxes/peers/edit_forum_topic_box.h"
|
||||
@@ -88,6 +84,7 @@ namespace {
|
||||
constexpr auto kHashtagResultsLimit = 5;
|
||||
constexpr auto kStartReorderThreshold = 30;
|
||||
constexpr auto kQueryPreviewLimit = 32;
|
||||
constexpr auto kPreviewPostsLimit = 3;
|
||||
|
||||
[[nodiscard]] int FixedOnTopDialogsCount(not_null<Dialogs::IndexedList*> list) {
|
||||
auto result = 0;
|
||||
@@ -555,7 +552,7 @@ int InnerWidget::searchInChatSkip() const {
|
||||
return _searchIn ? _searchIn->height() : 0;
|
||||
}
|
||||
|
||||
int InnerWidget::searchedOffset() const {
|
||||
int InnerWidget::previewOffset() const {
|
||||
auto result = peerSearchOffset();
|
||||
if (!_peerSearchResults.empty()) {
|
||||
result += (_peerSearchResults.size() * st::dialogsRowHeight)
|
||||
@@ -564,6 +561,15 @@ int InnerWidget::searchedOffset() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
int InnerWidget::searchedOffset() const {
|
||||
auto result = previewOffset();
|
||||
if (!_previewResults.empty()) {
|
||||
result += (_previewResults.size() * st::dialogsRowHeight)
|
||||
+ st::searchedBarHeight;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
|
||||
Expects(!folder || !_savedSublists);
|
||||
|
||||
@@ -810,7 +816,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
if (_searchIn) {
|
||||
p.translate(0, searchInChatSkip());
|
||||
if (_searchResults.empty()) {
|
||||
if (_previewResults.empty() && _searchResults.empty()) {
|
||||
p.fillRect(0, 0, fullWidth, st::lineWidth, st::shadowFg);
|
||||
}
|
||||
}
|
||||
@@ -924,7 +930,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
|
||||
const auto showUnreadInSearchResults = uniqueSearchResults();
|
||||
if (_searchResults.empty()) {
|
||||
if (_previewResults.empty() && _searchResults.empty()) {
|
||||
if (_loadingAnimation) {
|
||||
const auto text = tr::lng_contacts_loading(tr::now);
|
||||
p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg);
|
||||
@@ -933,7 +939,68 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
|
||||
p.translate(0, st::searchedBarHeight);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (!_previewResults.empty()) {
|
||||
const auto text = tr::lng_search_tab_public_posts(tr::now);
|
||||
p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg);
|
||||
p.setFont(st::searchedBarFont);
|
||||
p.setPen(st::searchedBarFg);
|
||||
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
|
||||
const auto moreFont = (_selectedMorePosts || _pressedMorePosts)
|
||||
? st::searchedBarFont->underline()
|
||||
: st::searchedBarFont;
|
||||
{
|
||||
const auto text = tr::lng_channels_your_more(tr::now);
|
||||
if (!_morePostsWidth) {
|
||||
_morePostsWidth = moreFont->width(text);
|
||||
}
|
||||
p.setFont(moreFont);
|
||||
p.drawTextLeft(
|
||||
width() - st::searchedBarPosition.x() - _morePostsWidth,
|
||||
st::searchedBarPosition.y(),
|
||||
width(),
|
||||
text);
|
||||
p.translate(0, st::searchedBarHeight);
|
||||
}
|
||||
auto skip = previewOffset();
|
||||
auto from = floorclamp(r.y() - skip, _st->height, 0, _previewResults.size());
|
||||
auto to = ceilclamp(r.y() + r.height() - skip, _st->height, 0, _previewResults.size());
|
||||
p.translate(0, from * _st->height);
|
||||
if (from < _previewResults.size()) {
|
||||
for (; from < to; ++from) {
|
||||
const auto &result = _previewResults[from];
|
||||
const auto active = isSearchResultActive(result.get(), activeEntry);
|
||||
const auto selected = _menuRow.key
|
||||
? isSearchResultActive(result.get(), _menuRow)
|
||||
: _chatPreviewRow.key
|
||||
? isSearchResultActive(result.get(), _chatPreviewRow)
|
||||
: (from == (isPressed()
|
||||
? _previewPressed
|
||||
: _previewSelected));
|
||||
Ui::RowPainter::Paint(p, result.get(), {
|
||||
.st = _st,
|
||||
.folder = _openedFolder,
|
||||
.forum = _openedForum,
|
||||
.currentBg = currentBg(),
|
||||
.filter = _filterId,
|
||||
.now = ms,
|
||||
.width = fullWidth,
|
||||
.active = active,
|
||||
.selected = selected,
|
||||
.paused = videoPaused,
|
||||
.search = true,
|
||||
.narrow = (fullWidth < st::columnMinimalWidthLeft / 2),
|
||||
.displayUnreadInfo = showUnreadInSearchResults,
|
||||
});
|
||||
p.translate(0, _st->height);
|
||||
}
|
||||
}
|
||||
if (to < _previewResults.size()) {
|
||||
p.translate(0, (_previewResults.size() - to) * _st->height);
|
||||
}
|
||||
}
|
||||
if (!_searchResults.empty()) {
|
||||
const auto text = showUnreadInSearchResults
|
||||
? u"Search results"_q
|
||||
: tr::lng_search_found_results(
|
||||
@@ -1330,6 +1397,8 @@ void InnerWidget::clearIrrelevantState() {
|
||||
setFilteredPressed(-1, false);
|
||||
_peerSearchSelected = -1;
|
||||
setPeerSearchPressed(-1);
|
||||
_previewSelected = -1;
|
||||
setPreviewPressed(-1);
|
||||
_searchedSelected = -1;
|
||||
setSearchedPressed(-1);
|
||||
} else if (_state == WidgetState::Filtered) {
|
||||
@@ -1446,6 +1515,32 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
|
||||
updateSelectedRow();
|
||||
}
|
||||
}
|
||||
if (!_previewResults.empty()) {
|
||||
auto skip = previewOffset();
|
||||
auto previewSelected = (mouseY >= skip) ? ((mouseY - skip) / _st->height) : -1;
|
||||
if (previewSelected < 0 || previewSelected >= _previewResults.size()) {
|
||||
previewSelected = -1;
|
||||
}
|
||||
if (_previewSelected != previewSelected) {
|
||||
updateSelectedRow();
|
||||
_previewSelected = previewSelected;
|
||||
updateSelectedRow();
|
||||
}
|
||||
auto selectedMorePosts = false;
|
||||
const auto from = skip - st::searchedBarHeight;
|
||||
if (mouseY <= skip && mouseY >= from) {
|
||||
const auto left = width()
|
||||
- _morePostsWidth
|
||||
- 2 * st::searchedBarPosition.x();
|
||||
if (_morePostsWidth > 0 && local.x() >= left) {
|
||||
selectedMorePosts = true;
|
||||
}
|
||||
}
|
||||
if (_selectedMorePosts != selectedMorePosts) {
|
||||
update(0, from, width(), st::searchedBarHeight);
|
||||
_selectedMorePosts = selectedMorePosts;
|
||||
}
|
||||
}
|
||||
if (!_searchResults.empty()) {
|
||||
auto skip = searchedOffset();
|
||||
auto searchedSelected = (mouseY >= skip) ? ((mouseY - skip) / _st->height) : -1;
|
||||
@@ -1495,7 +1590,9 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
|
||||
_hashtagDeletePressed = _hashtagDeleteSelected;
|
||||
setFilteredPressed(_filteredSelected, _selectedTopicJump);
|
||||
setPeerSearchPressed(_peerSearchSelected);
|
||||
setPreviewPressed(_previewSelected);
|
||||
setSearchedPressed(_searchedSelected);
|
||||
_pressedMorePosts = _selectedMorePosts;
|
||||
|
||||
const auto alt = (e->modifiers() & Qt::AltModifier);
|
||||
if (alt && showChatPreview()) {
|
||||
@@ -1862,8 +1959,12 @@ void InnerWidget::mousePressReleased(
|
||||
setFilteredPressed(-1, false);
|
||||
auto peerSearchPressed = _peerSearchPressed;
|
||||
setPeerSearchPressed(-1);
|
||||
auto previewPressed = _previewPressed;
|
||||
setPreviewPressed(-1);
|
||||
auto searchedPressed = _searchedPressed;
|
||||
setSearchedPressed(-1);
|
||||
const auto pressedMorePosts = _pressedMorePosts;
|
||||
_pressedMorePosts = false;
|
||||
if (wasDragging) {
|
||||
selectByMouse(globalPosition);
|
||||
}
|
||||
@@ -1879,8 +1980,12 @@ void InnerWidget::mousePressReleased(
|
||||
|| (filteredPressed >= 0 && filteredPressed == _filteredSelected)
|
||||
|| (peerSearchPressed >= 0
|
||||
&& peerSearchPressed == _peerSearchSelected)
|
||||
|| (previewPressed >= 0
|
||||
&& previewPressed == _previewSelected)
|
||||
|| (searchedPressed >= 0
|
||||
&& searchedPressed == _searchedSelected)) {
|
||||
&& searchedPressed == _searchedSelected)
|
||||
|| (pressedMorePosts
|
||||
&& pressedMorePosts == _selectedMorePosts)) {
|
||||
chooseRow(modifiers, pressedTopicRootId);
|
||||
}
|
||||
}
|
||||
@@ -1955,6 +2060,13 @@ void InnerWidget::setPeerSearchPressed(int pressed) {
|
||||
_peerSearchPressed = pressed;
|
||||
}
|
||||
|
||||
void InnerWidget::setPreviewPressed(int pressed) {
|
||||
if (base::in_range(_previewPressed, 0, _previewResults.size())) {
|
||||
_previewResults[_previewPressed]->stopLastRipple();
|
||||
}
|
||||
_previewPressed = pressed;
|
||||
}
|
||||
|
||||
void InnerWidget::setSearchedPressed(int pressed) {
|
||||
if (base::in_range(_searchedPressed, 0, _searchResults.size())) {
|
||||
_searchResults[_searchedPressed]->stopLastRipple();
|
||||
@@ -2313,6 +2425,8 @@ void InnerWidget::updateSelectedRow(Key key) {
|
||||
}
|
||||
} else if (_peerSearchSelected >= 0) {
|
||||
update(0, peerSearchOffset() + _peerSearchSelected * st::dialogsRowHeight, width(), st::dialogsRowHeight);
|
||||
} else if (_previewSelected >= 0) {
|
||||
update(0, previewOffset() + _previewSelected * _st->height, width(), _st->height);
|
||||
} else if (_searchedSelected >= 0) {
|
||||
update(0, searchedOffset() + _searchedSelected * _st->height, width(), _st->height);
|
||||
}
|
||||
@@ -2354,9 +2468,11 @@ void InnerWidget::clearSelection() {
|
||||
if (isSelected()) {
|
||||
updateSelectedRow();
|
||||
_collapsedSelected = -1;
|
||||
_selectedMorePosts = false;
|
||||
_selected = nullptr;
|
||||
_filteredSelected
|
||||
= _searchedSelected
|
||||
= _previewSelected
|
||||
= _peerSearchSelected
|
||||
= _hashtagSelected
|
||||
= -1;
|
||||
@@ -2449,6 +2565,11 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
|
||||
} else if (_state == WidgetState::Filtered) {
|
||||
if (base::in_range(_filteredSelected, 0, _filterResults.size())) {
|
||||
return { _filterResults[_filteredSelected].key(), FullMsgId() };
|
||||
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {
|
||||
return {
|
||||
_previewResults[_previewSelected]->item()->history(),
|
||||
_previewResults[_previewSelected]->item()->fullId()
|
||||
};
|
||||
} else if (base::in_range(_searchedSelected, 0, _searchResults.size())) {
|
||||
return {
|
||||
_searchResults[_searchedSelected]->item()->history(),
|
||||
@@ -2597,6 +2718,7 @@ void InnerWidget::searchRequested(bool loading) {
|
||||
_searchLoading = loading;
|
||||
if (loading) {
|
||||
clearSearchResults(true);
|
||||
clearPreviewResults();
|
||||
}
|
||||
refresh(true);
|
||||
}
|
||||
@@ -2660,6 +2782,7 @@ void InnerWidget::applySearchState(SearchState state) {
|
||||
}
|
||||
_searchState = std::move(state);
|
||||
_searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query);
|
||||
_searchWithPostsPreview = computeSearchWithPostsPreview();
|
||||
|
||||
updateSearchIn();
|
||||
moveSearchIn();
|
||||
@@ -2767,7 +2890,7 @@ void InnerWidget::appendToFiltered(Key key) {
|
||||
const auto height = filteredHeight();
|
||||
_filterResults.emplace_back(i->second.get());
|
||||
_filterResults.back().top = height;
|
||||
trackSearchResultsHistory(key.owningHistory());
|
||||
trackResultsHistory(key.owningHistory());
|
||||
}
|
||||
|
||||
InnerWidget::~InnerWidget() {
|
||||
@@ -2780,13 +2903,16 @@ void InnerWidget::clearSearchResults(bool clearPeerSearchResults) {
|
||||
_peerSearchResults.clear();
|
||||
}
|
||||
_searchResults.clear();
|
||||
_searchResultsLifetime.destroy();
|
||||
_searchResultsHistories.clear();
|
||||
_searchedCount = _searchedMigratedCount = 0;
|
||||
}
|
||||
|
||||
void InnerWidget::trackSearchResultsHistory(not_null<History*> history) {
|
||||
if (!_searchResultsHistories.emplace(history).second) {
|
||||
void InnerWidget::clearPreviewResults() {
|
||||
_previewResults.clear();
|
||||
_previewCount = 0;
|
||||
}
|
||||
|
||||
void InnerWidget::trackResultsHistory(not_null<History*> history) {
|
||||
if (!_trackedHistories.emplace(history).second) {
|
||||
return;
|
||||
}
|
||||
const auto channel = history->peer->asChannel();
|
||||
@@ -2827,7 +2953,7 @@ void InnerWidget::trackSearchResultsHistory(not_null<History*> history) {
|
||||
clearMouseSelection(true);
|
||||
}
|
||||
update();
|
||||
}, _searchResultsLifetime);
|
||||
}, _trackedLifetime);
|
||||
|
||||
if (const auto forum = channel->forum()) {
|
||||
forum->topicDestroyed(
|
||||
@@ -2857,7 +2983,7 @@ void InnerWidget::trackSearchResultsHistory(not_null<History*> history) {
|
||||
if (_chatPreviewRow.key.topic() == topic) {
|
||||
_chatPreviewRow = {};
|
||||
}
|
||||
}, _searchResultsLifetime);
|
||||
}, _trackedLifetime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2875,6 +3001,13 @@ Data::Thread *InnerWidget::updateFromParentDrag(QPoint globalPosition) {
|
||||
} else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) {
|
||||
return session().data().history(
|
||||
_peerSearchResults[_peerSearchSelected]->peer);
|
||||
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {
|
||||
if (const auto item = _previewResults[_previewSelected]->item()) {
|
||||
if (const auto topic = item->topic()) {
|
||||
return topic;
|
||||
}
|
||||
return item->history();
|
||||
}
|
||||
} else if (base::in_range(_searchedSelected, 0, _searchResults.size())) {
|
||||
if (const auto item = _searchResults[_searchedSelected]->item()) {
|
||||
if (const auto topic = item->topic()) {
|
||||
@@ -3028,12 +3161,14 @@ void InnerWidget::searchReceived(
|
||||
_searchLoading = false;
|
||||
|
||||
const auto uniquePeers = uniqueSearchResults();
|
||||
if (type == SearchRequestType::FromStart
|
||||
|| type == SearchRequestType::PeerFromStart) {
|
||||
const auto withPreview = _searchWithPostsPreview;
|
||||
const auto toPreview = withPreview && type.posts;
|
||||
if (type.start && !type.migrated && (!withPreview || !type.posts)) {
|
||||
clearSearchResults(false);
|
||||
}
|
||||
const auto isMigratedSearch = (type == SearchRequestType::MigratedFromStart)
|
||||
|| (type == SearchRequestType::MigratedFromOffset);
|
||||
if (!withPreview || toPreview) {
|
||||
clearPreviewResults();
|
||||
}
|
||||
|
||||
const auto key = (!_openedForum || _searchState.inChat.topic())
|
||||
? _searchState.inChat
|
||||
@@ -3042,34 +3177,40 @@ void InnerWidget::searchReceived(
|
||||
&& (!_searchState.inChat
|
||||
|| inject->history() == _searchState.inChat.history())) {
|
||||
Assert(_searchResults.empty());
|
||||
Assert(!toPreview);
|
||||
const auto index = int(_searchResults.size());
|
||||
_searchResults.push_back(
|
||||
std::make_unique<FakeRow>(
|
||||
key,
|
||||
inject,
|
||||
[=] { repaintSearchResult(index); }));
|
||||
trackSearchResultsHistory(inject->history());
|
||||
trackResultsHistory(inject->history());
|
||||
++fullCount;
|
||||
}
|
||||
auto &results = toPreview ? _previewResults : _searchResults;
|
||||
for (const auto &item : messages) {
|
||||
const auto history = item->history();
|
||||
if (!uniquePeers || !hasHistoryInResults(history)) {
|
||||
const auto index = int(_searchResults.size());
|
||||
_searchResults.push_back(
|
||||
std::make_unique<FakeRow>(
|
||||
key,
|
||||
item,
|
||||
[=] { repaintSearchResult(index); }));
|
||||
trackSearchResultsHistory(history);
|
||||
if (uniquePeers && !history->unreadCountKnown()) {
|
||||
if (toPreview || !uniquePeers || !hasHistoryInResults(history)) {
|
||||
const auto index = int(results.size());
|
||||
const auto repaint = toPreview
|
||||
? Fn<void()>([=] { repaintSearchResult(index); })
|
||||
: [=] { repaintPreviewResult(index); };
|
||||
results.push_back(
|
||||
std::make_unique<FakeRow>(key, item, repaint));
|
||||
trackResultsHistory(history);
|
||||
if (!toPreview && uniquePeers && !history->unreadCountKnown()) {
|
||||
history->owner().histories().requestDialogEntry(history);
|
||||
} else if (toPreview && results.size() >= kPreviewPostsLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMigratedSearch) {
|
||||
if (type.migrated) {
|
||||
_searchedMigratedCount = fullCount;
|
||||
} else {
|
||||
} else if (!withPreview || !toPreview) {
|
||||
_searchedCount = fullCount;
|
||||
} else {
|
||||
_previewCount = fullCount;
|
||||
}
|
||||
|
||||
refresh();
|
||||
@@ -3313,6 +3454,7 @@ void InnerWidget::clearMouseSelection(bool clearSelection) {
|
||||
} else if (_state == WidgetState::Filtered) {
|
||||
_filteredSelected
|
||||
= _peerSearchSelected
|
||||
= _previewSelected
|
||||
= _searchedSelected
|
||||
= _hashtagSelected = -1;
|
||||
}
|
||||
@@ -3416,6 +3558,19 @@ void InnerWidget::repaintSearchResult(int index) {
|
||||
_st->height);
|
||||
}
|
||||
|
||||
void InnerWidget::repaintPreviewResult(int index) {
|
||||
rtlupdate(
|
||||
0,
|
||||
previewOffset() + index * _st->height,
|
||||
width(),
|
||||
_st->height);
|
||||
}
|
||||
|
||||
bool InnerWidget::computeSearchWithPostsPreview() const {
|
||||
return (_searchHashOrCashtag != HashOrCashtag::None)
|
||||
&& (_searchState.tab == ChatSearchTab::MyMessages);
|
||||
}
|
||||
|
||||
void InnerWidget::clearFilter() {
|
||||
if (_state == WidgetState::Filtered || _searchState.inChat) {
|
||||
if (_searchState.inChat) {
|
||||
@@ -3428,6 +3583,9 @@ void InnerWidget::clearFilter() {
|
||||
_filterResultsGlobal.clear();
|
||||
_peerSearchResults.clear();
|
||||
_searchResults.clear();
|
||||
_previewResults.clear();
|
||||
_trackedHistories.clear();
|
||||
_trackedLifetime.destroy();
|
||||
_filter = QString();
|
||||
refresh(true);
|
||||
}
|
||||
@@ -3474,15 +3632,22 @@ void InnerWidget::selectSkip(int32 direction) {
|
||||
}
|
||||
scrollToDefaultSelected();
|
||||
} else if (_state == WidgetState::Filtered) {
|
||||
if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty() && _searchResults.empty()) {
|
||||
if (_hashtagResults.empty()
|
||||
&& _filterResults.empty()
|
||||
&& _peerSearchResults.empty()
|
||||
&& _previewResults.empty()
|
||||
&& _searchResults.empty()) {
|
||||
return;
|
||||
}
|
||||
if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size()) &&
|
||||
(_filteredSelected < 0 || _filteredSelected >= _filterResults.size()) &&
|
||||
(_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size()) &&
|
||||
(_searchedSelected < 0 || _searchedSelected >= _searchResults.size())) {
|
||||
if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty()) {
|
||||
if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size())
|
||||
&& (_filteredSelected < 0 || _filteredSelected >= _filterResults.size())
|
||||
&& (_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size())
|
||||
&& (_previewSelected < 0 || _previewSelected >= _previewResults.size())
|
||||
&& (_searchedSelected < 0 || _searchedSelected >= _searchResults.size())) {
|
||||
if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty() && _previewResults.empty()) {
|
||||
_searchedSelected = 0;
|
||||
} else if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty()) {
|
||||
_previewSelected = 0;
|
||||
} else if (_hashtagResults.empty() && _filterResults.empty()) {
|
||||
_peerSearchSelected = 0;
|
||||
} else if (_hashtagResults.empty()) {
|
||||
@@ -3493,30 +3658,36 @@ void InnerWidget::selectSkip(int32 direction) {
|
||||
} else {
|
||||
int32 cur = base::in_range(_hashtagSelected, 0, _hashtagResults.size())
|
||||
? _hashtagSelected
|
||||
: (base::in_range(_filteredSelected, 0, _filterResults.size())
|
||||
? (_hashtagResults.size() + _filteredSelected)
|
||||
: (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())
|
||||
? (_peerSearchSelected + _filterResults.size() + _hashtagResults.size())
|
||||
: (_searchedSelected + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size())));
|
||||
: base::in_range(_filteredSelected, 0, _filterResults.size())
|
||||
? (_hashtagResults.size() + _filteredSelected)
|
||||
: base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())
|
||||
? (_peerSearchSelected + _filterResults.size() + _hashtagResults.size())
|
||||
: base::in_range(_previewSelected, 0, _previewResults.size())
|
||||
? (_previewSelected + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size())
|
||||
: (_searchedSelected + _previewResults.size() + _peerSearchResults.size() + _filterResults.size() + _hashtagResults.size());
|
||||
cur = std::clamp(
|
||||
cur + direction,
|
||||
0,
|
||||
static_cast<int>(_hashtagResults.size()
|
||||
+ _filterResults.size()
|
||||
+ _peerSearchResults.size()
|
||||
+ _previewResults.size()
|
||||
+ _searchResults.size()) - 1);
|
||||
if (cur < _hashtagResults.size()) {
|
||||
_hashtagSelected = cur;
|
||||
_filteredSelected = _peerSearchSelected = _searchedSelected = -1;
|
||||
_filteredSelected = _peerSearchSelected = _previewSelected = _searchedSelected = -1;
|
||||
} else if (cur < _hashtagResults.size() + _filterResults.size()) {
|
||||
_filteredSelected = cur - _hashtagResults.size();
|
||||
_hashtagSelected = _peerSearchSelected = _searchedSelected = -1;
|
||||
_hashtagSelected = _peerSearchSelected = _previewSelected = _searchedSelected = -1;
|
||||
} else if (cur < _hashtagResults.size() + _filterResults.size() + _peerSearchResults.size()) {
|
||||
_peerSearchSelected = cur - _hashtagResults.size() - _filterResults.size();
|
||||
_hashtagSelected = _filteredSelected = _searchedSelected = -1;
|
||||
_hashtagSelected = _filteredSelected = _previewSelected = _searchedSelected = -1;
|
||||
} else if (cur < _hashtagResults.size() + _filterResults.size() + _peerSearchResults.size() + _previewResults.size()) {
|
||||
_previewSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size();
|
||||
_hashtagSelected = _filteredSelected = _peerSearchSelected = _searchedSelected = -1;
|
||||
} else {
|
||||
_hashtagSelected = _filteredSelected = _peerSearchSelected = -1;
|
||||
_searchedSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size();
|
||||
_searchedSelected = cur - _hashtagResults.size() - _filterResults.size() - _peerSearchResults.size() - _previewResults.size();
|
||||
_hashtagSelected = _filteredSelected = _peerSearchSelected = _previewSelected = -1;
|
||||
}
|
||||
}
|
||||
if (base::in_range(_hashtagSelected, 0, _hashtagResults.size())) {
|
||||
@@ -3533,6 +3704,13 @@ void InnerWidget::selectSkip(int32 direction) {
|
||||
const auto height = st::dialogsRowHeight
|
||||
+ (_peerSearchSelected ? 0 : st::searchedBarHeight);
|
||||
scrollToItem(from, height);
|
||||
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {
|
||||
const auto from = previewOffset()
|
||||
+ _previewSelected * _st->height
|
||||
+ (_previewSelected ? 0 : -st::searchedBarHeight);
|
||||
const auto height = _st->height
|
||||
+ (_previewSelected ? 0 : st::searchedBarHeight);
|
||||
scrollToItem(from, height);
|
||||
} else {
|
||||
const auto from = searchedOffset()
|
||||
+ _searchedSelected * _st->height
|
||||
@@ -3551,7 +3729,14 @@ void InnerWidget::scrollToEntry(const RowDescriptor &entry) {
|
||||
scrollToItem(dialogsOffset() + row->top(), row->height());
|
||||
}
|
||||
} else if (_state == WidgetState::Filtered) {
|
||||
for (int32 i = 0, c = _searchResults.size(); i < c; ++i) {
|
||||
for (auto i = 0, c = int(_previewResults.size()); i != c; ++i) {
|
||||
if (isSearchResultActive(_previewResults[i].get(), entry)) {
|
||||
const auto from = previewOffset() + i * _st->height;
|
||||
scrollToItem(from, _st->height);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (auto i = 0, c = int(_searchResults.size()); i != c; ++i) {
|
||||
if (isSearchResultActive(_searchResults[i].get(), entry)) {
|
||||
const auto from = searchedOffset() + i * _st->height;
|
||||
scrollToItem(from, _st->height);
|
||||
@@ -3646,33 +3831,45 @@ void InnerWidget::preloadRowsData() {
|
||||
}
|
||||
yTo -= otherStart;
|
||||
} else if (_state == WidgetState::Filtered) {
|
||||
int32 from = (yFrom - filteredOffset()) / _st->height;
|
||||
auto from = (yFrom - filteredOffset()) / _st->height;
|
||||
if (from < 0) from = 0;
|
||||
if (from < _filterResults.size()) {
|
||||
int32 to = (yTo / _st->height) + 1;
|
||||
if (to > _filterResults.size()) to = _filterResults.size();
|
||||
|
||||
const auto to = std::min(
|
||||
((yTo - filteredOffset()) / _st->height) + 1,
|
||||
int(_filterResults.size()));
|
||||
for (; from < to; ++from) {
|
||||
_filterResults[from].key().entry()->chatListPreloadData();
|
||||
}
|
||||
}
|
||||
|
||||
from = (yFrom > filteredOffset() + st::searchedBarHeight ? ((yFrom - filteredOffset() - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size();
|
||||
from = (yFrom - peerSearchOffset()) / st::dialogsRowHeight;
|
||||
if (from < 0) from = 0;
|
||||
if (from < _peerSearchResults.size()) {
|
||||
int32 to = (yTo > filteredOffset() + st::searchedBarHeight ? ((yTo - filteredOffset() - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size() + 1;
|
||||
if (to > _peerSearchResults.size()) to = _peerSearchResults.size();
|
||||
|
||||
const auto to = std::min(
|
||||
((yTo - peerSearchOffset()) / st::dialogsRowHeight) + 1,
|
||||
int(_peerSearchResults.size()));
|
||||
for (; from < to; ++from) {
|
||||
_peerSearchResults[from]->peer->loadUserpic();
|
||||
}
|
||||
}
|
||||
from = (yFrom > filteredOffset() + ((_peerSearchResults.empty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight) ? ((yFrom - filteredOffset() - (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size() - _peerSearchResults.size();
|
||||
|
||||
from = (yFrom - previewOffset()) / _st->height;
|
||||
if (from < 0) from = 0;
|
||||
if (from < _previewResults.size()) {
|
||||
const auto to = std::min(
|
||||
((yTo - previewOffset()) / _st->height) + 1,
|
||||
int(_previewResults.size()));
|
||||
for (; from < to; ++from) {
|
||||
_previewResults[from]->item()->history()->peer->loadUserpic();
|
||||
}
|
||||
}
|
||||
|
||||
from = (yFrom - searchedOffset()) / _st->height;
|
||||
if (from < 0) from = 0;
|
||||
if (from < _searchResults.size()) {
|
||||
int32 to = (yTo > filteredOffset() + (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight ? ((yTo - filteredOffset() - (_peerSearchResults.empty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / st::dialogsRowHeight) : 0) - _filterResults.size() - _peerSearchResults.size() + 1;
|
||||
if (to > _searchResults.size()) to = _searchResults.size();
|
||||
|
||||
const auto to = std::min(
|
||||
((yTo - searchedOffset()) / _st->height) + 1,
|
||||
int(_searchResults.size()));
|
||||
for (; from < to; ++from) {
|
||||
_searchResults[from]->item()->history()->peer->loadUserpic();
|
||||
}
|
||||
@@ -3803,6 +4000,14 @@ ChosenRow InnerWidget::computeChosenRow() const {
|
||||
.key = session().data().history(peer),
|
||||
.message = Data::UnreadMessagePosition
|
||||
};
|
||||
} else if (base::in_range(_previewSelected, 0, _previewResults.size())) {
|
||||
const auto result = _previewResults[_previewSelected].get();
|
||||
const auto topic = result->topic();
|
||||
const auto item = result->item();
|
||||
return {
|
||||
.key = (topic ? (Entry*)topic : (Entry*)item->history()),
|
||||
.message = item->position()
|
||||
};
|
||||
} else if (base::in_range(_searchedSelected, 0, _searchResults.size())) {
|
||||
const auto result = _searchResults[_searchedSelected].get();
|
||||
const auto topic = result->topic();
|
||||
@@ -3832,6 +4037,11 @@ bool InnerWidget::chooseRow(
|
||||
MsgId pressedTopicRootId) {
|
||||
if (chooseHashtag()) {
|
||||
return true;
|
||||
} else if (_selectedMorePosts) {
|
||||
if (_searchHashOrCashtag != HashOrCashtag::None) {
|
||||
_changeSearchTabRequests.fire(ChatSearchTab::PublicPosts);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const auto modifyChosenRow = [&](
|
||||
ChosenRow row,
|
||||
|
||||
@@ -72,13 +72,18 @@ struct ChosenRow {
|
||||
bool newWindow : 1 = false;
|
||||
};
|
||||
|
||||
enum class SearchRequestType : uchar {
|
||||
FromStart,
|
||||
FromOffset,
|
||||
PeerFromStart,
|
||||
PeerFromOffset,
|
||||
MigratedFromStart,
|
||||
MigratedFromOffset,
|
||||
struct SearchRequestType {
|
||||
bool migrated : 1 = false;
|
||||
bool posts : 1 = false;
|
||||
bool start : 1 = false;
|
||||
bool peer : 1 = false;
|
||||
|
||||
friend inline constexpr auto operator<=>(
|
||||
SearchRequestType a,
|
||||
SearchRequestType b) = default;
|
||||
friend inline constexpr bool operator==(
|
||||
SearchRequestType a,
|
||||
SearchRequestType b) = default;
|
||||
};
|
||||
|
||||
enum class SearchRequestDelay : uchar {
|
||||
@@ -283,6 +288,7 @@ private:
|
||||
void setHashtagPressed(int pressed);
|
||||
void setFilteredPressed(int pressed, bool pressedTopicJump);
|
||||
void setPeerSearchPressed(int pressed);
|
||||
void setPreviewPressed(int pressed);
|
||||
void setSearchedPressed(int pressed);
|
||||
bool isPressed() const {
|
||||
return (_collapsedPressed >= 0)
|
||||
@@ -290,7 +296,9 @@ private:
|
||||
|| (_hashtagPressed >= 0)
|
||||
|| (_filteredPressed >= 0)
|
||||
|| (_peerSearchPressed >= 0)
|
||||
|| (_searchedPressed >= 0);
|
||||
|| (_previewPressed >= 0)
|
||||
|| (_searchedPressed >= 0)
|
||||
|| _pressedMorePosts;
|
||||
}
|
||||
bool isSelected() const {
|
||||
return (_collapsedSelected >= 0)
|
||||
@@ -298,7 +306,9 @@ private:
|
||||
|| (_hashtagSelected >= 0)
|
||||
|| (_filteredSelected >= 0)
|
||||
|| (_peerSearchSelected >= 0)
|
||||
|| (_searchedSelected >= 0);
|
||||
|| (_previewSelected >= 0)
|
||||
|| (_searchedSelected >= 0)
|
||||
|| _selectedMorePosts;
|
||||
}
|
||||
bool uniqueSearchResults() const;
|
||||
bool hasHistoryInResults(not_null<History*> history) const;
|
||||
@@ -352,6 +362,7 @@ private:
|
||||
[[nodiscard]] int filteredHeight(int till = -1) const;
|
||||
[[nodiscard]] int peerSearchOffset() const;
|
||||
[[nodiscard]] int searchInChatOffset() const;
|
||||
[[nodiscard]] int previewOffset() const;
|
||||
[[nodiscard]] int searchedOffset() const;
|
||||
[[nodiscard]] int searchInChatSkip() const;
|
||||
[[nodiscard]] int hashtagsOffset() const;
|
||||
@@ -403,14 +414,18 @@ private:
|
||||
// const Ui::Text::String &text) const;
|
||||
void updateSearchIn();
|
||||
void repaintSearchResult(int index);
|
||||
void repaintPreviewResult(int index);
|
||||
|
||||
[[nodiscard]] bool computeSearchWithPostsPreview() const;
|
||||
|
||||
Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row);
|
||||
Ui::VideoUserpic *validateVideoUserpic(not_null<History*> history);
|
||||
|
||||
Row *shownRowByKey(Key key);
|
||||
void clearSearchResults(bool clearPeerSearchResults = true);
|
||||
void clearPreviewResults();
|
||||
void updateSelectedRow(Key key = Key());
|
||||
void trackSearchResultsHistory(not_null<History*> history);
|
||||
void trackResultsHistory(not_null<History*> history);
|
||||
|
||||
[[nodiscard]] QBrush currentBg() const;
|
||||
[[nodiscard]] RowDescriptor computeChatPreviewRow() const;
|
||||
@@ -449,6 +464,8 @@ private:
|
||||
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
|
||||
not_null<const style::DialogRow*> _st;
|
||||
mutable std::unique_ptr<Ui::TopicJumpCache> _topicJumpCache;
|
||||
bool _selectedMorePosts = false;
|
||||
bool _pressedMorePosts = false;
|
||||
int _collapsedSelected = -1;
|
||||
int _collapsedPressed = -1;
|
||||
bool _skipTopDialog = false;
|
||||
@@ -487,14 +504,21 @@ private:
|
||||
|
||||
EmptyState _emptyState = EmptyState::None;
|
||||
|
||||
base::flat_set<not_null<History*>> _trackedHistories;
|
||||
rpl::lifetime _trackedLifetime;
|
||||
|
||||
QString _peerSearchQuery;
|
||||
std::vector<std::unique_ptr<PeerSearchResult>> _peerSearchResults;
|
||||
int _peerSearchSelected = -1;
|
||||
int _peerSearchPressed = -1;
|
||||
|
||||
std::vector<std::unique_ptr<FakeRow>> _previewResults;
|
||||
int _previewCount = 0;
|
||||
int _previewSelected = -1;
|
||||
int _previewPressed = -1;
|
||||
int _morePostsWidth = 0;
|
||||
|
||||
std::vector<std::unique_ptr<FakeRow>> _searchResults;
|
||||
base::flat_set<not_null<History*>> _searchResultsHistories;
|
||||
rpl::lifetime _searchResultsLifetime;
|
||||
int _searchedCount = 0;
|
||||
int _searchedMigratedCount = 0;
|
||||
int _searchedSelected = -1;
|
||||
@@ -516,6 +540,7 @@ private:
|
||||
|
||||
SearchState _searchState;
|
||||
HashOrCashtag _searchHashOrCashtag = {};
|
||||
bool _searchWithPostsPreview = false;
|
||||
History *_searchInMigrated = nullptr;
|
||||
PeerData *_searchFromShown = nullptr;
|
||||
Ui::Text::String _searchFromUserText;
|
||||
|
||||
@@ -75,7 +75,7 @@ class FakeRow;
|
||||
class Key;
|
||||
struct ChosenRow;
|
||||
class InnerWidget;
|
||||
enum class SearchRequestType : uchar;
|
||||
struct SearchRequestType;
|
||||
enum class SearchRequestDelay : uchar;
|
||||
class Suggestions;
|
||||
class ChatSearchIn;
|
||||
@@ -129,6 +129,7 @@ public:
|
||||
[[nodiscard]] RowDescriptor resolveChatNext(RowDescriptor from = {}) const;
|
||||
[[nodiscard]] RowDescriptor resolveChatPrevious(RowDescriptor from = {}) const;
|
||||
void updateHasFocus(not_null<QWidget*> focused);
|
||||
void toggleFiltersMenu(bool value);
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e) override;
|
||||
@@ -151,10 +152,26 @@ protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
struct SearchProcessState {
|
||||
base::flat_map<QString, MTPmessages_Messages> cache;
|
||||
base::flat_map<mtpRequestId, QString> queries;
|
||||
|
||||
PeerData *lastPeer = nullptr;
|
||||
MsgId lastId = 0;
|
||||
int32 nextRate = 0;
|
||||
mtpRequestId requestId = 0;
|
||||
bool full = false;
|
||||
};
|
||||
|
||||
void chosenRow(const ChosenRow &row);
|
||||
void listScrollUpdated();
|
||||
void searchCursorMoved();
|
||||
void completeHashtag(QString tag);
|
||||
void requestPublicPosts(bool fromStart);
|
||||
void requestMessages(bool fromStart);
|
||||
[[nodiscard]] not_null<SearchProcessState*> currentSearchProcess();
|
||||
|
||||
[[nodiscard]] bool computeSearchWithPostsPreview() const;
|
||||
|
||||
[[nodiscard]] QString currentSearchQuery() const;
|
||||
[[nodiscard]] int currentSearchQueryCursorPosition() const;
|
||||
@@ -168,7 +185,8 @@ private:
|
||||
void searchReceived(
|
||||
SearchRequestType type,
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId);
|
||||
not_null<SearchProcessState*> process,
|
||||
bool cacheResults = false);
|
||||
void peerSearchReceived(
|
||||
const MTPcontacts_Found &result,
|
||||
mtpRequestId requestId);
|
||||
@@ -201,7 +219,7 @@ private:
|
||||
void showCalendar();
|
||||
void showSearchFrom();
|
||||
void showMainMenu();
|
||||
void clearSearchCache();
|
||||
void clearSearchCache(bool clearPosts);
|
||||
void setSearchQuery(const QString &query, int cursorPosition = -1);
|
||||
void updateControlsVisibility(bool fast = false);
|
||||
void updateLockUnlockVisibility(
|
||||
@@ -244,9 +262,11 @@ private:
|
||||
void searchFailed(
|
||||
SearchRequestType type,
|
||||
const MTP::Error &error,
|
||||
mtpRequestId requestId);
|
||||
not_null<SearchProcessState*> process);
|
||||
void peerSearchFailed(const MTP::Error &error, mtpRequestId requestId);
|
||||
void searchApplyEmpty(SearchRequestType type, mtpRequestId id);
|
||||
void searchApplyEmpty(
|
||||
SearchRequestType type,
|
||||
not_null<SearchProcessState*> process);
|
||||
void peerSearchApplyEmpty(mtpRequestId id);
|
||||
|
||||
void updateForceDisplayWide();
|
||||
@@ -298,6 +318,8 @@ private:
|
||||
std::unique_ptr<Ui::RequestsBar> _forumRequestsBar;
|
||||
std::unique_ptr<HistoryView::ContactStatus> _forumReportBar;
|
||||
|
||||
base::unique_qptr<Ui::RpWidget> _chatFilters;
|
||||
|
||||
object_ptr<Ui::ElasticScroll> _scroll;
|
||||
QPointer<InnerWidget> _inner;
|
||||
std::unique_ptr<Suggestions> _suggestions;
|
||||
@@ -318,6 +340,7 @@ private:
|
||||
bool _scrollToTopIsShown = false;
|
||||
bool _forumSearchRequested = false;
|
||||
HashOrCashtag _searchHashOrCashtag = {};
|
||||
bool _searchWithPostsPreview = false;
|
||||
|
||||
Data::Folder *_openedFolder = nullptr;
|
||||
Data::Forum *_openedForum = nullptr;
|
||||
@@ -358,19 +381,13 @@ private:
|
||||
PeerData *_searchQueryFrom = nullptr;
|
||||
std::vector<Data::ReactionId> _searchQueryTags;
|
||||
ChatSearchTab _searchQueryTab = {};
|
||||
int32 _searchNextRate = 0;
|
||||
bool _searchFull = false;
|
||||
bool _searchFullMigrated = false;
|
||||
int _searchInHistoryRequest = 0; // Not real mtpRequestId.
|
||||
mtpRequestId _searchRequest = 0;
|
||||
|
||||
PeerData *_lastSearchPeer = nullptr;
|
||||
MsgId _lastSearchId = 0;
|
||||
MsgId _lastSearchMigratedId = 0;
|
||||
SearchProcessState _searchProcess;
|
||||
SearchProcessState _migratedProcess;
|
||||
SearchProcessState _postsProcess;
|
||||
int _historiesRequest = 0; // Not real mtpRequestId.
|
||||
|
||||
base::flat_map<QString, MTPmessages_Messages> _searchCache;
|
||||
Api::SingleMessageSearch _singleMessageSearch;
|
||||
base::flat_map<mtpRequestId, QString> _searchQueries;
|
||||
base::flat_map<QString, MTPcontacts_Found> _peerSearchCache;
|
||||
base::flat_map<mtpRequestId, QString> _peerSearchQueries;
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ namespace {
|
||||
|
||||
constexpr auto kCollapsedChannelsCount = 5;
|
||||
constexpr auto kProbablyMaxChannels = 1000;
|
||||
constexpr auto kProbablyMaxRecommendations = 100;
|
||||
constexpr auto kCollapsedAppsCount = 5;
|
||||
constexpr auto kProbablyMaxApps = 100;
|
||||
|
||||
@@ -1163,8 +1162,22 @@ void PopularAppsController::fill() {
|
||||
appendRow(bot);
|
||||
}
|
||||
}
|
||||
const auto count = delegate()->peerListFullRowsCount();
|
||||
setCount(count);
|
||||
if (count > 0) {
|
||||
delegate()->peerListSetBelowWidget(object_ptr<Ui::DividerLabel>(
|
||||
(QWidget*)nullptr,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
(QWidget*)nullptr,
|
||||
tr::lng_bot_apps_which(
|
||||
lt_link,
|
||||
tr::lng_bot_apps_which_link(
|
||||
) | Ui::Text::ToLink(u"internal:about_popular_apps"_q),
|
||||
Ui::Text::WithEntities),
|
||||
st::dialogsPopularAppsAbout),
|
||||
st::dialogsPopularAppsPadding));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
setCount(delegate()->peerListFullRowsCount());
|
||||
}
|
||||
|
||||
void PopularAppsController::appendRow(not_null<UserData*> bot) {
|
||||
@@ -2326,4 +2339,21 @@ object_ptr<Ui::BoxContent> StarsExamplesBox(
|
||||
return Box<PeerListBox>(std::move(controller), std::move(initBox));
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> PopularAppsAboutBox(
|
||||
not_null<Window::SessionController*> window) {
|
||||
return Ui::MakeInformBox({
|
||||
.text = tr::lng_popular_apps_info_text(
|
||||
lt_bot,
|
||||
rpl::single(Ui::Text::Link(
|
||||
u"@botfather"_q,
|
||||
u"https://t.me/botfather"_q)),
|
||||
lt_link,
|
||||
tr::lng_popular_apps_info_here(
|
||||
) | Ui::Text::ToLink(tr::lng_popular_apps_info_url(tr::now)),
|
||||
Ui::Text::RichLangValue),
|
||||
.confirmText = tr::lng_popular_apps_info_confirm(),
|
||||
.title = tr::lng_popular_apps_info_title(),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
|
||||