Compare commits

...

82 Commits

Author SHA1 Message Date
John Preston
277d76df3e Version 5.10.2: Update Qt patches on Linux. 2025-01-08 17:42:51 +04:00
John Preston
1ac33d30bd Version 5.10.2: Improve gifts layout. 2025-01-08 17:36:45 +04:00
23rd
658cb438f8 Added spoiler entity to email pattern in recover box. 2025-01-08 14:25:40 +03:00
23rd
2b71625ffe Moved out part of common code for cloud password to td_ui. 2025-01-08 14:25:40 +03:00
23rd
2b13fc9a24 Slightly simplified Intro::Step::setDescriptionText. 2025-01-08 14:25:40 +03:00
23rd
9e18964e7f Added spoiler entity to email pattern in intro and cloud password. 2025-01-08 14:25:40 +03:00
23rd
43dfe559a6 Added simple context for marked text. 2025-01-08 10:58:56 +03:00
23rd
aab7ba264c Added ability to open peers in window with middle button from contacts. 2025-01-07 23:45:23 +03:00
23rd
b7162b5fad Added ability to open recent peers in window with middle button. 2025-01-07 23:45:23 +03:00
23rd
ce4a081155 Added initial ability to handle middle button in peer lists. 2025-01-07 23:45:23 +03:00
23rd
5df2a048e1 Fixed ability to copy selected text with presented compose search. 2025-01-07 23:12:49 +03:00
23rd
1b6a7fafa8 Added ability to instantly delete account in test DC. 2025-01-07 23:12:49 +03:00
23rd
2ab725e5e1 Added patch for colors of strikeout format to Linux. 2025-01-07 23:12:49 +03:00
John Preston
88a310a86e Version 5.10.2: Hide unique gift userpic. 2025-01-07 21:31:06 +04:00
John Preston
86319be256 Version 5.10.2.
- Fix double verification badge in profiles.
2025-01-07 21:23:42 +04:00
John Preston
9d68ef6421 Fix bot verification showing second check. 2025-01-07 20:59:52 +04:00
John Preston
2bb1c5d39b Show verify badge on Verifications Codes. 2025-01-07 20:59:17 +04:00
John Preston
3aa15c979d Version 5.10.1.
- Show "Boost group the send messages" information.
- Fix several gifts bugs and glitches.
- Fix several crashes.
2025-01-06 21:42:23 +04:00
John Preston
c062ba3426 Fix possible crash in sticker click. 2025-01-06 21:42:23 +04:00
John Preston
343560225c Fix crash in QR code copy on scale < 100. 2025-01-06 21:42:23 +04:00
John Preston
e0dd77f0c3 Fix possible crash in forward box. 2025-01-06 21:42:23 +04:00
John Preston
92ff07f723 Fix possible crash in message translation. 2025-01-06 21:42:23 +04:00
John Preston
a23dca080a Always show "View" button in gifts. 2025-01-06 21:42:23 +04:00
John Preston
6844f88567 Improve gift layout. 2025-01-06 21:42:23 +04:00
John Preston
e6060ea277 Improve gift corner badge display. 2025-01-06 21:42:23 +04:00
John Preston
549de7fa54 Show both verify and status emoji in opened chat. 2025-01-06 21:42:23 +04:00
John Preston
ecf9faa21d Fix usernames and QR overlap. 2025-01-06 21:42:23 +04:00
John Preston
a87ebd41e7 Fix reactions for call messages. 2025-01-06 21:42:23 +04:00
John Preston
183dd40f39 Improve gift phrases usage. 2025-01-06 21:42:23 +04:00
John Preston
c722c5c46f Improve gift pattern transparency. 2025-01-06 21:42:23 +04:00
John Preston
865200db5e Improve custom verification icon display. 2025-01-06 21:42:23 +04:00
John Preston
c3e15de759 Don't show Downloads if it's empty in search. 2025-01-06 21:42:23 +04:00
John Preston
44bfdbdc83 Add global search chat type filter. 2025-01-06 21:42:23 +04:00
John Preston
5f10c1875c Ask for boosts to unlock group restrictions. 2025-01-06 21:42:23 +04:00
23rd
a7ae7a8cda Slightly improved fade effect in price categories from star gift box. 2025-01-06 20:40:17 +03:00
23rd
706f142a98 Slightly improved ripple animation for button to gift credits. 2025-01-06 20:40:17 +03:00
23rd
08df3b2dff Removed unused badge in giveaway box. 2025-01-06 20:40:17 +03:00
23rd
14672ff145 Excluded cases of divisions by zero in dates from statistics charts. 2025-01-06 20:40:17 +03:00
Nikolai Nechaev
7fcd84d08e Fix handling of notification disappearing under cursor
Previously, notifications disappearing under cursor (e.g., because closed
manually or open from another device) did not notify the manager
properly, as the leaveEventHook was not triggered. This could lead to
notifications staying around when not supposed to (see #28813).

This commit fixes that by explicitly notifying manager when the
notification widget disappears under the cursor.

Fixes #28813.
2025-01-06 09:51:09 +04:00
dependabot[bot]
12f8686326 Bump jinja2 from 3.1.4 to 3.1.5 in /Telegram/build/docker/centos_env
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.4...3.1.5)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 09:48:00 +04:00
John Preston
aa445adfff Fix possible crash in ListWidget destructor. 2025-01-04 22:00:38 +04:00
John Preston
603aa5db5f Version 5.10: Fix build with GCC. 2025-01-02 12:23:07 +04:00
John Preston
c34289036f Version 5.10: Show folder tags premium promo. 2025-01-02 11:25:08 +04:00
John Preston
5b6bec775b Version 5.10: Fix build with Xcode. 2025-01-02 11:02:33 +04:00
John Preston
3b0fe3043f Version 5.10.
- Collectible Gifts.
- Reactions for Service Messages.
- Verification from Third Parties.
- Custom Emoji in Folder Names.
2025-01-02 10:54:50 +04:00
GitHub Action
c99165891f Update copyright year to 2025. 2025-01-02 10:48:21 +04:00
John Preston
4938b18f9d Fix display of gifts from bots. 2025-01-02 10:47:37 +04:00
John Preston
8895b4e8a3 Implement buying gifts for myself. 2024-12-31 21:40:18 +04:00
John Preston
a7321c9beb Return native verify icon to the right. 2024-12-31 17:18:59 +04:00
John Preston
c23b533704 Hide shared media layer when jumping to message. 2024-12-31 16:44:22 +04:00
John Preston
de34c75788 Support custom emoji in folder menus. 2024-12-31 16:29:10 +04:00
John Preston
06341efe0d Allow disabling animations in folder emoji. 2024-12-31 15:41:13 +04:00
John Preston
c810005f86 Don't parse empty messages in channels. 2024-12-31 13:44:52 +04:00
John Preston
cdedf283ac Show correct topic buttons in admin log. 2024-12-31 13:12:16 +04:00
John Preston
acfd92e2e6 Display emoji correctly in folder tags. 2024-12-31 13:12:16 +04:00
John Preston
51b81dba87 Fix animated side buttons with locks. 2024-12-31 13:12:16 +04:00
John Preston
7f6dfcf52f Improve new gift transactions a bit. 2024-12-31 13:12:16 +04:00
John Preston
92582d8434 Implement refunded upgraded gift view. 2024-12-31 13:12:16 +04:00
John Preston
e2bff474db Show upgraded gift from old "View" button. 2024-12-31 13:12:15 +04:00
John Preston
4f702e12b7 Improve upgrade/transfer toasts. 2024-12-31 13:12:15 +04:00
John Preston
083400d1c2 Implement unique gift transfer. 2024-12-31 13:12:15 +04:00
John Preston
7491337bfd Show forward original date for edited items. 2024-12-31 13:12:15 +04:00
John Preston
d6b833fbb2 Add icons for gift upgrading. 2024-12-31 13:12:15 +04:00
John Preston
2113a2b634 Implement nice unique gifts in the list. 2024-12-31 13:12:15 +04:00
John Preston
5df632264f Allow pay for upgrade when sending. 2024-12-31 13:12:15 +04:00
John Preston
42c350243a Implement unique gift view box. 2024-12-31 13:12:15 +04:00
John Preston
522ca3b04a Pause gift view ministars in an inactive window. 2024-12-31 13:12:15 +04:00
John Preston
2d53ec5d34 Implement unique gift view in chat. 2024-12-31 13:12:15 +04:00
John Preston
13d2f70c3a Implement upgraded unique gifts. 2024-12-31 13:12:15 +04:00
John Preston
a87d19998e Support bot verifications without modify access. 2024-12-31 13:12:15 +04:00
John Preston
6ddf241293 Update API scheme on layer 196. 2024-12-31 13:12:15 +04:00
John Preston
e43ec6c4ea Add unique gift phrases. 2024-12-31 13:12:15 +04:00
John Preston
5f3db95cbd Parse unique gift fields. 2024-12-31 13:12:15 +04:00
John Preston
d874829b06 Start animating emoji in filter titles. 2024-12-31 13:12:15 +04:00
John Preston
6cfbccd955 Handle report_delivery_until_date. 2024-12-25 11:09:35 +04:00
John Preston
0d821c3630 Implement simple bot verification management. 2024-12-25 11:09:35 +04:00
John Preston
b61e3b580d Return chat type icons. 2024-12-25 11:09:35 +04:00
John Preston
5c301353ec Improve verified badge display. 2024-12-25 11:09:35 +04:00
John Preston
0363421862 Apply server side bot verifications. 2024-12-25 11:09:35 +04:00
John Preston
6f18b9b691 Proof-of-concept custom verify badges. 2024-12-25 11:09:35 +04:00
John Preston
35e40be550 Support service messages reactions. 2024-12-25 11:09:35 +04:00
John Preston
c1528f532e Update API scheme to layer 196. 2024-12-25 11:09:35 +04:00
221 changed files with 8370 additions and 2357 deletions

2
LEGAL
View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2282,6 +2282,8 @@ void ParticipantsBoxSearchController::restoreState(
_allLoaded = my->allLoaded;
_offset = my->offset;
_query = my->query;
_timer.cancel();
_requestId = 0;
if (my->wasLoading) {
searchOnServer();
}

View File

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

View File

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

View 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,
};
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

@@ -68,7 +68,7 @@ struct StatisticalChart {
float64 oneDayPercentage = 0.;
float64 timeStep = 0.;
float64 timeStep = 1.;
bool isFooterHidden = false;
bool hasPercentages = false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -381,6 +381,7 @@ private:
PeerData *_searchQueryFrom = nullptr;
std::vector<Data::ReactionId> _searchQueryTags;
ChatSearchTab _searchQueryTab = {};
ChatTypeFilter _searchQueryFilter = {};
SearchProcessState _searchProcess;
SearchProcessState _migratedProcess;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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