Compare commits

...

323 Commits

Author SHA1 Message Date
John Preston
4505a2bf2d Beta version 5.9.2.
- Fix some jump-to-message highlightings.
- Fix some long round video messages sending.
- Fix possible crash in global photos / videos.
- Fix streaming starting for some large video files.
- Fix possible drop of top peers / recent chats cache.
- Fix possible crashes in audio on macOS (rollback OpenAL).
2024-12-24 10:44:15 +04:00
John Preston
a314380b08 Rollback OpenAL on macOS. 2024-12-23 22:51:33 +04:00
John Preston
0c07a015c6 Always show VIEW DISCUSSION from comments. 2024-12-23 22:46:43 +04:00
John Preston
a0c7697280 Fix affiliate program ending confirmation box.
Fixes #28780.
2024-12-23 22:46:42 +04:00
John Preston
04023da723 Highlight search query part in separate window chat. 2024-12-23 21:29:40 +04:00
John Preston
13ea045055 Fix collision of dice_sticker last frames. 2024-12-23 21:29:40 +04:00
John Preston
f03351d112 Allow filter gifts by "In Stock". 2024-12-23 21:29:40 +04:00
John Preston
48d9f10f5b Improve message part highlighting. 2024-12-23 21:29:39 +04:00
John Preston
2b53df98cd Fix long message parts highlighting in topics. 2024-12-23 21:29:34 +04:00
John Preston
99a7a13218 Always send video messages as not "big" files. 2024-12-23 21:27:12 +04:00
John Preston
2d2d4ac002 Reduce video message bitrate so they always fit. 2024-12-23 21:27:12 +04:00
John Preston
d12e8023e3 Fix scheduled replies loading. 2024-12-23 21:27:12 +04:00
John Preston
17181cee8f Fix possible crash in Forward folders switch. 2024-12-23 21:27:12 +04:00
23rd
a74ee911b3 Removed chats filters strip from forward box while search. 2024-12-23 11:02:09 +04:00
Ilya Fedin
b0b37172ce Fix the naming of TitleControlsLayout static methods 2024-12-23 11:01:35 +04:00
Pavel Zolotarevskiy
cccf048e3f Don't export a duplicate "text" field on star gift
Fixes #28781
2024-12-21 22:32:12 +04:00
John Preston
82e890746b Fix possible crash in global media search. 2024-12-20 21:07:20 +04:00
John Preston
188d65d700 Improve streaming of large files. 2024-12-20 21:07:20 +04:00
John Preston
4569f93e70 Fix username disappearance in My Profile. 2024-12-20 21:07:20 +04:00
23rd
bcd1d8461f Added ability to open context menu for active account from main menu. 2024-12-20 19:33:06 +03:00
23rd
183a9139f9 Moved out ability to mark as read all chats from hidden shortcut. 2024-12-20 19:33:06 +03:00
23rd
80a1e6ecf3 Fixed ability to mark as read all chats for wrong account. 2024-12-20 19:33:06 +03:00
23rd
aa1f8cfb8f Fixed display at least one injected sponsored message. 2024-12-20 15:53:43 +03:00
23rd
8060691f3d Fixed preview of chats filters in filter link box when window is small. 2024-12-19 15:54:26 +03:00
Ilya Fedin
bf26de495a Adapt to TitleControlsLayout change 2024-12-19 15:59:25 +04:00
Ilya Fedin
73b3f7e298 Adapt to TitleControlsOnLeft change 2024-12-19 15:59:25 +04:00
Ilya Fedin
22191649aa Update lib_base and lib_ui 2024-12-19 15:59:25 +04:00
bitxer
0557907310 Enhance experimental setting description 2024-12-19 15:49:24 +04:00
bitxer
0f283c484d Added experimental settings to prefer ipv6 when it is available 2024-12-19 15:49:24 +04:00
John Preston
e33ca9d316 Fix top/recent peers cache write error. 2024-12-19 13:51:47 +04:00
John Preston
f93f4c72f7 Beta version 5.9.1.
- Add global media overview tabs in chats search.
- Add main menu "My Profile" with my stories and gifts access.
- Highlight some of search query on result message open.
- Fix highlighting quotes from bottom parts of long messages.
- Allow forward and reply bars together.
- Make gift price categories scrollable.
- Auto-send deep-link /start in existing bot chats.
2024-12-18 19:12:12 +04:00
23rd
f0b9bc10c2 Added fade effect to price categories in star gift box. 2024-12-18 17:39:46 +03:00
John Preston
f583879aee Update OpenAL to 1.24.1 on Windows/macOS. 2024-12-18 18:22:54 +04:00
23rd
7ea6c6c84b Fixed width of username label with button for QR in profiles. 2024-12-18 16:21:07 +03:00
23rd
2532a0ff59 Moved out to single place ministars creation in top of box. 2024-12-18 15:47:57 +03:00
23rd
c3f354826d Fixed text color of custom icon in some phrases with links. 2024-12-18 15:01:57 +03:00
23rd
6d1e421ad7 Fixed color of strikeout format when spellcheck underline is present. 2024-12-18 11:54:26 +03:00
23rd
29b0055e39 Slightly improved some phrases on error while add participant to chat. 2024-12-18 11:03:44 +03:00
Ilya Fedin
2cb20fe342 Switch macOS packaged action to latest ffmpeg 2024-12-18 10:53:43 +04:00
Ilya Fedin
fc97fa4415 Fix snap action 2024-12-18 09:56:23 +04:00
Ilya Fedin
876a50f759 Update OpenAL to 1.24.1 on Linux 2024-12-18 09:55:58 +04:00
Ilya Fedin
eb0d2868f5 Expand "always run in background" behavior from GNOME/Pantheon to all Linux
Right now it checks the title controls layout that is typically set only by gtk based DEs and KDE matching the GNOME's and Pantheon's defaults.

There are more and more reports about window manager not to supporting both tray and minimization out of the box and title controls layout seem to typically be either unset or set to nothing leaving users with no way to run tdesktop with no window open.
2024-12-18 09:55:35 +04:00
John Preston
e215d5bc64 Fix build with Xcode. 2024-12-17 21:27:08 +04:00
John Preston
3f0d687656 Add cache for global media search requests. 2024-12-17 21:17:14 +04:00
John Preston
d59eb8e731 Support global media in chats search. 2024-12-17 21:17:14 +04:00
John Preston
04e9eed88d Reuse filters slider scroll for search. 2024-12-17 21:17:14 +04:00
John Preston
5072e95f16 Support folders strip touch-screen scroll. 2024-12-17 21:17:14 +04:00
John Preston
a2b8366477 Show forward original date in context menu. 2024-12-17 21:17:14 +04:00
John Preston
e9a6bee046 Fix sending old topic messages. 2024-12-17 21:13:29 +04:00
John Preston
080a8d7ee5 Fix "Open" miniapp button antialiasing. 2024-12-17 21:13:29 +04:00
John Preston
f94fd3118b Add "My Profile" instead of "My Stories". 2024-12-17 21:13:29 +04:00
John Preston
8ddb13d6e2 Show verified/premium badge in chat preview. 2024-12-17 21:13:29 +04:00
John Preston
c6cf8be8d4 Redesign gift visibility toggle. 2024-12-17 21:13:29 +04:00
John Preston
e92270a9ab Add "View Discussion" button to third column. 2024-12-17 21:13:29 +04:00
John Preston
65d6636a41 Add special toast title for anonymous stars. 2024-12-17 21:13:29 +04:00
John Preston
4701badb2a Highlight text in bottom of a long bubble. 2024-12-17 21:13:29 +04:00
John Preston
3565215c81 Highlight word from search query. 2024-12-17 21:13:29 +04:00
John Preston
3957fea5e4 Send start in bots auto in existing bot chats. 2024-12-17 21:13:29 +04:00
John Preston
10f1ae152d Fix crash in sending games from inline bots.
Regression was introduced in 2d1fb0562d.
2024-12-17 21:13:29 +04:00
John Preston
eb29b6bffe Allow forward+reply, options in single box. 2024-12-17 21:13:29 +04:00
John Preston
d157eb0b6e Enter selects from-row in reply-in-another-chat. 2024-12-17 21:13:29 +04:00
John Preston
0045eb4598 Make price categories scrollable. 2024-12-17 21:13:27 +04:00
23rd
b61c66c385 Fixed display of title widgets in separate panel while show animation. 2024-12-16 06:46:36 +03:00
23rd
56d6c4eb30 Fixed mouse track on right button for bots when there is unread badge. 2024-12-16 05:45:56 +03:00
23rd
a6030d708d Fixed display of unread state in chats filters strip after reorder. 2024-12-16 05:45:56 +03:00
23rd
683c3c4f36 Fixed Escape hotkey in info sections with search field. 2024-12-16 05:45:56 +03:00
23rd
bd084f9181 Fixed blink of submenu in profile section on section destroy. 2024-12-16 05:45:56 +03:00
23rd
fef133bf0a Fixed incorrect action type of userpic change with image from clipboard.
Fixed #28731.
2024-12-16 05:45:56 +03:00
23rd
15c226e6cf Removed confusing lock state from button in earn out section. 2024-12-16 05:45:56 +03:00
23rd
84f111d641 Fixed unreachable bottom button from contact media in some cases. 2024-12-16 05:45:55 +03:00
23rd
18c1e7ac60 Removed animation cache from dialogs widget on instant clear search. 2024-12-16 05:45:55 +03:00
Daniel Novomeský
ee6dbdced6 Update libjxl and libheif on Linux 2024-12-14 17:50:51 +04:00
Daniel Novomeský
cb443d797d Update kimageformats submodule 2024-12-14 17:50:51 +04:00
Daniel Novomeský
5a6497ec70 Upgrade libheif to 1.18.2, upgrade libjxl to 0.11.1 2024-12-14 17:50:51 +04:00
Andrey Egorov
893ca8bcbd Residence country instead of Citizenship 2024-12-06 18:29:17 +04:00
John Preston
f91e4c8b69 Version 5.9.
- Affiliate programs for bots.
- Add option to show folder tags in chats list.
2024-12-04 19:03:17 +04:00
Ilya Fedin
f7c777d07d Add branding colors to metainfo 2024-12-04 19:03:11 +04:00
John Preston
12a8e8616c Fix giveaway sticker badge. 2024-12-04 13:51:20 +04:00
John Preston
2fbf7e8504 Fix build with Xcode. 2024-12-04 13:51:20 +04:00
John Preston
6864e6d5bf Improve phrase for revoked starref link. 2024-12-04 13:51:20 +04:00
John Preston
09b4e0e21b Use nice format for numbers. 2024-12-04 13:51:20 +04:00
John Preston
f381005184 Add "New" badge for affiliate programs. 2024-12-04 13:51:20 +04:00
John Preston
42a2de4bf0 Add a NEW badge to "Earn Stars". 2024-12-04 13:51:20 +04:00
John Preston
1fd1e34844 Add link icon to connected starref programs. 2024-12-04 13:51:20 +04:00
John Preston
64dbbd7d09 Green badges for commissions. 2024-12-04 13:51:20 +04:00
John Preston
c137e577dc Allow sorting suggested starref programs. 2024-12-04 13:51:20 +04:00
John Preston
f592a9202f Improve arrow down in choose recipient box. 2024-12-04 13:51:20 +04:00
John Preston
cdc24d2e57 Fix states in joined/suggested lists. 2024-12-04 13:51:20 +04:00
John Preston
b8bf3f6520 Allow changing the recipients. 2024-12-04 13:51:20 +04:00
John Preston
82cec83d87 "Add {bot}" button in existing starrefs list. 2024-12-04 13:51:20 +04:00
John Preston
1e14667006 Add affiliate program point to bot info. 2024-12-04 13:51:20 +04:00
John Preston
6539d14852 Create/Update confirm box for the starref. 2024-12-04 13:51:20 +04:00
John Preston
400df0f980 Add referral link preview to the box. 2024-12-04 13:51:19 +04:00
John Preston
4cafb3f966 Make nicer footer in starref boxes. 2024-12-04 13:51:19 +04:00
John Preston
d8892c4eb4 Nice referral link icon in the box. 2024-12-04 13:51:19 +04:00
John Preston
1ebd25e76e Start/Update/End toasts for starref programs. 2024-12-04 13:51:19 +04:00
John Preston
ca89aa8377 Confirm box for revoke link / end program. 2024-12-04 13:51:19 +04:00
John Preston
ad6272bfe5 Show correct error on stopped starref. 2024-12-04 13:51:19 +04:00
John Preston
b401c37c39 Correctly show commission in stars stats. 2024-12-04 13:51:19 +04:00
John Preston
552dd318cd Implement nice starref start button. 2024-12-04 13:51:19 +04:00
John Preston
a97880132a Respect appconfig starref restrictions. 2024-12-04 13:51:19 +04:00
John Preston
89058c63c8 Check appconfig start ref prefixes. 2024-12-04 13:51:19 +04:00
John Preston
747e417809 Apply connected programs in realtime. 2024-12-04 13:51:19 +04:00
John Preston
fd26e1618c Add "Affiliate programs" to Manage Channel. 2024-12-04 13:51:19 +04:00
John Preston
86ea760011 Show list of programs in View Existing. 2024-12-04 13:51:19 +04:00
John Preston
824237deb3 Nice starref link box. 2024-12-04 13:51:19 +04:00
John Preston
ca8c70cc95 Fix creating default starref program. 2024-12-04 13:51:19 +04:00
John Preston
46fcc695a5 Add starref entry point to my/bot stars page. 2024-12-04 13:51:19 +04:00
John Preston
0e866a0266 Nice starref join confirmation box. 2024-12-04 13:51:19 +04:00
John Preston
63c36f5907 Implement nice limited duration slider. 2024-12-04 13:51:19 +04:00
John Preston
5299500d78 Implement nice commission slider. 2024-12-04 13:51:19 +04:00
John Preston
3b3d1aa9cc Update some icons for starref sections. 2024-12-04 13:51:19 +04:00
John Preston
62d2346471 Initial starref programs list implementation. 2024-12-04 13:51:19 +04:00
John Preston
1e15764bb9 Initial starref setup section implementation. 2024-12-04 13:51:19 +04:00
John Preston
a6bfd35f1a Add phrases for star referrals. 2024-12-04 13:51:18 +04:00
John Preston
3296efe46b Use correct format for double formatting. 2024-12-04 13:51:18 +04:00
John Preston
51ddfbc340 Update API scheme to layer 195. 2024-12-04 13:51:18 +04:00
23rd
42142d819a Added support of media with spoiler to export to JSON. 2024-12-04 11:48:07 +03:00
23rd
bbf9d523a6 Fixed incorrect calculation of title width in sponsored messages. 2024-12-04 11:48:07 +03:00
23rd
721877e10a Removed redundant special tab mode from PeerListBox. 2024-12-04 11:48:07 +03:00
23rd
a9e95a128f Fixed display of filters tabs on some cases of first activating. 2024-12-04 11:48:07 +03:00
John Preston
2b920eaa87 Use new Vazirmatn repository URL.
Co-authored-by: ilya-fedin <fedin-ilja2010@ya.ru>
2024-12-04 10:21:37 +04:00
Amir Hossein "Amiria" Maher
a3ec759e62 Update from 'vazir' to 'vazirmatn' 2024-12-04 10:21:37 +04:00
Ilya Fedin
3661442acd Add more screenshots to metainfo 2024-12-04 10:14:58 +04:00
Ilya Fedin
f232d329c5 Add transparency to the preview image 2024-12-04 10:14:18 +04:00
Ilya Fedin
ac5cf3bd80 Update summary in metainfo and comment in desktop file 2024-12-04 10:11:39 +04:00
Ilya Fedin
ac13ac7a2c Remove "Desktop" from application name on Linux 2024-12-04 10:08:04 +04:00
John Preston
0bccb35cb0 Use "Open" non-upper-case. 2024-12-03 17:26:46 +04:00
23rd
18d9484ab1 Removed Ui::show from LocalStorageBox. 2024-12-03 17:26:46 +04:00
23rd
fe7c06bc84 Added Enter shortcut to box for adding or removing of shared filter. 2024-12-03 17:26:46 +04:00
23rd
b6fb3bbf1d Fixed build of shortcuts settings included with another Session declare. 2024-12-03 17:26:46 +04:00
23rd
927d7a3aeb Renamed sessions_box to settings_active_sessions. 2024-12-03 17:26:46 +04:00
23rd
979973745b Fixed build of notifications type included with another Session declare. 2024-12-03 17:26:46 +04:00
23rd
afab863f11 Fixed non-closed last tag for inline buttons in HTML export. 2024-12-03 17:26:46 +04:00
23rd
168162c174 Fixed color of float button from sponsored message bar in new window. 2024-12-03 17:26:46 +04:00
23rd
2b122087c4 Re-fixed focus capture from compose search widget. 2024-12-03 17:26:46 +04:00
23rd
043d97cfdf Moved out SearchFieldController to td_ui. 2024-12-03 17:26:46 +04:00
GitHub Action
794818953d Update User-Agent for DNS to Chrome 131.0.0.0. 2024-12-03 17:22:11 +04:00
Ilya Fedin
783570fe9f Update Qt 6.8.0 -> 6.8.1 2024-12-03 12:15:46 +04:00
John Preston
b1e2a4243e Fix join requests list for legacy groups. 2024-11-29 20:18:31 +04:00
John Preston
b347308137 Show bot app name in title. 2024-11-29 20:18:31 +04:00
John Preston
b3c8a79946 Use langpack-ed about for Verification Codes. 2024-11-29 20:18:31 +04:00
23rd
9822c56f1a Removed display of right button for bots when there is unread badge. 2024-11-29 16:03:42 +03:00
23rd
cdd7ff5c6d Fixed count of current size for non-thumbed media with long bottom info. 2024-11-29 15:56:31 +03:00
23rd
5aba2f25cc Fixed drawing of currency icon with non-default scale in profile. 2024-11-29 15:56:31 +03:00
John Preston
96398daa78 Beta version 5.8.5: Fix build with Xcode. 2024-11-29 11:25:29 +04:00
John Preston
61ceb66415 Beta version 5.8.5.
- Fix pinned chats in folders.
- Fix emoji in folder tags.
- Fix several crashes.
2024-11-29 10:52:14 +04:00
John Preston
b4f173cdb3 Fix possible crash in ads preloading. 2024-11-29 10:33:29 +04:00
John Preston
03e4592082 Fix search in group/channel requests list. 2024-11-29 10:33:29 +04:00
John Preston
cf2dbe50a1 Fix crash in narrow column reactions view. 2024-11-29 10:33:29 +04:00
John Preston
e5bb5b75fe Fix crash in webview teardown on Windows. 2024-11-29 10:33:29 +04:00
23rd
cffce47eb1 Fixed emoji in chats filter tags. 2024-11-29 08:31:49 +03:00
23rd
ca0adba6cf Fixed pinned chats in chats filters.
Regression was introduced in e3465da979.
2024-11-28 22:08:22 +03:00
John Preston
8502b90c25 Beta version 5.8.4.
- Add option to show folder tags in chats list.
- Count group with topics as one chat in folder unread counter.
2024-11-28 21:10:45 +04:00
John Preston
cef43e7f06 Fix build with Xcode. 2024-11-28 21:10:45 +04:00
John Preston
18aaf3cc93 Fix build for MSVC. 2024-11-28 20:33:13 +04:00
John Preston
f13740cb7f Update lib_webview. 2024-11-28 20:27:35 +04:00
Daniel Novomeský
bddac79b40 Update kimageformats submodule 2024-11-28 20:26:18 +04:00
John Preston
e52baf555f Update video qualities list. 2024-11-28 20:21:26 +04:00
John Preston
475dec3014 Allow adding media to text with a link preview. 2024-11-28 20:21:26 +04:00
23rd
f2ed649694 Fixed focus capture from compose search widget. 2024-11-28 14:37:31 +03:00
23rd
e82506e0c4 Added ministars to button in service messages for premium requirements. 2024-11-28 12:28:12 +03:00
23rd
3071daa6f3 Replaced header style in statistics sections with classic subsections. 2024-11-27 15:58:54 +03:00
23rd
5d71286000 Fixed display of title in sponsored messages with shorter description. 2024-11-27 15:01:46 +03:00
23rd
339d7be9c1 Removed unused include directives from Application. 2024-11-27 14:38:06 +03:00
23rd
7f85494b1d Moved out UnreadState to td_ui. 2024-11-27 14:27:31 +03:00
23rd
a405794a03 Allowed to display of hundreds digit in unread badge of filters. 2024-11-27 12:25:18 +03:00
23rd
4e8e096fdb Removed display of corner badges in narrowed mode when entry has unread. 2024-11-27 12:22:33 +03:00
23rd
eb821c1f36 Improved display of unread badges in dialogs list with narrowed mode. 2024-11-27 11:58:34 +03:00
23rd
bf07b832f0 Replaced header in credits settings for credits history with subtitle. 2024-11-27 11:27:59 +03:00
23rd
5934614edb Fixed counting of unread topics from unread state in filters. 2024-11-27 11:27:59 +03:00
23rd
96b5c1d3d3 Added icons and color indices to dialogs menu to choose chats filters. 2024-11-27 11:27:59 +03:00
23rd
cb2972b145 Moved out fixing of ampersand from text in actions to single place. 2024-11-27 11:27:59 +03:00
23rd
cd5a1980c9 Fixed display of button for earn out when withdrawal is locked. 2024-11-27 11:27:59 +03:00
23rd
0e35107e17 Added ability to create new filter with selected dialog from menu. 2024-11-27 11:27:59 +03:00
23rd
10b026dfe0 Added ability to open peer in new window from short info box. 2024-11-27 11:27:59 +03:00
23rd
743c3c54a7 Added message counter to each peer in moderate box. 2024-11-27 11:27:59 +03:00
23rd
489c86dad8 Added ability to disable debug logs even for debug builds. 2024-11-27 11:27:59 +03:00
23rd
a9824fde91 Added right button for bots in list from recent dialogs. 2024-11-27 11:27:59 +03:00
23rd
6c62bbe6fb Added right button for bots in dialogs list in filtered mode. 2024-11-27 11:27:59 +03:00
23rd
8a3aa660cb Added initial implementation of right button for bots in dialogs list. 2024-11-27 11:27:59 +03:00
23rd
14cc7789d9 Allowed to choose filters from any profile of peers in chats list. 2024-11-27 11:27:59 +03:00
23rd
728d9a0993 Added ability to remove peer from chats filters when leave it. 2024-11-27 11:27:59 +03:00
23rd
aa8d543ed8 Fixed ability to remove peer from chats filter to make it empty. 2024-11-27 11:27:59 +03:00
23rd
b335981621 Removed duplicated search of correspond chat filter for tags in dialogs. 2024-11-27 11:27:59 +03:00
23rd
9d5ca1252a Added ability to pass preloaded chats filter tags toggle on logging in. 2024-11-27 11:27:59 +03:00
23rd
d5dbbd566f Fixed display tags with NoRead flag while history has fake unread state. 2024-11-27 11:27:59 +03:00
23rd
5362d54ab6 Fixed display of chats filter tag for active dialog rows. 2024-11-27 11:27:59 +03:00
23rd
1f162aa2a0 Fixed tags display when color of chats filter tag is toggled. 2024-11-27 11:27:59 +03:00
23rd
4a19f193ce Added float preview of color for chats filter to settings. 2024-11-27 11:27:59 +03:00
23rd
cfc40ee966 Added ability to set color for chats filter from settings. 2024-11-27 11:27:59 +03:00
23rd
6baba5a7b2 Added ability to disable chats filters tags from settings. 2024-11-27 11:27:59 +03:00
23rd
ba082081b3 Added api support for enabled tags of chats filters. 2024-11-27 11:27:59 +03:00
23rd
e314c68a56 Optimized height refresh of chats list for chat filters with rules. 2024-11-27 11:27:59 +03:00
23rd
889fcb3939 Optimized height refresh of non-main list on receiving chats filters. 2024-11-27 11:27:59 +03:00
23rd
632abd2225 Optimized height refresh of main list on receiving chats filters. 2024-11-27 11:27:59 +03:00
23rd
e3465da979 Switched order of data filter applying of changes from api. 2024-11-27 11:27:59 +03:00
23rd
f4523b2dba Added initial implementation of filter tags to dialog rows. 2024-11-27 11:27:59 +03:00
23rd
0d58b32914 Added ability to recount height of dialog rows by chats filter id. 2024-11-27 11:27:59 +03:00
23rd
aea2d34080 Added styles for dialog rows with tags. 2024-11-26 08:38:40 +03:00
23rd
d5dbde0a24 Added ability to cache filter colors in dialog entries. 2024-11-26 08:38:40 +03:00
23rd
f888008dc1 Removed counting of unread topics from unread state in filters. 2024-11-25 17:45:56 +03:00
23rd
021a5881c2 Improved display of history begin for channels with enabled auto-delete. 2024-11-23 14:10:04 +03:00
23rd
3e49b45418 Added chats filters list to each exception row in edit chats filter box. 2024-11-23 10:28:04 +03:00
23rd
23e22650f9 Added ability to choose filters for channel from its profile. 2024-11-23 10:28:04 +03:00
John Preston
85267a921e Version 5.8.3.
- Ctrl+Click on Reply in groups to reply in another chat.
- Fix freeze on forward messages box opening.
- Improve phrases in bot subscriptions.
- Several bugfixes.
2024-11-23 11:11:39 +04:00
John Preston
2c7089d47f Ctrl+Reply+Click replies in another chat. 2024-11-23 10:47:36 +04:00
John Preston
edc6cfe210 Replace non-started calls. 2024-11-23 10:04:55 +04:00
John Preston
03b6e2df17 Don't confirm each bot-url webapp open. 2024-11-23 09:37:26 +04:00
John Preston
5309138980 Support display of messageActionPaymentSentMe. 2024-11-23 09:36:09 +04:00
John Preston
a5e927ea4f Fix build for Windows. 2024-11-23 08:59:53 +04:00
23rd
ec83f4ae72 Removed unnecessary rebuild of chats filters strip when order is same. 2024-11-23 06:01:23 +03:00
23rd
71efd95136 Slightly shifted selection marks along x. 2024-11-23 05:28:36 +03:00
23rd
0c21eba1f8 Fixed display of preview for sticker packs. 2024-11-23 05:11:13 +03:00
23rd
2ae4e15f87 Fixed ability to copy transactions id from credits history entries.
Regression was introduced in 77e7796b3f.
2024-11-21 16:45:00 +04:00
23rd
d69905feae Added ability to subscribe again via slug from subscriptions list. 2024-11-21 16:45:00 +04:00
23rd
f795d56b2a Handled subscriptions list changes in settings section for credits. 2024-11-21 16:45:00 +04:00
23rd
4608ffcab4 Added weak pointer for rebuilder of subscription list to Data::Session. 2024-11-21 16:45:00 +04:00
23rd
9824df5f2a Added phrases for business subscriptions. 2024-11-21 16:45:00 +04:00
23rd
27a5ba4681 Added top close button to ReceiptCreditsBox. 2024-11-21 16:45:00 +04:00
23rd
73936dca73 Added mini stars to subscriptions with details in ReceiptCreditsBox. 2024-11-21 16:45:00 +04:00
23rd
213274e96c Replaced style of button to cancel subscription with hyper link. 2024-11-21 16:45:00 +04:00
23rd
7518361266 Improved style of ReceiptCreditsBox for subscription with detailed info. 2024-11-21 16:45:00 +04:00
23rd
f7aaece2f7 Extended width of ReceiptCreditsBox. 2024-11-21 16:45:00 +04:00
23rd
fbce06cb26 Added support of subscription status when it is cancelled by bot. 2024-11-21 16:45:00 +04:00
23rd
ccc0bf57a1 Added support of items with different heights in list of subscriptions. 2024-11-21 16:45:00 +04:00
23rd
ee29deee47 Improved display of detailed subscriptions in list of credits history. 2024-11-21 16:45:00 +04:00
23rd
17e54104a9 Added api support of subscription details. 2024-11-21 16:45:00 +04:00
23rd
479e369d29 Replaced list of credits subscriptions with peer list with widgets. 2024-11-21 16:45:00 +04:00
23rd
3042fb7299 Added initial very limited implementation of peer list with widgets. 2024-11-21 16:45:00 +04:00
23rd
36fa455aad Added third line to entries of list for credits history. 2024-11-21 16:45:00 +04:00
23rd
1c64e90537 Split creation of button for pagination in statistics lists. 2024-11-21 16:45:00 +04:00
23rd
09643aef82 Added support of credits subscriptions to send credits box. 2024-11-21 16:45:00 +04:00
23rd
03d9fb4115 Added api support of subscription period in credits invoices. 2024-11-21 16:45:00 +04:00
23rd
81bea04db0 Moved out peer bubble widget to separated file. 2024-11-21 16:45:00 +04:00
John Preston
9211e338e7 Fix tooltip render glitches on macOS. 2024-11-21 16:32:32 +04:00
John Preston
23464ac55f Don't repeat premium effect on chat reopen. 2024-11-21 13:13:06 +04:00
John Preston
af78e4ea29 Show scheduled limit error. 2024-11-21 13:10:59 +04:00
John Preston
e3d9216b10 Fix switch_inline_query with same_peer in topics.
Fixes #27290.
2024-11-21 11:41:46 +04:00
John Preston
9532a2e3da Fix emoji status set cancel.
Fixes #28674.
Fixes https://bugs.telegram.org/c/45691.
2024-11-21 10:01:31 +04:00
23rd
e70f50d837 Changed engine of chats filters in forward box with restoring state. 2024-11-20 16:37:49 +03:00
23rd
2dfa58aae2 Moved out some constant color indices to separated style file. 2024-11-20 16:37:49 +03:00
John Preston
c04f68f25c Version 5.8.2.
- Improve bottom label color in mini apps.
- Fix some fullscreen issues in mini apps.
- Fix miss-order of archived chats in forward box.
- Fix file dialog for Windows on ARM.
- Fix emoji status pack view from profile regression.
2024-11-19 14:32:15 +04:00
John Preston
e4902efefc Improve fullscreen miniapps on Linux. 2024-11-19 14:24:25 +04:00
John Preston
1267bcd255 Use separate window buttons only with QWindowContainer. 2024-11-19 13:16:54 +04:00
John Preston
0659ccc3f0 Fix user-driven fullscreen change on macOS. 2024-11-19 12:25:27 +04:00
23rd
bd2ae03ab4 Fixed display of long text for open button in web page views. 2024-11-19 10:39:40 +04:00
23rd
244696ae24 Fixed miss-order of archived dialogs in forward box. 2024-11-19 10:39:39 +04:00
John Preston
1438046dd4 Improve bottom label color in mini apps. 2024-11-19 10:37:47 +04:00
John Preston
5c62ba0835 Don't reopen forum on opening a link. 2024-11-18 16:37:22 +04:00
John Preston
20fadfef7f Fix file dialog for Windows on ARM. 2024-11-18 16:01:15 +04:00
John Preston
eed9541f9f Fix Withdraw button text. 2024-11-18 14:25:24 +04:00
John Preston
1594afa389 Fix emoji status pack view regression. 2024-11-18 14:17:01 +04:00
John Preston
9d74d93ed7 Fix some warnings from PVS-Studio.
Fixes #28667.

