Compare commits

..

217 Commits

Author SHA1 Message Date
John Preston
8f26a24f78 Beta version 6.2.5.
- New profile pages design.
- Qt 6.10 on Linux.
2025-10-29 14:34:51 +04:00
John Preston
6abd4bae58 Fix build with Qt 6 on Windows. 2025-10-29 14:34:51 +04:00
John Preston
f8844750f5 Fix build with Xcode. 2025-10-29 14:34:51 +04:00
John Preston
de984d44ac Fix build on Windows. 2025-10-29 14:34:51 +04:00
23rd
babcefeb23 Returned button for reaction reporting to profile info. 2025-10-29 13:15:05 +03:00
23rd
71a55290ab Removed display of empty placeholder for gifts when gifts are loaded. 2025-10-28 20:38:26 +03:00
23rd
8ef0b88633 Slightly improved position of status in profile top bar. 2025-10-28 14:08:11 +03:00
23rd
f9c3415aa7 Added handler of pinned gifts order event to profile top bar. 2025-10-28 13:53:41 +03:00
23rd
deb7d5914f Fixed api ability to toggle pinned star gifts. 2025-10-28 13:53:41 +03:00
23rd
469e4394bd Attempted to improve flexible scroll in some cases. 2025-10-28 13:53:41 +03:00
23rd
1bcf9dda0a Added placeholder for empty list of self gifts to edit peer color box. 2025-10-28 13:53:41 +03:00
23rd
02fafde09d Added correspond premium feature premium box in edit peer color box. 2025-10-28 13:53:41 +03:00
23rd
de5e1b3452 Added boost hint below preview in edit peer color box for groups. 2025-10-28 13:53:41 +03:00
23rd
4381db691b Slightly improved color of action buttons in profile top bar. 2025-10-28 13:53:41 +03:00
23rd
bd879262c2 Added limit for count of actions to profile top bar. 2025-10-28 13:53:41 +03:00
23rd
da88b4c475 Centered status with all elements as unified group in profile top bar. 2025-10-28 13:53:41 +03:00
23rd
276a18cddd Returned close button to profile top bar in Side wrap. 2025-10-28 13:53:41 +03:00
23rd
6eb54e55a5 Improved style of verified check in profile top bar. 2025-10-28 13:53:41 +03:00
23rd
e7fa330215 Split verified check icon. 2025-10-28 13:53:41 +03:00
23rd
27ed160a40 Added handler for palette changed event to profile top bar. 2025-10-28 13:53:40 +03:00
23rd
180b614c86 Improved foreground of pattern emoji in profile top bar. 2025-10-28 13:53:40 +03:00
23rd
e2e6b64632 Improved z-order of main button in edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
be46aacbe5 Moved up button for emoji status in edit peer color box for channels. 2025-10-28 13:53:40 +03:00
23rd
3bd46f3415 Added about labels about profile colors to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
7e6e2960bd Added ability to edit profile color to edit peer color box for channels. 2025-10-28 13:53:40 +03:00
23rd
9df4377450 Improved ability set emoji status locally in profile top bar. 2025-10-28 13:53:40 +03:00
23rd
294ab035f0 Moved out function for tabs in edit peer color box to anon namespace. 2025-10-28 13:53:40 +03:00
23rd
f96554e271 Moved out top bar preview creation to function in edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
2c1fdbe55b Added ability to retrieve required boost level for peer colors to api. 2025-10-28 13:53:40 +03:00
23rd
b5bce29514 Added about text for profile to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
97557b85d9 Added ability to apply profile style to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
8a5cc70d9b Added ability to set unique gift from edit peer color box for profile. 2025-10-28 13:53:40 +03:00
23rd
d5075891b2 Added ability to wear unique gift locally to profile top bar. 2025-10-28 13:53:40 +03:00
23rd
4df778f4e9 Added always display of story outline while preview to profile top bar. 2025-10-28 13:53:40 +03:00
23rd
3cda66f1f3 Added reset button for profile to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
e0991d9376 Added simple emoji pattern selector for profile to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
8c94742070 Added ability to set emoji pattern locally to profile top bar. 2025-10-28 13:53:40 +03:00
23rd
419b2b02bc Added ability to set bg locally to profile top bar. 2025-10-28 13:53:40 +03:00
23rd
b4998527dd Added simple profile top bar preview to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
8ac8598b20 Replaced controller in descriptor for profile top bar. 2025-10-28 13:53:40 +03:00
23rd
f7ddf8b024 Added ability to provide custom wrap value to profile top bar. 2025-10-28 13:53:40 +03:00
23rd
18f1563829 Added ability to provide custom key to profile top bar. 2025-10-28 13:53:40 +03:00
23rd
20ba35c0a7 Added simple color selector for profile to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
de4764c1c7 Added simple mode for profiles to color selector. 2025-10-28 13:53:40 +03:00
23rd
31bb8a3ac5 Extracted ColorSelector class to td_ui. 2025-10-28 13:53:40 +03:00
23rd
e0156abe7c Added ability to use color sample for peer profile color. 2025-10-28 13:53:40 +03:00
23rd
7d02e92843 Extracted ColorSample class to td_ui. 2025-10-28 13:53:40 +03:00
23rd
80aef75ea5 Added ability to retrieve color data from index for profile to api. 2025-10-28 13:53:40 +03:00
23rd
c940062787 Added initial sections for tabs to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
4b3b9ad140 Added simple tab selector to edit peer color box. 2025-10-28 13:53:40 +03:00
23rd
1bb2efc346 Split logic of box for edit peer color and inner of box. 2025-10-28 13:53:40 +03:00
23rd
0b699b45b1 Centered title with all badges as unified group in profile top bar. 2025-10-28 13:53:40 +03:00
23rd
3c0487db81 Fixed status position on change in profile top bar. 2025-10-28 13:53:40 +03:00
23rd
f62869e4df Fixed link color for status with profile color in profile top bar. 2025-10-28 13:53:40 +03:00
23rd
fce459f612 Added initial support of solo pattern emoji to profile top bar. 2025-10-28 13:53:40 +03:00
23rd
6a003114db Added single-loop mode for gift animations to top bar in Side wrap. 2025-10-28 13:53:40 +03:00
23rd
39ed5cab0d Added recreation of action buttons in profile top bar on full receiving. 2025-10-28 13:53:40 +03:00
23rd
4b67c8f4bb Set fixed position of mute menu in profile top bar. 2025-10-28 13:53:40 +03:00
23rd
65604add65 Removed action buttons for notification user from profile top bar. 2025-10-28 13:53:40 +03:00
23rd
3e69f65d1c Moved menu toggle from top to action button in profile top bar. 2025-10-28 13:53:40 +03:00
23rd
0b26dbbc9e Added middle elision to action buttons in profile top bar. 2025-10-28 13:53:40 +03:00
23rd
152d943f3d Fixed overridden colors for lottie icon in action buttons from top bar. 2025-10-28 13:53:40 +03:00
23rd
7fffd1d318 Added support of profile color to profile top bar for badges. 2025-10-28 13:53:40 +03:00
23rd
5d875341f4 Added support of profile color to profile top bar for stories outline. 2025-10-28 13:53:40 +03:00
23rd
61dbd4c4c2 Fixed possible crash from profile top bar in groups. 2025-10-28 13:53:40 +03:00
23rd
5f42011c7b Added support of profile color to top bar for gradient backgrounds. 2025-10-28 13:53:40 +03:00
23rd
7e56174ba9 Added support of profile color to profile top bar for solid backgrounds. 2025-10-28 13:53:40 +03:00
23rd
0a83b3f58c Added convenient method to resolve profile color by peer. 2025-10-28 13:53:40 +03:00
23rd
71fec23311 Added applying of profile colors to peers. 2025-10-28 13:53:40 +03:00
23rd
2ba3035a13 Added new data change flag for profile colors. 2025-10-28 13:53:40 +03:00
23rd
7a653a8e1b Added initial support of profile colors to peers. 2025-10-28 13:53:40 +03:00
23rd
a93b32fb53 Added initial api support of profile colors. 2025-10-28 13:53:40 +03:00
23rd
d82cc350c0 Added initial data structures for profile colors. 2025-10-28 13:53:39 +03:00
23rd
5997a7d48a Replaced color calculation from int with single function. 2025-10-28 13:53:39 +03:00
23rd
7641fb6712 Converted ABGR to RGB in fireworks module. 2025-10-28 13:53:39 +03:00
23rd
dfaf9b9d43 Fixed position of right sublabel for notes in profile section. 2025-10-28 13:53:39 +03:00
23rd
6147b0eec0 Removed main buttons from profile sections. 2025-10-28 13:53:39 +03:00
23rd
2eefef3649 Reduced left padding for info labels in profile section. 2025-10-28 13:53:39 +03:00
23rd
1905a67e6c Removed mute toggle from profile section. 2025-10-28 13:53:39 +03:00
23rd
38ab18f6fb Moved verification label under info in profile. 2025-10-28 13:53:39 +03:00
23rd
ddcc20c8a7 Removed actions button from profile top bar in section for stories. 2025-10-28 13:53:39 +03:00
23rd
f3c6763058 Removed additional divider after profile top bar. 2025-10-28 13:53:39 +03:00
23rd
91a11d2e74 Moved out some calculations from paint event in music button. 2025-10-28 13:53:39 +03:00
23rd
fb880481a4 Replaced custom music data formatting with common song name formatter. 2025-10-28 13:53:39 +03:00
23rd
9e6703f02f Slightly improved style of top bar in profile. 2025-10-28 13:53:39 +03:00
23rd
da5435d1cb Added leave button to action buttons in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
db0726364b Added report button to action buttons in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
944b2de852 Added join button to action buttons in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
53927502af Added gift button to action buttons in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
828d8ea051 Added discuss button to action buttons in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
f2e345b39f Added edit button to profile top bar for stories section. 2025-10-28 13:53:39 +03:00
23rd
6f625a899c Added profile top bar to self stories section. 2025-10-28 13:53:39 +03:00
23rd
28bc8c3bf3 Moved out creation of flexible scroll into info content widget. 2025-10-28 13:53:39 +03:00
23rd
ef30949943 Moved out creation of smooth scroll for info profile top bar to helper. 2025-10-28 13:53:39 +03:00
23rd
4f888bc418 Added ability to click on pinned gifts from profile top bar. 2025-10-28 13:53:39 +03:00
23rd
34caf6967e Slightly optimized calculating of userpic geometry in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
a728c783d9 Removed music button from cover. 2025-10-28 13:53:39 +03:00
23rd
934d232653 Added ability to set custom bg color to music button in profile. 2025-10-28 13:53:39 +03:00
23rd
4fbc7771c9 Removed cover from profile. 2025-10-28 13:53:39 +03:00
23rd
571ab422bf Moved out music button from cover to inner widget for profile. 2025-10-28 13:53:39 +03:00
23rd
9592f4de6e Added center alignment to music button in profile. 2025-10-28 13:53:39 +03:00
23rd
c126d99fd0 Fixed colorized online text in status from profile top bar. 2025-10-28 13:53:39 +03:00
23rd
11c8a272ec Improved processing of hiding all collectible things in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
f7170b8c50 Added initial hiding animation for gifts in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
9ce28f4cb7 Added offset to background in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
522a457d98 Fixed background in profile top bar on width resizing. 2025-10-28 13:53:39 +03:00
23rd
9e9ffaa27b Added menu to userpic when stories are presented in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
9ab4a8ab6e Added stories support to profile top bar. 2025-10-28 13:53:39 +03:00
23rd
5c784081c2 Replaced top call button with action button in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
9c84c0afe9 Improved adaptive colors in top buttons from profile top bar. 2025-10-28 13:53:39 +03:00
23rd
accc5e8b10 Added adaptive color to stars rating in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
1e0c71a411 Added simple appearing animation to pinned gifts in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
663687884b Added adaptive color to badges in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
ade2bdcafd Added ability to set overridden style to profile badges. 2025-10-28 13:53:39 +03:00
23rd
9e01e64f63 Fixed possible crash from profile badge tooltip. 2025-10-28 13:53:39 +03:00
23rd
7cc0fd879d Added adaptive color to top buttons in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
42bd835800 Added adaptive background color to action buttons in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
ef7b5cb8e7 Added initial few action buttons to profile top bar. 2025-10-28 13:53:39 +03:00
23rd
2651d79b63 Added ability to convert action button to toggle in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
ddc12c3f71 Added ability to pass static icon to action button in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
9c3c2e9fa7 Divided scrolling process of profile top bar on different steps. 2025-10-28 13:53:39 +03:00
23rd
564f9ac38d Added action button for profile top bar as separated class. 2025-10-28 13:53:39 +03:00
23rd
1b49bd0843 Adjusted some styles for profile top bar. 2025-10-28 13:53:39 +03:00
23rd
831d79d912 Added simple container that fits within certain width. 2025-10-28 13:53:39 +03:00
23rd
d61f809cca Added gradient background to recent gifts in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
130176fd7e Replaced raw DocumentId with full star gift data in api recent gifts. 2025-10-28 13:53:39 +03:00
23rd
1ea9be9877 Fixed labels position in profile top bar with back button. 2025-10-28 13:53:39 +03:00
23rd
e3b56aa05b Added initial support of pinned gifts in profile top bar. 2025-10-28 13:53:39 +03:00
23rd
4f4195c88a Added ability to api request of last pinned gifts for peer profile. 2025-10-28 13:53:39 +03:00
23rd
1d9a8af174 Added animated emoji pattern to profile top bar. 2025-10-28 13:53:39 +03:00
23rd
8b1e7e1fa9 Added initial static emoji pattern to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
eeb0b62bfa Moved out pattern points draw for unique gifts to separated class. 2025-10-28 13:53:38 +03:00
23rd
0a114bbe4a Added initial support of collectible background to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
474458e4c3 Moved out gradient for unique gifts to separated class. 2025-10-28 13:53:38 +03:00
23rd
c6b73631d8 Added support of topic icons to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
bce318c48e Added support of video userpic to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
9e4ac1c835 Created simple class for video userpic processing. 2025-10-28 13:53:38 +03:00
23rd
53933db077 Added userpic for monoforum broadcasts to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
4156d8e908 Added ability to open peer photo to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
6491d83085 Added members click callback to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
6259d30e2b Added online members count to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
3ee7a0dc16 Added badge gift tooltip to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
43d8723e35 Moved out badge gift tooltip from info profile cover to separated class. 2025-10-28 13:53:38 +03:00
23rd
d59ab17ac5 Added last seen button and rating to status in profile top bar. 2025-10-28 13:53:38 +03:00
23rd
d26cda097f Added ability to set opacity to stars rating from info profile cover. 2025-10-28 13:53:38 +03:00
23rd
6daa58b596 Improved width processing of title in profile top bar. 2025-10-28 13:53:38 +03:00
23rd
d0920d9eb9 Added calls button to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
fbd353501b Added toggle menu button to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
9876aa64e0 Moved out close and back buttons within profile top bar class. 2025-10-28 13:53:38 +03:00
23rd
b8ccf7b3d4 Replaced args for profile top bar with descriptor. 2025-10-28 13:53:38 +03:00
23rd
c15b5914b2 Added simple userpic to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
73aebb8890 Added initial implementation of smooth scroll to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
f9270710be Added initial position processing of title in profile top bar. 2025-10-28 13:53:38 +03:00
23rd
0b63abf5b4 Added initial status to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
51ae4af83e Extracted peer status label logic into separate StatusLabel class. 2025-10-28 13:53:38 +03:00
23rd
4832982988 Added initial title to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
b4d49715dc Provided peer to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
0de7b4eb39 Added back button to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
3e88451ea8 Added close button to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
5468fde492 Set profile top bar as flexible. 2025-10-28 13:53:38 +03:00
23rd
f17e13cad0 Added ability to paint round edges to profile top bar. 2025-10-28 13:53:38 +03:00
23rd
f511449273 Added dummy new class for top bar within info profile. 2025-10-28 13:53:38 +03:00
23rd
bbe65e212a Added initial ability to create pinned widgets to info profile. 2025-10-28 13:53:38 +03:00
ilya-fedin
5ebd0df15f Update patches on Linux 2025-10-25 20:56:56 +04:00
Ilya Fedin
66a34bcb89 Fix Docker image build 2025-10-24 15:53:58 +04:00
Sv. Lockal
acad1d4175 Fix compilation with Clang-21 + libstdc++-16
.emplace() can only be used on constructible objects, while private
nested struct is not considered as constructible due to access control.

Closes #29939

Signed-off-by: Sv. Lockal <lockalsash@gmail.com>
2025-10-24 07:15:19 +04:00
Ilya Fedin
716fade52a Remove unused yasm dependency from macOS packaged action
It's not used since openh264 is not built as part of tg_owt
2025-10-23 11:41:39 +04:00
Ilya Fedin
3b9312d9ac Install only really needed parts of Qt in macOS packaged action 2025-10-23 11:41:39 +04:00
Ilya Fedin
f87d072c79 Force bundled abseil-cpp for WebRTC in macOS packaged action
It fails to build with the one from brew due to a change in the API of new abseil-cpp versions
2025-10-23 11:08:21 +04:00
John Preston
b61724019a Update patches revision in snapcraft. 2025-10-23 10:56:01 +04:00
John Preston
3ffdb1ee56 Fix build on Linux. 2025-10-23 10:29:24 +04:00
Ilya Fedin
5fbf280e4a Combine startUrls and sendPaths
This commit allows to handle multiple URLs of all types as positional arguments simultaneously:
* tg:// links
* tonsite:// links
* interpret:// file paths
* generic file paths (to share files)

This allows to Drag'n'Drop files to the Telegram shortcut/binary.
2025-10-23 10:23:53 +04:00
Ilya Fedin
f787e0fa1d Allow to pass URLs without --
This allows to make tdesktop as a URL handler with generic file dialog in e.g. Firefox (previously the window was opening but no URL handled)
2025-10-23 10:23:53 +04:00
23rd
0d33b92d24 Added ability to edit cover for videos from edit caption box. 2025-10-23 10:09:08 +04:00
John Preston
8e5d0c66db Revert downgrade of xdg-desktop-portal. 2025-10-23 10:06:04 +04:00
Ilya Fedin
6e8ac60399 Actually share files with Drag'n'Drop to shortcut on macOS
The code supported this since 4.8.3 (6aef6d7f4e) for Linux but wasn't actually allowed on macOS via plist.
2025-10-22 22:02:37 +04:00
John Preston
f8bd80109c Update lib_base submodule. 2025-10-22 22:02:02 +04:00
Ilya Fedin
de89d349ad Implement launching maps on Linux 2025-10-22 22:01:45 +04:00
Ilya Fedin
dbc9beaa19 Make psLaunchMaps async 2025-10-22 22:01:45 +04:00
Ilya Fedin
1f171c4ed1 Remove unused includes from specific_linux.cpp 2025-10-22 22:01:45 +04:00
John Preston
2e03888505 Update patches revision. 2025-10-22 22:00:42 +04:00
Ilya Fedin
ab5eafbe68 Update Qt to 6.10.0 2025-10-22 21:59:38 +04:00
Ilya Fedin
73014a33fe Fix build with Qt 6.10 2025-10-22 21:59:38 +04:00
Ilya Fedin
7bdbe0ef77 Examples and tests don't get built by default with Qt 6 2025-10-22 21:59:38 +04:00
Ilya Fedin
d4dbad4649 Remove -no-sbom
Following 7d78de0673
2025-10-22 21:59:38 +04:00
Ilya Fedin
24b23bbb5a Don't hardcode Qt version in the action 2025-10-22 21:59:38 +04:00
John Preston
abab44a02b Adapt latest lib_ui changes for accessibility. 2025-10-22 21:56:41 +04:00
Reza Bakhshi Laktasaraei
b9c07e644f Setting accessibleName and accessibleRole for objects 2025-10-22 20:40:08 +04:00
Reza Bakhshi Laktasaraei
ce9c3b4ef8 enable clicking on country chooser by pressing space bar and enter key 2025-10-22 20:40:08 +04:00
Reza Bakhshi Laktasaraei
13862bd561 adding the entry poin for enabling accessibility 2025-10-22 20:40:08 +04:00
John Preston
856d38df49 Fix crash in stories volume control destructor. 2025-10-22 17:32:17 +04:00
John Preston
27a5e13107 Fix glitch in forum row switch from/to. 2025-10-22 17:31:43 +04:00
John Preston
a71c24f803 Version 6.2.4.
- Highlight links in contact notes.
- Show menu with screen share controls in wide group call mode.
- Fix possible crash in saved music removing.
- Fix crash in theme editor.
2025-10-22 12:54:20 +04:00
John Preston
758ec52b91 Fix build. 2025-10-22 12:54:20 +04:00
Ilya Fedin
96418bb9f1 Make QFileOpenEvent timeout immediate 2025-10-22 12:48:33 +04:00
23rd
f750d94b2d Fixed processing of recent self forwards from share box for non-premium. 2025-10-22 12:30:54 +04:00
23rd
6eb9695e1e Added links support to user notes. 2025-10-22 12:23:40 +04:00
John Preston
33bbad8053 Fix possible crash in saved music removing. 2025-10-22 12:18:12 +04:00
John Preston
3ea34461b2 Fix typing animation in chats list updates. 2025-10-22 12:18:12 +04:00
John Preston
b6a202b721 Fix error display in webview box. 2025-10-22 12:18:12 +04:00
John Preston
4d8cb022c5 Make screen share controls available. 2025-10-22 12:18:12 +04:00
John Preston
3046318de5 Update submodules. 2025-10-22 12:18:12 +04:00
John Preston
c351598f13 Simplify OpenGL surface renderer interface. 2025-10-22 12:18:12 +04:00
John Preston
02084be583 Fix crash in theme editor. 2025-10-13 16:43:13 +04:00
John Preston
e117d08b2c Version 6.2.3.
- Fix crash when viewing messages with hidden senders.
- Fix possible crash when editing contacts.
- Fix layout of character count warning in contact notes.
- Fix icons when editing contacts.
- Fix color of sending messages animation in complex themes.
- Fix crash in chat open on Flatpak build.
2025-10-12 12:06:00 +04:00
John Preston
58783170cd Fix a crash with Qt 6.10. 2025-10-12 12:04:13 +04:00
John Preston
9ba2426c02 Fix crash in messages with hidden senders. 2025-10-12 11:59:11 +04:00
23rd
2cbaa7b03d Removed ability to suggest things for contacts with paid messages. 2025-10-12 11:58:57 +04:00
23rd
836a0f3a73 Removed photo buttons in edit contact box when adding new contact. 2025-10-12 11:58:57 +04:00
23rd
e9977f551f Moved info profile text file to td_ui. 2025-10-12 11:58:57 +04:00
23rd
78abe362cd Disabled simple sending animation of text messages when window is wide. 2025-10-11 20:30:45 +03:00
23rd
47c2922d55 Fixed possible crash in edit contact box. 2025-10-11 17:04:02 +03:00
23rd
690f3fecbb Fixed stuck box on resetting contact photo to original. 2025-10-11 17:04:02 +03:00
23rd
d9242db7b3 Fixed display of animated icons in edit contact box on non-retina. 2025-10-11 16:29:21 +03:00
23rd
a9b5e22b37 Slightly improved behavior of visibility for chars limit in notes. 2025-10-11 14:00:44 +03:00
23rd
7b8f1704dc Applied easeOutQuint on X axis for simple sending animation for text. 2025-10-11 12:21:42 +03:00
23rd
de88ddf42b Fixed color of bubbles while simple sending animation in complex themes. 2025-10-11 12:21:42 +03:00
177 changed files with 7863 additions and 2148 deletions

View File

@@ -69,7 +69,7 @@ jobs:
run: |
brew update
brew upgrade || true
brew install ada-url autoconf automake boost cmake ffmpeg@6 jpeg-xl libavif libheif libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
brew install ada-url autoconf automake boost cmake ffmpeg@6 jpeg-xl libavif libheif libtool openal-soft openh264 openssl opus ninja pkg-config python qtbase qtimageformats qtsvg xz
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcodebuild -version > CACHE_KEY.txt
@@ -82,7 +82,7 @@ jobs:
fi
echo "CACHE_KEY=`md5 -q CACHE_KEY.txt`" >> $GITHUB_ENV
echo "MACOSX_DEPLOYMENT_TARGET=$(grep 'set(QT_SUPPORTED_MIN_MACOS_VERSION' /opt/homebrew/Cellar/qt/6.9.2/lib/cmake/Qt6/Qt6ConfigExtras.cmake | sed -E 's/^.*"(.*)"\)$/\1/')" >> $GITHUB_ENV
echo "MACOSX_DEPLOYMENT_TARGET=$(grep 'set(QT_SUPPORTED_MIN_MACOS_VERSION' /opt/homebrew/Cellar/qtbase/*/lib/cmake/Qt6/Qt6ConfigExtras.cmake | sed -E 's/^.*"(.*)"\)$/\1/')" >> $GITHUB_ENV
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
@@ -115,7 +115,8 @@ jobs:
cmake -Bbuild -GNinja . \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS_DEBUG="" \
-DCMAKE_CXX_FLAGS_DEBUG=""
-DCMAKE_CXX_FLAGS_DEBUG="" \
-DCMAKE_DISABLE_FIND_PACKAGE_absl=ON
cmake --build build --parallel

View File

