Compare commits
226 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c04f68f25c | ||
|
|
e4902efefc | ||
|
|
1267bcd255 | ||
|
|
0659ccc3f0 | ||
|
|
bd2ae03ab4 | ||
|
|
244696ae24 | ||
|
|
1438046dd4 | ||
|
|
5c62ba0835 | ||
|
|
20fadfef7f | ||
|
|
eed9541f9f | ||
|
|
1594afa389 | ||
|
|
9d74d93ed7 | ||
|
|
e4e2f47f8e | ||
|
|
be53bec9b7 | ||
|
|
bb32c546d4 | ||
|
|
ecb4ceec7b | ||
|
|
c080bd4c4d | ||
|
|
39780f49bf | ||
|
|
73349c3c89 | ||
|
|
42a70ff7d0 | ||
|
|
fa8262cbe9 | ||
|
|
7552328cdd | ||
|
|
60f4587d95 | ||
|
|
572c074c42 | ||
|
|
3cfbd6a93b | ||
|
|
d0911b6a45 | ||
|
|
06b85442f8 | ||
|
|
762592daff | ||
|
|
338122793c | ||
|
|
ef521624a0 | ||
|
|
341ab781b2 | ||
|
|
2fed657940 | ||
|
|
7bf78b3317 | ||
|
|
2d1fb0562d | ||
|
|
3d77bff0c9 | ||
|
|
4198203a7f | ||
|
|
21487641c1 | ||
|
|
c987872be8 | ||
|
|
07e367e1a0 | ||
|
|
db2e45c56e | ||
|
|
67bbdbfc70 | ||
|
|
83df3cba66 | ||
|
|
f4e2b4bcbd | ||
|
|
6c64c22f83 | ||
|
|
702aa944dd | ||
|
|
df45edd816 | ||
|
|
3f3143514e | ||
|
|
3e89910749 | ||
|
|
721a642a2f | ||
|
|
d16ccc9dc5 | ||
|
|
77e7796b3f | ||
|
|
983c949e8c | ||
|
|
e3f4f60e2d | ||
|
|
33aa904cb7 | ||
|
|
0248be5543 | ||
|
|
f7ca8212aa | ||
|
|
5eb59a1a43 | ||
|
|
7dd1e9bfbe | ||
|
|
067fd25a34 | ||
|
|
7cb26ba104 | ||
|
|
a4212cc865 | ||
|
|
0445f7d6e8 | ||
|
|
efe99b3f62 | ||
|
|
7f2c98f17a | ||
|
|
8f1d215851 | ||
|
|
013b58f6f6 | ||
|
|
9d3b3476c2 | ||
|
|
715874a98f | ||
|
|
d2109dd2cb | ||
|
|
ddaf11ed6a | ||
|
|
2b5f68003d | ||
|
|
1a759cc4e7 | ||
|
|
9e83562bf4 | ||
|
|
c03c19d26f | ||
|
|
ad9ebdf8e6 | ||
|
|
9c1701c62a | ||
|
|
edf6c42e9d | ||
|
|
f0f2a71a87 | ||
|
|
cf270bd9ce | ||
|
|
49223a4688 | ||
|
|
074bb1e66e | ||
|
|
7e2e510d8a | ||
|
|
1ed34c6fa0 | ||
|
|
78a0fa55b5 | ||
|
|
d37c040b36 | ||
|
|
e56bbf557d | ||
|
|
5abecec478 | ||
|
|
ccb41f778e | ||
|
|
059a4cf0d8 | ||
|
|
7a535a4554 | ||
|
|
f89167ef94 | ||
|
|
a77777f509 | ||
|
|
4a327ba584 | ||
|
|
a41e9bf67e | ||
|
|
6716973ce0 | ||
|
|
7cc81393d6 | ||
|
|
3e413a036f | ||
|
|
63a8fe7ee8 | ||
|
|
146409844d | ||
|
|
ba0da9f59e | ||
|
|
81aef519d4 | ||
|
|
bcd84518d1 | ||
|
|
f205952ff2 | ||
|
|
1d7622e0b5 | ||
|
|
4d9112283d | ||
|
|
dc49c788a8 | ||
|
|
36741ab780 | ||
|
|
53dffc5e88 | ||
|
|
607c7e7777 | ||
|
|
6f09e1699f | ||
|
|
8c35de48f3 | ||
|
|
b83d943841 | ||
|
|
b11b5caeb3 | ||
|
|
36924da59a | ||
|
|
f0a2c47613 | ||
|
|
5a4449f1a2 | ||
|
|
de3d7a7774 | ||
|
|
b06dbd1c00 | ||
|
|
1fa5e424e9 | ||
|
|
d81c7abf1a | ||
|
|
932215c91d | ||
|
|
7aa1141ba5 | ||
|
|
3699439506 | ||
|
|
76b1288f77 | ||
|
|
8fd9ae4e59 | ||
|
|
53abd2fe38 | ||
|
|
da8a4ba8ab | ||
|
|
9c3990c0c1 | ||
|
|
1eeb46d5fc |
@@ -112,6 +112,8 @@ PRIVATE
|
||||
api/api_bot.h
|
||||
api/api_chat_filters.cpp
|
||||
api/api_chat_filters.h
|
||||
api/api_chat_filters_remove_manager.cpp
|
||||
api/api_chat_filters_remove_manager.h
|
||||
api/api_chat_invite.cpp
|
||||
api/api_chat_invite.h
|
||||
api/api_chat_links.cpp
|
||||
@@ -292,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
|
||||
@@ -636,6 +640,8 @@ PRIVATE
|
||||
data/data_thread.h
|
||||
data/data_types.cpp
|
||||
data/data_types.h
|
||||
data/data_unread_value.cpp
|
||||
data/data_unread_value.h
|
||||
data/data_user.cpp
|
||||
data/data_user.h
|
||||
data/data_user_photos.cpp
|
||||
@@ -982,6 +988,10 @@ PRIVATE
|
||||
info/profile/info_profile_values.h
|
||||
info/profile/info_profile_widget.cpp
|
||||
info/profile/info_profile_widget.h
|
||||
info/reactions_list/info_reactions_list_widget.cpp
|
||||
info/reactions_list/info_reactions_list_widget.h
|
||||
info/requests_list/info_requests_list_widget.cpp
|
||||
info/requests_list/info_requests_list_widget.h
|
||||
info/saved/info_saved_sublists_widget.cpp
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
@@ -1032,6 +1042,10 @@ PRIVATE
|
||||
info/info_wrap_widget.h
|
||||
inline_bots/bot_attach_web_view.cpp
|
||||
inline_bots/bot_attach_web_view.h
|
||||
inline_bots/inline_bot_confirm_prepared.cpp
|
||||
inline_bots/inline_bot_confirm_prepared.h
|
||||
inline_bots/inline_bot_downloads.cpp
|
||||
inline_bots/inline_bot_downloads.h
|
||||
inline_bots/inline_bot_layout_internal.cpp
|
||||
inline_bots/inline_bot_layout_internal.h
|
||||
inline_bots/inline_bot_layout_item.cpp
|
||||
@@ -1537,6 +1551,10 @@ PRIVATE
|
||||
ui/widgets/expandable_peer_list.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
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
|
||||
|
||||
BIN
Telegram/Resources/animations/hello_status.tgs
Normal file
BIN
Telegram/Resources/animations/hello_status.tgs
Normal file
Binary file not shown.
@@ -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?";
|
||||
@@ -682,6 +684,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_messages_privacy" = "Messages";
|
||||
"lng_settings_voices_privacy" = "Voice messages";
|
||||
"lng_settings_bio_privacy" = "Bio";
|
||||
"lng_settings_gifts_privacy" = "Gifts";
|
||||
"lng_settings_birthday_privacy" = "Date of Birth";
|
||||
"lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
|
||||
"lng_settings_privacy_premium_link" = "Telegram Premium";
|
||||
@@ -1162,19 +1165,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_blocked_list_subtitle#other" = "{count} blocked users";
|
||||
|
||||
"lng_edit_privacy_everyone" = "Everybody";
|
||||
"lng_edit_privacy_no_miniapps" = "Not Mini Apps";
|
||||
"lng_edit_privacy_contacts" = "My contacts";
|
||||
"lng_edit_privacy_close_friends" = "Close friends";
|
||||
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
|
||||
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
|
||||
"lng_edit_privacy_nobody" = "Nobody";
|
||||
"lng_edit_privacy_premium" = "Premium users";
|
||||
"lng_edit_privacy_miniapps" = "Mini Apps";
|
||||
"lng_edit_privacy_exceptions" = "Add exceptions";
|
||||
"lng_edit_privacy_user_types" = "User types";
|
||||
"lng_edit_privacy_users_and_groups" = "Users and groups";
|
||||
"lng_edit_privacy_premium_status" = "all Telegram Premium subscribers";
|
||||
"lng_edit_privacy_miniapps_status" = "web mini apps that you use";
|
||||
|
||||
"lng_edit_privacy_exceptions_count#one" = "{count} user";
|
||||
"lng_edit_privacy_exceptions_count#other" = "{count} users";
|
||||
"lng_edit_privacy_exceptions_premium_and" = "Premium & {users}";
|
||||
"lng_edit_privacy_exceptions_miniapps_and" = "Mini Apps & {users}";
|
||||
"lng_edit_privacy_exceptions_add" = "Add users";
|
||||
|
||||
"lng_edit_privacy_phone_number_title" = "Phone number privacy";
|
||||
@@ -1228,6 +1236,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edit_privacy_birthday_yet" = "You haven't entered your date of birth yet.\n{link}";
|
||||
"lng_edit_privacy_birthday_yet_link" = "Add my birthday >";
|
||||
|
||||
"lng_edit_privacy_gifts_title" = "Gifts";
|
||||
"lng_edit_privacy_gifts_header" = "Who can display gifts on my profile";
|
||||
"lng_edit_privacy_gifts_always_empty" = "Always allow";
|
||||
"lng_edit_privacy_gifts_never_empty" = "Never allow";
|
||||
"lng_edit_privacy_gifts_exceptions" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile.";
|
||||
"lng_edit_privacy_gifts_always_title" = "Always allow";
|
||||
"lng_edit_privacy_gifts_never_title" = "Never allow";
|
||||
|
||||
"lng_edit_privacy_calls_title" = "Calls";
|
||||
"lng_edit_privacy_calls_header" = "Who can call me";
|
||||
"lng_edit_privacy_calls_always_empty" = "Always allow";
|
||||
@@ -1459,13 +1475,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_enable_notifications" = "Notifications";
|
||||
"lng_profile_send_message" = "Send Message";
|
||||
"lng_profile_open_app" = "Open App";
|
||||
"lng_profile_open_app_short" = "Open";
|
||||
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
|
||||
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
|
||||
"lng_profile_bot_permissions_title" = "Allow access to";
|
||||
"lng_profile_bot_emoji_status_access" = "Emoji Status";
|
||||
"lng_info_add_as_contact" = "Add to contacts";
|
||||
"lng_profile_shared_media" = "Shared media";
|
||||
"lng_profile_suggest_photo" = "Suggest Profile Photo";
|
||||
"lng_profile_suggest_photo_from_clipboard" = "Suggest From Clipboard";
|
||||
"lng_profile_set_photo_for" = "Set Profile Photo";
|
||||
"lng_profile_set_photo_for_from_clipboard" = "Set From Clipboard";
|
||||
"lng_profile_photo_reset" = "Reset to Original";
|
||||
"lng_profile_photo_from_clipboard" = "From clipboard";
|
||||
"lng_profile_suggest_sure" = "You can suggest {user} to set this photo for their Telegram profile.";
|
||||
"lng_profile_suggest_button" = "Suggest";
|
||||
"lng_profile_set_personal_sure" = "Only you will see this photo and it will replace any photo {user} sets for themselves.";
|
||||
@@ -1842,6 +1864,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_payment_init_recurring_for" = "You successfully transferred {amount} to {user} for {invoice} and allowed future recurring payments";
|
||||
"lng_action_payment_init_recurring" = "You successfully transferred {amount} to {user} and allowed future recurring payments";
|
||||
"lng_action_payment_used_recurring" = "You were charged {amount} via recurring payment";
|
||||
"lng_action_payment_bot_done" = "Bot connected to this account received {amount}";
|
||||
"lng_action_payment_bot_recurring" = "Bot connected to this account received {amount} via recurring payment";
|
||||
"lng_action_took_screenshot" = "{from} took a screenshot!";
|
||||
"lng_action_you_took_screenshot" = "You took a screenshot!";
|
||||
"lng_action_bot_allowed_from_domain" = "You allowed this bot to message you when you logged in on {domain}.";
|
||||
@@ -1874,6 +1898,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
|
||||
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
|
||||
"lng_action_gift_sent_subtitle" = "Gift for {user}";
|
||||
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
|
||||
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
|
||||
@@ -2276,6 +2301,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";
|
||||
@@ -2413,6 +2440,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_more_options" = "More Options";
|
||||
"lng_credits_balance_me" = "your balance";
|
||||
"lng_credits_buy_button" = "Buy More Stars";
|
||||
"lng_credits_gift_button" = "Gift Stars to Friends";
|
||||
"lng_credits_box_out_title" = "Confirm Your Purchase";
|
||||
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
|
||||
@@ -2421,6 +2451,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";
|
||||
@@ -2481,6 +2517,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_subscriber_subtitle" = "appx. {total} per month";
|
||||
|
||||
"lng_credits_subscription_row_to" = "Subscription";
|
||||
"lng_credits_subscription_row_to_bot" = "Bot";
|
||||
"lng_credits_subscription_row_to_business" = "Business";
|
||||
"lng_credits_subscription_row_from" = "Subscribed";
|
||||
|
||||
"lng_credits_subscription_row_next_on" = "Renews";
|
||||
@@ -2491,13 +2529,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";
|
||||
@@ -3412,6 +3453,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
|
||||
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_emoji_status_confirm" = "Confirm";
|
||||
"lng_bot_emoji_status_title" = "Set Emoji Status";
|
||||
"lng_bot_emoji_status_text" = "Do you want to set this emoji status suggested by {bot}?";
|
||||
"lng_bot_emoji_status_access_text" = "{bot} requests access to set your **emoji status**. You will be able to revoke this access in the profile page of {name}.";
|
||||
"lng_bot_emoji_status_access_allow" = "Allow";
|
||||
"lng_bot_share_prepared_title" = "Share Message";
|
||||
"lng_bot_share_prepared_about" = "{bot} mini app suggests you to send this message to a chat you select.";
|
||||
"lng_bot_share_prepared_button" = "Share With...";
|
||||
"lng_bot_download_file" = "Download File";
|
||||
"lng_bot_download_file_sure" = "{bot} suggests you download the following file:";
|
||||
"lng_bot_download_file_button" = "Download";
|
||||
"lng_bot_download_starting" = "Starting...";
|
||||
"lng_bot_download_failed" = "Failed. {retry}";
|
||||
"lng_bot_download_retry" = "Retry";
|
||||
|
||||
"lng_bot_status_users#one" = "{count} monthly user";
|
||||
"lng_bot_status_users#other" = "{count} monthly users";
|
||||
@@ -4356,7 +4411,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_rights_edit_admin_rank_about" = "A title that members will see instead of '{title}'.";
|
||||
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with equal or fewer rights.";
|
||||
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
|
||||
"lng_rights_about_by" = "This admin promoted by {user} at {date}.";
|
||||
"lng_rights_about_by" = "This admin promoted by {user} on {date}.";
|
||||
|
||||
"lng_rights_about_admin_cant_edit" = "You can't edit the rights of this admin.";
|
||||
"lng_rights_about_restriction_cant_edit" = "You cannot change the restrictions for this user.";
|
||||
@@ -4435,8 +4490,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_rights_chat_files" = "Files";
|
||||
"lng_rights_chat_voice_messages" = "Voice messages";
|
||||
"lng_rights_chat_video_messages" = "Video messages";
|
||||
"lng_rights_chat_restricted_by" = "Restricted by {user} at {date}.";
|
||||
"lng_rights_chat_banned_by" = "Banned by {user} at {date}.";
|
||||
"lng_rights_chat_restricted_by" = "Restricted by {user} on {date}.";
|
||||
"lng_rights_chat_banned_by" = "Banned by {user} on {date}.";
|
||||
"lng_rights_chat_banned_until_header" = "Restricted until";
|
||||
"lng_rights_chat_banned_forever" = "Forever";
|
||||
"lng_rights_chat_banned_day#one" = "For {count} day";
|
||||
@@ -5045,6 +5100,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_outdated_now" = "So Telegram Desktop can update to newer versions.";
|
||||
|
||||
"lng_filters_all" = "All chats";
|
||||
"lng_filters_all_short" = "All";
|
||||
"lng_filters_setup" = "Edit";
|
||||
"lng_filters_title" = "Folders";
|
||||
"lng_filters_subtitle" = "My folders";
|
||||
@@ -5099,11 +5155,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_toast_add" = "{chat} added to {folder} folder";
|
||||
"lng_filters_toast_remove" = "{chat} removed from {folder} folder";
|
||||
"lng_filters_shareable_status" = "shareable folder";
|
||||
"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.";
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
||||
<file alias="search.tgs">../../animations/search.tgs</file>
|
||||
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
|
||||
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
|
||||
|
||||
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
||||
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.7.2.0" />
|
||||
Version="5.8.5.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,7,2,0
|
||||
PRODUCTVERSION 5,7,2,0
|
||||
FILEVERSION 5,8,5,0
|
||||
PRODUCTVERSION 5,8,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.7.2.0"
|
||||
VALUE "FileVersion", "5.8.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.7.2.0"
|
||||
VALUE "ProductVersion", "5.8.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,7,2,0
|
||||
PRODUCTVERSION 5,7,2,0
|
||||
FILEVERSION 5,8,5,0
|
||||
PRODUCTVERSION 5,8,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.7.2.0"
|
||||
VALUE "FileVersion", "5.8.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.7.2.0"
|
||||
VALUE "ProductVersion", "5.8.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -152,6 +153,7 @@ void InitFilterLinkHeader(
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
: rpl::single(0)),
|
||||
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
|
||||
});
|
||||
const auto widget = header.widget;
|
||||
widget->resizeToWidth(st::boxWideWidth);
|
||||
|
||||
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal file
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_chat_filters_remove_manager.h"
|
||||
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
void RemoveChatFilter(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId,
|
||||
std::vector<not_null<PeerData*>> leave) {
|
||||
const auto api = &session->api();
|
||||
session->data().chatsFilters().apply(MTP_updateDialogFilter(
|
||||
MTP_flags(MTPDupdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()));
|
||||
if (leave.empty()) {
|
||||
api->request(MTPmessages_UpdateDialogFilter(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()
|
||||
)).send();
|
||||
} else {
|
||||
api->request(MTPchatlists_LeaveChatlist(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(ranges::views::all(
|
||||
leave
|
||||
) | ranges::views::transform([](not_null<PeerData*> peer) {
|
||||
return MTPInputPeer(peer->input);
|
||||
}) | ranges::to<QVector<MTPInputPeer>>())
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RemoveComplexChatFilter::RemoveComplexChatFilter() = default;
|
||||
|
||||
void RemoveComplexChatFilter::request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id) {
|
||||
const auto session = &weak->session();
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
const auto i = ranges::find(list, id, &Data::ChatFilter::id);
|
||||
const auto filter = (i != end(list)) ? *i : Data::ChatFilter();
|
||||
const auto has = filter.hasMyLinks();
|
||||
const auto confirm = [=](Fn<void()> action, bool onlyWhenHas = false) {
|
||||
if (!has && onlyWhenHas) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
weak->window().show(Ui::MakeConfirmBox({
|
||||
.text = (has
|
||||
? tr::lng_filters_delete_sure()
|
||||
: tr::lng_filters_remove_sure()),
|
||||
.confirmed = [=](Fn<void()> &&close) { close(); action(); },
|
||||
.confirmText = (has
|
||||
? tr::lng_box_delete()
|
||||
: tr::lng_filters_remove_yes()),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
const auto simple = [=] {
|
||||
confirm([=] { RemoveChatFilter(session, id, {}); });
|
||||
};
|
||||
const auto suggestRemoving = Api::ExtractSuggestRemoving(filter);
|
||||
if (suggestRemoving.empty()) {
|
||||
simple();
|
||||
return;
|
||||
} else if (_removingRequestId) {
|
||||
if (_removingId == id) {
|
||||
return;
|
||||
}
|
||||
session->api().request(_removingRequestId).cancel();
|
||||
}
|
||||
_removingId = id;
|
||||
_removingRequestId = session->api().request(
|
||||
MTPchatlists_GetLeaveChatlistSuggestions(
|
||||
MTP_inputChatlistDialogFilter(
|
||||
MTP_int(id)))
|
||||
).done(crl::guard(widget, [=, this](const MTPVector<MTPPeer> &result) {
|
||||
_removingRequestId = 0;
|
||||
const auto suggestRemovePeers = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([=](const MTPPeer &peer) {
|
||||
return session->data().peer(peerFromMTP(peer));
|
||||
}) | ranges::to_vector;
|
||||
const auto chosen = crl::guard(widget, [=](
|
||||
std::vector<not_null<PeerData*>> peers) {
|
||||
RemoveChatFilter(session, id, std::move(peers));
|
||||
});
|
||||
confirm(crl::guard(widget, [=] {
|
||||
Api::ProcessFilterRemove(
|
||||
weak,
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
suggestRemoving,
|
||||
suggestRemovePeers,
|
||||
chosen);
|
||||
}), true);
|
||||
})).fail(crl::guard(widget, [=, this] {
|
||||
_removingRequestId = 0;
|
||||
simple();
|
||||
})).send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal file
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
|
||||
class RemoveComplexChatFilter final {
|
||||
public:
|
||||
RemoveComplexChatFilter();
|
||||
|
||||
void request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id);
|
||||
|
||||
private:
|
||||
FilterId _removingId = 0;
|
||||
mtpRequestId _removingRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -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) {
|
||||
|
||||
@@ -117,11 +117,12 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.convertStars = int(stargift
|
||||
.starsConverted = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
: 0),
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.stargift = stargift.has_value(),
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
@@ -132,17 +133,26 @@ constexpr auto kTransactionsLimit = 100;
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
|
||||
const MTPStarsSubscription &tl) {
|
||||
const MTPStarsSubscription &tl,
|
||||
not_null<PeerData*> peer) {
|
||||
return Data::SubscriptionEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
.slug = qs(tl.data().vinvoice_slug().value_or_empty()),
|
||||
.until = base::unixtime::parse(tl.data().vuntil_date().v),
|
||||
.subscription = Data::PeerSubscription{
|
||||
.credits = tl.data().vpricing().data().vamount().v,
|
||||
.period = tl.data().vpricing().data().vperiod().v,
|
||||
},
|
||||
.barePeerId = peerFromMTP(tl.data().vpeer()).value,
|
||||
.photoId = (tl.data().vphoto()
|
||||
? peer->owner().photoFromWeb(
|
||||
*tl.data().vphoto(),
|
||||
ImageLocation())->id
|
||||
: 0),
|
||||
.cancelled = tl.data().is_canceled(),
|
||||
.cancelledByBot = tl.data().is_bot_canceled(),
|
||||
.expired = (base::unixtime::now() > tl.data().vuntil_date().v),
|
||||
.canRefulfill = tl.data().is_can_refulfill(),
|
||||
};
|
||||
@@ -165,7 +175,7 @@ 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{
|
||||
@@ -174,7 +184,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.balance = status.data().vbalance().v,
|
||||
.subscriptionsMissingBalance
|
||||
= status.data().vsubscriptions_missing_balance().value_or_empty(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value()
|
||||
&& !status.data().vsubscriptions_next_offset().has_value(),
|
||||
.token = qs(status.data().vnext_offset().value_or_empty()),
|
||||
.tokenSubscriptions = qs(
|
||||
status.data().vsubscriptions_next_offset().value_or_empty()),
|
||||
@@ -461,4 +472,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
|
||||
|
||||
@@ -40,6 +40,7 @@ void HandleWithdrawalButton(
|
||||
std::shared_ptr<Ui::Show> show) {
|
||||
Expects(receiver.currencyReceiver
|
||||
|| (receiver.creditsReceiver && receiver.creditsAmount));
|
||||
|
||||
struct State {
|
||||
rpl::lifetime lifetime;
|
||||
bool loading = false;
|
||||
@@ -58,8 +59,7 @@ void HandleWithdrawalButton(
|
||||
const auto processOut = [=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
}
|
||||
if (peer && !receiver.creditsAmount()) {
|
||||
} else if (peer && !receiver.creditsAmount()) {
|
||||
return;
|
||||
}
|
||||
state->loading = true;
|
||||
|
||||
@@ -772,12 +772,13 @@ std::optional<StarGift> FromTL(
|
||||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.convertStars = int64(data.vconvert_stars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
.birthday = data.is_birthday(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -800,7 +801,7 @@ std::optional<UserStarGift> FromTL(
|
||||
data.vmessage()->data().ventities().v),
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.convertStars = int64(data.vconvert_stars().value_or_empty()),
|
||||
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
|
||||
@@ -76,12 +76,13 @@ struct GiftOptionData {
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 0;
|
||||
int64 convertStars = 0;
|
||||
int64 starsConverted = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
bool birthday = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarGift &,
|
||||
@@ -91,7 +92,7 @@ struct StarGift {
|
||||
struct UserStarGift {
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 convertStars = 0;
|
||||
int64 starsConverted = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
|
||||
@@ -69,6 +69,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
|
||||
if (rule.always.premiums && (rule.option != Option::Everyone)) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowPremium());
|
||||
}
|
||||
if (rule.always.miniapps && (rule.option != Option::Everyone)) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowBots());
|
||||
}
|
||||
}
|
||||
if (!rule.ignoreNever) {
|
||||
const auto users = collectInputUsers(rule.never);
|
||||
@@ -83,6 +86,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
|
||||
MTP_inputPrivacyValueDisallowChatParticipants(
|
||||
MTP_vector<MTPlong>(chats)));
|
||||
}
|
||||
if (rule.never.miniapps && (rule.option != Option::Nobody)) {
|
||||
result.push_back(MTP_inputPrivacyValueDisallowBots());
|
||||
}
|
||||
}
|
||||
result.push_back([&] {
|
||||
switch (rule.option) {
|
||||
@@ -124,6 +130,10 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) {
|
||||
setOption(Option::CloseFriends);
|
||||
}, [&](const MTPDprivacyValueAllowPremium &) {
|
||||
result.always.premiums = true;
|
||||
}, [&](const MTPDprivacyValueAllowBots &) {
|
||||
result.always.miniapps = true;
|
||||
}, [&](const MTPDprivacyValueDisallowBots &) {
|
||||
result.never.miniapps = true;
|
||||
}, [&](const MTPDprivacyValueAllowUsers &data) {
|
||||
const auto &users = data.vusers().v;
|
||||
always.reserve(always.size() + users.size());
|
||||
@@ -199,6 +209,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
|
||||
case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages();
|
||||
case Key::About: return MTP_inputPrivacyKeyAbout();
|
||||
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
||||
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
||||
}
|
||||
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
||||
}
|
||||
@@ -228,6 +239,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
|
||||
case mtpc_inputPrivacyKeyAbout: return Key::About;
|
||||
case mtpc_privacyKeyBirthday:
|
||||
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
|
||||
case mtpc_privacyKeyStarGiftsAutoSave:
|
||||
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
Voices,
|
||||
About,
|
||||
Birthday,
|
||||
GiftsAutoSave,
|
||||
};
|
||||
enum class Option {
|
||||
Everyone,
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
struct Exceptions {
|
||||
std::vector<not_null<PeerData*>> peers;
|
||||
bool premiums = false;
|
||||
bool miniapps = false;
|
||||
};
|
||||
struct Rule {
|
||||
Option option = Option::Everyone;
|
||||
|
||||
@@ -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);
|
||||
@@ -3947,7 +3955,8 @@ void ApiWrap::sendInlineResult(
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
std::optional<MsgId> localMessageId) {
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done) {
|
||||
sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
@@ -4027,11 +4036,17 @@ void ApiWrap::sendInlineResult(
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(true);
|
||||
}
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(false);
|
||||
}
|
||||
});
|
||||
finishForwarding(action);
|
||||
}
|
||||
|
||||
@@ -361,7 +361,8 @@ public:
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
std::optional<MsgId> localMessageId);
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
void sendMessageFail(
|
||||
const MTP::Error &error,
|
||||
not_null<PeerData*> peer,
|
||||
|
||||
@@ -8,22 +8,106 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/choose_filter_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "core/application.h" // primaryWindow
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::Bold
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_media_player.h" // mediaPlayerMenuCheck
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QImage Icon(const Data::ChatFilter &f) {
|
||||
constexpr auto kScale = 0.75;
|
||||
const auto icon = Ui::LookupFilterIcon(Ui::ComputeFilterIcon(f)).normal;
|
||||
const auto originalWidth = icon->width();
|
||||
const auto originalHeight = icon->height();
|
||||
|
||||
const auto scaledWidth = int(originalWidth * kScale);
|
||||
const auto scaledHeight = int(originalHeight * kScale);
|
||||
|
||||
auto image = QImage(
|
||||
scaledWidth * style::DevicePixelRatio(),
|
||||
scaledHeight * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
const auto x = int((scaledWidth - originalWidth * kScale) / 2);
|
||||
const auto y = int((scaledHeight - originalHeight * kScale) / 2);
|
||||
|
||||
p.scale(kScale, kScale);
|
||||
icon->paint(p, x, y, scaledWidth, st::dialogsUnreadBgMuted->c);
|
||||
if (const auto color = f.colorIndex()) {
|
||||
p.resetTransform();
|
||||
const auto circleSize = scaledWidth / 3.;
|
||||
const auto r = QRectF(
|
||||
x + scaledWidth - circleSize,
|
||||
y + scaledHeight - circleSize - circleSize / 3.,
|
||||
circleSize,
|
||||
circleSize);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||
p.setBrush(Qt::transparent);
|
||||
p.drawEllipse(r + Margins(st::lineWidth * 1.5));
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
p.setBrush(Ui::EmptyUserpic::UserpicColor(*color).color2);
|
||||
p.drawEllipse(r);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
class FilterAction : public Ui::Menu::Action {
|
||||
public:
|
||||
using Ui::Menu::Action::Action;
|
||||
|
||||
void setIcon(QImage &&image) {
|
||||
_icon = std::move(image);
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override {
|
||||
Ui::Menu::Action::paintEvent(event);
|
||||
if (!_icon.isNull()) {
|
||||
const auto size = _icon.size() / style::DevicePixelRatio();
|
||||
auto p = QPainter(this);
|
||||
p.drawImage(
|
||||
width()
|
||||
- size.width()
|
||||
- st::menuWithIcons.itemPadding.right(),
|
||||
(height() - size.height()) / 2,
|
||||
_icon);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QImage _icon;
|
||||
|
||||
};
|
||||
|
||||
Data::ChatFilter ChangedFilter(
|
||||
const Data::ChatFilter &filter,
|
||||
not_null<History*> history,
|
||||
@@ -96,6 +180,9 @@ void ChangeFilterById(
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: failed to %1 a dialog to a folder. %2")
|
||||
.arg(add ? u"add"_q : u"remove"_q)
|
||||
.arg(error.type()));
|
||||
// Revert filter on fail.
|
||||
history->owner().chatsFilters().set(was);
|
||||
}).send();
|
||||
@@ -123,9 +210,7 @@ bool ChooseFilterValidator::canRemove(FilterId filterId) const {
|
||||
const auto list = _history->owner().chatsFilters().list();
|
||||
const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
|
||||
if (i != end(list)) {
|
||||
const auto &filter = *i;
|
||||
return filter.contains(_history)
|
||||
&& ((filter.always().size() > 1) || filter.flags());
|
||||
return Data::CanRemoveFromChatFilter(*i, _history);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -161,14 +246,15 @@ void FillChooseFilterMenu(
|
||||
not_null<History*> history) {
|
||||
const auto weak = base::make_weak(controller);
|
||||
const auto validator = ChooseFilterValidator(history);
|
||||
for (const auto &filter : history->owner().chatsFilters().list()) {
|
||||
const auto &list = history->owner().chatsFilters().list();
|
||||
const auto showColors = history->owner().chatsFilters().tagsEnabled();
|
||||
for (const auto &filter : list) {
|
||||
const auto id = filter.id();
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto contains = filter.contains(history);
|
||||
const auto action = menu->addAction(filter.title(), [=] {
|
||||
auto callback = [=] {
|
||||
const auto toAdd = !filter.contains(history);
|
||||
const auto r = validator.limitReached(id, toAdd);
|
||||
if (r.reached) {
|
||||
@@ -185,11 +271,58 @@ void FillChooseFilterMenu(
|
||||
validator.remove(id);
|
||||
}
|
||||
}
|
||||
}, contains ? &st::mediaPlayerMenuCheck : nullptr);
|
||||
};
|
||||
|
||||
const auto contains = filter.contains(history);
|
||||
auto item = base::make_unique_q<FilterAction>(
|
||||
menu.get(),
|
||||
menu->st().menu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu.get(),
|
||||
Ui::Text::FixAmpersandInAction(filter.title()),
|
||||
std::move(callback)),
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr,
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr);
|
||||
|
||||
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
|
||||
const auto &p = st::menuWithIcons.itemPadding;
|
||||
item->setMinWidth(item->minWidth() + p.left() - p.right() - p.top());
|
||||
const auto action = menu->addAction(std::move(item));
|
||||
action->setEnabled(contains
|
||||
? validator.canRemove(id)
|
||||
: validator.canAdd());
|
||||
}
|
||||
|
||||
const auto limit = [session = &controller->session()] {
|
||||
return Data::PremiumLimits(session).dialogFiltersCurrent();
|
||||
};
|
||||
if ((list.size() - 1) < limit()) {
|
||||
menu->addAction(tr::lng_filters_create(tr::now), [=] {
|
||||
const auto strong = weak.get();
|
||||
if (!strong) {
|
||||
return;
|
||||
}
|
||||
const auto session = &strong->session();
|
||||
const auto count = session->data().chatsFilters().list().size();
|
||||
if ((count - 1) >= limit()) {
|
||||
return;
|
||||
}
|
||||
auto filter =
|
||||
Data::ChatFilter({}, {}, {}, {}, {}, { history }, {}, {});
|
||||
const auto send = [=](const Data::ChatFilter &filter) {
|
||||
session->api().request(MTPmessages_UpdateDialogFilter(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
|
||||
MTP_int(count),
|
||||
filter.tl()
|
||||
)).done([=] {
|
||||
session->data().chatsFilters().reload();
|
||||
}).send();
|
||||
};
|
||||
strong->uiShow()->show(
|
||||
Box(EditFilterBox, strong, std::move(filter), send, nullptr));
|
||||
}, &st::menuIconShowInFolder);
|
||||
}
|
||||
|
||||
history->owner().chatsFilters().changed(
|
||||
) | rpl::start_with_next([=] {
|
||||
menu->hideMenu();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -36,13 +36,63 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
||||
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
||||
|
||||
using Exceptions = Api::UserPrivacy::Exceptions;
|
||||
|
||||
enum class SpecialRowType {
|
||||
Premiums,
|
||||
MiniApps,
|
||||
};
|
||||
|
||||
[[nodiscard]] PaintRoundImageCallback GeneratePremiumsUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(x, y),
|
||||
QPointF(x + size, y + size));
|
||||
gradient.setStops(Ui::Premium::ButtonGradientStops());
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] PaintRoundImageCallback GenerateMiniAppsUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
const auto &color1 = st::historyPeer6UserpicBg;
|
||||
const auto &color2 = st::historyPeer6UserpicBg2;
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto gradient = QLinearGradient(x, y, x, y + size);
|
||||
gradient.setStops({ { 0., color1->c }, { 1., color2->c } });
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::windowFilterTypeBots.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
void CreateRadiobuttonLock(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
const style::Checkbox &st) {
|
||||
@@ -102,7 +152,7 @@ public:
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const Exceptions &selected,
|
||||
bool allowChoosePremiums);
|
||||
std::optional<SpecialRowType> allowChooseSpecial);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
@@ -110,18 +160,20 @@ public:
|
||||
bool handleDeselectForeignRow(PeerListRowId itemId) override;
|
||||
|
||||
[[nodiscard]] bool premiumsSelected() const;
|
||||
[[nodiscard]] bool miniAppsSelected() const;
|
||||
|
||||
protected:
|
||||
void prepareViewHook() override;
|
||||
std::unique_ptr<Row> createRow(not_null<History*> history) override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> preparePremiumsRowList();
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> prepareSpecialRowList(
|
||||
SpecialRowType type);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::producer<QString> _title;
|
||||
Exceptions _selected;
|
||||
bool _allowChoosePremiums = false;
|
||||
std::optional<SpecialRowType> _allowChooseSpecial;
|
||||
|
||||
PeerListContentDelegate *_typesDelegate = nullptr;
|
||||
Fn<void(PeerListRowId)> _deselectOption;
|
||||
@@ -133,9 +185,9 @@ struct RowSelectionChange {
|
||||
bool checked = false;
|
||||
};
|
||||
|
||||
class PremiumsRow final : public PeerListRow {
|
||||
class SpecialRow final : public PeerListRow {
|
||||
public:
|
||||
PremiumsRow();
|
||||
explicit SpecialRow(SpecialRowType type);
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
@@ -147,66 +199,61 @@ public:
|
||||
|
||||
class TypesController final : public PeerListController {
|
||||
public:
|
||||
TypesController(not_null<Main::Session*> session, bool premiums);
|
||||
TypesController(not_null<Main::Session*> session, SpecialRowType type);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] bool premiumsSelected() const;
|
||||
[[nodiscard]] rpl::producer<bool> premiumsChanges() const;
|
||||
[[nodiscard]] bool specialSelected() const;
|
||||
[[nodiscard]] rpl::producer<bool> specialChanges() const;
|
||||
[[nodiscard]] auto rowSelectionChanges() const
|
||||
-> rpl::producer<RowSelectionChange>;
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
const SpecialRowType _type;
|
||||
|
||||
rpl::event_stream<> _selectionChanged;
|
||||
rpl::event_stream<RowSelectionChange> _rowSelectionChanges;
|
||||
|
||||
};
|
||||
|
||||
PremiumsRow::PremiumsRow() : PeerListRow(kPremiumsRowId) {
|
||||
setCustomStatus(tr::lng_edit_privacy_premium_status(tr::now));
|
||||
SpecialRow::SpecialRow(SpecialRowType type)
|
||||
: PeerListRow((type == SpecialRowType::Premiums)
|
||||
? kPremiumsRowId
|
||||
: kMiniAppsRowId) {
|
||||
setCustomStatus((id() == kPremiumsRowId)
|
||||
? tr::lng_edit_privacy_premium_status(tr::now)
|
||||
: tr::lng_edit_privacy_miniapps_status(tr::now));
|
||||
}
|
||||
|
||||
QString PremiumsRow::generateName() {
|
||||
return tr::lng_edit_privacy_premium(tr::now);
|
||||
QString SpecialRow::generateName() {
|
||||
return (id() == kPremiumsRowId)
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_miniapps(tr::now);
|
||||
}
|
||||
|
||||
QString PremiumsRow::generateShortName() {
|
||||
QString SpecialRow::generateShortName() {
|
||||
return generateName();
|
||||
}
|
||||
|
||||
PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
|
||||
PaintRoundImageCallback SpecialRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(x, y),
|
||||
QPointF(x + size, y + size));
|
||||
gradient.setStops(Ui::Premium::ButtonGradientStops());
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
return (id() == kPremiumsRowId)
|
||||
? GeneratePremiumsUserpicCallback(forceRound)
|
||||
: GenerateMiniAppsUserpicCallback(forceRound);
|
||||
}
|
||||
|
||||
bool PremiumsRow::useForumLikeUserpic() const {
|
||||
bool SpecialRow::useForumLikeUserpic() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
TypesController::TypesController(
|
||||
not_null<Main::Session*> session,
|
||||
bool premiums)
|
||||
: _session(session) {
|
||||
SpecialRowType type)
|
||||
: _session(session)
|
||||
, _type(type) {
|
||||
}
|
||||
|
||||
Main::Session &TypesController::session() const {
|
||||
@@ -214,12 +261,15 @@ Main::Session &TypesController::session() const {
|
||||
}
|
||||
|
||||
void TypesController::prepare() {
|
||||
delegate()->peerListAppendRow(std::make_unique<PremiumsRow>());
|
||||
delegate()->peerListAppendRow(std::make_unique<SpecialRow>(_type));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
bool TypesController::premiumsSelected() const {
|
||||
const auto row = delegate()->peerListFindRow(kPremiumsRowId);
|
||||
bool TypesController::specialSelected() const {
|
||||
const auto premiums = (_type == SpecialRowType::Premiums);
|
||||
const auto row = delegate()->peerListFindRow(premiums
|
||||
? kPremiumsRowId
|
||||
: kMiniAppsRowId);
|
||||
Assert(row != nullptr);
|
||||
|
||||
return row->checked();
|
||||
@@ -231,10 +281,10 @@ void TypesController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_rowSelectionChanges.fire({ row, checked });
|
||||
}
|
||||
|
||||
rpl::producer<bool> TypesController::premiumsChanges() const {
|
||||
rpl::producer<bool> TypesController::specialChanges() const {
|
||||
return _rowSelectionChanges.events(
|
||||
) | rpl::map([=] {
|
||||
return premiumsSelected();
|
||||
return specialSelected();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -247,12 +297,12 @@ PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const Exceptions &selected,
|
||||
bool allowChoosePremiums)
|
||||
std::optional<SpecialRowType> allowChooseSpecial)
|
||||
: ChatsListBoxController(session)
|
||||
, _session(session)
|
||||
, _title(std::move(title))
|
||||
, _selected(selected)
|
||||
, _allowChoosePremiums(allowChoosePremiums) {
|
||||
, _allowChooseSpecial(allowChooseSpecial) {
|
||||
}
|
||||
|
||||
Main::Session &PrivacyExceptionsBoxController::session() const {
|
||||
@@ -261,14 +311,18 @@ Main::Session &PrivacyExceptionsBoxController::session() const {
|
||||
|
||||
void PrivacyExceptionsBoxController::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(std::move(_title));
|
||||
if (_allowChoosePremiums || _selected.premiums) {
|
||||
delegate()->peerListSetAboveWidget(preparePremiumsRowList());
|
||||
if (_allowChooseSpecial || _selected.premiums || _selected.miniapps) {
|
||||
delegate()->peerListSetAboveWidget(prepareSpecialRowList(
|
||||
_allowChooseSpecial.value_or(_selected.premiums
|
||||
? SpecialRowType::Premiums
|
||||
: SpecialRowType::MiniApps)));
|
||||
}
|
||||
delegate()->peerListAddSelectedPeers(_selected.peers);
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::isForeignRow(PeerListRowId itemId) {
|
||||
return (itemId == kPremiumsRowId);
|
||||
return (itemId == kPremiumsRowId)
|
||||
|| (itemId == kMiniAppsRowId);
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
|
||||
@@ -280,7 +334,8 @@ bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
|
||||
return false;
|
||||
}
|
||||
|
||||
auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
auto PrivacyExceptionsBoxController::prepareSpecialRowList(
|
||||
SpecialRowType type)
|
||||
-> object_ptr<Ui::RpWidget> {
|
||||
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
const auto container = result.data();
|
||||
@@ -291,30 +346,39 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
_typesDelegate = lifetime.make_state<PeerListContentDelegateSimple>();
|
||||
const auto controller = lifetime.make_state<TypesController>(
|
||||
&session(),
|
||||
_selected.premiums);
|
||||
type);
|
||||
const auto content = result->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
_typesDelegate->setContent(content);
|
||||
controller->setDelegate(_typesDelegate);
|
||||
|
||||
const auto selectType = [&](PeerListRowId id) {
|
||||
const auto row = _typesDelegate->peerListFindRow(id);
|
||||
if (row) {
|
||||
content->changeCheckState(row, true, anim::type::instant);
|
||||
this->delegate()->peerListSetForeignRowChecked(
|
||||
row,
|
||||
true,
|
||||
anim::type::instant);
|
||||
}
|
||||
};
|
||||
if (_selected.premiums) {
|
||||
const auto row = _typesDelegate->peerListFindRow(kPremiumsRowId);
|
||||
Assert(row != nullptr);
|
||||
|
||||
content->changeCheckState(row, true, anim::type::instant);
|
||||
this->delegate()->peerListSetForeignRowChecked(
|
||||
row,
|
||||
true,
|
||||
anim::type::instant);
|
||||
selectType(kPremiumsRowId);
|
||||
} else if (_selected.miniapps) {
|
||||
selectType(kMiniAppsRowId);
|
||||
}
|
||||
container->add(CreatePeerListSectionSubtitle(
|
||||
container,
|
||||
tr::lng_edit_privacy_users_and_groups()));
|
||||
|
||||
controller->premiumsChanges(
|
||||
) | rpl::start_with_next([=](bool premiums) {
|
||||
_selected.premiums = premiums;
|
||||
controller->specialChanges(
|
||||
) | rpl::start_with_next([=](bool chosen) {
|
||||
if (type == SpecialRowType::Premiums) {
|
||||
_selected.premiums = chosen;
|
||||
} else {
|
||||
_selected.miniapps = chosen;
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
controller->rowSelectionChanges(
|
||||
@@ -329,6 +393,8 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
|
||||
if (itemId == kPremiumsRowId) {
|
||||
_selected.premiums = false;
|
||||
} else if (itemId == kMiniAppsRowId) {
|
||||
_selected.miniapps = false;
|
||||
}
|
||||
_typesDelegate->peerListSetRowChecked(row, false);
|
||||
}
|
||||
@@ -337,10 +403,14 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool PrivacyExceptionsBoxController::premiumsSelected() const {
|
||||
bool PrivacyExceptionsBoxController::premiumsSelected() const {
|
||||
return _selected.premiums;
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::miniAppsSelected() const {
|
||||
return _selected.miniapps;
|
||||
}
|
||||
|
||||
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto peer = row->peer();
|
||||
|
||||
@@ -412,6 +482,11 @@ EditPrivacyBox::EditPrivacyBox(
|
||||
// If we switch from Everyone to Contacts or Nobody suggest Premiums.
|
||||
_value.always.premiums = true;
|
||||
}
|
||||
if (_controller->allowMiniAppsToggle(Exception::Always)
|
||||
&& _value.option == Option::Everyone) {
|
||||
// If we switch from Everyone to Contacts or Nobody suggest MiniApps.
|
||||
_value.always.miniapps = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EditPrivacyBox::prepare() {
|
||||
@@ -427,12 +502,18 @@ void EditPrivacyBox::editExceptions(
|
||||
&_window->session(),
|
||||
_controller->exceptionBoxTitle(exception),
|
||||
exceptions(exception),
|
||||
_controller->allowPremiumsToggle(exception));
|
||||
(_controller->allowPremiumsToggle(exception)
|
||||
? SpecialRowType::Premiums
|
||||
: _controller->allowMiniAppsToggle(exception)
|
||||
? SpecialRowType::MiniApps
|
||||
: std::optional<SpecialRowType>()));
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
|
||||
exceptions(exception).peers = box->collectSelectedRows();
|
||||
exceptions(exception).premiums = controller->premiumsSelected();
|
||||
auto &setTo = exceptions(exception);
|
||||
setTo.peers = box->collectSelectedRows();
|
||||
setTo.premiums = controller->premiumsSelected();
|
||||
setTo.miniapps = controller->miniAppsSelected();
|
||||
const auto type = [&] {
|
||||
switch (exception) {
|
||||
case Exception::Always: return Exception::Never;
|
||||
@@ -440,11 +521,17 @@ void EditPrivacyBox::editExceptions(
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}();
|
||||
auto &removeFrom = exceptions(type).peers;
|
||||
auto &removeFrom = exceptions(type);
|
||||
for (const auto peer : exceptions(exception).peers) {
|
||||
removeFrom.erase(
|
||||
ranges::remove(removeFrom, peer),
|
||||
end(removeFrom));
|
||||
removeFrom.peers.erase(
|
||||
ranges::remove(removeFrom.peers, peer),
|
||||
end(removeFrom.peers));
|
||||
}
|
||||
if (setTo.premiums) {
|
||||
removeFrom.premiums = false;
|
||||
}
|
||||
if (setTo.miniapps) {
|
||||
removeFrom.miniapps = false;
|
||||
}
|
||||
done();
|
||||
box->closeBox();
|
||||
@@ -566,14 +653,21 @@ void EditPrivacyBox::setupContent() {
|
||||
lt_count,
|
||||
count)
|
||||
: tr::lng_edit_privacy_exceptions_add(tr::now);
|
||||
return !value.premiums
|
||||
? users
|
||||
: !count
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_premium_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users);
|
||||
return value.premiums
|
||||
? (!count
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_premium_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users))
|
||||
: value.miniapps
|
||||
? (!count
|
||||
? tr::lng_edit_privacy_miniapps(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_miniapps_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users))
|
||||
: users;
|
||||
});
|
||||
_controller->handleExceptionsChange(
|
||||
exception,
|
||||
|
||||
@@ -61,6 +61,10 @@ public:
|
||||
Exception exception) const {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] virtual bool allowMiniAppsToggle(
|
||||
Exception exception) const {
|
||||
return false;
|
||||
}
|
||||
virtual void handleExceptionsChange(
|
||||
Exception exception,
|
||||
rpl::producer<int> value) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,9 @@ void GiftCreditsBox(
|
||||
box->verticalLayout(),
|
||||
peer,
|
||||
0,
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); });
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); },
|
||||
tr::lng_credits_summary_options_subtitle(),
|
||||
{});
|
||||
|
||||
box->setPinnedToBottomContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
||||
@@ -266,7 +266,7 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
|
||||
raw,
|
||||
tr::lng_gift_sell_small(
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.convertStars * 1.)),
|
||||
rpl::single(entry.starsConverted * 1.)),
|
||||
st::starGiftSmallButton)
|
||||
: nullptr;
|
||||
if (convert) {
|
||||
@@ -1044,7 +1044,8 @@ void AddStarGiftTable(
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
const auto withSendButton = entry.in;
|
||||
const auto user = session->data().peer(peerId)->asUser();
|
||||
const auto withSendButton = entry.in && user && !user->isBot();
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
@@ -1144,12 +1145,17 @@ void AddCreditsHistoryEntryTable(
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto actorId = PeerId(entry.bareActorId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
if (actorId || peerId) {
|
||||
auto text = entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
AddTableRow(
|
||||
table,
|
||||
std::move(text),
|
||||
controller,
|
||||
actorId ? actorId : peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto peer = session->data().peer(peerId);
|
||||
@@ -1241,8 +1247,15 @@ void AddCreditsHistoryEntryTable(
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (!entry.subscriptionUntil.isNull() && !entry.title.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
tr::lng_credits_box_history_entry_subscription(
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
constexpr auto kOneLineCount = 24;
|
||||
const auto oneLine = entry.id.length() <= kOneLineCount;
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
@@ -1306,11 +1319,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);
|
||||
|
||||
@@ -240,7 +240,8 @@ int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
|
||||
}
|
||||
|
||||
void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
|
||||
if (!_progress || true) {
|
||||
#if 0 // not used
|
||||
if (!_progress) {
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(this);
|
||||
@@ -254,6 +255,7 @@ void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
|
||||
st::proxyCheckingPosition.y() + bottom
|
||||
},
|
||||
width());
|
||||
#endif
|
||||
}
|
||||
|
||||
QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -206,7 +206,9 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
content()->selectSkipPage(height(), 1);
|
||||
} else if (e->key() == Qt::Key_PageUp) {
|
||||
content()->selectSkipPage(height(), -1);
|
||||
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
|
||||
} else if (e->key() == Qt::Key_Escape
|
||||
&& _select
|
||||
&& !_select->entity()->getQuery().isEmpty()) {
|
||||
_select->entity()->clearQuery();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
@@ -215,7 +217,19 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
void PeerListBox::searchQueryChanged(const QString &query) {
|
||||
scrollToY(0);
|
||||
content()->searchQueryChanged(query);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::resizeEvent(QResizeEvent *e) {
|
||||
@@ -543,6 +557,19 @@ auto PeerListBox::collectSelectedRows()
|
||||
return result;
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
@@ -1385,10 +1412,12 @@ int PeerListContent::labelHeight() const {
|
||||
|
||||
void PeerListContent::refreshRows() {
|
||||
if (!_hiddenRows.empty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
if (!_ignoreHiddenRowsOnSearch || _normalizedSearchQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2050,13 +2079,16 @@ void PeerListContent::checkScrollForPreload() {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::searchQueryChanged(QString query) {
|
||||
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
|
||||
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
||||
const auto normalizedQuery = searchWordsList.join(' ');
|
||||
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
}
|
||||
if (_normalizedSearchQuery != normalizedQuery) {
|
||||
setSearchQuery(query, normalizedQuery);
|
||||
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
|
||||
Assert(_hiddenRows.empty());
|
||||
Assert(_hiddenRows.empty() || _ignoreHiddenRowsOnSearch);
|
||||
|
||||
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
|
||||
for (const auto &searchWord : searchWordsList) {
|
||||
@@ -2104,6 +2136,7 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||
}
|
||||
refreshRows();
|
||||
}
|
||||
return _normalizedSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
||||
@@ -2192,6 +2225,10 @@ void PeerListContent::dragLeft() {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
void PeerListContent::setIgnoreHiddenRowsOnSearch(bool value) {
|
||||
_ignoreHiddenRowsOnSearch = value;
|
||||
}
|
||||
|
||||
void PeerListContent::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
||||
@@ -652,12 +652,15 @@ public:
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
|
||||
void searchQueryChanged(QString query);
|
||||
using IsEmpty = bool;
|
||||
IsEmpty searchQueryChanged(QString query);
|
||||
bool submitted();
|
||||
|
||||
PeerListRowId updateFromParentDrag(QPoint globalPosition);
|
||||
void dragLeft();
|
||||
|
||||
void setIgnoreHiddenRowsOnSearch(bool value);
|
||||
|
||||
// Interface for the controller.
|
||||
void appendRow(std::unique_ptr<PeerListRow> row);
|
||||
void appendSearchRow(std::unique_ptr<PeerListRow> row);
|
||||
@@ -879,6 +882,7 @@ private:
|
||||
int _aboveHeight = 0;
|
||||
int _belowHeight = 0;
|
||||
bool _hideEmpty = false;
|
||||
bool _ignoreHiddenRowsOnSearch = false;
|
||||
object_ptr<Ui::RpWidget> _aboveWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _belowWidget = { nullptr };
|
||||
@@ -1102,6 +1106,9 @@ public:
|
||||
|
||||
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
|
||||
[[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));
|
||||
@@ -1168,4 +1175,11 @@ private:
|
||||
bool _scrollBottomFixed = false;
|
||||
int _addedTopScrollSkip = 0;
|
||||
|
||||
struct SpecialTabsMode final {
|
||||
bool enabled = false;
|
||||
bool searchIsActive = false;
|
||||
int topSkip = 0;
|
||||
};
|
||||
SpecialTabsMode _specialTabsMode;
|
||||
|
||||
};
|
||||
|
||||
@@ -1065,6 +1065,11 @@ std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ChooseTopicBoxController::MakeRow(
|
||||
not_null<Data::ForumTopic*> topic) {
|
||||
return std::make_unique<Row>(topic);
|
||||
}
|
||||
|
||||
auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
|
||||
-> std::unique_ptr<Row> {
|
||||
const auto skip = _filter && !_filter(topic);
|
||||
|
||||
@@ -335,6 +335,9 @@ public:
|
||||
void loadMoreRows() override;
|
||||
std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override;
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<PeerListRow> MakeRow(
|
||||
not_null<Data::ForumTopic*> topic);
|
||||
|
||||
private:
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
|
||||
388
Telegram/SourceFiles/boxes/peer_list_widgets.cpp
Normal file
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
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;
|
||||
|
||||
};
|
||||
@@ -30,10 +30,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_changes.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "ui/effects/outline_segments.h"
|
||||
#include "ui/widgets/menu/menu_multiline_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "history/history.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace {
|
||||
@@ -1645,6 +1648,51 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
const auto addToEnd = gsl::finally([&] {
|
||||
const auto addInfoAction = [&](
|
||||
not_null<PeerData*> by,
|
||||
tr::phrase<lngtag_user, lngtag_date> phrase,
|
||||
TimeId since) {
|
||||
auto text = phrase(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(by->name()),
|
||||
lt_date,
|
||||
Ui::Text::Bold(
|
||||
langDateTimeFull(base::unixtime::parse(since))),
|
||||
Ui::Text::WithEntities);
|
||||
auto button = base::make_unique_q<Ui::Menu::MultilineAction>(
|
||||
result->menu(),
|
||||
result->st().menu,
|
||||
st::historyHasCustomEmoji,
|
||||
st::historyHasCustomEmojiPosition,
|
||||
std::move(text));
|
||||
if (const auto n = _navigation) {
|
||||
button->setClickedCallback([=] {
|
||||
n->parentController()->show(PrepareShortInfoBox(by, n));
|
||||
});
|
||||
}
|
||||
result->addSeparator();
|
||||
result->addAction(std::move(button));
|
||||
};
|
||||
|
||||
if (const auto by = _additional.restrictedBy(participant)) {
|
||||
if (const auto since = _additional.restrictedSince(participant)) {
|
||||
addInfoAction(
|
||||
by,
|
||||
_additional.isKicked(participant)
|
||||
? tr::lng_rights_chat_banned_by
|
||||
: tr::lng_rights_chat_restricted_by,
|
||||
since);
|
||||
}
|
||||
} else if (user) {
|
||||
if (const auto by = _additional.adminPromotedBy(user)) {
|
||||
if (const auto since = _additional.adminPromotedSince(user)) {
|
||||
addInfoAction(by, tr::lng_rights_about_by, since);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (_navigation) {
|
||||
result->addAction(
|
||||
(participant->isUser()
|
||||
@@ -1652,39 +1700,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
: participant->isBroadcast()
|
||||
? tr::lng_context_view_channel
|
||||
: tr::lng_context_view_group)(tr::now),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->showPeerInfo(participant); }),
|
||||
crl::guard(this, [=, this] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(participant, _navigation));
|
||||
}),
|
||||
(participant->isUser()
|
||||
? &st::menuIconProfile
|
||||
: &st::menuIconInfo));
|
||||
}
|
||||
if (const auto by = _additional.restrictedBy(participant)) {
|
||||
result->addAction(
|
||||
(_role == Role::Kicked
|
||||
? tr::lng_channel_banned_status_removed_by
|
||||
: tr::lng_channel_banned_status_restricted_by)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
by->name()),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(by, _navigation));
|
||||
}),
|
||||
&st::menuIconAdmin);
|
||||
} else if (user) {
|
||||
if (const auto by = _additional.adminPromotedBy(user)) {
|
||||
result->addAction(
|
||||
tr::lng_channel_admin_status_promoted_by(
|
||||
tr::now,
|
||||
lt_user,
|
||||
by->name()),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(by, _navigation));
|
||||
}),
|
||||
&st::menuIconAdmin);
|
||||
}
|
||||
}
|
||||
if (_role == Role::Kicked) {
|
||||
if (_peer->isMegagroup()
|
||||
&& _additional.canRestrictParticipant(participant)) {
|
||||
|
||||
@@ -12,12 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -51,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h" // st::boxDividerLabel.
|
||||
@@ -264,8 +267,9 @@ private:
|
||||
class SingleRowController final : public PeerListController {
|
||||
public:
|
||||
SingleRowController(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status);
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked);
|
||||
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
@@ -273,8 +277,10 @@ public:
|
||||
Main::Session &session() const override;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<Main::Session*> _session;
|
||||
const base::weak_ptr<Data::Thread> _thread;
|
||||
rpl::producer<QString> _status;
|
||||
Fn<void()> _clicked;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
@@ -1144,36 +1150,59 @@ int Controller::descriptionTopSkipMin() const {
|
||||
}
|
||||
|
||||
SingleRowController::SingleRowController(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status)
|
||||
: _peer(peer)
|
||||
, _status(std::move(status)) {
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked)
|
||||
: _session(&thread->session())
|
||||
, _thread(thread)
|
||||
, _status(std::move(status))
|
||||
, _clicked(std::move(clicked)) {
|
||||
}
|
||||
|
||||
void SingleRowController::prepare() {
|
||||
auto row = std::make_unique<PeerListRow>(_peer);
|
||||
|
||||
const auto strong = _thread.get();
|
||||
if (!strong) {
|
||||
return;
|
||||
}
|
||||
const auto topic = strong->asTopic();
|
||||
auto row = topic
|
||||
? ChooseTopicBoxController::MakeRow(topic)
|
||||
: std::make_unique<PeerListRow>(strong->peer());
|
||||
const auto raw = row.get();
|
||||
std::move(
|
||||
_status
|
||||
) | rpl::start_with_next([=](const QString &status) {
|
||||
raw->setCustomStatus(status);
|
||||
delegate()->peerListUpdateRow(raw);
|
||||
}, _lifetime);
|
||||
|
||||
if (_status) {
|
||||
std::move(
|
||||
_status
|
||||
) | rpl::start_with_next([=](const QString &status) {
|
||||
raw->setCustomStatus(status);
|
||||
delegate()->peerListUpdateRow(raw);
|
||||
}, _lifetime);
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListRefreshRows();
|
||||
|
||||
if (topic) {
|
||||
topic->destroyed() | rpl::start_with_next([=] {
|
||||
while (delegate()->peerListFullRowsCount()) {
|
||||
delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}, _lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
void SingleRowController::loadMoreRows() {
|
||||
}
|
||||
|
||||
void SingleRowController::rowClicked(not_null<PeerListRow*> row) {
|
||||
ShowPeerInfoSync(row->peer());
|
||||
if (const auto onstack = _clicked) {
|
||||
onstack();
|
||||
} else {
|
||||
ShowPeerInfoSync(row->peer());
|
||||
}
|
||||
}
|
||||
|
||||
Main::Session &SingleRowController::session() const {
|
||||
return _peer->session();
|
||||
return *_session;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1186,14 +1215,29 @@ bool IsExpiredLink(const Api::InviteLink &data, TimeId now) {
|
||||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status) {
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked) {
|
||||
AddSinglePeerRow(
|
||||
container,
|
||||
peer->owner().history(peer),
|
||||
std::move(status),
|
||||
std::move(clicked));
|
||||
}
|
||||
|
||||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked) {
|
||||
const auto delegate = container->lifetime().make_state<
|
||||
PeerListContentDelegateSimple
|
||||
>();
|
||||
const auto controller = container->lifetime().make_state<
|
||||
SingleRowController
|
||||
>(peer, std::move(status));
|
||||
controller->setStyleOverrides(&st::peerListSingleRow);
|
||||
>(thread, std::move(status), std::move(clicked));
|
||||
controller->setStyleOverrides(thread->asTopic()
|
||||
? &st::chooseTopicList
|
||||
: &st::peerListSingleRow);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
|
||||
@@ -16,6 +16,10 @@ namespace Api {
|
||||
struct InviteLink;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -31,7 +35,14 @@ class BoxContent;
|
||||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status);
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked = nullptr);
|
||||
|
||||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked = nullptr);
|
||||
|
||||
void AddPermanentLinkBlock(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
|
||||
@@ -7,27 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
|
||||
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
|
||||
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
|
||||
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/requests_list/info_requests_list_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
@@ -262,14 +265,10 @@ RequestsBoxController::~RequestsBoxController() = default;
|
||||
void RequestsBoxController::Start(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
auto controller = std::make_unique<RequestsBoxController>(
|
||||
navigation,
|
||||
peer->migrateToOrMe());
|
||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
};
|
||||
navigation->parentController()->show(
|
||||
Box<PeerListBox>(std::move(controller), initBox));
|
||||
navigation->showSection(
|
||||
std::make_shared<Info::Memento>(
|
||||
peer->migrateToOrMe(),
|
||||
Info::Section::Type::RequestsList));
|
||||
}
|
||||
|
||||
Main::Session &RequestsBoxController::session() const {
|
||||
@@ -289,6 +288,58 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createSearchRow(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> RequestsBoxController::createRestoredRow(
|
||||
not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
return createRow(user, _dates[user]);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto RequestsBoxController::saveState() const
|
||||
-> std::unique_ptr<PeerListState> {
|
||||
auto result = PeerListController::saveState();
|
||||
|
||||
auto my = std::make_unique<SavedState>();
|
||||
my->dates = _dates;
|
||||
my->offsetDate = _offsetDate;
|
||||
my->offsetUser = _offsetUser;
|
||||
my->allLoaded = _allLoaded;
|
||||
my->wasLoading = (_loadRequestId != 0);
|
||||
if (const auto search = searchController()) {
|
||||
my->searchState = search->saveState();
|
||||
}
|
||||
result->controllerState = std::move(my);
|
||||
return result;
|
||||
}
|
||||
|
||||
void RequestsBoxController::restoreState(
|
||||
std::unique_ptr<PeerListState> state) {
|
||||
auto typeErasedState = state
|
||||
? state->controllerState.get()
|
||||
: nullptr;
|
||||
if (const auto my = dynamic_cast<SavedState*>(typeErasedState)) {
|
||||
if (const auto requestId = base::take(_loadRequestId)) {
|
||||
_api.request(requestId).cancel();
|
||||
}
|
||||
_dates = std::move(my->dates);
|
||||
_offsetDate = my->offsetDate;
|
||||
_offsetUser = my->offsetUser;
|
||||
_allLoaded = my->allLoaded;
|
||||
if (const auto search = searchController()) {
|
||||
search->restoreState(std::move(my->searchState));
|
||||
}
|
||||
if (my->wasLoading) {
|
||||
loadMoreRows();
|
||||
}
|
||||
PeerListController::restoreState(std::move(state));
|
||||
if (delegate()->peerListFullRowsCount() || _allLoaded) {
|
||||
refreshDescription();
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RequestsBoxController::prepare() {
|
||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
||||
delegate()->peerListSetTitle(_peer->isBroadcast()
|
||||
@@ -356,9 +407,7 @@ void RequestsBoxController::refreshDescription() {
|
||||
}
|
||||
|
||||
void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_navigation->parentController()->show(PrepareShortInfoBox(
|
||||
row->peer(),
|
||||
_navigation));
|
||||
_navigation->showPeerInfo(row->peer());
|
||||
}
|
||||
|
||||
void RequestsBoxController::rowElementClicked(
|
||||
@@ -405,6 +454,7 @@ void RequestsBoxController::appendRow(
|
||||
not_null<UserData*> user,
|
||||
TimeId date) {
|
||||
if (!delegate()->peerListFindRow(user->id.value)) {
|
||||
_dates.emplace(user, date);
|
||||
if (auto row = createRow(user, date)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
setDescriptionText(QString());
|
||||
@@ -503,6 +553,7 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createRow(
|
||||
const auto search = static_cast<RequestsBoxSearchController*>(
|
||||
searchController());
|
||||
date = search->dateForUser(user);
|
||||
_dates.emplace(user, date);
|
||||
}
|
||||
return std::make_unique<Row>(_helper.get(), user, date);
|
||||
}
|
||||
@@ -574,6 +625,36 @@ TimeId RequestsBoxSearchController::dateForUser(not_null<UserData*> user) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto RequestsBoxSearchController::saveState() const
|
||||
-> std::unique_ptr<SavedStateBase> {
|
||||
auto result = std::make_unique<SavedState>();
|
||||
result->query = _query;
|
||||
result->offsetDate = _offsetDate;
|
||||
result->offsetUser = _offsetUser;
|
||||
result->allLoaded = _allLoaded;
|
||||
result->wasLoading = (_requestId != 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
void RequestsBoxSearchController::restoreState(
|
||||
std::unique_ptr<SavedStateBase> state) {
|
||||
if (auto my = dynamic_cast<SavedState*>(state.get())) {
|
||||
if (auto requestId = base::take(_requestId)) {
|
||||
_api.request(requestId).cancel();
|
||||
}
|
||||
_cache.clear();
|
||||
_queries.clear();
|
||||
|
||||
_allLoaded = my->allLoaded;
|
||||
_offsetDate = my->offsetDate;
|
||||
_offsetUser = my->offsetUser;
|
||||
_query = my->query;
|
||||
if (my->wasLoading) {
|
||||
searchOnServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RequestsBoxSearchController::searchInCache() {
|
||||
const auto i = _cache.find(_query);
|
||||
if (i != _cache.cend()) {
|
||||
|
||||
@@ -35,15 +35,32 @@ public:
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowElementClicked(not_null<PeerListRow*> row, int element) override;
|
||||
void rowElementClicked(
|
||||
not_null<PeerListRow*> row,
|
||||
int element) override;
|
||||
void loadMoreRows() override;
|
||||
|
||||
std::unique_ptr<PeerListRow> createSearchRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
std::unique_ptr<PeerListRow> createRestoredRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
|
||||
std::unique_ptr<PeerListState> saveState() const override;
|
||||
void restoreState(std::unique_ptr<PeerListState> state) override;
|
||||
|
||||
private:
|
||||
class RowHelper;
|
||||
|
||||
struct SavedState : SavedStateBase {
|
||||
using SearchStateBase = PeerListSearchController::SavedStateBase;
|
||||
std::unique_ptr<SearchStateBase> searchState;
|
||||
base::flat_map<not_null<UserData*>, TimeId> dates;
|
||||
TimeId offsetDate = 0;
|
||||
UserData *offsetUser = nullptr;
|
||||
bool allLoaded = false;
|
||||
bool wasLoading = false;
|
||||
};
|
||||
|
||||
static std::unique_ptr<PeerListSearchController> CreateSearchController(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
@@ -63,6 +80,8 @@ private:
|
||||
not_null<PeerData*> _peer;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<not_null<UserData*>, TimeId> _dates;
|
||||
|
||||
TimeId _offsetDate = 0;
|
||||
UserData *_offsetUser = nullptr;
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
@@ -82,7 +101,17 @@ public:
|
||||
void removeFromCache(not_null<UserData*> user);
|
||||
[[nodiscard]] TimeId dateForUser(not_null<UserData*> user);
|
||||
|
||||
std::unique_ptr<SavedStateBase> saveState() const override;
|
||||
void restoreState(std::unique_ptr<SavedStateBase> state) override;
|
||||
|
||||
private:
|
||||
struct SavedState : SavedStateBase {
|
||||
QString query;
|
||||
TimeId offsetDate = 0;
|
||||
UserData *offsetUser = nullptr;
|
||||
bool allLoaded = false;
|
||||
bool wasLoading = false;
|
||||
};
|
||||
struct Item {
|
||||
not_null<UserData*> user;
|
||||
TimeId date = 0;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -210,7 +214,7 @@ void ProcessFullPhoto(
|
||||
) | rpl::map([=] {
|
||||
const auto user = peer->asUser();
|
||||
const auto username = peer->username();
|
||||
const auto channelId = user->personalChannelId();
|
||||
const auto channelId = user ? user->personalChannelId() : 0;
|
||||
const auto channel = channelId
|
||||
? user->owner().channel(channelId).get()
|
||||
: nullptr;
|
||||
@@ -446,6 +450,7 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void()> open,
|
||||
Fn<bool()> videoPaused,
|
||||
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
|
||||
const style::ShortInfoBox *stOverride) {
|
||||
const auto type = peer->isSelf()
|
||||
? PeerShortInfoType::Self
|
||||
@@ -463,6 +468,13 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
std::move(videoPaused),
|
||||
stOverride);
|
||||
|
||||
if (menuFiller) {
|
||||
result->fillMenuRequests(
|
||||
) | rpl::start_with_next([=](Ui::Menu::MenuCallback callback) {
|
||||
menuFiller(std::move(callback));
|
||||
}, result->lifetime());
|
||||
}
|
||||
|
||||
result->openRequests(
|
||||
) | rpl::start_with_next(open, result->lifetime());
|
||||
|
||||
@@ -481,10 +493,21 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
return navigation->parentController()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
};
|
||||
auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
|
||||
const auto controller = navigation->parentController();
|
||||
const auto peerSeparateId = Window::SeparateId(peer);
|
||||
if (controller->windowId() != peerSeparateId) {
|
||||
addAction(tr::lng_context_new_window(tr::now), [=] {
|
||||
Ui::PreventDelayedActivation();
|
||||
controller->showInNewWindow(peer);
|
||||
}, &st::menuIconNewWindow);
|
||||
}
|
||||
};
|
||||
return PrepareShortInfoBox(
|
||||
peer,
|
||||
open,
|
||||
videoIsPaused,
|
||||
std::move(menuFiller),
|
||||
stOverride);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -77,7 +77,7 @@ bool operator==(const Descriptor &a, const Descriptor &b) {
|
||||
struct Preload {
|
||||
Descriptor descriptor;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::weak_ptr<ChatHelpers::Show> show;
|
||||
std::weak_ptr<Main::SessionShow> show;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<Preload> &Preloads() {
|
||||
@@ -133,6 +133,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_subtitle_business();
|
||||
case PremiumFeature::Effects:
|
||||
return tr::lng_premium_summary_subtitle_effects();
|
||||
case PremiumFeature::FilterTags:
|
||||
return tr::lng_premium_summary_subtitle_filter_tags();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_subtitle_location();
|
||||
@@ -196,6 +198,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_about_business();
|
||||
case PremiumFeature::Effects:
|
||||
return tr::lng_premium_summary_about_effects();
|
||||
case PremiumFeature::FilterTags:
|
||||
return tr::lng_premium_summary_about_filter_tags();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_about_location();
|
||||
@@ -534,6 +538,7 @@ struct VideoPreviewDocument {
|
||||
case PremiumFeature::LastSeen: return "last_seen";
|
||||
case PremiumFeature::MessagePrivacy: return "message_privacy";
|
||||
case PremiumFeature::Effects: return "effects";
|
||||
case PremiumFeature::FilterTags: return "folder_tags";
|
||||
|
||||
case PremiumFeature::BusinessLocation: return "business_location";
|
||||
case PremiumFeature::BusinessHours: return "business_hours";
|
||||
|
||||
@@ -71,6 +71,7 @@ enum class PremiumFeature {
|
||||
MessagePrivacy,
|
||||
Business,
|
||||
Effects,
|
||||
FilterTags,
|
||||
|
||||
// Business features.
|
||||
BusinessLocation,
|
||||
|
||||
@@ -79,7 +79,6 @@ void ShowReportMessageBox(
|
||||
auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
Data::ReportInput reportInput) -> void {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
report(reportInput, [=](const Api::ReportResult &result) {
|
||||
if (!result.error.isEmpty()) {
|
||||
if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
|
||||
@@ -199,6 +198,7 @@ void ShowReportMessageBox(
|
||||
}
|
||||
}));
|
||||
} else if (result.successful) {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
show->showToast(
|
||||
tr::lng_report_thanks(tr::now),
|
||||
kToastDuration);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/widgets/chat_filters_tabs_strip.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
@@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/share_message_phrase_factory.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -81,11 +83,14 @@ public:
|
||||
void activateSkipColumn(int direction);
|
||||
void activateSkipPage(int pageHeight, int direction);
|
||||
void updateFilter(QString filter = QString());
|
||||
[[nodiscard]] bool isFilterEmpty() const;
|
||||
void selectActive();
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<> searchRequests() const;
|
||||
|
||||
void applyChatFilter(FilterId id);
|
||||
|
||||
protected:
|
||||
void visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
@@ -166,7 +171,9 @@ private:
|
||||
int _upon = -1;
|
||||
int _visibleTop = 0;
|
||||
|
||||
std::unique_ptr<Dialogs::IndexedList> _chatsIndexed;
|
||||
std::unique_ptr<Dialogs::IndexedList> _defaultChatsIndexed;
|
||||
std::unique_ptr<Dialogs::IndexedList> _customChatsIndexed;
|
||||
not_null<Dialogs::IndexedList*> _chatsIndexed;
|
||||
QString _filter;
|
||||
std::vector<not_null<Dialogs::Row*>> _filtered;
|
||||
|
||||
@@ -282,6 +289,10 @@ void ShareBox::prepare() {
|
||||
|
||||
_select->setQueryChangedCallback([=](const QString &query) {
|
||||
applyFilterUpdate(query);
|
||||
if (_chatsFilters) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}
|
||||
});
|
||||
_select->setItemRemovedCallback([=](uint64 itemId) {
|
||||
if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) {
|
||||
@@ -337,10 +348,32 @@ void ShareBox::prepare() {
|
||||
{ .suggestCustomEmoji = true });
|
||||
|
||||
_select->raise();
|
||||
|
||||
{
|
||||
const auto chatsFilters = AddChatFiltersTabsStrip(
|
||||
this,
|
||||
_descriptor.session,
|
||||
[this](FilterId id) {
|
||||
_inner->applyChatFilter(id);
|
||||
scrollToY(0);
|
||||
});
|
||||
chatsFilters->lower();
|
||||
chatsFilters->heightValue() | rpl::start_with_next([this](int h) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}, lifetime());
|
||||
_select->heightValue() | rpl::start_with_next([=](int h) {
|
||||
chatsFilters->moveToLeft(0, h);
|
||||
}, chatsFilters->lifetime());
|
||||
_chatsFilters = chatsFilters;
|
||||
}
|
||||
}
|
||||
|
||||
int ShareBox::getTopScrollSkip() const {
|
||||
return _select->isHidden() ? 0 : _select->height();
|
||||
return (_select->isHidden() ? 0 : _select->height())
|
||||
+ ((_chatsFilters && _inner && _inner->isFilterEmpty())
|
||||
? _chatsFilters->height()
|
||||
: 0);
|
||||
}
|
||||
|
||||
int ShareBox::getBottomScrollSkip() const {
|
||||
@@ -671,9 +704,10 @@ ShareBox::Inner::Inner(
|
||||
, _descriptor(descriptor)
|
||||
, _show(std::move(show))
|
||||
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
|
||||
, _chatsIndexed(
|
||||
, _defaultChatsIndexed(
|
||||
std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add)) {
|
||||
Dialogs::SortMode::Add))
|
||||
, _chatsIndexed(_defaultChatsIndexed.get()) {
|
||||
_rowsTop = st::shareRowsTop;
|
||||
_rowHeight = st::shareRowHeight;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
@@ -691,7 +725,7 @@ ShareBox::Inner::Inner(
|
||||
const auto self = _descriptor.session->user();
|
||||
const auto selfHistory = self->owner().history(self);
|
||||
if (_descriptor.filterCallback(selfHistory)) {
|
||||
_chatsIndexed->addToEnd(selfHistory);
|
||||
_defaultChatsIndexed->addToEnd(selfHistory);
|
||||
}
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
@@ -699,7 +733,7 @@ ShareBox::Inner::Inner(
|
||||
if (!history->peer->isSelf()
|
||||
&& (history->asForum()
|
||||
|| _descriptor.filterCallback(history))) {
|
||||
_chatsIndexed->addToEnd(history);
|
||||
_defaultChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -722,7 +756,7 @@ ShareBox::Inner::Inner(
|
||||
|
||||
_descriptor.session->changes().realtimeNameUpdates(
|
||||
) | rpl::start_with_next([=](const Data::NameUpdate &update) {
|
||||
_chatsIndexed->peerNameChanged(
|
||||
_defaultChatsIndexed->peerNameChanged(
|
||||
update.peer,
|
||||
update.oldFirstLetters);
|
||||
}, lifetime());
|
||||
@@ -1331,6 +1365,10 @@ void ShareBox::Inner::updateFilter(QString filter) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ShareBox::Inner::isFilterEmpty() const {
|
||||
return _filter.isEmpty();
|
||||
}
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> ShareBox::Inner::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
@@ -1339,6 +1377,30 @@ rpl::producer<> ShareBox::Inner::searchRequests() const {
|
||||
return _searchRequests.events();
|
||||
}
|
||||
|
||||
void ShareBox::Inner::applyChatFilter(FilterId id) {
|
||||
if (!id) {
|
||||
_chatsIndexed = _defaultChatsIndexed.get();
|
||||
} else {
|
||||
_customChatsIndexed = std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add);
|
||||
_chatsIndexed = _customChatsIndexed.get();
|
||||
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
if (const auto history = row->history()) {
|
||||
if (history->asForum()
|
||||
|| _descriptor.filterCallback(history)) {
|
||||
_customChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto &data = _descriptor.session->data();
|
||||
addList(data.chatsFilters().chatsList(id)->indexed());
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ShareBox::Inner::peopleReceived(
|
||||
const QString &query,
|
||||
const QVector<MTPPeer> &my,
|
||||
|
||||
@@ -174,6 +174,8 @@ private:
|
||||
bool _peopleFull = false;
|
||||
mtpRequestId _peopleRequest = 0;
|
||||
|
||||
RpWidget *_chatsFilters = nullptr;
|
||||
|
||||
using PeopleCache = QMap<QString, MTPcontacts_Found>;
|
||||
PeopleCache _peopleCache;
|
||||
|
||||
|
||||
@@ -139,6 +139,23 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool SortForBirthday(not_null<PeerData*> peer) {
|
||||
const auto user = peer->asUser();
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
const auto birthday = user->birthday();
|
||||
if (!birthday) {
|
||||
return false;
|
||||
}
|
||||
const auto is = [&](const QDate &date) {
|
||||
return (date.day() == birthday.day())
|
||||
&& (date.month() == birthday.month());
|
||||
};
|
||||
const auto now = QDate::currentDate();
|
||||
return is(now) || is(now.addDays(1)) || is(now.addDays(-1));
|
||||
}
|
||||
|
||||
PreviewDelegate::PreviewDelegate(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::ChatStyle*> st,
|
||||
@@ -216,7 +233,7 @@ auto GenerateGiftMedia(
|
||||
return tr::lng_action_gift_got_stars_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
gift.info.convertStars,
|
||||
gift.info.starsConverted,
|
||||
Ui::Text::RichLangValue);
|
||||
});
|
||||
auto description = data.text.empty()
|
||||
@@ -1068,10 +1085,23 @@ void SendGiftBox(
|
||||
const auto padding = st::giftBoxPadding;
|
||||
const auto available = width - padding.left() - padding.right();
|
||||
const auto perRow = available / single.width();
|
||||
const auto count = int(gifts.list.size());
|
||||
|
||||
auto order = ranges::views::ints
|
||||
| ranges::views::take(count)
|
||||
| ranges::to_vector;
|
||||
|
||||
if (SortForBirthday(peer)) {
|
||||
ranges::stable_partition(order, [&](int i) {
|
||||
const auto &gift = gifts.list[i];
|
||||
const auto stars = std::get_if<GiftTypeStars>(&gift);
|
||||
return stars && stars->info.birthday;
|
||||
});
|
||||
}
|
||||
|
||||
auto x = padding.left();
|
||||
auto y = padding.top();
|
||||
state->buttons.resize(gifts.list.size());
|
||||
state->buttons.resize(count);
|
||||
for (auto &button : state->buttons) {
|
||||
if (!button) {
|
||||
button = std::make_unique<GiftButton>(raw, &state->delegate);
|
||||
@@ -1079,9 +1109,9 @@ void SendGiftBox(
|
||||
}
|
||||
}
|
||||
const auto api = gifts.api;
|
||||
for (auto i = 0, count = int(gifts.list.size()); i != count; ++i) {
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto button = state->buttons[i].get();
|
||||
const auto &descriptor = gifts.list[i];
|
||||
const auto &descriptor = gifts.list[order[i]];
|
||||
button->setDescriptor(descriptor);
|
||||
|
||||
const auto last = !((i + 1) % perRow);
|
||||
@@ -1108,12 +1138,12 @@ void SendGiftBox(
|
||||
}
|
||||
});
|
||||
}
|
||||
if (gifts.list.size() % perRow) {
|
||||
if (count % perRow) {
|
||||
y += padding.bottom() + single.height();
|
||||
} else {
|
||||
y += padding.bottom() - st::giftBoxGiftSkip.y();
|
||||
}
|
||||
raw->resize(raw->width(), gifts.list.empty() ? 0 : y);
|
||||
raw->resize(raw->width(), count ? y : 0);
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -169,7 +171,12 @@ void StartRtmpProcess::finish(JoinInfo info) {
|
||||
void StartRtmpProcess::createBox() {
|
||||
auto done = [=] {
|
||||
const auto peer = _request->peer;
|
||||
finish({ .peer = peer, .joinAs = peer, .rtmp = true });
|
||||
const auto joinAs = (peer->isChat() && peer->asChat()->amCreator())
|
||||
? peer
|
||||
: (peer->isChannel() && peer->asChannel()->amCreator())
|
||||
? peer
|
||||
: peer->session().user();
|
||||
finish({ .peer = peer, .joinAs = joinAs, .rtmp = true });
|
||||
};
|
||||
auto revoke = [=] {
|
||||
const auto guard = base::make_weak(&_request->guard);
|
||||
|
||||
@@ -822,7 +822,7 @@ void StickersListFooter::mousePressEvent(QMouseEvent *e) {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
_iconsMousePos = e ? e->globalPos() : QCursor::pos();
|
||||
_iconsMousePos = e->globalPos();
|
||||
updateSelected();
|
||||
|
||||
if (_selected == SpecialOver::Settings) {
|
||||
|
||||
@@ -192,9 +192,7 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
||||
};
|
||||
const auto session = thumb
|
||||
? &thumb->owner()->session()
|
||||
: media
|
||||
? &media->owner()->session()
|
||||
: nullptr;
|
||||
: &media->owner()->session();
|
||||
return LottieCachedFromContent(
|
||||
method,
|
||||
baseKey,
|
||||
|
||||
@@ -33,6 +33,10 @@ base::options::toggle TabbedPanelShowOnClick({
|
||||
|
||||
const char kOptionTabbedPanelShowOnClick[] = "tabbed-panel-show-on-click";
|
||||
|
||||
bool ShowPanelOnClick() {
|
||||
return TabbedPanelShowOnClick.value();
|
||||
}
|
||||
|
||||
TabbedPanel::TabbedPanel(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace ChatHelpers {
|
||||
class TabbedSelector;
|
||||
|
||||
extern const char kOptionTabbedPanelShowOnClick[];
|
||||
[[nodiscard]] bool ShowPanelOnClick();
|
||||
|
||||
struct TabbedPanelDescriptor {
|
||||
Window::SessionController *regularWindow = nullptr;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -189,6 +189,7 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
||||
const auto game = media ? media->game() : nullptr;
|
||||
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) {
|
||||
openLink();
|
||||
return;
|
||||
}
|
||||
const auto bot = _bot;
|
||||
const auto title = game->title;
|
||||
|
||||
@@ -240,7 +240,7 @@ QByteArray Settings::serialize() const {
|
||||
+ Serialize::stringSize(_customFontFamily)
|
||||
+ sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken)
|
||||
+ sizeof(qint32) * 6;
|
||||
+ sizeof(qint32) * 7;
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -309,7 +309,7 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_thirdSectionExtendedBy)
|
||||
<< qint32(_notifyFromAll ? 1 : 0)
|
||||
<< qint32(_nativeWindowFrame.current() ? 1 : 0)
|
||||
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0)
|
||||
<< qint32(0) // Legacy system dark mode
|
||||
<< _cameraDeviceId.current()
|
||||
<< qint32(_ipRevealWarning ? 1 : 0)
|
||||
<< qint32(_groupCallPushToTalk ? 1 : 0)
|
||||
@@ -396,11 +396,12 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
|
||||
<< _tonsiteStorageToken
|
||||
<< qint32(_includeMutedCounterFolders ? 1 : 0)
|
||||
<< qint32(0) // Old IV zoom
|
||||
<< qint32(_chatFiltersHorizontal.current() ? 1 : 0)
|
||||
<< qint32(_skipToastsInFocus ? 1 : 0)
|
||||
<< qint32(_recordVideoMessages ? 1 : 0)
|
||||
<< SerializeVideoQuality(_videoQuality)
|
||||
<< qint32(_ivZoom.current());
|
||||
<< qint32(_ivZoom.current())
|
||||
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0);
|
||||
}
|
||||
|
||||
Ensures(result.size() == size);
|
||||
@@ -528,6 +529,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 skipToastsInFocus = _skipToastsInFocus ? 1 : 0;
|
||||
qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0;
|
||||
quint32 videoQuality = SerializeVideoQuality(_videoQuality);
|
||||
quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -609,6 +611,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
stream >> nativeWindowFrame;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
// Read over this one below, if was in the file.
|
||||
stream >> systemDarkModeEnabled;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
@@ -838,8 +841,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
stream >> includeMutedCounterFolders;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
qint32 oldIvZoom = 0;
|
||||
stream >> oldIvZoom;
|
||||
stream >> chatFiltersHorizontal;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> skipToastsInFocus;
|
||||
@@ -853,6 +855,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> ivZoom;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> systemDarkModeEnabled;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -1068,6 +1073,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_skipToastsInFocus = (skipToastsInFocus == 1);
|
||||
_recordVideoMessages = (recordVideoMessages == 1);
|
||||
_videoQuality = DeserializeVideoQuality(videoQuality);
|
||||
_chatFiltersHorizontal = (chatFiltersHorizontal == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
@@ -1452,13 +1458,13 @@ void Settings::resetOnLastLogout() {
|
||||
_thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
|
||||
_notifyFromAll = true;
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
_storiesClickTooltipHidden = false;
|
||||
_ttlVoiceClickTooltipHidden = false;
|
||||
_ivZoom = 100;
|
||||
_recordVideoMessages = false;
|
||||
_videoQuality = {};
|
||||
_chatFiltersHorizontal = false;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
@@ -1634,4 +1640,16 @@ void Settings::setVideoQuality(Media::VideoQuality value) {
|
||||
_videoQuality = value;
|
||||
}
|
||||
|
||||
bool Settings::chatFiltersHorizontal() const {
|
||||
return _chatFiltersHorizontal.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> Settings::chatFiltersHorizontalChanges() const {
|
||||
return _chatFiltersHorizontal.changes();
|
||||
}
|
||||
|
||||
void Settings::setChatFiltersHorizontal(bool value) {
|
||||
_chatFiltersHorizontal = value;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -929,6 +929,10 @@ public:
|
||||
[[nodiscard]] rpl::producer<int> ivZoomValue() const;
|
||||
void setIvZoom(int value);
|
||||
|
||||
[[nodiscard]] bool chatFiltersHorizontal() const;
|
||||
[[nodiscard]] rpl::producer<bool> chatFiltersHorizontalChanges() const;
|
||||
void setChatFiltersHorizontal(bool value);
|
||||
|
||||
[[nodiscard]] Media::VideoQuality videoQuality() const;
|
||||
void setVideoQuality(Media::VideoQuality quality);
|
||||
|
||||
@@ -1032,7 +1036,7 @@ private:
|
||||
bool _notifyFromAll = true;
|
||||
rpl::variable<bool> _nativeWindowFrame = false;
|
||||
rpl::variable<std::optional<bool>> _systemDarkMode = std::nullopt;
|
||||
rpl::variable<bool> _systemDarkModeEnabled = false;
|
||||
rpl::variable<bool> _systemDarkModeEnabled = true;
|
||||
rpl::variable<WindowTitleContent> _windowTitleContent;
|
||||
WindowPosition _windowPosition; // per-window
|
||||
bool _disableOpenGL = false;
|
||||
@@ -1070,6 +1074,7 @@ private:
|
||||
QByteArray _tonsiteStorageToken;
|
||||
rpl::variable<int> _ivZoom = 100;
|
||||
Media::VideoQuality _videoQuality;
|
||||
rpl::variable<bool> _chatFiltersHorizontal = false;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -618,6 +618,7 @@ bool ResolveUsernameOrPhone(
|
||||
.startAutoSubmit = myContext.botStartAutoSubmit,
|
||||
.botAppName = (appname.isEmpty() ? postParam : appname),
|
||||
.botAppForceConfirmation = myContext.mayShowConfirmation,
|
||||
.botAppFullScreen = (params.value(u"mode"_q) == u"fullscreen"_q),
|
||||
.attachBotUsername = params.value(u"attach"_q),
|
||||
.attachBotToggleCommand = (params.contains(u"startattach"_q)
|
||||
? params.value(u"startattach"_q)
|
||||
|
||||
@@ -340,12 +340,12 @@ void PhoneClickHandler::onClick(ClickContext context) const {
|
||||
if (Trim(phone) != Trim(controller->session().user()->phone())) {
|
||||
menu->addAction(
|
||||
tr::lng_info_add_as_contact(tr::now),
|
||||
[=, raw = resolvePhoneAction.get()] {
|
||||
[=, raw = Ui::MakeWeak(resolvePhoneAction.get())] {
|
||||
controller->show(
|
||||
Box<AddContactBox>(
|
||||
_session,
|
||||
raw->firstName(),
|
||||
raw->lastName(),
|
||||
&controller->session(),
|
||||
raw ? raw->firstName() : QString(),
|
||||
raw ? raw->lastName() : QString(),
|
||||
Trim(phone)));
|
||||
},
|
||||
&st::menuIconInvite);
|
||||
|
||||
@@ -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 = 5007002;
|
||||
constexpr auto AppVersionStr = "5.7.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 5008005;
|
||||
constexpr auto AppVersionStr = "5.8.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -126,7 +126,6 @@ void RecentPeers::applyLocal(QByteArray serialized) {
|
||||
).arg(count));
|
||||
DEBUG_LOG(("Failed bytes: %1.").arg(
|
||||
QString::fromUtf8(serialized.mid(streamPosition).toHex())));
|
||||
_list.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ void SponsoredMessages::clearOldRequests() {
|
||||
|
||||
SponsoredMessages::AppendResult SponsoredMessages::append(
|
||||
not_null<History*> history) {
|
||||
if (isTopBarFor(history)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -69,19 +69,20 @@ struct CreditsHistoryEntry final {
|
||||
QString successLink;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int convertStars = 0;
|
||||
int starsConverted = 0;
|
||||
int floodSkip = 0;
|
||||
bool converted = false;
|
||||
bool anonymous = false;
|
||||
bool savedToProfile = false;
|
||||
bool fromGiftsList = false;
|
||||
bool soldOutInfo = false;
|
||||
bool reaction = false;
|
||||
bool refunded = false;
|
||||
bool pending = false;
|
||||
bool failed = false;
|
||||
bool in = false;
|
||||
bool gift = false;
|
||||
bool converted : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
bool stargift : 1 = false;
|
||||
bool savedToProfile : 1 = false;
|
||||
bool fromGiftsList : 1 = false;
|
||||
bool soldOutInfo : 1 = false;
|
||||
bool reaction : 1 = false;
|
||||
bool refunded : 1 = false;
|
||||
bool pending : 1 = false;
|
||||
bool failed : 1 = false;
|
||||
bool in : 1 = false;
|
||||
bool gift : 1 = false;
|
||||
};
|
||||
|
||||
struct CreditsStatusSlice final {
|
||||
|
||||
@@ -745,6 +745,14 @@ bool DocumentData::emojiUsesTextColor() const {
|
||||
return (_flags & Flag::UseTextColor);
|
||||
}
|
||||
|
||||
void DocumentData::overrideEmojiUsesTextColor(bool value) {
|
||||
if (value) {
|
||||
_flags |= Flag::UseTextColor;
|
||||
} else {
|
||||
_flags &= ~Flag::UseTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
bool DocumentData::hasThumbnail() const {
|
||||
return _thumbnail.location.valid()
|
||||
&& !thumbnailFailed()
|
||||
|
||||
@@ -206,6 +206,7 @@ public:
|
||||
[[nodiscard]] bool isPremiumSticker() const;
|
||||
[[nodiscard]] bool isPremiumEmoji() const;
|
||||
[[nodiscard]] bool emojiUsesTextColor() const;
|
||||
void overrideEmojiUsesTextColor(bool value);
|
||||
|
||||
[[nodiscard]] bool hasThumbnail() const;
|
||||
[[nodiscard]] bool thumbnailLoading() const;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -139,7 +139,7 @@ struct GiftCode {
|
||||
TextWithEntities message;
|
||||
ChannelData *channel = nullptr;
|
||||
MsgId giveawayMsgId = 0;
|
||||
int convertStars = 0;
|
||||
int starsConverted = 0;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int count = 0;
|
||||
|
||||
@@ -1225,6 +1225,9 @@ not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
|
||||
}
|
||||
|
||||
void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
|
||||
if (!document->sticker()) {
|
||||
return;
|
||||
}
|
||||
const auto id = ReactionId{ { document->id } };
|
||||
const auto favorite = (_unresolvedFavoriteId == id);
|
||||
const auto i = _unresolvedTop.find(id);
|
||||
|
||||
@@ -17,11 +17,16 @@ ChatBotCommands::Changed ChatBotCommands::update(
|
||||
clear();
|
||||
} else {
|
||||
for (const auto &commands : list) {
|
||||
auto &value = operator[](commands.userId);
|
||||
changed |= commands.commands.empty()
|
||||
? remove(commands.userId)
|
||||
: !ranges::equal(value, commands.commands);
|
||||
value = commands.commands;
|
||||
if (commands.commands.empty()) {
|
||||
changed |= remove(commands.userId);
|
||||
} else {
|
||||
auto &value = operator[](commands.userId);
|
||||
const auto isEqual = ranges::equal(value, commands.commands);
|
||||
changed |= !isEqual;
|
||||
if (!isEqual) {
|
||||
value = commands.commands;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
|
||||
@@ -1957,6 +1957,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;
|
||||
@@ -337,6 +338,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 +1101,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());
|
||||
});
|
||||
|
||||
68
Telegram/SourceFiles/data/data_unread_value.cpp
Normal file
68
Telegram/SourceFiles/data/data_unread_value.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
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 "data/data_unread_value.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
rpl::producer<Dialogs::UnreadState> MainListUnreadState(
|
||||
not_null<Dialogs::MainList*> list) {
|
||||
return rpl::single(rpl::empty) | rpl::then(
|
||||
list->unreadStateChanges() | rpl::to_empty
|
||||
) | rpl::map([=] {
|
||||
return list->unreadState();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state) {
|
||||
const auto folderId = Data::Folder::kId;
|
||||
if (const auto folder = session->data().folderLoaded(folderId)) {
|
||||
return state - folder->chatsList()->unreadState();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId) {
|
||||
if (filterId > 0) {
|
||||
const auto filters = &session->data().chatsFilters();
|
||||
return MainListUnreadState(filters->chatsList(filterId));
|
||||
}
|
||||
return MainListUnreadState(
|
||||
session->data().chatsList()
|
||||
) | rpl::map([=](const Dialogs::UnreadState &state) {
|
||||
return MainListMapUnreadState(session, state);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<bool> IncludeMutedCounterFoldersValue() {
|
||||
using namespace Window::Notifications;
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
Core::App().notifications().settingsChanged(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == ChangeType::IncludeMuted
|
||||
) | rpl::to_empty
|
||||
) | rpl::map([] {
|
||||
return Core::App().settings().includeMutedCounterFolders();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
30
Telegram/SourceFiles/data/data_unread_value.h
Normal file
30
Telegram/SourceFiles/data/data_unread_value.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 Dialogs {
|
||||
struct UnreadState;
|
||||
} // namespace Dialogs
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state);
|
||||
|
||||
[[nodiscard]] rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> IncludeMutedCounterFoldersValue();
|
||||
|
||||
} // namespace Data
|
||||
@@ -345,6 +345,24 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
|
||||
const auto privacyChanged = (botInfo->privacyPolicyUrl != privacy);
|
||||
botInfo->privacyPolicyUrl = privacy;
|
||||
|
||||
if (const auto settings = d.vapp_settings()) {
|
||||
const auto &data = settings->data();
|
||||
botInfo->botAppColorTitleDay = Ui::MaybeColorFromSerialized(
|
||||
data.vheader_color()).value_or(QColor(0, 0, 0, 0));
|
||||
botInfo->botAppColorTitleNight = Ui::MaybeColorFromSerialized(
|
||||
data.vheader_dark_color()).value_or(QColor(0, 0, 0, 0));
|
||||
botInfo->botAppColorBodyDay = Ui::MaybeColorFromSerialized(
|
||||
data.vbackground_color()).value_or(QColor(0, 0, 0, 0));
|
||||
botInfo->botAppColorBodyNight = Ui::MaybeColorFromSerialized(
|
||||
data.vbackground_dark_color()).value_or(QColor(0, 0, 0, 0));
|
||||
} else {
|
||||
botInfo->botAppColorTitleDay
|
||||
= botInfo->botAppColorTitleNight
|
||||
= botInfo->botAppColorBodyDay
|
||||
= botInfo->botAppColorBodyNight
|
||||
= QColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
if (changedCommands || changedButton || privacyChanged) {
|
||||
owner().botCommandsChanged(this);
|
||||
}
|
||||
@@ -580,6 +598,9 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||
} else {
|
||||
user->setBotInfoVersion(-1);
|
||||
}
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
info->canManageEmojiStatus = update.is_bot_can_manage_emoji_status();
|
||||
}
|
||||
if (const auto pinned = update.vpinned_msg_id()) {
|
||||
SetTopPinnedMessageId(user, pinned->v);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ struct BotInfo {
|
||||
QString botMenuButtonUrl;
|
||||
QString privacyPolicyUrl;
|
||||
|
||||
QColor botAppColorTitleDay = QColor(0, 0, 0, 0);
|
||||
QColor botAppColorTitleNight = QColor(0, 0, 0, 0);
|
||||
QColor botAppColorBodyDay = QColor(0, 0, 0, 0);
|
||||
QColor botAppColorBodyNight = QColor(0, 0, 0, 0);
|
||||
|
||||
QString startToken;
|
||||
Dialogs::EntryState inlineReturnTo;
|
||||
|
||||
@@ -47,6 +52,7 @@ struct BotInfo {
|
||||
bool cantJoinGroups : 1 = false;
|
||||
bool supportsAttachMenu : 1 = false;
|
||||
bool canEditInformation : 1 = false;
|
||||
bool canManageEmojiStatus : 1 = false;
|
||||
bool supportsBusiness : 1 = false;
|
||||
bool hasMainApp : 1 = false;
|
||||
};
|
||||
|
||||
@@ -652,22 +652,27 @@ void CustomEmojiManager::unregisterListener(not_null<Listener*> listener) {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<DocumentData*>> CustomEmojiManager::resolve(
|
||||
DocumentId documentId) {
|
||||
auto CustomEmojiManager::resolve(DocumentId documentId)
|
||||
-> rpl::producer<not_null<DocumentData*>, rpl::empty_error> {
|
||||
return [=](auto consumer) {
|
||||
auto result = rpl::lifetime();
|
||||
const auto put = [=](not_null<DocumentData*> document) {
|
||||
const auto put = [=](
|
||||
not_null<DocumentData*> document,
|
||||
bool resolved = true) {
|
||||
if (!document->sticker()) {
|
||||
if (resolved) {
|
||||
consumer.put_error({});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
consumer.put_next_copy(document);
|
||||
return true;
|
||||
};
|
||||
if (!put(owner().document(documentId))) {
|
||||
const auto listener = new CallbackListener(put);
|
||||
if (!put(owner().document(documentId), false)) {
|
||||
const auto listener = result.make_state<CallbackListener>(put);
|
||||
resolve(documentId, listener);
|
||||
result.add([=] {
|
||||
unregisterListener(listener);
|
||||
delete listener;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
@@ -763,6 +768,9 @@ void CustomEmojiManager::request() {
|
||||
requestFinished();
|
||||
}).fail([=] {
|
||||
LOG(("API Error: Failed to get documents for emoji."));
|
||||
for (const auto &id : ids) {
|
||||
processListeners(_owner->document(id.v));
|
||||
}
|
||||
requestFinished();
|
||||
}).send();
|
||||
}
|
||||
@@ -792,7 +800,8 @@ void CustomEmojiManager::processLoaders(not_null<DocumentData*> document) {
|
||||
}
|
||||
}
|
||||
|
||||
void CustomEmojiManager::processListeners(not_null<DocumentData*> document) {
|
||||
void CustomEmojiManager::processListeners(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto id = document->id;
|
||||
if (const auto listeners = _resolvers.take(id)) {
|
||||
for (const auto &listener : *listeners) {
|
||||
|
||||
@@ -66,8 +66,8 @@ public:
|
||||
void resolve(DocumentId documentId, not_null<Listener*> listener);
|
||||
void unregisterListener(not_null<Listener*> listener);
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<DocumentData*>> resolve(
|
||||
DocumentId documentId);
|
||||
[[nodiscard]] auto resolve(DocumentId documentId)
|
||||
-> rpl::producer<not_null<DocumentData*>, rpl::empty_error>;
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
|
||||
not_null<DocumentData*> document,
|
||||
|
||||
@@ -206,22 +206,20 @@ void Stickers::incrementSticker(not_null<DocumentData*> document) {
|
||||
auto &sets = setsRef();
|
||||
auto it = sets.find(Data::Stickers::CloudRecentSetId);
|
||||
if (it == sets.cend()) {
|
||||
if (it == sets.cend()) {
|
||||
it = sets.emplace(
|
||||
it = sets.emplace(
|
||||
Data::Stickers::CloudRecentSetId,
|
||||
std::make_unique<Data::StickersSet>(
|
||||
&session().data(),
|
||||
Data::Stickers::CloudRecentSetId,
|
||||
std::make_unique<Data::StickersSet>(
|
||||
&session().data(),
|
||||
Data::Stickers::CloudRecentSetId,
|
||||
uint64(0), // accessHash
|
||||
uint64(0), // hash
|
||||
tr::lng_recent_stickers(tr::now),
|
||||
QString(),
|
||||
0, // count
|
||||
SetFlag::Special,
|
||||
TimeId(0))).first;
|
||||
} else {
|
||||
it->second->title = tr::lng_recent_stickers(tr::now);
|
||||
}
|
||||
uint64(0), // accessHash
|
||||
uint64(0), // hash
|
||||
tr::lng_recent_stickers(tr::now),
|
||||
QString(),
|
||||
0, // count
|
||||
SetFlag::Special,
|
||||
TimeId(0))).first;
|
||||
} else {
|
||||
it->second->title = tr::lng_recent_stickers(tr::now);
|
||||
}
|
||||
const auto set = it->second.get();
|
||||
auto removedFromEmoji = std::vector<not_null<EmojiPtr>>();
|
||||
|
||||
@@ -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 }};
|
||||
@@ -692,6 +709,10 @@ dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) {
|
||||
}
|
||||
dialogsSearchTabsPadding: 8px;
|
||||
|
||||
chatsFiltersTabs: SettingsSlider(dialogsSearchTabs) {
|
||||
rippleBottomSkip: 0px;
|
||||
}
|
||||
|
||||
dialogsStoriesList: DialogsStoriesList {
|
||||
small: dialogsStories;
|
||||
full: dialogsStoriesFull;
|
||||
|
||||
136
Telegram/SourceFiles/dialogs/dialogs_common.h
Normal file
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);
|
||||
|
||||
@@ -11,13 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/ui/chat_search_empty.h"
|
||||
#include "dialogs/ui/chat_search_in.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
#include "dialogs/ui/dialogs_video_userpic.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/dialogs_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "dialogs/dialogs_search_tags.h"
|
||||
#include "history/view/history_view_chat_preview.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -31,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"
|
||||
@@ -44,7 +43,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
@@ -65,10 +63,11 @@ 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"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "boxes/peers/edit_forum_topic_box.h"
|
||||
@@ -77,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"
|
||||
|
||||
@@ -90,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) {
|
||||
@@ -116,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) {
|
||||
@@ -224,6 +246,7 @@ InnerWidget::InnerWidget(
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_topicJumpCache = nullptr;
|
||||
_chatsFilterTags.clear();
|
||||
}, lifetime());
|
||||
|
||||
session().downloaderTaskFinished(
|
||||
@@ -290,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();
|
||||
@@ -412,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;
|
||||
}
|
||||
@@ -696,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) {
|
||||
@@ -704,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.;
|
||||
@@ -1054,6 +1215,47 @@ 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).toUpper();
|
||||
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;
|
||||
@@ -1398,7 +1600,7 @@ void InnerWidget::clearIrrelevantState() {
|
||||
setHashtagPressed(-1);
|
||||
_hashtagDeleteSelected = _hashtagDeletePressed = false;
|
||||
_filteredSelected = -1;
|
||||
setFilteredPressed(-1, false);
|
||||
setFilteredPressed(-1, false, false);
|
||||
_peerSearchSelected = -1;
|
||||
setPeerSearchPressed(-1);
|
||||
_previewSelected = -1;
|
||||
@@ -1413,6 +1615,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)) {
|
||||
@@ -1453,16 +1675,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)
|
||||
@@ -1495,15 +1720,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();
|
||||
}
|
||||
}
|
||||
@@ -1588,11 +1822,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);
|
||||
@@ -1621,7 +1855,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(),
|
||||
@@ -1647,7 +1882,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(),
|
||||
@@ -1681,6 +1917,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
|
||||
@@ -1953,6 +2208,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;
|
||||
@@ -1960,7 +2216,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;
|
||||
@@ -1972,12 +2228,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)
|
||||
@@ -1990,7 +2250,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()) {
|
||||
@@ -2011,14 +2280,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;
|
||||
@@ -2029,7 +2315,7 @@ void InnerWidget::setPressed(Row *pressed, bool pressedTopicJump) {
|
||||
}
|
||||
|
||||
void InnerWidget::clearPressed() {
|
||||
setPressed(nullptr, false);
|
||||
setPressed(nullptr, false, false);
|
||||
}
|
||||
|
||||
void InnerWidget::setHashtagPressed(int pressed) {
|
||||
@@ -2039,15 +2325,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;
|
||||
@@ -2124,7 +2427,7 @@ void InnerWidget::dialogRowReplaced(
|
||||
_selected = newRow;
|
||||
}
|
||||
if (_pressed == oldRow) {
|
||||
setPressed(newRow, _pressedTopicJump);
|
||||
setPressed(newRow, _pressedTopicJump, _pressedBotApp);
|
||||
}
|
||||
if (_dragging == oldRow) {
|
||||
if (newRow) {
|
||||
@@ -2816,7 +3119,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();
|
||||
}
|
||||
};
|
||||
@@ -2889,7 +3192,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());
|
||||
@@ -3947,6 +4250,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;
|
||||
@@ -4623,4 +4962,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"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user