Compare commits

..

194 Commits

Author SHA1 Message Date
John Preston
a7958f5235 Save custom app icon color with digest to settings. 2022-02-08 16:29:10 +03:00
John Preston
2ae5fc0fac Proof-of-concept macOS app icon change. 2022-02-08 15:21:06 +03:00
John Preston
666251f23e Allow to set custom app icon on macOS. 2022-02-08 00:24:11 +03:00
John Preston
d89d8b09da Preload chats in support switch. 2022-02-08 00:24:11 +03:00
John Preston
0aa20b4479 Decrypt shiftedDcId / protocolDcId in mtp_ logs. 2022-02-07 15:26:26 +03:00
John Preston
8658dba97a Improve mtproto connections logging. 2022-02-07 14:44:00 +03:00
23rd
20c911651f Replaced universal initializers with constructors in Ui::Text::Link. 2022-02-06 18:29:38 +03:00
John Preston
bef20ba4a2 Fix loading libdrm before first use.
Fixes #24022.
2022-02-06 13:50:23 +03:00
John Preston
ee325031a0 Fix build without wayland integration. 2022-02-05 19:20:40 +03:00
John Preston
b57549546d Update lib_ui for the indexed links fixes. 2022-02-05 19:19:54 +03:00
23rd
1e4d278604 Fixed formatting text of pinned messages in service messages. 2022-02-05 18:39:43 +03:00
23rd
80aa596310 Fixed possible incorrect order of links in Ui. 2022-02-05 18:39:43 +03:00
John Preston
4913288061 Fix "Fix chats order" toggle in support mode. 2022-02-05 15:09:18 +03:00
John Preston
1a43cd8a67 Fix the date in the latest changelog entry. 2022-02-05 01:41:41 +03:00
John Preston
bcb0511083 Version 3.5.1: Fix build with MSVC. 2022-02-04 22:56:45 +03:00
John Preston
2dda044dd1 Fix crash in video message without a thumbnail. 2022-02-04 22:56:29 +03:00
John Preston
da18ab3d41 Fix group call settings button position. 2022-02-04 22:56:28 +03:00
John Preston
f26cae8807 Version 3.5.1.
- Keep the screen on while watching a video
or participating in a video chat.
- Save experimental settings between relaunches.
- Bug fixes and other minor improvements.
2022-02-04 22:20:17 +03:00
23rd
641bb01ba2 Added ability to set forward options from ShareBox. 2022-02-04 22:20:17 +03:00
John Preston
02c9b61840 Fix PowerSaveBlocker in case QWindow was destroyed. 2022-02-04 16:44:20 +03:00
Ilya Fedin
99e8d22c51 Move xdg-foreign support to lib_base 2022-02-04 16:31:40 +03:00
John Preston
cd9b3368da Don't request scheduled messages in channels I can't write to. 2022-02-04 09:50:12 +03:00
John Preston
f918c6bb83 Pass QWindow to PowerSaveBlocker. 2022-02-04 09:43:56 +03:00
gasinvein
28dff5ba6d Correct year in changelog for 2022 releases 2022-02-03 11:44:20 +03:00
John Preston
4d978f5b36 Add PowerSaveBlocker implementation for macOS. 2022-02-02 17:07:31 +03:00
John Preston
ef41878815 Fix date tooltip on outgoing messages. 2022-02-02 15:46:55 +03:00
John Preston
6a663932f3 Fix message viewers display in message reactions box. 2022-02-02 15:40:39 +03:00
John Preston
67c538ae8f Add AllowLinuxNvidiaOpenGL option. 2022-02-02 15:33:41 +03:00
John Preston
38137e16a0 Save / restore experimental settings in a json. 2022-02-02 12:05:33 +03:00
John Preston
9c01295521 Create PowerSaveBlocker-s on calls / video / audio. 2022-02-02 12:05:33 +03:00
Ilya Fedin
c7b6db00ca Build WebRTC without PipeWire support in snap
WebRTC no more supports PipeWire 0.2, so it's impossible to build PipeWire support until core22 runtime is released
2022-02-02 09:32:44 +03:00
Ilya Fedin
7f0bdc5d36 Fix desktop environment list deduplication 2022-02-02 09:23:14 +03:00
John Preston
5f6d8f74dd Warn about supergroup clearing history just for me. 2022-02-01 19:41:51 +03:00
John Preston
9086319b99 Update lib_ui for accounts reordering. 2022-02-01 19:41:29 +03:00
23rd
37cd4f51eb Added view-profile-in-chats-list-context-menu option. 2022-02-01 18:48:02 +03:00
23rd
74416568d6 Distributed tabbed-panel-show-on-click option to sections as well. 2022-02-01 18:48:02 +03:00
23rd
a8c3d6c39b Added ability to reorder accounts in MainMenu. 2022-02-01 18:48:02 +03:00
John Preston
5939c2dbfc Don't crash on no-frame with SUCCESS status. 2022-02-01 16:51:51 +03:00
John Preston
4bef1e9f59 Use unreliable video duration if open with audio. 2022-02-01 16:51:51 +03:00
John Preston
18bf48bf90 Fix possible crash in view button click handler. 2022-02-01 16:51:51 +03:00
John Preston
b415b293cf Fix crash from background access to style::icon-s. 2022-02-01 16:51:51 +03:00
John Preston
3a1bb1966d Fix comparator in tgcalls. 2022-02-01 16:51:51 +03:00
Ilya Fedin
d4b686ff65 Update tg_owt 2022-02-01 15:30:02 +03:00
Ilya Fedin
8148974e9f Update cmake_helpers 2022-02-01 14:42:21 +03:00
Ilya Fedin
eaa2573c66 Build ffmpeg without xlib as it was needed only for libva/vdpau 2022-02-01 14:42:21 +03:00
GitHub Action
4d315f8e61 Update User-Agent for DNS to Chrome 97.0.4692.99. 2022-02-01 14:42:05 +03:00
Ilya Fedin
e5981ed22b Move jemalloc initialization code to cmake_helpers 2022-02-01 14:41:54 +03:00
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
John Preston
e19180cc86 Beta version 3.4.6: Fix build with Xcode. 2022-01-18 21:56:20 +03:00
John Preston
10cb891f48 Beta version 3.4.6.
- Add snap layouts support on Windows 11.
- Fix crash in drafts after accounts switching.
2022-01-18 21:47:13 +03:00
John Preston
c8f7a8c795 Add a tab with "Who Seen" to "Who Reacted" box. 2022-01-18 21:44:59 +03:00
John Preston
74a28ffdf7 Use correct string for reacted / seen item. 2022-01-18 19:55:24 +03:00
John Preston
ecedce0c2f Fix crash in drafts saving.
Regression was introduced in 43559fb6b7.
2022-01-18 19:05:31 +03:00
John Preston
bd4f993292 Add Windows 11 snap layouts to call / voicechat. 2022-01-18 18:39:55 +03:00
John Preston
4934b026d3 Improve call / voicechat title controls on Windows 11. 2022-01-18 15:53:04 +03:00
John Preston
11f183a79f Better animate sent reaction in flipped context. 2022-01-18 14:45:28 +03:00
John Preston
ae426a41e0 Better reaction layout outside of a bubble. 2022-01-18 14:37:14 +03:00
John Preston
d6edc3728d Workaround selection glitches on macOS. 2022-01-18 14:11:15 +03:00
John Preston
e121487170 Fix appear animation when sending a reaction in a group. 2022-01-18 13:09:42 +03:00
John Preston
72a093ec77 Support Windows 11 snap layouts in the main window. 2022-01-18 12:59:54 +03:00
John Preston
4996d90782 Fix first my reaction userpic in groups. 2022-01-17 21:29:28 +03:00
John Preston
9a451a1423 Don't suggest "Set As Quick" on already quick. 2022-01-17 19:21:34 +03:00
John Preston
4d11ad45db Use common title bar buttons in Call Panel. 2022-01-17 19:21:34 +03:00
John Preston
1657c2c7f2 Fix context menu on sent images / documents. 2022-01-17 19:21:34 +03:00
John Preston
c5e7048a3d Don't copy-on-click "pre" entities. 2022-01-17 16:49:14 +03:00
John Preston
1f194da2f0 Improve macOS title bar font and rounding. 2022-01-17 16:48:32 +03:00
John Preston
0954b04f24 Fix title controls on Windows 11. 2022-01-17 13:39:14 +03:00
John Preston
4659499340 Update window title icons. 2022-01-17 11:18:12 +03:00
John Preston
6eb4584408 Fix links before monospace formatting. 2022-01-17 10:51:35 +03:00
23rd
4ba4b77b95 Fixed formatting of text in entry of archived folder.
Fixed #23906.
2022-01-17 10:12:18 +03:00
365 changed files with 7600 additions and 5435 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.

