Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
277d76df3e | ||
|
|
1ac33d30bd | ||
|
|
658cb438f8 | ||
|
|
2b71625ffe | ||
|
|
2b13fc9a24 | ||
|
|
9e18964e7f | ||
|
|
43dfe559a6 | ||
|
|
aab7ba264c | ||
|
|
b7162b5fad | ||
|
|
ce4a081155 | ||
|
|
5df2a048e1 | ||
|
|
1b6a7fafa8 | ||
|
|
2ab725e5e1 | ||
|
|
88a310a86e | ||
|
|
86319be256 | ||
|
|
9d68ef6421 | ||
|
|
2bb1c5d39b | ||
|
|
3aa15c979d | ||
|
|
c062ba3426 | ||
|
|
343560225c | ||
|
|
e0dd77f0c3 | ||
|
|
92ff07f723 | ||
|
|
a23dca080a | ||
|
|
6844f88567 | ||
|
|
e6060ea277 | ||
|
|
549de7fa54 | ||
|
|
ecf9faa21d | ||
|
|
a87ebd41e7 | ||
|
|
183dd40f39 | ||
|
|
c722c5c46f | ||
|
|
865200db5e | ||
|
|
c3e15de759 | ||
|
|
44bfdbdc83 | ||
|
|
5f10c1875c | ||
|
|
a7ae7a8cda | ||
|
|
706f142a98 | ||
|
|
08df3b2dff | ||
|
|
14672ff145 | ||
|
|
7fcd84d08e | ||
|
|
12f8686326 | ||
|
|
aa445adfff | ||
|
|
603aa5db5f | ||
|
|
c34289036f | ||
|
|
5b6bec775b | ||
|
|
3b0fe3043f | ||
|
|
c99165891f | ||
|
|
4938b18f9d | ||
|
|
8895b4e8a3 | ||
|
|
a7321c9beb | ||
|
|
c23b533704 | ||
|
|
de34c75788 | ||
|
|
06341efe0d | ||
|
|
c810005f86 | ||
|
|
cdedf283ac | ||
|
|
acfd92e2e6 | ||
|
|
51b81dba87 | ||
|
|
7f6dfcf52f | ||
|
|
92582d8434 | ||
|
|
e2bff474db | ||
|
|
4f702e12b7 | ||
|
|
083400d1c2 | ||
|
|
7491337bfd | ||
|
|
d6b833fbb2 | ||
|
|
2113a2b634 | ||
|
|
5df632264f | ||
|
|
42c350243a | ||
|
|
522ca3b04a | ||
|
|
2d53ec5d34 | ||
|
|
13d2f70c3a | ||
|
|
a87d19998e | ||
|
|
6ddf241293 | ||
|
|
e43ec6c4ea | ||
|
|
5f3db95cbd | ||
|
|
d874829b06 | ||
|
|
6cfbccd955 | ||
|
|
0d821c3630 | ||
|
|
b61e3b580d | ||
|
|
5c301353ec | ||
|
|
0363421862 | ||
|
|
6f18b9b691 | ||
|
|
35e40be550 | ||
|
|
c1528f532e |
2
LEGAL
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2024 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2025 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -246,6 +246,8 @@ PRIVATE
|
||||
boxes/peers/prepare_short_info_box.h
|
||||
boxes/peers/replace_boost_box.cpp
|
||||
boxes/peers/replace_boost_box.h
|
||||
boxes/peers/verify_peers_box.cpp
|
||||
boxes/peers/verify_peers_box.h
|
||||
boxes/about_box.cpp
|
||||
boxes/about_box.h
|
||||
boxes/about_sponsored_box.cpp
|
||||
@@ -330,6 +332,8 @@ PRIVATE
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
boxes/stickers_box.h
|
||||
boxes/transfer_gift_box.cpp
|
||||
boxes/transfer_gift_box.h
|
||||
boxes/translate_box.cpp
|
||||
boxes/translate_box.h
|
||||
boxes/url_auth_box.cpp
|
||||
@@ -626,6 +630,7 @@ PRIVATE
|
||||
data/data_shared_media.h
|
||||
data/data_sparse_ids.cpp
|
||||
data/data_sparse_ids.h
|
||||
data/data_star_gift.h
|
||||
data/data_statistics.h
|
||||
data/data_stories.cpp
|
||||
data/data_stories.h
|
||||
@@ -796,6 +801,8 @@ PRIVATE
|
||||
history/view/media/history_view_story_mention.h
|
||||
history/view/media/history_view_theme_document.cpp
|
||||
history/view/media/history_view_theme_document.h
|
||||
history/view/media/history_view_unique_gift.cpp
|
||||
history/view/media/history_view_unique_gift.h
|
||||
history/view/media/history_view_userpic_suggestion.cpp
|
||||
history/view/media/history_view_userpic_suggestion.h
|
||||
history/view/media/history_view_web_page.cpp
|
||||
@@ -1402,8 +1409,6 @@ PRIVATE
|
||||
settings/business/settings_recipients_helper.h
|
||||
settings/business/settings_working_hours.cpp
|
||||
settings/business/settings_working_hours.h
|
||||
settings/cloud_password/settings_cloud_password_common.cpp
|
||||
settings/cloud_password/settings_cloud_password_common.h
|
||||
settings/cloud_password/settings_cloud_password_email.cpp
|
||||
settings/cloud_password/settings_cloud_password_email.h
|
||||
settings/cloud_password/settings_cloud_password_email_confirm.cpp
|
||||
@@ -1416,6 +1421,8 @@ PRIVATE
|
||||
settings/cloud_password/settings_cloud_password_manage.h
|
||||
settings/cloud_password/settings_cloud_password_start.cpp
|
||||
settings/cloud_password/settings_cloud_password_start.h
|
||||
settings/cloud_password/settings_cloud_password_step.cpp
|
||||
settings/cloud_password/settings_cloud_password_step.h
|
||||
settings/settings_active_sessions.cpp
|
||||
settings/settings_active_sessions.h
|
||||
settings/settings_advanced.cpp
|
||||
|
||||
BIN
Telegram/Resources/art/verified_bg.webp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/art/verified_fg.webp
Normal file
|
After Width: | Height: | Size: 500 B |
BIN
Telegram/Resources/icons/menu/tradable.png
Normal file
|
After Width: | Height: | Size: 811 B |
BIN
Telegram/Resources/icons/menu/tradable@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/menu/tradable@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/menu/unique.png
Normal file
|
After Width: | Height: | Size: 868 B |
BIN
Telegram/Resources/icons/menu/unique@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/unique@3x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
@@ -1628,11 +1628,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_bot_star_ref" = "Affiliate Program";
|
||||
"lng_manage_peer_bot_star_ref_off" = "Off";
|
||||
"lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there.";
|
||||
"lng_manage_peer_bot_verify" = "Verify Accounts";
|
||||
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
|
||||
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
|
||||
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
|
||||
"lng_manage_peer_bot_about" = "Use {bot} to manage this bot.";
|
||||
|
||||
"lng_bot_verify_title" = "Choose Chat to Verify";
|
||||
"lng_bot_verify_bot_title" = "Verify Bot";
|
||||
"lng_bot_verify_bot_text" = "Do you want to verify {name} with your verification mark and description?";
|
||||
"lng_bot_verify_bot_about" = "You can customize your description for each bot.";
|
||||
"lng_bot_verify_bot_submit" = "Verify Bot";
|
||||
"lng_bot_verify_bot_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
|
||||
"lng_bot_verify_bot_remove" = "This bot is already verified by you. Do you want to remove verification?";
|
||||
"lng_bot_verify_user_title" = "Verify User";
|
||||
"lng_bot_verify_user_text" = "Do you want to verify {name} with your verification mark and description?";
|
||||
"lng_bot_verify_user_about" = "You can customize your description for each account.";
|
||||
"lng_bot_verify_user_submit" = "Verify User";
|
||||
"lng_bot_verify_user_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
|
||||
"lng_bot_verify_user_remove" = "This account is already verified by you. Do you want to remove verification?";
|
||||
"lng_bot_verify_channel_title" = "Verify Channel";
|
||||
"lng_bot_verify_channel_text" = "Do you want to verify {name} with your verification mark and description?";
|
||||
"lng_bot_verify_channel_about" = "You can customize your description for each channel.";
|
||||
"lng_bot_verify_channel_submit" = "Verify Channel";
|
||||
"lng_bot_verify_channel_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
|
||||
"lng_bot_verify_channel_remove" = "This channel is already verified by you. Do you want to remove verification?";
|
||||
"lng_bot_verify_group_title" = "Verify Group";
|
||||
"lng_bot_verify_group_text" = "Do you want to verify {name} with your verification mark and description?";
|
||||
"lng_bot_verify_group_about" = "You can customize your description for each group.";
|
||||
"lng_bot_verify_group_submit" = "Verify Group";
|
||||
"lng_bot_verify_group_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
|
||||
"lng_bot_verify_group_remove" = "This group is already verified by you. Do you want to remove verification?";
|
||||
"lng_bot_verify_description_label" = "Description";
|
||||
"lng_bot_verify_remove_title" = "Remove verification";
|
||||
"lng_bot_verify_remove_submit" = "Remove";
|
||||
"lng_bot_verify_remove_done" = "You've removed this verification.";
|
||||
|
||||
"lng_star_ref_title" = "Affiliate Program";
|
||||
"lng_star_ref_about" = "Reward those who help grow your user base.";
|
||||
"lng_star_ref_share_title" = "Share revenue with affiliates";
|
||||
@@ -1985,21 +2016,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_proximity_distance_km#other" = "{count} km";
|
||||
"lng_action_webview_data_done" = "Data from the \"{text}\" button was transferred to the bot.";
|
||||
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
|
||||
"lng_action_gift_unique_received" = "{user} sent you a unique collectible item";
|
||||
"lng_action_gift_sent" = "You sent a gift for {cost}";
|
||||
"lng_action_gift_unique_sent" = "You sent a unique collectible item";
|
||||
"lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible";
|
||||
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible";
|
||||
"lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible";
|
||||
"lng_action_gift_transferred" = "{user} transferred you a gift";
|
||||
"lng_action_gift_transferred_mine" = "You transferred a gift to {user}";
|
||||
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
|
||||
"lng_action_gift_self_bought" = "You bought a gift for {cost}";
|
||||
"lng_action_gift_self_subtitle" = "Saved Gift";
|
||||
"lng_action_gift_self_about#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_self_about#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_self_about_unique" = "You can display this gift on your page or turn it into unique collectible and send to others.";
|
||||
"lng_action_gift_for_stars#one" = "{count} Star";
|
||||
"lng_action_gift_for_stars#other" = "{count} Stars";
|
||||
"lng_action_gift_got_subtitle" = "Gift from {user}";
|
||||
"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_upgradable_text" = "Upgrade this gift to a unique collectible.";
|
||||
"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.";
|
||||
"lng_action_gift_sent_upgradable" = "{user} can upgrade this gift to a unique collectible.";
|
||||
"lng_action_gift_premium_months#one" = "{count} Month Premium";
|
||||
"lng_action_gift_premium_months#other" = "{count} Months Premium";
|
||||
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
|
||||
"lng_action_gift_refunded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned.";
|
||||
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
|
||||
"lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile.";
|
||||
"lng_action_suggested_photo_button" = "View Photo";
|
||||
@@ -2403,6 +2449,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_button" = "Subscribe for {cost} per month";
|
||||
|
||||
"lng_premium_summary_new_badge" = "NEW";
|
||||
"lng_soon_badge" = "Soon";
|
||||
|
||||
"lng_premium_success" = "You've successfully subscribed to Telegram Premium!";
|
||||
"lng_premium_unavailable" = "This feature requires subscription to **Telegram Premium**.\n\nUnfortunately, **Telegram Premium** is not available in your region.";
|
||||
@@ -2576,6 +2623,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
|
||||
"lng_credits_box_history_entry_peer" = "Recipient";
|
||||
"lng_credits_box_history_entry_peer_in" = "From";
|
||||
"lng_credits_box_history_entry_gift_from" = "Gift From";
|
||||
"lng_credits_box_history_entry_via" = "Via";
|
||||
"lng_credits_box_history_entry_play_market" = "Play Store";
|
||||
"lng_credits_box_history_entry_app_store" = "App Store";
|
||||
@@ -2585,6 +2633,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
|
||||
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
|
||||
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
|
||||
"lng_credits_box_history_entry_gift_transfer" = "Gift Transfer";
|
||||
"lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
|
||||
"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
|
||||
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
|
||||
@@ -2612,6 +2661,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_history_entry_about_link" = "here";
|
||||
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
|
||||
"lng_credits_box_history_entry_subscription" = "Monthly subscription fee";
|
||||
"lng_credits_box_history_entry_gift_upgrade" = "Collectible Upgrade";
|
||||
|
||||
"lng_credits_subscription_section" = "My subscriptions";
|
||||
"lng_credits_box_subscription_title" = "Subscription";
|
||||
@@ -3186,14 +3236,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_send_title" = "Send a Gift";
|
||||
"lng_gift_send_message" = "Enter Message";
|
||||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile.";
|
||||
"lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message.";
|
||||
"lng_gift_send_unique" = "Make Unique for {price}";
|
||||
"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
|
||||
"lng_gift_send_unique_link" = "Learn More >";
|
||||
"lng_gift_send_premium_about" = "Only {user} will see your message.";
|
||||
"lng_gift_send_button" = "Send a Gift for {cost}";
|
||||
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
|
||||
"lng_gift_sent_title" = "Gift Sent!";
|
||||
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
|
||||
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
|
||||
"lng_gift_limited_of_one" = "unique";
|
||||
"lng_gift_limited_of_count" = "1 of {amount}";
|
||||
"lng_gift_collectible_tag" = "gift";
|
||||
"lng_gift_price_unique" = "Unique";
|
||||
"lng_gift_view_unpack" = "Unpack";
|
||||
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
|
||||
"lng_gift_hidden_hint" = "This gift is hidden. Only you can see it.";
|
||||
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
|
||||
@@ -3204,6 +3262,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_visibility_hidden" = "Not visible on your page";
|
||||
"lng_gift_visibility_show" = "show";
|
||||
"lng_gift_visibility_hide" = "hide";
|
||||
"lng_gift_self_status" = "buy yourself a gift";
|
||||
"lng_gift_self_title" = "Buy a Gift";
|
||||
"lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later.";
|
||||
"lng_gift_unique_owner" = "Owner";
|
||||
"lng_gift_unique_owner_change" = "change";
|
||||
"lng_gift_unique_status" = "Status";
|
||||
"lng_gift_unique_status_non" = "Non-Unique";
|
||||
"lng_gift_unique_status_upgrade" = "upgrade";
|
||||
"lng_gift_unique_number" = "Collectible #{index}";
|
||||
"lng_gift_unique_model" = "Model";
|
||||
"lng_gift_unique_backdrop" = "Backdrop";
|
||||
"lng_gift_unique_symbol" = "Symbol";
|
||||
"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute.";
|
||||
"lng_gift_unique_availability#one" = "{count} of {amount} issued";
|
||||
"lng_gift_unique_availability#other" = "{count} of {amount} issued";
|
||||
"lng_gift_unique_info" = "Gifted to {recipient} on {date}.";
|
||||
"lng_gift_unique_info_sender" = "Gifted by {from} to {recipient} on {date}.";
|
||||
"lng_gift_unique_info_sender_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\".";
|
||||
"lng_gift_unique_info_reciever" = "Gifted to {recipient} on {date}.";
|
||||
"lng_gift_unique_info_reciever_comment" = "Gifted to {recipient} on {date} with the comment \"{text}\".";
|
||||
"lng_gift_availability_left#one" = "{count} of {amount} left";
|
||||
"lng_gift_availability_left#other" = "{count} of {amount} left";
|
||||
"lng_gift_availability_none" = "None of {amount} left";
|
||||
@@ -3226,6 +3304,45 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_send_small" = "send a gift";
|
||||
"lng_gift_sell_small#one" = "sell for {count} Star";
|
||||
"lng_gift_sell_small#other" = "sell for {count} Stars";
|
||||
"lng_gift_upgrade_title" = "Upgrade Gift";
|
||||
"lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction.";
|
||||
"lng_gift_upgrade_preview_title" = "Make Unique";
|
||||
"lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible.";
|
||||
"lng_gift_upgrade_unique_title" = "Unique";
|
||||
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
|
||||
"lng_gift_upgrade_transferable_title" = "Transferable";
|
||||
"lng_gift_upgrade_transferable_about" = "Send your upgraded gift to any of your friends on Telegram.";
|
||||
"lng_gift_upgrade_tradable_title" = "Tradable";
|
||||
"lng_gift_upgrade_tradable_about" = "Sell or auction your gift on third-party NFT marketplaces.";
|
||||
"lng_gift_upgrade_button" = "Upgrade for {price}";
|
||||
"lng_gift_upgrade_free" = "Upgrade for Free";
|
||||
"lng_gift_upgrade_confirm" = "Confirm";
|
||||
"lng_gift_upgrade_add_my" = "Add my name to the gift";
|
||||
"lng_gift_upgrade_add_my_comment" = "Add my name and comment";
|
||||
"lng_gift_upgrade_add_sender" = "Add sender's name to the gift";
|
||||
"lng_gift_upgrade_add_comment" = "Add sender's name and comment";
|
||||
"lng_gift_upgraded_title" = "Gift Upgraded";
|
||||
"lng_gift_upgraded_about" = "Your gift {name} now has unique attributes and can be transferred to others";
|
||||
"lng_gift_transferred_title" = "Gift Transferred";
|
||||
"lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}.";
|
||||
"lng_gift_transfer_title" = "Transfer {name}";
|
||||
"lng_gift_transfer_via_blockchain" = "Send via Blockchain";
|
||||
"lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day";
|
||||
"lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days";
|
||||
"lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour";
|
||||
"lng_gift_transfer_unlocks_hours#other" = "unlocks in {count} hours";
|
||||
"lng_gift_transfer_unlocks_title" = "Unlocking in progress";
|
||||
"lng_gift_transfer_unlocks_about" = "{when}, you'll be able to send this collectible to any TON blockchain address outside Telegram for sale or auction.";
|
||||
"lng_gift_transfer_unlocks_when_days#one" = "In {count} day";
|
||||
"lng_gift_transfer_unlocks_when_days#other" = "In {count} days";
|
||||
"lng_gift_transfer_unlocks_when_hours#one" = "In {count} hour";
|
||||
"lng_gift_transfer_unlocks_when_hours#other" = "In {count} hours";
|
||||
"lng_gift_transfer_unlocks_update_title" = "Update required";
|
||||
"lng_gift_transfer_unlocks_update_about" = "Please update your Telegram application to the latest version.";
|
||||
"lng_gift_transfer_sure" = "Do you want to transfer ownership of {name} to {recipient}?";
|
||||
"lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?";
|
||||
"lng_gift_transfer_button" = "Transfer";
|
||||
"lng_gift_transfer_button_for" = "Transfer for {price}";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
@@ -4029,6 +4146,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_search_messages_from" = "Show messages from";
|
||||
"lng_search_messages_n_of_amount" = "{n} of {amount}";
|
||||
"lng_search_messages_none" = "No results";
|
||||
"lng_search_filter_all" = "All chats";
|
||||
"lng_search_filter_private" = "Private chats";
|
||||
"lng_search_filter_group" = "Group chats";
|
||||
"lng_search_filter_channel" = "Channels";
|
||||
|
||||
"lng_media_save_progress" = "{ready} of {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Save As...";
|
||||
@@ -5237,6 +5358,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_edit" = "Edit Folder";
|
||||
"lng_filters_setup_menu" = "Edit Folders";
|
||||
"lng_filters_new_name" = "Folder name";
|
||||
"lng_filters_enable_animations" = "Enable animations";
|
||||
"lng_filters_disable_animations" = "Disable animations";
|
||||
"lng_filters_add_chats" = "Add Chats";
|
||||
"lng_filters_remove_chats" = "Add Chats to Exclude";
|
||||
"lng_filters_include" = "Included chats";
|
||||
@@ -5879,6 +6002,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_search_tab_no_results_text" = "There were no results for \"{query}\".";
|
||||
"lng_search_tab_no_results_retry" = "Try another hashtag.";
|
||||
"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it.";
|
||||
"lng_search_tab_try_in_all" = "Search in All Messages";
|
||||
|
||||
"lng_contact_details_button" = "View Contact";
|
||||
"lng_contact_details_title" = "Contact details";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.9.2.0" />
|
||||
Version="5.10.2.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,9,2,0
|
||||
PRODUCTVERSION 5,9,2,0
|
||||
FILEVERSION 5,10,2,0
|
||||
PRODUCTVERSION 5,10,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.9.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "FileVersion", "5.10.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.9.2.0"
|
||||
VALUE "ProductVersion", "5.10.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,9,2,0
|
||||
PRODUCTVERSION 5,9,2,0
|
||||
FILEVERSION 5,10,2,0
|
||||
PRODUCTVERSION 5,10,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.9.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "FileVersion", "5.10.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.9.2.0"
|
||||
VALUE "ProductVersion", "5.10.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_chat_filters.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
@@ -14,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -25,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/filter_link_header.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/vertical_list.h"
|
||||
@@ -49,7 +52,7 @@ public:
|
||||
ToggleChatsController(
|
||||
not_null<Window::SessionController*> window,
|
||||
ToggleAction action,
|
||||
const QString &title,
|
||||
Data::ChatFilterTitle title,
|
||||
std::vector<not_null<PeerData*>> chats,
|
||||
std::vector<not_null<PeerData*>> additional);
|
||||
|
||||
@@ -75,7 +78,6 @@ private:
|
||||
Ui::RpWidget *_addedBottomWidget = nullptr;
|
||||
|
||||
ToggleAction _action = ToggleAction::Adding;
|
||||
QString _filterTitle;
|
||||
base::flat_set<not_null<PeerData*>> _checkable;
|
||||
std::vector<not_null<PeerData*>> _chats;
|
||||
std::vector<not_null<PeerData*>> _additional;
|
||||
@@ -106,9 +108,9 @@ private:
|
||||
|
||||
[[nodiscard]] TextWithEntities AboutText(
|
||||
Ui::FilterLinkHeaderType type,
|
||||
const QString &title) {
|
||||
TextWithEntities title) {
|
||||
using Type = Ui::FilterLinkHeaderType;
|
||||
auto boldTitle = Ui::Text::Bold(title);
|
||||
auto boldTitle = Ui::Text::Wrapped(title, EntityType::Bold);
|
||||
return (type == Type::AddingFilter)
|
||||
? tr::lng_filters_by_link_sure(
|
||||
tr::now,
|
||||
@@ -138,19 +140,28 @@ void InitFilterLinkHeader(
|
||||
not_null<PeerListBox*> box,
|
||||
Fn<void(int minHeight, int maxHeight, int addedTopHeight)> adjust,
|
||||
Ui::FilterLinkHeaderType type,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
Data::ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
rpl::producer<int> count,
|
||||
bool horizontalFilters) {
|
||||
const auto icon = Ui::LookupFilterIcon(
|
||||
Ui::LookupFilterIconByEmoji(
|
||||
iconEmoji
|
||||
).value_or(Ui::FilterIcon::Custom)).active;
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> repaint) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &box->peerListUiShow()->session(),
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto header = Ui::MakeFilterLinkHeader(box, {
|
||||
.type = type,
|
||||
.title = TitleText(type)(tr::now),
|
||||
.about = AboutText(type, title),
|
||||
.folderTitle = title,
|
||||
.about = AboutText(type, title.text),
|
||||
.makeAboutContext = makeContext,
|
||||
.folderTitle = title.text,
|
||||
.folderIcon = icon,
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
@@ -248,12 +259,11 @@ void ImportInvite(
|
||||
ToggleChatsController::ToggleChatsController(
|
||||
not_null<Window::SessionController*> window,
|
||||
ToggleAction action,
|
||||
const QString &title,
|
||||
Data::ChatFilterTitle title,
|
||||
std::vector<not_null<PeerData*>> chats,
|
||||
std::vector<not_null<PeerData*>> additional)
|
||||
: _window(window)
|
||||
, _action(action)
|
||||
, _filterTitle(title)
|
||||
, _chats(std::move(chats))
|
||||
, _additional(std::move(additional)) {
|
||||
setStyleOverrides(&st::filterLinkChatsList);
|
||||
@@ -529,7 +539,7 @@ void ShowImportError(
|
||||
|
||||
void ShowImportToast(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &title,
|
||||
Data::ChatFilterTitle title,
|
||||
Ui::FilterLinkHeaderType type,
|
||||
int added) {
|
||||
const auto strong = weak.get();
|
||||
@@ -540,14 +550,27 @@ void ShowImportToast(
|
||||
const auto phrase = created
|
||||
? tr::lng_filters_added_title
|
||||
: tr::lng_filters_updated_title;
|
||||
auto text = Ui::Text::Bold(phrase(tr::now, lt_folder, title));
|
||||
auto text = Ui::Text::Wrapped(
|
||||
phrase(tr::now, lt_folder, title.text, Ui::Text::WithEntities),
|
||||
EntityType::Bold);
|
||||
if (added > 0) {
|
||||
const auto phrase = created
|
||||
? tr::lng_filters_added_also
|
||||
: tr::lng_filters_updated_also;
|
||||
text.append('\n').append(phrase(tr::now, lt_count, added));
|
||||
}
|
||||
strong->showToast(std::move(text));
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](not_null<QWidget*> widget) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
strong->showToast({
|
||||
.text = std::move(text),
|
||||
.textContext = makeContext,
|
||||
});
|
||||
}
|
||||
|
||||
void HandleEnterInBox(not_null<Ui::BoxContent*> box) {
|
||||
@@ -574,8 +597,8 @@ void ProcessFilterInvite(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &slug,
|
||||
FilterId filterId,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
Data::ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
std::vector<not_null<PeerData*>> peers,
|
||||
std::vector<not_null<PeerData*>> already) {
|
||||
const auto strong = weak.get();
|
||||
@@ -616,10 +639,19 @@ void ProcessFilterInvite(
|
||||
|
||||
raw->setRealContentHeight(box->heightValue());
|
||||
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title,
|
||||
title.text,
|
||||
makeContext,
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
@@ -720,7 +752,7 @@ void CheckFilterInvite(
|
||||
if (!strong) {
|
||||
return;
|
||||
}
|
||||
auto title = QString();
|
||||
auto title = Data::ChatFilterTitle();
|
||||
auto iconEmoji = QString();
|
||||
auto filterId = FilterId();
|
||||
auto peers = std::vector<not_null<PeerData*>>();
|
||||
@@ -739,7 +771,8 @@ void CheckFilterInvite(
|
||||
return result;
|
||||
};
|
||||
result.match([&](const MTPDchatlists_chatlistInvite &data) {
|
||||
title = qs(data.vtitle());
|
||||
title.text = ParseTextWithEntities(session, data.vtitle());
|
||||
title.isStatic = data.is_title_noanimate();
|
||||
iconEmoji = data.vemoticon().value_or_empty();
|
||||
peers = parseList(data.vpeers());
|
||||
}, [&](const MTPDchatlists_chatlistInviteAlready &data) {
|
||||
@@ -804,8 +837,8 @@ void ProcessFilterUpdate(
|
||||
|
||||
void ProcessFilterRemove(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
Data::ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
std::vector<not_null<PeerData*>> all,
|
||||
std::vector<not_null<PeerData*>> suggest,
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> done) {
|
||||
@@ -839,10 +872,19 @@ void ProcessFilterRemove(
|
||||
raw->adjust(min, max, addedTop);
|
||||
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
|
||||
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title,
|
||||
title.text,
|
||||
makeContext,
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
|
||||
@@ -17,6 +17,7 @@ class SessionController;
|
||||
|
||||
namespace Data {
|
||||
class ChatFilter;
|
||||
struct ChatFilterTitle;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
@@ -36,8 +37,8 @@ void ProcessFilterUpdate(
|
||||
|
||||
void ProcessFilterRemove(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
Data::ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
std::vector<not_null<PeerData*>> all,
|
||||
std::vector<not_null<PeerData*>> suggest,
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> done);
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_credits.h"
|
||||
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -73,6 +74,11 @@ constexpr auto kTransactionsLimit = 100;
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto nonUniqueGift = stargift
|
||||
? stargift->match([&](const MTPDstarGift &data) {
|
||||
return &data;
|
||||
}, [](const auto &) { return (const MTPDstarGift*)nullptr; })
|
||||
: nullptr;
|
||||
const auto reaction = tl.data().is_reaction();
|
||||
const auto amount = Data::FromTL(tl.data().vstars());
|
||||
const auto starrefAmount = tl.data().vstarref_amount()
|
||||
@@ -85,6 +91,10 @@ constexpr auto kTransactionsLimit = 100;
|
||||
: 0;
|
||||
const auto incoming = (amount >= StarsAmount());
|
||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
||||
const auto parsedGift = stargift
|
||||
? FromTL(&peer->session(), *stargift)
|
||||
: std::optional<Data::StarGift>();
|
||||
const auto giftStickerId = parsedGift ? parsedGift->document->id : 0;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
@@ -97,10 +107,9 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.barePeerId = saveActorId ? peer->id.value : barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.bareGiftStickerId = (stargift
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.bareGiftStickerId = giftStickerId,
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
|
||||
.starrefAmount = starrefAmount,
|
||||
.starrefCommission = starrefCommission,
|
||||
.starrefRecipientId = starrefBarePeerId,
|
||||
@@ -129,12 +138,13 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.starsConverted = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
.starsConverted = int(nonUniqueGift
|
||||
? nonUniqueGift->vconvert_stars().v
|
||||
: 0),
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.stargift = stargift.has_value(),
|
||||
.giftUpgraded = tl.data().is_stargift_upgrade(),
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
|
||||
@@ -601,7 +601,7 @@ auto PremiumGiftCodeOptions::requestStarGifts()
|
||||
_giftsHash = data.vhash().v;
|
||||
const auto &list = data.vgifts().v;
|
||||
const auto session = &_peer->session();
|
||||
auto gifts = std::vector<StarGift>();
|
||||
auto gifts = std::vector<Data::StarGift>();
|
||||
gifts.reserve(list.size());
|
||||
for (const auto &gift : list) {
|
||||
if (auto parsed = FromTL(session, gift)) {
|
||||
@@ -620,7 +620,8 @@ auto PremiumGiftCodeOptions::requestStarGifts()
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<StarGift> &PremiumGiftCodeOptions::starGifts() const {
|
||||
auto PremiumGiftCodeOptions::starGifts() const
|
||||
-> const std::vector<Data::StarGift> & {
|
||||
return _gifts;
|
||||
}
|
||||
|
||||
@@ -758,31 +759,77 @@ rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
}) | rpl::take(1) | rpl::map(random));
|
||||
}
|
||||
|
||||
std::optional<StarGift> FromTL(
|
||||
std::optional<Data::StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift) {
|
||||
const auto &data = gift.data();
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
return {};
|
||||
}
|
||||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().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(),
|
||||
};
|
||||
return gift.match([&](const MTPDstarGift &data) {
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
return std::optional<Data::StarGift>();
|
||||
}
|
||||
return std::optional<Data::StarGift>(Data::StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
|
||||
.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(),
|
||||
.upgradable = data.vupgrade_stars().has_value(),
|
||||
.birthday = data.is_birthday(),
|
||||
});
|
||||
}, [&](const MTPDstarGiftUnique &data) {
|
||||
const auto total = data.vavailability_total().v;
|
||||
auto model = std::optional<Data::UniqueGiftModel>();
|
||||
auto pattern = std::optional<Data::UniqueGiftPattern>();
|
||||
for (const auto &attribute : data.vattributes().v) {
|
||||
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
|
||||
model = FromTL(session, data);
|
||||
}, [&](const MTPDstarGiftAttributePattern &data) {
|
||||
pattern = FromTL(session, data);
|
||||
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
|
||||
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
});
|
||||
}
|
||||
if (!model
|
||||
|| !model->document->sticker()
|
||||
|| !pattern
|
||||
|| !pattern->document->sticker()) {
|
||||
return std::optional<Data::StarGift>();
|
||||
}
|
||||
auto result = Data::StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
|
||||
.title = qs(data.vtitle()),
|
||||
.ownerId = peerFromUser(UserId(data.vowner_id().v)),
|
||||
.number = data.vnum().v,
|
||||
.model = *model,
|
||||
.pattern = *pattern,
|
||||
}),
|
||||
.document = model->document,
|
||||
.limitedLeft = (total - data.vavailability_issued().v),
|
||||
.limitedCount = total,
|
||||
};
|
||||
const auto unique = result.unique.get();
|
||||
for (const auto &attribute : data.vattributes().v) {
|
||||
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
|
||||
}, [&](const MTPDstarGiftAttributePattern &data) {
|
||||
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
|
||||
unique->backdrop = FromTL(data);
|
||||
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
unique->originalDetails = FromTL(session, data);
|
||||
});
|
||||
}
|
||||
return std::make_optional(result);
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<UserStarGift> FromTL(
|
||||
std::optional<Data::UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift) {
|
||||
const auto session = &to->session();
|
||||
@@ -790,8 +837,11 @@ std::optional<UserStarGift> FromTL(
|
||||
auto parsed = FromTL(session, data.vgift());
|
||||
if (!parsed) {
|
||||
return {};
|
||||
} else if (const auto unique = parsed->unique.get()) {
|
||||
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
|
||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
||||
}
|
||||
return UserStarGift{
|
||||
return Data::UserStarGift{
|
||||
.info = std::move(*parsed),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
@@ -802,15 +852,73 @@ std::optional<UserStarGift> FromTL(
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
|
||||
.starsUpgradedBySender = int64(
|
||||
data.vupgrade_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
.messageId = data.vmsg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.upgradable = data.is_can_upgrade(),
|
||||
.anonymous = data.is_name_hidden(),
|
||||
.hidden = data.is_unsaved(),
|
||||
.mine = to->isSelf(),
|
||||
};
|
||||
}
|
||||
|
||||
Data::UniqueGiftModel FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributeModel &data) {
|
||||
auto result = Data::UniqueGiftModel{
|
||||
.document = session->data().processDocument(data.vdocument()),
|
||||
};
|
||||
result.name = qs(data.vname());
|
||||
result.rarityPermille = data.vrarity_permille().v;
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::UniqueGiftPattern FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributePattern &data) {
|
||||
auto result = Data::UniqueGiftPattern{
|
||||
.document = session->data().processDocument(data.vdocument()),
|
||||
};
|
||||
result.document->overrideEmojiUsesTextColor(true);
|
||||
result.name = qs(data.vname());
|
||||
result.rarityPermille = data.vrarity_permille().v;
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) {
|
||||
auto result = Data::UniqueGiftBackdrop();
|
||||
result.name = qs(data.vname());
|
||||
result.rarityPermille = data.vrarity_permille().v;
|
||||
result.centerColor = Ui::ColorFromSerialized(
|
||||
data.vcenter_color());
|
||||
result.edgeColor = Ui::ColorFromSerialized(
|
||||
data.vedge_color());
|
||||
result.patternColor = Ui::ColorFromSerialized(
|
||||
data.vpattern_color());
|
||||
result.textColor = Ui::ColorFromSerialized(
|
||||
data.vtext_color());
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::UniqueGiftOriginalDetails FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
auto result = Data::UniqueGiftOriginalDetails();
|
||||
result.date = data.vdate().v;
|
||||
result.senderId = data.vsender_id()
|
||||
? peerFromUser(
|
||||
UserId(data.vsender_id().value_or_empty()))
|
||||
: PeerId();
|
||||
result.recipientId = peerFromUser(
|
||||
UserId(data.vrecipient_id().v));
|
||||
result.message = data.vmessage()
|
||||
? ParseTextWithEntities(session, *data.vmessage())
|
||||
: TextWithEntities();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "data/data_premium_subscription_option.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class History;
|
||||
@@ -73,34 +74,6 @@ struct GiftOptionData {
|
||||
int months = 0;
|
||||
};
|
||||
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 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 &,
|
||||
const StarGift &) = default;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 starsConverted = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
bool anonymous = false;
|
||||
bool hidden = false;
|
||||
bool mine = false;
|
||||
};
|
||||
|
||||
class Premium final {
|
||||
public:
|
||||
explicit Premium(not_null<ApiWrap*> api);
|
||||
@@ -223,7 +196,7 @@ public:
|
||||
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> requestStarGifts();
|
||||
[[nodiscard]] const std::vector<StarGift> &starGifts() const;
|
||||
[[nodiscard]] const std::vector<Data::StarGift> &starGifts() const;
|
||||
|
||||
private:
|
||||
struct Token final {
|
||||
@@ -253,7 +226,7 @@ private:
|
||||
base::flat_map<Token, Store> _stores;
|
||||
|
||||
int32 _giftsHash = 0;
|
||||
std::vector<StarGift> _gifts;
|
||||
std::vector<Data::StarGift> _gifts;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
@@ -283,11 +256,23 @@ enum class RequirePremiumState {
|
||||
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] std::optional<StarGift> FromTL(
|
||||
[[nodiscard]] std::optional<Data::StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<UserStarGift> FromTL(
|
||||
[[nodiscard]] std::optional<Data::UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift);
|
||||
|
||||
[[nodiscard]] Data::UniqueGiftModel FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributeModel &data);
|
||||
[[nodiscard]] Data::UniqueGiftPattern FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributePattern &data);
|
||||
[[nodiscard]] Data::UniqueGiftBackdrop FromTL(
|
||||
const MTPDstarGiftAttributeBackdrop &data);
|
||||
[[nodiscard]] Data::UniqueGiftOriginalDetails FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributeOriginalDetails &data);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -229,7 +229,7 @@ EntitiesInText EntitiesFromMTP(
|
||||
}
|
||||
|
||||
MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
not_null<Main::Session*> session,
|
||||
Main::Session *session,
|
||||
const EntitiesInText &entities,
|
||||
ConvertOption option) {
|
||||
auto v = QVector<MTPMessageEntity>();
|
||||
@@ -283,6 +283,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
v.push_back(MTP_messageEntityMention(offset, length));
|
||||
} break;
|
||||
case EntityType::MentionName: {
|
||||
Assert(session != nullptr);
|
||||
const auto valid = MentionNameEntity(
|
||||
session,
|
||||
offset,
|
||||
@@ -344,4 +345,14 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
return MTP_vector<MTPMessageEntity>(std::move(v));
|
||||
}
|
||||
|
||||
TextWithEntities ParseTextWithEntities(
|
||||
Main::Session *session,
|
||||
const MTPTextWithEntities &text) {
|
||||
const auto &data = text.data();
|
||||
return {
|
||||
.text = qs(data.vtext()),
|
||||
.entities = EntitiesFromMTP(session, data.ventities().v),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -25,8 +25,12 @@ enum class ConvertOption {
|
||||
const QVector<MTPMessageEntity> &entities);
|
||||
|
||||
[[nodiscard]] MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
not_null<Main::Session*> session,
|
||||
Main::Session *session,
|
||||
const EntitiesInText &entities,
|
||||
ConvertOption option = ConvertOption::WithLocal);
|
||||
|
||||
[[nodiscard]] TextWithEntities ParseTextWithEntities(
|
||||
Main::Session *session,
|
||||
const MTPTextWithEntities &text);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -1218,7 +1218,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTP_int(d.vttl_period().value_or_empty()),
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1255,7 +1256,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTP_int(d.vttl_period().value_or_empty()),
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
|
||||
@@ -3267,13 +3267,13 @@ void ApiWrap::finishForwarding(const SendAction &action) {
|
||||
const auto topicRootId = action.replyTo.topicRootId;
|
||||
auto toForward = history->resolveForwardDraft(topicRootId);
|
||||
if (!toForward.items.empty()) {
|
||||
const auto error = GetErrorTextForSending(
|
||||
const auto error = GetErrorForSending(
|
||||
history->peer,
|
||||
{
|
||||
.topicRootId = topicRootId,
|
||||
.forward = &toForward.items,
|
||||
});
|
||||
if (!error.isEmpty()) {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "core/application.h" // primaryWindow
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::Bold
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
@@ -169,15 +171,26 @@ void ChangeFilterById(
|
||||
)).done([=, chat = history->peer->name(), name = filter.title()] {
|
||||
const auto account = not_null(&history->session().account());
|
||||
if (const auto controller = Core::App().windowFor(account)) {
|
||||
controller->showToast((add
|
||||
? tr::lng_filters_toast_add
|
||||
: tr::lng_filters_toast_remove)(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(chat),
|
||||
lt_folder,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities));
|
||||
const auto isStatic = name.isStatic;
|
||||
const auto textContext = [=](not_null<QWidget*> widget) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
controller->showToast({
|
||||
.text = (add
|
||||
? tr::lng_filters_toast_add
|
||||
: tr::lng_filters_toast_remove)(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(chat),
|
||||
lt_folder,
|
||||
Ui::Text::Wrapped(name.text, EntityType::Bold),
|
||||
Ui::Text::WithEntities),
|
||||
.textContext = textContext,
|
||||
});
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: failed to %1 a dialog to a folder. %2")
|
||||
@@ -274,19 +287,24 @@ void FillChooseFilterMenu(
|
||||
};
|
||||
|
||||
const auto contains = filter.contains(history);
|
||||
const auto title = filter.title();
|
||||
auto item = base::make_unique_q<FilterAction>(
|
||||
menu.get(),
|
||||
menu->st().menu,
|
||||
st::foldersMenu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu.get(),
|
||||
Ui::Text::FixAmpersandInAction(filter.title()),
|
||||
Ui::Text::FixAmpersandInAction(title.text.text),
|
||||
std::move(callback)),
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr,
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr);
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = [raw = item.get()] { raw->update(); },
|
||||
.customEmojiLoopLimit = title.isStatic ? -1 : 0,
|
||||
};
|
||||
item->setMarkedText(title.text, QString(), context);
|
||||
|
||||
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)
|
||||
|
||||
@@ -15,8 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_peer.h"
|
||||
@@ -38,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
@@ -350,13 +353,17 @@ void EditFilterBox(
|
||||
rpl::variable<bool> hasLinks;
|
||||
rpl::variable<bool> chatlist;
|
||||
rpl::variable<bool> creating;
|
||||
rpl::variable<TextWithEntities> title;
|
||||
rpl::variable<bool> staticTitle;
|
||||
rpl::variable<int> colorIndex;
|
||||
};
|
||||
const auto owner = &window->session().data();
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.rules = filter,
|
||||
.chatlist = filter.chatlist(),
|
||||
.creating = filter.title().isEmpty(),
|
||||
.creating = filter.title().empty(),
|
||||
.title = filter.titleText(),
|
||||
.staticTitle = filter.staticTitle(),
|
||||
});
|
||||
state->colorIndex = filter.colorIndex().value_or(kNoTag);
|
||||
state->links = owner->chatsFilters().chatlistLinks(filter.id()),
|
||||
@@ -394,32 +401,70 @@ void EditFilterBox(
|
||||
tr::lng_filters_edit()));
|
||||
box->setCloseByOutsideClick(false);
|
||||
|
||||
const auto session = &window->session();
|
||||
Data::AmPremiumValue(
|
||||
&window->session()
|
||||
session
|
||||
) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
const auto current = state->title.current();
|
||||
const auto name = content->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::windowFilterNameInput,
|
||||
tr::lng_filters_new_name(),
|
||||
filter.title()),
|
||||
Ui::InputField::Mode::SingleLine,
|
||||
tr::lng_filters_new_name()),
|
||||
st::markdownLinkFieldPadding);
|
||||
InitMessageFieldHandlers(window, name, ChatHelpers::PauseReason::Layer);
|
||||
name->setTextWithTags({
|
||||
current.text,
|
||||
TextUtilities::ConvertEntitiesToTextTags(current.entities),
|
||||
}, Ui::InputField::HistoryAction::Clear);
|
||||
name->setMaxLength(kMaxFilterTitleLength);
|
||||
name->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
name->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
box->getDelegate()->outerContainer(),
|
||||
name,
|
||||
&window->session());
|
||||
|
||||
const auto nameEditing = box->lifetime().make_state<NameEditing>(
|
||||
NameEditing{ name });
|
||||
|
||||
const auto staticTitle = Ui::CreateChild<Ui::LinkButton>(
|
||||
name,
|
||||
QString());
|
||||
staticTitle->setClickedCallback([=] {
|
||||
state->staticTitle = !state->staticTitle.current();
|
||||
});
|
||||
state->staticTitle.value() | rpl::start_with_next([=](bool value) {
|
||||
staticTitle->setText(value
|
||||
? tr::lng_filters_enable_animations(tr::now)
|
||||
: tr::lng_filters_disable_animations(tr::now));
|
||||
const auto paused = [=] {
|
||||
using namespace Window;
|
||||
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
|
||||
};
|
||||
name->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
.customEmojiLoopLimit = value ? -1 : 0,
|
||||
});
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kEmojiChat) || paused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kChatSpoiler) || paused();
|
||||
});
|
||||
name->update();
|
||||
}, staticTitle->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
staticTitle->widthValue(),
|
||||
name->widthValue()
|
||||
) | rpl::start_with_next([=](int inner, int outer) {
|
||||
staticTitle->moveToRight(
|
||||
st::windowFilterStaticTitlePosition.x(),
|
||||
st::windowFilterStaticTitlePosition.y(),
|
||||
outer);
|
||||
}, staticTitle->lifetime());
|
||||
|
||||
state->creating.value(
|
||||
) | rpl::filter(!_1) | rpl::start_with_next([=] {
|
||||
nameEditing->custom = true;
|
||||
@@ -430,7 +475,13 @@ void EditFilterBox(
|
||||
if (!nameEditing->settingDefault) {
|
||||
nameEditing->custom = true;
|
||||
}
|
||||
auto entered = name->getTextWithTags();
|
||||
state->title = TextWithEntities{
|
||||
std::move(entered.text),
|
||||
TextUtilities::ConvertTextTagsToEntities(entered.tags),
|
||||
};
|
||||
}, name->lifetime());
|
||||
|
||||
const auto updateDefaultTitle = [=](const Data::ChatFilter &filter) {
|
||||
if (nameEditing->custom) {
|
||||
return;
|
||||
@@ -443,6 +494,11 @@ void EditFilterBox(
|
||||
}
|
||||
};
|
||||
|
||||
state->title.value(
|
||||
) | rpl::start_with_next([=](const TextWithEntities &value) {
|
||||
staticTitle->setVisible(!value.entities.isEmpty());
|
||||
}, staticTitle->lifetime());
|
||||
|
||||
const auto outer = box->getDelegate()->outerContainer();
|
||||
CreateIconSelector(
|
||||
outer,
|
||||
@@ -545,18 +601,28 @@ void EditFilterBox(
|
||||
colors->width(),
|
||||
h);
|
||||
}, preview->lifetime());
|
||||
const auto previewTag = preview->lifetime().make_state<QImage>();
|
||||
const auto previewAlpha = preview->lifetime().make_state<float64>(1);
|
||||
|
||||
struct TagState {
|
||||
Ui::Animations::Simple animation;
|
||||
Ui::ChatsFilterTagContext context;
|
||||
QImage frame;
|
||||
float64 alpha = 1.;
|
||||
};
|
||||
const auto tag = preview->lifetime().make_state<TagState>();
|
||||
tag->context.textContext = Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [] {},
|
||||
};
|
||||
preview->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(preview);
|
||||
p.setOpacity(*previewAlpha);
|
||||
const auto size = previewTag->size() / style::DevicePixelRatio();
|
||||
p.setOpacity(tag->alpha);
|
||||
const auto size = tag->frame.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);
|
||||
p.drawImage(rect.topLeft(), tag->frame);
|
||||
if (p.opacity() < 1) {
|
||||
p.setOpacity(1. - p.opacity());
|
||||
p.setFont(st::normalFont);
|
||||
@@ -573,16 +639,20 @@ void EditFilterBox(
|
||||
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);
|
||||
const auto upperTitle = [=] {
|
||||
auto value = state->title.current();
|
||||
value.text = value.text.toUpper();
|
||||
return value;
|
||||
};
|
||||
state->title.changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
tag->context.color = palette(state->colorIndex.current())->c;
|
||||
tag->frame = Ui::ChatsFilterTag(
|
||||
upperTitle(),
|
||||
tag->context);
|
||||
preview->update();
|
||||
}, preview->lifetime());
|
||||
for (auto i = 0; i < kColorsCount; ++i) {
|
||||
@@ -596,12 +666,12 @@ void EditFilterBox(
|
||||
const auto color = palette(i);
|
||||
button->setBrush(color);
|
||||
if (progress == 1) {
|
||||
*previewTag = Ui::ChatsFilterTag(
|
||||
name->getLastText().toUpper(),
|
||||
color->c,
|
||||
false);
|
||||
tag->context.color = color->c;
|
||||
tag->frame = Ui::ChatsFilterTag(
|
||||
upperTitle(),
|
||||
tag->context);
|
||||
if (i == kNoTag) {
|
||||
*previewAlpha = 0.;
|
||||
tag->alpha = 0.;
|
||||
}
|
||||
}
|
||||
buttons.push_back(button);
|
||||
@@ -616,17 +686,17 @@ void EditFilterBox(
|
||||
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) {
|
||||
tag->animation.stop();
|
||||
tag->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);
|
||||
tag->context.color = anim::color(c1, c2, progress);
|
||||
tag->frame = Ui::ChatsFilterTag(
|
||||
upperTitle(),
|
||||
tag->context);
|
||||
tag->alpha = anim::interpolateF(a1, a2, progress);
|
||||
preview->update();
|
||||
}, 0., 1., st::universalDuration);
|
||||
}
|
||||
@@ -672,9 +742,11 @@ void EditFilterBox(
|
||||
}
|
||||
|
||||
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
|
||||
const auto title = name->getLastText().trimmed();
|
||||
auto title = state->title.current();
|
||||
const auto staticTitle = !title.entities.isEmpty()
|
||||
&& state->staticTitle.current();
|
||||
const auto rules = data->current();
|
||||
if (title.isEmpty()) {
|
||||
if (title.empty()) {
|
||||
name->showError();
|
||||
box->scrollToY(0);
|
||||
return {};
|
||||
@@ -691,7 +763,9 @@ void EditFilterBox(
|
||||
const auto colorIndex = (rawColorIndex >= kNoTag
|
||||
? std::nullopt
|
||||
: std::make_optional(rawColorIndex));
|
||||
return rules.withTitle(title).withColorIndex(colorIndex);
|
||||
return rules.withTitle(
|
||||
{ std::move(title), staticTitle }
|
||||
).withColorIndex(colorIndex);
|
||||
};
|
||||
|
||||
Ui::AddSubsectionTitle(
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/filters/edit_filter_chats_list.h"
|
||||
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -63,13 +64,27 @@ private:
|
||||
|
||||
class ExceptionRow final : public ChatsListBoxController::Row {
|
||||
public:
|
||||
explicit ExceptionRow(not_null<History*> history);
|
||||
ExceptionRow(
|
||||
not_null<History*> history,
|
||||
not_null<PeerListDelegate*> delegate);
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
|
||||
void paintStatusText(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) override;
|
||||
|
||||
private:
|
||||
Ui::Text::String _filtersText;
|
||||
|
||||
};
|
||||
|
||||
class TypeController final : public PeerListController {
|
||||
@@ -126,15 +141,32 @@ Flag TypeRow::flag() const {
|
||||
return static_cast<Flag>(id() & 0xFFFF);
|
||||
}
|
||||
|
||||
ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
|
||||
auto filters = QStringList();
|
||||
ExceptionRow::ExceptionRow(
|
||||
not_null<History*> history,
|
||||
not_null<PeerListDelegate*> delegate)
|
||||
: Row(history) {
|
||||
auto filters = TextWithEntities();
|
||||
for (const auto &filter : history->owner().chatsFilters().list()) {
|
||||
if (filter.contains(history) && filter.id()) {
|
||||
filters << filter.title();
|
||||
if (!filters.empty()) {
|
||||
filters.append(u", "_q);
|
||||
}
|
||||
auto title = filter.title();
|
||||
filters.append(title.isStatic
|
||||
? Data::ForceCustomEmojiStatic(std::move(title.text))
|
||||
: std::move(title.text));
|
||||
}
|
||||
}
|
||||
if (!filters.isEmpty()) {
|
||||
setCustomStatus(filters.join(", "));
|
||||
if (!filters.empty()) {
|
||||
const auto repaint = [=] { delegate->peerListUpdateRow(this); };
|
||||
_filtersText.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
filters,
|
||||
kMarkupTextOptions,
|
||||
Core::MarkedTextContext{
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = repaint,
|
||||
});
|
||||
} else if (peer()->isSelf()) {
|
||||
setCustomStatus(tr::lng_saved_forward_here(tr::now));
|
||||
}
|
||||
@@ -176,6 +208,37 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback(
|
||||
};
|
||||
}
|
||||
|
||||
void ExceptionRow::paintStatusText(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) {
|
||||
if (_filtersText.isEmpty()) {
|
||||
Row::paintStatusText(
|
||||
p,
|
||||
st,
|
||||
x,
|
||||
y,
|
||||
availableWidth,
|
||||
outerWidth,
|
||||
selected);
|
||||
} else {
|
||||
p.setPen(selected ? st.statusFgOver : st.statusFg);
|
||||
_filtersText.draw(p, {
|
||||
.position = { x, y },
|
||||
.outerWidth = outerWidth,
|
||||
.availableWidth = availableWidth,
|
||||
.palette = &st::defaultTextPalette,
|
||||
.now = crl::now(),
|
||||
.pausedEmoji = false,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TypeController::TypeController(
|
||||
not_null<Main::Session*> session,
|
||||
Flags options,
|
||||
@@ -418,7 +481,7 @@ void EditFilterChatsListController::prepareViewHook() {
|
||||
const auto rows = std::make_unique<std::optional<ExceptionRow>[]>(count);
|
||||
auto i = 0;
|
||||
for (const auto &history : _peers) {
|
||||
rows[i++].emplace(history);
|
||||
rows[i++].emplace(history, delegate());
|
||||
}
|
||||
auto pointers = std::vector<ExceptionRow*>();
|
||||
pointers.reserve(count);
|
||||
@@ -499,7 +562,7 @@ auto EditFilterChatsListController::createRow(not_null<History*> history)
|
||||
return nullptr;
|
||||
}
|
||||
return history->inChatList()
|
||||
? std::make_unique<ExceptionRow>(history)
|
||||
? std::make_unique<ExceptionRow>(history, delegate())
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_invite_link.h" // InviteLinkQrBox.
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -482,7 +483,7 @@ private:
|
||||
const not_null<Window::SessionController*> _window;
|
||||
InviteLinkData _data;
|
||||
|
||||
QString _filterTitle;
|
||||
Data::ChatFilterTitle _filterTitle;
|
||||
base::flat_set<not_null<History*>> _filterChats;
|
||||
base::flat_map<not_null<PeerData*>, QString> _denied;
|
||||
rpl::variable<base::flat_set<not_null<PeerData*>>> _selected;
|
||||
@@ -535,6 +536,14 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||
}, verticalLayout->lifetime());
|
||||
verticalLayout->add(std::move(icon.widget));
|
||||
|
||||
const auto isStatic = _filterTitle.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &_window->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
verticalLayout->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
verticalLayout,
|
||||
@@ -544,9 +553,13 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||
? tr::lng_filters_link_no_about(Ui::Text::WithEntities)
|
||||
: tr::lng_filters_link_share_about(
|
||||
lt_folder,
|
||||
rpl::single(Ui::Text::Bold(_filterTitle)),
|
||||
rpl::single(Ui::Text::Wrapped(
|
||||
_filterTitle.text,
|
||||
EntityType::Bold)),
|
||||
Ui::Text::WithEntities)),
|
||||
st::settingsFilterDividerLabel)),
|
||||
st::settingsFilterDividerLabel,
|
||||
st::defaultPopupMenu,
|
||||
makeContext)),
|
||||
st::filterLinkDividerLabelPadding);
|
||||
|
||||
verticalLayout->geometryValue(
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_premium_option.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "boxes/peer_list_controllers.h" // ContactsBoxController.
|
||||
@@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
|
||||
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||
#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox.
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -44,12 +46,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/widgets/label_with_custom_emoji.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/table_layout.h"
|
||||
@@ -65,6 +69,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kRarityTooltipDuration = 3 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] QString CreateMessageLink(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
@@ -125,7 +131,8 @@ namespace {
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id,
|
||||
bool withSendGiftButton = false) {
|
||||
rpl::producer<QString> button = nullptr,
|
||||
Fn<void()> handler = nullptr) {
|
||||
auto result = object_ptr<Ui::AbstractButton>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
@@ -136,19 +143,17 @@ namespace {
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
withSendGiftButton ? peer->shortName() : peer->name(),
|
||||
(button && handler) ? peer->shortName() : peer->name(),
|
||||
st::giveawayGiftCodeValue);
|
||||
const auto send = withSendGiftButton
|
||||
const auto send = (button && handler)
|
||||
? Ui::CreateChild<Ui::RoundButton>(
|
||||
raw,
|
||||
tr::lng_gift_send_small(),
|
||||
std::move(button),
|
||||
st::starGiftSmallButton)
|
||||
: nullptr;
|
||||
if (send) {
|
||||
send->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
send->setClickedCallback([=] {
|
||||
Ui::ShowStarGiftBox(controller->parentController(), peer);
|
||||
});
|
||||
send->setClickedCallback(std::move(handler));
|
||||
}
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
@@ -237,6 +242,57 @@ void AddTableRow(
|
||||
valueMargins);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeAttributeValue(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const Data::UniqueGiftAttribute &attribute,
|
||||
Fn<void(not_null<Ui::RpWidget*>, int)> showTooltip) {
|
||||
auto result = object_ptr<Ui::RpWidget>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
attribute.name,
|
||||
st::giveawayGiftCodeValue);
|
||||
const auto permille = attribute.rarityPermille;
|
||||
|
||||
const auto text = QString::number(permille / 10.) + '%';
|
||||
const auto rarity = Ui::CreateChild<Ui::RoundButton>(
|
||||
raw,
|
||||
rpl::single(text),
|
||||
st::starGiftSmallButton);
|
||||
rarity->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
rarity->widthValue()
|
||||
) | rpl::start_with_next([=](int width, int convertWidth) {
|
||||
const auto convertSkip = convertWidth
|
||||
? (st::normalFont->spacew + convertWidth)
|
||||
: 0;
|
||||
label->resizeToNaturalWidth(width - convertSkip);
|
||||
label->moveToLeft(0, 0, width);
|
||||
rarity->moveToLeft(
|
||||
label->width() + st::normalFont->spacew,
|
||||
(st::giveawayGiftCodeValue.style.font->ascent
|
||||
- st::starGiftSmallButton.style.font->ascent),
|
||||
width);
|
||||
}, label->lifetime());
|
||||
|
||||
label->heightValue() | rpl::start_with_next([=](int height) {
|
||||
raw->resize(
|
||||
raw->width(),
|
||||
height + st::giveawayGiftCodeValueMargin.bottom());
|
||||
}, raw->lifetime());
|
||||
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
rarity->setClickedCallback([=] {
|
||||
showTooltip(rarity, permille);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
@@ -338,13 +394,58 @@ void AddTableRow(
|
||||
: 0;
|
||||
label->resizeToNaturalWidth(width - toggleSkip);
|
||||
label->moveToLeft(0, 0, width);
|
||||
if (toggle) {
|
||||
toggle->moveToLeft(
|
||||
label->width() + st::normalFont->spacew,
|
||||
(st::giveawayGiftCodeValue.style.font->ascent
|
||||
- st::starGiftSmallButton.style.font->ascent),
|
||||
width);
|
||||
}
|
||||
toggle->moveToLeft(
|
||||
label->width() + st::normalFont->spacew,
|
||||
(st::giveawayGiftCodeValue.style.font->ascent
|
||||
- st::starGiftSmallButton.style.font->ascent),
|
||||
width);
|
||||
}, label->lifetime());
|
||||
|
||||
label->heightValue() | rpl::start_with_next([=](int height) {
|
||||
raw->resize(
|
||||
raw->width(),
|
||||
height + st::giveawayGiftCodeValueMargin.bottom());
|
||||
}, raw->lifetime());
|
||||
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeNonUniqueStatusTableValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
Fn<void()> startUpgrade) {
|
||||
auto result = object_ptr<Ui::RpWidget>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
tr::lng_gift_unique_status_non(),
|
||||
st::giveawayGiftCodeValue,
|
||||
st::defaultPopupMenu);
|
||||
|
||||
const auto upgrade = Ui::CreateChild<Ui::RoundButton>(
|
||||
raw,
|
||||
tr::lng_gift_unique_status_upgrade(),
|
||||
st::starGiftSmallButton);
|
||||
upgrade->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
upgrade->setClickedCallback(startUpgrade);
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
upgrade->widthValue()
|
||||
) | rpl::start_with_next([=](int width, int toggleWidth) {
|
||||
const auto toggleSkip = toggleWidth
|
||||
? (st::normalFont->spacew + toggleWidth)
|
||||
: 0;
|
||||
label->resizeToNaturalWidth(width - toggleSkip);
|
||||
label->moveToLeft(0, 0, width);
|
||||
upgrade->moveToLeft(
|
||||
label->width() + st::normalFont->spacew,
|
||||
(st::giveawayGiftCodeValue.style.font->ascent
|
||||
- st::starGiftSmallButton.style.font->ascent),
|
||||
width);
|
||||
}, label->lifetime());
|
||||
|
||||
label->heightValue() | rpl::start_with_next([=](int height) {
|
||||
@@ -1092,7 +1193,8 @@ void AddStarGiftTable(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void(bool)> toggleVisibility,
|
||||
Fn<void()> convertToStars) {
|
||||
Fn<void()> convertToStars,
|
||||
Fn<void()> startUpgrade) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
@@ -1100,14 +1202,41 @@ void AddStarGiftTable(
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
const auto user = session->data().peer(peerId)->asUser();
|
||||
const auto withSendButton = entry.in && user && !user->isBot();
|
||||
const auto unique = entry.uniqueGift.get();
|
||||
const auto selfBareId = session->userPeerId().value;
|
||||
const auto giftToSelf = (peerId == session->userPeerId())
|
||||
&& (entry.in || entry.bareGiftOwnerId == selfBareId);
|
||||
if (unique) {
|
||||
const auto ownerId = PeerId(entry.bareGiftOwnerId);
|
||||
const auto transfer = entry.in
|
||||
&& entry.bareMsgId
|
||||
&& (unique->starsForTransfer >= 0);
|
||||
auto send = transfer ? tr::lng_gift_unique_owner_change() : nullptr;
|
||||
auto handler = transfer ? Fn<void()>([=] {
|
||||
ShowTransferGiftBox(
|
||||
controller->parentController(),
|
||||
entry.uniqueGift,
|
||||
MsgId(entry.bareMsgId));
|
||||
}) : nullptr;
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
MakePeerTableValue(table, controller, peerId, withSendButton),
|
||||
tr::lng_gift_unique_owner(),
|
||||
MakePeerTableValue(table, controller, ownerId, send, handler),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
} else if (peerId) {
|
||||
if (!giftToSelf) {
|
||||
const auto user = session->data().peer(peerId)->asUser();
|
||||
const auto withSendButton = entry.in && user && !user->isBot();
|
||||
auto send = withSendButton ? tr::lng_gift_send_small() : nullptr;
|
||||
auto handler = send ? Fn<void()>([=] {
|
||||
Ui::ShowStarGiftBox(controller->parentController(), user);
|
||||
}) : nullptr;
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
MakePeerTableValue(table, controller, peerId, send, handler),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
} else if (!entry.soldOutInfo) {
|
||||
AddTableRow(
|
||||
table,
|
||||
@@ -1115,21 +1244,21 @@ void AddStarGiftTable(
|
||||
MakeHiddenPeerTableValue(table, controller),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
if (!entry.firstSaleDate.isNull()) {
|
||||
if (!unique && !entry.firstSaleDate.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_first_sale(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(entry.firstSaleDate))));
|
||||
}
|
||||
if (!entry.lastSaleDate.isNull()) {
|
||||
if (!unique && !entry.lastSaleDate.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_last_sale(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(entry.lastSaleDate))));
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
if (!unique && !entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
@@ -1137,7 +1266,87 @@ void AddStarGiftTable(
|
||||
}
|
||||
const auto marginWithButton = st::giveawayGiftCodeValueMargin
|
||||
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
|
||||
{
|
||||
if (unique) {
|
||||
const auto raw = std::make_shared<Ui::ImportantTooltip*>(nullptr);
|
||||
const auto showTooltip = [=](
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
int rarity) {
|
||||
if (*raw) {
|
||||
(*raw)->toggleAnimated(false);
|
||||
}
|
||||
const auto text = QString::number(rarity / 10.) + '%';
|
||||
const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
|
||||
container,
|
||||
Ui::MakeNiceTooltipLabel(
|
||||
container,
|
||||
tr::lng_gift_unique_rarity(
|
||||
lt_percent,
|
||||
rpl::single(TextWithEntities{ text }),
|
||||
Ui::Text::WithEntities),
|
||||
st::boxWideWidth,
|
||||
st::defaultImportantTooltipLabel),
|
||||
st::defaultImportantTooltip);
|
||||
tooltip->toggleFast(false);
|
||||
|
||||
const auto update = [=] {
|
||||
const auto geometry = Ui::MapFrom(
|
||||
container,
|
||||
widget,
|
||||
widget->rect());
|
||||
const auto countPosition = [=](QSize size) {
|
||||
const auto left = geometry.x()
|
||||
+ (geometry.width() - size.width()) / 2;
|
||||
const auto right = container->width()
|
||||
- st::normalFont->spacew;
|
||||
return QPoint(
|
||||
std::max(std::min(left, right - size.width()), 0),
|
||||
geometry.y() - size.height() - st::normalFont->descent);
|
||||
};
|
||||
tooltip->pointAt(geometry, RectPart::Top, countPosition);
|
||||
};
|
||||
container->widthValue(
|
||||
) | rpl::start_with_next(update, tooltip->lifetime());
|
||||
|
||||
update();
|
||||
tooltip->toggleAnimated(true);
|
||||
|
||||
*raw = tooltip;
|
||||
tooltip->shownValue() | rpl::filter(
|
||||
!rpl::mappers::_1
|
||||
) | rpl::start_with_next([=] {
|
||||
crl::on_main(tooltip, [=] {
|
||||
if (tooltip->isHidden()) {
|
||||
if (*raw == tooltip) {
|
||||
*raw = nullptr;
|
||||
}
|
||||
delete tooltip;
|
||||
}
|
||||
});
|
||||
}, tooltip->lifetime());
|
||||
|
||||
base::timer_once(
|
||||
kRarityTooltipDuration
|
||||
) | rpl::start_with_next([=] {
|
||||
tooltip->toggleAnimated(false);
|
||||
}, tooltip->lifetime());
|
||||
};
|
||||
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_unique_model(),
|
||||
MakeAttributeValue(container, unique->model, showTooltip),
|
||||
marginWithButton);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_unique_backdrop(),
|
||||
MakeAttributeValue(container, unique->backdrop, showTooltip),
|
||||
marginWithButton);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_unique_symbol(),
|
||||
MakeAttributeValue(container, unique->pattern, showTooltip),
|
||||
marginWithButton);
|
||||
} else {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_value(),
|
||||
@@ -1159,26 +1368,111 @@ void AddStarGiftTable(
|
||||
std::move(toggleVisibility)),
|
||||
marginWithButton);
|
||||
}
|
||||
if (entry.limitedCount > 0) {
|
||||
if (entry.limitedCount > 0 && !entry.giftRefunded) {
|
||||
auto amount = rpl::single(TextWithEntities{
|
||||
Lang::FormatCountDecimal(entry.limitedCount)
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_availability(),
|
||||
((entry.limitedLeft > 0)
|
||||
? tr::lng_gift_availability_left(
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.limitedLeft * 1.),
|
||||
((!unique && !entry.limitedLeft)
|
||||
? tr::lng_gift_availability_none(
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_gift_availability_none(
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
Ui::Text::WithEntities)));
|
||||
: (unique
|
||||
? tr::lng_gift_unique_availability
|
||||
: tr::lng_gift_availability_left)(
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.limitedLeft * 1.),
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
Ui::Text::WithEntities)));
|
||||
}
|
||||
if (!entry.description.empty()) {
|
||||
if (!unique && startUpgrade) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_unique_status(),
|
||||
MakeNonUniqueStatusTableValue(
|
||||
table,
|
||||
controller,
|
||||
std::move(startUpgrade)),
|
||||
marginWithButton);
|
||||
}
|
||||
if (unique) {
|
||||
const auto &original = unique->originalDetails;
|
||||
if (original.recipientId) {
|
||||
const auto owner = &controller->session().data();
|
||||
const auto to = owner->peer(original.recipientId);
|
||||
const auto from = original.senderId
|
||||
? owner->peer(original.senderId).get()
|
||||
: nullptr;
|
||||
const auto date = base::unixtime::parse(original.date).date();
|
||||
const auto dateText = TextWithEntities{ langDayOfMonth(date) };
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(update),
|
||||
};
|
||||
};
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
(from
|
||||
? (original.message.empty()
|
||||
? tr::lng_gift_unique_info_sender(
|
||||
lt_from,
|
||||
rpl::single(Ui::Text::Link(from->name(), 2)),
|
||||
lt_recipient,
|
||||
rpl::single(Ui::Text::Link(to->name(), 1)),
|
||||
lt_date,
|
||||
rpl::single(dateText),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_gift_unique_info_sender_comment(
|
||||
lt_from,
|
||||
rpl::single(Ui::Text::Link(from->name(), 2)),
|
||||
lt_recipient,
|
||||
rpl::single(Ui::Text::Link(to->name(), 1)),
|
||||
lt_date,
|
||||
rpl::single(dateText),
|
||||
lt_text,
|
||||
rpl::single(original.message),
|
||||
Ui::Text::WithEntities))
|
||||
: (original.message.empty()
|
||||
? tr::lng_gift_unique_info_reciever(
|
||||
lt_recipient,
|
||||
rpl::single(Ui::Text::Link(to->name(), 1)),
|
||||
lt_date,
|
||||
rpl::single(dateText),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_gift_unique_info_reciever_comment(
|
||||
lt_recipient,
|
||||
rpl::single(Ui::Text::Link(to->name(), 1)),
|
||||
lt_date,
|
||||
rpl::single(dateText),
|
||||
lt_text,
|
||||
rpl::single(original.message),
|
||||
Ui::Text::WithEntities))),
|
||||
st::giveawayGiftMessage,
|
||||
st::defaultPopupMenu,
|
||||
makeContext);
|
||||
const auto showBoxLink = [=](not_null<PeerData*> peer) {
|
||||
return std::make_shared<LambdaClickHandler>([=] {
|
||||
controller->uiShow()->showBox(
|
||||
PrepareShortInfoBox(peer, controller));
|
||||
});
|
||||
};
|
||||
label->setLink(1, showBoxLink(to));
|
||||
if (from) {
|
||||
label->setLink(2, showBoxLink(from));
|
||||
}
|
||||
label->setSelectable(true);
|
||||
table->addRow(
|
||||
std::move(label),
|
||||
nullptr,
|
||||
st::giveawayGiftCodeLabelMargin,
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
} else if (!entry.description.empty()) {
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
@@ -1252,6 +1546,8 @@ void AddCreditsHistoryEntryTable(
|
||||
? tr::lng_credits_box_history_entry_referred()
|
||||
: entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
: entry.giftUpgraded
|
||||
? tr::lng_credits_box_history_entry_gift_from()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
AddTableRow(
|
||||
table,
|
||||
|
||||
@@ -59,7 +59,8 @@ void AddStarGiftTable(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void(bool)> toggleVisibility,
|
||||
Fn<void()> convertToStars);
|
||||
Fn<void()> convertToStars,
|
||||
Fn<void()> startUpgrade);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
#include "passport/passport_panel_edit_contact.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
@@ -171,8 +172,9 @@ PasscodeBox::PasscodeBox(
|
||||
bool turningOff)
|
||||
: _session(session)
|
||||
, _api(&_session->mtp())
|
||||
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _turningOff(turningOff)
|
||||
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _about(_textWidth)
|
||||
, _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old())
|
||||
, _newPasscode(
|
||||
this,
|
||||
@@ -193,10 +195,11 @@ PasscodeBox::PasscodeBox(
|
||||
const CloudFields &fields)
|
||||
: _session(session)
|
||||
, _api(mtp)
|
||||
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _turningOff(fields.turningOff)
|
||||
, _cloudPwd(true)
|
||||
, _cloudFields(fields)
|
||||
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _about(_textWidth)
|
||||
, _oldPasscode(this, st::defaultInputField, tr::lng_cloud_password_enter_old())
|
||||
, _newPasscode(
|
||||
this,
|
||||
@@ -274,7 +277,7 @@ void PasscodeBox::prepare() {
|
||||
: _cloudPwd
|
||||
? tr::lng_cloud_password_about(tr::now)
|
||||
: tr::lng_passcode_about(tr::now)));
|
||||
_aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5);
|
||||
_aboutHeight = _about.countHeight(_textWidth);
|
||||
const auto onlyCheck = onlyCheckCurrent();
|
||||
if (onlyCheck) {
|
||||
_oldPasscode->show();
|
||||
@@ -382,28 +385,27 @@ void PasscodeBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
Painter p(this);
|
||||
|
||||
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
|
||||
int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_showRecoverLink && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip;
|
||||
p.setPen(st::boxTextFg);
|
||||
_about.drawLeft(p, st::boxPadding.left(), abouty, w, width());
|
||||
_about.drawLeft(p, st::boxPadding.left(), abouty, _textWidth, width());
|
||||
|
||||
if (!_hintText.isEmpty() && _oldError.isEmpty()) {
|
||||
_hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), w, width(), 1, style::al_topleft);
|
||||
_hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), _textWidth, width(), 1, style::al_topleft);
|
||||
}
|
||||
|
||||
if (!_oldError.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeTextLine), _oldError, style::al_left);
|
||||
p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), _textWidth, st::passcodeTextLine), _oldError, style::al_left);
|
||||
}
|
||||
|
||||
if (!_newError.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeTextLine), _newError, style::al_left);
|
||||
p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), _textWidth, st::passcodeTextLine), _newError, style::al_left);
|
||||
}
|
||||
|
||||
if (!_emailError.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeTextLine), _emailError, style::al_left);
|
||||
p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), _textWidth, st::passcodeTextLine), _emailError, style::al_left);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1141,11 +1143,21 @@ RecoverBox::RecoverBox(
|
||||
Fn<void()> closeParent)
|
||||
: _session(session)
|
||||
, _api(mtp)
|
||||
, _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5))
|
||||
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _cloudFields(fields)
|
||||
, _recoverCode(this, st::defaultInputField, tr::lng_signin_code())
|
||||
, _noEmailAccess(this, tr::lng_signin_try_password(tr::now))
|
||||
, _patternLabel(
|
||||
this,
|
||||
tr::lng_signin_recover_hint(
|
||||
lt_recover_email,
|
||||
rpl::single(Ui::Text::WrapEmailPattern(pattern)),
|
||||
Ui::Text::WithEntities),
|
||||
st::termsContent,
|
||||
st::defaultPopupMenu,
|
||||
[=](Fn<void()> update) { return CommonTextContext{ std::move(update) }; })
|
||||
, _closeParent(std::move(closeParent)) {
|
||||
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (_cloudFields.pendingResetDate != 0 || !session) {
|
||||
_noEmailAccess.destroy();
|
||||
} else {
|
||||
@@ -1176,19 +1188,24 @@ rpl::producer<> RecoverBox::recoveryExpired() const {
|
||||
return _recoveryExpired.events();
|
||||
}
|
||||
|
||||
void RecoverBox::prepare() {
|
||||
setTitle(tr::lng_signin_recover_title());
|
||||
|
||||
addButton(tr::lng_passcode_submit(), [=] { submit(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
void RecoverBox::updateHeight() {
|
||||
setDimensions(
|
||||
st::boxWidth,
|
||||
(st::passcodePadding.top()
|
||||
+ st::passcodePadding.bottom()
|
||||
+ st::passcodeTextLine
|
||||
+ _recoverCode->height()
|
||||
+ _patternLabel->height()
|
||||
+ st::passcodeTextLine));
|
||||
}
|
||||
|
||||
void RecoverBox::prepare() {
|
||||
setTitle(tr::lng_signin_recover_title());
|
||||
|
||||
addButton(tr::lng_passcode_submit(), [=] { submit(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
updateHeight();
|
||||
|
||||
_recoverCode->changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -1205,23 +1222,42 @@ void RecoverBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st::boxTextFg);
|
||||
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
|
||||
p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeTextLine - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeTextLine), _pattern, style::al_left);
|
||||
|
||||
if (!_error.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeTextLine), _error, style::al_left);
|
||||
p.drawText(
|
||||
QRect(
|
||||
st::boxPadding.left(),
|
||||
_recoverCode->y() + _recoverCode->height(),
|
||||
_textWidth,
|
||||
st::passcodeTextLine),
|
||||
_error,
|
||||
style::al_left);
|
||||
}
|
||||
}
|
||||
|
||||
void RecoverBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height());
|
||||
_recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine);
|
||||
_patternLabel->resizeToWidth(_textWidth);
|
||||
_patternLabel->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
st::passcodePadding.top());
|
||||
|
||||
_recoverCode->resize(
|
||||
st::boxWidth - st::boxPadding.left() - st::boxPadding.right(),
|
||||
_recoverCode->height());
|
||||
_recoverCode->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
rect::m::sum::v(st::passcodePadding) + _patternLabel->height());
|
||||
if (_noEmailAccess) {
|
||||
_noEmailAccess->moveToLeft(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height() + (st::passcodeTextLine - _noEmailAccess->height()) / 2);
|
||||
_noEmailAccess->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
rect::bottom(_recoverCode)
|
||||
+ (st::passcodeTextLine - _noEmailAccess->height()) / 2);
|
||||
}
|
||||
|
||||
updateHeight();
|
||||
}
|
||||
|
||||
void RecoverBox::setInnerFocus() {
|
||||
|
||||
@@ -154,6 +154,7 @@ private:
|
||||
|
||||
Main::Session *_session = nullptr;
|
||||
MTP::Sender _api;
|
||||
const int _textWidth;
|
||||
|
||||
QString _pattern;
|
||||
|
||||
@@ -219,17 +220,18 @@ private:
|
||||
void proceedToChange(const QString &code);
|
||||
void checkSubmitFail(const MTP::Error &error);
|
||||
void setError(const QString &error);
|
||||
void updateHeight();
|
||||
|
||||
Main::Session *_session = nullptr;
|
||||
MTP::Sender _api;
|
||||
const int _textWidth;
|
||||
mtpRequestId _submitRequest = 0;
|
||||
|
||||
QString _pattern;
|
||||
|
||||
PasscodeBox::CloudFields _cloudFields;
|
||||
|
||||
object_ptr<Ui::InputField> _recoverCode;
|
||||
object_ptr<Ui::LinkButton> _noEmailAccess;
|
||||
object_ptr<Ui::FlatLabel> _patternLabel;
|
||||
Fn<void()> _closeParent;
|
||||
|
||||
QString _error;
|
||||
|
||||
@@ -788,31 +788,29 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
|| _isVerifyCodesChat) {
|
||||
return 0;
|
||||
}
|
||||
return _badge.drawGetWidth(
|
||||
p,
|
||||
QRect(
|
||||
return _badge.drawGetWidth(p, {
|
||||
.peer = peer(),
|
||||
.rectForName = QRect(
|
||||
nameLeft,
|
||||
nameTop,
|
||||
availableWidth,
|
||||
st::semiboldFont->height),
|
||||
nameWidth,
|
||||
outerWidth,
|
||||
{
|
||||
.peer = peer(),
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
.premium = &(selected
|
||||
? st::dialogsPremiumIcon.over
|
||||
: st::dialogsPremiumIcon.icon),
|
||||
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
|
||||
.premiumFg = &(selected
|
||||
? st::dialogsVerifiedIconBgOver
|
||||
: st::dialogsVerifiedIconBg),
|
||||
.customEmojiRepaint = repaint,
|
||||
.now = now,
|
||||
.paused = false,
|
||||
});
|
||||
.nameWidth = nameWidth,
|
||||
.outerWidth = outerWidth,
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
.premium = &(selected
|
||||
? st::dialogsPremiumIcon.over
|
||||
: st::dialogsPremiumIcon.icon),
|
||||
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
|
||||
.premiumFg = &(selected
|
||||
? st::dialogsVerifiedIconBgOver
|
||||
: st::dialogsVerifiedIconBg),
|
||||
.customEmojiRepaint = repaint,
|
||||
.now = now,
|
||||
.paused = false,
|
||||
});
|
||||
}
|
||||
|
||||
void PeerListRow::paintStatusText(
|
||||
@@ -1682,6 +1680,10 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
||||
_controller->rowClicked(row);
|
||||
}
|
||||
}
|
||||
} else if (button == Qt::MiddleButton && pressed == _selected) {
|
||||
if (auto row = getRow(pressed.index)) {
|
||||
_controller->rowMiddleClicked(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -482,6 +482,8 @@ public:
|
||||
}
|
||||
|
||||
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
|
||||
virtual void rowMiddleClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
virtual void rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "ui/effects/outline_segments.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "window/window_separate_id.h"
|
||||
#include "window/window_session_controller.h" // showAddContact()
|
||||
#include "base/unixtime.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -64,6 +65,10 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
public:
|
||||
using ContactsBoxController::ContactsBoxController;
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> wheelClicks() const {
|
||||
return _wheelClicks.events();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
@@ -72,6 +77,14 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void rowMiddleClicked(
|
||||
not_null<PeerListRow*> row) override {
|
||||
_wheelClicks.fire(row->peer());
|
||||
}
|
||||
|
||||
private:
|
||||
rpl::event_stream<not_null<PeerData*>> _wheelClicks;
|
||||
|
||||
};
|
||||
auto controller = std::make_unique<Controller>(
|
||||
&sessionController->session());
|
||||
@@ -100,6 +113,10 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
online ? &st::contactsSortOnlineIconOver : nullptr);
|
||||
});
|
||||
raw->setSortMode(Mode::Online);
|
||||
|
||||
raw->wheelClicks() | rpl::start_with_next([=](not_null<PeerData*> p) {
|
||||
sessionController->showInNewWindow(p);
|
||||
}, box->lifetime());
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), std::move(init));
|
||||
}
|
||||
|
||||
@@ -2282,6 +2282,8 @@ void ParticipantsBoxSearchController::restoreState(
|
||||
_allLoaded = my->allLoaded;
|
||||
_offset = my->offset;
|
||||
_query = my->query;
|
||||
_timer.cancel();
|
||||
_requestId = 0;
|
||||
if (my->wasLoading) {
|
||||
searchOnServer();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/peers/verify_peers_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/username_box.h"
|
||||
@@ -366,6 +367,7 @@ private:
|
||||
void fillBotEditIntroButton();
|
||||
void fillBotEditCommandsButton();
|
||||
void fillBotEditSettingsButton();
|
||||
void fillBotVerifyAccounts();
|
||||
|
||||
void submitTitle();
|
||||
void submitDescription();
|
||||
@@ -1206,6 +1208,7 @@ void Controller::fillManageSection() {
|
||||
Ui::Text::RichLangValue),
|
||||
st::boxDividerLabel),
|
||||
st::defaultBoxDividerLabelPadding));
|
||||
fillBotVerifyAccounts();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1796,6 +1799,39 @@ void Controller::fillBotEditSettingsButton() {
|
||||
{ &st::menuIconSettings });
|
||||
}
|
||||
|
||||
void Controller::fillBotVerifyAccounts() {
|
||||
Expects(_isBot);
|
||||
|
||||
const auto user = _peer->asUser();
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
_controls.buttonsLayout,
|
||||
object_ptr<Ui::VerticalLayout>(
|
||||
_controls.buttonsLayout)));
|
||||
wrap->toggleOn(rpl::single(
|
||||
rpl::empty
|
||||
) | rpl::then(user->owner().botCommandsChanges(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == _peer
|
||||
) | rpl::to_empty) | rpl::map([=] {
|
||||
const auto info = user->botInfo.get();
|
||||
return info && info->verifierSettings;
|
||||
}));
|
||||
|
||||
const auto inner = wrap->entity();
|
||||
Ui::AddSkip(inner);
|
||||
AddButtonWithCount(
|
||||
inner,
|
||||
tr::lng_manage_peer_bot_verify(),
|
||||
rpl::never<QString>(),
|
||||
[controller = _navigation->parentController(), user] {
|
||||
controller->show(MakeVerifyPeersBox(controller, user));
|
||||
},
|
||||
{ &st::menuIconFactcheck });
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDivider(inner);
|
||||
}
|
||||
|
||||
void Controller::submitTitle() {
|
||||
Expects(_controls.title != nullptr);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_helpers.h" // GetErrorTextForSending.
|
||||
#include "history/history_item_helpers.h" // GetErrorForSending.
|
||||
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -1492,27 +1492,14 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto error = [&] {
|
||||
for (const auto thread : result) {
|
||||
const auto error = GetErrorTextForSending(
|
||||
thread,
|
||||
{ .text = &comment });
|
||||
if (!error.isEmpty()) {
|
||||
return std::make_pair(error, thread);
|
||||
}
|
||||
}
|
||||
return std::make_pair(QString(), result.front());
|
||||
}();
|
||||
if (!error.first.isEmpty()) {
|
||||
auto text = TextWithEntities();
|
||||
if (result.size() > 1) {
|
||||
text.append(
|
||||
Ui::Text::Bold(error.second->chatListName())
|
||||
).append("\n\n");
|
||||
}
|
||||
text.append(error.first);
|
||||
const auto errorWithThread = GetErrorForSending(
|
||||
result,
|
||||
{ .text = &comment });
|
||||
if (errorWithThread.error) {
|
||||
if (*box) {
|
||||
(*box)->uiShow()->showBox(Ui::MakeInformBox(text));
|
||||
(*box)->uiShow()->showBox(MakeSendErrorBox(
|
||||
errorWithThread,
|
||||
result.size() > 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
295
Telegram/SourceFiles/boxes/peers/verify_peers_box.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
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/peers/verify_peers_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kSetupVerificationToastDuration = 4 * crl::time(1000);
|
||||
|
||||
class Controller final : public ChatsListBoxController {
|
||||
public:
|
||||
Controller(not_null<Main::Session*> session, not_null<UserData*> bot)
|
||||
: ChatsListBoxController(session)
|
||||
, _bot(bot) {
|
||||
}
|
||||
|
||||
Main::Session &session() const override;
|
||||
|
||||
void rowClicked(gsl::not_null<PeerListRow*> row) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Row> createRow(not_null<History*> history) override;
|
||||
void prepareViewHook() override;
|
||||
|
||||
void confirmAdd(not_null<PeerData*> peer);
|
||||
void confirmRemove(not_null<PeerData*> peer);
|
||||
|
||||
const not_null<UserData*> _bot;
|
||||
|
||||
};
|
||||
|
||||
void Setup(
|
||||
not_null<UserData*> bot,
|
||||
not_null<PeerData*> peer,
|
||||
QString description,
|
||||
Fn<void(QString)> done) {
|
||||
using Flag = MTPbots_SetCustomVerification::Flag;
|
||||
bot->session().api().request(MTPbots_SetCustomVerification(
|
||||
MTP_flags(Flag::f_bot
|
||||
| Flag::f_enabled
|
||||
| (description.isEmpty() ? Flag() : Flag::f_custom_description)),
|
||||
bot->inputUser,
|
||||
peer->input,
|
||||
MTP_string(description)
|
||||
)).done([=] {
|
||||
done(QString());
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
done(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Remove(
|
||||
not_null<UserData*> bot,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(QString)> done) {
|
||||
bot->session().api().request(MTPbots_SetCustomVerification(
|
||||
MTP_flags(MTPbots_SetCustomVerification::Flag::f_bot),
|
||||
bot->inputUser,
|
||||
peer->input,
|
||||
MTPstring()
|
||||
)).done([=] {
|
||||
done(QString());
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
done(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
Main::Session &Controller::session() const {
|
||||
return _bot->session();
|
||||
}
|
||||
|
||||
void Controller::rowClicked(gsl::not_null<PeerListRow*> row) {
|
||||
const auto peer = row->peer();
|
||||
const auto details = peer->botVerifyDetails();
|
||||
const auto already = details && (details->botId == peerToUser(_bot->id));
|
||||
if (already) {
|
||||
confirmRemove(peer);
|
||||
} else {
|
||||
confirmAdd(peer);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::confirmAdd(not_null<PeerData*> peer) {
|
||||
const auto bot = _bot;
|
||||
const auto show = delegate()->peerListUiShow();
|
||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
struct State {
|
||||
Ui::InputField *field = nullptr;
|
||||
QString description;
|
||||
bool sent = false;
|
||||
};
|
||||
const auto settings = bot->botInfo
|
||||
? bot->botInfo->verifierSettings.get()
|
||||
: nullptr;
|
||||
const auto modify = settings && settings->canModifyDescription;
|
||||
const auto state = std::make_shared<State>(State{
|
||||
.description = settings ? settings->customDescription : QString()
|
||||
});
|
||||
|
||||
const auto limit = session().appConfig().get<int>(
|
||||
u"bot_verification_description_length_limit"_q,
|
||||
70);
|
||||
const auto send = [=] {
|
||||
if (modify && state->description.size() > limit) {
|
||||
state->field->showError();
|
||||
return;
|
||||
} else if (state->sent) {
|
||||
return;
|
||||
}
|
||||
state->sent = true;
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
const auto description = modify ? state->description : QString();
|
||||
Setup(bot, peer, description, [=](QString error) {
|
||||
if (error.isEmpty()) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
show->showToast({
|
||||
.text = PeerVerifyPhrases(peer).sent(
|
||||
tr::now,
|
||||
lt_name,
|
||||
Ui::Text::Bold(peer->shortName()),
|
||||
Ui::Text::WithEntities),
|
||||
.duration = kSetupVerificationToastDuration,
|
||||
});
|
||||
} else {
|
||||
state->sent = false;
|
||||
show->showToast(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto phrases = PeerVerifyPhrases(peer);
|
||||
Ui::ConfirmBox(box, {
|
||||
.text = phrases.text(
|
||||
lt_name,
|
||||
rpl::single(Ui::Text::Bold(peer->shortName())),
|
||||
Ui::Text::WithEntities),
|
||||
.confirmed = send,
|
||||
.confirmText = phrases.submit(),
|
||||
.title = phrases.title(),
|
||||
});
|
||||
if (!modify) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ui::AddSubsectionTitle(
|
||||
box->verticalLayout(),
|
||||
tr::lng_bot_verify_description_label(),
|
||||
QMargins(0, 0, 0, -st::defaultSubsectionTitlePadding.bottom()));
|
||||
|
||||
const auto field = box->addRow(object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::createPollField,
|
||||
Ui::InputField::Mode::NoNewlines,
|
||||
rpl::single(state->description),
|
||||
state->description
|
||||
), st::createPollFieldPadding);
|
||||
state->field = field;
|
||||
|
||||
box->setFocusCallback([=] {
|
||||
field->setFocusFast();
|
||||
});
|
||||
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
|
||||
field->changes() | rpl::start_with_next([=] {
|
||||
state->description = field->getLastText();
|
||||
}, field->lifetime());
|
||||
|
||||
field->setMaxLength(limit * 2);
|
||||
Ui::AddLengthLimitLabel(field, limit, std::nullopt);
|
||||
|
||||
Ui::AddDividerText(box->verticalLayout(), phrases.about());
|
||||
}));
|
||||
}
|
||||
|
||||
void Controller::confirmRemove(not_null<PeerData*> peer) {
|
||||
const auto bot = _bot;
|
||||
const auto show = delegate()->peerListUiShow();
|
||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto sent = std::make_shared<bool>();
|
||||
const auto send = [=] {
|
||||
if (*sent) {
|
||||
return;
|
||||
}
|
||||
*sent = true;
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
Remove(bot, peer, [=](QString error) {
|
||||
if (error.isEmpty()) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
show->showToast(tr::lng_bot_verify_remove_done(tr::now));
|
||||
} else {
|
||||
*sent = false;
|
||||
show->showToast(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
Ui::ConfirmBox(box, {
|
||||
.text = PeerVerifyPhrases(peer).remove(),
|
||||
.confirmed = send,
|
||||
.confirmText = tr::lng_bot_verify_remove_submit(),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
.title = tr::lng_bot_verify_remove_title(),
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
auto Controller::createRow(not_null<History*> history)
|
||||
-> std::unique_ptr<Row> {
|
||||
const auto peer = history->peer;
|
||||
const auto may = peer->isUser() || peer->isChannel();
|
||||
return may ? std::make_unique<Row>(history) : nullptr;
|
||||
}
|
||||
|
||||
void Controller::prepareViewHook() {
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
object_ptr<Ui::BoxContent> MakeVerifyPeersBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<UserData*> bot) {
|
||||
const auto session = &window->session();
|
||||
auto controller = std::make_unique<Controller>(session, bot);
|
||||
auto init = [=](not_null<PeerListBox*> box) {
|
||||
box->setTitle(tr::lng_bot_verify_title());
|
||||
box->addButton(tr::lng_box_done(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), std::move(init));
|
||||
}
|
||||
|
||||
BotVerifyPhrases PeerVerifyPhrases(not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (user->isBot()) {
|
||||
return {
|
||||
.title = tr::lng_bot_verify_bot_title,
|
||||
.text = tr::lng_bot_verify_bot_text,
|
||||
.about = tr::lng_bot_verify_bot_about,
|
||||
.submit = tr::lng_bot_verify_bot_submit,
|
||||
.sent = tr::lng_bot_verify_bot_sent,
|
||||
.remove = tr::lng_bot_verify_bot_remove,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
.title = tr::lng_bot_verify_user_title,
|
||||
.text = tr::lng_bot_verify_user_text,
|
||||
.about = tr::lng_bot_verify_user_about,
|
||||
.submit = tr::lng_bot_verify_user_submit,
|
||||
.sent = tr::lng_bot_verify_user_sent,
|
||||
.remove = tr::lng_bot_verify_user_remove,
|
||||
};
|
||||
}
|
||||
} else if (peer->isBroadcast()) {
|
||||
return {
|
||||
.title = tr::lng_bot_verify_channel_title,
|
||||
.text = tr::lng_bot_verify_channel_text,
|
||||
.about = tr::lng_bot_verify_channel_about,
|
||||
.submit = tr::lng_bot_verify_channel_submit,
|
||||
.sent = tr::lng_bot_verify_channel_sent,
|
||||
.remove = tr::lng_bot_verify_channel_remove,
|
||||
};
|
||||
}
|
||||
return {
|
||||
.title = tr::lng_bot_verify_group_title,
|
||||
.text = tr::lng_bot_verify_group_text,
|
||||
.about = tr::lng_bot_verify_group_about,
|
||||
.submit = tr::lng_bot_verify_group_submit,
|
||||
.sent = tr::lng_bot_verify_group_sent,
|
||||
.remove = tr::lng_bot_verify_group_remove,
|
||||
};
|
||||
}
|
||||
36
Telegram/SourceFiles/boxes/peers/verify_peers_box.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
class PeerData;
|
||||
class UserData;
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> MakeVerifyPeersBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<UserData*> bot);
|
||||
|
||||
struct BotVerifyPhrases {
|
||||
tr::phrase<> title;
|
||||
tr::phrase<lngtag_name> text;
|
||||
tr::phrase<> about;
|
||||
tr::phrase<> submit;
|
||||
tr::phrase<lngtag_name> sent;
|
||||
tr::phrase<> remove;
|
||||
};
|
||||
[[nodiscard]] BotVerifyPhrases PeerVerifyPhrases(not_null<PeerData*> peer);
|
||||
@@ -133,8 +133,6 @@ 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();
|
||||
@@ -152,6 +150,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_business_subtitle_chat_intro();
|
||||
case PremiumFeature::ChatLinks:
|
||||
return tr::lng_business_subtitle_chat_links();
|
||||
case PremiumFeature::FilterTags:
|
||||
return tr::lng_premium_summary_subtitle_filter_tags();
|
||||
}
|
||||
Unexpected("PremiumFeature in SectionTitle.");
|
||||
}
|
||||
@@ -198,8 +198,6 @@ 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();
|
||||
@@ -217,6 +215,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_business_about_chat_intro();
|
||||
case PremiumFeature::ChatLinks:
|
||||
return tr::lng_business_about_chat_links();
|
||||
case PremiumFeature::FilterTags:
|
||||
return tr::lng_premium_summary_about_filter_tags();
|
||||
}
|
||||
Unexpected("PremiumFeature in SectionTitle.");
|
||||
}
|
||||
@@ -538,7 +538,6 @@ 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";
|
||||
@@ -548,6 +547,7 @@ struct VideoPreviewDocument {
|
||||
case PremiumFeature::BusinessBots: return "business_bots";
|
||||
case PremiumFeature::ChatIntro: return "business_intro";
|
||||
case PremiumFeature::ChatLinks: return "business_links";
|
||||
case PremiumFeature::FilterTags: return "folder_tags";
|
||||
}
|
||||
return "";
|
||||
}();
|
||||
@@ -1647,6 +1647,11 @@ void TelegramBusinessPreviewBox(
|
||||
tr::lng_business_about_chat_links,
|
||||
st::settingsBusinessPromoChatLinks);
|
||||
break;
|
||||
case PremiumFeature::FilterTags: push(
|
||||
tr::lng_premium_summary_subtitle_filter_tags,
|
||||
tr::lng_premium_summary_about_filter_tags,
|
||||
st::settingsPremiumIconTags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,20 +7,89 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/self_destruction_box.h"
|
||||
|
||||
#include "api/api_authorizations.h"
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "api/api_self_destruct.h"
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_self_destruct.h"
|
||||
#include "api/api_authorizations.h"
|
||||
#include "main/main_session.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "ui/text/text_utilities.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 "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using Type = SelfDestructionBox::Type;
|
||||
|
||||
void AddDeleteAccount(
|
||||
not_null<Ui::BoxContent*> box,
|
||||
not_null<Main::Session*> session) {
|
||||
if (!session->isTestMode()) {
|
||||
return;
|
||||
}
|
||||
const auto maybeState = session->api().cloudPassword().stateCurrent();
|
||||
if (!maybeState || !maybeState->hasPassword) {
|
||||
return;
|
||||
}
|
||||
const auto top = box->addTopButton(st::infoTopBarMenu);
|
||||
const auto menu
|
||||
= top->lifetime().make_state<base::unique_qptr<Ui::PopupMenu>>();
|
||||
const auto handler = [=] {
|
||||
session->api().cloudPassword().state(
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
|
||||
auto fields = PasscodeBox::CloudFields::From(state);
|
||||
fields.customTitle = tr::lng_settings_destroy_title();
|
||||
fields.customDescription = tr::lng_context_mark_read_all_sure_2(
|
||||
tr::now,
|
||||
Ui::Text::RichLangValue).text;
|
||||
fields.customSubmitButton = tr::lng_theme_delete();
|
||||
fields.customCheckCallback = [=](
|
||||
const Core::CloudPasswordResult &result,
|
||||
QPointer<PasscodeBox> box) {
|
||||
session->api().request(MTPaccount_DeleteAccount(
|
||||
MTP_flags(MTPaccount_DeleteAccount::Flag::f_password),
|
||||
MTP_string("Manual"),
|
||||
result.result
|
||||
)).done([=] {
|
||||
if (box) {
|
||||
box->uiShow()->hideLayer();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (box) {
|
||||
box->handleCustomCheckError(error.type());
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
box->uiShow()->showBox(Box<PasscodeBox>(session, fields));
|
||||
}, top->lifetime());
|
||||
};
|
||||
top->setClickedCallback([=] {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
top,
|
||||
st::popupMenuWithIcons);
|
||||
|
||||
const auto addAction = Ui::Menu::CreateAddActionCallback(menu->get());
|
||||
addAction({
|
||||
.text = tr::lng_settings_destroy_title(tr::now),
|
||||
.handler = handler,
|
||||
.icon = &st::menuIconDeleteAttention,
|
||||
.isAttention = true,
|
||||
});
|
||||
(*menu)->popup(QCursor::pos());
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<int> Values(Type type) {
|
||||
switch (type) {
|
||||
case Type::Account: return { 30, 90, 180, 365, 548, 720 };
|
||||
@@ -151,4 +220,6 @@ void SelfDestructionBox::prepare() {
|
||||
} else {
|
||||
showContent();
|
||||
}
|
||||
|
||||
AddDeleteAccount(this, _session);
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@ void SendCreditsBox(
|
||||
}),
|
||||
session,
|
||||
st::creditsBoxButtonLabel,
|
||||
box->getDelegate()->style().button.textFg->c);
|
||||
&box->getDelegate()->style().button.textFg);
|
||||
|
||||
const auto buttonWidth = st::boxWidth
|
||||
- rect::m::sum::h(stBox.buttonPadding);
|
||||
@@ -524,7 +524,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg) {
|
||||
const style::color *textFg) {
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
@@ -539,7 +539,10 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
context([=] { buttonLabel->update(); }));
|
||||
}, buttonLabel->lifetime());
|
||||
if (textFg) {
|
||||
buttonLabel->setTextColorOverride(textFg);
|
||||
buttonLabel->setTextColorOverride((*textFg)->c);
|
||||
style::PaletteChanged() | rpl::start_with_next([=] {
|
||||
buttonLabel->setTextColorOverride((*textFg)->c);
|
||||
}, buttonLabel->lifetime());
|
||||
}
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
@@ -561,7 +564,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
rpl::producer<TextWithEntities> text,
|
||||
not_null<Main::Session*> session,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg) {
|
||||
const style::color *textFg) {
|
||||
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
|
||||
@@ -43,14 +43,14 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg = {});
|
||||
const style::color *textFg = nullptr);
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
not_null<Main::Session*> session,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg = {});
|
||||
const style::color *textFg = nullptr);
|
||||
|
||||
void SendStarGift(
|
||||
not_null<Main::Session*> session,
|
||||
|
||||
@@ -220,7 +220,7 @@ SendFilesCheck DefaultCheckForPeer(
|
||||
}
|
||||
|
||||
SendFilesCheck DefaultCheckForPeer(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer) {
|
||||
return [=](
|
||||
const Ui::PreparedFile &file,
|
||||
@@ -228,7 +228,7 @@ SendFilesCheck DefaultCheckForPeer(
|
||||
bool silent) {
|
||||
const auto error = Data::FileRestrictionError(peer, file, compress);
|
||||
if (error && !silent) {
|
||||
show->showToast(*error);
|
||||
Data::ShowSendErrorToast(show, peer, error);
|
||||
}
|
||||
return !error.has_value();
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ using SendFilesCheck = Fn<bool(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
[[nodiscard]] SendFilesCheck DefaultCheckForPeer(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
using SendFilesConfirmed = Fn<void(
|
||||
|
||||
@@ -356,7 +356,8 @@ void ShareBox::prepare() {
|
||||
[this](FilterId id) {
|
||||
_inner->applyChatFilter(id);
|
||||
scrollToY(0);
|
||||
});
|
||||
},
|
||||
Window::GifPauseReason::Layer);
|
||||
chatsFilters->lower();
|
||||
chatsFilters->heightValue() | rpl::start_with_next([this](int h) {
|
||||
updateScrollSkips();
|
||||
@@ -1507,26 +1508,11 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto error = [&] {
|
||||
for (const auto thread : result) {
|
||||
const auto error = GetErrorTextForSending(
|
||||
thread,
|
||||
{ .forward = &items, .text = &comment });
|
||||
if (!error.isEmpty()) {
|
||||
return std::make_pair(error, thread);
|
||||
}
|
||||
}
|
||||
return std::make_pair(QString(), result.front());
|
||||
}();
|
||||
if (!error.first.isEmpty()) {
|
||||
auto text = TextWithEntities();
|
||||
if (result.size() > 1) {
|
||||
text.append(
|
||||
Ui::Text::Bold(error.second->chatListName())
|
||||
).append("\n\n");
|
||||
}
|
||||
text.append(error.first);
|
||||
show->showBox(Ui::MakeInformBox(text));
|
||||
const auto error = GetErrorForSending(
|
||||
result,
|
||||
{ .forward = &items, .text = &comment });
|
||||
if (error.error) {
|
||||
show->showBox(MakeSendErrorBox(error, result.size() > 1));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1736,30 +1722,13 @@ void FastShareLink(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto error = [&] {
|
||||
for (const auto thread : result) {
|
||||
const auto error = GetErrorTextForSending(
|
||||
thread,
|
||||
{ .text = &comment });
|
||||
if (!error.isEmpty()) {
|
||||
return std::make_pair(error, thread);
|
||||
}
|
||||
}
|
||||
return std::make_pair(QString(), result.front());
|
||||
}();
|
||||
if (!error.first.isEmpty()) {
|
||||
auto text = TextWithEntities();
|
||||
if (result.size() > 1) {
|
||||
text.append(
|
||||
Ui::Text::Bold(error.second->chatListName())
|
||||
).append("\n\n");
|
||||
}
|
||||
text.append(error.first);
|
||||
const auto error = GetErrorForSending(
|
||||
result,
|
||||
{ .text = &comment });
|
||||
if (error.error) {
|
||||
if (const auto weak = *box) {
|
||||
weak->getDelegate()->show(Ui::MakeConfirmBox({
|
||||
.text = text,
|
||||
.inform = true,
|
||||
}));
|
||||
weak->getDelegate()->show(
|
||||
MakeSendErrorBox(error, result.size() > 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
struct GiftCode;
|
||||
struct CreditsHistoryEntry;
|
||||
} // namespace Data
|
||||
|
||||
namespace Payments {
|
||||
enum class CheckoutResult;
|
||||
} // namespace Payments
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
|
||||
void ChooseStarGiftRecipient(
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
@@ -20,4 +37,52 @@ void ShowStarGiftBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void AddUniqueGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<Data::UniqueGift> data,
|
||||
rpl::producer<QString> subtitleOverride = nullptr);
|
||||
|
||||
struct PatternPoint {
|
||||
QPointF position;
|
||||
float64 scale = 1.;
|
||||
float64 opacity = 1.;
|
||||
};
|
||||
[[nodiscard]] const std::vector<PatternPoint> &PatternPoints();
|
||||
[[nodiscard]] const std::vector<PatternPoint> &PatternPointsSmall();
|
||||
|
||||
void PaintPoints(
|
||||
QPainter &p,
|
||||
const std::vector<PatternPoint> &points,
|
||||
base::flat_map<float64, QImage> &cache,
|
||||
not_null<Text::CustomEmoji*> emoji,
|
||||
const Data::UniqueGift &gift,
|
||||
const QRect &rect,
|
||||
float64 shown = 1.);
|
||||
|
||||
struct StarGiftUpgradeArgs {
|
||||
not_null<Window::SessionController*> controller;
|
||||
base::required<uint64> stargiftId;
|
||||
Fn<void(bool)> ready;
|
||||
not_null<UserData*> user;
|
||||
MsgId itemId = 0;
|
||||
int cost = 0;
|
||||
bool canAddSender = false;
|
||||
bool canAddComment = false;
|
||||
bool canAddMyComment = false;
|
||||
bool addDetailsDefault = false;
|
||||
};
|
||||
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args);
|
||||
|
||||
void AddUniqueCloseButton(not_null<GenericBox*> box);
|
||||
|
||||
void RequestStarsFormAndSubmit(
|
||||
not_null<Window::SessionController*> window,
|
||||
MTPInputInvoice invoice,
|
||||
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done);
|
||||
|
||||
void ShowGiftTransferredToast(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
not_null<PeerData*> to,
|
||||
const MTPUpdates &result);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
416
Telegram/SourceFiles/boxes/transfer_gift_box.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
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/transfer_gift_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "data/data_user.h"
|
||||
#include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle.
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h" // peerListSingleRow.
|
||||
#include "styles/style_dialogs.h" // recentPeersSpecialName.
|
||||
|
||||
namespace {
|
||||
|
||||
struct ExportOption {
|
||||
object_ptr<Ui::RpWidget> content = { nullptr };
|
||||
Fn<bool(int, int, int)> overrideKey;
|
||||
Fn<void()> activate;
|
||||
};
|
||||
|
||||
class Controller final : public ContactsBoxController {
|
||||
public:
|
||||
Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Fn<void(not_null<PeerData*>)> choose);
|
||||
|
||||
void noSearchSubmit();
|
||||
|
||||
private:
|
||||
void prepareViewHook() override;
|
||||
void setupExportOption();
|
||||
|
||||
bool overrideKeyboardNavigation(
|
||||
int direction,
|
||||
int fromIndex,
|
||||
int toIndex) override;
|
||||
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
const std::shared_ptr<Data::UniqueGift> _gift;
|
||||
const Fn<void(not_null<PeerData*>)> _choose;
|
||||
ExportOption _exportOption;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] ExportOption MakeExportOption(
|
||||
not_null<Window::SessionController*> window,
|
||||
TimeId when) {
|
||||
const auto activate = [=] {
|
||||
const auto now = base::unixtime::now();
|
||||
const auto left = (when > now) ? (when - now) : 0;
|
||||
const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0;
|
||||
window->show(Ui::MakeInformBox({
|
||||
.text = (!hours
|
||||
? tr::lng_gift_transfer_unlocks_update_about()
|
||||
: tr::lng_gift_transfer_unlocks_about(
|
||||
lt_when,
|
||||
((hours >= 24)
|
||||
? tr::lng_gift_transfer_unlocks_when_days(
|
||||
lt_count,
|
||||
rpl::single((hours / 24) * 1.))
|
||||
: tr::lng_gift_transfer_unlocks_when_hours(
|
||||
lt_count,
|
||||
rpl::single(hours * 1.))))),
|
||||
.title = (!hours
|
||||
? tr::lng_gift_transfer_unlocks_update_title()
|
||||
: tr::lng_gift_transfer_unlocks_title()),
|
||||
}));
|
||||
};
|
||||
|
||||
class ExportRow final : public PeerListRow {
|
||||
public:
|
||||
explicit ExportRow(TimeId when)
|
||||
: PeerListRow(Data::FakePeerIdForJustName("ton-export").value) {
|
||||
const auto now = base::unixtime::now();
|
||||
_available = (when <= now);
|
||||
if (const auto left = when - now; left > 0) {
|
||||
const auto hours = std::max((left + 1800) / 3600, 1);
|
||||
const auto days = hours / 24;
|
||||
setCustomStatus(days
|
||||
? tr::lng_gift_transfer_unlocks_days(
|
||||
tr::now,
|
||||
lt_count,
|
||||
days)
|
||||
: tr::lng_gift_transfer_unlocks_hours(
|
||||
tr::now,
|
||||
lt_count,
|
||||
hours));
|
||||
}
|
||||
}
|
||||
|
||||
QString generateName() override {
|
||||
return tr::lng_gift_transfer_via_blockchain(tr::now);
|
||||
}
|
||||
QString generateShortName() override {
|
||||
return generateName();
|
||||
}
|
||||
auto generatePaintUserpicCallback(bool forceRound)
|
||||
-> PaintRoundImageCallback override {
|
||||
return [=](
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) mutable {
|
||||
Ui::EmptyUserpic::PaintCurrency(p, x, y, outerWidth, size);
|
||||
};
|
||||
}
|
||||
|
||||
const style::PeerListItem &computeSt(
|
||||
const style::PeerListItem &st) const override {
|
||||
return _available ? st::recentPeersSpecialName : st;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _available = false;
|
||||
|
||||
};
|
||||
|
||||
class ExportController final : public PeerListController {
|
||||
public:
|
||||
ExportController(
|
||||
not_null<Main::Session*> session,
|
||||
TimeId when,
|
||||
Fn<void()> activate)
|
||||
: _session(session)
|
||||
, _when(when)
|
||||
, _activate(std::move(activate)) {
|
||||
}
|
||||
|
||||
void prepare() override {
|
||||
delegate()->peerListAppendRow(
|
||||
std::make_unique<ExportRow>(_when));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
void loadMoreRows() override {
|
||||
}
|
||||
void rowClicked(not_null<PeerListRow*> row) override {
|
||||
_activate();
|
||||
}
|
||||
Main::Session &session() const override {
|
||||
return *_session;
|
||||
}
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
TimeId _when = 0;
|
||||
Fn<void()> _activate;
|
||||
|
||||
};
|
||||
|
||||
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
const auto container = result.data();
|
||||
|
||||
Ui::AddSkip(container);
|
||||
|
||||
const auto delegate = container->lifetime().make_state<
|
||||
PeerListContentDelegateSimple
|
||||
>();
|
||||
const auto controller = container->lifetime().make_state<
|
||||
ExportController
|
||||
>(&window->session(), when, activate);
|
||||
controller->setStyleOverrides(&st::peerListSingleRow);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
delegate->setContent(content);
|
||||
controller->setDelegate(delegate);
|
||||
|
||||
Ui::AddSkip(container);
|
||||
container->add(CreatePeerListSectionSubtitle(
|
||||
container,
|
||||
tr::lng_contacts_header()));
|
||||
|
||||
const auto overrideKey = [=](int direction, int from, int to) {
|
||||
if (!content->isVisible()) {
|
||||
return false;
|
||||
} else if (direction > 0 && from < 0 && to >= 0) {
|
||||
if (content->hasSelection()) {
|
||||
const auto was = content->selectedIndex();
|
||||
const auto now = content->selectSkip(1).reallyMovedTo;
|
||||
if (was != now) {
|
||||
return true;
|
||||
}
|
||||
content->clearSelection();
|
||||
} else {
|
||||
content->selectSkip(1);
|
||||
return true;
|
||||
}
|
||||
} else if (direction < 0 && to < 0) {
|
||||
if (!content->hasSelection()) {
|
||||
content->selectLast();
|
||||
} else if (from >= 0 || content->hasSelection()) {
|
||||
content->selectSkip(-1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
.content = std::move(result),
|
||||
.overrideKey = overrideKey,
|
||||
.activate = activate,
|
||||
};
|
||||
}
|
||||
|
||||
Controller::Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Fn<void(not_null<PeerData*>)> choose)
|
||||
: ContactsBoxController(&window->session())
|
||||
, _gift(std::move(gift))
|
||||
, _choose(std::move(choose)) {
|
||||
if (const auto when = _gift->exportAt) {
|
||||
_exportOption = MakeExportOption(window, when);
|
||||
}
|
||||
if (_exportOption.content) {
|
||||
setStyleOverrides(&st::peerListSmallSkips);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::noSearchSubmit() {
|
||||
if (const auto onstack = _exportOption.activate) {
|
||||
onstack();
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::overrideKeyboardNavigation(
|
||||
int direction,
|
||||
int fromIndex,
|
||||
int toIndex) {
|
||||
return _exportOption.overrideKey
|
||||
&& _exportOption.overrideKey(direction, fromIndex, toIndex);
|
||||
}
|
||||
|
||||
void Controller::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
|
||||
lt_name,
|
||||
rpl::single(UniqueGiftName(*_gift))));
|
||||
setupExportOption();
|
||||
}
|
||||
|
||||
void Controller::setupExportOption() {
|
||||
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> Controller::createRow(
|
||||
not_null<UserData*> user) {
|
||||
if (user->isSelf()
|
||||
|| user->isBot()
|
||||
|| user->isServiceUser()
|
||||
|| user->isInaccessible()) {
|
||||
return nullptr;
|
||||
}
|
||||
return ContactsBoxController::createRow(user);
|
||||
}
|
||||
|
||||
void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
_choose(row->peer());
|
||||
}
|
||||
|
||||
void TransferGift(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> to,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId messageId,
|
||||
Fn<void(Payments::CheckoutResult)> done) {
|
||||
Expects(to->isUser());
|
||||
|
||||
const auto session = &window->session();
|
||||
const auto weak = base::make_weak(window);
|
||||
auto formDone = [=](
|
||||
Payments::CheckoutResult result,
|
||||
const MTPUpdates *updates) {
|
||||
if (result == Payments::CheckoutResult::Paid && updates) {
|
||||
if (const auto strong = weak.get()) {
|
||||
Ui::ShowGiftTransferredToast(strong, to, *updates);
|
||||
}
|
||||
}
|
||||
done(result);
|
||||
};
|
||||
if (gift->starsForTransfer <= 0) {
|
||||
session->api().request(MTPpayments_TransferStarGift(
|
||||
MTP_int(messageId.bare),
|
||||
to->asUser()->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
session->api().applyUpdates(result);
|
||||
formDone(Payments::CheckoutResult::Paid, &result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast(error.type());
|
||||
}
|
||||
formDone(Payments::CheckoutResult::Failed, nullptr);
|
||||
}).send();
|
||||
return;
|
||||
}
|
||||
Ui::RequestStarsFormAndSubmit(
|
||||
window,
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
MTP_int(messageId.bare),
|
||||
to->asUser()->inputUser),
|
||||
std::move(formDone));
|
||||
}
|
||||
|
||||
void ShowTransferToBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId) {
|
||||
const auto stars = gift->starsForTransfer;
|
||||
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_gift_transfer_title(
|
||||
lt_name,
|
||||
rpl::single(UniqueGiftName(*gift))));
|
||||
|
||||
auto transfer = (stars > 0)
|
||||
? tr::lng_gift_transfer_button_for(
|
||||
lt_price,
|
||||
tr::lng_action_gift_for_stars(
|
||||
lt_count,
|
||||
rpl::single(stars * 1.)))
|
||||
: tr::lng_gift_transfer_button();
|
||||
|
||||
struct State {
|
||||
bool sent = false;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
auto callback = [=] {
|
||||
if (state->sent) {
|
||||
return;
|
||||
}
|
||||
state->sent = true;
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
const auto done = [=](Payments::CheckoutResult result) {
|
||||
if (result != Payments::CheckoutResult::Paid) {
|
||||
state->sent = false;
|
||||
} else {
|
||||
controller->showPeerHistory(peer);
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}
|
||||
};
|
||||
TransferGift(controller, peer, gift, msgId, done);
|
||||
};
|
||||
|
||||
Ui::ConfirmBox(box, {
|
||||
.text = (stars > 0)
|
||||
? tr::lng_gift_transfer_sure_for(
|
||||
lt_name,
|
||||
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
|
||||
lt_recipient,
|
||||
rpl::single(Ui::Text::Bold(peer->shortName())),
|
||||
lt_price,
|
||||
tr::lng_action_gift_for_stars(
|
||||
lt_count,
|
||||
rpl::single(stars * 1.),
|
||||
Ui::Text::Bold),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_gift_transfer_sure(
|
||||
lt_name,
|
||||
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
|
||||
lt_recipient,
|
||||
rpl::single(Ui::Text::Bold(peer->shortName())),
|
||||
Ui::Text::WithEntities),
|
||||
.confirmed = std::move(callback),
|
||||
.confirmText = std::move(transfer),
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowTransferGiftBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId) {
|
||||
auto controller = std::make_unique<Controller>(
|
||||
window,
|
||||
gift,
|
||||
[=](not_null<PeerData*> peer) {
|
||||
ShowTransferToBox(window, peer, gift, msgId);
|
||||
});
|
||||
const auto controllerRaw = controller.get();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
|
||||
box->noSearchSubmits() | rpl::start_with_next([=] {
|
||||
controllerRaw->noSearchSubmit();
|
||||
}, box->lifetime());
|
||||
};
|
||||
window->show(
|
||||
Box<PeerListBox>(std::move(controller), std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
21
Telegram/SourceFiles/boxes/transfer_gift_box.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
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 Data {
|
||||
struct UniqueGift;
|
||||
} // namespace Data
|
||||
|
||||
void ShowTransferGiftBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId);
|
||||
@@ -303,6 +303,7 @@ void Call::startOutgoing() {
|
||||
_api.request(MTPphone_RequestCall(
|
||||
MTP_flags(flags),
|
||||
_user->inputUser,
|
||||
MTPInputGroupCall(),
|
||||
MTP_int(base::RandomValue<int32>()),
|
||||
MTP_bytes(_gaHash),
|
||||
MTP_phoneCallProtocol(
|
||||
|
||||
@@ -1377,6 +1377,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
inputCall(),
|
||||
joinAs()->input,
|
||||
MTP_string(_joinHash),
|
||||
MTPlong(), // key_fingerprint
|
||||
MTP_dataJSON(MTP_bytes(json))
|
||||
)).done([=](
|
||||
const MTPUpdates &updates,
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "history/history_item_helpers.h" // GetErrorTextForSending.
|
||||
#include "history/history_item_helpers.h" // GetErrorForSending.
|
||||
#include "history/history.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -139,30 +139,13 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto error = [&] {
|
||||
for (const auto thread : result) {
|
||||
const auto error = GetErrorTextForSending(
|
||||
thread,
|
||||
{ .text = &comment });
|
||||
if (!error.isEmpty()) {
|
||||
return std::make_pair(error, thread);
|
||||
}
|
||||
}
|
||||
return std::make_pair(QString(), result.front());
|
||||
}();
|
||||
if (!error.first.isEmpty()) {
|
||||
auto text = TextWithEntities();
|
||||
if (result.size() > 1) {
|
||||
text.append(
|
||||
Ui::Text::Bold(error.second->chatListName())
|
||||
).append("\n\n");
|
||||
}
|
||||
text.append(error.first);
|
||||
const auto error = GetErrorForSending(
|
||||
result,
|
||||
{ .text = &comment });
|
||||
if (error.error) {
|
||||
if (const auto weak = *box) {
|
||||
weak->getDelegate()->show(ConfirmBox({
|
||||
.text = text,
|
||||
.inform = true,
|
||||
}));
|
||||
weak->getDelegate()->show(
|
||||
MakeSendErrorBox(error, result.size() > 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ namespace {
|
||||
|
||||
constexpr auto kDontCacheLottieAfterArea = 512 * 512;
|
||||
|
||||
[[nodiscard]] uint64 LocalStickerId(QStringView name) {
|
||||
auto full = u"local_sticker:"_q;
|
||||
full.append(name);
|
||||
return XXH64(full.data(), full.size() * sizeof(QChar), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint8 LottieCacheKeyShift(uint8 replacementsTag, StickerLottieSize sizeTag) {
|
||||
@@ -315,16 +321,9 @@ QSize ComputeStickerSize(not_null<DocumentData*> document, QSize box) {
|
||||
return HistoryView::NonEmptySize(request.size(dimensions, 8) / ratio);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 LocalTgsStickerId(QStringView name) {
|
||||
auto full = u"local_tgs_sticker:"_q;
|
||||
full.append(name);
|
||||
return XXH64(full.data(), full.size() * sizeof(QChar), 0);
|
||||
}
|
||||
|
||||
not_null<DocumentData*> GenerateLocalTgsSticker(
|
||||
not_null<DocumentData*> GenerateLocalSticker(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &name) {
|
||||
const auto path = u":/animations/"_q + name + u".tgs"_q;
|
||||
const QString &path) {
|
||||
auto task = FileLoadTask(
|
||||
session,
|
||||
path,
|
||||
@@ -335,7 +334,7 @@ not_null<DocumentData*> GenerateLocalTgsSticker(
|
||||
{},
|
||||
false,
|
||||
nullptr,
|
||||
LocalTgsStickerId(name));
|
||||
LocalStickerId(path));
|
||||
task.process({ .generateGoodThumbnail = false });
|
||||
const auto result = task.peekResult();
|
||||
Assert(result != nullptr);
|
||||
@@ -348,8 +347,18 @@ not_null<DocumentData*> GenerateLocalTgsSticker(
|
||||
document->setLocation(Core::FileLocation(path));
|
||||
|
||||
Ensures(document->sticker());
|
||||
Ensures(document->sticker()->isLottie());
|
||||
return document;
|
||||
}
|
||||
|
||||
not_null<DocumentData*> GenerateLocalTgsSticker(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &name) {
|
||||
const auto result = GenerateLocalSticker(
|
||||
session,
|
||||
u":/animations/"_q + name + u".tgs"_q);
|
||||
|
||||
Ensures(result->sticker()->isLottie());
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -130,6 +130,10 @@ bool PaintStickerThumbnailPath(
|
||||
not_null<DocumentData*> document,
|
||||
QSize box);
|
||||
|
||||
[[nodiscard]] not_null<DocumentData*> GenerateLocalSticker(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &path);
|
||||
|
||||
[[nodiscard]] not_null<DocumentData*> GenerateLocalTgsSticker(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &name);
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/gifs_list_widget.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "ui/controls/tabbed_search.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
@@ -1042,23 +1043,39 @@ void TabbedSelector::checkRestrictedPeer() {
|
||||
? Data::RestrictionError(
|
||||
_currentPeer,
|
||||
ChatRestriction::SendOther)
|
||||
: std::nullopt)
|
||||
: std::nullopt;
|
||||
if (error) {
|
||||
if (!_restrictedLabel) {
|
||||
_restrictedLabel.create(
|
||||
this,
|
||||
*error,
|
||||
st::stickersRestrictedLabel);
|
||||
_restrictedLabel->show();
|
||||
updateRestrictedLabelGeometry();
|
||||
currentTab()->footer()->hide();
|
||||
_scroll->hide();
|
||||
_bottomShadow->hide();
|
||||
update();
|
||||
}
|
||||
: Data::SendError())
|
||||
: Data::SendError();
|
||||
const auto changed = (_restrictedLabelKey != error.text);
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
_restrictedLabelKey = error.text;
|
||||
if (error) {
|
||||
const auto show = _show;
|
||||
const auto peer = _currentPeer;
|
||||
_restrictedLabel.create(
|
||||
this,
|
||||
rpl::single(error.boostsToLift
|
||||
? Ui::Text::Link(error.text)
|
||||
: TextWithEntities{ error.text }),
|
||||
st::stickersRestrictedLabel);
|
||||
const auto lifting = error.boostsToLift;
|
||||
_restrictedLabel->setClickHandlerFilter([=](auto...) {
|
||||
const auto window = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
window->resolveBoostState(peer->asChannel(), lifting);
|
||||
return false;
|
||||
});
|
||||
_restrictedLabel->show();
|
||||
updateRestrictedLabelGeometry();
|
||||
currentTab()->footer()->hide();
|
||||
_scroll->hide();
|
||||
_bottomShadow->hide();
|
||||
update();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_restrictedLabelKey = QString();
|
||||
}
|
||||
if (_restrictedLabel) {
|
||||
_restrictedLabel.destroy();
|
||||
|
||||
@@ -309,6 +309,7 @@ private:
|
||||
object_ptr<Ui::PlainShadow> _bottomShadow;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr };
|
||||
QString _restrictedLabelKey;
|
||||
std::vector<Tab> _tabs;
|
||||
SelectorTab _currentTabType = SelectorTab::Emoji;
|
||||
|
||||
|
||||
@@ -286,13 +286,20 @@ std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
|
||||
return std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||
std::move(result),
|
||||
my->customEmojiLoopLimit);
|
||||
} else if (my->customEmojiLoopLimit) {
|
||||
return std::make_unique<Ui::Text::FirstFrameEmoji>(
|
||||
std::move(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Fn<void()> UiIntegration::createSpoilerRepaint(const std::any &context) {
|
||||
const auto my = std::any_cast<MarkedTextContext>(&context);
|
||||
return my ? my->customEmojiRepaint : nullptr;
|
||||
if (my) {
|
||||
return my->customEmojiRepaint;
|
||||
}
|
||||
const auto common = std::any_cast<CommonTextContext>(&context);
|
||||
return common ? common->repaint : nullptr;
|
||||
}
|
||||
|
||||
rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
|
||||
|
||||
@@ -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 = 5009002;
|
||||
constexpr auto AppVersionStr = "5.9.2";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 5010002;
|
||||
constexpr auto AppVersionStr = "5.10.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -57,6 +57,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
|
||||
data.vdate(),
|
||||
data.vaction(),
|
||||
data.vreactions() ? *data.vreactions() : MTPMessageReactions(),
|
||||
MTP_int(data.vttl_period().value_or_empty()));
|
||||
}, [&](const MTPDmessage &data) {
|
||||
return MTP_message(
|
||||
@@ -89,7 +90,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
MTP_int(data.vttl_period().value_or_empty()),
|
||||
MTP_int(shortcutId),
|
||||
MTP_long(data.veffect().value_or_empty()),
|
||||
(data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()));
|
||||
(data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()),
|
||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
|
||||
data.vdate(),
|
||||
data.vaction(),
|
||||
data.vreactions() ? *data.vreactions() : MTPMessageReactions(),
|
||||
MTP_int(data.vttl_period().value_or_empty()));
|
||||
}, [&](const MTPDmessage &data) {
|
||||
return MTP_message(
|
||||
@@ -93,7 +94,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
MTP_int(data.vttl_period().value_or_empty()),
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTP_long(data.veffect().value_or_empty()), // effect
|
||||
data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck());
|
||||
data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck(),
|
||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -266,7 +268,8 @@ void ScheduledMessages::sendNowSimpleMessage(
|
||||
MTP_int(update.vttl_period().value_or_empty()),
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTP_long(local->effectId()), // effect
|
||||
MTPFactCheck()),
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
localFlags,
|
||||
NewMessageType::Unread);
|
||||
|
||||
|
||||
@@ -74,46 +74,47 @@ struct PeerUpdate {
|
||||
Color = (1ULL << 14),
|
||||
BackgroundEmoji = (1ULL << 15),
|
||||
StoriesState = (1ULL << 16),
|
||||
VerifyInfo = (1ULL << 17),
|
||||
|
||||
// For users
|
||||
CanShareContact = (1ULL << 17),
|
||||
IsContact = (1ULL << 18),
|
||||
PhoneNumber = (1ULL << 19),
|
||||
OnlineStatus = (1ULL << 20),
|
||||
BotCommands = (1ULL << 21),
|
||||
BotCanBeInvited = (1ULL << 22),
|
||||
BotStartToken = (1ULL << 23),
|
||||
CommonChats = (1ULL << 24),
|
||||
PeerGifts = (1ULL << 25),
|
||||
HasCalls = (1ULL << 26),
|
||||
SupportInfo = (1ULL << 27),
|
||||
IsBot = (1ULL << 28),
|
||||
EmojiStatus = (1ULL << 29),
|
||||
BusinessDetails = (1ULL << 30),
|
||||
Birthday = (1ULL << 31),
|
||||
PersonalChannel = (1ULL << 32),
|
||||
StarRefProgram = (1ULL << 33),
|
||||
CanShareContact = (1ULL << 18),
|
||||
IsContact = (1ULL << 19),
|
||||
PhoneNumber = (1ULL << 20),
|
||||
OnlineStatus = (1ULL << 21),
|
||||
BotCommands = (1ULL << 22),
|
||||
BotCanBeInvited = (1ULL << 23),
|
||||
BotStartToken = (1ULL << 24),
|
||||
CommonChats = (1ULL << 25),
|
||||
PeerGifts = (1ULL << 26),
|
||||
HasCalls = (1ULL << 27),
|
||||
SupportInfo = (1ULL << 28),
|
||||
IsBot = (1ULL << 29),
|
||||
EmojiStatus = (1ULL << 30),
|
||||
BusinessDetails = (1ULL << 31),
|
||||
Birthday = (1ULL << 32),
|
||||
PersonalChannel = (1ULL << 33),
|
||||
StarRefProgram = (1ULL << 34),
|
||||
|
||||
// For chats and channels
|
||||
InviteLinks = (1ULL << 34),
|
||||
Members = (1ULL << 35),
|
||||
Admins = (1ULL << 36),
|
||||
BannedUsers = (1ULL << 37),
|
||||
Rights = (1ULL << 38),
|
||||
PendingRequests = (1ULL << 39),
|
||||
Reactions = (1ULL << 40),
|
||||
InviteLinks = (1ULL << 35),
|
||||
Members = (1ULL << 36),
|
||||
Admins = (1ULL << 37),
|
||||
BannedUsers = (1ULL << 38),
|
||||
Rights = (1ULL << 39),
|
||||
PendingRequests = (1ULL << 40),
|
||||
Reactions = (1ULL << 41),
|
||||
|
||||
// For channels
|
||||
ChannelAmIn = (1ULL << 41),
|
||||
StickersSet = (1ULL << 42),
|
||||
EmojiSet = (1ULL << 43),
|
||||
ChannelLinkedChat = (1ULL << 44),
|
||||
ChannelLocation = (1ULL << 45),
|
||||
Slowmode = (1ULL << 46),
|
||||
GroupCall = (1ULL << 47),
|
||||
ChannelAmIn = (1ULL << 42),
|
||||
StickersSet = (1ULL << 43),
|
||||
EmojiSet = (1ULL << 44),
|
||||
ChannelLinkedChat = (1ULL << 45),
|
||||
ChannelLocation = (1ULL << 46),
|
||||
Slowmode = (1ULL << 47),
|
||||
GroupCall = (1ULL << 48),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 47),
|
||||
LastUsedBit = (1ULL << 48),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_chat_invite.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "window/notifications_manager.h"
|
||||
|
||||
namespace {
|
||||
@@ -713,6 +714,33 @@ bool ChannelData::canRestrictParticipant(
|
||||
return adminRights() & AdminRight::BanUsers;
|
||||
}
|
||||
|
||||
void ChannelData::setBotVerifyDetails(Ui::BotVerifyDetails details) {
|
||||
if (!details) {
|
||||
if (_botVerifyDetails) {
|
||||
_botVerifyDetails = nullptr;
|
||||
session().changes().peerUpdated(this, UpdateFlag::VerifyInfo);
|
||||
}
|
||||
} else if (!_botVerifyDetails) {
|
||||
_botVerifyDetails = std::make_unique<Ui::BotVerifyDetails>(details);
|
||||
session().changes().peerUpdated(this, UpdateFlag::VerifyInfo);
|
||||
} else if (*_botVerifyDetails != details) {
|
||||
*_botVerifyDetails = details;
|
||||
session().changes().peerUpdated(this, UpdateFlag::VerifyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelData::setBotVerifyDetailsIcon(DocumentId iconId) {
|
||||
if (!iconId) {
|
||||
setBotVerifyDetails({});
|
||||
} else {
|
||||
auto info = _botVerifyDetails
|
||||
? *_botVerifyDetails
|
||||
: Ui::BotVerifyDetails();
|
||||
info.iconId = iconId;
|
||||
setBotVerifyDetails(info);
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelData::setAdminRights(ChatAdminRights rights) {
|
||||
if (rights == adminRights()) {
|
||||
return;
|
||||
@@ -1251,6 +1279,8 @@ void ApplyChannelUpdate(
|
||||
.paidEnabled = update.is_paid_reactions_available(),
|
||||
});
|
||||
}
|
||||
channel->setBotVerifyDetails(
|
||||
ParseBotVerifyDetails(update.vbot_verification()));
|
||||
channel->owner().stories().apply(channel, update.vstories());
|
||||
channel->fullUpdated();
|
||||
channel->setPendingRequestsCount(
|
||||
|
||||
@@ -376,6 +376,12 @@ public:
|
||||
[[nodiscard]] bool canRestrictParticipant(
|
||||
not_null<PeerData*> participant) const;
|
||||
|
||||
void setBotVerifyDetails(Ui::BotVerifyDetails details);
|
||||
void setBotVerifyDetailsIcon(DocumentId iconId);
|
||||
[[nodiscard]] Ui::BotVerifyDetails *botVerifyDetails() const {
|
||||
return _botVerifyDetails.get();
|
||||
}
|
||||
|
||||
void setInviteLink(const QString &newInviteLink);
|
||||
[[nodiscard]] QString inviteLink() const {
|
||||
return _inviteLink;
|
||||
@@ -546,6 +552,8 @@ private:
|
||||
std::unique_ptr<Data::GroupCall> _call;
|
||||
PeerId _callDefaultJoinAs = 0;
|
||||
|
||||
std::unique_ptr<Ui::BotVerifyDetails> _botVerifyDetails;
|
||||
|
||||
};
|
||||
|
||||
namespace Data {
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_chat_filters.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "history/history.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -39,23 +40,38 @@ constexpr auto kLoadExceptionsPerRequest = 100;
|
||||
|
||||
} // namespace
|
||||
|
||||
TextWithEntities ForceCustomEmojiStatic(TextWithEntities text) {
|
||||
for (auto &entity : text.entities) {
|
||||
if (entity.type() == EntityType::CustomEmoji) {
|
||||
entity = EntityInText(
|
||||
EntityType::CustomEmoji,
|
||||
entity.offset(),
|
||||
entity.length(),
|
||||
u"force-static:"_q + entity.data());
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
ChatFilter::ChatFilter(
|
||||
FilterId id,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
std::optional<uint8> colorIndex,
|
||||
Flags flags,
|
||||
base::flat_set<not_null<History*>> always,
|
||||
std::vector<not_null<History*>> pinned,
|
||||
base::flat_set<not_null<History*>> never)
|
||||
: _id(id)
|
||||
, _title(title)
|
||||
, _iconEmoji(iconEmoji)
|
||||
, _title(std::move(title.text))
|
||||
, _iconEmoji(std::move(iconEmoji))
|
||||
, _colorIndex(colorIndex)
|
||||
, _always(std::move(always))
|
||||
, _pinned(std::move(pinned))
|
||||
, _never(std::move(never))
|
||||
, _flags(flags) {
|
||||
, _flags(title.isStatic
|
||||
? (flags | Flag::StaticTitle)
|
||||
: (flags & ~Flag::StaticTitle)) {
|
||||
}
|
||||
|
||||
ChatFilter ChatFilter::FromTL(
|
||||
@@ -69,7 +85,8 @@ ChatFilter ChatFilter::FromTL(
|
||||
| (data.is_bots() ? Flag::Bots : Flag(0))
|
||||
| (data.is_exclude_muted() ? Flag::NoMuted : Flag(0))
|
||||
| (data.is_exclude_read() ? Flag::NoRead : Flag(0))
|
||||
| (data.is_exclude_archived() ? Flag::NoArchived : Flag(0));
|
||||
| (data.is_exclude_archived() ? Flag::NoArchived : Flag(0))
|
||||
| (data.is_title_noanimate() ? Flag::StaticTitle : Flag(0));
|
||||
auto &&to_histories = ranges::views::transform([&](
|
||||
const MTPInputPeer &input) {
|
||||
const auto peer = Data::PeerFromInputMTP(owner, input);
|
||||
@@ -95,7 +112,10 @@ ChatFilter ChatFilter::FromTL(
|
||||
};
|
||||
return ChatFilter(
|
||||
data.vid().v,
|
||||
qs(data.vtitle()),
|
||||
{
|
||||
Api::ParseTextWithEntities(&owner->session(), data.vtitle()),
|
||||
data.is_title_noanimate(),
|
||||
},
|
||||
qs(data.vemoticon().value_or_empty()),
|
||||
data.vcolor()
|
||||
? std::make_optional(data.vcolor()->v)
|
||||
@@ -104,7 +124,7 @@ ChatFilter ChatFilter::FromTL(
|
||||
std::move(list),
|
||||
std::move(pinned),
|
||||
{ never.begin(), never.end() });
|
||||
}, [](const MTPDdialogFilterDefault &d) {
|
||||
}, [](const MTPDdialogFilterDefault &) {
|
||||
return ChatFilter();
|
||||
}, [&](const MTPDdialogFilterChatlist &data) {
|
||||
auto &&to_histories = ranges::views::transform([&](
|
||||
@@ -143,13 +163,17 @@ ChatFilter ChatFilter::FromTL(
|
||||
};
|
||||
return ChatFilter(
|
||||
data.vid().v,
|
||||
qs(data.vtitle()),
|
||||
{
|
||||
Api::ParseTextWithEntities(&owner->session(), data.vtitle()),
|
||||
data.is_title_noanimate(),
|
||||
},
|
||||
qs(data.vemoticon().value_or_empty()),
|
||||
data.vcolor()
|
||||
? std::make_optional(data.vcolor()->v)
|
||||
: std::nullopt,
|
||||
(Flag::Chatlist
|
||||
| (data.is_has_my_invites() ? Flag::HasMyLinks : Flag())),
|
||||
| (data.is_has_my_invites() ? Flag::HasMyLinks : Flag())
|
||||
| (data.is_title_noanimate() ? Flag::StaticTitle : Flag(0))),
|
||||
std::move(list),
|
||||
std::move(pinned),
|
||||
{});
|
||||
@@ -162,9 +186,14 @@ ChatFilter ChatFilter::withId(FilterId id) const {
|
||||
return result;
|
||||
}
|
||||
|
||||
ChatFilter ChatFilter::withTitle(const QString &title) const {
|
||||
ChatFilter ChatFilter::withTitle(ChatFilterTitle title) const {
|
||||
auto result = *this;
|
||||
result._title = title;
|
||||
result._title = std::move(title.text);
|
||||
if (title.isStatic) {
|
||||
result._flags |= Flag::StaticTitle;
|
||||
} else {
|
||||
result._flags &= ~Flag::StaticTitle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -209,14 +238,21 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
|
||||
for (const auto &history : always) {
|
||||
include.push_back(history->peer->input);
|
||||
}
|
||||
auto title = MTP_textWithEntities(
|
||||
MTP_string(_title.text),
|
||||
Api::EntitiesToMTP(
|
||||
nullptr,
|
||||
_title.entities,
|
||||
Api::ConvertOption::SkipLocal));
|
||||
if (_flags & Flag::Chatlist) {
|
||||
using TLFlag = MTPDdialogFilterChatlist::Flag;
|
||||
const auto flags = TLFlag::f_emoticon
|
||||
| (_colorIndex ? TLFlag::f_color : TLFlag(0));
|
||||
| (_colorIndex ? TLFlag::f_color : TLFlag(0))
|
||||
| (staticTitle() ? TLFlag::f_title_noanimate : TLFlag(0));
|
||||
return MTP_dialogFilterChatlist(
|
||||
MTP_flags(flags),
|
||||
MTP_int(replaceId ? replaceId : _id),
|
||||
MTP_string(_title),
|
||||
std::move(title),
|
||||
MTP_string(_iconEmoji),
|
||||
MTP_int(_colorIndex.value_or(0)),
|
||||
MTP_vector<MTPInputPeer>(pinned),
|
||||
@@ -225,6 +261,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
|
||||
using TLFlag = MTPDdialogFilter::Flag;
|
||||
const auto flags = TLFlag::f_emoticon
|
||||
| (_colorIndex ? TLFlag::f_color : TLFlag(0))
|
||||
| (staticTitle() ? TLFlag::f_title_noanimate : TLFlag(0))
|
||||
| ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0))
|
||||
| ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0))
|
||||
| ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0))
|
||||
@@ -243,7 +280,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
|
||||
return MTP_dialogFilter(
|
||||
MTP_flags(flags),
|
||||
MTP_int(replaceId ? replaceId : _id),
|
||||
MTP_string(_title),
|
||||
std::move(title),
|
||||
MTP_string(_iconEmoji),
|
||||
MTP_int(_colorIndex.value_or(0)),
|
||||
MTP_vector<MTPInputPeer>(pinned),
|
||||
@@ -255,10 +292,14 @@ FilterId ChatFilter::id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
QString ChatFilter::title() const {
|
||||
const TextWithEntities &ChatFilter::titleText() const {
|
||||
return _title;
|
||||
}
|
||||
|
||||
ChatFilterTitle ChatFilter::title() const {
|
||||
return { _title, !!(_flags & Flag::StaticTitle) };
|
||||
}
|
||||
|
||||
QString ChatFilter::iconEmoji() const {
|
||||
return _iconEmoji;
|
||||
}
|
||||
@@ -271,6 +312,10 @@ ChatFilter::Flags ChatFilter::flags() const {
|
||||
return _flags;
|
||||
}
|
||||
|
||||
bool ChatFilter::staticTitle() const {
|
||||
return _flags & Flag::StaticTitle;
|
||||
}
|
||||
|
||||
bool ChatFilter::chatlist() const {
|
||||
return _flags & Flag::Chatlist;
|
||||
}
|
||||
@@ -662,7 +707,8 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
|
||||
|| (filter.hasMyLinks() != updated.hasMyLinks());
|
||||
const auto listUpdated = rulesChanged
|
||||
|| pinnedChanged
|
||||
|| (filter.title() != updated.title())
|
||||
|| (filter.titleText() != updated.titleText())
|
||||
|| (filter.staticTitle() != updated.staticTitle())
|
||||
|| (filter.iconEmoji() != updated.iconEmoji());
|
||||
const auto colorChanged = filter.colorIndex() != updated.colorIndex();
|
||||
const auto colorExistenceChanged = (!filter.colorIndex())
|
||||
|
||||
@@ -25,6 +25,17 @@ namespace Data {
|
||||
|
||||
class Session;
|
||||
|
||||
struct ChatFilterTitle {
|
||||
TextWithEntities text;
|
||||
bool isStatic = false;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return text.empty();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] TextWithEntities ForceCustomEmojiStatic(TextWithEntities text);
|
||||
|
||||
class ChatFilter final {
|
||||
public:
|
||||
enum class Flag : ushort {
|
||||
@@ -40,9 +51,10 @@ public:
|
||||
|
||||
Chatlist = (1 << 8),
|
||||
HasMyLinks = (1 << 9),
|
||||
StaticTitle = (1 << 10),
|
||||
|
||||
NewChats = (1 << 10), // Telegram Business exceptions.
|
||||
ExistingChats = (1 << 11),
|
||||
NewChats = (1 << 11), // Telegram Business exceptions.
|
||||
ExistingChats = (1 << 12),
|
||||
};
|
||||
friend constexpr inline bool is_flag_type(Flag) { return true; };
|
||||
using Flags = base::flags<Flag>;
|
||||
@@ -50,8 +62,8 @@ public:
|
||||
ChatFilter() = default;
|
||||
ChatFilter(
|
||||
FilterId id,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
std::optional<uint8> colorIndex,
|
||||
Flags flags,
|
||||
base::flat_set<not_null<History*>> always,
|
||||
@@ -59,7 +71,7 @@ public:
|
||||
base::flat_set<not_null<History*>> never);
|
||||
|
||||
[[nodiscard]] ChatFilter withId(FilterId id) const;
|
||||
[[nodiscard]] ChatFilter withTitle(const QString &title) const;
|
||||
[[nodiscard]] ChatFilter withTitle(ChatFilterTitle title) const;
|
||||
[[nodiscard]] ChatFilter withColorIndex(std::optional<uint8>) const;
|
||||
[[nodiscard]] ChatFilter withChatlist(
|
||||
bool chatlist,
|
||||
@@ -72,10 +84,12 @@ public:
|
||||
[[nodiscard]] MTPDialogFilter tl(FilterId replaceId = 0) const;
|
||||
|
||||
[[nodiscard]] FilterId id() const;
|
||||
[[nodiscard]] QString title() const;
|
||||
[[nodiscard]] ChatFilterTitle title() const;
|
||||
[[nodiscard]] const TextWithEntities &titleText() const;
|
||||
[[nodiscard]] QString iconEmoji() const;
|
||||
[[nodiscard]] std::optional<uint8> colorIndex() const;
|
||||
[[nodiscard]] Flags flags() const;
|
||||
[[nodiscard]] bool staticTitle() const;
|
||||
[[nodiscard]] bool chatlist() const;
|
||||
[[nodiscard]] bool hasMyLinks() const;
|
||||
[[nodiscard]] const base::flat_set<not_null<History*>> &always() const;
|
||||
@@ -88,7 +102,7 @@ public:
|
||||
|
||||
private:
|
||||
FilterId _id = 0;
|
||||
QString _title;
|
||||
TextWithEntities _title;
|
||||
QString _iconEmoji;
|
||||
std::optional<uint8> _colorIndex;
|
||||
base::flat_set<not_null<History*>> _always;
|
||||
@@ -99,7 +113,7 @@ private:
|
||||
};
|
||||
|
||||
inline bool operator==(const ChatFilter &a, const ChatFilter &b) {
|
||||
return (a.title() == b.title())
|
||||
return (a.titleText() == b.titleText())
|
||||
&& (a.iconEmoji() == b.iconEmoji())
|
||||
&& (a.colorIndex() == b.colorIndex())
|
||||
&& (a.flags() == b.flags())
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
@@ -17,6 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -167,7 +171,7 @@ bool CanSendAnyOf(
|
||||
Unexpected("Peer type in CanSendAnyOf.");
|
||||
}
|
||||
|
||||
std::optional<QString> RestrictionError(
|
||||
SendError RestrictionError(
|
||||
not_null<PeerData*> peer,
|
||||
ChatRestriction restriction) {
|
||||
using Flag = ChatRestriction;
|
||||
@@ -175,10 +179,13 @@ std::optional<QString> RestrictionError(
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (user->meRequiresPremiumToWrite()
|
||||
&& !user->session().premium()) {
|
||||
return tr::lng_restricted_send_non_premium(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user->shortName());
|
||||
return SendError({
|
||||
.text = tr::lng_restricted_send_non_premium(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user->shortName()),
|
||||
.premiumToLift = true,
|
||||
});
|
||||
}
|
||||
const auto result = (restriction == Flag::SendVoiceMessages)
|
||||
? tr::lng_restricted_send_voice_messages(
|
||||
@@ -194,7 +201,7 @@ std::optional<QString> RestrictionError(
|
||||
? u"can't send polls :("_q
|
||||
: (restriction == Flag::PinMessages)
|
||||
? u"can't pin :("_q
|
||||
: std::optional<QString>();
|
||||
: SendError();
|
||||
|
||||
Ensures(result.has_value());
|
||||
return result;
|
||||
@@ -253,6 +260,15 @@ std::optional<QString> RestrictionError(
|
||||
Unexpected("Restriction in Data::RestrictionErrorKey.");
|
||||
}
|
||||
}
|
||||
if (all
|
||||
&& channel->boostsUnrestrict()
|
||||
&& !channel->unrestrictedByBoosts()) {
|
||||
return SendError({
|
||||
.text = tr::lng_restricted_boost_group(tr::now),
|
||||
.boostsToLift = (channel->boostsUnrestrict()
|
||||
- channel->boostsApplied()),
|
||||
});
|
||||
}
|
||||
switch (restriction) {
|
||||
case Flag::SendPolls:
|
||||
return all
|
||||
@@ -302,10 +318,10 @@ std::optional<QString> RestrictionError(
|
||||
}
|
||||
Unexpected("Restriction in Data::RestrictionErrorKey.");
|
||||
}
|
||||
return std::nullopt;
|
||||
return SendError();
|
||||
}
|
||||
|
||||
std::optional<QString> AnyFileRestrictionError(not_null<PeerData*> peer) {
|
||||
SendError AnyFileRestrictionError(not_null<PeerData*> peer) {
|
||||
using Restriction = ChatRestriction;
|
||||
for (const auto right : FilesSendRestrictionsList()) {
|
||||
if (!RestrictionError(peer, right)) {
|
||||
@@ -315,7 +331,7 @@ std::optional<QString> AnyFileRestrictionError(not_null<PeerData*> peer) {
|
||||
return RestrictionError(peer, Restriction::SendFiles);
|
||||
}
|
||||
|
||||
std::optional<QString> FileRestrictionError(
|
||||
SendError FileRestrictionError(
|
||||
not_null<PeerData*> peer,
|
||||
const Ui::PreparedList &list,
|
||||
std::optional<bool> compress) {
|
||||
@@ -339,7 +355,7 @@ std::optional<QString> FileRestrictionError(
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<QString> FileRestrictionError(
|
||||
SendError FileRestrictionError(
|
||||
not_null<PeerData*> peer,
|
||||
const Ui::PreparedFile &file,
|
||||
std::optional<bool> compress) {
|
||||
@@ -383,4 +399,32 @@ std::optional<QString> FileRestrictionError(
|
||||
return {};
|
||||
}
|
||||
|
||||
void ShowSendErrorToast(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Data::SendError error) {
|
||||
return ShowSendErrorToast(navigation->uiShow(), peer, error);
|
||||
}
|
||||
|
||||
void ShowSendErrorToast(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Data::SendError error) {
|
||||
Expects(peer->isChannel());
|
||||
|
||||
if (!error.boostsToLift) {
|
||||
show->showToast(*error);
|
||||
return;
|
||||
}
|
||||
const auto boost = [=] {
|
||||
const auto window = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
window->resolveBoostState(peer->asChannel(), error.boostsToLift);
|
||||
};
|
||||
show->showToast({
|
||||
.text = Ui::Text::Link(*error),
|
||||
.filter = [=](const auto &...) { boost(); return false; },
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -7,11 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui {
|
||||
struct PreparedList;
|
||||
struct PreparedFile;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
enum class ChatAdminRight {
|
||||
ChangeInfo = (1 << 0),
|
||||
PostMessages = (1 << 1),
|
||||
@@ -175,18 +183,65 @@ struct RestrictionsSetOptions {
|
||||
return CanSendAnyOf(peer, AllSendRestrictions(), forbidInForums);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<QString> RestrictionError(
|
||||
struct SendError {
|
||||
SendError(QString text = QString()) : text(std::move(text)) {
|
||||
}
|
||||
|
||||
struct Args {
|
||||
QString text;
|
||||
int boostsToLift = 0;
|
||||
bool premiumToLift = false;
|
||||
};
|
||||
SendError(Args &&args)
|
||||
: text(std::move(args.text))
|
||||
, boostsToLift(args.boostsToLift)
|
||||
, premiumToLift(args.premiumToLift) {
|
||||
}
|
||||
|
||||
QString text;
|
||||
int boostsToLift = 0;
|
||||
bool premiumToLift = false;
|
||||
|
||||
[[nodiscard]] SendError value_or(SendError other) const {
|
||||
return *this ? *this : other;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return !text.isEmpty();
|
||||
}
|
||||
[[nodiscard]] bool has_value() const {
|
||||
return !text.isEmpty();
|
||||
}
|
||||
[[nodiscard]] const QString &operator*() const {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
struct SendErrorWithThread {
|
||||
SendError error;
|
||||
Thread *thread = nullptr;
|
||||
};
|
||||
|
||||
[[nodiscard]] SendError RestrictionError(
|
||||
not_null<PeerData*> peer,
|
||||
ChatRestriction restriction);
|
||||
[[nodiscard]] std::optional<QString> AnyFileRestrictionError(
|
||||
not_null<PeerData*> peer);
|
||||
[[nodiscard]] std::optional<QString> FileRestrictionError(
|
||||
[[nodiscard]] SendError AnyFileRestrictionError(not_null<PeerData*> peer);
|
||||
[[nodiscard]] SendError FileRestrictionError(
|
||||
not_null<PeerData*> peer,
|
||||
const Ui::PreparedList &list,
|
||||
std::optional<bool> compress);
|
||||
[[nodiscard]] std::optional<QString> FileRestrictionError(
|
||||
[[nodiscard]] SendError FileRestrictionError(
|
||||
not_null<PeerData*> peer,
|
||||
const Ui::PreparedFile &file,
|
||||
std::optional<bool> compress);
|
||||
|
||||
void ShowSendErrorToast(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
SendError error);
|
||||
void ShowSendErrorToast(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
SendError error);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct UniqueGift;
|
||||
|
||||
struct CreditTopupOption final {
|
||||
uint64 credits = 0;
|
||||
QString product;
|
||||
@@ -62,7 +64,10 @@ struct CreditsHistoryEntry final {
|
||||
uint64 barePeerId = 0;
|
||||
uint64 bareGiveawayMsgId = 0;
|
||||
uint64 bareGiftStickerId = 0;
|
||||
uint64 bareGiftOwnerId = 0;
|
||||
uint64 bareActorId = 0;
|
||||
uint64 stargiftId = 0;
|
||||
std::shared_ptr<UniqueGift> uniqueGift;
|
||||
StarsAmount starrefAmount;
|
||||
int starrefCommission = 0;
|
||||
uint64 starrefRecipientId = 0;
|
||||
@@ -73,13 +78,20 @@ struct CreditsHistoryEntry final {
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int starsConverted = 0;
|
||||
int starsToUpgrade = 0;
|
||||
int starsUpgradedBySender = 0;
|
||||
int floodSkip = 0;
|
||||
bool converted : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
bool stargift : 1 = false;
|
||||
bool giftTransferred : 1 = false;
|
||||
bool giftRefunded : 1 = false;
|
||||
bool giftUpgraded : 1 = false;
|
||||
bool savedToProfile : 1 = false;
|
||||
bool fromGiftsList : 1 = false;
|
||||
bool soldOutInfo : 1 = false;
|
||||
bool canUpgradeGift : 1 = false;
|
||||
bool hasGiftComment : 1 = false;
|
||||
bool reaction : 1 = false;
|
||||
bool refunded : 1 = false;
|
||||
bool pending : 1 = false;
|
||||
|
||||
@@ -124,6 +124,15 @@ DownloadManager::DownloadManager()
|
||||
|
||||
DownloadManager::~DownloadManager() = default;
|
||||
|
||||
bool DownloadManager::empty() const {
|
||||
for (const auto &[session, data] : _sessions) {
|
||||
if (!data.downloading.empty() || !data.downloaded.empty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DownloadManager::trackSession(not_null<Main::Session*> session) {
|
||||
auto &data = _sessions.emplace(session, SessionData()).first->second;
|
||||
data.downloaded = deserialize(session);
|
||||
|
||||
@@ -80,6 +80,8 @@ public:
|
||||
DownloadManager();
|
||||
~DownloadManager();
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
|
||||
void trackSession(not_null<Main::Session*> session);
|
||||
void itemVisibilitiesUpdated(not_null<Main::Session*> session);
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ void GroupCall::discard(const MTPDgroupCallDiscarded &data) {
|
||||
Core::App().calls().applyGroupCallUpdateChecked(
|
||||
&peer->session(),
|
||||
MTP_updateGroupCall(
|
||||
MTP_flags(MTPDupdateGroupCall::Flag::f_chat_id),
|
||||
MTP_long(peer->isChat()
|
||||
? peerToChat(peer->id).bare
|
||||
: peerToChannel(peer->id).bare),
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
|
||||
constexpr auto kReportDeliveriesPerRequest = 50;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -566,6 +567,58 @@ void Histories::sendPendingReadInbox(not_null<History*> history) {
|
||||
}
|
||||
}
|
||||
|
||||
void Histories::reportDelivery(not_null<HistoryItem*> item) {
|
||||
auto &set = _pendingDeliveryReport[item->history()->peer];
|
||||
if (!set.emplace(item->id).second) {
|
||||
return;
|
||||
}
|
||||
crl::on_main(&session(), [=] {
|
||||
reportPendingDeliveries();
|
||||
});
|
||||
}
|
||||
|
||||
void Histories::reportPendingDeliveries() {
|
||||
auto &pending = _pendingDeliveryReport;
|
||||
for (auto i = begin(pending); i != end(pending);) {
|
||||
auto &[peer, ids] = *i;
|
||||
auto list = QVector<MTPint>();
|
||||
if (_deliveryReportSent.contains(peer)) {
|
||||
++i;
|
||||
continue;
|
||||
} else if (ids.size() > kReportDeliveriesPerRequest) {
|
||||
const auto count = kReportDeliveriesPerRequest;
|
||||
list.reserve(count);
|
||||
for (auto j = begin(ids), till = j + count; j != till; ++j) {
|
||||
list.push_back(MTP_int(*j));
|
||||
}
|
||||
ids.erase(begin(ids), begin(ids) + count);
|
||||
} else if (!ids.empty()) {
|
||||
list.reserve(ids.size());
|
||||
for (const auto &id : ids) {
|
||||
list.push_back(MTP_int(id));
|
||||
}
|
||||
ids.clear();
|
||||
}
|
||||
if (ids.empty()) {
|
||||
i = pending.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
_deliveryReportSent.emplace(peer);
|
||||
const auto finish = [=] {
|
||||
_deliveryReportSent.remove(peer);
|
||||
if (_pendingDeliveryReport.contains(peer)) {
|
||||
reportPendingDeliveries();
|
||||
}
|
||||
};
|
||||
session().api().request(MTPmessages_ReportMessagesDelivery(
|
||||
MTP_flags(0),
|
||||
peer->input,
|
||||
MTP_vector(std::move(list))
|
||||
)).done(finish).fail(finish).send();
|
||||
}
|
||||
}
|
||||
|
||||
void Histories::sendReadRequests() {
|
||||
DEBUG_LOG(("Reading: send requests with count %1.").arg(_states.size()));
|
||||
if (_states.empty()) {
|
||||
|
||||
@@ -63,6 +63,7 @@ public:
|
||||
void readInboxOnNewMessage(not_null<HistoryItem*> item);
|
||||
void readClientSideMessage(not_null<HistoryItem*> item);
|
||||
void sendPendingReadInbox(not_null<History*> history);
|
||||
void reportDelivery(not_null<HistoryItem*> item);
|
||||
|
||||
void requestDialogEntry(not_null<Data::Folder*> folder);
|
||||
void requestDialogEntry(
|
||||
@@ -201,6 +202,7 @@ private:
|
||||
void postponeRequestDialogEntries();
|
||||
|
||||
void sendDialogRequests();
|
||||
void reportPendingDeliveries();
|
||||
|
||||
[[nodiscard]] bool isCreatingTopic(
|
||||
not_null<History*> history,
|
||||
@@ -236,6 +238,11 @@ private:
|
||||
base::flat_map<FullMsgId, MsgId> _createdTopicIds;
|
||||
base::flat_set<mtpRequestId> _creatingTopicRequests;
|
||||
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
base::flat_set<MsgId>> _pendingDeliveryReport;
|
||||
base::flat_set<not_null<PeerData*>> _deliveryReportSent;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_service_box.h"
|
||||
#include "history/view/media/history_view_story_mention.h"
|
||||
#include "history/view/media/history_view_premium_gift.h"
|
||||
#include "history/view/media/history_view_unique_gift.h"
|
||||
#include "history/view/media/history_view_userpic_suggestion.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "ui/image/image.h"
|
||||
@@ -2374,6 +2375,16 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
|
||||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing) {
|
||||
if (const auto raw = _data.unique.get()) {
|
||||
return std::make_unique<HistoryView::MediaGeneric>(
|
||||
message,
|
||||
HistoryView::GenerateUniqueGiftMedia(message, replacing, raw),
|
||||
HistoryView::MediaGenericDescriptor{
|
||||
.maxWidth = st::msgServiceGiftBoxSize.width(),
|
||||
.paintBg = HistoryView::UniqueGiftBg(message, raw),
|
||||
.service = true,
|
||||
});
|
||||
}
|
||||
return std::make_unique<HistoryView::ServiceBox>(
|
||||
message,
|
||||
std::make_unique<HistoryView::PremiumGift>(message, this));
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Data {
|
||||
class CloudImage;
|
||||
class WallPaper;
|
||||
class Session;
|
||||
struct UniqueGift;
|
||||
|
||||
enum class CallFinishReason : char {
|
||||
Missed,
|
||||
@@ -135,19 +136,29 @@ enum class GiftType : uchar {
|
||||
|
||||
struct GiftCode {
|
||||
QString slug;
|
||||
uint64 stargiftId = 0;
|
||||
DocumentData *document = nullptr;
|
||||
std::shared_ptr<UniqueGift> unique;
|
||||
TextWithEntities message;
|
||||
ChannelData *channel = nullptr;
|
||||
MsgId giveawayMsgId = 0;
|
||||
MsgId upgradeMsgId = 0;
|
||||
int starsConverted = 0;
|
||||
int starsToUpgrade = 0;
|
||||
int starsUpgradedBySender = 0;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int count = 0;
|
||||
GiftType type = GiftType::Premium;
|
||||
bool viaGiveaway : 1 = false;
|
||||
bool transferred : 1 = false;
|
||||
bool upgradable : 1 = false;
|
||||
bool unclaimed : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
bool converted : 1 = false;
|
||||
bool upgraded : 1 = false;
|
||||
bool refunded : 1 = false;
|
||||
bool upgrade : 1 = false;
|
||||
bool saved : 1 = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
@@ -1246,6 +1247,15 @@ void PeerData::setStoriesHidden(bool hidden) {
|
||||
}
|
||||
}
|
||||
|
||||
Ui::BotVerifyDetails *PeerData::botVerifyDetails() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->botVerifyDetails();
|
||||
} else if (const auto channel = asChannel()) {
|
||||
return channel->botVerifyDetails();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Data::Forum *PeerData::forum() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->forum();
|
||||
|
||||
@@ -23,6 +23,7 @@ enum class ChatRestriction;
|
||||
|
||||
namespace Ui {
|
||||
class EmptyUserpic;
|
||||
struct BotVerifyDetails;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Main {
|
||||
@@ -233,6 +234,8 @@ public:
|
||||
[[nodiscard]] bool hasStoriesHidden() const;
|
||||
void setStoriesHidden(bool hidden);
|
||||
|
||||
[[nodiscard]] Ui::BotVerifyDetails *botVerifyDetails() const;
|
||||
|
||||
[[nodiscard]] bool isNotificationsUser() const {
|
||||
return (id == peerFromUser(333000))
|
||||
|| (id == kServiceNotificationsId);
|
||||
@@ -521,7 +524,7 @@ private:
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
|
||||
DocumentId _emojiStatusId = 0;
|
||||
uint64 _backgroundEmojiId = 0;
|
||||
DocumentId _backgroundEmojiId = 0;
|
||||
crl::time _lastFullUpdate = 0;
|
||||
|
||||
QString _name;
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_bot.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/mime_type.h" // Core::IsMimeSticker
|
||||
@@ -569,6 +570,8 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
||||
| (data.is_stories_hidden() ? Flag::StoriesHidden : Flag())
|
||||
: Flag());
|
||||
result->setFlags((result->flags() & ~flagsMask) | flagsSet);
|
||||
result->setBotVerifyDetailsIcon(
|
||||
data.vbot_verification_icon().value_or_empty());
|
||||
if (minimal) {
|
||||
if (result->input.type() == mtpc_inputPeerEmpty) {
|
||||
result->input = MTP_inputPeerUser(
|
||||
@@ -984,6 +987,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
? Flag::StoriesHidden
|
||||
: Flag());
|
||||
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
|
||||
channel->setBotVerifyDetailsIcon(
|
||||
data.vbot_verification_icon().value_or_empty());
|
||||
if (!minimal && storiesState) {
|
||||
result->setStoriesState(!storiesState->maxId
|
||||
? UserData::StoriesState::None
|
||||
@@ -2720,7 +2725,7 @@ HistoryItem *Session::addNewMessage(
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type) {
|
||||
const auto peerId = PeerFromMessage(data);
|
||||
if (!peerId) {
|
||||
if (!peerId || data.type() == mtpc_messageEmpty) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -4591,7 +4596,8 @@ void Session::serviceNotification(
|
||||
MTPint(), // stories_max_id
|
||||
MTPPeerColor(), // color
|
||||
MTPPeerColor(), // profile_color
|
||||
MTPint())); // bot_active_users
|
||||
MTPint(), // bot_active_users
|
||||
MTPlong())); // bot_verification_icon
|
||||
}
|
||||
const auto history = this->history(PeerData::kServiceNotificationsId);
|
||||
const auto insert = [=] {
|
||||
@@ -4649,7 +4655,8 @@ void Session::insertCheckedServiceNotification(
|
||||
MTPint(), // ttl_period
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
localFlags,
|
||||
NewMessageType::Unread);
|
||||
}
|
||||
|
||||
88
Telegram/SourceFiles/data/data_star_gift.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct UniqueGiftAttribute {
|
||||
QString name;
|
||||
int rarityPermille = 0;
|
||||
};
|
||||
|
||||
struct UniqueGiftModel : UniqueGiftAttribute {
|
||||
not_null<DocumentData*> document;
|
||||
};
|
||||
|
||||
struct UniqueGiftPattern : UniqueGiftAttribute {
|
||||
not_null<DocumentData*> document;
|
||||
};
|
||||
|
||||
struct UniqueGiftBackdrop : UniqueGiftAttribute {
|
||||
QColor centerColor;
|
||||
QColor edgeColor;
|
||||
QColor patternColor;
|
||||
QColor textColor;
|
||||
};
|
||||
|
||||
struct UniqueGiftOriginalDetails {
|
||||
PeerId senderId = 0;
|
||||
PeerId recipientId = 0;
|
||||
TimeId date = 0;
|
||||
TextWithEntities message;
|
||||
};
|
||||
|
||||
struct UniqueGift {
|
||||
QString title;
|
||||
PeerId ownerId = 0;
|
||||
int number = 0;
|
||||
int starsForTransfer = -1;
|
||||
TimeId exportAt = 0;
|
||||
UniqueGiftModel model;
|
||||
UniqueGiftPattern pattern;
|
||||
UniqueGiftBackdrop backdrop;
|
||||
UniqueGiftOriginalDetails originalDetails;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline QString UniqueGiftName(const UniqueGift &gift) {
|
||||
return gift.title + u" #"_q + QString::number(gift.number);
|
||||
}
|
||||
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
std::shared_ptr<UniqueGift> unique;
|
||||
int64 stars = 0;
|
||||
int64 starsConverted = 0;
|
||||
int64 starsToUpgrade = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
bool upgradable = false;
|
||||
bool birthday = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarGift &,
|
||||
const StarGift &) = default;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 starsConverted = 0;
|
||||
int64 starsUpgradedBySender = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
bool upgradable = false;
|
||||
bool anonymous = false;
|
||||
bool hidden = false;
|
||||
bool mine = false;
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -68,7 +68,7 @@ struct StatisticalChart {
|
||||
|
||||
float64 oneDayPercentage = 0.;
|
||||
|
||||
float64 timeStep = 0.;
|
||||
float64 timeStep = 1.;
|
||||
|
||||
bool isFooterHidden = false;
|
||||
bool hasPercentages = false;
|
||||
|
||||
@@ -423,7 +423,7 @@ bool Story::hasDirectLink() const {
|
||||
return !_peer->username().isEmpty();
|
||||
}
|
||||
|
||||
std::optional<QString> Story::errorTextForForward(
|
||||
Data::SendError Story::errorTextForForward(
|
||||
not_null<Thread*> to) const {
|
||||
const auto peer = to->peer();
|
||||
const auto holdsPhoto = v::is<not_null<PhotoData*>>(_media.data);
|
||||
@@ -433,10 +433,10 @@ std::optional<QString> Story::errorTextForForward(
|
||||
const auto second = holdsPhoto
|
||||
? ChatRestriction::SendVideos
|
||||
: ChatRestriction::SendPhotos;
|
||||
if (const auto error = Data::RestrictionError(peer, first)) {
|
||||
return *error;
|
||||
} else if (const auto error = Data::RestrictionError(peer, second)) {
|
||||
return *error;
|
||||
if (const auto one = Data::RestrictionError(peer, first)) {
|
||||
return one;
|
||||
} else if (const auto two = Data::RestrictionError(peer, second)) {
|
||||
return two;
|
||||
} else if (!Data::CanSend(to, first, false)
|
||||
|| !Data::CanSend(to, second, false)) {
|
||||
return tr::lng_forward_cant(tr::now);
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Data {
|
||||
class Session;
|
||||
class Thread;
|
||||
class MediaPreload;
|
||||
struct SendError;
|
||||
|
||||
enum class StoryPrivacy : uchar {
|
||||
Public,
|
||||
@@ -191,7 +192,7 @@ public:
|
||||
[[nodiscard]] bool canReport() const;
|
||||
|
||||
[[nodiscard]] bool hasDirectLink() const;
|
||||
[[nodiscard]] std::optional<QString> errorTextForForward(
|
||||
[[nodiscard]] Data::SendError errorTextForForward(
|
||||
not_null<Thread*> to) const;
|
||||
|
||||
void setCaption(TextWithEntities &&caption);
|
||||
|
||||
@@ -329,6 +329,8 @@ enum class MessageFlag : uint64 {
|
||||
HasRestrictions = (1ULL << 48),
|
||||
|
||||
EstimatedDate = (1ULL << 49),
|
||||
|
||||
ReactionsAllowed = (1ULL << 50),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
@@ -36,6 +36,31 @@ constexpr auto kSetOnlineAfterActivity = TimeId(30);
|
||||
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
|
||||
bool ApplyBotVerifierSettings(
|
||||
not_null<BotInfo*> info,
|
||||
const MTPBotVerifierSettings *settings) {
|
||||
if (!settings) {
|
||||
const auto taken = base::take(info->verifierSettings);
|
||||
return taken != nullptr;
|
||||
}
|
||||
const auto &data = settings->data();
|
||||
const auto parsed = BotVerifierSettings{
|
||||
.iconId = DocumentId(data.vicon().v),
|
||||
.company = qs(data.vcompany()),
|
||||
.customDescription = qs(data.vcustom_description().value_or_empty()),
|
||||
.canModifyDescription = data.is_can_modify_custom_description(),
|
||||
};
|
||||
if (!info->verifierSettings) {
|
||||
info->verifierSettings = std::make_unique<BotVerifierSettings>(
|
||||
parsed);
|
||||
return true;
|
||||
} else if (*info->verifierSettings != parsed) {
|
||||
*info->verifierSettings = parsed;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BotInfo::BotInfo() = default;
|
||||
@@ -232,7 +257,11 @@ void UserData::setPersonalChannel(ChannelId channelId, MsgId messageId) {
|
||||
}
|
||||
}
|
||||
|
||||
void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) {
|
||||
void UserData::setName(
|
||||
const QString &newFirstName,
|
||||
const QString &newLastName,
|
||||
const QString &newPhoneName,
|
||||
const QString &newUsername) {
|
||||
bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty();
|
||||
|
||||
QString newFullName;
|
||||
@@ -245,7 +274,14 @@ void UserData::setName(const QString &newFirstName, const QString &newLastName,
|
||||
firstName = newFirstName;
|
||||
lastName = newLastName;
|
||||
}
|
||||
newFullName = lastName.isEmpty() ? firstName : tr::lng_full_name(tr::now, lt_first_name, firstName, lt_last_name, lastName);
|
||||
newFullName = lastName.isEmpty()
|
||||
? firstName
|
||||
: tr::lng_full_name(
|
||||
tr::now,
|
||||
lt_first_name,
|
||||
firstName,
|
||||
lt_last_name,
|
||||
lastName);
|
||||
}
|
||||
updateNameDelayed(newFullName, newPhoneName, newUsername);
|
||||
}
|
||||
@@ -372,8 +408,14 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
|
||||
= botInfo->botAppColorBodyNight
|
||||
= QColor(0, 0, 0, 0);
|
||||
}
|
||||
const auto changedVerifierSettings = ApplyBotVerifierSettings(
|
||||
botInfo.get(),
|
||||
d.vverifier_settings());
|
||||
|
||||
if (changedCommands || changedButton || privacyChanged) {
|
||||
if (changedCommands
|
||||
|| changedButton
|
||||
|| privacyChanged
|
||||
|| changedVerifierSettings) {
|
||||
owner().botCommandsChanged(this);
|
||||
}
|
||||
} break;
|
||||
@@ -514,6 +556,33 @@ bool UserData::isUsernameEditable(QString username) const {
|
||||
return _username.isEditable(username);
|
||||
}
|
||||
|
||||
void UserData::setBotVerifyDetails(Ui::BotVerifyDetails details) {
|
||||
if (!details) {
|
||||
if (_botVerifyDetails) {
|
||||
_botVerifyDetails = nullptr;
|
||||
session().changes().peerUpdated(this, UpdateFlag::VerifyInfo);
|
||||
}
|
||||
} else if (!_botVerifyDetails) {
|
||||
_botVerifyDetails = std::make_unique<Ui::BotVerifyDetails>(details);
|
||||
session().changes().peerUpdated(this, UpdateFlag::VerifyInfo);
|
||||
} else if (*_botVerifyDetails != details) {
|
||||
*_botVerifyDetails = details;
|
||||
session().changes().peerUpdated(this, UpdateFlag::VerifyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void UserData::setBotVerifyDetailsIcon(DocumentId iconId) {
|
||||
if (!iconId) {
|
||||
setBotVerifyDetails({});
|
||||
} else {
|
||||
auto info = _botVerifyDetails
|
||||
? *_botVerifyDetails
|
||||
: Ui::BotVerifyDetails();
|
||||
info.iconId = iconId;
|
||||
setBotVerifyDetails(info);
|
||||
}
|
||||
}
|
||||
|
||||
const QString &UserData::phone() const {
|
||||
return _phone;
|
||||
}
|
||||
@@ -725,6 +794,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||
user->owner().businessInfo().applyGreetingSettings(
|
||||
FromMTP(&user->owner(), update.vbusiness_greeting_message()));
|
||||
}
|
||||
user->setBotVerifyDetails(
|
||||
ParseBotVerifyDetails(update.vbot_verification()));
|
||||
|
||||
user->owner().stories().apply(user, update.vstories());
|
||||
|
||||
@@ -746,4 +817,18 @@ StarRefProgram ParseStarRefProgram(const MTPStarRefProgram *program) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Ui::BotVerifyDetails ParseBotVerifyDetails(const MTPBotVerification *info) {
|
||||
if (!info) {
|
||||
return {};
|
||||
}
|
||||
const auto &data = info->data();
|
||||
const auto description = qs(data.vdescription());
|
||||
const auto flags = TextParseLinks;
|
||||
return {
|
||||
.botId = UserId(data.vbot_id().v),
|
||||
.iconId = DocumentId(data.vicon().v),
|
||||
.description = TextUtilities::ParseEntities(description, flags),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -31,6 +31,21 @@ struct StarRefProgram {
|
||||
StarRefProgram) = default;
|
||||
};
|
||||
|
||||
struct BotVerifierSettings {
|
||||
DocumentId iconId = 0;
|
||||
QString company;
|
||||
QString customDescription;
|
||||
bool canModifyDescription = false;
|
||||
|
||||
explicit operator bool() const {
|
||||
return iconId != 0;
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
const BotVerifierSettings &a,
|
||||
const BotVerifierSettings &b) = default;
|
||||
};
|
||||
|
||||
struct BotInfo {
|
||||
BotInfo();
|
||||
|
||||
@@ -57,6 +72,7 @@ struct BotInfo {
|
||||
ChatAdminRights channelAdminRights;
|
||||
|
||||
StarRefProgram starRefProgram;
|
||||
std::unique_ptr<BotVerifierSettings> verifierSettings;
|
||||
|
||||
int version = 0;
|
||||
int descriptionVersion = 0;
|
||||
@@ -177,6 +193,12 @@ public:
|
||||
[[nodiscard]] const std::vector<QString> &usernames() const;
|
||||
[[nodiscard]] bool isUsernameEditable(QString username) const;
|
||||
|
||||
void setBotVerifyDetails(Ui::BotVerifyDetails details);
|
||||
void setBotVerifyDetailsIcon(DocumentId iconId);
|
||||
[[nodiscard]] Ui::BotVerifyDetails *botVerifyDetails() const {
|
||||
return _botVerifyDetails.get();
|
||||
}
|
||||
|
||||
enum class ContactStatus : char {
|
||||
Unknown,
|
||||
Contact,
|
||||
@@ -255,6 +277,7 @@ private:
|
||||
std::vector<Data::UnavailableReason> _unavailableReasons;
|
||||
QString _phone;
|
||||
QString _privateForwardName;
|
||||
std::unique_ptr<Ui::BotVerifyDetails> _botVerifyDetails;
|
||||
|
||||
ChannelId _personalChannelId = 0;
|
||||
MsgId _personalChannelMessageId = 0;
|
||||
@@ -272,4 +295,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update);
|
||||
[[nodiscard]] StarRefProgram ParseStarRefProgram(
|
||||
const MTPStarRefProgram *program);
|
||||
|
||||
[[nodiscard]] Ui::BotVerifyDetails ParseBotVerifyDetails(
|
||||
const MTPBotVerification *info);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ffmpeg/ffmpeg_frame_generator.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "storage/file_download.h" // kMaxFileInMemory
|
||||
#include "ui/chat/chats_filter_tag.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
@@ -104,6 +105,18 @@ private:
|
||||
return u"userpic:"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ScaledSimplePrefix() {
|
||||
return u"scaled-simple:"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ScaledCustomPrefix() {
|
||||
return u"scaled-custom:"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ForceStaticPrefix() {
|
||||
return u"force-static:"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString InternalPadding(QMargins value) {
|
||||
return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
|
||||
).arg(value.left()
|
||||
@@ -536,7 +549,20 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||
Fn<void()> update,
|
||||
SizeTag tag,
|
||||
int sizeOverride) {
|
||||
if (data.startsWith(InternalPrefix())) {
|
||||
if (data.startsWith(ScaledSimplePrefix())) {
|
||||
const auto text = data.mid(ScaledSimplePrefix().size());
|
||||
const auto emoji = Ui::Emoji::Find(text);
|
||||
Assert(emoji != nullptr);
|
||||
return Ui::MakeScaledSimpleEmoji(emoji);
|
||||
} else if (data.startsWith(ScaledCustomPrefix())) {
|
||||
const auto original = data.mid(ScaledCustomPrefix().size());
|
||||
return Ui::MakeScaledCustomEmoji(
|
||||
create(original, std::move(update), SizeTag::Large));
|
||||
} else if (data.startsWith(ForceStaticPrefix())) {
|
||||
const auto original = data.mid(ForceStaticPrefix().size());
|
||||
return std::make_unique<Ui::Text::FirstFrameEmoji>(
|
||||
create(original, std::move(update), tag, sizeOverride));
|
||||
} else if (data.startsWith(InternalPrefix())) {
|
||||
return internal(data);
|
||||
} else if (data.startsWith(UserpicEmojiPrefix())) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
|
||||
@@ -31,6 +31,11 @@ ThreeStateIcon {
|
||||
active: icon;
|
||||
}
|
||||
|
||||
VerifiedBadge {
|
||||
color: color;
|
||||
height: pixels;
|
||||
}
|
||||
|
||||
ForumTopicIcon {
|
||||
size: pixels;
|
||||
font: font;
|
||||
@@ -106,8 +111,10 @@ taggedForumDialogRow: DialogRow(forumDialogRow) {
|
||||
height: 96px;
|
||||
tagTop: 77px;
|
||||
}
|
||||
dialogRowFilterTagSkip : 4px;
|
||||
dialogRowFilterTagFont : font(10px);
|
||||
dialogRowFilterTagSkip: 4px;
|
||||
dialogRowFilterTagStyle: TextStyle(defaultTextStyle) {
|
||||
font: font(10px);
|
||||
}
|
||||
dialogRowOpenBotTextStyle: semiboldTextStyle;
|
||||
dialogRowOpenBotHeight: 20px;
|
||||
dialogRowOpenBotRight: 10px;
|
||||
@@ -401,6 +408,17 @@ dialogsLockIcon: ThreeStateIcon {
|
||||
active: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedActive, point(4px, 0px) }};
|
||||
}
|
||||
|
||||
dialogsVerifiedColors: VerifiedBadge {
|
||||
height: 20px;
|
||||
color: dialogsVerifiedIconBg;
|
||||
}
|
||||
dialogsVerifiedColorsOver: VerifiedBadge(dialogsVerifiedColors) {
|
||||
color: dialogsVerifiedIconBgOver;
|
||||
}
|
||||
dialogsVerifiedColorsActive: VerifiedBadge(dialogsVerifiedColors) {
|
||||
color: dialogsVerifiedIconBgActive;
|
||||
}
|
||||
|
||||
dialogsVerifiedIcon: icon {
|
||||
{ "dialogs/dialogs_verified_star", dialogsVerifiedIconBg },
|
||||
{ "dialogs/dialogs_verified_check", dialogsVerifiedIconFg },
|
||||
@@ -771,3 +789,7 @@ dialogsPopularAppsPadding: margins(10px, 8px, 10px, 12px);
|
||||
dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) {
|
||||
minWidth: 128px;
|
||||
}
|
||||
|
||||
foldersMenu: Menu(menuWithIcons) {
|
||||
itemPadding: margins(54px, 8px, 44px, 8px);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
@@ -78,6 +79,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_color_indices.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_media_player.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
@@ -142,7 +144,8 @@ constexpr auto kPreviewPostsLimit = 3;
|
||||
|
||||
[[nodiscard]] object_ptr<SearchEmpty> MakeSearchEmpty(
|
||||
QWidget *parent,
|
||||
SearchState state) {
|
||||
SearchState state,
|
||||
Fn<void()> resetChatTypeFilter) {
|
||||
const auto query = state.query.trimmed();
|
||||
const auto hashtag = !query.isEmpty() && (query[0] == '#');
|
||||
const auto trimmed = hashtag ? query.mid(1).trimmed() : query;
|
||||
@@ -156,6 +159,9 @@ constexpr auto kPreviewPostsLimit = 3;
|
||||
const auto waiting = trimmed.isEmpty()
|
||||
&& state.tags.empty()
|
||||
&& !fromPeer;
|
||||
const auto suggestAllChats = !waiting
|
||||
&& state.tab == ChatSearchTab::MyMessages
|
||||
&& state.filter != ChatTypeFilter::All;
|
||||
const auto icon = waiting
|
||||
? SearchEmptyIcon::Search
|
||||
: SearchEmptyIcon::NoResults;
|
||||
@@ -179,7 +185,10 @@ constexpr auto kPreviewPostsLimit = 3;
|
||||
tr::now,
|
||||
lt_query,
|
||||
trimmed.mid(0, kQueryPreviewLimit)));
|
||||
if (hashtag) {
|
||||
if (suggestAllChats) {
|
||||
text.append("\n\n").append(
|
||||
Ui::Text::Link(tr::lng_search_tab_try_in_all(tr::now)));
|
||||
} else if (hashtag) {
|
||||
text.append("\n").append(
|
||||
tr::lng_search_tab_no_results_retry(tr::now));
|
||||
}
|
||||
@@ -189,11 +198,29 @@ constexpr auto kPreviewPostsLimit = 3;
|
||||
parent,
|
||||
icon,
|
||||
rpl::single(std::move(text)));
|
||||
if (suggestAllChats) {
|
||||
result->handlerActivated(
|
||||
) | rpl::start_with_next(resetChatTypeFilter, result->lifetime());
|
||||
}
|
||||
result->show();
|
||||
result->resizeToWidth(parent->width());
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ChatTypeFilterLabel(ChatTypeFilter filter) {
|
||||
switch (filter) {
|
||||
case ChatTypeFilter::All:
|
||||
return tr::lng_search_filter_all(tr::now);
|
||||
case ChatTypeFilter::Private:
|
||||
return tr::lng_search_filter_private(tr::now);
|
||||
case ChatTypeFilter::Groups:
|
||||
return tr::lng_search_filter_group(tr::now);
|
||||
case ChatTypeFilter::Channels:
|
||||
return tr::lng_search_filter_channel(tr::now);
|
||||
}
|
||||
Unexpected("Chat type filter in search results.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct InnerWidget::CollapsedRow {
|
||||
@@ -220,6 +247,11 @@ struct InnerWidget::PeerSearchResult {
|
||||
BasicRow row;
|
||||
};
|
||||
|
||||
struct InnerWidget::TagCache {
|
||||
Ui::ChatsFilterTagContext context;
|
||||
QImage frame;
|
||||
};
|
||||
|
||||
Key InnerWidget::FilterResult::key() const {
|
||||
return row->key();
|
||||
}
|
||||
@@ -1178,6 +1210,25 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
p.setFont(st::searchedBarFont);
|
||||
p.setPen(st::searchedBarFg);
|
||||
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), text);
|
||||
const auto filterOver = _selectedChatTypeFilter
|
||||
|| _pressedChatTypeFilter;
|
||||
const auto filterFont = filterOver
|
||||
? st::searchedBarFont->underline()
|
||||
: st::searchedBarFont;
|
||||
if (_searchState.tab == ChatSearchTab::MyMessages) {
|
||||
const auto text = ChatTypeFilterLabel(_searchState.filter);
|
||||
if (!_chatTypeFilterWidth) {
|
||||
_chatTypeFilterWidth = filterFont->width(text);
|
||||
}
|
||||
p.setFont(filterFont);
|
||||
p.drawTextLeft(
|
||||
(width()
|
||||
- st::searchedBarPosition.x()
|
||||
- _chatTypeFilterWidth),
|
||||
st::searchedBarPosition.y(),
|
||||
width(),
|
||||
text);
|
||||
}
|
||||
p.translate(0, st::searchedBarHeight);
|
||||
|
||||
auto skip = searchedOffset();
|
||||
@@ -1379,43 +1430,52 @@ void InnerWidget::paintPeerSearchResult(
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
// draw chat icon
|
||||
if (const auto chatTypeIcon = Ui::ChatTypeIcon(peer, context)) {
|
||||
if (const auto info = peer->botVerifyDetails()) {
|
||||
if (!result->badge.ready(info)) {
|
||||
result->badge.set(
|
||||
info,
|
||||
peer->owner().customEmojiManager().factory(),
|
||||
[=] { updateSearchResult(peer); });
|
||||
}
|
||||
const auto &st = Ui::VerifiedStyle(context);
|
||||
const auto position = rectForName.topLeft();
|
||||
const auto skip = result->badge.drawVerified(p, position, st);
|
||||
rectForName.setLeft(position.x() + skip + st::dialogsChatTypeSkip);
|
||||
} else if (const auto chatTypeIcon = Ui::ChatTypeIcon(peer, context)) {
|
||||
chatTypeIcon->paint(p, rectForName.topLeft(), context.width);
|
||||
rectForName.setLeft(rectForName.left()
|
||||
+ chatTypeIcon->width()
|
||||
+ st::dialogsChatTypeSkip);
|
||||
}
|
||||
const auto badgeWidth = result->badge.drawGetWidth(
|
||||
p,
|
||||
rectForName,
|
||||
result->name.maxWidth(),
|
||||
context.width,
|
||||
{
|
||||
.peer = peer,
|
||||
.verified = (context.active
|
||||
? &st::dialogsVerifiedIconActive
|
||||
: context.selected
|
||||
? &st::dialogsVerifiedIconOver
|
||||
: &st::dialogsVerifiedIcon),
|
||||
.premium = &ThreeStateIcon(
|
||||
st::dialogsPremiumIcon,
|
||||
context.active,
|
||||
context.selected),
|
||||
.scam = (context.active
|
||||
? &st::dialogsScamFgActive
|
||||
: context.selected
|
||||
? &st::dialogsScamFgOver
|
||||
: &st::dialogsScamFg),
|
||||
.premiumFg = (context.active
|
||||
? &st::dialogsVerifiedIconBgActive
|
||||
: context.selected
|
||||
? &st::dialogsVerifiedIconBgOver
|
||||
: &st::dialogsVerifiedIconBg),
|
||||
.customEmojiRepaint = [=] { updateSearchResult(peer); },
|
||||
.now = context.now,
|
||||
.paused = context.paused,
|
||||
});
|
||||
const auto badgeWidth = result->badge.drawGetWidth(p, {
|
||||
.peer = peer,
|
||||
.rectForName = rectForName,
|
||||
.nameWidth = result->name.maxWidth(),
|
||||
.outerWidth = context.width,
|
||||
.verified = (context.active
|
||||
? &st::dialogsVerifiedIconActive
|
||||
: context.selected
|
||||
? &st::dialogsVerifiedIconOver
|
||||
: &st::dialogsVerifiedIcon),
|
||||
.premium = &ThreeStateIcon(
|
||||
st::dialogsPremiumIcon,
|
||||
context.active,
|
||||
context.selected),
|
||||
.scam = (context.active
|
||||
? &st::dialogsScamFgActive
|
||||
: context.selected
|
||||
? &st::dialogsScamFgOver
|
||||
: &st::dialogsScamFg),
|
||||
.premiumFg = (context.active
|
||||
? &st::dialogsVerifiedIconBgActive
|
||||
: context.selected
|
||||
? &st::dialogsVerifiedIconBgOver
|
||||
: &st::dialogsVerifiedIconBg),
|
||||
.customEmojiRepaint = [=] { updateSearchResult(peer); },
|
||||
.now = context.now,
|
||||
.prioritizeVerification = true,
|
||||
.paused = context.paused,
|
||||
});
|
||||
rectForName.setWidth(rectForName.width() - badgeWidth);
|
||||
|
||||
QRect tr(context.st->textLeft, context.st->textTop, namewidth, st::dialogsTextFont->height);
|
||||
@@ -1461,112 +1521,6 @@ void InnerWidget::paintSearchTags(
|
||||
const auto position = QPoint(_searchTagsLeft, top);
|
||||
_searchTags->paint(p, position, context.now, context.paused);
|
||||
}
|
||||
//
|
||||
//void InnerWidget::paintSearchInChat(
|
||||
// Painter &p,
|
||||
// const Ui::PaintContext &context) const {
|
||||
// auto height = searchInChatSkip();
|
||||
//
|
||||
// auto top = 0;
|
||||
// p.setFont(st::searchedBarFont);
|
||||
// auto fullRect = QRect(0, top, width(), height - top);
|
||||
// p.fillRect(fullRect, currentBg());
|
||||
// if (_searchFromShown) {
|
||||
// p.setPen(st::dialogsTextFg);
|
||||
// p.setTextPalette(st::dialogsSearchFromPalette);
|
||||
// paintSearchInPeer(p, _searchFromShown, _searchFromUserUserpic, top, _searchFromUserText);
|
||||
// p.restoreTextPalette();
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//template <typename PaintUserpic>
|
||||
//void InnerWidget::paintSearchInFilter(
|
||||
// Painter &p,
|
||||
// PaintUserpic paintUserpic,
|
||||
// int top,
|
||||
// const style::icon *icon,
|
||||
// const Ui::Text::String &text) const {
|
||||
// const auto savedPen = p.pen();
|
||||
// const auto userpicLeft = st::defaultDialogRow.padding.left();
|
||||
// const auto userpicTop = top
|
||||
// + (st::dialogsSearchInHeight - st::dialogsSearchInPhotoSize) / 2;
|
||||
// paintUserpic(p, userpicLeft, userpicTop, st::dialogsSearchInPhotoSize);
|
||||
//
|
||||
// const auto nameleft = st::defaultDialogRow.padding.left()
|
||||
// + st::dialogsSearchInPhotoSize
|
||||
// + st::dialogsSearchInPhotoPadding;
|
||||
// const auto namewidth = width()
|
||||
// - nameleft
|
||||
// - st::defaultDialogRow.padding.left()
|
||||
// - st::defaultDialogRow.padding.right()
|
||||
// - st::dialogsCancelSearch.width;
|
||||
// auto rectForName = QRect(
|
||||
// nameleft,
|
||||
// top + (st::dialogsSearchInHeight - st::semiboldFont->height) / 2,
|
||||
// namewidth,
|
||||
// st::semiboldFont->height);
|
||||
// if (icon) {
|
||||
// icon->paint(p, rectForName.topLeft(), width());
|
||||
// rectForName.setLeft(rectForName.left()
|
||||
// + icon->width()
|
||||
// + st::dialogsChatTypeSkip);
|
||||
// }
|
||||
// p.setPen(savedPen);
|
||||
// text.drawLeftElided(
|
||||
// p,
|
||||
// rectForName.left(),
|
||||
// rectForName.top(),
|
||||
// rectForName.width(),
|
||||
// width());
|
||||
//}
|
||||
//
|
||||
//void InnerWidget::paintSearchInPeer(
|
||||
// Painter &p,
|
||||
// not_null<PeerData*> peer,
|
||||
// Ui::PeerUserpicView &userpic,
|
||||
// int top,
|
||||
// const Ui::Text::String &text) const {
|
||||
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||
// peer->paintUserpicLeft(p, userpic, x, y, width(), size);
|
||||
// };
|
||||
// const auto icon = Ui::ChatTypeIcon(peer);
|
||||
// paintSearchInFilter(p, paintUserpic, top, icon, text);
|
||||
//}
|
||||
//
|
||||
//void InnerWidget::paintSearchInSaved(
|
||||
// Painter &p,
|
||||
// int top,
|
||||
// const Ui::Text::String &text) const {
|
||||
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||
// Ui::EmptyUserpic::PaintSavedMessages(p, x, y, width(), size);
|
||||
// };
|
||||
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
||||
//}
|
||||
//
|
||||
//void InnerWidget::paintSearchInReplies(
|
||||
// Painter &p,
|
||||
// int top,
|
||||
// const Ui::Text::String &text) const {
|
||||
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||
// Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, width(), size);
|
||||
// };
|
||||
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
||||
//}
|
||||
//
|
||||
//void InnerWidget::paintSearchInTopic(
|
||||
// Painter &p,
|
||||
// const Ui::PaintContext &context,
|
||||
// not_null<Data::ForumTopic*> topic,
|
||||
// Ui::PeerUserpicView &userpic,
|
||||
// int top,
|
||||
// const Ui::Text::String &text) const {
|
||||
// const auto paintUserpic = [&](Painter &p, int x, int y, int size) {
|
||||
// p.translate(x, y);
|
||||
// topic->paintUserpic(p, userpic, context);
|
||||
// p.translate(-x, -y);
|
||||
// };
|
||||
// paintSearchInFilter(p, paintUserpic, top, nullptr, text);
|
||||
//}
|
||||
|
||||
void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_chatPreviewTouchGlobal || _touchDragStartGlobal) {
|
||||
@@ -1791,6 +1745,20 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
|
||||
_searchedSelected = searchedSelected;
|
||||
updateSelectedRow();
|
||||
}
|
||||
auto selectedChatTypeFilter = false;
|
||||
const auto from = skip - st::searchedBarHeight;
|
||||
if (mouseY <= skip && mouseY >= from) {
|
||||
const auto left = width()
|
||||
- _chatTypeFilterWidth
|
||||
- 2 * st::searchedBarPosition.x();
|
||||
if (_chatTypeFilterWidth > 0 && local.x() >= left) {
|
||||
selectedChatTypeFilter = true;
|
||||
}
|
||||
}
|
||||
if (_selectedChatTypeFilter != selectedChatTypeFilter) {
|
||||
update(0, from, width(), st::searchedBarHeight);
|
||||
_selectedChatTypeFilter = selectedChatTypeFilter;
|
||||
}
|
||||
}
|
||||
if (!inTags && wasSelected != isSelected()) {
|
||||
setCursor(wasSelected ? style::cur_default : style::cur_pointer);
|
||||
@@ -1832,6 +1800,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
|
||||
setPreviewPressed(_previewSelected);
|
||||
setSearchedPressed(_searchedSelected);
|
||||
_pressedMorePosts = _selectedMorePosts;
|
||||
_pressedChatTypeFilter = _selectedChatTypeFilter;
|
||||
|
||||
const auto alt = (e->modifiers() & Qt::AltModifier);
|
||||
if (alt && showChatPreview()) {
|
||||
@@ -2229,6 +2198,8 @@ void InnerWidget::mousePressReleased(
|
||||
setSearchedPressed(-1);
|
||||
const auto pressedMorePosts = _pressedMorePosts;
|
||||
_pressedMorePosts = false;
|
||||
const auto pressedChatTypeFilter = _pressedChatTypeFilter;
|
||||
_pressedChatTypeFilter = false;
|
||||
if (wasDragging) {
|
||||
selectByMouse(globalPosition);
|
||||
}
|
||||
@@ -2253,7 +2224,9 @@ void InnerWidget::mousePressReleased(
|
||||
|| (searchedPressed >= 0
|
||||
&& searchedPressed == _searchedSelected)
|
||||
|| (pressedMorePosts
|
||||
&& pressedMorePosts == _selectedMorePosts)) {
|
||||
&& pressedMorePosts == _selectedMorePosts)
|
||||
|| (pressedChatTypeFilter
|
||||
&& pressedChatTypeFilter == _selectedChatTypeFilter)) {
|
||||
if (pressedBotApp && (pressed || filteredPressed >= 0)) {
|
||||
const auto &row = pressed
|
||||
? pressed
|
||||
@@ -2780,6 +2753,7 @@ void InnerWidget::clearSelection() {
|
||||
updateSelectedRow();
|
||||
_collapsedSelected = -1;
|
||||
_selectedMorePosts = false;
|
||||
_selectedChatTypeFilter = false;
|
||||
_selected = nullptr;
|
||||
_filteredSelected
|
||||
= _searchedSelected
|
||||
@@ -3091,6 +3065,10 @@ void InnerWidget::applySearchState(SearchState state) {
|
||||
if (state.inChat) {
|
||||
onHashtagFilterUpdate(QStringView());
|
||||
}
|
||||
if (state.filter != _searchState.filter) {
|
||||
_chatTypeFilterWidth = 0;
|
||||
update();
|
||||
}
|
||||
_searchState = std::move(state);
|
||||
_searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query);
|
||||
_searchWithPostsPreview = computeSearchWithPostsPreview();
|
||||
@@ -3359,6 +3337,11 @@ rpl::producer<ChatSearchTab> InnerWidget::changeSearchTabRequests() const {
|
||||
return _changeSearchTabRequests.events();
|
||||
}
|
||||
|
||||
auto InnerWidget::changeSearchFilterRequests() const
|
||||
-> rpl::producer<ChatTypeFilter>{
|
||||
return _changeSearchFilterRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> InnerWidget::cancelSearchRequests() const {
|
||||
return _cancelSearchRequests.events();
|
||||
}
|
||||
@@ -3647,7 +3630,9 @@ void InnerWidget::refreshEmpty() {
|
||||
}
|
||||
} else if (_searchEmptyState != _searchState) {
|
||||
_searchEmptyState = _searchState;
|
||||
_searchEmpty = MakeSearchEmpty(this, _searchState);
|
||||
_searchEmpty = MakeSearchEmpty(this, _searchState, [=] {
|
||||
_changeSearchFilterRequests.fire(ChatTypeFilter::All);
|
||||
});
|
||||
if (_controller->session().data().chatsListLoaded()) {
|
||||
_searchEmpty->animate();
|
||||
}
|
||||
@@ -4262,32 +4247,41 @@ QImage *InnerWidget::cacheChatsFilterTag(
|
||||
return nullptr;
|
||||
}
|
||||
const auto key = SerializeFilterTagsKey(filter.id(), more, active);
|
||||
{
|
||||
const auto it = _chatsFilterTags.find(key);
|
||||
if (it != end(_chatsFilterTags)) {
|
||||
return &it->second;
|
||||
auto &entry = _chatsFilterTags[key];
|
||||
if (!entry.frame.isNull()) {
|
||||
if (!entry.context.loading) {
|
||||
return &entry.frame;
|
||||
}
|
||||
for (const auto &[k, emoji] : entry.context.emoji) {
|
||||
if (!emoji->ready()) {
|
||||
return &entry.frame; // Still waiting for emoji.
|
||||
}
|
||||
}
|
||||
}
|
||||
auto roundedText = QString();
|
||||
auto roundedText = TextWithEntities();
|
||||
auto colorIndex = -1;
|
||||
if (filter.id()) {
|
||||
roundedText = filter.title().toUpper();
|
||||
roundedText = filter.title().text;
|
||||
roundedText.text = roundedText.text.toUpper();
|
||||
if (filter.colorIndex()) {
|
||||
colorIndex = *(filter.colorIndex());
|
||||
}
|
||||
} else if (more > 0) {
|
||||
roundedText = QChar('+') + QString::number(more);
|
||||
roundedText.text = QChar('+') + QString::number(more);
|
||||
colorIndex = st::colorIndexBlue;
|
||||
}
|
||||
if (roundedText.isEmpty() || colorIndex < 0) {
|
||||
if (roundedText.empty() || colorIndex < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return &_chatsFilterTags.emplace(
|
||||
key,
|
||||
Ui::ChatsFilterTag(
|
||||
std::move(roundedText),
|
||||
Ui::EmptyUserpic::UserpicColor(colorIndex).color2->c,
|
||||
active)).first->second;
|
||||
const auto color = Ui::EmptyUserpic::UserpicColor(colorIndex).color2;
|
||||
entry.context.color = color->c;
|
||||
entry.context.active = active;
|
||||
entry.context.textContext = Core::MarkedTextContext{
|
||||
.session = &session(),
|
||||
.customEmojiRepaint = [] {},
|
||||
};
|
||||
entry.frame = Ui::ChatsFilterTag(roundedText, entry.context);
|
||||
return &entry.frame;
|
||||
}
|
||||
|
||||
bool InnerWidget::chooseHashtag() {
|
||||
@@ -4369,7 +4363,7 @@ ChosenRow InnerWidget::computeChosenRow() const {
|
||||
}
|
||||
|
||||
bool InnerWidget::isUserpicPress() const {
|
||||
return (_lastRowLocalMouseX >= 0)
|
||||
return (_lastRowLocalMouseX >= 0)
|
||||
&& (_lastRowLocalMouseX < _st->nameLeft)
|
||||
&& (_collapsedSelected < 0
|
||||
|| _collapsedSelected >= _collapsedRows.size());
|
||||
@@ -4389,6 +4383,24 @@ bool InnerWidget::chooseRow(
|
||||
_changeSearchTabRequests.fire(ChatSearchTab::PublicPosts);
|
||||
}
|
||||
return true;
|
||||
} else if (_selectedChatTypeFilter) {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
for (const auto tab : {
|
||||
ChatTypeFilter::All,
|
||||
ChatTypeFilter::Private,
|
||||
ChatTypeFilter::Groups,
|
||||
ChatTypeFilter::Channels,
|
||||
}) {
|
||||
_menu->addAction(ChatTypeFilterLabel(tab), [=] {
|
||||
_changeSearchFilterRequests.fire_copy(tab);
|
||||
}, (tab == _searchState.filter)
|
||||
? &st::mediaPlayerMenuCheck
|
||||
: nullptr);
|
||||
}
|
||||
_menu->popup(QCursor::pos());
|
||||
return true;
|
||||
}
|
||||
const auto modifyChosenRow = [&](
|
||||
ChosenRow row,
|
||||
|
||||
@@ -65,6 +65,7 @@ class SearchEmpty;
|
||||
class ChatSearchIn;
|
||||
enum class HashOrCashtag : uchar;
|
||||
struct RightButton;
|
||||
enum class ChatTypeFilter : uchar;
|
||||
|
||||
struct ChosenRow {
|
||||
Key key;
|
||||
@@ -176,6 +177,8 @@ public:
|
||||
[[nodiscard]] rpl::producer<> listBottomReached() const;
|
||||
[[nodiscard]] auto changeSearchTabRequests() const
|
||||
-> rpl::producer<ChatSearchTab>;
|
||||
[[nodiscard]] auto changeSearchFilterRequests() const
|
||||
-> rpl::producer<ChatTypeFilter>;
|
||||
[[nodiscard]] rpl::producer<> cancelSearchRequests() const;
|
||||
[[nodiscard]] rpl::producer<> cancelSearchFromRequests() const;
|
||||
[[nodiscard]] rpl::producer<> changeSearchFromRequests() const;
|
||||
@@ -221,6 +224,7 @@ private:
|
||||
struct CollapsedRow;
|
||||
struct HashtagResult;
|
||||
struct PeerSearchResult;
|
||||
struct TagCache;
|
||||
|
||||
enum class JumpSkip {
|
||||
PreviousOrBegin,
|
||||
@@ -307,7 +311,8 @@ private:
|
||||
|| (_peerSearchPressed >= 0)
|
||||
|| (_previewPressed >= 0)
|
||||
|| (_searchedPressed >= 0)
|
||||
|| _pressedMorePosts;
|
||||
|| _pressedMorePosts
|
||||
|| _pressedChatTypeFilter;
|
||||
}
|
||||
bool isSelected() const {
|
||||
return (_collapsedSelected >= 0)
|
||||
@@ -317,7 +322,8 @@ private:
|
||||
|| (_peerSearchSelected >= 0)
|
||||
|| (_previewSelected >= 0)
|
||||
|| (_searchedSelected >= 0)
|
||||
|| _selectedMorePosts;
|
||||
|| _selectedMorePosts
|
||||
|| _selectedChatTypeFilter;
|
||||
}
|
||||
bool uniqueSearchResults() const;
|
||||
bool hasHistoryInResults(not_null<History*> history) const;
|
||||
@@ -485,6 +491,8 @@ private:
|
||||
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
|
||||
not_null<const style::DialogRow*> _st;
|
||||
mutable std::unique_ptr<Ui::TopicJumpCache> _topicJumpCache;
|
||||
bool _selectedChatTypeFilter = false;
|
||||
bool _pressedChatTypeFilter = false;
|
||||
bool _selectedMorePosts = false;
|
||||
bool _pressedMorePosts = false;
|
||||
int _collapsedSelected = -1;
|
||||
@@ -542,6 +550,7 @@ private:
|
||||
int _previewSelected = -1;
|
||||
int _previewPressed = -1;
|
||||
int _morePostsWidth = 0;
|
||||
int _chatTypeFilterWidth = 0;
|
||||
|
||||
std::vector<std::unique_ptr<FakeRow>> _searchResults;
|
||||
int _searchedCount = 0;
|
||||
@@ -553,6 +562,7 @@ private:
|
||||
|
||||
std::unique_ptr<ChatSearchIn> _searchIn;
|
||||
rpl::event_stream<ChatSearchTab> _changeSearchTabRequests;
|
||||
rpl::event_stream<ChatTypeFilter> _changeSearchFilterRequests;
|
||||
rpl::event_stream<> _cancelSearchRequests;
|
||||
rpl::event_stream<> _cancelSearchFromRequests;
|
||||
rpl::event_stream<> _changeSearchFromRequests;
|
||||
@@ -579,7 +589,7 @@ private:
|
||||
|
||||
base::flat_map<FilterId, int> _chatsFilterScrollStates;
|
||||
|
||||
std::unordered_map<ChatsFilterTagsKey, QImage> _chatsFilterTags;
|
||||
std::unordered_map<ChatsFilterTagsKey, TagCache> _chatsFilterTags;
|
||||
bool _waitingAllChatListEntryRefreshesForTags = false;
|
||||
rpl::lifetime _handleChatListEntryTagRefreshesLifetime;
|
||||
|
||||
|
||||
@@ -129,11 +129,19 @@ struct EntryState {
|
||||
const EntryState&) = default;
|
||||
};
|
||||
|
||||
enum class ChatTypeFilter : uchar {
|
||||
All,
|
||||
Private,
|
||||
Groups,
|
||||
Channels,
|
||||
};
|
||||
|
||||
struct SearchState {
|
||||
Key inChat;
|
||||
PeerData *fromPeer = nullptr;
|
||||
std::vector<Data::ReactionId> tags;
|
||||
ChatSearchTab tab = {};
|
||||
ChatTypeFilter filter = ChatTypeFilter::All;
|
||||
QString query;
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
|
||||
@@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/text/text.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/text/text.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "ui/userpic_view.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
|
||||
@@ -453,6 +453,15 @@ Widget::Widget(
|
||||
copy.tab = tab;
|
||||
applySearchState(std::move(copy));
|
||||
}, lifetime());
|
||||
_inner->changeSearchFilterRequests(
|
||||
) | rpl::filter([=](ChatTypeFilter filter) {
|
||||
return (_searchState.filter != filter)
|
||||
&& (_searchState.tab == ChatSearchTab::MyMessages);
|
||||
}) | rpl::start_with_next([=](ChatTypeFilter filter) {
|
||||
auto copy = _searchState;
|
||||
copy.filter = filter;
|
||||
applySearchState(copy);
|
||||
}, lifetime());
|
||||
_inner->cancelSearchRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
cancelSearch({
|
||||
@@ -1345,6 +1354,7 @@ void Widget::toggleFiltersMenu(bool enabled) {
|
||||
controller()->setActiveChatsFilter(id);
|
||||
}
|
||||
},
|
||||
Window::GifPauseReason::Any,
|
||||
controller(),
|
||||
true);
|
||||
raw->show();
|
||||
@@ -2200,6 +2210,7 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
|
||||
const auto fromPeer = searchFromPeer();
|
||||
const auto &inTags = searchInTags();
|
||||
const auto tab = _searchState.tab;
|
||||
const auto filter = _searchState.filter;
|
||||
const auto fromStartType = SearchRequestType{
|
||||
.start = true,
|
||||
.peer = (inPeer != nullptr),
|
||||
@@ -2231,6 +2242,7 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
|
||||
_searchQueryFrom = fromPeer;
|
||||
_searchQueryTags = inTags;
|
||||
_searchQueryTab = tab;
|
||||
_searchQueryFilter = filter;
|
||||
process->nextRate = 0;
|
||||
process->full = false;
|
||||
_migratedProcess.full = false;
|
||||
@@ -2241,12 +2253,14 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
|
||||
} else if (_searchQuery != query
|
||||
|| _searchQueryFrom != fromPeer
|
||||
|| _searchQueryTags != inTags
|
||||
|| _searchQueryTab != tab) {
|
||||
|| _searchQueryTab != tab
|
||||
|| _searchQueryFilter != filter) {
|
||||
const auto process = currentSearchProcess();
|
||||
_searchQuery = query;
|
||||
_searchQueryFrom = fromPeer;
|
||||
_searchQueryTags = inTags;
|
||||
_searchQueryTab = tab;
|
||||
_searchQueryFilter = filter;
|
||||
process->nextRate = 0;
|
||||
process->full = false;
|
||||
_migratedProcess.full = false;
|
||||
@@ -2325,7 +2339,6 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
|
||||
_peerSearchQuery = peerQuery;
|
||||
_peerSearchRequest = 0;
|
||||
peerSearchReceived(i->second, 0);
|
||||
result = true;
|
||||
}
|
||||
} else if (_peerSearchQuery != peerQuery) {
|
||||
_peerSearchQuery = peerQuery;
|
||||
@@ -2597,9 +2610,18 @@ void Widget::requestMessages(bool fromStart) {
|
||||
const auto type = SearchRequestType{
|
||||
.start = fromStart,
|
||||
};
|
||||
const auto flags = session().settings().skipArchiveInSearch()
|
||||
? MTPmessages_SearchGlobal::Flag::f_folder_id
|
||||
: MTPmessages_SearchGlobal::Flag(0);
|
||||
using Flag = MTPmessages_SearchGlobal::Flag;
|
||||
const auto flags = Flag()
|
||||
| (session().settings().skipArchiveInSearch()
|
||||
? Flag::f_folder_id
|
||||
: Flag())
|
||||
| (_searchQueryFilter == ChatTypeFilter::Private
|
||||
? Flag::f_users_only
|
||||
: _searchQueryFilter == ChatTypeFilter::Groups
|
||||
? Flag::f_groups_only
|
||||
: _searchQueryFilter == ChatTypeFilter::Channels
|
||||
? Flag::f_broadcasts_only
|
||||
: Flag());
|
||||
const auto folderId = 0;
|
||||
_searchProcess.requestId = session().api().request(
|
||||
MTPmessages_SearchGlobal(
|
||||
@@ -3183,6 +3205,10 @@ bool Widget::applySearchState(SearchState state) {
|
||||
const auto queryEmptyChanged = queryChanged
|
||||
? (_searchState.query.isEmpty() != state.query.isEmpty())
|
||||
: false;
|
||||
if (queryEmptyChanged || tabChanged) {
|
||||
state.filter = ChatTypeFilter::All;
|
||||
}
|
||||
const auto filterChanged = (_searchState.filter != state.filter);
|
||||
|
||||
if (forum) {
|
||||
if (_openedForum == forum) {
|
||||
@@ -3244,6 +3270,7 @@ bool Widget::applySearchState(SearchState state) {
|
||||
if (searchCleared
|
||||
|| inChatChanged
|
||||
|| fromPeerChanged
|
||||
|| filterChanged
|
||||
|| tagsChanged
|
||||
|| tabChanged) {
|
||||
clearSearchCache(searchCleared);
|
||||
|
||||
@@ -381,6 +381,7 @@ private:
|
||||
PeerData *_searchQueryFrom = nullptr;
|
||||
std::vector<Data::ReactionId> _searchQueryTags;
|
||||
ChatSearchTab _searchQueryTab = {};
|
||||
ChatTypeFilter _searchQueryFilter = {};
|
||||
|
||||
SearchProcessState _searchProcess;
|
||||
SearchProcessState _migratedProcess;
|
||||
|
||||
@@ -62,6 +62,13 @@ void SearchEmpty::setup(Icon icon, rpl::producer<TextWithEntities> text) {
|
||||
label->move((size.width() - label->width()) / 2, top - sub);
|
||||
}, lifetime());
|
||||
|
||||
label->setClickHandlerFilter([=](
|
||||
const ClickHandlerPtr &handler,
|
||||
Qt::MouseButton) {
|
||||
_handlerActivated.fire_copy(handler);
|
||||
return false;
|
||||
});
|
||||
|
||||
_animate = [animate] {
|
||||
animate(anim::repeat::once);
|
||||
};
|
||||
|
||||
@@ -29,10 +29,15 @@ public:
|
||||
|
||||
void animate();
|
||||
|
||||
[[nodiscard]] rpl::producer<ClickHandlerPtr> handlerActivated() const {
|
||||
return _handlerActivated.events();
|
||||
}
|
||||
|
||||
private:
|
||||
void setup(Icon icon, rpl::producer<TextWithEntities> text);
|
||||
|
||||
Fn<void()> _animate;
|
||||
rpl::event_stream<ClickHandlerPtr> _handlerActivated;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -411,11 +411,11 @@ void PaintRow(
|
||||
from,
|
||||
videoUserpic,
|
||||
context,
|
||||
context.narrow
|
||||
(context.narrow
|
||||
&& !badgesState.empty()
|
||||
&& !draft
|
||||
&& item
|
||||
&& !item->isEmpty());
|
||||
&& !item->isEmpty()));
|
||||
}
|
||||
|
||||
const auto nameleft = context.st->nameLeft;
|
||||
@@ -449,6 +449,17 @@ void PaintRow(
|
||||
? tr::lng_badge_psa_default(tr::now)
|
||||
: custom;
|
||||
PaintRowTopRight(p, text, rectForName, context);
|
||||
} else if (const auto info = from ? from->botVerifyDetails() : nullptr) {
|
||||
if (!rowBadge.ready(info)) {
|
||||
rowBadge.set(
|
||||
info,
|
||||
from->owner().customEmojiManager().factory(),
|
||||
customEmojiRepaint);
|
||||
}
|
||||
const auto &st = Ui::VerifiedStyle(context);
|
||||
const auto position = rectForName.topLeft();
|
||||
const auto skip = rowBadge.drawVerified(p, position, st);
|
||||
rectForName.setLeft(position.x() + skip + st::dialogsChatTypeSkip);
|
||||
} else if (from) {
|
||||
if (const auto chatTypeIcon = ChatTypeIcon(from, context)) {
|
||||
chatTypeIcon->paint(p, rectForName.topLeft(), context.width);
|
||||
@@ -694,12 +705,46 @@ void PaintRow(
|
||||
}
|
||||
|
||||
p.setFont(st::semiboldFont);
|
||||
const auto paintPeerBadge = [&] {
|
||||
const auto badgeWidth = rowBadge.drawGetWidth(p, {
|
||||
.peer = from,
|
||||
.rectForName = rectForName,
|
||||
.nameWidth = rowName.maxWidth(),
|
||||
.outerWidth = context.width,
|
||||
.verified = (context.active
|
||||
? &st::dialogsVerifiedIconActive
|
||||
: context.selected
|
||||
? &st::dialogsVerifiedIconOver
|
||||
: &st::dialogsVerifiedIcon),
|
||||
.premium = &ThreeStateIcon(
|
||||
st::dialogsPremiumIcon,
|
||||
context.active,
|
||||
context.selected),
|
||||
.scam = (context.active
|
||||
? &st::dialogsScamFgActive
|
||||
: context.selected
|
||||
? &st::dialogsScamFgOver
|
||||
: &st::dialogsScamFg),
|
||||
.premiumFg = (context.active
|
||||
? &st::dialogsVerifiedIconBgActive
|
||||
: context.selected
|
||||
? &st::dialogsVerifiedIconBgOver
|
||||
: &st::dialogsVerifiedIconBg),
|
||||
.customEmojiRepaint = customEmojiRepaint,
|
||||
.now = context.now,
|
||||
.paused = context.paused,
|
||||
});
|
||||
rectForName.setWidth(rectForName.width() - badgeWidth);
|
||||
};
|
||||
if (flags
|
||||
& (Flag::SavedMessages
|
||||
| Flag::RepliesMessages
|
||||
| Flag::VerifyCodes
|
||||
| Flag::HiddenAuthor
|
||||
| Flag::MyNotes)) {
|
||||
if (!context.search && (flags & Flag::VerifyCodes)) {
|
||||
paintPeerBadge();
|
||||
}
|
||||
auto text = (flags & Flag::SavedMessages)
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: (flags & Flag::RepliesMessages)
|
||||
@@ -718,40 +763,14 @@ void PaintRow(
|
||||
: context.selected
|
||||
? st::dialogsNameFgOver
|
||||
: st::dialogsNameFg);
|
||||
p.drawTextLeft(rectForName.left(), rectForName.top(), context.width, text);
|
||||
p.drawTextLeft(
|
||||
rectForName.left(),
|
||||
rectForName.top(),
|
||||
context.width,
|
||||
text);
|
||||
} else if (from) {
|
||||
if ((history || sublist) && !context.search) {
|
||||
const auto badgeWidth = rowBadge.drawGetWidth(
|
||||
p,
|
||||
rectForName,
|
||||
rowName.maxWidth(),
|
||||
context.width,
|
||||
{
|
||||
.peer = from,
|
||||
.verified = (context.active
|
||||
? &st::dialogsVerifiedIconActive
|
||||
: context.selected
|
||||
? &st::dialogsVerifiedIconOver
|
||||
: &st::dialogsVerifiedIcon),
|
||||
.premium = &ThreeStateIcon(
|
||||
st::dialogsPremiumIcon,
|
||||
context.active,
|
||||
context.selected),
|
||||
.scam = (context.active
|
||||
? &st::dialogsScamFgActive
|
||||
: context.selected
|
||||
? &st::dialogsScamFgOver
|
||||
: &st::dialogsScamFg),
|
||||
.premiumFg = (context.active
|
||||
? &st::dialogsVerifiedIconBgActive
|
||||
: context.selected
|
||||
? &st::dialogsVerifiedIconBgOver
|
||||
: &st::dialogsVerifiedIconBg),
|
||||
.customEmojiRepaint = customEmojiRepaint,
|
||||
.now = context.now,
|
||||
.paused = context.paused,
|
||||
});
|
||||
rectForName.setWidth(rectForName.width() - badgeWidth);
|
||||
paintPeerBadge();
|
||||
}
|
||||
p.setPen(context.active
|
||||
? st::dialogsNameFgActive
|
||||
@@ -839,6 +858,14 @@ const style::icon *ChatTypeIcon(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const style::VerifiedBadge &VerifiedStyle(const PaintContext &context) {
|
||||
return context.active
|
||||
? st::dialogsVerifiedColorsActive
|
||||
: context.selected
|
||||
? st::dialogsVerifiedColorsOver
|
||||
: st::dialogsVerifiedColors;
|
||||
}
|
||||
|
||||
void RowPainter::Paint(
|
||||
Painter &p,
|
||||
not_null<const Row*> row,
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace style {
|
||||
struct DialogRow;
|
||||
struct VerifiedBadge;
|
||||
} // namespace style
|
||||
|
||||
namespace st {
|
||||
@@ -79,6 +80,9 @@ struct PaintContext {
|
||||
const PaintContext &context);
|
||||
[[nodiscard]] const style::icon *ChatTypeIcon(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] const style::VerifiedBadge &VerifiedStyle(
|
||||
const PaintContext &context);
|
||||
|
||||
class RowPainter {
|
||||
public:
|
||||
static void Paint(
|
||||
|
||||
@@ -12,11 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "core/application.h"
|
||||
#include "data/components/recent_peers.h"
|
||||
#include "data/components/top_peers.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_download_manager.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -414,6 +416,7 @@ public:
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowMiddleClicked(not_null<PeerListRow*> row) override;
|
||||
bool rowTrackPress(not_null<PeerListRow*> row) override;
|
||||
void rowTrackPressCancel() override;
|
||||
bool rowTrackPressSkipMouseSelection() override;
|
||||
@@ -671,6 +674,11 @@ void Suggestions::ObjectListController::rowClicked(
|
||||
_chosen.fire(row->peer());
|
||||
}
|
||||
|
||||
void Suggestions::ObjectListController::rowMiddleClicked(
|
||||
not_null<PeerListRow*> row) {
|
||||
window()->showInNewWindow(row->peer());
|
||||
}
|
||||
|
||||
void Suggestions::ObjectListController::setupPlainDivider(
|
||||
rpl::producer<QString> title) {
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(
|
||||
@@ -1305,18 +1313,7 @@ Suggestions::Suggestions(
|
||||
, _tabs(
|
||||
_tabsScroll->setOwnedWidget(
|
||||
object_ptr<Ui::SettingsSlider>(this, st::dialogsSearchTabs)))
|
||||
, _tabKeys{
|
||||
{ Tab::Chats },
|
||||
{ Tab::Channels },
|
||||
{ Tab::Apps },
|
||||
{ Tab::Media, MediaType::Photo },
|
||||
{ Tab::Media, MediaType::Video },
|
||||
{ Tab::Downloads },
|
||||
{ Tab::Media, MediaType::Link },
|
||||
{ Tab::Media, MediaType::File },
|
||||
{ Tab::Media, MediaType::MusicFile },
|
||||
{ Tab::Media, MediaType::RoundVoiceFile },
|
||||
}
|
||||
, _tabKeys(TabKeysFor(controller))
|
||||
, _chatsScroll(std::make_unique<Ui::ElasticScroll>(this))
|
||||
, _chatsContent(
|
||||
_chatsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
||||
@@ -1987,6 +1984,26 @@ float64 Suggestions::shownOpacity() const {
|
||||
return _shownAnimation.value(_hidden ? 0. : 1.);
|
||||
}
|
||||
|
||||
std::vector<Suggestions::Key> Suggestions::TabKeysFor(
|
||||
not_null<Window::SessionController*> controller) {
|
||||
auto result = std::vector<Key>{
|
||||
{ Tab::Chats },
|
||||
{ Tab::Channels },
|
||||
{ Tab::Apps },
|
||||
{ Tab::Media, MediaType::Photo },
|
||||
{ Tab::Media, MediaType::Video },
|
||||
{ Tab::Downloads },
|
||||
{ Tab::Media, MediaType::Link },
|
||||
{ Tab::Media, MediaType::File },
|
||||
{ Tab::Media, MediaType::MusicFile },
|
||||
{ Tab::Media, MediaType::RoundVoiceFile },
|
||||
};
|
||||
if (Core::App().downloadManager().empty()) {
|
||||
result.erase(ranges::find(result, Key{ Tab::Downloads }));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Suggestions::paintEvent(QPaintEvent *e) {
|
||||
const auto opacity = shownOpacity();
|
||||
auto color = st::windowBg->c;
|
||||
|
||||
@@ -147,6 +147,9 @@ private:
|
||||
rpl::variable<int> count;
|
||||
};
|
||||
|
||||
[[nodiscard]] static std::vector<Key> TabKeysFor(
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
|
||||