Compare commits

...

125 Commits

Author SHA1 Message Date
John Preston
eccaca8808 Version 3.5: Fix build with GCC. 2022-02-01 01:51:17 +03:00
John Preston
36e6c76b59 Version 3.5: Merge some lib_ui fixes. 2022-02-01 01:27:33 +03:00
John Preston
06c35f5b51 Version 3.5.
- Use a new type of detailed stickers with smooth animations.
- Create new sets by sending .webm videos to @stickers.
- Bring your custom animated stickers from other apps.
- See smaller, compact animations when reacting to messages.
- See real-time animations in chat when a user reacts to your message.
- React with additional emoji expressing love,
appreciation, anger or surprise.
- Tap the new button in chats to jump to your messages
that have unseen reactions.
- Watch the animations for unseen reactions play
when you hit the button.
- The app will warn you before closing
if you are uploading photos or files to a chat.
- Enjoy better screencast quality in video chats.
2022-02-01 00:51:00 +03:00
John Preston
ca21b7efae Allow disable Cmd+Q warning on macOS. 2022-02-01 00:43:20 +03:00
John Preston
9eb20ede33 Fix click area of web page preview article photo. 2022-02-01 00:26:52 +03:00
John Preston
656146c445 Improve ViewButton + Reactions view. 2022-02-01 00:26:34 +03:00
John Preston
2eb8ed59cc Update unread reaction icon to a heart. 2022-01-31 23:32:04 +03:00
John Preston
9d6eeace54 Fix quilt by system on macOS. 2022-01-31 23:16:56 +03:00
John Preston
315c85fb8d Always show unread mentions / reactions buttons. 2022-01-31 23:16:56 +03:00
John Preston
b7a70a2f28 Improve unread reactions list consistency. 2022-01-31 23:16:56 +03:00
John Preston
63bf564757 Reaction notifications only from non-blocked contacts. 2022-01-31 16:18:40 +03:00
John Preston
6a9c5818ba Show go-to-user buttons in t.me links preview. 2022-01-31 16:17:58 +03:00
John Preston
98e7de01b0 Improve naming in dialogs_layout module. 2022-01-31 16:17:34 +03:00
John Preston
8ef7325e16 No confirmation for graph.org / te.legra.ph domains. 2022-01-31 16:14:03 +03:00
John Preston
17de379145 Fix document thumbnails on Retina screens. 2022-01-31 13:18:52 +03:00
John Preston
eb784c665a Fix build with tg_owt@M98 on macOS. 2022-01-31 13:18:42 +03:00
John Preston
07beb3e86b Correctly clear unread reactions. 2022-01-31 11:19:46 +03:00
John Preston
18919a6b4a Fix tg_owt@M98 build on Linux. 2022-01-30 19:39:33 +03:00
John Preston
71d4b64691 Fix link / forward preview on Retina screen.
Regression was introduced in 3ff17a8789.
2022-01-30 00:34:19 +03:00
John Preston
9918a20946 Fix tg_owt@M98 build with MSVC. 2022-01-29 15:18:23 +03:00
John Preston
ae5e7d641a Build on macOS with WebRTC M98. 2022-01-29 14:06:55 +03:00
John Preston
4e88ea970e Fix build with GCC. 2022-01-29 12:47:20 +03:00
John Preston
f6ff0f3b2c Fix build for macOS 10.12+. 2022-01-28 23:52:11 +03:00
John Preston
84d58e574f Closed alpha version 3.4.8.1. 2022-01-28 19:11:13 +03:00
John Preston
1dd7cc956b Show reaction notifications in groups. 2022-01-28 19:10:07 +03:00
John Preston
54e7dfe986 Open exact message on local reaction notification. 2022-01-28 19:10:07 +03:00
John Preston
4e9a52343f Process unread reactions to unknown messages. 2022-01-28 19:10:07 +03:00
John Preston
51c805d77a Enable reading reactions on the server. 2022-01-28 19:10:07 +03:00
John Preston
8bde488662 Support multiple reaction animations in one message. 2022-01-28 19:10:07 +03:00
John Preston
4f1e04cf9e Animate reactions when reading. 2022-01-28 19:10:07 +03:00
John Preston
e509da8fd8 Instantly mark as read visible new reactions. 2022-01-28 19:10:07 +03:00
John Preston
f6bfe2c9a8 Mark all reactions as read context menu. 2022-01-28 19:10:07 +03:00
John Preston
15719b73b4 Fix updating unread message reactions. 2022-01-28 19:10:07 +03:00
John Preston
8da9638563 Count correct scroll-for-message with unread reaction. 2022-01-28 19:10:07 +03:00
John Preston
a5afeebc0c Show reaction notification by unread status. 2022-01-28 19:10:07 +03:00
John Preston
9903266722 Index new unread reactions in history. 2022-01-28 19:10:07 +03:00
John Preston
8f33d5903d Display unread reactions badge in chats list. 2022-01-28 19:10:07 +03:00
John Preston
e9c79886d2 Track unread mentions and unread reactions the same way. 2022-01-28 19:10:07 +03:00
John Preston
6207770120 Paint unread mention badge as an icon. 2022-01-28 19:10:07 +03:00
John Preston
2a99f1a1ef Add tabbed-panel-show-on-click option. 2022-01-28 19:10:07 +03:00
John Preston
4aafcebef5 Add empty experimental settings section. 2022-01-28 19:10:07 +03:00
John Preston
3a78e94f2f Don't downscale screen captured frame too much. 2022-01-28 19:10:07 +03:00
John Preston
b5aca56914 Fix pausing stickers in StickerSetBox. 2022-01-28 19:10:07 +03:00
John Preston
a6621233d0 Show first frame of webm in photo editor. 2022-01-28 19:10:07 +03:00
John Preston
b4eb25de58 Support webm stickers in StickerSetBox. 2022-01-28 19:10:07 +03:00
John Preston
d96a8d028a Support webm stickers in field autocomplete. 2022-01-28 19:10:07 +03:00
John Preston
f45c47f3d5 Limit inline results repainting rate. 2022-01-28 19:10:07 +03:00
John Preston
827ce46d3c Support webm stickers in bot inline results. 2022-01-28 19:10:07 +03:00
John Preston
91c84d63de Force libvpx_vp9 decoder for VP9 videos.
Webm stickers depend on decoder support for alpha channel.
2022-01-28 19:10:07 +03:00
John Preston
545392f90f Show webm stickers in media preview. 2022-01-28 19:10:07 +03:00
John Preston
fa61cf3c85 Show webm animation in stickers box set thumbnail. 2022-01-28 19:10:07 +03:00
John Preston
c359646702 Animate webm pack icon in the panel. 2022-01-28 19:10:07 +03:00
John Preston
51fef843f0 Optimize sets stickers / gifs panel repainting. 2022-01-28 19:10:07 +03:00
John Preston
1a3a0fb124 Fix caching of webm stickers in local storage. 2022-01-28 19:10:07 +03:00
John Preston
f1d9cca119 Fix crash and pause in stickers panel with webm. 2022-01-28 19:10:07 +03:00
John Preston
8e749173de Render webm stickers in StickersListWidget. 2022-01-28 19:10:07 +03:00
John Preston
20dbf18106 Init webm player for sticker set thumbnails. 2022-01-28 19:10:07 +03:00
John Preston
10ff71e8f6 Support svg path outline for Webm stickers. 2022-01-28 19:10:07 +03:00
John Preston
170cc77a1b Use QColor instead of optional<QColor> in lottie. 2022-01-28 19:10:07 +03:00
John Preston
589673e420 Fix non-sticker Webm in media viewer. 2022-01-28 19:10:07 +03:00
John Preston
2f9c39fe53 Support selecting Webm stickers. 2022-01-28 19:10:07 +03:00
John Preston
044c7f3ce9 Generate opaque good thumbnails for non-sticker Webm. 2022-01-28 19:10:07 +03:00
John Preston
d18e28978a Cache good thumbnail in Webp for Webm stickers. 2022-01-28 19:10:07 +03:00
John Preston
581b84afe0 Update API scheme on layer 138. 2022-01-28 19:10:07 +03:00
John Preston
846cabeda5 Premultiply YUVA alpha in FFMpegReaderImplementation. 2022-01-28 19:10:07 +03:00
John Preston
9b59ef00af Generate good video thumbnail for Webm stickers. 2022-01-28 19:10:07 +03:00
John Preston
079772a399 Correctly preserve first frame alpha in video streaming. 2022-01-28 19:10:07 +03:00
John Preston
2e39befd7c Don't trust AVFormatContext duration in Webm video.
It reports some strange numbers like 1000, which is 1ms.
2022-01-28 19:10:07 +03:00
John Preston
219ffd2c48 Handle clicks on Webm stickers. 2022-01-28 19:10:07 +03:00
John Preston
63d15e4479 Support Webm sticker display in chat history. 2022-01-28 19:10:07 +03:00
John Preston
2f01efdd64 Correctly detect Webm stickers. 2022-01-28 19:10:07 +03:00
John Preston
8b7d2c880e Support rendering Webm videos with alpha. 2022-01-28 19:10:07 +03:00
John Preston
1755ead681 Update API scheme to layer 138. 2022-01-28 19:10:07 +03:00
John Preston
1e1f7be708 Don't scroll reactions dropdown too fast.
Fixes #23985.
2022-01-28 19:08:44 +03:00
John Preston
7170bec25d Update scroll-history-to-bottom arrow icon. 2022-01-27 17:23:31 +03:00
John Preston
7ad1a7dd37 Fix userpics re-generating in message reactions. 2022-01-27 17:18:06 +03:00
John Preston
217e9b2475 Fix build with GCC. 2022-01-27 16:20:39 +03:00
John Preston
28f2c213f7 Remove app module. Support delayed quit by Cmd+Q on macOS. 2022-01-27 15:54:20 +03:00
John Preston
b8f1cebeb6 Support confirm-on-Quit on macOS. 2022-01-26 14:50:10 +03:00
John Preston
6a3ad52aef Add upload cancel confirmation on Quit and Log Out. 2022-01-26 13:15:28 +03:00
John Preston
8c349c0515 Fix non-Retina spelling error underline on macOS. 2022-01-26 12:42:12 +03:00
John Preston
9038dfb3b8 Load more reactions per page after the first request. 2022-01-26 11:57:21 +03:00
John Preston
2e94488eb4 Fix possible crash in ListWidget destructor. 2022-01-26 10:40:22 +03:00
23rd
59ed41abfe Added ability to select links and monospaced text with pressed Alt key. 2022-01-26 10:39:57 +03:00
23rd
8bea6776f5 Replaced checking global key modifiers with qt adapters. 2022-01-26 10:34:38 +03:00
23rd
0143d22a21 Split qt_adapters to separated files. 2022-01-26 10:33:28 +03:00
23rd
021d0053be Fixed hiding spoilers in captions when switching sections. 2022-01-26 10:33:20 +03:00
Ilya Fedin
443ca0b390 Don't use BYPRODUCTS with appdata changelog
Due to recursive dependency
2022-01-25 18:25:58 +04:00
Ilya Fedin
2e0224589f Update cmake_helpers 2022-01-25 16:14:01 +04:00
Ilya Fedin
f8da59595a Fix appdata changelog target name 2022-01-25 16:14:01 +04:00
Ilya Fedin
e8568c6701 Set encoding when reading changelog for appdata 2022-01-25 16:14:01 +04:00
gasinvein
4ca3f6a1b3 Ensure appdata releases tag is replaced rather than added 2022-01-25 16:14:01 +04:00
John Preston
6af255923a Use generate_target for timestamped changelog generator. 2022-01-25 14:53:26 +03:00
Ilya Fedin
624d83dc60 Update cmake_helpers 2022-01-23 18:25:37 +04:00
Ilya Fedin
6073da2843 Disable libvpx unit tests in Dockerfile & prepare.py 2022-01-23 18:25:37 +04:00
Ilya Fedin
ca5d2c115d Update tg_owt 2022-01-23 18:25:37 +04:00
John Preston
86f3d88116 Fix build for Windows and Linux. 2022-01-23 12:23:31 +03:00
John Preston
7a971b5855 Fix build for Windows x64. 2022-01-23 10:17:15 +03:00
John Preston
0a7de3340a Revert submodules downgrade. 2022-01-23 10:07:41 +03:00
John Preston
2cb73eefeb Build docker for Linux with external libvpx. 2022-01-23 01:07:36 +03:00
John Preston
152aa06930 Qt patch with a possible work-through-proxy fix. 2022-01-23 00:54:41 +03:00
John Preston
cbca78ff63 Fix build with GCC. 2022-01-23 00:27:17 +03:00
John Preston
7c46b292ac Fix build with MSVC. 2022-01-23 00:26:16 +03:00
John Preston
883509903f Fix build with Xcode. 2022-01-22 23:27:13 +03:00
Ilya Fedin
37b2951058 Update submodules 2022-01-22 21:18:37 +04:00
Ilya Fedin
ceb323ac7c Use QMenuBar instead of own global menu implementation on Linux
This is another attempt of 79f96480c2
2022-01-22 21:18:37 +04:00
Ilya Fedin
b65d40a22b Get rid of custom SNI implementation
XDG is inventing new tray specification, so SNI will be outdated soon and it's better to just use QSystemTrayIcon.
I believe all the major drawbacks of QSystemTrayIcon are solved and we can live with minor ones.
Given the planned MainWindow refactoring, it seems it's the best time to do that.
2022-01-22 21:18:37 +04:00
John Preston
aed49b9289 Build FFmpeg with libvpx on macOS. 2022-01-22 19:51:00 +03:00
John Preston
72d81cc52f Build 32 bit Windows version with external_vpx. 2022-01-22 19:50:55 +03:00
c0re100
4bb3aec168 Fix incorrect admin log
Type: Restricted to Member
2022-01-22 18:43:43 +04:00
Ilya Fedin
c0a81f2428 Use the suggested workaround for qtwayland build arguments instead of patching 2022-01-22 18:42:59 +04:00
Ilya Fedin
692adacc2a Fix support icon on Linux when system icon is present 2022-01-22 18:39:53 +04:00
John Preston
26dbeb6831 Fix build with Xcode. 2022-01-22 16:42:38 +03:00
John Preston
07f72c20eb Change application icon only in support mode.
Fixes #23895.
2022-01-22 16:40:57 +03:00
John Preston
8407b0cccf Fix buffer double-delete in Images::Blur. 2022-01-21 16:01:16 +03:00
John Preston
3ff17a8789 Refactor image transformation interfaces. 2022-01-21 15:33:44 +03:00
John Preston
a9a6d8a568 Fix creating links containing '$'.
Regression was introduced in desktop-app/lib_ui@8331322c20.