Some warnings fixed detailed in this post:
https://pvs-studio.com/en/blog/posts/cpp/1186/
2024-11-18 12:50:36 +04:00
John Preston
e4e2f47f8e Adjust build script for Windows Store ARM version. 2024-11-18 01:28:36 +04:00
John Preston
be53bec9b7 Version 5.8.1.
- Fix several possible crashes.
2024-11-17 23:33:45 +04:00
John Preston
bb32c546d4 Fix possible crash in swipe to reply. 2024-11-17 23:24:08 +04:00
John Preston
ecb4ceec7b Fix possible crash in phone click handler. 2024-11-17 23:21:16 +04:00
John Preston
c080bd4c4d Don't resubscribe on folder strip rebuild. 2024-11-17 23:09:42 +04:00
John Preston
39780f49bf Fix folder context menu Remove text color. 2024-11-17 23:00:46 +04:00
John Preston
73349c3c89 Fix crash in chats strip reorder. 2024-11-17 22:52:52 +04:00
John Preston
42a70ff7d0 Fix crash with inline results sending. 2024-11-17 22:41:52 +04:00
John Preston
fa8262cbe9 Version 5.8.
- Updates in Mini Apps platform.
- Chat folders in forward and share boxes.
- Horizontal strip mode for chat folders.
2024-11-17 16:05:22 +04:00
John Preston
7552328cdd Multi-select in mini app send prepared. 2024-11-17 15:08:17 +04:00
John Preston
60f4587d95 Sort birthday gifts to the front. 2024-11-17 15:08:17 +04:00
John Preston
572c074c42 Add gifts-to-profile privacy. 2024-11-17 15:08:17 +04:00
John Preston
3cfbd6a93b Support non-convertible star gifts from bots. 2024-11-17 15:08:16 +04:00
John Preston
d0911b6a45 Send to buy premium from miniapps emoji status. 2024-11-17 15:08:16 +04:00
John Preston
06b85442f8 Fix miniapp downloads in fullscreen. 2024-11-17 15:08:16 +04:00
John Preston
762592daff Allow RTMP for manage call admins. 2024-11-17 15:08:16 +04:00
John Preston
338122793c Implement bot downloads list UI. 2024-11-17 15:08:16 +04:00
John Preston
ef521624a0 Check file by server before downloading. 2024-11-17 15:08:16 +04:00
John Preston
341ab781b2 Implement download of files in miniapps. 2024-11-17 15:08:16 +04:00
John Preston
2fed657940 Make nice share message from miniapp. 2024-11-17 15:08:16 +04:00
John Preston
7bf78b3317 Use initial bot web app header/body colors. 2024-11-17 15:08:16 +04:00
John Preston
2d1fb0562d Prepared messages sharing from miniapp. 2024-11-17 15:08:16 +04:00
John Preston
3d77bff0c9 Support bot emoji status access. 2024-11-17 15:08:15 +04:00
John Preston
4198203a7f Implement emoji status set from miniapps. 2024-11-17 15:08:15 +04:00
John Preston
21487641c1 Support miniapp fullscreen API. 2024-11-17 15:08:15 +04:00
John Preston
c987872be8 Start fullscreen support in SeparatePanel. 2024-11-17 15:08:15 +04:00
John Preston
07e367e1a0 Update API scheme to layer 193. 2024-11-17 15:08:15 +04:00
23rd
db2e45c56e Added ability to go to top of chats list by clicking on selected filter. 2024-11-17 13:43:19 +03:00
23rd
67bbdbfc70 Added ability to change userpic with image from clipboard. 2024-11-17 13:27:57 +03:00
23rd
83df3cba66 Added debug logs for changing chats filters from context menu. 2024-11-17 13:27:57 +03:00
John Preston
f4e2b4bcbd Don't drop the old state of "systemDarkModeEnabled". 2024-11-17 14:07:22 +04:00
Ilya Fedin
6c64c22f83 Don't expand minimum window size for folders
Turn them into horizontal automatically instead
2024-11-17 14:04:27 +04:00
John Preston
702aa944dd Move read/reacted list to a layer. 2024-11-17 14:03:29 +04:00
John Preston
df45edd816 Move channel requests to a layer. 2024-11-17 14:03:29 +04:00
23rd
3f3143514e Added attention style to menu item to delete chats filter. 2024-11-17 11:20:02 +03:00
23rd
3e89910749 Replaced user info with short box for admins in section of kicked users. 2024-11-17 10:46:12 +03:00
23rd
721a642a2f Added date to info who promoted or restricted participants in menu. 2024-11-17 10:40:35 +03:00
23rd
d16ccc9dc5 Fixed display of chats filters strip on removing last chats filter. 2024-11-17 01:52:12 +03:00
23rd
77e7796b3f Fit transactions id from credits history entries to table cell. 2024-11-16 14:43:34 +03:00
23rd
983c949e8c Added ability to provide preloaded credits topup options to list. 2024-11-16 11:39:47 +03:00
23rd
e3f4f60e2d Replaced list of credits topup options with big label in settings. 2024-11-16 11:39:47 +03:00
23rd
33aa904cb7 Added shortcuts to flip photos in media viewer. 2024-11-16 11:39:40 +03:00
23rd
0248be5543 Removed from display extended credits topup options by default. 2024-11-16 07:34:08 +03:00
23rd
f7ca8212aa Moved out down arrow icon of "Show More" button to single place. 2024-11-16 07:34:02 +03:00
23rd
5eb59a1a43 Improved style of tabs for boost gifts in boosts section. 2024-11-16 06:27:07 +03:00
John Preston
7dd1e9bfbe Beta version 5.7.4: Revert accident submodules. 2024-11-16 00:04:20 +04:00
John Preston
067fd25a34 Beta version 5.7.4.
- Enable auto night mode by default.
- Use muted chats folder badge in horizontal strip.
- Fix custom userpic context menu in comments and topics.
- Fix scrolling of chats folders together with the list.
- Fix chats folders list disappearing in some cases.
- Fix topic groups in share box with chats folders.
- Fix bot commands list in some groups.
- Fix crash in channel short info box display.
- Fix crash in bot monetization section opening.
- Fix crash in bot sponsored messages destruction.
2024-11-15 23:59:52 +04:00
John Preston
7cb26ba104 Fix crash in possible ads teardown. 2024-11-15 23:53:59 +04:00
John Preston
a4212cc865 Fix point state detection on media messages. 2024-11-15 23:53:59 +04:00
John Preston
0445f7d6e8 Enable auto-night-mode by default. 2024-11-15 23:53:59 +04:00
John Preston
efe99b3f62 Fix special userpic menu in topics. 2024-11-15 23:53:59 +04:00
23rd
7f2c98f17a Fixed mouse events in chats filters strip with inner padding. 2024-11-15 22:47:39 +03:00
23rd
8f1d215851 Fixed rebuild of chats filters in strip when premium status is changed. 2024-11-15 22:47:39 +03:00
23rd
013b58f6f6 Fixed display of forums in share box with applied chats filter. 2024-11-15 20:04:42 +03:00
23rd
9d3b3476c2 Fixed display of chats filters strip in new windows. 2024-11-15 11:00:35 +03:00
23rd
715874a98f Slightly improved phrase for no results in forward box. 2024-11-15 11:00:30 +03:00
23rd
d2109dd2cb Replaced outer padding for chats filters strip with inner padding. 2024-11-15 11:00:26 +03:00
23rd
ddaf11ed6a Improved style of sponsored messages with media with max width. 2024-11-15 11:00:14 +03:00
23rd
2b5f68003d Fixed pagination of subscriptions in list. 2024-11-15 11:00:07 +03:00
23rd
1a759cc4e7 Fixed crash from short info box for non-users. 2024-11-15 10:59:40 +03:00
23rd
9e83562bf4 Fixed display of pinned chats from main list in forward box. 2024-11-15 10:57:08 +03:00
23rd
c03c19d26f Slightly improved style of chats lists strip in boxes. 2024-11-15 10:57:08 +03:00
23rd
ad9ebdf8e6 Fixed propagation scroll from chats filters strip to chats list. 2024-11-15 10:57:08 +03:00
23rd
9c1701c62a Added support of muted chats filters to chats filters strip. 2024-11-15 10:57:08 +03:00
23rd
edf6c42e9d Added short variant of unfiltered chats list to chats filters strip. 2024-11-15 10:57:08 +03:00
23rd
f0f2a71a87 Fixed disappearing of chats filters strip in forward box after search. 2024-11-15 10:57:08 +03:00
23rd
cf270bd9ce Removed chats filters strip from forum layout. 2024-11-15 10:57:08 +03:00
23rd
49223a4688 Fixed possible crash for bot owners in earn section for bots. 2024-11-15 10:57:08 +03:00
ziplantil
074bb1e66e Fix bug in ChatBotCommands::update when commands empty (#28631) 2024-11-14 11:35:57 +04:00
434 changed files with 17765 additions and 3228 deletions

View File

@@ -69,7 +69,7 @@ jobs:
run: |
brew update
brew upgrade || true
brew install ada-url autoconf automake boost cmake ffmpeg@6 libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
brew install ada-url autoconf automake boost cmake ffmpeg libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcodebuild -version > CACHE_KEY.txt

View File

@@ -57,14 +57,14 @@ jobs:
sudo iptables -P FORWARD ACCEPT
sudo snap install --classic snapcraft
sudo usermod -aG lxd $USER
sudo snap run lxd init --auto
sudo snap run lxd waitready
sudo lxd init --auto
sudo lxd waitready
- name: Free up some disk space.
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
- name: Telegram Desktop snap build.
run: sg lxd -c 'snap run snapcraft --verbosity=debug'
run: sudo -u $USER snap run snapcraft --verbosity=debug
- name: Move artifact.
if: env.UPLOAD_ARTIFACT == 'true'

View File

@@ -58,7 +58,7 @@ Version **1.8.15** was the last that supports older systems
* Guideline Support Library ([MIT License](https://github.com/Microsoft/GSL/blob/master/LICENSE))
* Range-v3 ([Boost License](https://github.com/ericniebler/range-v3/blob/master/LICENSE.txt))
* Open Sans font ([Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html))
* Vazir font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazir-font/blob/master/OFL.txt))
* Vazirmatn font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazirmatn/blob/master/OFL.txt))
* Emoji alpha codes ([MIT License](https://github.com/emojione/emojione/blob/master/extras/alpha-codes/LICENSE.md))
* xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE))
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))

View File

