Compare commits

...

167 Commits

Author SHA1 Message Date
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
242 changed files with 8592 additions and 1552 deletions

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
@@ -1403,6 +1410,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
@@ -1551,6 +1560,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
@@ -1562,8 +1573,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.

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

@@ -290,6 +290,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_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 +299,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?";
@@ -1473,6 +1475,7 @@ 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";
@@ -1542,6 +1545,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";
@@ -1615,11 +1619,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";
@@ -1861,6 +1953,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}.";
@@ -2296,6 +2390,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";
@@ -2428,11 +2524,14 @@ 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";
@@ -2444,6 +2543,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";
@@ -2489,6 +2594,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";
@@ -2504,6 +2613,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";
@@ -2514,13 +2625,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";
@@ -3358,6 +3472,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";
@@ -5140,11 +5255,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.";

View File

@@ -29,6 +29,7 @@
<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.8.2.0" />
Version="5.9.0.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,8,2,0
PRODUCTVERSION 5,8,2,0
FILEVERSION 5,9,0,0
PRODUCTVERSION 5,9,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "5.8.2.0"
VALUE "FileVersion", "5.9.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.8.2.0"
VALUE "ProductVersion", "5.9.0.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,8,2,0
PRODUCTVERSION 5,8,2,0
FILEVERSION 5,9,0,0
PRODUCTVERSION 5,9,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "5.8.2.0"
VALUE "FileVersion", "5.9.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.8.2.0"
VALUE "ProductVersion", "5.9.0.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
@@ -548,6 +549,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,
@@ -610,6 +631,8 @@ void ProcessFilterInvite(
box->addButton(std::move(owned));
HandleEnterInBox(box);
struct State {
bool importing = false;
};
@@ -829,6 +852,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"
@@ -295,20 +296,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 &) {
@@ -133,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(),
};
@@ -166,13 +187,13 @@ 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()
@@ -259,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));
}
@@ -339,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());
@@ -371,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()
@@ -463,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

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

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

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,
@@ -126,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;
}
@@ -164,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) {
@@ -188,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

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

@@ -122,7 +122,7 @@ void GiftCreditsBox(
Main::MakeSessionShow(box->uiShow(), &peer->session()),
box->verticalLayout(),
peer,
0,
StarsAmount(),
[=] { gifted(); box->uiShow()->hideLayer(); },
tr::lng_credits_summary_options_subtitle(),
{});

View File

@@ -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));
@@ -1146,9 +1146,43 @@ void AddCreditsHistoryEntryTable(
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 (actorId || 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(
@@ -1229,7 +1263,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));
}
{
@@ -1247,25 +1281,20 @@ 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 = 22;
const auto oneLine = entry.id.size() <= kOneLineCount;
auto multiLine = QString();
if (!oneLine) {
for (auto i = 0; i < entry.id.size(); ++i) {
multiLine.append(entry.id[i]);
if ((i + 1) % kOneLineCount == 0) {
multiLine.append('\n');
}
}
}
constexpr auto kOneLineCount = 24;
const auto oneLine = entry.id.length() <= kOneLineCount;
auto label = object_ptr<Ui::FlatLabel>(
table,
rpl::single(
Ui::Text::Wrapped(
{ oneLine ? entry.id : std::move(multiLine) },
EntityType::Code,
{})),
Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
oneLine
? st::giveawayGiftCodeValue
: st::giveawayGiftCodeValueMultiline);
@@ -1324,11 +1353,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

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

@@ -217,19 +217,7 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
void PeerListBox::searchQueryChanged(const QString &query) {
scrollToY(0);
const auto isEmpty = content()->searchQueryChanged(query);
if (_specialTabsMode.enabled) {
const auto was = _specialTabsMode.searchIsActive;
_specialTabsMode.searchIsActive = !isEmpty;
if (was != _specialTabsMode.searchIsActive) {
if (_specialTabsMode.searchIsActive) {
_specialTabsMode.topSkip = _addedTopScrollSkip;
setAddedTopScrollSkip(0);
} else {
setAddedTopScrollSkip(_specialTabsMode.topSkip);
}
}
}
content()->searchQueryChanged(query);
}
void PeerListBox::resizeEvent(QResizeEvent *e) {
@@ -561,15 +549,6 @@ 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 = {};
}
}
PeerListRow::PeerListRow(not_null<PeerData*> peer)
: PeerListRow(peer, peer->id.value) {
}
@@ -2079,7 +2058,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()) {
@@ -2136,7 +2115,6 @@ PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
}
refreshRows();
}
return _normalizedSearchQuery.isEmpty();
}
std::unique_ptr<PeerListState> PeerListContent::saveState() const {

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);
@@ -1108,8 +1107,6 @@ public:
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
void setSpecialTabMode(bool value);
void peerListSetTitle(rpl::producer<QString> title) override {
setTitle(std::move(title));
}
@@ -1175,11 +1172,4 @@ private:
bool _scrollBottomFixed = false;
int _addedTopScrollSkip = 0;
struct SpecialTabsMode final {
bool enabled = false;
bool searchIsActive = false;
int topSkip = 0;
};
SpecialTabsMode _specialTabsMode;
};

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

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

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

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

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

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

View File