@@ -1044,6 +1044,8 @@ PRIVATE
info/polls/info_polls_results_widget.h
info/profile/info_profile_actions.cpp
info/profile/info_profile_actions.h
info/profile/info_profile_badge_tooltip.cpp
info/profile/info_profile_badge_tooltip.h
info/profile/info_profile_badge.cpp
info/profile/info_profile_badge.h
info/profile/info_profile_cover.cpp
@@ -1058,8 +1060,10 @@ PRIVATE
info/profile/info_profile_members_controllers.h
info/profile/info_profile_phone_menu.cpp
info/profile/info_profile_phone_menu.h
info/profile/info_profile_text.cpp
info/profile/info_profile_text.h
info/profile/info_profile_status_label.cpp
info/profile/info_profile_status_label.h
info/profile/info_profile_top_bar.cpp
info/profile/info_profile_top_bar.h
info/profile/info_profile_values.cpp
info/profile/info_profile_values.h
info/profile/info_profile_widget.cpp
@@ -1669,8 +1673,12 @@ PRIVATE
ui/item_text_options.cpp
ui/item_text_options.h
ui/resize_area.h
ui/top_background_gradient.cpp
ui/top_background_gradient.h
ui/unread_badge.cpp
ui/unread_badge.h
ui/peer/video_userpic_player.cpp
ui/peer/video_userpic_player.h
window/main_window.cpp
window/main_window.h
window/notifications_manager.cpp

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M19.035,15.184L16.561,14.901C15.786,14.771 14.725,15.259 13.376,16.365C13.315,16.416 13.142,16.533 12.95,16.54C12.764,16.547 12.56,16.444 12.493,16.408C10.162,15.134 8.734,13.687 7.467,11.306C7.435,11.247 7.356,11.086 7.38,10.938C7.402,10.802 7.524,10.676 7.567,10.624C8.595,9.364 9.108,8.299 9.108,7.429L8.826,4.974C8.709,3.99 7.881,3.25 6.887,3.25L5.202,3.25C4.101,3.25 3.185,4.166 3.254,5.267C3.77,13.586 10.424,20.23 18.733,20.746C19.834,20.815 20.75,19.899 20.75,18.798L20.75,17.113C20.76,16.129 20.019,15.301 19.035,15.184Z" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 649 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M11.157,13.843L11.157,21.158L7.506,21.158C6.265,21.158 5.26,20.152 5.26,18.911L5.26,13.843L11.157,13.843ZM18.741,13.829L18.74,18.911C18.74,20.152 17.735,21.158 16.494,21.158L12.842,21.158L12.842,13.843L18.6,13.843C18.648,13.843 18.695,13.838 18.741,13.829ZM14.727,3.208C15.308,3.208 15.832,3.33 16.301,3.575C16.77,3.819 17.144,4.158 17.423,4.591C17.702,5.025 17.841,5.519 17.841,6.075C17.841,6.454 17.768,6.802 17.622,7.119C17.492,7.402 17.319,7.652 17.104,7.871L17.037,7.935L19.302,7.935C19.923,7.935 20.426,8.438 20.426,9.058L20.426,11.313C20.426,11.933 19.923,12.436 19.302,12.436L18.74,12.436L18.741,12.45C18.695,12.441 18.648,12.436 18.6,12.436L12.842,12.436L12.842,7.951L11.16,7.951L11.16,6.729C11.16,6.118 10.977,5.631 10.611,5.268C10.245,4.905 9.804,4.723 9.287,4.723C8.8,4.723 8.403,4.853 8.097,5.114C7.79,5.375 7.637,5.736 7.637,6.197C7.637,6.668 7.827,7.078 8.207,7.427C8.521,7.715 8.94,7.885 9.464,7.935L11.157,7.935L11.157,12.436L4.698,12.436C4.077,12.436 3.574,11.933 3.574,11.313L3.574,9.058C3.574,8.438 4.077,7.935 4.698,7.935L6.849,7.935C6.603,7.702 6.408,7.43 6.265,7.119C6.119,6.802 6.046,6.454 6.046,6.075C6.046,5.519 6.185,5.025 6.464,4.591C6.743,4.158 7.118,3.819 7.59,3.575C8.061,3.33 8.585,3.208 9.16,3.208C9.8,3.208 10.369,3.371 10.868,3.698C11.367,4.025 11.727,4.487 11.948,5.085C12.169,4.487 12.527,4.025 13.023,3.698C13.519,3.371 14.087,3.208 14.727,3.208ZM14.608,4.723C14.087,4.723 13.643,4.905 13.276,5.268C12.91,5.631 12.843,6.118 12.843,6.729L12.842,7.935L14.423,7.935C14.909,7.888 15.305,7.739 15.61,7.487L15.679,7.427C16.059,7.078 16.249,6.668 16.249,6.197C16.249,5.736 16.096,5.375 15.79,5.114C15.484,4.853 15.09,4.723 14.608,4.723Z" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M17.176,12.972C19.682,12.972 21.713,15.004 21.713,17.509C21.713,20.015 19.682,22.046 17.176,22.046C14.67,22.046 12.639,20.015 12.639,17.509C12.639,15.004 14.67,12.972 17.176,12.972ZM11.343,12.729C11.998,12.729 12.619,12.78 13.209,12.881C11.869,14.011 11.019,15.701 11.019,17.59C11.019,18.624 11.273,19.598 11.723,20.453L5.218,20.453C4.358,20.453 3.759,20.317 3.422,20.046C3.085,19.774 2.917,19.391 2.917,18.895C2.917,18.245 3.113,17.562 3.505,16.845C3.897,16.128 4.462,15.459 5.2,14.836C5.938,14.214 6.824,13.707 7.86,13.316C8.895,12.925 10.056,12.729 11.343,12.729ZM17.176,14.593C16.848,14.593 16.582,14.858 16.582,15.186L16.582,16.915L14.853,16.915C14.525,16.915 14.259,17.181 14.259,17.509C14.259,17.837 14.525,18.103 14.853,18.103L16.582,18.103L16.582,19.832C16.582,20.16 16.848,20.426 17.176,20.426C17.504,20.426 17.77,20.16 17.77,19.832L17.77,18.103L19.499,18.103C19.827,18.103 20.093,17.837 20.093,17.509C20.093,17.181 19.827,16.915 19.499,16.915L17.77,16.915L17.77,15.186C17.77,14.858 17.504,14.593 17.176,14.593ZM11.343,10.974C12.117,10.974 12.822,10.784 13.459,10.405C14.096,10.025 14.605,9.514 14.985,8.872C15.365,8.229 15.556,7.507 15.556,6.706C15.556,5.934 15.364,5.233 14.98,4.605C14.596,3.976 14.085,3.476 13.446,3.105C12.807,2.733 12.106,2.548 11.343,2.548C10.579,2.548 9.878,2.735 9.239,3.111C8.601,3.486 8.089,3.989 7.705,4.621C7.322,5.253 7.13,5.953 7.13,6.722C7.133,7.513 7.325,8.229 7.705,8.872C8.085,9.514 8.595,10.025 9.234,10.405C9.873,10.784 10.576,10.974 11.343,10.974Z" fill="#FFFFFF" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12.329,3.574C13.94,3.574 15.245,4.88 15.245,6.491L15.245,7.463C15.245,8 14.81,8.435 14.273,8.435C13.736,8.435 13.301,8 13.301,7.463L13.301,6.491C13.301,5.954 12.866,5.519 12.329,5.519L6.171,5.519C5.634,5.519 5.199,5.954 5.199,6.491L5.199,17.509C5.199,18.046 5.634,18.481 6.171,18.481L12.329,18.481C12.866,18.481 13.301,18.046 13.301,17.509L13.301,16.537C13.301,16 13.736,15.565 14.273,15.565C14.81,15.565 15.245,16 15.245,16.537L15.245,17.509C15.245,19.12 13.94,20.426 12.329,20.426L6.171,20.426C4.56,20.426 3.255,19.12 3.255,17.509L3.255,6.491C3.255,4.88 4.56,3.574 6.171,3.574L12.329,3.574ZM19.045,7.945L21.927,11.13C22.374,11.624 22.374,12.376 21.927,12.87L19.045,16.055C18.685,16.453 18.07,16.484 17.672,16.124C17.274,15.763 17.243,15.149 17.603,14.751L19.212,12.972L10.06,12.972C9.523,12.972 9.088,12.537 9.088,12C9.088,11.463 9.523,11.028 10.06,11.028L19.212,11.028L17.603,9.249C17.257,8.867 17.272,8.285 17.626,7.921L17.672,7.876C18.07,7.516 18.685,7.547 19.045,7.945Z" fill="#FFFFFF" fill-rule="nonzero"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12.009,2.759C17.307,2.759 21.602,6.688 21.602,11.533C21.602,16.379 17.307,20.307 12.009,20.307C10.566,20.307 9.197,20.016 7.969,19.494C7.602,19.784 7.273,20.004 6.98,20.154C6.092,20.609 5.496,20.772 4.144,20.914C3.778,20.952 3.534,20.625 3.819,20.34C4.452,19.708 4.793,18.577 4.966,17.49C3.384,15.925 2.417,13.833 2.417,11.533C2.417,6.688 6.711,2.759 12.009,2.759Z" fill="#FFFFFF" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M11.922,2.528C12.663,2.528 13.264,3.129 13.264,3.87L13.264,4.492C16.232,5.216 18.32,7.681 18.32,10.805L18.32,14.987C18.32,15.133 18.373,15.273 18.47,15.382L19.678,16.735C20.342,17.382 19.668,18.491 18.731,18.491L5.261,18.491C4.324,18.491 3.661,17.382 4.324,16.735L5.532,15.382C5.629,15.273 5.682,15.133 5.682,14.987L5.682,10.805C5.682,7.735 7.675,5.317 10.556,4.538L10.556,3.87C10.556,3.151 11.121,2.564 11.831,2.529L11.922,2.528ZM12.001,21.472C13.159,21.472 14.107,20.558 14.107,19.525L9.895,19.525C9.895,20.558 10.832,21.472 12.001,21.472Z" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12,2.833C17.063,2.833 21.167,6.937 21.167,12C21.167,17.063 17.063,21.167 12,21.167C6.937,21.167 2.833,17.063 2.833,12C2.833,6.937 6.937,2.833 12,2.833ZM12,14.444C11.347,14.444 10.813,14.957 10.779,15.602L10.778,15.689C10.778,16.364 11.325,16.911 12,16.911C12.653,16.911 13.187,16.398 13.221,15.754L13.222,15.667C13.222,14.992 12.675,14.444 12,14.444ZM12,7.111C11.404,7.111 10.922,7.594 10.922,8.19L10.922,11.995C10.922,12.591 11.404,13.074 12,13.074C12.596,13.074 13.078,12.591 13.078,11.995L13.078,8.19C13.078,7.594 12.596,7.111 12,7.111Z" fill="#FFFFFF" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 680 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M14.107,19.525C14.107,20.558 13.159,21.472 12.001,21.472C10.832,21.472 9.895,20.558 9.895,19.525L14.107,19.525ZM3.505,3.471L20.618,19.654C20.962,19.979 20.977,20.52 20.652,20.864C20.327,21.207 19.786,21.222 19.443,20.898L2.329,4.714C1.986,4.389 1.971,3.848 2.295,3.505C2.62,3.161 3.162,3.146 3.505,3.471ZM5.718,10.105L14.585,18.491L5.261,18.491C4.324,18.491 3.661,17.382 4.324,16.735L5.532,15.382C5.629,15.273 5.682,15.133 5.682,14.987L5.682,10.805C5.682,10.568 5.694,10.334 5.718,10.105ZM11.922,2.528C12.663,2.528 13.264,3.129 13.264,3.87L13.264,4.492C16.232,5.216 18.32,7.681 18.32,10.805L18.32,14.987C18.32,15.133 18.373,15.273 18.47,15.382L18.781,15.73L8.174,5.699C8.862,5.176 9.666,4.779 10.556,4.538L10.556,3.87C10.556,3.151 11.121,2.564 11.831,2.529L11.922,2.528Z" fill="#FFFFFF" fill-rule="nonzero"/>
</svg>

After

Width:  |  Height:  |  Size: 910 B

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_my_stories" = "My Stories";
"lng_menu_my_groups" = "My Groups";
"lng_menu_my_channels" = "My Channels";
"lng_open_menu" = "Open navigation menu";
"lng_disable_notifications_from_tray" = "Disable notifications";
"lng_enable_notifications_from_tray" = "Enable notifications";
@@ -127,6 +128,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_cancel" = "Cancel";
"lng_continue" = "Continue";
"lng_close" = "Close";
"lng_minimize_window" = "Minimize";
"lng_maximize_window" = "Maximize";
"lng_restore_window" = "Restore";
"lng_go_back" = "Go back";
"lng_connecting" = "Connecting...";
"lng_reconnecting#one" = "Reconnect in {count} s...";
"lng_reconnecting#other" = "Reconnect in {count} s...";
@@ -388,6 +393,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_country_ph" = "Search";
"lng_country_none" = "Country not found";
"lng_country_select" = "Select Country";
"lng_phone_number" = "Phone number";
"lng_code_ph" = "Code";
"lng_code_desc" = "We've sent an activation code to your phone.\nPlease enter it below.";
@@ -970,6 +976,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_font_family" = "Font family";
"lng_settings_color_title" = "Color preview";
"lng_settings_color_tab_profile" = "Profile";
"lng_settings_color_tab_name" = "Name";
"lng_settings_color_reply" = "Reply to your message";
"lng_settings_color_reply_channel" = "Reply to your channel message";
"lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color.";
@@ -984,10 +992,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_color_emoji_off" = "Off";
"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them.";
"lng_settings_color_emoji_about_channel" = "Select an icon to create a custom pattern for replies to your messages.";
"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name.";
"lng_settings_color_changed" = "Your name color has been updated!";
"lng_settings_color_changed_channel" = "Your channel color has been updated!";
"lng_settings_color_changed_profile" = "Your profile style has been updated!";
"lng_settings_color_changed_profile_channel" = "Your channel profile style has been updated!";
"lng_settings_color_apply" = "Apply Style";
"lng_settings_color_profile_emoji" = "Add icons to Profile";
"lng_settings_color_profile_emoji_channel" = "Profile Logo";
"lng_settings_color_reset" = "Reset Profile Color";
"lng_settings_color_profile_about" = "You can change the color of your name and customize replies to you. {link}";
"lng_settings_color_profile_about_link" = "Change {emoji}";
"lng_settings_color_choose_channel" = "Choose a color and a logo for your channel's profile";
"lng_settings_color_choose_group" = "Choose a color and a logo for the group's profile";
"lng_settings_color_group_boost_footer#one" = "The group has **{count}** boost. {link}";
"lng_settings_color_group_boost_footer#other" = "The group has **{count}** boosts. {link}";
"lng_settings_color_group_boost_footer_link" = "What are boosts?";
"lng_suggest_hide_new_title" = "Hide new chats?";
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";
@@ -1640,6 +1659,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_open_app_short" = "Open";
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
"lng_profile_open_photo" = "Open Photo";
"lng_profile_bot_permissions_title" = "Allow access to";
"lng_profile_bot_emoji_status_access" = "Emoji Status";
"lng_info_add_as_contact" = "Add to contacts";
@@ -1662,6 +1682,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_changed_photo_title" = "Photo updated";
"lng_profile_changed_photo_about" = "You can change it in {link}.";
"lng_profile_changed_photo_link" = "Settings";
"lng_profile_action_short_message" = "Message";
"lng_profile_action_short_mute" = "Mute";
"lng_profile_action_short_unmute" = "Unmute";
"lng_profile_action_short_call" = "Call";
"lng_profile_action_short_discuss" = "Discuss";
"lng_profile_action_short_gift" = "Gift";
"lng_profile_action_short_join" = "Join";
"lng_profile_action_short_report" = "Report";
"lng_profile_action_short_leave" = "Leave";
"lng_profile_action_short_more" = "More";
"lng_media_type_photos" = "Photos";
"lng_media_type_gifs" = "GIFs";
"lng_media_type_videos" = "Videos";
@@ -2736,6 +2768,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_filter_tags" = "Display folder names for each chat in the chat list.";
"lng_premium_summary_subtitle_todo_lists" = "Checklists";
"lng_premium_summary_about_todo_lists" = "Plan, assign, and complete tasks - seamlessly and efficiently.";
"lng_premium_summary_subtitle_peer_colors" = "Name and Profile Colors";
"lng_premium_summary_about_peer_colors" = "Choose a color and logo for your profile and replies to your messages.";
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
"lng_premium_summary_button" = "Subscribe for {cost} per month";
@@ -3615,6 +3649,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_your_finished" = "none left";
"lng_gift_stars_tabs_all" = "All Gifts";
"lng_gift_stars_tabs_my" = "My Gifts";
"lng_gift_stars_tabs_my_empty" = "You don't have any gifts you can use as a profile cover.";
"lng_gift_stars_tabs_my_empty_next" = "Browse gifts available for purchase {emoji}";
"lng_gift_stars_tabs_collectibles" = "Collectibles";
"lng_gift_send_title" = "Send a Gift";
"lng_gift_send_message" = "Enter Message";

View File

