Merge tag 'v5.10.3' into dev

This commit is contained in:
AlexeyZavar 2025-01-15 10:10:05 +03:00
commit 20358a2f58
450 changed files with 19166 additions and 4378 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

@ -326,6 +326,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
@ -402,8 +404,6 @@ PRIVATE
boxes/send_gif_with_caption_box.h
boxes/send_files_box.cpp
boxes/send_files_box.h
boxes/sessions_box.cpp
boxes/sessions_box.h
boxes/share_box.cpp
boxes/share_box.h
boxes/star_gift_box.cpp
@ -412,6 +412,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
@ -550,6 +552,7 @@ PRIVATE
core/sandbox.h
core/shortcuts.cpp
core/shortcuts.h
core/stars_amount.h
core/ui_integration.cpp
core/ui_integration.h
core/update_checker.cpp
@ -707,6 +710,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
@ -877,6 +881,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
@ -999,6 +1005,12 @@ PRIVATE
info/bot/earn/info_bot_earn_list.h
info/bot/earn/info_bot_earn_widget.cpp
info/bot/earn/info_bot_earn_widget.h
info/bot/starref/info_bot_starref_common.cpp
info/bot/starref/info_bot_starref_common.h
info/bot/starref/info_bot_starref_join_widget.cpp
info/bot/starref/info_bot_starref_join_widget.h
info/bot/starref/info_bot_starref_setup_widget.cpp
info/bot/starref/info_bot_starref_setup_widget.h
info/channel_statistics/boosts/create_giveaway_box.cpp
info/channel_statistics/boosts/create_giveaway_box.h
info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp
@ -1021,6 +1033,12 @@ PRIVATE
info/downloads/info_downloads_provider.h
info/downloads/info_downloads_widget.cpp
info/downloads/info_downloads_widget.h
info/global_media/info_global_media_widget.cpp
info/global_media/info_global_media_widget.h
info/global_media/info_global_media_inner_widget.cpp
info/global_media/info_global_media_inner_widget.h
info/global_media/info_global_media_provider.cpp
info/global_media/info_global_media_provider.h
info/media/info_media_buttons.h
info/media/info_media_common.cpp
info/media/info_media_common.h
@ -1471,8 +1489,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
@ -1485,6 +1501,10 @@ 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
settings/settings_advanced.h
settings/settings_blocked_peers.cpp
@ -1646,8 +1666,6 @@ PRIVATE
ui/item_text_options.cpp
ui/item_text_options.h
ui/resize_area.h
ui/search_field_controller.cpp
ui/search_field_controller.h
ui/unread_badge.cpp
ui/unread_badge.h
window/main_window.cpp

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_activate" = "Use this account";
"lng_menu_set_status" = "Set Emoji Status";
"lng_menu_change_status" = "Change Emoji Status";
"lng_menu_my_profile" = "My Profile";
"lng_menu_my_stories" = "My Stories";
"lng_menu_my_groups" = "My Groups";
"lng_menu_my_channels" = "My Channels";
@ -288,6 +289,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_error_cant_add_member" = "Sorry, you can't add the bot to this group. Ask a group admin to do it.";
"lng_error_cant_add_bot" = "Sorry, this bot can't be added to groups.";
"lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
"lng_error_you_blocked_user" = "Sorry, you can't add this user or bot to groups because you've blocked them. Please unblock to proceed.";
"lng_error_add_admin_not_member" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
"lng_error_user_admin_invalid" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_channel_bots_too_much" = "Sorry, this channel has too many bots.";
"lng_error_group_bots_too_much" = "There are too many bots in this group. Please remove some of the bots you're not using first.";
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them.";
"lng_error_cant_ban_admin" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
"lng_error_cant_reply_other" = "This message can't be replied in another chat.";
@ -1475,6 +1481,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_enable_notifications" = "Notifications";
"lng_profile_send_message" = "Send Message";
"lng_profile_open_app" = "Open App";
"lng_profile_open_app_short" = "Open";
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
"lng_profile_bot_permissions_title" = "Allow access to";
@ -1544,6 +1551,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_channel_title" = "Manage Channel";
"lng_manage_bot_title" = "Manage Bot";
"lng_manage_peer_recent_actions" = "Recent actions";
"lng_manage_peer_star_ref" = "Affiliate programs";
"lng_manage_peer_members" = "Members";
"lng_manage_peer_subscribers" = "Subscribers";
"lng_manage_peer_administrators" = "Administrators";
@ -1617,11 +1625,130 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_bot_balance" = "Balance";
"lng_manage_peer_bot_balance_currency" = "Toncoin";
"lng_manage_peer_bot_balance_credits" = "Stars";
"lng_manage_peer_bot_star_ref" = "Affiliate Program";
"lng_manage_peer_bot_star_ref_off" = "Off";
"lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there.";
"lng_manage_peer_bot_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";
"lng_star_ref_share_about" = "Set the commission for revenue generated by users referred to you.";
"lng_star_ref_launch_title" = "Launch your affiliate program";
"lng_star_ref_launch_about" = "Telegram will feature your program for millions of potential affiliates.";
"lng_star_ref_let_title" = "Let affiliate promote you";
"lng_star_ref_let_about" = "Affiliates will share your referral link with their audience.";
"lng_star_ref_commission_title" = "Commission";
"lng_star_ref_commission_about" = "Define the percentage of star revenue your affiliates earn for referring users to your bot.";
"lng_star_ref_duration_title" = "Duration";
"lng_star_ref_duration_about" = "Set the duration for which affiliates will earn commissions from referred users.";
"lng_star_ref_existing_title" = "View existing programs";
"lng_star_ref_existing_about" = "Explore what other mini apps offer.";
"lng_star_ref_add_bot" = "Add {bot}";
"lng_star_ref_end" = "End Affiliate Program";
"lng_star_ref_start" = "Start Affiliate Program";
"lng_star_ref_start_disabled" = "Available in {time}";
"lng_star_ref_start_info" = "By creating an affiliate program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_update" = "Update Affiliate Program";
"lng_star_ref_update_info" = "By updating an affiliate program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_button_link" = "terms and conditions";
"lng_star_ref_tos_url" = "https://telegram.org/tos/mini-apps";
"lng_star_ref_warning_title" = "Warning";
"lng_star_ref_warning_text" = "Once you start the affiliate program, you won't be able to decrease its commission or duration. You can only increase these parameters or end the program, which will disable all previously distributed referral links.";
"lng_star_ref_warning_change" = "This change is irreversible. You won't be able to reduce commission or duration. You can only increase these parameters or end the program, which will disable all previously shared referral links.";
"lng_star_ref_warning_start" = "Start";
"lng_star_ref_warning_update" = "Update";
"lng_star_ref_warning_if_end" = "If you end your affiliate program:";
"lng_star_ref_warning_if_end1" = "Any referral links already shared will be disabled in **24** hours.";
"lng_star_ref_warning_if_end2" = "All participating affiliates will be notified.";
"lng_star_ref_warning_if_end3" = "You will be able to start a new affiliate program only in **24** hours.";
"lng_star_ref_warning_end" = "End Anyway";
"lng_star_ref_created_title" = "Affiliate program started";
"lng_star_ref_created_text" = "Any Telegram user, channel owner or mini app developer can now join your program.";
"lng_star_ref_updated_title" = "Affiliate program updated";
"lng_star_ref_updated_text" = "Any Telegram user, channel owner or mini app developer can join your program.";
"lng_star_ref_ended_title" = "Affiliate program ended";
"lng_star_ref_ended_text" = "Participating affiliates have been notified. All referral links will be disabled in **24** hours.";
"lng_star_ref_list_title" = "Affiliate Programs";
"lng_star_ref_list_about_channel" = "Promote mini apps to your subscribers and earn a share of their revenue in Stars.";
"lng_star_ref_list_text" = "Earn a commission each time a user who first accessed a mini app through your referral link spends **Stars** within it.";
"lng_star_ref_list_my" = "My Programs";
"lng_star_ref_list_my_open" = "Open App";
"lng_star_ref_list_my_copy" = "Copy Link";
"lng_star_ref_list_my_leave" = "Leave";
"lng_star_ref_list_subtitle" = "Programs";
"lng_star_ref_sort_text" = "Sort by {sort}";
"lng_star_ref_sort_profitability" = "Profitability";
"lng_star_ref_sort_date" = "Date";
"lng_star_ref_sort_revenue" = "Revenue";
"lng_star_ref_reliable_title" = "Reliable";
"lng_star_ref_reliable_about" = "Receive guaranteed commissions for spending by users you refer.";
"lng_star_ref_transparent_title" = "Transparent";
"lng_star_ref_transparent_about" = "Track your commissions from referred users in real time.";
"lng_star_ref_simple_title" = "Simple";
"lng_star_ref_simple_about" = "Choose a mini app below, get your referral link, and start earning Stars.";
"lng_star_ref_duration_forever" = "Forever";
"lng_star_ref_one_about" = "{app} will share {amount} of the revenue from each user you refer to it {duration}.";
"lng_star_ref_one_about_for_forever" = "for **lifetime**";
"lng_star_ref_one_about_for_months#one" = "for **{count} month**";
"lng_star_ref_one_about_for_months#other" = "for **{count} months**";
"lng_star_ref_one_about_for_years#one" = "for **{count} year**";
"lng_star_ref_one_about_for_years#other" = "for **{count} years**";
"lng_star_ref_one_daily_revenue" = "Daily revenue per user: {amount}";
"lng_star_ref_one_join" = "Join Program";
"lng_star_ref_one_join_text" = "By joining this program, you agree to the {terms} of Affiliate Programs.";
"lng_star_ref_joined_title" = "Program joined";
"lng_star_ref_joined_text" = "You can now copy the referral link.";
"lng_star_ref_link_title" = "Referral Link";
"lng_star_ref_link_about_channel" = "Share this link with your subscribers to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_about_user" = "Share this link with your friends to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_about_bot" = "Share this link with your users to earn a {amount} commission on their spending in {app} {duration}.";
"lng_star_ref_link_recipient" = "Commissions will be sent to:";
"lng_star_ref_link_copy" = "Copy Link";
"lng_star_ref_link_copy_none" = "No one have opened {app} through this link.";
"lng_star_ref_link_copy_users#one" = "{count} user have opened {app} through this link.";
"lng_star_ref_link_copy_users#other" = "{count} users have opened {app} through this link.";
"lng_star_ref_link_copied_title" = "Link copied to clipboard";
"lng_star_ref_link_copied_text" = "Share this link and earn {amount} of what people who use it spend in {app}!";
"lng_star_ref_stopped" = "This affiliate link is no longer active.";
"lng_star_ref_revoke_title" = "Revoke Link";
"lng_star_ref_revoke_text" = "Are you sure you want to revoke the link of {bot}?";
"lng_star_ref_revoked_title" = "Link removed";
"lng_star_ref_revoked_text" = "It will no longer work.";
"lng_manage_discussion_group" = "Discussion";
"lng_manage_discussion_group_add" = "Add a group";
@ -1889,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";
@ -1952,6 +2094,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_giveaway_results_credits#other" = "{count} winners of the giveaway were randomly selected by Telegram and received their prize.";
"lng_action_giveaway_results_credits_some" = "Some winners of the giveaway were randomly selected by Telegram and received their prize.";
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
"lng_action_boost_apply_me" = "You boosted the group";
"lng_action_boost_apply#one" = "{from} boosted the group";
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
@ -2300,11 +2443,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies.";
"lng_premium_summary_subtitle_effects" = "Message Effects";
"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages.";
"lng_premium_summary_subtitle_filter_tags" = "Tag Your Chats";
"lng_premium_summary_about_filter_tags" = "Display folder names for each chat in the chat list.";
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
"lng_premium_summary_button" = "Subscribe for {cost} per month";
"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.";
@ -2432,11 +2578,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
"lng_credits_summary_options_about_link" = "Terms and Conditions";
"lng_credits_summary_options_about_url" = "https://telegram.org/tos/stars";
"lng_credits_summary_earn_title" = "Earn Stars";
"lng_credits_summary_earn_about" = "Distribute links to mini apps and earn a share of their revenue in Stars.";
"lng_credits_summary_history_tab_full" = "All Transactions";
"lng_credits_summary_history_tab_in" = "Incoming";
"lng_credits_summary_history_tab_out" = "Outgoing";
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance";
"lng_credits_commission" = "{amount} commission";
"lng_credits_more_options" = "More Options";
"lng_credits_balance_me" = "your balance";
"lng_credits_buy_button" = "Buy More Stars";
@ -2475,6 +2624,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";
@ -2484,6 +2634,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}";
@ -2499,6 +2650,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
"lng_credits_box_history_entry_id" = "Transaction ID";
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
"lng_credits_box_history_entry_reason_star_ref" = "Affiliate Program";
"lng_credits_box_history_entry_affiliate" = "Affiliate";
"lng_credits_box_history_entry_miniapp" = "Mini App";
"lng_credits_box_history_entry_referred" = "Referred User";
"lng_credits_box_history_entry_success_date" = "Transaction date";
"lng_credits_box_history_entry_success_url" = "Transaction link";
"lng_credits_box_history_entry_media" = "Media";
@ -2507,6 +2662,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";
@ -3077,27 +3233,59 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_sold_out" = "sold out";
"lng_gift_stars_tabs_all" = "All Gifts";
"lng_gift_stars_tabs_limited" = "Limited";
"lng_gift_stars_tabs_in_stock" = "In Stock";
"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.";
"lng_gift_availability" = "Availability";
"lng_gift_from_hidden" = "Hidden User";
"lng_gift_visibility" = "Visibility";
"lng_gift_visibility_shown" = "Visible on your page";
"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";
"lng_gift_display_on_page" = "Display on my Page";
"lng_gift_display_on_page_hide" = "Hide from my Page";
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
@ -3117,6 +3305,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.";
@ -3373,6 +3600,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_replies_no_comments" = "No comments here yet...";
"lng_verification_codes" = "Verification Codes";
"lng_verification_codes_about" = "Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn't request any codes — don't worry! Most likely, someone made a mistake when entering their number.";
"lng_archived_name" = "Archived chats";
"lng_archived_add" = "Archive";
@ -3547,6 +3775,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_mark_read_sure" = "Are you sure you want to mark all chats from this folder as read?";
"lng_context_mark_read_all" = "Mark all chats as read";
"lng_context_mark_read_all_sure" = "Are you sure you want to mark all chats as read?";
"lng_context_mark_read_all_sure_2" = "**This action cannot be undone.**";
"lng_context_mark_read_mentions_all" = "Mark all mentions as read";
"lng_context_mark_read_reactions_all" = "Read all reactions";
"lng_context_archive_expand" = "Expand";
@ -3706,6 +3935,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_paid_react_agree_link" = "Terms of Service";
"lng_paid_react_toast#one" = "Star Sent!";
"lng_paid_react_toast#other" = "Stars Sent!";
"lng_paid_react_toast_anonymous#one" = "Star sent anonymously!";
"lng_paid_react_toast_anonymous#other" = "Stars sent anonymously!";
"lng_paid_react_toast_text#one" = "You reacted with **{count} Star**.";
"lng_paid_react_toast_text#other" = "You reacted with **{count} Stars**.";
"lng_paid_react_undo" = "Undo";
@ -3916,6 +4147,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...";
@ -4812,6 +5047,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forward_show_captions" = "Show captions";
"lng_forward_change_recipient" = "Change recipient";
"lng_forward_sender_names_removed" = "Sender names removed";
"lng_forward_header_short" = "Forward";
"lng_forward_action_show_sender" = "Show Sender Name";
"lng_forward_action_show_senders" = "Show Sender Names";
"lng_forward_action_hide_sender" = "Hide Sender Name";
"lng_forward_action_hide_senders" = "Hide Sender Names";
"lng_forward_action_show_caption" = "Show Caption";
"lng_forward_action_show_captions" = "Show Captions";
"lng_forward_action_hide_caption" = "Hide Caption";
"lng_forward_action_hide_captions" = "Hide Captions";
"lng_forward_action_change_recipient" = "Change Recipient";
"lng_forward_action_remove" = "Do Not Forward";
"lng_passport_title" = "Telegram Passport";
"lng_passport_request1" = "{bot} requests access to your personal data";
@ -5113,6 +5359,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";
@ -5155,11 +5403,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_view_subtitle" = "Tabs view";
"lng_filters_vertical" = "Tabs on the left";
"lng_filters_horizontal" = "Tabs at the top";
"lng_filters_enable_tags" = "Show Folder Tags";
"lng_filters_enable_tags_about" = "Display folder names for each chat in the chat list.";
"lng_filters_enable_tags_about_premium" = "Subscribe to **{link}** to display folder names for each chat in the chat list.";
"lng_filters_tag_color_subtitle" = "Folder color in chat list";
"lng_filters_tag_color_about" = "Choose a color for the tag of this folder.";
"lng_filters_tag_color_no" = "No Tag";
"lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder.";
"lng_filters_link" = "Share Folder";
"lng_filters_link_has" = "Invite links";
"lng_filters_checkbox_remove_bot" = "Remove bot from all folders";
"lng_filters_checkbox_remove_group" = "Remove group from all folders";
"lng_filters_checkbox_remove_channel" = "Remove channel from all folders";
"lng_filters_link_create" = "Create an Invite Link";
"lng_filters_link_cant" = "You cant share folders which include or exclude specific chat types like 'Groups', 'Contacts', etc.";
"lng_filters_link_about" = "Share access to some of this folder's groups and channels with others.";
@ -5705,6 +5963,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_recent_chats" = "Chats";
"lng_recent_channels" = "Channels";
"lng_recent_apps" = "Apps";
"lng_all_photos" = "Photos";
"lng_all_videos" = "Videos";
"lng_all_downloads" = "Downloads";
"lng_all_links" = "Links";
"lng_all_files" = "Files";
"lng_all_music" = "Music";
"lng_all_voice" = "Voice";
"lng_channels_none_title" = "No channels yet...";
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
"lng_channels_your_title" = "Channels you joined";
@ -5738,6 +6003,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