Before

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -389,9 +389,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_auto_start_disabled_uwp" = "Starting with the system was disabled in Windows Settings.\n\nPlease enable Telegram Desktop in the Startup Apps Settings.";
"lng_settings_open_system_settings" = "Open Settings";
"lng_settings_add_sendto" = "Place Telegram in \"Send to\" menu";
"lng_settings_mac_warn_before_quit" = "Show warning before quitting with {text}";
"lng_settings_section_scale" = "Interface Scale";
"lng_settings_scale_auto" = "Auto ({cur})";
"lng_settings_experimental" = "Experimental settings";
"lng_settings_experimental_about" = "Warning! Those are experimental settings. Some may not work. Others may break the app. Any of them may disappear in the next version without a trace. Use at your own risk.";
"lng_settings_experimental_restore" = "Restore default values";
"lng_settings_experimental_irrelevant" = "This option isn't relevant for your system.";
"lng_settings_section_chat_settings" = "Chat Settings";
"lng_settings_replace_emojis" = "Replace emoji";
"lng_settings_suggest_emoji" = "Suggest emoji replacements";
@@ -730,7 +736,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sure_logout" = "Are you sure you want to log out?";
"lng_settings_need_restart" = "You need to restart for applying some of the new settings. Restart now?";
"lng_settings_restart_now" = "RESTART";
"lng_settings_restart_now" = "Restart";
"lng_settings_restart_later" = "Later";
"lng_sessions_header" = "Current session";
"lng_sessions_other_header" = "Active sessions";
@@ -1748,6 +1755,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_mark_read_all" = "Mark all chats as read";
"lng_context_mark_read_all_sure" = "Are you sure you want to mark all chats as read?";
"lng_context_mark_read_mentions_all" = "Mark all mentions as read";
"lng_context_mark_read_reactions_all" = "Read all reactions";
"lng_context_archive_expand" = "Expand";
"lng_context_archive_collapse" = "Collapse";
"lng_context_archive_to_menu" = "Move to main menu";
@@ -1908,6 +1916,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_delete_for_me_chat_hint#other" = "This will delete them just for you, not for other participants of the chat.";
"lng_delete_for_me_hint#one" = "This will delete it just for you.";
"lng_delete_for_me_hint#other" = "This will delete them just for you.";
"lng_delete_clear_for_me" = "This will clear history just for you, not for other participants of the chat.";
"lng_edit_auto_delete_settings" = "Edit Auto-Delete Settings";
"lng_enable_auto_delete" = "Enable Auto-Delete";
"lng_selected_unsend_about_user_one" = "You can also delete the message you sent from {user}'s inbox by checking \"Unsend my messages\".";
@@ -1922,6 +1931,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_box_delete" = "Delete";
"lng_box_leave" = "Leave";
"lng_upload_sure_stop" = "Are you sure you want to stop uploading your files?\n\nIf you do, you'll need to start over.";
"lng_upload_show_file" = "Show file";
"lng_about_version" = "version {version}";
"lng_about_text1" = "Official free messaging app based on {api_link}\nfor speed and security.";
"lng_about_text1_api" = "Telegram API";
@@ -3058,6 +3070,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mac_menu_preferences" = "Preferences...";
"lng_mac_menu_quit_telegram" = "Quit {telegram}";
"lng_mac_menu_about_telegram" = "About {telegram}";
//"lng_mac_menu_warn_before_quit" = "Warn Before Quitting ({text})";
"lng_mac_menu_file" = "File";
"lng_mac_menu_logout" = "Log Out";
"lng_mac_menu_edit" = "Edit";
@@ -3084,4 +3097,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mac_touchbar_favorite_stickers" = "Favorite stickers";
"lng_mac_hold_to_quit" = "Hold {text} to Quit";
// Keys finished

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.5.0" />
Version="3.5.1.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,5,0
PRODUCTVERSION 3,4,5,0
FILEVERSION 3,5,1,0
PRODUCTVERSION 3,5,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "3.4.5.0"
VALUE "FileVersion", "3.5.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.4.5.0"
VALUE "ProductVersion", "3.5.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,4,5,0
PRODUCTVERSION 3,4,5,0
FILEVERSION 3,5,1,0
PRODUCTVERSION 3,5,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "3.4.5.0"
VALUE "FileVersion", "3.5.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.4.5.0"
VALUE "ProductVersion", "3.5.1.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