@@ -45,6 +45,10 @@
<file alias="photo_suggest_icon.tgs">../../animations/photo_suggest_icon.tgs</file>
<file alias="toast/saved_messages.tgs">../../animations/toast/saved_messages.tgs</file>
<file alias="toast/tagged.tgs">../../animations/toast/tagged.tgs</file>
<file alias="my_gifts_empty.tgs">../../animations/my_gifts_empty.tgs</file>
<file alias="profile_muting.tgs">../../animations/profile/profile_muting.tgs</file>
<file alias="profile_unmuting.tgs">../../animations/profile/profile_unmuting.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="6.2.2.0" />
Version="6.2.5.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 6,2,2,0
PRODUCTVERSION 6,2,2,0
FILEVERSION 6,2,5,0
PRODUCTVERSION 6,2,5,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "6.2.2.0"
VALUE "FileVersion", "6.2.5.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "6.2.2.0"
VALUE "ProductVersion", "6.2.5.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 6,2,2,0
PRODUCTVERSION 6,2,2,0
FILEVERSION 6,2,5,0
PRODUCTVERSION 6,2,5,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", "6.2.2.0"
VALUE "FileVersion", "6.2.5.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "6.2.2.0"
VALUE "ProductVersion", "6.2.5.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "data/data_peer.h"
#include "window/themes/window_theme.h"
#include "ui/chat/chat_style.h"
#include "ui/color_int_conversion.h"
namespace Api {
namespace {
@@ -20,8 +22,9 @@ constexpr auto kRequestEach = 3600 * crl::time(1000);
PeerColors::PeerColors(not_null<ApiWrap*> api)
: _api(&api->instance())
, _timer([=] { request(); }) {
, _timer([=] { request(); requestProfile(); }) {
request();
requestProfile();
_timer.callEach(kRequestEach);
}
@@ -45,6 +48,24 @@ void PeerColors::request() {
}).send();
}
void PeerColors::requestProfile() {
if (_profileRequestId) {
return;
}
_profileRequestId = _api.request(MTPhelp_GetPeerProfileColors(
MTP_int(_profileHash)
)).done([=](const MTPhelp_PeerColors &result) {
_profileRequestId = 0;
result.match([&](const MTPDhelp_peerColors &data) {
_profileHash = data.vhash().v;
applyProfile(data);
}, [](const MTPDhelp_peerColorsNotModified &) {
});
}).fail([=] {
_profileRequestId = 0;
}).send();
}
std::vector<uint8> PeerColors::suggested() const {
return _suggested.current();
}
@@ -76,21 +97,27 @@ const base::flat_map<uint8, int> &PeerColors::requiredLevelsChannel() const {
return _requiredLevelsChannel;
}
int PeerColors::requiredGroupLevelFor(PeerId channel, uint8 index) const {
int PeerColors::requiredLevelFor(
PeerId channel,
uint8 index,
bool isMegagroup,
bool profile) const {
if (Data::DecideColorIndex(channel) == index) {
return 0;
} else if (const auto i = _requiredLevelsGroup.find(index)
; i != end(_requiredLevelsGroup)) {
return i->second;
}
return 1;
}
int PeerColors::requiredChannelLevelFor(PeerId channel, uint8 index) const {
if (Data::DecideColorIndex(channel) == index) {
return 0;
} else if (const auto i = _requiredLevelsChannel.find(index)
; i != end(_requiredLevelsChannel)) {
if (profile) {
const auto it = _profileColors.find(index);
if (it != end(_profileColors)) {
return isMegagroup
? it->second.requiredLevelsGroup
: it->second.requiredLevelsChannel;
}
return 1;
}
const auto &levels = isMegagroup
? _requiredLevelsGroup
: _requiredLevelsChannel;
if (const auto i = levels.find(index); i != end(levels)) {
return i->second;
}
return 1;
@@ -165,4 +192,87 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
_suggested = std::move(suggested);
}
void PeerColors::applyProfile(const MTPDhelp_peerColors &data) {
const auto parseColors = [](const MTPhelp_PeerColorSet &set) {
const auto toUint = [](const MTPint &c) {
return (uint32(1) << 24) | uint32(c.v);
};
return set.match([&](const MTPDhelp_peerColorSet &) {
LOG(("API Error: peerColorSet in profile colors result!"));
return Data::ColorProfileSet();
}, [&](const MTPDhelp_peerColorProfileSet &data) {
auto set = Data::ColorProfileSet();
set.palette.reserve(data.vpalette_colors().v.size());
set.bg.reserve(data.vbg_colors().v.size());
set.story.reserve(data.vstory_colors().v.size());
for (const auto &c : data.vpalette_colors().v) {
set.palette.push_back(Ui::ColorFromSerialized(toUint(c)));
}
for (const auto &c : data.vbg_colors().v) {
set.bg.push_back(Ui::ColorFromSerialized(toUint(c)));
}
for (const auto &c : data.vstory_colors().v) {
set.story.push_back(Ui::ColorFromSerialized(toUint(c)));
}
return set;
});
};
auto suggested = std::vector<Data::ColorProfileData>();
const auto &list = data.vcolors().v;
suggested.reserve(list.size());
for (const auto &color : list) {
const auto &data = color.data();
const auto colorIndexBare = data.vcolor_id().v;
if (colorIndexBare < 0 || colorIndexBare >= Ui::kColorIndexCount) {
LOG(("API Error: Bad color index: %1").arg(colorIndexBare));
continue;
}
const auto colorIndex = uint8(colorIndexBare);
auto result = ProfileColorOption();
result.isHidden = data.is_hidden();
if (const auto min = data.vgroup_min_level()) {
result.requiredLevelsGroup = min->v;
}
if (const auto min = data.vchannel_min_level()) {
result.requiredLevelsChannel = min->v;
}
if (const auto light = data.vcolors()) {
result.data.light = parseColors(*light);
}
if (const auto dark = data.vdark_colors()) {
result.data.dark = parseColors(*dark);
}
_profileColors[colorIndex] = std::move(result);
}
}
std::optional<Data::ColorProfileSet> PeerColors::colorProfileFor(
not_null<PeerData*> peer) const {
if (const auto colorProfileIndex = peer->colorProfileIndex()) {
return colorProfileFor(*colorProfileIndex);
}
return std::nullopt;
}
std::optional<Data::ColorProfileSet> PeerColors::colorProfileFor(
uint8 index) const {
const auto i = _profileColors.find(index);
if (i != end(_profileColors)) {
return Window::Theme::IsNightMode()
? i->second.data.dark
: i->second.data.light;
}
return std::nullopt;
}
std::vector<uint8> PeerColors::profileColorIndices() const {
auto result = std::vector<uint8>();
result.reserve(_profileColors.size());
for (const auto &[index, option] : _profileColors) {
result.push_back(index);
}
return result;
}
} // namespace Api

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/timer.h"
#include "data/data_peer_colors.h"
#include "mtproto/sender.h"
class ApiWrap;
@@ -34,27 +35,45 @@ public:
[[nodiscard]] auto requiredLevelsChannel() const
-> const base::flat_map<uint8, int> &;
[[nodiscard]] int requiredGroupLevelFor(
PeerId channel,
uint8 index) const;
[[nodiscard]] int requiredChannelLevelFor(
[[nodiscard]] int requiredLevelFor(
PeerId channel,
uint8 index,
bool isMegagroup,
bool profile) const;
[[nodiscard]] std::optional<Data::ColorProfileSet> colorProfileFor(
not_null<PeerData*> peer) const;
[[nodiscard]] std::optional<Data::ColorProfileSet> colorProfileFor(
uint8 index) const;
[[nodiscard]] std::vector<uint8> profileColorIndices() const;
private:
struct ProfileColorOption {
Data::ColorProfileData data;
int requiredLevelsChannel = 0;
int requiredLevelsGroup = 0;
bool isHidden = false;
};
void request();
void requestProfile();
void apply(const MTPDhelp_peerColors &data);
void applyProfile(const MTPDhelp_peerColors &data);
MTP::Sender _api;
int32 _hash = 0;
int32 _profileHash = 0;
mtpRequestId _requestId = 0;
mtpRequestId _profileRequestId = 0;
base::Timer _timer;
rpl::variable<std::vector<uint8>> _suggested;
base::flat_map<uint8, int> _requiredLevelsGroup;
base::flat_map<uint8, int> _requiredLevelsChannel;
rpl::event_stream<> _colorIndicesChanged;
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;
base::flat_map<uint8, ProfileColorOption> _profileColors;
};

View File

@@ -525,6 +525,14 @@ void EditCaptionBox::rebuildPreview() {
_content->modifyRequests(
) | rpl::start_to_stream(_photoEditorOpens, _content->lifetime());
_content->editCoverRequests() | rpl::start_with_next([=] {
setupEditCoverHandler();
}, _content->lifetime());
_content->clearCoverRequests() | rpl::start_with_next([=] {
setupClearCoverHandler();
}, _content->lifetime());
_content->heightValue(
) | rpl::start_to_stream(_contentHeight, _content->lifetime());
@@ -740,6 +748,89 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
}, lifetime());
}
void EditCaptionBox::setupEditCoverHandler() {
if (_preparedList.files.empty()) {
return;
}
const auto &file = _preparedList.files.front();
if (!file.isVideoFile()) {
return;
}
const auto show = _controller->uiShow();
const auto replace = [=](Ui::PreparedList list) {
if (list.files.empty()) {
return;
}
auto &entry = _preparedList.files.front();
const auto video = entry.information
? std::get_if<Ui::PreparedFileInformation::Video>(
&entry.information->media)
: nullptr;
if (!video) {
return;
}
auto old = std::shared_ptr<Ui::PreparedFile>(
std::move(entry.videoCover));
entry.videoCover = std::make_unique<Ui::PreparedFile>(
std::move(list.files.front()));
Editor::OpenWithPreparedFile(
this,
show,
entry.videoCover.get(),
st::sendMediaPreviewSize,
crl::guard(this, [=](bool ok) {
if (!ok) {
_preparedList.files.front().videoCover = old
? std::make_unique<Ui::PreparedFile>(
std::move(*old))
: nullptr;
}
rebuildPreview();
}),
video->thumbnail.size());
};
const auto checkResult = [=](const Ui::PreparedList &list) {
if (list.files.empty()) {
return true;
}
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
show->showToast(tr::lng_choose_cover_bad(tr::now));
return false;
}
return true;
};
const auto callback = [=](FileDialog::OpenResult &&result) {
const auto premium = show->session().premium();
const auto showError = [=](tr::phrase<> t) {
show->showToast(t(tr::now));
};
auto list = Storage::PreparedFileFromFilesDialog(
std::move(result),
checkResult,
showError,
st::sendMediaPreviewSize,
premium);
if (list) {
replace(std::move(*list));
}
};
FileDialog::GetOpenPath(
this,
tr::lng_choose_cover(tr::now),
FileDialog::ImagesFilter(),
crl::guard(this, callback));
}
void EditCaptionBox::setupClearCoverHandler() {
if (_preparedList.files.empty()) {
return;
}
auto &entry = _preparedList.files.front();
entry.videoCover = nullptr;
rebuildPreview();
}
void EditCaptionBox::setupDragArea() {
auto enterFilter = [=](not_null<const QMimeData*> data) {
return !_isAllowedEditMedia

View File

@@ -87,6 +87,8 @@ private:
void rebuildPreview();
void setupEditEventHandler();
void setupPhotoEditorEventHandler();
void setupEditCoverHandler();
void setupClearCoverHandler();
void setupField();
void setupFieldAutocomplete();
void setupControls();

View File

@@ -449,25 +449,25 @@ void Controller::setupNotesField() {
};
const auto limitState = _notesField->lifetime().make_state<LimitState>();
const auto checkCharsLimitation = [=] {
const auto checkCharsLimitation = [=, w = _notesField->window()] {
const auto limit = Data::PremiumLimits(
&_user->session()).contactNoteLengthCurrent();
const auto remove = Ui::ComputeFieldCharacterCount(_notesField)
- limit;
if (!limitState->charsLimitation) {
const auto border = _notesField->st().borderActive;
limitState->charsLimitation = base::make_unique_q<Limit>(
_box->verticalLayout(),
emojiButton,
style::al_top,
QMargins{ 0, -st::lineWidth, 0, 0 });
_notesField->heightValue(
) | rpl::start_with_next([=](int height) {
const auto &st = _notesField->st();
const auto hasMultipleLines = height >
(st.textMargins.top()
+ st.style.font->height
+ st.textMargins.bottom() * 2);
limitState->charsLimitation->setVisible(hasMultipleLines);
QMargins{ 0, -border - _notesField->st().border, 0, 0 });
rpl::combine(
limitState->charsLimitation->geometryValue(),
_notesField->geometryValue()
) | rpl::start_with_next([=](QRect limit, QRect field) {
limitState->charsLimitation->setVisible(
(w->mapToGlobal(limit.bottomLeft()).y() - border)
< w->mapToGlobal(field.bottomLeft()).y());
limitState->charsLimitation->raise();
}, limitState->charsLimitation->lifetime());
}
@@ -484,7 +484,10 @@ void Controller::setupNotesField() {
}
void Controller::setupPhotoButtons() {
const auto iconSize = st::restoreUserpicIcon.size;
if (!_user->isContact()) {
return;
}
const auto iconPlaceholder = st::restoreUserpicIcon.size * 2;
auto nameValue = _firstNameField
? rpl::merge(
rpl::single(_firstNameField->getLastText().trimmed()),
@@ -515,7 +518,8 @@ void Controller::setupPhotoButtons() {
.sessionWindow = base::make_weak(_window),
}));
});
suggestBirthdayWrap->toggleOn(rpl::single(!_user->birthday().valid()));
suggestBirthdayWrap->toggleOn(rpl::single(!_user->birthday().valid()
&& !_user->starsPerMessageChecked()));
_suggestIcon = Ui::MakeAnimatedIcon({
.generator = [] {
@@ -524,7 +528,7 @@ void Controller::setupPhotoButtons() {
QByteArray(),
u":/animations/photo_suggest_icon.tgs"_q));
},
.sizeOverride = iconSize * style::DevicePixelRatio(),
.sizeOverride = iconPlaceholder,
.colorized = true,
});
@@ -535,30 +539,36 @@ void Controller::setupPhotoButtons() {
QByteArray(),
u":/animations/camera_outline.tgs"_q));
},
.sizeOverride = iconSize * style::DevicePixelRatio(),
.sizeOverride = iconPlaceholder,
.colorized = true,
});
const auto suggestButtonWrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
suggestButtonWrap->toggleOn(
rpl::single(!_user->starsPerMessageChecked()));
const auto suggestButton = Settings::AddButtonWithIcon(
inner,
suggestButtonWrap->entity(),
tr::lng_suggest_photo_for(lt_user, rpl::duplicate(nameValue)),
st::settingsButtonLight,
{ nullptr });
_suggestIconWidget = Ui::CreateChild<Ui::RpWidget>(suggestButton);
_suggestIconWidget->resize(iconSize * style::DevicePixelRatio());
_suggestIconWidget->resize(iconPlaceholder);
_suggestIconWidget->paintRequest() | rpl::start_with_next([=] {
if (_suggestIcon && _suggestIcon->valid()) {
auto p = QPainter(_suggestIconWidget);
const auto frame = _suggestIcon->frame(st::lightButtonFg->c);
const auto rect = _suggestIconWidget->rect();
p.drawImage(rect, frame);
p.drawImage(_suggestIconWidget->rect(), frame);
}
}, _suggestIconWidget->lifetime());
suggestButton->sizeValue() | rpl::start_with_next([=](QSize size) {
_suggestIconWidget->move(
st::settingsButtonLight.iconLeft - iconSize.width() / 2,
st::settingsButtonLight.iconLeft - iconPlaceholder.width() / 4,
(size.height() - _suggestIconWidget->height()) / 2);
}, _suggestIconWidget->lifetime());
@@ -579,19 +589,18 @@ void Controller::setupPhotoButtons() {
{ nullptr });
_cameraIconWidget = Ui::CreateChild<Ui::RpWidget>(setButton);
_cameraIconWidget->resize(iconSize * style::DevicePixelRatio());
_cameraIconWidget->resize(iconPlaceholder);
_cameraIconWidget->paintRequest() | rpl::start_with_next([=] {
if (_cameraIcon && _cameraIcon->valid()) {
auto p = QPainter(_cameraIconWidget);
const auto frame = _cameraIcon->frame(st::lightButtonFg->c);
const auto rect = _cameraIconWidget->rect();
p.drawImage(rect, frame);
p.drawImage(_cameraIconWidget->rect(), frame);
}
}, _cameraIconWidget->lifetime());
setButton->sizeValue() | rpl::start_with_next([=](QSize size) {
_cameraIconWidget->move(
st::settingsButtonLight.iconLeft - iconSize.width() / 2,
st::settingsButtonLight.iconLeft - iconPlaceholder.width() / 4,
(size.height() - _cameraIconWidget->height()) / 2);
}, _cameraIconWidget->lifetime());
@@ -634,7 +643,7 @@ void Controller::setupPhotoButtons() {
resetButtonWrap->toggleOn(
_user->session().changes().peerFlagsValue(
_user,
Data::PeerUpdate::Flag::FullInfo
Data::PeerUpdate::Flag::FullInfo | Data::PeerUpdate::Flag::Photo
) | rpl::map([=] {
return _user->hasPersonalPhoto();
}) | rpl::distinct_until_changed());
@@ -645,8 +654,9 @@ void Controller::setupPhotoButtons() {
tr::now,
lt_user,
_user->shortName()),
.confirmed = [=] {
.confirmed = [=](Fn<void()> close) {
_window->session().api().peerPhoto().clearPersonal(_user);
close();
},
.confirmText = tr::lng_profile_photo_reset(tr::now),
}));
@@ -810,11 +820,6 @@ void Controller::processChosenPhoto(QImage &&image, bool suggest) {
Api::PeerPhoto::UserPhoto photo{
.image = base::duplicate(image),
};
if (suggest && _suggestIcon && _suggestIcon->valid()) {
_suggestIcon->animate([=] { _suggestIconWidget->update(); });
} else if (!suggest && _cameraIcon && _cameraIcon->valid()) {
_cameraIcon->animate([=] { _cameraIconWidget->update(); });
}
if (suggest) {
_window->session().api().peerPhoto().suggest(_user, std::move(photo));
_window->showPeerHistory(_user->id);
@@ -831,11 +836,6 @@ void Controller::processChosenPhotoWithMarkup(
.markupDocumentId = data.id,
.markupColors = std::move(data.colors),
};
if (suggest && _suggestIcon && _suggestIcon->valid()) {
_suggestIcon->animate([=] { _suggestIconWidget->update(); });
} else if (!suggest && _cameraIcon && _cameraIcon->valid()) {
_cameraIcon->animate([=] { _cameraIconWidget->update(); });
}
if (suggest) {
_window->session().api().peerPhoto().suggest(_user, std::move(photo));
_window->showPeerHistory(_user->id);

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/event_filter.h"
#include "base/unixtime.h"
#include "boxes/peer_list_box.h"
#include "boxes/star_gift_box.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_premium_limits.h"
#include "data/data_channel.h"
@@ -36,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/empty_userpic.h"
#include "ui/dynamic_image.h"
#include "ui/painter.h"
#include "ui/top_background_gradient.h"
#include "styles/style_boxes.h"
#include "styles/style_credits.h"
#include "styles/style_premium.h"
@@ -895,9 +895,9 @@ public:
auto p = QPainter(&_backgroundCache);
p.setClipRect(inner);
const auto skip = inner.width() / 3;
Ui::PaintPoints(
Ui::PaintBgPoints(
p,
Ui::PatternPointsSmall(),
Ui::PatternBgPointsSmall(),
_patternCache,
_patternEmoji.get(),
*_unique,

View File

@@ -135,6 +135,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_subtitle_effects();
case PremiumFeature::TodoLists:
return tr::lng_premium_summary_subtitle_todo_lists();
case PremiumFeature::PeerColors:
return tr::lng_premium_summary_subtitle_peer_colors();
case PremiumFeature::BusinessLocation:
return tr::lng_business_subtitle_location();
@@ -202,6 +204,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_about_effects();
case PremiumFeature::TodoLists:
return tr::lng_premium_summary_about_todo_lists();
case PremiumFeature::PeerColors:
return tr::lng_premium_summary_about_peer_colors();
case PremiumFeature::BusinessLocation:
return tr::lng_business_about_location();
@@ -543,6 +547,7 @@ struct VideoPreviewDocument {
case PremiumFeature::MessagePrivacy: return "message_privacy";
case PremiumFeature::Effects: return "effects";
case PremiumFeature::TodoLists: return "todo";
case PremiumFeature::PeerColors: return "peer_colors";
case PremiumFeature::BusinessLocation: return "business_location";
case PremiumFeature::BusinessHours: return "business_hours";

View File

@@ -73,6 +73,7 @@ enum class PremiumFeature {
Effects,
FilterTags,
TodoLists,
PeerColors,
// Business features.
BusinessLocation,

View File

@@ -1734,7 +1734,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
result,
msgIds);
const auto showRecentForwardsToSelf = result.size() == 1
&& result.front()->peer()->isSelf();
&& result.front()->peer()->isSelf()
&& history->owner().session().premium();
const auto requestType = Data::Histories::RequestType::Send;
for (const auto thread : result) {
if (!comment.text.isEmpty()) {

View File

@@ -91,6 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/top_background_gradient.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
@@ -571,61 +572,6 @@ auto GenerateGiftMedia(
};
}
[[nodiscard]] QImage CreateGradient(
QSize size,
const Data::UniqueGift &gift) {
const auto ratio = style::DevicePixelRatio();
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
auto p = QPainter(&result);
auto hq = PainterHighQualityEnabler(p);
auto gradient = QRadialGradient(
QRect(QPoint(), size).center(),
size.height() / 2);
gradient.setStops({
{ 0., gift.backdrop.centerColor },
{ 1., gift.backdrop.edgeColor },
});
p.setBrush(gradient);
p.setPen(Qt::NoPen);
p.drawRect(QRect(QPoint(), size));
p.end();
const auto mask = Images::CornersMask(st::boxRadius);
return Images::Round(std::move(result), mask, RectPart::FullTop);
}
void PrepareImage(
QImage &image,
not_null<Text::CustomEmoji*> emoji,
const PatternPoint &point,
const Data::UniqueGift &gift) {
if (!image.isNull() || !emoji->ready()) {
return;
}
const auto ratio = style::DevicePixelRatio();
const auto size = Emoji::GetSizeNormal() / ratio;
image = QImage(
2 * QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(ratio);
image.fill(Qt::transparent);
auto p = QPainter(&image);
auto hq = PainterHighQualityEnabler(p);
p.setOpacity(point.opacity);
if (point.scale < 1.) {
p.translate(size, size);
p.scale(point.scale, point.scale);
p.translate(-size, -size);
}
const auto shift = (2 * size - (Emoji::GetSizeLarge() / ratio)) / 2;
emoji->paint(p, {
.textColor = gift.backdrop.patternColor,
.position = QPoint(shift, shift),
});
}
PreviewWrap::PreviewWrap(
not_null<QWidget*> parent,
not_null<PeerData*> recipient,
@@ -3910,13 +3856,15 @@ void AddUniqueGiftCover(
const auto pointsHeight = st::uniqueGiftSubtitleTop;
const auto ratio = style::DevicePixelRatio();
if (gift.gradient.size() != cover->size() * ratio) {
gift.gradient = CreateGradient(cover->size(), *gift.gift);
gift.gradient = Ui::CreateTopBgGradient(
cover->size(),
*gift.gift);
}
p.drawImage(0, 0, gift.gradient);
PaintPoints(
Ui::PaintBgPoints(
p,
PatternPoints(),
Ui::PatternBgPoints(),
gift.emojis,
gift.emoji.get(),
*gift.gift,
@@ -4021,13 +3969,15 @@ void AddWearGiftCover(
const auto pointsHeight = st::uniqueGiftSubtitleTop;
const auto ratio = style::DevicePixelRatio();
if (state->gradient.size() != cover->size() * ratio) {
state->gradient = CreateGradient(cover->size(), state->gift);
state->gradient = Ui::CreateTopBgGradient(
cover->size(),
state->gift);
}
p.drawImage(0, 0, state->gradient);
PaintPoints(
Ui::PaintBgPoints(
p,
PatternPoints(),
Ui::PatternBgPoints(),
state->emojis,
state->emoji.get(),
state->gift,
@@ -5060,119 +5010,11 @@ void UpgradeBox(
AddUniqueCloseButton(box, {});
}
const std::vector<PatternPoint> &PatternPoints() {
static const auto kSmall = 0.7;
static const auto kFaded = 0.2;
static const auto kLarge = 0.85;
static const auto kOpaque = 0.3;
static const auto result = std::vector<PatternPoint>{
{ { 0.5, 0.066 }, kSmall, kFaded },
{ { 0.177, 0.168 }, kSmall, kFaded },
{ { 0.822, 0.168 }, kSmall, kFaded },
{ { 0.37, 0.168 }, kLarge, kOpaque },
{ { 0.63, 0.168 }, kLarge, kOpaque },
{ { 0.277, 0.308 }, kSmall, kOpaque },
{ { 0.723, 0.308 }, kSmall, kOpaque },
{ { 0.13, 0.42 }, kSmall, kFaded },
{ { 0.87, 0.42 }, kSmall, kFaded },
{ { 0.27, 0.533 }, kLarge, kOpaque },
{ { 0.73, 0.533 }, kLarge, kOpaque },
{ { 0.2, 0.73 }, kSmall, kFaded },
{ { 0.8, 0.73 }, kSmall, kFaded },
{ { 0.302, 0.825 }, kLarge, kOpaque },
{ { 0.698, 0.825 }, kLarge, kOpaque },
{ { 0.5, 0.876 }, kLarge, kFaded },
{ { 0.144, 0.936 }, kSmall, kFaded },
{ { 0.856, 0.936 }, kSmall, kFaded },
};
return result;
}
const std::vector<PatternPoint> &PatternPointsSmall() {
static const auto kSmall = 0.45;
static const auto kFaded = 0.2;
static const auto kLarge = 0.55;
static const auto kOpaque = 0.3;
static const auto result = std::vector<PatternPoint>{
{ { 0.5, 0.066 }, kSmall, kFaded },
{ { 0.177, 0.168 }, kSmall, kFaded },
{ { 0.822, 0.168 }, kSmall, kFaded },
{ { 0.37, 0.168 }, kLarge, kOpaque },
{ { 0.63, 0.168 }, kLarge, kOpaque },
{ { 0.277, 0.308 }, kSmall, kOpaque },
{ { 0.723, 0.308 }, kSmall, kOpaque },
{ { 0.13, 0.42 }, kSmall, kFaded },
{ { 0.87, 0.42 }, kSmall, kFaded },
{ { 0.27, 0.533 }, kLarge, kOpaque },
{ { 0.73, 0.533 }, kLarge, kOpaque },
{ { 0.2, 0.73 }, kSmall, kFaded },
{ { 0.8, 0.73 }, kSmall, kFaded },
{ { 0.302, 0.825 }, kLarge, kOpaque },
{ { 0.698, 0.825 }, kLarge, kOpaque },
{ { 0.5, 0.876 }, kLarge, kFaded },
{ { 0.144, 0.936 }, kSmall, kFaded },
{ { 0.856, 0.936 }, kSmall, kFaded },
};
return result;
}
void PaintPoints(
QPainter &p,
const std::vector<PatternPoint> &points,
base::flat_map<float64, QImage> &cache,
not_null<Text::CustomEmoji*> emoji,
const Data::UniqueGift &gift,
const QRect &rect,
float64 shown) {
const auto origin = rect.topLeft();
const auto width = rect.width();
const auto height = rect.height();
const auto ratio = style::DevicePixelRatio();
const auto paintPoint = [&](const PatternPoint &point) {
const auto key = (1. + point.opacity) * 10. + point.scale;
auto &image = cache[key];
PrepareImage(image, emoji, point, gift);
if (!image.isNull()) {
const auto position = origin + QPoint(
int(point.position.x() * width),
int(point.position.y() * height));
if (shown < 1.) {
p.save();
p.translate(position);
p.scale(shown, shown);
p.translate(-position);
}
const auto size = image.size() / ratio;
p.drawImage(
position - QPoint(size.width() / 2, size.height() / 2),
image);
if (shown < 1.) {
p.restore();
}
}
};
for (const auto &point : points) {
paintPoint(point);
}
}
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
const auto weak = base::make_weak(args.controller);

View File

@@ -87,23 +87,6 @@ void ShowUniqueGiftSellBox(
void GiftReleasedByHandler(not_null<PeerData*> peer);
struct PatternPoint {
QPointF position;
float64 scale = 1.;
float64 opacity = 1.;
};
[[nodiscard]] const std::vector<PatternPoint> &PatternPoints();
[[nodiscard]] const std::vector<PatternPoint> &PatternPointsSmall();
void PaintPoints(
QPainter &p,
const std::vector<PatternPoint> &points,
base::flat_map<float64, QImage> &cache,
not_null<Text::CustomEmoji*> emoji,
const Data::UniqueGift &gift,
const QRect &rect,
float64 shown = 1.);
struct StarGiftUpgradeArgs {
not_null<Window::SessionController*> controller;
base::required<uint64> stargiftId;

View File

@@ -46,13 +46,9 @@ class Panel::Incoming::RendererGL final : public Ui::GL::Renderer {
public:
explicit RendererGL(not_null<Incoming*> owner);
void init(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) override;
void init(QOpenGLFunctions &f) override;
void deinit(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions *f) override;
void deinit(QOpenGLFunctions *f) override;
void paint(
not_null<QOpenGLWidget*> widget,
@@ -100,7 +96,7 @@ public:
explicit RendererSW(not_null<Incoming*> owner);
void paintFallback(
Painter &&p,
Painter &p,
const QRegion &clip,
Ui::GL::Backend backend) override;
@@ -123,9 +119,7 @@ Panel::Incoming::RendererGL::RendererGL(not_null<Incoming*> owner)
}, _lifetime);
}
void Panel::Incoming::RendererGL::init(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
void Panel::Incoming::RendererGL::init(QOpenGLFunctions &f) {
constexpr auto kQuads = 2;
constexpr auto kQuadVertices = kQuads * 4;
constexpr auto kQuadValues = kQuadVertices * 4;
@@ -168,9 +162,7 @@ void Panel::Incoming::RendererGL::init(
}));
}
void Panel::Incoming::RendererGL::deinit(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions *f) {
void Panel::Incoming::RendererGL::deinit(QOpenGLFunctions *f) {
_textures.destroy(f);
_imageProgram = std::nullopt;
_texturedVertexShader = nullptr;
@@ -437,7 +429,7 @@ Panel::Incoming::RendererSW::RendererSW(not_null<Incoming*> owner)
}
void Panel::Incoming::RendererSW::paintFallback(
Painter &&p,
Painter &p,
const QRegion &clip,
Ui::GL::Backend backend) {
const auto markGuard = gsl::finally([&] {

View File

@@ -2483,7 +2483,8 @@ void Panel::updateButtonsGeometry() {
}
const auto wideMenuShown = _call->canManage()
|| _call->showChooseJoinAs();
|| _call->showChooseJoinAs()
|| (!rtmp && messagesEnabled); // Screen share there.
toggle(_settings, !hidden && !wideMenuShown);
toggle(_wideMenu, !hidden && wideMenuShown);

View File

@@ -312,9 +312,7 @@ Viewport::RendererGL::RendererGL(not_null<Viewport*> owner)
}, _lifetime);
}
void Viewport::RendererGL::init(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
void Viewport::RendererGL::init(QOpenGLFunctions &f) {
_frameBuffer.emplace();
_frameBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
_frameBuffer->create();
@@ -389,9 +387,7 @@ void Viewport::RendererGL::ensureARGB32Program() {
}));
}
void Viewport::RendererGL::deinit(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions *f) {
void Viewport::RendererGL::deinit(QOpenGLFunctions *f) {
_frameBuffer = std::nullopt;
_frameVertexShader = nullptr;
_imageProgram = std::nullopt;

View File

@@ -28,13 +28,9 @@ class Viewport::RendererGL final : public Ui::GL::Renderer {
public:
explicit RendererGL(not_null<Viewport*> owner);
void init(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) override;
void init(QOpenGLFunctions &f) override;
void deinit(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions *f) override;
void deinit(QOpenGLFunctions *f) override;
void paint(
not_null<QOpenGLWidget*> widget,

View File

@@ -37,7 +37,7 @@ Viewport::RendererSW::RendererSW(not_null<Viewport*> owner)
}
void Viewport::RendererSW::paintFallback(
Painter &&p,
Painter &p,
const QRegion &clip,
Ui::GL::Backend backend) {
auto bg = clip;

View File

@@ -20,7 +20,7 @@ public:
explicit RendererSW(not_null<Viewport*> owner);
void paintFallback(
Painter &&p,
Painter &p,
const QRegion &clip,
Ui::GL::Backend backend) override;

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_volume_item.h"
#include "calls/group/calls_group_common.h"
#include "ui/color_int_conversion.h"
#include "ui/effects/animation_value.h"
#include "ui/effects/cross_line.h"
#include "ui/widgets/continuous_sliders.h"
@@ -254,17 +255,11 @@ void MenuVolumeItem::setSliderVolume(int volume) {
void MenuVolumeItem::updateSliderColor(float64 value) {
value = std::clamp(value, 0., 1.);
const auto color = [](int rgb) {
return QColor(
int((rgb & 0xFF0000) >> 16),
int((rgb & 0x00FF00) >> 8),
int(rgb & 0x0000FF));
};
const auto colors = std::array<QColor, 4>{ {
color(0xF66464),
color(0xD0B738),
color(0x24CD80),
color(0x3BBCEC),
Ui::ColorFromSerialized(0xF66464),
Ui::ColorFromSerialized(0xD0B738),
Ui::ColorFromSerialized(0x24CD80),
Ui::ColorFromSerialized(0x3BBCEC),
} };
_slider->setActiveFgOverride((value < 0.25)
? anim::color(colors[0], colors[1], value / 0.25)

View File

@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_download_manager.h"
#include "base/battery_saving.h"
#include "base/event_filter.h"
#include "base/invoke_queued.h"
#include "base/concurrent_timer.h"
#include "base/options.h"
#include "base/qt_signal_producer.h"
@@ -85,6 +86,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qthelp_regex.h"
#include "base/qthelp_url.h"
#include "boxes/premium_limits_box.h"
#include "ui/accessible/ui_accessible_factory.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/location_picker.h"
#include "styles/style_window.h"
@@ -103,7 +105,6 @@ namespace {
constexpr auto kQuitPreventTimeoutMs = crl::time(1500);
constexpr auto kAutoLockTimeoutLateMs = crl::time(3000);
constexpr auto kClearEmojiImageSourceTimeout = 10 * crl::time(1000);
constexpr auto kFileOpenTimeoutMs = crl::time(1000);
LaunchState GlobalLaunchState/* = LaunchState::Running*/;
@@ -168,8 +169,7 @@ Application::Application()
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
, _tray(std::make_unique<Tray>())
, _autoLockTimer([=] { checkAutoLock(); })
, _fileOpenTimer([=] { checkFileOpen(); }) {
, _autoLockTimer([=] { checkAutoLock(); }) {
Ui::Integration::Set(&_private->uiIntegration);
_platformIntegration->init();
@@ -287,6 +287,7 @@ void Application::run() {
QCoreApplication::instance()->installTranslator(_translator.get());
style::StartManager(cScale());
Ui::Accessible::Init();
Ui::InitTextOptions();
Ui::StartCachedCorners();
Ui::Emoji::Init();
@@ -692,24 +693,26 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
case QEvent::FileOpen: {
if (object == QCoreApplication::instance()) {
const auto event = static_cast<QFileOpenEvent*>(e);
if (const auto file = event->file(); !file.isEmpty()) {
_filesToOpen.append(file);
_fileOpenTimer.callOnce(kFileOpenTimeoutMs);
} else if (event->url().scheme() == u"tg"_q
|| event->url().scheme() == u"tonsite"_q) {
const auto url = QString::fromUtf8(
event->url().toEncoded().trimmed());
cSetStartUrl(url.mid(0, 8192));
checkStartUrl();
if (_lastActivePrimaryWindow
&& StartUrlRequiresActivate(url)) {
_lastActivePrimaryWindow->activate();
}
} else if (event->url().scheme() == u"interpret"_q) {
_filesToOpen.append(event->url().toString());
_fileOpenTimer.callOnce(kFileOpenTimeoutMs);
if (_urlsToOpen.isEmpty()) {
InvokeQueued(this, [=] {
const auto activateRequired = ranges::any_of(
ranges::views::all(
_urlsToOpen
) | ranges::views::transform([](const QUrl &url) {
return url.toString();
}),
StartUrlRequiresActivate);
cRefStartUrls() << base::take(_urlsToOpen);
checkStartUrls();
if (_lastActivePrimaryWindow && activateRequired) {
_lastActivePrimaryWindow->activate();
}
});
}
const auto event = static_cast<QFileOpenEvent*>(e);
_urlsToOpen << event->url().toString(QUrl::FullyEncoded).mid(
0,
8192);
}
} break;
@@ -1081,37 +1084,27 @@ bool Application::canApplyLangPackWithoutRestart() const {
return true;
}
void Application::checkFileOpen() {
cSetSendPaths(_filesToOpen);
_filesToOpen.clear();
checkSendPaths();
}
void Application::checkSendPaths() {
if (!cSendPaths().isEmpty()
void Application::checkStartUrls() {
if (!Core::App().passcodeLocked()) {
cRefStartUrls() = ranges::views::all(
cRefStartUrls()
) | ranges::views::filter([&](const QUrl &url) {
if (url.scheme() == u"tonsite"_q) {
iv().showTonSite(url.toString(), {});
return false;
} else if (_lastActivePrimaryWindow) {
return !openLocalUrl(url.toString(), {});
}
return true;
}) | ranges::to<QList<QUrl>>;
}
if (!cRefStartUrls().isEmpty()
&& _lastActivePrimaryWindow
&& !_lastActivePrimaryWindow->locked()) {
_lastActivePrimaryWindow->widget()->sendPaths();
}
}
void Application::checkStartUrl() {
if (!cStartUrl().isEmpty()) {
const auto url = cStartUrl();
if (!Core::App().passcodeLocked()) {
if (url.startsWith("tonsite://", Qt::CaseInsensitive)) {
cSetStartUrl(QString());
iv().showTonSite(url, {});
} else if (_lastActivePrimaryWindow) {
cSetStartUrl(QString());
if (!openLocalUrl(url, {})) {
cSetStartUrl(url);
}
}
}
}
}
bool Application::openLocalUrl(const QString &url, QVariant context) {
return openCustomUrl("tg://", LocalUrlHandlers(), url, context);
}

View File

@@ -270,9 +270,7 @@ public:
}
// Internal links.
void checkStartUrl();
void checkSendPaths();
void checkFileOpen();
void checkStartUrls();
bool openLocalUrl(const QString &url, QVariant context);
bool openInternalUrl(const QString &url, QVariant context);
[[nodiscard]] QString changelogLink() const;
@@ -451,8 +449,7 @@ private:
crl::time _shouldLockAt = 0;
base::Timer _autoLockTimer;
QStringList _filesToOpen;
base::Timer _fileOpenTimer;
QList<QUrl> _urlsToOpen;
std::optional<base::Timer> _saveSettingsTimer;

View File

@@ -536,15 +536,18 @@ void Launcher::processArguments() {
{ "-tosettings" , KeyFormat::NoValues },
{ "-startintray" , KeyFormat::NoValues },
{ "-quit" , KeyFormat::NoValues },
{ "-sendpath" , KeyFormat::AllLeftValues },
{ "-workdir" , KeyFormat::OneValue },
{ "--" , KeyFormat::OneValue },
{ "--" , KeyFormat::AllLeftValues },
{ "-scale" , KeyFormat::OneValue },
};
auto parseResult = QMap<QByteArray, QStringList>();
auto parsingKey = QByteArray();
auto parsingFormat = KeyFormat::NoValues;
for (const auto &argument : std::as_const(_arguments)) {
for (auto i = _arguments.cbegin(); i != _arguments.cend(); ++i) {
if (i == _arguments.cbegin()) {
continue;
}
const auto &argument = *i;
switch (parsingFormat) {
case KeyFormat::OneValue: {
parseResult[parsingKey] = QStringList(argument.mid(0, 8192));
@@ -559,7 +562,9 @@ void Launcher::processArguments() {
if (it != parseMap.end()) {
parsingFormat = it->second;
parseResult[parsingKey] = QStringList();
continue;
}
parseResult["--"].push_back(argument.mid(0, 8192));
} break;
}
}
@@ -579,12 +584,15 @@ void Launcher::processArguments() {
gStartToSettings = parseResult.contains("-tosettings");
gStartInTray = parseResult.contains("-startintray");
gQuit = parseResult.contains("-quit");
gSendPaths = parseResult.value("-sendpath", {});
_customWorkingDir = parseResult.value("-workdir", {}).join(QString());
if (!_customWorkingDir.isEmpty()) {
_customWorkingDir = QDir(_customWorkingDir).absolutePath() + '/';
}
gStartUrl = parseResult.value("--", {}).join(QString());
const auto startUrls = parseResult.value("--", {});
gStartUrls = startUrls | ranges::views::transform([&](const QString &url) {
return QUrl::fromUserInput(url, _initialWorkingDir);
}) | ranges::views::filter(&QUrl::isValid) | ranges::to<QList<QUrl>>;
const auto scaleKey = parseResult.value("-scale", {});
if (scaleKey.size() > 0) {

View File

@@ -35,47 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/qpa/qplatformscreen.h>
namespace Core {
namespace {
QChar _toHex(ushort v) {
v = v & 0x000F;
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
}
ushort _fromHex(QChar c) {
return ((c.unicode() >= uchar('a')) ? (c.unicode() - uchar('a') + 10) : (c.unicode() - uchar('0'))) & 0x000F;
}
QString _escapeTo7bit(const QString &str) {
QString result;
result.reserve(str.size() * 2);
for (int i = 0, l = str.size(); i != l; ++i) {
QChar ch(str.at(i));
ushort uch(ch.unicode());
if (uch < 32 || uch > 127 || uch == ushort(uchar('%'))) {
result.append('%').append(_toHex(uch >> 12)).append(_toHex(uch >> 8)).append(_toHex(uch >> 4)).append(_toHex(uch));
} else {
result.append(ch);
}
}
return result;
}
QString _escapeFrom7bit(const QString &str) {
QString result;
result.reserve(str.size());
for (int i = 0, l = str.size(); i != l; ++i) {
QChar ch(str.at(i));
if (ch == QChar::fromLatin1('%') && i + 4 < l) {
result.append(QChar(ushort((_fromHex(str.at(i + 1)) << 12) | (_fromHex(str.at(i + 2)) << 8) | (_fromHex(str.at(i + 3)) << 4) | _fromHex(str.at(i + 4)))));
i += 4;
} else {
result.append(ch);
}
}
return result;
}
} // namespace
bool Sandbox::QuitOnStartRequested = false;
@@ -277,18 +236,15 @@ void Sandbox::socketConnected() {
_secondInstance = true;
QString commands;
const QStringList &lst(cSendPaths());
for (QStringList::const_iterator i = lst.cbegin(), e = lst.cend(); i != e; ++i) {
commands += u"SEND:"_q + _escapeTo7bit(*i) + ';';
}
if (qEnvironmentVariableIsSet("XDG_ACTIVATION_TOKEN")) {
commands += u"XDG_ACTIVATION_TOKEN:"_q + _escapeTo7bit(qEnvironmentVariable("XDG_ACTIVATION_TOKEN")) + ';';
commands += u"XDG_ACTIVATION_TOKEN:"_q + qgetenv("XDG_ACTIVATION_TOKEN").toBase64() + ';';
}
if (!cStartUrl().isEmpty()) {
commands += u"OPEN:"_q + _escapeTo7bit(cStartUrl()) + ';';
} else if (cQuit()) {
for (const auto &url : cRefStartUrls()) {
commands += u"OPEN:"_q + url.toString(QUrl::FullyEncoded) + ';';
}
if (cQuit()) {
commands += u"CMD:quit;"_q;
} else {
} else if (cRefStartUrls().isEmpty()) {
commands += u"CMD:show;"_q;
}
@@ -434,11 +390,11 @@ void Sandbox::newInstanceConnected() {
void Sandbox::readClients() {
// This method can be called before Application is constructed.
QString startUrl;
QStringList toSend;
QList<QUrl> startUrls;
for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) {
i->second.append(i->first->readAll());
if (i->second.size()) {
bool activationRequired = false;
QString cmds(QString::fromLatin1(i->second));
int32 from = 0, l = cmds.length();
for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) {
@@ -448,21 +404,13 @@ void Sandbox::readClients() {
const auto windowId = execExternal(cmds.mid(from + 4, to - from - 4));
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
i->first->write(response.data(), response.size());
} else if (cmd.startsWith(u"SEND:"_q)) {
if (cSendPaths().isEmpty()) {
toSend.append(_escapeFrom7bit(cmds.mid(from + 5, to - from - 5)));
}
} else if (cmd.startsWith(u"XDG_ACTIVATION_TOKEN:"_q)) {
qputenv("XDG_ACTIVATION_TOKEN", _escapeFrom7bit(cmds.mid(from + 21, to - from - 21)).toUtf8());
qputenv("XDG_ACTIVATION_TOKEN", QByteArray::fromBase64(cmds.mid(from + 21, to - from - 21).toLatin1()));
} else if (cmd.startsWith(u"OPEN:"_q)) {
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
const auto activationRequired = StartUrlRequiresActivate(startUrl);
const auto processId = QApplication::applicationPid();
const auto windowId = activationRequired
? execExternal("show")
: 0;
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
i->first->write(response.data(), response.size());
startUrls.append(cmds.mid(from + 5, to - from - 5).mid(0, 8192));
if (!activationRequired) {
activationRequired = StartUrlRequiresActivate(startUrls.back().toString());
}
} else {
LOG(("Sandbox Error: unknown command %1 passed in local socket").arg(cmd.toString()));
}
@@ -471,21 +419,17 @@ void Sandbox::readClients() {
if (from > 0) {
i->second = i->second.mid(from);
}
const auto processId = QApplication::applicationPid();
const auto windowId = activationRequired
? execExternal("show")
: 0;
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
i->first->write(response.data(), response.size());
}
}
if (!toSend.isEmpty()) {
QStringList paths(cSendPaths());
paths.append(toSend);
cSetSendPaths(paths);
}
cRefStartUrls() << base::take(startUrls);
if (_application) {
_application->checkSendPaths();
}
if (!startUrl.isEmpty()) {
cSetStartUrl(startUrl);
}
if (_application) {
_application->checkStartUrl();
_application->checkStartUrls();
}
}

View File

@@ -440,6 +440,18 @@ QString UiIntegration::phraseQuoteHeaderCopy() {
return tr::lng_code_block_header_copy(tr::now);
}
QString UiIntegration::phraseMinimize() {
return tr::lng_minimize_window(tr::now);
}
QString UiIntegration::phraseMaximize() {
return tr::lng_maximize_window(tr::now);
}
QString UiIntegration::phraseRestore() {
return tr::lng_restore_window(tr::now);
}
bool OpenGLLastCheckFailed() {
return QFile::exists(OpenGLCheckFilePath());
}

View File

@@ -92,6 +92,9 @@ public:
QString phraseBotAllowWriteTitle() override;
QString phraseBotAllowWriteConfirm() override;
QString phraseQuoteHeaderCopy() override;
QString phraseMinimize() override;
QString phraseMaximize() override;
QString phraseRestore() override;
};

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 6002002;
constexpr auto AppVersionStr = "6.2.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 6002005;
constexpr auto AppVersionStr = "6.2.5";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -7,18 +7,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/components/recent_shared_media_gifts.h"
#include "api/api_credits.h" // InputSavedStarGiftId
#include "api/api_premium.h"
#include "apiwrap.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
namespace Data {
namespace {
constexpr auto kReloadThreshold = 60 * crl::time(1000);
constexpr auto kMaxGifts = 3;
constexpr auto kMaxPinnedGifts = 6;
} // namespace
@@ -29,19 +37,42 @@ RecentSharedMediaGifts::RecentSharedMediaGifts(
RecentSharedMediaGifts::~RecentSharedMediaGifts() = default;
std::vector<Data::SavedStarGift> RecentSharedMediaGifts::filterGifts(
const std::deque<Data::SavedStarGift> &gifts,
bool onlyPinnedToTop) {
auto result = std::vector<Data::SavedStarGift>();
const auto maxCount = onlyPinnedToTop ? kMaxPinnedGifts : kMaxGifts;
for (const auto &gift : gifts) {
if (!onlyPinnedToTop || gift.pinned) {
result.push_back(gift);
if (result.size() >= maxCount) {
break;
}
}
}
return result;
}
void RecentSharedMediaGifts::request(
not_null<PeerData*> peer,
Fn<void(std::vector<DocumentId>)> done) {
Fn<void(std::vector<SavedStarGift>)> done,
bool onlyPinnedToTop) {
const auto it = _recent.find(peer->id);
if (it != _recent.end()) {
auto &entry = it->second;
if (entry.lastRequestTime
&& entry.lastRequestTime + kReloadThreshold > crl::now()) {
done(std::vector<DocumentId>(entry.ids.begin(), entry.ids.end()));
done(filterGifts(entry.gifts, onlyPinnedToTop));
return;
}
if (entry.requestId) {
peer->session().api().request(entry.requestId).cancel();
entry.pendingCallbacks.push_back([=] {
const auto it = _recent.find(peer->id);
if (it != _recent.end()) {
done(filterGifts(it->second.gifts, onlyPinnedToTop));
}
});
return;
}
}
@@ -51,7 +82,7 @@ void RecentSharedMediaGifts::request(
peer->input,
MTP_int(0), // collection_id
MTP_string(QString()),
MTP_int(kMaxGifts)
MTP_int(kMaxPinnedGifts)
)).done([=](const MTPpayments_SavedStarGifts &result) {
const auto &data = result.data();
const auto owner = &peer->owner();
@@ -60,22 +91,144 @@ void RecentSharedMediaGifts::request(
auto &entry = _recent[peer->id];
entry.lastRequestTime = crl::now();
entry.requestId = 0;
entry.ids = {};
entry.gifts.clear();
auto conter = 0;
for (const auto &gift : data.vgifts().v) {
if (auto parsed = Api::FromTL(peer, gift)) {
entry.ids.push_back(parsed->info.document->id);
if (entry.ids.size() > kMaxGifts) {
entry.ids.pop_front();
}
if (++conter >= kMaxGifts) {
break;
}
entry.gifts.push_back(std::move(*parsed));
}
}
done(std::vector<DocumentId>(entry.ids.begin(), entry.ids.end()));
done(filterGifts(entry.gifts, onlyPinnedToTop));
for (const auto &callback : entry.pendingCallbacks) {
callback();
}
entry.pendingCallbacks.clear();
}).send();
}
void RecentSharedMediaGifts::clearLastRequestTime(
not_null<PeerData*> peer) {
const auto it = _recent.find(peer->id);
if (it != _recent.end()) {
it->second.lastRequestTime = 0;
}
}
void RecentSharedMediaGifts::togglePinned(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
const Data::SavedStarGiftId &manageId,
bool pinned,
std::shared_ptr<Data::UniqueGift> uniqueData,
std::shared_ptr<Data::UniqueGift> replacingData) {
const auto performToggle = [=](const std::vector<SavedStarGift> &gifts) {
const auto limit = _session->appConfig().pinnedGiftsLimit();
auto manageIds = std::vector<Data::SavedStarGiftId>();
if (pinned) {
manageIds.push_back(manageId);
for (const auto &gift : gifts) {
if (gift.pinned && gift.manageId != manageId) {
manageIds.push_back(gift.manageId);
if (manageIds.size() >= limit) {
break;
}
}
}
} else {
for (const auto &gift : gifts) {
if (gift.pinned && gift.manageId != manageId) {
manageIds.push_back(gift.manageId);
}
}
}
auto inputs = QVector<MTPInputSavedStarGift>();
inputs.reserve(manageIds.size());
for (const auto &id : manageIds) {
inputs.push_back(Api::InputSavedStarGiftId(id));
}
_session->api().request(MTPpayments_ToggleStarGiftsPinnedToTop(
peer->input,
MTP_vector<MTPInputSavedStarGift>(std::move(inputs))
)).done([=] {
const auto updateLocal = [=] {
using GiftAction = Data::GiftUpdate::Action;
_session->data().notifyGiftUpdate({
.id = manageId,
.action = (pinned ? GiftAction::Pin : GiftAction::Unpin),
});
if (pinned) {
show->showToast({
.title = (uniqueData
? tr::lng_gift_pinned_done_title(
tr::now,
lt_gift,
Data::UniqueGiftName(*uniqueData))
: QString()),
.text = (replacingData
? tr::lng_gift_pinned_done_replaced(
tr::now,
lt_gift,
TextWithEntities{
Data::UniqueGiftName(*replacingData),
},
Ui::Text::WithEntities)
: tr::lng_gift_pinned_done(
tr::now,
Ui::Text::WithEntities)),
.duration = Ui::Toast::kDefaultDuration * 2,
});
}
};
if (!pinned) {
auto result = std::deque<SavedStarGift>();
for (const auto &id : manageIds) {
for (const auto &gift : gifts) {
if (gift.manageId == id) {
result.push_back(gift);
break;
}
}
}
_recent[peer->id].gifts = std::move(result);
updateLocal();
} else {
_session->api().request(MTPpayments_GetSavedStarGift(
MTP_vector<MTPInputSavedStarGift>(
1,
Api::InputSavedStarGiftId(manageId))
)).done([=](const MTPpayments_SavedStarGifts &result) {
const auto &tlGift = result.data().vgifts().v.front();
if (auto parsed = Api::FromTL(peer, tlGift)) {
auto result = std::deque<SavedStarGift>();
for (const auto &id : manageIds) {
if (parsed->manageId == id) {
parsed->pinned = true;
result.push_back(*parsed);
continue;
}
for (const auto &gift : gifts) {
if (gift.manageId == id) {
result.push_back(gift);
break;
}
}
}
_recent[peer->id].gifts = std::move(result);
updateLocal();
}
}).send();
}
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
}).send();
};
request(peer, performToggle, true);
}
} // namespace Data

View File

@@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_star_gift.h"
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Main {
class Session;
} // namespace Main
@@ -20,13 +26,29 @@ public:
void request(
not_null<PeerData*> peer,
Fn<void(std::vector<DocumentId>)> done);
Fn<void(std::vector<Data::SavedStarGift>)> done,
bool onlyPinnedToTop = false);
void clearLastRequestTime(not_null<PeerData*> peer);
void togglePinned(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
const Data::SavedStarGiftId &manageId,
bool pinned,
std::shared_ptr<Data::UniqueGift> uniqueData,
std::shared_ptr<Data::UniqueGift> replacingData = nullptr);
private:
[[nodiscard]] std::vector<Data::SavedStarGift> filterGifts(
const std::deque<SavedStarGift> &gifts,
bool onlyPinnedToTop);
struct Entry {
std::deque<DocumentId> ids;
std::deque<SavedStarGift> gifts;
crl::time lastRequestTime = 0;
mtpRequestId requestId = 0;
std::vector<Fn<void()>> pendingCallbacks;
};
const not_null<Main::Session*> _session;

View File

@@ -74,55 +74,56 @@ struct PeerUpdate {
Usernames = (1ULL << 12),
TranslationDisabled = (1ULL << 13),
Color = (1ULL << 14),
BackgroundEmoji = (1ULL << 15),
StoriesState = (1ULL << 16),
VerifyInfo = (1ULL << 17),
StarsPerMessage = (1ULL << 18),
ColorProfile = (1ULL << 15),
BackgroundEmoji = (1ULL << 16),
StoriesState = (1ULL << 17),
VerifyInfo = (1ULL << 18),
StarsPerMessage = (1ULL << 19),
// For users
CanShareContact = (1ULL << 19),
IsContact = (1ULL << 20),
PhoneNumber = (1ULL << 21),
OnlineStatus = (1ULL << 22),
BotCommands = (1ULL << 23),
BotCanBeInvited = (1ULL << 24),
BotStartToken = (1ULL << 25),
CommonChats = (1ULL << 26),
PeerGifts = (1ULL << 27),
HasCalls = (1ULL << 28),
SupportInfo = (1ULL << 29),
IsBot = (1ULL << 30),
EmojiStatus = (1ULL << 31),
BusinessDetails = (1ULL << 32),
Birthday = (1ULL << 33),
PersonalChannel = (1ULL << 34),
StarRefProgram = (1ULL << 35),
PaysPerMessage = (1ULL << 36),
GiftSettings = (1ULL << 37),
StarsRating = (1ULL << 38),
ContactNote = (1ULL << 39),
CanShareContact = (1ULL << 20),
IsContact = (1ULL << 21),
PhoneNumber = (1ULL << 22),
OnlineStatus = (1ULL << 23),
BotCommands = (1ULL << 24),
BotCanBeInvited = (1ULL << 25),
BotStartToken = (1ULL << 26),
CommonChats = (1ULL << 27),
PeerGifts = (1ULL << 28),
HasCalls = (1ULL << 29),
SupportInfo = (1ULL << 30),
IsBot = (1ULL << 31),
EmojiStatus = (1ULL << 32),
BusinessDetails = (1ULL << 33),
Birthday = (1ULL << 34),
PersonalChannel = (1ULL << 35),
StarRefProgram = (1ULL << 36),
PaysPerMessage = (1ULL << 37),
GiftSettings = (1ULL << 38),
StarsRating = (1ULL << 39),
ContactNote = (1ULL << 40),
// For chats and channels
InviteLinks = (1ULL << 40),
Members = (1ULL << 41),
Admins = (1ULL << 42),
BannedUsers = (1ULL << 43),
Rights = (1ULL << 44),
PendingRequests = (1ULL << 45),
Reactions = (1ULL << 46),
InviteLinks = (1ULL << 41),
Members = (1ULL << 42),
Admins = (1ULL << 43),
BannedUsers = (1ULL << 44),
Rights = (1ULL << 45),
PendingRequests = (1ULL << 46),
Reactions = (1ULL << 47),
// For channels
ChannelAmIn = (1ULL << 47),
StickersSet = (1ULL << 48),
EmojiSet = (1ULL << 49),
DiscussionLink = (1ULL << 50),
MonoforumLink = (1ULL << 51),
ChannelLocation = (1ULL << 52),
Slowmode = (1ULL << 53),
GroupCall = (1ULL << 54),
ChannelAmIn = (1ULL << 48),
StickersSet = (1ULL << 49),
EmojiSet = (1ULL << 50),
DiscussionLink = (1ULL << 51),
MonoforumLink = (1ULL << 52),
ChannelLocation = (1ULL << 53),
Slowmode = (1ULL << 54),
GroupCall = (1ULL << 55),
// For iteration
LastUsedBit = (1ULL << 54),
LastUsedBit = (1ULL << 55),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@@ -1117,9 +1117,10 @@ void DocumentData::save(
if (!toFile.isEmpty()) {
if (!media->bytes().isEmpty()) {
QFile f(toFile);
f.open(QIODevice::WriteOnly);
f.write(media->bytes());
f.close();
if (f.open(QIODevice::WriteOnly)) {
f.write(media->bytes());
f.close();
}
setLocation(Core::FileLocation(toFile));
session().local().writeFileLocation(

View File

@@ -1054,6 +1054,17 @@ bool PeerData::changeColor(
return changed1 || changed2 || changed3;
}
bool PeerData::changeColorProfile(
const tl::conditional<MTPPeerColor> &cloudColor) {
const auto changed1 = cloudColor
? changeColorProfileIndex(Data::ColorIndexFromColor(cloudColor))
: clearColorProfileIndex();
const auto changed2 = changeProfileBackgroundEmojiId(
Data::BackgroundEmojiIdFromColor(cloudColor));
const auto changed3 = changeColorProfileCollectible(cloudColor);
return changed1 || changed2 || changed3;
}
void PeerData::fillNames() {
_nameWords.clear();
_nameFirstLetters.clear();
@@ -1410,6 +1421,66 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) {
return true;
}
bool PeerData::changeColorProfileCollectible(Ui::ColorCollectible data) {
if (!_colorProfileCollectible || (*_colorProfileCollectible != data)) {
_colorProfileCollectible = std::make_shared<Ui::ColorCollectible>(
std::move(data));
return true;
}
return false;
}
bool PeerData::changeColorProfileCollectible(
const tl::conditional<MTPPeerColor> &cloudColor) {
if (!cloudColor) {
return clearColorProfileCollectible();
}
return cloudColor->match([&](const MTPDpeerColorCollectible &data) {
return changeColorProfileCollectible(Data::ParseColorCollectible(data));
}, [&](const MTPDpeerColor &) {
return clearColorProfileCollectible();
}, [&](const MTPDinputPeerColorCollectible &) {
return clearColorProfileCollectible();
});
}
bool PeerData::clearColorProfileCollectible() {
if (!_colorProfileCollectible) {
return false;
}
_colorProfileCollectible = nullptr;
return true;
}
bool PeerData::changeColorProfileIndex(uint8 index) {
index %= Ui::kColorIndexCount;
if (_colorProfileIndex == index) {
return false;
}
_colorProfileIndex = index;
return true;
}
bool PeerData::clearColorProfileIndex() {
if (!_colorProfileIndex.has_value()) {
return false;
}
_colorProfileIndex = std::nullopt;
return true;
}
DocumentId PeerData::profileBackgroundEmojiId() const {
return _profileBackgroundEmojiId;
}
bool PeerData::changeProfileBackgroundEmojiId(DocumentId id) {
if (_profileBackgroundEmojiId == id) {
return false;
}
_profileBackgroundEmojiId = id;
return true;
}
void PeerData::setEmojiStatus(const MTPEmojiStatus &status) {
const auto parsed = owner().emojiStatuses().parse(status);
setEmojiStatus(parsed.id, parsed.until);

View File

@@ -229,6 +229,22 @@ public:
[[nodiscard]] DocumentId backgroundEmojiId() const;
bool changeBackgroundEmojiId(DocumentId id);
[[nodiscard]] std::optional<uint8> colorProfileIndex() const {
return _colorProfileIndex;
}
[[nodiscard]] auto colorProfileCollectible() const
-> const std::shared_ptr<Ui::ColorCollectible> & {
return _colorProfileCollectible;
}
bool changeColorProfileCollectible(Ui::ColorCollectible data);
bool changeColorProfileCollectible(
const tl::conditional<MTPPeerColor> &cloudColor);
bool clearColorProfileCollectible();
bool changeColorProfileIndex(uint8 index);
bool clearColorProfileIndex();
[[nodiscard]] DocumentId profileBackgroundEmojiId() const;
bool changeProfileBackgroundEmojiId(DocumentId id);
void setEmojiStatus(const MTPEmojiStatus &status);
void setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until = 0);
[[nodiscard]] EmojiStatusId emojiStatusId() const;
@@ -504,6 +520,7 @@ public:
bool changeColorCollectible(
const tl::conditional<MTPPeerColor> &cloudColor);
bool changeColor(const tl::conditional<MTPPeerColor> &cloudColor);
bool changeColorProfile(const tl::conditional<MTPPeerColor> &cloudColor);
enum class BlockStatus : char {
Unknown,
@@ -608,6 +625,7 @@ private:
EmojiStatusId _emojiStatusId;
DocumentId _backgroundEmojiId = 0;
DocumentId _profileBackgroundEmojiId = 0;
crl::time _lastFullUpdate = 0;
QString _name;
@@ -621,12 +639,14 @@ private:
BarSettings _barSettings = PeerBarSettings(PeerBarSetting::Unknown);
std::unique_ptr<PeerBarDetails> _barDetails;
std::shared_ptr<Ui::ColorCollectible> _colorCollectible;
std::shared_ptr<Ui::ColorCollectible> _colorProfileCollectible;
BlockStatus _blockStatus = BlockStatus::Unknown;
LoadedStatus _loadedStatus = LoadedStatus::Not;
TranslationFlag _translationFlag = TranslationFlag::Unknown;
uint8 _colorIndex : 6 = 0;
uint8 _colorIndexCloud : 1 = 0;
std::optional<uint8> _colorProfileIndex;
uint8 _userpicHasVideo : 1 = 0;
QString _about;

View File

@@ -0,0 +1,23 @@
/*
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 Data {
struct ColorProfileSet {
std::vector<QColor> palette;
std::vector<QColor> bg;
std::vector<QColor> story;
};
struct ColorProfileData {
ColorProfileSet light;
ColorProfileSet dark;
};
} // namespace Data

View File

@@ -23,9 +23,7 @@ class SendActionManager final {
public:
struct AnimationUpdate {
not_null<Thread*> thread;
int left = 0;
int width = 0;
int height = 0;
QRect rect;
bool textUpdated = false;
};
explicit SendActionManager();

View File

@@ -792,6 +792,9 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
_peerDecorationsUpdated.fire_copy(result);
}
}
if (result->changeColorProfile(data.vprofile_color())) {
flags |= UpdateFlag::ColorProfile;
}
});
if (minimal) {
@@ -1106,6 +1109,9 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
_peerDecorationsUpdated.fire_copy(result);
}
}
if (result->changeColorProfile(data.vprofile_color())) {
flags |= UpdateFlag::ColorProfile;
}
}, [&](const MTPDchannelForbidden &data) {
const auto channel = result->asChannel();

View File

@@ -458,7 +458,7 @@ void Entry::updateChatListEntryHeight() {
session().changes().entryUpdated(this, Data::EntryUpdate::Flag::Height);
}
[[nodiscard]] bool Entry::hasChatsFilterTags(FilterId exclude) const {
bool Entry::hasChatsFilterTags(FilterId exclude) const {
if (!owner().chatsFilters().tagsEnabled()) {
return false;
}

View File

@@ -336,10 +336,9 @@ InnerWidget::InnerWidget(
) | rpl::start_with_next([=](
const Data::SendActionManager::AnimationUpdate &update) {
const auto updateRect = Ui::RowPainter::SendActionAnimationRect(
_st,
update.left,
update.width,
update.height,
update.thread,
_filterId,
update.rect,
width(),
update.textUpdated);
updateDialogRow(
@@ -374,9 +373,6 @@ InnerWidget::InnerWidget(
) | rpl::start_with_next([=](bool refreshHeight) {
if (refreshHeight) {
_chatsFilterTags.clear();
}
if (refreshHeight && _filterId) {
// Height of the main list will be refreshed in other way.
_shownList->updateHeights(_narrowRatio);
}
refreshWithCollapsedRows();
@@ -527,14 +523,25 @@ InnerWidget::InnerWidget(
) | rpl::start_with_next([=](
RowDescriptor previous,
RowDescriptor next) {
updateDialogRow(previous);
if (const auto sublist = previous.key.sublist()) {
updateDialogRow({ { sublist->owningHistory() }, {} });
}
updateDialogRow(next);
if (const auto sublist = next.key.sublist()) {
updateDialogRow({ { sublist->owningHistory() }, {} });
}
const auto update = [&](const RowDescriptor &descriptor) {
if (const auto topic = descriptor.key.topic()) {
if (_openedForum == topic->forum()) {
updateDialogRow(descriptor);
} else {
updateDialogRow({ { topic->owningHistory() }, {} });
}
} else if (const auto sublist = descriptor.key.sublist()) {
if (_savedSublists == sublist->parent()) {
updateDialogRow(descriptor);
} else {
updateDialogRow({ { sublist->owningHistory() }, {} });
}
} else {
updateDialogRow(descriptor);
}
};
update(previous);
update(next);
}, lifetime());
_controller->activeChatsFilter(

View File

@@ -317,31 +317,32 @@ Row::~Row() {
clearTopicJumpRipple();
}
void Row::recountHeight(float64 narrowRatio, FilterId filterId) {
if (const auto history = _id.history()) {
const auto hasTags = _id.entry()->hasChatsFilterTags(filterId);
const style::DialogRow &Row::ComputeSt(
not_null<const Entry*> entry,
FilterId filterId) {
if (const auto history = entry->asHistory()) {
const auto hasTags = entry->hasChatsFilterTags(filterId);
const auto wideRow = history->isForum()
|| history->amMonoforumAdmin();
_height = wideRow
? anim::interpolate(
hasTags
? st::taggedForumDialogRow.height
: st::forumDialogRow.height,
st::defaultDialogRow.height,
narrowRatio)
return wideRow
? (hasTags ? st::taggedForumDialogRow : st::forumDialogRow)
: hasTags
? anim::interpolate(
st::taggedDialogRow.height,
st::defaultDialogRow.height,
narrowRatio)
: st::defaultDialogRow.height;
} else if (_id.folder()) {
_height = st::defaultDialogRow.height;
} else if (_id.topic()) {
_height = st::forumTopicRow.height;
} else {
_height = st::defaultDialogRow.height;
? st::taggedDialogRow
: st::defaultDialogRow;
} else if (entry->asTopic()) {
return st::forumTopicRow;
}
return st::defaultDialogRow;
}
void Row::recountHeight(float64 narrowRatio, FilterId filterId) {
const auto &st = ComputeSt(_id.entry(), filterId);
_height = ((&st == &st::defaultDialogRow) || !_id.history())
? st::defaultDialogRow.height
: anim::interpolate(
st.height,
st::defaultDialogRow.height,
narrowRatio);
}
uint64 Row::sortKey(FilterId filterId) const {

View File

@@ -87,6 +87,10 @@ public:
Row(Key key, int index, int top);
~Row();
[[nodiscard]] static const style::DialogRow &ComputeSt(
not_null<const Entry*> entry,
FilterId filterId);
[[nodiscard]] int top() const {
return _top;
}

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_values.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_thread.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "dialogs/dialogs_list.h"
@@ -1198,20 +1199,20 @@ void RowPainter::Paint(
}
QRect RowPainter::SendActionAnimationRect(
not_null<const style::DialogRow*> st,
int animationLeft,
int animationWidth,
int animationHeight,
not_null<const Data::Thread*> thread,
FilterId filterId,
QRect rect,
int fullWidth,
bool textUpdated) {
const auto nameleft = st->nameLeft;
const auto namewidth = fullWidth - nameleft - st->padding.right();
const auto texttop = st->textTop;
const auto &st = Row::ComputeSt(thread, filterId);
const auto nameleft = st.nameLeft;
const auto namewidth = fullWidth - nameleft - st.padding.right();
const auto texttop = st.textTop;
return QRect(
nameleft + (textUpdated ? 0 : animationLeft),
texttop,
textUpdated ? namewidth : animationWidth,
animationHeight);
nameleft + (textUpdated ? 0 : rect.x()),
texttop + rect.y(),
textUpdated ? namewidth : rect.width(),
rect.height());
}
void PaintCollapsedRow(

View File

@@ -22,6 +22,7 @@ extern const style::DialogRow &defaultDialogRow;
namespace Data {
class Forum;
class Folder;
class Thread;
} // namespace Data
namespace Dialogs {
@@ -95,10 +96,9 @@ public:
not_null<const FakeRow*> row,
const PaintContext &context);
static QRect SendActionAnimationRect(
not_null<const style::DialogRow*> st,
int animationLeft,
int animationWidth,
int animationHeight,
not_null<const Data::Thread*> thread,
FilterId filterId,
QRect rect,
int fullWidth,
bool textUpdated);
};

View File

@@ -23,9 +23,9 @@ QString LocationClickHandler::copyToClipboardContextItemText() const {
}
void LocationClickHandler::onClick(ClickContext context) const {
if (!psLaunchMaps(_point)) {
File::OpenUrl(_text);
}
Platform::LaunchMaps(_point, [text = _text] {
File::OpenUrl(text);
});
}
void LocationClickHandler::setup() {

View File

@@ -7148,6 +7148,11 @@ void HistoryWidget::startItemRevealAnimations() {
void HistoryWidget::startMessageSendingAnimation(
not_null<HistoryItem*> item) {
if (_list->elementChatMode() == HistoryView::ElementChatMode::Default
&& width() > st::columnMaximalWidthLeft
&& !item->media()) {
return;
}
auto &sendingAnimation = controller()->sendingAnimation();
if (!sendingAnimation.checkExpectedType(item)) {
return;

View File

@@ -76,6 +76,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_message_reactions.h"
#include "data/data_peer_values.h"
#include "styles/style_chat.h"
#include "styles/style_window.h" // columnMaximalWidthLeft
#include <QtWidgets/QApplication>
#include <QtCore/QMimeData>
@@ -2019,6 +2020,11 @@ void ListWidget::startItemRevealAnimations() {
void ListWidget::startMessageSendingAnimation(
not_null<HistoryItem*> item) {
if (elementChatMode() == HistoryView::ElementChatMode::Default
&& width() > st::columnMaximalWidthLeft
&& !item->media()) {
return;
}
const auto sendingAnimation = _delegate->listSendingAnimation();
if (!sendingAnimation || !sendingAnimation->checkExpectedType(item)) {
return;

View File

@@ -903,10 +903,12 @@ void Reply::paint(
if (namew > 0) {
p.setPen(!inBubble
? st->msgImgReplyBarColor()->c
: FromNameFg(
: (colorCollectible || colorIndexPlusOne)
? FromNameFg(
context,
colorIndexPlusOne - 1,
colorCollectible));
colorCollectible)
: stm->msgServiceFg->c);
_name.drawLeftElided(
p,
x + st::historyReplyPadding.left() + previewSkip,

View File

@@ -387,14 +387,17 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
if (force
|| sendActionChanged
|| (sendActionResult && !anim::Disabled())) {
const auto height = std::max(
st::normalFont->height,
st::dialogsMiniPreviewTop + st::dialogsMiniPreview);
const auto left = 0;
const auto top = Ui::Emoji::GetCustomSkipNormal();
const auto width = _sendActionAnimation.width() + _animationLeft;
const auto height = std::max({
st::normalFont->height - top,
st::dialogsMiniPreviewTop + st::dialogsMiniPreview - top,
Ui::Emoji::GetCustomSizeNormal(),
});
_history->peer->owner().sendActionManager().updateAnimation({
_topic ? ((Data::Thread*)_topic) : _history,
0,
_sendActionAnimation.width() + _animationLeft,
height,
{ left, top, width, height },
(force || sendActionChanged)
});
}

View File

@@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "boxes/background_preview_box.h"
#include "boxes/star_gift_box.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
@@ -35,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_theme.h"
#include "ui/cached_round_corners.h"
#include "ui/painter.h"
#include "ui/top_background_gradient.h"
#include "ui/ui_utility.h"
#include "window/section_widget.h"
#include "window/window_session_controller.h"
@@ -707,9 +707,9 @@ void GiftThemeBox::cacheUniqueBackground(int width, int height) {
auto p = QPainter(&_backgroundCache);
p.setClipRect(inner);
const auto skip = inner.width() / 3;
Ui::PaintPoints(
Ui::PaintBgPoints(
p,
Ui::PatternPointsSmall(),
Ui::PatternBgPointsSmall(),
_patternCache,
_patternEmoji.get(),
*_data.unique,

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/rect.h"
#include "ui/top_background_gradient.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_credits.h"
@@ -476,9 +477,9 @@ auto UniqueGiftBg(
const auto top = (webpreview ? 2 : 1) * (-shift);
const auto outer = QRect(-shift, top, doubled, doubled);
p.setClipRect(inner);
Ui::PaintPoints(
Ui::PaintBgPoints(
p,
Ui::PatternPoints(),
Ui::PatternBgPoints(),
state->cache,
state->pattern.get(),
*gift,

View File

@@ -23,6 +23,7 @@ InfoToggle {
InfoPeerBadge {
verified: icon;
verifiedCheck: icon;
premium: icon;
premiumFg: color;
position: point;
@@ -344,6 +345,133 @@ infoLayerTopBar: InfoTopBar(infoTopBar) {
infoLayerTopBarMenuPosition: point(40px, 37px);
infoTopBarColoredBack: IconButton(infoTopBarBack) {
icon: icon {{ "info/info_back", groupCallMembersFg }};
iconOver: icon {{ "info/info_back", groupCallMembersFg }};
ripple: universalRippleAnimation;
}
infoLayerTopBarColoredBack: IconButton(infoLayerTopBarBack) {
icon: icon {{ "info/info_back", groupCallMembersFg }};
iconOver: icon {{ "info/info_back", groupCallMembersFg }};
ripple: universalRippleAnimation;
}
infoTopBarColoredClose: IconButton(infoTopBarClose) {
icon: icon {{ "info/info_close", groupCallMembersFg }};
iconOver: icon {{ "info/info_close", groupCallMembersFg }};
ripple: universalRippleAnimation;
}
infoTopBarColoredMenu: IconButton(infoTopBarMenu) {
icon: icon {{ "title_menu_dots", groupCallMembersFg }};
iconOver: icon {{ "title_menu_dots", groupCallMembersFg }};
ripple: universalRippleAnimation;
}
infoLayerTopBarColoredMenu: IconButton(infoLayerTopBarMenu) {
icon: icon {{ "title_menu_dots", groupCallMembersFg }};
iconOver: icon {{ "title_menu_dots", groupCallMembersFg }};
ripple: universalRippleAnimation;
}
infoTopBarColoredEdit: IconButton(infoTopBarEdit) {
icon: icon {{ "menu/edit", groupCallMembersFg }};
iconOver: icon {{ "menu/edit", groupCallMembersFg }};
ripple: universalRippleAnimation;
}
infoLayerTopBarColoredEdit: IconButton(infoLayerTopBarEdit) {
icon: icon {{ "menu/edit", groupCallMembersFg }};
iconOver: icon {{ "menu/edit", groupCallMembersFg }};
ripple: universalRippleAnimation;
}
infoTopBarBlackBack: IconButton(infoTopBarBack) {
icon: icon {{ "info/info_back", windowBoldFg }};
iconOver: icon {{ "info/info_back", windowBoldFg }};
ripple: universalRippleAnimation;
}
infoLayerTopBarBlackBack: IconButton(infoLayerTopBarBack) {
icon: icon {{ "info/info_back", windowBoldFg }};
iconOver: icon {{ "info/info_back", windowBoldFg }};
ripple: universalRippleAnimation;
}
infoTopBarBlackClose: IconButton(infoTopBarClose) {
icon: icon {{ "info/info_close", windowBoldFg }};
iconOver: icon {{ "info/info_close", windowBoldFg }};
ripple: universalRippleAnimation;
}
infoTopBarBlackEdit: IconButton(infoTopBarEdit) {
icon: icon {{ "menu/edit", windowBoldFg }};
iconOver: icon {{ "menu/edit", windowBoldFg }};
ripple: universalRippleAnimation;
}
infoLayerTopBarBlackEdit: IconButton(infoLayerTopBarEdit) {
icon: icon {{ "menu/edit", windowBoldFg }};
iconOver: icon {{ "menu/edit", windowBoldFg }};
ripple: universalRippleAnimation;
}
infoProfileTopBarHeightMax: 180px + infoLayerTopBarHeight;
infoProfileTopBarTitleTop: 113px;
infoProfileTopBarStatusTop: 134px;
infoProfileTopBarPhotoTop: 24px;
infoProfileTopBarPhotoBgShift: 55px;
infoProfileTopBarPhotoBgNoActionsShift: 30px;
infoProfileTopBarPhotoSize: 80px;
infoProfileTopBarLastSeenSkip: 8px;
infoProfileTopBarGiftSize: 20px;
infoProfileTopBarGiftLeft: point(15px, 13px);
infoProfileTopBarGiftTopLeft: point(4px, 4px);
infoProfileTopBarGiftBottomLeft: point(9px, 16px);
infoProfileTopBarGiftRight: point(50px, 13px);
infoProfileTopBarGiftTopRight: point(30px, 4px);
infoProfileTopBarGiftBottomRight: point(40px, 13px);
infoProfileTopBarActionButtonBgOpacity: 0.16;
infoProfileTopBarActionButtonSize: 52px;
infoProfileTopBarActionButtonIconSize: 24px;
infoProfileTopBarActionButtonLottieSize: infoProfileTopBarActionButtonIconSize - 2px;
infoProfileTopBarActionButtonIconTop: 6px;
infoProfileTopBarActionButtonTextTop: infoProfileTopBarActionButtonIconSize + infoProfileTopBarActionButtonIconTop + 1px;
infoProfileTopBarActionButtonTextSkip: 4px;
infoProfileTopBarActionButtonFont: font(11px);
infoProfileTopBarActionButtonsPadding: margins(18px, 0px, 18px, 16px);
infoProfileTopBarActionButtonsHeight: infoProfileTopBarActionButtonSize + infoProfileTopBarActionButtonsPadding.topBottom;
infoProfileTopBarActionButtonsSpace: 10px;
infoProfileTopBarStep2: infoProfileTopBarHeightMax - infoLayerTopBarHeight;
infoProfileTopBarStep1: infoProfileTopBarStep2 - infoProfileTopBarActionButtonsHeight;
infoProfileTopBarNoActionsHeightMax: infoProfileTopBarHeightMax - infoProfileTopBarActionButtonSize;
infoProfileTopBarActionMenuSkip: 10px;
infoProfileTopBarActionMessage: icon{{ "profile/message-22x22", windowBoldFg }};
infoProfileTopBarActionMute: icon{{ "profile/mute-22x22", windowBoldFg }};
infoProfileTopBarActionUnmute: icon{{ "profile/unmute-22x22", windowBoldFg }};
infoProfileTopBarActionCall: icon{{ "profile/call-22x22", windowBoldFg }};
infoProfileTopBarActionGift: icon{{ "profile/gift-22x22", windowBoldFg }};
infoProfileTopBarActionJoin: icon{{ "profile/join-22x22", windowBoldFg }};
infoProfileTopBarActionReport: icon{{ "profile/report-22x22", windowBoldFg }};
infoProfileTopBarActionLeave: icon{{ "profile/leave-22x22", windowBoldFg }};
infoProfileTopBarActionMore: icon{{ "profile/profile_more", windowBoldFg }};
infoProfileTopBarBoostFooter: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
style: TextStyle(defaultTextStyle) {
font: font(11px);
}
palette: TextPalette(defaultTextPalette) {
linkFg: windowBoldFg;
selectLinkFg: windowBoldFg;
}
}
infoProfileTopBarBoostFooterColored: FlatLabel(infoProfileTopBarBoostFooter) {
textFg: groupCallVideoSubTextFg;
palette: TextPalette(defaultTextPalette) {
linkFg: groupCallIconFg;
selectLinkFg: groupCallIconFg;
}
}
infoMinimalWidth: 324px;
infoDesiredWidth: 392px;
infoMinimalLayerMargin: 48px;
@@ -445,14 +573,12 @@ infoRatingDeductedBadge: RoundButton(customEmojiTextBadge) {
infoProfileInaccessibleUserpic: icon {{ "info/inaccessible_userpic", historyPeerUserpicFg }};
infoVerifiedCheckPosition: point(4px, 2px);
infoVerifiedCheck: icon {
{ "profile_verified_star", profileVerifiedCheckBg },
{ "profile_verified_check", profileVerifiedCheckFg }
};
infoVerifiedStar: icon {{ "profile_verified_star", profileVerifiedCheckBg }};
infoPremiumStar: icon {{ "profile_premium", profileVerifiedCheckBg }};
infoPeerBadge: InfoPeerBadge {
verified: infoVerifiedCheck;
verified: infoVerifiedStar;
verifiedCheck: icon {{ "profile_verified_check", profileVerifiedCheckFg }};
premium: infoPremiumStar;
premiumFg: profileVerifiedCheckBg;
position: infoVerifiedCheckPosition;
@@ -461,18 +587,23 @@ infoPeerBadge: InfoPeerBadge {
infoBotVerifyBadge: InfoPeerBadge(infoPeerBadge) {
position: point(-2px, 2px);
}
infoColoredPeerBadge: InfoPeerBadge(infoPeerBadge) {
verified: icon {{ "profile_verified_star", groupCallMembersFg }}; // Will be colorized.
verifiedCheck: icon {{ "profile_verified_check", groupCallMembersFg }};
premium: icon {{ "profile_premium", groupCallMembersFg }};
premiumFg: groupCallMembersFg;
}
infoColoredBotVerifyBadge: InfoPeerBadge(infoColoredPeerBadge) {
position: point(-2px, 2px);
}
infoIconFg: windowBoldFg;
infoProfileSkip: 7px;
infoProfileLabeledPadding: margins(79px, 9px, 30px, 7px);
infoProfileLabeledUsernamePadding: margins(79px, 9px, 20px, 7px);
infoProfileSeparatorPadding: margins(
77px,
infoProfileSkip,
0px,
infoProfileSkip);
infoProfileLabeledPadding: margins(23px, 9px, 20px, 7px);
infoProfileLabeledUsernamePadding: margins(23px, 9px, 20px, 7px);
infoProfilePersonalChannelPadding: margins(79px, 9px, 20px, 7px);
infoProfileLabeledButtonQr: IconButton(defaultIconButton) {
width: 34px;
height: 34px;
@@ -482,7 +613,7 @@ infoProfileLabeledButtonQr: IconButton(defaultIconButton) {
rippleAreaSize: 34px;
ripple: defaultRippleAnimation;
}
infoProfileLabeledButtonQrRightSkip: 10px;
infoProfileLabeledButtonQrRightSkip: -4px;
infoIconInformation: icon {{ "info/info_information", infoIconFg }};
infoIconAddMember: icon {{ "info/info_add_member", infoIconFg }};
@@ -534,7 +665,7 @@ infoPersonalChannelNameLabel: FlatLabel(infoProfileStatus) {
style: semiboldTextStyle;
maxHeight: 20px;
}
infoPersonalChannelDateSkip: 22px;
infoPersonalChannelDateSkip: 20px;
infoPersonalChannelDateLabel: FlatLabel(infoProfileStatus) {
textFg: dialogsDateFg;
style: TextStyle(semiboldTextStyle) {
@@ -542,7 +673,7 @@ infoPersonalChannelDateLabel: FlatLabel(infoProfileStatus) {
}
maxHeight: 20px;
}
infoPersonalChannelUserpicSkip: 3px;
infoPersonalChannelUserpicSkip: -5px;
infoPersonalChannelUserpic: UserpicButton(defaultUserpicButton) {
size: size(42px, 42px);
photoSize: 42px;
@@ -572,13 +703,11 @@ infoProfileButton: SettingsButton(defaultSettingsButton) {
toggleOver: infoProfileToggleOver;
toggleSkip: 20px;
}
infoNotificationsButton: SettingsButton(infoProfileButton) {
padding: margins(79px, 13px, 8px, 9px);
}
infoMainButton: SettingsButton(infoProfileButton) {
textFg: lightButtonFg;
textFgOver: lightButtonFgOver;
style: semiboldTextStyle;
padding: margins(23px, 10px, 8px, 8px);
}
infoMainButtonAttention: SettingsButton(infoMainButton) {
textFg: attentionButtonFg;
@@ -1294,16 +1423,5 @@ collectionSubTabs: SubTabs(defaultSubTabs) {
earnTonIconSize: 16px;
earnTonIconMargin: margins(0px, 2px, 0px, 0px);
infoMusicButtonRipple: defaultRippleAnimation;
infoMusicButtonPerformer: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
maxHeight: 20px;
}
infoMusicButtonTitle: FlatLabel(defaultFlatLabel) {
textFg: windowBoldFg;
style: semiboldTextStyle;
maxHeight: 20px;
}
infoMusicButtonPadding: margins(48px, 6px, 12px, 6px);
infoMusicButtonBottom: 8px;
infoMusicButtonLine: 2px;
infoMusicButtonRipple: universalRippleAnimation;
infoMusicButtonPadding: margins(16px, 6px, 13px, 6px);

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "info/info_flexible_scroll.h"
#include "info/info_wrap_widget.h"
#include "info/statistics/info_statistics_tag.h"
#include "ui/controls/swipe_handler_data.h"
@@ -154,6 +155,42 @@ protected:
doSetInnerWidget(std::move(inner)));
}
template <typename Widget, typename FlexibleData>
Widget *setupFlexibleInnerWidget(
object_ptr<Widget> inner,
FlexibleData &flexibleScroll,
Fn<void(Ui::RpWidget*)> customSetup = nullptr) {
if (inner->hasFlexibleTopBar()) {
auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this));
filler->resize(1, 1);
flexibleScroll.contentHeightValue.events(
) | rpl::start_with_next([=](int h) {
filler->resize(filler->width(), h);
}, filler->lifetime());
filler->widthValue(
) | rpl::start_to_stream(
flexibleScroll.fillerWidthValue,
lifetime());
if (customSetup) {
customSetup(filler);
}
// ScrollArea -> PaddingWrap -> RpWidget.
inner->setParent(filler->parentWidget()->parentWidget());
inner->raise();
using InnerPtr = base::unique_qptr<Widget>;
auto owner = filler->lifetime().make_state<InnerPtr>(
std::move(inner.release()));
return owner->get();
} else {
return setInnerWidget(std::move(inner));
}
}
[[nodiscard]] not_null<Controller*> controller() const {
return _controller;
}

View File

@@ -0,0 +1,198 @@
/*
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 "info/info_flexible_scroll.h"
#include "ui/widgets/scroll_area.h"
#include "styles/style_info.h"
#include <QtWidgets/QApplication>
#include <QtWidgets/QScrollBar>
namespace Info {
FlexibleScrollHelper::FlexibleScrollHelper(
not_null<Ui::ScrollArea*> scroll,
not_null<Ui::RpWidget*> inner,
not_null<Ui::RpWidget*> pinnedToTop,
Fn<void(QMargins)> setPaintPadding,
Fn<void(rpl::producer<not_null<QEvent*>>&&)> setViewport,
FlexibleScrollData &data)
: _scroll(scroll)
, _inner(inner)
, _pinnedToTop(pinnedToTop)
, _setPaintPadding(setPaintPadding)
, _setViewport(setViewport)
, _data(data) {
setupScrollAnimation();
setupScrollHandling();
}
void FlexibleScrollHelper::setupScrollAnimation() {
constexpr auto kScrollStepTime = crl::time(260);
const auto clearScrollState = [=] {
_scrollAnimation.stop();
_scrollTopFrom = 0;
_scrollTopTo = 0;
_timeOffset = 0;
_lastScrollApplied = 0;
};
_scrollAnimation.init([=](crl::time now) {
const auto progress = float64(now
- _scrollAnimation.started()
- _timeOffset) / kScrollStepTime;
const auto eased = anim::easeOutQuint(1.0, progress);
const auto scrollCurrent = anim::interpolate(
_scrollTopFrom,
_scrollTopTo,
std::clamp(eased, 0., 1.));
_scroll->scrollToY(scrollCurrent);
_lastScrollApplied = scrollCurrent;
if (progress >= 1) {
clearScrollState();
}
});
}
void FlexibleScrollHelper::setupScrollHandling() {
const auto heightDiff = [=] {
return _pinnedToTop->maximumHeight()
- _pinnedToTop->minimumHeight();
};
rpl::combine(
_pinnedToTop->heightValue(),
_inner->heightValue()
) | rpl::start_with_next([=](int, int h) {
_data.contentHeightValue.fire(h + heightDiff());
}, _pinnedToTop->lifetime());
const auto singleStep = _scroll->verticalScrollBar()->singleStep()
* QApplication::wheelScrollLines();
const auto step1 = (_pinnedToTop->maximumHeight()
< st::infoProfileTopBarHeightMax)
? (st::infoProfileTopBarStep2 + st::lineWidth)
: st::infoProfileTopBarStep1;
const auto step2 = st::infoProfileTopBarStep2;
// const auto stepDepreciation = singleStep
// - st::infoProfileTopBarActionButtonsHeight;
_scrollTopPrevious = _scroll->scrollTop();
_scroll->scrollTopValue(
) | rpl::start_with_next([=](int top) {
if (_applyingFakeScrollState) {
return;
}
const auto diff = top - _scrollTopPrevious;
if (std::abs(diff) == singleStep) {
const auto previousValue = top - diff;
const auto nextStep = (diff > 0)
? ((previousValue == 0)
? step1
: (previousValue == step1)
? step2
: -1)
// : ((top < step1
// && (top + stepDepreciation != step1
// || _scrollAnimation.animating()))
: ((top < step1)
? 0
: (top < step2)
? step1
: -1);
{
_applyingFakeScrollState = true;
_scroll->scrollToY(previousValue);
_applyingFakeScrollState = false;
}
if (_scrollAnimation.animating()
&& ((_scrollTopTo > _scrollTopFrom) != (diff > 0))) {
auto overriddenDirection = true;
if (_scrollTopTo > _scrollTopFrom) {
// From going down to going up.
if (_scrollTopTo == step1) {
_scrollTopTo = 0;
} else if (_scrollTopTo == step2) {
_scrollTopTo = step1;
} else {
overriddenDirection = false;
}
} else {
// From going up to going down.
if (_scrollTopTo == 0) {
_scrollTopTo = step1;
} else if (_scrollTopTo == step1) {
_scrollTopTo = step2;
} else {
overriddenDirection = false;
}
}
if (overriddenDirection) {
_timeOffset = crl::now() - _scrollAnimation.started();
_scrollTopFrom = _lastScrollApplied
? _lastScrollApplied
: previousValue;
return;
} else {
_scrollAnimation.stop();
_scrollTopFrom = 0;
_scrollTopTo = 0;
_timeOffset = 0;
_lastScrollApplied = 0;
}
}
_scrollTopFrom = _lastScrollApplied
? _lastScrollApplied
: previousValue;
if (!_scrollAnimation.animating()) {
_scrollTopTo = ((nextStep != -1) ? nextStep : top);
_scrollAnimation.start();
} else {
if (_scrollTopTo > _scrollTopFrom) {
// Down.
if (_scrollTopTo == step1) {
_scrollTopTo = step2;
} else {
_scrollTopTo += diff;
}
} else {
// Up.
if (_scrollTopTo == step2) {
_scrollTopTo = step1;
} else if (_scrollTopTo == step1) {
_scrollTopTo = 0;
} else {
_scrollTopTo += diff;
}
}
_timeOffset = (crl::now() - _scrollAnimation.started());
}
return;
}
_scrollTopPrevious = top;
const auto current = heightDiff() - top;
_inner->moveToLeft(0, std::min(0, current));
_pinnedToTop->resize(
_pinnedToTop->width(),
std::max(current + _pinnedToTop->minimumHeight(), 0));
}, _inner->lifetime());
_data.fillerWidthValue.events(
) | rpl::start_with_next([=](int w) {
_inner->resizeToWidth(w);
}, _inner->lifetime());
_setPaintPadding({ 0, _pinnedToTop->minimumHeight(), 0, 0 });
_setViewport(_pinnedToTop->events(
) | rpl::filter([](not_null<QEvent*> e) {
return e->type() == QEvent::Wheel;
}));
}
} // namespace Info

View File

@@ -0,0 +1,51 @@
/*
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
#include "ui/widgets/scroll_area.h"
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
namespace Info {
struct FlexibleScrollData {
rpl::event_stream<int> contentHeightValue;
rpl::event_stream<int> fillerWidthValue;
};
class FlexibleScrollHelper final {
public:
FlexibleScrollHelper(
not_null<Ui::ScrollArea*> scroll,
not_null<Ui::RpWidget*> inner,
not_null<Ui::RpWidget*> pinnedToTop,
Fn<void(QMargins)> setPaintPadding,
Fn<void(rpl::producer<not_null<QEvent*>>&&)> setViewport,
FlexibleScrollData &data);
private:
void setupScrollAnimation();
void setupScrollHandling();
const not_null<Ui::ScrollArea*> _scroll;
const not_null<Ui::RpWidget*> _inner;
const not_null<Ui::RpWidget*> _pinnedToTop;
const Fn<void(QMargins)> _setPaintPadding;
const Fn<void(rpl::producer<not_null<QEvent*>>&&)> _setViewport;
FlexibleScrollData &_data;
Ui::Animations::Basic _scrollAnimation;
int _scrollTopFrom = 0;
int _scrollTopTo = 0;
crl::time _timeOffset = 0;
int _lastScrollApplied = 0;
int _scrollTopPrevious = 0;
bool _applyingFakeScrollState = false;
};
} // namespace Info

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_widget.h"
#include "info/profile/info_profile_values.h"
#include "info/media/info_media_widget.h"
#include "info/stories/info_stories_widget.h"
#include "info/info_content_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
@@ -65,8 +66,13 @@ const style::InfoTopBar &TopBarStyle(Wrap wrap) {
[[nodiscard]] bool HasCustomTopBar(not_null<const Controller*> controller) {
const auto section = controller->section();
return (section.type() == Section::Type::BotStarRef)
|| (section.type() == Section::Type::Profile)
|| ((section.type() == Section::Type::Settings)
&& section.settingsType()->hasCustomTopBar());
&& section.settingsType()->hasCustomTopBar())
|| (section.type() == Section::Type::Stories
&& controller->key().storiesAlbumId() != Stories::ArchiveId()
&& controller->key().storiesPeer()
&& controller->key().storiesPeer()->isSelf());
}
[[nodiscard]] Fn<Ui::StringWithNumbers(int)> SelectedTitleForMedia(

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/ui_integration.h"
#include "data/components/recent_shared_media_gifts.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
@@ -371,11 +372,13 @@ not_null<Ui::SettingsButton*> AddPeerGiftsButton(
) | rpl::start_with_next([=] {
state->appearedLifetime.destroy();
const auto requestDone = crl::guard(wrap, [=](
std::vector<DocumentId> ids) {
std::vector<Data::SavedStarGift> gifts) {
state->emojiList.clear();
for (const auto &id : ids) {
for (const auto &gift : gifts) {
state->emojiList.push_back(
peer->owner().customEmojiManager().create(id, refresh));
peer->owner().customEmojiManager().create(
gift.info.document->id,
refresh));
}
state->textRefreshed.fire({});
});

View File

@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_premium.h"
#include "base/unixtime.h"
#include "boxes/send_credits_box.h" // SetButtonMarkedLabel
#include "boxes/star_gift_box.h"
#include "boxes/sticker_set_box.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h"
@@ -36,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/dynamic_thumbnails.h"
#include "ui/effects/premium_graphics.h"
#include "ui/painter.h"
#include "ui/top_background_gradient.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_credits.h"
@@ -620,9 +620,9 @@ void GiftButton::cacheUniqueBackground(
auto p = QPainter(&_uniqueBackgroundCache);
p.setClipRect(inner);
const auto skip = inner.width() / 3;
Ui::PaintPoints(
Ui::PaintBgPoints(
p,
Ui::PatternPointsSmall(),
Ui::PatternBgPointsSmall(),
_uniquePatternCache,
_uniquePatternEmoji.get(),
*unique,

View File

@@ -766,7 +766,8 @@ void DeleteContactNote(
std::move(
notesText
) | rpl::start_with_next([=, raw = notesLine.text](
const TextWithEntities &note) {
TextWithEntities note) {
TextUtilities::ParseEntities(note, TextParseLinks);
raw->setMarkedText(note, context);
}, notesLine.text->lifetime());
@@ -809,21 +810,21 @@ void DeleteContactNote(
}, notesLine.wrap->lifetime());
const auto subtextLabel = Ui::CreateChild<Ui::FlatLabel>(
notesLine.subtext->parentWidget(),
notesLine.wrap->entity(),
tr::lng_info_notes_private(tr::now),
st::infoLabel);
subtextLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
rpl::combine(
notesLine.subtext->geometryValue(),
notesContainer->widthValue()
notesLine.wrap->entity()->widthValue(),
notesLine.subtext->geometryValue()
) | rpl::start_with_next([=, skip = st::lineWidth * 5](
const QRect &subtextGeometry,
int parentWidth) {
int width,
const QRect &subtextGeometry) {
subtextLabel->moveToRight(
subtextLabel->width(),
0,
subtextGeometry.y() + skip,
parentWidth);
width);
}, subtextLabel->lifetime());
notesContainer->add(std::move(notesLine.wrap));
@@ -1161,24 +1162,8 @@ public:
private:
object_ptr<Ui::RpWidget> setupPersonalChannel(not_null<UserData*> user);
object_ptr<Ui::RpWidget> setupInfo();
object_ptr<Ui::RpWidget> setupMuteToggle();
void setupAboutVerification();
void setupMainApp();
void setupBotPermissions();
void setupMainButtons();
Ui::MultiSlideTracker fillTopicButtons();
Ui::MultiSlideTracker fillUserButtons(
not_null<UserData*> user);
Ui::MultiSlideTracker fillChannelButtons(
not_null<ChannelData*> channel);
Ui::MultiSlideTracker fillDiscussionButtons(
not_null<ChannelData*> channel);
void addShowTopicsListButton(
Ui::MultiSlideTracker &tracker,
not_null<Data::Forum*> forum);
void addViewChannelButton(
Ui::MultiSlideTracker &tracker,
not_null<ChannelData*> channel);
void addReportReaction(Ui::MultiSlideTracker &tracker);
void addReportReaction(
@@ -1609,7 +1594,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
const auto qrButton = Ui::CreateChild<Ui::IconButton>(
usernameLine.text->parentWidget(),
st::infoProfileLabeledButtonQr);
const auto rightSkip = 0;// st::infoProfileLabeledButtonQrRightSkip;
const auto rightSkip = st::infoProfileLabeledButtonQrRightSkip;
fitLabelToButton(qrButton, usernameLine.text, rightSkip);
fitLabelToButton(qrButton, usernameLine.subtext, rightSkip);
qrButton->setClickedCallback([=] {
@@ -1693,7 +1678,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
const auto qr = Ui::CreateChild<Ui::IconButton>(
linkLine.text->parentWidget(),
st::infoProfileLabeledButtonQr);
const auto rightSkip = 0;// st::infoProfileLabeledButtonQrRightSkip;
const auto rightSkip = st::infoProfileLabeledButtonQrRightSkip;
fitLabelToButton(qr, linkLine.text, rightSkip);
fitLabelToButton(qr, linkLine.subtext, rightSkip);
qr->setClickedCallback([=, peer = _peer] {
@@ -1727,33 +1712,6 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
addTranslateToMenu(about.text, AboutWithAdvancedValue(_peer));
}
}
if (!_peer->isSelf()) {
// No notifications toggle for Self => no separator.
const auto user = _peer->asUser();
const auto app = user && user->botInfo && user->botInfo->hasMainApp;
const auto padding = app
? QMargins(
st::infoOpenAppMargin.left(),
st::infoProfileSeparatorPadding.top(),
st::infoOpenAppMargin.right(),
0)
: st::infoProfileSeparatorPadding;
result->add(object_ptr<Ui::SlideWrap<>>(
result,
object_ptr<Ui::PlainShadow>(result),
padding)
)->setDuration(
st::infoSlideDuration
)->toggleOn(
std::move(tracker).atLeastOneShownValue()
);
}
object_ptr<FloatingIcon>(
result,
st::infoIconInformation,
st::infoInformationIconPosition);
return result;
}
@@ -1817,7 +1775,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
std::move(text),
st::infoLabel,
st::infoLabeled,
st::infoProfileLabeledPadding);
st::infoProfilePersonalChannelPadding);
onlyChannelWrap->entity()->add(std::move(line.wrap));
line.text->setClickHandlerFilter([=](
@@ -1864,7 +1822,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
not_null<HistoryItem*> item,
anim::type animated) {
const auto &stUserpic = st::infoPersonalChannelUserpic;
const auto &stLabeled = st::infoProfileLabeledPadding;
const auto &stLabeled = st::infoProfilePersonalChannelPadding;
messageChannelWrap->toggle(false, anim::type::instant);
clear();
@@ -1979,10 +1937,10 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
rpl::single(item->history()->peer->asChannel())),
st::infoLabel),
QMargins(
st::infoProfileLabeledPadding.left(),
st::infoProfilePersonalChannelPadding.left(),
0,
st::infoProfileLabeledPadding.right(),
st::infoProfileLabeledPadding.bottom()));
st::infoProfilePersonalChannelPadding.right(),
st::infoProfilePersonalChannelPadding.bottom()));
}
{
const auto button = Ui::CreateSimpleRectButton(
@@ -2038,77 +1996,6 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
return result;
}
object_ptr<Ui::RpWidget> DetailsFiller::setupMuteToggle() {
const auto peer = _peer;
const auto topicRootId = _topic ? _topic->rootId() : MsgId();
const auto makeThread = [=] {
return topicRootId
? static_cast<Data::Thread*>(peer->forumTopicFor(topicRootId))
: peer->owner().history(peer).get();
};
auto result = object_ptr<Ui::SettingsButton>(
_wrap,
tr::lng_profile_enable_notifications(),
st::infoNotificationsButton);
result->toggleOn(_topic
? NotificationsEnabledValue(_topic)
: NotificationsEnabledValue(peer), true);
result->setAcceptBoth();
const auto notifySettings = &peer->owner().notifySettings();
MuteMenu::SetupMuteMenu(
result.data(),
result->clicks(
) | rpl::filter([=](Qt::MouseButton button) {
if (button == Qt::RightButton) {
return true;
}
const auto topic = topicRootId
? peer->forumTopicFor(topicRootId)
: nullptr;
Assert(!topicRootId || topic != nullptr);
const auto is = topic
? notifySettings->isMuted(topic)
: notifySettings->isMuted(peer);
if (is) {
if (topic) {
notifySettings->update(topic, { .unmute = true });
} else {
notifySettings->update(peer, { .unmute = true });
}
return false;
} else {
return true;
}
}) | rpl::to_empty,
makeThread,
_controller->uiShow());
object_ptr<FloatingIcon>(
result,
st::infoIconNotifications,
st::infoNotificationsIconPosition);
return result;
}
void DetailsFiller::setupAboutVerification() {
const auto peer = _peer;
const auto inner = _wrap->add(object_ptr<Ui::VerticalLayout>(_wrap));
peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::VerifyInfo
) | rpl::start_with_next([=] {
const auto info = peer->botVerifyDetails();
while (inner->count()) {
delete inner->widgetAt(0);
}
if (!info) {
Ui::AddDivider(inner);
} else if (!info->description.empty()) {
Ui::AddDividerText(inner, rpl::single(info->description));
}
inner->resizeToWidth(inner->width());
}, inner->lifetime());
}
void DetailsFiller::setupMainApp() {
const auto button = _wrap->add(
object_ptr<Ui::RoundButton>(
@@ -2177,33 +2064,6 @@ void DetailsFiller::setupBotPermissions() {
AddSkip(_wrap);
}
void DetailsFiller::setupMainButtons() {
auto wrapButtons = [=](auto &&callback) {
auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap));
auto tracker = callback();
topSkip->toggleOn(std::move(tracker).atLeastOneShownValue());
};
if (_topic) {
wrapButtons([=] {
return fillTopicButtons();
});
} else if (const auto user = _peer->asUser()) {
wrapButtons([=] {
return fillUserButtons(user);
});
} else if (const auto channel = _peer->asChannel()) {
if (channel->isMegagroup()) {
wrapButtons([=] {
return fillDiscussionButtons(channel);
});
} else {
wrapButtons([=] {
return fillChannelButtons(channel);
});
}
}
}
void DetailsFiller::addReportReaction(Ui::MultiSlideTracker &tracker) {
v::match(_origin.data, [&](GroupReactionOrigin data) {
const auto user = _peer->asUser();
@@ -2260,159 +2120,10 @@ void DetailsFiller::addReportReaction(
st::infoMainButtonAttention);
}
Ui::MultiSlideTracker DetailsFiller::fillTopicButtons() {
Ui::MultiSlideTracker tracker;
addShowTopicsListButton(tracker, _topic->forum());
return tracker;
}
void DetailsFiller::addShowTopicsListButton(
Ui::MultiSlideTracker &tracker,
not_null<Data::Forum*> forum) {
using namespace rpl::mappers;
const auto window = _controller->parentController();
const auto peer = forum->peer();
auto showTopicsVisible = rpl::combine(
window->adaptive().oneColumnValue(),
window->shownForum().value(),
_1 || (_2 != forum));
const auto callback = [=] {
if (const auto forum = peer->forum()) {
if (peer->useSubsectionTabs()) {
window->searchInChat(forum->history());
} else {
window->showForum(forum);
}
}
};
AddMainButton(
_wrap,
(forum->peer()->isBot()
? tr::lng_bot_show_threads_list()
: tr::lng_forum_show_topics_list()),
std::move(showTopicsVisible),
callback,
tracker);
}
Ui::MultiSlideTracker DetailsFiller::fillUserButtons(
not_null<UserData*> user) {
using namespace rpl::mappers;
Ui::MultiSlideTracker tracker;
if (user->isSelf()) {
return tracker;
}
auto window = _controller->parentController();
auto addSendMessageButton = [&] {
auto activePeerValue = window->activeChatValue(
) | rpl::map([](Dialogs::Key key) {
return key.peer();
});
auto sendMessageVisible = rpl::combine(
_controller->wrapValue(),
std::move(activePeerValue),
(_1 != Wrap::Side) || (_2 != user));
auto sendMessage = [window, user] {
window->showPeerHistory(
user,
Window::SectionShow::Way::Forward);
};
AddMainButton(
_wrap,
tr::lng_profile_send_message(),
std::move(sendMessageVisible),
std::move(sendMessage),
tracker);
};
if (!user->isVerifyCodes()) {
addSendMessageButton();
}
if (!_sublist) {
addReportReaction(tracker);
}
return tracker;
}
Ui::MultiSlideTracker DetailsFiller::fillChannelButtons(
not_null<ChannelData*> channel) {
Ui::MultiSlideTracker tracker;
addViewChannelButton(tracker, channel);
return tracker;
}
void DetailsFiller::addViewChannelButton(
Ui::MultiSlideTracker &tracker,
not_null<ChannelData*> channel) {
using namespace rpl::mappers;
auto window = _controller->parentController();
auto activePeerValue = window->activeChatValue(
) | rpl::map([](Dialogs::Key key) {
return key.peer();
});
auto viewChannelVisible = rpl::combine(
_controller->wrapValue(),
std::move(activePeerValue),
(_1 != Wrap::Side) || (_2 != channel));
auto viewChannel = [=] {
window->showPeerHistory(
channel,
Window::SectionShow::Way::Forward);
};
AddMainButton(
_wrap,
tr::lng_profile_view_channel(),
std::move(viewChannelVisible),
std::move(viewChannel),
tracker);
}
Ui::MultiSlideTracker DetailsFiller::fillDiscussionButtons(
not_null<ChannelData*> channel) {
using namespace rpl::mappers;
Ui::MultiSlideTracker tracker;
auto window = _controller->parentController();
auto viewDiscussionVisible = window->dialogsEntryStateValue(
) | rpl::map([=](const Dialogs::EntryState &state) {
const auto history = state.key.history();
return (state.section == Dialogs::EntryState::Section::Replies)
&& history
&& (history->peer == channel);
});
auto viewDiscussion = [=] {
window->showPeerHistory(
channel,
Window::SectionShow::Way::Forward);
};
AddMainButton(
_wrap,
tr::lng_profile_view_discussion(),
std::move(viewDiscussionVisible),
std::move(viewDiscussion),
tracker);
if (const auto forum = channel->forum()) {
if (channel->useSubsectionTabs()) {
addShowTopicsListButton(tracker, forum);
}
} else if (const auto broadcast = channel->monoforumBroadcast()) {
addViewChannelButton(tracker, broadcast);
}
return tracker;
}
object_ptr<Ui::RpWidget> DetailsFiller::fill() {
Expects(!_topic || !_topic->creating());
if (!_topic) {
setupAboutVerification();
} else {
add(object_ptr<Ui::BoxContentDivider>(_wrap));
}
@@ -2430,11 +2141,13 @@ object_ptr<Ui::RpWidget> DetailsFiller::fill() {
setupBotPermissions();
}
}
if (!user->isSelf() && !_sublist) {
auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap));
Ui::MultiSlideTracker tracker;
addReportReaction(tracker);
topSkip->toggleOn(std::move(tracker).atLeastOneShownValue());
}
}
if (!_sublist && !_peer->isSelf()) {
add(setupMuteToggle());
}
setupMainButtons();
add(CreateSkipWidget(_wrap));
return std::move(_wrap);

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/profile/info_profile_badge.h"
#include "data/data_changes.h"
#include "data/data_emoji_statuses.h"
#include "data/data_peer.h"
#include "data/data_session.h"
@@ -90,11 +91,15 @@ void Badge::setContent(Content content) {
? (Data::FrameSizeFromTag(sizeTag())
/ style::DevicePixelRatio())
: 0;
const auto &style = st();
const auto icon = (_content.badge == BadgeType::Verified)
? &_st.verified
? &style.verified
: id
? nullptr
: &_st.premium;
: &style.premium;
const auto iconForeground = (_content.badge == BadgeType::Verified)
? &style.verifiedCheck
: nullptr;
if (id) {
_emojiStatus = _session->data().customEmojiManager().create(
Data::EmojiStatusCustomId(id),
@@ -113,7 +118,7 @@ void Badge::setContent(Content content) {
) | rpl::start_with_next([=, check = _view.data()]{
if (_emojiStatus) {
auto args = Ui::Text::CustomEmoji::Context{
.textColor = _st.premiumFg->c,
.textColor = style.premiumFg->c,
.now = crl::now(),
.paused = ((_animationPaused && _animationPaused())
|| On(PowerSaving::kEmojiStatus)),
@@ -125,8 +130,20 @@ void Badge::setContent(Content content) {
}
}
if (icon) {
Painter p(check);
icon->paint(p, emoji, 0, check->width());
auto p = Painter(check);
if (_overrideSt) {
icon->paint(
p,
emoji,
0,
check->width(),
_overrideSt->premiumFg->c);
} else {
icon->paint(p, emoji, 0, check->width());
}
if (iconForeground) {
iconForeground->paint(p, emoji, 0, check->width());
}
}
}, _view->lifetime());
} break;
@@ -179,6 +196,13 @@ void Badge::setPremiumClickCallback(Fn<void()> callback) {
}
}
void Badge::setOverrideStyle(const style::InfoPeerBadge *st) {
const auto was = _content;
_overrideSt = st;
_content = {};
setContent(was);
}
rpl::producer<> Badge::updated() const {
return _updated.events();
}
@@ -187,24 +211,30 @@ void Badge::move(int left, int top, int bottom) {
if (!_view) {
return;
}
const auto &style = st();
const auto star = !_emojiStatus
&& (_content.badge == BadgeType::Premium
|| _content.badge == BadgeType::Verified);
const auto fake = !_emojiStatus && !star;
const auto skip = fake ? 0 : _st.position.x();
const auto skip = fake ? 0 : style.position.x();
const auto badgeLeft = left + skip;
const auto badgeTop = top
+ (star
? _st.position.y()
? style.position.y()
: (bottom - top - _view->height()) / 2);
_view->moveToLeft(badgeLeft, badgeTop);
}
const style::InfoPeerBadge &Badge::st() const {
return _overrideSt ? *_overrideSt : _st;
}
Data::CustomEmojiSizeTag Badge::sizeTag() const {
using SizeTag = Data::CustomEmojiSizeTag;
return (_st.sizeTag == 2)
const auto &style = st();
return (style.sizeTag == 2)
? SizeTag::Isolated
: (_st.sizeTag == 1)
: (style.sizeTag == 1)
? SizeTag::Large
: SizeTag::Normal;
}
@@ -237,4 +267,18 @@ rpl::producer<Badge::Content> VerifiedContentForPeer(
});
}
rpl::producer<Badge::Content> BotVerifyBadgeForPeer(
not_null<PeerData*> peer) {
return peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::VerifyInfo
) | rpl::map([=] {
const auto info = peer->botVerifyDetails();
return Badge::Content{
.badge = info ? BadgeType::BotVerified : BadgeType::None,
.emojiStatusId = { info ? info->iconId : DocumentId() },
};
});
}
} // namespace Info::Profile

View File

@@ -70,6 +70,7 @@ public:
[[nodiscard]] Ui::RpWidget *widget() const;
void setPremiumClickCallback(Fn<void()> callback);
void setOverrideStyle(const style::InfoPeerBadge *st);
[[nodiscard]] rpl::producer<> updated() const;
void move(int left, int top, int bottom);
@@ -77,9 +78,11 @@ public:
private:
void setContent(Content content);
[[nodiscard]] const style::InfoPeerBadge &st() const;
const not_null<QWidget*> _parent;
const style::InfoPeerBadge &_st;
const style::InfoPeerBadge *_overrideSt = nullptr;
const not_null<Main::Session*> _session;
EmojiStatusPanel *_emojiStatusPanel = nullptr;
const int _customStatusLoopsLimit = 0;
@@ -98,5 +101,7 @@ private:
not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<Badge::Content> VerifiedContentForPeer(
not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<Badge::Content> BotVerifyBadgeForPeer(
not_null<PeerData*> peer);
} // namespace Info::Profile

View File

@@ -0,0 +1,224 @@
/*
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 "info/profile/info_profile_badge_tooltip.h"
#include "data/data_emoji_statuses.h"
#include "base/event_filter.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "styles/style_info.h"
namespace Info::Profile {
namespace {
constexpr auto kGlareDurationStep = crl::time(320);
constexpr auto kGlareTimeout = crl::time(1000);
} // namespace
BadgeTooltip::BadgeTooltip(
not_null<QWidget*> parent,
std::shared_ptr<Data::EmojiStatusCollectible> collectible,
not_null<QWidget*> pointTo)
: Ui::RpWidget(parent)
, _st(st::infoGiftTooltip)
, _collectible(std::move(collectible))
, _text(_collectible->title)
, _font(st::infoGiftTooltipFont)
, _inner(_font->width(_text), _font->height)
, _outer(_inner.grownBy(_st.padding))
, _stroke(st::lineWidth)
, _skip(2 * _stroke)
, _full(_outer + QSize(2 * _skip, _st.arrow + 2 * _skip))
, _glareSize(_outer.height() * 3)
, _glareRange(_outer.width() + _glareSize)
, _glareDuration(_glareRange * kGlareDurationStep / _glareSize)
, _glareTimer([=] { showGlare(); }) {
resize(_full + QSize(0, _st.shift));
setupGeometry(pointTo);
}
void BadgeTooltip::fade(bool shown) {
if (_shown == shown) {
return;
}
show();
_shown = shown;
_showAnimation.start([=] {
update();
if (!_showAnimation.animating()) {
if (!_shown) {
hide();
} else {
showGlare();
}
}
}, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration, anim::easeInCirc);
}
void BadgeTooltip::showGlare() {
_glareAnimation.start([=] {
update();
if (!_glareAnimation.animating()) {
_glareTimer.callOnce(kGlareTimeout);
}
}, 0., 1., _glareDuration);
}
void BadgeTooltip::finishAnimating() {
_showAnimation.stop();
if (!_shown) {
hide();
}
}
void BadgeTooltip::setOpacity(float64 opacity) {
_opacity = opacity;
update();
}
crl::time BadgeTooltip::glarePeriod() const {
return _glareDuration + kGlareTimeout;
}
void BadgeTooltip::paintEvent(QPaintEvent *e) {
const auto glare = _glareAnimation.value(0.);
_glareRight = anim::interpolate(0, _glareRange, glare);
prepareImage();
auto p = QPainter(this);
const auto shown = _showAnimation.value(_shown ? 1. : 0.);
p.setOpacity(shown * _opacity);
const auto imageHeight = _image.height() / _image.devicePixelRatio();
const auto top = anim::interpolate(0, height() - imageHeight, shown);
p.drawImage(0, top, _image);
}
void BadgeTooltip::setupGeometry(not_null<QWidget*> pointTo) {
auto widget = pointTo.get();
const auto parent = parentWidget();
const auto refresh = [=, weak = base::make_weak(pointTo)] {
const auto strong = weak.get();
if (!strong) {
hide();
return setGeometry({});
}
const auto rect = Ui::MapFrom(parent, pointTo, pointTo->rect());
const auto point = QPoint(rect.center().x(), rect.y());
const auto left = point.x() - (width() / 2);
const auto skip = _st.padding.left();
setGeometry(
std::min(std::max(left, skip), parent->width() - width() - skip),
std::max(point.y() - height() - _st.margin.bottom(), skip),
width(),
height());
const auto arrowMiddle = point.x() - x();
if (_arrowMiddle != arrowMiddle) {
_arrowMiddle = arrowMiddle;
update();
}
};
refresh();
while (widget && widget != parent) {
base::install_event_filter(this, widget, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::Resize
|| e->type() == QEvent::Move
|| e->type() == QEvent::ZOrderChange) {
refresh();
raise();
}
return base::EventFilterResult::Continue;
});
widget = widget->parentWidget();
}
}
void BadgeTooltip::prepareImage() {
const auto ratio = style::DevicePixelRatio();
const auto arrow = _st.arrow;
const auto size = _full * ratio;
if (_image.size() != size) {
_image = QImage(size, QImage::Format_ARGB32_Premultiplied);
_image.setDevicePixelRatio(ratio);
} else if (_imageGlareRight == _glareRight
&& _imageArrowMiddle == _arrowMiddle) {
return;
}
_imageGlareRight = _glareRight;
_imageArrowMiddle = _arrowMiddle;
_image.fill(Qt::transparent);
const auto gfrom = _imageGlareRight - _glareSize;
const auto gtill = _imageGlareRight;
auto path = QPainterPath();
const auto width = _outer.width();
const auto height = _outer.height();
const auto radius = (height + 1) / 2;
const auto diameter = height;
path.moveTo(radius, 0);
path.lineTo(width - radius, 0);
path.arcTo(
QRect(QPoint(width - diameter, 0), QSize(diameter, diameter)),
90,
-180);
const auto xarrow = _arrowMiddle - _skip;
if (xarrow - arrow <= radius || xarrow + arrow >= width - radius) {
path.lineTo(radius, height);
} else {
path.lineTo(xarrow + arrow, height);
path.lineTo(xarrow, height + arrow);
path.lineTo(xarrow - arrow, height);
path.lineTo(radius, height);
}
path.arcTo(
QRect(QPoint(0, 0), QSize(diameter, diameter)),
-90,
-180);
path.closeSubpath();
auto p = QPainter(&_image);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
if (gtill > 0) {
auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
gradient.setStops({
{ 0., _collectible->edgeColor },
{ 0.5, _collectible->centerColor },
{ 1., _collectible->edgeColor },
});
p.setBrush(gradient);
} else {
p.setBrush(_collectible->edgeColor);
}
p.translate(_skip, _skip);
p.drawPath(path);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setBrush(Qt::NoBrush);
auto copy = _collectible->textColor;
copy.setAlpha(0);
if (gtill > 0) {
auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
gradient.setStops({
{ 0., copy },
{ 0.5, _collectible->textColor },
{ 1., copy },
});
p.setPen(QPen(gradient, _stroke));
} else {
p.setPen(QPen(copy, _stroke));
}
p.drawPath(path);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setFont(_font);
p.setPen(QColor(255, 255, 255));
p.drawText(_st.padding.left(), _st.padding.top() + _font->ascent, _text);
}
} // namespace Info::Profile

View File

@@ -0,0 +1,70 @@
/*
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
#include "ui/rp_widget.h"
#include "ui/effects/animations.h"
#include "base/timer.h"
namespace style {
struct ImportantTooltip;
} // namespace style
namespace Data {
struct EmojiStatusCollectible;
} // namespace Data
namespace Info::Profile {
class BadgeTooltip final : public Ui::RpWidget {
public:
BadgeTooltip(
not_null<QWidget*> parent,
std::shared_ptr<Data::EmojiStatusCollectible> collectible,
not_null<QWidget*> pointTo);
void fade(bool shown);
void finishAnimating();
void setOpacity(float64 opacity);
[[nodiscard]] crl::time glarePeriod() const;
private:
void paintEvent(QPaintEvent *e) override;
void setupGeometry(not_null<QWidget*> pointTo);
void prepareImage();
void showGlare();
const style::ImportantTooltip &_st;
std::shared_ptr<Data::EmojiStatusCollectible> _collectible;
QString _text;
const style::font &_font;
QSize _inner;
QSize _outer;
int _stroke = 0;
int _skip = 0;
QSize _full;
int _glareSize = 0;
int _glareRange = 0;
crl::time _glareDuration = 0;
base::Timer _glareTimer;
Ui::Animations::Simple _showAnimation;
Ui::Animations::Simple _glareAnimation;
QImage _image;
int _glareRight = 0;
int _imageGlareRight = 0;
int _arrowMiddle = 0;
int _imageArrowMiddle = 0;
bool _shown = false;
float64 _opacity = 1.;
};
} // namespace Info::Profile

View File

@@ -18,15 +18,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_changes.h"
#include "data/data_saved_music.h"
#include "data/data_session.h"
#include "data/data_forum_topic.h"
#include "data/stickers/data_custom_emoji.h"
#include "info/profile/info_profile_badge.h"
#include "info/profile/info_profile_badge_tooltip.h"
#include "info/profile/info_profile_emoji_status_panel.h"
#include "info/profile/info_profile_music_button.h"
#include "info/profile/info_profile_status_label.h"
#include "info/profile/info_profile_values.h"
#include "info/saved/info_saved_music_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "boxes/peers/edit_forum_topic_box.h"
@@ -66,41 +65,6 @@ constexpr auto kGiftBadgeGlares = 3;
constexpr auto kGlareDurationStep = crl::time(320);
constexpr auto kGlareTimeout = crl::time(1000);
[[nodiscard]] auto MembersStatusText(int count) {
return tr::lng_chat_status_members(tr::now, lt_count_decimal, count);
};
[[nodiscard]] auto OnlineStatusText(int count) {
return tr::lng_chat_status_online(tr::now, lt_count_decimal, count);
};
[[nodiscard]] auto ChatStatusText(
int fullCount,
int onlineCount,
bool isGroup) {
if (onlineCount > 1 && onlineCount <= fullCount) {
return tr::lng_chat_status_members_online(
tr::now,
lt_members_count,
MembersStatusText(fullCount),
lt_online_count,
OnlineStatusText(onlineCount));
} else if (fullCount > 0) {
return isGroup
? tr::lng_chat_status_members(
tr::now,
lt_count_decimal,
fullCount)
: tr::lng_chat_status_subscribers(
tr::now,
lt_count_decimal,
fullCount);
}
return isGroup
? tr::lng_group_status(tr::now)
: tr::lng_channel_status(tr::now);
};
[[nodiscard]] const style::InfoProfileCover &CoverStyle(
not_null<PeerData*> peer,
Data::ForumTopic *topic,
@@ -114,7 +78,9 @@ constexpr auto kGlareTimeout = crl::time(1000);
: st::infoProfileCover;
}
[[nodiscard]] QMargins LargeCustomEmojiMargins() {
} // namespace
QMargins LargeCustomEmojiMargins() {
const auto ratio = style::DevicePixelRatio();
const auto emoji = Ui::Emoji::GetSizeLarge() / ratio;
const auto size = Data::FrameSizeFromTag(Data::CustomEmojiSizeTag::Large)
@@ -124,258 +90,6 @@ constexpr auto kGlareTimeout = crl::time(1000);
return { left, left, right, right };
}
[[nodiscard]] MusicButtonData DocumentMusicButtonData(
not_null<DocumentData*> document) {
if (const auto song = document->song()) {
if (!song->performer.isEmpty() || !song->title.isEmpty()) {
return {
.performer = song->performer,
.title = song->title,
};
}
}
const auto name = document->filename();
return {
.title = !name.isEmpty() ? name : tr::lng_all_music(tr::now),
};
}
} // namespace
class Cover::BadgeTooltip final : public Ui::RpWidget {
public:
BadgeTooltip(
not_null<QWidget*> parent,
std::shared_ptr<Data::EmojiStatusCollectible> collectible,
not_null<QWidget*> pointTo);
void fade(bool shown);
void finishAnimating();
[[nodiscard]] crl::time glarePeriod() const;
private:
void paintEvent(QPaintEvent *e) override;
void setupGeometry(not_null<QWidget*> pointTo);
void prepareImage();
void showGlare();
const style::ImportantTooltip &_st;
std::shared_ptr<Data::EmojiStatusCollectible> _collectible;
QString _text;
const style::font &_font;
QSize _inner;
QSize _outer;
int _stroke = 0;
int _skip = 0;
QSize _full;
int _glareSize = 0;
int _glareRange = 0;
crl::time _glareDuration = 0;
base::Timer _glareTimer;
Ui::Animations::Simple _showAnimation;
Ui::Animations::Simple _glareAnimation;
QImage _image;
int _glareRight = 0;
int _imageGlareRight = 0;
int _arrowMiddle = 0;
int _imageArrowMiddle = 0;
bool _shown = false;
};
Cover::BadgeTooltip::BadgeTooltip(
not_null<QWidget*> parent,
std::shared_ptr<Data::EmojiStatusCollectible> collectible,
not_null<QWidget*> pointTo)
: Ui::RpWidget(parent)
, _st(st::infoGiftTooltip)
, _collectible(std::move(collectible))
, _text(_collectible->title)
, _font(st::infoGiftTooltipFont)
, _inner(_font->width(_text), _font->height)
, _outer(_inner.grownBy(_st.padding))
, _stroke(st::lineWidth)
, _skip(2 * _stroke)
, _full(_outer + QSize(2 * _skip, _st.arrow + 2 * _skip))
, _glareSize(_outer.height() * 3)
, _glareRange(_outer.width() + _glareSize)
, _glareDuration(_glareRange * kGlareDurationStep / _glareSize)
, _glareTimer([=] { showGlare(); }) {
resize(_full + QSize(0, _st.shift));
setupGeometry(pointTo);
}
void Cover::BadgeTooltip::fade(bool shown) {
if (_shown == shown) {
return;
}
show();
_shown = shown;
_showAnimation.start([=] {
update();
if (!_showAnimation.animating()) {
if (!_shown) {
hide();
} else {
showGlare();
}
}
}, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration, anim::easeInCirc);
}
void Cover::BadgeTooltip::showGlare() {
_glareAnimation.start([=] {
update();
if (!_glareAnimation.animating()) {
_glareTimer.callOnce(kGlareTimeout);
}
}, 0., 1., _glareDuration);
}
void Cover::BadgeTooltip::finishAnimating() {
_showAnimation.stop();
if (!_shown) {
hide();
}
}
crl::time Cover::BadgeTooltip::glarePeriod() const {
return _glareDuration + kGlareTimeout;
}
void Cover::BadgeTooltip::paintEvent(QPaintEvent *e) {
const auto glare = _glareAnimation.value(0.);
_glareRight = anim::interpolate(0, _glareRange, glare);
prepareImage();
auto p = QPainter(this);
const auto shown = _showAnimation.value(_shown ? 1. : 0.);
p.setOpacity(shown);
const auto imageHeight = _image.height() / _image.devicePixelRatio();
const auto top = anim::interpolate(0, height() - imageHeight, shown);
p.drawImage(0, top, _image);
}
void Cover::BadgeTooltip::setupGeometry(not_null<QWidget*> pointTo) {
auto widget = pointTo.get();
const auto parent = parentWidget();
const auto refresh = [=] {
const auto rect = Ui::MapFrom(parent, pointTo, pointTo->rect());
const auto point = QPoint(rect.center().x(), rect.y());
const auto left = point.x() - (width() / 2);
const auto skip = _st.padding.left();
setGeometry(
std::min(std::max(left, skip), parent->width() - width() - skip),
std::max(point.y() - height() - _st.margin.bottom(), skip),
width(),
height());
const auto arrowMiddle = point.x() - x();
if (_arrowMiddle != arrowMiddle) {
_arrowMiddle = arrowMiddle;
update();
}
};
refresh();
while (widget && widget != parent) {
base::install_event_filter(this, widget, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::Resize || e->type() == QEvent::Move || e->type() == QEvent::ZOrderChange) {
refresh();
raise();
}
return base::EventFilterResult::Continue;
});
widget = widget->parentWidget();
}
}
void Cover::BadgeTooltip::prepareImage() {
const auto ratio = style::DevicePixelRatio();
const auto arrow = _st.arrow;
const auto size = _full * ratio;
if (_image.size() != size) {
_image = QImage(size, QImage::Format_ARGB32_Premultiplied);
_image.setDevicePixelRatio(ratio);
} else if (_imageGlareRight == _glareRight
&& _imageArrowMiddle == _arrowMiddle) {
return;
}
_imageGlareRight = _glareRight;
_imageArrowMiddle = _arrowMiddle;
_image.fill(Qt::transparent);
const auto gfrom = _imageGlareRight - _glareSize;
const auto gtill = _imageGlareRight;
auto path = QPainterPath();
const auto width = _outer.width();
const auto height = _outer.height();
const auto radius = (height + 1) / 2;
const auto diameter = height;
path.moveTo(radius, 0);
path.lineTo(width - radius, 0);
path.arcTo(
QRect(QPoint(width - diameter, 0), QSize(diameter, diameter)),
90,
-180);
const auto xarrow = _arrowMiddle - _skip;
if (xarrow - arrow <= radius || xarrow + arrow >= width - radius) {
path.lineTo(radius, height);
} else {
path.lineTo(xarrow + arrow, height);
path.lineTo(xarrow, height + arrow);
path.lineTo(xarrow - arrow, height);
path.lineTo(radius, height);
}
path.arcTo(
QRect(QPoint(0, 0), QSize(diameter, diameter)),
-90,
-180);
path.closeSubpath();
auto p = QPainter(&_image);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
if (gtill > 0) {
auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
gradient.setStops({
{ 0., _collectible->edgeColor },
{ 0.5, _collectible->centerColor },
{ 1., _collectible->edgeColor },
});
p.setBrush(gradient);
} else {
p.setBrush(_collectible->edgeColor);
}
p.translate(_skip, _skip);
p.drawPath(path);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setBrush(Qt::NoBrush);
auto copy = _collectible->textColor;
copy.setAlpha(0);
if (gtill > 0) {
auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
gradient.setStops({
{ 0., copy },
{ 0.5, _collectible->textColor },
{ 1., copy },
});
p.setPen(QPen(gradient, _stroke));
} else {
p.setPen(QPen(copy, _stroke));
}
p.drawPath(path);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setFont(_font);
p.setPen(QColor(255, 255, 255));
p.drawText(_st.padding.left(), _st.padding.top() + _font->ascent, _text);
}
TopicIconView::TopicIconView(
not_null<Data::ForumTopic*> topic,
Fn<bool()> paused,
@@ -583,20 +297,6 @@ Cover::Cover(
nullptr) {
}
[[nodiscard]] rpl::producer<Badge::Content> BotVerifyBadgeForPeer(
not_null<PeerData*> peer) {
return peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::VerifyInfo
) | rpl::map([=] {
const auto info = peer->botVerifyDetails();
return Badge::Content{
.badge = info ? BadgeType::BotVerified : BadgeType::None,
.emojiStatusId = { info ? info->iconId : DocumentId() },
};
});
}
Cover::Cover(
QWidget *parent,
not_null<Window::SessionController*> controller,
@@ -685,8 +385,8 @@ Cover::Cover(
: Fn<Data::StarsRatingPending()>()))
: nullptr)
, _status(this, _st.status)
, _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen)
, _refreshStatusTimer([this] { refreshStatusText(); }) {
, _statusLabel(std::make_unique<StatusLabel>(_status.data(), _peer))
, _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen) {
_peer->updateFull();
if (const auto broadcast = _peer->monoforumBroadcast()) {
broadcast->updateFull();
@@ -740,9 +440,6 @@ Cover::Cover(
initViewers(std::move(title));
setupChildGeometry();
setupUniqueBadgeTooltip();
if (_role != Role::EditContact) {
setupSavedMusic();
}
if (_userpic) {
} else if (topic->canEdit()) {
@@ -845,45 +542,13 @@ void Cover::setupChildGeometry() {
}, lifetime());
}
void Cover::setupSavedMusic() {
if (!Data::SavedMusic::Supported(_peer->id)) {
return;
}
Data::SavedMusicList(
_peer,
nullptr,
1
) | rpl::map([=](const Data::SavedMusicSlice &data) {
return data.size() ? data[0].get() : nullptr;
}) | rpl::start_with_next([=](HistoryItem *item) {
const auto media = item ? item->media() : nullptr;
const auto document = media ? media->document() : nullptr;
if (!document) {
_musicButton = nullptr;
resize(width(), _st.height);
} else if (!_musicButton) {
using namespace Info::Saved;
_musicButton = std::make_unique<MusicButton>(
this,
DocumentMusicButtonData(document),
[=] { _controller->showSection(MakeMusic(_peer)); });
_musicButton->show();
widthValue(
) | rpl::start_with_next([=](int newWidth) {
_musicButton->resizeToWidth(newWidth);
const auto skip = st::infoMusicButtonBottom;
_musicButton->moveToLeft(0, _st.height - skip, newWidth);
resize(width(), _st.height + _musicButton->height());
}, _musicButton->lifetime());
} else {
_musicButton->updateData(DocumentMusicButtonData(document));
Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
std::move(count) | rpl::start_with_next([=](int value) {
if (_statusLabel) {
_statusLabel->setOnlineCount(value);
refreshStatusGeometry(width());
}
}, lifetime());
}
Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
_onlineCount = std::move(count);
return this;
}
@@ -900,13 +565,16 @@ void Cover::initViewers(rpl::producer<QString> title) {
refreshNameGeometry(width());
}, lifetime());
rpl::combine(
_peer->session().changes().peerFlagsValue(
_peer,
Flag::OnlineStatus | Flag::Members),
_onlineCount.value()
_statusLabel->setMembersLinkCallback([=] {
_showSection.fire(Section::Type::Members);
});
_peer->session().changes().peerFlagsValue(
_peer,
Flag::OnlineStatus | Flag::Members
) | rpl::start_with_next([=] {
refreshStatusText();
_statusLabel->refresh();
refreshStatusGeometry(width());
}, lifetime());
_peer->session().changes().peerFlagsValue(
@@ -1056,62 +724,7 @@ void Cover::setupChangePersonal() {
}, _changePersonal->lifetime());
}
void Cover::refreshStatusText() {
auto hasMembersLink = [&] {
if (auto megagroup = _peer->asMegagroup()) {
return megagroup->canViewMembers();
}
return false;
}();
auto statusText = [&]() -> TextWithEntities {
using namespace Ui::Text;
auto currentTime = base::unixtime::now();
if (auto user = _peer->asUser()) {
const auto result = Data::OnlineTextFull(user, currentTime);
const auto showOnline = Data::OnlineTextActive(user, currentTime);
const auto updateIn = Data::OnlineChangeTimeout(user, currentTime);
if (showOnline) {
_refreshStatusTimer.callOnce(updateIn);
}
return showOnline
? Ui::Text::Colorized(result)
: TextWithEntities{ .text = result };
} else if (auto chat = _peer->asChat()) {
if (!chat->amIn()) {
return tr::lng_chat_status_unaccessible({}, WithEntities);
}
const auto onlineCount = _onlineCount.current();
const auto fullCount = std::max(
chat->count,
int(chat->participants.size()));
return { .text = ChatStatusText(fullCount, onlineCount, true) };
} else if (auto broadcast = _peer->monoforumBroadcast()) {
auto result = ChatStatusText(
qMax(broadcast->membersCount(), 1),
0,
false);
return TextWithEntities{ .text = result };
} else if (auto channel = _peer->asChannel()) {
const auto onlineCount = _onlineCount.current();
const auto fullCount = qMax(channel->membersCount(), 1);
auto result = ChatStatusText(
fullCount,
onlineCount,
channel->isMegagroup());
return hasMembersLink
? Ui::Text::Link(result)
: TextWithEntities{ .text = result };
}
return tr::lng_chat_status_unaccessible(tr::now, WithEntities);
}();
_status->setMarkedText(statusText);
if (hasMembersLink) {
_status->setLink(1, std::make_shared<LambdaClickHandler>([=] {
_showSection.fire(Section::Type::Members);
}));
}
refreshStatusGeometry(width());
}
Cover::~Cover() {
base::take(_badgeTooltip);

View File

@@ -44,9 +44,12 @@ struct InfoProfileCover;
namespace Info::Profile {
class BadgeTooltip;
class EmojiStatusPanel;
class MusicButton;
class Badge;
class StatusLabel;
[[nodiscard]] QMargins LargeCustomEmojiMargins();
class TopicIconView final {
public:
@@ -128,7 +131,6 @@ public:
[[nodiscard]] std::optional<QImage> updatedPersonalPhoto() const;
private:
class BadgeTooltip;
Cover(
QWidget *parent,
@@ -141,9 +143,7 @@ private:
void setupShowLastSeen();
void setupChildGeometry();
void setupSavedMusic();
void initViewers(rpl::producer<QString> title);
void refreshStatusText();
void refreshNameGeometry(int newWidth);
void refreshStatusGeometry(int newWidth);
void refreshUploadPhotoOverlay();
@@ -161,7 +161,6 @@ private:
rpl::variable<Badge::Content> _badgeContent;
const std::unique_ptr<Badge> _badge;
const std::unique_ptr<Badge> _verified;
rpl::variable<int> _onlineCount;
const Fn<not_null<QWidget*>()> _parentForTooltip;
std::unique_ptr<BadgeTooltip> _badgeTooltip;
@@ -176,12 +175,10 @@ private:
object_ptr<Ui::FlatLabel> _name = { nullptr };
std::unique_ptr<Ui::StarsRating> _starsRating;
object_ptr<Ui::FlatLabel> _status = { nullptr };
std::unique_ptr<StatusLabel> _statusLabel;
rpl::variable<int> _statusShift = 0;
object_ptr<Ui::RoundButton> _showLastSeen = { nullptr };
//object_ptr<CoverDropArea> _dropArea = { nullptr };
base::Timer _refreshStatusTimer;
std::unique_ptr<MusicButton> _musicButton;
rpl::event_stream<Section> _showSection;

View File

@@ -8,14 +8,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_inner_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "info/profile/info_profile_widget.h"
#include "info/profile/info_profile_cover.h"
#include "info/profile/info_profile_icon.h"
#include "info/profile/info_profile_members.h"
#include "info/profile/info_profile_music_button.h"
#include "info/profile/info_profile_top_bar.h"
#include "info/profile/info_profile_actions.h"
#include "info/media/info_media_buttons.h"
#include "info/saved/info_saved_music_widget.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_forum_topic.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
@@ -23,22 +27,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_saved_music.h"
#include "data/data_saved_sublist.h"
#include "info_profile_actions.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "lang/lang_keys.h"
#include "ui/text/format_song_document_name.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "styles/style_info.h"
namespace Info {
namespace Profile {
namespace {
[[nodiscard]] MusicButtonData DocumentMusicButtonData(
not_null<DocumentData*> document) {
return { Ui::Text::FormatSongNameFor(document) };
}
void AddAboutVerification(
not_null<Ui::VerticalLayout*> layout,
not_null<PeerData*> peer) {
const auto inner = layout->add(object_ptr<Ui::VerticalLayout>(layout));
peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::VerifyInfo
) | rpl::start_with_next([=] {
const auto info = peer->botVerifyDetails();
while (inner->count()) {
delete inner->widgetAt(0);
}
if (!info) {
Ui::AddDivider(inner);
} else if (!info->description.empty()) {
Ui::AddDividerText(inner, rpl::single(info->description));
}
inner->resizeToWidth(inner->width());
}, inner->lifetime());
}
} // namespace
InnerWidget::InnerWidget(
QWidget *parent,
not_null<Controller*> controller,
@@ -78,7 +116,7 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
}
auto result = object_ptr<Ui::VerticalLayout>(parent);
_cover = AddCover(result, _controller, _peer, _topic, _sublist);
setupSavedMusic(result);
if (_topic && _topic->creating()) {
return result;
}
@@ -98,7 +136,7 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
}
}
if (auto actions = SetupActions(_controller, result.data(), _peer)) {
result->add(object_ptr<Ui::BoxContentDivider>(result));
addAboutVerificationOrDivider(result);
result->add(std::move(actions));
}
if (_peer->isChat() || _peer->isMegagroup()) {
@@ -114,7 +152,7 @@ void InnerWidget::setupMembers(not_null<Ui::VerticalLayout*> container) {
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto inner = wrap->entity();
inner->add(object_ptr<Ui::BoxContentDivider>(inner));
addAboutVerificationOrDivider(inner);
_members = inner->add(object_ptr<Members>(inner, _controller));
_members->scrollToRequests(
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
@@ -128,7 +166,10 @@ void InnerWidget::setupMembers(not_null<Ui::VerticalLayout*> container) {
: MapFrom(this, _members, QPoint(0, request.ymax)).y();
_scrollToRequests.fire({ min, max });
}, _members->lifetime());
_cover->setOnlineCount(_members->onlineCountValue());
_members->onlineCountValue(
) | rpl::start_with_next([=](int count) {
_onlineCount.fire_copy(count);
}, _members->lifetime());
using namespace rpl::mappers;
wrap->toggleOn(
@@ -136,6 +177,60 @@ void InnerWidget::setupMembers(not_null<Ui::VerticalLayout*> container) {
anim::type::instant);
}
void InnerWidget::setupSavedMusic(not_null<Ui::VerticalLayout*> container) {
auto musicValue = Data::SavedMusic::Supported(_peer->id)
? Data::SavedMusicList(
_peer,
nullptr,
1
) | rpl::map([=](const Data::SavedMusicSlice &data) {
return data.size() ? data[0].get() : nullptr;
}) | rpl::type_erased()
: rpl::single<HistoryItem*>((HistoryItem*)(nullptr));
const auto divider = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
rpl::combine(
std::move(musicValue),
_topBarColor.value()
) | rpl::start_with_next([=](
HistoryItem *item,
std::optional<QColor> color) {
while (divider->entity()->count()) {
delete divider->entity()->widgetAt(0);
}
if (item) {
if (const auto document = item->media()
? item->media()->document()
: nullptr) {
const auto music = divider->entity()->add(
object_ptr<MusicButton>(
divider->entity(),
DocumentMusicButtonData(document),
[window = _controller, peer = _peer] {
window->showSection(Info::Saved::MakeMusic(peer));
}));
music->setOverrideBg(color);
}
divider->toggle(true, anim::type::normal);
}
}, lifetime());
divider->finishAnimating();
}
void InnerWidget::addAboutVerificationOrDivider(
not_null<Ui::VerticalLayout*> content) {
if (_aboutVerificationAdded) {
Ui::AddDivider(content);
} else {
AddAboutVerification(content, _peer);
_aboutVerificationAdded = true;
}
}
object_ptr<Ui::RpWidget> InnerWidget::setupSharedMedia(
not_null<RpWidget*> parent) {
using namespace rpl::mappers;
@@ -263,16 +358,10 @@ object_ptr<Ui::RpWidget> InnerWidget::setupSharedMedia(
auto layout = result->entity();
layout->add(object_ptr<Ui::BoxContentDivider>(layout));
layout->add(object_ptr<Ui::FixedHeightWidget>(
layout,
st::infoSharedMediaBottomSkip)
)->setAttribute(Qt::WA_TransparentForMouseEvents);
addAboutVerificationOrDivider(layout);
Ui::AddSkip(layout, st::infoSharedMediaBottomSkip);
layout->add(std::move(content));
layout->add(object_ptr<Ui::FixedHeightWidget>(
layout,
st::infoSharedMediaBottomSkip)
)->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(layout, st::infoSharedMediaBottomSkip);
_sharedMediaWrap = result;
return result;
@@ -323,5 +412,38 @@ int InnerWidget::resizeGetHeight(int newWidth) {
return _content->heightNoMargins();
}
void InnerWidget::enableBackButton() {
_backToggles.force_assign(true);
}
void InnerWidget::showFinished() {
_showFinished.fire({});
}
bool InnerWidget::hasFlexibleTopBar() const {
return true;
}
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToTop(
not_null<Ui::RpWidget*> parent) {
const auto content = Ui::CreateChild<TopBar>(
parent,
TopBar::Descriptor{
.controller = _controller->parentController(),
.key = _controller->key(),
.wrap = _controller->wrapValue(),
.backToggles = _backToggles.value(),
.showFinished = _showFinished.events(),
});
content->setOnlineCount(_onlineCount.events());
_topBarColor = content->edgeColor();
return base::make_weak(not_null<Ui::RpWidget*>{ content });
}
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToBottom(
not_null<Ui::RpWidget*> parent) {
return nullptr;
}
} // namespace Profile
} // namespace Info

View File

@@ -37,7 +37,6 @@ namespace Profile {
class Memento;
class Members;
class Cover;
struct Origin;
class InnerWidget final : public Ui::RpWidget {
@@ -53,6 +52,15 @@ public:
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
rpl::producer<int> desiredHeightValue() const override;
bool hasFlexibleTopBar() const;
base::weak_qptr<Ui::RpWidget> createPinnedToTop(
not_null<Ui::RpWidget*> parent);
base::weak_qptr<Ui::RpWidget> createPinnedToBottom(
not_null<Ui::RpWidget*> parent);
void enableBackButton();
void showFinished();
protected:
int resizeGetHeight(int newWidth) override;
void visibleTopBottomUpdated(
@@ -65,30 +73,40 @@ private:
Origin origin);
object_ptr<RpWidget> setupSharedMedia(not_null<RpWidget*> parent);
void setupMembers(not_null<Ui::VerticalLayout*> container);
void setupSavedMusic(not_null<Ui::VerticalLayout*> container);
int countDesiredHeight() const;
void updateDesiredHeight() {
_desiredHeight.fire(countDesiredHeight());
}
void addAboutVerificationOrDivider(not_null<Ui::VerticalLayout*> content);
const not_null<Controller*> _controller;
const not_null<PeerData*> _peer;
PeerData * const _migrated = nullptr;
Data::ForumTopic * const _topic = nullptr;
Data::SavedSublist * const _sublist = nullptr;
bool _inResize = false;
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
rpl::event_stream<int> _desiredHeight;
rpl::variable<bool> _backToggles;
rpl::event_stream<int> _onlineCount;
rpl::event_stream<> _showFinished;
PeerData *_reactionGroup = nullptr;
std::shared_ptr<Data::PhotoMedia> _nonPersonalView;
rpl::variable<std::optional<QColor>> _topBarColor;
Members *_members = nullptr;
Cover *_cover = nullptr;
Ui::SlideWrap<RpWidget> *_sharedMediaWrap = nullptr;
object_ptr<RpWidget> _content;
bool _inResize = false;
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
rpl::event_stream<int> _desiredHeight;
bool _aboutVerificationAdded = false;
};

View File

@@ -7,8 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/profile/info_profile_music_button.h"
#include "ui/widgets/labels.h"
#include "ui/effects/animation_value.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "styles/style_chat.h"
#include "styles/style_info.h"
namespace Info::Profile {
@@ -18,79 +22,119 @@ MusicButton::MusicButton(
MusicButtonData data,
Fn<void()> handler)
: RippleButton(parent, st::infoMusicButtonRipple)
, _performer(std::make_unique<Ui::FlatLabel>(
this,
u"- "_q + data.performer,
st::infoMusicButtonPerformer))
, _title(std::make_unique<Ui::FlatLabel>(
this,
data.title,
st::infoMusicButtonTitle)) {
rpl::combine(
_title->naturalWidthValue(),
_performer->naturalWidthValue()
) | rpl::start_with_next([=] {
resizeToWidth(widthNoMargins());
}, lifetime());
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
_performer->setAttribute(Qt::WA_TransparentForMouseEvents);
, _noteSymbol(u"\u266B"_q + QChar(' '))
, _noteWidth(st::normalFont->width(_noteSymbol)) {
updateData(std::move(data));
setClickedCallback(std::move(handler));
}
MusicButton::~MusicButton() = default;
void MusicButton::updateData(MusicButtonData data) {
_performer->setText(u"- "_q + data.performer);
_title->setText(data.title);
resizeToWidth(widthNoMargins());
const auto result = data.name.textWithEntities();
const auto performerLength = result.entities.empty()
? 0
: int(result.entities.front().length());
_performer.setText(
st::semiboldTextStyle,
result.text.mid(0, performerLength));
_title.setText(
st::defaultTextStyle,
result.text.mid(performerLength, result.text.size()));
update();
}
void MusicButton::setOverrideBg(std::optional<QColor> color) {
_overrideBg = color;
update();
}
void MusicButton::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
p.fillRect(e->rect(), st::windowBgOver);
if (_overrideBg) {
p.fillRect(e->rect(), Ui::BlendColors(
*_overrideBg,
Qt::black,
st::infoProfileTopBarActionButtonBgOpacity));
} else {
p.fillRect(e->rect(), st::shadowFg);
}
paintRipple(p, QPoint());
auto pen = st::windowBoldFg->p;
pen.setCapStyle(Qt::RoundCap);
pen.setWidthF(st::infoMusicButtonLine);
p.setPen(pen);
const auto &icon = st::topicButtonArrow;
const auto iconWidth = icon.width();
const auto iconHeight = icon.height();
const auto line = st::infoMusicButtonLine;
const auto length = height() / 4.;
const auto half = height() / 2.;
const auto left = st::infoProfileCover.photoLeft + (line / 2.);
const auto padding = st::infoMusicButtonPadding;
const auto skip = st::normalFont->spacew;
auto hq = PainterHighQualityEnabler(p);
p.drawLine(
left, half - length / 2.,
left, half + length / 2.);
p.drawLine(
left + 2.5 * line, half - length,
left + 2.5 * line, half + length);
p.drawLine(
left + 5 * line, half - length * 3 / 4.,
left + 5 * line, half + length * 3 / 4.);
const auto titleWidth = _title.maxWidth();
const auto performerWidth = _performer.maxWidth();
const auto totalNeeded = titleWidth + performerWidth + skip;
const auto availableWidth = width()
- rect::m::sum::h(padding)
- iconWidth
- skip
- _noteWidth;
auto actualTitleWidth = 0;
auto actualPerformerWidth = 0;
if (totalNeeded <= availableWidth) {
actualTitleWidth = titleWidth;
actualPerformerWidth = performerWidth;
} else {
const auto ratio = float64(titleWidth) / totalNeeded;
actualPerformerWidth = int(availableWidth * (1.0 - ratio));
actualTitleWidth = availableWidth - actualPerformerWidth;
}
const auto totalContentWidth = _noteWidth
+ actualPerformerWidth
+ skip
+ actualTitleWidth
+ skip
+ iconWidth;
const auto centerX = width() / 2;
const auto contentStartX = centerX - totalContentWidth / 2;
const auto textTop = (height() - st::normalFont->height) / 2;
p.setPen(_overrideBg ? st::groupCallMembersFg : st::windowBoldFg);
p.setFont(st::normalFont);
p.drawText(contentStartX, textTop + st::normalFont->ascent, _noteSymbol);
_performer.draw(p, {
.position = { contentStartX + _noteWidth, textTop },
.availableWidth = actualPerformerWidth,
.now = crl::now(),
.elisionLines = 1,
.elisionMiddle = true,
});
p.setPen(_overrideBg ? st::groupCallVideoSubTextFg : st::windowSubTextFg);
_title.draw(p, {
.position = QPoint(
contentStartX + _noteWidth + actualPerformerWidth + skip,
textTop),
.availableWidth = actualTitleWidth,
.now = crl::now(),
.elisionLines = 1,
.elisionMiddle = true,
});
const auto iconLeft = contentStartX
+ _noteWidth
+ actualPerformerWidth
+ actualTitleWidth
+ skip
+ skip;
const auto iconTop = (height() - iconHeight) / 2;
icon.paint(p, iconLeft, iconTop, iconWidth, p.pen().color());
}
int MusicButton::resizeGetHeight(int newWidth) {
const auto padding = st::infoMusicButtonPadding;
const auto &font = st::infoMusicButtonTitle.style.font;
const auto top = padding.top();
const auto skip = st::normalFont->spacew;
const auto available = newWidth - padding.left() - padding.right();
_title->resizeToNaturalWidth(available);
_title->moveToLeft(padding.left(), top);
if (const auto left = available - _title->width() - skip; left > 0) {
_performer->show();
_performer->resizeToNaturalWidth(left);
_performer->moveToLeft(padding.left() + _title->width() + skip, top);
} else {
_performer->hide();
}
const auto &font = st::defaultTextStyle.font;
return padding.top() + font->height + padding.bottom();
}

View File

@@ -8,16 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/widgets/buttons.h"
namespace Ui {
class FlatLabel;
} // namespace Ui
#include "ui/text/format_song_name.h"
#include "ui/text/text.h"
namespace Info::Profile {
struct MusicButtonData {
QString performer;
QString title;
Ui::Text::FormatSongName name;
};
class MusicButton final : public Ui::RippleButton {
@@ -26,13 +23,18 @@ public:
~MusicButton();
void updateData(MusicButtonData data);
void setOverrideBg(std::optional<QColor> color);
private:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
std::unique_ptr<Ui::FlatLabel> _performer;
std::unique_ptr<Ui::FlatLabel> _title;
Ui::Text::String _performer;
Ui::Text::String _title;
std::optional<QColor> _overrideBg;
const QString _noteSymbol;
const int _noteWidth;
};

View File

@@ -0,0 +1,152 @@
/*
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 "info/profile/info_profile_status_label.h"
#include "data/data_peer_values.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "ui/widgets/labels.h"
#include "ui/text/text_utilities.h"
#include "ui/basic_click_handlers.h"
#include "base/unixtime.h"
namespace Info::Profile {
namespace {
[[nodiscard]] auto MembersStatusText(int count) {
return tr::lng_chat_status_members(tr::now, lt_count_decimal, count);
};
[[nodiscard]] auto OnlineStatusText(int count) {
return tr::lng_chat_status_online(tr::now, lt_count_decimal, count);
};
[[nodiscard]] auto ChatStatusText(
int fullCount,
int onlineCount,
bool isGroup) {
if (onlineCount > 1 && onlineCount <= fullCount) {
return tr::lng_chat_status_members_online(
tr::now,
lt_members_count,
MembersStatusText(fullCount),
lt_online_count,
OnlineStatusText(onlineCount));
} else if (fullCount > 0) {
return isGroup
? tr::lng_chat_status_members(
tr::now,
lt_count_decimal,
fullCount)
: tr::lng_chat_status_subscribers(
tr::now,
lt_count_decimal,
fullCount);
}
return isGroup
? tr::lng_group_status(tr::now)
: tr::lng_channel_status(tr::now);
};
} // namespace
StatusLabel::StatusLabel(
not_null<Ui::FlatLabel*> label,
not_null<PeerData*> peer)
: _label(label)
, _peer(peer)
, _refreshTimer([=] { refresh(); }) {
}
void StatusLabel::setOnlineCount(int count) {
_onlineCount = count;
refresh();
}
void StatusLabel::refresh() {
auto hasMembersLink = [&] {
if (auto megagroup = _peer->asMegagroup()) {
return megagroup->canViewMembers();
}
return false;
}();
auto statusText = [&]() -> TextWithEntities {
using namespace Ui::Text;
auto currentTime = base::unixtime::now();
if (auto user = _peer->asUser()) {
const auto result = Data::OnlineTextFull(user, currentTime);
const auto showOnline = Data::OnlineTextActive(
user,
currentTime);
const auto updateIn = Data::OnlineChangeTimeout(
user,
currentTime);
if (showOnline) {
_refreshTimer.callOnce(updateIn);
}
return (showOnline && _colorized)
? Ui::Text::Colorized(result)
: TextWithEntities{ .text = result };
} else if (auto chat = _peer->asChat()) {
if (!chat->amIn()) {
return tr::lng_chat_status_unaccessible(
{},
WithEntities);
}
const auto onlineCount = _onlineCount;
const auto fullCount = std::max(
chat->count,
int(chat->participants.size()));
return { .text = ChatStatusText(
fullCount,
onlineCount,
true) };
} else if (auto broadcast = _peer->monoforumBroadcast()) {
auto result = ChatStatusText(
qMax(broadcast->membersCount(), 1),
0,
false);
return TextWithEntities{ .text = result };
} else if (auto channel = _peer->asChannel()) {
const auto onlineCount = _onlineCount;
const auto fullCount = qMax(channel->membersCount(), 1);
auto result = ChatStatusText(
fullCount,
onlineCount,
channel->isMegagroup());
return hasMembersLink
? Ui::Text::Link(result)
: TextWithEntities{ .text = result };
}
return tr::lng_chat_status_unaccessible(tr::now, WithEntities);
}();
_label->setMarkedText(statusText);
if (hasMembersLink && _membersLinkCallback) {
_label->setLink(
1,
std::make_shared<LambdaClickHandler>(_membersLinkCallback));
}
}
void StatusLabel::setMembersLinkCallback(Fn<void()> callback) {
_membersLinkCallback = std::move(callback);
}
Fn<void()> StatusLabel::membersLinkCallback() const {
return _membersLinkCallback;
}
void StatusLabel::setColorized(bool enabled) {
_colorized = enabled;
refresh();
}
} // namespace Info::Profile

View File

@@ -0,0 +1,40 @@
/*
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
#include "base/timer.h"
namespace Ui {
class FlatLabel;
} // namespace Ui
namespace Info::Profile {
class StatusLabel final {
public:
StatusLabel(
not_null<Ui::FlatLabel*> label,
not_null<PeerData*> peer);
void refresh();
void setMembersLinkCallback(Fn<void()> callback);
[[nodiscard]] Fn<void()> membersLinkCallback() const;
void setOnlineCount(int count);
void setColorized(bool enabled);
private:
const not_null<Ui::FlatLabel*> _label;
const not_null<PeerData*> _peer;
int _onlineCount = 0;
bool _colorized = true;
Fn<void()> _membersLinkCallback;
base::Timer _refreshTimer;
};
} // namespace Info::Profile

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,290 @@
/*
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
#include "base/object_ptr.h"
#include "info/info_controller.h" // Key
#include "info/profile/info_profile_badge.h"
#include "ui/rp_widget.h"
#include "ui/userpic_view.h"
namespace Data {
class ForumTopic;
class DocumentMedia;
struct SavedStarGift;
struct ColorProfileSet;
class SavedStarGiftId;
} // namespace Data
namespace Info::Profile {
class BadgeTooltip;
class TopicIconView;
} // namespace Info::Profile
namespace Lottie {
class Animation;
class MultiPlayer;
} // namespace Lottie
namespace Ui {
class VideoUserpicPlayer;
struct OutlineSegment;
namespace Text {
class CustomEmoji;
} // namespace Text
} // namespace Ui
class PeerData;
namespace base {
class Timer;
} // namespace base
namespace style {
struct InfoTopBar;
struct InfoPeerBadge;
struct FlatLabel;
} //namespace style
class QGraphicsOpacityEffect;
namespace Ui {
class FlatLabel;
class IconButton;
class PopupMenu;
class RoundButton;
class StarsRating;
template <typename Widget>
class FadeWrap;
class HorizontalFitContainer;
namespace Animations {
class Simple;
} // namespace Animations
} //namespace Ui
namespace Info {
class Controller;
class Key;
enum class Wrap;
} //namespace Info
namespace Ui::Menu {
struct MenuCallback;
} //namespace Ui::Menu
namespace Info::Profile {
class Badge;
class StatusLabel;
class TopBar final : public Ui::RpWidget {
public:
enum class Source {
Profile,
Stories,
Preview,
};
struct Descriptor {
not_null<Window::SessionController*> controller;
Key key;
rpl::producer<Wrap> wrap;
Source source = Source::Profile;
PeerData *peer = nullptr;
rpl::variable<bool> backToggles;
rpl::producer<> showFinished;
};
struct AnimatedPatternPoint {
QPointF basePosition;
float64 size;
float64 startTime;
float64 endTime;
};
TopBar(not_null<Ui::RpWidget*> parent, Descriptor descriptor);
~TopBar();
void setOnlineCount(rpl::producer<int> &&count);
void setRoundEdges(bool value);
void setLottieSingleLoop(bool value);
void setEnableBackButtonValue(rpl::producer<bool> &&producer);
void setColorProfileIndex(std::optional<uint8> index);
void setPatternEmojiId(std::optional<DocumentId> patternEmojiId);
void setLocalEmojiStatusId(EmojiStatusId emojiStatusId);
void addTopBarMenuButton(
not_null<Window::SessionController*> controller,
Wrap wrap,
bool shouldUseColored);
void addTopBarEditButton(
not_null<Window::SessionController*> controller,
Wrap wrap,
bool shouldUseColored);
rpl::producer<std::optional<QColor>> edgeColor() const;
protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
private:
void paintEdges(QPainter &p, const QBrush &brush) const;
void paintEdges(QPainter &p) const;
void updateLabelsPosition();
[[nodiscard]] int titleMostLeft() const;
[[nodiscard]] int statusMostLeft() const;
[[nodiscard]] QRect userpicGeometry() const;
void updateUserpicButtonGeometry();
void updateGiftButtonsGeometry(
float64 progressCurrent,
const QRect &userpicRect);
void paintUserpic(QPainter &p, const QRect &geometry);
void updateVideoUserpic();
void showTopBarMenu(not_null<Window::SessionController*> controller, bool check);
void fillTopBarMenu(
not_null<Window::SessionController*> controller,
const Ui::Menu::MenuCallback &addAction);
void setupUserpicButton(not_null<Window::SessionController*> controller);
void setupActions(not_null<Window::SessionController*> controller);
void setupButtons(
not_null<Window::SessionController*> controller,
rpl::producer<bool> backToggles,
Source source);
void setupShowLastSeen(not_null<Window::SessionController*> controller);
void setupUniqueBadgeTooltip();
void hideBadgeTooltip();
void setupAnimatedPattern(const QRect &userpicGeometry = QRect());
void paintAnimatedPattern(
QPainter &p,
const QRect &rect,
const QRect &userpicGeometry);
void setupPinnedToTopGifts(not_null<Window::SessionController*> controller);
void setupNewGifts(
not_null<Window::SessionController*> controller,
const std::vector<Data::SavedStarGift> &gifts);
void setupGiftButtons(not_null<Window::SessionController*> controller);
void paintPinnedToTopGifts(
QPainter &p,
const QRect &rect,
const QRect &userpicGeometry);
[[nodiscard]] QPointF calculateGiftPosition(
int position,
float64 progress,
const QRect &userpicRect) const;
void adjustColors(const std::optional<QColor> &edgeColor);
void updateCollectibleStatus();
void updateBadgeContent();
void setupStoryOutline(const QRect &geometry = QRect());
void updateStoryOutline(std::optional<QColor> edgeColor);
void paintStoryOutline(QPainter &p, const QRect &geometry);
void updateStatusPosition(float64 progressCurrent);
[[nodiscard]] const style::FlatLabel &statusStyle() const;
void setupStatusWithRating();
[[nodiscard]] auto effectiveColorProfile()
const -> std::optional<Data::ColorProfileSet>;
[[nodiscard]] auto effectiveCollectible()
const -> std::shared_ptr<Data::EmojiStatusCollectible>;
const not_null<PeerData*> _peer;
Data::ForumTopic *_topic = nullptr;
const Key _key;
rpl::variable<Wrap> _wrap;
const style::InfoTopBar &_st;
const Source _source;
std::unique_ptr<base::Timer> _badgeTooltipHide;
const std::unique_ptr<Badge> _botVerify;
rpl::variable<Badge::Content> _badgeContent;
const Fn<bool()> _gifPausedChecker;
const std::unique_ptr<Badge> _badge;
const std::unique_ptr<Badge> _verified;
const bool _hasActions;
const int _minForProgress;
std::unique_ptr<BadgeTooltip> _badgeTooltip;
std::vector<std::unique_ptr<BadgeTooltip>> _badgeOldTooltips;
uint64 _badgeCollectibleId = 0;
object_ptr<Ui::FlatLabel> _title;
std::unique_ptr<Ui::StarsRating> _starsRating;
object_ptr<Ui::FlatLabel> _status;
std::unique_ptr<StatusLabel> _statusLabel;
rpl::variable<int> _statusShift = 0;
object_ptr<Ui::RoundButton> _showLastSeen = { nullptr };
QGraphicsOpacityEffect *_showLastSeenOpacity = nullptr;
std::shared_ptr<style::FlatLabel> _statusSt;
std::shared_ptr<style::InfoPeerBadge> _botVerifySt;
std::shared_ptr<style::InfoPeerBadge> _badgeSt;
std::shared_ptr<style::InfoPeerBadge> _verifiedSt;
rpl::variable<float64> _progress = 0.;
bool _roundEdges = true;
rpl::variable<std::optional<QColor>> _edgeColor;
bool _hasGradientBg = false;
std::optional<QColor> _solidBg;
QImage _cachedGradient;
QPainterPath _cachedClipPath;
std::unique_ptr<Ui::Text::CustomEmoji> _patternEmoji;
QImage _basePatternImage;
std::vector<AnimatedPatternPoint> _animatedPoints;
QRect _lastUserpicRect;
Ui::PeerUserpicView _userpicView;
InMemoryKey _userpicUniqueKey;
QImage _cachedUserpic;
QImage _monoforumMask;
std::unique_ptr<Ui::VideoUserpicPlayer> _videoUserpicPlayer;
std::unique_ptr<TopicIconView> _topicIconView;
base::unique_qptr<Ui::IconButton> _close;
base::unique_qptr<Ui::FadeWrap<Ui::IconButton>> _back;
base::unique_qptr<Ui::IconButton> _topBarButton;
base::unique_qptr<Ui::PopupMenu> _peerMenu;
Ui::RpWidget *_actionMore = nullptr;
base::unique_qptr<Ui::AbstractButton> _userpicButton;
base::unique_qptr<Ui::HorizontalFitContainer> _actions;
std::unique_ptr<Lottie::MultiPlayer> _lottiePlayer;
bool _lottieSingleLoop = false;
struct PinnedToTopGiftEntry {
Data::SavedStarGiftId manageId;
// QString slug;
Lottie::Animation *animation = nullptr;
std::shared_ptr<Data::DocumentMedia> media;
QImage bg;
QImage lastFrame;
int position = 0;
base::unique_qptr<Ui::AbstractButton> button;
};
bool _pinnedToTopGiftsFirstTimeShowed = false;
std::vector<PinnedToTopGiftEntry> _pinnedToTopGifts;
std::unique_ptr<Ui::Animations::Simple> _giftsAppearing;
std::unique_ptr<Ui::Animations::Simple> _giftsHiding;
rpl::lifetime _giftsLoadingLifetime;
QBrush _storyOutlineBrush;
std::vector<Ui::OutlineSegment> _storySegments;
bool _hasStories = false;
std::optional<uint8> _localColorProfileIndex;
std::optional<DocumentId> _localPatternEmojiId;
std::shared_ptr<Data::EmojiStatusCollectible> _localCollectible;
};
} // namespace Info::Profile

View File

@@ -0,0 +1,190 @@
/*
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 "info/profile/info_profile_top_bar_action_button.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "lottie/lottie_icon.h"
#include "styles/style_layers.h"
#include "styles/style_info.h"
namespace Info::Profile {
namespace {
constexpr auto kIconFadeStart = 0.4;
constexpr auto kIconFadeRange = 1.0 - kIconFadeStart;
} // namespace
TopBarActionButton::TopBarActionButton(
not_null<QWidget*> parent,
const QString &text,
const QString &lottieName)
: RippleButton(parent, st::universalRippleAnimation)
, _text(text) {
setupLottie(lottieName);
}
TopBarActionButton::TopBarActionButton(
not_null<QWidget*> parent,
const QString &text,
const style::icon &icon)
: RippleButton(parent, st::universalRippleAnimation)
, _text(text)
, _icon(&icon) {
}
TopBarActionButton::~TopBarActionButton() = default;
void TopBarActionButton::setupLottie(const QString &lottieName) {
_lottie = std::make_unique<Lottie::Icon>(Lottie::IconDescriptor{
.name = lottieName,
.color = _lottieColor,
.sizeOverride = Size(st::infoProfileTopBarActionButtonLottieSize),
});
_lottie->animate([=] { update(); }, 0, _lottie->framesCount() - 1);
}
void TopBarActionButton::convertToToggle(
const style::icon &offIcon,
const style::icon &onIcon,
const QString &offLottie,
const QString &onLottie) {
_isToggle = true;
_offIcon = &offIcon;
_onIcon = &onIcon;
_offLottie = offLottie;
_onLottie = onLottie;
_icon = _offIcon;
_lottie.reset();
}
void TopBarActionButton::toggle(bool state) {
if (!_isToggle || _toggleState == state) {
return;
}
_toggleState = state;
const auto &lottie = _toggleState ? _onLottie : _offLottie;
setupLottie(lottie);
_lottie->animate([=] {
update();
if (_lottie->frameIndex() == _lottie->framesCount() - 1) {
_icon = _toggleState ? _onIcon : _offIcon;
_lottie.reset();
}
}, 0, _lottie->framesCount() - 1);
}
void TopBarActionButton::finishAnimating() {
if (_lottie) {
_icon = _toggleState ? _onIcon : _offIcon;
_lottie.reset();
update();
}
}
void TopBarActionButton::setText(const QString &text) {
_text = text;
update();
}
void TopBarActionButton::setLottieColor(const style::color *color) {
_lottieColor = color;
_lottie.reset();
update();
}
void TopBarActionButton::setStyle(const TopBarActionButtonStyle &style) {
_bgColor = style.bgColor;
_fgColor = style.fgColor;
_shadowColor = style.shadowColor;
update();
}
void TopBarActionButton::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
const auto progress = float64(height())
/ st::infoProfileTopBarActionButtonSize;
p.setOpacity(progress);
p.setPen(Qt::NoPen);
p.setBrush(_bgColor);
{
auto hq = PainterHighQualityEnabler(p);
// Todo shadows.
p.drawRoundedRect(rect(), st::boxRadius, st::boxRadius);
}
paintRipple(p, 0, 0);
const auto iconSize = st::infoProfileTopBarActionButtonIconSize;
const auto iconTop = st::infoProfileTopBarActionButtonIconTop;
if (_lottie || _icon) {
const auto iconScale = (progress > kIconFadeStart)
? (progress - kIconFadeStart) / kIconFadeRange
: 0.0;
p.setOpacity(iconScale);
p.save();
const auto iconLeft = (width() - iconSize) / 2;
const auto half = iconSize / 2;
const auto iconCenter = QPoint(iconLeft + half, iconTop + half);
p.translate(iconCenter);
p.scale(iconScale, iconScale);
p.translate(-iconCenter);
if (_lottie) {
_lottie->paint(p, iconLeft, iconTop, _fgColor);
} else if (_icon) {
if (_fgColor) {
_icon->paint(p, iconLeft, iconTop, width(), *_fgColor);
} else {
_icon->paint(p, iconLeft, iconTop, width());
}
}
p.restore();
p.setOpacity(progress);
}
const auto skip = st::infoProfileTopBarActionButtonTextSkip;
p.setClipRect(0, 0, width(), height() - skip);
if (_fgColor.has_value()) {
p.setPen(*_fgColor);
} else {
p.setPen(st::windowBoldFg);
}
p.setFont(st::infoProfileTopBarActionButtonFont);
const auto textScale = std::max(kIconFadeStart, progress);
const auto textRect = rect()
- QMargins(0, st::infoProfileTopBarActionButtonTextTop, 0, 0);
const auto textCenter = rect::center(textRect);
p.translate(textCenter);
p.scale(textScale, textScale);
p.translate(-textCenter);
const auto elidedText = st::infoProfileTopBarActionButtonFont->elided(
_text,
textRect.width(),
Qt::ElideMiddle);
p.drawText(textRect, elidedText, style::al_top);
}
QImage TopBarActionButton::prepareRippleMask() const {
return Ui::RippleAnimation::RoundRectMask(size(), st::boxRadius);
}
QPoint TopBarActionButton::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos());
}
} // namespace Info::Profile

View File

@@ -0,0 +1,75 @@
/*
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
#include "ui/widgets/buttons.h"
namespace Lottie {
class Icon;
} // namespace Lottie
namespace Info::Profile {
struct TopBarActionButtonStyle {
QColor bgColor;
std::optional<QColor> fgColor;
std::optional<QColor> shadowColor;
};
class TopBarActionButton final : public Ui::RippleButton {
public:
TopBarActionButton(
not_null<QWidget*> parent,
const QString &text,
const QString &lottieName);
TopBarActionButton(
not_null<QWidget*> parent,
const QString &text,
const style::icon &icon);
void convertToToggle(
const style::icon &offIcon,
const style::icon &onIcon,
const QString &offLottie,
const QString &onLottie);
void setLottieColor(const style::color *color);
void toggle(bool state);
void finishAnimating();
void setText(const QString &text);
void setStyle(const TopBarActionButtonStyle &style);
~TopBarActionButton();
protected:
void paintEvent(QPaintEvent *e) override;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
private:
void setupLottie(const QString &lottieName);
QString _text;
std::unique_ptr<Lottie::Icon> _lottie;
const style::icon *_icon = nullptr;
bool _isToggle = false;
bool _toggleState = false;
const style::icon *_offIcon = nullptr;
const style::icon *_onIcon = nullptr;
QString _offLottie;
QString _onLottie;
const style::color *_lottieColor = nullptr;
QColor _bgColor;
std::optional<QColor> _fgColor;
std::optional<QColor> _shadowColor;
};
} // namespace Info::Profile

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "info/profile/info_profile_inner_widget.h"
#include "info/profile/info_profile_members.h"
#include "info/settings/info_settings_widget.h"
#include "ui/widgets/scroll_area.h"
#include "ui/ui_utility.h"
#include "data/data_peer.h"
@@ -20,9 +21,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "info/info_controller.h"
#include "styles/style_info.h"
#include <QtWidgets/QApplication>
#include <QtWidgets/QScrollBar>
namespace Info::Settings {
struct SectionCustomTopBarData;
} // namespace Info::Settings
namespace Info::Profile {
using Info::Settings::SectionCustomTopBarData;
Memento::Memento(not_null<Controller*> controller)
: Memento(
controller->peer(),
@@ -84,13 +95,15 @@ Widget::Widget(
QWidget *parent,
not_null<Controller*> controller,
Origin origin)
: ContentWidget(parent, controller) {
: ContentWidget(parent, controller)
, _inner(
setupFlexibleInnerWidget(
object_ptr<InnerWidget>(this, controller, origin),
_flexibleScroll))
, _pinnedToTop(_inner->createPinnedToTop(this))
, _pinnedToBottom(_inner->createPinnedToBottom(this)) {
controller->setSearchEnabledByContent(false);
_inner = setInnerWidget(object_ptr<InnerWidget>(
this,
controller,
origin));
_inner->move(0, 0);
_inner->scrollToRequests(
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
@@ -101,12 +114,68 @@ Widget::Widget(
scrollTo(request);
}
}, lifetime());
if (_pinnedToTop) {
_inner->widthValue(
) | rpl::start_with_next([=](int w) {
_pinnedToTop->resizeToWidth(w);
setScrollTopSkip(_pinnedToTop->height());
}, _pinnedToTop->lifetime());
_pinnedToTop->heightValue(
) | rpl::start_with_next([=](int h) {
setScrollTopSkip(h);
}, _pinnedToTop->lifetime());
}
if (_pinnedToBottom) {
const auto processHeight = [=] {
setScrollBottomSkip(_pinnedToBottom->height());
_pinnedToBottom->moveToLeft(
_pinnedToBottom->x(),
height() - _pinnedToBottom->height());
};
_inner->sizeValue(
) | rpl::start_with_next([=](const QSize &s) {
_pinnedToBottom->resizeToWidth(s.width());
}, _pinnedToBottom->lifetime());
rpl::combine(
_pinnedToBottom->heightValue(),
heightValue()
) | rpl::start_with_next(processHeight, _pinnedToBottom->lifetime());
}
if (_pinnedToTop
&& _pinnedToTop->minimumHeight()
&& _inner->hasFlexibleTopBar()) {
_flexibleScrollHelper = std::make_unique<FlexibleScrollHelper>(
scroll(),
_inner,
_pinnedToTop.get(),
[=](QMargins margins) {
ContentWidget::setPaintPadding(std::move(margins));
},
[=](rpl::producer<not_null<QEvent*>> &&events) {
ContentWidget::setViewport(std::move(events));
},
_flexibleScroll);
}
}
void Widget::setInnerFocus() {
_inner->setFocus();
}
void Widget::enableBackButton() {
_inner->enableBackButton();
}
void Widget::showFinished() {
_inner->showFinished();
}
rpl::producer<QString> Widget::title() {
if (const auto topic = controller()->key().topic()) {
return topic->peer()->isBot()

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "info/info_content_widget.h"
#include "ui/effects/animations.h"
namespace Data {
class ForumTopic;
@@ -78,6 +79,8 @@ public:
not_null<Memento*> memento);
void setInnerFocus() override;
void enableBackButton() override;
void showFinished() override;
rpl::producer<QString> title() override;
rpl::producer<Dialogs::Stories::Content> titleStories() override;
@@ -88,7 +91,11 @@ private:
std::shared_ptr<ContentMemento> doCreateMemento() override;
FlexibleScrollData _flexibleScroll;
InnerWidget *_inner = nullptr;
base::weak_qptr<Ui::RpWidget> _pinnedToTop;
base::weak_qptr<Ui::RpWidget> _pinnedToBottom;
std::unique_ptr<FlexibleScrollHelper> _flexibleScrollHelper;
};

View File

@@ -43,6 +43,11 @@ MusicProvider::MusicProvider(not_null<AbstractController*> controller)
: _controller(controller)
, _peer(controller->key().musicPeer())
, _history(_peer->owner().history(_peer)) {
_controller->session().data().itemRemoved(
) | rpl::start_with_next([this](auto item) {
itemRemoved(item);
}, _lifetime);
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &layout : _layouts) {
@@ -214,6 +219,13 @@ std::vector<ListSection> MusicProvider::fillSections(
return result;
}
void MusicProvider::itemRemoved(not_null<const HistoryItem*> item) {
if (const auto i = _layouts.find(item); i != end(_layouts)) {
_layoutRemoved.fire(i->second.item.get());
_layouts.erase(i);
}
}
void MusicProvider::markLayoutsStale() {
for (auto &layout : _layouts) {
layout.second.stale = true;

View File

@@ -96,6 +96,7 @@ private:
not_null<const Media::BaseLayout*> item,
not_null<const Media::BaseLayout*> previous) override;
void itemRemoved(not_null<const HistoryItem*> item);
void markLayoutsStale();
void clearStaleLayouts();
void clear();
@@ -115,7 +116,9 @@ private:
int _idsLimit = kMinimalIdsLimit;
Data::SavedMusicSlice _slice;
std::unordered_map<not_null<HistoryItem*>, Media::CachedItem> _layouts;
std::unordered_map<
not_null<const HistoryItem*>,
Media::CachedItem> _layouts;
rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved;
rpl::event_stream<> _refreshed;

View File

@@ -43,46 +43,22 @@ Widget::Widget(
: ContentWidget(parent, controller)
, _self(controller->key().settingsSelf())
, _type(controller->section().settingsType())
, _inner([&] {
auto inner = _type->create(
this,
controller->parentController(),
scroll(),
controller->wrapValue(
) | rpl::map([](Wrap wrap) { return (wrap == Wrap::Layer)
? ::Settings::Container::Layer
: ::Settings::Container::Section; }));
if (inner->hasFlexibleTopBar()) {
auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this));
filler->resize(1, 1);
_flexibleScroll.contentHeightValue.events(
) | rpl::start_with_next([=](int h) {
filler->resize(filler->width(), h);
}, filler->lifetime());
filler->widthValue(
) | rpl::start_to_stream(
_flexibleScroll.fillerWidthValue,
lifetime());
controller->stepDataReference() = SectionCustomTopBarData{
.backButtonEnables = _flexibleScroll.backButtonEnables.events(),
.wrapValue = controller->wrapValue(),
};
// ScrollArea -> PaddingWrap -> RpWidget.
inner->setParent(filler->parentWidget()->parentWidget());
inner->raise();
using InnerPtr = base::unique_qptr<::Settings::AbstractSection>;
auto owner = filler->lifetime().make_state<InnerPtr>(
std::move(inner.release()));
return owner->get();
} else {
return setInnerWidget(std::move(inner));
}
}())
, _inner(setupFlexibleInnerWidget(
_type->create(
this,
controller->parentController(),
scroll(),
controller->wrapValue(
) | rpl::map([](Wrap wrap) { return (wrap == Wrap::Layer)
? ::Settings::Container::Layer
: ::Settings::Container::Section; })),
_flexibleScroll,
[=](Ui::RpWidget*) {
controller->stepDataReference() = SectionCustomTopBarData{
.backButtonEnables = _flexibleScroll.backButtonEnables.events(),
.wrapValue = controller->wrapValue(),
};
}))
, _pinnedToTop(_inner->createPinnedToTop(this))
, _pinnedToBottom(_inner->createPinnedToBottom(this)) {
_inner->sectionShowOther(

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/peer_gifts/info_peer_gifts_widget.h"
#include "info/profile/info_profile_actions.h"
#include "info/profile/info_profile_icon.h"
#include "info/profile/info_profile_top_bar.h"
#include "info/profile/info_profile_values.h"
#include "info/profile/info_profile_widget.h"
#include "info/stories/info_stories_albums.h"
@@ -293,7 +294,6 @@ void InnerWidget::createProfileTop() {
startTop();
using namespace Profile;
AddCover(_top, _controller, _peer, nullptr, nullptr);
AddDetails(_top, _controller, _peer, nullptr, nullptr, { v::null });
auto tracker = Ui::MultiSlideTracker();
@@ -481,6 +481,46 @@ void InnerWidget::addGiftsButton(Ui::MultiSlideTracker &tracker) {
tracker.track(giftsWrap);
}
bool InnerWidget::hasFlexibleTopBar() const {
return (_controller->key().storiesAlbumId() != Stories::ArchiveId()
&& _controller->key().storiesPeer()
&& _controller->key().storiesPeer()->isSelf());
}
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToTop(
not_null<Ui::RpWidget*> parent) {
if (!hasFlexibleTopBar()) {
return nullptr;
}
const auto content = Ui::CreateChild<Profile::TopBar>(
parent,
Profile::TopBar::Descriptor{
.controller = _controller->parentController(),
.key = _controller->key(),
.wrap = _controller->wrapValue(),
.source = Profile::TopBar::Source::Stories,
.peer = _peer,
.backToggles = _backToggles.value(),
.showFinished = _showFinished.events(),
});
_topBarColor = content->edgeColor();
return base::make_weak(not_null<Ui::RpWidget*>{ content });
}
base::weak_qptr<Ui::RpWidget> InnerWidget::createPinnedToBottom(
not_null<Ui::RpWidget*> parent) {
return nullptr;
}
void InnerWidget::enableBackButton() {
_backToggles.force_assign(true);
}
void InnerWidget::showFinished() {
_showFinished.fire({});
}
void InnerWidget::finalizeTop() {
const auto addPossibleAlbums = !_addingToAlbumId
&& (_albumId.current() != Data::kStoriesAlbumIdArchive);

View File

@@ -86,6 +86,15 @@ public:
[[nodiscard]] rpl::producer<int> albumIdChanges() const;
[[nodiscard]] rpl::producer<Data::StoryAlbumUpdate> changes() const;
bool hasFlexibleTopBar() const;
base::weak_qptr<Ui::RpWidget> createPinnedToTop(
not_null<Ui::RpWidget*> parent);
base::weak_qptr<Ui::RpWidget> createPinnedToBottom(
not_null<Ui::RpWidget*> parent);
void enableBackButton();
void showFinished();
protected:
int resizeGetHeight(int newWidth) override;
void visibleTopBottomUpdated(
@@ -152,6 +161,10 @@ private:
rpl::variable<int> _topHeight;
rpl::variable<bool> _albumEmpty;
rpl::variable<bool> _backToggles;
rpl::event_stream<> _showFinished;
rpl::variable<std::optional<QColor>> _topBarColor;
};
} // namespace Info::Stories

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