@@ -294,6 +294,8 @@ PRIVATE
boxes/peer_list_box.h
boxes/peer_list_controllers.cpp
boxes/peer_list_controllers.h
boxes/peer_list_widgets.cpp
boxes/peer_list_widgets.h
boxes/peer_lists_box.cpp
boxes/peer_lists_box.h
boxes/passcode_box.cpp
@@ -320,8 +322,6 @@ PRIVATE
boxes/send_gif_with_caption_box.h
boxes/send_files_box.cpp
boxes/send_files_box.h
boxes/sessions_box.cpp
boxes/sessions_box.h
boxes/share_box.cpp
boxes/share_box.h
boxes/star_gift_box.cpp
@@ -468,6 +468,7 @@ PRIVATE
core/sandbox.h
core/shortcuts.cpp
core/shortcuts.h
core/stars_amount.h
core/ui_integration.cpp
core/ui_integration.h
core/update_checker.cpp
@@ -917,6 +918,12 @@ PRIVATE
info/bot/earn/info_bot_earn_list.h
info/bot/earn/info_bot_earn_widget.cpp
info/bot/earn/info_bot_earn_widget.h
info/bot/starref/info_bot_starref_common.cpp
info/bot/starref/info_bot_starref_common.h
info/bot/starref/info_bot_starref_join_widget.cpp
info/bot/starref/info_bot_starref_join_widget.h
info/bot/starref/info_bot_starref_setup_widget.cpp
info/bot/starref/info_bot_starref_setup_widget.h
info/channel_statistics/boosts/create_giveaway_box.cpp
info/channel_statistics/boosts/create_giveaway_box.h
info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp
@@ -939,6 +946,12 @@ PRIVATE
info/downloads/info_downloads_provider.h
info/downloads/info_downloads_widget.cpp
info/downloads/info_downloads_widget.h
info/global_media/info_global_media_widget.cpp
info/global_media/info_global_media_widget.h
info/global_media/info_global_media_inner_widget.cpp
info/global_media/info_global_media_inner_widget.h
info/global_media/info_global_media_provider.cpp
info/global_media/info_global_media_provider.h
info/media/info_media_buttons.h
info/media/info_media_common.cpp
info/media/info_media_common.h
@@ -986,6 +999,10 @@ PRIVATE
info/profile/info_profile_values.h
info/profile/info_profile_widget.cpp
info/profile/info_profile_widget.h
info/reactions_list/info_reactions_list_widget.cpp
info/reactions_list/info_reactions_list_widget.h
info/requests_list/info_requests_list_widget.cpp
info/requests_list/info_requests_list_widget.h
info/saved/info_saved_sublists_widget.cpp
info/saved/info_saved_sublists_widget.h
info/settings/info_settings_widget.cpp
@@ -1036,6 +1053,10 @@ PRIVATE
info/info_wrap_widget.h
inline_bots/bot_attach_web_view.cpp
inline_bots/bot_attach_web_view.h
inline_bots/inline_bot_confirm_prepared.cpp
inline_bots/inline_bot_confirm_prepared.h
inline_bots/inline_bot_downloads.cpp
inline_bots/inline_bot_downloads.h
inline_bots/inline_bot_layout_internal.cpp
inline_bots/inline_bot_layout_internal.h
inline_bots/inline_bot_layout_item.cpp
@@ -1395,6 +1416,8 @@ PRIVATE
settings/cloud_password/settings_cloud_password_manage.h
settings/cloud_password/settings_cloud_password_start.cpp
settings/cloud_password/settings_cloud_password_start.h
settings/settings_active_sessions.cpp
settings/settings_active_sessions.h
settings/settings_advanced.cpp
settings/settings_advanced.h
settings/settings_blocked_peers.cpp
@@ -1543,6 +1566,8 @@ PRIVATE
ui/widgets/label_with_custom_emoji.h
ui/widgets/chat_filters_tabs_strip.cpp
ui/widgets/chat_filters_tabs_strip.h
ui/widgets/peer_bubble.cpp
ui/widgets/peer_bubble.h
ui/countryinput.cpp
ui/countryinput.h
ui/dynamic_thumbnails.cpp
@@ -1554,8 +1579,6 @@ PRIVATE
ui/item_text_options.cpp
ui/item_text_options.h
ui/resize_area.h
ui/search_field_controller.cpp
ui/search_field_controller.h
ui/unread_badge.cpp
ui/unread_badge.h
window/main_window.cpp

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_activate" = "Use this account";
"lng_menu_set_status" = "Set Emoji Status";
"lng_menu_change_status" = "Change Emoji Status";
"lng_menu_my_profile" = "My Profile";
"lng_menu_my_stories" = "My Stories";
"lng_menu_my_groups" = "My Groups";
"lng_menu_my_channels" = "My Channels";
@@ -288,8 +289,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_cant_add_member" = "Sorry, you can't add the bot to this group. Ask a group admin to do it.";
"lng_error_cant_add_bot" = "Sorry, this bot can't be added to groups.";
"lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
"lng_error_you_blocked_user" = "Sorry, you can't add this user or bot to groups because you've blocked them. Please unblock to proceed.";
"lng_error_add_admin_not_member" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
"lng_error_user_admin_invalid" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_channel_bots_too_much" = "Sorry, this channel has too many bots.";
"lng_error_group_bots_too_much" = "There are too many bots in this group. Please remove some of the bots you're not using first.";
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them.";
"lng_error_cant_ban_admin" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_cant_reply_other" = "This message can't be replied in another chat.";
"lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group.";
"lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel.";
"lng_error_post_link_invalid" = "Unfortunately, you can't access this message. You aren't a member of the chat where it was posted.";
@@ -298,6 +305,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins.";
"lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins.";
"lng_error_nocopy_story" = "Sorry, the creator of this story disabled copying.";
"lng_error_schedule_limit" = "Sorry, you can't schedule more than 100 messages.";
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
"lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?";
"lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?";
@@ -682,6 +690,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_messages_privacy" = "Messages";
"lng_settings_voices_privacy" = "Voice messages";
"lng_settings_bio_privacy" = "Bio";
"lng_settings_gifts_privacy" = "Gifts";
"lng_settings_birthday_privacy" = "Date of Birth";
"lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
"lng_settings_privacy_premium_link" = "Telegram Premium";
@@ -1162,19 +1171,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_blocked_list_subtitle#other" = "{count} blocked users";
"lng_edit_privacy_everyone" = "Everybody";
"lng_edit_privacy_no_miniapps" = "Not Mini Apps";
"lng_edit_privacy_contacts" = "My contacts";
"lng_edit_privacy_close_friends" = "Close friends";
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
"lng_edit_privacy_nobody" = "Nobody";
"lng_edit_privacy_premium" = "Premium users";
"lng_edit_privacy_miniapps" = "Mini Apps";
"lng_edit_privacy_exceptions" = "Add exceptions";
"lng_edit_privacy_user_types" = "User types";
"lng_edit_privacy_users_and_groups" = "Users and groups";
"lng_edit_privacy_premium_status" = "all Telegram Premium subscribers";
"lng_edit_privacy_miniapps_status" = "web mini apps that you use";
"lng_edit_privacy_exceptions_count#one" = "{count} user";
"lng_edit_privacy_exceptions_count#other" = "{count} users";
"lng_edit_privacy_exceptions_premium_and" = "Premium & {users}";
"lng_edit_privacy_exceptions_miniapps_and" = "Mini Apps & {users}";
"lng_edit_privacy_exceptions_add" = "Add users";
"lng_edit_privacy_phone_number_title" = "Phone number privacy";
@@ -1228,6 +1242,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_privacy_birthday_yet" = "You haven't entered your date of birth yet.\n{link}";
"lng_edit_privacy_birthday_yet_link" = "Add my birthday >";
"lng_edit_privacy_gifts_title" = "Gifts";
"lng_edit_privacy_gifts_header" = "Who can display gifts on my profile";
"lng_edit_privacy_gifts_always_empty" = "Always allow";
"lng_edit_privacy_gifts_never_empty" = "Never allow";
"lng_edit_privacy_gifts_exceptions" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile.";
"lng_edit_privacy_gifts_always_title" = "Always allow";
"lng_edit_privacy_gifts_never_title" = "Never allow";
"lng_edit_privacy_calls_title" = "Calls";
"lng_edit_privacy_calls_header" = "Who can call me";
"lng_edit_privacy_calls_always_empty" = "Always allow";
@@ -1459,13 +1481,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_enable_notifications" = "Notifications";
"lng_profile_send_message" = "Send Message";
"lng_profile_open_app" = "Open App";
"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_bot_permissions_title" = "Allow access to";
"lng_profile_bot_emoji_status_access" = "Emoji Status";
"lng_info_add_as_contact" = "Add to contacts";
"lng_profile_shared_media" = "Shared media";
"lng_profile_suggest_photo" = "Suggest Profile Photo";
"lng_profile_suggest_photo_from_clipboard" = "Suggest From Clipboard";
"lng_profile_set_photo_for" = "Set Profile Photo";
"lng_profile_set_photo_for_from_clipboard" = "Set From Clipboard";
"lng_profile_photo_reset" = "Reset to Original";
"lng_profile_photo_from_clipboard" = "From clipboard";
"lng_profile_suggest_sure" = "You can suggest {user} to set this photo for their Telegram profile.";
"lng_profile_suggest_button" = "Suggest";
"lng_profile_set_personal_sure" = "Only you will see this photo and it will replace any photo {user} sets for themselves.";
@@ -1523,6 +1551,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_channel_title" = "Manage Channel";
"lng_manage_bot_title" = "Manage Bot";
"lng_manage_peer_recent_actions" = "Recent actions";
"lng_manage_peer_star_ref" = "Affiliate programs";
"lng_manage_peer_members" = "Members";
"lng_manage_peer_subscribers" = "Subscribers";
"lng_manage_peer_administrators" = "Administrators";
@@ -1596,11 +1625,99 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_bot_balance" = "Balance";
"lng_manage_peer_bot_balance_currency" = "Toncoin";
"lng_manage_peer_bot_balance_credits" = "Stars";
"lng_manage_peer_bot_star_ref" = "Affiliate Program";
"lng_manage_peer_bot_star_ref_off" = "Off";
"lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there.";
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
"lng_manage_peer_bot_about" = "Use {bot} to manage this bot.";
"lng_star_ref_title" = "Affiliate Program";
"lng_star_ref_about" = "Reward those who help grow your user base.";
"lng_star_ref_share_title" = "Share revenue with affiliates";
"lng_star_ref_share_about" = "Set the commission for revenue generated by users referred to you.";
"lng_star_ref_launch_title" = "Launch your affiliate program";
"lng_star_ref_launch_about" = "Telegram will feature your program for millions of potential affiliates.";
"lng_star_ref_let_title" = "Let affiliate promote you";
"lng_star_ref_let_about" = "Affiliates will share your referral link with their audience.";
"lng_star_ref_commission_title" = "Commission";
"lng_star_ref_commission_about" = "Define the percentage of star revenue your affiliates earn for referring users to your bot.";
"lng_star_ref_duration_title" = "Duration";
"lng_star_ref_duration_about" = "Set the duration for which affiliates will earn commissions from referred users.";
"lng_star_ref_existing_title" = "View existing programs";
"lng_star_ref_existing_about" = "Explore what other mini apps offer.";
"lng_star_ref_add_bot" = "Add {bot}";
"lng_star_ref_end" = "End Affiliate Program";
"lng_star_ref_start" = "Start Affiliate Program";
"lng_star_ref_start_disabled" = "Available in {time}";
"lng_star_ref_start_info" = "By creating an affiliate program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_update" = "Update Affiliate Program";
"lng_star_ref_update_info" = "By updating an affiliate program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_button_link" = "terms and conditions";
"lng_star_ref_tos_url" = "https://telegram.org/tos/mini-apps";
"lng_star_ref_warning_title" = "Warning";
"lng_star_ref_warning_text" = "Once you start the affiliate program, you won't be able to decrease its commission or duration. You can only increase these parameters or end the program, which will disable all previously distributed referral links.";
"lng_star_ref_warning_change" = "This change is irreversible. You won't be able to reduce commission or duration. You can only increase these parameters or end the program, which will disable all previously shared referral links.";
"lng_star_ref_warning_start" = "Start";
"lng_star_ref_warning_update" = "Update";
"lng_star_ref_warning_if_end" = "If you end your affiliate program:";
"lng_star_ref_warning_if_end1" = "Any referral links already shared will be disabled in **24** hours.";
"lng_star_ref_warning_if_end2" = "All participating affiliates will be notified.";
"lng_star_ref_warning_if_end3" = "You will be able to start a new affiliate program only in **24** hours.";
"lng_star_ref_warning_end" = "End Anyway";
"lng_star_ref_created_title" = "Affiliate program started";
"lng_star_ref_created_text" = "Any Telegram user, channel owner or mini app developer can now join your program.";
"lng_star_ref_updated_title" = "Affiliate program updated";
"lng_star_ref_updated_text" = "Any Telegram user, channel owner or mini app developer can join your program.";
"lng_star_ref_ended_title" = "Affiliate program ended";
"lng_star_ref_ended_text" = "Participating affiliates have been notified. All referral links will be disabled in **24** hours.";
"lng_star_ref_list_title" = "Affiliate Programs";
"lng_star_ref_list_about_channel" = "Promote mini apps to your subscribers and earn a share of their revenue in Stars.";
"lng_star_ref_list_text" = "Earn a commission each time a user who first accessed a mini app through your referral link spends **Stars** within it.";
"lng_star_ref_list_my" = "My Programs";
"lng_star_ref_list_my_open" = "Open App";
"lng_star_ref_list_my_copy" = "Copy Link";
"lng_star_ref_list_my_leave" = "Leave";
"lng_star_ref_list_subtitle" = "Programs";
"lng_star_ref_sort_text" = "Sort by {sort}";
"lng_star_ref_sort_profitability" = "Profitability";
"lng_star_ref_sort_date" = "Date";
"lng_star_ref_sort_revenue" = "Revenue";
"lng_star_ref_reliable_title" = "Reliable";
"lng_star_ref_reliable_about" = "Receive guaranteed commissions for spending by users you refer.";
"lng_star_ref_transparent_title" = "Transparent";
"lng_star_ref_transparent_about" = "Track your commissions from referred users in real time.";
"lng_star_ref_simple_title" = "Simple";
"lng_star_ref_simple_about" = "Choose a mini app below, get your referral link, and start earning Stars.";
"lng_star_ref_duration_forever" = "Forever";
"lng_star_ref_one_about" = "{app} will share {amount} of the revenue from each user you refer to it {duration}.";
"lng_star_ref_one_about_for_forever" = "for **lifetime**";
"lng_star_ref_one_about_for_months#one" = "for **{count} month**";
"lng_star_ref_one_about_for_months#other" = "for **{count} months**";
"lng_star_ref_one_about_for_years#one" = "for **{count} year**";
"lng_star_ref_one_about_for_years#other" = "for **{count} years**";
"lng_star_ref_one_daily_revenue" = "Daily revenue per user: {amount}";
"lng_star_ref_one_join" = "Join Program";
"lng_star_ref_one_join_text" = "By joining this program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_joined_title" = "Program joined";
"lng_star_ref_joined_text" = "You can now copy the referral link.";
"lng_star_ref_link_title" = "Referral Link";
"lng_star_ref_link_about_channel" = "Share this link with your subscribers to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_about_user" = "Share this link with your friends to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_about_bot" = "Share this link with your users to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_recipient" = "Commissions will be sent to:";
"lng_star_ref_link_copy" = "Copy Link";
"lng_star_ref_link_copy_none" = "No one have opened {app} through this link.";
"lng_star_ref_link_copy_users#one" = "{count} user have opened {app} through this link.";
"lng_star_ref_link_copy_users#other" = "{count} users have opened {app} through this link.";
"lng_star_ref_link_copied_title" = "Link copied to clipboard";
"lng_star_ref_link_copied_text" = "Share this link and earn {amount} of what people who use it spend in {app}!";
"lng_star_ref_stopped" = "This affiliate link is no longer active.";
"lng_star_ref_revoke_title" = "Revoke Link";
"lng_star_ref_revoke_text" = "Are you sure you want to revoke the link of {bot}?";
"lng_star_ref_revoked_title" = "Link removed";
"lng_star_ref_revoked_text" = "It will no longer work.";
"lng_manage_discussion_group" = "Discussion";
"lng_manage_discussion_group_add" = "Add a group";
@@ -1842,6 +1959,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_payment_init_recurring_for" = "You successfully transferred {amount} to {user} for {invoice} and allowed future recurring payments";
"lng_action_payment_init_recurring" = "You successfully transferred {amount} to {user} and allowed future recurring payments";
"lng_action_payment_used_recurring" = "You were charged {amount} via recurring payment";
"lng_action_payment_bot_done" = "Bot connected to this account received {amount}";
"lng_action_payment_bot_recurring" = "Bot connected to this account received {amount} via recurring payment";
"lng_action_took_screenshot" = "{from} took a screenshot!";
"lng_action_you_took_screenshot" = "You took a screenshot!";
"lng_action_bot_allowed_from_domain" = "You allowed this bot to message you when you logged in on {domain}.";
@@ -1874,6 +1993,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
"lng_action_gift_sent_subtitle" = "Gift for {user}";
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
@@ -2276,6 +2396,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies.";
"lng_premium_summary_subtitle_effects" = "Message Effects";
"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages.";
"lng_premium_summary_subtitle_filter_tags" = "Tag Your Chats";
"lng_premium_summary_about_filter_tags" = "Display folder names for each chat in the chat list.";
"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";
@@ -2408,11 +2530,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
"lng_credits_summary_options_about_link" = "Terms and Conditions";
"lng_credits_summary_options_about_url" = "https://telegram.org/tos/stars";
"lng_credits_summary_earn_title" = "Earn Stars";
"lng_credits_summary_earn_about" = "Distribute links to mini apps and earn a share of their revenue in Stars.";
"lng_credits_summary_history_tab_full" = "All Transactions";
"lng_credits_summary_history_tab_in" = "Incoming";
"lng_credits_summary_history_tab_out" = "Outgoing";
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance";
"lng_credits_commission" = "{amount} commission";
"lng_credits_more_options" = "More Options";
"lng_credits_balance_me" = "your balance";
"lng_credits_buy_button" = "Buy More Stars";
"lng_credits_gift_button" = "Gift Stars to Friends";
"lng_credits_box_out_title" = "Confirm Your Purchase";
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
@@ -2421,6 +2549,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?";
"lng_credits_box_out_media_user#one" = "Do you want to unlock {media} from {user} for **{count} Star**?";
"lng_credits_box_out_media_user#other" = "Do you want to unlock {media} from {user} for **{count} Stars**?";
"lng_credits_box_out_subscription_bot#one" = "Do you want to subscribe to **{title}** in **{recipient}** for **{count}** star per month?";
"lng_credits_box_out_subscription_bot#other" = "Do you want to subscribe to **{title}** in **{recipient}** for **{count}** stars per month?";
"lng_credits_box_out_subscription_business#one" = "Do you want to subscribe to **{title}** from **{recipient}** for **{count}** star per month?";
"lng_credits_box_out_subscription_business#other" = "Do you want to subscribe to **{title}** from **{recipient}** for **{count}** stars per month?";
"lng_credits_box_out_subscription_confirm#one" = "Subscribe for {emoji} {count} / month";
"lng_credits_box_out_subscription_confirm#other" = "Subscribe for {emoji} {count} / month";
"lng_credits_box_out_photo" = "a photo";
"lng_credits_box_out_photos#one" = "{count} photo";
"lng_credits_box_out_photos#other" = "{count} photos";
@@ -2466,6 +2600,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
"lng_credits_box_history_entry_id" = "Transaction ID";
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
"lng_credits_box_history_entry_reason_star_ref" = "Affiliate Program";
"lng_credits_box_history_entry_affiliate" = "Affiliate";
"lng_credits_box_history_entry_miniapp" = "Mini App";
"lng_credits_box_history_entry_referred" = "Referred User";
"lng_credits_box_history_entry_success_date" = "Transaction date";
"lng_credits_box_history_entry_success_url" = "Transaction link";
"lng_credits_box_history_entry_media" = "Media";
@@ -2481,6 +2619,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_subscriber_subtitle" = "appx. {total} per month";
"lng_credits_subscription_row_to" = "Subscription";
"lng_credits_subscription_row_to_bot" = "Bot";
"lng_credits_subscription_row_to_business" = "Business";
"lng_credits_subscription_row_from" = "Subscribed";
"lng_credits_subscription_row_next_on" = "Renews";
@@ -2491,13 +2631,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_subscription_on_about" = "If you cancel now, you will still be able to access your subscription until {date}.";
"lng_credits_subscription_off_button" = "Renew Subscription";
"lng_credits_subscription_off_rejoin_button" = "Subscribe again";
"lng_credits_subscription_off_about" = "You have canceled your subscription.";
"lng_credits_subscription_off_by_bot_about" = "{bot} has canceled your subscription.";
"lng_credits_subscription_status_on" = "renews on {date}";
"lng_credits_subscription_status_off" = "expires on {date}";
"lng_credits_subscription_status_none" = "expired on {date}";
"lng_credits_subscription_status_off_right" = "canceled";
"lng_credits_subscription_status_none_right" = "expired";
"lng_credits_subscription_status_off_by_bot_right" = "canceled\nby bot";
"lng_credits_small_balance_title#one" = "{count} Star Needed";
"lng_credits_small_balance_title#other" = "{count} Stars Needed";
@@ -3039,6 +3182,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_sold_out" = "sold out";
"lng_gift_stars_tabs_all" = "All Gifts";
"lng_gift_stars_tabs_limited" = "Limited";
"lng_gift_stars_tabs_in_stock" = "In Stock";
"lng_gift_send_title" = "Send a Gift";
"lng_gift_send_message" = "Enter Message";
"lng_gift_send_anonymous" = "Hide My Name";
@@ -3055,11 +3199,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
"lng_gift_availability" = "Availability";
"lng_gift_from_hidden" = "Hidden User";
"lng_gift_visibility" = "Visibility";
"lng_gift_visibility_shown" = "Visible on your page";
"lng_gift_visibility_hidden" = "Not visible on your page";
"lng_gift_visibility_show" = "show";
"lng_gift_visibility_hide" = "hide";
"lng_gift_availability_left#one" = "{count} of {amount} left";
"lng_gift_availability_left#other" = "{count} of {amount} left";
"lng_gift_availability_none" = "None of {amount} left";
"lng_gift_display_on_page" = "Display on my Page";
"lng_gift_display_on_page_hide" = "Hide from my Page";
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
@@ -3335,6 +3482,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_replies_no_comments" = "No comments here yet...";
"lng_verification_codes" = "Verification Codes";
"lng_verification_codes_about" = "Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn't request any codes — don't worry! Most likely, someone made a mistake when entering their number.";
"lng_archived_name" = "Archived chats";
"lng_archived_add" = "Archive";
@@ -3412,6 +3560,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
"lng_bot_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps.";
"lng_bot_emoji_status_confirm" = "Confirm";
"lng_bot_emoji_status_title" = "Set Emoji Status";
"lng_bot_emoji_status_text" = "Do you want to set this emoji status suggested by {bot}?";
"lng_bot_emoji_status_access_text" = "{bot} requests access to set your **emoji status**. You will be able to revoke this access in the profile page of {name}.";
"lng_bot_emoji_status_access_allow" = "Allow";
"lng_bot_share_prepared_title" = "Share Message";
"lng_bot_share_prepared_about" = "{bot} mini app suggests you to send this message to a chat you select.";
"lng_bot_share_prepared_button" = "Share With...";
"lng_bot_download_file" = "Download File";
"lng_bot_download_file_sure" = "{bot} suggests you download the following file:";
"lng_bot_download_file_button" = "Download";
"lng_bot_download_starting" = "Starting...";
"lng_bot_download_failed" = "Failed. {retry}";
"lng_bot_download_retry" = "Retry";
"lng_bot_status_users#one" = "{count} monthly user";
"lng_bot_status_users#other" = "{count} monthly users";
@@ -3495,6 +3657,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_mark_read_sure" = "Are you sure you want to mark all chats from this folder as read?";
"lng_context_mark_read_all" = "Mark all chats as read";
"lng_context_mark_read_all_sure" = "Are you sure you want to mark all chats as read?";
"lng_context_mark_read_all_sure_2" = "**This action cannot be undone.**";
"lng_context_mark_read_mentions_all" = "Mark all mentions as read";
"lng_context_mark_read_reactions_all" = "Read all reactions";
"lng_context_archive_expand" = "Expand";
@@ -3654,6 +3817,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_paid_react_agree_link" = "Terms of Service";
"lng_paid_react_toast#one" = "Star Sent!";
"lng_paid_react_toast#other" = "Stars Sent!";
"lng_paid_react_toast_anonymous#one" = "Star sent anonymously!";
"lng_paid_react_toast_anonymous#other" = "Stars sent anonymously!";
"lng_paid_react_toast_text#one" = "You reacted with **{count} Star**.";
"lng_paid_react_toast_text#other" = "You reacted with **{count} Stars**.";
"lng_paid_react_undo" = "Undo";
@@ -4356,7 +4521,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_edit_admin_rank_about" = "A title that members will see instead of '{title}'.";
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with equal or fewer rights.";
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
"lng_rights_about_by" = "This admin promoted by {user} at {date}.";
"lng_rights_about_by" = "This admin promoted by {user} on {date}.";
"lng_rights_about_admin_cant_edit" = "You can't edit the rights of this admin.";
"lng_rights_about_restriction_cant_edit" = "You cannot change the restrictions for this user.";
@@ -4435,8 +4600,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_chat_files" = "Files";
"lng_rights_chat_voice_messages" = "Voice messages";
"lng_rights_chat_video_messages" = "Video messages";
"lng_rights_chat_restricted_by" = "Restricted by {user} at {date}.";
"lng_rights_chat_banned_by" = "Banned by {user} at {date}.";
"lng_rights_chat_restricted_by" = "Restricted by {user} on {date}.";
"lng_rights_chat_banned_by" = "Banned by {user} on {date}.";
"lng_rights_chat_banned_until_header" = "Restricted until";
"lng_rights_chat_banned_forever" = "Forever";
"lng_rights_chat_banned_day#one" = "For {count} day";
@@ -4760,6 +4925,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forward_show_captions" = "Show captions";
"lng_forward_change_recipient" = "Change recipient";
"lng_forward_sender_names_removed" = "Sender names removed";
"lng_forward_header_short" = "Forward";
"lng_forward_action_show_sender" = "Show Sender Name";
"lng_forward_action_show_senders" = "Show Sender Names";
"lng_forward_action_hide_sender" = "Hide Sender Name";
"lng_forward_action_hide_senders" = "Hide Sender Names";
"lng_forward_action_show_caption" = "Show Caption";
"lng_forward_action_show_captions" = "Show Captions";
"lng_forward_action_hide_caption" = "Hide Caption";
"lng_forward_action_hide_captions" = "Hide Captions";
"lng_forward_action_change_recipient" = "Change Recipient";
"lng_forward_action_remove" = "Do Not Forward";
"lng_passport_title" = "Telegram Passport";
"lng_passport_request1" = "{bot} requests access to your personal data";
@@ -5045,6 +5221,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_outdated_now" = "So Telegram Desktop can update to newer versions.";
"lng_filters_all" = "All chats";
"lng_filters_all_short" = "All";
"lng_filters_setup" = "Edit";
"lng_filters_title" = "Folders";
"lng_filters_subtitle" = "My folders";
@@ -5102,11 +5279,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_view_subtitle" = "Tabs view";
"lng_filters_vertical" = "Tabs on the left";
"lng_filters_horizontal" = "Tabs at the top";
"lng_filters_enable_tags" = "Show Folder Tags";
"lng_filters_enable_tags_about" = "Display folder names for each chat in the chat list.";
"lng_filters_enable_tags_about_premium" = "Subscribe to **{link}** to display folder names for each chat in the chat list.";
"lng_filters_tag_color_subtitle" = "Folder color in chat list";
"lng_filters_tag_color_about" = "Choose a color for the tag of this folder.";
"lng_filters_tag_color_no" = "No Tag";
"lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder.";
"lng_filters_link" = "Share Folder";
"lng_filters_link_has" = "Invite links";
"lng_filters_checkbox_remove_bot" = "Remove bot from all folders";
"lng_filters_checkbox_remove_group" = "Remove group from all folders";
"lng_filters_checkbox_remove_channel" = "Remove channel from all folders";
"lng_filters_link_create" = "Create an Invite Link";
"lng_filters_link_cant" = "You cant share folders which include or exclude specific chat types like 'Groups', 'Contacts', etc.";
"lng_filters_link_about" = "Share access to some of this folder's groups and channels with others.";
@@ -5652,6 +5839,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_recent_chats" = "Chats";
"lng_recent_channels" = "Channels";
"lng_recent_apps" = "Apps";
"lng_all_photos" = "Photos";
"lng_all_videos" = "Videos";
"lng_all_downloads" = "Downloads";
"lng_all_links" = "Links";
"lng_all_files" = "Files";
"lng_all_music" = "Music";
"lng_all_voice" = "Voice";
"lng_channels_none_title" = "No channels yet...";
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
"lng_channels_your_title" = "Channels you joined";

View File

@@ -28,6 +28,8 @@
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
<file alias="search.tgs">../../animations/search.tgs</file>
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
<file alias="starref_link.tgs">../../animations/starref_link.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