@@ -51,6 +51,7 @@ inline bool operator==(
struct PeersWithReactions {
std::vector<PeerWithReaction> list;
std::vector<PeerId> read;
int fullReactionsCount = 0;
bool unknown = false;
};
@@ -59,6 +60,7 @@ inline bool operator==(
const PeersWithReactions &b) noexcept {
return (a.fullReactionsCount == b.fullReactionsCount)
&& (a.list == b.list)
&& (a.read == b.read)
&& (a.unknown == b.unknown);
}
@@ -244,13 +246,15 @@ struct State {
}
[[nodiscard]] PeersWithReactions WithEmptyReactions(
const Peers &peers) {
return PeersWithReactions{
Peers &&peers) {
auto result = PeersWithReactions{
.list = peers.list | ranges::views::transform([](PeerId peer) {
return PeerWithReaction{.peer = peer };
}) | ranges::to_vector,
.unknown = peers.unknown,
};
result.read = std::move(peers.list);
return result;
}
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
@@ -285,6 +289,7 @@ struct State {
result.match([&](
const MTPDmessages_messageReactionsList &data) {
session->data().processUsers(data.vusers());
session->data().processChats(data.vchats());
auto parsed = PeersWithReactions{
.fullReactionsCount = data.vcount().v,
@@ -293,7 +298,7 @@ struct State {
for (const auto &vote : data.vreactions().v) {
vote.match([&](const auto &data) {
parsed.list.push_back(PeerWithReaction{
.peer = peerFromUser(data.vuser_id()),
.peer = peerFromMTP(data.vpeer_id()),
.reaction = qs(data.vreaction()),
});
});
@@ -319,7 +324,7 @@ struct State {
return rpl::combine(
WhoReactedIds(item, QString(), context),
WhoReadIds(item, context)
) | rpl::map([=](PeersWithReactions reacted, Peers read) {
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
if (reacted.unknown || read.unknown) {
return PeersWithReactions{ .unknown = true };
}
@@ -329,7 +334,8 @@ struct State {
list.push_back({ .peer = peer });
}
}
return reacted;
reacted.read = std::move(read.list);
return std::move(reacted);
});
}
@@ -438,6 +444,108 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
RegenerateUserpics(state, small, large);
}
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context,
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds) {
const auto small = st.userpics.size;
const auto large = st.photoSize;
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto resolveWhoRead = reaction.isEmpty()
&& WhoReadExists(item);
const auto state = lifetime.make_state<State>();
const auto pushNext = [=] {
consumer.put_next_copy(state->current);
};
const auto resolveWhoReacted = !reaction.isEmpty()
|| item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
: resolveWhoRead
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
: WhoReactedIds(item, reaction, context);
state->current.type = resolveWhoRead
? DetectSeenType(item)
: Ui::WhoReadType::Reacted;
if (resolveWhoReacted) {
const auto &list = item->reactions();
state->current.fullReactionsCount = reaction.isEmpty()
? ranges::accumulate(
list,
0,
ranges::plus{},
[](const auto &pair) { return pair.second; })
: list.contains(reaction)
? list.find(reaction)->second
: 0;
// #TODO reactions
state->current.singleReaction = !reaction.isEmpty()
? reaction
: (list.size() == 1)
? list.front().first
: QString();
}
std::move(
idsWithReactions
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
if (peers.unknown) {
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.fullReactionsCount = state->current.fullReactionsCount,
.fullReadCount = state->current.fullReadCount,
.unknown = true,
});
return;
}
state->current.fullReadCount = int(peers.read.size());
state->current.fullReactionsCount = peers.fullReactionsCount;
if (whoReadIds) {
const auto reacted = peers.list.size() - ranges::count(
peers.list,
QString(),
&PeerWithReaction::reaction);
whoReadIds->list = (peers.read.size() > reacted)
? std::move(peers.read)
: std::vector<PeerId>();
}
if (UpdateUserpics(state, item, peers.list)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.list.empty()) {
pushNext();
}
}, lifetime);
item->history()->session().downloaderTaskFinished(
) | rpl::filter([=] {
return state->someUserpicsNotLoaded && !state->scheduled;
}) | rpl::start_with_next([=] {
for (const auto &userpic : state->userpics) {
if (userpic.peer->userpicUniqueKey(userpic.view)
!= userpic.uniqueKey) {
state->scheduled = true;
crl::on_main(&state->guard, [=] {
state->scheduled = false;
RegenerateUserpics(state, small, large);
pushNext();
});
return;
}
}
}, lifetime);
return lifetime;
};
}
} // namespace
bool WhoReadExists(not_null<HistoryItem*> item) {
@@ -482,8 +590,9 @@ bool WhoReactedExists(not_null<HistoryItem*> item) {
rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
not_null<QWidget*> context,
const style::WhoRead &st) {
return WhoReacted(item, QString(), context, st);
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds) {
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
}
rpl::producer<Ui::WhoReadContent> WhoReacted(
@@ -491,88 +600,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
const QString &reaction,
not_null<QWidget*> context,
const style::WhoRead &st) {
const auto small = st.userpics.size;
const auto large = st.photoSize;
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto resolveWhoRead = reaction.isEmpty() && WhoReadExists(item);
const auto state = lifetime.make_state<State>();
const auto pushNext = [=] {
consumer.put_next_copy(state->current);
};
const auto resolveWhoReacted = !reaction.isEmpty()
|| item->canViewReactions();
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
? WhoReadOrReactedIds(item, context)
: resolveWhoRead
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
: WhoReactedIds(item, reaction, context);
state->current.type = resolveWhoRead
? DetectSeenType(item)
: Ui::WhoReadType::Reacted;
if (resolveWhoReacted) {
const auto &list = item->reactions();
state->current.fullReactionsCount = reaction.isEmpty()
? ranges::accumulate(
list,
0,
ranges::plus{},
[](const auto &pair) { return pair.second; })
: list.contains(reaction)
? list.find(reaction)->second
: 0;
// #TODO reactions
state->current.singleReaction = !reaction.isEmpty()
? reaction
: (list.size() == 1)
? list.front().first
: QString();
}
std::move(
idsWithReactions
) | rpl::start_with_next([=](const PeersWithReactions &peers) {
if (peers.unknown) {
state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{
.type = state->current.type,
.fullReactionsCount = state->current.fullReactionsCount,
.unknown = true,
});
return;
}
state->current.fullReactionsCount = peers.fullReactionsCount;
if (UpdateUserpics(state, item, peers.list)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.list.empty()) {
pushNext();
}
}, lifetime);
item->history()->session().downloaderTaskFinished(
) | rpl::filter([=] {
return state->someUserpicsNotLoaded && !state->scheduled;
}) | rpl::start_with_next([=] {
for (const auto &userpic : state->userpics) {
if (userpic.peer->userpicUniqueKey(userpic.view)
!= userpic.uniqueKey) {
state->scheduled = true;
crl::on_main(&state->guard, [=] {
state->scheduled = false;
RegenerateUserpics(state, small, large);
pushNext();
});
return;
}
}
}, lifetime);
return lifetime;
};
return WhoReacted(item, reaction, context, st, nullptr);
}
} // namespace Api