@ -29,6 +29,7 @@
<file alias="search.tgs">../../animations/search.tgs</file>
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
<file alias="starref_link.tgs">../../animations/starref_link.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>

View file

@ -4,6 +4,7 @@
<file alias="art/bg_thumbnail.png">../../art/bg_thumbnail.png</file>
<file alias="art/bg_initial.jpg">../../art/bg_initial.jpg</file>
<file alias="art/business_logo.png">../../art/business_logo.png</file>
<file alias="art/affiliate_logo.png">../../art/affiliate_logo.png</file>
<file alias="art/logo_256.png">../../art/logo_256.png</file>
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.8.3.0" />
Version="5.10.3.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,8,3,0
PRODUCTVERSION 5,8,3,0
FILEVERSION 5,10,3,0
PRODUCTVERSION 5,10,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.8.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "FileVersion", "5.10.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.8.3.0"
VALUE "ProductVersion", "5.10.3.0"
END
END
BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,8,3,0
PRODUCTVERSION 5,8,3,0
FILEVERSION 5,10,3,0
PRODUCTVERSION 5,10,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.8.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "FileVersion", "5.10.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.8.3.0"
VALUE "ProductVersion", "5.10.3.0"
END
END
BLOCK "VarFileInfo"

View file

@ -7,12 +7,15 @@ 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"
#include "boxes/premium_limits_box.h"
#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"
@ -24,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"
@ -48,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);
@ -74,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;
@ -105,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,
@ -137,23 +140,33 @@ void InitFilterLinkHeader(
not_null<PeerListBox*> box,
Fn<void(int minHeight, int maxHeight, int addedTopHeight)> adjust,
Ui::FilterLinkHeaderType type,
const QString &title,
const QString &iconEmoji,
rpl::producer<int> count) {
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)
: rpl::single(0)),
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
.horizontalFilters = horizontalFilters,
});
const auto widget = header.widget;
widget->resizeToWidth(st::boxWideWidth);
@ -246,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);
@ -527,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();
@ -538,22 +550,55 @@ 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) {
const auto isEnter = [=](not_null<QEvent*> event) {
if (event->type() == QEvent::KeyPress) {
if (const auto k = static_cast<QKeyEvent*>(event.get())) {
return (k->key() == Qt::Key_Enter)
|| (k->key() == Qt::Key_Return);
}
}
return false;
};
base::install_event_filter(box, [=](not_null<QEvent*> event) {
if (isEnter(event)) {
box->triggerButton(0);
return base::EventFilterResult::Cancel;
}
return base::EventFilterResult::Continue;
});
}
void ProcessFilterInvite(
base::weak_ptr<Window::SessionController> weak,
const QString &slug,
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();
@ -572,6 +617,8 @@ void ProcessFilterInvite(
title,
std::move(peers),
std::move(already));
const auto horizontalFilters = !strong->enoughSpaceForFilters()
|| Core::App().settings().chatFiltersHorizontal();
const auto raw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->setStyle(st::filterInviteBox);
@ -588,14 +635,23 @@ void ProcessFilterInvite(
});
InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
raw->adjust(min, max, addedTop);
}, type, title, iconEmoji, rpl::duplicate(badge));
}, type, title, iconEmoji, rpl::duplicate(badge), horizontalFilters);
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();
@ -610,6 +666,8 @@ void ProcessFilterInvite(
box->addButton(std::move(owned));
HandleEnterInBox(box);
struct State {
bool importing = false;
};
@ -694,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*>>();
@ -713,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) {
@ -778,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) {
@ -798,6 +857,8 @@ void ProcessFilterRemove(
title,
std::move(suggest),
std::move(all));
const auto horizontalFilters = !strong->enoughSpaceForFilters()
|| Core::App().settings().chatFiltersHorizontal();
const auto raw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->setStyle(st::filterInviteBox);
@ -809,12 +870,21 @@ void ProcessFilterRemove(
});
InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
raw->adjust(min, max, addedTop);
}, type, title, iconEmoji, rpl::single(0));
}, 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();
@ -829,6 +899,8 @@ void ProcessFilterRemove(
box->addButton(std::move(owned));
HandleEnterInBox(box);
raw->selectedValue(
) | rpl::start_with_next([=](
base::flat_set<not_null<PeerData*>> &&peers) {

View file

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

@ -207,32 +207,12 @@ void ConfirmSubscriptionBox(
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWideWidth - photoSize,
photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
Settings::AddMiniStars(
content,
Ui::CreateChild<Ui::RpWidget>(content),
photoSize,
box->width(),
2.);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(

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,9 +74,27 @@ 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 incoming = (int64(tl.data().vstars().v) >= 0);
const auto amount = Data::FromTL(tl.data().vstars());
const auto starrefAmount = tl.data().vstarref_amount()
? Data::FromTL(*tl.data().vstarref_amount())
: StarsAmount();
const auto starrefCommission
= tl.data().vstarref_commission_permille().value_or_empty();
const auto starrefBarePeerId = tl.data().vstarref_peer()
? peerFromMTP(*tl.data().vstarref_peer()).value
: 0;
const auto incoming = (amount >= StarsAmount());
const auto saveActorId = (reaction || !extended.empty()) && incoming;
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()),
@ -83,15 +102,17 @@ constexpr auto kTransactionsLimit = 100;
.date = base::unixtime::parse(tl.data().vdate().v),
.photoId = photo ? photo->id : 0,
.extended = std::move(extended),
.credits = tl.data().vstars().v,
.credits = Data::FromTL(tl.data().vstars()),
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = saveActorId ? peer->id.value : barePeerId,
.bareGiveawayMsgId = uint64(
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,
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@ -117,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(),
@ -181,7 +203,7 @@ constexpr auto kTransactionsLimit = 100;
return Data::CreditsStatusSlice{
.list = std::move(entries),
.subscriptions = std::move(subscriptions),
.balance = status.data().vbalance().v,
.balance = Data::FromTL(status.data().vbalance()),
.subscriptionsMissingBalance
= status.data().vsubscriptions_missing_balance().value_or_empty(),
.allLoaded = !status.data().vnext_offset().has_value()
@ -268,8 +290,8 @@ void CreditsStatus::request(
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
)).done([=](const TLResult &result) {
_requestId = 0;
const auto balance = result.data().vbalance().v;
_peer->session().credits().apply(_peer->id, balance);
const auto &balance = result.data().vbalance();
_peer->session().credits().apply(_peer->id, Data::FromTL(balance));
if (const auto onstack = done) {
onstack(StatusFromTL(result, _peer));
}
@ -348,7 +370,9 @@ rpl::producer<not_null<PeerData*>> PremiumPeerBot(
const auto api = lifetime.make_state<MTP::Sender>(&session->mtp());
api->request(MTPcontacts_ResolveUsername(
MTP_string(username)
MTP_flags(0),
MTP_string(username),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
session->data().processUsers(result.data().vusers());
session->data().processChats(result.data().vchats());
@ -380,12 +404,13 @@ rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
)).done([=](const MTPpayments_StarsRevenueStats &result) {
const auto &data = result.data();
const auto &status = data.vstatus().data();
using Data::FromTL;
_data = Data::CreditsEarnStatistics{
.revenueGraph = StatisticalGraphFromTL(
data.vrevenue_graph()),
.currentBalance = status.vcurrent_balance().v,
.availableBalance = status.vavailable_balance().v,
.overallRevenue = status.voverall_revenue().v,
.currentBalance = FromTL(status.vcurrent_balance()),
.availableBalance = FromTL(status.vavailable_balance()),
.overallRevenue = FromTL(status.voverall_revenue()),
.usdRate = data.vusd_rate().v,
.isWithdrawalEnabled = status.is_withdrawal_enabled(),
.nextWithdrawalAt = status.vnext_withdrawal_at()

View file

@ -74,12 +74,17 @@ const FoundMessages &MessagesSearchMerged::messages() const {
return _concatedFound;
}
const MessagesSearch::Request &MessagesSearchMerged::request() const {
return _request;
}
void MessagesSearchMerged::clear() {
_concatedFound = {};
_migratedFirstFound = {};
}
void MessagesSearchMerged::search(const Request &search) {
_request = search;
if (_migratedSearch) {
_waitingForTotal = true;
_migratedSearch->searchMessages(search);

View file

@ -31,6 +31,7 @@ public:
void searchMore();
[[nodiscard]] const FoundMessages &messages() const;
[[nodiscard]] const Request &request() const;
[[nodiscard]] rpl::producer<> newFounds() const;
[[nodiscard]] rpl::producer<> nextFounds() const;
@ -39,6 +40,7 @@ private:
void addFound(const FoundMessages &data);
MessagesSearch _apiSearch;
Request _request;
std::optional<MessagesSearch> _migratedSearch;
FoundMessages _migratedFirstFound;

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

@ -181,7 +181,9 @@ std::optional<HistoryItem*> SingleMessageSearch::performLookupByUsername(
ready();
};
_requestId = _session->api().request(MTPcontacts_ResolveUsername(
MTP_string(username)
MTP_flags(0),
MTP_string(username),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
result.match([&](const MTPDcontacts_resolvedPeer &data) {
_session->data().processUsers(data.vusers());

View file

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

@ -1226,7 +1226,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;
@ -1263,7 +1264,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

@ -756,19 +756,32 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
const style::WhoRead &st) {
return WhoReacted(item, reaction, context, st, nullptr);
}
rpl::producer<Ui::WhoReadContent> WhenEdited(
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenDate(
not_null<PeerData*> author,
TimeId date) {
TimeId date,
Ui::WhoReadType type) {
return rpl::single(Ui::WhoReadContent{
.participants = { Ui::WhoReadParticipant{
.name = author->name(),
.date = FormatReadDate(date, QDateTime::currentDateTime()),
.id = author->id.value,
} },
.type = Ui::WhoReadType::Edited,
.type = type,
.fullReadCount = 1,
});
}
rpl::producer<Ui::WhoReadContent> WhenEdited(
not_null<PeerData*> author,
TimeId date) {
return WhenDate(author, date, Ui::WhoReadType::Edited);
}
rpl::producer<Ui::WhoReadContent> WhenOriginal(
not_null<PeerData*> author,
TimeId date) {
return WhenDate(author, date, Ui::WhoReadType::Original);
}
} // namespace Api

View file

@ -64,5 +64,8 @@ struct WhoReadList {
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
not_null<PeerData*> author,
TimeId date);
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenOriginal(
not_null<PeerData*> author,
TimeId date);
} // namespace Api

View file

@ -3263,6 +3263,31 @@ void ApiWrap::sharedMediaDone(
}
}
mtpRequestId ApiWrap::requestGlobalMedia(
Storage::SharedMediaType type,
const QString &query,
int32 offsetRate,
Data::MessagePosition offsetPosition,
Fn<void(Api::GlobalMediaResult)> done) {
auto prepared = Api::PrepareGlobalMediaRequest(
_session,
offsetRate,
offsetPosition,
type,
query);
if (!prepared) {
done({});
return 0;
}
return request(
std::move(*prepared)
).done([=](const Api::SearchRequestResult &result) {
done(Api::ParseGlobalMediaResult(_session, result));
}).fail([=] {
done({});
}).send();
}
void ApiWrap::sendAction(const SendAction &action) {
if (!action.options.scheduled
&& !action.options.shortcutId
@ -3286,13 +3311,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

@ -59,6 +59,7 @@ class Show;
namespace Api {
struct SearchResult;
struct GlobalMediaResult;
class Updates;
class Authorizations;
@ -288,6 +289,12 @@ public:
Storage::SharedMediaType type,
MsgId messageId,
SliceType slice);
mtpRequestId requestGlobalMedia(
Storage::SharedMediaType type,
const QString &query,
int32 offsetRate,
Data::MessagePosition offsetPosition,
Fn<void(Api::GlobalMediaResult)> done);
void readFeaturedSetDelayed(uint64 setId);
@ -509,6 +516,10 @@ private:
MsgId topicRootId,
SharedMediaType type,
Api::SearchResult &&parsed);
void globalMediaDone(
SharedMediaType type,
FullMsgId messageId,
Api::GlobalMediaResult &&parsed);
void sendSharedContact(
const QString &phone,
@ -672,6 +683,17 @@ private:
};
base::flat_set<HistoryRequest> _historyRequests;
struct GlobalMediaRequest {
SharedMediaType mediaType = {};
FullMsgId aroundId;
SliceType sliceType = {};
friend inline auto operator<=>(
const GlobalMediaRequest&,
const GlobalMediaRequest&) = default;
};
base::flat_set<GlobalMediaRequest> _globalMediaRequests;
std::unique_ptr<DialogsLoadState> _dialogsLoadState;
TimeId _dialogsLoadTill = 0;
rpl::variable<bool> _dialogsLoadMayBlockByDate = false;

View file

@ -262,10 +262,16 @@ void ShowAddParticipantsError(
return tr::lng_bot_already_in_group(tr::now);
} else if (error == u"BOT_GROUPS_BLOCKED"_q) {
return tr::lng_error_cant_add_bot(tr::now);
} else if (error == u"ADMINS_TOO_MUCH"_q) {
return ((chat->isChat() || chat->isMegagroup())
? tr::lng_error_admin_limit
: tr::lng_error_admin_limit_channel)(tr::now);
} else if (error == u"YOU_BLOCKED_USER"_q) {
return tr::lng_error_you_blocked_user(tr::now);
} else if (error == u"CHAT_ADMIN_INVITE_REQUIRED"_q) {
return tr::lng_error_add_admin_not_member(tr::now);
} else if (error == u"USER_ADMIN_INVALID"_q) {
return tr::lng_error_user_admin_invalid(tr::now);
} else if (error == u"BOTS_TOO_MUCH"_q) {
return (chat->isChannel()
? tr::lng_error_channel_bots_too_much
: tr::lng_error_group_bots_too_much)(tr::now);
}
return tr::lng_failed_add_participant(tr::now);
}();

View file

@ -8,22 +8,108 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/choose_filter_box.h"
#include "apiwrap.h"
#include "boxes/filters/edit_filter_box.h"
#include "boxes/premium_limits_box.h"
#include "core/application.h" // primaryWindow
#include "core/ui_integration.h"
#include "data/data_chat_filters.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/empty_userpic.h"
#include "ui/filter_icons.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h" // Ui::Text::Bold
#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_dialogs.h"
#include "styles/style_media_player.h" // mediaPlayerMenuCheck
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
namespace {
[[nodiscard]] QImage Icon(const Data::ChatFilter &f) {
constexpr auto kScale = 0.75;
const auto icon = Ui::LookupFilterIcon(Ui::ComputeFilterIcon(f)).normal;
const auto originalWidth = icon->width();
const auto originalHeight = icon->height();
const auto scaledWidth = int(originalWidth * kScale);
const auto scaledHeight = int(originalHeight * kScale);
auto image = QImage(
scaledWidth * style::DevicePixelRatio(),
scaledHeight * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(style::DevicePixelRatio());
image.fill(Qt::transparent);
{
auto p = QPainter(&image);
auto hq = PainterHighQualityEnabler(p);
const auto x = int((scaledWidth - originalWidth * kScale) / 2);
const auto y = int((scaledHeight - originalHeight * kScale) / 2);
p.scale(kScale, kScale);
icon->paint(p, x, y, scaledWidth, st::dialogsUnreadBgMuted->c);
if (const auto color = f.colorIndex()) {
p.resetTransform();
const auto circleSize = scaledWidth / 3.;
const auto r = QRectF(
x + scaledWidth - circleSize,
y + scaledHeight - circleSize - circleSize / 3.,
circleSize,
circleSize);
p.setPen(Qt::NoPen);
p.setCompositionMode(QPainter::CompositionMode_Clear);
p.setBrush(Qt::transparent);
p.drawEllipse(r + Margins(st::lineWidth * 1.5));
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
p.setBrush(Ui::EmptyUserpic::UserpicColor(*color).color2);
p.drawEllipse(r);
}
}
return image;
}
class FilterAction : public Ui::Menu::Action {
public:
using Ui::Menu::Action::Action;
void setIcon(QImage &&image) {
_icon = std::move(image);
}
protected:
void paintEvent(QPaintEvent *event) override {
Ui::Menu::Action::paintEvent(event);
if (!_icon.isNull()) {
const auto size = _icon.size() / style::DevicePixelRatio();
auto p = QPainter(this);
p.drawImage(
width()
- size.width()
- st::menuWithIcons.itemPadding.right(),
(height() - size.height()) / 2,
_icon);
}
}
private:
QImage _icon;
};
Data::ChatFilter ChangedFilter(
const Data::ChatFilter &filter,
not_null<History*> history,
@ -85,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")
@ -126,9 +223,7 @@ bool ChooseFilterValidator::canRemove(FilterId filterId) const {
const auto list = _history->owner().chatsFilters().list();
const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
if (i != end(list)) {
const auto &filter = *i;
return filter.contains(_history)
&& ((filter.always().size() > 1) || filter.flags());
return Data::CanRemoveFromChatFilter(*i, _history);
}
return false;
}
@ -164,14 +259,15 @@ void FillChooseFilterMenu(
not_null<History*> history) {
const auto weak = base::make_weak(controller);
const auto validator = ChooseFilterValidator(history);
for (const auto &filter : history->owner().chatsFilters().list()) {
const auto &list = history->owner().chatsFilters().list();
const auto showColors = history->owner().chatsFilters().tagsEnabled();
for (const auto &filter : list) {
const auto id = filter.id();
if (!id) {
continue;
}
const auto contains = filter.contains(history);
const auto action = menu->addAction(filter.title(), [=] {
auto callback = [=] {
const auto toAdd = !filter.contains(history);
const auto r = validator.limitReached(id, toAdd);
if (r.reached) {
@ -188,11 +284,63 @@ void FillChooseFilterMenu(
validator.remove(id);
}
}
}, contains ? &st::mediaPlayerMenuCheck : nullptr);
};
const auto contains = filter.contains(history);
const auto title = filter.title();
auto item = base::make_unique_q<FilterAction>(
menu.get(),
st::foldersMenu,
Ui::Menu::CreateAction(
menu.get(),
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 action = menu->addAction(std::move(item));
action->setEnabled(contains
? validator.canRemove(id)
: validator.canAdd());
}
const auto limit = [session = &controller->session()] {
return Data::PremiumLimits(session).dialogFiltersCurrent();
};
if ((list.size() - 1) < limit()) {
menu->addAction(tr::lng_filters_create(tr::now), [=] {
const auto strong = weak.get();
if (!strong) {
return;
}
const auto session = &strong->session();
const auto count = session->data().chatsFilters().list().size();
if ((count - 1) >= limit()) {
return;
}
auto filter =
Data::ChatFilter({}, {}, {}, {}, {}, { history }, {}, {});
const auto send = [=](const Data::ChatFilter &filter) {
session->api().request(MTPmessages_UpdateDialogFilter(
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
MTP_int(count),
filter.tl()
)).done([=] {
session->data().chatsFilters().reload();
}).send();
};
strong->uiShow()->show(
Box(EditFilterBox, strong, std::move(filter), send, nullptr));
}, &st::menuIconShowInFolder);
}
history->owner().chatsFilters().changed(
) | rpl::start_with_next([=] {
menu->hideMenu();

View file

@ -254,7 +254,7 @@ EditCaptionBox::EditCaptionBox(
, _initialList(std::move(list))
, _saved(std::move(saved)) {
Expects(!_initialList.files.empty());
Expects(!item->media() || item->media()->allowsEditCaption());
Expects(item->allowsEditMedia());
_mediaEditManager.start(item, spoilered, invertCaption);

View file

@ -7,22 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/filters/edit_filter_box.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "boxes/filters/edit_filter_chats_list.h"
#include "boxes/filters/edit_filter_chats_preview.h"
#include "boxes/filters/edit_filter_links.h"
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_options.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/effects/panel_animation.h"
#include "ui/filter_icons.h"
#include "ui/filter_icon_panel.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "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"
@ -30,22 +26,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "settings/settings_common.h"
#include "base/event_filter.h"
#include "lang/lang_keys.h"
#include "history/history.h"
#include "info/userpic/info_userpic_color_circle_button.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "settings/settings_common.h"
#include "ui/chat/chats_filter_tag.h"
#include "ui/effects/animation_value_f.h"
#include "ui/effects/animations.h"
#include "ui/effects/panel_animation.h"
#include "ui/empty_userpic.h"
#include "ui/filter_icon_panel.h"
#include "ui/filter_icons.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/slide_wrap.h"
#include "window/window_controller.h"
#include "apiwrap.h"
#include "window/window_session_controller.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_dialogs.h"
#include "styles/style_layers.h"
#include "styles/style_window.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
#include "styles/style_info_userpic_builder.h"
namespace {
@ -336,6 +344,8 @@ void EditFilterBox(
const Data::ChatFilter &data,
Fn<void(Data::ChatFilter)> next)> saveAnd) {
using namespace rpl::mappers;
constexpr auto kColorsCount = 8;
constexpr auto kNoTag = kColorsCount - 1;
struct State {
rpl::variable<Data::ChatFilter> rules;
@ -343,13 +353,19 @@ 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()),
state->hasLinks = state->links.value() | rpl::map([=](const auto &v) {
return !v.empty();
@ -385,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;
@ -421,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;
@ -434,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,
@ -504,10 +569,184 @@ void EditFilterBox(
Ui::AddDividerText(excludeInner, tr::lng_filters_exclude_about());
Ui::AddSkip(excludeInner);
{
const auto wrap = content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
content,
object_ptr<Ui::VerticalLayout>(content)));
const auto colors = wrap->entity();
const auto session = &window->session();
wrap->toggleOn(
rpl::combine(
session->premiumPossibleValue(),
session->data().chatsFilters().tagsEnabledValue(),
Data::AmPremiumValue(session)
) | rpl::map([=] (bool possible, bool tagsEnabled, bool premium) {
return possible && (tagsEnabled || !premium);
}),
anim::type::instant);
const auto isPremium = session->premium();
const auto title = Ui::AddSubsectionTitle(
colors,
tr::lng_filters_tag_color_subtitle());
const auto preview = Ui::CreateChild<Ui::RpWidget>(colors);
title->geometryValue(
) | rpl::start_with_next([=](const QRect &r) {
const auto h = st::normalFont->height;
preview->setGeometry(
colors->x(),
r.y() + (r.height() - h) / 2 + st::lineWidth,
colors->width(),
h);
}, preview->lifetime());
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(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(), tag->frame);
if (p.opacity() < 1) {
p.setOpacity(1. - p.opacity());
p.setFont(st::normalFont);
p.setPen(st::windowSubTextFg);
p.drawText(
preview->rect() - st::boxRowPadding,
tr::lng_filters_tag_color_no(tr::now),
style::al_right);
}
}, preview->lifetime());
const auto side = st::userpicBuilderEmojiAccentColorSize;
const auto line = colors->add(
Ui::CreateSkipWidget(colors, side),
st::boxRowPadding);
auto buttons = std::vector<not_null<UserpicBuilder::CircleButton*>>();
const auto palette = [](int i) {
return Ui::EmptyUserpic::UserpicColor(i).color2;
};
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) {
const auto button = Ui::CreateChild<UserpicBuilder::CircleButton>(
line);
button->resize(side, side);
const auto progress = isPremium
? (state->colorIndex.current() == i)
: (i == kNoTag);
button->setSelectedProgress(progress);
const auto color = palette(i);
button->setBrush(color);
if (progress == 1) {
tag->context.color = color->c;
tag->frame = Ui::ChatsFilterTag(
upperTitle(),
tag->context);
if (i == kNoTag) {
tag->alpha = 0.;
}
}
buttons.push_back(button);
}
for (auto i = 0; i < kColorsCount; ++i) {
const auto &button = buttons[i];
button->setClickedCallback([=] {
const auto was = state->colorIndex.current();
const auto now = i;
if (was != now) {
const auto c1 = palette(was);
const auto c2 = palette(now);
const auto a1 = (was == kNoTag) ? 0. : 1.;
const auto a2 = (now == kNoTag) ? 0. : 1.;
tag->animation.stop();
tag->animation.start([=](float64 progress) {
if (was >= 0) {
buttons[was]->setSelectedProgress(1. - progress);
}
buttons[now]->setSelectedProgress(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);
}
state->colorIndex = now;
});
if (!session->premium()) {
button->setClickedCallback([w = window] {
ShowPremiumPreviewToBuy(w, PremiumFeature::FilterTags);
});
}
}
line->sizeValue() | rpl::start_with_next([=](const QSize &size) {
const auto totalWidth = buttons.size() * side;
const auto spacing = (size.width() - totalWidth)
/ (buttons.size() - 1);
for (auto i = 0; i < kColorsCount; ++i) {
const auto &button = buttons[i];
button->moveToLeft(i * (side + spacing), 0);
}
}, line->lifetime());
{
const auto last = buttons.back();
const auto icon = Ui::CreateChild<Ui::RpWidget>(last);
icon->resize(side, side);
icon->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(icon);
(session->premium()
? st::windowFilterSmallRemove.icon
: st::historySendDisabledIcon).paintInCenter(
p,
QRectF(icon->rect()),
st::historyPeerUserpicFg->c);
}, icon->lifetime());
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
last->setBrush(st::historyPeerArchiveUserpicBg);
}
Ui::AddSkip(colors);
Ui::AddSkip(colors);
Ui::AddDividerText(colors, tr::lng_filters_tag_color_about());
Ui::AddSkip(colors);
}
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
const auto title = name->getLastText().trimmed();
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 {};
@ -520,7 +759,13 @@ void EditFilterBox(
window->window().showToast(tr::lng_filters_default(tr::now));
return {};
}
return rules.withTitle(title);
const auto rawColorIndex = state->colorIndex.current();
const auto colorIndex = (rawColorIndex >= kNoTag
? std::nullopt
: std::make_optional(rawColorIndex));
return rules.withTitle(
{ std::move(title), staticTitle }
).withColorIndex(colorIndex);
};
Ui::AddSubsectionTitle(

View file

@ -7,12 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/layers/generic_box.h"
namespace Window {
class SessionController;
} // namespace Window
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Data {
class ChatFilter;
} // namespace Data

View file

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

@ -47,6 +47,7 @@ void GiftCreditsBox(
const auto content = box->setPinnedToTopContent(
object_ptr<Ui::VerticalLayout>(box));
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto &stUser = st::premiumGiftsUserpicButton;
@ -58,39 +59,19 @@ void GiftCreditsBox(
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWidth - stUser.photoSize,
stUser.photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(stUser.photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
Settings::AddMiniStars(
content,
Ui::CreateChild<Ui::RpWidget>(content),
stUser.photoSize,
box->width(),
2.);
{
Ui::AddSkip(content);
const auto arrow = Ui::Text::SingleCustomEmoji(
peer->owner().customEmojiManager().registerInternalEmoji(
st::topicButtonArrow,
st::channelEarnLearnArrowMargins,
false));
true));
auto link = tr::lng_credits_box_history_entry_gift_about_link(
lt_emoji,
rpl::single(arrow),
@ -122,7 +103,7 @@ void GiftCreditsBox(
Main::MakeSessionShow(box->uiShow(), &peer->session()),
box->verticalLayout(),
peer,
0,
StarsAmount(),
[=] { gifted(); box->uiShow()->hideLayer(); },
tr::lng_credits_summary_options_subtitle(),
{});

View file

@ -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,7 +242,58 @@ void AddTableRow(
valueMargins);
}
object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
[[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,
const Data::CreditsHistoryEntry &entry,
@ -255,8 +311,8 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
auto star = session->data().customEmojiManager().creditsEmoji();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
rpl::single(
star.append(' ' + Lang::FormatCountDecimal(entry.credits))),
rpl::single(star.append(
' ' + Lang::FormatStarsAmountDecimal(entry.credits))),
st::giveawayGiftCodeValue,
st::defaultPopupMenu,
std::move(makeContext));
@ -302,6 +358,107 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
return result;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeVisibilityTableValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
bool savedToProfile,
Fn<void(bool)> toggleVisibility) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
(savedToProfile
? tr::lng_gift_visibility_shown()
: tr::lng_gift_visibility_hidden()),
st::giveawayGiftCodeValue,
st::defaultPopupMenu);
const auto toggle = Ui::CreateChild<Ui::RoundButton>(
raw,
(savedToProfile
? tr::lng_gift_visibility_hide()
: tr::lng_gift_visibility_show()),
st::starGiftSmallButton);
toggle->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
toggle->setClickedCallback([=] {
toggleVisibility(!savedToProfile);
});
rpl::combine(
raw->widthValue(),
toggle->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);
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) {
raw->resize(
raw->width(),
height + st::giveawayGiftCodeValueMargin.bottom());
}, raw->lifetime());
label->setAttribute(Qt::WA_TransparentForMouseEvents);
return result;
}
not_null<Ui::FlatLabel*> AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
@ -1035,7 +1192,9 @@ void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry,
Fn<void()> convertToStars) {
Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars,
Fn<void()> startUpgrade) {
auto table = container->add(
object_ptr<Ui::TableLayout>(
container,
@ -1043,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,
@ -1058,23 +1244,109 @@ 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))));
}
{
const auto margin = st::giveawayGiftCodeValueMargin
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
if (!unique && !entry.date.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
}
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(),
@ -1083,34 +1355,124 @@ void AddStarGiftTable(
controller,
entry,
std::move(convertToStars)),
margin);
marginWithButton);
}
if (!entry.date.isNull()) {
if (toggleVisibility) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
tr::lng_gift_visibility(),
MakeVisibilityTableValue(
table,
controller,
entry.savedToProfile,
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,
@ -1146,10 +1508,46 @@ void AddCreditsHistoryEntryTable(
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
const auto actorId = PeerId(entry.bareActorId);
const auto starrefRecipientId = PeerId(entry.starrefRecipientId);
const auto session = &controller->session();
if (actorId || peerId) {
auto text = entry.in
if (entry.starrefCommission) {
if (entry.starrefAmount) {
AddTableRow(
table,
tr::lng_star_ref_commission_title(),
rpl::single(TextWithEntities{
QString::number(entry.starrefCommission / 10.) + '%' }));
} else {
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_credits_box_history_entry_reason_star_ref(
Ui::Text::WithEntities));
}
}
if (starrefRecipientId && entry.starrefAmount) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_affiliate(),
controller,
starrefRecipientId);
}
if (peerId && entry.starrefCommission) {
AddTableRow(
table,
(entry.starrefAmount
? tr::lng_credits_box_history_entry_referred
: tr::lng_credits_box_history_entry_miniapp)(),
controller,
peerId);
}
if (actorId || (!entry.starrefCommission && peerId)) {
auto text = entry.starrefCommission
? tr::lng_credits_box_history_entry_referred()
: entry.in
? tr::lng_credits_box_history_entry_peer_in()
: entry.giftUpgraded
? tr::lng_credits_box_history_entry_gift_from()
: tr::lng_credits_box_history_entry_peer();
AddTableRow(
table,
@ -1229,7 +1627,7 @@ void AddCreditsHistoryEntryTable(
tr::lng_gift_link_label_gift(),
tr::lng_gift_stars_title(
lt_count,
rpl::single(float64(entry.credits)),
rpl::single(entry.credits.value()),
Ui::Text::RichLangValue));
}
{

View file

@ -58,7 +58,9 @@ void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry,
Fn<void()> convertToStars);
Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars,
Fn<void()> startUpgrade);
void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,

View file

@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/local_storage_box.h"
#include "boxes/abstract_box.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/labels.h"
@ -21,8 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/cache/storage_cache_database.h"
#include "data/data_session.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@ -282,19 +281,19 @@ LocalStorageBox::LocalStorageBox(
_timeLimit = settings.totalTimeLimit;
}
void LocalStorageBox::Show(not_null<::Main::Session*> session) {
void LocalStorageBox::Show(not_null<Window::SessionController*> controller) {
auto shared = std::make_shared<object_ptr<LocalStorageBox>>(
Box<LocalStorageBox>(session, CreateTag()));
Box<LocalStorageBox>(&controller->session(), CreateTag()));
const auto weak = shared->data();
rpl::combine(
session->data().cache().statsOnMain(),
session->data().cacheBigFile().statsOnMain()
controller->session().data().cache().statsOnMain(),
controller->session().data().cacheBigFile().statsOnMain()
) | rpl::start_with_next([=](
Database::Stats &&stats,
Database::Stats &&statsBig) {
weak->update(std::move(stats), std::move(statsBig));
if (auto &strong = *shared) {
Ui::show(std::move(strong));
controller->uiShow()->show(std::move(strong));
}
}, weak->lifetime());
}

View file

@ -14,6 +14,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Window {
class SessionController;
} // namespace Window
namespace Storage {
namespace Cache {
class Database;
@ -40,7 +44,7 @@ public:
not_null<Main::Session*> session,
CreateTag);
static void Show(not_null<Main::Session*> session);
static void Show(not_null<Window::SessionController*> controller);
protected:
void prepare() override;

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_chat_filters.h"
#include "data/data_chat_participant_status.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
@ -86,19 +87,35 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
return result;
}
[[nodiscard]] rpl::producer<int> MessagesCountValue(
[[nodiscard]] rpl::producer<base::flat_map<PeerId, int>> MessagesCountValue(
not_null<History*> history,
not_null<PeerData*> from) {
std::vector<not_null<PeerData*>> from) {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
auto search = lifetime.make_state<Api::MessagesSearch>(history);
consumer.put_next(0);
search->messagesFounds(
) | rpl::start_with_next([=](const Api::FoundMessages &found) {
consumer.put_next_copy(found.total);
}, lifetime);
search->searchMessages({ .from = from });
struct State final {
base::flat_map<PeerId, int> messagesCounts;
int index = 0;
rpl::lifetime apiLifetime;
};
const auto search = lifetime.make_state<Api::MessagesSearch>(history);
const auto state = lifetime.make_state<State>();
const auto send = [=](auto repeat) -> void {
if (state->index >= from.size()) {
consumer.put_next_copy(state->messagesCounts);
return;
}
const auto peer = from[state->index];
const auto peerId = peer->id;
state->apiLifetime = search->messagesFounds(
) | rpl::start_with_next([=](const Api::FoundMessages &found) {
state->messagesCounts[peerId] = found.total;
state->index++;
repeat(repeat);
});
search->searchMessages({ .from = peer });
};
consumer.put_next({});
send(send);
return lifetime;
};
@ -273,15 +290,50 @@ void CreateModerateMessagesBox(
false,
st::defaultBoxCheckbox),
st::boxRowPadding + buttonPadding);
if (isSingle) {
const auto history = items.front()->history();
const auto history = items.front()->history();
auto messagesCounts = MessagesCountValue(history, participants);
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{
.messagesCounts = rpl::duplicate(messagesCounts),
.participants = participants,
});
Ui::AddExpandablePeerList(deleteAll, controller, inner);
{
tr::lng_selected_delete_sure(
lt_count,
rpl::combine(
MessagesCountValue(history, participants.front()),
deleteAll->checkedValue()
) | rpl::map([s = items.size()](int all, bool checked) {
return float64((checked && all) ? all : s);
std::move(messagesCounts),
isSingle
? deleteAll->checkedValue()
: rpl::merge(
controller->toggleRequestsFromInner.events(),
controller->checkAllRequests.events())
) | rpl::map([=, s = items.size()](const auto &map, bool c) {
const auto checked = (isSingle && !c)
? Participants()
: controller->collectRequests
? controller->collectRequests()
: Participants();
auto result = 0;
for (const auto &[peerId, count] : map) {
for (const auto &peer : checked) {
if (peer->id == peerId) {
result += count;
break;
}
}
}
for (const auto &item : items) {
for (const auto &peer : checked) {
if (peer->id == item->from()->id) {
result--;
break;
}
}
result++;
}
return float64(result);
})
) | rpl::start_with_next([=](const QString &text) {
title->setText(text);
@ -289,10 +341,6 @@ void CreateModerateMessagesBox(
- rect::m::sum::h(st::boxRowPadding));
}, title->lifetime());
}
const auto controller = box->lifetime().make_state<Controller>(
Controller::Data{ .participants = participants });
Ui::AddExpandablePeerList(deleteAll, controller, inner);
handleSubmition(deleteAll);
handleConfirmation(deleteAll, controller, [=](
@ -512,6 +560,7 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
const auto container = box->verticalLayout();
const auto maybeUser = peer->asUser();
const auto isBot = maybeUser && maybeUser->isBot();
Ui::AddSkip(container);
Ui::AddSkip(container);
@ -595,7 +644,7 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
}();
const auto maybeBotCheckbox = [&]() -> Ui::Checkbox* {
if (!maybeUser || !maybeUser->isBot()) {
if (!isBot) {
return nullptr;
}
Ui::AddSkip(container);
@ -608,6 +657,40 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
st::defaultBoxCheckbox));
}();
const auto removeFromChatsFilters = [=](
not_null<History*> history) -> std::vector<FilterId> {
auto result = std::vector<FilterId>();
for (const auto &filter : peer->owner().chatsFilters().list()) {
if (filter.withoutAlways(history) != filter) {
result.push_back(filter.id());
}
}
return result;
};
const auto maybeChatsFiltersCheckbox = [&]() -> Ui::Checkbox* {
const auto history = (isBot || !maybeUser)
? peer->owner().history(peer).get()
: nullptr;
if (!history || removeFromChatsFilters(history).empty()) {
return nullptr;
}
Ui::AddSkip(container);
Ui::AddSkip(container);
return box->addRow(
object_ptr<Ui::Checkbox>(
container,
(maybeBotCheckbox
? tr::lng_filters_checkbox_remove_bot
: (peer->isChannel() && !peer->isMegagroup())
? tr::lng_filters_checkbox_remove_channel
: tr::lng_filters_checkbox_remove_group)(
tr::now,
Ui::Text::WithEntities),
false,
st::defaultBoxCheckbox));
}();
Ui::AddSkip(container);
auto buttonText = maybeUser
@ -622,10 +705,35 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
box->addButton(std::move(buttonText), [=] {
const auto revoke = maybeCheckbox && maybeCheckbox->checked();
const auto stopBot = maybeBotCheckbox && maybeBotCheckbox->checked();
const auto removeFromChats = maybeChatsFiltersCheckbox
&& maybeChatsFiltersCheckbox->checked();
Core::App().closeChatFromWindows(peer);
if (stopBot) {
peer->session().api().blockedPeers().block(peer);
}
if (removeFromChats) {
const auto history = peer->owner().history(peer).get();
const auto removeFrom = removeFromChatsFilters(history);
for (const auto &filter : peer->owner().chatsFilters().list()) {
if (!ranges::contains(removeFrom, filter.id())) {
continue;
}
const auto result = filter.withoutAlways(history);
if (result == filter) {
continue;
}
const auto tl = result.tl();
peer->owner().chatsFilters().apply(MTP_updateDialogFilter(
MTP_flags(MTPDupdateDialogFilter::Flag::f_filter),
MTP_int(filter.id()),
tl));
peer->session().api().request(MTPmessages_UpdateDialogFilter(
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
MTP_int(filter.id()),
tl
)).send();
}
}
// Don't delete old history by default,
// because Android app doesn't.
//

View file

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

@ -120,6 +120,9 @@ void PeerListBox::createMultiSelect() {
content()->submitted();
});
_select->entity()->setQueryChangedCallback([=](const QString &query) {
if (_customQueryChangedCallback) {
_customQueryChangedCallback(query);
}
searchQueryChanged(query);
});
_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
@ -138,6 +141,10 @@ void PeerListBox::createMultiSelect() {
_select->moveToLeft(0, 0);
}
void PeerListBox::appendQueryChangedCallback(Fn<void(QString)> callback) {
_customQueryChangedCallback = std::move(callback);
}
void PeerListBox::setAddedTopScrollSkip(int skip) {
_addedTopScrollSkip = skip;
_scrollBottomFixed = false;
@ -217,19 +224,7 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
void PeerListBox::searchQueryChanged(const QString &query) {
scrollToY(0);
const auto isEmpty = content()->searchQueryChanged(query);
if (_specialTabsMode.enabled) {
const auto was = _specialTabsMode.searchIsActive;
_specialTabsMode.searchIsActive = !isEmpty;
if (was != _specialTabsMode.searchIsActive) {
if (_specialTabsMode.searchIsActive) {
_specialTabsMode.topSkip = _addedTopScrollSkip;
setAddedTopScrollSkip(0);
} else {
setAddedTopScrollSkip(_specialTabsMode.topSkip);
}
}
}
content()->searchQueryChanged(query);
}
void PeerListBox::resizeEvent(QResizeEvent *e) {
@ -486,7 +481,7 @@ void PeerListBox::addSelectItem(
void PeerListBox::addSelectItem(
uint64 itemId,
const QString &text,
Ui::MultiSelect::PaintRoundImage paintUserpic,
PaintRoundImageCallback paintUserpic,
anim::type animated) {
if (!_select) {
createMultiSelect();
@ -561,13 +556,8 @@ rpl::producer<int> PeerListBox::multiSelectHeightValue() const {
return _select ? _select->heightValue() : rpl::single(0);
}
void PeerListBox::setSpecialTabMode(bool value) {
content()->setIgnoreHiddenRowsOnSearch(value);
if (value) {
_specialTabsMode.enabled = true;
} else {
_specialTabsMode = {};
}
rpl::producer<> PeerListBox::noSearchSubmits() const {
return content()->noSearchSubmits();
}
PeerListRow::PeerListRow(not_null<PeerData*> peer)
@ -798,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(
@ -1291,6 +1279,9 @@ void PeerListContent::clearAllContent() {
= _normalizedSearchQuery
= _mentionHighlight
= QString();
if (_controller->hasComplexSearch()) {
_controller->search(QString());
}
}
void PeerListContent::convertRowToSearchResult(not_null<PeerListRow*> row) {
@ -1689,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);
}
}
}
@ -2079,7 +2074,7 @@ void PeerListContent::checkScrollForPreload() {
}
}
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
void PeerListContent::searchQueryChanged(QString query) {
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
const auto normalizedQuery = searchWordsList.join(' ');
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
@ -2136,7 +2131,6 @@ PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
}
refreshRows();
}
return _normalizedSearchQuery.isEmpty();
}
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
@ -2211,6 +2205,9 @@ bool PeerListContent::submitted() {
_controller->rowClicked(row);
return true;
}
} else {
_noSearchSubmits.fire({});
return true;
}
return false;
}

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) {
}
@ -652,8 +654,7 @@ public:
[[nodiscard]] bool hasPressed() const;
void clearSelection();
using IsEmpty = bool;
IsEmpty searchQueryChanged(QString query);
void searchQueryChanged(QString query);
bool submitted();
PeerListRowId updateFromParentDrag(QPoint globalPosition);
@ -713,7 +714,7 @@ public:
update();
}
std::unique_ptr<PeerListState> saveState() const;
[[nodiscard]] std::unique_ptr<PeerListState> saveState() const;
void restoreState(std::unique_ptr<PeerListState> state);
void showRowMenu(
@ -721,10 +722,14 @@ public:
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
auto scrollToRequests() const {
[[nodiscard]] auto scrollToRequests() const {
return _scrollToRequests.events();
}
[[nodiscard]] auto noSearchSubmits() const {
return _noSearchSubmits.events();
}
~PeerListContent();
protected:
@ -891,6 +896,8 @@ private:
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
rpl::event_stream<> _noSearchSubmits;
std::vector<std::unique_ptr<PeerListRow>> _searchRows;
base::Timer _repaintByStatus;
base::unique_qptr<Ui::PopupMenu> _contextMenu;
@ -1107,8 +1114,7 @@ public:
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
void setSpecialTabMode(bool value);
[[nodiscard]] rpl::producer<> noSearchSubmits() const;
void peerListSetTitle(rpl::producer<QString> title) override {
setTitle(std::move(title));
@ -1133,6 +1139,8 @@ public:
void showFinished() override;
void appendQueryChangedCallback(Fn<void(QString)>);
protected:
void prepare() override;
void setInnerFocus() override;
@ -1170,16 +1178,10 @@ private:
object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
const std::shared_ptr<Main::SessionShow> _show;
Fn<void(QString)> _customQueryChangedCallback;
std::unique_ptr<PeerListController> _controller;
Fn<void(PeerListBox*)> _init;
bool _scrollBottomFixed = false;
int _addedTopScrollSkip = 0;
struct SpecialTabsMode final {
bool enabled = false;
bool searchIsActive = false;
int topSkip = 0;
};
SpecialTabsMode _specialTabsMode;
};

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"
@ -46,6 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "history/admin_log/history_admin_log_section.h"
#include "info/bot/earn/info_bot_earn_widget.h"
#include "info/bot/starref/info_bot_starref_join_widget.h"
#include "info/bot/starref/info_bot_starref_setup_widget.h"
#include "info/channel_statistics/boosts/info_boosts_widget.h"
#include "info/channel_statistics/earn/earn_format.h"
#include "info/channel_statistics/earn/earn_icons.h"
@ -60,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/emoji_button.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/new_badges.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "ui/vertical_list.h"
@ -78,6 +82,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
@ -358,9 +363,11 @@ private:
void fillBotUsernamesButton();
void fillBotCurrencyButton();
void fillBotCreditsButton();
void fillBotAffiliateProgram();
void fillBotEditIntroButton();
void fillBotEditCommandsButton();
void fillBotEditSettingsButton();
void fillBotVerifyAccounts();
void submitTitle();
void submitDescription();
@ -1181,6 +1188,7 @@ void Controller::fillManageSection() {
fillBotUsernamesButton();
fillBotCurrencyButton();
fillBotCreditsButton();
fillBotAffiliateProgram();
fillBotEditIntroButton();
fillBotEditCommandsButton();
fillBotEditSettingsButton();
@ -1200,6 +1208,7 @@ void Controller::fillManageSection() {
Ui::Text::RichLangValue),
st::boxDividerLabel),
st::defaultBoxDividerLabelPadding));
fillBotVerifyAccounts();
return;
}
@ -1238,6 +1247,9 @@ void Controller::fillManageSection() {
&& (channel->isBroadcast() || channel->isGigagroup());
const auto hasRecentActions = isChannel
&& (channel->hasAdminRights() || channel->amCreator());
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)
&& isChannel
&& channel->canPostMessages();
const auto canEditStickers = isChannel && channel->canEditStickers();
const auto canDeleteChannel = isChannel && channel->canDelete();
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
@ -1420,10 +1432,21 @@ void Controller::fillManageSection() {
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_recent_actions(),
rpl::single(QString()), //Empty count.
rpl::single(QString()), // Empty count.
std::move(callback),
{ &st::menuIconGroupLog });
}
if (hasStarRef) {
auto callback = [=] {
_navigation->showSection(Info::BotStarRef::Join::Make(_peer));
};
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_star_ref(),
rpl::single(QString()), // Empty count.
std::move(callback),
{ .icon = &st::menuIconStarRefShare, .newBadge = true });
}
if (canEditStickers || canDeleteChannel) {
::AddSkip(_controls.buttonsLayout);
@ -1664,7 +1687,7 @@ void Controller::fillBotCreditsButton() {
auto &lifetime = _controls.buttonsLayout->lifetime();
const auto state = lifetime.make_state<State>();
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
state->balance = Lang::FormatCountDecimal(balance);
state->balance = Lang::FormatStarsAmountDecimal(balance);
}
const auto wrap = _controls.buttonsLayout->add(
@ -1689,7 +1712,7 @@ void Controller::fillBotCreditsButton() {
if (data.balance) {
wrap->toggle(true, anim::type::normal);
}
state->balance = Lang::FormatCountDecimal(data.balance);
state->balance = Lang::FormatStarsAmountDecimal(data.balance);
});
}
{
@ -1711,6 +1734,35 @@ void Controller::fillBotCreditsButton() {
}
void Controller::fillBotAffiliateProgram() {
Expects(_isBot);
if (!Info::BotStarRef::Setup::Allowed(_peer)) {
return;
}
const auto user = _peer->asUser();
auto label = user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::StarRefProgram
) | rpl::map([=] {
const auto commission = user->botInfo
? user->botInfo->starRefProgram.commission
: 0;
return commission
? Info::BotStarRef::FormatCommission(commission)
: tr::lng_manage_peer_bot_star_ref_off(tr::now);
});
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_bot_star_ref(),
std::move(label),
[controller = _navigation->parentController(), user] {
controller->showSection(Info::BotStarRef::Setup::Make(user));
},
{ .icon = &st::menuIconSharing, .newBadge = true });
}
void Controller::fillBotEditIntroButton() {
Expects(_isBot);
@ -1747,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);
@ -2227,7 +2312,9 @@ void Controller::saveHistoryVisibility() {
void Controller::toggleBotManager(const QString &command) {
const auto controller = _navigation->parentController();
_api.request(MTPcontacts_ResolveUsername(
MTP_string(kBotManagerUsername.utf16())
MTP_flags(0),
MTP_string(kBotManagerUsername.utf16()),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
_peer->owner().processUsers(result.data().vusers());
_peer->owner().processChats(result.data().vchats());
@ -2501,6 +2588,13 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
st.button);
const auto button = result.data();
button->addClickHandler(callback);
const auto badge = descriptor.newBadge
? Ui::NewBadge::CreateNewBadge(
button,
tr::lng_premium_summary_new_badge()).get()
: nullptr;
if (descriptor) {
AddButtonIcon(
button,
@ -2509,7 +2603,7 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
}
auto labelText = rpl::combine(
std::move(text),
rpl::duplicate(text),
std::move(count),
button->widthValue()
) | rpl::map([&st](const QString &text, const QString &count, int width) {
@ -2524,11 +2618,40 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
: count;
});
if (badge) {
rpl::combine(
std::move(text),
rpl::duplicate(labelText),
button->widthValue()
) | rpl::start_with_next([=](
const QString &text,
const QString &label,
int width) {
const auto space = st.button.style.font->spacew;
const auto left = st.button.padding.left()
+ st.button.style.font->width(text)
+ space;
const auto right = st.labelPosition.x()
+ st.label.style.font->width(label)
+ (space * 2);
const auto available = width - left - right;
badge->setVisible(available >= badge->width());
if (!badge->isHidden()) {
const auto top = st.button.padding.top()
+ st.button.style.font->ascent
- st::settingsPremiumNewBadge.style.font->ascent
- st::settingsPremiumNewBadgePadding.top();
badge->moveToLeft(left, top, width);
}
}, badge->lifetime());
}
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button,
std::move(labelText),
st.label);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->show();
rpl::combine(
button->widthValue(),

View file

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

@ -7,27 +7,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/peer_short_info_box.h"
#include "ui/effects/radial_animation.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/wrap.h"
#include "ui/image/image_prepare.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "base/event_filter.h"
#include "core/application.h"
#include "info/profile/info_profile_text.h"
#include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "base/event_filter.h"
#include "lang/lang_keys.h"
#include "ui/effects/radial_animation.h"
#include "ui/image/image_prepare.h"
#include "ui/painter.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/wrap.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
namespace {
using MenuCallback = Ui::Menu::MenuCallback;
constexpr auto kShadowMaxAlpha = 80;
constexpr auto kInactiveBarOpacity = 0.5;
@ -833,6 +842,24 @@ void PeerShortInfoBox::refreshRoundedTopImage(const QColor &color) {
RectPart::TopLeft | RectPart::TopRight);
}
rpl::producer<MenuCallback> PeerShortInfoBox::fillMenuRequests() const {
return _fillMenuRequests.events();
}
void PeerShortInfoBox::contextMenuEvent(QContextMenuEvent *e) {
_menuHolder = nullptr;
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
this,
st::popupMenuWithIcons);
_fillMenuRequests.fire(Ui::Menu::CreateAddActionCallback(menu));
_menuHolder.reset(menu);
if (menu->empty()) {
_menuHolder = nullptr;
return;
}
menu->popup(e->globalPos());
}
rpl::producer<QString> PeerShortInfoBox::nameValue() const {
return _fields.value(
) | rpl::map([](const PeerShortInfoFields &fields) {

View file

@ -15,6 +15,10 @@ struct ShortInfoCover;
struct ShortInfoBox;
} // namespace style
namespace Ui::Menu {
struct MenuCallback;
} // namespace Ui::Menu
namespace Media::Streaming {
class Document;
class Instance;
@ -160,6 +164,11 @@ public:
[[nodiscard]] rpl::producer<> openRequests() const;
[[nodiscard]] rpl::producer<int> moveRequests() const;
[[nodiscard]] auto fillMenuRequests() const
-> rpl::producer<Ui::Menu::MenuCallback>;
protected:
void contextMenuEvent(QContextMenuEvent *e) override;
private:
void prepare() override;
@ -192,6 +201,9 @@ private:
not_null<Ui::VerticalLayout*> _rows;
PeerShortInfoCover _cover;
base::unique_qptr<Ui::RpWidget> _menuHolder;
rpl::event_stream<Ui::Menu::MenuCallback> _fillMenuRequests;
rpl::event_stream<> _openRequests;
};

View file

@ -7,26 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/prepare_short_info_box.h"
#include "base/unixtime.h"
#include "boxes/peers/peer_short_info_box.h"
#include "core/application.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_file_origin.h"
#include "data/data_peer.h"
#include "data/data_peer_values.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_streaming.h"
#include "data/data_file_origin.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_peer_values.h"
#include "data/data_user_photos.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "data/data_streaming.h"
#include "data/data_user.h"
#include "data/data_user_photos.h"
#include "info/profile/info_profile_values.h"
#include "ui/text/format_values.h"
#include "base/unixtime.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/delayed_activation.h" // PreventDelayedActivation
#include "ui/text/format_values.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "window/window_session_controller.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
namespace {
@ -446,6 +450,7 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
Fn<void()> open,
Fn<bool()> videoPaused,
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
const style::ShortInfoBox *stOverride) {
const auto type = peer->isSelf()
? PeerShortInfoType::Self
@ -463,6 +468,13 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
std::move(videoPaused),
stOverride);
if (menuFiller) {
result->fillMenuRequests(
) | rpl::start_with_next([=](Ui::Menu::MenuCallback callback) {
menuFiller(std::move(callback));
}, result->lifetime());
}
result->openRequests(
) | rpl::start_with_next(open, result->lifetime());
@ -481,10 +493,21 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
return navigation->parentController()->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
};
auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
const auto controller = navigation->parentController();
const auto peerSeparateId = Window::SeparateId(peer);
if (controller->windowId() != peerSeparateId) {
addAction(tr::lng_context_new_window(tr::now), [=] {
Ui::PreventDelayedActivation();
controller->showInNewWindow(peer);
}, &st::menuIconNewWindow);
}
};
return PrepareShortInfoBox(
peer,
open,
videoIsPaused,
std::move(menuFiller),
stOverride);
}

View file

@ -16,6 +16,10 @@ struct ShortInfoCover;
struct ShortInfoBox;
} // namespace style
namespace Ui::Menu {
struct MenuCallback;
} // namespace Ui::Menu
namespace Ui {
class BoxContent;
} // namespace Ui
@ -35,6 +39,7 @@ struct PreparedShortInfoUserpic {
not_null<PeerData*> peer,
Fn<void()> open,
Fn<bool()> videoPaused,
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
const style::ShortInfoBox *stOverride = nullptr);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(

View file

@ -202,10 +202,11 @@ void Controller::prepare() {
const auto session = &_to->session();
auto above = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
above->add(
CreateBoostReplaceUserpics(
CreateUserpicsTransfer(
above.data(),
_selectedPeers.value(),
_to),
_to,
UserpicsTransferType::BoostReplace),
st::boxRowPadding + st::boostReplaceUserpicsPadding);
above->add(
object_ptr<Ui::FlatLabel>(
@ -366,10 +367,11 @@ object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
});
box->verticalLayout()->insert(
0,
CreateBoostReplaceUserpics(
CreateUserpicsTransfer(
box,
rpl::single(std::vector{ peer }),
to),
to,
UserpicsTransferType::BoostReplace),
st::boxRowPadding + st::boostReplaceUserpicsPadding);
});
@ -536,10 +538,11 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
return Box<PeerListBox>(std::move(controller), std::move(initBox));
}
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
not_null<Ui::RpWidget*> parent,
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to) {
not_null<PeerData*> to,
UserpicsTransferType type) {
struct State {
std::vector<not_null<PeerData*>> from;
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
@ -640,13 +643,18 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
button->render(&q, position, QRegion(), QWidget::DrawChildren);
}
state->painting = false;
const auto boosting = (type == UserpicsTransferType::BoostReplace);
const auto last = state->buttons.back().get();
const auto back = boosting ? last : right;
const auto add = st::boostReplaceIconAdd;
const auto skip = st::boostReplaceIconSkip;
const auto w = st::boostReplaceIcon.width() + 2 * skip;
const auto h = st::boostReplaceIcon.height() + 2 * skip;
const auto x = last->x() + last->width() - w + add.x();
const auto y = last->y() + last->height() - h + add.y();
const auto &icon = boosting
? st::boostReplaceIcon
: st::starrefJoinIcon;
const auto skip = boosting ? st::boostReplaceIconSkip : 0;
const auto w = icon.width() + 2 * skip;
const auto h = icon.height() + 2 * skip;
const auto x = back->x() + back->width() - w + add.x();
const auto y = back->y() + back->height() - h + add.y();
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
brush.setStops(Ui::Premium::ButtonGradientStops());
@ -654,7 +662,7 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
pen.setWidthF(stroke);
q.setPen(pen);
q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
st::boostReplaceIcon.paint(q, x + skip, y + skip, outerw);
icon.paint(q, x + skip, y + skip, outerw);
const auto size = st::boostReplaceArrow.size();
st::boostReplaceArrow.paint(

View file

@ -53,10 +53,15 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
enum class UserpicsTransferType {
BoostReplace,
StarRefJoin,
};
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
not_null<Ui::RpWidget*> parent,
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to);
not_null<PeerData*> to,
UserpicsTransferType type);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
not_null<Ui::RpWidget*> parent,

View file

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

@ -150,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.");
}
@ -213,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.");
}
@ -543,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 "";
}();
@ -1642,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

@ -71,6 +71,7 @@ enum class PremiumFeature {
MessagePrivacy,
Business,
Effects,
FilterTags,
// Business features.
BusinessLocation,

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,

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