Fixes #23921.
2022-01-21 15:32:45 +03:00
John Preston
9877845b9c Don't always enable screencast logs. 2022-01-21 15:32:45 +03:00
John Preston
dc89262461 Version 3.4.8.
- Nice animations in reactions.
- Add snap layouts support on Windows 11.
- Bug fixes and other minor improvements.
2022-01-19 19:12:22 +03:00
John Preston
a5425042cf Fix possible crash when update is ready.
Regression was introduced in 9a0be43ef5.
2022-01-19 19:03:19 +03:00
John Preston
d6e03c3e48 Fix possible crash with incorrect local time. 2022-01-19 19:03:19 +03:00
John Preston
e91eecf34f Fix possible crash in empty sticker set. 2022-01-19 19:03:19 +03:00
John Preston
1a8cc87e60 Fix reaction images on Retina screen. 2022-01-19 15:40:04 +03:00
John Preston
0863941642 Beta version 3.4.7.
- Fix a crash on launch on Windows.
2022-01-19 09:34:17 +03:00
John Preston
b331aee599 Fix a crash on Windows < 11. 2022-01-19 09:33:34 +03:00
271 changed files with 5689 additions and 4736 deletions

3
.gitmodules vendored
View File

@@ -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

View File

@@ -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
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 B

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 973 B

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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(

View 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

View 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

View File

@@ -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;

View File

@@ -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()),
});
});

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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();
};

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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());
}

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -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;

View File

@@ -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)

View File

@@ -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();

View File

@@ -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));
}

View File

@@ -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) {

View File

@@ -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, [=] {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -24,6 +24,8 @@ namespace ChatHelpers {
class TabbedSelector;
extern const char kOptionTabbedPanelShowOnClick[];
class TabbedPanel : public Ui::RpWidget {
public:
TabbedPanel(

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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())))

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -14,7 +14,7 @@ class FileLoader;
namespace Media {
namespace Clip {
enum Notification : int;
enum class Notification;
class ReaderPointer;
} // namespace Clip
} // namespace Media

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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),

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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>;

View File

@@ -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;
};

View File

@@ -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(

View File

@@ -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>;

View File

@@ -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 }};

View File

@@ -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"

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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(

View File

@@ -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(

View File

@@ -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;

View File

@@ -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>

View File

@@ -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"

Some files were not shown because too many files have changed in this diff Show More