View File

@@ -15,6 +15,7 @@ struct WhoRead;
namespace Ui {
struct WhoReadContent;
enum class WhoReadType;
} // namespace Ui
namespace Api {
@@ -22,15 +23,21 @@ namespace Api {
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
struct WhoReadList {
std::vector<PeerId> list;
Ui::WhoReadType type = {};
};
// The context must be destroyed before the session holding this item.
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
not_null<QWidget*> context,
const style::WhoRead &st); // Cache results for this lifetime.
not_null<QWidget*> context, // Cache results for this lifetime.
const style::WhoRead &st,
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
not_null<HistoryItem*> item,
const QString &reaction,
not_null<QWidget*> context,
const style::WhoRead &st); // Cache results for this lifetime.
not_null<QWidget*> context, // Cache results for this lifetime.
const style::WhoRead &st);
} // namespace Api

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

@@ -318,7 +318,7 @@ void ProxyRow::updateFields(View &&view) {
TextWithEntities()
.append(_view.type)
.append(' ')
.append(Ui::Text::Link(endpoint, {})),
.append(Ui::Text::Link(endpoint, QString())),
Ui::ItemTextDefaultOptions());
const auto state = _view.state;

View File

@@ -156,6 +156,12 @@ void DeleteMessagesBox::prepare() {
: tr::lng_box_leave();
}, _revoke->lifetime());
}
} else if (canDelete
&& _wipeHistoryJustClear
&& (peer->isMegagroup() || peer->isChat())) {
appendDetails({
tr::lng_delete_clear_for_me(tr::now)
});
}
} else if (_moderateFrom) {
Assert(_moderateInChannel != nullptr);

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

@@ -1116,7 +1116,8 @@ void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link) {
auto submitCallback = [=](
std::vector<not_null<PeerData*>> &&result,
TextWithTags &&comment,
Api::SendOptions options) {
Api::SendOptions options,
Data::ForwardOptions) {
if (*sending || result.empty()) {
return;
}

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

@@ -15,11 +15,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_account.h"
#include "ui/boxes/confirm_box.h"
#include "apiwrap.h"
#include "ui/chat/forward_options_box.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/text/text_options.h"
#include "chat_helpers/message_field.h"
@@ -40,6 +44,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
#include "styles/style_media_player.h"
namespace {
class ForwardOptionItem final : public Ui::Menu::Action {
public:
using Ui::Menu::Action::Action;
void init(bool checked) {
enableMouseSelecting();
AbstractButton::setDisabled(true);
_checkView = std::make_unique<Ui::CheckView>(st::defaultCheck, false);
_checkView->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
setIcon(checked ? &st::mediaPlayerMenuCheck : nullptr);
}, lifetime());
_checkView->setChecked(checked, anim::type::normal);
AbstractButton::clicks(
) | rpl::start_with_next([=] {
_checkView->setChecked(
!_checkView->checked(),
anim::type::normal);
}, lifetime());
}
not_null<Ui::CheckView*> checkView() const {
return _checkView.get();
}
private:
std::unique_ptr<Ui::CheckView> _checkView;
};
} // namespace
class ShareBox::Inner final : public Ui::RpWidget {
public:
@@ -442,17 +484,68 @@ SendMenu::Type ShareBox::sendMenuType() const {
: SendMenu::Type::Scheduled;
}
void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
if (_menu) {
_menu = nullptr;
return;
}
_menu.emplace(parent, st::popupMenuWithIcons);
if (_descriptor.forwardOptions.show) {
auto createView = [&](rpl::producer<QString> &&text, bool checked) {
auto item = base::make_unique_q<ForwardOptionItem>(
_menu->menu(),
st::popupMenuWithIcons.menu,
new QAction(QString(), _menu->menu()),
nullptr,
nullptr);
std::move(
text
) | rpl::start_with_next([action = item->action()](QString text) {
action->setText(text);
}, item->lifetime());
item->init(checked);
const auto view = item->checkView();
_menu->addAction(std::move(item));
return view;
};
Ui::FillForwardOptions(
std::move(createView),
_descriptor.forwardOptions.messagesCount,
_forwardOptions,
[=](Ui::ForwardOptions value) { _forwardOptions = value; },
_menu->lifetime());
_menu->addSeparator();
}
const auto result = SendMenu::FillSendMenu(
_menu.get(),
sendMenuType(),
[=] { submitSilent(); },
[=] { submitScheduled(); });
const auto success = (result == SendMenu::FillMenuResult::Success);
if (_descriptor.forwardOptions.show || success) {
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::BottomRight);
_menu->popup(QCursor::pos());
}
}
void ShareBox::createButtons() {
clearButtons();
if (_hasSelected) {
const auto send = addButton(tr::lng_share_confirm(), [=] {
submit({});
});
SendMenu::SetupMenuAndShortcuts(
send,
[=] { return sendMenuType(); },
[=] { submitSilent(); },
[=] { submitScheduled(); });
_forwardOptions.hasCaptions = _descriptor.forwardOptions.hasCaptions;
send->setAcceptBoth();
send->clicks(
) | rpl::start_with_next([=](Qt::MouseButton button) {
if (button == Qt::RightButton) {
showMenu(send);
}
}, send->lifetime());
} else if (_descriptor.copyCallback) {
addButton(_copyLinkText.value(), [=] { copyLink(); });
}
@@ -488,10 +581,17 @@ void ShareBox::innerSelectedChanged(PeerData *peer, bool checked) {
void ShareBox::submit(Api::SendOptions options) {
if (const auto onstack = _descriptor.submitCallback) {
const auto forwardOptions = (_forwardOptions.hasCaptions
&& _forwardOptions.dropCaptions)
? Data::ForwardOptions::NoNamesAndCaptions
: _forwardOptions.dropNames
? Data::ForwardOptions::NoSenderNames
: Data::ForwardOptions::PreserveInfo;
onstack(
_inner->selected(),
_comment->entity()->getTextWithAppliedMarkdown(),
options);
options,
forwardOptions);
}
}

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "base/observer.h"
#include "base/timer.h"
#include "ui/chat/forward_options_box.h"
#include "ui/effects/animations.h"
#include "ui/effects/round_checkbox.h"
#include "mtproto/sender.h"
@@ -41,12 +42,17 @@ class Row;
class IndexedList;
} // namespace Dialogs
namespace Data {
enum class ForwardOptions;
} // namespace Data
namespace Ui {
class MultiSelect;
class InputField;
struct ScrollToRequest;
template <typename Widget>
class SlideWrap;
class PopupMenu;
} // namespace Ui
QString AppendShareGameScoreUrl(
@@ -63,7 +69,8 @@ public:
using SubmitCallback = Fn<void(
std::vector<not_null<PeerData*>>&&,
TextWithTags&&,
Api::SendOptions)>;
Api::SendOptions,
Data::ForwardOptions option)>;
using FilterCallback = Fn<bool(PeerData*)>;
struct Descriptor {
@@ -79,6 +86,11 @@ public:
const style::MultiSelect *stMultiSelect = nullptr;
const style::InputField *stComment = nullptr;
const style::PeerList *st = nullptr;
struct {
int messagesCount = 0;
bool show = false;
bool hasCaptions = false;
} forwardOptions;
};
ShareBox(QWidget*, Descriptor &&descriptor);
@@ -119,6 +131,8 @@ private:
mtpRequestId requestId);
void peopleFail(const MTP::Error &error, mtpRequestId requestId);
void showMenu(not_null<Ui::RpWidget*> parent);
Descriptor _descriptor;
MTP::Sender _api;
@@ -126,6 +140,9 @@ private:
object_ptr<Ui::SlideWrap<Ui::InputField>> _comment;
object_ptr<Ui::RpWidget> _bottomWidget;
base::unique_qptr<Ui::PopupMenu> _menu;
Ui::ForwardOptions _forwardOptions;
class Inner;
QPointer<Inner> _inner;

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