@@ -669,7 +669,8 @@ bool Instance::inCall() const {
return false;
}
const auto state = _currentCall->state();
return (state != Call::State::Busy);
return (state != Call::State::Busy)
&& (state != Call::State::WaitingUserConfirmation);
}
bool Instance::inGroupCall() const {

View File

@@ -993,7 +993,12 @@ void Panel::paint(QRect clip) {
bool Panel::handleClose() const {
if (_call) {
window()->hide();
if (_call->state() == Call::State::WaitingUserConfirmation
|| _call->state() == Call::State::Busy) {
_call->hangup();
} else {
window()->hide();
}
return true;
}
return false;
@@ -1028,6 +1033,7 @@ void Panel::stateChanged(State state) {
_startVideo = base::make_unique_q<Ui::CallButton>(
widget(),
st::callStartVideo);
_startVideo->show();
_startVideo->setText(tr::lng_call_start_video());
_startVideo->clicks() | rpl::map_to(true) | rpl::start_to_stream(
_startOutgoingRequests,

View File

@@ -899,7 +899,9 @@ void GifsListWidget::searchForGifs(const QString &query) {
if (!_searchBot && !_searchBotRequestId) {
const auto username = session().serverConfig().gifSearchUsername;
_searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
MTP_string(username)
MTP_flags(0),
MTP_string(username),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
auto &data = result.data();
session().data().processUsers(data.vusers());

View File

@@ -9,13 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_abstract_structure.h"
#include "data/data_forum.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "data/data_message_reactions.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_download_manager.h"
#include "base/battery_saving.h"
#include "base/event_filter.h"
@@ -33,15 +28,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/ui_integration.h"
#include "chat_helpers/emoji_keywords.h"
#include "chat_helpers/stickers_emoji_image_loader.h"
#include "base/qt/qt_common_adapters.h"
#include "base/platform/base_platform_global_shortcuts.h"
#include "base/platform/base_platform_url_scheme.h"
#include "base/platform/base_platform_last_input.h"
#include "base/platform/base_platform_info.h"
#include "platform/platform_specific.h"
#include "platform/platform_integration.h"
#include "mainwindow.h"
#include "dialogs/dialogs_entry.h"
#include "history/history.h"
#include "apiwrap.h"
#include "api/api_updates.h"
@@ -50,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "iv/iv_delegate_impl.h"
#include "iv/iv_instance.h"
#include "iv/iv_data.h"
#include "lang/lang_file_parser.h"
#include "lang/lang_translator.h"
#include "lang/lang_cloud_manager.h"
#include "lang/lang_hardcoded.h"
@@ -58,7 +49,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "inline_bots/bot_attach_web_view.h"
#include "mainwidget.h"
#include "tray.h"
#include "core/file_utilities.h"
#include "core/click_handler_types.h" // ClickHandlerContext.
#include "core/crash_reports.h"
#include "main/main_account.h"
@@ -68,8 +58,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/view/media_view_open_common.h"
#include "mtproto/mtproto_dc_options.h"
#include "mtproto/mtproto_config.h"
#include "mtproto/mtp_instance.h"
#include "media/audio/media_audio.h"
#include "media/audio/media_audio_track.h"
#include "media/player/media_player_instance.h"
#include "media/player/media_player_float.h"
@@ -77,18 +65,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/system_media_controls_manager.h"
#include "window/notifications_manager.h"
#include "window/themes/window_theme.h"
#include "window/window_lock_widgets.h"
#include "history/history_location_manager.h"
#include "ui/widgets/tooltip.h"
#include "ui/gl/gl_detection.h"
#include "ui/image/image.h"
#include "ui/text/text_options.h"
#include "ui/emoji_config.h"
#include "ui/effects/animations.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/cached_round_corners.h"
#include "ui/power_saving.h"
#include "storage/serialize_common.h"
#include "storage/storage_domain.h"
#include "storage/storage_databases.h"
#include "storage/localstorage.h"
@@ -101,7 +83,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "base/qthelp_regex.h"
#include "base/qthelp_url.h"
#include "boxes/connection_box.h"
#include "boxes/premium_limits_box.h"
#include "ui/boxes/confirm_box.h"
#include "styles/style_window.h"

View File

@@ -106,6 +106,10 @@ void ComputeDebugMode() {
auto file = QFile(debugModeSettingPath);
if (file.exists() && file.open(QIODevice::ReadOnly)) {
Logs::SetDebugEnabled(file.read(1) != "0");
#if defined _DEBUG
} else {
Logs::SetDebugEnabled(true);
#endif
}
if (cDebugMode()) {
Logs::SetDebugEnabled(true);

View File

@@ -31,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/edit_privacy_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/star_gift_box.h"
#include "boxes/language_box.h"
#include "passport/passport_form_controller.h"
@@ -51,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_peer_menu.h"
#include "window/themes/window_theme_editor_box.h" // GenerateSlug.
#include "payments/payments_checkout_process.h"
#include "settings/settings_active_sessions.h"
#include "settings/settings_credits.h"
#include "settings/settings_credits_graphics.h"
#include "settings/settings_information.h"
@@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_premium.h"
#include "mainwidget.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_domain.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
@@ -556,8 +557,19 @@ bool ResolveUsernameOrPhone(
? ResolveType::Profile
: ResolveType::Default;
auto startToken = params.value(u"start"_q);
auto referral = params.value(u"ref"_q);
if (!startToken.isEmpty()) {
resolveType = ResolveType::BotStart;
if (referral.isEmpty()) {
const auto appConfig = &controller->session().appConfig();
const auto &prefixes = appConfig->startRefPrefixes();
for (const auto &prefix : prefixes) {
if (startToken.startsWith(prefix)) {
referral = startToken.mid(prefix.size());
break;
}
}
}
} else if (params.contains(u"startgroup"_q)) {
resolveType = ResolveType::AddToGroup;
startToken = params.value(u"startgroup"_q);
@@ -613,6 +625,7 @@ bool ResolveUsernameOrPhone(
}
: Window::RepliesByLinkInfo{ v::null },
.resolveType = resolveType,
.referral = referral,
.startToken = startToken,
.startAdminRights = adminRights,
.startAutoSubmit = myContext.botStartAutoSubmit,

View File

@@ -0,0 +1,99 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/basic_types.h"
inline constexpr auto kOneStarInNano = int64(1'000'000'000);
class StarsAmount {
public:
StarsAmount() = default;
explicit StarsAmount(int64 whole) : _whole(whole) {}
StarsAmount(int64 whole, int64 nano) : _whole(whole), _nano(nano) {
normalize();
}
[[nodiscard]] int64 whole() const {
return _whole;
}
[[nodiscard]] int64 nano() const {
return _nano;
}
[[nodiscard]] double value() const {
return double(_whole) + double(_nano) / kOneStarInNano;
}
[[nodiscard]] bool empty() const {
return !_whole && !_nano;
}
[[nodiscard]] inline bool operator!() const {
return empty();
}
[[nodiscard]] inline explicit operator bool() const {
return !empty();
}
inline StarsAmount &operator+=(StarsAmount other) {
_whole += other._whole;
_nano += other._nano;
normalize();
return *this;
}
inline StarsAmount &operator-=(StarsAmount other) {
_whole -= other._whole;
_nano -= other._nano;
normalize();
return *this;
}
inline StarsAmount &operator*=(int64 multiplier) {
_whole *= multiplier;
_nano *= multiplier;
normalize();
return *this;
}
friend inline auto operator<=>(StarsAmount, StarsAmount) = default;
friend inline bool operator==(StarsAmount, StarsAmount) = default;
[[nodiscard]] StarsAmount abs() const {
return (_whole < 0) ? StarsAmount(-_whole, -_nano) : *this;
}
private:
int64 _whole = 0;
int64 _nano = 0;
void normalize() {
if (_nano < 0) {
const auto shifts = (-_nano + kOneStarInNano - 1)
/ kOneStarInNano;
_nano += shifts * kOneStarInNano;
_whole -= shifts;
} else if (_nano >= kOneStarInNano) {
const auto shifts = _nano / kOneStarInNano;
_nano -= shifts * kOneStarInNano;
_whole += shifts;
}
}
};
[[nodiscard]] inline StarsAmount operator+(StarsAmount a, StarsAmount b) {
return a += b;
}
[[nodiscard]] inline StarsAmount operator-(StarsAmount a, StarsAmount b) {
return a -= b;
}
[[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) {
return a *= b;
}

View File

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

View File

@@ -19,6 +19,11 @@ constexpr auto kReloadThreshold = 60 * crl::time(1000);
} // namespace
StarsAmount FromTL(const MTPStarsAmount &value) {
const auto &data = value.data();
return StarsAmount(data.vamount().v, data.vnanos().v);
}
Credits::Credits(not_null<Main::Session*> session)
: _session(session)
, _reload([=] { load(true); }) {
@@ -27,7 +32,7 @@ Credits::Credits(not_null<Main::Session*> session)
Credits::~Credits() = default;
void Credits::apply(const MTPDupdateStarsBalance &data) {
apply(data.vbalance().v);
apply(FromTL(data.vbalance()));
}
rpl::producer<float64> Credits::rateValue(
@@ -65,13 +70,13 @@ rpl::producer<bool> Credits::loadedValue() const {
) | rpl::then(_loadedChanges.events() | rpl::map_to(true));
}
uint64 Credits::balance() const {
StarsAmount Credits::balance() const {
return _nonLockedBalance.current();
}
uint64 Credits::balance(PeerId peerId) const {
StarsAmount Credits::balance(PeerId peerId) const {
const auto it = _cachedPeerBalances.find(peerId);
return (it != _cachedPeerBalances.end()) ? it->second : 0;
return (it != _cachedPeerBalances.end()) ? it->second : StarsAmount();
}
uint64 Credits::balanceCurrency(PeerId peerId) const {
@@ -79,17 +84,19 @@ uint64 Credits::balanceCurrency(PeerId peerId) const {
return (it != _cachedPeerCurrencyBalances.end()) ? it->second : 0;
}
rpl::producer<uint64> Credits::balanceValue() const {
rpl::producer<StarsAmount> Credits::balanceValue() const {
return _nonLockedBalance.value();
}
void Credits::updateNonLockedValue() {
_nonLockedBalance = (_balance >= _locked) ? (_balance - _locked) : 0;
_nonLockedBalance = (_balance >= _locked)
? (_balance - _locked)
: StarsAmount();
}
void Credits::lock(int count) {
void Credits::lock(StarsAmount count) {
Expects(loaded());
Expects(count >= 0);
Expects(count >= StarsAmount(0));
Expects(_locked + count <= _balance);
_locked += count;
@@ -97,8 +104,8 @@ void Credits::lock(int count) {
updateNonLockedValue();
}
void Credits::unlock(int count) {
Expects(count >= 0);
void Credits::unlock(StarsAmount count) {
Expects(count >= StarsAmount(0));
Expects(_locked >= count);
_locked -= count;
@@ -106,12 +113,12 @@ void Credits::unlock(int count) {
updateNonLockedValue();
}
void Credits::withdrawLocked(int count) {
Expects(count >= 0);
void Credits::withdrawLocked(StarsAmount count) {
Expects(count >= StarsAmount(0));
Expects(_locked >= count);
_locked -= count;
apply(_balance >= count ? (_balance - count) : 0);
apply(_balance >= count ? (_balance - count) : StarsAmount(0));
invalidate();
}
@@ -119,7 +126,7 @@ void Credits::invalidate() {
_reload.call();
}
void Credits::apply(uint64 balance) {
void Credits::apply(StarsAmount balance) {
_balance = balance;
updateNonLockedValue();
@@ -129,7 +136,7 @@ void Credits::apply(uint64 balance) {
}
}
void Credits::apply(PeerId peerId, uint64 balance) {
void Credits::apply(PeerId peerId, StarsAmount balance) {
_cachedPeerBalances[peerId] = balance;
}

View File

@@ -17,30 +17,32 @@ class Session;
namespace Data {
[[nodiscard]] StarsAmount FromTL(const MTPStarsAmount &value);
class Credits final {
public:
explicit Credits(not_null<Main::Session*> session);
~Credits();
void load(bool force = false);
void apply(uint64 balance);
void apply(PeerId peerId, uint64 balance);
void apply(StarsAmount balance);
void apply(PeerId peerId, StarsAmount balance);
[[nodiscard]] bool loaded() const;
[[nodiscard]] rpl::producer<bool> loadedValue() const;
[[nodiscard]] uint64 balance() const;
[[nodiscard]] uint64 balance(PeerId peerId) const;
[[nodiscard]] rpl::producer<uint64> balanceValue() const;
[[nodiscard]] StarsAmount balance() const;
[[nodiscard]] StarsAmount balance(PeerId peerId) const;
[[nodiscard]] rpl::producer<StarsAmount> balanceValue() const;
[[nodiscard]] rpl::producer<float64> rateValue(
not_null<PeerData*> ownedBotOrChannel);
void applyCurrency(PeerId peerId, uint64 balance);
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
void lock(int count);
void unlock(int count);
void withdrawLocked(int count);
void lock(StarsAmount count);
void unlock(StarsAmount count);
void withdrawLocked(StarsAmount count);
void invalidate();
void apply(const MTPDupdateStarsBalance &data);
@@ -52,12 +54,12 @@ private:
std::unique_ptr<Api::CreditsStatus> _loader;
base::flat_map<PeerId, uint64> _cachedPeerBalances;
base::flat_map<PeerId, StarsAmount> _cachedPeerBalances;
base::flat_map<PeerId, uint64> _cachedPeerCurrencyBalances;
uint64 _balance = 0;
uint64 _locked = 0;
rpl::variable<uint64> _nonLockedBalance;
StarsAmount _balance;
StarsAmount _locked;
rpl::variable<StarsAmount> _nonLockedBalance;
rpl::event_stream<> _loadedChanges;
crl::time _lastLoaded = 0;
float64 _rate = 0.;

View File

@@ -92,27 +92,28 @@ struct PeerUpdate {
BusinessDetails = (1ULL << 30),
Birthday = (1ULL << 31),
PersonalChannel = (1ULL << 32),
StarRefProgram = (1ULL << 33),
// For chats and channels
InviteLinks = (1ULL << 33),
Members = (1ULL << 34),
Admins = (1ULL << 35),
BannedUsers = (1ULL << 36),
Rights = (1ULL << 37),
PendingRequests = (1ULL << 38),
Reactions = (1ULL << 39),
InviteLinks = (1ULL << 34),
Members = (1ULL << 35),
Admins = (1ULL << 36),
BannedUsers = (1ULL << 37),
Rights = (1ULL << 38),
PendingRequests = (1ULL << 39),
Reactions = (1ULL << 40),
// For channels
ChannelAmIn = (1ULL << 40),
StickersSet = (1ULL << 41),
EmojiSet = (1ULL << 42),
ChannelLinkedChat = (1ULL << 43),
ChannelLocation = (1ULL << 44),
Slowmode = (1ULL << 45),
GroupCall = (1ULL << 46),
ChannelAmIn = (1ULL << 41),
StickersSet = (1ULL << 42),
EmojiSet = (1ULL << 43),
ChannelLinkedChat = (1ULL << 44),
ChannelLocation = (1ULL << 45),
Slowmode = (1ULL << 46),
GroupCall = (1ULL << 47),
// For iteration
LastUsedBit = (1ULL << 46),
LastUsedBit = (1ULL << 47),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@@ -168,6 +168,12 @@ ChatFilter ChatFilter::withTitle(const QString &title) const {
return result;
}
ChatFilter ChatFilter::withColorIndex(std::optional<uint8> c) const {
auto result = *this;
result._colorIndex = c;
return result;
}
ChatFilter ChatFilter::withChatlist(bool chatlist, bool hasMyLinks) const {
auto result = *this;
result._flags &= Flag::RulesMask;
@@ -182,6 +188,14 @@ ChatFilter ChatFilter::withChatlist(bool chatlist, bool hasMyLinks) const {
return result;
}
ChatFilter ChatFilter::withoutAlways(not_null<History*> history) const {
auto result = *this;
if (CanRemoveFromChatFilter(result, history)) {
result._always.remove(history);
}
return result;
}
MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
auto always = _always;
auto pinned = QVector<MTPInputPeer>();
@@ -277,7 +291,9 @@ const base::flat_set<not_null<History*>> &ChatFilter::never() const {
return _never;
}
bool ChatFilter::contains(not_null<History*> history) const {
bool ChatFilter::contains(
not_null<History*> history,
bool ignoreFakeUnread) const {
const auto flag = [&] {
const auto peer = history->peer;
if (const auto user = peer->asUser()) {
@@ -314,7 +330,7 @@ bool ChatFilter::contains(not_null<History*> history) const {
&& (!(_flags & Flag::NoRead)
|| state.unread
|| state.mention
|| history->fakeUnreadWhileOpened())
|| (!ignoreFakeUnread && history->fakeUnreadWhileOpened()))
&& (!(_flags & Flag::NoArchived)
|| (history->folderKnown() && !history->folder())))
|| _always.contains(history);
@@ -350,8 +366,11 @@ void ChatFilters::clear() {
_list.clear();
}
void ChatFilters::setPreloaded(const QVector<MTPDialogFilter> &result) {
void ChatFilters::setPreloaded(
const QVector<MTPDialogFilter> &result,
bool tagsEnabled) {
_loadRequestId = -1;
_tagsEnabled = tagsEnabled;
received(result);
crl::on_main(&_owner->session(), [=] {
if (_loadRequestId == -1) {
@@ -377,6 +396,7 @@ void ChatFilters::load(bool force) {
api.request(_loadRequestId).cancel();
_loadRequestId = api.request(MTPmessages_GetDialogFilters(
)).done([=](const MTPmessages_DialogFilters &result) {
_tagsEnabled = result.data().is_tags_enabled();
received(result.data().vfilters().v);
_loadRequestId = 0;
}).fail([=] {
@@ -388,12 +408,38 @@ void ChatFilters::load(bool force) {
}).send();
}
bool ChatFilters::tagsEnabled() const {
return _tagsEnabled.current();
}
rpl::producer<bool> ChatFilters::tagsEnabledValue() const {
return _tagsEnabled.value();
}
void ChatFilters::requestToggleTags(bool value, Fn<void()> fail) {
if (_toggleTagsRequestId) {
return;
}
_toggleTagsRequestId = _owner->session().api().request(
MTPmessages_ToggleDialogFilterTags(MTP_bool(value))
).done([=](const MTPBool &result) {
_tagsEnabled = value;
_toggleTagsRequestId = 0;
}).fail([=](const MTP::Error &error) {
const auto message = error.type();
_toggleTagsRequestId = 0;
LOG(("API Error: Toggle Tags - %1").arg(message));
fail();
}).send();
}
void ChatFilters::received(const QVector<MTPDialogFilter> &list) {
auto position = 0;
auto changed = false;
for (const auto &filter : list) {
auto parsed = ChatFilter::FromTL(filter, _owner);
const auto b = begin(_list) + position, e = end(_list);
const auto b = begin(_list) + position;
const auto e = end(_list);
const auto i = ranges::find(b, e, parsed.id(), &ChatFilter::id);
if (i == e) {
applyInsert(std::move(parsed), position);
@@ -618,14 +664,26 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
|| pinnedChanged
|| (filter.title() != updated.title())
|| (filter.iconEmoji() != updated.iconEmoji());
if (!listUpdated && !chatlistChanged) {
const auto colorChanged = filter.colorIndex() != updated.colorIndex();
const auto colorExistenceChanged = (!filter.colorIndex())
!= (!updated.colorIndex());
if (!listUpdated && !chatlistChanged && !colorChanged) {
return false;
}
const auto wasFilter = std::move(filter);
filter = std::move(updated);
auto entryToRefreshHeight = (Dialogs::Entry*)(nullptr);
if (rulesChanged) {
const auto filterList = _owner->chatsFilters().chatsList(id);
const auto areTagsEnabled = tagsEnabled();
const auto tagsExistence = [&](not_null<Dialogs::Row*> row) {
return (!areTagsEnabled || entryToRefreshHeight)
? false
: row->entry()->hasChatsFilterTags(0);
};
const auto feedHistory = [&](not_null<History*> history) {
const auto now = updated.contains(history);
const auto was = filter.contains(history);
const auto now = filter.contains(history);
const auto was = wasFilter.contains(history);
if (now != was) {
if (now) {
history->addToChatList(id, filterList);
@@ -637,7 +695,11 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
const auto feedList = [&](not_null<const Dialogs::MainList*> list) {
for (const auto &entry : *list->indexed()) {
if (const auto history = entry->history()) {
const auto wasTags = tagsExistence(entry);
feedHistory(history);
if (wasTags != tagsExistence(entry)) {
entryToRefreshHeight = entry->entry();
}
}
}
};
@@ -645,14 +707,13 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
if (const auto folder = _owner->folderLoaded(Data::Folder::kId)) {
feedList(folder->chatsList());
}
if (exceptionsChanged && !updated.always().empty()) {
if (exceptionsChanged && !filter.always().empty()) {
_exceptionsToLoad.push_back(id);
Ui::PostponeCall(&_owner->session(), [=] {
_owner->session().api().requestMoreDialogsIfNeeded();
});
}
}
filter = std::move(updated);
if (pinnedChanged) {
const auto filterList = _owner->chatsFilters().chatsList(id);
filterList->pinned()->applyList(filter.pinned());
@@ -660,6 +721,16 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
if (chatlistChanged) {
_isChatlistChanged.fire_copy(id);
}
if (colorChanged) {
_tagColorChanged.fire_copy(TagColorChanged{
.filterId = id,
.colorExistenceChanged = colorExistenceChanged,
});
}
if (entryToRefreshHeight) {
// Trigger a full refresh of height for the main list.
entryToRefreshHeight->updateChatListEntryHeight();
}
return listUpdated;
}
@@ -802,6 +873,10 @@ rpl::producer<FilterId> ChatFilters::isChatlistChanged() const {
return _isChatlistChanged.events();
}
rpl::producer<TagColorChanged> ChatFilters::tagColorChanged() const {
return _tagColorChanged.events();
}
bool ChatFilters::loadNextExceptions(bool chatsListLoaded) {
if (_exceptionsLoadRequestId) {
return true;
@@ -1019,4 +1094,14 @@ void ChatFilters::checkLoadMoreChatsLists() {
}
}
bool CanRemoveFromChatFilter(
const ChatFilter &filter,
not_null<History*> history) {
using Flag = ChatFilter::Flag;
const auto flagsWithoutNoReadNoArchivedNoMuted = filter.flags()
& ~(Flag::NoRead | Flag::NoArchived | Flag::NoMuted);
return (filter.always().size() > 1 || flagsWithoutNoReadNoArchivedNoMuted)
&& filter.contains(history);
}
} // namespace Data

View File

@@ -60,9 +60,11 @@ public:
[[nodiscard]] ChatFilter withId(FilterId id) const;
[[nodiscard]] ChatFilter withTitle(const QString &title) const;
[[nodiscard]] ChatFilter withColorIndex(std::optional<uint8>) const;
[[nodiscard]] ChatFilter withChatlist(
bool chatlist,
bool hasMyLinks) const;
[[nodiscard]] ChatFilter withoutAlways(not_null<History*>) const;
[[nodiscard]] static ChatFilter FromTL(
const MTPDialogFilter &data,
@@ -80,7 +82,9 @@ public:
[[nodiscard]] const std::vector<not_null<History*>> &pinned() const;
[[nodiscard]] const base::flat_set<not_null<History*>> &never() const;
[[nodiscard]] bool contains(not_null<History*> history) const;
[[nodiscard]] bool contains(
not_null<History*> history,
bool ignoreFakeUnread = false) const;
private:
FilterId _id = 0;
@@ -123,12 +127,19 @@ struct SuggestedFilter {
QString description;
};
struct TagColorChanged final {
FilterId filterId = 0;
bool colorExistenceChanged = false;
};
class ChatFilters final {
public:
explicit ChatFilters(not_null<Session*> owner);
~ChatFilters();
void setPreloaded(const QVector<MTPDialogFilter> &result);
void setPreloaded(
const QVector<MTPDialogFilter> &result,
bool tagsEnabled);
void load();
void reload();
@@ -139,6 +150,7 @@ public:
[[nodiscard]] const std::vector<ChatFilter> &list() const;
[[nodiscard]] rpl::producer<> changed() const;
[[nodiscard]] rpl::producer<FilterId> isChatlistChanged() const;
[[nodiscard]] rpl::producer<TagColorChanged> tagColorChanged() const;
[[nodiscard]] bool loaded() const;
[[nodiscard]] bool has() const;
@@ -185,6 +197,10 @@ public:
FilterId id) const;
void moreChatsHide(FilterId id, bool localOnly = false);
[[nodiscard]] bool tagsEnabled() const;
[[nodiscard]] rpl::producer<bool> tagsEnabledValue() const;
void requestToggleTags(bool value, Fn<void()> fail);
private:
struct MoreChatsData {
std::vector<not_null<PeerData*>> missing;
@@ -209,9 +225,11 @@ private:
base::flat_map<FilterId, std::unique_ptr<Dialogs::MainList>> _chatsLists;
rpl::event_stream<> _listChanged;
rpl::event_stream<FilterId> _isChatlistChanged;
rpl::event_stream<TagColorChanged> _tagColorChanged;
mtpRequestId _loadRequestId = 0;
mtpRequestId _saveOrderRequestId = 0;
mtpRequestId _saveOrderAfterId = 0;
mtpRequestId _toggleTagsRequestId = 0;
bool _loaded = false;
bool _reloading = false;
@@ -220,6 +238,8 @@ private:
rpl::event_stream<> _suggestedUpdated;
crl::time _suggestedLastReceived = 0;
rpl::variable<bool> _tagsEnabled = false;
std::deque<FilterId> _exceptionsToLoad;
mtpRequestId _exceptionsLoadRequestId = 0;
@@ -233,4 +253,8 @@ private:
};
[[nodiscard]] bool CanRemoveFromChatFilter(
const ChatFilter &filter,
not_null<History*> history);
} // namespace Data

View File

@@ -57,12 +57,15 @@ struct CreditsHistoryEntry final {
QDateTime lastSaleDate;
PhotoId photoId = 0;
std::vector<CreditsHistoryMedia> extended;
uint64 credits = 0;
StarsAmount credits;
uint64 bareMsgId = 0;
uint64 barePeerId = 0;
uint64 bareGiveawayMsgId = 0;
uint64 bareGiftStickerId = 0;
uint64 bareActorId = 0;
StarsAmount starrefAmount;
int starrefCommission = 0;
uint64 starrefRecipientId = 0;
PeerType peerType;
QDateTime subscriptionUntil;
QDateTime successDate;
@@ -89,7 +92,7 @@ struct CreditsStatusSlice final {
using OffsetToken = QString;
std::vector<CreditsHistoryEntry> list;
std::vector<SubscriptionEntry> subscriptions;
uint64 balance = 0;
StarsAmount balance;
uint64 subscriptionsMissingBalance = 0;
bool allLoaded = false;
OffsetToken token;

View File

@@ -7,22 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "core/stars_amount.h"
#include "data/data_statistics_chart.h"
#include <QtCore/QDateTime>
namespace Data {
using CreditsEarnInt = uint64;
struct CreditsEarnStatistics final {
explicit operator bool() const {
return !!usdRate;
}
Data::StatisticalGraph revenueGraph;
CreditsEarnInt currentBalance = 0;
CreditsEarnInt availableBalance = 0;
CreditsEarnInt overallRevenue = 0;
StarsAmount currentBalance;
StarsAmount availableBalance;
StarsAmount overallRevenue;
float64 usdRate = 0.;
bool isWithdrawalEnabled = false;
QDateTime nextWithdrawalAt;

View File

@@ -897,10 +897,12 @@ Dialogs::UnreadState ForumTopic::unreadStateFor(
const auto muted = this->muted();
result.messages = count;
result.chats = count ? 1 : 0;
result.chatsTopic = count ? 1 : 0;
result.mentions = unreadMentions().has() ? 1 : 0;
result.reactions = unreadReactions().has() ? 1 : 0;
result.messagesMuted = muted ? result.messages : 0;
result.chatsMuted = muted ? result.chats : 0;
result.chatsTopicMuted = muted ? result.chats : 0;
result.reactionsMuted = muted ? result.reactions : 0;
result.known = known;
return result;

View File

@@ -2220,7 +2220,7 @@ void MessageReactions::scheduleSendPaid(
_paid->scheduledPrivacySet = anonymous.has_value();
}
if (count > 0) {
_item->history()->session().credits().lock(count);
_item->history()->session().credits().lock(StarsAmount(count));
}
_item->history()->owner().reactions().schedulePaid(_item);
}
@@ -2233,7 +2233,8 @@ void MessageReactions::cancelScheduledPaid() {
if (_paid) {
if (_paid->scheduledFlag) {
if (const auto amount = int(_paid->scheduled)) {
_item->history()->session().credits().unlock(amount);
_item->history()->session().credits().unlock(
StarsAmount(amount));
}
_paid->scheduled = 0;
_paid->scheduledFlag = 0;
@@ -2296,9 +2297,9 @@ void MessageReactions::finishPaidSending(
if (const auto amount = send.count) {
const auto credits = &_item->history()->session().credits();
if (success) {
credits->withdrawLocked(amount);
credits->withdrawLocked(StarsAmount(amount));
} else {
credits->unlock(amount);
credits->unlock(StarsAmount(amount));
}
}
}

View File

@@ -1404,10 +1404,6 @@ void Session::forgetPassportCredentials() {
_passportCredentials = nullptr;
}
QString Session::nameSortKey(const QString &name) const {
return TextUtilities::RemoveAccents(name).toLower();
}
void Session::setupMigrationViewer() {
session().changes().peerUpdates(
PeerUpdate::Flag::Migration
@@ -1957,6 +1953,19 @@ rpl::producer<> Session::pinnedDialogsOrderUpdated() const {
return _pinnedDialogsOrderUpdated.events();
}
Session::CreditsSubsRebuilderPtr Session::createCreditsSubsRebuilder() {
if (auto result = activeCreditsSubsRebuilder()) {
return result;
}
auto result = std::make_shared<CreditsSubsRebuilder>();
_creditsSubsRebuilder = result;
return result;
}
Session::CreditsSubsRebuilderPtr Session::activeCreditsSubsRebuilder() const {
return _creditsSubsRebuilder.lock();
}
void Session::registerHeavyViewPart(not_null<ViewElement*> view) {
_heavyViewParts.emplace(view);
}

View File

@@ -70,6 +70,7 @@ class Chatbots;
class BusinessInfo;
struct ReactionId;
struct UnavailableReason;
struct CreditsStatusSlice;
struct RepliesReadTillUpdate {
FullMsgId id;
@@ -114,8 +115,6 @@ public:
return *_session;
}
[[nodiscard]] QString nameSortKey(const QString &name) const;
[[nodiscard]] Groups &groups() {
return _groups;
}
@@ -337,6 +336,11 @@ public:
void notifyPinnedDialogsOrderUpdated();
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
using CreditsSubsRebuilder = rpl::event_stream<Data::CreditsStatusSlice>;
using CreditsSubsRebuilderPtr = std::shared_ptr<CreditsSubsRebuilder>;
[[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder();
[[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const;
void registerRestricted(
not_null<const HistoryItem*> item,
const QString &reason);
@@ -1095,6 +1099,8 @@ private:
MessageIdsList _mimeForwardIds;
std::weak_ptr<CreditsSubsRebuilder> _creditsSubsRebuilder;
using CredentialsWithGeneration = std::pair<
const Passport::SavedCredentials,
int>;

View File

@@ -18,6 +18,7 @@ struct PeerSubscription final {
}
};
using PhotoId = uint64;
struct SubscriptionEntry final {
explicit operator bool() const {
return !id.isEmpty();
@@ -25,10 +26,14 @@ struct SubscriptionEntry final {
QString id;
QString inviteHash;
QString title;
QString slug;
QDateTime until;
PeerSubscription subscription;
uint64 barePeerId = 0;
PhotoId photoId = PhotoId(0);
bool cancelled = false;
bool cancelledByBot = false;
bool expired = false;
bool canRefulfill = false;
};

View File

@@ -114,8 +114,8 @@ void MessageCursor::applyTo(not_null<Ui::InputField*> field) {
}
PeerId PeerFromMessage(const MTPmessage &message) {
return message.match([](const MTPDmessageEmpty &) {
return PeerId(0);
return message.match([](const MTPDmessageEmpty &data) {
return data.vpeer_id() ? peerFromMTP(*data.vpeer_id()) : PeerId(0);
}, [](const auto &data) {
return peerFromMTP(data.vpeer_id());
});

View File

@@ -205,6 +205,16 @@ void UserData::setBusinessDetails(Data::BusinessDetails details) {
session().changes().peerUpdated(this, UpdateFlag::BusinessDetails);
}
void UserData::setStarRefProgram(StarRefProgram program) {
const auto info = botInfo.get();
if (info && info->starRefProgram != program) {
info->starRefProgram = program;
session().changes().peerUpdated(
this,
Data::PeerUpdate::Flag::StarRefProgram);
}
}
ChannelId UserData::personalChannelId() const {
return _personalChannelId;
}
@@ -600,6 +610,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
}
if (const auto info = user->botInfo.get()) {
info->canManageEmojiStatus = update.is_bot_can_manage_emoji_status();
user->setStarRefProgram(
Data::ParseStarRefProgram(update.vstarref_program()));
}
if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(user, pinned->v);
@@ -719,4 +731,19 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->fullUpdated();
}
StarRefProgram ParseStarRefProgram(const MTPStarRefProgram *program) {
if (!program) {
return {};
}
auto result = StarRefProgram();
const auto &data = program->data();
result.commission = data.vcommission_permille().v;
result.durationMonths = data.vduration_months().value_or_empty();
result.revenuePerUser = data.vdaily_revenue_per_user()
? Data::FromTL(*data.vdaily_revenue_per_user())
: StarsAmount();
result.endDate = data.vend_date().value_or_empty();
return result;
}
} // namespace Data

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "core/stars_amount.h"
#include "data/data_birthday.h"
#include "data/data_peer.h"
#include "data/data_chat_participant_status.h"
@@ -19,6 +20,17 @@ struct BotCommand;
struct BusinessDetails;
} // namespace Data
struct StarRefProgram {
StarsAmount revenuePerUser;
TimeId endDate = 0;
ushort commission = 0;
uint8 durationMonths = 0;
friend inline constexpr bool operator==(
StarRefProgram,
StarRefProgram) = default;
};
struct BotInfo {
BotInfo();
@@ -44,6 +56,8 @@ struct BotInfo {
ChatAdminRights groupAdminRights;
ChatAdminRights channelAdminRights;
StarRefProgram starRefProgram;
int version = 0;
int descriptionVersion = 0;
int activeUsers = 0;
@@ -206,6 +220,8 @@ public:
[[nodiscard]] const Data::BusinessDetails &businessDetails() const;
void setBusinessDetails(Data::BusinessDetails details);
void setStarRefProgram(StarRefProgram program);
[[nodiscard]] ChannelId personalChannelId() const;
[[nodiscard]] MsgId personalChannelMessageId() const;
void setPersonalChannel(ChannelId channelId, MsgId messageId);
@@ -253,4 +269,7 @@ namespace Data {
void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update);
[[nodiscard]] StarRefProgram ParseStarRefProgram(
const MTPStarRefProgram *program);
} // namespace Data

View File

@@ -22,6 +22,7 @@ DialogRow {
topicsSkipBig: pixels;
topicsHeight: pixels;
unreadMarkDiameter: pixels;
tagTop: pixels;
}
ThreeStateIcon {
@@ -89,6 +90,11 @@ defaultDialogRow: DialogRow {
textLeft: 68px;
textTop: 34px;
}
taggedDialogRow: DialogRow(defaultDialogRow) {
height: 72px;
textTop: 30px;
tagTop: 52px;
}
forumDialogRow: DialogRow(defaultDialogRow) {
height: 80px;
textTop: 32px;
@@ -96,6 +102,17 @@ forumDialogRow: DialogRow(defaultDialogRow) {
topicsSkipBig: 14px;
topicsHeight: 21px;
}
taggedForumDialogRow: DialogRow(forumDialogRow) {
height: 96px;
tagTop: 77px;
}
dialogRowFilterTagSkip : 4px;
dialogRowFilterTagFont : font(10px);
dialogRowOpenBotTextStyle: semiboldTextStyle;
dialogRowOpenBotHeight: 20px;
dialogRowOpenBotRight: 10px;
dialogRowOpenBotTop: 32px;
dialogRowOpenBotRecentTop: 28px;
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};

View File

@@ -0,0 +1,136 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Ui {
class RippleAnimation;
} // namespace Ui
namespace Dialogs {
class Row;
enum class SortMode {
Date = 0x00,
Name = 0x01,
Add = 0x02,
};
struct PositionChange {
int from = -1;
int to = -1;
int height = 0;
};
struct UnreadState {
int messages = 0;
int messagesMuted = 0;
int chats = 0;
int chatsMuted = 0;
int chatsTopic = 0;
int chatsTopicMuted = 0;
int marks = 0;
int marksMuted = 0;
int reactions = 0;
int reactionsMuted = 0;
int forums = 0;
int forumsMuted = 0;
int mentions = 0;
bool known = false;
UnreadState &operator+=(const UnreadState &other) {
messages += other.messages;
messagesMuted += other.messagesMuted;
chats += other.chats;
chatsMuted += other.chatsMuted;
chatsTopic += other.chatsTopic;
chatsTopicMuted += other.chatsTopicMuted;
marks += other.marks;
marksMuted += other.marksMuted;
reactions += other.reactions;
reactionsMuted += other.reactionsMuted;
forums += other.forums;
forumsMuted += other.forumsMuted;
mentions += other.mentions;
return *this;
}
UnreadState &operator-=(const UnreadState &other) {
messages -= other.messages;
messagesMuted -= other.messagesMuted;
chats -= other.chats;
chatsMuted -= other.chatsMuted;
chatsTopic -= other.chatsTopic;
chatsTopicMuted -= other.chatsTopicMuted;
marks -= other.marks;
marksMuted -= other.marksMuted;
reactions -= other.reactions;
reactionsMuted -= other.reactionsMuted;
forums -= other.forums;
forumsMuted -= other.forumsMuted;
mentions -= other.mentions;
return *this;
}
};
inline UnreadState operator+(const UnreadState &a, const UnreadState &b) {
auto result = a;
result += b;
return result;
}
inline UnreadState operator-(const UnreadState &a, const UnreadState &b) {
auto result = a;
result -= b;
return result;
}
struct BadgesState {
int unreadCounter = 0;
bool unread : 1 = false;
bool unreadMuted : 1 = false;
bool mention : 1 = false;
bool mentionMuted : 1 = false;
bool reaction : 1 = false;
bool reactionMuted : 1 = false;
friend inline constexpr auto operator<=>(
BadgesState,
BadgesState) = default;
[[nodiscard]] bool empty() const {
return !unread && !mention && !reaction;
}
};
enum class CountInBadge : uchar {
Default,
Chats,
Messages,
};
enum class IncludeInBadge : uchar {
Default,
Unmuted,
All,
UnmutedOrAll,
};
struct RowsByLetter {
not_null<Row*> main;
base::flat_map<QChar, not_null<Row*>> letters;
};
struct RightButton final {
QImage bg;
QImage selectedBg;
QImage activeBg;
Ui::Text::String text;
std::unique_ptr<Ui::RippleAnimation> ripple;
};
} // namespace Dialogs

View File

@@ -254,7 +254,31 @@ void Entry::notifyUnreadStateChange(const UnreadState &wasState) {
return state.messages || state.marks || state.mentions;
};
if (isForFilters(wasState) != isForFilters(nowState)) {
const auto wasTags = _tagColors.size();
owner().chatsFilters().refreshHistory(history);
// Hack for History::fakeUnreadWhileOpened().
if (!isForFilters(nowState)
&& (wasTags > 0)
&& (wasTags == _tagColors.size())) {
auto updateRequested = false;
for (const auto &filter : filters.list()) {
if (!(filter.flags() & Data::ChatFilter::Flag::NoRead)
|| !_chatListLinks.contains(filter.id())
|| filter.contains(history, true)) {
continue;
}
const auto wasTagsCount = _tagColors.size();
setColorIndexForFilterId(filter.id(), std::nullopt);
updateRequested |= (wasTagsCount != _tagColors.size());
}
if (updateRequested) {
updateChatListEntryHeight();
session().changes().peerUpdated(
history->peer,
Data::PeerUpdate::Flag::Name);
}
}
}
}
updateChatListEntryPostponed();
@@ -332,9 +356,29 @@ int Entry::posInChatList(FilterId filterId) const {
return mainChatListLink(filterId)->index();
}
void Entry::setColorIndexForFilterId(
FilterId filterId,
std::optional<uint8> colorIndex) {
if (!filterId) {
return;
}
if (colorIndex) {
_tagColors[filterId] = *colorIndex;
} else {
_tagColors.remove(filterId);
}
}
not_null<Row*> Entry::addToChatList(
FilterId filterId,
not_null<MainList*> list) {
if (filterId) {
const auto &list = owner().chatsFilters().list();
const auto it = ranges::find(list, filterId, &Data::ChatFilter::id);
if (it != end(list)) {
setColorIndexForFilterId(filterId, it->colorIndex());
}
}
if (const auto main = maybeMainChatListLink(filterId)) {
return main;
}
@@ -350,6 +394,12 @@ void Entry::removeFromChatList(
if (isPinnedDialog(filterId)) {
owner().setChatPinned(this, filterId, false);
}
if (filterId) {
const auto it = _tagColors.find(filterId);
if (it != end(_tagColors)) {
_tagColors.erase(it);
}
}
const auto i = _chatListLinks.find(filterId);
if (i == end(_chatListLinks)) {
@@ -397,4 +447,18 @@ void Entry::updateChatListEntryHeight() {
session().changes().entryUpdated(this, Data::EntryUpdate::Flag::Height);
}
[[nodiscard]] bool Entry::hasChatsFilterTags(FilterId exclude) const {
if (!owner().chatsFilters().tagsEnabled()) {
return false;
}
if (exclude) {
if (_tagColors.size() == 1) {
if (_tagColors.begin()->first == exclude) {
return false;
}
}
}
return !_tagColors.empty();
}
} // namespace Dialogs

View File

@@ -10,10 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/flat_map.h"
#include "base/weak_ptr.h"
#include "base/flags.h"
#include "dialogs/dialogs_key.h"
#include "dialogs/dialogs_common.h"
#include "ui/unread_badge.h"
class HistoryItem;
class History;
class UserData;
namespace Main {
@@ -26,6 +27,7 @@ class Forum;
class Folder;
class ForumTopic;
class SavedSublist;
class Thread;
} // namespace Data
namespace Ui {
@@ -39,108 +41,11 @@ struct PaintContext;
namespace Dialogs {
struct UnreadState;
class Row;
class IndexedList;
class MainList;
struct RowsByLetter {
not_null<Row*> main;
base::flat_map<QChar, not_null<Row*>> letters;
};
enum class SortMode {
Date = 0x00,
Name = 0x01,
Add = 0x02,
};
struct PositionChange {
int from = -1;
int to = -1;
int height = 0;
};
struct UnreadState {
int messages = 0;
int messagesMuted = 0;
int chats = 0;
int chatsMuted = 0;
int marks = 0;
int marksMuted = 0;
int reactions = 0;
int reactionsMuted = 0;
int mentions = 0;
bool known = false;
UnreadState &operator+=(const UnreadState &other) {
messages += other.messages;
messagesMuted += other.messagesMuted;
chats += other.chats;
chatsMuted += other.chatsMuted;
marks += other.marks;
marksMuted += other.marksMuted;
reactions += other.reactions;
reactionsMuted += other.reactionsMuted;
mentions += other.mentions;
return *this;
}
UnreadState &operator-=(const UnreadState &other) {
messages -= other.messages;
messagesMuted -= other.messagesMuted;
chats -= other.chats;
chatsMuted -= other.chatsMuted;
marks -= other.marks;
marksMuted -= other.marksMuted;
reactions -= other.reactions;
reactionsMuted -= other.reactionsMuted;
mentions -= other.mentions;
return *this;
}
};
inline UnreadState operator+(const UnreadState &a, const UnreadState &b) {
auto result = a;
result += b;
return result;
}
inline UnreadState operator-(const UnreadState &a, const UnreadState &b) {
auto result = a;
result -= b;
return result;
}
struct BadgesState {
int unreadCounter = 0;
bool unread : 1 = false;
bool unreadMuted : 1 = false;
bool mention : 1 = false;
bool mentionMuted : 1 = false;
bool reaction : 1 = false;
bool reactionMuted : 1 = false;
friend inline constexpr auto operator<=>(
BadgesState,
BadgesState) = default;
[[nodiscard]] bool empty() const {
return !unread && !mention && !reaction;
}
};
enum class CountInBadge : uchar {
Default,
Chats,
Messages,
};
enum class IncludeInBadge : uchar {
Default,
Unmuted,
All,
UnmutedOrAll,
};
[[nodiscard]] BadgesState BadgesForUnread(
const UnreadState &state,
CountInBadge count = CountInBadge::Default,
@@ -186,6 +91,7 @@ public:
not_null<Row*> addToChatList(
FilterId filterId,
not_null<MainList*> list);
void setColorIndexForFilterId(FilterId, std::optional<uint8>);
void removeFromChatList(
FilterId filterId,
not_null<MainList*> list);
@@ -251,6 +157,7 @@ public:
return _chatListPeerBadge;
}
[[nodiscard]] bool hasChatsFilterTags(FilterId exclude) const;
protected:
void notifyUnreadStateChange(const UnreadState &wasState);
inline auto unreadStateChangeNotifier(bool required);
@@ -281,6 +188,7 @@ private:
uint64 _sortKeyInChatList = 0;
uint64 _sortKeyByDate = 0;
base::flat_map<FilterId, int> _pinnedIndex;
base::flat_map<FilterId, uint8> _tagColors;
mutable Ui::PeerBadge _chatListPeerBadge;
mutable Ui::Text::String _chatListNameText;
mutable int _chatListNameVersion = 0;
@@ -295,7 +203,7 @@ auto Entry::unreadStateChangeNotifier(bool required) {
_flags |= Flag::InUnreadChangeBlock;
const auto notify = required && inChatList();
const auto wasState = notify ? chatListUnreadState() : UnreadState();
return gsl::finally([=] {
return gsl::finally([=, this] {
_flags &= ~Flag::InUnreadChangeBlock;
if (notify) {
Assert(inChatList());

View File

@@ -7,13 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/dialogs_entry.h"
#include "dialogs/dialogs_list.h"
class History;
namespace Dialogs {
struct RowsByLetter;
class Row;
class IndexedList {
public:
IndexedList(SortMode sortMode, FilterId filterId = 0);

View File

@@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_options.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "data/data_drafts.h"
#include "data/data_folder.h"
@@ -62,6 +63,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "ui/chat/chats_filter_tag.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/loading_element.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
@@ -73,6 +76,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_dialogs.h"
#include "styles/style_chat.h" // popupMenuExpandedSeparator
#include "styles/style_chat_helpers.h"
#include "styles/style_color_indices.h"
#include "styles/style_window.h"
#include "styles/style_menu_icons.h"
@@ -86,6 +90,15 @@ constexpr auto kStartReorderThreshold = 30;
constexpr auto kQueryPreviewLimit = 32;
constexpr auto kPreviewPostsLimit = 3;
[[nodiscard]] InnerWidget::ChatsFilterTagsKey SerializeFilterTagsKey(
FilterId filterId,
uint8 more,
bool active) {
return (filterId & 0xFFFFFFFF)
| (static_cast<int64_t>(more) << 32)
| (static_cast<int64_t>(active) << 40);
}
[[nodiscard]] int FixedOnTopDialogsCount(not_null<Dialogs::IndexedList*> list) {
auto result = 0;
for (const auto &row : *list) {
@@ -112,6 +125,19 @@ constexpr auto kPreviewPostsLimit = 3;
return result;
}
[[nodiscard]] UserData *MaybeBotWithApp(Row *row) {
if (row) {
if (const auto history = row->key().history()) {
if (const auto user = history->peer->asUser()) {
if (user->botInfo && user->botInfo->hasMainApp) {
return user;
}
}
}
}
return nullptr;
}
[[nodiscard]] object_ptr<SearchEmpty> MakeSearchEmpty(
QWidget *parent,
SearchState state) {
@@ -220,6 +246,7 @@ InnerWidget::InnerWidget(
style::PaletteChanged(
) | rpl::start_with_next([=] {
_topicJumpCache = nullptr;
_chatsFilterTags.clear();
}, lifetime());
session().downloaderTaskFinished(
@@ -286,12 +313,85 @@ InnerWidget::InnerWidget(
}, lifetime());
rpl::merge(
session().settings().archiveCollapsedChanges() | rpl::to_empty,
session().data().chatsFilters().changed()
) | rpl::start_with_next([=] {
session().settings().archiveCollapsedChanges() | rpl::map_to(false),
session().data().chatsFilters().changed() | rpl::map_to(true)
) | rpl::start_with_next([=](bool refreshHeight) {
if (refreshHeight) {
_chatsFilterTags.clear();
}
if (refreshHeight && _filterId) {
// Height of the main list will be refreshed in other way.
_shownList->updateHeights(_narrowRatio);
}
refreshWithCollapsedRows();
}, lifetime());
session().data().chatsFilters().tagsEnabledValue(
) | rpl::distinct_until_changed() | rpl::start_with_next([=](bool tags) {
_handleChatListEntryTagRefreshesLifetime.destroy();
if (_shownList->updateHeights(_narrowRatio)) {
refresh();
}
if (!tags) {
return;
}
using Event = Data::Session::ChatListEntryRefresh;
session().data().chatListEntryRefreshes(
) | rpl::filter([=](const Event &event) {
if (_waitingAllChatListEntryRefreshesForTags) {
return false;
}
if (event.existenceChanged) {
if (event.key.entry()->inChatList(_filterId)) {
_waitingAllChatListEntryRefreshesForTags = true;
return true;
}
}
return false;
}) | rpl::start_with_next([=](const Event &event) {
Ui::PostponeCall(crl::guard(this, [=] {
_waitingAllChatListEntryRefreshesForTags = false;
if (_shownList->updateHeights(_narrowRatio)) {
refresh();
}
}));
}, _handleChatListEntryTagRefreshesLifetime);
session().data().chatsFilters().tagColorChanged(
) | rpl::start_with_next([=](Data::TagColorChanged data) {
const auto filterId = data.filterId;
const auto key = SerializeFilterTagsKey(filterId, 0, false);
const auto activeKey = SerializeFilterTagsKey(filterId, 0, true);
{
auto &tags = _chatsFilterTags;
if (const auto it = tags.find(key); it != tags.end()) {
tags.erase(it);
}
if (const auto it = tags.find(activeKey); it != tags.end()) {
tags.erase(it);
}
}
if (data.colorExistenceChanged) {
auto &filters = session().data().chatsFilters();
for (const auto &filter : filters.list()) {
if (filter.id() != filterId) {
continue;
}
const auto c = filter.colorIndex();
const auto list = filters.chatsList(filterId);
for (const auto &row : list->indexed()->all()) {
row->entry()->setColorIndexForFilterId(filterId, c);
}
}
if (_shownList->updateHeights(_narrowRatio)) {
refresh();
}
} else {
update();
}
}, _handleChatListEntryTagRefreshesLifetime);
}, lifetime());
session().settings().archiveInMainMenuChanges(
) | rpl::start_with_next([=] {
refresh();
@@ -408,7 +508,7 @@ bool InnerWidget::updateEntryHeight(not_null<Entry*> entry) {
result.top = top;
}
if (result.row->key().entry() == entry) {
result.row->recountHeight(_narrowRatio);
result.row->recountHeight(_narrowRatio, _filterId);
changing = true;
top = result.top;
}
@@ -692,7 +792,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
not_null<Row*> row,
bool selected,
bool mayBeActive) {
const auto key = row->key();
const auto &key = row->key();
const auto active = mayBeActive && isRowActive(row, activeEntry);
const auto forum = key.history() && key.history()->isForum();
if (forum && !_topicJumpCache) {
@@ -700,8 +800,73 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
}
const auto expanding = forum
&& (key.history()->peer->id == childListShown.peerId);
context.rightButton = maybeCacheRightButton(row);
context.st = (forum ? &st::forumDialogRow : _st.get());
auto chatsFilterTags = std::vector<QImage*>();
if (context.narrow) {
context.chatsFilterTags = nullptr;
} else if (row->entry()->hasChatsFilterTags(context.filter)) {
const auto a = active;
context.st = forum
? &st::taggedForumDialogRow
: &st::taggedDialogRow;
auto availableWidth = context.width
- context.st->padding.right()
- st::dialogsUnreadPadding
- context.st->nameLeft;
auto more = uint8(0);
const auto &list = session().data().chatsFilters().list();
for (const auto &filter : list) {
if (!row->entry()->inChatList(filter.id())
|| (filter.id() == context.filter)) {
continue;
}
if (active
&& (filter.flags() & Data::ChatFilter::Flag::NoRead)
&& !filter.contains(key.history(), true)) {
// Hack for History::fakeUnreadWhileOpened().
continue;
}
if (const auto tag = cacheChatsFilterTag(filter, 0, a)) {
if (more) {
more++;
continue;
}
const auto tagWidth = tag->width()
/ style::DevicePixelRatio();
if (availableWidth < tagWidth) {
more++;
} else {
chatsFilterTags.push_back(tag);
availableWidth -= tagWidth
+ st::dialogRowFilterTagSkip;
}
}
}
if (more) {
if (const auto tag = cacheChatsFilterTag({}, more, a)) {
const auto tagWidth = tag->width()
/ style::DevicePixelRatio();
if (availableWidth < tagWidth) {
more++;
if (!chatsFilterTags.empty()) {
const auto tag = cacheChatsFilterTag({}, more, a);
if (tag) {
chatsFilterTags.back() = tag;
}
}
} else {
chatsFilterTags.push_back(tag);
}
}
}
context.chatsFilterTags = &chatsFilterTags;
} else {
context.chatsFilterTags = nullptr;
}
context.topicsExpanded = (expanding && !active)
? childListShown.shown
: 0.;
@@ -1050,6 +1215,46 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
}
}
[[nodiscard]] RightButton *InnerWidget::maybeCacheRightButton(Row *row) {
if (const auto user = MaybeBotWithApp(row)) {
const auto it = _rightButtons.find(user->id);
if (it == _rightButtons.end()) {
auto rightButton = RightButton();
const auto text = tr::lng_profile_open_app_short(tr::now);
rightButton.text.setText(st::dialogRowOpenBotTextStyle, text);
const auto size = QSize(
rightButton.text.maxWidth()
+ rightButton.text.minHeight(),
st::dialogRowOpenBotHeight);
const auto generateBg = [&](const style::color &c) {
auto bg = QImage(
style::DevicePixelRatio() * size,
QImage::Format_ARGB32_Premultiplied);
bg.setDevicePixelRatio(style::DevicePixelRatio());
bg.fill(Qt::transparent);
{
auto p = QPainter(&bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(c);
const auto r = size.height() / 2;
p.drawRoundedRect(Rect(size), r, r);
}
return bg;
};
rightButton.bg = generateBg(st::activeButtonBg);
rightButton.selectedBg = generateBg(st::activeButtonBgOver);
rightButton.activeBg = generateBg(st::activeButtonFg);
return &(_rightButtons.emplace(
user->id,
std::move(rightButton)).first->second);
} else {
return &(it->second);
}
}
return nullptr;
}
Ui::VideoUserpic *InnerWidget::validateVideoUserpic(not_null<Row*> row) {
const auto history = row->history();
return history ? validateVideoUserpic(history) : nullptr;
@@ -1394,7 +1599,7 @@ void InnerWidget::clearIrrelevantState() {
setHashtagPressed(-1);
_hashtagDeleteSelected = _hashtagDeletePressed = false;
_filteredSelected = -1;
setFilteredPressed(-1, false);
setFilteredPressed(-1, false, false);
_peerSearchSelected = -1;
setPeerSearchPressed(-1);
_previewSelected = -1;
@@ -1409,6 +1614,26 @@ void InnerWidget::clearIrrelevantState() {
}
}
bool InnerWidget::lookupIsInBotAppButton(
Row *row,
QPoint localPosition) {
if (const auto user = MaybeBotWithApp(row)) {
const auto it = _rightButtons.find(user->id);
if (it != _rightButtons.end()) {
const auto s = it->second.bg.size() / style::DevicePixelRatio();
const auto r = QRect(
width() - s.width() - st::dialogRowOpenBotRight,
st::dialogRowOpenBotTop,
s.width(),
s.height());
if (r.contains(localPosition)) {
return true;
}
}
}
return false;
}
void InnerWidget::selectByMouse(QPoint globalPosition) {
const auto local = mapFromGlobal(globalPosition);
if (updateReorderPinned(local)) {
@@ -1449,16 +1674,19 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
: (mouseY >= offset)
? _shownList->rowAtY(mouseY - offset)
: nullptr;
const auto mappedY = selected ? mouseY - offset - selected->top() : 0;
const auto selectedTopicJump = selected
&& selected->lookupIsInTopicJump(
local.x(),
mouseY - offset - selected->top());
&& selected->lookupIsInTopicJump(local.x(), mappedY);
const auto selectedBotApp = selected
&& lookupIsInBotAppButton(selected, QPoint(local.x(), mappedY));
if (_collapsedSelected != collapsedSelected
|| _selected != selected
|| _selectedTopicJump != selectedTopicJump) {
|| _selectedTopicJump != selectedTopicJump
|| _selectedBotApp != selectedBotApp) {
updateSelectedRow();
_selected = selected;
_selectedTopicJump = selectedTopicJump;
_selectedBotApp = selectedBotApp;
_collapsedSelected = collapsedSelected;
updateSelectedRow();
setCursor((_selected || _collapsedSelected >= 0)
@@ -1491,15 +1719,24 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
if (filteredSelected < 0 || filteredSelected >= _filterResults.size()) {
filteredSelected = -1;
}
const auto mappedY = (filteredSelected >= 0)
? mouseY - skip - _filterResults[filteredSelected].top
: 0;
const auto selectedTopicJump = (filteredSelected >= 0)
&& _filterResults[filteredSelected].row->lookupIsInTopicJump(
local.x(),
mouseY - skip - _filterResults[filteredSelected].top);
mappedY);
const auto selectedBotApp = (filteredSelected >= 0)
&& lookupIsInBotAppButton(
_filterResults[filteredSelected].row,
QPoint(local.x(), mappedY));
if (_filteredSelected != filteredSelected
|| _selectedTopicJump != selectedTopicJump) {
|| _selectedTopicJump != selectedTopicJump
|| _selectedBotApp != selectedBotApp) {
updateSelectedRow();
_filteredSelected = filteredSelected;
_selectedTopicJump = selectedTopicJump;
_selectedBotApp = selectedBotApp;
updateSelectedRow();
}
}
@@ -1584,11 +1821,11 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
selectByMouse(e->globalPos());
_pressButton = e->button();
setPressed(_selected, _selectedTopicJump);
setPressed(_selected, _selectedTopicJump, _selectedBotApp);
setCollapsedPressed(_collapsedSelected);
setHashtagPressed(_hashtagSelected);
_hashtagDeletePressed = _hashtagDeleteSelected;
setFilteredPressed(_filteredSelected, _selectedTopicJump);
setFilteredPressed(_filteredSelected, _selectedTopicJump, _selectedBotApp);
setPeerSearchPressed(_peerSearchSelected);
setPreviewPressed(_previewSelected);
setSearchedPressed(_searchedSelected);
@@ -1617,7 +1854,8 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
};
const auto origin = e->pos()
- QPoint(0, dialogsOffset() + _pressed->top());
if (_pressedTopicJump) {
if (addBotAppRipple(origin, updateCallback)) {
} else if (_pressedTopicJump) {
row->addTopicJumpRipple(
origin,
_topicJumpCache.get(),
@@ -1643,7 +1881,8 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
const auto origin = e->pos()
- QPoint(0, filteredOffset() + result.top);
const auto updateCallback = [=] { repaintDialogRow(filterId, row); };
if (_pressedTopicJump) {
if (addBotAppRipple(origin, updateCallback)) {
} else if (_pressedTopicJump) {
row->addTopicJumpRipple(
origin,
_topicJumpCache.get(),
@@ -1677,6 +1916,25 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
}
}
bool InnerWidget::addBotAppRipple(QPoint origin, Fn<void()> updateCallback) {
if (!(_pressedBotApp && _pressedBotAppData)) {
return false;
}
const auto size = _pressedBotAppData->bg.size()
/ style::DevicePixelRatio();
if (!_pressedBotAppData->ripple) {
_pressedBotAppData->ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
Ui::RippleAnimation::RoundRectMask(size, size.height() / 2),
updateCallback);
}
const auto shift = QPoint(
width() - size.width() - st::dialogRowOpenBotRight,
st::dialogRowOpenBotTop);
_pressedBotAppData->ripple->add(origin - shift);
return true;
}
const std::vector<Key> &InnerWidget::pinnedChatsOrder() const {
const auto owner = &session().data();
return _savedSublists
@@ -1949,6 +2207,7 @@ void InnerWidget::mousePressReleased(
setCollapsedPressed(-1);
const auto pressedTopicRootId = _pressedTopicJumpRootId;
const auto pressedTopicJump = _pressedTopicJump;
const auto pressedBotApp = _pressedBotApp;
auto pressed = _pressed;
clearPressed();
auto hashtagPressed = _hashtagPressed;
@@ -1956,7 +2215,7 @@ void InnerWidget::mousePressReleased(
auto hashtagDeletePressed = _hashtagDeletePressed;
_hashtagDeletePressed = false;
auto filteredPressed = _filteredPressed;
setFilteredPressed(-1, false);
setFilteredPressed(-1, false, false);
auto peerSearchPressed = _peerSearchPressed;
setPeerSearchPressed(-1);
auto previewPressed = _previewPressed;
@@ -1968,12 +2227,16 @@ void InnerWidget::mousePressReleased(
if (wasDragging) {
selectByMouse(globalPosition);
}
if (_pressedBotAppData && _pressedBotAppData->ripple) {
_pressedBotAppData->ripple->lastStop();
}
updateSelectedRow();
if (!wasDragging && button == Qt::LeftButton) {
if ((collapsedPressed >= 0 && collapsedPressed == _collapsedSelected)
|| (pressed
&& pressed == _selected
&& pressedTopicJump == _selectedTopicJump)
&& pressedTopicJump == _selectedTopicJump
&& pressedBotApp == _selectedBotApp)
|| (hashtagPressed >= 0
&& hashtagPressed == _hashtagSelected
&& hashtagDeletePressed == _hashtagDeleteSelected)
@@ -1986,7 +2249,16 @@ void InnerWidget::mousePressReleased(
&& searchedPressed == _searchedSelected)
|| (pressedMorePosts
&& pressedMorePosts == _selectedMorePosts)) {
chooseRow(modifiers, pressedTopicRootId);
if (pressedBotApp && (pressed || filteredPressed >= 0)) {
const auto &row = pressed
? pressed
: _filterResults[filteredPressed].row.get();
if (const auto user = MaybeBotWithApp(row)) {
_openBotMainAppRequests.fire(peerToUser(user->id));
}
} else {
chooseRow(modifiers, pressedTopicRootId);
}
}
}
if (auto activated = ClickHandler::unpressed()) {
@@ -2007,14 +2279,31 @@ void InnerWidget::setCollapsedPressed(int pressed) {
}
}
void InnerWidget::setPressed(Row *pressed, bool pressedTopicJump) {
if (_pressed != pressed || (pressed && _pressedTopicJump != pressedTopicJump)) {
void InnerWidget::setPressed(
Row *pressed,
bool pressedTopicJump,
bool pressedBotApp) {
if ((_pressed != pressed)
|| (pressed && _pressedTopicJump != pressedTopicJump)
|| (pressed && _pressedBotApp != pressedBotApp)) {
if (_pressed) {
_pressed->stopLastRipple();
}
if (_pressedBotAppData && _pressedBotAppData->ripple) {
_pressedBotAppData->ripple->lastStop();
}
_pressed = pressed;
if (pressed || !pressedTopicJump) {
if (pressed || !pressedTopicJump || !pressedBotApp) {
_pressedTopicJump = pressedTopicJump;
_pressedBotApp = pressedBotApp;
if (pressedBotApp) {
if (const auto user = MaybeBotWithApp(pressed)) {
const auto it = _rightButtons.find(user->id);
if (it != _rightButtons.end()) {
_pressedBotAppData = &(it->second);
}
}
}
const auto history = pressedTopicJump
? pressed->history()
: nullptr;
@@ -2025,7 +2314,7 @@ void InnerWidget::setPressed(Row *pressed, bool pressedTopicJump) {
}
void InnerWidget::clearPressed() {
setPressed(nullptr, false);
setPressed(nullptr, false, false);
}
void InnerWidget::setHashtagPressed(int pressed) {
@@ -2035,15 +2324,32 @@ void InnerWidget::setHashtagPressed(int pressed) {
_hashtagPressed = pressed;
}
void InnerWidget::setFilteredPressed(int pressed, bool pressedTopicJump) {
void InnerWidget::setFilteredPressed(
int pressed,
bool pressedTopicJump,
bool pressedBotApp) {
if (_filteredPressed != pressed
|| (pressed >= 0 && _pressedTopicJump != pressedTopicJump)) {
|| (pressed >= 0 && _pressedTopicJump != pressedTopicJump)
|| (pressed >= 0 && _pressedBotApp != pressedBotApp)) {
if (base::in_range(_filteredPressed, 0, _filterResults.size())) {
_filterResults[_filteredPressed].row->stopLastRipple();
}
if (_pressedBotAppData && _pressedBotAppData->ripple) {
_pressedBotAppData->ripple->lastStop();
}
_filteredPressed = pressed;
if (pressed >= 0 || !pressedTopicJump) {
if (pressed >= 0 || !pressedTopicJump || !pressedBotApp) {
_pressedTopicJump = pressedTopicJump;
_pressedBotApp = pressedBotApp;
if (pressed >= 0 && pressedBotApp) {
const auto &row = _filterResults[pressed].row;
if (const auto history = row->history()) {
const auto it = _rightButtons.find(history->peer->id);
if (it != _rightButtons.end()) {
_pressedBotAppData = &(it->second);
}
}
}
const auto history = pressedTopicJump
? _filterResults[pressed].row->history()
: nullptr;
@@ -2120,7 +2426,7 @@ void InnerWidget::dialogRowReplaced(
_selected = newRow;
}
if (_pressed == oldRow) {
setPressed(newRow, _pressedTopicJump);
setPressed(newRow, _pressedTopicJump, _pressedBotApp);
}
if (_dragging == oldRow) {
if (newRow) {
@@ -2812,7 +3118,7 @@ void InnerWidget::applySearchState(SearchState state) {
end(results));
for (const auto e = end(_filterResults); i != e; ++i) {
i->top = top;
i->row->recountHeight(_narrowRatio);
i->row->recountHeight(_narrowRatio, _filterId);
top += i->row->height();
}
};
@@ -2885,7 +3191,7 @@ void InnerWidget::appendToFiltered(Key key) {
}
}
auto row = std::make_unique<Row>(key, 0, 0);
row->recountHeight(_narrowRatio);
row->recountHeight(_narrowRatio, _filterId);
const auto &[i, ok] = _filterResultsGlobal.emplace(key, std::move(row));
const auto height = filteredHeight();
_filterResults.emplace_back(i->second.get());
@@ -3943,6 +4249,42 @@ void InnerWidget::restoreChatsFilterScrollState(FilterId filterId) {
}
}
QImage *InnerWidget::cacheChatsFilterTag(
const Data::ChatFilter &filter,
uint8 more,
bool active) {
if (!filter.id() && !more) {
return nullptr;
}
const auto key = SerializeFilterTagsKey(filter.id(), more, active);
{
const auto it = _chatsFilterTags.find(key);
if (it != end(_chatsFilterTags)) {
return &it->second;
}
}
auto roundedText = QString();
auto colorIndex = -1;
if (filter.id()) {
roundedText = filter.title().toUpper();
if (filter.colorIndex()) {
colorIndex = *(filter.colorIndex());
}
} else if (more > 0) {
roundedText = QChar('+') + QString::number(more);
colorIndex = st::colorIndexBlue;
}
if (roundedText.isEmpty() || colorIndex < 0) {
return nullptr;
}
return &_chatsFilterTags.emplace(
key,
Ui::ChatsFilterTag(
std::move(roundedText),
Ui::EmptyUserpic::UserpicColor(colorIndex).color2->c,
active)).first->second;
}
bool InnerWidget::chooseHashtag() {
if (_state != WidgetState::Filtered) {
return false;
@@ -4619,4 +4961,8 @@ bool InnerWidget::jumpToDialogRow(RowDescriptor to) {
return _controller->jumpToChatListEntry(to);
}
rpl::producer<UserId> InnerWidget::openBotMainAppRequests() const {
return _openBotMainAppRequests.events();
}
} // namespace Dialogs

View File

@@ -41,6 +41,7 @@ class SessionController;
} // namespace Window
namespace Data {
class ChatFilter;
class Thread;
class Folder;
class Forum;
@@ -63,6 +64,7 @@ class SearchTags;
class SearchEmpty;
class ChatSearchIn;
enum class HashOrCashtag : uchar;
struct RightButton;
struct ChosenRow {
Key key;
@@ -99,6 +101,8 @@ enum class WidgetState {
class InnerWidget final : public Ui::RpWidget {
public:
using ChatsFilterTagsKey = int64;
struct ChildListShown {
PeerId peerId = 0;
float64 shown = 0.;
@@ -197,6 +201,8 @@ public:
return _touchCancelRequests.events();
}
[[nodiscard]] rpl::producer<UserId> openBotMainAppRequests() const;
protected:
void visibleTopBottomUpdated(
int visibleTop,
@@ -283,10 +289,13 @@ private:
void scrollToItem(int top, int height);
void scrollToDefaultSelected();
void setCollapsedPressed(int pressed);
void setPressed(Row *pressed, bool pressedTopicJump);
void setPressed(Row *pressed, bool pressedTopicJump, bool pressedBotApp);
void clearPressed();
void setHashtagPressed(int pressed);
void setFilteredPressed(int pressed, bool pressedTopicJump);
void setFilteredPressed(
int pressed,
bool pressedTopicJump,
bool pressedBotApp);
void setPeerSearchPressed(int pressed);
void setPreviewPressed(int pressed);
void setSearchedPressed(int pressed);
@@ -320,6 +329,8 @@ private:
void updateRowCornerStatusShown(not_null<History*> history);
void repaintDialogRowCornerStatus(not_null<History*> history);
bool addBotAppRipple(QPoint origin, Fn<void()> updateCallback);
void setupShortcuts();
RowDescriptor computeJump(
const RowDescriptor &to,
@@ -448,6 +459,16 @@ private:
void saveChatsFilterScrollState(FilterId filterId);
void restoreChatsFilterScrollState(FilterId filterId);
[[nodiscard]] bool lookupIsInBotAppButton(
Row *row,
QPoint localPosition);
[[nodiscard]] RightButton *maybeCacheRightButton(Row *row);
[[nodiscard]] QImage *cacheChatsFilterTag(
const Data::ChatFilter &filter,
uint8 more,
bool active);
const not_null<Window::SessionController*> _controller;
not_null<IndexedList*> _shownList;
@@ -475,6 +496,10 @@ private:
bool _selectedTopicJump = false;
bool _pressedTopicJump = false;
RightButton *_pressedBotAppData = nullptr;
bool _selectedBotApp = false;
bool _pressedBotApp = false;
Row *_dragging = nullptr;
int _draggingIndex = -1;
int _aboveIndex = -1;
@@ -554,6 +579,12 @@ private:
base::flat_map<FilterId, int> _chatsFilterScrollStates;
std::unordered_map<ChatsFilterTagsKey, QImage> _chatsFilterTags;
bool _waitingAllChatListEntryRefreshesForTags = false;
rpl::lifetime _handleChatListEntryTagRefreshesLifetime;
std::unordered_map<PeerId, RightButton> _rightButtons;
Fn<void()> _loadMoreCallback;
Fn<void()> _loadMoreFilteredCallback;
rpl::event_stream<> _listBottomReached;
@@ -565,6 +596,7 @@ private:
rpl::event_stream<SearchRequestDelay> _searchRequests;
rpl::event_stream<QString> _completeHashtagRequests;
rpl::event_stream<> _refreshHashtagsRequests;
rpl::event_stream<UserId> _openBotMainAppRequests;
RowDescriptor _chatPreviewRow;
bool _chatPreviewScheduled = false;

View File

@@ -32,7 +32,7 @@ not_null<Row*> List::addToEnd(Key key) {
key,
std::make_unique<Row>(key, _rows.size(), height())
).first->second.get();
result->recountHeight(_narrowRatio);
result->recountHeight(_narrowRatio, _filterId);
_rows.emplace_back(result);
if (_sortMode == SortMode::Date) {
adjustByDate(result);
@@ -112,7 +112,7 @@ bool List::updateHeight(Key key, float64 narrowRatio) {
const auto index = row->index();
auto top = row->top();
const auto was = row->height();
row->recountHeight(narrowRatio);
row->recountHeight(narrowRatio, _filterId);
if (row->height() == was) {
return false;
}
@@ -129,7 +129,7 @@ bool List::updateHeights(float64 narrowRatio) {
auto top = 0;
for (const auto &row : _rows) {
row->_top = top;
row->recountHeight(narrowRatio);
row->recountHeight(narrowRatio, _filterId);
top += row->height();
}
return (height() != was);

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/dialogs_common.h"
#include "dialogs/dialogs_indexed_list.h"
#include "dialogs/dialogs_pinned_list.h"

View File

@@ -302,7 +302,8 @@ void BasicRow::paintUserpic(
not_null<Entry*> entry,
PeerData *peer,
Ui::VideoUserpic *videoUserpic,
const Ui::PaintContext &context) const {
const Ui::PaintContext &context,
bool hasUnreadBadgesAbove) const {
PaintUserpic(p, entry, peer, videoUserpic, _userpic, context);
}
@@ -316,11 +317,19 @@ Row::~Row() {
clearTopicJumpRipple();
}
void Row::recountHeight(float64 narrowRatio) {
void Row::recountHeight(float64 narrowRatio, FilterId filterId) {
if (const auto history = _id.history()) {
const auto hasTags = _id.entry()->hasChatsFilterTags(filterId);
_height = history->isForum()
? anim::interpolate(
st::forumDialogRow.height,
hasTags
? st::taggedForumDialogRow.height
: st::forumDialogRow.height,
st::defaultDialogRow.height,
narrowRatio)
: hasTags
? anim::interpolate(
st::taggedDialogRow.height,
st::defaultDialogRow.height,
narrowRatio)
: st::defaultDialogRow.height;
@@ -363,12 +372,15 @@ void Row::setCornerBadgeShown(
void Row::updateCornerBadgeShown(
not_null<PeerData*> peer,
Fn<void()> updateCallback) const {
Fn<void()> updateCallback,
bool hasUnreadBadgesAbove) const {
const auto user = peer->asUser();
const auto now = user ? base::unixtime::now() : TimeId();
const auto channel = user ? nullptr : peer->asChannel();
const auto nextLayer = [&] {
if (user && Data::IsUserOnline(user, now)) {
if (hasUnreadBadgesAbove) {
return kNoneLayer;
} else if (user && Data::IsUserOnline(user, now)) {
return kTopLayer;
} else if (channel
&& (Data::ChannelHasActiveCall(channel)
@@ -525,9 +537,10 @@ void Row::paintUserpic(
not_null<Entry*> entry,
PeerData *peer,
Ui::VideoUserpic *videoUserpic,
const Ui::PaintContext &context) const {
const Ui::PaintContext &context,
bool hasUnreadBadgesAbove) const {
if (peer) {
updateCornerBadgeShown(peer);
updateCornerBadgeShown(peer, nullptr, hasUnreadBadgesAbove);
}
const auto cornerBadgeShown = !_cornerBadgeUserpic
@@ -543,7 +556,7 @@ void Row::paintUserpic(
? storiesFolder->storiesCount()
: false;
if (!cornerBadgeShown && !storiesHas) {
BasicRow::paintUserpic(p, entry, peer, videoUserpic, context);
BasicRow::paintUserpic(p, entry, peer, videoUserpic, context, false);
if (!peer || !_cornerBadgeShown) {
_cornerBadgeUserpic = nullptr;
}

View File

@@ -51,7 +51,8 @@ public:
not_null<Entry*> entry,
PeerData *peer,
Ui::VideoUserpic *videoUserpic,
const Ui::PaintContext &context) const;
const Ui::PaintContext &context,
bool hasUnreadBadgesAbove) const;
void addRipple(QPoint origin, QSize size, Fn<void()> updateCallback);
virtual void stopLastRipple();
@@ -94,17 +95,19 @@ public:
return _height;
}
void recountHeight(float64 narrowRatio);
void recountHeight(float64 narrowRatio, FilterId filterId);
void updateCornerBadgeShown(
not_null<PeerData*> peer,
Fn<void()> updateCallback = nullptr) const;
Fn<void()> updateCallback = nullptr,
bool hasUnreadBadgesAbove = false) const;
void paintUserpic(
Painter &p,
not_null<Entry*> entry,
PeerData *peer,
Ui::VideoUserpic *videoUserpic,
const Ui::PaintContext &context) const final override;
const Ui::PaintContext &context,
bool hasUnreadBadgesAbove) const final override;
[[nodiscard]] bool lookupIsInTopicJump(int x, int y) const;
void stopLastRipple() override;

View File

@@ -478,6 +478,12 @@ Widget::Widget(
) | rpl::start_with_next([=](const ChosenRow &row) {
chosenRow(row);
}, lifetime());
_inner->openBotMainAppRequests(
) | rpl::start_with_next([=](UserId userId) {
if (const auto user = session().data().user(userId)) {
openBotMainApp(user);
}
}, lifetime());
_scroll->geometryChanged(
) | rpl::start_with_next(crl::guard(_inner, [=] {
@@ -1437,7 +1443,9 @@ void Widget::updateSuggestions(anim::type animated) {
}
}, _suggestions->lifetime());
_suggestions->recentAppChosen(
rpl::merge(
_suggestions->openBotMainAppRequests(),
_suggestions->recentAppChosen()
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
if (const auto info = user->botInfo.get()) {

View File

@@ -7,41 +7,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/ui/dialogs_layout.h"
#include "base/unixtime.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_drafts.h"
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_peer_values.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "dialogs/dialogs_list.h"
#include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/dialogs_video_userpic.h"
#include "styles/style_dialogs.h"
#include "styles/style_window.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "history/history_unread_things.h"
#include "history/view/history_view_item_preview.h"
#include "history/view/history_view_send_action.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "storage/localstorage.h"
#include "support/support_helper.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/unread_badge.h"
#include "ui/unread_badge_paint.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "core/ui_integration.h"
#include "lang/lang_keys.h"
#include "support/support_helper.h"
#include "main/main_session.h"
#include "history/view/history_view_send_action.h"
#include "history/view/history_view_item_preview.h"
#include "history/history_unread_things.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "history/history.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_folder.h"
#include "data/data_peer_values.h"
#include "styles/style_dialogs.h"
#include "styles/style_widgets.h"
#include "styles/style_window.h"
namespace Dialogs::Ui {
namespace {
@@ -84,14 +85,64 @@ void PaintRowTopRight(
text);
}
int PaintRightButton(QPainter &p, const PaintContext &context) {
if (context.width < st::columnMinimalWidthLeft) {
return 0;
}
if (const auto rightButton = context.rightButton) {
const auto size = rightButton->bg.size() / style::DevicePixelRatio();
const auto left = context.width
- size.width()
- st::dialogRowOpenBotRight;
const auto top = st::dialogRowOpenBotTop;
p.drawImage(
left,
top,
context.active
? rightButton->activeBg
: context.selected
? rightButton->selectedBg
: rightButton->bg);
if (rightButton->ripple) {
rightButton->ripple->paint(
p,
left,
top,
size.width() - size.height() / 2,
context.active
? &st::universalRippleAnimation.color->c
: &st::activeButtonBgRipple->c);
if (rightButton->ripple->empty()) {
rightButton->ripple.reset();
}
}
p.setPen(context.active
? st::activeButtonBg
: context.selected
? st::activeButtonFgOver
: st::activeButtonFg);
rightButton->text.draw(p, {
.position = QPoint(
left + size.height() / 2,
top + (st::dialogRowOpenBotHeight - rightButton->text.minHeight()) / 2),
.outerWidth = size.width() - size.height() / 2,
.availableWidth = size.width() - size.height() / 2,
.elisionLines = 1,
});
return size.width() + st::dialogsUnreadPadding;
}
return 0;
}
int PaintBadges(
QPainter &p,
const PaintContext &context,
BadgesState badgesState,
int right,
int top,
bool displayPinnedIcon = false,
int pinnedIconTop = 0) {
bool displayPinnedIcon,
int pinnedIconTop,
bool narrow) {
auto initial = right;
if (badgesState.unread
&& !badgesState.unreadCounter
@@ -123,11 +174,20 @@ int PaintBadges(
st.active = context.active;
st.selected = context.selected;
st.muted = badgesState.unreadMuted;
const auto counter = (badgesState.unreadCounter > 0)
const auto counter = (badgesState.unreadCounter <= 0)
? QString()
: !narrow
? QString::number(badgesState.unreadCounter)
: QString();
: ((badgesState.mention || badgesState.reaction)
&& (badgesState.unreadCounter > 999))
? (u"99+"_q)
: (badgesState.unreadCounter > 999999)
? (u"99999+"_q)
: QString::number(badgesState.unreadCounter);
const auto badge = PaintUnreadBadge(p, counter, right, top, st);
right -= badge.width() + st.padding;
} else if (const auto used = PaintRightButton(p, context)) {
return used - st::dialogsUnreadPadding;
} else if (displayPinnedIcon) {
const auto &icon = ThreeStateIcon(
st::dialogsPinnedIcon,
@@ -189,7 +249,10 @@ void PaintNarrowCounter(
context,
badgesState,
context.st->padding.left() + context.st->photoSize,
top);
top,
false,
0,
true);
}
int PaintWideCounter(
@@ -210,7 +273,8 @@ int PaintWideCounter(
context.width - context.st->padding.right(),
top,
displayPinnedIcon,
texttop);
texttop,
false);
return availableWidth - used;
}
@@ -341,10 +405,20 @@ void PaintRow(
row->userpicView(),
context);
} else {
row->paintUserpic(p, entry, from, videoUserpic, context);
row->paintUserpic(
p,
entry,
from,
videoUserpic,
context,
context.narrow
&& !badgesState.empty()
&& !draft
&& item
&& !item->isEmpty());
}
auto nameleft = context.st->nameLeft;
const auto nameleft = context.st->nameLeft;
if (context.topicsExpanded > 0.) {
PaintExpandedTopicsBar(p, context.topicsExpanded);
}
@@ -430,7 +504,9 @@ void PaintRow(
}
auto availableWidth = namewidth;
if (entry->isPinnedDialog(context.filter)
if (const auto used = PaintRightButton(p, context)) {
availableWidth -= used;
} else if (entry->isPinnedDialog(context.filter)
&& (context.filter || !entry->fixedOnTopIndex())) {
auto &icon = ThreeStateIcon(
st::dialogsPinnedIcon,
@@ -528,7 +604,9 @@ void PaintRow(
}
} else if (!item) {
auto availableWidth = namewidth;
if (entry->isPinnedDialog(context.filter)
if (const auto used = PaintRightButton(p, context)) {
availableWidth -= used;
} else if (entry->isPinnedDialog(context.filter)
&& (context.filter || !entry->fixedOnTopIndex())) {
auto &icon = ThreeStateIcon(
st::dialogsPinnedIcon,
@@ -712,6 +790,15 @@ void PaintRow(
.elisionLines = 1,
});
}
if (const auto tags = context.chatsFilterTags) {
auto left = nameleft;
for (const auto &tag : *tags) {
p.drawImage(left, context.st->tagTop, *tag);
left += st::dialogRowFilterTagSkip
+ (tag->width() / style::DevicePixelRatio());
}
}
}
} // namespace

View File

@@ -29,6 +29,7 @@ namespace Dialogs {
class Row;
class FakeRow;
class BasicRow;
struct RightButton;
} // namespace Dialogs
namespace Dialogs::Ui {
@@ -53,6 +54,8 @@ struct TopicJumpCache {
};
struct PaintContext {
RightButton *rightButton = nullptr;
std::vector<QImage*> *chatsFilterTags = nullptr;
not_null<const style::DialogRow*> st;
TopicJumpCache *topicJumpCache = nullptr;
Data::Folder *folder = nullptr;

View File

@@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/buttons.h"
@@ -76,12 +77,18 @@ public:
bool selected,
bool actionSelected) override;
bool rightActionDisabled() const override;
void rightActionAddRipple(
QPoint point,
Fn<void()> updateCallback) override;
void rightActionStopLastRipple() override;
const style::PeerListItem &computeSt(
const style::PeerListItem &st) const override;
private:
const not_null<History*> _history;
std::unique_ptr<Ui::Text::String> _mainAppText;
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
QString _badgeString;
QSize _badgeSize;
uint32 _counter : 30 = 0;
@@ -181,7 +188,17 @@ void FillEntryMenu(
RecentRow::RecentRow(not_null<PeerData*> peer)
: PeerListRow(peer)
, _history(peer->owner().history(peer)) {
, _history(peer->owner().history(peer))
, _mainAppText([&]() -> std::unique_ptr<Ui::Text::String> {
if (const auto user = peer->asUser()) {
if (user->botInfo && user->botInfo->hasMainApp) {
return std::make_unique<Ui::Text::String>(
st::dialogRowOpenBotTextStyle,
tr::lng_profile_open_app_short(tr::now));
}
}
return nullptr;
}()) {
if (peer->isSelf() || peer->isRepliesChat() || peer->isVerifyCodes()) {
setCustomStatus(u" "_q);
} else if (const auto chat = peer->asChat()) {
@@ -244,10 +261,22 @@ bool RecentRow::refreshBadge() {
}
QSize RecentRow::rightActionSize() const {
if (_mainAppText && _badgeSize.isEmpty()) {
return QSize(
_mainAppText->maxWidth() + _mainAppText->minHeight(),
st::dialogRowOpenBotHeight);
}
return _badgeSize;
}
QMargins RecentRow::rightActionMargins() const {
if (_mainAppText && _badgeSize.isEmpty()) {
return QMargins(
0,
st::dialogRowOpenBotRecentTop,
st::dialogRowOpenBotRight,
0);
}
if (_badgeSize.isEmpty()) {
return {};
}
@@ -263,6 +292,32 @@ void RecentRow::rightActionPaint(
int outerWidth,
bool selected,
bool actionSelected) {
if (_mainAppText && _badgeSize.isEmpty()) {
const auto size = RecentRow::rightActionSize();
p.setPen(Qt::NoPen);
p.setBrush(actionSelected
? st::activeButtonBgOver
: st::activeButtonBg);
const auto radius = size.height() / 2;
p.drawRoundedRect(QRect(QPoint(x, y), size), radius, radius);
if (_actionRipple) {
_actionRipple->paint(p, x, y, outerWidth);
if (_actionRipple->empty()) {
_actionRipple.reset();
}
}
p.setPen(actionSelected
? st::activeButtonFgOver
: st::activeButtonFg);
const auto top = 0
+ (st::dialogRowOpenBotHeight - _mainAppText->minHeight()) / 2;
_mainAppText->draw(p, {
.position = QPoint(x + size.height() / 2, y + top),
.outerWidth = outerWidth,
.availableWidth = outerWidth,
.elisionLines = 1,
});
}
if (!_counter && !_unread) {
return;
} else if (_badgeString.isEmpty()) {
@@ -280,7 +335,31 @@ void RecentRow::rightActionPaint(
}
bool RecentRow::rightActionDisabled() const {
return true;
return !_mainAppText || !_badgeSize.isEmpty();
}
void RecentRow::rightActionAddRipple(
QPoint point,
Fn<void()> updateCallback) {
if (!_mainAppText || !_badgeSize.isEmpty()) {
return;
}
if (!_actionRipple) {
const auto size = rightActionSize();
const auto radius = size.height() / 2;
auto mask = Ui::RippleAnimation::RoundRectMask(size, radius);
_actionRipple = std::make_unique<Ui::RippleAnimation>(
st::defaultActiveButton.ripple,
std::move(mask),
std::move(updateCallback));
}
_actionRipple->add(point);
}
void RecentRow::rightActionStopLastRipple() {
if (_actionRipple) {
_actionRipple->lastStop();
}
}
const style::PeerListItem &RecentRow::computeSt(
@@ -357,14 +436,18 @@ private:
class RecentsController final : public Suggestions::ObjectListController {
public:
using RightActionCallback = Fn<void(not_null<PeerData*>)>;
RecentsController(
not_null<Window::SessionController*> window,
RecentPeersList list);
RecentPeersList list,
RightActionCallback rightActionCallback);
void prepare() override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
void rowRightActionClicked(not_null<PeerListRow*> row) override;
QString savedMessagesChatStatus() const override;
@@ -374,6 +457,7 @@ private:
[[nodiscard]] Fn<void()> removeAllCallback();
RecentPeersList _recent;
RightActionCallback _rightActionCallback;
rpl::lifetime _lifetime;
};
@@ -671,9 +755,11 @@ void Suggestions::ObjectListController::setupExpandDivider(
RecentsController::RecentsController(
not_null<Window::SessionController*> window,
RecentPeersList list)
RecentPeersList list,
RightActionCallback rightActionCallback)
: ObjectListController(window)
, _recent(std::move(list)) {
, _recent(std::move(list))
, _rightActionCallback(std::move(rightActionCallback)) {
}
void RecentsController::prepare() {
@@ -735,6 +821,14 @@ base::unique_qptr<Ui::PopupMenu> RecentsController::rowContextMenu(
return result;
}
void RecentsController::rowRightActionClicked(not_null<PeerListRow*> row) {
if (_rightActionCallback) {
if (const auto peer = row->peer()) {
_rightActionCallback(peer);
}
}
}
QString RecentsController::savedMessagesChatStatus() const {
return tr::lng_saved_forward_here(tr::now);
}
@@ -1307,7 +1401,8 @@ void Suggestions::setupChats() {
.removeOneText = tr::lng_recent_remove(tr::now),
.removeOne = removeOne,
.removeAllText = tr::lng_recent_hide_top(
tr::now).replace('&', u"&&"_q),
tr::now,
Ui::Text::FixAmpersandInAction),
.removeAllConfirm = tr::lng_recent_hide_sure(tr::now),
.removeAll = removeAll,
});
@@ -1785,7 +1880,8 @@ auto Suggestions::setupRecentPeers(RecentPeersList recentPeers)
-> std::unique_ptr<ObjectList> {
const auto controller = lifetime().make_state<RecentsController>(
_controller,
std::move(recentPeers));
std::move(recentPeers),
[=](not_null<PeerData*> p) { _openBotMainAppRequests.fire_copy(p); });
const auto addToScroll = [=] {
return _topPeersWrap->toggled() ? _topPeers->height() : 0;

View File

@@ -88,6 +88,10 @@ public:
-> rpl::producer<not_null<PeerData*>> {
return _popularApps->chosen.events();
}
[[nodiscard]] auto openBotMainAppRequests() const
-> rpl::producer<not_null<PeerData*>> {
return _openBotMainAppRequests.events();
}
class ObjectListController;
@@ -174,6 +178,7 @@ private:
const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap;
const not_null<TopPeersStrip*> _topPeers;
rpl::event_stream<not_null<PeerData*>> _topPeerChosen;
rpl::event_stream<not_null<PeerData*>> _openBotMainAppRequests;
const std::unique_ptr<ObjectList> _recent;

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo_media.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "dialogs/dialogs_entry.h"
#include "dialogs/ui/dialogs_layout.h"
#include "ui/painter.h"
#include "styles/style_dialogs.h"

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