Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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
|
||||
)
|
||||
|
||||
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 |
@@ -389,9 +389,13 @@ 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_section_chat_settings" = "Chat Settings";
|
||||
"lng_settings_replace_emojis" = "Replace emoji";
|
||||
"lng_settings_suggest_emoji" = "Suggest emoji replacements";
|
||||
@@ -1748,6 +1752,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";
|
||||
@@ -1922,6 +1927,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 +3066,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 +3093,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.6.0" />
|
||||
Version="3.5.0.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,6,0
|
||||
PRODUCTVERSION 3,4,6,0
|
||||
FILEVERSION 3,5,0,0
|
||||
PRODUCTVERSION 3,5,0,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.6.0"
|
||||
VALUE "FileVersion", "3.5.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.4.6.0"
|
||||
VALUE "ProductVersion", "3.5.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,4,6,0
|
||||
PRODUCTVERSION 3,4,6,0
|
||||
FILEVERSION 3,5,0,0
|
||||
PRODUCTVERSION 3,5,0,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.6.0"
|
||||
VALUE "FileVersion", "3.5.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.4.6.0"
|
||||
VALUE "ProductVersion", "3.5.0.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;
|
||||
|
||||
@@ -289,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,
|
||||
@@ -297,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()),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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, [=] {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/text_entity.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
@@ -26,8 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "facades.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
namespace {
|
||||
|
||||
void SearchByHashtag(ClickContext context, const QString &tag) {
|
||||
@@ -62,7 +61,13 @@ bool UrlRequiresConfirmation(const QUrl &url) {
|
||||
using namespace qthelp;
|
||||
|
||||
return !regex_match(
|
||||
"(^|\\.)(telegram\\.(org|me|dog)|t\\.me|telegra\\.ph|telesco\\.pe)$",
|
||||
"(^|\\.)("
|
||||
"telegram\\.(org|me|dog)"
|
||||
"|t\\.me"
|
||||
"|te\\.?legra\\.ph"
|
||||
"|graph\\.org"
|
||||
"|telesco\\.pe"
|
||||
")$",
|
||||
url.host(),
|
||||
RegExOption::CaseInsensitive);
|
||||
}
|
||||
@@ -101,8 +106,7 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
||||
open();
|
||||
} else {
|
||||
const auto parsedUrl = QUrl::fromUserInput(url);
|
||||
if (UrlRequiresConfirmation(parsedUrl)
|
||||
&& QGuiApplication::keyboardModifiers() != Qt::ControlModifier) {
|
||||
if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) {
|
||||
Core::App().hideMediaView();
|
||||
const auto displayed = parsedUrl.isValid()
|
||||
? parsedUrl.toDisplayString()
|
||||
|
||||
@@ -227,7 +227,8 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_closeToTaskbar.current() ? 1 : 0)
|
||||
<< _customDeviceModel.current()
|
||||
<< qint32(_playerRepeatMode.current())
|
||||
<< qint32(_playerOrderMode.current());
|
||||
<< qint32(_playerOrderMode.current())
|
||||
<< qint32(_macWarnBeforeQuit ? 1 : 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -314,6 +315,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
QString customDeviceModel = _customDeviceModel.current();
|
||||
qint32 playerRepeatMode = static_cast<qint32>(_playerRepeatMode.current());
|
||||
qint32 playerOrderMode = static_cast<qint32>(_playerOrderMode.current());
|
||||
qint32 macWarnBeforeQuit = _macWarnBeforeQuit ? 1 : 0;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -482,6 +484,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
>> playerRepeatMode
|
||||
>> playerOrderMode;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> macWarnBeforeQuit;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -636,6 +641,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
case Media::Player::OrderMode::Reverse:
|
||||
case Media::Player::OrderMode::Shuffle: _playerOrderMode = uncheckedPlayerOrderMode; break;
|
||||
}
|
||||
_macWarnBeforeQuit = macWarnBeforeQuit ? 1 : 0;
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
||||
@@ -657,6 +657,13 @@ public:
|
||||
return _playerOrderMode.changes();
|
||||
}
|
||||
|
||||
void setMacWarnBeforeQuit(bool value) {
|
||||
_macWarnBeforeQuit = value;
|
||||
}
|
||||
[[nodiscard]] bool macWarnBeforeQuit() const {
|
||||
return _macWarnBeforeQuit;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
|
||||
@@ -760,6 +767,7 @@ private:
|
||||
rpl::variable<QString> _customDeviceModel;
|
||||
rpl::variable<Media::Player::RepeatMode> _playerRepeatMode;
|
||||
rpl::variable<Media::Player::OrderMode> _playerOrderMode;
|
||||
bool _macWarnBeforeQuit = true;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/crash_report_window.h"
|
||||
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/application.h"
|
||||
#include "core/launcher.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/update_checker.h"
|
||||
@@ -15,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/main_window.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/zlib_help.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtGui/QScreen>
|
||||
@@ -208,7 +208,7 @@ void NotStartedWindow::updateControls() {
|
||||
|
||||
void NotStartedWindow::closeEvent(QCloseEvent *e) {
|
||||
deleteLater();
|
||||
App::quit();
|
||||
Core::Quit();
|
||||
}
|
||||
|
||||
void NotStartedWindow::resizeEvent(QResizeEvent *e) {
|
||||
@@ -906,7 +906,7 @@ void LastCrashedWindow::setUpdatingState(UpdatingState state, bool force) {
|
||||
case UpdatingReady:
|
||||
if (Core::checkReadyUpdate()) {
|
||||
cSetRestartingUpdate(true);
|
||||
App::quit();
|
||||
Core::Quit();
|
||||
return;
|
||||
} else {
|
||||
setUpdatingState(UpdatingFail);
|
||||
|
||||
@@ -41,7 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "history/history.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
@@ -109,6 +109,14 @@ MimeType MimeTypeForData(const QByteArray &data) {
|
||||
return MimeType(QMimeDatabase().mimeTypeForData(data));
|
||||
}
|
||||
|
||||
bool IsMimeStickerLottie(const QString &mime) {
|
||||
return (mime == u"application/x-tgsticker"_q);
|
||||
}
|
||||
|
||||
bool IsMimeStickerWebm(const QString &mime) {
|
||||
return (mime == u"video/webm"_q);
|
||||
}
|
||||
|
||||
bool IsMimeStickerAnimated(const QString &mime) {
|
||||
return (mime == u"application/x-tgsticker"_q);
|
||||
}
|
||||
|
||||
@@ -24,10 +24,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtCore/QLockFile>
|
||||
#include <QtGui/QSessionManager>
|
||||
@@ -196,7 +195,7 @@ void Sandbox::QuitWhenStarted() {
|
||||
|
||||
void Sandbox::launchApplication() {
|
||||
InvokeQueued(this, [=] {
|
||||
if (App::quitting()) {
|
||||
if (Quitting()) {
|
||||
quit();
|
||||
} else if (_application) {
|
||||
return;
|
||||
@@ -261,8 +260,12 @@ void Sandbox::setupScreenScale() {
|
||||
Sandbox::~Sandbox() = default;
|
||||
|
||||
bool Sandbox::event(QEvent *e) {
|
||||
if (e->type() == QEvent::Close || e->type() == QEvent::Quit) {
|
||||
App::quit();
|
||||
if (e->type() == QEvent::Quit && !Quitting()) {
|
||||
Quit(QuitReason::QtQuitEvent);
|
||||
e->ignore();
|
||||
return false;
|
||||
} else if (e->type() == QEvent::Close) {
|
||||
Quit();
|
||||
}
|
||||
return QApplication::event(e);
|
||||
}
|
||||
@@ -314,16 +317,16 @@ void Sandbox::socketReading() {
|
||||
psActivateProcess(pid);
|
||||
}
|
||||
LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid));
|
||||
return App::quit();
|
||||
return Quit();
|
||||
}
|
||||
}
|
||||
|
||||
void Sandbox::socketError(QLocalSocket::LocalSocketError e) {
|
||||
if (App::quitting()) return;
|
||||
if (Quitting()) return;
|
||||
|
||||
if (_secondInstance) {
|
||||
LOG(("Could not write show command, error %1, quitting...").arg(e));
|
||||
return App::quit();
|
||||
return Quit();
|
||||
}
|
||||
|
||||
if (e == QLocalSocket::ServerNotFoundError) {
|
||||
@@ -339,7 +342,7 @@ void Sandbox::socketError(QLocalSocket::LocalSocketError e) {
|
||||
|
||||
if (!_localServer.listen(_localServerName)) {
|
||||
LOG(("Failed to start listening to %1 server: %2").arg(_localServerName, _localServer.errorString()));
|
||||
return App::quit();
|
||||
return Quit();
|
||||
}
|
||||
#endif // !Q_OS_WINRT
|
||||
|
||||
@@ -348,11 +351,11 @@ void Sandbox::socketError(QLocalSocket::LocalSocketError e) {
|
||||
&& Core::checkReadyUpdate()) {
|
||||
cSetRestartingUpdate(true);
|
||||
DEBUG_LOG(("Sandbox Info: installing update instead of starting app..."));
|
||||
return App::quit();
|
||||
return Quit();
|
||||
}
|
||||
|
||||
if (cQuit()) {
|
||||
return App::quit();
|
||||
return Quit();
|
||||
}
|
||||
|
||||
singleInstanceChecked();
|
||||
@@ -403,7 +406,7 @@ void Sandbox::singleInstanceChecked() {
|
||||
void Sandbox::socketDisconnected() {
|
||||
if (_secondInstance) {
|
||||
DEBUG_LOG(("Sandbox Error: socket disconnected before command response received, quitting..."));
|
||||
return App::quit();
|
||||
return Quit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,7 +498,7 @@ void Sandbox::removeClients() {
|
||||
}
|
||||
|
||||
void Sandbox::checkForQuit() {
|
||||
if (App::quitting()) {
|
||||
if (Quitting()) {
|
||||
quit();
|
||||
}
|
||||
}
|
||||
@@ -629,10 +632,10 @@ MTP::ProxyData Sandbox::sandboxProxy() const {
|
||||
}
|
||||
|
||||
void Sandbox::closeApplication() {
|
||||
if (App::launchState() == App::QuitProcessed) {
|
||||
if (CurrentLaunchState() == LaunchState::QuitProcessed) {
|
||||
return;
|
||||
}
|
||||
App::setLaunchState(App::QuitProcessed);
|
||||
SetLaunchState(LaunchState::QuitProcessed);
|
||||
|
||||
_application = nullptr;
|
||||
|
||||
@@ -656,7 +659,7 @@ void Sandbox::execExternal(const QString &cmd) {
|
||||
PreLaunchWindow::instance()->activate();
|
||||
}
|
||||
} else if (cmd == "quit") {
|
||||
App::quit();
|
||||
Quit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtproto_proxy_data.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
|
||||
@@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "core/application.h"
|
||||
#include "core/changelogs.h"
|
||||
@@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "settings/settings_intro.h"
|
||||
#include "ui/layers/box_content.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
@@ -1175,7 +1174,7 @@ void Updater::check() {
|
||||
void Updater::handleReady() {
|
||||
stop();
|
||||
_action = Action::Ready;
|
||||
if (!App::quitting()) {
|
||||
if (!Quitting()) {
|
||||
cSetLastUpdateCheck(base::unixtime::now());
|
||||
Local::writeSettings();
|
||||
}
|
||||
@@ -1203,7 +1202,7 @@ void Updater::handleProgress() {
|
||||
|
||||
void Updater::scheduleNext() {
|
||||
stop();
|
||||
if (!App::quitting()) {
|
||||
if (!Quitting()) {
|
||||
cSetLastUpdateCheck(base::unixtime::now());
|
||||
Local::writeSettings();
|
||||
start(true);
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 3004006;
|
||||
constexpr auto AppVersionStr = "3.4.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 3005000;
|
||||
constexpr auto AppVersionStr = "3.5";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "countries/countries_instance.h"
|
||||
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
namespace Countries {
|
||||
namespace {
|
||||
|
||||
@@ -302,7 +302,7 @@ bool ShouldAutoPlay(
|
||||
const Full &data,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<DocumentData*> document) {
|
||||
return data.shouldDownload(
|
||||
return document->sticker() || data.shouldDownload(
|
||||
SourceFromPeer(peer),
|
||||
AutoPlayTypeFromDocument(document),
|
||||
document->size);
|
||||
|
||||
@@ -32,7 +32,7 @@ inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) {
|
||||
++i;
|
||||
Assert(i != 64);
|
||||
}
|
||||
return (i + 1);
|
||||
return i;
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
@@ -116,18 +116,19 @@ struct HistoryUpdate {
|
||||
TopPromoted = (1U << 2),
|
||||
Folder = (1U << 3),
|
||||
UnreadMentions = (1U << 4),
|
||||
ClientSideMessages = (1U << 5),
|
||||
ChatOccupied = (1U << 6),
|
||||
MessageSent = (1U << 7),
|
||||
ScheduledSent = (1U << 8),
|
||||
ForwardDraft = (1U << 9),
|
||||
OutboxRead = (1U << 10),
|
||||
BotKeyboard = (1U << 11),
|
||||
CloudDraft = (1U << 12),
|
||||
LocalDraftSet = (1U << 13),
|
||||
PinnedMessages = (1U << 14),
|
||||
UnreadReactions = (1U << 5),
|
||||
ClientSideMessages = (1U << 6),
|
||||
ChatOccupied = (1U << 7),
|
||||
MessageSent = (1U << 8),
|
||||
ScheduledSent = (1U << 9),
|
||||
ForwardDraft = (1U << 10),
|
||||
OutboxRead = (1U << 11),
|
||||
BotKeyboard = (1U << 12),
|
||||
CloudDraft = (1U << 13),
|
||||
LocalDraftSet = (1U << 14),
|
||||
PinnedMessages = (1U << 15),
|
||||
|
||||
LastUsedBit = (1U << 14),
|
||||
LastUsedBit = (1U << 15),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
@@ -150,8 +151,9 @@ struct MessageUpdate {
|
||||
BotCallbackSent = (1U << 6),
|
||||
NewMaybeAdded = (1U << 7),
|
||||
RepliesUnreadCount = (1U << 8),
|
||||
NewUnreadReaction = (1U << 9),
|
||||
|
||||
LastUsedBit = (1U << 8),
|
||||
LastUsedBit = (1U << 9),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
@@ -270,9 +272,11 @@ private:
|
||||
void sendNotifications();
|
||||
|
||||
private:
|
||||
static constexpr auto kCount = details::CountBit<Flag>();
|
||||
static constexpr auto kCount = details::CountBit<Flag>() + 1;
|
||||
|
||||
void sendRealtimeNotifications(not_null<DataType*> data, Flags flags);
|
||||
void sendRealtimeNotifications(
|
||||
not_null<DataType*> data,
|
||||
Flags flags);
|
||||
|
||||
std::array<rpl::event_stream<UpdateType>, kCount> _realtimeStreams;
|
||||
base::flat_map<not_null<DataType*>, Flags> _updates;
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -202,13 +203,13 @@ bool ChatFilter::contains(not_null<History*> history) const {
|
||||
|| ((_flags & flag)
|
||||
&& (!(_flags & Flag::NoMuted)
|
||||
|| !history->mute()
|
||||
|| (history->hasUnreadMentions()
|
||||
|| (history->unreadMentions().has()
|
||||
&& history->folderKnown()
|
||||
&& !history->folder()))
|
||||
&& (!(_flags & Flag::NoRead)
|
||||
|| history->unreadCount()
|
||||
|| history->unreadMark()
|
||||
|| history->hasUnreadMentions()
|
||||
|| history->unreadMentions().has()
|
||||
|| history->fakeUnreadWhileOpened())
|
||||
&& (!(_flags & Flag::NoArchived)
|
||||
|| (history->folderKnown() && !history->folder())))
|
||||
|
||||
@@ -52,7 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
const auto kAnimatedStickerDimensions = QSize(
|
||||
const auto kLottieStickerDimensions = QSize(
|
||||
kStickerSideSize,
|
||||
kStickerSideSize);
|
||||
|
||||
@@ -262,6 +262,22 @@ Data::FileOrigin StickerData::setOrigin() const {
|
||||
: Data::FileOrigin();
|
||||
}
|
||||
|
||||
bool StickerData::isStatic() const {
|
||||
return (type == StickerType::Webp);
|
||||
}
|
||||
|
||||
bool StickerData::isLottie() const {
|
||||
return (type == StickerType::Tgs);
|
||||
}
|
||||
|
||||
bool StickerData::isAnimated() const {
|
||||
return !isStatic();
|
||||
}
|
||||
|
||||
bool StickerData::isWebm() const {
|
||||
return (type == StickerType::Webm);
|
||||
}
|
||||
|
||||
VoiceData::~VoiceData() {
|
||||
if (!waveform.isEmpty()
|
||||
&& waveform[0] == -1
|
||||
@@ -305,21 +321,25 @@ void DocumentData::setattributes(
|
||||
dimensions = QSize(data.vw().v, data.vh().v);
|
||||
}, [&](const MTPDdocumentAttributeAnimated &data) {
|
||||
if (type == FileDocument
|
||||
|| type == StickerDocument
|
||||
|| type == VideoDocument) {
|
||||
|| type == VideoDocument
|
||||
|| (sticker() && sticker()->type != StickerType::Webm)) {
|
||||
type = AnimatedDocument;
|
||||
_additional = nullptr;
|
||||
}
|
||||
}, [&](const MTPDdocumentAttributeSticker &data) {
|
||||
if (type == FileDocument) {
|
||||
const auto was = type;
|
||||
if (type == FileDocument || type == VideoDocument) {
|
||||
type = StickerDocument;
|
||||
_additional = std::make_unique<StickerData>();
|
||||
}
|
||||
if (sticker()) {
|
||||
sticker()->alt = qs(data.valt());
|
||||
if (!sticker()->set.id
|
||||
if (const auto info = sticker()) {
|
||||
if (was == VideoDocument) {
|
||||
info->type = StickerType::Webm;
|
||||
}
|
||||
info->alt = qs(data.valt());
|
||||
if (!info->set.id
|
||||
|| data.vstickerset().type() == mtpc_inputStickerSetID) {
|
||||
sticker()->set = data.vstickerset().match([&](
|
||||
info->set = data.vstickerset().match([&](
|
||||
const MTPDinputStickerSetID &data) {
|
||||
return StickerSetIdentifier{
|
||||
.id = data.vid().v,
|
||||
@@ -339,6 +359,8 @@ void DocumentData::setattributes(
|
||||
type = data.is_round_message()
|
||||
? RoundVideoDocument
|
||||
: VideoDocument;
|
||||
} else if (const auto info = sticker()) {
|
||||
info->type = StickerType::Webm;
|
||||
}
|
||||
_duration = data.vduration().v;
|
||||
setMaybeSupportsStreaming(data.is_supports_streaming());
|
||||
@@ -380,12 +402,19 @@ void DocumentData::setattributes(
|
||||
}
|
||||
if (type == StickerDocument
|
||||
&& ((size > Storage::kMaxStickerBytesSize)
|
||||
|| (!sticker()->animated
|
||||
|| (!sticker()->isLottie()
|
||||
&& !GoodStickerDimensions(
|
||||
dimensions.width(),
|
||||
dimensions.height())))) {
|
||||
type = FileDocument;
|
||||
_additional = nullptr;
|
||||
} else if (type == FileDocument
|
||||
&& hasMimeType(qstr("video/webm"))
|
||||
&& (size < Storage::kMaxStickerBytesSize)
|
||||
&& GoodStickerDimensions(dimensions.width(), dimensions.height())) {
|
||||
type = StickerDocument;
|
||||
_additional = std::make_unique<StickerData>();
|
||||
sticker()->type = StickerType::Webm;
|
||||
}
|
||||
if (isAudioFile() || isAnimation() || isVoiceMessage()) {
|
||||
setMaybeSupportsStreaming(true);
|
||||
@@ -397,8 +426,8 @@ void DocumentData::validateLottieSticker() {
|
||||
&& hasMimeType(qstr("application/x-tgsticker"))) {
|
||||
type = StickerDocument;
|
||||
_additional = std::make_unique<StickerData>();
|
||||
sticker()->animated = true;
|
||||
dimensions = kAnimatedStickerDimensions;
|
||||
sticker()->type = StickerType::Tgs;
|
||||
dimensions = kLottieStickerDimensions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1158,6 +1187,9 @@ bool DocumentData::hasRemoteLocation() const {
|
||||
}
|
||||
|
||||
bool DocumentData::useStreamingLoader() const {
|
||||
if (const auto info = sticker()) {
|
||||
return info->isWebm();
|
||||
}
|
||||
return isAnimation()
|
||||
|| isVideoFile()
|
||||
|| isAudioFile()
|
||||
@@ -1368,6 +1400,10 @@ TimeId DocumentData::getDuration() const {
|
||||
return std::max(voice->duration, 0);
|
||||
} else if (isAnimation() || isVideoFile()) {
|
||||
return std::max(_duration, 0);
|
||||
} else if (const auto sticker = this->sticker()) {
|
||||
if (sticker->isWebm()) {
|
||||
return std::max(_duration, 0);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -57,12 +57,22 @@ struct DocumentAdditionalData {
|
||||
|
||||
};
|
||||
|
||||
struct StickerData : public DocumentAdditionalData {
|
||||
Data::FileOrigin setOrigin() const;
|
||||
enum class StickerType : uchar {
|
||||
Webp,
|
||||
Tgs,
|
||||
Webm,
|
||||
};
|
||||
|
||||
struct StickerData : public DocumentAdditionalData {
|
||||
[[nodiscard]] Data::FileOrigin setOrigin() const;
|
||||
[[nodiscard]] bool isStatic() const;
|
||||
[[nodiscard]] bool isLottie() const;
|
||||
[[nodiscard]] bool isAnimated() const;
|
||||
[[nodiscard]] bool isWebm() const;
|
||||
|
||||
bool animated = false;
|
||||
QString alt;
|
||||
StickerSetIdentifier set;
|
||||
StickerType type = StickerType::Webp;
|
||||
};
|
||||
|
||||
struct SongData : public DocumentAdditionalData {
|
||||
|
||||
@@ -37,6 +37,7 @@ constexpr auto kGoodThumbQuality = 87;
|
||||
|
||||
enum class FileType {
|
||||
Video,
|
||||
VideoSticker,
|
||||
AnimatedSticker,
|
||||
WallPaper,
|
||||
WallPatternPNG,
|
||||
@@ -49,15 +50,19 @@ enum class FileType {
|
||||
|| owner->isAnimation()
|
||||
|| owner->isWallPaper()
|
||||
|| owner->isTheme()
|
||||
|| (owner->sticker() && owner->sticker()->animated);
|
||||
|| (owner->sticker() && owner->sticker()->isAnimated());
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PrepareGoodThumbnail(
|
||||
const QString &path,
|
||||
QByteArray data,
|
||||
FileType type) {
|
||||
if (type == FileType::Video) {
|
||||
return ::Media::Clip::PrepareForSending(path, data).thumbnail;
|
||||
if (type == FileType::Video || type == FileType::VideoSticker) {
|
||||
auto result = ::Media::Clip::PrepareForSending(path, data);
|
||||
if (result.isWebmSticker && type == FileType::Video) {
|
||||
result.thumbnail = Images::Opaque(std::move(result.thumbnail));
|
||||
}
|
||||
return result.thumbnail;
|
||||
} else if (type == FileType::AnimatedSticker) {
|
||||
return Lottie::ReadThumbnail(Lottie::ReadContent(data, path));
|
||||
} else if (type == FileType::Theme) {
|
||||
@@ -260,7 +265,7 @@ void DocumentMedia::checkStickerLarge() {
|
||||
return;
|
||||
}
|
||||
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
||||
if (data->animated || !loaded()) {
|
||||
if (data->isAnimated() || !loaded()) {
|
||||
return;
|
||||
}
|
||||
if (_bytes.isEmpty()) {
|
||||
@@ -366,9 +371,9 @@ bool DocumentMedia::thumbnailEnoughForSticker() const {
|
||||
|
||||
void DocumentMedia::checkStickerSmall() {
|
||||
const auto data = _owner->sticker();
|
||||
if ((data && data->animated) || thumbnailEnoughForSticker()) {
|
||||
if ((data && data->isAnimated()) || thumbnailEnoughForSticker()) {
|
||||
_owner->loadThumbnail(_owner->stickerSetOrigin());
|
||||
if (data && data->animated) {
|
||||
if (data && data->isAnimated()) {
|
||||
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
||||
}
|
||||
} else {
|
||||
@@ -383,7 +388,7 @@ Image *DocumentMedia::getStickerLarge() {
|
||||
|
||||
Image *DocumentMedia::getStickerSmall() {
|
||||
const auto data = _owner->sticker();
|
||||
if ((data && data->animated) || thumbnailEnoughForSticker()) {
|
||||
if ((data && data->isAnimated()) || thumbnailEnoughForSticker()) {
|
||||
return thumbnail();
|
||||
}
|
||||
return _sticker.get();
|
||||
@@ -409,9 +414,11 @@ void DocumentMedia::GenerateGoodThumbnail(
|
||||
? FileType::WallPaper
|
||||
: document->isTheme()
|
||||
? FileType::Theme
|
||||
: document->sticker()
|
||||
: !document->sticker()
|
||||
? FileType::Video
|
||||
: document->sticker()->isLottie()
|
||||
? FileType::AnimatedSticker
|
||||
: FileType::Video;
|
||||
: FileType::VideoSticker;
|
||||
auto location = document->location().isEmpty()
|
||||
? nullptr
|
||||
: std::make_unique<Core::FileLocation>(document->location());
|
||||
@@ -428,7 +435,8 @@ void DocumentMedia::GenerateGoodThumbnail(
|
||||
auto bytes = QByteArray();
|
||||
if (!result.isNull()) {
|
||||
auto buffer = QBuffer(&bytes);
|
||||
const auto format = (type == FileType::AnimatedSticker)
|
||||
const auto format = (type == FileType::AnimatedSticker
|
||||
|| type == FileType::VideoSticker)
|
||||
? "WEBP"
|
||||
: (type == FileType::WallPatternPNG
|
||||
|| type == FileType::WallPatternSVG)
|
||||
|
||||
@@ -14,7 +14,7 @@ class FileLoader;
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
enum Notification : int;
|
||||
enum class Notification;
|
||||
class ReaderPointer;
|
||||
} // namespace Clip
|
||||
} // namespace Media
|
||||
|
||||
@@ -51,9 +51,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_file_origin.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
@@ -128,9 +128,9 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
Images::CornersMask(pxRadius)).first->second;
|
||||
}
|
||||
}
|
||||
Images::prepareRound(square, *cache.lastUsed);
|
||||
square = Images::Round(std::move(square), *cache.lastUsed);
|
||||
} else {
|
||||
Images::prepareRound(square, radius);
|
||||
square = Images::Round(std::move(square), radius);
|
||||
}
|
||||
square.setDevicePixelRatio(factor);
|
||||
return square;
|
||||
@@ -464,7 +464,7 @@ MediaPhoto::MediaPhoto(
|
||||
}
|
||||
|
||||
MediaPhoto::~MediaPhoto() {
|
||||
if (uploading() && !App::quitting()) {
|
||||
if (uploading() && !Core::Quitting()) {
|
||||
parent()->history()->session().uploader().cancel(parent()->fullId());
|
||||
}
|
||||
parent()->history()->owner().unregisterPhotoItem(_photo, parent());
|
||||
@@ -649,7 +649,7 @@ MediaFile::MediaFile(
|
||||
}
|
||||
|
||||
MediaFile::~MediaFile() {
|
||||
if (uploading() && !App::quitting()) {
|
||||
if (uploading() && !Core::Quitting()) {
|
||||
parent()->history()->session().uploader().cancel(parent()->fullId());
|
||||
}
|
||||
parent()->history()->owner().unregisterDocumentItem(
|
||||
@@ -947,14 +947,16 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
|
||||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing) {
|
||||
if (_document->sticker()) {
|
||||
if (const auto info = _document->sticker(); info && !info->isWebm()) {
|
||||
return std::make_unique<HistoryView::UnwrappedMedia>(
|
||||
message,
|
||||
std::make_unique<HistoryView::Sticker>(
|
||||
message,
|
||||
_document,
|
||||
replacing));
|
||||
} else if (_document->isAnimation() || _document->isVideoFile()) {
|
||||
} else if (_document->isAnimation()
|
||||
|| _document->isVideoFile()
|
||||
|| (info && info->isWebm())) {
|
||||
return std::make_unique<HistoryView::Gif>(
|
||||
message,
|
||||
realParent,
|
||||
|
||||
@@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
@@ -415,7 +417,7 @@ void Reactions::updateAllInHistory(not_null<PeerData*> peer, bool enabled) {
|
||||
|
||||
void Reactions::repaintCollected() {
|
||||
const auto now = crl::now();
|
||||
auto closest = 0;
|
||||
auto closest = crl::time();
|
||||
for (auto i = begin(_repaintItems); i != end(_repaintItems);) {
|
||||
if (i->second <= now) {
|
||||
_owner->requestItemRepaint(i->first);
|
||||
@@ -471,6 +473,35 @@ bool Reactions::sending(not_null<HistoryItem*> item) const {
|
||||
return _sentRequests.contains(item->fullId());
|
||||
}
|
||||
|
||||
bool Reactions::HasUnread(const MTPMessageReactions &data) {
|
||||
return data.match([&](const MTPDmessageReactions &data) {
|
||||
if (const auto &recent = data.vrecent_reactions()) {
|
||||
for (const auto &one : recent->v) {
|
||||
if (one.match([&](const MTPDmessagePeerReaction &data) {
|
||||
return data.is_unread();
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void Reactions::CheckUnknownForUnread(
|
||||
not_null<Session*> owner,
|
||||
const MTPMessage &message) {
|
||||
message.match([&](const MTPDmessage &data) {
|
||||
if (data.vreactions() && HasUnread(*data.vreactions())) {
|
||||
const auto peerId = peerFromMTP(data.vpeer_id());
|
||||
if (const auto history = owner->historyLoaded(peerId)) {
|
||||
owner->histories().requestDialogEntry(history);
|
||||
}
|
||||
}
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
MessageReactions::MessageReactions(not_null<HistoryItem*> item)
|
||||
: _item(item) {
|
||||
}
|
||||
@@ -491,7 +522,9 @@ void MessageReactions::add(const QString &reaction) {
|
||||
}
|
||||
const auto j = _recent.find(_chosen);
|
||||
if (j != end(_recent)) {
|
||||
j->second.erase(ranges::remove(j->second, self), end(j->second));
|
||||
j->second.erase(
|
||||
ranges::remove(j->second, self, &RecentReaction::peer),
|
||||
end(j->second));
|
||||
if (j->second.empty() || removed) {
|
||||
_recent.erase(j);
|
||||
}
|
||||
@@ -501,7 +534,7 @@ void MessageReactions::add(const QString &reaction) {
|
||||
if (!reaction.isEmpty()) {
|
||||
if (_item->canViewReactions()) {
|
||||
auto &list = _recent[reaction];
|
||||
list.insert(begin(list), self);
|
||||
list.insert(begin(list), RecentReaction{ self });
|
||||
}
|
||||
++_list[reaction];
|
||||
}
|
||||
@@ -514,24 +547,81 @@ void MessageReactions::remove() {
|
||||
add(QString());
|
||||
}
|
||||
|
||||
void MessageReactions::set(
|
||||
bool MessageReactions::checkIfChanged(
|
||||
const QVector<MTPReactionCount> &list,
|
||||
const QVector<MTPMessageUserReaction> &recent,
|
||||
const QVector<MTPMessagePeerReaction> &recent) const {
|
||||
auto &owner = _item->history()->owner();
|
||||
if (owner.reactions().sending(_item)) {
|
||||
// We'll apply non-stale data from the request response.
|
||||
return false;
|
||||
}
|
||||
auto existing = base::flat_set<QString>();
|
||||
for (const auto &count : list) {
|
||||
const auto changed = count.match([&](const MTPDreactionCount &data) {
|
||||
const auto reaction = qs(data.vreaction());
|
||||
const auto nowCount = data.vcount().v;
|
||||
const auto i = _list.find(reaction);
|
||||
const auto wasCount = (i != end(_list)) ? i->second : 0;
|
||||
if (wasCount != nowCount) {
|
||||
return true;
|
||||
}
|
||||
existing.emplace(reaction);
|
||||
return false;
|
||||
});
|
||||
if (changed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const auto &[reaction, count] : _list) {
|
||||
if (!existing.contains(reaction)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
auto parsed = base::flat_map<QString, std::vector<RecentReaction>>();
|
||||
for (const auto &reaction : recent) {
|
||||
reaction.match([&](const MTPDmessagePeerReaction &data) {
|
||||
const auto emoji = qs(data.vreaction());
|
||||
if (_list.contains(emoji)) {
|
||||
parsed[emoji].push_back(RecentReaction{
|
||||
.peer = owner.peer(peerFromMTP(data.vpeer_id())),
|
||||
.unread = data.is_unread(),
|
||||
.big = data.is_big(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return !ranges::equal(_recent, parsed, [](
|
||||
const auto &a,
|
||||
const auto &b) {
|
||||
return ranges::equal(a.second, b.second, [](
|
||||
const RecentReaction &a,
|
||||
const RecentReaction &b) {
|
||||
return (a.peer == b.peer) && (a.big == b.big);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool MessageReactions::change(
|
||||
const QVector<MTPReactionCount> &list,
|
||||
const QVector<MTPMessagePeerReaction> &recent,
|
||||
bool ignoreChosen) {
|
||||
auto &owner = _item->history()->owner();
|
||||
if (owner.reactions().sending(_item)) {
|
||||
// We'll apply non-stale data from the request response.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
auto changed = false;
|
||||
auto existing = base::flat_set<QString>();
|
||||
for (const auto &count : list) {
|
||||
count.match([&](const MTPDreactionCount &data) {
|
||||
const auto reaction = qs(data.vreaction());
|
||||
if (data.is_chosen() && !ignoreChosen) {
|
||||
if (_chosen != reaction) {
|
||||
if (!ignoreChosen) {
|
||||
if (data.is_chosen() && _chosen != reaction) {
|
||||
_chosen = reaction;
|
||||
changed = true;
|
||||
} else if (!data.is_chosen() && _chosen == reaction) {
|
||||
_chosen = QString();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
const auto nowCount = data.vcount().v;
|
||||
@@ -556,14 +646,16 @@ void MessageReactions::set(
|
||||
_chosen = QString();
|
||||
}
|
||||
}
|
||||
auto parsed = base::flat_map<
|
||||
QString,
|
||||
std::vector<not_null<UserData*>>>();
|
||||
auto parsed = base::flat_map<QString, std::vector<RecentReaction>>();
|
||||
for (const auto &reaction : recent) {
|
||||
reaction.match([&](const MTPDmessageUserReaction &data) {
|
||||
reaction.match([&](const MTPDmessagePeerReaction &data) {
|
||||
const auto emoji = qs(data.vreaction());
|
||||
if (_list.contains(emoji)) {
|
||||
parsed[emoji].push_back(owner.user(data.vuser_id()));
|
||||
parsed[emoji].push_back(RecentReaction{
|
||||
.peer = owner.peer(peerFromMTP(data.vpeer_id())),
|
||||
.unread = data.is_unread(),
|
||||
.big = data.is_big(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -571,10 +663,7 @@ void MessageReactions::set(
|
||||
_recent = std::move(parsed);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
owner.notifyItemDataChange(_item);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
const base::flat_map<QString, int> &MessageReactions::list() const {
|
||||
@@ -582,7 +671,7 @@ const base::flat_map<QString, int> &MessageReactions::list() const {
|
||||
}
|
||||
|
||||
auto MessageReactions::recent() const
|
||||
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> & {
|
||||
-> const base::flat_map<QString, std::vector<RecentReaction>> & {
|
||||
return _recent;
|
||||
}
|
||||
|
||||
@@ -590,6 +679,23 @@ bool MessageReactions::empty() const {
|
||||
return _list.empty();
|
||||
}
|
||||
|
||||
bool MessageReactions::hasUnread() const {
|
||||
for (auto &[emoji, list] : _recent) {
|
||||
if (ranges::contains(list, true, &RecentReaction::unread)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MessageReactions::markRead() {
|
||||
for (auto &[emoji, list] : _recent) {
|
||||
for (auto &reaction : list) {
|
||||
reaction.unread = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString MessageReactions::chosen() const {
|
||||
return _chosen;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,11 @@ public:
|
||||
|
||||
void updateAllInHistory(not_null<PeerData*> peer, bool enabled);
|
||||
|
||||
[[nodiscard]] static bool HasUnread(const MTPMessageReactions &data);
|
||||
static void CheckUnknownForUnread(
|
||||
not_null<Session*> owner,
|
||||
const MTPMessage &message);
|
||||
|
||||
private:
|
||||
struct ImageSet {
|
||||
QImage bottomInfo;
|
||||
@@ -125,28 +130,48 @@ private:
|
||||
|
||||
};
|
||||
|
||||
struct RecentReaction {
|
||||
not_null<PeerData*> peer;
|
||||
bool unread = false;
|
||||
bool big = false;
|
||||
|
||||
inline friend constexpr bool operator==(
|
||||
const RecentReaction &a,
|
||||
const RecentReaction &b) noexcept {
|
||||
return (a.peer.get() == b.peer.get())
|
||||
&& (a.unread == b.unread)
|
||||
&& (a.big == b.big);
|
||||
}
|
||||
};
|
||||
|
||||
class MessageReactions final {
|
||||
public:
|
||||
explicit MessageReactions(not_null<HistoryItem*> item);
|
||||
|
||||
void add(const QString &reaction);
|
||||
void remove();
|
||||
void set(
|
||||
bool change(
|
||||
const QVector<MTPReactionCount> &list,
|
||||
const QVector<MTPMessageUserReaction> &recent,
|
||||
const QVector<MTPMessagePeerReaction> &recent,
|
||||
bool ignoreChosen);
|
||||
[[nodiscard]] bool checkIfChanged(
|
||||
const QVector<MTPReactionCount> &list,
|
||||
const QVector<MTPMessagePeerReaction> &recent) const;
|
||||
[[nodiscard]] const base::flat_map<QString, int> &list() const;
|
||||
[[nodiscard]] auto recent() const
|
||||
-> const base::flat_map<QString, std::vector<not_null<UserData*>>> &;
|
||||
-> const base::flat_map<QString, std::vector<RecentReaction>> &;
|
||||
[[nodiscard]] QString chosen() const;
|
||||
[[nodiscard]] bool empty() const;
|
||||
|
||||
[[nodiscard]] bool hasUnread() const;
|
||||
void markRead();
|
||||
|
||||
private:
|
||||
const not_null<HistoryItem*> _item;
|
||||
|
||||
QString _chosen;
|
||||
base::flat_map<QString, int> _list;
|
||||
base::flat_map<QString, std::vector<not_null<UserData*>>> _recent;
|
||||
base::flat_map<QString, std::vector<RecentReaction>> _recent;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -320,7 +320,11 @@ void PeerData::paintUserpic(
|
||||
int y,
|
||||
int size) const {
|
||||
if (const auto userpic = currentUserpic(view)) {
|
||||
p.drawPixmap(x, y, userpic->pixCircled(size, size));
|
||||
const auto circled = Images::Option::RoundCircle;
|
||||
p.drawPixmap(
|
||||
x,
|
||||
y,
|
||||
userpic->pix(size, size, { .options = circled }));
|
||||
} else {
|
||||
ensureEmptyUserpic()->paint(p, x, y, x + size + x, size);
|
||||
}
|
||||
@@ -380,10 +384,14 @@ QPixmap PeerData::genUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int size) const {
|
||||
if (const auto userpic = currentUserpic(view)) {
|
||||
return userpic->pixCircled(size, size);
|
||||
const auto circle = Images::Option::RoundCircle;
|
||||
return userpic->pix(size, size, { .options = circle });
|
||||
}
|
||||
auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(
|
||||
QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
result.fill(Qt::transparent);
|
||||
{
|
||||
Painter p(&result);
|
||||
@@ -404,15 +412,13 @@ QImage PeerData::generateUserpicImage(
|
||||
ImageRoundRadius radius) const {
|
||||
if (const auto userpic = currentUserpic(view)) {
|
||||
const auto options = (radius == ImageRoundRadius::Ellipse)
|
||||
? (Images::Option::RoundedAll | Images::Option::Circled)
|
||||
? Images::Option::RoundCircle
|
||||
: (radius == ImageRoundRadius::None)
|
||||
? Images::Options()
|
||||
: (Images::Option::RoundedAll | Images::Option::RoundedSmall);
|
||||
? Images::Option()
|
||||
: Images::Option::RoundSmall;
|
||||
return userpic->pixNoCache(
|
||||
size,
|
||||
size,
|
||||
Images::Option::Smooth | options
|
||||
).toImage();
|
||||
{ size, size },
|
||||
{ .options = options }).toImage();
|
||||
}
|
||||
auto result = QImage(
|
||||
QSize(size, size),
|
||||
|
||||
@@ -36,7 +36,7 @@ using Data::kPhotoSizeCount;
|
||||
const Data::CloudFile &file) {
|
||||
return (v::is<WebFileLocation>(file.location.file().data)
|
||||
&& image.format() == QImage::Format_ARGB32)
|
||||
? Images::prepareOpaque(std::move(image))
|
||||
? Images::Opaque(std::move(image))
|
||||
: image;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,18 +41,13 @@ void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
|
||||
st::msgReplyBarSize.height(),
|
||||
h * st::msgReplyBarSize.height() / w);
|
||||
thumbSize *= cIntRetinaFactor();
|
||||
const auto prepareOptions = Images::Option::Smooth
|
||||
| Images::Option::TransparentBackground
|
||||
| options;
|
||||
options |= Images::Option::TransparentBackground;
|
||||
auto outerSize = st::msgReplyBarSize.height();
|
||||
auto bitmap = image->pixNoCache(
|
||||
thumbSize.width(),
|
||||
thumbSize.height(),
|
||||
prepareOptions,
|
||||
outerSize,
|
||||
outerSize);
|
||||
thumbSize,
|
||||
{ .options = options, .outer = { outerSize, outerSize } });
|
||||
_image = std::make_unique<Image>(bitmap.toImage());
|
||||
_good = ((options & Images::Option::Blurred) == 0);
|
||||
_good = ((options & Images::Option::Blur) == 0);
|
||||
}
|
||||
|
||||
Image *ReplyPreview::image(
|
||||
@@ -69,13 +64,13 @@ Image *ReplyPreview::image(
|
||||
}
|
||||
const auto thumbnail = _documentMedia->thumbnail();
|
||||
const auto option = _document->isVideoMessage()
|
||||
? Images::Option::Circled
|
||||
? Images::Option::RoundCircle
|
||||
: Images::Option::None;
|
||||
if (thumbnail) {
|
||||
prepare(thumbnail, option);
|
||||
} else if (!_image) {
|
||||
if (const auto image = _documentMedia->thumbnailInline()) {
|
||||
prepare(image, option | Images::Option::Blurred);
|
||||
prepare(image, option | Images::Option::Blur);
|
||||
}
|
||||
}
|
||||
if (_good || !_document->hasThumbnail()) {
|
||||
@@ -102,7 +97,7 @@ Image *ReplyPreview::image(
|
||||
prepare(large, Images::Option(0));
|
||||
} else if (!_image) {
|
||||
if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
prepare(blurred, Images::Option::Blurred);
|
||||
prepare(blurred, Images::Option::Blur);
|
||||
}
|
||||
}
|
||||
if (_good) {
|
||||
|
||||
@@ -64,7 +64,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/random.h"
|
||||
#include "facades.h" // Notify::switchInlineBotButtonReceived
|
||||
#include "app.h"
|
||||
#include "styles/style_boxes.h" // st::backgroundSize
|
||||
|
||||
namespace Data {
|
||||
@@ -291,7 +290,7 @@ void Session::clear() {
|
||||
_sentMessagesData.clear();
|
||||
cSetRecentInlineBots(RecentInlineBots());
|
||||
cSetRecentStickers(RecentStickerPack());
|
||||
App::clearMousedItems();
|
||||
HistoryView::Element::ClearGlobal();
|
||||
_histories->clearAll();
|
||||
_webpages.clear();
|
||||
_locations.clear();
|
||||
@@ -1304,7 +1303,14 @@ void Session::photoLoadFail(
|
||||
void Session::markMediaRead(not_null<const DocumentData*> document) {
|
||||
const auto i = _documentItems.find(document);
|
||||
if (i != end(_documentItems)) {
|
||||
_session->api().markMediaRead({ begin(i->second), end(i->second) });
|
||||
auto items = base::flat_set<not_null<HistoryItem*>>();
|
||||
items.reserve(i->second.size());
|
||||
for (const auto &item : i->second) {
|
||||
if (item->isUnreadMention() || item->isIncomingUnreadMedia()) {
|
||||
items.emplace(item);
|
||||
}
|
||||
}
|
||||
_session->api().markContentsRead(items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1480,6 +1486,12 @@ void Session::requestAnimationPlayInline(not_null<HistoryItem*> item) {
|
||||
}
|
||||
}
|
||||
|
||||
void Session::requestUnreadReactionsAnimation(not_null<HistoryItem*> item) {
|
||||
enumerateItemViews(item, [&](not_null<ViewElement*> view) {
|
||||
view->animateUnreadReactions();
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<not_null<HistoryItem*>> Session::animationPlayInlineRequest() const {
|
||||
return _animationPlayInlineRequest.events();
|
||||
}
|
||||
@@ -1611,6 +1623,7 @@ void Session::unregisterShownSpoiler(FullMsgId id) {
|
||||
void Session::hideShownSpoilers() {
|
||||
for (const auto &item : _shownSpoilers) {
|
||||
item->hideSpoilers();
|
||||
requestItemTextRefresh(item);
|
||||
}
|
||||
_shownSpoilers = base::flat_set<not_null<HistoryItem*>>();
|
||||
}
|
||||
@@ -1859,6 +1872,7 @@ void Session::updateEditedMessage(const MTPMessage &data) {
|
||||
return message(peerFromMTP(data.vpeer_id()), data.vid().v);
|
||||
});
|
||||
if (!existing) {
|
||||
Reactions::CheckUnknownForUnread(this, data);
|
||||
return;
|
||||
}
|
||||
if (existing->isLocalUpdateMedia() && data.type() == mtpc_message) {
|
||||
@@ -3673,20 +3687,22 @@ void Session::unregisterItemView(not_null<ViewElement*> view) {
|
||||
_views.erase(i);
|
||||
}
|
||||
}
|
||||
if (App::hoveredItem() == view) {
|
||||
App::hoveredItem(nullptr);
|
||||
|
||||
using namespace HistoryView;
|
||||
if (Element::Hovered() == view) {
|
||||
Element::Hovered(nullptr);
|
||||
}
|
||||
if (App::pressedItem() == view) {
|
||||
App::pressedItem(nullptr);
|
||||
if (Element::Pressed() == view) {
|
||||
Element::Pressed(nullptr);
|
||||
}
|
||||
if (App::hoveredLinkItem() == view) {
|
||||
App::hoveredLinkItem(nullptr);
|
||||
if (Element::HoveredLink() == view) {
|
||||
Element::HoveredLink(nullptr);
|
||||
}
|
||||
if (App::pressedLinkItem() == view) {
|
||||
App::pressedLinkItem(nullptr);
|
||||
if (Element::PressedLink() == view) {
|
||||
Element::PressedLink(nullptr);
|
||||
}
|
||||
if (App::mousedItem() == view) {
|
||||
App::mousedItem(nullptr);
|
||||
if (Element::Moused() == view) {
|
||||
Element::Moused(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -253,6 +253,7 @@ public:
|
||||
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> itemViewRefreshRequest() const;
|
||||
void requestItemTextRefresh(not_null<HistoryItem*> item);
|
||||
void requestAnimationPlayInline(not_null<HistoryItem*> item);
|
||||
void requestUnreadReactionsAnimation(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> animationPlayInlineRequest() const;
|
||||
void notifyHistoryUnloaded(not_null<const History*> history);
|
||||
[[nodiscard]] rpl::producer<not_null<const History*>> historyUnloaded() const;
|
||||
|
||||
@@ -234,52 +234,53 @@ enum class MessageFlag : uint32 {
|
||||
Outgoing = (1U << 11),
|
||||
Pinned = (1U << 12),
|
||||
MediaIsUnread = (1U << 13),
|
||||
MentionsMe = (1U << 14),
|
||||
IsOrWasScheduled = (1U << 15),
|
||||
NoForwards = (1U << 16),
|
||||
HasUnreadReaction = (1U << 14),
|
||||
MentionsMe = (1U << 15),
|
||||
IsOrWasScheduled = (1U << 16),
|
||||
NoForwards = (1U << 17),
|
||||
|
||||
// Needs to return back to inline mode.
|
||||
HasSwitchInlineButton = (1U << 17),
|
||||
HasSwitchInlineButton = (1U << 18),
|
||||
|
||||
// For "shared links" indexing.
|
||||
HasTextLinks = (1U << 18),
|
||||
HasTextLinks = (1U << 19),
|
||||
|
||||
// Group / channel create or migrate service message.
|
||||
IsGroupEssential = (1U << 19),
|
||||
IsGroupEssential = (1U << 20),
|
||||
|
||||
// Edited media is generated on the client
|
||||
// and should not update media from server.
|
||||
IsLocalUpdateMedia = (1U << 20),
|
||||
IsLocalUpdateMedia = (1U << 21),
|
||||
|
||||
// Sent from inline bot, need to re-set media when sent.
|
||||
FromInlineBot = (1U << 21),
|
||||
FromInlineBot = (1U << 22),
|
||||
|
||||
// Generated on the client side and should be unread.
|
||||
ClientSideUnread = (1U << 22),
|
||||
ClientSideUnread = (1U << 23),
|
||||
|
||||
// In a supergroup.
|
||||
HasAdminBadge = (1U << 23),
|
||||
HasAdminBadge = (1U << 24),
|
||||
|
||||
// Outgoing message that is being sent.
|
||||
BeingSent = (1U << 24),
|
||||
BeingSent = (1U << 25),
|
||||
|
||||
// Outgoing message and failed to be sent.
|
||||
SendingFailed = (1U << 25),
|
||||
SendingFailed = (1U << 26),
|
||||
|
||||
// No media and only a several emoji text.
|
||||
IsolatedEmoji = (1U << 26),
|
||||
IsolatedEmoji = (1U << 27),
|
||||
|
||||
// Message existing in the message history.
|
||||
HistoryEntry = (1U << 27),
|
||||
HistoryEntry = (1U << 28),
|
||||
|
||||
// Local message, not existing on the server.
|
||||
Local = (1U << 28),
|
||||
Local = (1U << 29),
|
||||
|
||||
// Fake message for some UI element.
|
||||
FakeHistoryItem = (1U << 29),
|
||||
FakeHistoryItem = (1U << 30),
|
||||
|
||||
// Contact sign-up message, notification should be skipped for Silent.
|
||||
IsContactSignUp = (1U << 30),
|
||||
IsContactSignUp = (1U << 31),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
@@ -994,7 +994,7 @@ std::vector<not_null<DocumentData*>> Stickers::getListByEmoji(
|
||||
const auto CreateSortKey = [&](
|
||||
not_null<DocumentData*> document,
|
||||
int base) {
|
||||
if (document->sticker() && document->sticker()->animated) {
|
||||
if (document->sticker() && document->sticker()->isAnimated()) {
|
||||
base += kSlice;
|
||||
}
|
||||
return TimeId(base + int((document->id ^ seed) % kSlice));
|
||||
@@ -1005,7 +1005,7 @@ std::vector<not_null<DocumentData*>> Stickers::getListByEmoji(
|
||||
auto myCounter = 0;
|
||||
const auto CreateMySortKey = [&](not_null<DocumentData*> document) {
|
||||
auto base = kSlice * 6;
|
||||
if (!document->sticker() || !document->sticker()->animated) {
|
||||
if (!document->sticker() || !document->sticker()->isAnimated()) {
|
||||
base -= kSlice;
|
||||
}
|
||||
return (base - (++myCounter));
|
||||
@@ -1019,7 +1019,7 @@ std::vector<not_null<DocumentData*>> Stickers::getListByEmoji(
|
||||
const auto InstallDateAdjusted = [&](
|
||||
TimeId date,
|
||||
not_null<DocumentData*> document) {
|
||||
return (document->sticker() && document->sticker()->animated)
|
||||
return (document->sticker() && document->sticker()->isAnimated())
|
||||
? date
|
||||
: date / 2;
|
||||
};
|
||||
|
||||
@@ -49,7 +49,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
|
||||
return (data.is_archived() ? Flag::Archived : Flag())
|
||||
| (data.is_official() ? Flag::Official : Flag())
|
||||
| (data.is_masks() ? Flag::Masks : Flag())
|
||||
| (data.vinstalled_date() ? Flag::Installed : Flag());
|
||||
| (data.vinstalled_date() ? Flag::Installed : Flag())
|
||||
| (data.is_gifs() ? Flag::Webm : Flag());
|
||||
}
|
||||
|
||||
StickersSet::StickersSet(
|
||||
|
||||
@@ -54,6 +54,7 @@ enum class StickersSetFlag {
|
||||
Featured = (1 << 5),
|
||||
Unread = (1 << 6),
|
||||
Special = (1 << 7),
|
||||
Webm = (1 << 8),
|
||||
};
|
||||
inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
|
||||
using StickersSetFlags = base::flags<StickersSetFlag>;
|
||||
|
||||
@@ -302,3 +302,10 @@ dialogsMiniPreviewRadius: 2px;
|
||||
dialogsMiniPreviewSkip: 2px;
|
||||
dialogsMiniPreviewRight: 3px;
|
||||
dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }};
|
||||
|
||||
dialogsUnreadMention: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }};
|
||||
dialogsUnreadMentionOver: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }};
|
||||
dialogsUnreadMentionActive: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }};
|
||||
dialogsUnreadReaction: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFg }};
|
||||
dialogsUnreadReactionOver: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgOver }};
|
||||
dialogsUnreadReactionActive: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgActive }};
|
||||
|
||||
@@ -50,7 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/unread_badge.h"
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
@@ -47,12 +47,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
@@ -595,7 +594,9 @@ void Widget::checkUpdateStatus() {
|
||||
|
||||
using Checker = Core::UpdateChecker;
|
||||
if (Checker().state() == Checker::State::Ready) {
|
||||
if (_updateTelegram) return;
|
||||
if (_updateTelegram) {
|
||||
return;
|
||||
}
|
||||
_updateTelegram.create(
|
||||
this,
|
||||
tr::lng_update_telegram(tr::now),
|
||||
@@ -605,11 +606,15 @@ void Widget::checkUpdateStatus() {
|
||||
_updateTelegram->show();
|
||||
_updateTelegram->setClickedCallback([] {
|
||||
Core::checkReadyUpdate();
|
||||
App::restart();
|
||||
Core::Restart();
|
||||
});
|
||||
_connecting->raise();
|
||||
if (_connecting) {
|
||||
_connecting->raise();
|
||||
}
|
||||
} else {
|
||||
if (!_updateTelegram) return;
|
||||
if (!_updateTelegram) {
|
||||
return;
|
||||
}
|
||||
_updateTelegram.destroy();
|
||||
}
|
||||
updateControlsGeometry();
|
||||
|
||||
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "history/view/history_view_send_action.h"
|
||||
#include "history/view/history_view_item_preview.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
@@ -84,38 +85,72 @@ void PaintNarrowCounter(
|
||||
bool displayUnreadCounter,
|
||||
bool displayUnreadMark,
|
||||
bool displayMentionBadge,
|
||||
bool displayReactionBadge,
|
||||
int unreadCount,
|
||||
bool selected,
|
||||
bool active,
|
||||
bool unreadMuted,
|
||||
bool mentionMuted) {
|
||||
bool mentionOrReactionMuted) {
|
||||
auto skipBeforeMention = 0;
|
||||
if (displayUnreadCounter || displayUnreadMark) {
|
||||
auto counter = (unreadCount > 0)
|
||||
const auto counter = (unreadCount > 0)
|
||||
? QString::number(unreadCount)
|
||||
: QString();
|
||||
const auto allowDigits = displayMentionBadge ? 1 : 3;
|
||||
auto unreadRight = st::dialogsPadding.x() + st::dialogsPhotoSize;
|
||||
auto unreadTop = st::dialogsPadding.y() + st::dialogsPhotoSize - st::dialogsUnreadHeight;
|
||||
auto unreadWidth = 0;
|
||||
const auto allowDigits = (displayMentionBadge
|
||||
|| displayReactionBadge)
|
||||
? 1
|
||||
: 3;
|
||||
const auto unreadRight = st::dialogsPadding.x()
|
||||
+ st::dialogsPhotoSize;
|
||||
const auto unreadTop = st::dialogsPadding.y()
|
||||
+ st::dialogsPhotoSize
|
||||
- st::dialogsUnreadHeight;
|
||||
|
||||
UnreadBadgeStyle st;
|
||||
st.active = active;
|
||||
st.selected = selected;
|
||||
st.muted = unreadMuted;
|
||||
paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth, allowDigits);
|
||||
skipBeforeMention += unreadWidth + st.padding;
|
||||
const auto badge = PaintUnreadBadge(
|
||||
p,
|
||||
counter,
|
||||
unreadRight,
|
||||
unreadTop,
|
||||
st,
|
||||
allowDigits);
|
||||
skipBeforeMention += badge.width() + st.padding;
|
||||
}
|
||||
if (displayMentionBadge) {
|
||||
auto counter = qsl("@");
|
||||
auto unreadRight = st::dialogsPadding.x() + st::dialogsPhotoSize - skipBeforeMention;
|
||||
auto unreadTop = st::dialogsPadding.y() + st::dialogsPhotoSize - st::dialogsUnreadHeight;
|
||||
auto unreadWidth = 0;
|
||||
if (displayMentionBadge || displayReactionBadge) {
|
||||
const auto counter = QString();
|
||||
const auto unreadRight = st::dialogsPadding.x()
|
||||
+ st::dialogsPhotoSize
|
||||
- skipBeforeMention;
|
||||
const auto unreadTop = st::dialogsPadding.y()
|
||||
+ st::dialogsPhotoSize
|
||||
- st::dialogsUnreadHeight;
|
||||
|
||||
UnreadBadgeStyle st;
|
||||
st.active = active;
|
||||
st.muted = mentionMuted;
|
||||
st.selected = selected;
|
||||
st.muted = mentionOrReactionMuted;
|
||||
st.padding = 0;
|
||||
st.textTop = 0;
|
||||
paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth);
|
||||
const auto badge = PaintUnreadBadge(
|
||||
p,
|
||||
counter,
|
||||
unreadRight,
|
||||
unreadTop,
|
||||
st);
|
||||
(displayMentionBadge
|
||||
? (st.active
|
||||
? st::dialogsUnreadMentionActive
|
||||
: st.selected
|
||||
? st::dialogsUnreadMentionOver
|
||||
: st::dialogsUnreadMention)
|
||||
: (st.active
|
||||
? st::dialogsUnreadReactionActive
|
||||
: st.selected
|
||||
? st::dialogsUnreadReactionOver
|
||||
: st::dialogsUnreadReaction)).paintInCenter(p, badge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,49 +162,90 @@ int PaintWideCounter(
|
||||
bool displayUnreadCounter,
|
||||
bool displayUnreadMark,
|
||||
bool displayMentionBadge,
|
||||
bool displayReactionBadge,
|
||||
bool displayPinnedIcon,
|
||||
int unreadCount,
|
||||
bool active,
|
||||
bool selected,
|
||||
bool unreadMuted,
|
||||
bool mentionMuted) {
|
||||
bool mentionOrReactionMuted) {
|
||||
const auto initial = availableWidth;
|
||||
auto hadOneBadge = false;
|
||||
if (displayUnreadCounter || displayUnreadMark) {
|
||||
auto counter = (unreadCount > 0)
|
||||
const auto counter = (unreadCount > 0)
|
||||
? QString::number(unreadCount)
|
||||
: QString();
|
||||
auto unreadRight = fullWidth - st::dialogsPadding.x();
|
||||
auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
|
||||
auto unreadWidth = 0;
|
||||
const auto unreadRight = fullWidth
|
||||
- st::dialogsPadding.x();
|
||||
const auto unreadTop = texttop
|
||||
+ st::dialogsTextFont->ascent
|
||||
- st::dialogsUnreadFont->ascent
|
||||
- (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
|
||||
|
||||
UnreadBadgeStyle st;
|
||||
st.active = active;
|
||||
st.selected = selected;
|
||||
st.muted = unreadMuted;
|
||||
paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth);
|
||||
availableWidth -= unreadWidth + st.padding;
|
||||
const auto badge = PaintUnreadBadge(
|
||||
p,
|
||||
counter,
|
||||
unreadRight,
|
||||
unreadTop,
|
||||
st);
|
||||
availableWidth -= badge.width() + st.padding;
|
||||
|
||||
hadOneBadge = true;
|
||||
} else if (displayPinnedIcon) {
|
||||
auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon));
|
||||
icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth);
|
||||
const auto &icon = active
|
||||
? st::dialogsPinnedIconActive
|
||||
: selected
|
||||
? st::dialogsPinnedIconOver
|
||||
: st::dialogsPinnedIcon;
|
||||
icon.paint(
|
||||
p,
|
||||
fullWidth - st::dialogsPadding.x() - icon.width(),
|
||||
texttop,
|
||||
fullWidth);
|
||||
availableWidth -= icon.width() + st::dialogsUnreadPadding;
|
||||
|
||||
hadOneBadge = true;
|
||||
}
|
||||
if (displayMentionBadge) {
|
||||
auto counter = qsl("@");
|
||||
auto unreadRight = fullWidth - st::dialogsPadding.x() - (initial - availableWidth);
|
||||
auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
|
||||
auto unreadWidth = 0;
|
||||
if (displayMentionBadge || displayReactionBadge) {
|
||||
const auto counter = QString();
|
||||
const auto unreadRight = fullWidth
|
||||
- st::dialogsPadding.x()
|
||||
- (initial - availableWidth);
|
||||
const auto unreadTop = texttop
|
||||
+ st::dialogsTextFont->ascent
|
||||
- st::dialogsUnreadFont->ascent
|
||||
- (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
|
||||
|
||||
UnreadBadgeStyle st;
|
||||
st.active = active;
|
||||
st.muted = mentionMuted;
|
||||
st.selected = selected;
|
||||
st.muted = mentionOrReactionMuted;
|
||||
st.padding = 0;
|
||||
st.textTop = 0;
|
||||
paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth);
|
||||
availableWidth -= unreadWidth + st.padding + (hadOneBadge ? st::dialogsUnreadPadding : 0);
|
||||
const auto badge = PaintUnreadBadge(
|
||||
p,
|
||||
counter,
|
||||
unreadRight,
|
||||
unreadTop,
|
||||
st);
|
||||
(displayMentionBadge
|
||||
? (st.active
|
||||
? st::dialogsUnreadMentionActive
|
||||
: st.selected
|
||||
? st::dialogsUnreadMentionOver
|
||||
: st::dialogsUnreadMention)
|
||||
: (st.active
|
||||
? st::dialogsUnreadReactionActive
|
||||
: st.selected
|
||||
? st::dialogsUnreadReactionOver
|
||||
: st::dialogsUnreadReaction)).paintInCenter(p, badge);
|
||||
availableWidth -= badge.width()
|
||||
+ st.padding
|
||||
+ (hadOneBadge ? st::dialogsUnreadPadding : 0);
|
||||
}
|
||||
return availableWidth;
|
||||
}
|
||||
@@ -572,35 +648,7 @@ QImage colorizeCircleHalf(UnreadBadgeSizeData *data, int size, int half, int xof
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namepsace
|
||||
|
||||
const style::icon *ChatTypeIcon(
|
||||
not_null<PeerData*> peer,
|
||||
bool active,
|
||||
bool selected) {
|
||||
if (peer->isChat() || peer->isMegagroup()) {
|
||||
return &(active
|
||||
? st::dialogsChatIconActive
|
||||
: (selected ? st::dialogsChatIconOver : st::dialogsChatIcon));
|
||||
} else if (peer->isChannel()) {
|
||||
return &(active
|
||||
? st::dialogsChannelIconActive
|
||||
: (selected
|
||||
? st::dialogsChannelIconOver
|
||||
: st::dialogsChannelIcon));
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
if (ShowUserBotIcon(user)) {
|
||||
return &(active
|
||||
? st::dialogsBotIconActive
|
||||
: (selected
|
||||
? st::dialogsBotIconOver
|
||||
: st::dialogsBotIcon));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st) {
|
||||
void PaintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st) {
|
||||
Assert(rect.height() == st.size);
|
||||
|
||||
int index = (st.muted ? 0x03 : 0x00) + (st.active ? 0x02 : (st.selected ? 0x01 : 0x00));
|
||||
@@ -634,46 +682,77 @@ void paintUnreadBadge(Painter &p, const QRect &rect, const UnreadBadgeStyle &st)
|
||||
p.drawPixmap(rect.x() + sizehalf + bar, rect.y(), badgeData->right[index]);
|
||||
}
|
||||
|
||||
} // namepsace
|
||||
|
||||
const style::icon *ChatTypeIcon(
|
||||
not_null<PeerData*> peer,
|
||||
bool active,
|
||||
bool selected) {
|
||||
if (peer->isChat() || peer->isMegagroup()) {
|
||||
return &(active
|
||||
? st::dialogsChatIconActive
|
||||
: (selected ? st::dialogsChatIconOver : st::dialogsChatIcon));
|
||||
} else if (peer->isChannel()) {
|
||||
return &(active
|
||||
? st::dialogsChannelIconActive
|
||||
: (selected
|
||||
? st::dialogsChannelIconOver
|
||||
: st::dialogsChannelIcon));
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
if (ShowUserBotIcon(user)) {
|
||||
return &(active
|
||||
? st::dialogsBotIconActive
|
||||
: (selected
|
||||
? st::dialogsBotIconOver
|
||||
: st::dialogsBotIcon));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UnreadBadgeStyle::UnreadBadgeStyle()
|
||||
: size(st::dialogsUnreadHeight)
|
||||
, padding(st::dialogsUnreadPadding)
|
||||
, font(st::dialogsUnreadFont) {
|
||||
}
|
||||
|
||||
void paintUnreadCount(
|
||||
QRect PaintUnreadBadge(
|
||||
Painter &p,
|
||||
const QString &unreadCount,
|
||||
int x,
|
||||
int y,
|
||||
const UnreadBadgeStyle &st,
|
||||
int *outUnreadWidth,
|
||||
int allowDigits) {
|
||||
const auto text = (allowDigits > 0) && (unreadCount.size() > allowDigits + 1)
|
||||
? qsl("..") + unreadCount.mid(unreadCount.size() - allowDigits)
|
||||
: unreadCount;
|
||||
|
||||
int unreadWidth = st.font->width(text);
|
||||
int unreadRectWidth = unreadWidth + 2 * st.padding;
|
||||
int unreadRectHeight = st.size;
|
||||
accumulate_max(unreadRectWidth, unreadRectHeight);
|
||||
const auto unreadRectHeight = st.size;
|
||||
const auto unreadWidth = st.font->width(text);
|
||||
const auto unreadRectWidth = std::max(
|
||||
unreadWidth + 2 * st.padding,
|
||||
unreadRectHeight);
|
||||
|
||||
int unreadRectLeft = x;
|
||||
if ((st.align & Qt::AlignHorizontal_Mask) & style::al_center) {
|
||||
unreadRectLeft = (x - unreadRectWidth) / 2;
|
||||
} else if ((st.align & Qt::AlignHorizontal_Mask) & style::al_right) {
|
||||
unreadRectLeft = x - unreadRectWidth;
|
||||
}
|
||||
int unreadRectTop = y;
|
||||
if (outUnreadWidth) {
|
||||
*outUnreadWidth = unreadRectWidth;
|
||||
}
|
||||
const auto unreadRectLeft = ((st.align & Qt::AlignHorizontal_Mask) & style::al_center)
|
||||
? (x - unreadRectWidth) / 2
|
||||
: ((st.align & Qt::AlignHorizontal_Mask) & style::al_right)
|
||||
? (x - unreadRectWidth)
|
||||
: x;
|
||||
const auto unreadRectTop = y;
|
||||
|
||||
paintUnreadBadge(p, QRect(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), st);
|
||||
const auto badge = QRect(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight);
|
||||
PaintUnreadBadge(p, badge, st);
|
||||
|
||||
auto textTop = st.textTop ? st.textTop : (unreadRectHeight - st.font->height) / 2;
|
||||
const auto textTop = st.textTop ? st.textTop : (unreadRectHeight - st.font->height) / 2;
|
||||
p.setFont(st.font);
|
||||
p.setPen(st.active ? st::dialogsUnreadFgActive : (st.selected ? st::dialogsUnreadFgOver : st::dialogsUnreadFg));
|
||||
p.setPen(st.active
|
||||
? st::dialogsUnreadFgActive
|
||||
: st.selected
|
||||
? st::dialogsUnreadFgOver
|
||||
: st::dialogsUnreadFg);
|
||||
p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st.font->ascent, text);
|
||||
|
||||
return badge;
|
||||
}
|
||||
|
||||
void RowPainter::paint(
|
||||
@@ -690,7 +769,7 @@ void RowPainter::paint(
|
||||
const auto unreadCount = entry->chatListUnreadCount();
|
||||
const auto unreadMark = entry->chatListUnreadMark();
|
||||
const auto unreadMuted = entry->chatListMutedBadge();
|
||||
const auto mentionMuted = (entry->folder() != nullptr);
|
||||
const auto mentionOrReactionMuted = (entry->folder() != nullptr);
|
||||
const auto item = entry->chatListMessage();
|
||||
const auto cloudDraft = [&]() -> const Data::Draft*{
|
||||
if (history && (!item || (!unreadCount && !unreadMark))) {
|
||||
@@ -717,8 +796,10 @@ void RowPainter::paint(
|
||||
: QDateTime();
|
||||
}();
|
||||
const auto displayMentionBadge = history
|
||||
? history->hasUnreadMentions()
|
||||
: false;
|
||||
&& history->unreadMentions().has();
|
||||
const auto displayReactionBadge = !displayMentionBadge
|
||||
&& history
|
||||
&& history->unreadReactions().has();
|
||||
const auto displayUnreadCounter = [&] {
|
||||
if (displayMentionBadge
|
||||
&& unreadCount == 1
|
||||
@@ -734,6 +815,7 @@ void RowPainter::paint(
|
||||
&& unreadMark;
|
||||
const auto displayPinnedIcon = !displayUnreadCounter
|
||||
&& !displayMentionBadge
|
||||
&& !displayReactionBadge
|
||||
&& !displayUnreadMark
|
||||
&& entry->isPinnedDialog(filterId)
|
||||
&& (filterId || !entry->fixedOnTopIndex());
|
||||
@@ -762,12 +844,13 @@ void RowPainter::paint(
|
||||
displayUnreadCounter,
|
||||
displayUnreadMark,
|
||||
displayMentionBadge,
|
||||
displayReactionBadge,
|
||||
displayPinnedIcon,
|
||||
unreadCount,
|
||||
active,
|
||||
selected,
|
||||
unreadMuted,
|
||||
mentionMuted);
|
||||
mentionOrReactionMuted);
|
||||
const auto &color = active
|
||||
? st::dialogsTextFgServiceActive
|
||||
: (selected
|
||||
@@ -806,10 +889,12 @@ void RowPainter::paint(
|
||||
displayUnreadCounter,
|
||||
displayUnreadMark,
|
||||
displayMentionBadge,
|
||||
displayReactionBadge,
|
||||
unreadCount,
|
||||
selected,
|
||||
active,
|
||||
unreadMuted,
|
||||
mentionMuted);
|
||||
mentionOrReactionMuted);
|
||||
};
|
||||
paintRow(
|
||||
p,
|
||||
@@ -877,9 +962,12 @@ void RowPainter::paint(
|
||||
const auto unreadMark = displayUnreadInfo
|
||||
&& history->chatListUnreadMark();
|
||||
const auto unreadMuted = history->chatListMutedBadge();
|
||||
const auto mentionMuted = (history->folder() != nullptr);
|
||||
const auto mentionOrReactionMuted = (history->folder() != nullptr);
|
||||
const auto displayMentionBadge = displayUnreadInfo
|
||||
&& history->hasUnreadMentions();
|
||||
&& history->unreadMentions().has();
|
||||
const auto displayReactionBadge = displayUnreadInfo
|
||||
&& !displayMentionBadge
|
||||
&& history->unreadReactions().has();
|
||||
const auto displayUnreadCounter = (unreadCount > 0);
|
||||
const auto displayUnreadMark = !displayUnreadCounter
|
||||
&& !displayMentionBadge
|
||||
@@ -898,12 +986,13 @@ void RowPainter::paint(
|
||||
displayUnreadCounter,
|
||||
displayUnreadMark,
|
||||
displayMentionBadge,
|
||||
displayReactionBadge,
|
||||
displayPinnedIcon,
|
||||
unreadCount,
|
||||
active,
|
||||
selected,
|
||||
unreadMuted,
|
||||
mentionMuted);
|
||||
mentionOrReactionMuted);
|
||||
|
||||
const auto itemRect = QRect(
|
||||
nameleft,
|
||||
@@ -924,10 +1013,12 @@ void RowPainter::paint(
|
||||
displayUnreadCounter,
|
||||
displayUnreadMark,
|
||||
displayMentionBadge,
|
||||
displayReactionBadge,
|
||||
unreadCount,
|
||||
selected,
|
||||
active,
|
||||
unreadMuted,
|
||||
mentionMuted);
|
||||
mentionOrReactionMuted);
|
||||
};
|
||||
const auto showSavedMessages = history->peer->isSelf()
|
||||
&& !row->searchInChat();
|
||||
@@ -1018,13 +1109,12 @@ void PaintCollapsedRow(
|
||||
const auto unreadRight = fullWidth - st::dialogsPadding.x();
|
||||
UnreadBadgeStyle st;
|
||||
st.muted = true;
|
||||
paintUnreadCount(
|
||||
PaintUnreadBadge(
|
||||
p,
|
||||
QString::number(unread),
|
||||
unreadRight,
|
||||
unreadTop,
|
||||
st,
|
||||
nullptr);
|
||||
st);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,13 +83,12 @@ struct UnreadBadgeStyle {
|
||||
UnreadBadgeSize sizeId = UnreadBadgeInDialogs;
|
||||
style::font font;
|
||||
};
|
||||
void paintUnreadCount(
|
||||
QRect PaintUnreadBadge(
|
||||
Painter &p,
|
||||
const QString &t,
|
||||
int x,
|
||||
int y,
|
||||
const UnreadBadgeStyle &st,
|
||||
int *outUnreadWidth = nullptr,
|
||||
int allowDigits = 0);
|
||||
|
||||
void clearUnreadBadgesCache();
|
||||
|
||||
@@ -90,9 +90,7 @@ PhotoEditorContent::PhotoEditorContent(
|
||||
|
||||
p.setTransform(_imageMatrix);
|
||||
|
||||
p.drawPixmap(
|
||||
_imageRect,
|
||||
_photo->pix(_imageRect.width(), _imageRect.height()));
|
||||
p.drawPixmap(_imageRect, _photo->pix(_imageRect.size()));
|
||||
}, lifetime());
|
||||
|
||||
setupDragArea();
|
||||
|
||||
@@ -90,12 +90,10 @@ QImage EdgeButton::rounded(std::optional<QColor> color) const {
|
||||
result.setDevicePixelRatio(cIntRetinaFactor());
|
||||
result.fill(color.value_or(Qt::white));
|
||||
|
||||
using Option = Images::Option;
|
||||
const auto options = Option::Smooth
|
||||
| Option::RoundedLarge
|
||||
| (_left ? Option::RoundedTopLeft : Option::RoundedTopRight)
|
||||
| (_left ? Option::RoundedBottomLeft : Option::RoundedBottomRight);
|
||||
return Images::prepare(std::move(result), 0, 0, options, 0, 0);
|
||||
const auto parts = RectPart::None
|
||||
| (_left ? RectPart::TopLeft : RectPart::TopRight)
|
||||
| (_left ? RectPart::BottomLeft : RectPart::BottomRight);
|
||||
return Images::Round(std::move(result), ImageRoundRadius::Large, parts);
|
||||
}
|
||||
|
||||
QImage EdgeButton::prepareRippleMask() const {
|
||||
@@ -151,10 +149,9 @@ ButtonBar::ButtonBar(
|
||||
result.setDevicePixelRatio(cIntRetinaFactor());
|
||||
result.fill(bg->c);
|
||||
|
||||
const auto options = Images::Option::Smooth
|
||||
| Images::Option::RoundedLarge
|
||||
| Images::Option::RoundedAll;
|
||||
_roundedBg = Images::prepare(std::move(result), 0, 0, options, 0, 0);
|
||||
_roundedBg = Images::Round(
|
||||
std::move(result),
|
||||
ImageRoundRadius::Large);
|
||||
}, lifetime());
|
||||
|
||||
paintRequest(
|
||||
|
||||
@@ -34,11 +34,11 @@ ItemSticker::ItemSticker(
|
||||
}
|
||||
const auto updateThumbnail = [=] {
|
||||
const auto guard = gsl::finally([&] {
|
||||
setAspectRatio(_pixmap.isNull()
|
||||
? 1.0
|
||||
: (_pixmap.height() / float64(_pixmap.width())));
|
||||
if (_pixmap.isNull()) {
|
||||
setAspectRatio(1.);
|
||||
}
|
||||
});
|
||||
if (stickerData->animated) {
|
||||
if (stickerData->isLottie()) {
|
||||
_lottie.player = ChatHelpers::LottiePlayerFromDocument(
|
||||
_mediaView.get(),
|
||||
ChatHelpers::StickerLottieSize::MessageHistory,
|
||||
@@ -54,16 +54,33 @@ ItemSticker::ItemSticker(
|
||||
update();
|
||||
}, _lottie.lifetime);
|
||||
return true;
|
||||
} else if (stickerData->isWebm()
|
||||
&& !_document->dimensions.isEmpty()) {
|
||||
const auto callback = [=](::Media::Clip::Notification) {
|
||||
const auto size = _document->dimensions;
|
||||
if (_webm && _webm->ready() && !_webm->started()) {
|
||||
_webm->start({ .frame = size, .keepAlpha = true });
|
||||
}
|
||||
if (_webm && _webm->started()) {
|
||||
updatePixmap(_webm->current(
|
||||
{ .frame = size, .keepAlpha = true },
|
||||
0));
|
||||
_webm = nullptr;
|
||||
}
|
||||
};
|
||||
_webm = ::Media::Clip::MakeReader(
|
||||
_mediaView->owner()->location(),
|
||||
_mediaView->bytes(),
|
||||
callback);
|
||||
return true;
|
||||
}
|
||||
const auto sticker = _mediaView->getStickerLarge();
|
||||
if (!sticker) {
|
||||
return false;
|
||||
}
|
||||
auto pixmap = sticker->pixNoCache(
|
||||
sticker->width() * cIntRetinaFactor(),
|
||||
sticker->height() * cIntRetinaFactor(),
|
||||
Images::Option::Smooth);
|
||||
pixmap.setDevicePixelRatio(cRetinaFactor());
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto pixmap = sticker->pixNoCache(sticker->size() * ratio);
|
||||
pixmap.setDevicePixelRatio(ratio);
|
||||
updatePixmap(std::move(pixmap));
|
||||
return true;
|
||||
};
|
||||
@@ -85,6 +102,9 @@ void ItemSticker::updatePixmap(QPixmap &&pixmap) {
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
if (!_pixmap.isNull()) {
|
||||
setAspectRatio(_pixmap.height() / float64(_pixmap.width()));
|
||||
}
|
||||
}
|
||||
|
||||
void ItemSticker::paint(
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "editor/scene/scene_item_base.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
@@ -47,6 +48,7 @@ private:
|
||||
std::unique_ptr<Lottie::SinglePlayer> player;
|
||||
rpl::lifetime lifetime;
|
||||
} _lottie;
|
||||
::Media::Clip::ReaderPointer _webm;
|
||||
QPixmap _pixmap;
|
||||
|
||||
rpl::lifetime _loadingLifetime;
|
||||
|
||||
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "export/output/export_output_result.h"
|
||||
#include "export/output/export_output_stats.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "styles/style_export.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
|
||||