@@ -4,6 +4,7 @@
<file alias="art/bg_thumbnail.png">../../art/bg_thumbnail.png</file>
<file alias="art/bg_initial.jpg">../../art/bg_initial.jpg</file>
<file alias="art/business_logo.png">../../art/business_logo.png</file>
<file alias="art/affiliate_logo.png">../../art/affiliate_logo.png</file>
<file alias="art/logo_256.png">../../art/logo_256.png</file>
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_filters.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "boxes/peer_list_box.h"
#include "boxes/premium_limits_box.h"
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
@@ -139,7 +140,8 @@ void InitFilterLinkHeader(
Ui::FilterLinkHeaderType type,
const QString &title,
const QString &iconEmoji,
rpl::producer<int> count) {
rpl::producer<int> count,
bool horizontalFilters) {
const auto icon = Ui::LookupFilterIcon(
Ui::LookupFilterIconByEmoji(
iconEmoji
@@ -153,7 +155,7 @@ void InitFilterLinkHeader(
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
? std::move(count)
: rpl::single(0)),
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
.horizontalFilters = horizontalFilters,
});
const auto widget = header.widget;
widget->resizeToWidth(st::boxWideWidth);
@@ -548,6 +550,26 @@ void ShowImportToast(
strong->showToast(std::move(text));
}
void HandleEnterInBox(not_null<Ui::BoxContent*> box) {
const auto isEnter = [=](not_null<QEvent*> event) {
if (event->type() == QEvent::KeyPress) {
if (const auto k = static_cast<QKeyEvent*>(event.get())) {
return (k->key() == Qt::Key_Enter)
|| (k->key() == Qt::Key_Return);
}
}
return false;
};
base::install_event_filter(box, [=](not_null<QEvent*> event) {
if (isEnter(event)) {
box->triggerButton(0);
return base::EventFilterResult::Cancel;
}
return base::EventFilterResult::Continue;
});
}
void ProcessFilterInvite(
base::weak_ptr<Window::SessionController> weak,
const QString &slug,
@@ -572,6 +594,8 @@ void ProcessFilterInvite(
title,
std::move(peers),
std::move(already));
const auto horizontalFilters = !strong->enoughSpaceForFilters()
|| Core::App().settings().chatFiltersHorizontal();
const auto raw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->setStyle(st::filterInviteBox);
@@ -588,7 +612,7 @@ void ProcessFilterInvite(
});
InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
raw->adjust(min, max, addedTop);
}, type, title, iconEmoji, rpl::duplicate(badge));
}, type, title, iconEmoji, rpl::duplicate(badge), horizontalFilters);
raw->setRealContentHeight(box->heightValue());
@@ -610,6 +634,8 @@ void ProcessFilterInvite(
box->addButton(std::move(owned));
HandleEnterInBox(box);
struct State {
bool importing = false;
};
@@ -798,6 +824,8 @@ void ProcessFilterRemove(
title,
std::move(suggest),
std::move(all));
const auto horizontalFilters = !strong->enoughSpaceForFilters()
|| Core::App().settings().chatFiltersHorizontal();
const auto raw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->setStyle(st::filterInviteBox);
@@ -809,7 +837,7 @@ void ProcessFilterRemove(
});
InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
raw->adjust(min, max, addedTop);
}, type, title, iconEmoji, rpl::single(0));
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
auto owned = Ui::FilterLinkProcessButton(
box,
@@ -829,6 +857,8 @@ void ProcessFilterRemove(
box->addButton(std::move(owned));
HandleEnterInBox(box);
raw->selectedValue(
) | rpl::start_with_next([=](
base::flat_set<not_null<PeerData*>> &&peers) {

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_invite.h"
#include "apiwrap.h"
#include "api/api_credits.h"
#include "boxes/premium_limits_box.h"
#include "core/application.h"
#include "data/components/credits.h"
@@ -206,32 +207,12 @@ void ConfirmSubscriptionBox(
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWideWidth - photoSize,
photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
Settings::AddMiniStars(
content,
Ui::CreateChild<Ui::RpWidget>(content),
photoSize,
box->width(),
2.);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
@@ -295,20 +276,39 @@ void ConfirmSubscriptionBox(
const auto buttonWidth = state->saveButton
? state->saveButton->width()
: 0;
const auto finish = [=] {
state->api = std::nullopt;
state->loading.force_assign(false);
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
state->api->request(
MTPpayments_SendStarsForm(
MTP_long(formId),
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
).done([=](const MTPpayments_PaymentResult &result) {
state->api = std::nullopt;
state->loading.force_assign(false);
result.match([&](const MTPDpayments_paymentResult &data) {
session->api().applyUpdates(data.vupdates());
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
});
if (weak) {
box->closeBox();
const auto refill = session->data().activeCreditsSubsRebuilder();
const auto strong = weak.data();
if (!strong) {
return;
}
if (!refill) {
return finish();
}
const auto api
= strong->lifetime().make_state<Api::CreditsHistory>(
session->user(),
true,
true);
api->requestSubscriptions({}, [=](Data::CreditsStatusSlice d) {
refill->fire(std::move(d));
finish();
});
}).fail([=](const MTP::Error &error) {
const auto id = error.type();
if (weak) {

View File

@@ -74,7 +74,16 @@ constexpr auto kTransactionsLimit = 100;
}).value;
const auto stargift = tl.data().vstargift();
const auto reaction = tl.data().is_reaction();
const auto incoming = (int64(tl.data().vstars().v) >= 0);
const auto amount = Data::FromTL(tl.data().vstars());
const auto starrefAmount = tl.data().vstarref_amount()
? Data::FromTL(*tl.data().vstarref_amount())
: StarsAmount();
const auto starrefCommission
= tl.data().vstarref_commission_permille().value_or_empty();
const auto starrefBarePeerId = tl.data().vstarref_peer()
? peerFromMTP(*tl.data().vstarref_peer()).value
: 0;
const auto incoming = (amount >= StarsAmount());
const auto saveActorId = (reaction || !extended.empty()) && incoming;
return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()),
@@ -83,7 +92,7 @@ constexpr auto kTransactionsLimit = 100;
.date = base::unixtime::parse(tl.data().vdate().v),
.photoId = photo ? photo->id : 0,
.extended = std::move(extended),
.credits = tl.data().vstars().v,
.credits = Data::FromTL(tl.data().vstars()),
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = saveActorId ? peer->id.value : barePeerId,
.bareGiveawayMsgId = uint64(
@@ -92,6 +101,9 @@ constexpr auto kTransactionsLimit = 100;
? owner->processDocument(stargift->data().vsticker())->id
: 0),
.bareActorId = saveActorId ? barePeerId : uint64(0),
.starrefAmount = starrefAmount,
.starrefCommission = starrefCommission,
.starrefRecipientId = starrefBarePeerId,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@@ -117,11 +129,12 @@ constexpr auto kTransactionsLimit = 100;
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.convertStars = int(stargift
.starsConverted = int(stargift
? stargift->data().vconvert_stars().v
: 0),
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
.converted = stargift && incoming,
.stargift = stargift.has_value(),
.reaction = tl.data().is_reaction(),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
@@ -132,17 +145,26 @@ constexpr auto kTransactionsLimit = 100;
}
[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
const MTPStarsSubscription &tl) {
const MTPStarsSubscription &tl,
not_null<PeerData*> peer) {
return Data::SubscriptionEntry{
.id = qs(tl.data().vid()),
.inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()),
.title = qs(tl.data().vtitle().value_or_empty()),
.slug = qs(tl.data().vinvoice_slug().value_or_empty()),
.until = base::unixtime::parse(tl.data().vuntil_date().v),
.subscription = Data::PeerSubscription{
.credits = tl.data().vpricing().data().vamount().v,
.period = tl.data().vpricing().data().vperiod().v,
},
.barePeerId = peerFromMTP(tl.data().vpeer()).value,
.photoId = (tl.data().vphoto()
? peer->owner().photoFromWeb(
*tl.data().vphoto(),
ImageLocation())->id
: 0),
.cancelled = tl.data().is_canceled(),
.cancelledByBot = tl.data().is_bot_canceled(),
.expired = (base::unixtime::now() > tl.data().vuntil_date().v),
.canRefulfill = tl.data().is_can_refulfill(),
};
@@ -165,16 +187,17 @@ constexpr auto kTransactionsLimit = 100;
if (const auto history = data.vsubscriptions()) {
subscriptions.reserve(history->v.size());
for (const auto &tl : history->v) {
subscriptions.push_back(SubscriptionFromTL(tl));
subscriptions.push_back(SubscriptionFromTL(tl, peer));
}
}
return Data::CreditsStatusSlice{
.list = std::move(entries),
.subscriptions = std::move(subscriptions),
.balance = status.data().vbalance().v,
.balance = Data::FromTL(status.data().vbalance()),
.subscriptionsMissingBalance
= status.data().vsubscriptions_missing_balance().value_or_empty(),
.allLoaded = !status.data().vnext_offset().has_value(),
.allLoaded = !status.data().vnext_offset().has_value()
&& !status.data().vsubscriptions_next_offset().has_value(),
.token = qs(status.data().vnext_offset().value_or_empty()),
.tokenSubscriptions = qs(
status.data().vsubscriptions_next_offset().value_or_empty()),
@@ -257,8 +280,8 @@ void CreditsStatus::request(
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
)).done([=](const TLResult &result) {
_requestId = 0;
const auto balance = result.data().vbalance().v;
_peer->session().credits().apply(_peer->id, balance);
const auto &balance = result.data().vbalance();
_peer->session().credits().apply(_peer->id, Data::FromTL(balance));
if (const auto onstack = done) {
onstack(StatusFromTL(result, _peer));
}
@@ -337,7 +360,9 @@ rpl::producer<not_null<PeerData*>> PremiumPeerBot(
const auto api = lifetime.make_state<MTP::Sender>(&session->mtp());
api->request(MTPcontacts_ResolveUsername(
MTP_string(username)
MTP_flags(0),
MTP_string(username),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
session->data().processUsers(result.data().vusers());
session->data().processChats(result.data().vchats());
@@ -369,12 +394,13 @@ rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
)).done([=](const MTPpayments_StarsRevenueStats &result) {
const auto &data = result.data();
const auto &status = data.vstatus().data();
using Data::FromTL;
_data = Data::CreditsEarnStatistics{
.revenueGraph = StatisticalGraphFromTL(
data.vrevenue_graph()),
.currentBalance = status.vcurrent_balance().v,
.availableBalance = status.vavailable_balance().v,
.overallRevenue = status.voverall_revenue().v,
.currentBalance = FromTL(status.vcurrent_balance()),
.availableBalance = FromTL(status.vavailable_balance()),
.overallRevenue = FromTL(status.voverall_revenue()),
.usdRate = data.vusd_rate().v,
.isWithdrawalEnabled = status.is_withdrawal_enabled(),
.nextWithdrawalAt = status.vnext_withdrawal_at()
@@ -461,4 +487,20 @@ Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
return _options;
}
void EditCreditsSubscription(
not_null<Main::Session*> session,
const QString &id,
bool cancel,
Fn<void()> done,
Fn<void(QString)> fail) {
using Flag = MTPpayments_ChangeStarsSubscription::Flag;
session->api().request(
MTPpayments_ChangeStarsSubscription(
MTP_flags(Flag::f_canceled),
MTP_inputPeerSelf(),
MTP_string(id),
MTP_bool(cancel)
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
}
} // namespace Api

View File

@@ -109,4 +109,11 @@ private:
[[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot(
not_null<Main::Session*> session);
void EditCreditsSubscription(
not_null<Main::Session*> session,
const QString &id,
bool cancel,
Fn<void()> done,
Fn<void(QString)> fail);
} // namespace Api

View File

@@ -40,6 +40,7 @@ void HandleWithdrawalButton(
std::shared_ptr<Ui::Show> show) {
Expects(receiver.currencyReceiver
|| (receiver.creditsReceiver && receiver.creditsAmount));
struct State {
rpl::lifetime lifetime;
bool loading = false;
@@ -58,8 +59,7 @@ void HandleWithdrawalButton(
const auto processOut = [=] {
if (state->loading) {
return;
}
if (peer && !receiver.creditsAmount()) {
} else if (peer && !receiver.creditsAmount()) {
return;
}
state->loading = true;

View File

@@ -74,12 +74,17 @@ const FoundMessages &MessagesSearchMerged::messages() const {
return _concatedFound;
}
const MessagesSearch::Request &MessagesSearchMerged::request() const {
return _request;
}
void MessagesSearchMerged::clear() {
_concatedFound = {};
_migratedFirstFound = {};
}
void MessagesSearchMerged::search(const Request &search) {
_request = search;
if (_migratedSearch) {
_waitingForTotal = true;
_migratedSearch->searchMessages(search);

View File

@@ -31,6 +31,7 @@ public:
void searchMore();
[[nodiscard]] const FoundMessages &messages() const;
[[nodiscard]] const Request &request() const;
[[nodiscard]] rpl::producer<> newFounds() const;
[[nodiscard]] rpl::producer<> nextFounds() const;
@@ -39,6 +40,7 @@ private:
void addFound(const FoundMessages &data);
MessagesSearch _apiSearch;
Request _request;
std::optional<MessagesSearch> _migratedSearch;
FoundMessages _migratedFirstFound;

View File

@@ -772,12 +772,13 @@ std::optional<StarGift> FromTL(
return StarGift{
.id = uint64(data.vid().v),
.stars = int64(data.vstars().v),
.convertStars = int64(data.vconvert_stars().v),
.starsConverted = int64(data.vconvert_stars().v),
.document = document,
.limitedLeft = remaining.value_or_empty(),
.limitedCount = total.value_or_empty(),
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
.birthday = data.is_birthday(),
};
}
@@ -800,7 +801,7 @@ std::optional<UserStarGift> FromTL(
data.vmessage()->data().ventities().v),
}
: TextWithEntities()),
.convertStars = int64(data.vconvert_stars().value_or_empty()),
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
.fromId = (data.vfrom_id()
? peerFromUser(data.vfrom_id()->v)
: PeerId()),

View File

@@ -76,12 +76,13 @@ struct GiftOptionData {
struct StarGift {
uint64 id = 0;
int64 stars = 0;
int64 convertStars = 0;
int64 starsConverted = 0;
not_null<DocumentData*> document;
int limitedLeft = 0;
int limitedCount = 0;
TimeId firstSaleDate = 0;
TimeId lastSaleDate = 0;
bool birthday = false;
friend inline bool operator==(
const StarGift &,
@@ -91,7 +92,7 @@ struct StarGift {
struct UserStarGift {
StarGift info;
TextWithEntities message;
int64 convertStars = 0;
int64 starsConverted = 0;
PeerId fromId = 0;
MsgId messageId = 0;
TimeId date = 0;

View File

@@ -181,7 +181,9 @@ std::optional<HistoryItem*> SingleMessageSearch::performLookupByUsername(
ready();
};
_requestId = _session->api().request(MTPcontacts_ResolveUsername(
MTP_string(username)
MTP_flags(0),
MTP_string(username),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
result.match([&](const MTPDcontacts_resolvedPeer &data) {
_session->data().processUsers(data.vusers());

View File

@@ -69,6 +69,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
if (rule.always.premiums && (rule.option != Option::Everyone)) {
result.push_back(MTP_inputPrivacyValueAllowPremium());
}
if (rule.always.miniapps && (rule.option != Option::Everyone)) {
result.push_back(MTP_inputPrivacyValueAllowBots());
}
}
if (!rule.ignoreNever) {
const auto users = collectInputUsers(rule.never);
@@ -83,6 +86,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
MTP_inputPrivacyValueDisallowChatParticipants(
MTP_vector<MTPlong>(chats)));
}
if (rule.never.miniapps && (rule.option != Option::Nobody)) {
result.push_back(MTP_inputPrivacyValueDisallowBots());
}
}
result.push_back([&] {
switch (rule.option) {
@@ -124,6 +130,10 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) {
setOption(Option::CloseFriends);
}, [&](const MTPDprivacyValueAllowPremium &) {
result.always.premiums = true;
}, [&](const MTPDprivacyValueAllowBots &) {
result.always.miniapps = true;
}, [&](const MTPDprivacyValueDisallowBots &) {
result.never.miniapps = true;
}, [&](const MTPDprivacyValueAllowUsers &data) {
const auto &users = data.vusers().v;
always.reserve(always.size() + users.size());
@@ -199,6 +209,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages();
case Key::About: return MTP_inputPrivacyKeyAbout();
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
}
Unexpected("Key in Api::UserPrivacy::KetToTL.");
}
@@ -228,6 +239,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
case mtpc_inputPrivacyKeyAbout: return Key::About;
case mtpc_privacyKeyBirthday:
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
case mtpc_privacyKeyStarGiftsAutoSave:
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
}
return std::nullopt;
}

View File

@@ -31,6 +31,7 @@ public:
Voices,
About,
Birthday,
GiftsAutoSave,
};
enum class Option {
Everyone,
@@ -41,6 +42,7 @@ public:
struct Exceptions {
std::vector<not_null<PeerData*>> peers;
bool premiums = false;
bool miniapps = false;
};
struct Rule {
Option option = Option::Everyone;

View File

@@ -756,19 +756,32 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
const style::WhoRead &st) {
return WhoReacted(item, reaction, context, st, nullptr);
}
rpl::producer<Ui::WhoReadContent> WhenEdited(
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenDate(
not_null<PeerData*> author,
TimeId date) {
TimeId date,
Ui::WhoReadType type) {
return rpl::single(Ui::WhoReadContent{
.participants = { Ui::WhoReadParticipant{
.name = author->name(),
.date = FormatReadDate(date, QDateTime::currentDateTime()),
.id = author->id.value,
} },
.type = Ui::WhoReadType::Edited,
.type = type,
.fullReadCount = 1,
});
}
rpl::producer<Ui::WhoReadContent> WhenEdited(
not_null<PeerData*> author,
TimeId date) {
return WhenDate(author, date, Ui::WhoReadType::Edited);
}
rpl::producer<Ui::WhoReadContent> WhenOriginal(
not_null<PeerData*> author,
TimeId date) {
return WhenDate(author, date, Ui::WhoReadType::Original);
}
} // namespace Api

View File

@@ -64,5 +64,8 @@ struct WhoReadList {
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
not_null<PeerData*> author,
TimeId date);
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenOriginal(
not_null<PeerData*> author,
TimeId date);
} // namespace Api

View File

@@ -559,6 +559,14 @@ void ApiWrap::sendMessageFail(
: tr::lng_error_noforwards_group(tr::now), kJoinErrorDuration);
} else if (error == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
Settings::ShowPremium(&session(), "premium_stickers");
} else if (error == u"SCHEDULE_TOO_MUCH"_q) {
auto &scheduled = _session->scheduledMessages();
if (const auto item = scheduled.lookupItem(peer->id, itemId.msg)) {
scheduled.removeSending(item);
}
if (show) {
show->showToast(tr::lng_error_schedule_limit(tr::now));
}
}
if (const auto item = _session->data().message(itemId)) {
Assert(randomId != 0);
@@ -3211,6 +3219,31 @@ void ApiWrap::sharedMediaDone(
}
}
mtpRequestId ApiWrap::requestGlobalMedia(
Storage::SharedMediaType type,
const QString &query,
int32 offsetRate,
Data::MessagePosition offsetPosition,
Fn<void(Api::GlobalMediaResult)> done) {
auto prepared = Api::PrepareGlobalMediaRequest(
_session,
offsetRate,
offsetPosition,
type,
query);
if (!prepared) {
done({});
return 0;
}
return request(
std::move(*prepared)
).done([=](const Api::SearchRequestResult &result) {
done(Api::ParseGlobalMediaResult(_session, result));
}).fail([=] {
done({});
}).send();
}
void ApiWrap::sendAction(const SendAction &action) {
if (!action.options.scheduled
&& !action.options.shortcutId
@@ -3947,7 +3980,8 @@ void ApiWrap::sendInlineResult(
not_null<UserData*> bot,
not_null<InlineBots::Result*> data,
const SendAction &action,
std::optional<MsgId> localMessageId) {
std::optional<MsgId> localMessageId,
Fn<void(bool)> done) {
sendAction(action);
const auto history = action.history;
@@ -4027,11 +4061,17 @@ void ApiWrap::sendInlineResult(
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
if (done) {
done(true);
}
}, [=](const MTP::Error &error, const MTP::Response &response) {
sendMessageFail(error, peer, randomId, newId);
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
if (done) {
done(false);
}
});
finishForwarding(action);
}

View File

@@ -59,6 +59,7 @@ class Show;
namespace Api {
struct SearchResult;
struct GlobalMediaResult;
class Updates;
class Authorizations;
@@ -288,6 +289,12 @@ public:
Storage::SharedMediaType type,
MsgId messageId,
SliceType slice);
mtpRequestId requestGlobalMedia(
Storage::SharedMediaType type,
const QString &query,
int32 offsetRate,
Data::MessagePosition offsetPosition,
Fn<void(Api::GlobalMediaResult)> done);
void readFeaturedSetDelayed(uint64 setId);
@@ -361,7 +368,8 @@ public:
not_null<UserData*> bot,
not_null<InlineBots::Result*> data,
const SendAction &action,
std::optional<MsgId> localMessageId);
std::optional<MsgId> localMessageId,
Fn<void(bool)> done = nullptr);
void sendMessageFail(
const MTP::Error &error,
not_null<PeerData*> peer,
@@ -508,6 +516,10 @@ private:
MsgId topicRootId,
SharedMediaType type,
Api::SearchResult &&parsed);
void globalMediaDone(
SharedMediaType type,
FullMsgId messageId,
Api::GlobalMediaResult &&parsed);
void sendSharedContact(
const QString &phone,
@@ -671,6 +683,17 @@ private:
};
base::flat_set<HistoryRequest> _historyRequests;
struct GlobalMediaRequest {
SharedMediaType mediaType = {};
FullMsgId aroundId;
SliceType sliceType = {};
friend inline auto operator<=>(
const GlobalMediaRequest&,
const GlobalMediaRequest&) = default;
};
base::flat_set<GlobalMediaRequest> _globalMediaRequests;
std::unique_ptr<DialogsLoadState> _dialogsLoadState;
TimeId _dialogsLoadTill = 0;
rpl::variable<bool> _dialogsLoadMayBlockByDate = false;

View File

@@ -262,10 +262,16 @@ void ShowAddParticipantsError(
return tr::lng_bot_already_in_group(tr::now);
} else if (error == u"BOT_GROUPS_BLOCKED"_q) {
return tr::lng_error_cant_add_bot(tr::now);
} else if (error == u"ADMINS_TOO_MUCH"_q) {
return ((chat->isChat() || chat->isMegagroup())
? tr::lng_error_admin_limit
: tr::lng_error_admin_limit_channel)(tr::now);
} else if (error == u"YOU_BLOCKED_USER"_q) {
return tr::lng_error_you_blocked_user(tr::now);
} else if (error == u"CHAT_ADMIN_INVITE_REQUIRED"_q) {
return tr::lng_error_add_admin_not_member(tr::now);
} else if (error == u"USER_ADMIN_INVALID"_q) {
return tr::lng_error_user_admin_invalid(tr::now);
} else if (error == u"BOTS_TOO_MUCH"_q) {
return (chat->isChannel()
? tr::lng_error_channel_bots_too_much
: tr::lng_error_group_bots_too_much)(tr::now);
}
return tr::lng_failed_add_participant(tr::now);
}();

View File

@@ -8,22 +8,106 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/choose_filter_box.h"
#include "apiwrap.h"
#include "boxes/filters/edit_filter_box.h"
#include "boxes/premium_limits_box.h"
#include "core/application.h" // primaryWindow
#include "data/data_chat_filters.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/empty_userpic.h"
#include "ui/filter_icons.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h" // Ui::Text::Bold
#include "ui/widgets/buttons.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_dialogs.h"
#include "styles/style_media_player.h" // mediaPlayerMenuCheck
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
namespace {
[[nodiscard]] QImage Icon(const Data::ChatFilter &f) {
constexpr auto kScale = 0.75;
const auto icon = Ui::LookupFilterIcon(Ui::ComputeFilterIcon(f)).normal;
const auto originalWidth = icon->width();
const auto originalHeight = icon->height();
const auto scaledWidth = int(originalWidth * kScale);
const auto scaledHeight = int(originalHeight * kScale);
auto image = QImage(
scaledWidth * style::DevicePixelRatio(),
scaledHeight * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(style::DevicePixelRatio());
image.fill(Qt::transparent);
{
auto p = QPainter(&image);
auto hq = PainterHighQualityEnabler(p);
const auto x = int((scaledWidth - originalWidth * kScale) / 2);
const auto y = int((scaledHeight - originalHeight * kScale) / 2);
p.scale(kScale, kScale);
icon->paint(p, x, y, scaledWidth, st::dialogsUnreadBgMuted->c);
if (const auto color = f.colorIndex()) {
p.resetTransform();
const auto circleSize = scaledWidth / 3.;
const auto r = QRectF(
x + scaledWidth - circleSize,
y + scaledHeight - circleSize - circleSize / 3.,
circleSize,
circleSize);
p.setPen(Qt::NoPen);
p.setCompositionMode(QPainter::CompositionMode_Clear);
p.setBrush(Qt::transparent);
p.drawEllipse(r + Margins(st::lineWidth * 1.5));
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setBrush(Ui::EmptyUserpic::UserpicColor(*color).color2);
p.drawEllipse(r);
}
}
return image;
}
class FilterAction : public Ui::Menu::Action {
public:
using Ui::Menu::Action::Action;
void setIcon(QImage &&image) {
_icon = std::move(image);
}
protected:
void paintEvent(QPaintEvent *event) override {
Ui::Menu::Action::paintEvent(event);
if (!_icon.isNull()) {
const auto size = _icon.size() / style::DevicePixelRatio();
auto p = QPainter(this);
p.drawImage(
width()
- size.width()
- st::menuWithIcons.itemPadding.right(),
(height() - size.height()) / 2,
_icon);
}
}
private:
QImage _icon;
};
Data::ChatFilter ChangedFilter(
const Data::ChatFilter &filter,
not_null<History*> history,
@@ -96,6 +180,9 @@ void ChangeFilterById(
Ui::Text::WithEntities));
}
}).fail([=](const MTP::Error &error) {
LOG(("API Error: failed to %1 a dialog to a folder. %2")
.arg(add ? u"add"_q : u"remove"_q)
.arg(error.type()));
// Revert filter on fail.
history->owner().chatsFilters().set(was);
}).send();
@@ -123,9 +210,7 @@ bool ChooseFilterValidator::canRemove(FilterId filterId) const {
const auto list = _history->owner().chatsFilters().list();
const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
if (i != end(list)) {
const auto &filter = *i;
return filter.contains(_history)
&& ((filter.always().size() > 1) || filter.flags());
return Data::CanRemoveFromChatFilter(*i, _history);
}
return false;
}
@@ -161,14 +246,15 @@ void FillChooseFilterMenu(
not_null<History*> history) {
const auto weak = base::make_weak(controller);
const auto validator = ChooseFilterValidator(history);
for (const auto &filter : history->owner().chatsFilters().list()) {
const auto &list = history->owner().chatsFilters().list();
const auto showColors = history->owner().chatsFilters().tagsEnabled();
for (const auto &filter : list) {
const auto id = filter.id();
if (!id) {
continue;
}
const auto contains = filter.contains(history);
const auto action = menu->addAction(filter.title(), [=] {
auto callback = [=] {
const auto toAdd = !filter.contains(history);
const auto r = validator.limitReached(id, toAdd);
if (r.reached) {
@@ -185,11 +271,58 @@ void FillChooseFilterMenu(
validator.remove(id);
}
}
}, contains ? &st::mediaPlayerMenuCheck : nullptr);
};
const auto contains = filter.contains(history);
auto item = base::make_unique_q<FilterAction>(
menu.get(),
menu->st().menu,
Ui::Menu::CreateAction(
menu.get(),
Ui::Text::FixAmpersandInAction(filter.title()),
std::move(callback)),
contains ? &st::mediaPlayerMenuCheck : nullptr,
contains ? &st::mediaPlayerMenuCheck : nullptr);
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
const auto &p = st::menuWithIcons.itemPadding;
item->setMinWidth(item->minWidth() + p.left() - p.right() - p.top());
const auto action = menu->addAction(std::move(item));
action->setEnabled(contains
? validator.canRemove(id)
: validator.canAdd());
}
const auto limit = [session = &controller->session()] {
return Data::PremiumLimits(session).dialogFiltersCurrent();
};
if ((list.size() - 1) < limit()) {
menu->addAction(tr::lng_filters_create(tr::now), [=] {
const auto strong = weak.get();
if (!strong) {
return;
}
const auto session = &strong->session();
const auto count = session->data().chatsFilters().list().size();
if ((count - 1) >= limit()) {
return;
}
auto filter =
Data::ChatFilter({}, {}, {}, {}, {}, { history }, {}, {});
const auto send = [=](const Data::ChatFilter &filter) {
session->api().request(MTPmessages_UpdateDialogFilter(
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
MTP_int(count),
filter.tl()
)).done([=] {
session->data().chatsFilters().reload();
}).send();
};
strong->uiShow()->show(
Box(EditFilterBox, strong, std::move(filter), send, nullptr));
}, &st::menuIconShowInFolder);
}
history->owner().chatsFilters().changed(
) | rpl::start_with_next([=] {
menu->hideMenu();

View File

@@ -254,7 +254,7 @@ EditCaptionBox::EditCaptionBox(
, _initialList(std::move(list))
, _saved(std::move(saved)) {
Expects(!_initialList.files.empty());
Expects(!item->media() || item->media()->allowsEditCaption());
Expects(item->allowsEditMedia());
_mediaEditManager.start(item, spoilered, invertCaption);

View File

@@ -36,13 +36,63 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_window.h"
namespace {
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
using Exceptions = Api::UserPrivacy::Exceptions;
enum class SpecialRowType {
Premiums,
MiniApps,
};
[[nodiscard]] PaintRoundImageCallback GeneratePremiumsUserpicCallback(
bool forceRound) {
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
auto gradient = QLinearGradient(
QPointF(x, y),
QPointF(x + size, y + size));
gradient.setStops(Ui::Premium::ButtonGradientStops());
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(gradient);
if (forceRound) {
p.drawEllipse(x, y, size, size);
} else {
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
p.drawRoundedRect(x, y, size, size, radius, radius);
}
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
};
}
[[nodiscard]] PaintRoundImageCallback GenerateMiniAppsUserpicCallback(
bool forceRound) {
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
const auto &color1 = st::historyPeer6UserpicBg;
const auto &color2 = st::historyPeer6UserpicBg2;
auto hq = PainterHighQualityEnabler(p);
auto gradient = QLinearGradient(x, y, x, y + size);
gradient.setStops({ { 0., color1->c }, { 1., color2->c } });
p.setPen(Qt::NoPen);
p.setBrush(gradient);
if (forceRound) {
p.drawEllipse(x, y, size, size);
} else {
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
p.drawRoundedRect(x, y, size, size, radius, radius);
}
st::windowFilterTypeBots.paintInCenter(p, QRect(x, y, size, size));
};
}
void CreateRadiobuttonLock(
not_null<Ui::RpWidget*> widget,
const style::Checkbox &st) {
@@ -102,7 +152,7 @@ public:
not_null<Main::Session*> session,
rpl::producer<QString> title,
const Exceptions &selected,
bool allowChoosePremiums);
std::optional<SpecialRowType> allowChooseSpecial);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
@@ -110,18 +160,20 @@ public:
bool handleDeselectForeignRow(PeerListRowId itemId) override;
[[nodiscard]] bool premiumsSelected() const;
[[nodiscard]] bool miniAppsSelected() const;
protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override;
private:
[[nodiscard]] object_ptr<Ui::RpWidget> preparePremiumsRowList();
[[nodiscard]] object_ptr<Ui::RpWidget> prepareSpecialRowList(
SpecialRowType type);
const not_null<Main::Session*> _session;
rpl::producer<QString> _title;
Exceptions _selected;
bool _allowChoosePremiums = false;
std::optional<SpecialRowType> _allowChooseSpecial;
PeerListContentDelegate *_typesDelegate = nullptr;
Fn<void(PeerListRowId)> _deselectOption;
@@ -133,9 +185,9 @@ struct RowSelectionChange {
bool checked = false;
};
class PremiumsRow final : public PeerListRow {
class SpecialRow final : public PeerListRow {
public:
PremiumsRow();
explicit SpecialRow(SpecialRowType type);
QString generateName() override;
QString generateShortName() override;
@@ -147,66 +199,61 @@ public:
class TypesController final : public PeerListController {
public:
TypesController(not_null<Main::Session*> session, bool premiums);
TypesController(not_null<Main::Session*> session, SpecialRowType type);
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
[[nodiscard]] bool premiumsSelected() const;
[[nodiscard]] rpl::producer<bool> premiumsChanges() const;
[[nodiscard]] bool specialSelected() const;
[[nodiscard]] rpl::producer<bool> specialChanges() const;
[[nodiscard]] auto rowSelectionChanges() const
-> rpl::producer<RowSelectionChange>;
private:
const not_null<Main::Session*> _session;
const SpecialRowType _type;
rpl::event_stream<> _selectionChanged;
rpl::event_stream<RowSelectionChange> _rowSelectionChanges;
};
PremiumsRow::PremiumsRow() : PeerListRow(kPremiumsRowId) {
setCustomStatus(tr::lng_edit_privacy_premium_status(tr::now));
SpecialRow::SpecialRow(SpecialRowType type)
: PeerListRow((type == SpecialRowType::Premiums)
? kPremiumsRowId
: kMiniAppsRowId) {
setCustomStatus((id() == kPremiumsRowId)
? tr::lng_edit_privacy_premium_status(tr::now)
: tr::lng_edit_privacy_miniapps_status(tr::now));
}
QString PremiumsRow::generateName() {
return tr::lng_edit_privacy_premium(tr::now);
QString SpecialRow::generateName() {
return (id() == kPremiumsRowId)
? tr::lng_edit_privacy_premium(tr::now)
: tr::lng_edit_privacy_miniapps(tr::now);
}
QString PremiumsRow::generateShortName() {
QString SpecialRow::generateShortName() {
return generateName();
}
PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
PaintRoundImageCallback SpecialRow::generatePaintUserpicCallback(
bool forceRound) {
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
auto gradient = QLinearGradient(
QPointF(x, y),
QPointF(x + size, y + size));
gradient.setStops(Ui::Premium::ButtonGradientStops());
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(gradient);
if (forceRound) {
p.drawEllipse(x, y, size, size);
} else {
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
p.drawRoundedRect(x, y, size, size, radius, radius);
}
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
};
return (id() == kPremiumsRowId)
? GeneratePremiumsUserpicCallback(forceRound)
: GenerateMiniAppsUserpicCallback(forceRound);
}
bool PremiumsRow::useForumLikeUserpic() const {
bool SpecialRow::useForumLikeUserpic() const {
return true;
}
TypesController::TypesController(
not_null<Main::Session*> session,
bool premiums)
: _session(session) {
SpecialRowType type)
: _session(session)
, _type(type) {
}
Main::Session &TypesController::session() const {
@@ -214,12 +261,15 @@ Main::Session &TypesController::session() const {
}
void TypesController::prepare() {
delegate()->peerListAppendRow(std::make_unique<PremiumsRow>());
delegate()->peerListAppendRow(std::make_unique<SpecialRow>(_type));
delegate()->peerListRefreshRows();
}
bool TypesController::premiumsSelected() const {
const auto row = delegate()->peerListFindRow(kPremiumsRowId);
bool TypesController::specialSelected() const {
const auto premiums = (_type == SpecialRowType::Premiums);
const auto row = delegate()->peerListFindRow(premiums
? kPremiumsRowId
: kMiniAppsRowId);
Assert(row != nullptr);
return row->checked();
@@ -231,10 +281,10 @@ void TypesController::rowClicked(not_null<PeerListRow*> row) {
_rowSelectionChanges.fire({ row, checked });
}
rpl::producer<bool> TypesController::premiumsChanges() const {
rpl::producer<bool> TypesController::specialChanges() const {
return _rowSelectionChanges.events(
) | rpl::map([=] {
return premiumsSelected();
return specialSelected();
});
}
@@ -247,12 +297,12 @@ PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
not_null<Main::Session*> session,
rpl::producer<QString> title,
const Exceptions &selected,
bool allowChoosePremiums)
std::optional<SpecialRowType> allowChooseSpecial)
: ChatsListBoxController(session)
, _session(session)
, _title(std::move(title))
, _selected(selected)
, _allowChoosePremiums(allowChoosePremiums) {
, _allowChooseSpecial(allowChooseSpecial) {
}
Main::Session &PrivacyExceptionsBoxController::session() const {
@@ -261,14 +311,18 @@ Main::Session &PrivacyExceptionsBoxController::session() const {
void PrivacyExceptionsBoxController::prepareViewHook() {
delegate()->peerListSetTitle(std::move(_title));
if (_allowChoosePremiums || _selected.premiums) {
delegate()->peerListSetAboveWidget(preparePremiumsRowList());
if (_allowChooseSpecial || _selected.premiums || _selected.miniapps) {
delegate()->peerListSetAboveWidget(prepareSpecialRowList(
_allowChooseSpecial.value_or(_selected.premiums
? SpecialRowType::Premiums
: SpecialRowType::MiniApps)));
}
delegate()->peerListAddSelectedPeers(_selected.peers);
}
bool PrivacyExceptionsBoxController::isForeignRow(PeerListRowId itemId) {
return (itemId == kPremiumsRowId);
return (itemId == kPremiumsRowId)
|| (itemId == kMiniAppsRowId);
}
bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
@@ -280,7 +334,8 @@ bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
return false;
}
auto PrivacyExceptionsBoxController::preparePremiumsRowList()
auto PrivacyExceptionsBoxController::prepareSpecialRowList(
SpecialRowType type)
-> object_ptr<Ui::RpWidget> {
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
const auto container = result.data();
@@ -291,30 +346,39 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
_typesDelegate = lifetime.make_state<PeerListContentDelegateSimple>();
const auto controller = lifetime.make_state<TypesController>(
&session(),
_selected.premiums);
type);
const auto content = result->add(object_ptr<PeerListContent>(
container,
controller));
_typesDelegate->setContent(content);
controller->setDelegate(_typesDelegate);
const auto selectType = [&](PeerListRowId id) {
const auto row = _typesDelegate->peerListFindRow(id);
if (row) {
content->changeCheckState(row, true, anim::type::instant);
this->delegate()->peerListSetForeignRowChecked(
row,
true,
anim::type::instant);
}
};
if (_selected.premiums) {
const auto row = _typesDelegate->peerListFindRow(kPremiumsRowId);
Assert(row != nullptr);
content->changeCheckState(row, true, anim::type::instant);
this->delegate()->peerListSetForeignRowChecked(
row,
true,
anim::type::instant);
selectType(kPremiumsRowId);
} else if (_selected.miniapps) {
selectType(kMiniAppsRowId);
}
container->add(CreatePeerListSectionSubtitle(
container,
tr::lng_edit_privacy_users_and_groups()));
controller->premiumsChanges(
) | rpl::start_with_next([=](bool premiums) {
_selected.premiums = premiums;
controller->specialChanges(
) | rpl::start_with_next([=](bool chosen) {
if (type == SpecialRowType::Premiums) {
_selected.premiums = chosen;
} else {
_selected.miniapps = chosen;
}
}, lifetime);
controller->rowSelectionChanges(
@@ -329,6 +393,8 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
if (itemId == kPremiumsRowId) {
_selected.premiums = false;
} else if (itemId == kMiniAppsRowId) {
_selected.miniapps = false;
}
_typesDelegate->peerListSetRowChecked(row, false);
}
@@ -337,10 +403,14 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
return result;
}
[[nodiscard]] bool PrivacyExceptionsBoxController::premiumsSelected() const {
bool PrivacyExceptionsBoxController::premiumsSelected() const {
return _selected.premiums;
}
bool PrivacyExceptionsBoxController::miniAppsSelected() const {
return _selected.miniapps;
}
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto peer = row->peer();
@@ -412,6 +482,11 @@ EditPrivacyBox::EditPrivacyBox(
// If we switch from Everyone to Contacts or Nobody suggest Premiums.
_value.always.premiums = true;
}
if (_controller->allowMiniAppsToggle(Exception::Always)
&& _value.option == Option::Everyone) {
// If we switch from Everyone to Contacts or Nobody suggest MiniApps.
_value.always.miniapps = true;
}
}
void EditPrivacyBox::prepare() {
@@ -427,12 +502,18 @@ void EditPrivacyBox::editExceptions(
&_window->session(),
_controller->exceptionBoxTitle(exception),
exceptions(exception),
_controller->allowPremiumsToggle(exception));
(_controller->allowPremiumsToggle(exception)
? SpecialRowType::Premiums
: _controller->allowMiniAppsToggle(exception)
? SpecialRowType::MiniApps
: std::optional<SpecialRowType>()));
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
exceptions(exception).peers = box->collectSelectedRows();
exceptions(exception).premiums = controller->premiumsSelected();
auto &setTo = exceptions(exception);
setTo.peers = box->collectSelectedRows();
setTo.premiums = controller->premiumsSelected();
setTo.miniapps = controller->miniAppsSelected();
const auto type = [&] {
switch (exception) {
case Exception::Always: return Exception::Never;
@@ -440,11 +521,17 @@ void EditPrivacyBox::editExceptions(
}
Unexpected("Invalid exception value.");
}();
auto &removeFrom = exceptions(type).peers;
auto &removeFrom = exceptions(type);
for (const auto peer : exceptions(exception).peers) {
removeFrom.erase(
ranges::remove(removeFrom, peer),
end(removeFrom));
removeFrom.peers.erase(
ranges::remove(removeFrom.peers, peer),
end(removeFrom.peers));
}
if (setTo.premiums) {
removeFrom.premiums = false;
}
if (setTo.miniapps) {
removeFrom.miniapps = false;
}
done();
box->closeBox();
@@ -566,14 +653,21 @@ void EditPrivacyBox::setupContent() {
lt_count,
count)
: tr::lng_edit_privacy_exceptions_add(tr::now);
return !value.premiums
? users
: !count
? tr::lng_edit_privacy_premium(tr::now)
: tr::lng_edit_privacy_exceptions_premium_and(
tr::now,
lt_users,
users);
return value.premiums
? (!count
? tr::lng_edit_privacy_premium(tr::now)
: tr::lng_edit_privacy_exceptions_premium_and(
tr::now,
lt_users,
users))
: value.miniapps
? (!count
? tr::lng_edit_privacy_miniapps(tr::now)
: tr::lng_edit_privacy_exceptions_miniapps_and(
tr::now,
lt_users,
users))
: users;
});
_controller->handleExceptionsChange(
exception,

View File

@@ -61,6 +61,10 @@ public:
Exception exception) const {
return false;
}
[[nodiscard]] virtual bool allowMiniAppsToggle(
Exception exception) const {
return false;
}
virtual void handleExceptionsChange(
Exception exception,
rpl::producer<int> value) {

View File

@@ -7,22 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/filters/edit_filter_box.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "boxes/filters/edit_filter_chats_list.h"
#include "boxes/filters/edit_filter_chats_preview.h"
#include "boxes/filters/edit_filter_links.h"
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_options.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/effects/panel_animation.h"
#include "ui/filter_icons.h"
#include "ui/filter_icon_panel.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_channel.h"
#include "data/data_chat_filters.h"
#include "data/data_peer.h"
@@ -30,22 +24,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "settings/settings_common.h"
#include "base/event_filter.h"
#include "lang/lang_keys.h"
#include "history/history.h"
#include "info/userpic/info_userpic_color_circle_button.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "settings/settings_common.h"
#include "ui/chat/chats_filter_tag.h"
#include "ui/effects/animation_value_f.h"
#include "ui/effects/animations.h"
#include "ui/effects/panel_animation.h"
#include "ui/empty_userpic.h"
#include "ui/filter_icon_panel.h"
#include "ui/filter_icons.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/slide_wrap.h"
#include "window/window_controller.h"
#include "apiwrap.h"
#include "window/window_session_controller.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_dialogs.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
#include "styles/style_info_userpic_builder.h"
namespace {
@@ -336,6 +341,8 @@ void EditFilterBox(
const Data::ChatFilter &data,
Fn<void(Data::ChatFilter)> next)> saveAnd) {
using namespace rpl::mappers;
constexpr auto kColorsCount = 8;
constexpr auto kNoTag = kColorsCount - 1;
struct State {
rpl::variable<Data::ChatFilter> rules;
@@ -343,6 +350,7 @@ void EditFilterBox(
rpl::variable<bool> hasLinks;
rpl::variable<bool> chatlist;
rpl::variable<bool> creating;
rpl::variable<int> colorIndex;
};
const auto owner = &window->session().data();
const auto state = box->lifetime().make_state<State>(State{
@@ -350,6 +358,7 @@ void EditFilterBox(
.chatlist = filter.chatlist(),
.creating = filter.title().isEmpty(),
});
state->colorIndex = filter.colorIndex().value_or(kNoTag);
state->links = owner->chatsFilters().chatlistLinks(filter.id()),
state->hasLinks = state->links.value() | rpl::map([=](const auto &v) {
return !v.empty();
@@ -504,6 +513,164 @@ void EditFilterBox(
Ui::AddDividerText(excludeInner, tr::lng_filters_exclude_about());
Ui::AddSkip(excludeInner);
{
const auto wrap = content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
content,
object_ptr<Ui::VerticalLayout>(content)));
const auto colors = wrap->entity();
const auto session = &window->session();
wrap->toggleOn(
rpl::combine(
session->premiumPossibleValue(),
session->data().chatsFilters().tagsEnabledValue(),
Data::AmPremiumValue(session)
) | rpl::map([=] (bool possible, bool tagsEnabled, bool premium) {
return possible && (tagsEnabled || !premium);
}),
anim::type::instant);
const auto isPremium = session->premium();
const auto title = Ui::AddSubsectionTitle(
colors,
tr::lng_filters_tag_color_subtitle());
const auto preview = Ui::CreateChild<Ui::RpWidget>(colors);
title->geometryValue(
) | rpl::start_with_next([=](const QRect &r) {
const auto h = st::normalFont->height;
preview->setGeometry(
colors->x(),
r.y() + (r.height() - h) / 2 + st::lineWidth,
colors->width(),
h);
}, preview->lifetime());
const auto previewTag = preview->lifetime().make_state<QImage>();
const auto previewAlpha = preview->lifetime().make_state<float64>(1);
preview->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(preview);
p.setOpacity(*previewAlpha);
const auto size = previewTag->size() / style::DevicePixelRatio();
const auto rect = QRect(
preview->width() - size.width() - st::boxRowPadding.right(),
(st::normalFont->height - size.height()) / 2,
size.width(),
size.height());
p.drawImage(rect.topLeft(), *previewTag);
if (p.opacity() < 1) {
p.setOpacity(1. - p.opacity());
p.setFont(st::normalFont);
p.setPen(st::windowSubTextFg);
p.drawText(
preview->rect() - st::boxRowPadding,
tr::lng_filters_tag_color_no(tr::now),
style::al_right);
}
}, preview->lifetime());
const auto side = st::userpicBuilderEmojiAccentColorSize;
const auto line = colors->add(
Ui::CreateSkipWidget(colors, side),
st::boxRowPadding);
auto buttons = std::vector<not_null<UserpicBuilder::CircleButton*>>();
const auto animation
= line->lifetime().make_state<Ui::Animations::Simple>();
const auto palette = [](int i) {
return Ui::EmptyUserpic::UserpicColor(i).color2;
};
name->changes() | rpl::start_with_next([=] {
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
palette(state->colorIndex.current())->c,
false);
preview->update();
}, preview->lifetime());
for (auto i = 0; i < kColorsCount; ++i) {
const auto button = Ui::CreateChild<UserpicBuilder::CircleButton>(
line);
button->resize(side, side);
const auto progress = isPremium
? (state->colorIndex.current() == i)
: (i == kNoTag);
button->setSelectedProgress(progress);
const auto color = palette(i);
button->setBrush(color);
if (progress == 1) {
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
color->c,
false);
if (i == kNoTag) {
*previewAlpha = 0.;
}
}
buttons.push_back(button);
}
for (auto i = 0; i < kColorsCount; ++i) {
const auto &button = buttons[i];
button->setClickedCallback([=] {
const auto was = state->colorIndex.current();
const auto now = i;
if (was != now) {
const auto c1 = palette(was);
const auto c2 = palette(now);
const auto a1 = (was == kNoTag) ? 0. : 1.;
const auto a2 = (now == kNoTag) ? 0. : 1.;
animation->stop();
animation->start([=](float64 progress) {
if (was >= 0) {
buttons[was]->setSelectedProgress(1. - progress);
}
buttons[now]->setSelectedProgress(progress);
*previewTag = Ui::ChatsFilterTag(
name->getLastText().toUpper(),
anim::color(c1, c2, progress),
false);
*previewAlpha = anim::interpolateF(a1, a2, progress);
preview->update();
}, 0., 1., st::universalDuration);
}
state->colorIndex = now;
});
if (!session->premium()) {
button->setClickedCallback([w = window] {
ShowPremiumPreviewToBuy(w, PremiumFeature::FilterTags);
});
}
}
line->sizeValue() | rpl::start_with_next([=](const QSize &size) {
const auto totalWidth = buttons.size() * side;
const auto spacing = (size.width() - totalWidth)
/ (buttons.size() - 1);
for (auto i = 0; i < kColorsCount; ++i) {
const auto &button = buttons[i];
button->moveToLeft(i * (side + spacing), 0);
}
}, line->lifetime());
{
const auto last = buttons.back();
const auto icon = Ui::CreateChild<Ui::RpWidget>(last);
icon->resize(side, side);
icon->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(icon);
(session->premium()
? st::windowFilterSmallRemove.icon
: st::historySendDisabledIcon).paintInCenter(
p,
QRectF(icon->rect()),
st::historyPeerUserpicFg->c);
}, icon->lifetime());
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
last->setBrush(st::historyPeerArchiveUserpicBg);
}
Ui::AddSkip(colors);
Ui::AddSkip(colors);
Ui::AddDividerText(colors, tr::lng_filters_tag_color_about());
Ui::AddSkip(colors);
}
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
const auto title = name->getLastText().trimmed();
const auto rules = data->current();
@@ -520,7 +687,11 @@ void EditFilterBox(
window->window().showToast(tr::lng_filters_default(tr::now));
return {};
}
return rules.withTitle(title);
const auto rawColorIndex = state->colorIndex.current();
const auto colorIndex = (rawColorIndex >= kNoTag
? std::nullopt
: std::make_optional(rawColorIndex));
return rules.withTitle(title).withColorIndex(colorIndex);
};
Ui::AddSubsectionTitle(

View File

@@ -7,12 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/layers/generic_box.h"
namespace Window {
class SessionController;
} // namespace Window
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Data {
class ChatFilter;
} // namespace Data

View File

@@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/filters/edit_filter_chats_list.h"
#include "data/data_chat_filters.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "history/history.h"
#include "window/window_session_controller.h"
#include "lang/lang_keys.h"
@@ -125,7 +127,15 @@ Flag TypeRow::flag() const {
}
ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
if (peer()->isSelf()) {
auto filters = QStringList();
for (const auto &filter : history->owner().chatsFilters().list()) {
if (filter.contains(history) && filter.id()) {
filters << filter.title();
}
}
if (!filters.isEmpty()) {
setCustomStatus(filters.join(", "));
} else if (peer()->isSelf()) {
setCustomStatus(tr::lng_saved_forward_here(tr::now));
}
}

View File

@@ -47,6 +47,7 @@ void GiftCreditsBox(
const auto content = box->setPinnedToTopContent(
object_ptr<Ui::VerticalLayout>(box));
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto &stUser = st::premiumGiftsUserpicButton;
@@ -58,39 +59,19 @@ void GiftCreditsBox(
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWidth - stUser.photoSize,
stUser.photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(stUser.photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
Settings::AddMiniStars(
content,
Ui::CreateChild<Ui::RpWidget>(content),
stUser.photoSize,
box->width(),
2.);
{
Ui::AddSkip(content);
const auto arrow = Ui::Text::SingleCustomEmoji(
peer->owner().customEmojiManager().registerInternalEmoji(
st::topicButtonArrow,
st::channelEarnLearnArrowMargins,
false));
true));
auto link = tr::lng_credits_box_history_entry_gift_about_link(
lt_emoji,
rpl::single(arrow),
@@ -122,8 +103,10 @@ void GiftCreditsBox(
Main::MakeSessionShow(box->uiShow(), &peer->session()),
box->verticalLayout(),
peer,
0,
[=] { gifted(); box->uiShow()->hideLayer(); });
StarsAmount(),
[=] { gifted(); box->uiShow()->hideLayer(); },
tr::lng_credits_summary_options_subtitle(),
{});
box->setPinnedToBottomContent(
object_ptr<Ui::VerticalLayout>(box));

View File

@@ -237,7 +237,7 @@ void AddTableRow(
valueMargins);
}
object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
[[nodiscard]] object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
const Data::CreditsHistoryEntry &entry,
@@ -255,8 +255,8 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
auto star = session->data().customEmojiManager().creditsEmoji();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
rpl::single(
star.append(' ' + Lang::FormatCountDecimal(entry.credits))),
rpl::single(star.append(
' ' + Lang::FormatStarsAmountDecimal(entry.credits))),
st::giveawayGiftCodeValue,
st::defaultPopupMenu,
std::move(makeContext));
@@ -266,7 +266,7 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
raw,
tr::lng_gift_sell_small(
lt_count_decimal,
rpl::single(entry.convertStars * 1.)),
rpl::single(entry.starsConverted * 1.)),
st::starGiftSmallButton)
: nullptr;
if (convert) {
@@ -302,6 +302,62 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
return result;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeVisibilityTableValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
bool savedToProfile,
Fn<void(bool)> toggleVisibility) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
(savedToProfile
? tr::lng_gift_visibility_shown()
: tr::lng_gift_visibility_hidden()),
st::giveawayGiftCodeValue,
st::defaultPopupMenu);
const auto toggle = Ui::CreateChild<Ui::RoundButton>(
raw,
(savedToProfile
? tr::lng_gift_visibility_hide()
: tr::lng_gift_visibility_show()),
st::starGiftSmallButton);
toggle->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
toggle->setClickedCallback([=] {
toggleVisibility(!savedToProfile);
});
rpl::combine(
raw->widthValue(),
toggle->widthValue()
) | rpl::start_with_next([=](int width, int toggleWidth) {
const auto toggleSkip = toggleWidth
? (st::normalFont->spacew + toggleWidth)
: 0;
label->resizeToNaturalWidth(width - toggleSkip);
label->moveToLeft(0, 0, width);
if (toggle) {
toggle->moveToLeft(
label->width() + st::normalFont->spacew,
(st::giveawayGiftCodeValue.style.font->ascent
- st::starGiftSmallButton.style.font->ascent),
width);
}
}, label->lifetime());
label->heightValue() | rpl::start_with_next([=](int height) {
raw->resize(
raw->width(),
height + st::giveawayGiftCodeValueMargin.bottom());
}, raw->lifetime());
label->setAttribute(Qt::WA_TransparentForMouseEvents);
return result;
}
not_null<Ui::FlatLabel*> AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
@@ -1035,6 +1091,7 @@ void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry,
Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars) {
auto table = container->add(
object_ptr<Ui::TableLayout>(
@@ -1044,7 +1101,8 @@ void AddStarGiftTable(
const auto peerId = PeerId(entry.barePeerId);
const auto session = &controller->session();
if (peerId) {
const auto withSendButton = entry.in;
const auto user = session->data().peer(peerId)->asUser();
const auto withSendButton = entry.in && user && !user->isBot();
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer_in(),
@@ -1071,9 +1129,15 @@ void AddStarGiftTable(
rpl::single(Ui::Text::WithEntities(
langDateTime(entry.lastSaleDate))));
}
if (!entry.date.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
}
const auto marginWithButton = st::giveawayGiftCodeValueMargin
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
{
const auto margin = st::giveawayGiftCodeValueMargin
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
AddTableRow(
table,
tr::lng_gift_link_label_value(),
@@ -1082,13 +1146,18 @@ void AddStarGiftTable(
controller,
entry,
std::move(convertToStars)),
margin);
marginWithButton);
}
if (!entry.date.isNull()) {
if (toggleVisibility) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
tr::lng_gift_visibility(),
MakeVisibilityTableValue(
table,
controller,
entry.savedToProfile,
std::move(toggleVisibility)),
marginWithButton);
}
if (entry.limitedCount > 0) {
auto amount = rpl::single(TextWithEntities{
@@ -1144,12 +1213,51 @@ void AddCreditsHistoryEntryTable(
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
const auto actorId = PeerId(entry.bareActorId);
const auto starrefRecipientId = PeerId(entry.starrefRecipientId);
const auto session = &controller->session();
if (peerId) {
auto text = entry.in
if (entry.starrefCommission) {
if (entry.starrefAmount) {
AddTableRow(
table,
tr::lng_star_ref_commission_title(),
rpl::single(TextWithEntities{
QString::number(entry.starrefCommission / 10.) + '%' }));
} else {
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_credits_box_history_entry_reason_star_ref(
Ui::Text::WithEntities));
}
}
if (starrefRecipientId && entry.starrefAmount) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_affiliate(),
controller,
starrefRecipientId);
}
if (peerId && entry.starrefCommission) {
AddTableRow(
table,
(entry.starrefAmount
? tr::lng_credits_box_history_entry_referred
: tr::lng_credits_box_history_entry_miniapp)(),
controller,
peerId);
}
if (actorId || (!entry.starrefCommission && peerId)) {
auto text = entry.starrefCommission
? tr::lng_credits_box_history_entry_referred()
: entry.in
? tr::lng_credits_box_history_entry_peer_in()
: tr::lng_credits_box_history_entry_peer();
AddTableRow(table, std::move(text), controller, peerId);
AddTableRow(
table,
std::move(text),
controller,
actorId ? actorId : peerId);
}
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
const auto peer = session->data().peer(peerId);
@@ -1223,7 +1331,7 @@ void AddCreditsHistoryEntryTable(
tr::lng_gift_link_label_gift(),
tr::lng_gift_stars_title(
lt_count,
rpl::single(float64(entry.credits)),
rpl::single(entry.credits.value()),
Ui::Text::RichLangValue));
}
{
@@ -1241,8 +1349,15 @@ void AddCreditsHistoryEntryTable(
}));
}
}
if (!entry.subscriptionUntil.isNull() && !entry.title.isEmpty()) {
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_credits_box_history_entry_subscription(
Ui::Text::WithEntities));
}
if (!entry.id.isEmpty()) {
constexpr auto kOneLineCount = 18;
constexpr auto kOneLineCount = 24;
const auto oneLine = entry.id.length() <= kOneLineCount;
auto label = object_ptr<Ui::FlatLabel>(
table,
@@ -1306,11 +1421,24 @@ void AddSubscriptionEntryTable(
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(s.barePeerId);
const auto user = peerIsUser(peerId)
? controller->session().data().peer(peerId)->asUser()
: nullptr;
AddTableRow(
table,
tr::lng_credits_subscription_row_to(),
(!s.title.isEmpty() && user && user->botInfo)
? tr::lng_credits_subscription_row_to_bot()
: (!s.title.isEmpty() && user && !user->botInfo)
? tr::lng_credits_subscription_row_to_business()
: tr::lng_credits_subscription_row_to(),
controller,
peerId);
if (!s.title.isEmpty()) {
AddTableRow(
table,
tr::lng_credits_subscription_row_to(),
rpl::single(Ui::Text::WithEntities(s.title)));
}
if (!s.until.isNull()) {
if (s.subscription.period > 0) {
const auto subscribed = s.until.addSecs(-s.subscription.period);

View File

@@ -58,6 +58,7 @@ void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry,
Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars);
void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,

View File

@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/local_storage_box.h"
#include "boxes/abstract_box.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/labels.h"
@@ -21,8 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/cache/storage_cache_database.h"
#include "data/data_session.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -240,7 +239,8 @@ int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
}
void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
if (!_progress || true) {
#if 0 // not used
if (!_progress) {
return;
}
auto p = QPainter(this);
@@ -254,6 +254,7 @@ void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
st::proxyCheckingPosition.y() + bottom
},
width());
#endif
}
QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {
@@ -280,19 +281,19 @@ LocalStorageBox::LocalStorageBox(
_timeLimit = settings.totalTimeLimit;
}
void LocalStorageBox::Show(not_null<::Main::Session*> session) {
void LocalStorageBox::Show(not_null<Window::SessionController*> controller) {
auto shared = std::make_shared<object_ptr<LocalStorageBox>>(
Box<LocalStorageBox>(session, CreateTag()));
Box<LocalStorageBox>(&controller->session(), CreateTag()));
const auto weak = shared->data();
rpl::combine(
session->data().cache().statsOnMain(),
session->data().cacheBigFile().statsOnMain()
controller->session().data().cache().statsOnMain(),
controller->session().data().cacheBigFile().statsOnMain()
) | rpl::start_with_next([=](
Database::Stats &&stats,
Database::Stats &&statsBig) {
weak->update(std::move(stats), std::move(statsBig));
if (auto &strong = *shared) {
Ui::show(std::move(strong));
controller->uiShow()->show(std::move(strong));
}
}, weak->lifetime());
}

View File

@@ -14,6 +14,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Window {
class SessionController;
} // namespace Window
namespace Storage {
namespace Cache {
class Database;
@@ -40,7 +44,7 @@ public:
not_null<Main::Session*> session,
CreateTag);
static void Show(not_null<Main::Session*> session);
static void Show(not_null<Window::SessionController*> controller);
protected:
void prepare() override;

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_chat_filters.h"
#include "data/data_chat_participant_status.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
@@ -86,19 +87,35 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
return result;
}
[[nodiscard]] rpl::producer<int> MessagesCountValue(
[[nodiscard]] rpl::producer<base::flat_map<PeerId, int>> MessagesCountValue(
not_null<History*> history,
not_null<PeerData*> from) {
std::vector<not_null<PeerData*>> from) {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
auto search = lifetime.make_state<Api::MessagesSearch>(history);
consumer.put_next(0);
search->messagesFounds(
) | rpl::start_with_next([=](const Api::FoundMessages &found) {
consumer.put_next_copy(found.total);
}, lifetime);
search->searchMessages({ .from = from });
struct State final {
base::flat_map<PeerId, int> messagesCounts;
int index = 0;
rpl::lifetime apiLifetime;
};
const auto search = lifetime.make_state<Api::MessagesSearch>(history);
const auto state = lifetime.make_state<State>();
const auto send = [=](auto repeat) -> void {
if (state->index >= from.size()) {
consumer.put_next_copy(state->messagesCounts);
return;
}
const auto peer = from[state->index];
const auto peerId = peer->id;
state->apiLifetime = search->messagesFounds(
) | rpl::start_with_next([=](const Api::FoundMessages &found) {
state->messagesCounts[peerId] = found.total;
state->index++;
repeat(repeat);
});
search->searchMessages({ .from = peer });
};
consumer.put_next({});
send(send);
return lifetime;
};
@@ -273,15 +290,50 @@ void CreateModerateMessagesBox(
false,
st::defaultBoxCheckbox),
st::boxRowPadding + buttonPadding);
if (isSingle) {
const auto history = items.front()->history();
const auto history = items.front()->history();
auto messagesCounts = MessagesCountValue(history, participants);
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{
.messagesCounts = rpl::duplicate(messagesCounts),
.participants = participants,
});
Ui::AddExpandablePeerList(deleteAll, controller, inner);
{
tr::lng_selected_delete_sure(
lt_count,
rpl::combine(
MessagesCountValue(history, participants.front()),
deleteAll->checkedValue()
) | rpl::map([s = items.size()](int all, bool checked) {
return float64((checked && all) ? all : s);
std::move(messagesCounts),
isSingle
? deleteAll->checkedValue()
: rpl::merge(
controller->toggleRequestsFromInner.events(),
controller->checkAllRequests.events())
) | rpl::map([=, s = items.size()](const auto &map, bool c) {
const auto checked = (isSingle && !c)
? Participants()
: controller->collectRequests
? controller->collectRequests()
: Participants();
auto result = 0;
for (const auto &[peerId, count] : map) {
for (const auto &peer : checked) {
if (peer->id == peerId) {
result += count;
break;
}
}
}
for (const auto &item : items) {
for (const auto &peer : checked) {
if (peer->id == item->from()->id) {
result--;
break;
}
}
result++;
}
return float64(result);
})
) | rpl::start_with_next([=](const QString &text) {
title->setText(text);
@@ -289,10 +341,6 @@ void CreateModerateMessagesBox(
- rect::m::sum::h(st::boxRowPadding));
}, title->lifetime());
}
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{ .participants = participants });
Ui::AddExpandablePeerList(deleteAll, controller, inner);
handleSubmition(deleteAll);
handleConfirmation(deleteAll, controller, [=](
@@ -512,6 +560,7 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
const auto container = box->verticalLayout();
const auto maybeUser = peer->asUser();
const auto isBot = maybeUser && maybeUser->isBot();
Ui::AddSkip(container);
Ui::AddSkip(container);
@@ -595,7 +644,7 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
}();
const auto maybeBotCheckbox = [&]() -> Ui::Checkbox* {
if (!maybeUser || !maybeUser->isBot()) {
if (!isBot) {
return nullptr;
}
Ui::AddSkip(container);
@@ -608,6 +657,40 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
st::defaultBoxCheckbox));
}();
const auto removeFromChatsFilters = [=](
not_null<History*> history) -> std::vector<FilterId> {
auto result = std::vector<FilterId>();
for (const auto &filter : peer->owner().chatsFilters().list()) {
if (filter.withoutAlways(history) != filter) {
result.push_back(filter.id());
}
}
return result;
};
const auto maybeChatsFiltersCheckbox = [&]() -> Ui::Checkbox* {
const auto history = (isBot || !maybeUser)
? peer->owner().history(peer).get()
: nullptr;
if (!history || removeFromChatsFilters(history).empty()) {
return nullptr;
}
Ui::AddSkip(container);
Ui::AddSkip(container);
return box->addRow(
object_ptr<Ui::Checkbox>(
container,
(maybeBotCheckbox
? tr::lng_filters_checkbox_remove_bot
: (peer->isChannel() && !peer->isMegagroup())
? tr::lng_filters_checkbox_remove_channel
: tr::lng_filters_checkbox_remove_group)(
tr::now,
Ui::Text::WithEntities),
false,
st::defaultBoxCheckbox));
}();
Ui::AddSkip(container);
auto buttonText = maybeUser
@@ -622,10 +705,35 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
box->addButton(std::move(buttonText), [=] {
const auto revoke = maybeCheckbox && maybeCheckbox->checked();
const auto stopBot = maybeBotCheckbox && maybeBotCheckbox->checked();
const auto removeFromChats = maybeChatsFiltersCheckbox
&& maybeChatsFiltersCheckbox->checked();
Core::App().closeChatFromWindows(peer);
if (stopBot) {
peer->session().api().blockedPeers().block(peer);
}
if (removeFromChats) {
const auto history = peer->owner().history(peer).get();
const auto removeFrom = removeFromChatsFilters(history);
for (const auto &filter : peer->owner().chatsFilters().list()) {
if (!ranges::contains(removeFrom, filter.id())) {
continue;
}
const auto result = filter.withoutAlways(history);
if (result == filter) {
continue;
}
const auto tl = result.tl();
peer->owner().chatsFilters().apply(MTP_updateDialogFilter(
MTP_flags(MTPDupdateDialogFilter::Flag::f_filter),
MTP_int(filter.id()),
tl));
peer->session().api().request(MTPmessages_UpdateDialogFilter(
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
MTP_int(filter.id()),
tl
)).send();
}
}
// Don't delete old history by default,
// because Android app doesn't.
//

View File

@@ -120,6 +120,9 @@ void PeerListBox::createMultiSelect() {
content()->submitted();
});
_select->entity()->setQueryChangedCallback([=](const QString &query) {
if (_customQueryChangedCallback) {
_customQueryChangedCallback(query);
}
searchQueryChanged(query);
});
_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
@@ -138,6 +141,10 @@ void PeerListBox::createMultiSelect() {
_select->moveToLeft(0, 0);
}
void PeerListBox::appendQueryChangedCallback(Fn<void(QString)> callback) {
_customQueryChangedCallback = std::move(callback);
}
void PeerListBox::setAddedTopScrollSkip(int skip) {
_addedTopScrollSkip = skip;
_scrollBottomFixed = false;
@@ -217,16 +224,7 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
void PeerListBox::searchQueryChanged(const QString &query) {
scrollToY(0);
const auto isEmpty = content()->searchQueryChanged(query);
if (_specialTabsMode.enabled) {
_specialTabsMode.searchIsActive = !isEmpty;
if (_specialTabsMode.searchIsActive) {
_specialTabsMode.topSkip = _addedTopScrollSkip;
setAddedTopScrollSkip(0);
} else {
setAddedTopScrollSkip(_specialTabsMode.topSkip);
}
}
content()->searchQueryChanged(query);
}
void PeerListBox::resizeEvent(QResizeEvent *e) {
@@ -483,7 +481,7 @@ void PeerListBox::addSelectItem(
void PeerListBox::addSelectItem(
uint64 itemId,
const QString &text,
Ui::MultiSelect::PaintRoundImage paintUserpic,
PaintRoundImageCallback paintUserpic,
anim::type animated) {
if (!_select) {
createMultiSelect();
@@ -558,13 +556,8 @@ rpl::producer<int> PeerListBox::multiSelectHeightValue() const {
return _select ? _select->heightValue() : rpl::single(0);
}
void PeerListBox::setSpecialTabMode(bool value) {
content()->setIgnoreHiddenRowsOnSearch(value);
if (value) {
_specialTabsMode.enabled = true;
} else {
_specialTabsMode = {};
}
rpl::producer<> PeerListBox::noSearchSubmits() const {
return content()->noSearchSubmits();
}
PeerListRow::PeerListRow(not_null<PeerData*> peer)
@@ -1288,6 +1281,9 @@ void PeerListContent::clearAllContent() {
= _normalizedSearchQuery
= _mentionHighlight
= QString();
if (_controller->hasComplexSearch()) {
_controller->search(QString());
}
}
void PeerListContent::convertRowToSearchResult(not_null<PeerListRow*> row) {
@@ -2076,7 +2072,7 @@ void PeerListContent::checkScrollForPreload() {
}
}
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
void PeerListContent::searchQueryChanged(QString query) {
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
const auto normalizedQuery = searchWordsList.join(' ');
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
@@ -2133,7 +2129,6 @@ PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
}
refreshRows();
}
return _normalizedSearchQuery.isEmpty();
}
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
@@ -2208,6 +2203,9 @@ bool PeerListContent::submitted() {
_controller->rowClicked(row);
return true;
}
} else {
_noSearchSubmits.fire({});
return true;
}
return false;
}

View File

@@ -652,8 +652,7 @@ public:
[[nodiscard]] bool hasPressed() const;
void clearSelection();
using IsEmpty = bool;
IsEmpty searchQueryChanged(QString query);
void searchQueryChanged(QString query);
bool submitted();
PeerListRowId updateFromParentDrag(QPoint globalPosition);
@@ -713,7 +712,7 @@ public:
update();
}
std::unique_ptr<PeerListState> saveState() const;
[[nodiscard]] std::unique_ptr<PeerListState> saveState() const;
void restoreState(std::unique_ptr<PeerListState> state);
void showRowMenu(
@@ -721,10 +720,14 @@ public:
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
auto scrollToRequests() const {
[[nodiscard]] auto scrollToRequests() const {
return _scrollToRequests.events();
}
[[nodiscard]] auto noSearchSubmits() const {
return _noSearchSubmits.events();
}
~PeerListContent();
protected:
@@ -891,6 +894,8 @@ private:
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
rpl::event_stream<> _noSearchSubmits;
std::vector<std::unique_ptr<PeerListRow>> _searchRows;
base::Timer _repaintByStatus;
base::unique_qptr<Ui::PopupMenu> _contextMenu;
@@ -1107,8 +1112,7 @@ public:
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
void setSpecialTabMode(bool value);
[[nodiscard]] rpl::producer<> noSearchSubmits() const;
void peerListSetTitle(rpl::producer<QString> title) override {
setTitle(std::move(title));
@@ -1133,6 +1137,8 @@ public:
void showFinished() override;
void appendQueryChangedCallback(Fn<void(QString)>);
protected:
void prepare() override;
void setInnerFocus() override;
@@ -1170,16 +1176,10 @@ private:
object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
const std::shared_ptr<Main::SessionShow> _show;
Fn<void(QString)> _customQueryChangedCallback;
std::unique_ptr<PeerListController> _controller;
Fn<void(PeerListBox*)> _init;
bool _scrollBottomFixed = false;
int _addedTopScrollSkip = 0;
struct SpecialTabsMode final {
bool enabled = false;
bool searchIsActive = false;
int topSkip = 0;
};
SpecialTabsMode _specialTabsMode;
};

View File

@@ -1065,6 +1065,11 @@ std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
return nullptr;
}
std::unique_ptr<PeerListRow> ChooseTopicBoxController::MakeRow(
not_null<Data::ForumTopic*> topic) {
return std::make_unique<Row>(topic);
}
auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
-> std::unique_ptr<Row> {
const auto skip = _filter && !_filter(topic);

View File

@@ -335,6 +335,9 @@ public:
void loadMoreRows() override;
std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override;
[[nodiscard]] static std::unique_ptr<PeerListRow> MakeRow(
not_null<Data::ForumTopic*> topic);
private:
class Row final : public PeerListRow {
public:

View File

@@ -0,0 +1,388 @@
/*
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 "boxes/peer_list_widgets.h"
#include "ui/painter.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_boxes.h"
namespace {
using State = std::unique_ptr<PeerListState>;
} // namespace
PeerListWidgets::PeerListWidgets(
not_null<Ui::RpWidget*> parent,
not_null<PeerListController*> controller)
: Ui::RpWidget(parent)
, _controller(controller)
, _st(controller->computeListSt()) {
_content = base::make_unique_q<Ui::VerticalLayout>(this);
parent->sizeValue() | rpl::start_with_next([this](const QSize &size) {
_content->resizeToWidth(size.width());
resize(size.width(), _content->height());
}, lifetime());
}
crl::time PeerListWidgets::paintRow(
Painter &p,
crl::time now,
bool selected,
not_null<PeerListRow*> row) {
const auto &st = row->computeSt(_st.item);
row->lazyInitialize(st);
const auto outerWidth = _content->width();
const auto w = outerWidth;
auto refreshStatusAt = row->refreshStatusTime();
if (refreshStatusAt > 0 && now >= refreshStatusAt) {
row->refreshStatus();
refreshStatusAt = row->refreshStatusTime();
}
const auto refreshStatusIn = (refreshStatusAt > 0)
? std::max(refreshStatusAt - now, crl::time(1))
: 0;
row->paintUserpic(
p,
st,
st.photoPosition.x(),
st.photoPosition.y(),
outerWidth);
p.setPen(st::contactsNameFg);
const auto skipRight = st.photoPosition.x();
const auto rightActionSize = row->rightActionSize();
const auto rightActionMargins = rightActionSize.isEmpty()
? QMargins()
: row->rightActionMargins();
const auto &name = row->name();
const auto namePosition = st.namePosition;
const auto namex = namePosition.x();
const auto namey = namePosition.y();
auto namew = outerWidth - namex - skipRight;
if (!rightActionSize.isEmpty()
&& (namey < rightActionMargins.top() + rightActionSize.height())
&& (namey + st.nameStyle.font->height
> rightActionMargins.top())) {
namew -= rightActionMargins.left()
+ rightActionSize.width()
+ rightActionMargins.right()
- skipRight;
}
const auto statusx = st.statusPosition.x();
const auto statusy = st.statusPosition.y();
auto statusw = outerWidth - statusx - skipRight;
if (!rightActionSize.isEmpty()
&& (statusy < rightActionMargins.top() + rightActionSize.height())
&& (statusy + st::contactsStatusFont->height
> rightActionMargins.top())) {
statusw -= rightActionMargins.left()
+ rightActionSize.width()
+ rightActionMargins.right()
- skipRight;
}
namew -= row->paintNameIconGetWidth(
p,
[=] { updateRow(row); },
now,
namex,
namey,
name.maxWidth(),
namew,
w,
selected);
auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio();
p.setPen(anim::pen(st.nameFg, st.nameFgChecked, nameCheckedRatio));
name.drawLeftElided(p, namex, namey, namew, w);
p.setFont(st::contactsStatusFont);
row->paintStatusText(p, st, statusx, statusy, statusw, w, selected);
row->elementsPaint(p, outerWidth, selected, 0);
return refreshStatusIn;
}
void PeerListWidgets::appendRow(std::unique_ptr<PeerListRow> row) {
Expects(row != nullptr);
if (_rowsById.find(row->id()) == _rowsById.cend()) {
const auto raw = row.get();
const auto &st = raw->computeSt(_st.item);
raw->setAbsoluteIndex(_rows.size());
_rows.push_back(std::move(row));
const auto widget = _content->add(
object_ptr<Ui::AbstractButton>::fromRaw(
Ui::CreateSimpleSettingsButton(
_content.get(),
st.button.ripple,
st.button.textBgOver)));
widget->resize(widget->width(), st.height);
widget->paintRequest() | rpl::start_with_next([=, this] {
auto p = Painter(widget);
const auto selected = widget->isOver() || widget->isDown();
paintRow(p, crl::now(), selected, raw);
}, widget->lifetime());
widget->setClickedCallback([this, raw] {
_controller->rowClicked(raw);
});
}
}
PeerListRow *PeerListWidgets::findRow(PeerListRowId id) {
const auto it = _rowsById.find(id);
return (it == _rowsById.cend()) ? nullptr : it->second.get();
}
void PeerListWidgets::updateRow(not_null<PeerListRow*> row) {
const auto it = ranges::find_if(
_rows,
[row](const auto &r) { return r.get() == row; });
if (it != _rows.end()) {
const auto index = std::distance(_rows.begin(), it);
if (const auto widget = _content->widgetAt(index)) {
widget->update();
}
}
}
int PeerListWidgets::fullRowsCount() {
return _rows.size();
}
[[nodiscard]] not_null<PeerListRow*> PeerListWidgets::rowAt(int index) {
Expects(index >= 0 && index < _rows.size());
return _rows[index].get();
}
void PeerListWidgets::refreshRows() {
_content->resizeToWidth(width());
resize(width(), _content->height());
}
void PeerListWidgetsDelegate::setContent(PeerListWidgets *content) {
_content = content;
}
void PeerListWidgetsDelegate::setUiShow(
std::shared_ptr<Main::SessionShow> uiShow) {
_uiShow = std::move(uiShow);
}
void PeerListWidgetsDelegate::peerListSetHideEmpty(bool hide) {
Unexpected("...PeerListWidgetsDelegate::peerListSetHideEmpty");
}
void PeerListWidgetsDelegate::peerListAppendRow(
std::unique_ptr<PeerListRow> row) {
_content->appendRow(std::move(row));
}
void PeerListWidgetsDelegate::peerListAppendSearchRow(
std::unique_ptr<PeerListRow> row) {
Unexpected("...PeerListWidgetsDelegate::peerListAppendSearchRow");
}
void PeerListWidgetsDelegate::peerListAppendFoundRow(
not_null<PeerListRow*> row) {
Unexpected("...PeerListWidgetsDelegate::peerListAppendFoundRow");
}
void PeerListWidgetsDelegate::peerListPrependRow(
std::unique_ptr<PeerListRow> row) {
Unexpected("...PeerListWidgetsDelegate::peerListPrependRow");
}
void PeerListWidgetsDelegate::peerListPrependRowFromSearchResult(
not_null<PeerListRow*> row) {
Unexpected(
"...PeerListWidgetsDelegate::peerListPrependRowFromSearchResult");
}
PeerListRow* PeerListWidgetsDelegate::peerListFindRow(PeerListRowId id) {
return _content->findRow(id);
}
auto PeerListWidgetsDelegate::peerListLastRowMousePosition()
-> std::optional<QPoint> {
Unexpected("...PeerListWidgetsDelegate::peerListLastRowMousePosition");
}
void PeerListWidgetsDelegate::peerListUpdateRow(not_null<PeerListRow*> row) {
_content->updateRow(row);
}
void PeerListWidgetsDelegate::peerListRemoveRow(not_null<PeerListRow*> row) {
Unexpected("...PeerListWidgetsDelegate::peerListRemoveRow");
}
void PeerListWidgetsDelegate::peerListConvertRowToSearchResult(
not_null<PeerListRow*> row) {
Unexpected(
"...PeerListWidgetsDelegate::peerListConvertRowToSearchResult");
}
void PeerListWidgetsDelegate::peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) {
Unexpected("...PeerListWidgetsDelegate::peerListSetRowChecked");
}
void PeerListWidgetsDelegate::peerListSetRowHidden(
not_null<PeerListRow*> row,
bool hidden) {
Unexpected("...PeerListWidgetsDelegate::peerListSetRowHidden");
}
void PeerListWidgetsDelegate::peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) {
}
int PeerListWidgetsDelegate::peerListFullRowsCount() {
return _content->fullRowsCount();
}
not_null<PeerListRow*> PeerListWidgetsDelegate::peerListRowAt(int index) {
return _content->rowAt(index);
}
int PeerListWidgetsDelegate::peerListSearchRowsCount() {
Unexpected("...PeerListWidgetsDelegate::peerListSearchRowsCount");
}
not_null<PeerListRow*> PeerListWidgetsDelegate::peerListSearchRowAt(int) {
Unexpected("...PeerListWidgetsDelegate::peerListSearchRowAt");
}
void PeerListWidgetsDelegate::peerListRefreshRows() {
_content->refreshRows();
}
void PeerListWidgetsDelegate::peerListSetDescription(
object_ptr<Ui::FlatLabel>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetDescription");
}
void PeerListWidgetsDelegate::peerListSetSearchNoResults(
object_ptr<Ui::FlatLabel>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetSearchNoResults");
}
void PeerListWidgetsDelegate::peerListSetAboveWidget(
object_ptr<Ui::RpWidget>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetAboveWidget");
}
void PeerListWidgetsDelegate::peerListSetAboveSearchWidget(
object_ptr<Ui::RpWidget>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetAboveSearchWidget");
}
void PeerListWidgetsDelegate::peerListSetBelowWidget(
object_ptr<Ui::RpWidget>) {
Unexpected("...PeerListWidgetsDelegate::peerListSetBelowWidget");
}
void PeerListWidgetsDelegate::peerListSetSearchMode(PeerListSearchMode mode) {
Unexpected("...PeerListWidgetsDelegate::peerListSetSearchMode");
}
void PeerListWidgetsDelegate::peerListMouseLeftGeometry() {
Unexpected("...PeerListWidgetsDelegate::peerListMouseLeftGeometry");
}
void PeerListWidgetsDelegate::peerListSortRows(
Fn<bool(const PeerListRow &, const PeerListRow &)>) {
Unexpected("...PeerListWidgetsDelegate::peerListSortRows");
}
int PeerListWidgetsDelegate::peerListPartitionRows(
Fn<bool(const PeerListRow &a)> border) {
Unexpected("...PeerListWidgetsDelegate::peerListPartitionRows");
}
State PeerListWidgetsDelegate::peerListSaveState() const {
Unexpected("...PeerListWidgetsDelegate::peerListSaveState");
return nullptr;
}
void PeerListWidgetsDelegate::peerListRestoreState(
std::unique_ptr<PeerListState> state) {
Unexpected("...PeerListWidgetsDelegate::peerListRestoreState");
}
void PeerListWidgetsDelegate::peerListShowRowMenu(
not_null<PeerListRow*> row,
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
}
void PeerListWidgetsDelegate::peerListSelectSkip(int direction) {
// _content->selectSkip(direction);
}
void PeerListWidgetsDelegate::peerListPressLeftToContextMenu(bool shown) {
Unexpected("...PeerListWidgetsDelegate::peerListPressLeftToContextMenu");
}
bool PeerListWidgetsDelegate::peerListTrackRowPressFromGlobal(QPoint) {
return false;
}
std::shared_ptr<Main::SessionShow> PeerListWidgetsDelegate::peerListUiShow() {
Expects(_uiShow != nullptr);
return _uiShow;
}
void PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) {
Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedPeerInBunch");
}
void PeerListWidgetsDelegate::peerListAddSelectedRowInBunch(
not_null<PeerListRow*> row) {
Unexpected("...PeerListWidgetsDelegate::peerListAddSelectedRowInBunch");
}
void PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch() {
Unexpected("...PeerListWidgetsDelegate::peerListFinishSelectedRowsBunch");
}
void PeerListWidgetsDelegate::peerListSetTitle(rpl::producer<QString> title) {
Unexpected("...PeerListWidgetsDelegate::peerListSetTitle");
}
void PeerListWidgetsDelegate::peerListSetAdditionalTitle(
rpl::producer<QString> title) {
Unexpected("...PeerListWidgetsDelegate::peerListSetAdditionalTitle");
}
bool PeerListWidgetsDelegate::peerListIsRowChecked(
not_null<PeerListRow*> row) {
Unexpected("...PeerListWidgetsDelegate::peerListIsRowChecked");
return false;
}
void PeerListWidgetsDelegate::peerListScrollToTop() {
Unexpected("...PeerListWidgetsDelegate::peerListScrollToTop");
}
int PeerListWidgetsDelegate::peerListSelectedRowsCount() {
Unexpected("...PeerListWidgetsDelegate::peerListSelectedRowsCount");
return 0;
}

View File

@@ -0,0 +1,110 @@
/*
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 "boxes/peer_list_box.h"
namespace Ui {
class VerticalLayout;
} // namespace Ui
class PeerListWidgets : public Ui::RpWidget {
public:
PeerListWidgets(
not_null<Ui::RpWidget*> parent,
not_null<PeerListController*> controller);
crl::time paintRow(
Painter &p,
crl::time now,
bool selected,
not_null<PeerListRow*> row);
void appendRow(std::unique_ptr<PeerListRow> row);
PeerListRow* findRow(PeerListRowId id);
void updateRow(not_null<PeerListRow*> row);
int fullRowsCount();
[[nodiscard]] not_null<PeerListRow*> rowAt(int index);
void refreshRows();
private:
const not_null<PeerListController*> _controller;
const style::PeerList &_st;
base::unique_qptr<Ui::VerticalLayout> _content;
std::vector<std::unique_ptr<PeerListRow>> _rows;
std::map<PeerListRowId, not_null<PeerListRow*>> _rowsById;
std::map<PeerData*, std::vector<not_null<PeerListRow*>>> _rowsByPeer;
};
class PeerListWidgetsDelegate : public PeerListDelegate {
public:
void setContent(PeerListWidgets *content);
void setUiShow(std::shared_ptr<Main::SessionShow> uiShow);
void peerListSetHideEmpty(bool hide) override;
void peerListAppendRow(std::unique_ptr<PeerListRow> row) override;
void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) override;
void peerListAppendFoundRow(not_null<PeerListRow*> row) override;
void peerListPrependRow(std::unique_ptr<PeerListRow> row) override;
void peerListPrependRowFromSearchResult(
not_null<PeerListRow*> row) override;
PeerListRow *peerListFindRow(PeerListRowId id) override;
std::optional<QPoint> peerListLastRowMousePosition() override;
void peerListUpdateRow(not_null<PeerListRow*> row) override;
void peerListRemoveRow(not_null<PeerListRow*> row) override;
void peerListConvertRowToSearchResult(
not_null<PeerListRow*> row) override;
void peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) override;
void peerListSetRowHidden(
not_null<PeerListRow*> row,
bool hidden) override;
void peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) override;
int peerListFullRowsCount() override;
not_null<PeerListRow*> peerListRowAt(int index) override;
int peerListSearchRowsCount() override;
not_null<PeerListRow*> peerListSearchRowAt(int index) override;
void peerListRefreshRows() override;
void peerListSetDescription(object_ptr<Ui::FlatLabel>) override;
void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel>) override;
void peerListSetAboveWidget(object_ptr<Ui::RpWidget>) override;
void peerListSetAboveSearchWidget(object_ptr<Ui::RpWidget>) override;
void peerListSetBelowWidget(object_ptr<Ui::RpWidget>) override;
void peerListSetSearchMode(PeerListSearchMode mode) override;
void peerListMouseLeftGeometry() override;
void peerListSortRows(
Fn<bool(const PeerListRow &, const PeerListRow &)>) override;
int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) override;
std::unique_ptr<PeerListState> peerListSaveState() const override;
void peerListRestoreState(std::unique_ptr<PeerListState> state) override;
void peerListShowRowMenu(
not_null<PeerListRow*> row,
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
void peerListSelectSkip(int direction) override;
void peerListPressLeftToContextMenu(bool shown) override;
bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
void peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) override;
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override;
void peerListFinishSelectedRowsBunch() override;
void peerListSetTitle(rpl::producer<QString> title) override;
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
void peerListScrollToTop() override;
int peerListSelectedRowsCount() override;
private:
PeerListWidgets *_content = nullptr;
std::shared_ptr<Main::SessionShow> _uiShow;
};

View File

@@ -30,10 +30,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "base/unixtime.h"
#include "ui/effects/outline_segments.h"
#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h"
#include "ui/text/text_utilities.h"
#include "info/profile/info_profile_values.h"
#include "window/window_session_controller.h"
#include "history/history.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
namespace {
@@ -1645,6 +1648,51 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
auto result = base::make_unique_q<Ui::PopupMenu>(
parent,
st::popupMenuWithIcons);
const auto addToEnd = gsl::finally([&] {
const auto addInfoAction = [&](
not_null<PeerData*> by,
tr::phrase<lngtag_user, lngtag_date> phrase,
TimeId since) {
auto text = phrase(
tr::now,
lt_user,
Ui::Text::Bold(by->name()),
lt_date,
Ui::Text::Bold(
langDateTimeFull(base::unixtime::parse(since))),
Ui::Text::WithEntities);
auto button = base::make_unique_q<Ui::Menu::MultilineAction>(
result->menu(),
result->st().menu,
st::historyHasCustomEmoji,
st::historyHasCustomEmojiPosition,
std::move(text));
if (const auto n = _navigation) {
button->setClickedCallback([=] {
n->parentController()->show(PrepareShortInfoBox(by, n));
});
}
result->addSeparator();
result->addAction(std::move(button));
};
if (const auto by = _additional.restrictedBy(participant)) {
if (const auto since = _additional.restrictedSince(participant)) {
addInfoAction(
by,
_additional.isKicked(participant)
? tr::lng_rights_chat_banned_by
: tr::lng_rights_chat_restricted_by,
since);
}
} else if (user) {
if (const auto by = _additional.adminPromotedBy(user)) {
if (const auto since = _additional.adminPromotedSince(user)) {
addInfoAction(by, tr::lng_rights_about_by, since);
}
}
}
});
if (_navigation) {
result->addAction(
(participant->isUser()
@@ -1652,39 +1700,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
: participant->isBroadcast()
? tr::lng_context_view_channel
: tr::lng_context_view_group)(tr::now),
crl::guard(this, [=] {
_navigation->showPeerInfo(participant); }),
crl::guard(this, [=, this] {
_navigation->parentController()->show(
PrepareShortInfoBox(participant, _navigation));
}),
(participant->isUser()
? &st::menuIconProfile
: &st::menuIconInfo));
}
if (const auto by = _additional.restrictedBy(participant)) {
result->addAction(
(_role == Role::Kicked
? tr::lng_channel_banned_status_removed_by
: tr::lng_channel_banned_status_restricted_by)(
tr::now,
lt_user,
by->name()),
crl::guard(this, [=] {
_navigation->parentController()->show(
PrepareShortInfoBox(by, _navigation));
}),
&st::menuIconAdmin);
} else if (user) {
if (const auto by = _additional.adminPromotedBy(user)) {
result->addAction(
tr::lng_channel_admin_status_promoted_by(
tr::now,
lt_user,
by->name()),
crl::guard(this, [=] {
_navigation->parentController()->show(
PrepareShortInfoBox(by, _navigation));
}),
&st::menuIconAdmin);
}
}
if (_role == Role::Kicked) {
if (_peer->isMegagroup()
&& _additional.canRestrictParticipant(participant)) {

View File

@@ -46,6 +46,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "history/admin_log/history_admin_log_section.h"
#include "info/bot/earn/info_bot_earn_widget.h"
#include "info/bot/starref/info_bot_starref_join_widget.h"
#include "info/bot/starref/info_bot_starref_setup_widget.h"
#include "info/channel_statistics/boosts/info_boosts_widget.h"
#include "info/channel_statistics/earn/earn_format.h"
#include "info/channel_statistics/earn/earn_icons.h"
@@ -60,6 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/emoji_button.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/new_badges.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "ui/vertical_list.h"
@@ -78,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
@@ -358,6 +362,7 @@ private:
void fillBotUsernamesButton();
void fillBotCurrencyButton();
void fillBotCreditsButton();
void fillBotAffiliateProgram();
void fillBotEditIntroButton();
void fillBotEditCommandsButton();
void fillBotEditSettingsButton();
@@ -1181,6 +1186,7 @@ void Controller::fillManageSection() {
fillBotUsernamesButton();
fillBotCurrencyButton();
fillBotCreditsButton();
fillBotAffiliateProgram();
fillBotEditIntroButton();
fillBotEditCommandsButton();
fillBotEditSettingsButton();
@@ -1238,6 +1244,9 @@ void Controller::fillManageSection() {
&& (channel->isBroadcast() || channel->isGigagroup());
const auto hasRecentActions = isChannel
&& (channel->hasAdminRights() || channel->amCreator());
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)
&& isChannel
&& channel->canPostMessages();
const auto canEditStickers = isChannel && channel->canEditStickers();
const auto canDeleteChannel = isChannel && channel->canDelete();
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
@@ -1420,10 +1429,21 @@ void Controller::fillManageSection() {
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_recent_actions(),
rpl::single(QString()), //Empty count.
rpl::single(QString()), // Empty count.
std::move(callback),
{ &st::menuIconGroupLog });
}
if (hasStarRef) {
auto callback = [=] {
_navigation->showSection(Info::BotStarRef::Join::Make(_peer));
};
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_star_ref(),
rpl::single(QString()), // Empty count.
std::move(callback),
{ .icon = &st::menuIconStarRefShare, .newBadge = true });
}
if (canEditStickers || canDeleteChannel) {
::AddSkip(_controls.buttonsLayout);
@@ -1664,7 +1684,7 @@ void Controller::fillBotCreditsButton() {
auto &lifetime = _controls.buttonsLayout->lifetime();
const auto state = lifetime.make_state<State>();
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
state->balance = Lang::FormatCountDecimal(balance);
state->balance = Lang::FormatStarsAmountDecimal(balance);
}
const auto wrap = _controls.buttonsLayout->add(
@@ -1689,7 +1709,7 @@ void Controller::fillBotCreditsButton() {
if (data.balance) {
wrap->toggle(true, anim::type::normal);
}
state->balance = Lang::FormatCountDecimal(data.balance);
state->balance = Lang::FormatStarsAmountDecimal(data.balance);
});
}
{
@@ -1711,6 +1731,35 @@ void Controller::fillBotCreditsButton() {
}
void Controller::fillBotAffiliateProgram() {
Expects(_isBot);
if (!Info::BotStarRef::Setup::Allowed(_peer)) {
return;
}
const auto user = _peer->asUser();
auto label = user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::StarRefProgram
) | rpl::map([=] {
const auto commission = user->botInfo
? user->botInfo->starRefProgram.commission
: 0;
return commission
? Info::BotStarRef::FormatCommission(commission)
: tr::lng_manage_peer_bot_star_ref_off(tr::now);
});
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_bot_star_ref(),
std::move(label),
[controller = _navigation->parentController(), user] {
controller->showSection(Info::BotStarRef::Setup::Make(user));
},
{ .icon = &st::menuIconSharing, .newBadge = true });
}
void Controller::fillBotEditIntroButton() {
Expects(_isBot);
@@ -2227,7 +2276,9 @@ void Controller::saveHistoryVisibility() {
void Controller::toggleBotManager(const QString &command) {
const auto controller = _navigation->parentController();
_api.request(MTPcontacts_ResolveUsername(
MTP_string(kBotManagerUsername.utf16())
MTP_flags(0),
MTP_string(kBotManagerUsername.utf16()),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
_peer->owner().processUsers(result.data().vusers());
_peer->owner().processChats(result.data().vchats());
@@ -2501,6 +2552,13 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
st.button);
const auto button = result.data();
button->addClickHandler(callback);
const auto badge = descriptor.newBadge
? Ui::NewBadge::CreateNewBadge(
button,
tr::lng_premium_summary_new_badge()).get()
: nullptr;
if (descriptor) {
AddButtonIcon(
button,
@@ -2509,7 +2567,7 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
}
auto labelText = rpl::combine(
std::move(text),
rpl::duplicate(text),
std::move(count),
button->widthValue()
) | rpl::map([&st](const QString &text, const QString &count, int width) {
@@ -2524,11 +2582,40 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
: count;
});
if (badge) {
rpl::combine(
std::move(text),
rpl::duplicate(labelText),
button->widthValue()
) | rpl::start_with_next([=](
const QString &text,
const QString &label,
int width) {
const auto space = st.button.style.font->spacew;
const auto left = st.button.padding.left()
+ st.button.style.font->width(text)
+ space;
const auto right = st.labelPosition.x()
+ st.label.style.font->width(label)
+ (space * 2);
const auto available = width - left - right;
badge->setVisible(available >= badge->width());
if (!badge->isHidden()) {
const auto top = st.button.padding.top()
+ st.button.style.font->ascent
- st::settingsPremiumNewBadge.style.font->ascent
- st::settingsPremiumNewBadgePadding.top();
badge->moveToLeft(left, top, width);
}
}, badge->lifetime());
}
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button,
std::move(labelText),
st.label);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->show();
rpl::combine(
button->widthValue(),

View File

@@ -12,12 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "boxes/gift_premium_box.h"
#include "boxes/peer_list_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/share_box.h"
#include "core/application.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/components/credits.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_session.h"
@@ -51,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_credits.h"
#include "styles/style_dialogs.h"
#include "styles/style_giveaway.h"
#include "styles/style_info.h"
#include "styles/style_layers.h" // st::boxDividerLabel.
@@ -264,8 +267,9 @@ private:
class SingleRowController final : public PeerListController {
public:
SingleRowController(
not_null<PeerData*> peer,
rpl::producer<QString> status);
not_null<Data::Thread*> thread,
rpl::producer<QString> status,
Fn<void()> clicked);
void prepare() override;
void loadMoreRows() override;
@@ -273,8 +277,10 @@ public:
Main::Session &session() const override;
private:
const not_null<PeerData*> _peer;
const not_null<Main::Session*> _session;
const base::weak_ptr<Data::Thread> _thread;
rpl::producer<QString> _status;
Fn<void()> _clicked;
rpl::lifetime _lifetime;
};
@@ -1144,36 +1150,59 @@ int Controller::descriptionTopSkipMin() const {
}
SingleRowController::SingleRowController(
not_null<PeerData*> peer,
rpl::producer<QString> status)
: _peer(peer)
, _status(std::move(status)) {
not_null<Data::Thread*> thread,
rpl::producer<QString> status,
Fn<void()> clicked)
: _session(&thread->session())
, _thread(thread)
, _status(std::move(status))
, _clicked(std::move(clicked)) {
}
void SingleRowController::prepare() {
auto row = std::make_unique<PeerListRow>(_peer);
const auto strong = _thread.get();
if (!strong) {
return;
}
const auto topic = strong->asTopic();
auto row = topic
? ChooseTopicBoxController::MakeRow(topic)
: std::make_unique<PeerListRow>(strong->peer());
const auto raw = row.get();
std::move(
_status
) | rpl::start_with_next([=](const QString &status) {
raw->setCustomStatus(status);
delegate()->peerListUpdateRow(raw);
}, _lifetime);
if (_status) {
std::move(
_status
) | rpl::start_with_next([=](const QString &status) {
raw->setCustomStatus(status);
delegate()->peerListUpdateRow(raw);
}, _lifetime);
}
delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows();
if (topic) {
topic->destroyed() | rpl::start_with_next([=] {
while (delegate()->peerListFullRowsCount()) {
delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
}
delegate()->peerListRefreshRows();
}, _lifetime);
}
}
void SingleRowController::loadMoreRows() {
}
void SingleRowController::rowClicked(not_null<PeerListRow*> row) {
ShowPeerInfoSync(row->peer());
if (const auto onstack = _clicked) {
onstack();
} else {
ShowPeerInfoSync(row->peer());
}
}
Main::Session &SingleRowController::session() const {
return _peer->session();
return *_session;
}
} // namespace
@@ -1186,14 +1215,29 @@ bool IsExpiredLink(const Api::InviteLink &data, TimeId now) {
void AddSinglePeerRow(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
rpl::producer<QString> status) {
rpl::producer<QString> status,
Fn<void()> clicked) {
AddSinglePeerRow(
container,
peer->owner().history(peer),
std::move(status),
std::move(clicked));
}
void AddSinglePeerRow(
not_null<Ui::VerticalLayout*> container,
not_null<Data::Thread*> thread,
rpl::producer<QString> status,
Fn<void()> clicked) {
const auto delegate = container->lifetime().make_state<
PeerListContentDelegateSimple
>();
const auto controller = container->lifetime().make_state<
SingleRowController
>(peer, std::move(status));
controller->setStyleOverrides(&st::peerListSingleRow);
>(thread, std::move(status), std::move(clicked));
controller->setStyleOverrides(thread->asTopic()
? &st::chooseTopicList
: &st::peerListSingleRow);
const auto content = container->add(object_ptr<PeerListContent>(
container,
controller));

View File

@@ -16,6 +16,10 @@ namespace Api {
struct InviteLink;
} // namespace Api
namespace Data {
class Thread;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
@@ -31,7 +35,14 @@ class BoxContent;
void AddSinglePeerRow(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
rpl::producer<QString> status);
rpl::producer<QString> status,
Fn<void()> clicked = nullptr);
void AddSinglePeerRow(
not_null<Ui::VerticalLayout*> container,
not_null<Data::Thread*> thread,
rpl::producer<QString> status,
Fn<void()> clicked = nullptr);
void AddPermanentLinkBlock(
std::shared_ptr<Ui::Show> show,

View File

@@ -7,27 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/edit_peer_requests_box.h"
#include "ui/effects/ripple_animation.h"
#include "api/api_invite_links.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "boxes/peers/edit_peer_requests_box.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "base/unixtime.h"
#include "data/data_user.h"
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "info/requests_list/info_requests_list_widget.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mtproto/sender.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/round_rect.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "lang/lang_keys.h"
#include "window/window_session_controller.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "styles/style_boxes.h"
namespace {
@@ -262,14 +265,10 @@ RequestsBoxController::~RequestsBoxController() = default;
void RequestsBoxController::Start(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) {
auto controller = std::make_unique<RequestsBoxController>(
navigation,
peer->migrateToOrMe());
const auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
};
navigation->parentController()->show(
Box<PeerListBox>(std::move(controller), initBox));
navigation->showSection(
std::make_shared<Info::Memento>(
peer->migrateToOrMe(),
Info::Section::Type::RequestsList));
}
Main::Session &RequestsBoxController::session() const {
@@ -289,6 +288,58 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createSearchRow(
return nullptr;
}
std::unique_ptr<PeerListRow> RequestsBoxController::createRestoredRow(
not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
return createRow(user, _dates[user]);
}
return nullptr;
}
auto RequestsBoxController::saveState() const
-> std::unique_ptr<PeerListState> {
auto result = PeerListController::saveState();
auto my = std::make_unique<SavedState>();
my->dates = _dates;
my->offsetDate = _offsetDate;
my->offsetUser = _offsetUser;
my->allLoaded = _allLoaded;
my->wasLoading = (_loadRequestId != 0);
if (const auto search = searchController()) {
my->searchState = search->saveState();
}
result->controllerState = std::move(my);
return result;
}
void RequestsBoxController::restoreState(
std::unique_ptr<PeerListState> state) {
auto typeErasedState = state
? state->controllerState.get()
: nullptr;
if (const auto my = dynamic_cast<SavedState*>(typeErasedState)) {
if (const auto requestId = base::take(_loadRequestId)) {
_api.request(requestId).cancel();
}
_dates = std::move(my->dates);
_offsetDate = my->offsetDate;
_offsetUser = my->offsetUser;
_allLoaded = my->allLoaded;
if (const auto search = searchController()) {
search->restoreState(std::move(my->searchState));
}
if (my->wasLoading) {
loadMoreRows();
}
PeerListController::restoreState(std::move(state));
if (delegate()->peerListFullRowsCount() || _allLoaded) {
refreshDescription();
delegate()->peerListRefreshRows();
}
}
}
void RequestsBoxController::prepare() {
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
delegate()->peerListSetTitle(_peer->isBroadcast()
@@ -356,9 +407,7 @@ void RequestsBoxController::refreshDescription() {
}
void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) {
_navigation->parentController()->show(PrepareShortInfoBox(
row->peer(),
_navigation));
_navigation->showPeerInfo(row->peer());
}
void RequestsBoxController::rowElementClicked(
@@ -405,6 +454,7 @@ void RequestsBoxController::appendRow(
not_null<UserData*> user,
TimeId date) {
if (!delegate()->peerListFindRow(user->id.value)) {
_dates.emplace(user, date);
if (auto row = createRow(user, date)) {
delegate()->peerListAppendRow(std::move(row));
setDescriptionText(QString());
@@ -503,6 +553,7 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createRow(
const auto search = static_cast<RequestsBoxSearchController*>(
searchController());
date = search->dateForUser(user);
_dates.emplace(user, date);
}
return std::make_unique<Row>(_helper.get(), user, date);
}
@@ -574,6 +625,36 @@ TimeId RequestsBoxSearchController::dateForUser(not_null<UserData*> user) {
return {};
}
auto RequestsBoxSearchController::saveState() const
-> std::unique_ptr<SavedStateBase> {
auto result = std::make_unique<SavedState>();
result->query = _query;
result->offsetDate = _offsetDate;
result->offsetUser = _offsetUser;
result->allLoaded = _allLoaded;
result->wasLoading = (_requestId != 0);
return result;
}
void RequestsBoxSearchController::restoreState(
std::unique_ptr<SavedStateBase> state) {
if (auto my = dynamic_cast<SavedState*>(state.get())) {
if (auto requestId = base::take(_requestId)) {
_api.request(requestId).cancel();
}
_cache.clear();
_queries.clear();
_allLoaded = my->allLoaded;
_offsetDate = my->offsetDate;
_offsetUser = my->offsetUser;
_query = my->query;
if (my->wasLoading) {
searchOnServer();
}
}
}
bool RequestsBoxSearchController::searchInCache() {
const auto i = _cache.find(_query);
if (i != _cache.cend()) {

View File

@@ -35,15 +35,32 @@ public:
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowElementClicked(not_null<PeerListRow*> row, int element) override;
void rowElementClicked(
not_null<PeerListRow*> row,
int element) override;
void loadMoreRows() override;
std::unique_ptr<PeerListRow> createSearchRow(
not_null<PeerData*> peer) override;
std::unique_ptr<PeerListRow> createRestoredRow(
not_null<PeerData*> peer) override;
std::unique_ptr<PeerListState> saveState() const override;
void restoreState(std::unique_ptr<PeerListState> state) override;
private:
class RowHelper;
struct SavedState : SavedStateBase {
using SearchStateBase = PeerListSearchController::SavedStateBase;
std::unique_ptr<SearchStateBase> searchState;
base::flat_map<not_null<UserData*>, TimeId> dates;
TimeId offsetDate = 0;
UserData *offsetUser = nullptr;
bool allLoaded = false;
bool wasLoading = false;
};
static std::unique_ptr<PeerListSearchController> CreateSearchController(
not_null<PeerData*> peer);
@@ -63,6 +80,8 @@ private:
not_null<PeerData*> _peer;
MTP::Sender _api;
base::flat_map<not_null<UserData*>, TimeId> _dates;
TimeId _offsetDate = 0;
UserData *_offsetUser = nullptr;
mtpRequestId _loadRequestId = 0;
@@ -82,7 +101,17 @@ public:
void removeFromCache(not_null<UserData*> user);
[[nodiscard]] TimeId dateForUser(not_null<UserData*> user);
std::unique_ptr<SavedStateBase> saveState() const override;
void restoreState(std::unique_ptr<SavedStateBase> state) override;
private:
struct SavedState : SavedStateBase {
QString query;
TimeId offsetDate = 0;
UserData *offsetUser = nullptr;
bool allLoaded = false;
bool wasLoading = false;
};
struct Item {
not_null<UserData*> user;
TimeId date = 0;

View File

@@ -7,27 +7,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/peer_short_info_box.h"
#include "ui/effects/radial_animation.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/wrap.h"
#include "ui/image/image_prepare.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "base/event_filter.h"
#include "core/application.h"
#include "info/profile/info_profile_text.h"
#include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "base/event_filter.h"
#include "lang/lang_keys.h"
#include "ui/effects/radial_animation.h"
#include "ui/image/image_prepare.h"
#include "ui/painter.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/wrap.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
namespace {
using MenuCallback = Ui::Menu::MenuCallback;
constexpr auto kShadowMaxAlpha = 80;
constexpr auto kInactiveBarOpacity = 0.5;
@@ -833,6 +842,24 @@ void PeerShortInfoBox::refreshRoundedTopImage(const QColor &color) {
RectPart::TopLeft | RectPart::TopRight);
}
rpl::producer<MenuCallback> PeerShortInfoBox::fillMenuRequests() const {
return _fillMenuRequests.events();
}
void PeerShortInfoBox::contextMenuEvent(QContextMenuEvent *e) {
_menuHolder = nullptr;
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
this,
st::popupMenuWithIcons);
_fillMenuRequests.fire(Ui::Menu::CreateAddActionCallback(menu));
_menuHolder.reset(menu);
if (menu->empty()) {
_menuHolder = nullptr;
return;
}
menu->popup(e->globalPos());
}
rpl::producer<QString> PeerShortInfoBox::nameValue() const {
return _fields.value(
) | rpl::map([](const PeerShortInfoFields &fields) {

View File

@@ -15,6 +15,10 @@ struct ShortInfoCover;
struct ShortInfoBox;
} // namespace style
namespace Ui::Menu {
struct MenuCallback;
} // namespace Ui::Menu
namespace Media::Streaming {
class Document;
class Instance;
@@ -160,6 +164,11 @@ public:
[[nodiscard]] rpl::producer<> openRequests() const;
[[nodiscard]] rpl::producer<int> moveRequests() const;
[[nodiscard]] auto fillMenuRequests() const
-> rpl::producer<Ui::Menu::MenuCallback>;
protected:
void contextMenuEvent(QContextMenuEvent *e) override;
private:
void prepare() override;
@@ -192,6 +201,9 @@ private:
not_null<Ui::VerticalLayout*> _rows;
PeerShortInfoCover _cover;
base::unique_qptr<Ui::RpWidget> _menuHolder;
rpl::event_stream<Ui::Menu::MenuCallback> _fillMenuRequests;
rpl::event_stream<> _openRequests;
};

View File

@@ -7,26 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/prepare_short_info_box.h"
#include "base/unixtime.h"
#include "boxes/peers/peer_short_info_box.h"
#include "core/application.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_file_origin.h"
#include "data/data_peer.h"
#include "data/data_peer_values.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_streaming.h"
#include "data/data_file_origin.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_peer_values.h"
#include "data/data_user_photos.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "data/data_streaming.h"
#include "data/data_user.h"
#include "data/data_user_photos.h"
#include "info/profile/info_profile_values.h"
#include "ui/text/format_values.h"
#include "base/unixtime.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/delayed_activation.h" // PreventDelayedActivation
#include "ui/text/format_values.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "window/window_session_controller.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
namespace {
@@ -210,7 +214,7 @@ void ProcessFullPhoto(
) | rpl::map([=] {
const auto user = peer->asUser();
const auto username = peer->username();
const auto channelId = user->personalChannelId();
const auto channelId = user ? user->personalChannelId() : 0;
const auto channel = channelId
? user->owner().channel(channelId).get()
: nullptr;
@@ -446,6 +450,7 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
Fn<void()> open,
Fn<bool()> videoPaused,
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
const style::ShortInfoBox *stOverride) {
const auto type = peer->isSelf()
? PeerShortInfoType::Self
@@ -463,6 +468,13 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
std::move(videoPaused),
stOverride);
if (menuFiller) {
result->fillMenuRequests(
) | rpl::start_with_next([=](Ui::Menu::MenuCallback callback) {
menuFiller(std::move(callback));
}, result->lifetime());
}
result->openRequests(
) | rpl::start_with_next(open, result->lifetime());
@@ -481,10 +493,21 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
return navigation->parentController()->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
};
auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
const auto controller = navigation->parentController();
const auto peerSeparateId = Window::SeparateId(peer);
if (controller->windowId() != peerSeparateId) {
addAction(tr::lng_context_new_window(tr::now), [=] {
Ui::PreventDelayedActivation();
controller->showInNewWindow(peer);
}, &st::menuIconNewWindow);
}
};
return PrepareShortInfoBox(
peer,
open,
videoIsPaused,
std::move(menuFiller),
stOverride);
}

View File

@@ -16,6 +16,10 @@ struct ShortInfoCover;
struct ShortInfoBox;
} // namespace style
namespace Ui::Menu {
struct MenuCallback;
} // namespace Ui::Menu
namespace Ui {
class BoxContent;
} // namespace Ui
@@ -35,6 +39,7 @@ struct PreparedShortInfoUserpic {
not_null<PeerData*> peer,
Fn<void()> open,
Fn<bool()> videoPaused,
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
const style::ShortInfoBox *stOverride = nullptr);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(

View File

@@ -202,10 +202,11 @@ void Controller::prepare() {
const auto session = &_to->session();
auto above = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
above->add(
CreateBoostReplaceUserpics(
CreateUserpicsTransfer(
above.data(),
_selectedPeers.value(),
_to),
_to,
UserpicsTransferType::BoostReplace),
st::boxRowPadding + st::boostReplaceUserpicsPadding);
above->add(
object_ptr<Ui::FlatLabel>(
@@ -366,10 +367,11 @@ object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
});
box->verticalLayout()->insert(
0,
CreateBoostReplaceUserpics(
CreateUserpicsTransfer(
box,
rpl::single(std::vector{ peer }),
to),
to,
UserpicsTransferType::BoostReplace),
st::boxRowPadding + st::boostReplaceUserpicsPadding);
});
@@ -536,10 +538,11 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
return Box<PeerListBox>(std::move(controller), std::move(initBox));
}
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
not_null<Ui::RpWidget*> parent,
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to) {
not_null<PeerData*> to,
UserpicsTransferType type) {
struct State {
std::vector<not_null<PeerData*>> from;
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
@@ -640,13 +643,18 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
button->render(&q, position, QRegion(), QWidget::DrawChildren);
}
state->painting = false;
const auto boosting = (type == UserpicsTransferType::BoostReplace);
const auto last = state->buttons.back().get();
const auto back = boosting ? last : right;
const auto add = st::boostReplaceIconAdd;
const auto skip = st::boostReplaceIconSkip;
const auto w = st::boostReplaceIcon.width() + 2 * skip;
const auto h = st::boostReplaceIcon.height() + 2 * skip;
const auto x = last->x() + last->width() - w + add.x();
const auto y = last->y() + last->height() - h + add.y();
const auto &icon = boosting
? st::boostReplaceIcon
: st::starrefJoinIcon;
const auto skip = boosting ? st::boostReplaceIconSkip : 0;
const auto w = icon.width() + 2 * skip;
const auto h = icon.height() + 2 * skip;
const auto x = back->x() + back->width() - w + add.x();
const auto y = back->y() + back->height() - h + add.y();
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
brush.setStops(Ui::Premium::ButtonGradientStops());
@@ -654,7 +662,7 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
pen.setWidthF(stroke);
q.setPen(pen);
q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
st::boostReplaceIcon.paint(q, x + skip, y + skip, outerw);
icon.paint(q, x + skip, y + skip, outerw);
const auto size = st::boostReplaceArrow.size();
st::boostReplaceArrow.paint(

View File

@@ -53,10 +53,15 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
enum class UserpicsTransferType {
BoostReplace,
StarRefJoin,
};
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
not_null<Ui::RpWidget*> parent,
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to);
not_null<PeerData*> to,
UserpicsTransferType type);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
not_null<Ui::RpWidget*> parent,

View File

@@ -77,7 +77,7 @@ bool operator==(const Descriptor &a, const Descriptor &b) {
struct Preload {
Descriptor descriptor;
std::shared_ptr<Data::DocumentMedia> media;
std::weak_ptr<ChatHelpers::Show> show;
std::weak_ptr<Main::SessionShow> show;
};
[[nodiscard]] std::vector<Preload> &Preloads() {
@@ -133,6 +133,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_subtitle_business();
case PremiumFeature::Effects:
return tr::lng_premium_summary_subtitle_effects();
case PremiumFeature::FilterTags:
return tr::lng_premium_summary_subtitle_filter_tags();
case PremiumFeature::BusinessLocation:
return tr::lng_business_subtitle_location();
@@ -196,6 +198,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_about_business();
case PremiumFeature::Effects:
return tr::lng_premium_summary_about_effects();
case PremiumFeature::FilterTags:
return tr::lng_premium_summary_about_filter_tags();
case PremiumFeature::BusinessLocation:
return tr::lng_business_about_location();
@@ -534,6 +538,7 @@ struct VideoPreviewDocument {
case PremiumFeature::LastSeen: return "last_seen";
case PremiumFeature::MessagePrivacy: return "message_privacy";
case PremiumFeature::Effects: return "effects";
case PremiumFeature::FilterTags: return "folder_tags";
case PremiumFeature::BusinessLocation: return "business_location";
case PremiumFeature::BusinessHours: return "business_hours";

View File

@@ -71,6 +71,7 @@ enum class PremiumFeature {
MessagePrivacy,
Business,
Effects,
FilterTags,
// Business features.
BusinessLocation,

View File

@@ -26,17 +26,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits_graphics.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/credits_graphics.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
#include "ui/image/image_prepare.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/peer_bubble.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_credits.h"
#include "styles/style_giveaway.h"
#include "styles/style_info.h" // inviteLinkSubscribeBoxTerms
#include "styles/style_layers.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
@@ -92,6 +97,44 @@ struct PaidMediaData {
};
}
void AddTerms(
not_null<Ui::BoxContent*> box,
not_null<Ui::RpWidget*> button,
const style::Box &stBox) {
const auto terms = Ui::CreateChild<Ui::FlatLabel>(
button->parentWidget(),
tr::lng_channel_invite_subscription_terms(
lt_link,
rpl::combine(
tr::lng_paid_react_agree_link(),
tr::lng_group_invite_subscription_about_url()
) | rpl::map([](const QString &text, const QString &url) {
return Ui::Text::Link(text, url);
}),
Ui::Text::RichLangValue),
st::inviteLinkSubscribeBoxTerms);
const auto &buttonPadding = stBox.buttonPadding;
const auto style = box->lifetime().make_state<style::Box>(style::Box{
.buttonPadding = buttonPadding + QMargins(0, 0, 0, terms->height()),
.buttonHeight = stBox.buttonHeight,
.button = stBox.button,
.margin = stBox.margin,
.title = stBox.title,
.bg = stBox.bg,
.titleAdditionalFg = stBox.titleAdditionalFg,
.shadowIgnoreTopSkip = stBox.shadowIgnoreTopSkip,
.shadowIgnoreBottomSkip = stBox.shadowIgnoreBottomSkip,
});
button->geometryValue() | rpl::start_with_next([=](const QRect &rect) {
terms->resizeToWidth(box->width()
- rect::m::sum::h(st::boxRowPadding));
terms->moveToLeft(
rect.x() + (rect.width() - terms->width()) / 2,
rect::bottom(rect) + buttonPadding.bottom() / 2);
}, terms->lifetime());
box->setStyle(*style);
}
[[nodiscard]] rpl::producer<TextWithEntities> SendCreditsConfirmText(
not_null<Main::Session*> session,
not_null<Payments::CreditsFormData*> form) {
@@ -150,6 +193,18 @@ struct PaidMediaData {
}
const auto bot = session->data().user(form->botId);
if (form->invoice.subscriptionPeriod) {
return (bot->botInfo
? tr::lng_credits_box_out_subscription_bot
: tr::lng_credits_box_out_subscription_business)(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_title,
rpl::single(TextWithEntities{ form->title }),
lt_recipient,
rpl::single(TextWithEntities{ bot->name() }),
Ui::Text::RichLangValue);
}
return tr::lng_credits_box_out_sure(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
@@ -190,6 +245,57 @@ struct PaidMediaData {
st::defaultUserpicButton);
}
[[nodiscard]] not_null<Ui::RpWidget*> SendCreditsBadge(
not_null<Ui::RpWidget*> parent,
int credits) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent);
const auto &font = st::chatGiveawayBadgeFont;
const auto text = QString::number(credits);
const auto iconHeight = font->ascent - font->descent;
const auto iconWidth = iconHeight + st::lineWidth;
const auto width = font->width(text) + iconWidth + st::lineWidth;
const auto inner = QRect(0, 0, width, font->height);
const auto rect = inner + st::subscriptionCreditsBadgePadding;
const auto size = rect.size();
const auto svg = widget->lifetime().make_state<QSvgRenderer>(
Ui::Premium::Svg());
const auto half = st::chatGiveawayBadgeStroke / 2.;
const auto left = st::subscriptionCreditsBadgePadding.left();
const auto smaller = QRectF(rect.translated(-rect.topLeft()))
- Margins(half);
const auto radius = smaller.height() / 2.;
widget->resize(size);
widget->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(QPen(st::premiumButtonFg, st::chatGiveawayBadgeStroke * 1.));
p.setBrush(st::creditsBg3);
p.drawRoundedRect(smaller, radius, radius);
p.translate(0, font->descent / 2);
p.setPen(st::premiumButtonFg);
p.setBrush(st::premiumButtonFg);
svg->render(
&p,
QRect(
left,
half + (inner.height() - iconHeight) / 2,
iconHeight,
iconHeight));
p.setFont(font);
p.drawText(
left + iconWidth,
st::subscriptionCreditsBadgePadding.top() + font->ascent,
text);
}, widget->lifetime());
return widget;
}
} // namespace
void SendCreditsBox(
@@ -203,7 +309,8 @@ void SendCreditsBox(
rpl::variable<bool> confirmButtonBusy = false;
};
const auto state = box->lifetime().make_state<State>();
box->setStyle(st::giveawayGiftCodeBox);
const auto &stBox = st::giveawayGiftCodeBox;
box->setStyle(stBox);
box->setNoContentMargin(true);
const auto session = form->invoice.session;
@@ -245,14 +352,36 @@ void SendCreditsBox(
content,
SendCreditsThumbnail(content, session, form.get(), photoSize)));
thumb->setAttribute(Qt::WA_TransparentForMouseEvents);
if (form->invoice.subscriptionPeriod) {
const auto badge = SendCreditsBadge(content, form->invoice.amount);
thumb->geometryValue() | rpl::start_with_next([=](const QRect &r) {
badge->moveToLeft(
r.x() + (r.width() - badge->width()) / 2,
rect::bottom(r) - badge->height() / 2);
}, badge->lifetime());
Ui::AddSkip(content);
Ui::AddSkip(content);
}
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_credits_box_out_title(),
form->invoice.subscriptionPeriod
? rpl::single(form->title)
: tr::lng_credits_box_out_title(),
st::settingsPremiumUserTitle)));
if (form->invoice.subscriptionPeriod && form->botId && form->photo) {
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto bot = session->data().user(form->botId);
box->addRow(
object_ptr<Ui::CenterWrap<>>(
box,
Ui::CreatePeerBubble(box, bot)));
Ui::AddSkip(content);
}
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
@@ -306,6 +435,9 @@ void SendCreditsBox(
}
}).send();
});
if (form->invoice.subscriptionPeriod) {
AddTerms(box, button, stBox);
}
{
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
@@ -317,12 +449,14 @@ void SendCreditsBox(
SetButtonMarkedLabel(
button,
rpl::combine(
tr::lng_credits_box_out_confirm(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_emoji,
rpl::single(CreditsEmojiSmall(session)),
Ui::Text::RichLangValue),
(form->invoice.subscriptionPeriod
? tr::lng_credits_box_out_subscription_confirm
: tr::lng_credits_box_out_confirm)(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_emoji,
rpl::single(CreditsEmojiSmall(session)),
Ui::Text::RichLangValue),
state->confirmButtonBusy.value()
) | rpl::map([](TextWithEntities &&text, bool busy) {
return busy ? TextWithEntities() : std::move(text);
@@ -332,7 +466,7 @@ void SendCreditsBox(
box->getDelegate()->style().button.textFg->c);
const auto buttonWidth = st::boxWidth
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
- rect::m::sum::h(stBox.buttonPadding);
button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
@@ -341,15 +475,11 @@ void SendCreditsBox(
{
const auto close = Ui::CreateChild<Ui::IconButton>(
box.get(),
content,
st::boxTitleClose);
close->setClickedCallback([=] {
box->closeBox();
});
box->widthValue(
) | rpl::start_with_next([=](int width) {
close->setClickedCallback([=] { box->closeBox(); });
content->widthValue() | rpl::start_with_next([=](int) {
close->moveToRight(0, 0);
close->raise();
}, close->lifetime());
}

View File

@@ -1388,7 +1388,8 @@ void ShareBox::Inner::applyChatFilter(FilterId id) {
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
for (const auto &row : list->all()) {
if (const auto history = row->history()) {
if (_descriptor.filterCallback(history)) {
if (history->asForum()
|| _descriptor.filterCallback(history)) {
_customChatsIndexed->addToEnd(history);
}
}

View File

@@ -57,6 +57,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/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/buttons.h"
@@ -71,11 +72,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_premium.h"
#include "styles/style_settings.h"
#include <QtWidgets/QApplication>
namespace Ui {
namespace {
constexpr auto kPriceTabAll = 0;
constexpr auto kPriceTabLimited = -1;
constexpr auto kPriceTabInStock = -2;
constexpr auto kGiftMessageLimit = 255;
constexpr auto kSentToastDuration = 3 * crl::time(1000);
@@ -139,6 +143,27 @@ private:
};
[[nodiscard]] bool SortForBirthday(not_null<PeerData*> peer) {
const auto user = peer->asUser();
if (!user) {
return false;
}
const auto birthday = user->birthday();
if (!birthday) {
return false;
}
const auto is = [&](const QDate &date) {
return (date.day() == birthday.day())
&& (date.month() == birthday.month());
};
const auto now = QDate::currentDate();
return is(now) || is(now.addDays(1)) || is(now.addDays(-1));
}
[[nodiscard]] bool IsSoldOut(const Api::StarGift &info) {
return info.limitedCount && !info.limitedLeft;
}
PreviewDelegate::PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
@@ -216,7 +241,7 @@ auto GenerateGiftMedia(
return tr::lng_action_gift_got_stars_text(
tr::now,
lt_count,
gift.info.convertStars,
gift.info.starsConverted,
Ui::Text::RichLangValue);
});
auto description = data.text.empty()
@@ -537,6 +562,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
return simple(tr::lng_gift_stars_tabs_all(tr::now));
} else if (price == kPriceTabLimited) {
return simple(tr::lng_gift_stars_tabs_limited(tr::now));
} else if (price == kPriceTabInStock) {
return simple(tr::lng_gift_stars_tabs_in_stock(tr::now));
}
auto &manager = session->data().customEmojiManager();
auto result = Text::String();
@@ -572,18 +599,33 @@ struct GiftPriceTabs {
struct State {
rpl::variable<std::vector<int>> prices;
rpl::variable<int> priceTab = kPriceTabAll;
rpl::variable<int> fullWidth;
std::vector<Button> buttons;
int dragx = 0;
int pressx = 0;
float64 dragscroll = 0.;
float64 scroll = 0.;
int scrollMax = 0;
int selected = -1;
int pressed = -1;
int active = -1;
};
const auto state = raw->lifetime().make_state<State>();
const auto scroll = [=] {
return QPoint(int(base::SafeRound(state->scroll)), 0);
};
state->prices = std::move(
gifts
) | rpl::map([](const std::vector<GiftTypeStars> &gifts) {
auto result = std::vector<int>();
result.push_back(kPriceTabAll);
auto special = 1;
auto same = true;
auto sameKey = 0;
auto hasNonSoldOut = false;
auto hasSoldOut = false;
auto hasLimited = false;
for (const auto &gift : gifts) {
if (same) {
const auto key = gift.info.stars
@@ -594,10 +636,13 @@ struct GiftPriceTabs {
same = false;
}
}
if (gift.info.limitedCount
&& (result.size() < 2 || result[1] != kPriceTabLimited)) {
result.insert(begin(result) + 1, kPriceTabLimited);
if (IsSoldOut(gift.info)) {
hasSoldOut = true;
} else {
hasNonSoldOut = true;
}
if (gift.info.limitedCount) {
hasLimited = true;
}
if (!ranges::contains(result, gift.info.stars)) {
result.push_back(gift.info.stars);
@@ -606,6 +651,12 @@ struct GiftPriceTabs {
if (same) {
return std::vector<int>();
}
if (hasSoldOut && hasNonSoldOut) {
result.insert(begin(result) + (special++), kPriceTabInStock);
}
if (hasLimited) {
result.insert(begin(result) + (special++), kPriceTabLimited);
}
ranges::sort(begin(result) + 1, end(result));
return result;
});
@@ -664,6 +715,9 @@ struct GiftPriceTabs {
button.geometry = QRect(QPoint(x, y), r.size());
x += r.width() + st::giftBoxTabSkip;
}
state->fullWidth = x
- st::giftBoxTabSkip
+ st::giftBoxTabsMargin.right();
const auto height = state->buttons.empty()
? 0
: (y
@@ -673,13 +727,35 @@ struct GiftPriceTabs {
raw->update();
}, raw->lifetime());
rpl::combine(
raw->widthValue(),
state->fullWidth.value()
) | rpl::start_with_next([=](int outer, int inner) {
state->scrollMax = std::max(0, inner - outer);
}, raw->lifetime());
raw->setMouseTracking(true);
raw->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto type = e->type();
switch (type) {
case QEvent::Leave: setSelected(-1); break;
case QEvent::MouseMove: {
const auto position = static_cast<QMouseEvent*>(e.get())->pos();
const auto me = static_cast<QMouseEvent*>(e.get());
const auto mousex = me->pos().x();
const auto drag = QApplication::startDragDistance();
if (state->dragx > 0) {
state->scroll = std::clamp(
state->dragscroll + state->dragx - mousex,
0.,
state->scrollMax * 1.);
raw->update();
break;
} else if (state->pressx > 0
&& std::abs(state->pressx - mousex) > drag) {
state->dragx = state->pressx;
state->dragscroll = state->scroll;
}
const auto position = me->pos() + scroll();
for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
if (state->buttons[i].geometry.contains(position)) {
setSelected(i);
@@ -687,17 +763,32 @@ struct GiftPriceTabs {
}
}
} break;
case QEvent::Wheel: {
const auto me = static_cast<QWheelEvent*>(e.get());
state->scroll = std::clamp(
state->scroll - Ui::ScrollDeltaF(me).x(),
0.,
state->scrollMax * 1.);
raw->update();
} break;
case QEvent::MouseButtonPress: {
const auto me = static_cast<QMouseEvent*>(e.get());
if (me->button() != Qt::LeftButton) {
break;
}
const auto position = me->pos();
for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
if (state->buttons[i].geometry.contains(position)) {
setActive(i);
break;
}
state->pressed = state->selected;
state->pressx = me->pos().x();
} break;
case QEvent::MouseButtonRelease: {
const auto me = static_cast<QMouseEvent*>(e.get());
if (me->button() != Qt::LeftButton) {
break;
}
const auto dragx = std::exchange(state->dragx, 0);
const auto pressed = std::exchange(state->pressed, -1);
state->pressx = 0;
if (!dragx && pressed >= 0 && state->selected == pressed) {
setActive(pressed);
}
} break;
}
@@ -707,8 +798,9 @@ struct GiftPriceTabs {
auto p = QPainter(raw);
auto hq = PainterHighQualityEnabler(p);
const auto padding = st::giftBoxTabPadding;
const auto shift = -scroll();
for (const auto &button : state->buttons) {
const auto geometry = button.geometry;
const auto geometry = button.geometry.translated(shift);
if (button.active) {
p.setBrush(st::giftBoxTabBgActive);
p.setPen(Qt::NoPen);
@@ -723,6 +815,14 @@ struct GiftPriceTabs {
.availableWidth = button.text.maxWidth(),
});
}
{
const auto &icon = st::defaultEmojiSuggestions;
const auto w = icon.fadeRight.width();
const auto &c = st::boxDividerBg->c;
const auto r = QRect(0, 0, w, raw->height());
icon.fadeRight.fill(p, r.translated(raw->width() - w, 0), c);
icon.fadeLeft.fill(p, r, c);
}
}, raw->lifetime());
return {
@@ -858,7 +958,7 @@ void SoldOutBox(
Data::CreditsHistoryEntry{
.firstSaleDate = base::unixtime::parse(gift.info.firstSaleDate),
.lastSaleDate = base::unixtime::parse(gift.info.lastSaleDate),
.credits = uint64(gift.info.stars),
.credits = StarsAmount(gift.info.stars),
.bareGiftStickerId = gift.info.document->id,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = gift.info.limitedCount,
@@ -1068,10 +1168,23 @@ void SendGiftBox(
const auto padding = st::giftBoxPadding;
const auto available = width - padding.left() - padding.right();
const auto perRow = available / single.width();
const auto count = int(gifts.list.size());
auto order = ranges::views::ints
| ranges::views::take(count)
| ranges::to_vector;
if (SortForBirthday(peer)) {
ranges::stable_partition(order, [&](int i) {
const auto &gift = gifts.list[i];
const auto stars = std::get_if<GiftTypeStars>(&gift);
return stars && stars->info.birthday;
});
}
auto x = padding.left();
auto y = padding.top();
state->buttons.resize(gifts.list.size());
state->buttons.resize(count);
for (auto &button : state->buttons) {
if (!button) {
button = std::make_unique<GiftButton>(raw, &state->delegate);
@@ -1079,9 +1192,9 @@ void SendGiftBox(
}
}
const auto api = gifts.api;
for (auto i = 0, count = int(gifts.list.size()); i != count; ++i) {
for (auto i = 0; i != count; ++i) {
const auto button = state->buttons[i].get();
const auto &descriptor = gifts.list[i];
const auto &descriptor = gifts.list[order[i]];
button->setDescriptor(descriptor);
const auto last = !((i + 1) % perRow);
@@ -1098,9 +1211,7 @@ void SendGiftBox(
button->setClickedCallback([=] {
const auto star = std::get_if<GiftTypeStars>(&descriptor);
if (star
&& star->info.limitedCount
&& !star->info.limitedLeft) {
if (star && IsSoldOut(star->info)) {
window->show(Box(SoldOutBox, window, *star));
} else {
window->show(
@@ -1108,12 +1219,12 @@ void SendGiftBox(
}
});
}
if (gifts.list.size() % perRow) {
if (count % perRow) {
y += padding.bottom() + single.height();
} else {
y += padding.bottom() - st::giftBoxGiftSkip.y();
}
raw->resize(raw->width(), gifts.list.empty() ? 0 : y);
raw->resize(raw->width(), count ? y : 0);
}, raw->lifetime());
return result;
@@ -1205,6 +1316,8 @@ void AddBlock(
gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) {
return (price == kPriceTabLimited)
? (!gift.info.limitedCount)
: (price == kPriceTabInStock)
? IsSoldOut(gift.info)
: (price && gift.info.stars != price);
}), end(gifts));
return GiftsDescriptor{
@@ -1240,32 +1353,12 @@ void GiftBox(
AddSkip(content);
AddSkip(content);
{
const auto widget = CreateChild<RpWidget>(content);
using ColoredMiniStars = Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Premium::CreditsIconGradientStops());
widget->resize(
st::boxWidth - stUser.photoSize,
stUser.photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft((size.width() - widget->width()) / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
Settings::AddMiniStars(
content,
Ui::CreateChild<Ui::RpWidget>(content),
stUser.photoSize,
box->width(),
2.);
AddSkip(content);
AddSkip(box->verticalLayout());

View File

@@ -1826,8 +1826,8 @@ void StickersBox::Inner::setPressed(SelectedRow pressed) {
if (_megagroupSet && pressedIndex >= 0 && pressedIndex < _rows.size()) {
update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight);
auto &set = _rows[pressedIndex];
auto rippleMask = Ui::RippleAnimation::RectMask(QSize(width(), _rowHeight));
if (!set->ripple) {
auto rippleMask = Ui::RippleAnimation::RectMask(QSize(width(), _rowHeight));
set->ripple = std::make_unique<Ui::RippleAnimation>(st::defaultRippleAnimation, std::move(rippleMask), [this, pressedIndex] {
update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight);
});

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