Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7958f5235 | ||
|
|
2ae5fc0fac | ||
|
|
666251f23e | ||
|
|
d89d8b09da | ||
|
|
0aa20b4479 | ||
|
|
8658dba97a | ||
|
|
20c911651f | ||
|
|
bef20ba4a2 | ||
|
|
ee325031a0 | ||
|
|
b57549546d | ||
|
|
1e4d278604 | ||
|
|
80aa596310 | ||
|
|
4913288061 | ||
|
|
1a43cd8a67 | ||
|
|
bcb0511083 | ||
|
|
2dda044dd1 | ||
|
|
da18ab3d41 | ||
|
|
f26cae8807 | ||
|
|
641bb01ba2 | ||
|
|
02c9b61840 | ||
|
|
99e8d22c51 | ||
|
|
cd9b3368da | ||
|
|
f918c6bb83 | ||
|
|
28dff5ba6d | ||
|
|
4d978f5b36 | ||
|
|
ef41878815 | ||
|
|
6a663932f3 | ||
|
|
67c538ae8f | ||
|
|
38137e16a0 | ||
|
|
9c01295521 | ||
|
|
c7b6db00ca | ||
|
|
7f0bdc5d36 | ||
|
|
5f6d8f74dd | ||
|
|
9086319b99 | ||
|
|
37cd4f51eb | ||
|
|
74416568d6 | ||
|
|
a8c3d6c39b | ||
|
|
5939c2dbfc | ||
|
|
4bef1e9f59 | ||
|
|
18bf48bf90 | ||
|
|
b415b293cf | ||
|
|
3a1bb1966d | ||
|
|
d4b686ff65 | ||
|
|
8148974e9f | ||
|
|
eaa2573c66 | ||
|
|
4d315f8e61 | ||
|
|
e5981ed22b | ||
|
|
eccaca8808 | ||
|
|
36e6c76b59 | ||
|
|
06c35f5b51 | ||
|
|
ca21b7efae | ||
|
|
9eb20ede33 | ||
|
|
656146c445 | ||
|
|
2eb8ed59cc | ||
|
|
9d6eeace54 | ||
|
|
315c85fb8d | ||
|
|
b7a70a2f28 | ||
|
|
63bf564757 | ||
|
|
6a9c5818ba | ||
|
|
98e7de01b0 | ||
|
|
8ef7325e16 | ||
|
|
17de379145 | ||
|
|
eb784c665a | ||
|
|
07beb3e86b | ||
|
|
18919a6b4a | ||
|
|
71d4b64691 | ||
|
|
9918a20946 | ||
|
|
ae5e7d641a | ||
|
|
4e88ea970e | ||
|
|
f6ff0f3b2c | ||
|
|
84d58e574f | ||
|
|
1dd7cc956b | ||
|
|
54e7dfe986 | ||
|
|
4e9a52343f | ||
|
|
51c805d77a | ||
|
|
8bde488662 | ||
|
|
4f1e04cf9e | ||
|
|
e509da8fd8 | ||
|
|
f6bfe2c9a8 | ||
|
|
15719b73b4 | ||
|
|
8da9638563 | ||
|
|
a5afeebc0c | ||
|
|
9903266722 | ||
|
|
8f33d5903d | ||
|
|
e9c79886d2 | ||
|
|
6207770120 | ||
|
|
2a99f1a1ef | ||
|
|
4aafcebef5 | ||
|
|
3a78e94f2f | ||
|
|
b5aca56914 | ||
|
|
a6621233d0 | ||
|
|
b4eb25de58 | ||
|
|
d96a8d028a | ||
|
|
f45c47f3d5 | ||
|
|
827ce46d3c | ||
|
|
91c84d63de | ||
|
|
545392f90f | ||
|
|
fa61cf3c85 | ||
|
|
c359646702 | ||
|
|
51fef843f0 | ||
|
|
1a3a0fb124 | ||
|
|
f1d9cca119 | ||
|
|
8e749173de | ||
|
|
20dbf18106 | ||
|
|
10ff71e8f6 | ||
|
|
170cc77a1b | ||
|
|
589673e420 | ||
|
|
2f9c39fe53 | ||
|
|
044c7f3ce9 | ||
|
|
d18e28978a | ||
|
|
581b84afe0 | ||
|
|
846cabeda5 | ||
|
|
9b59ef00af | ||
|
|
079772a399 | ||
|
|
2e39befd7c | ||
|
|
219ffd2c48 | ||
|
|
63d15e4479 | ||
|
|
2f01efdd64 | ||
|
|
8b7d2c880e | ||
|
|
1755ead681 | ||
|
|
1e1f7be708 | ||
|
|
7170bec25d | ||
|
|
7ad1a7dd37 | ||
|
|
217e9b2475 | ||
|
|
28f2c213f7 | ||
|
|
b8f1cebeb6 | ||
|
|
6a3ad52aef | ||
|
|
8c349c0515 | ||
|
|
9038dfb3b8 | ||
|
|
2e94488eb4 | ||
|
|
59ed41abfe | ||
|
|
8bea6776f5 | ||
|
|
0143d22a21 | ||
|
|
021d0053be | ||
|
|
443ca0b390 | ||
|
|
2e0224589f | ||
|
|
f8da59595a | ||
|
|
e8568c6701 | ||
|
|
4ca3f6a1b3 | ||
|
|
6af255923a | ||
|
|
624d83dc60 | ||
|
|
6073da2843 | ||
|
|
ca5d2c115d | ||
|
|
86f3d88116 | ||
|
|
7a971b5855 | ||
|
|
0a7de3340a | ||
|
|
2cb73eefeb | ||
|
|
152aa06930 | ||
|
|
cbca78ff63 | ||
|
|
7c46b292ac | ||
|
|
883509903f | ||
|
|
37b2951058 | ||
|
|
ceb323ac7c | ||
|
|
b65d40a22b | ||
|
|
aed49b9289 | ||
|
|
72d81cc52f | ||
|
|
4bb3aec168 | ||
|
|
c0a81f2428 | ||
|
|
692adacc2a | ||
|
|
26dbeb6831 | ||
|
|
07f72c20eb | ||
|
|
8407b0cccf | ||
|
|
3ff17a8789 | ||
|
|
a9a6d8a568 | ||
|
|
9877845b9c | ||
|
|
dc89262461 | ||
|
|
a5425042cf | ||
|
|
d6e03c3e48 | ||
|
|
e91eecf34f | ||
|
|
1a8cc87e60 | ||
|
|
0863941642 | ||
|
|
b331aee599 | ||
|
|
e19180cc86 | ||
|
|
10cb891f48 | ||
|
|
c8f7a8c795 | ||
|
|
74a28ffdf7 | ||
|
|
ecedce0c2f | ||
|
|
bd4f993292 | ||
|
|
4934b026d3 | ||
|
|
11f183a79f | ||
|
|
ae426a41e0 | ||
|
|
d6edc3728d | ||
|
|
e121487170 | ||
|
|
72a093ec77 | ||
|
|
4996d90782 | ||
|
|
9a451a1423 | ||
|
|
4d11ad45db | ||
|
|
1657c2c7f2 | ||
|
|
c5e7048a3d | ||
|
|
1f194da2f0 | ||
|
|
0954b04f24 | ||
|
|
4659499340 | ||
|
|
6eb4584408 | ||
|
|
4ba4b77b95 | ||
|
|
f9bf6dbc1e | ||
|
|
64b5269648 | ||
|
|
f394cecf55 | ||
|
|
8b56676c23 | ||
|
|
e2713ea627 | ||
|
|
f5e50409d3 | ||
|
|
050916a56a | ||
|
|
cdf36cc387 |
3
.gitmodules
vendored
@@ -52,9 +52,6 @@
|
||||
[submodule "Telegram/lib_qr"]
|
||||
path = Telegram/lib_qr
|
||||
url = https://github.com/desktop-app/lib_qr.git
|
||||
[submodule "Telegram/ThirdParty/libdbusmenu-qt"]
|
||||
path = Telegram/ThirdParty/libdbusmenu-qt
|
||||
url = https://github.com/desktop-app/libdbusmenu-qt.git
|
||||
[submodule "Telegram/ThirdParty/hunspell"]
|
||||
path = Telegram/ThirdParty/hunspell
|
||||
url = https://github.com/hunspell/hunspell
|
||||
|
||||
@@ -147,6 +147,8 @@ PRIVATE
|
||||
api/api_text_entities.h
|
||||
api/api_toggling_media.cpp
|
||||
api/api_toggling_media.h
|
||||
api/api_unread_things.cpp
|
||||
api/api_unread_things.h
|
||||
api/api_updates.cpp
|
||||
api/api_updates.h
|
||||
api/api_user_privacy.cpp
|
||||
@@ -686,6 +688,8 @@ PRIVATE
|
||||
history/history_message.h
|
||||
history/history_service.cpp
|
||||
history/history_service.h
|
||||
history/history_unread_things.cpp
|
||||
history/history_unread_things.h
|
||||
history/history_widget.cpp
|
||||
history/history_widget.h
|
||||
info/info_content_widget.cpp
|
||||
@@ -1027,6 +1031,8 @@ PRIVATE
|
||||
settings/settings_codes.h
|
||||
settings/settings_common.cpp
|
||||
settings/settings_common.h
|
||||
settings/settings_experimental.cpp
|
||||
settings/settings_experimental.h
|
||||
settings/settings_folders.cpp
|
||||
settings/settings_folders.h
|
||||
settings/settings_information.cpp
|
||||
@@ -1193,8 +1199,6 @@ PRIVATE
|
||||
window/themes/window_themes_generate_name.h
|
||||
apiwrap.cpp
|
||||
apiwrap.h
|
||||
app.cpp
|
||||
app.h
|
||||
config.h
|
||||
facades.cpp
|
||||
facades.h
|
||||
@@ -1334,8 +1338,6 @@ else()
|
||||
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_statusnotifieritem
|
||||
desktop-app::external_dbusmenu_qt
|
||||
desktop-app::external_glibmm
|
||||
desktop-app::external_glib
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 524 B |
|
Before Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 873 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 393 B |
|
Before Width: | Height: | Size: 616 B |
|
Before Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 889 B |
|
Before Width: | Height: | Size: 173 B |
|
Before Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 545 B |
|
Before Width: | Height: | Size: 228 B |
|
Before Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 661 B |
|
Before Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 715 B |
|
Before Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/dialogs/dialogs_mention.png
Normal file
|
After Width: | Height: | Size: 569 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mention@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/dialogs/dialogs_mention@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/dialogs/dialogs_reaction.png
Normal file
|
After Width: | Height: | Size: 340 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_reaction@2x.png
Normal file
|
After Width: | Height: | Size: 565 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_reaction@3x.png
Normal file
|
After Width: | Height: | Size: 814 B |
|
Before Width: | Height: | Size: 453 B After Width: | Height: | Size: 463 B |
|
Before Width: | Height: | Size: 973 B After Width: | Height: | Size: 819 B |
|
Before Width: | Height: | Size: 946 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 727 B |
|
Before Width: | Height: | Size: 995 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/history_unread_reaction.png
Normal file
|
After Width: | Height: | Size: 635 B |
BIN
Telegram/Resources/icons/history_unread_reaction@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/history_unread_reaction@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 11 KiB |
@@ -389,9 +389,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_auto_start_disabled_uwp" = "Starting with the system was disabled in Windows Settings.\n\nPlease enable Telegram Desktop in the Startup Apps Settings.";
|
||||
"lng_settings_open_system_settings" = "Open Settings";
|
||||
"lng_settings_add_sendto" = "Place Telegram in \"Send to\" menu";
|
||||
"lng_settings_mac_warn_before_quit" = "Show warning before quitting with {text}";
|
||||
"lng_settings_section_scale" = "Interface Scale";
|
||||
"lng_settings_scale_auto" = "Auto ({cur})";
|
||||
|
||||
"lng_settings_experimental" = "Experimental settings";
|
||||
"lng_settings_experimental_about" = "Warning! Those are experimental settings. Some may not work. Others may break the app. Any of them may disappear in the next version without a trace. Use at your own risk.";
|
||||
"lng_settings_experimental_restore" = "Restore default values";
|
||||
"lng_settings_experimental_irrelevant" = "This option isn't relevant for your system.";
|
||||
|
||||
"lng_settings_section_chat_settings" = "Chat Settings";
|
||||
"lng_settings_replace_emojis" = "Replace emoji";
|
||||
"lng_settings_suggest_emoji" = "Suggest emoji replacements";
|
||||
@@ -730,7 +736,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_sure_logout" = "Are you sure you want to log out?";
|
||||
|
||||
"lng_settings_need_restart" = "You need to restart for applying some of the new settings. Restart now?";
|
||||
"lng_settings_restart_now" = "RESTART";
|
||||
"lng_settings_restart_now" = "Restart";
|
||||
"lng_settings_restart_later" = "Later";
|
||||
|
||||
"lng_sessions_header" = "Current session";
|
||||
"lng_sessions_other_header" = "Active sessions";
|
||||
@@ -1748,6 +1755,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_mark_read_all" = "Mark all chats as read";
|
||||
"lng_context_mark_read_all_sure" = "Are you sure you want to mark all chats as read?";
|
||||
"lng_context_mark_read_mentions_all" = "Mark all mentions as read";
|
||||
"lng_context_mark_read_reactions_all" = "Read all reactions";
|
||||
"lng_context_archive_expand" = "Expand";
|
||||
"lng_context_archive_collapse" = "Collapse";
|
||||
"lng_context_archive_to_menu" = "Move to main menu";
|
||||
@@ -1908,6 +1916,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_delete_for_me_chat_hint#other" = "This will delete them just for you, not for other participants of the chat.";
|
||||
"lng_delete_for_me_hint#one" = "This will delete it just for you.";
|
||||
"lng_delete_for_me_hint#other" = "This will delete them just for you.";
|
||||
"lng_delete_clear_for_me" = "This will clear history just for you, not for other participants of the chat.";
|
||||
"lng_edit_auto_delete_settings" = "Edit Auto-Delete Settings";
|
||||
"lng_enable_auto_delete" = "Enable Auto-Delete";
|
||||
"lng_selected_unsend_about_user_one" = "You can also delete the message you sent from {user}'s inbox by checking \"Unsend my messages\".";
|
||||
@@ -1922,6 +1931,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_box_delete" = "Delete";
|
||||
"lng_box_leave" = "Leave";
|
||||
|
||||
"lng_upload_sure_stop" = "Are you sure you want to stop uploading your files?\n\nIf you do, you'll need to start over.";
|
||||
"lng_upload_show_file" = "Show file";
|
||||
|
||||
"lng_about_version" = "version {version}";
|
||||
"lng_about_text1" = "Official free messaging app based on {api_link}\nfor speed and security.";
|
||||
"lng_about_text1_api" = "Telegram API";
|
||||
@@ -3058,6 +3070,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mac_menu_preferences" = "Preferences...";
|
||||
"lng_mac_menu_quit_telegram" = "Quit {telegram}";
|
||||
"lng_mac_menu_about_telegram" = "About {telegram}";
|
||||
//"lng_mac_menu_warn_before_quit" = "Warn Before Quitting ({text})";
|
||||
"lng_mac_menu_file" = "File";
|
||||
"lng_mac_menu_logout" = "Log Out";
|
||||
"lng_mac_menu_edit" = "Edit";
|
||||
@@ -3084,4 +3097,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_mac_touchbar_favorite_stickers" = "Favorite stickers";
|
||||
|
||||
"lng_mac_hold_to_quit" = "Hold {text} to Quit";
|
||||
|
||||
// Keys finished
|
||||
|
||||
@@ -190,7 +190,7 @@ messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int =
|
||||
messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
|
||||
messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
|
||||
|
||||
dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
|
||||
dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
|
||||
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
|
||||
|
||||
photoEmpty#2331b22d id:long = Photo;
|
||||
@@ -566,7 +566,7 @@ inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
|
||||
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
|
||||
inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
|
||||
|
||||
stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
|
||||
stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true gifs:flags.6?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
|
||||
|
||||
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
|
||||
messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
|
||||
@@ -1307,17 +1307,20 @@ auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut
|
||||
|
||||
reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount;
|
||||
|
||||
messageReactions#87b6e36 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector<ReactionCount> recent_reactons:flags.1?Vector<MessageUserReaction> = MessageReactions;
|
||||
messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector<ReactionCount> recent_reactions:flags.1?Vector<MessagePeerReaction> = MessageReactions;
|
||||
|
||||
messageUserReaction#932844fa user_id:long reaction:string = MessageUserReaction;
|
||||
|
||||
messages.messageReactionsList#a366923c flags:# count:int reactions:Vector<MessageUserReaction> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;
|
||||
messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector<MessagePeerReaction> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;
|
||||
|
||||
availableReaction#c077ec01 flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction;
|
||||
|
||||
messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions;
|
||||
messages.availableReactions#768e3aad hash:int reactions:Vector<AvailableReaction> = messages.AvailableReactions;
|
||||
|
||||
messages.translateNoResult#67ca4737 = messages.TranslatedText;
|
||||
messages.translateResultText#a214f7d0 text:string = messages.TranslatedText;
|
||||
|
||||
messagePeerReaction#51b67eff flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:string = MessagePeerReaction;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -1596,12 +1599,15 @@ messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPe
|
||||
messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates;
|
||||
messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
|
||||
messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
|
||||
messages.sendReaction#25690ce4 flags:# peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
|
||||
messages.sendReaction#25690ce4 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
|
||||
messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector<int> = Updates;
|
||||
messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList;
|
||||
messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector<string> = Updates;
|
||||
messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
|
||||
messages.setDefaultReaction#d960c4d4 reaction:string = Bool;
|
||||
messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
|
||||
messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
|
||||
messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||
@@ -1750,4 +1756,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 137
|
||||
// LAYER 138
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.4.4.0" />
|
||||
Version="3.5.1.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,4,4,0
|
||||
PRODUCTVERSION 3,4,4,0
|
||||
FILEVERSION 3,5,1,0
|
||||
PRODUCTVERSION 3,5,1,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.4.4.0"
|
||||
VALUE "FileVersion", "3.5.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.4.4.0"
|
||||
VALUE "ProductVersion", "3.5.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,4,4,0
|
||||
PRODUCTVERSION 3,4,4,0
|
||||
FILEVERSION 3,5,1,0
|
||||
PRODUCTVERSION 3,5,1,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", "3.4.4.0"
|
||||
VALUE "FileVersion", "3.5.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.4.4.0"
|
||||
VALUE "ProductVersion", "3.5.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -252,12 +252,13 @@ void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
if (_photo) {
|
||||
if (const auto image = _photo->image(Data::PhotoSize::Small)) {
|
||||
const auto size = st::confirmInvitePhotoSize;
|
||||
p.drawPixmap(
|
||||
(width() - st::confirmInvitePhotoSize) / 2,
|
||||
(width() - size) / 2,
|
||||
st::confirmInvitePhotoTop,
|
||||
image->pixCircled(
|
||||
st::confirmInvitePhotoSize,
|
||||
st::confirmInvitePhotoSize));
|
||||
image->pix(
|
||||
{ size, size },
|
||||
{ .options = Images::Option::RoundCircle }));
|
||||
}
|
||||
} else if (_photoEmpty) {
|
||||
_photoEmpty->paint(
|
||||
|
||||
138
Telegram/SourceFiles/api/api_unread_things.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
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_unread_things.h"
|
||||
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPreloadIfLess = 5;
|
||||
constexpr auto kFirstRequestLimit = 10;
|
||||
constexpr auto kNextRequestLimit = 100;
|
||||
|
||||
} // namespace
|
||||
|
||||
UnreadThings::UnreadThings(not_null<ApiWrap*> api) : _api(api) {
|
||||
}
|
||||
|
||||
bool UnreadThings::trackMentions(PeerData *peer) const {
|
||||
return peer && (peer->isChat() || peer->isMegagroup());
|
||||
}
|
||||
|
||||
bool UnreadThings::trackReactions(PeerData *peer) const {
|
||||
return trackMentions(peer) || (peer && peer->isUser());
|
||||
}
|
||||
|
||||
void UnreadThings::preloadEnough(History *history) {
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
if (trackMentions(history->peer)) {
|
||||
preloadEnoughMentions(history);
|
||||
}
|
||||
if (trackReactions(history->peer)) {
|
||||
preloadEnoughReactions(history);
|
||||
}
|
||||
}
|
||||
|
||||
void UnreadThings::mediaAndMentionsRead(
|
||||
const base::flat_set<MsgId> &readIds,
|
||||
ChannelData *channel) {
|
||||
for (const auto &msgId : readIds) {
|
||||
_api->requestMessageData(channel, msgId, [=] {
|
||||
const auto item = channel
|
||||
? _api->session().data().message(channel->id, msgId)
|
||||
: _api->session().data().nonChannelMessage(msgId);
|
||||
if (item && item->mentionsMe()) {
|
||||
item->markMediaAndMentionRead();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void UnreadThings::preloadEnoughMentions(not_null<History*> history) {
|
||||
const auto fullCount = history->unreadMentions().count();
|
||||
const auto loadedCount = history->unreadMentions().loadedCount();
|
||||
const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount);
|
||||
if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) {
|
||||
requestMentions(history, loadedCount);
|
||||
}
|
||||
}
|
||||
|
||||
void UnreadThings::preloadEnoughReactions(not_null<History*> history) {
|
||||
const auto fullCount = history->unreadReactions().count();
|
||||
const auto loadedCount = history->unreadReactions().loadedCount();
|
||||
const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount);
|
||||
if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) {
|
||||
requestReactions(history, loadedCount);
|
||||
}
|
||||
}
|
||||
|
||||
void UnreadThings::requestMentions(not_null<History*> history, int loaded) {
|
||||
if (_mentionsRequests.contains(history)) {
|
||||
return;
|
||||
}
|
||||
const auto offsetId = std::max(
|
||||
history->unreadMentions().maxLoaded(),
|
||||
MsgId(1));
|
||||
const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit;
|
||||
const auto addOffset = loaded ? -(limit + 1) : -limit;
|
||||
const auto maxId = 0;
|
||||
const auto minId = 0;
|
||||
const auto requestId = _api->request(MTPmessages_GetUnreadMentions(
|
||||
history->peer->input,
|
||||
MTP_int(offsetId),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
MTP_int(maxId),
|
||||
MTP_int(minId)
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_mentionsRequests.remove(history);
|
||||
history->unreadMentions().addSlice(result, loaded);
|
||||
}).fail([=] {
|
||||
_mentionsRequests.remove(history);
|
||||
}).send();
|
||||
_mentionsRequests.emplace(history, requestId);
|
||||
}
|
||||
|
||||
void UnreadThings::requestReactions(not_null<History*> history, int loaded) {
|
||||
if (_reactionsRequests.contains(history)) {
|
||||
return;
|
||||
}
|
||||
const auto offsetId = loaded
|
||||
? std::max(history->unreadReactions().maxLoaded(), MsgId(1))
|
||||
: MsgId(1);
|
||||
const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit;
|
||||
const auto addOffset = loaded ? -(limit + 1) : -limit;
|
||||
const auto maxId = 0;
|
||||
const auto minId = 0;
|
||||
const auto requestId = _api->request(MTPmessages_GetUnreadReactions(
|
||||
history->peer->input,
|
||||
MTP_int(offsetId),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
MTP_int(maxId),
|
||||
MTP_int(minId)
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_reactionsRequests.remove(history);
|
||||
history->unreadReactions().addSlice(result, loaded);
|
||||
}).fail([=] {
|
||||
_reactionsRequests.remove(history);
|
||||
}).send();
|
||||
_reactionsRequests.emplace(history, requestId);
|
||||
}
|
||||
|
||||
} // namespace UnreadThings
|
||||
44
Telegram/SourceFiles/api/api_unread_things.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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
|
||||
|
||||
class History;
|
||||
class ApiWrap;
|
||||
class PeerData;
|
||||
class ChannelData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class UnreadThings final {
|
||||
public:
|
||||
explicit UnreadThings(not_null<ApiWrap*> api);
|
||||
|
||||
[[nodiscard]] bool trackMentions(PeerData *peer) const;
|
||||
[[nodiscard]] bool trackReactions(PeerData *peer) const;
|
||||
|
||||
void preloadEnough(History *history);
|
||||
|
||||
void mediaAndMentionsRead(
|
||||
const base::flat_set<MsgId> &readIds,
|
||||
ChannelData *channel = nullptr);
|
||||
|
||||
private:
|
||||
void preloadEnoughMentions(not_null<History*> history);
|
||||
void preloadEnoughReactions(not_null<History*> history);
|
||||
|
||||
void requestMentions(not_null<History*> history, int loaded);
|
||||
void requestReactions(not_null<History*> history, int loaded);
|
||||
|
||||
const not_null<ApiWrap*> _api;
|
||||
|
||||
base::flat_map<not_null<History*>, mtpRequestId> _mentionsRequests;
|
||||
base::flat_map<not_null<History*>, mtpRequestId> _reactionsRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
#include "api/api_unread_things.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "mtproto/mtp_instance.h"
|
||||
@@ -30,10 +31,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_send_action.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "core/application.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_facade.h"
|
||||
@@ -46,7 +49,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/text/format_values.h" // Ui::FormatPhone
|
||||
#include "app.h" // App::quitting
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
@@ -912,7 +914,7 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) {
|
||||
|
||||
_lastWasOnline = isOnline;
|
||||
_lastSetOnline = ms;
|
||||
if (!App::quitting()) {
|
||||
if (!Core::Quitting()) {
|
||||
_onlineRequest = api().request(MTPaccount_UpdateStatus(
|
||||
MTP_bool(!isOnline)
|
||||
)).send();
|
||||
@@ -1179,25 +1181,26 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateReadMessagesContents: {
|
||||
const auto &d = update.c_updateReadMessagesContents();
|
||||
auto possiblyReadMentions = base::flat_set<MsgId>();
|
||||
auto unknownReadIds = base::flat_set<MsgId>();
|
||||
for (const auto &msgId : d.vmessages().v) {
|
||||
if (const auto item = _session->data().nonChannelMessage(msgId.v)) {
|
||||
if (item->isUnreadMedia() || item->isUnreadMention()) {
|
||||
item->markMediaRead();
|
||||
item->markMediaAndMentionRead();
|
||||
_session->data().requestItemRepaint(item);
|
||||
|
||||
if (item->out()
|
||||
&& item->history()->peer->isUser()
|
||||
&& !requestingDifference()) {
|
||||
item->history()->peer->asUser()->madeAction(base::unixtime::now());
|
||||
item->history()->peer->asUser()->madeAction(
|
||||
base::unixtime::now());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Perhaps it was an unread mention!
|
||||
possiblyReadMentions.insert(msgId.v);
|
||||
unknownReadIds.insert(msgId.v);
|
||||
}
|
||||
}
|
||||
session().api().checkForUnreadMentions(possiblyReadMentions);
|
||||
session().api().unreadThings().mediaAndMentionsRead(unknownReadIds);
|
||||
} break;
|
||||
|
||||
case mtpc_updateReadHistoryInbox: {
|
||||
@@ -1566,19 +1569,21 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto possiblyReadMentions = base::flat_set<MsgId>();
|
||||
auto unknownReadIds = base::flat_set<MsgId>();
|
||||
for (const auto &msgId : d.vmessages().v) {
|
||||
if (auto item = session().data().message(channel->id, msgId.v)) {
|
||||
if (item->isUnreadMedia() || item->isUnreadMention()) {
|
||||
item->markMediaRead();
|
||||
item->markMediaAndMentionRead();
|
||||
session().data().requestItemRepaint(item);
|
||||
}
|
||||
} else {
|
||||
// Perhaps it was an unread mention!
|
||||
possiblyReadMentions.insert(msgId.v);
|
||||
unknownReadIds.insert(msgId.v);
|
||||
}
|
||||
}
|
||||
session().api().checkForUnreadMentions(possiblyReadMentions, channel);
|
||||
session().api().unreadThings().mediaAndMentionsRead(
|
||||
unknownReadIds,
|
||||
channel);
|
||||
} break;
|
||||
|
||||
// Edited messages.
|
||||
@@ -1626,6 +1631,16 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
d.vmsg_id().v);
|
||||
if (item) {
|
||||
item->updateReactions(&d.vreactions());
|
||||
} else {
|
||||
const auto hasUnreadReaction = Data::Reactions::HasUnread(
|
||||
d.vreactions());
|
||||
if (hasUnreadReaction || history->unreadReactions().has()) {
|
||||
// The unread reactions count could change.
|
||||
history->owner().histories().requestDialogEntry(history);
|
||||
}
|
||||
if (hasUnreadReaction) {
|
||||
history->unreadReactions().checkAdd(d.vmsg_id().v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -51,6 +51,7 @@ inline bool operator==(
|
||||
|
||||
struct PeersWithReactions {
|
||||
std::vector<PeerWithReaction> list;
|
||||
std::vector<PeerId> read;
|
||||
int fullReactionsCount = 0;
|
||||
bool unknown = false;
|
||||
};
|
||||
@@ -59,6 +60,7 @@ inline bool operator==(
|
||||
const PeersWithReactions &b) noexcept {
|
||||
return (a.fullReactionsCount == b.fullReactionsCount)
|
||||
&& (a.list == b.list)
|
||||
&& (a.read == b.read)
|
||||
&& (a.unknown == b.unknown);
|
||||
}
|
||||
|
||||
@@ -244,13 +246,15 @@ struct State {
|
||||
}
|
||||
|
||||
[[nodiscard]] PeersWithReactions WithEmptyReactions(
|
||||
const Peers &peers) {
|
||||
return PeersWithReactions{
|
||||
Peers &&peers) {
|
||||
auto result = PeersWithReactions{
|
||||
.list = peers.list | ranges::views::transform([](PeerId peer) {
|
||||
return PeerWithReaction{.peer = peer };
|
||||
}) | ranges::to_vector,
|
||||
.unknown = peers.unknown,
|
||||
};
|
||||
result.read = std::move(peers.list);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
|
||||
@@ -285,6 +289,7 @@ struct State {
|
||||
result.match([&](
|
||||
const MTPDmessages_messageReactionsList &data) {
|
||||
session->data().processUsers(data.vusers());
|
||||
session->data().processChats(data.vchats());
|
||||
|
||||
auto parsed = PeersWithReactions{
|
||||
.fullReactionsCount = data.vcount().v,
|
||||
@@ -293,7 +298,7 @@ struct State {
|
||||
for (const auto &vote : data.vreactions().v) {
|
||||
vote.match([&](const auto &data) {
|
||||
parsed.list.push_back(PeerWithReaction{
|
||||
.peer = peerFromUser(data.vuser_id()),
|
||||
.peer = peerFromMTP(data.vpeer_id()),
|
||||
.reaction = qs(data.vreaction()),
|
||||
});
|
||||
});
|
||||
@@ -319,7 +324,7 @@ struct State {
|
||||
return rpl::combine(
|
||||
WhoReactedIds(item, QString(), context),
|
||||
WhoReadIds(item, context)
|
||||
) | rpl::map([=](PeersWithReactions reacted, Peers read) {
|
||||
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
|
||||
if (reacted.unknown || read.unknown) {
|
||||
return PeersWithReactions{ .unknown = true };
|
||||
}
|
||||
@@ -329,7 +334,8 @@ struct State {
|
||||
list.push_back({ .peer = peer });
|
||||
}
|
||||
}
|
||||
return reacted;
|
||||
reacted.read = std::move(read.list);
|
||||
return std::move(reacted);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -438,6 +444,108 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||
RegenerateUserpics(state, small, large);
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||
const auto small = st.userpics.size;
|
||||
const auto large = st.photoSize;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto resolveWhoRead = reaction.isEmpty()
|
||||
&& WhoReadExists(item);
|
||||
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto pushNext = [=] {
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
|
||||
const auto resolveWhoReacted = !reaction.isEmpty()
|
||||
|| item->canViewReactions();
|
||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||
? WhoReadOrReactedIds(item, context)
|
||||
: resolveWhoRead
|
||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||
: WhoReactedIds(item, reaction, context);
|
||||
state->current.type = resolveWhoRead
|
||||
? DetectSeenType(item)
|
||||
: Ui::WhoReadType::Reacted;
|
||||
if (resolveWhoReacted) {
|
||||
const auto &list = item->reactions();
|
||||
state->current.fullReactionsCount = reaction.isEmpty()
|
||||
? ranges::accumulate(
|
||||
list,
|
||||
0,
|
||||
ranges::plus{},
|
||||
[](const auto &pair) { return pair.second; })
|
||||
: list.contains(reaction)
|
||||
? list.find(reaction)->second
|
||||
: 0;
|
||||
|
||||
// #TODO reactions
|
||||
state->current.singleReaction = !reaction.isEmpty()
|
||||
? reaction
|
||||
: (list.size() == 1)
|
||||
? list.front().first
|
||||
: QString();
|
||||
}
|
||||
std::move(
|
||||
idsWithReactions
|
||||
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
|
||||
if (peers.unknown) {
|
||||
state->userpics.clear();
|
||||
consumer.put_next(Ui::WhoReadContent{
|
||||
.type = state->current.type,
|
||||
.fullReactionsCount = state->current.fullReactionsCount,
|
||||
.fullReadCount = state->current.fullReadCount,
|
||||
.unknown = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
state->current.fullReadCount = int(peers.read.size());
|
||||
state->current.fullReactionsCount = peers.fullReactionsCount;
|
||||
if (whoReadIds) {
|
||||
const auto reacted = peers.list.size() - ranges::count(
|
||||
peers.list,
|
||||
QString(),
|
||||
&PeerWithReaction::reaction);
|
||||
whoReadIds->list = (peers.read.size() > reacted)
|
||||
? std::move(peers.read)
|
||||
: std::vector<PeerId>();
|
||||
}
|
||||
if (UpdateUserpics(state, item, peers.list)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.list.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||
}) | rpl::start_with_next([=] {
|
||||
for (const auto &userpic : state->userpics) {
|
||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||
!= userpic.uniqueKey) {
|
||||
state->scheduled = true;
|
||||
crl::on_main(&state->guard, [=] {
|
||||
state->scheduled = false;
|
||||
RegenerateUserpics(state, small, large);
|
||||
pushNext();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||
@@ -482,8 +590,9 @@ bool WhoReactedExists(not_null<HistoryItem*> item) {
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st) {
|
||||
return WhoReacted(item, QString(), context, st);
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
@@ -491,88 +600,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st) {
|
||||
const auto small = st.userpics.size;
|
||||
const auto large = st.photoSize;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto resolveWhoRead = reaction.isEmpty() && WhoReadExists(item);
|
||||
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto pushNext = [=] {
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
|
||||
const auto resolveWhoReacted = !reaction.isEmpty()
|
||||
|| item->canViewReactions();
|
||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||
? WhoReadOrReactedIds(item, context)
|
||||
: resolveWhoRead
|
||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||
: WhoReactedIds(item, reaction, context);
|
||||
state->current.type = resolveWhoRead
|
||||
? DetectSeenType(item)
|
||||
: Ui::WhoReadType::Reacted;
|
||||
if (resolveWhoReacted) {
|
||||
const auto &list = item->reactions();
|
||||
state->current.fullReactionsCount = reaction.isEmpty()
|
||||
? ranges::accumulate(
|
||||
list,
|
||||
0,
|
||||
ranges::plus{},
|
||||
[](const auto &pair) { return pair.second; })
|
||||
: list.contains(reaction)
|
||||
? list.find(reaction)->second
|
||||
: 0;
|
||||
|
||||
// #TODO reactions
|
||||
state->current.singleReaction = !reaction.isEmpty()
|
||||
? reaction
|
||||
: (list.size() == 1)
|
||||
? list.front().first
|
||||
: QString();
|
||||
}
|
||||
std::move(
|
||||
idsWithReactions
|
||||
) | rpl::start_with_next([=](const PeersWithReactions &peers) {
|
||||
if (peers.unknown) {
|
||||
state->userpics.clear();
|
||||
consumer.put_next(Ui::WhoReadContent{
|
||||
.type = state->current.type,
|
||||
.fullReactionsCount = state->current.fullReactionsCount,
|
||||
.unknown = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
state->current.fullReactionsCount = peers.fullReactionsCount;
|
||||
if (UpdateUserpics(state, item, peers.list)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.list.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||
}) | rpl::start_with_next([=] {
|
||||
for (const auto &userpic : state->userpics) {
|
||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||
!= userpic.uniqueKey) {
|
||||
state->scheduled = true;
|
||||
crl::on_main(&state->guard, [=] {
|
||||
state->scheduled = false;
|
||||
RegenerateUserpics(state, small, large);
|
||||
pushNext();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
return WhoReacted(item, reaction, context, st, nullptr);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -15,6 +15,7 @@ struct WhoRead;
|
||||
|
||||
namespace Ui {
|
||||
struct WhoReadContent;
|
||||
enum class WhoReadType;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
@@ -22,15 +23,21 @@ namespace Api {
|
||||
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
|
||||
|
||||
struct WhoReadList {
|
||||
std::vector<PeerId> list;
|
||||
Ui::WhoReadType type = {};
|
||||
};
|
||||
|
||||
// The context must be destroyed before the session holding this item.
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st); // Cache results for this lifetime.
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st); // Cache results for this lifetime.
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_user_privacy.h"
|
||||
#include "api/api_views.h"
|
||||
#include "api/api_confirm_phone.h"
|
||||
#include "api/api_unread_things.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -51,7 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/random.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -87,7 +88,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "facades.h"
|
||||
#include "app.h" // App::quitting
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -97,9 +97,6 @@ constexpr auto kSaveCloudDraftTimeout = 1000;
|
||||
constexpr auto kTopPromotionInterval = TimeId(60 * 60);
|
||||
constexpr auto kTopPromotionMinDelay = TimeId(10);
|
||||
constexpr auto kSmallDelayMs = 5;
|
||||
constexpr auto kUnreadMentionsPreloadIfLess = 5;
|
||||
constexpr auto kUnreadMentionsFirstRequestLimit = 10;
|
||||
constexpr auto kUnreadMentionsNextRequestLimit = 100;
|
||||
constexpr auto kSharedMediaLimit = 100;
|
||||
constexpr auto kReadFeaturedSetsTimeout = crl::time(1000);
|
||||
constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
|
||||
@@ -143,7 +140,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
, _confirmPhone(std::make_unique<Api::ConfirmPhone>(this))
|
||||
, _peerPhoto(std::make_unique<Api::PeerPhoto>(this))
|
||||
, _polls(std::make_unique<Api::Polls>(this))
|
||||
, _chatParticipants(std::make_unique<Api::ChatParticipants>(this)) {
|
||||
, _chatParticipants(std::make_unique<Api::ChatParticipants>(this))
|
||||
, _unreadThings(std::make_unique<Api::UnreadThings>(this)) {
|
||||
crl::on_main(session, [=] {
|
||||
// You can't use _session->lifetime() in the constructor,
|
||||
// only queued, because it is not constructed yet.
|
||||
@@ -1288,7 +1286,7 @@ void ApiWrap::migrateFail(not_null<PeerData*> peer, const QString &error) {
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::markMediaRead(
|
||||
void ApiWrap::markContentsRead(
|
||||
const base::flat_set<not_null<HistoryItem*>> &items) {
|
||||
auto markedIds = QVector<MTPint>();
|
||||
auto channelMarkedIds = base::flat_map<
|
||||
@@ -1296,12 +1294,7 @@ void ApiWrap::markMediaRead(
|
||||
QVector<MTPint>>();
|
||||
markedIds.reserve(items.size());
|
||||
for (const auto &item : items) {
|
||||
if ((!item->isUnreadMedia() || item->out())
|
||||
&& !item->isUnreadMention()) {
|
||||
continue;
|
||||
}
|
||||
item->markMediaRead();
|
||||
if (!item->isRegular()) {
|
||||
if (!item->markContentsRead(true) || !item->isRegular()) {
|
||||
continue;
|
||||
}
|
||||
if (const auto channel = item->history()->peer->asChannel()) {
|
||||
@@ -1325,13 +1318,8 @@ void ApiWrap::markMediaRead(
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::markMediaRead(not_null<HistoryItem*> item) {
|
||||
if ((!item->isUnreadMedia() || item->out())
|
||||
&& !item->isUnreadMention()) {
|
||||
return;
|
||||
}
|
||||
item->markMediaRead();
|
||||
if (!item->isRegular()) {
|
||||
void ApiWrap::markContentsRead(not_null<HistoryItem*> item) {
|
||||
if (!item->markContentsRead(true) || !item->isRegular()) {
|
||||
return;
|
||||
}
|
||||
const auto ids = MTP_vector<MTPint>(1, MTP_int(item->id));
|
||||
@@ -2127,7 +2115,7 @@ bool ApiWrap::isQuitPrevent() {
|
||||
|
||||
void ApiWrap::checkQuitPreventFinished() {
|
||||
if (_draftsSaveRequestIds.empty()) {
|
||||
if (App::quitting()) {
|
||||
if (Core::Quitting()) {
|
||||
LOG(("ApiWrap doesn't prevent quit any more."));
|
||||
}
|
||||
Core::App().quitPreventFinished();
|
||||
@@ -2911,45 +2899,6 @@ void ApiWrap::jumpToHistoryDate(not_null<PeerData*> peer, const QDate &date) {
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::preloadEnoughUnreadMentions(not_null<History*> history) {
|
||||
auto fullCount = history->getUnreadMentionsCount();
|
||||
auto loadedCount = history->getUnreadMentionsLoadedCount();
|
||||
auto allLoaded = (fullCount >= 0) ? (loadedCount >= fullCount) : false;
|
||||
if (fullCount < 0 || loadedCount >= kUnreadMentionsPreloadIfLess || allLoaded) {
|
||||
return;
|
||||
}
|
||||
if (_unreadMentionsRequests.contains(history)) {
|
||||
return;
|
||||
}
|
||||
auto offsetId = loadedCount ? history->getMaxLoadedUnreadMention() : 1;
|
||||
auto limit = loadedCount ? kUnreadMentionsNextRequestLimit : kUnreadMentionsFirstRequestLimit;
|
||||
auto addOffset = loadedCount ? -(limit + 1) : -limit;
|
||||
auto maxId = 0;
|
||||
auto minId = 0;
|
||||
auto requestId = request(MTPmessages_GetUnreadMentions(history->peer->input, MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), MTP_int(minId))).done([this, history](const MTPmessages_Messages &result) {
|
||||
_unreadMentionsRequests.remove(history);
|
||||
history->addUnreadMentionsSlice(result);
|
||||
}).fail([this, history] {
|
||||
_unreadMentionsRequests.remove(history);
|
||||
}).send();
|
||||
_unreadMentionsRequests.emplace(history, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::checkForUnreadMentions(
|
||||
const base::flat_set<MsgId> &possiblyReadMentions,
|
||||
ChannelData *channel) {
|
||||
for (const auto &msgId : possiblyReadMentions) {
|
||||
requestMessageData(channel, msgId, [=] {
|
||||
const auto item = channel
|
||||
? _session->data().message(channel->id, msgId)
|
||||
: _session->data().nonChannelMessage(msgId);
|
||||
if (item && item->mentionsMe()) {
|
||||
item->markMediaRead();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::requestSharedMediaCount(
|
||||
not_null<PeerData*> peer,
|
||||
Storage::SharedMediaType type) {
|
||||
@@ -4147,3 +4096,7 @@ Api::Polls &ApiWrap::polls() {
|
||||
Api::ChatParticipants &ApiWrap::chatParticipants() {
|
||||
return *_chatParticipants;
|
||||
}
|
||||
|
||||
Api::UnreadThings &ApiWrap::unreadThings() {
|
||||
return *_unreadThings;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ class ConfirmPhone;
|
||||
class PeerPhoto;
|
||||
class Polls;
|
||||
class ChatParticipants;
|
||||
class UnreadThings;
|
||||
|
||||
namespace details {
|
||||
|
||||
@@ -206,8 +207,9 @@ public:
|
||||
FnMut<void(not_null<ChannelData*>)> done,
|
||||
Fn<void(const QString &)> fail = nullptr);
|
||||
|
||||
void markMediaRead(const base::flat_set<not_null<HistoryItem*>> &items);
|
||||
void markMediaRead(not_null<HistoryItem*> item);
|
||||
void markContentsRead(
|
||||
const base::flat_set<not_null<HistoryItem*>> &items);
|
||||
void markContentsRead(not_null<HistoryItem*> item);
|
||||
|
||||
void deleteAllFromParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
@@ -250,11 +252,6 @@ public:
|
||||
|
||||
void jumpToDate(Dialogs::Key chat, const QDate &date);
|
||||
|
||||
void preloadEnoughUnreadMentions(not_null<History*> history);
|
||||
void checkForUnreadMentions(
|
||||
const base::flat_set<MsgId> &possiblyReadMentions,
|
||||
ChannelData *channel = nullptr);
|
||||
|
||||
using SliceType = Data::LoadDirection;
|
||||
void requestSharedMedia(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -356,6 +353,7 @@ public:
|
||||
[[nodiscard]] Api::PeerPhoto &peerPhoto();
|
||||
[[nodiscard]] Api::Polls &polls();
|
||||
[[nodiscard]] Api::ChatParticipants &chatParticipants();
|
||||
[[nodiscard]] Api::UnreadThings &unreadThings();
|
||||
|
||||
void updatePrivacyLastSeens();
|
||||
|
||||
@@ -562,8 +560,6 @@ private:
|
||||
mtpRequestId _contactsRequestId = 0;
|
||||
mtpRequestId _contactsStatusesRequestId = 0;
|
||||
|
||||
base::flat_map<not_null<History*>, mtpRequestId> _unreadMentionsRequests;
|
||||
|
||||
base::flat_set<std::tuple<
|
||||
not_null<PeerData*>,
|
||||
SharedMediaType,
|
||||
@@ -636,6 +632,7 @@ private:
|
||||
const std::unique_ptr<Api::PeerPhoto> _peerPhoto;
|
||||
const std::unique_ptr<Api::Polls> _polls;
|
||||
const std::unique_ptr<Api::ChatParticipants> _chatParticipants;
|
||||
const std::unique_ptr<Api::UnreadThings> _unreadThings;
|
||||
|
||||
mtpRequestId _wallPaperRequestId = 0;
|
||||
QString _wallPaperSlug;
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "app.h"
|
||||
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/application.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtGui/QFontDatabase>
|
||||
|
||||
namespace {
|
||||
|
||||
App::LaunchState _launchState = App::Launched;
|
||||
|
||||
HistoryView::Element *hoveredItem = nullptr,
|
||||
*pressedItem = nullptr,
|
||||
*hoveredLinkItem = nullptr,
|
||||
*pressedLinkItem = nullptr,
|
||||
*mousedItem = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace App {
|
||||
|
||||
void hoveredItem(HistoryView::Element *item) {
|
||||
::hoveredItem = item;
|
||||
}
|
||||
|
||||
HistoryView::Element *hoveredItem() {
|
||||
return ::hoveredItem;
|
||||
}
|
||||
|
||||
void pressedItem(HistoryView::Element *item) {
|
||||
::pressedItem = item;
|
||||
}
|
||||
|
||||
HistoryView::Element *pressedItem() {
|
||||
return ::pressedItem;
|
||||
}
|
||||
|
||||
void hoveredLinkItem(HistoryView::Element *item) {
|
||||
::hoveredLinkItem = item;
|
||||
}
|
||||
|
||||
HistoryView::Element *hoveredLinkItem() {
|
||||
return ::hoveredLinkItem;
|
||||
}
|
||||
|
||||
void pressedLinkItem(HistoryView::Element *item) {
|
||||
::pressedLinkItem = item;
|
||||
}
|
||||
|
||||
HistoryView::Element *pressedLinkItem() {
|
||||
return ::pressedLinkItem;
|
||||
}
|
||||
|
||||
void mousedItem(HistoryView::Element *item) {
|
||||
::mousedItem = item;
|
||||
}
|
||||
|
||||
HistoryView::Element *mousedItem() {
|
||||
return ::mousedItem;
|
||||
}
|
||||
|
||||
void clearMousedItems() {
|
||||
hoveredItem(nullptr);
|
||||
pressedItem(nullptr);
|
||||
hoveredLinkItem(nullptr);
|
||||
pressedLinkItem(nullptr);
|
||||
mousedItem(nullptr);
|
||||
}
|
||||
|
||||
void quit() {
|
||||
if (quitting()) {
|
||||
return;
|
||||
} else if (Core::IsAppLaunched()
|
||||
&& Core::App().exportPreventsQuit()) {
|
||||
return;
|
||||
}
|
||||
setLaunchState(QuitRequested);
|
||||
|
||||
if (auto window = App::wnd()) {
|
||||
if (!Core::Sandbox::Instance().isSavingSession()) {
|
||||
window->hide();
|
||||
}
|
||||
}
|
||||
Core::Application::QuitAttempt();
|
||||
}
|
||||
|
||||
bool quitting() {
|
||||
return _launchState != Launched;
|
||||
}
|
||||
|
||||
LaunchState launchState() {
|
||||
return _launchState;
|
||||
}
|
||||
|
||||
void setLaunchState(LaunchState state) {
|
||||
_launchState = state;
|
||||
}
|
||||
|
||||
void restart() {
|
||||
using namespace Core;
|
||||
const auto updateReady = !UpdaterDisabled()
|
||||
&& (UpdateChecker().state() == UpdateChecker::State::Ready);
|
||||
if (updateReady) {
|
||||
cSetRestartingUpdate(true);
|
||||
} else {
|
||||
cSetRestarting(true);
|
||||
cSetRestartingToSettings(true);
|
||||
}
|
||||
App::quit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace HistoryView {
|
||||
class Element;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace App {
|
||||
void hoveredItem(HistoryView::Element *item);
|
||||
HistoryView::Element *hoveredItem();
|
||||
void pressedItem(HistoryView::Element *item);
|
||||
HistoryView::Element *pressedItem();
|
||||
void hoveredLinkItem(HistoryView::Element *item);
|
||||
HistoryView::Element *hoveredLinkItem();
|
||||
void pressedLinkItem(HistoryView::Element *item);
|
||||
HistoryView::Element *pressedLinkItem();
|
||||
void mousedItem(HistoryView::Element *item);
|
||||
HistoryView::Element *mousedItem();
|
||||
void clearMousedItems();
|
||||
|
||||
enum LaunchState {
|
||||
Launched = 0,
|
||||
QuitRequested = 1,
|
||||
QuitProcessed = 2,
|
||||
};
|
||||
void quit();
|
||||
bool quitting();
|
||||
LaunchState launchState();
|
||||
void setLaunchState(LaunchState state);
|
||||
void restart();
|
||||
|
||||
};
|
||||
@@ -1530,10 +1530,8 @@ RevokePublicLinkBox::Inner::Inner(
|
||||
Ui::NameTextOptions());
|
||||
row.status.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
Ui::Text::Link(
|
||||
peer->userName(),
|
||||
_session->createInternalLink(peer->userName())),
|
||||
Ui::DialogTextOptions());
|
||||
_session->createInternalLink(
|
||||
Ui::Text::Link(peer->userName())));
|
||||
_rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,15 +321,11 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
const auto takeHeight = (width > height)
|
||||
? size
|
||||
: (height * size / width);
|
||||
return Images::prepare(
|
||||
image,
|
||||
takeWidth * cIntRetinaFactor(),
|
||||
takeHeight * cIntRetinaFactor(),
|
||||
Images::Option::Smooth
|
||||
| Images::Option::TransparentBackground
|
||||
| blur,
|
||||
size,
|
||||
size);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
return Images::Prepare(image, QSize(takeWidth, takeHeight) * ratio, {
|
||||
.options = Images::Option::TransparentBackground | blur,
|
||||
.outer = { size, size },
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PrepareScaledFromFull(
|
||||
@@ -667,7 +663,7 @@ void BackgroundPreviewBox::setScaledFromThumb() {
|
||||
_paper.backgroundColors(),
|
||||
_paper.gradientRotation(),
|
||||
_paper.patternOpacity(),
|
||||
_paper.document() ? Images::Option::Blurred : Images::Option(0));
|
||||
_paper.document() ? Images::Option::Blur : Images::Option());
|
||||
auto blurred = (_paper.document() || _paper.isPattern())
|
||||
? QImage()
|
||||
: PrepareScaledNonPattern(
|
||||
|
||||
@@ -318,7 +318,7 @@ void ProxyRow::updateFields(View &&view) {
|
||||
TextWithEntities()
|
||||
.append(_view.type)
|
||||
.append(' ')
|
||||
.append(Ui::Text::Link(endpoint, {})),
|
||||
.append(Ui::Text::Link(endpoint, QString())),
|
||||
Ui::ItemTextDefaultOptions());
|
||||
|
||||
const auto state = _view.state;
|
||||
|
||||
@@ -156,6 +156,12 @@ void DeleteMessagesBox::prepare() {
|
||||
: tr::lng_box_leave();
|
||||
}, _revoke->lifetime());
|
||||
}
|
||||
} else if (canDelete
|
||||
&& _wipeHistoryJustClear
|
||||
&& (peer->isMegagroup() || peer->isChat())) {
|
||||
appendDetails({
|
||||
tr::lng_delete_clear_for_me(tr::now)
|
||||
});
|
||||
}
|
||||
} else if (_moderateFrom) {
|
||||
Assert(_moderateInChannel != nullptr);
|
||||
|
||||
@@ -32,7 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_passport.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1116,7 +1116,8 @@ void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link) {
|
||||
auto submitCallback = [=](
|
||||
std::vector<not_null<PeerData*>> &&result,
|
||||
TextWithTags &&comment,
|
||||
Api::SendOptions options) {
|
||||
Api::SendOptions options,
|
||||
Data::ForwardOptions) {
|
||||
if (*sending || result.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ void AddReactionIcon(
|
||||
crl::async([icon = std::move(state->icon)]{});
|
||||
}
|
||||
if (!state->image.isNull()) {
|
||||
p.drawImage(0, 0, state->image);
|
||||
p.drawImage(QRect(0, 0, size, size), state->image);
|
||||
}
|
||||
}, icon->lifetime());
|
||||
}
|
||||
|
||||
@@ -183,11 +183,10 @@ void PeerShortInfoCover::paint(QPainter &p) {
|
||||
_widget->size() * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(Qt::black);
|
||||
Images::prepareRound(
|
||||
image,
|
||||
_userpicImage = Images::Round(
|
||||
std::move(image),
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
_userpicImage = std::move(image);
|
||||
}
|
||||
|
||||
paintCoverImage(p, frame.isNull() ? _userpicImage : frame);
|
||||
@@ -229,8 +228,8 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
|
||||
image,
|
||||
QRect(0, from * factor, roundedWidth * factor, rounded * factor));
|
||||
q.end();
|
||||
Images::prepareRound(
|
||||
_roundedTopImage,
|
||||
_roundedTopImage = Images::Round(
|
||||
std::move(_roundedTopImage),
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
p.drawImage(
|
||||
@@ -244,9 +243,8 @@ void PeerShortInfoCover::paintBars(QPainter &p) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
if (_shadowTop.isNull()) {
|
||||
_shadowTop = Images::GenerateShadow(height, kShadowMaxAlpha, 0);
|
||||
_shadowTop = _shadowTop.scaled(QSize(_st.size, height) * factor);
|
||||
Images::prepareRound(
|
||||
_shadowTop,
|
||||
_shadowTop = Images::Round(
|
||||
_shadowTop.scaled(QSize(_st.size, height) * factor),
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
}
|
||||
@@ -771,8 +769,8 @@ int PeerShortInfoBox::fillRoundedTopHeight() {
|
||||
void PeerShortInfoBox::refreshRoundedTopImage(const QColor &color) {
|
||||
_roundedTopColor = color;
|
||||
_roundedTop.fill(color);
|
||||
Images::prepareRound(
|
||||
_roundedTop,
|
||||
_roundedTop = Images::Round(
|
||||
std::move(_roundedTop),
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
}
|
||||
|
||||
@@ -51,19 +51,15 @@ void GenerateImage(
|
||||
bool blurred = false) {
|
||||
using namespace Images;
|
||||
const auto size = st::shortInfoWidth;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto options = Option::Smooth
|
||||
| Option::RoundedSmall
|
||||
| Option::RoundedTopLeft
|
||||
| Option::RoundedTopRight
|
||||
| (blurred ? Option::Blurred : Option());
|
||||
state->current.photo = Images::prepare(
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto options = Option::RoundSmall
|
||||
| Option::RoundSkipBottomLeft
|
||||
| Option::RoundSkipBottomRight
|
||||
| (blurred ? Option::Blur : Option());
|
||||
state->current.photo = Images::Prepare(
|
||||
std::move(image),
|
||||
size * factor,
|
||||
size * factor,
|
||||
options,
|
||||
size,
|
||||
size);
|
||||
QSize(size, size) * ratio,
|
||||
{ .options = options, .outer = { size, size } });
|
||||
}
|
||||
|
||||
void GenerateImage(
|
||||
|
||||
@@ -15,11 +15,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/chat/forward_options_box.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
@@ -40,6 +44,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_media_player.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class ForwardOptionItem final : public Ui::Menu::Action {
|
||||
public:
|
||||
using Ui::Menu::Action::Action;
|
||||
|
||||
void init(bool checked) {
|
||||
enableMouseSelecting();
|
||||
|
||||
AbstractButton::setDisabled(true);
|
||||
|
||||
_checkView = std::make_unique<Ui::CheckView>(st::defaultCheck, false);
|
||||
_checkView->checkedChanges(
|
||||
) | rpl::start_with_next([=](bool checked) {
|
||||
setIcon(checked ? &st::mediaPlayerMenuCheck : nullptr);
|
||||
}, lifetime());
|
||||
|
||||
_checkView->setChecked(checked, anim::type::normal);
|
||||
AbstractButton::clicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
_checkView->setChecked(
|
||||
!_checkView->checked(),
|
||||
anim::type::normal);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<Ui::CheckView*> checkView() const {
|
||||
return _checkView.get();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::CheckView> _checkView;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class ShareBox::Inner final : public Ui::RpWidget {
|
||||
public:
|
||||
@@ -442,17 +484,68 @@ SendMenu::Type ShareBox::sendMenuType() const {
|
||||
: SendMenu::Type::Scheduled;
|
||||
}
|
||||
|
||||
void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
if (_menu) {
|
||||
_menu = nullptr;
|
||||
return;
|
||||
}
|
||||
_menu.emplace(parent, st::popupMenuWithIcons);
|
||||
|
||||
if (_descriptor.forwardOptions.show) {
|
||||
auto createView = [&](rpl::producer<QString> &&text, bool checked) {
|
||||
auto item = base::make_unique_q<ForwardOptionItem>(
|
||||
_menu->menu(),
|
||||
st::popupMenuWithIcons.menu,
|
||||
new QAction(QString(), _menu->menu()),
|
||||
nullptr,
|
||||
nullptr);
|
||||
std::move(
|
||||
text
|
||||
) | rpl::start_with_next([action = item->action()](QString text) {
|
||||
action->setText(text);
|
||||
}, item->lifetime());
|
||||
item->init(checked);
|
||||
const auto view = item->checkView();
|
||||
_menu->addAction(std::move(item));
|
||||
return view;
|
||||
};
|
||||
Ui::FillForwardOptions(
|
||||
std::move(createView),
|
||||
_descriptor.forwardOptions.messagesCount,
|
||||
_forwardOptions,
|
||||
[=](Ui::ForwardOptions value) { _forwardOptions = value; },
|
||||
_menu->lifetime());
|
||||
|
||||
_menu->addSeparator();
|
||||
}
|
||||
|
||||
const auto result = SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
sendMenuType(),
|
||||
[=] { submitSilent(); },
|
||||
[=] { submitScheduled(); });
|
||||
const auto success = (result == SendMenu::FillMenuResult::Success);
|
||||
if (_descriptor.forwardOptions.show || success) {
|
||||
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::BottomRight);
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
void ShareBox::createButtons() {
|
||||
clearButtons();
|
||||
if (_hasSelected) {
|
||||
const auto send = addButton(tr::lng_share_confirm(), [=] {
|
||||
submit({});
|
||||
});
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
send,
|
||||
[=] { return sendMenuType(); },
|
||||
[=] { submitSilent(); },
|
||||
[=] { submitScheduled(); });
|
||||
_forwardOptions.hasCaptions = _descriptor.forwardOptions.hasCaptions;
|
||||
|
||||
send->setAcceptBoth();
|
||||
send->clicks(
|
||||
) | rpl::start_with_next([=](Qt::MouseButton button) {
|
||||
if (button == Qt::RightButton) {
|
||||
showMenu(send);
|
||||
}
|
||||
}, send->lifetime());
|
||||
} else if (_descriptor.copyCallback) {
|
||||
addButton(_copyLinkText.value(), [=] { copyLink(); });
|
||||
}
|
||||
@@ -488,10 +581,17 @@ void ShareBox::innerSelectedChanged(PeerData *peer, bool checked) {
|
||||
|
||||
void ShareBox::submit(Api::SendOptions options) {
|
||||
if (const auto onstack = _descriptor.submitCallback) {
|
||||
const auto forwardOptions = (_forwardOptions.hasCaptions
|
||||
&& _forwardOptions.dropCaptions)
|
||||
? Data::ForwardOptions::NoNamesAndCaptions
|
||||
: _forwardOptions.dropNames
|
||||
? Data::ForwardOptions::NoSenderNames
|
||||
: Data::ForwardOptions::PreserveInfo;
|
||||
onstack(
|
||||
_inner->selected(),
|
||||
_comment->entity()->getTextWithAppliedMarkdown(),
|
||||
options);
|
||||
options,
|
||||
forwardOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "base/observer.h"
|
||||
#include "base/timer.h"
|
||||
#include "ui/chat/forward_options_box.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/round_checkbox.h"
|
||||
#include "mtproto/sender.h"
|
||||
@@ -41,12 +42,17 @@ class Row;
|
||||
class IndexedList;
|
||||
} // namespace Dialogs
|
||||
|
||||
namespace Data {
|
||||
enum class ForwardOptions;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class MultiSelect;
|
||||
class InputField;
|
||||
struct ScrollToRequest;
|
||||
template <typename Widget>
|
||||
class SlideWrap;
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
QString AppendShareGameScoreUrl(
|
||||
@@ -63,7 +69,8 @@ public:
|
||||
using SubmitCallback = Fn<void(
|
||||
std::vector<not_null<PeerData*>>&&,
|
||||
TextWithTags&&,
|
||||
Api::SendOptions)>;
|
||||
Api::SendOptions,
|
||||
Data::ForwardOptions option)>;
|
||||
using FilterCallback = Fn<bool(PeerData*)>;
|
||||
|
||||
struct Descriptor {
|
||||
@@ -79,6 +86,11 @@ public:
|
||||
const style::MultiSelect *stMultiSelect = nullptr;
|
||||
const style::InputField *stComment = nullptr;
|
||||
const style::PeerList *st = nullptr;
|
||||
struct {
|
||||
int messagesCount = 0;
|
||||
bool show = false;
|
||||
bool hasCaptions = false;
|
||||
} forwardOptions;
|
||||
};
|
||||
ShareBox(QWidget*, Descriptor &&descriptor);
|
||||
|
||||
@@ -119,6 +131,8 @@ private:
|
||||
mtpRequestId requestId);
|
||||
void peopleFail(const MTP::Error &error, mtpRequestId requestId);
|
||||
|
||||
void showMenu(not_null<Ui::RpWidget*> parent);
|
||||
|
||||
Descriptor _descriptor;
|
||||
MTP::Sender _api;
|
||||
|
||||
@@ -126,6 +140,9 @@ private:
|
||||
object_ptr<Ui::SlideWrap<Ui::InputField>> _comment;
|
||||
object_ptr<Ui::RpWidget> _bottomWidget;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
Ui::ForwardOptions _forwardOptions;
|
||||
|
||||
class Inner;
|
||||
QPointer<Inner> _inner;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -51,6 +52,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
constexpr auto kStickersPanelPerRow = 5;
|
||||
constexpr auto kMinRepaintDelay = crl::time(33);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
|
||||
using Data::StickersSet;
|
||||
using Data::StickersPack;
|
||||
@@ -99,7 +102,8 @@ private:
|
||||
struct Element {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
Lottie::Animation *animated = nullptr;
|
||||
Lottie::Animation *lottie = nullptr;
|
||||
Media::Clip::ReaderPointer webm;
|
||||
Ui::Animations::Simple overAnimation;
|
||||
};
|
||||
|
||||
@@ -107,8 +111,18 @@ private:
|
||||
|
||||
QSize boundingBoxSize() const;
|
||||
|
||||
void paintSticker(Painter &p, int index, QPoint position) const;
|
||||
void paintSticker(
|
||||
Painter &p,
|
||||
int index,
|
||||
QPoint position,
|
||||
bool paused,
|
||||
crl::time now) const;
|
||||
void setupLottie(int index);
|
||||
void setupWebm(int index);
|
||||
void clipCallback(
|
||||
Media::Clip::Notification notification,
|
||||
not_null<DocumentData*> document,
|
||||
int index);
|
||||
|
||||
void updateSelected();
|
||||
void setSelected(int selected);
|
||||
@@ -123,6 +137,8 @@ private:
|
||||
not_null<Lottie::MultiPlayer*> getLottiePlayer();
|
||||
|
||||
void showPreview();
|
||||
void updateItems();
|
||||
void repaintItems(crl::time now = 0);
|
||||
|
||||
not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
@@ -142,6 +158,12 @@ private:
|
||||
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
|
||||
int _visibleTop = 0;
|
||||
int _visibleBottom = 0;
|
||||
crl::time _lastScrolledAt = 0;
|
||||
crl::time _lastUpdatedAt = 0;
|
||||
base::Timer _updateItemsTimer;
|
||||
|
||||
StickerSetIdentifier _input;
|
||||
|
||||
mtpRequestId _installRequest = 0;
|
||||
@@ -370,9 +392,12 @@ StickerSetBox::Inner::Inner(
|
||||
, _pathGradient(std::make_unique<Ui::PathShiftGradient>(
|
||||
st::windowBgRipple,
|
||||
st::windowBgOver,
|
||||
[=] { update(); }))
|
||||
[=] { repaintItems(); }))
|
||||
, _updateItemsTimer([=] { updateItems(); })
|
||||
, _input(set)
|
||||
, _previewTimer([=] { showPreview(); }) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
_api.request(MTPmessages_GetStickerSet(
|
||||
Data::InputStickerSet(_input),
|
||||
MTP_int(0) // hash
|
||||
@@ -387,7 +412,7 @@ StickerSetBox::Inner::Inner(
|
||||
|
||||
_controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
updateItems();
|
||||
}, lifetime());
|
||||
|
||||
setMouseTracking(true);
|
||||
@@ -735,7 +760,7 @@ not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() {
|
||||
Lottie::MakeFrameRenderer());
|
||||
_lottiePlayer->updates(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
updateItems();
|
||||
}, lifetime());
|
||||
}
|
||||
return _lottiePlayer.get();
|
||||
@@ -756,6 +781,7 @@ int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const {
|
||||
void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
p.fillRect(e->rect(), st::boxBg);
|
||||
if (_elements.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -764,6 +790,9 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
_pathGradient->startFrame(0, width(), width() / 2);
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
for (int32 i = from; i < to; ++i) {
|
||||
for (int32 j = 0; j < kStickersPanelPerRow; ++j) {
|
||||
int32 index = i * kStickersPanelPerRow + j;
|
||||
@@ -771,16 +800,12 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
break;
|
||||
}
|
||||
const auto pos = QPoint(st::stickersPadding.left() + j * st::stickersSize.width(), st::stickersPadding.top() + i * st::stickersSize.height());
|
||||
paintSticker(p, index, pos);
|
||||
paintSticker(p, index, pos, paused, now);
|
||||
}
|
||||
}
|
||||
|
||||
if (_lottiePlayer) {
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
if (!paused) {
|
||||
_lottiePlayer->markFrameShown();
|
||||
}
|
||||
if (_lottiePlayer && !paused) {
|
||||
_lottiePlayer->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,6 +818,12 @@ QSize StickerSetBox::Inner::boundingBoxSize() const {
|
||||
void StickerSetBox::Inner::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
if (_visibleTop != visibleTop || _visibleBottom != visibleBottom) {
|
||||
_visibleTop = visibleTop;
|
||||
_visibleBottom = visibleBottom;
|
||||
_lastScrolledAt = crl::now();
|
||||
update();
|
||||
}
|
||||
const auto pauseInRows = [&](int fromRow, int tillRow) {
|
||||
Expects(fromRow <= tillRow);
|
||||
|
||||
@@ -802,8 +833,10 @@ void StickerSetBox::Inner::visibleTopBottomUpdated(
|
||||
if (index >= _elements.size()) {
|
||||
break;
|
||||
}
|
||||
if (const auto animated = _elements[index].animated) {
|
||||
_lottiePlayer->pause(animated);
|
||||
if (const auto lottie = _elements[index].lottie) {
|
||||
_lottiePlayer->pause(lottie);
|
||||
} else if (auto &webm = _elements[index].webm) {
|
||||
webm = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -834,17 +867,63 @@ void StickerSetBox::Inner::visibleTopBottomUpdated(
|
||||
void StickerSetBox::Inner::setupLottie(int index) {
|
||||
auto &element = _elements[index];
|
||||
|
||||
element.animated = ChatHelpers::LottieAnimationFromDocument(
|
||||
element.lottie = ChatHelpers::LottieAnimationFromDocument(
|
||||
getLottiePlayer(),
|
||||
element.documentMedia.get(),
|
||||
ChatHelpers::StickerLottieSize::StickerSet,
|
||||
boundingBoxSize() * cIntRetinaFactor());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::setupWebm(int index) {
|
||||
auto &element = _elements[index];
|
||||
|
||||
const auto document = element.document;
|
||||
auto callback = [=](Media::Clip::Notification notification) {
|
||||
clipCallback(notification, document, index);
|
||||
};
|
||||
element.webm = Media::Clip::MakeReader(
|
||||
element.documentMedia->owner()->location(),
|
||||
element.documentMedia->bytes(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::clipCallback(
|
||||
Media::Clip::Notification notification,
|
||||
not_null<DocumentData*> document,
|
||||
int index) {
|
||||
const auto i = (index < _elements.size()
|
||||
&& _elements[index].document == document)
|
||||
? (_elements.begin() + index)
|
||||
: ranges::find(_elements, document, &Element::document);
|
||||
if (i == end(_elements)) {
|
||||
return;
|
||||
}
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case Notification::Reinit: {
|
||||
auto &webm = i->webm;
|
||||
if (webm->state() == State::Error) {
|
||||
webm.setBad();
|
||||
} else if (webm->ready() && !webm->started()) {
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
i->document,
|
||||
boundingBoxSize());
|
||||
webm->start({ .frame = size, .keepAlpha = true });
|
||||
}
|
||||
} break;
|
||||
|
||||
case Notification::Repaint: break;
|
||||
}
|
||||
|
||||
updateItems();
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::paintSticker(
|
||||
Painter &p,
|
||||
int index,
|
||||
QPoint position) const {
|
||||
QPoint position,
|
||||
bool paused,
|
||||
crl::time now) const {
|
||||
if (const auto over = _elements[index].overAnimation.value((index == _selected) ? 1. : 0.)) {
|
||||
p.setOpacity(over);
|
||||
auto tl = position;
|
||||
@@ -856,47 +935,46 @@ void StickerSetBox::Inner::paintSticker(
|
||||
const auto &element = _elements[index];
|
||||
const auto document = element.document;
|
||||
const auto &media = element.documentMedia;
|
||||
const auto sticker = document->sticker();
|
||||
media->checkStickerSmall();
|
||||
|
||||
const auto isAnimated = document->sticker()->animated;
|
||||
if (isAnimated
|
||||
&& !element.animated
|
||||
&& media->loaded()) {
|
||||
const_cast<Inner*>(this)->setupLottie(index);
|
||||
if (media->loaded()) {
|
||||
if (sticker->isLottie() && !element.lottie) {
|
||||
const_cast<Inner*>(this)->setupLottie(index);
|
||||
} else if (sticker->isWebm() && !element.webm) {
|
||||
const_cast<Inner*>(this)->setupWebm(index);
|
||||
}
|
||||
}
|
||||
|
||||
auto w = 1;
|
||||
auto h = 1;
|
||||
if (isAnimated && !document->dimensions.isEmpty()) {
|
||||
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
|
||||
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
|
||||
w = std::max(size.width(), 1);
|
||||
h = std::max(size.height(), 1);
|
||||
} else {
|
||||
auto coef = qMin((st::stickersSize.width() - st::roundRadiusSmall * 2) / float64(document->dimensions.width()), (st::stickersSize.height() - st::roundRadiusSmall * 2) / float64(document->dimensions.height()));
|
||||
if (coef > 1) coef = 1;
|
||||
w = std::max(qRound(coef * document->dimensions.width()), 1);
|
||||
h = std::max(qRound(coef * document->dimensions.height()), 1);
|
||||
}
|
||||
QPoint ppos = position + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2);
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
document,
|
||||
boundingBoxSize());
|
||||
const auto ppos = position + QPoint(
|
||||
(st::stickersSize.width() - size.width()) / 2,
|
||||
(st::stickersSize.height() - size.height()) / 2);
|
||||
|
||||
if (element.animated && element.animated->ready()) {
|
||||
const auto frame = element.animated->frame();
|
||||
if (element.lottie && element.lottie->ready()) {
|
||||
const auto frame = element.lottie->frame();
|
||||
p.drawImage(
|
||||
QRect(ppos, frame.size() / cIntRetinaFactor()),
|
||||
frame);
|
||||
|
||||
_lottiePlayer->unpause(element.animated);
|
||||
_lottiePlayer->unpause(element.lottie);
|
||||
} else if (element.webm && element.webm->started()) {
|
||||
p.drawPixmap(ppos, element.webm->current({
|
||||
.frame = size,
|
||||
.keepAlpha = true,
|
||||
}, paused ? 0 : now));
|
||||
} else if (const auto image = media->getStickerSmall()) {
|
||||
p.drawPixmapLeft(
|
||||
ppos,
|
||||
width(),
|
||||
image->pix(w, h));
|
||||
image->pix(size));
|
||||
} else {
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, QSize(w, h)),
|
||||
QRect(ppos, size),
|
||||
_pathGradient.get());
|
||||
}
|
||||
}
|
||||
@@ -965,4 +1043,23 @@ void StickerSetBox::Inner::archiveStickers() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::updateItems() {
|
||||
const auto now = crl::now();
|
||||
|
||||
const auto delay = std::max(
|
||||
_lastScrolledAt + kMinAfterScrollDelay - now,
|
||||
_lastUpdatedAt + kMinRepaintDelay - now);
|
||||
if (delay <= 0) {
|
||||
repaintItems(now);
|
||||
} else if (!_updateItemsTimer.isActive()
|
||||
|| _updateItemsTimer.remainingTime() > kMinRepaintDelay) {
|
||||
_updateItemsTimer.callOnce(std::max(delay, kMinRepaintDelay));
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::repaintItems(crl::time now) {
|
||||
_lastUpdatedAt = now ? now : crl::now();
|
||||
update();
|
||||
}
|
||||
|
||||
StickerSetBox::Inner::~Inner() = default;
|
||||
|
||||
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "main/main_session.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -143,10 +144,7 @@ private:
|
||||
int32 count,
|
||||
const QString &title,
|
||||
int titleWidth,
|
||||
bool installed,
|
||||
bool official,
|
||||
bool unread,
|
||||
bool archived,
|
||||
Data::StickersSetFlags flagsOverride,
|
||||
bool removed,
|
||||
int32 pixw,
|
||||
int32 pixh);
|
||||
@@ -154,6 +152,10 @@ private:
|
||||
|
||||
bool isRecentSet() const;
|
||||
bool isMasksSet() const;
|
||||
bool isWebm() const;
|
||||
bool isInstalled() const;
|
||||
bool isUnread() const;
|
||||
bool isArchived() const;
|
||||
|
||||
const not_null<StickersSet*> set;
|
||||
DocumentData *sticker = nullptr;
|
||||
@@ -162,16 +164,14 @@ private:
|
||||
int32 count = 0;
|
||||
QString title;
|
||||
int titleWidth = 0;
|
||||
bool installed = false;
|
||||
bool official = false;
|
||||
bool unread = false;
|
||||
bool archived = false;
|
||||
Data::StickersSetFlags flagsOverride;
|
||||
bool removed = false;
|
||||
int32 pixw = 0;
|
||||
int32 pixh = 0;
|
||||
anim::value yadd;
|
||||
std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
Media::Clip::ReaderPointer webm;
|
||||
};
|
||||
struct MegagroupSet {
|
||||
inline bool operator==(const MegagroupSet &other) const {
|
||||
@@ -221,16 +221,26 @@ private:
|
||||
void setActionSel(int32 actionSel);
|
||||
float64 aboveShadowOpacity() const;
|
||||
void validateLottieAnimation(not_null<Row*> row);
|
||||
void validateWebmAnimation(not_null<Row*> row);
|
||||
void validateAnimation(not_null<Row*> row);
|
||||
void updateRowThumbnail(not_null<Row*> row);
|
||||
|
||||
void clipCallback(
|
||||
not_null<Row*> row,
|
||||
Media::Clip::Notification notification);
|
||||
|
||||
void readVisibleSets();
|
||||
|
||||
void updateControlsGeometry();
|
||||
void rebuildAppendSet(not_null<StickersSet*> set, int maxNameWidth);
|
||||
void fillSetCover(not_null<StickersSet*> set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
|
||||
int fillSetCount(not_null<StickersSet*> set) const;
|
||||
QString fillSetTitle(not_null<StickersSet*> set, int maxNameWidth, int *outTitleWidth) const;
|
||||
void fillSetFlags(not_null<StickersSet*> set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived);
|
||||
[[nodiscard]] QString fillSetTitle(
|
||||
not_null<StickersSet*> set,
|
||||
int maxNameWidth,
|
||||
int *outTitleWidth) const;
|
||||
[[nodiscard]] Data::StickersSetFlags fillSetFlags(
|
||||
not_null<StickersSet*> set) const;
|
||||
void rebuildMegagroupSet();
|
||||
void fixupMegagroupSetAddress();
|
||||
void handleMegagroupSetAddressChange();
|
||||
@@ -323,19 +333,18 @@ void StickersBox::CounterWidget::setCounter(int counter) {
|
||||
auto dummy = QImage(1, 1, QImage::Format_ARGB32_Premultiplied);
|
||||
Painter p(&dummy);
|
||||
|
||||
auto newWidth = 0;
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
const auto badge = Dialogs::Ui::PaintUnreadBadge(p, _text, 0, 0, _st);
|
||||
|
||||
resize(newWidth, st::stickersFeaturedBadgeSize);
|
||||
resize(badge.width(), st::stickersFeaturedBadgeSize);
|
||||
}
|
||||
|
||||
void StickersBox::CounterWidget::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
if (!_text.isEmpty()) {
|
||||
auto unreadRight = rtl() ? 0 : width();
|
||||
auto unreadTop = 0;
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
const auto unreadRight = rtl() ? 0 : width();
|
||||
const auto unreadTop = 0;
|
||||
Dialogs::Ui::PaintUnreadBadge(p, _text, unreadRight, unreadTop, _st);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,10 +1039,7 @@ StickersBox::Inner::Row::Row(
|
||||
int32 count,
|
||||
const QString &title,
|
||||
int titleWidth,
|
||||
bool installed,
|
||||
bool official,
|
||||
bool unread,
|
||||
bool archived,
|
||||
Data::StickersSetFlags flagsOverride,
|
||||
bool removed,
|
||||
int32 pixw,
|
||||
int32 pixh)
|
||||
@@ -1042,10 +1048,7 @@ StickersBox::Inner::Row::Row(
|
||||
, count(count)
|
||||
, title(title)
|
||||
, titleWidth(titleWidth)
|
||||
, installed(installed)
|
||||
, official(official)
|
||||
, unread(unread)
|
||||
, archived(archived)
|
||||
, flagsOverride(flagsOverride)
|
||||
, removed(removed)
|
||||
, pixw(pixw)
|
||||
, pixh(pixh) {
|
||||
@@ -1062,6 +1065,22 @@ bool StickersBox::Inner::Row::isMasksSet() const {
|
||||
return (set->flags & SetFlag::Masks);
|
||||
}
|
||||
|
||||
bool StickersBox::Inner::Row::isWebm() const {
|
||||
return (set->flags & SetFlag::Webm);
|
||||
}
|
||||
|
||||
bool StickersBox::Inner::Row::isInstalled() const {
|
||||
return (flagsOverride & SetFlag::Installed);
|
||||
}
|
||||
|
||||
bool StickersBox::Inner::Row::isUnread() const {
|
||||
return (flagsOverride & SetFlag::Unread);
|
||||
}
|
||||
|
||||
bool StickersBox::Inner::Row::isArchived() const {
|
||||
return (flagsOverride & SetFlag::Archived);
|
||||
}
|
||||
|
||||
StickersBox::Inner::Inner(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -1302,7 +1321,7 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
|
||||
p.setPen(st::contactsNameFg);
|
||||
p.drawTextLeft(namex, namey, width(), row->title, row->titleWidth);
|
||||
|
||||
if (row->unread) {
|
||||
if (row->isUnread()) {
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::stickersFeaturedUnreadBg);
|
||||
|
||||
@@ -1344,22 +1363,17 @@ void StickersBox::Inner::paintRowThumbnail(
|
||||
row->stickerMedia->thumbnailWanted(origin);
|
||||
}
|
||||
}
|
||||
validateLottieAnimation(row);
|
||||
if (!row->lottie) {
|
||||
const auto thumb = row->thumbnailMedia
|
||||
? row->thumbnailMedia->image()
|
||||
: row->stickerMedia
|
||||
? row->stickerMedia->thumbnail()
|
||||
: nullptr;
|
||||
if (!thumb) {
|
||||
return;
|
||||
}
|
||||
p.drawPixmapLeft(
|
||||
left + (st::contactsPhotoSize - row->pixw) / 2,
|
||||
st::contactsPadding.top() + (st::contactsPhotoSize - row->pixh) / 2,
|
||||
width(),
|
||||
thumb->pix(row->pixw, row->pixh));
|
||||
} else if (row->lottie->ready()) {
|
||||
validateAnimation(row);
|
||||
const auto thumb = row->thumbnailMedia
|
||||
? row->thumbnailMedia->image()
|
||||
: row->stickerMedia
|
||||
? row->stickerMedia->thumbnail()
|
||||
: nullptr;
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
const auto x = left + (st::contactsPhotoSize - row->pixw) / 2;
|
||||
const auto y = st::contactsPadding.top() + (st::contactsPhotoSize - row->pixh) / 2;
|
||||
if (row->lottie && row->lottie->ready()) {
|
||||
const auto frame = row->lottie->frame();
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
p.drawImage(
|
||||
@@ -1369,17 +1383,30 @@ void StickersBox::Inner::paintRowThumbnail(
|
||||
size.width(),
|
||||
size.height()),
|
||||
frame);
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
if (!paused) {
|
||||
row->lottie->markFrameShown();
|
||||
}
|
||||
} else if (row->webm && row->webm->started()) {
|
||||
p.drawPixmapLeft(
|
||||
x,
|
||||
y,
|
||||
width(),
|
||||
row->webm->current(
|
||||
{ .frame = { row->pixw, row->pixh }, .keepAlpha = true },
|
||||
paused ? 0 : crl::now()));
|
||||
} else if (thumb) {
|
||||
p.drawPixmapLeft(
|
||||
x,
|
||||
y,
|
||||
width(),
|
||||
thumb->pix(row->pixw, row->pixh));
|
||||
}
|
||||
}
|
||||
|
||||
void StickersBox::Inner::validateLottieAnimation(not_null<Row*> row) {
|
||||
if (row->lottie
|
||||
|| !ChatHelpers::HasLottieThumbnail(
|
||||
row->set->flags,
|
||||
row->thumbnailMedia.get(),
|
||||
row->stickerMedia.get())) {
|
||||
return;
|
||||
@@ -1401,6 +1428,51 @@ void StickersBox::Inner::validateLottieAnimation(not_null<Row*> row) {
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickersBox::Inner::validateWebmAnimation(not_null<Row*> row) {
|
||||
if (row->webm
|
||||
|| !ChatHelpers::HasWebmThumbnail(
|
||||
row->set->flags,
|
||||
row->thumbnailMedia.get(),
|
||||
row->stickerMedia.get())) {
|
||||
return;
|
||||
}
|
||||
auto callback = [=](Media::Clip::Notification notification) {
|
||||
clipCallback(row, notification);
|
||||
};
|
||||
row->webm = ChatHelpers::WebmThumbnail(
|
||||
row->thumbnailMedia.get(),
|
||||
row->stickerMedia.get(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void StickersBox::Inner::clipCallback(
|
||||
not_null<Row*> row,
|
||||
Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case Notification::Reinit: {
|
||||
if (!row->webm) {
|
||||
return;
|
||||
} else if (row->webm->state() == State::Error) {
|
||||
row->webm.setBad();
|
||||
} else if (row->webm->ready() && !row->webm->started()) {
|
||||
row->webm->start({
|
||||
.frame = { row->pixw, row->pixh },
|
||||
.keepAlpha = true,
|
||||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case Notification::Repaint: break;
|
||||
}
|
||||
updateRowThumbnail(row);
|
||||
}
|
||||
|
||||
void StickersBox::Inner::validateAnimation(not_null<Row*> row) {
|
||||
validateWebmAnimation(row);
|
||||
validateLottieAnimation(row);
|
||||
}
|
||||
|
||||
void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
|
||||
const auto rowTop = [&] {
|
||||
if (row == _megagroupSelectedSet.get()) {
|
||||
@@ -1429,7 +1501,7 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
|
||||
void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int index) {
|
||||
auto removeButton = (_isInstalled && !row->removed);
|
||||
auto rect = relativeButtonRect(removeButton);
|
||||
if (!_isInstalled && row->installed && !row->archived && !row->removed) {
|
||||
if (!_isInstalled && row->isInstalled() && !row->isArchived() && !row->removed) {
|
||||
// Checkbox after installed from Trending or Archived.
|
||||
int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (rect.width() + st::stickersFeaturedInstalled.width()) / 2);
|
||||
int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2;
|
||||
@@ -1516,7 +1588,7 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
|
||||
auto rippleMask = Ui::RippleAnimation::ellipseMask(QSize(rippleSize, rippleSize));
|
||||
ensureRipple(st::stickersRemove.ripple, std::move(rippleMask), removeButton);
|
||||
}
|
||||
} else if (!row->installed || row->archived || row->removed) {
|
||||
} else if (!row->isInstalled() || row->isArchived() || row->removed) {
|
||||
auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
|
||||
auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::roundRadiusSmall);
|
||||
ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton);
|
||||
@@ -1649,7 +1721,7 @@ void StickersBox::Inner::updateSelected() {
|
||||
selected = selectedIndex;
|
||||
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
|
||||
const auto row = _rows[selectedIndex].get();
|
||||
if (!_megagroupSet && (_isInstalled || !row->installed || row->archived || row->removed)) {
|
||||
if (!_megagroupSet && (_isInstalled || !row->isInstalled() || row->isArchived() || row->removed)) {
|
||||
auto removeButton = (_isInstalled && !row->removed);
|
||||
auto rect = myrtlrect(relativeButtonRect(removeButton));
|
||||
actionSel = rect.contains(local) ? selectedIndex : -1;
|
||||
@@ -1952,8 +2024,10 @@ void StickersBox::Inner::rebuildMegagroupSet() {
|
||||
auto sticker = (DocumentData*)nullptr;
|
||||
auto pixw = 0, pixh = 0;
|
||||
fillSetCover(set, &sticker, &pixw, &pixh);
|
||||
auto installed = true, official = false, unread = false, archived = false, removed = false;
|
||||
if (!_megagroupSelectedSet || _megagroupSelectedSet->set->id != set->id) {
|
||||
auto flagsOverride = SetFlag::Installed;
|
||||
auto removed = false;
|
||||
if (!_megagroupSelectedSet
|
||||
|| _megagroupSelectedSet->set->id != set->id) {
|
||||
_megagroupSetField->setText(set->shortName);
|
||||
_megagroupSetField->finishAnimating();
|
||||
}
|
||||
@@ -1963,10 +2037,7 @@ void StickersBox::Inner::rebuildMegagroupSet() {
|
||||
count,
|
||||
title,
|
||||
titleWidth,
|
||||
installed,
|
||||
official,
|
||||
unread,
|
||||
archived,
|
||||
flagsOverride,
|
||||
removed,
|
||||
pixw,
|
||||
pixh);
|
||||
@@ -2095,13 +2166,14 @@ void StickersBox::Inner::updateRows() {
|
||||
}
|
||||
}
|
||||
if (!row->isRecentSet()) {
|
||||
auto wasInstalled = row->installed;
|
||||
auto wasArchived = row->archived;
|
||||
fillSetFlags(set, &row->installed, &row->official, &row->unread, &row->archived);
|
||||
auto wasInstalled = row->isInstalled();
|
||||
auto wasArchived = row->isArchived();
|
||||
row->flagsOverride = fillSetFlags(set);
|
||||
if (_isInstalled) {
|
||||
row->archived = false;
|
||||
row->flagsOverride &= ~SetFlag::Archived;
|
||||
}
|
||||
if (row->installed != wasInstalled || row->archived != wasArchived) {
|
||||
if (row->isInstalled() != wasInstalled
|
||||
|| row->isArchived() != wasArchived) {
|
||||
row->ripple.reset();
|
||||
}
|
||||
}
|
||||
@@ -2143,11 +2215,11 @@ int StickersBox::Inner::countMaxNameWidth() const {
|
||||
void StickersBox::Inner::rebuildAppendSet(
|
||||
not_null<StickersSet*> set,
|
||||
int maxNameWidth) {
|
||||
bool installed = true, official = true, unread = false, archived = false, removed = false;
|
||||
if (set->id != Data::Stickers::CloudRecentSetId) {
|
||||
fillSetFlags(set, &installed, &official, &unread, &archived);
|
||||
}
|
||||
if (_isInstalled && archived) {
|
||||
auto flagsOverride = (set->id != Data::Stickers::CloudRecentSetId)
|
||||
? fillSetFlags(set)
|
||||
: SetFlag::Installed;
|
||||
auto removed = false;
|
||||
if (_isInstalled && (flagsOverride & SetFlag::Archived)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2176,17 +2248,14 @@ void StickersBox::Inner::rebuildAppendSet(
|
||||
raw->count = count;
|
||||
raw->title = title;
|
||||
raw->titleWidth = titleWidth;
|
||||
raw->installed = installed;
|
||||
raw->official = official;
|
||||
raw->unread = unread;
|
||||
raw->archived = archived;
|
||||
raw->flagsOverride = flagsOverride;
|
||||
raw->removed = removed;
|
||||
raw->pixw = pixw;
|
||||
raw->pixh = pixh;
|
||||
raw->yadd = {};
|
||||
auto oldStickerMedia = std::move(raw->stickerMedia);
|
||||
auto oldThumbnailMedia = std::move(raw->thumbnailMedia);
|
||||
raw->stickerMedia = sticker->activeMediaView();
|
||||
raw->stickerMedia = sticker ? sticker->activeMediaView() : nullptr;
|
||||
raw->thumbnailMedia = set->activeThumbnailView();
|
||||
if (raw->thumbnailMedia != oldThumbnailMedia
|
||||
|| (!raw->thumbnailMedia && raw->stickerMedia != oldStickerMedia)) {
|
||||
@@ -2200,10 +2269,7 @@ void StickersBox::Inner::rebuildAppendSet(
|
||||
count,
|
||||
title,
|
||||
titleWidth,
|
||||
installed,
|
||||
official,
|
||||
unread,
|
||||
archived,
|
||||
flagsOverride,
|
||||
removed,
|
||||
pixw,
|
||||
pixh));
|
||||
@@ -2289,20 +2355,12 @@ QString StickersBox::Inner::fillSetTitle(
|
||||
return result;
|
||||
}
|
||||
|
||||
void StickersBox::Inner::fillSetFlags(
|
||||
not_null<StickersSet*> set,
|
||||
bool *outInstalled,
|
||||
bool *outOfficial,
|
||||
bool *outUnread,
|
||||
bool *outArchived) {
|
||||
*outInstalled = (set->flags & SetFlag::Installed);
|
||||
*outOfficial = (set->flags & SetFlag::Official);
|
||||
*outArchived = (set->flags & SetFlag::Archived);
|
||||
if (_section == Section::Featured) {
|
||||
*outUnread = (set->flags & SetFlag::Unread);
|
||||
} else {
|
||||
*outUnread = false;
|
||||
}
|
||||
Data::StickersSetFlags StickersBox::Inner::fillSetFlags(
|
||||
not_null<StickersSet*> set) const {
|
||||
const auto result = set->flags;
|
||||
return (_section == Section::Featured)
|
||||
? result
|
||||
: (result & ~SetFlag::Unread);
|
||||
}
|
||||
|
||||
template <typename Check>
|
||||
@@ -2319,7 +2377,7 @@ StickersSetsOrder StickersBox::Inner::collectSets(Check check) const {
|
||||
|
||||
StickersSetsOrder StickersBox::Inner::getOrder() const {
|
||||
return collectSets([](Row *row) {
|
||||
return !row->archived && !row->removed && !row->isRecentSet();
|
||||
return !row->isArchived() && !row->removed && !row->isRecentSet();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2394,7 +2452,7 @@ void StickersBox::Inner::readVisibleSets() {
|
||||
int rowTo = ceilclamp(itemsVisibleBottom, _rowHeight, 0, _rows.size());
|
||||
for (int i = rowFrom; i < rowTo; ++i) {
|
||||
const auto row = _rows[i].get();
|
||||
if (!row->unread) {
|
||||
if (!row->isUnread()) {
|
||||
continue;
|
||||
}
|
||||
if ((i * _rowHeight < itemsVisibleTop)
|
||||
|
||||
@@ -411,49 +411,45 @@ callBarSignalBars: CallSignalBars(callPanelSignalBars) {
|
||||
color: callBarFg;
|
||||
}
|
||||
|
||||
callTitleButton: IconButton {
|
||||
width: 34px;
|
||||
height: 30px;
|
||||
iconPosition: point(0px, 0px);
|
||||
}
|
||||
callTitleButton: windowTitleButton;
|
||||
callTitleMinimizeIcon: icon {
|
||||
{ "calls/calls_minimize_shadow", windowShadowFg },
|
||||
{ "calls/calls_minimize_main", callNameFg },
|
||||
{ "title_shadow_minimize", windowShadowFg },
|
||||
{ "title_button_minimize", callNameFg },
|
||||
};
|
||||
callTitleMinimizeIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_minimize_shadow", windowShadowFg },
|
||||
{ "calls/calls_minimize_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_minimize", windowShadowFg },
|
||||
{ "title_button_minimize", callNameFg },
|
||||
};
|
||||
callTitleMaximizeIcon: icon {
|
||||
{ "calls/calls_maximize_shadow", windowShadowFg },
|
||||
{ "calls/calls_maximize_main", callNameFg },
|
||||
{ "title_shadow_maximize", windowShadowFg },
|
||||
{ "title_button_maximize", callNameFg },
|
||||
};
|
||||
callTitleMaximizeIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_maximize_shadow", windowShadowFg },
|
||||
{ "calls/calls_maximize_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_maximize", windowShadowFg },
|
||||
{ "title_button_maximize", callNameFg },
|
||||
};
|
||||
callTitleRestoreIcon: icon {
|
||||
{ "calls/calls_restore_shadow", windowShadowFg },
|
||||
{ "calls/calls_restore_main", callNameFg },
|
||||
{ "title_shadow_restore", windowShadowFg },
|
||||
{ "title_button_restore", callNameFg },
|
||||
};
|
||||
callTitleRestoreIconOver: icon {
|
||||
{ size(34px, 30px), callBgButton },
|
||||
{ size(34px, 30px), callMuteRipple },
|
||||
{ "calls/calls_restore_shadow", windowShadowFg },
|
||||
{ "calls/calls_restore_main", callNameFg },
|
||||
{ windowTitleButtonSize, callBgButton },
|
||||
{ windowTitleButtonSize, callMuteRipple },
|
||||
{ "title_shadow_restore", windowShadowFg },
|
||||
{ "title_button_restore", callNameFg },
|
||||
};
|
||||
callTitleCloseIcon: icon {
|
||||
{ "calls/calls_close_shadow", windowShadowFg },
|
||||
{ "calls/calls_close_main", callNameFg },
|
||||
{ "title_shadow_close", windowShadowFg },
|
||||
{ "title_button_close", callNameFg },
|
||||
};
|
||||
callTitleCloseIconOver: icon {
|
||||
{ size(34px, 30px), titleButtonCloseBgOver },
|
||||
{ "calls/calls_close_shadow", windowShadowFg },
|
||||
{ "calls/calls_close_main", titleButtonCloseFgOver },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgOver },
|
||||
{ "title_shadow_close", windowShadowFg },
|
||||
{ "title_button_close", titleButtonCloseFgOver },
|
||||
};
|
||||
callTitle: WindowTitle(defaultWindowTitle) {
|
||||
height: 0px;
|
||||
@@ -1055,37 +1051,37 @@ groupCallDelaySlider: MediaSlider(defaultContinuousSlider) {
|
||||
groupCallDelayMargin: margins(22px, 5px, 20px, 10px);
|
||||
|
||||
groupCallTitleButton: IconButton {
|
||||
width: 24px;
|
||||
height: 21px;
|
||||
width: windowTitleButtonWidth;
|
||||
height: windowTitleHeight;
|
||||
iconPosition: point(0px, 0px);
|
||||
}
|
||||
groupCallTitleMinimizeIcon: icon {
|
||||
{ "title_button_minimize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_minimize", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleMinimizeIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_minimize", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_minimize", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleMaximizeIcon: icon {
|
||||
{ "title_button_maximize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_maximize", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleMaximizeIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_maximize", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_maximize", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleRestoreIcon: icon {
|
||||
{ "title_button_restore", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_restore", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleRestoreIconOver: icon {
|
||||
{ size(24px, 21px), groupCallMembersBgOver },
|
||||
{ "title_button_restore", groupCallMembersFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, groupCallMembersBgOver },
|
||||
{ "title_button_restore", groupCallMembersFg },
|
||||
};
|
||||
groupCallTitleCloseIcon: icon {
|
||||
{ "title_button_close", groupCallMemberNotJoinedStatus, point(4px, 4px) },
|
||||
{ "title_button_close", groupCallMemberNotJoinedStatus },
|
||||
};
|
||||
groupCallTitleCloseIconOver: icon {
|
||||
{ size(24px, 21px), titleButtonCloseBgOver },
|
||||
{ "title_button_close", titleButtonCloseFgOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgOver },
|
||||
{ "title_button_close", titleButtonCloseFgOver },
|
||||
};
|
||||
groupCallTitle: WindowTitle(defaultWindowTitle) {
|
||||
height: 0px;
|
||||
@@ -1199,7 +1195,7 @@ desktopCaptureSourceSkips: size(2px, 10px);
|
||||
desktopCaptureSourceTitle: WindowTitle(groupCallTitle) {
|
||||
bg: groupCallMembersBgOver;
|
||||
bgActive: groupCallMembersBgOver;
|
||||
height: 21px;
|
||||
height: windowTitleHeight;
|
||||
}
|
||||
desktopCapturePadding: margins(7px, 7px, 7px, 33px);
|
||||
desktopCaptureLabelBottom: 7px;
|
||||
|
||||
@@ -162,14 +162,13 @@ Call::Call(
|
||||
, _user(user)
|
||||
, _api(&_user->session().mtp())
|
||||
, _type(type)
|
||||
, _discardByTimeoutTimer([=] { hangup(); })
|
||||
, _videoIncoming(
|
||||
std::make_unique<Webrtc::VideoTrack>(
|
||||
StartVideoState(video)))
|
||||
, _videoOutgoing(
|
||||
std::make_unique<Webrtc::VideoTrack>(
|
||||
StartVideoState(video))) {
|
||||
_discardByTimeoutTimer.setCallback([=] { hangup(); });
|
||||
|
||||
if (_type == Type::Outgoing) {
|
||||
setState(State::Requesting);
|
||||
} else {
|
||||
|
||||
@@ -32,7 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/toast/toast.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "app.h" // App::quitting
|
||||
|
||||
#include <tgcalls/VideoCaptureInterface.h>
|
||||
#include <tgcalls/StaticThreads.h>
|
||||
@@ -247,7 +246,7 @@ void Instance::destroyCall(not_null<Call*> call) {
|
||||
_currentCallChanges.fire(nullptr);
|
||||
taken.reset();
|
||||
|
||||
if (App::quitting()) {
|
||||
if (Core::Quitting()) {
|
||||
LOG(("Calls::Instance doesn't prevent quit any more."));
|
||||
}
|
||||
Core::App().quitPreventFinished();
|
||||
@@ -285,7 +284,7 @@ void Instance::destroyGroupCall(not_null<GroupCall*> call) {
|
||||
_currentGroupCallChanges.fire(nullptr);
|
||||
taken.reset();
|
||||
|
||||
if (App::quitting()) {
|
||||
if (Core::Quitting()) {
|
||||
LOG(("Calls::Instance doesn't prevent quit any more."));
|
||||
}
|
||||
Core::App().quitPreventFinished();
|
||||
|
||||
@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/power_save_blocker.h"
|
||||
#include "window/main_window.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
@@ -61,8 +62,8 @@ Panel::Panel(not_null<Call*> call)
|
||||
, _user(call->user())
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
widget(),
|
||||
, _controls(Ui::Platform::SetupSeparateTitleControls(
|
||||
window(),
|
||||
st::callTitle,
|
||||
[=](bool maximized) { toggleFullScreen(maximized); }))
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -144,7 +145,7 @@ void Panel::initWindow() {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#ifndef Q_OS_MAC
|
||||
if (_controls->geometry().contains(widgetPoint)) {
|
||||
if (_controls->controls.geometry().contains(widgetPoint)) {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#endif // !Q_OS_MAC
|
||||
@@ -357,6 +358,7 @@ void Panel::reinitWithCall(Call *call) {
|
||||
if (!_call) {
|
||||
_incoming = nullptr;
|
||||
_outgoingVideoBubble = nullptr;
|
||||
_powerSaveBlocker = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -497,6 +499,11 @@ void Panel::reinitWithCall(Call *call) {
|
||||
_camera->raise();
|
||||
_mute->raise();
|
||||
|
||||
_powerSaveBlocker = std::make_unique<base::PowerSaveBlocker>(
|
||||
base::PowerSaveBlockType::PreventDisplaySleep,
|
||||
u"Video call is active"_q,
|
||||
window()->windowHandle());
|
||||
|
||||
_incoming->widget()->lower();
|
||||
}
|
||||
|
||||
@@ -550,7 +557,7 @@ void Panel::initLayout() {
|
||||
}, widget()->lifetime());
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
_controls->raise();
|
||||
_controls->wrap.raise();
|
||||
#endif // !Q_OS_MAC
|
||||
}
|
||||
|
||||
@@ -628,7 +635,7 @@ void Panel::updateControlsGeometry() {
|
||||
}
|
||||
if (_fingerprint) {
|
||||
#ifndef Q_OS_MAC
|
||||
const auto controlsGeometry = _controls->geometry();
|
||||
const auto controlsGeometry = _controls->controls.geometry();
|
||||
const auto halfWidth = widget()->width() / 2;
|
||||
const auto minLeft = (controlsGeometry.center().x() < halfWidth)
|
||||
? (controlsGeometry.width() + st::callFingerprintTop)
|
||||
@@ -804,6 +811,10 @@ void Panel::stateChanged(State state) {
|
||||
&& (state != State::EndedByOtherDevice)
|
||||
&& (state != State::FailedHangingUp)
|
||||
&& (state != State::Failed)) {
|
||||
if (state == State::Busy) {
|
||||
_powerSaveBlocker = nullptr;
|
||||
}
|
||||
|
||||
auto toggleButton = [&](auto &&button, bool visible) {
|
||||
button->toggle(
|
||||
visible,
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "calls/calls_call.h"
|
||||
#include "calls/group/ui/desktop_capture_choose_source.h"
|
||||
#include "ui/effects/animations.h"
|
||||
@@ -18,6 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class Image;
|
||||
|
||||
namespace base {
|
||||
class PowerSaveBlocker;
|
||||
} // namespace base
|
||||
|
||||
namespace Data {
|
||||
class PhotoMedia;
|
||||
class CloudImageView;
|
||||
@@ -37,7 +42,7 @@ namespace GL {
|
||||
enum class Backend;
|
||||
} // namespace GL
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
struct SeparateTitleControls;
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
||||
@@ -126,9 +131,11 @@ private:
|
||||
std::unique_ptr<Incoming> _incoming;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
||||
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
|
||||
#endif // !Q_OS_MAC
|
||||
|
||||
std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker;
|
||||
|
||||
QSize _incomingFrameSize;
|
||||
|
||||
rpl::lifetime _callLifetime;
|
||||
|
||||
@@ -165,8 +165,11 @@ void Userpic::refreshPhoto() {
|
||||
void Userpic::createCache(Image *image) {
|
||||
const auto size = this->size();
|
||||
const auto real = size * cIntRetinaFactor();
|
||||
auto options = Images::Option::Smooth | Images::Option::Circled;
|
||||
// _useTransparency ? (Images::Option::RoundedLarge | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::Smooth) : Images::Option::None;
|
||||
//_useTransparency
|
||||
// ? (Images::Option::RoundLarge
|
||||
// | Images::Option::RoundSkipBottomLeft
|
||||
// | Images::Option::RoundSkipBottomRight)
|
||||
// : Images::Option::None;
|
||||
if (image) {
|
||||
auto width = image->width();
|
||||
auto height = image->height();
|
||||
@@ -178,14 +181,16 @@ void Userpic::createCache(Image *image) {
|
||||
width = real;
|
||||
}
|
||||
_userPhoto = image->pixNoCache(
|
||||
width,
|
||||
height,
|
||||
options,
|
||||
size,
|
||||
size);
|
||||
{ width, height },
|
||||
{
|
||||
.options = Images::Option::RoundCircle,
|
||||
.outer = { size, size },
|
||||
});
|
||||
_userPhoto.setDevicePixelRatio(cRetinaFactor());
|
||||
} else {
|
||||
auto filled = QImage(QSize(real, real), QImage::Format_ARGB32_Premultiplied);
|
||||
auto filled = QImage(
|
||||
QSize(real, real),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
filled.setDevicePixelRatio(cRetinaFactor());
|
||||
filled.fill(Qt::transparent);
|
||||
{
|
||||
@@ -195,7 +200,10 @@ void Userpic::createCache(Image *image) {
|
||||
_peer->name
|
||||
).paint(p, 0, 0, size, size);
|
||||
}
|
||||
//Images::prepareRound(filled, ImageRoundRadius::Large, RectPart::TopLeft | RectPart::TopRight);
|
||||
//_userPhoto = Images::PixmapFast(Images::Round(
|
||||
// std::move(filled),
|
||||
// ImageRoundRadius::Large,
|
||||
// RectPart::TopLeft | RectPart::TopRight));
|
||||
_userPhoto = Images::PixmapFast(std::move(filled));
|
||||
}
|
||||
|
||||
|
||||
@@ -169,12 +169,12 @@ void VideoBubble::prepareFrame() {
|
||||
for (; from != till; from += fromPerLine, to += toPerLine) {
|
||||
memcpy(to, from, lineSize);
|
||||
}
|
||||
Images::prepareRound(
|
||||
_frame,
|
||||
_frame = Images::Round(
|
||||
std::move(_frame),
|
||||
ImageRoundRadius::Large,
|
||||
RectPart::AllCorners,
|
||||
QRect(QPoint(), size));
|
||||
_frame = std::move(_frame).mirrored(true, false);
|
||||
QRect(QPoint(), size)
|
||||
).mirrored(true, false);
|
||||
}
|
||||
|
||||
void VideoBubble::setState(Webrtc::VideoState state) {
|
||||
|
||||
@@ -2396,6 +2396,7 @@ bool GroupCall::tryCreateScreencast() {
|
||||
tgcalls::GroupInstanceDescriptor descriptor = {
|
||||
.threads = tgcalls::StaticThreads::getThreads(),
|
||||
.config = tgcalls::GroupConfig{
|
||||
.need_log = Logs::DebugEnabled(),
|
||||
},
|
||||
.networkStateUpdated = [=](tgcalls::GroupNetworkState networkState) {
|
||||
crl::on_main(weak, [=] {
|
||||
|
||||
@@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/power_save_blocker.h"
|
||||
#include "apiwrap.h" // api().kick.
|
||||
#include "api/api_chat_participants.h" // api().kick.
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
@@ -88,10 +89,14 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||
, _peer(call->peer())
|
||||
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
widget(),
|
||||
, _controls(Ui::Platform::SetupSeparateTitleControls(
|
||||
window(),
|
||||
st::groupCallTitle))
|
||||
#endif // !Q_OS_MAC
|
||||
, _powerSaveBlocker(std::make_unique<base::PowerSaveBlocker>(
|
||||
base::PowerSaveBlockType::PreventDisplaySleep,
|
||||
u"Video chat is active"_q,
|
||||
window()->windowHandle()))
|
||||
, _viewport(
|
||||
std::make_unique<Viewport>(widget(), PanelMode::Wide, _window.backend()))
|
||||
, _mute(std::make_unique<Ui::CallMuteButton>(
|
||||
@@ -302,9 +307,9 @@ void Panel::initWidget() {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
// title geometry depends on _controls->geometry,
|
||||
// some geometries depends on _controls->controls.geometry,
|
||||
// which is not updated here yet.
|
||||
crl::on_main(widget(), [=] { refreshTitle(); });
|
||||
crl::on_main(widget(), [=] { updateControlsGeometry(); });
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
@@ -1368,7 +1373,7 @@ void Panel::initLayout() {
|
||||
initGeometry();
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
_controls->raise();
|
||||
_controls->wrap.raise();
|
||||
|
||||
Ui::Platform::TitleControlsLayoutChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -1413,7 +1418,7 @@ QRect Panel::computeTitleRect() const {
|
||||
#ifdef Q_OS_MAC
|
||||
return QRect(70, 0, width - remove - 70, 28);
|
||||
#else // Q_OS_MAC
|
||||
const auto controls = _controls->geometry();
|
||||
const auto controls = _controls->controls.geometry();
|
||||
const auto right = controls.x() + controls.width() + skip;
|
||||
return (controls.center().x() < width / 2)
|
||||
? QRect(right, 0, width - right - remove, controls.height())
|
||||
@@ -1884,7 +1889,8 @@ void Panel::updateControlsGeometry() {
|
||||
#ifdef Q_OS_MAC
|
||||
const auto controlsOnTheLeft = true;
|
||||
#else // Q_OS_MAC
|
||||
const auto controlsOnTheLeft = _controls->geometry().center().x()
|
||||
const auto center = _controls->controls.geometry().center();
|
||||
const auto controlsOnTheLeft = center.x()
|
||||
< widget()->width() / 2;
|
||||
#endif // Q_OS_MAC
|
||||
const auto menux = st::groupCallMenuTogglePosition.x();
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/flags.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
@@ -21,6 +22,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class Image;
|
||||
|
||||
namespace base {
|
||||
class PowerSaveBlocker;
|
||||
} // namespace base
|
||||
|
||||
namespace Data {
|
||||
class PhotoMedia;
|
||||
class CloudImageView;
|
||||
@@ -51,7 +56,7 @@ namespace Toast {
|
||||
class Instance;
|
||||
} // namespace Toast
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
struct SeparateTitleControls;
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
||||
@@ -194,9 +199,11 @@ private:
|
||||
rpl::variable<PanelMode> _mode;
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
||||
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
|
||||
#endif // !Q_OS_MAC
|
||||
|
||||
const std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker;
|
||||
|
||||
rpl::lifetime _callLifetime;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _title = { nullptr };
|
||||
|
||||
@@ -129,7 +129,8 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||
auto submitCallback = [=](
|
||||
std::vector<not_null<PeerData*>> &&result,
|
||||
TextWithTags &&comment,
|
||||
Api::SendOptions options) {
|
||||
Api::SendOptions options,
|
||||
Data::ForwardOptions) {
|
||||
if (*sending || result.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
@@ -37,7 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/random.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -102,8 +103,13 @@ private:
|
||||
|
||||
QSize stickerBoundingBox() const;
|
||||
void setupLottie(StickerSuggestion &suggestion);
|
||||
void setupWebm(StickerSuggestion &suggestion);
|
||||
void repaintSticker(not_null<DocumentData*> document);
|
||||
void repaintStickerAtIndex(int index);
|
||||
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
|
||||
void clipCallback(
|
||||
Media::Clip::Notification notification,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<FieldAutocomplete*> _parent;
|
||||
@@ -141,6 +147,13 @@ private:
|
||||
|
||||
};
|
||||
|
||||
struct FieldAutocomplete::StickerSuggestion {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
Media::Clip::ReaderPointer webm;
|
||||
};
|
||||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
@@ -340,7 +353,7 @@ FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
for (auto &suggestion : _srows) {
|
||||
if (!suggestion.animated) {
|
||||
if (!suggestion.lottie && !suggestion.webm) {
|
||||
continue;
|
||||
}
|
||||
const auto i = ranges::find(
|
||||
@@ -348,7 +361,8 @@ FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
||||
suggestion.document,
|
||||
&StickerSuggestion::document);
|
||||
if (i != end(result)) {
|
||||
i->animated = std::move(suggestion.animated);
|
||||
i->lottie = std::move(suggestion.lottie);
|
||||
i->webm = std::move(suggestion.webm);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -812,6 +826,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
width(),
|
||||
std::min(st::msgMaxWidth / 2, width() / 2));
|
||||
|
||||
const auto now = crl::now();
|
||||
int32 rows = rowscount(_srows->size(), _stickersPerRow);
|
||||
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
|
||||
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
|
||||
@@ -825,12 +840,15 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
auto &sticker = (*_srows)[index];
|
||||
const auto document = sticker.document;
|
||||
const auto &media = sticker.documentMedia;
|
||||
if (!document->sticker()) continue;
|
||||
const auto info = document->sticker();
|
||||
if (!info) continue;
|
||||
|
||||
if (document->sticker()->animated
|
||||
&& !sticker.animated
|
||||
&& media->loaded()) {
|
||||
setupLottie(sticker);
|
||||
if (media->loaded()) {
|
||||
if (info->isLottie() && !sticker.lottie) {
|
||||
setupLottie(sticker);
|
||||
} else if (info->isWebm() && !sticker.webm) {
|
||||
setupWebm(sticker);
|
||||
}
|
||||
}
|
||||
|
||||
QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height());
|
||||
@@ -841,46 +859,34 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
|
||||
media->checkStickerSmall();
|
||||
auto w = 1;
|
||||
auto h = 1;
|
||||
if (sticker.animated && !document->dimensions.isEmpty()) {
|
||||
const auto request = Lottie::FrameRequest{ stickerBoundingBox() * cIntRetinaFactor() };
|
||||
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
|
||||
w = std::max(size.width(), 1);
|
||||
h = std::max(size.height(), 1);
|
||||
} else {
|
||||
const auto coef = std::min(
|
||||
std::min(
|
||||
(st::stickerPanSize.width() - st::roundRadiusSmall * 2) / float64(document->dimensions.width()),
|
||||
(st::stickerPanSize.height() - st::roundRadiusSmall * 2) / float64(document->dimensions.height())),
|
||||
1.);
|
||||
w = std::max(qRound(coef * document->dimensions.width()), 1);
|
||||
h = std::max(qRound(coef * document->dimensions.height()), 1);
|
||||
}
|
||||
|
||||
if (sticker.animated && sticker.animated->ready()) {
|
||||
const auto frame = sticker.animated->frame();
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
const auto ppos = pos + QPoint(
|
||||
(st::stickerPanSize.width() - size.width()) / 2,
|
||||
(st::stickerPanSize.height() - size.height()) / 2);
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
document,
|
||||
stickerBoundingBox());
|
||||
const auto ppos = pos + QPoint(
|
||||
(st::stickerPanSize.width() - size.width()) / 2,
|
||||
(st::stickerPanSize.height() - size.height()) / 2);
|
||||
if (sticker.lottie && sticker.lottie->ready()) {
|
||||
const auto frame = sticker.lottie->frame();
|
||||
p.drawImage(
|
||||
QRect(ppos, size),
|
||||
QRect(ppos, frame.size() / cIntRetinaFactor()),
|
||||
frame);
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
if (!paused) {
|
||||
sticker.animated->markFrameShown();
|
||||
sticker.lottie->markFrameShown();
|
||||
}
|
||||
} else if (sticker.webm && sticker.webm->started()) {
|
||||
p.drawPixmap(ppos, sticker.webm->current({
|
||||
.frame = size,
|
||||
.keepAlpha = true,
|
||||
}, paused ? 0 : now));
|
||||
} else if (const auto image = media->getStickerSmall()) {
|
||||
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
|
||||
p.drawPixmapLeft(ppos, width(), image->pix(w, h));
|
||||
p.drawPixmapLeft(ppos, width(), image->pix(size));
|
||||
} else {
|
||||
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, QSize(w, h)),
|
||||
QRect(ppos, size),
|
||||
_pathGradient.get());
|
||||
}
|
||||
}
|
||||
@@ -1271,19 +1277,30 @@ auto FieldAutocomplete::Inner::getLottieRenderer()
|
||||
|
||||
void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
|
||||
const auto document = suggestion.document;
|
||||
suggestion.animated = ChatHelpers::LottiePlayerFromDocument(
|
||||
suggestion.lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
suggestion.documentMedia.get(),
|
||||
ChatHelpers::StickerLottieSize::InlineResults,
|
||||
stickerBoundingBox() * cIntRetinaFactor(),
|
||||
Lottie::Quality::Default,
|
||||
getLottieRenderer());
|
||||
|
||||
suggestion.animated->updates(
|
||||
suggestion.lottie->updates(
|
||||
) | rpl::start_with_next([=] {
|
||||
repaintSticker(document);
|
||||
}, _stickersLifetime);
|
||||
}
|
||||
|
||||
void FieldAutocomplete::Inner::setupWebm(StickerSuggestion &suggestion) {
|
||||
const auto document = suggestion.document;
|
||||
auto callback = [=](Media::Clip::Notification notification) {
|
||||
clipCallback(notification, document);
|
||||
};
|
||||
suggestion.webm = Media::Clip::MakeReader(
|
||||
suggestion.documentMedia->owner()->location(),
|
||||
suggestion.documentMedia->bytes(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
QSize FieldAutocomplete::Inner::stickerBoundingBox() const {
|
||||
return QSize(
|
||||
st::stickerPanSize.width() - st::roundRadiusSmall * 2,
|
||||
@@ -1299,7 +1316,10 @@ void FieldAutocomplete::Inner::repaintSticker(
|
||||
if (i == end(*_srows)) {
|
||||
return;
|
||||
}
|
||||
const auto index = (i - begin(*_srows));
|
||||
repaintStickerAtIndex(i - begin(*_srows));
|
||||
}
|
||||
|
||||
void FieldAutocomplete::Inner::repaintStickerAtIndex(int index) {
|
||||
const auto row = (index / _stickersPerRow);
|
||||
const auto col = (index % _stickersPerRow);
|
||||
update(
|
||||
@@ -1309,6 +1329,36 @@ void FieldAutocomplete::Inner::repaintSticker(
|
||||
st::stickerPanSize.height());
|
||||
}
|
||||
|
||||
void FieldAutocomplete::Inner::clipCallback(
|
||||
Media::Clip::Notification notification,
|
||||
not_null<DocumentData*> document) {
|
||||
const auto i = ranges::find(
|
||||
*_srows,
|
||||
document,
|
||||
&StickerSuggestion::document);
|
||||
if (i == end(*_srows)) {
|
||||
return;
|
||||
}
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case Notification::Reinit: {
|
||||
if (!i->webm) {
|
||||
break;
|
||||
} else if (i->webm->state() == State::Error) {
|
||||
i->webm.setBad();
|
||||
} else if (i->webm->ready() && !i->webm->started()) {
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
i->document,
|
||||
stickerBoundingBox());
|
||||
i->webm->start({ .frame = size, .keepAlpha = true });
|
||||
}
|
||||
} break;
|
||||
|
||||
case Notification::Repaint: break;
|
||||
}
|
||||
repaintStickerAtIndex(i - begin(*_srows));
|
||||
}
|
||||
|
||||
void FieldAutocomplete::Inner::selectByMouse(QPoint globalPosition) {
|
||||
_mouseSelection = true;
|
||||
_lastMousePosition = globalPosition;
|
||||
|
||||
@@ -129,12 +129,7 @@ protected:
|
||||
private:
|
||||
class Inner;
|
||||
friend class Inner;
|
||||
|
||||
struct StickerSuggestion {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
std::unique_ptr<Lottie::SinglePlayer> animated;
|
||||
};
|
||||
struct StickerSuggestion;
|
||||
|
||||
struct MentionRow {
|
||||
not_null<UserData*> user;
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "api/api_toggling_media.h" // Api::ToggleSavedGif
|
||||
#include "base/const_string.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -45,6 +46,8 @@ namespace {
|
||||
constexpr auto kSearchRequestDelay = 400;
|
||||
constexpr auto kInlineItemsMaxPerRow = 5;
|
||||
constexpr auto kSearchBotUsername = "gif"_cs;
|
||||
constexpr auto kMinRepaintDelay = crl::time(33);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -188,14 +191,14 @@ GifsListWidget::GifsListWidget(
|
||||
|
||||
controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
updateInlineItems();
|
||||
}, lifetime());
|
||||
|
||||
controller->gifPauseLevelChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs)) {
|
||||
update();
|
||||
updateInlineItems();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
@@ -235,10 +238,11 @@ object_ptr<TabbedSelector::InnerFooter> GifsListWidget::createFooter() {
|
||||
void GifsListWidget::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
auto top = getVisibleTop();
|
||||
const auto top = getVisibleTop();
|
||||
Inner::visibleTopBottomUpdated(visibleTop, visibleBottom);
|
||||
if (top != getVisibleTop()) {
|
||||
_lastScrolled = crl::now();
|
||||
_lastScrolledAt = crl::now();
|
||||
update();
|
||||
}
|
||||
checkLoadMore();
|
||||
}
|
||||
@@ -437,8 +441,7 @@ void GifsListWidget::selectInlineResult(
|
||||
return;
|
||||
}
|
||||
|
||||
forceSend |= (QGuiApplication::keyboardModifiers()
|
||||
== Qt::ControlModifier);
|
||||
forceSend |= base::IsCtrlPressed();
|
||||
if (const auto photo = item->getPhoto()) {
|
||||
using Data::PhotoSize;
|
||||
const auto media = photo->activeMediaView();
|
||||
@@ -498,7 +501,7 @@ void GifsListWidget::clearSelection() {
|
||||
setCursor(style::cur_default);
|
||||
}
|
||||
_selected = _pressed = -1;
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
TabbedSelector::InnerFooter *GifsListWidget::getFooter() const {
|
||||
@@ -544,7 +547,7 @@ void GifsListWidget::refreshSavedGifs() {
|
||||
deleteUnusedGifLayouts();
|
||||
|
||||
resizeToWidth(width());
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
if (isVisible()) {
|
||||
@@ -672,7 +675,7 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
|
||||
}
|
||||
|
||||
resizeToWidth(width());
|
||||
update();
|
||||
repaintItems();
|
||||
|
||||
_lastMousePos = QCursor::pos();
|
||||
updateSelected();
|
||||
@@ -711,16 +714,13 @@ void GifsListWidget::inlineItemLayoutChanged(const InlineBots::Layout::ItemBase
|
||||
}
|
||||
}
|
||||
|
||||
void GifsListWidget::inlineItemRepaint(const InlineBots::Layout::ItemBase *layout) {
|
||||
auto ms = crl::now();
|
||||
if (_lastScrolled + 100 <= ms) {
|
||||
update();
|
||||
} else {
|
||||
_updateInlineItems.callOnce(_lastScrolled + 100 - ms);
|
||||
}
|
||||
void GifsListWidget::inlineItemRepaint(
|
||||
const InlineBots::Layout::ItemBase *layout) {
|
||||
updateInlineItems();
|
||||
}
|
||||
|
||||
bool GifsListWidget::inlineItemVisible(const InlineBots::Layout::ItemBase *layout) {
|
||||
bool GifsListWidget::inlineItemVisible(
|
||||
const InlineBots::Layout::ItemBase *layout) {
|
||||
auto position = layout->position();
|
||||
if (position < 0 || !isVisible()) {
|
||||
return false;
|
||||
@@ -930,12 +930,22 @@ void GifsListWidget::showPreview() {
|
||||
}
|
||||
|
||||
void GifsListWidget::updateInlineItems() {
|
||||
auto ms = crl::now();
|
||||
if (_lastScrolled + 100 <= ms) {
|
||||
update();
|
||||
} else {
|
||||
_updateInlineItems.callOnce(_lastScrolled + 100 - ms);
|
||||
const auto now = crl::now();
|
||||
|
||||
const auto delay = std::max(
|
||||
_lastScrolledAt + kMinAfterScrollDelay - now,
|
||||
_lastUpdatedAt + kMinRepaintDelay - now);
|
||||
if (delay <= 0) {
|
||||
repaintItems(now);
|
||||
} else if (!_updateInlineItems.isActive()
|
||||
|| _updateInlineItems.remainingTime() > kMinRepaintDelay) {
|
||||
_updateInlineItems.callOnce(std::max(delay, kMinRepaintDelay));
|
||||
}
|
||||
}
|
||||
|
||||
void GifsListWidget::repaintItems(crl::time now) {
|
||||
_lastUpdatedAt = now ? now : crl::now();
|
||||
update();
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -133,12 +133,14 @@ private:
|
||||
void paintInlineItems(Painter &p, QRect clip);
|
||||
|
||||
void updateInlineItems();
|
||||
void repaintItems(crl::time now = 0);
|
||||
void showPreview();
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
Section _section = Section::Gifs;
|
||||
crl::time _lastScrolled = 0;
|
||||
crl::time _lastScrolledAt = 0;
|
||||
crl::time _lastUpdatedAt = 0;
|
||||
base::Timer _updateInlineItems;
|
||||
bool _inlineWithThumb = false;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
#include <QtCore/QStack>
|
||||
|
||||
@@ -15,7 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
@@ -143,9 +146,11 @@ void SetupMenuAndShortcuts(
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
void SetupUnreadMentionsMenu(
|
||||
void SetupReadAllMenu(
|
||||
not_null<Ui::RpWidget*> button,
|
||||
Fn<PeerData*()> currentPeer) {
|
||||
Fn<PeerData*()> currentPeer,
|
||||
const QString &text,
|
||||
Fn<void(not_null<PeerData*>, Fn<void()>)> sendReadRequest) {
|
||||
struct State {
|
||||
base::unique_qptr<Ui::PopupMenu> menu;
|
||||
base::flat_set<not_null<PeerData*>> sentForPeers;
|
||||
@@ -159,19 +164,11 @@ void SetupUnreadMentionsMenu(
|
||||
state->menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
button,
|
||||
st::popupMenuWithIcons);
|
||||
const auto text = tr::lng_context_mark_read_mentions_all(tr::now);
|
||||
state->menu->addAction(text, [=] {
|
||||
if (!state->sentForPeers.emplace(peer).second) {
|
||||
return;
|
||||
}
|
||||
peer->session().api().request(MTPmessages_ReadMentions(
|
||||
peer->input
|
||||
)).done([=](const MTPmessages_AffectedHistory &result) {
|
||||
state->sentForPeers.remove(peer);
|
||||
peer->session().api().applyAffectedHistory(peer, result);
|
||||
}).fail([=] {
|
||||
state->sentForPeers.remove(peer);
|
||||
}).send();
|
||||
sendReadRequest(peer, [=] { state->sentForPeers.remove(peer); });
|
||||
}, &st::menuIconMarkRead);
|
||||
state->menu->popup(QCursor::pos());
|
||||
};
|
||||
@@ -183,7 +180,38 @@ void SetupUnreadMentionsMenu(
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void SetupUnreadMentionsMenu(
|
||||
not_null<Ui::RpWidget*> button,
|
||||
Fn<PeerData*()> currentPeer) {
|
||||
const auto text = tr::lng_context_mark_read_mentions_all(tr::now);
|
||||
const auto sendRequest = [=](not_null<PeerData*> peer, Fn<void()> done) {
|
||||
peer->session().api().request(MTPmessages_ReadMentions(
|
||||
peer->input
|
||||
)).done([=](const MTPmessages_AffectedHistory &result) {
|
||||
done();
|
||||
peer->session().api().applyAffectedHistory(peer, result);
|
||||
peer->owner().history(peer)->unreadMentions().clear();
|
||||
}).fail(done).send();
|
||||
};
|
||||
SetupReadAllMenu(button, currentPeer, text, sendRequest);
|
||||
}
|
||||
|
||||
void SetupUnreadReactionsMenu(
|
||||
not_null<Ui::RpWidget*> button,
|
||||
Fn<PeerData*()> currentPeer) {
|
||||
const auto text = tr::lng_context_mark_read_reactions_all(tr::now);
|
||||
const auto sendRequest = [=](not_null<PeerData*> peer, Fn<void()> done) {
|
||||
peer->session().api().request(MTPmessages_ReadReactions(
|
||||
peer->input
|
||||
)).done([=](const MTPmessages_AffectedHistory &result) {
|
||||
done();
|
||||
peer->session().api().applyAffectedHistory(peer, result);
|
||||
peer->owner().history(peer)->unreadReactions().clear();
|
||||
}).fail(done).send();
|
||||
};
|
||||
SetupReadAllMenu(button, currentPeer, text, sendRequest);
|
||||
}
|
||||
|
||||
} // namespace SendMenu
|
||||
|
||||
@@ -54,4 +54,8 @@ void SetupUnreadMentionsMenu(
|
||||
not_null<Ui::RpWidget*> button,
|
||||
Fn<PeerData*()> currentPeer);
|
||||
|
||||
void SetupUnreadReactionsMenu(
|
||||
not_null<Ui::RpWidget*> button,
|
||||
Fn<PeerData*()> currentPeer);
|
||||
|
||||
} // namespace SendMenu
|
||||
|
||||
@@ -141,7 +141,7 @@ void DicePack::generateLocal(int index, const QString &name) {
|
||||
_map.emplace(index, document);
|
||||
|
||||
Ensures(document->sticker());
|
||||
Ensures(document->sticker()->animated);
|
||||
Ensures(document->sticker()->isLottie());
|
||||
}
|
||||
|
||||
DicePacks::DicePacks(not_null<Main::Session*> session) : _session(session) {
|
||||
|
||||
@@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h" // GifPauseReason.
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_toggling_media.h" // Api::ToggleFavedSticker
|
||||
#include "styles/style_chat_helpers.h"
|
||||
@@ -53,6 +54,8 @@ constexpr auto kSearchRequestDelay = 400;
|
||||
constexpr auto kRecentDisplayLimit = 20;
|
||||
constexpr auto kPreloadOfficialPages = 4;
|
||||
constexpr auto kOfficialLoadLimit = 40;
|
||||
constexpr auto kMinRepaintDelay = crl::time(33);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
|
||||
using Data::StickersSet;
|
||||
using Data::StickersPack;
|
||||
@@ -97,6 +100,7 @@ struct StickerIcon {
|
||||
uint64 setId = 0;
|
||||
StickersSet *set = nullptr;
|
||||
mutable std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
mutable Media::Clip::ReaderPointer webm;
|
||||
mutable QPixmap savedFrame;
|
||||
DocumentData *sticker = nullptr;
|
||||
ChannelData *megagroup = nullptr;
|
||||
@@ -109,7 +113,7 @@ struct StickerIcon {
|
||||
|
||||
};
|
||||
|
||||
class StickersListWidget::Footer : public TabbedSelector::InnerFooter {
|
||||
class StickersListWidget::Footer final : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
explicit Footer(
|
||||
not_null<StickersListWidget*> parent,
|
||||
@@ -120,7 +124,7 @@ public:
|
||||
uint64 setId,
|
||||
ValidateIconAnimations animations);
|
||||
void refreshIcons(ValidateIconAnimations animations);
|
||||
bool hasOnlyFeaturedSets() const;
|
||||
[[nodiscard]] bool hasOnlyFeaturedSets() const;
|
||||
|
||||
void leaveToChildEvent(QEvent *e, QWidget *child) override;
|
||||
|
||||
@@ -130,7 +134,7 @@ public:
|
||||
|
||||
void clearHeavyData();
|
||||
|
||||
rpl::producer<> openSettingsRequests() const;
|
||||
[[nodiscard]] rpl::producer<> openSettingsRequests() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
@@ -149,22 +153,35 @@ private:
|
||||
Settings,
|
||||
};
|
||||
using OverState = std::variant<SpecialOver, int>;
|
||||
struct IconInfo {
|
||||
int index = 0;
|
||||
int left = 0;
|
||||
bool visible = false;
|
||||
};
|
||||
|
||||
void enumerateVisibleIcons(Fn<void(const StickerIcon &, int)> callback);
|
||||
void enumerateVisibleIcons(Fn<void(const IconInfo &)> callback);
|
||||
void enumerateIcons(Fn<void(const IconInfo &)> callback);
|
||||
|
||||
bool iconsAnimationCallback(crl::time now);
|
||||
void setSelectedIcon(
|
||||
int newSelected,
|
||||
ValidateIconAnimations animations);
|
||||
void validateIconLottieAnimation(const StickerIcon &icon);
|
||||
void validateIconWebmAnimation(const StickerIcon &icon);
|
||||
void validateIconAnimation(const StickerIcon &icon);
|
||||
|
||||
void refreshIconsGeometry(ValidateIconAnimations animations);
|
||||
void updateSelected();
|
||||
void updateSetIcon(uint64 setId);
|
||||
void updateSetIconAt(int left);
|
||||
void finishDragging();
|
||||
void paintStickerSettingsIcon(Painter &p) const;
|
||||
void paintSearchIcon(Painter &p) const;
|
||||
void paintSetIcon(Painter &p, const StickerIcon &icon, int x) const;
|
||||
void paintSetIcon(
|
||||
Painter &p,
|
||||
const IconInfo &info,
|
||||
crl::time now,
|
||||
bool paused) const;
|
||||
void paintSelectionBar(Painter &p) const;
|
||||
void paintLeftRightFading(Painter &p) const;
|
||||
|
||||
@@ -173,6 +190,8 @@ private:
|
||||
void resizeSearchControls();
|
||||
void scrollByWheelEvent(not_null<QWheelEvent*> e);
|
||||
|
||||
void clipCallback(Media::Clip::Notification notification, uint64 setId);
|
||||
|
||||
const not_null<StickersListWidget*> _pan;
|
||||
const bool _searchButtonVisible = true;
|
||||
|
||||
@@ -205,6 +224,16 @@ private:
|
||||
|
||||
};
|
||||
|
||||
struct StickersListWidget::Sticker {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
Lottie::Animation *lottie = nullptr;
|
||||
Media::Clip::ReaderPointer webm;
|
||||
QPixmap savedFrame;
|
||||
|
||||
void ensureMediaCreated();
|
||||
};
|
||||
|
||||
auto StickersListWidget::PrepareStickers(
|
||||
const QVector<DocumentData*> &pack)
|
||||
-> std::vector<Sticker> {
|
||||
@@ -280,6 +309,7 @@ void StickersListWidget::Footer::clearHeavyData() {
|
||||
count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
auto &icon = _icons[i];
|
||||
icon.webm = nullptr;
|
||||
icon.lottie = nullptr;
|
||||
icon.lifetime.destroy();
|
||||
icon.stickerMedia = nullptr;
|
||||
@@ -354,7 +384,7 @@ void StickersListWidget::Footer::returnFocus() {
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::enumerateVisibleIcons(
|
||||
Fn<void(const StickerIcon &, int)> callback) {
|
||||
Fn<void(const IconInfo &)> callback) {
|
||||
auto iconsX = qRound(_iconsX.current());
|
||||
auto x = _iconsLeft - (iconsX % st::stickerIconWidth);
|
||||
auto first = floorclamp(iconsX, st::stickerIconWidth, 0, _icons.size());
|
||||
@@ -364,13 +394,32 @@ void StickersListWidget::Footer::enumerateVisibleIcons(
|
||||
0,
|
||||
_icons.size());
|
||||
for (auto index = first; index != last; ++index) {
|
||||
callback(_icons[index], x);
|
||||
callback({ .index = index, .left = x, .visible = true });
|
||||
x += st::stickerIconWidth;
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::enumerateIcons(
|
||||
Fn<void(const IconInfo &)> callback) {
|
||||
auto iconsX = qRound(_iconsX.current());
|
||||
auto x = _iconsLeft - (iconsX % st::stickerIconWidth);
|
||||
auto first = floorclamp(iconsX, st::stickerIconWidth, 0, _icons.size());
|
||||
auto last = ceilclamp(
|
||||
iconsX + width(),
|
||||
st::stickerIconWidth,
|
||||
0,
|
||||
_icons.size());
|
||||
x -= first * st::stickerIconWidth;
|
||||
for (auto i = 0, count = int(_icons.size()); i != count; ++i) {
|
||||
const auto visible = (i >= first && i < last);
|
||||
callback({ .index = i, .left = x, .visible = visible });
|
||||
x += st::stickerIconWidth;
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::preloadImages() {
|
||||
enumerateVisibleIcons([](const StickerIcon &icon, int x) {
|
||||
enumerateVisibleIcons([&](const IconInfo &info) {
|
||||
const auto &icon = _icons[info.index];
|
||||
if (const auto sticker = icon.sticker) {
|
||||
Assert(icon.set != nullptr);
|
||||
if (icon.set->hasThumbnail()) {
|
||||
@@ -479,8 +528,11 @@ void StickersListWidget::Footer::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
p.setClipRect(clip);
|
||||
|
||||
enumerateVisibleIcons([&](const StickerIcon &icon, int x) {
|
||||
paintSetIcon(p, icon, x);
|
||||
const auto now = crl::now();
|
||||
const auto paused = _pan->controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
enumerateVisibleIcons([&](const IconInfo &info) {
|
||||
paintSetIcon(p, info, now, paused);
|
||||
});
|
||||
|
||||
paintSelectionBar(p);
|
||||
@@ -673,6 +725,36 @@ void StickersListWidget::Footer::scrollByWheelEvent(
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::clipCallback(
|
||||
Media::Clip::Notification notification,
|
||||
uint64 setId) {
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case Notification::Reinit: {
|
||||
enumerateIcons([&](const IconInfo &info) {
|
||||
auto &icon = _icons[info.index];
|
||||
if (icon.setId != setId || !icon.webm) {
|
||||
return;
|
||||
} else if (icon.webm->state() == State::Error) {
|
||||
icon.webm.setBad();
|
||||
} else if (!info.visible) {
|
||||
icon.webm = nullptr;
|
||||
} else if (icon.webm->ready() && !icon.webm->started()) {
|
||||
icon.webm->start({
|
||||
.frame = { icon.pixw, icon.pixh },
|
||||
.keepAlpha = true,
|
||||
});
|
||||
}
|
||||
updateSetIconAt(info.left);
|
||||
});
|
||||
} break;
|
||||
|
||||
case Notification::Repaint:
|
||||
updateSetIcon(setId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::updateSelected() {
|
||||
if (_iconDown != SpecialOver::None) {
|
||||
return;
|
||||
@@ -733,6 +815,7 @@ void StickersListWidget::Footer::refreshIcons(
|
||||
if (const auto i = indices.find(now.setId); i != end(indices)) {
|
||||
auto &was = _icons[i->second];
|
||||
if (now.sticker == was.sticker) {
|
||||
now.webm = std::move(was.webm);
|
||||
now.lottie = std::move(was.lottie);
|
||||
now.lifetime = std::move(was.lifetime);
|
||||
now.savedFrame = std::move(was.savedFrame);
|
||||
@@ -792,6 +875,7 @@ void StickersListWidget::Footer::validateIconLottieAnimation(
|
||||
if (icon.lottie
|
||||
|| !icon.sticker
|
||||
|| !HasLottieThumbnail(
|
||||
icon.set ? icon.set->flags : Data::StickersSetFlags(),
|
||||
icon.thumbnailMedia.get(),
|
||||
icon.stickerMedia.get())) {
|
||||
return;
|
||||
@@ -817,30 +901,90 @@ void StickersListWidget::Footer::validateIconLottieAnimation(
|
||||
}, icon.lifetime);
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::validateIconWebmAnimation(
|
||||
const StickerIcon &icon) {
|
||||
icon.ensureMediaCreated();
|
||||
if (icon.webm
|
||||
|| !icon.sticker
|
||||
|| !HasWebmThumbnail(
|
||||
icon.set ? icon.set->flags : Data::StickersSetFlags(),
|
||||
icon.thumbnailMedia.get(),
|
||||
icon.stickerMedia.get())) {
|
||||
return;
|
||||
}
|
||||
const auto id = icon.setId;
|
||||
auto callback = [=](Media::Clip::Notification notification) {
|
||||
clipCallback(notification, id);
|
||||
};
|
||||
icon.webm = WebmThumbnail(
|
||||
icon.thumbnailMedia.get(),
|
||||
icon.stickerMedia.get(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::validateIconAnimation(
|
||||
const StickerIcon &icon) {
|
||||
validateIconWebmAnimation(icon);
|
||||
validateIconLottieAnimation(icon);
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::updateSetIcon(uint64 setId) {
|
||||
enumerateVisibleIcons([&](const StickerIcon &icon, int x) {
|
||||
if (icon.setId != setId) {
|
||||
enumerateVisibleIcons([&](const IconInfo &info) {
|
||||
if (_icons[info.index].setId != setId) {
|
||||
return;
|
||||
}
|
||||
update(x, _iconsTop, st::stickerIconWidth, st::emojiFooterHeight);
|
||||
updateSetIconAt(info.left);
|
||||
});
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::updateSetIconAt(int left) {
|
||||
update(left, _iconsTop, st::stickerIconWidth, st::emojiFooterHeight);
|
||||
}
|
||||
|
||||
void StickersListWidget::Footer::paintSetIcon(
|
||||
Painter &p,
|
||||
const StickerIcon &icon,
|
||||
int x) const {
|
||||
const IconInfo &info,
|
||||
crl::time now,
|
||||
bool paused) const {
|
||||
const auto &icon = _icons[info.index];
|
||||
if (icon.sticker) {
|
||||
icon.ensureMediaCreated();
|
||||
const_cast<Footer*>(this)->validateIconLottieAnimation(icon);
|
||||
const_cast<Footer*>(this)->validateIconAnimation(icon);
|
||||
const auto origin = icon.sticker->stickerSetOrigin();
|
||||
const auto thumb = icon.thumbnailMedia
|
||||
? icon.thumbnailMedia->image()
|
||||
: icon.stickerMedia
|
||||
? icon.stickerMedia->thumbnail()
|
||||
: nullptr;
|
||||
if (!icon.lottie
|
||||
|| (!icon.lottie->ready() && !icon.savedFrame.isNull())) {
|
||||
const auto x = info.left + (st::stickerIconWidth - icon.pixw) / 2;
|
||||
const auto y = _iconsTop + (st::emojiFooterHeight - icon.pixh) / 2;
|
||||
if (icon.lottie && icon.lottie->ready()) {
|
||||
const auto frame = icon.lottie->frame();
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
if (icon.savedFrame.isNull()) {
|
||||
icon.savedFrame = QPixmap::fromImage(frame, Qt::ColorOnly);
|
||||
icon.savedFrame.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
p.drawImage(
|
||||
QRect(
|
||||
info.left + (st::stickerIconWidth - size.width()) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height()),
|
||||
frame);
|
||||
if (!paused) {
|
||||
icon.lottie->markFrameShown();
|
||||
}
|
||||
} else if (icon.webm && icon.webm->started()) {
|
||||
const auto frame = icon.webm->current(
|
||||
{ .frame = { icon.pixw, icon.pixh }, .keepAlpha = true },
|
||||
paused ? 0 : now);
|
||||
if (icon.savedFrame.isNull()) {
|
||||
icon.savedFrame = frame;
|
||||
icon.savedFrame.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
p.drawPixmapLeft(x, y, width(), frame);
|
||||
} else if (!icon.savedFrame.isNull() || thumb) {
|
||||
const auto pixmap = !icon.savedFrame.isNull()
|
||||
? icon.savedFrame
|
||||
: (!icon.lottie && thumb)
|
||||
@@ -851,33 +995,17 @@ void StickersListWidget::Footer::paintSetIcon(
|
||||
} else if (icon.savedFrame.isNull()) {
|
||||
icon.savedFrame = pixmap;
|
||||
}
|
||||
p.drawPixmapLeft(
|
||||
x + (st::stickerIconWidth - icon.pixw) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - icon.pixh) / 2,
|
||||
width(),
|
||||
pixmap);
|
||||
} else if (icon.lottie->ready()) {
|
||||
const auto frame = icon.lottie->frame();
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
if (icon.savedFrame.isNull()) {
|
||||
icon.savedFrame = QPixmap::fromImage(frame, Qt::ColorOnly);
|
||||
icon.savedFrame.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
p.drawImage(
|
||||
QRect(
|
||||
x + (st::stickerIconWidth - size.width()) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height()),
|
||||
frame);
|
||||
const auto paused = _pan->controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
if (!paused) {
|
||||
icon.lottie->markFrameShown();
|
||||
}
|
||||
p.drawPixmapLeft(x, y, width(), pixmap);
|
||||
}
|
||||
} else if (icon.megagroup) {
|
||||
icon.megagroup->paintUserpicLeft(p, icon.megagroupUserpic, x + (st::stickerIconWidth - st::stickerGroupCategorySize) / 2, _iconsTop + (st::emojiFooterHeight - st::stickerGroupCategorySize) / 2, width(), st::stickerGroupCategorySize);
|
||||
const auto size = st::stickerGroupCategorySize;
|
||||
icon.megagroup->paintUserpicLeft(
|
||||
p,
|
||||
icon.megagroupUserpic,
|
||||
info.left + (st::stickerIconWidth - size) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - size) / 2,
|
||||
width(),
|
||||
st::stickerGroupCategorySize);
|
||||
} else {
|
||||
const auto paintedIcon = [&] {
|
||||
if (icon.setId == Data::Stickers::FeaturedSetId) {
|
||||
@@ -892,7 +1020,7 @@ void StickersListWidget::Footer::paintSetIcon(
|
||||
}();
|
||||
paintedIcon->paint(
|
||||
p,
|
||||
x + (st::stickerIconWidth - paintedIcon->width()) / 2,
|
||||
info.left + (st::stickerIconWidth - paintedIcon->width()) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - paintedIcon->height()) / 2,
|
||||
width());
|
||||
}
|
||||
@@ -927,6 +1055,8 @@ StickersListWidget::StickersListWidget(
|
||||
, _api(&controller->session().mtp())
|
||||
, _section(Section::Stickers)
|
||||
, _isMasks(masks)
|
||||
, _updateItemsTimer([=] { updateItems(); })
|
||||
, _updateSetsTimer([=] { updateSets(); })
|
||||
, _pathGradient(std::make_unique<Ui::PathShiftGradient>(
|
||||
st::windowBgRipple,
|
||||
st::windowBgOver,
|
||||
@@ -950,7 +1080,7 @@ StickersListWidget::StickersListWidget(
|
||||
session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (isVisible()) {
|
||||
update();
|
||||
updateItems();
|
||||
readVisibleFeatured(getVisibleTop(), getVisibleBottom());
|
||||
}
|
||||
}, lifetime());
|
||||
@@ -1024,7 +1154,13 @@ object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
|
||||
void StickersListWidget::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
const auto top = getVisibleTop();
|
||||
Inner::visibleTopBottomUpdated(visibleTop, visibleBottom);
|
||||
if (top != getVisibleTop()) {
|
||||
_lastScrolledAt = crl::now();
|
||||
_repaintSetsIds.clear();
|
||||
update();
|
||||
}
|
||||
if (_section == Section::Featured) {
|
||||
checkVisibleFeatured(visibleTop, visibleBottom);
|
||||
} else {
|
||||
@@ -1106,7 +1242,7 @@ void StickersListWidget::preloadMoreOfficial() {
|
||||
}
|
||||
});
|
||||
resizeToWidth(width());
|
||||
update();
|
||||
repaintItems();
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -1470,8 +1606,8 @@ void StickersListWidget::takeHeavyData(Set &to, Set &from) {
|
||||
}
|
||||
}
|
||||
for (const auto &sticker : fromList) {
|
||||
if (sticker.animated) {
|
||||
to.lottiePlayer->remove(sticker.animated);
|
||||
if (sticker.lottie) {
|
||||
to.lottiePlayer->remove(sticker.lottie);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1480,7 +1616,8 @@ void StickersListWidget::takeHeavyData(Set &to, Set &from) {
|
||||
void StickersListWidget::takeHeavyData(Sticker &to, Sticker &from) {
|
||||
to.documentMedia = std::move(from.documentMedia);
|
||||
to.savedFrame = std::move(from.savedFrame);
|
||||
to.animated = base::take(from.animated);
|
||||
to.lottie = base::take(from.lottie);
|
||||
to.webm = base::take(from.webm);
|
||||
}
|
||||
|
||||
auto StickersListWidget::shownSets() const -> const std::vector<Set> & {
|
||||
@@ -1600,6 +1737,9 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
? &_pressed
|
||||
: &_selected);
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto paused = controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
if (sets.empty() && _section == Section::Search) {
|
||||
paintEmptySearchResults(p);
|
||||
}
|
||||
@@ -1679,9 +1819,11 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
|
||||
auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false;
|
||||
auto deleteSelected = false;
|
||||
paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected);
|
||||
paintSticker(p, set, info.rowsTop, info.section, index, now, paused, selected, deleteSelected);
|
||||
}
|
||||
if (!paused) {
|
||||
markLottieFrameShown(set);
|
||||
}
|
||||
markLottieFrameShown(set);
|
||||
return true;
|
||||
}
|
||||
if (setHasTitle(set) && clip.top() < info.rowsTop) {
|
||||
@@ -1725,21 +1867,19 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
|
||||
auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false;
|
||||
auto deleteSelected = selected && selectedSticker->overDelete;
|
||||
paintSticker(p, set, info.rowsTop, info.section, index, selected, deleteSelected);
|
||||
paintSticker(p, set, info.rowsTop, info.section, index, now, paused, selected, deleteSelected);
|
||||
}
|
||||
}
|
||||
markLottieFrameShown(set);
|
||||
if (!paused) {
|
||||
markLottieFrameShown(set);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void StickersListWidget::markLottieFrameShown(Set &set) {
|
||||
if (const auto player = set.lottiePlayer.get()) {
|
||||
const auto paused = controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
if (!paused) {
|
||||
player->markFrameShown();
|
||||
}
|
||||
player->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1772,7 +1912,8 @@ void StickersListWidget::clearHeavyIn(Set &set, bool clearSavedFrames) {
|
||||
if (clearSavedFrames) {
|
||||
sticker.savedFrame = QPixmap();
|
||||
}
|
||||
sticker.animated = nullptr;
|
||||
sticker.webm = nullptr;
|
||||
sticker.lottie = nullptr;
|
||||
sticker.documentMedia = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -1792,7 +1933,7 @@ void StickersListWidget::pauseInvisibleLottieIn(const SectionInfo &info) {
|
||||
if (index >= info.count) {
|
||||
break;
|
||||
}
|
||||
if (const auto animated = set.stickers[index].animated) {
|
||||
if (const auto animated = set.stickers[index].lottie) {
|
||||
player->pause(animated);
|
||||
}
|
||||
}
|
||||
@@ -1874,16 +2015,13 @@ void StickersListWidget::ensureLottiePlayer(Set &set) {
|
||||
|
||||
raw->updates(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto &sets = shownSets();
|
||||
enumerateSections([&](const SectionInfo &info) {
|
||||
if (shownSets()[info.section].lottiePlayer.get() == raw) {
|
||||
update(
|
||||
0,
|
||||
info.rowsTop,
|
||||
width(),
|
||||
info.rowsBottom - info.rowsTop);
|
||||
return false;
|
||||
if (sets[info.section].lottiePlayer.get() != raw) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
updateSet(info);
|
||||
return false;
|
||||
});
|
||||
}, set.lottieLifetime);
|
||||
}
|
||||
@@ -1894,20 +2032,170 @@ void StickersListWidget::setupLottie(Set &set, int section, int index) {
|
||||
|
||||
// Document should be loaded already for the animation to be set up.
|
||||
Assert(sticker.documentMedia != nullptr);
|
||||
sticker.animated = LottieAnimationFromDocument(
|
||||
sticker.lottie = LottieAnimationFromDocument(
|
||||
set.lottiePlayer.get(),
|
||||
sticker.documentMedia.get(),
|
||||
StickerLottieSize::StickersPanel,
|
||||
boundingBoxSize() * cIntRetinaFactor());
|
||||
}
|
||||
|
||||
void StickersListWidget::setupWebm(Set &set, int section, int index) {
|
||||
auto &sticker = set.stickers[index];
|
||||
|
||||
// Document should be loaded already for the animation to be set up.
|
||||
Assert(sticker.documentMedia != nullptr);
|
||||
const auto setId = set.id;
|
||||
const auto document = sticker.document;
|
||||
auto callback = [=](Media::Clip::Notification notification) {
|
||||
clipCallback(notification, setId, document, index);
|
||||
};
|
||||
sticker.webm = Media::Clip::MakeReader(
|
||||
sticker.documentMedia->owner()->location(),
|
||||
sticker.documentMedia->bytes(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void StickersListWidget::clipCallback(
|
||||
Media::Clip::Notification notification,
|
||||
uint64 setId,
|
||||
not_null<DocumentData*> document,
|
||||
int indexHint) {
|
||||
Expects(indexHint >= 0);
|
||||
|
||||
auto &sets = shownSets();
|
||||
enumerateSections([&](const SectionInfo &info) {
|
||||
auto &set = sets[info.section];
|
||||
if (set.id != setId) {
|
||||
return true;
|
||||
}
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case Notification::Reinit: {
|
||||
const auto j = (indexHint < set.stickers.size()
|
||||
&& set.stickers[indexHint].document == document)
|
||||
? (begin(set.stickers) + indexHint)
|
||||
: ranges::find(set.stickers, document, &Sticker::document);
|
||||
if (j == end(set.stickers) || !j->webm) {
|
||||
break;
|
||||
}
|
||||
const auto index = j - begin(set.stickers);
|
||||
auto &webm = j->webm;
|
||||
if (webm->state() == State::Error) {
|
||||
webm.setBad();
|
||||
} else if (webm->ready() && !webm->started()) {
|
||||
const auto size = ComputeStickerSize(
|
||||
j->document,
|
||||
boundingBoxSize());
|
||||
webm->start({ .frame = size, .keepAlpha = true });
|
||||
} else if (webm->autoPausedGif() && !itemVisible(info, index)) {
|
||||
webm = nullptr;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Notification::Repaint: break;
|
||||
}
|
||||
|
||||
updateSet(info);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
bool StickersListWidget::itemVisible(
|
||||
const SectionInfo &info,
|
||||
int index) const {
|
||||
const auto visibleTop = getVisibleTop();
|
||||
const auto visibleBottom = getVisibleBottom();
|
||||
const auto row = index / _columnCount;
|
||||
const auto top = info.rowsTop + row * _singleSize.height();
|
||||
const auto bottom = top + _singleSize.height();
|
||||
return (visibleTop < bottom) && (visibleBottom > top);
|
||||
}
|
||||
|
||||
void StickersListWidget::updateSets() {
|
||||
if (_repaintSetsIds.empty()) {
|
||||
return;
|
||||
}
|
||||
auto repaint = base::take(_repaintSetsIds);
|
||||
auto &sets = shownSets();
|
||||
enumerateSections([&](const SectionInfo &info) {
|
||||
if (repaint.contains(sets[info.section].id)) {
|
||||
updateSet(info);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void StickersListWidget::updateSet(const SectionInfo &info) {
|
||||
auto &set = shownSets()[info.section];
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto delay = std::max(
|
||||
_lastScrolledAt + kMinAfterScrollDelay - now,
|
||||
set.lastUpdateTime + kMinRepaintDelay - now);
|
||||
if (delay <= 0) {
|
||||
repaintItems(info, now);
|
||||
} else {
|
||||
_repaintSetsIds.emplace(set.id);
|
||||
if (!_updateSetsTimer.isActive()
|
||||
|| _updateSetsTimer.remainingTime() > kMinRepaintDelay) {
|
||||
_updateSetsTimer.callOnce(std::max(delay, kMinRepaintDelay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::repaintItems(
|
||||
const SectionInfo &info,
|
||||
crl::time now) {
|
||||
update(
|
||||
0,
|
||||
info.rowsTop,
|
||||
width(),
|
||||
info.rowsBottom - info.rowsTop);
|
||||
auto &set = shownSets()[info.section];
|
||||
set.lastUpdateTime = now;
|
||||
}
|
||||
|
||||
void StickersListWidget::updateItems() {
|
||||
const auto now = crl::now();
|
||||
const auto delay = std::max(
|
||||
_lastScrolledAt + kMinAfterScrollDelay - now,
|
||||
_lastFullUpdatedAt + kMinRepaintDelay - now);
|
||||
if (delay <= 0) {
|
||||
repaintItems(now);
|
||||
} else if (!_updateItemsTimer.isActive()
|
||||
|| _updateItemsTimer.remainingTime() > kMinRepaintDelay) {
|
||||
_updateItemsTimer.callOnce(std::max(delay, kMinRepaintDelay));
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::repaintItems(crl::time now) {
|
||||
update();
|
||||
_repaintSetsIds.clear();
|
||||
if (!now) {
|
||||
now = crl::now();
|
||||
}
|
||||
_lastFullUpdatedAt = now;
|
||||
for (auto &set : shownSets()) {
|
||||
set.lastUpdateTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
QSize StickersListWidget::boundingBoxSize() const {
|
||||
return QSize(
|
||||
_singleSize.width() - st::roundRadiusSmall * 2,
|
||||
_singleSize.height() - st::roundRadiusSmall * 2);
|
||||
}
|
||||
|
||||
void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section, int index, bool selected, bool deleteSelected) {
|
||||
void StickersListWidget::paintSticker(
|
||||
Painter &p,
|
||||
Set &set,
|
||||
int y,
|
||||
int section,
|
||||
int index,
|
||||
crl::time now,
|
||||
bool paused,
|
||||
bool selected,
|
||||
bool deleteSelected) {
|
||||
auto &sticker = set.stickers[index];
|
||||
sticker.ensureMediaCreated();
|
||||
const auto document = sticker.document;
|
||||
@@ -1916,11 +2204,14 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||
return;
|
||||
}
|
||||
|
||||
const auto isAnimated = document->sticker()->animated;
|
||||
if (isAnimated
|
||||
&& !sticker.animated
|
||||
const auto isLottie = document->sticker()->isLottie();
|
||||
const auto isWebm = document->sticker()->isWebm();
|
||||
if (isLottie
|
||||
&& !sticker.lottie
|
||||
&& media->loaded()) {
|
||||
setupLottie(set, section, index);
|
||||
} else if (isWebm && !sticker.webm && media->loaded()) {
|
||||
setupWebm(set, section, index);
|
||||
}
|
||||
|
||||
int row = (index / _columnCount), col = (index % _columnCount);
|
||||
@@ -1934,25 +2225,15 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||
|
||||
media->checkStickerSmall();
|
||||
|
||||
auto w = 1;
|
||||
auto h = 1;
|
||||
if (isAnimated && !document->dimensions.isEmpty()) {
|
||||
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
|
||||
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
|
||||
w = std::max(size.width(), 1);
|
||||
h = std::max(size.height(), 1);
|
||||
} else {
|
||||
auto coef = qMin((_singleSize.width() - st::roundRadiusSmall * 2) / float64(document->dimensions.width()), (_singleSize.height() - st::roundRadiusSmall * 2) / float64(document->dimensions.height()));
|
||||
if (coef > 1) coef = 1;
|
||||
w = std::max(qRound(coef * document->dimensions.width()), 1);
|
||||
h = std::max(qRound(coef * document->dimensions.height()), 1);
|
||||
}
|
||||
auto ppos = pos + QPoint((_singleSize.width() - w) / 2, (_singleSize.height() - h) / 2);
|
||||
const auto size = ComputeStickerSize(document, boundingBoxSize());
|
||||
const auto ppos = pos + QPoint(
|
||||
(_singleSize.width() - size.width()) / 2,
|
||||
(_singleSize.height() - size.height()) / 2);
|
||||
|
||||
if (sticker.animated && sticker.animated->ready()) {
|
||||
if (sticker.lottie && sticker.lottie->ready()) {
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = boundingBoxSize() * cIntRetinaFactor();
|
||||
const auto frame = sticker.animated->frame(request);
|
||||
const auto frame = sticker.lottie->frame(request);
|
||||
p.drawImage(
|
||||
QRect(ppos, frame.size() / cIntRetinaFactor()),
|
||||
frame);
|
||||
@@ -1960,18 +2241,22 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||
sticker.savedFrame = QPixmap::fromImage(frame, Qt::ColorOnly);
|
||||
sticker.savedFrame.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
set.lottiePlayer->unpause(sticker.animated);
|
||||
set.lottiePlayer->unpause(sticker.lottie);
|
||||
} else if (sticker.webm && sticker.webm->started()) {
|
||||
const auto frame = sticker.webm->current(
|
||||
{ .frame = size, .keepAlpha = true },
|
||||
paused ? 0 : now);
|
||||
if (sticker.savedFrame.isNull()) {
|
||||
sticker.savedFrame = frame;
|
||||
sticker.savedFrame.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
p.drawPixmapLeft(ppos, width(), frame);
|
||||
} else {
|
||||
const auto image = media->getStickerSmall();
|
||||
const auto pixmap = !sticker.savedFrame.isNull()
|
||||
? sticker.savedFrame
|
||||
: image
|
||||
? image->pixSingle(
|
||||
w,
|
||||
h,
|
||||
w,
|
||||
h,
|
||||
ImageRoundRadius::None)
|
||||
? image->pixSingle(size, { .outer = size })
|
||||
: QPixmap();
|
||||
if (!pixmap.isNull()) {
|
||||
p.drawPixmapLeft(ppos, width(), pixmap);
|
||||
@@ -1982,7 +2267,7 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||
PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, QSize{ w, h }),
|
||||
QRect(ppos, size),
|
||||
_pathGradient.get());
|
||||
}
|
||||
}
|
||||
@@ -2203,7 +2488,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
auto pressed = _pressed;
|
||||
setPressed(v::null);
|
||||
if (pressed != _selected) {
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
auto activated = ClickHandler::unpressed();
|
||||
@@ -2305,7 +2590,7 @@ void StickersListWidget::removeRecentSticker(int section, int index) {
|
||||
if (refresh) {
|
||||
refreshRecentStickers();
|
||||
updateSelected();
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2365,7 +2650,7 @@ void StickersListWidget::enterFromChildEvent(QEvent *e, QWidget *child) {
|
||||
void StickersListWidget::clearSelection() {
|
||||
setPressed(v::null);
|
||||
setSelected(v::null);
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
TabbedSelector::InnerFooter *StickersListWidget::getFooter() const {
|
||||
@@ -2425,7 +2710,7 @@ void StickersListWidget::refreshStickers() {
|
||||
|
||||
_lastMousePosition = QCursor::pos();
|
||||
updateSelected();
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshMySets() {
|
||||
@@ -3007,7 +3292,7 @@ void StickersListWidget::showStickerSet(uint64 setId) {
|
||||
if (_footer) {
|
||||
_footer->refreshIcons(ValidateIconAnimations::Scroll);
|
||||
}
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
scrollTo(0);
|
||||
@@ -3039,7 +3324,7 @@ void StickersListWidget::showStickerSet(uint64 setId) {
|
||||
|
||||
_lastMousePosition = QCursor::pos();
|
||||
|
||||
update();
|
||||
repaintItems();
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshMegagroupSetGeometry() {
|
||||
|
||||
@@ -39,6 +39,11 @@ class DocumentMedia;
|
||||
class StickersSet;
|
||||
} // namespace Data
|
||||
|
||||
namespace Media::Clip {
|
||||
class ReaderPointer;
|
||||
enum class Notification;
|
||||
} // namespace Media::Clip
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct StickerIcon;
|
||||
@@ -113,6 +118,7 @@ protected:
|
||||
|
||||
private:
|
||||
class Footer;
|
||||
struct Sticker;
|
||||
|
||||
enum class Section {
|
||||
Featured,
|
||||
@@ -178,15 +184,6 @@ private:
|
||||
int rowsBottom = 0;
|
||||
};
|
||||
|
||||
struct Sticker {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
Lottie::Animation *animated = nullptr;
|
||||
QPixmap savedFrame;
|
||||
|
||||
void ensureMediaCreated();
|
||||
};
|
||||
|
||||
struct Set {
|
||||
Set(
|
||||
uint64 id,
|
||||
@@ -208,6 +205,7 @@ private:
|
||||
QString shortName;
|
||||
std::vector<Sticker> stickers;
|
||||
std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||
crl::time lastUpdateTime = 0;
|
||||
|
||||
std::unique_ptr<Lottie::MultiPlayer> lottiePlayer;
|
||||
rpl::lifetime lottieLifetime;
|
||||
@@ -279,11 +277,27 @@ private:
|
||||
|
||||
void paintStickers(Painter &p, QRect clip);
|
||||
void paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected);
|
||||
void paintSticker(Painter &p, Set &set, int y, int section, int index, bool selected, bool deleteSelected);
|
||||
void paintSticker(
|
||||
Painter &p,
|
||||
Set &set,
|
||||
int y,
|
||||
int section,
|
||||
int index,
|
||||
crl::time now,
|
||||
bool paused,
|
||||
bool selected,
|
||||
bool deleteSelected);
|
||||
void paintEmptySearchResults(Painter &p);
|
||||
|
||||
void ensureLottiePlayer(Set &set);
|
||||
void setupLottie(Set &set, int section, int index);
|
||||
void setupWebm(Set &set, int section, int index);
|
||||
void clipCallback(
|
||||
Media::Clip::Notification notification,
|
||||
uint64 setId,
|
||||
not_null<DocumentData*> document,
|
||||
int indexHint);
|
||||
[[nodiscard]] bool itemVisible(const SectionInfo &info, int index) const;
|
||||
void markLottieFrameShown(Set &set);
|
||||
void checkVisibleLottie();
|
||||
void pauseInvisibleLottieIn(const SectionInfo &info);
|
||||
@@ -292,6 +306,13 @@ private:
|
||||
void takeHeavyData(Sticker &to, Sticker &from);
|
||||
void clearHeavyIn(Set &set, bool clearSavedFrames = true);
|
||||
void clearHeavyData();
|
||||
void updateItems();
|
||||
void updateSets();
|
||||
void repaintItems(crl::time now = 0);
|
||||
void updateSet(const SectionInfo &info);
|
||||
void repaintItems(
|
||||
const SectionInfo &info,
|
||||
crl::time now);
|
||||
|
||||
int stickersRight() const;
|
||||
bool featuredHasAddButton(int index) const;
|
||||
@@ -302,8 +323,8 @@ private:
|
||||
void refreshMegagroupSetGeometry();
|
||||
QRect megagroupSetButtonRectFinal() const;
|
||||
|
||||
const Data::StickersSetsOrder &defaultSetsOrder() const;
|
||||
Data::StickersSetsOrder &defaultSetsOrderRef();
|
||||
[[nodiscard]] const Data::StickersSetsOrder &defaultSetsOrder() const;
|
||||
[[nodiscard]] Data::StickersSetsOrder &defaultSetsOrderRef();
|
||||
|
||||
enum class AppendSkip {
|
||||
None,
|
||||
@@ -316,7 +337,6 @@ private:
|
||||
bool externalLayout,
|
||||
AppendSkip skip = AppendSkip::None);
|
||||
|
||||
void selectEmoji(EmojiPtr emoji);
|
||||
int stickersLeft() const;
|
||||
QRect stickerRect(int section, int sel);
|
||||
|
||||
@@ -350,12 +370,19 @@ private:
|
||||
base::flat_set<not_null<DocumentData*>> _favedStickersMap;
|
||||
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
|
||||
|
||||
crl::time _lastScrolledAt = 0;
|
||||
crl::time _lastFullUpdatedAt = 0;
|
||||
|
||||
mtpRequestId _officialRequestId = 0;
|
||||
int _officialOffset = 0;
|
||||
|
||||
Section _section = Section::Stickers;
|
||||
const bool _isMasks;
|
||||
|
||||
base::Timer _updateItemsTimer;
|
||||
base::Timer _updateSetsTimer;
|
||||
base::flat_set<uint64> _repaintSetsIds;
|
||||
|
||||
bool _displayingSet = false;
|
||||
uint64 _removingSetId = 0;
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "history/view/media/history_view_media_common.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
@@ -130,16 +132,18 @@ not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
||||
}
|
||||
|
||||
bool HasLottieThumbnail(
|
||||
Data::StickersSetFlags flags,
|
||||
Data::StickersSetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media) {
|
||||
if (thumb) {
|
||||
return !thumb->content().isEmpty();
|
||||
return !(flags & Data::StickersSetFlag::Webm)
|
||||
&& !thumb->content().isEmpty();
|
||||
} else if (!media) {
|
||||
return false;
|
||||
}
|
||||
const auto document = media->owner();
|
||||
if (const auto info = document->sticker()) {
|
||||
if (!info->animated) {
|
||||
if (!info->isLottie()) {
|
||||
return false;
|
||||
}
|
||||
media->automaticLoad(document->stickerSetOrigin(), nullptr);
|
||||
@@ -189,6 +193,44 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
||||
box);
|
||||
}
|
||||
|
||||
bool HasWebmThumbnail(
|
||||
Data::StickersSetFlags flags,
|
||||
Data::StickersSetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media) {
|
||||
if (thumb) {
|
||||
return (flags & Data::StickersSetFlag::Webm)
|
||||
&& !thumb->content().isEmpty();
|
||||
} else if (!media) {
|
||||
return false;
|
||||
}
|
||||
const auto document = media->owner();
|
||||
if (const auto info = document->sticker()) {
|
||||
if (!info->isWebm()) {
|
||||
return false;
|
||||
}
|
||||
media->automaticLoad(document->stickerSetOrigin(), nullptr);
|
||||
if (!media->loaded()) {
|
||||
return false;
|
||||
}
|
||||
return document->bigFileBaseCacheKey().valid();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Media::Clip::ReaderPointer WebmThumbnail(
|
||||
Data::StickersSetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media,
|
||||
Fn<void(Media::Clip::Notification)> callback) {
|
||||
return thumb
|
||||
? ::Media::Clip::MakeReader(
|
||||
thumb->content(),
|
||||
std::move(callback))
|
||||
: ::Media::Clip::MakeReader(
|
||||
media->owner()->location(),
|
||||
media->bytes(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
bool PaintStickerThumbnailPath(
|
||||
QPainter &p,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
@@ -235,4 +277,15 @@ bool PaintStickerThumbnailPath(
|
||||
});
|
||||
}
|
||||
|
||||
QSize ComputeStickerSize(not_null<DocumentData*> document, QSize box) {
|
||||
const auto sticker = document->sticker();
|
||||
const auto dimensions = document->dimensions;
|
||||
if (!sticker || !sticker->isLottie() || dimensions.isEmpty()) {
|
||||
return HistoryView::DownscaledSize(dimensions, box);
|
||||
}
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto request = Lottie::FrameRequest{ box * ratio };
|
||||
return HistoryView::NonEmptySize(request.size(dimensions, true) / ratio);
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -7,12 +7,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace base {
|
||||
template <typename Enum>
|
||||
class Flags;
|
||||
} // namespace base
|
||||
|
||||
namespace Storage {
|
||||
namespace Cache {
|
||||
struct Key;
|
||||
} // namespace Cache
|
||||
} // namespace Storage
|
||||
|
||||
namespace Media::Clip {
|
||||
class ReaderPointer;
|
||||
enum class Notification;
|
||||
} // namespace Media::Clip
|
||||
|
||||
namespace Lottie {
|
||||
class SinglePlayer;
|
||||
class MultiPlayer;
|
||||
@@ -33,6 +43,8 @@ class PathShiftGradient;
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
class StickersSetThumbnailView;
|
||||
enum class StickersSetFlag;
|
||||
using StickersSetFlags = base::flags<StickersSetFlag>;
|
||||
} // namespace Data
|
||||
|
||||
namespace ChatHelpers {
|
||||
@@ -70,6 +82,7 @@ enum class StickerLottieSize : uchar {
|
||||
QSize box);
|
||||
|
||||
[[nodiscard]] bool HasLottieThumbnail(
|
||||
Data::StickersSetFlags flags,
|
||||
Data::StickersSetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media);
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
||||
@@ -79,6 +92,15 @@ enum class StickerLottieSize : uchar {
|
||||
QSize box,
|
||||
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
|
||||
|
||||
[[nodiscard]] bool HasWebmThumbnail(
|
||||
Data::StickersSetFlags flags,
|
||||
Data::StickersSetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media);
|
||||
[[nodiscard]] Media::Clip::ReaderPointer WebmThumbnail(
|
||||
Data::StickersSetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media,
|
||||
Fn<void(Media::Clip::Notification)> callback);
|
||||
|
||||
bool PaintStickerThumbnailPath(
|
||||
QPainter &p,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
@@ -91,4 +113,8 @@ bool PaintStickerThumbnailPath(
|
||||
QRect target,
|
||||
not_null<Ui::PathShiftGradient*> gradient);
|
||||
|
||||
[[nodiscard]] QSize ComputeStickerSize(
|
||||
not_null<DocumentData*> document,
|
||||
QSize box);
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "mainwindow.h"
|
||||
#include "core/application.h"
|
||||
#include "base/options.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
@@ -22,8 +23,16 @@ namespace {
|
||||
constexpr auto kHideTimeoutMs = 300;
|
||||
constexpr auto kDelayedHideTimeoutMs = 3000;
|
||||
|
||||
base::options::toggle TabbedPanelShowOnClick({
|
||||
.id = kOptionTabbedPanelShowOnClick,
|
||||
.name = "Show tabbed panel by click",
|
||||
.description = "Show Emoji / Stickers / GIFs panel only after a click.",
|
||||
});
|
||||
|
||||
} // namespace
|
||||
|
||||
const char kOptionTabbedPanelShowOnClick[] = "tabbed-panel-show-on-click";
|
||||
|
||||
TabbedPanel::TabbedPanel(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -408,7 +417,9 @@ void TabbedPanel::showStarted() {
|
||||
}
|
||||
|
||||
bool TabbedPanel::eventFilter(QObject *obj, QEvent *e) {
|
||||
if (e->type() == QEvent::Enter) {
|
||||
if (TabbedPanelShowOnClick.value()) {
|
||||
return false;
|
||||
} else if (e->type() == QEvent::Enter) {
|
||||
otherEnter();
|
||||
} else if (e->type() == QEvent::Leave) {
|
||||
otherLeave();
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace ChatHelpers {
|
||||
|
||||
class TabbedSelector;
|
||||
|
||||
extern const char kOptionTabbedPanelShowOnClick[];
|
||||
|
||||
class TabbedPanel : public Ui::RpWidget {
|
||||
public:
|
||||
TabbedPanel(
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/ui_integration.h"
|
||||
#include "chat_helpers/emoji_keywords.h"
|
||||
#include "chat_helpers/stickers_emoji_image_loader.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "base/platform/base_platform_url_scheme.h"
|
||||
#include "base/platform/base_platform_last_input.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
@@ -83,7 +83,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/connection_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtCore/QMimeDatabase>
|
||||
#include <QtGui/QGuiApplication>
|
||||
@@ -96,6 +95,8 @@ constexpr auto kQuitPreventTimeoutMs = crl::time(1500);
|
||||
constexpr auto kAutoLockTimeoutLateMs = crl::time(3000);
|
||||
constexpr auto kClearEmojiImageSourceTimeout = 10 * crl::time(1000);
|
||||
|
||||
LaunchState GlobalLaunchState/* = LaunchState::Running*/;
|
||||
|
||||
void SetCrashAnnotationsGL() {
|
||||
#ifdef Q_OS_WIN
|
||||
CrashReports::SetAnnotation("OpenGL ANGLE", [] {
|
||||
@@ -235,7 +236,7 @@ void Application::run() {
|
||||
|
||||
if (cLaunchMode() == LaunchModeAutoStart && Platform::AutostartSkip()) {
|
||||
Platform::AutostartToggle(false);
|
||||
App::quit();
|
||||
Quit();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -340,7 +341,7 @@ void Application::showOpenGLCrashNotification() {
|
||||
Ui::GL::CrashCheckFinish();
|
||||
Core::App().settings().setDisableOpenGL(false);
|
||||
Local::writeSettings();
|
||||
App::restart();
|
||||
Restart();
|
||||
};
|
||||
const auto keepDisabled = [=] {
|
||||
Ui::GL::ForceDisable(true);
|
||||
@@ -639,6 +640,24 @@ void Application::logout(Main::Account *account) {
|
||||
}
|
||||
}
|
||||
|
||||
void Application::logoutWithChecks(Main::Account *account) {
|
||||
const auto weak = base::make_weak(account);
|
||||
const auto retry = [=] {
|
||||
if (const auto account = weak.get()) {
|
||||
logoutWithChecks(account);
|
||||
}
|
||||
};
|
||||
if (!account || !account->sessionExists()) {
|
||||
logout(account);
|
||||
} else if (_exportManager->inProgress(&account->session())) {
|
||||
_exportManager->stopWithConfirmation(retry);
|
||||
} else if (account->session().uploadsInProgress()) {
|
||||
account->session().uploadsStopWithConfirmation(retry);
|
||||
} else {
|
||||
logout(account);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::forceLogOut(
|
||||
not_null<Main::Account*> account,
|
||||
const TextWithEntities &explanation) {
|
||||
@@ -703,7 +722,7 @@ void Application::switchDebugMode() {
|
||||
if (Logs::DebugEnabled()) {
|
||||
Logs::SetDebugEnabled(false);
|
||||
_launcher->writeDebugModeSetting();
|
||||
App::restart();
|
||||
Restart();
|
||||
} else {
|
||||
Logs::SetDebugEnabled(true);
|
||||
_launcher->writeDebugModeSetting();
|
||||
@@ -724,7 +743,7 @@ void Application::switchFreeType() {
|
||||
}
|
||||
cSetUseFreeType(true);
|
||||
}
|
||||
App::restart();
|
||||
Restart();
|
||||
}
|
||||
|
||||
void Application::writeInstallBetaVersionsSetting() {
|
||||
@@ -742,13 +761,47 @@ Main::Session *Application::maybeActiveSession() const {
|
||||
bool Application::exportPreventsQuit() {
|
||||
if (_exportManager->inProgress()) {
|
||||
_exportManager->stopWithConfirmation([] {
|
||||
App::quit();
|
||||
Quit();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Application::uploadPreventsQuit() {
|
||||
if (!_domain->started()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &[index, account] : _domain->accounts()) {
|
||||
if (!account->sessionExists()) {
|
||||
continue;
|
||||
}
|
||||
if (account->session().uploadsInProgress()) {
|
||||
account->session().uploadsStopWithConfirmation([=] {
|
||||
for (const auto &[index, account] : _domain->accounts()) {
|
||||
if (account->sessionExists()) {
|
||||
account->session().uploadsStop();
|
||||
}
|
||||
}
|
||||
Quit();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Application::preventsQuit(QuitReason reason) {
|
||||
if (exportPreventsQuit() || uploadPreventsQuit()) {
|
||||
return true;
|
||||
} else if (const auto window = activeWindow()) {
|
||||
if (window->widget()->isActive()) {
|
||||
return window->widget()->preventsQuit(reason);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int Application::unreadBadge() const {
|
||||
return _domain->unreadBadge();
|
||||
}
|
||||
@@ -954,7 +1007,7 @@ void Application::localPasscodeChanged() {
|
||||
}
|
||||
|
||||
bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||
if (App::quitting() || !_primaryWindow) {
|
||||
if (Quitting() || !_primaryWindow) {
|
||||
return false;
|
||||
} else if (_calls->hasActivePanel(session)) {
|
||||
return true;
|
||||
@@ -1128,9 +1181,10 @@ void Application::refreshGlobalProxy() {
|
||||
Sandbox::Instance().refreshGlobalProxy();
|
||||
}
|
||||
|
||||
void Application::QuitAttempt() {
|
||||
void QuitAttempt() {
|
||||
const auto savingSession = Sandbox::Instance().isSavingSession();
|
||||
if (!IsAppLaunched()
|
||||
|| Sandbox::Instance().isSavingSession()
|
||||
|| savingSession
|
||||
|| App().readyToQuit()) {
|
||||
Sandbox::QuitWhenStarted();
|
||||
}
|
||||
@@ -1161,12 +1215,18 @@ bool Application::readyToQuit() {
|
||||
}
|
||||
|
||||
void Application::quitPreventFinished() {
|
||||
if (App::quitting()) {
|
||||
if (Quitting()) {
|
||||
QuitAttempt();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::quitDelayed() {
|
||||
if (_primaryWindow) {
|
||||
_primaryWindow->widget()->hide();
|
||||
}
|
||||
for (const auto &[history, window] : _secondaryWindows) {
|
||||
window->widget()->hide();
|
||||
}
|
||||
if (!_private->quitTimer.isActive()) {
|
||||
_private->quitTimer.setCallback([] { Sandbox::QuitWhenStarted(); });
|
||||
_private->quitTimer.callOnce(kQuitPreventTimeoutMs);
|
||||
@@ -1180,14 +1240,16 @@ void Application::startShortcuts() {
|
||||
) | rpl::start_with_next([=](Main::Session *session) {
|
||||
const auto support = session && session->supportMode();
|
||||
Shortcuts::ToggleSupportShortcuts(support);
|
||||
Platform::SetApplicationIcon(Window::CreateIcon(session));
|
||||
Platform::SetApplicationIcon(Window::CreateIcon(
|
||||
session,
|
||||
Platform::IsMac()));
|
||||
}, _lifetime);
|
||||
|
||||
Shortcuts::Requests(
|
||||
) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
|
||||
using Command = Shortcuts::Command;
|
||||
request->check(Command::Quit) && request->handle([] {
|
||||
App::quit();
|
||||
Quit();
|
||||
return true;
|
||||
});
|
||||
request->check(Command::Lock) && request->handle([=] {
|
||||
@@ -1229,4 +1291,39 @@ Application &App() {
|
||||
return *Application::Instance;
|
||||
}
|
||||
|
||||
void Quit(QuitReason reason) {
|
||||
if (Quitting()) {
|
||||
return;
|
||||
} else if (IsAppLaunched() && App().preventsQuit(reason)) {
|
||||
return;
|
||||
}
|
||||
SetLaunchState(LaunchState::QuitRequested);
|
||||
|
||||
QuitAttempt();
|
||||
}
|
||||
|
||||
bool Quitting() {
|
||||
return GlobalLaunchState != LaunchState::Running;
|
||||
}
|
||||
|
||||
LaunchState CurrentLaunchState() {
|
||||
return GlobalLaunchState;
|
||||
}
|
||||
|
||||
void SetLaunchState(LaunchState state) {
|
||||
GlobalLaunchState = state;
|
||||
}
|
||||
|
||||
void Restart() {
|
||||
const auto updateReady = !UpdaterDisabled()
|
||||
&& (UpdateChecker().state() == UpdateChecker::State::Ready);
|
||||
if (updateReady) {
|
||||
cSetRestartingUpdate(true);
|
||||
} else {
|
||||
cSetRestarting(true);
|
||||
cSetRestartingToSettings(true);
|
||||
}
|
||||
Quit();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -39,10 +39,6 @@ namespace ChatHelpers {
|
||||
class EmojiKeywords;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace App {
|
||||
void quit();
|
||||
} // namespace App
|
||||
|
||||
namespace Main {
|
||||
class Domain;
|
||||
class Account;
|
||||
@@ -107,6 +103,17 @@ namespace Core {
|
||||
class Launcher;
|
||||
struct LocalUrlHandler;
|
||||
|
||||
enum class LaunchState {
|
||||
Running,
|
||||
QuitRequested,
|
||||
QuitProcessed,
|
||||
};
|
||||
|
||||
enum class QuitReason {
|
||||
Default,
|
||||
QtQuitEvent,
|
||||
};
|
||||
|
||||
class Application final : public QObject {
|
||||
public:
|
||||
struct ProxyChange {
|
||||
@@ -239,9 +246,11 @@ public:
|
||||
}
|
||||
|
||||
void logout(Main::Account *account = nullptr);
|
||||
void logoutWithChecks(Main::Account *account);
|
||||
void forceLogOut(
|
||||
not_null<Main::Account*> account,
|
||||
const TextWithEntities &explanation);
|
||||
[[nodiscard]] bool uploadPreventsQuit();
|
||||
void checkLocalTime();
|
||||
void lockByPasscode();
|
||||
void unlockPasscode();
|
||||
@@ -253,6 +262,8 @@ public:
|
||||
void checkAutoLockIn(crl::time time);
|
||||
void localPasscodeChanged();
|
||||
|
||||
[[nodiscard]] bool preventsQuit(QuitReason reason);
|
||||
|
||||
[[nodiscard]] crl::time lastNonIdleTime() const;
|
||||
void updateNonIdle();
|
||||
|
||||
@@ -301,8 +312,7 @@ private:
|
||||
void startEmojiImageLoader();
|
||||
void startSystemDarkModeViewer();
|
||||
|
||||
friend void App::quit();
|
||||
static void QuitAttempt();
|
||||
friend void QuitAttempt();
|
||||
void quitDelayed();
|
||||
[[nodiscard]] bool readyToQuit();
|
||||
|
||||
@@ -387,4 +397,12 @@ private:
|
||||
[[nodiscard]] bool IsAppLaunched();
|
||||
[[nodiscard]] Application &App();
|
||||
|
||||
[[nodiscard]] LaunchState CurrentLaunchState();
|
||||
void SetLaunchState(LaunchState state);
|
||||
|
||||
void Quit(QuitReason reason = QuitReason::Default);
|
||||
[[nodiscard]] bool Quitting();
|
||||
|
||||
void Restart();
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "storage/storage_domain.h"
|
||||
#include "data/data_session.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
@@ -66,6 +66,18 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- Spoiler formatting hides text in chat, "
|
||||
"as well as in the chat list and notifications.\n"
|
||||
},
|
||||
{
|
||||
3004005,
|
||||
"- Fix crash in monospace blocks processing.\n"
|
||||
|
||||
"- Fix reaction animations stopping after an hour uptime.\n"
|
||||
},
|
||||
{
|
||||
3004006,
|
||||
"- Add snap layouts support on Windows 11.\n"
|
||||
|
||||
"- Fix crash in drafts after accounts switching.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||