Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f91e4c8b69 | ||
|
|
f7c777d07d | ||
|
|
12a8e8616c | ||
|
|
2fbf7e8504 | ||
|
|
6864e6d5bf | ||
|
|
09b4e0e21b | ||
|
|
f381005184 | ||
|
|
42a2de4bf0 | ||
|
|
1fd1e34844 | ||
|
|
64dbbd7d09 | ||
|
|
c137e577dc | ||
|
|
f592a9202f | ||
|
|
cdc24d2e57 | ||
|
|
b8bf3f6520 | ||
|
|
82cec83d87 | ||
|
|
1e14667006 | ||
|
|
6539d14852 | ||
|
|
400df0f980 | ||
|
|
4cafb3f966 | ||
|
|
d8892c4eb4 | ||
|
|
1ebd25e76e | ||
|
|
ca89aa8377 | ||
|
|
ad6272bfe5 | ||
|
|
b401c37c39 | ||
|
|
552dd318cd | ||
|
|
a97880132a | ||
|
|
89058c63c8 | ||
|
|
747e417809 | ||
|
|
fd26e1618c | ||
|
|
86ea760011 | ||
|
|
824237deb3 | ||
|
|
ca8c70cc95 | ||
|
|
46fcc695a5 | ||
|
|
0e866a0266 | ||
|
|
63c36f5907 | ||
|
|
5299500d78 | ||
|
|
3b3d1aa9cc | ||
|
|
62d2346471 | ||
|
|
1e15764bb9 | ||
|
|
a6bfd35f1a | ||
|
|
3296efe46b | ||
|
|
51ddfbc340 | ||
|
|
42142d819a | ||
|
|
bbf9d523a6 | ||
|
|
721877e10a | ||
|
|
a9e95a128f | ||
|
|
2b920eaa87 | ||
|
|
a3ec759e62 | ||
|
|
3661442acd | ||
|
|
f232d329c5 | ||
|
|
ac5cf3bd80 | ||
|
|
ac13ac7a2c | ||
|
|
0bccb35cb0 | ||
|
|
18d9484ab1 | ||
|
|
fe7c06bc84 | ||
|
|
b6fb3bbf1d | ||
|
|
927d7a3aeb | ||
|
|
979973745b | ||
|
|
afab863f11 | ||
|
|
168162c174 | ||
|
|
2b122087c4 | ||
|
|
043d97cfdf | ||
|
|
794818953d | ||
|
|
783570fe9f | ||
|
|
b1e2a4243e | ||
|
|
b347308137 | ||
|
|
b3c8a79946 | ||
|
|
9822c56f1a | ||
|
|
cdd7ff5c6d | ||
|
|
5aba2f25cc | ||
|
|
96398daa78 | ||
|
|
61ceb66415 | ||
|
|
b4f173cdb3 | ||
|
|
03e4592082 | ||
|
|
cf2dbe50a1 | ||
|
|
e5bb5b75fe | ||
|
|
cffce47eb1 | ||
|
|
ca0adba6cf | ||
|
|
8502b90c25 | ||
|
|
cef43e7f06 | ||
|
|
18aaf3cc93 | ||
|
|
f13740cb7f | ||
|
|
bddac79b40 | ||
|
|
e52baf555f | ||
|
|
475dec3014 | ||
|
|
f2ed649694 | ||
|
|
e82506e0c4 | ||
|
|
3071daa6f3 | ||
|
|
5d71286000 | ||
|
|
339d7be9c1 | ||
|
|
7f85494b1d | ||
|
|
a405794a03 | ||
|
|
4e8e096fdb | ||
|
|
eb821c1f36 | ||
|
|
bf07b832f0 | ||
|
|
5934614edb | ||
|
|
96b5c1d3d3 | ||
|
|
cb2972b145 | ||
|
|
cd5a1980c9 | ||
|
|
0e35107e17 | ||
|
|
10b026dfe0 | ||
|
|
743c3c54a7 | ||
|
|
489c86dad8 | ||
|
|
a9824fde91 | ||
|
|
6c62bbe6fb | ||
|
|
8a3aa660cb | ||
|
|
14cc7789d9 | ||
|
|
728d9a0993 | ||
|
|
aa8d543ed8 | ||
|
|
b335981621 | ||
|
|
9d5ca1252a | ||
|
|
d5dbbd566f | ||
|
|
5362d54ab6 | ||
|
|
1f162aa2a0 | ||
|
|
4a19f193ce | ||
|
|
cfc40ee966 | ||
|
|
6baba5a7b2 | ||
|
|
ba082081b3 | ||
|
|
e314c68a56 | ||
|
|
889fcb3939 | ||
|
|
632abd2225 | ||
|
|
e3465da979 | ||
|
|
f4523b2dba | ||
|
|
0d58b32914 | ||
|
|
aea2d34080 | ||
|
|
d5dbde0a24 | ||
|
|
f888008dc1 | ||
|
|
021a5881c2 | ||
|
|
3e49b45418 | ||
|
|
23e22650f9 | ||
|
|
85267a921e | ||
|
|
2c7089d47f | ||
|
|
edc6cfe210 | ||
|
|
03b6e2df17 | ||
|
|
5309138980 | ||
|
|
a5e927ea4f | ||
|
|
ec83f4ae72 | ||
|
|
71efd95136 | ||
|
|
0c21eba1f8 | ||
|
|
2ae4e15f87 | ||
|
|
d69905feae | ||
|
|
f795d56b2a | ||
|
|
4608ffcab4 | ||
|
|
9824df5f2a | ||
|
|
27a5ba4681 | ||
|
|
73936dca73 | ||
|
|
213274e96c | ||
|
|
7518361266 | ||
|
|
f7aaece2f7 | ||
|
|
fbce06cb26 | ||
|
|
ccc0bf57a1 | ||
|
|
ee29deee47 | ||
|
|
17e54104a9 | ||
|
|
479e369d29 | ||
|
|
3042fb7299 | ||
|
|
36fa455aad | ||
|
|
1c64e90537 | ||
|
|
09643aef82 | ||
|
|
03d9fb4115 | ||
|
|
81bea04db0 | ||
|
|
9211e338e7 | ||
|
|
23464ac55f | ||
|
|
af78e4ea29 | ||
|
|
e3d9216b10 | ||
|
|
9532a2e3da | ||
|
|
e70f50d837 | ||
|
|
2dfa58aae2 |
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
Telegram/Resources/animations/starref_link.tgs
Normal file
BIN
Telegram/Resources/art/affiliate_logo.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
Telegram/Resources/icons/menu/affiliate_simple.png
Normal file
|
After Width: | Height: | Size: 655 B |
BIN
Telegram/Resources/icons/menu/affiliate_simple@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/affiliate_simple@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/affiliate_transparent.png
Normal file
|
After Width: | Height: | Size: 820 B |
BIN
Telegram/Resources/icons/menu/affiliate_transparent@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/affiliate_transparent@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/menu/bot.png
Normal file
|
After Width: | Height: | Size: 586 B |
BIN
Telegram/Resources/icons/menu/bot@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/menu/bot@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/menu/bot_add.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
Telegram/Resources/icons/menu/bot_add@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/bot_add@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/stars_share.png
Normal file
|
After Width: | Height: | Size: 791 B |
BIN
Telegram/Resources/icons/menu/stars_share@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/stars_share@3x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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 can’t 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.";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
{});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
388
Telegram/SourceFiles/boxes/peer_list_widgets.cpp
Normal 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;
|
||||
}
|
||||
110
Telegram/SourceFiles/boxes/peer_list_widgets.h
Normal 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;
|
||||
|
||||
};
|
||||
@@ -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(),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -71,6 +71,7 @@ enum class PremiumFeature {
|
||||
MessagePrivacy,
|
||||
Business,
|
||||
Effects,
|
||||
FilterTags,
|
||||
|
||||
// Business features.
|
||||
BusinessLocation,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
99
Telegram/SourceFiles/core/stars_amount.h
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }};
|
||||
|
||||
136
Telegram/SourceFiles/dialogs/dialogs_common.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||