@@ -411,49 +411,45 @@ callBarSignalBars: CallSignalBars(callPanelSignalBars) {
color: callBarFg;
}
callTitleButton: IconButton {
width: 34px;
height: 30px;
iconPosition: point(0px, 0px);
}
callTitleButton: windowTitleButton;
callTitleMinimizeIcon: icon {
{ "calls/calls_minimize_shadow", windowShadowFg },
{ "calls/calls_minimize_main", callNameFg },
{ "title_shadow_minimize", windowShadowFg },
{ "title_button_minimize", callNameFg },
};
callTitleMinimizeIconOver: icon {
{ size(34px, 30px), callBgButton },
{ size(34px, 30px), callMuteRipple },
{ "calls/calls_minimize_shadow", windowShadowFg },
{ "calls/calls_minimize_main", callNameFg },
{ windowTitleButtonSize, callBgButton },
{ windowTitleButtonSize, callMuteRipple },
{ "title_shadow_minimize", windowShadowFg },
{ "title_button_minimize", callNameFg },
};
callTitleMaximizeIcon: icon {
{ "calls/calls_maximize_shadow", windowShadowFg },
{ "calls/calls_maximize_main", callNameFg },
{ "title_shadow_maximize", windowShadowFg },
{ "title_button_maximize", callNameFg },
};
callTitleMaximizeIconOver: icon {
{ size(34px, 30px), callBgButton },
{ size(34px, 30px), callMuteRipple },
{ "calls/calls_maximize_shadow", windowShadowFg },
{ "calls/calls_maximize_main", callNameFg },
{ windowTitleButtonSize, callBgButton },
{ windowTitleButtonSize, callMuteRipple },
{ "title_shadow_maximize", windowShadowFg },
{ "title_button_maximize", callNameFg },
};
callTitleRestoreIcon: icon {
{ "calls/calls_restore_shadow", windowShadowFg },
{ "calls/calls_restore_main", callNameFg },
{ "title_shadow_restore", windowShadowFg },
{ "title_button_restore", callNameFg },
};
callTitleRestoreIconOver: icon {
{ size(34px, 30px), callBgButton },
{ size(34px, 30px), callMuteRipple },
{ "calls/calls_restore_shadow", windowShadowFg },
{ "calls/calls_restore_main", callNameFg },
{ windowTitleButtonSize, callBgButton },
{ windowTitleButtonSize, callMuteRipple },
{ "title_shadow_restore", windowShadowFg },
{ "title_button_restore", callNameFg },
};
callTitleCloseIcon: icon {
{ "calls/calls_close_shadow", windowShadowFg },
{ "calls/calls_close_main", callNameFg },
{ "title_shadow_close", windowShadowFg },
{ "title_button_close", callNameFg },
};
callTitleCloseIconOver: icon {
{ size(34px, 30px), titleButtonCloseBgOver },
{ "calls/calls_close_shadow", windowShadowFg },
{ "calls/calls_close_main", titleButtonCloseFgOver },
{ windowTitleButtonSize, titleButtonCloseBgOver },
{ "title_shadow_close", windowShadowFg },
{ "title_button_close", titleButtonCloseFgOver },
};
callTitle: WindowTitle(defaultWindowTitle) {
height: 0px;
@@ -1055,37 +1051,37 @@ groupCallDelaySlider: MediaSlider(defaultContinuousSlider) {
groupCallDelayMargin: margins(22px, 5px, 20px, 10px);
groupCallTitleButton: IconButton {
width: 24px;
height: 21px;
width: windowTitleButtonWidth;
height: windowTitleHeight;
iconPosition: point(0px, 0px);
}
groupCallTitleMinimizeIcon: icon {
{ "title_button_minimize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
{ "title_button_minimize", groupCallMemberNotJoinedStatus },
};
groupCallTitleMinimizeIconOver: icon {
{ size(24px, 21px), groupCallMembersBgOver },
{ "title_button_minimize", groupCallMembersFg, point(4px, 4px) },
{ windowTitleButtonSize, groupCallMembersBgOver },
{ "title_button_minimize", groupCallMembersFg },
};
groupCallTitleMaximizeIcon: icon {
{ "title_button_maximize", groupCallMemberNotJoinedStatus, point(4px, 4px) },
{ "title_button_maximize", groupCallMemberNotJoinedStatus },
};
groupCallTitleMaximizeIconOver: icon {
{ size(24px, 21px), groupCallMembersBgOver },
{ "title_button_maximize", groupCallMembersFg, point(4px, 4px) },
{ windowTitleButtonSize, groupCallMembersBgOver },
{ "title_button_maximize", groupCallMembersFg },
};
groupCallTitleRestoreIcon: icon {
{ "title_button_restore", groupCallMemberNotJoinedStatus, point(4px, 4px) },
{ "title_button_restore", groupCallMemberNotJoinedStatus },
};
groupCallTitleRestoreIconOver: icon {
{ size(24px, 21px), groupCallMembersBgOver },
{ "title_button_restore", groupCallMembersFg, point(4px, 4px) },
{ windowTitleButtonSize, groupCallMembersBgOver },
{ "title_button_restore", groupCallMembersFg },
};
groupCallTitleCloseIcon: icon {
{ "title_button_close", groupCallMemberNotJoinedStatus, point(4px, 4px) },
{ "title_button_close", groupCallMemberNotJoinedStatus },
};
groupCallTitleCloseIconOver: icon {
{ size(24px, 21px), titleButtonCloseBgOver },
{ "title_button_close", titleButtonCloseFgOver, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonCloseBgOver },
{ "title_button_close", titleButtonCloseFgOver },
};
groupCallTitle: WindowTitle(defaultWindowTitle) {
height: 0px;
@@ -1199,7 +1195,7 @@ desktopCaptureSourceSkips: size(2px, 10px);
desktopCaptureSourceTitle: WindowTitle(groupCallTitle) {
bg: groupCallMembersBgOver;
bgActive: groupCallMembersBgOver;
height: 21px;
height: windowTitleHeight;
}
desktopCapturePadding: margins(7px, 7px, 7px, 33px);
desktopCaptureLabelBottom: 7px;

View File

@@ -162,14 +162,13 @@ Call::Call(
, _user(user)
, _api(&_user->session().mtp())
, _type(type)
, _discardByTimeoutTimer([=] { hangup(); })
, _videoIncoming(
std::make_unique<Webrtc::VideoTrack>(
StartVideoState(video)))
, _videoOutgoing(
std::make_unique<Webrtc::VideoTrack>(
StartVideoState(video))) {
_discardByTimeoutTimer.setCallback([=] { hangup(); });
if (_type == Type::Outgoing) {
setState(State::Requesting);
} else {

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

@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "platform/platform_specific.h"
#include "base/platform/base_platform_info.h"
#include "base/power_save_blocker.h"
#include "window/main_window.h"
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
@@ -61,8 +62,8 @@ Panel::Panel(not_null<Call*> call)
, _user(call->user())
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
#ifndef Q_OS_MAC
, _controls(std::make_unique<Ui::Platform::TitleControls>(
widget(),
, _controls(Ui::Platform::SetupSeparateTitleControls(
window(),
st::callTitle,
[=](bool maximized) { toggleFullScreen(maximized); }))
#endif // !Q_OS_MAC
@@ -144,7 +145,7 @@ void Panel::initWindow() {
return Flag::None | Flag(0);
}
#ifndef Q_OS_MAC
if (_controls->geometry().contains(widgetPoint)) {
if (_controls->controls.geometry().contains(widgetPoint)) {
return Flag::None | Flag(0);
}
#endif // !Q_OS_MAC
@@ -357,6 +358,7 @@ void Panel::reinitWithCall(Call *call) {
if (!_call) {
_incoming = nullptr;
_outgoingVideoBubble = nullptr;
_powerSaveBlocker = nullptr;
return;
}
@@ -497,6 +499,11 @@ void Panel::reinitWithCall(Call *call) {
_camera->raise();
_mute->raise();
_powerSaveBlocker = std::make_unique<base::PowerSaveBlocker>(
base::PowerSaveBlockType::PreventDisplaySleep,
u"Video call is active"_q,
window()->windowHandle());
_incoming->widget()->lower();
}
@@ -550,7 +557,7 @@ void Panel::initLayout() {
}, widget()->lifetime());
#ifndef Q_OS_MAC
_controls->raise();
_controls->wrap.raise();
#endif // !Q_OS_MAC
}
@@ -628,7 +635,7 @@ void Panel::updateControlsGeometry() {
}
if (_fingerprint) {
#ifndef Q_OS_MAC
const auto controlsGeometry = _controls->geometry();
const auto controlsGeometry = _controls->controls.geometry();
const auto halfWidth = widget()->width() / 2;
const auto minLeft = (controlsGeometry.center().x() < halfWidth)
? (controlsGeometry.width() + st::callFingerprintTop)
@@ -804,6 +811,10 @@ void Panel::stateChanged(State state) {
&& (state != State::EndedByOtherDevice)
&& (state != State::FailedHangingUp)
&& (state != State::Failed)) {
if (state == State::Busy) {
_powerSaveBlocker = nullptr;
}
auto toggleButton = [&](auto &&button, bool visible) {
button->toggle(
visible,

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/weak_ptr.h"
#include "base/timer.h"
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
#include "calls/calls_call.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/effects/animations.h"
@@ -18,6 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class Image;
namespace base {
class PowerSaveBlocker;
} // namespace base
namespace Data {
class PhotoMedia;
class CloudImageView;
@@ -37,7 +42,7 @@ namespace GL {
enum class Backend;
} // namespace GL
namespace Platform {
class TitleControls;
struct SeparateTitleControls;
} // namespace Platform
} // namespace Ui
@@ -126,9 +131,11 @@ private:
std::unique_ptr<Incoming> _incoming;
#ifndef Q_OS_MAC
std::unique_ptr<Ui::Platform::TitleControls> _controls;
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
#endif // !Q_OS_MAC
std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker;
QSize _incomingFrameSize;
rpl::lifetime _callLifetime;

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

@@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "base/qt_signal_producer.h"
#include "base/timer_rpl.h"
#include "base/power_save_blocker.h"
#include "apiwrap.h" // api().kick.
#include "api/api_chat_participants.h" // api().kick.
#include "webrtc/webrtc_video_track.h"
@@ -88,10 +89,14 @@ Panel::Panel(not_null<GroupCall*> call)
, _peer(call->peer())
, _layerBg(std::make_unique<Ui::LayerManager>(widget()))
#ifndef Q_OS_MAC
, _controls(std::make_unique<Ui::Platform::TitleControls>(
widget(),
, _controls(Ui::Platform::SetupSeparateTitleControls(
window(),
st::groupCallTitle))
#endif // !Q_OS_MAC
, _powerSaveBlocker(std::make_unique<base::PowerSaveBlocker>(
base::PowerSaveBlockType::PreventDisplaySleep,
u"Video chat is active"_q,
window()->windowHandle()))
, _viewport(
std::make_unique<Viewport>(widget(), PanelMode::Wide, _window.backend()))
, _mute(std::make_unique<Ui::CallMuteButton>(
@@ -302,9 +307,9 @@ void Panel::initWidget() {
updateControlsGeometry();
}
// title geometry depends on _controls->geometry,
// some geometries depends on _controls->controls.geometry,
// which is not updated here yet.
crl::on_main(widget(), [=] { refreshTitle(); });
crl::on_main(widget(), [=] { updateControlsGeometry(); });
}, lifetime());
}
@@ -1368,7 +1373,7 @@ void Panel::initLayout() {
initGeometry();
#ifndef Q_OS_MAC
_controls->raise();
_controls->wrap.raise();
Ui::Platform::TitleControlsLayoutChanged(
) | rpl::start_with_next([=] {
@@ -1413,7 +1418,7 @@ QRect Panel::computeTitleRect() const {
#ifdef Q_OS_MAC
return QRect(70, 0, width - remove - 70, 28);
#else // Q_OS_MAC
const auto controls = _controls->geometry();
const auto controls = _controls->controls.geometry();
const auto right = controls.x() + controls.width() + skip;
return (controls.center().x() < width / 2)
? QRect(right, 0, width - right - remove, controls.height())
@@ -1884,7 +1889,8 @@ void Panel::updateControlsGeometry() {
#ifdef Q_OS_MAC
const auto controlsOnTheLeft = true;
#else // Q_OS_MAC
const auto controlsOnTheLeft = _controls->geometry().center().x()
const auto center = _controls->controls.geometry().center();
const auto controlsOnTheLeft = center.x()
< widget()->width() / 2;
#endif // Q_OS_MAC
const auto menux = st::groupCallMenuTogglePosition.x();

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/flags.h"
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_choose_join_as.h"
@@ -21,6 +22,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class Image;
namespace base {
class PowerSaveBlocker;
} // namespace base
namespace Data {
class PhotoMedia;
class CloudImageView;
@@ -51,7 +56,7 @@ namespace Toast {
class Instance;
} // namespace Toast
namespace Platform {
class TitleControls;
struct SeparateTitleControls;
} // namespace Platform
} // namespace Ui
@@ -194,9 +199,11 @@ private:
rpl::variable<PanelMode> _mode;
#ifndef Q_OS_MAC
std::unique_ptr<Ui::Platform::TitleControls> _controls;
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
#endif // !Q_OS_MAC
const std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker;
rpl::lifetime _callLifetime;
object_ptr<Ui::FlatLabel> _title = { nullptr };

View File

@@ -129,7 +129,8 @@ object_ptr<ShareBox> ShareInviteLinkBox(
auto submitCallback = [=](
std::vector<not_null<PeerData*>> &&result,
TextWithTags &&comment,
Api::SendOptions options) {
Api::SendOptions options,
Data::ForwardOptions) {
if (*sending || result.empty()) {
return;
}

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"
@@ -72,6 +72,12 @@ std::map<int, const char*> BetaLogs() {
"- Fix crash in monospace blocks processing.\n"
"- Fix reaction animations stopping after an hour uptime.\n"
},
{
3004006,
"- Add snap layouts support on Windows 11.\n"
"- Fix crash in drafts after accounts switching.\n"
}
};
};

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

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