mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-21 16:47:07 +02:00
Compare commits
136 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b3552d8c2c | ||
|
206a15191f | ||
|
22de3980d1 | ||
|
9502120ee3 | ||
|
7f1a3320dd | ||
|
8a4fd7f182 | ||
|
b0a4d15d64 | ||
|
11d0f9db03 | ||
|
f024ceecdd | ||
|
08c07a0785 | ||
|
b843f91b3c | ||
|
4b2c5b3321 | ||
|
2b43f2682a | ||
|
7da0124286 | ||
|
feaeef6482 | ||
|
15bcfeec1d | ||
|
5fb002ab4c | ||
|
f56ddbb1e0 | ||
|
6bd2a7c962 | ||
|
9d591ae806 | ||
|
7d30e3913c | ||
|
19d7dd7aa3 | ||
|
45444253fd | ||
|
308ade6a7e | ||
|
66d54ccd54 | ||
|
ccf8d91f01 | ||
|
7c940a0480 | ||
|
277a2edb57 | ||
|
bd742689e9 | ||
|
9ca0c3c41d | ||
|
ce8650b4de | ||
|
4bce9d440e | ||
|
adc691f516 | ||
|
fc67a801e3 | ||
|
6a3657ca87 | ||
|
0537c5f273 | ||
|
cc4a5f30b6 | ||
|
b0d7c3e9b1 | ||
|
3bf7c44fc9 | ||
|
4ff4e63a11 | ||
|
72a35ba58b | ||
|
b6a31979f2 | ||
|
7c710e22cc | ||
|
ab58e7a225 | ||
|
c9fb97cd7c | ||
|
789f3e1584 | ||
|
0fc8229be1 | ||
|
a1e555267e | ||
|
0ac88c0cb5 | ||
|
d43a6da62b | ||
|
940455f786 | ||
|
0f74456f30 | ||
|
7840fa6d90 | ||
|
95ccc99fee | ||
|
7b0a156bba | ||
|
0d8ae7bb37 | ||
|
9491cff1df | ||
|
51dc5d6e37 | ||
|
f4c739ab92 | ||
|
0dd8ae3d77 | ||
|
7d2878d81c | ||
|
bd70a05861 | ||
|
0605c7b2bc | ||
|
8e83a55143 | ||
|
4ab4eb8ef2 | ||
|
d1e6150874 | ||
|
4121c99f36 | ||
|
827040f487 | ||
|
9032489786 | ||
|
8ea7bd4913 | ||
|
97b021efaf | ||
|
b3f9a77ba7 | ||
|
63fdc1f876 | ||
|
17cf354c58 | ||
|
c6fd8bcb99 | ||
|
1684465e04 | ||
|
fe2df96953 | ||
|
ee9d0cfd99 | ||
|
7b7e18e752 | ||
|
928be4151b | ||
|
37dd648686 | ||
|
93a590774e | ||
|
22b99b6d3e | ||
|
101d626d4f | ||
|
3633c19208 | ||
|
e302f328f7 | ||
|
f74ba95e95 | ||
|
c38982d286 | ||
|
fe9bac096b | ||
|
5b809c4fc6 | ||
|
4729e51e14 | ||
|
960cf7a34b | ||
|
852ab19760 | ||
|
2e45d9fc6b | ||
|
74b71b92b6 | ||
|
bbc14ba74f | ||
|
45c7829cd8 | ||
|
f2aa3afbbb | ||
|
909b01241b | ||
|
abb58c58a0 | ||
|
700e10d32c | ||
|
4ac48d0e4a | ||
|
345b2cb835 | ||
|
b962309498 | ||
|
e99cb9bfb8 | ||
|
9e12e18f90 | ||
|
66fc9b38df | ||
|
5dbe429e6b | ||
|
b2481ea6c1 | ||
|
86a294ce4b | ||
|
a8d1eadfbf | ||
|
b07d3c5403 | ||
|
892db55ae1 | ||
|
93615fef65 | ||
|
87452706ef | ||
|
3569615b21 | ||
|
86f7d09d31 | ||
|
d5d1254393 | ||
|
4df90cfb9e | ||
|
ec6862d31a | ||
|
6f23010382 | ||
|
c672f105d3 | ||
|
e60d501e4a | ||
|
0d7175058b | ||
|
3b0bd9d1d1 | ||
|
bd28ac6e1f | ||
|
0c2d00c792 | ||
|
140ba653b9 | ||
|
f64f008f77 | ||
|
a6315bef05 | ||
|
f810d7c82a | ||
|
cf61dedc79 | ||
|
2ab9587f5f | ||
|
4950b52359 | ||
|
03af444735 | ||
|
7f6221b409 |
292 changed files with 15895 additions and 10118 deletions
|
@ -9,10 +9,7 @@
|
||||||
"--compile-commands-dir=${workspaceFolder}/out"
|
"--compile-commands-dir=${workspaceFolder}/out"
|
||||||
],
|
],
|
||||||
"cmake.generator": "Ninja Multi-Config",
|
"cmake.generator": "Ninja Multi-Config",
|
||||||
"cmake.buildDirectory": "${workspaceFolder}/out",
|
"cmake.buildDirectory": "${workspaceFolder}/out"
|
||||||
"cmake.configureSettings": {
|
|
||||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"ms-vscode.cpptools-extension-pack",
|
"ms-vscode.cpptools-extension-pack",
|
||||||
|
|
19
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
19
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
|
@ -5,7 +5,7 @@ body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for reporting issues of Telegram Desktop!
|
Thanks for reporting issues of AyuGram Desktop!
|
||||||
|
|
||||||
To make it easier for us to help you please enter detailed information below.
|
To make it easier for us to help you please enter detailed information below.
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
@ -39,12 +39,9 @@ body:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Version of Telegram Desktop
|
label: Version of AyuGram Desktop
|
||||||
description: >
|
description: >
|
||||||
Please note we don't support versions from Linux distro repositories.
|
**Don't use 'latest'**, specify actual version.
|
||||||
If you need support for these versions, **please contact your distro maintainer**
|
|
||||||
or your distro bugtracker.
|
|
||||||
**Don't use 'latest'**, specify actual version, **that's a reason to close your issue**.
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -52,11 +49,7 @@ body:
|
||||||
label: Installation source
|
label: Installation source
|
||||||
multiple: false
|
multiple: false
|
||||||
options:
|
options:
|
||||||
- Static binary from official website
|
- Binary from GitHub / official Telegram source
|
||||||
- Microsoft Store
|
|
||||||
- Mac App Store
|
|
||||||
- Flatpak
|
|
||||||
- Snap
|
|
||||||
- Other (unofficial) source
|
- Other (unofficial) source
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
@ -65,9 +58,7 @@ body:
|
||||||
label: Crash ID
|
label: Crash ID
|
||||||
description: >
|
description: >
|
||||||
If you're reporting a crash, please enter the crash ID from the crash reporter
|
If you're reporting a crash, please enter the crash ID from the crash reporter
|
||||||
opening on the next launch after crash. **You have to enable beta versions
|
opening on the next launch after crash.
|
||||||
installation in Settings -> Advanced for the reporter to appear.**
|
|
||||||
You don't have to wait for a beta version to arrive.
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Logs
|
label: Logs
|
||||||
|
|
BIN
Telegram/Resources/icons/chat/mini_info_alert.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_info_alert.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 311 B |
BIN
Telegram/Resources/icons/chat/mini_info_alert@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_info_alert@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 578 B |
BIN
Telegram/Resources/icons/chat/mini_info_alert@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/mini_info_alert@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 829 B |
BIN
Telegram/Resources/icons/payments/premium_emoji.png
Normal file
BIN
Telegram/Resources/icons/payments/premium_emoji.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 370 B |
BIN
Telegram/Resources/icons/payments/premium_emoji@2x.png
Normal file
BIN
Telegram/Resources/icons/payments/premium_emoji@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 712 B |
BIN
Telegram/Resources/icons/payments/premium_emoji@3x.png
Normal file
BIN
Telegram/Resources/icons/payments/premium_emoji@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 926 B |
|
@ -1218,6 +1218,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_edit_privacy_contacts" = "My contacts";
|
"lng_edit_privacy_contacts" = "My contacts";
|
||||||
"lng_edit_privacy_close_friends" = "Close friends";
|
"lng_edit_privacy_close_friends" = "Close friends";
|
||||||
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
|
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
|
||||||
|
"lng_edit_privacy_paid" = "Paid";
|
||||||
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
|
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
|
||||||
"lng_edit_privacy_nobody" = "Nobody";
|
"lng_edit_privacy_nobody" = "Nobody";
|
||||||
"lng_edit_privacy_premium" = "Premium users";
|
"lng_edit_privacy_premium" = "Premium users";
|
||||||
|
@ -1356,6 +1357,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium.";
|
"lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium.";
|
||||||
"lng_messages_privacy_premium" = "Only subscribers of {link} can select this option.";
|
"lng_messages_privacy_premium" = "Only subscribers of {link} can select this option.";
|
||||||
"lng_messages_privacy_premium_link" = "Telegram Premium";
|
"lng_messages_privacy_premium_link" = "Telegram Premium";
|
||||||
|
"lng_messages_privacy_charge" = "Charge for messages";
|
||||||
|
"lng_messages_privacy_charge_about" = "Charge a fee for messages from people outside your contacts or those you haven't messaged first.";
|
||||||
|
"lng_messages_privacy_price" = "Set your price per message";
|
||||||
|
"lng_messages_privacy_price_about" = "You will receive {percent} of the selected fee ({amount}) for each incoming message.";
|
||||||
|
"lng_messages_privacy_exceptions" = "Exceptions";
|
||||||
|
"lng_messages_privacy_remove_fee" = "Remove Fee";
|
||||||
|
"lng_messages_privacy_remove_about" = "Add users or entire groups who won't be charged for sending messages to you.";
|
||||||
|
|
||||||
"lng_self_destruct_title" = "Account self-destruction";
|
"lng_self_destruct_title" = "Account self-destruction";
|
||||||
"lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts.";
|
"lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts.";
|
||||||
|
@ -2158,6 +2166,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
|
"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?";
|
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
|
||||||
"lng_action_payment_refunded" = "{peer} refunded {amount}";
|
"lng_action_payment_refunded" = "{peer} refunded {amount}";
|
||||||
|
"lng_action_paid_message_sent#one" = "You paid {count} Star to {action}";
|
||||||
|
"lng_action_paid_message_sent#other" = "You paid {count} Stars to {action}";
|
||||||
|
"lng_action_paid_message_one" = "send a message";
|
||||||
|
"lng_action_paid_message_some#one" = "send {count} message";
|
||||||
|
"lng_action_paid_message_some#other" = "send {count} messages";
|
||||||
|
"lng_action_paid_message_got#one" = "You received {count} Star from {name}";
|
||||||
|
"lng_action_paid_message_got#other" = "You received {count} Stars from {name}";
|
||||||
|
"lng_you_paid_stars#one" = "You paid {count} Star.";
|
||||||
|
"lng_you_paid_stars#other" = "You paid {count} Stars.";
|
||||||
|
|
||||||
"lng_similar_channels_title" = "Similar channels";
|
"lng_similar_channels_title" = "Similar channels";
|
||||||
"lng_similar_channels_view_all" = "View all";
|
"lng_similar_channels_view_all" = "View all";
|
||||||
|
@ -2664,6 +2681,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||||
"lng_credits_summary_balance" = "Balance";
|
"lng_credits_summary_balance" = "Balance";
|
||||||
"lng_credits_commission" = "{amount} commission";
|
"lng_credits_commission" = "{amount} commission";
|
||||||
|
"lng_credits_paid_messages_fee#one" = "Fee for {count} Message";
|
||||||
|
"lng_credits_paid_messages_fee#other" = "Fee for {count} Messages";
|
||||||
|
"lng_credits_paid_messages_fee_about" = "You receive {percent} of the price that you charge for each incoming message. {link}";
|
||||||
|
"lng_credits_paid_messages_fee_about_link" = "Change Fee {emoji}";
|
||||||
|
"lng_credits_paid_messages_full" = "Full Price";
|
||||||
|
"lng_credits_premium_gift_duration" = "Duration";
|
||||||
"lng_credits_more_options" = "More Options";
|
"lng_credits_more_options" = "More Options";
|
||||||
"lng_credits_balance_me" = "your balance";
|
"lng_credits_balance_me" = "your balance";
|
||||||
"lng_credits_buy_button" = "Buy More Stars";
|
"lng_credits_buy_button" = "Buy More Stars";
|
||||||
|
@ -2777,6 +2800,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||||
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
|
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
|
||||||
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
|
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
|
||||||
|
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
|
||||||
|
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
|
||||||
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
|
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
|
||||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||||
"lng_credits_enough" = "You have enough stars at the moment. {link}";
|
"lng_credits_enough" = "You have enough stars at the moment. {link}";
|
||||||
|
@ -3310,6 +3335,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
|
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
|
||||||
"lng_gift_premium_features" = "See Features >";
|
"lng_gift_premium_features" = "See Features >";
|
||||||
"lng_gift_premium_label" = "Premium";
|
"lng_gift_premium_label" = "Premium";
|
||||||
|
"lng_gift_premium_by_stars" = "or {amount}";
|
||||||
"lng_gift_stars_subtitle" = "Gift Stars";
|
"lng_gift_stars_subtitle" = "Gift Stars";
|
||||||
"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
|
"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
|
||||||
"lng_gift_stars_link" = "What are Stars >";
|
"lng_gift_stars_link" = "What are Stars >";
|
||||||
|
@ -3321,8 +3347,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_gift_send_title" = "Send a Gift";
|
"lng_gift_send_title" = "Send a Gift";
|
||||||
"lng_gift_send_message" = "Enter Message";
|
"lng_gift_send_message" = "Enter Message";
|
||||||
"lng_gift_send_anonymous" = "Hide My Name";
|
"lng_gift_send_anonymous" = "Hide My Name";
|
||||||
|
"lng_gift_send_pay_with_stars" = "Pay with {amount}";
|
||||||
|
"lng_gift_send_stars_balance" = "Your balance is {amount}. {link}";
|
||||||
|
"lng_gift_send_stars_balance_link" = "Get More Stars >";
|
||||||
"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile.";
|
"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_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_anonymous_about_paid" = "You can hide your name from visitors to {user}'s profile. {recipient} will still see your name.";
|
||||||
"lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins.";
|
"lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins.";
|
||||||
"lng_gift_send_unique" = "Make Unique for {price}";
|
"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_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
|
||||||
|
@ -3398,6 +3428,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
|
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
|
||||||
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
||||||
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
|
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
|
||||||
|
"lng_gift_pinned_done" = "The gift will always be shown on top.";
|
||||||
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
|
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
|
||||||
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
|
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
|
||||||
"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift.";
|
"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift.";
|
||||||
|
@ -3456,6 +3487,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_gift_transfer_button_for" = "Transfer for {price}";
|
"lng_gift_transfer_button_for" = "Transfer for {price}";
|
||||||
"lng_gift_transfer_wear" = "Wear";
|
"lng_gift_transfer_wear" = "Wear";
|
||||||
"lng_gift_transfer_take_off" = "Take Off";
|
"lng_gift_transfer_take_off" = "Take Off";
|
||||||
|
"lng_gift_menu_show" = "Show";
|
||||||
|
"lng_gift_menu_hide" = "Hide";
|
||||||
"lng_gift_wear_title" = "Wear {name}";
|
"lng_gift_wear_title" = "Wear {name}";
|
||||||
"lng_gift_wear_about" = "and get these benefits:";
|
"lng_gift_wear_about" = "and get these benefits:";
|
||||||
"lng_gift_wear_badge_title" = "Radiant Badge";
|
"lng_gift_wear_badge_title" = "Radiant Badge";
|
||||||
|
@ -3609,6 +3642,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join.";
|
"lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join.";
|
||||||
"lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}.";
|
"lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}.";
|
||||||
"lng_new_contact_about_status_link" = "Telegram Premium";
|
"lng_new_contact_about_status_link" = "Telegram Premium";
|
||||||
|
"lng_new_contact_not_contact" = "Not a contact";
|
||||||
|
"lng_new_contact_phone_number" = "Phone number";
|
||||||
|
"lng_new_contact_registration" = "Registration";
|
||||||
|
"lng_new_contact_common_groups" = "Common groups";
|
||||||
|
"lng_new_contact_groups#one" = "{count} group {emoji} {arrow}";
|
||||||
|
"lng_new_contact_groups#other" = "{count} groups {emoji} {arrow}";
|
||||||
|
"lng_new_contact_not_official" = "Not an official account";
|
||||||
|
"lng_new_contact_updated_name" = "User updated name {when}";
|
||||||
|
"lng_new_contact_updated_photo" = "User updated photo {when}";
|
||||||
|
"lng_new_contact_updated_now" = "less than an hour ago";
|
||||||
|
"lng_new_contact_updated_hours#one" = "{count} hour ago";
|
||||||
|
"lng_new_contact_updated_hours#other" = "{count} hours ago";
|
||||||
|
"lng_new_contact_updated_days#one" = "{count} day ago";
|
||||||
|
"lng_new_contact_updated_days#other" = "{count} days ago";
|
||||||
|
"lng_new_contact_updated_months#one" = "{count} month ago";
|
||||||
|
"lng_new_contact_updated_months#other" = "{count} months ago";
|
||||||
"lng_from_request_title_channel" = "Response to your join request";
|
"lng_from_request_title_channel" = "Response to your join request";
|
||||||
"lng_from_request_title_group" = "Response to your join request";
|
"lng_from_request_title_group" = "Response to your join request";
|
||||||
"lng_from_request_body" = "You received this message because you requested to join {name} on {date}.";
|
"lng_from_request_body" = "You received this message because you requested to join {name} on {date}.";
|
||||||
|
@ -3637,6 +3686,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_send_anonymous_ph" = "Send anonymously...";
|
"lng_send_anonymous_ph" = "Send anonymously...";
|
||||||
"lng_story_reply_ph" = "Reply privately...";
|
"lng_story_reply_ph" = "Reply privately...";
|
||||||
"lng_story_comment_ph" = "Comment story...";
|
"lng_story_comment_ph" = "Comment story...";
|
||||||
|
"lng_message_paid_ph" = "Message for {amount}";
|
||||||
"lng_send_text_no" = "Text not allowed.";
|
"lng_send_text_no" = "Text not allowed.";
|
||||||
"lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
|
"lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
|
||||||
"lng_send_text_type_and_last" = "{types} and {last}";
|
"lng_send_text_type_and_last" = "{types} and {last}";
|
||||||
|
@ -4798,6 +4848,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_rights_boosts_no_restrict" = "Do not restrict boosters";
|
"lng_rights_boosts_no_restrict" = "Do not restrict boosters";
|
||||||
"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media.";
|
"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media.";
|
||||||
"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages.";
|
"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages.";
|
||||||
|
"lng_rights_charge_stars" = "Charge Stars for Messages";
|
||||||
|
"lng_rights_charge_stars_about" = "If you turn this on, regular members of the group will have to pay Stars to send messages.";
|
||||||
|
"lng_rights_charge_price" = "Set price per message";
|
||||||
|
"lng_rights_charge_price_about" = "Your group will receive {percent} of the selected fee ({amount}) for each incoming message.";
|
||||||
|
|
||||||
"lng_slowmode_enabled" = "Slow Mode is active.\nYou can send your next message in {left}.";
|
"lng_slowmode_enabled" = "Slow Mode is active.\nYou can send your next message in {left}.";
|
||||||
"lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time.";
|
"lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time.";
|
||||||
|
@ -4805,6 +4859,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_slowmode_seconds#one" = "{count} second";
|
"lng_slowmode_seconds#one" = "{count} second";
|
||||||
"lng_slowmode_seconds#other" = "{count} seconds";
|
"lng_slowmode_seconds#other" = "{count} seconds";
|
||||||
|
|
||||||
|
"lng_payment_confirm_title" = "Confirm payment";
|
||||||
|
"lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message.";
|
||||||
|
"lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message.";
|
||||||
|
"lng_payment_confirm_amount#one" = "**{count}** Star";
|
||||||
|
"lng_payment_confirm_amount#other" = "**{count}** Stars";
|
||||||
|
"lng_payment_confirm_users#one" = "You selected **{count}** user who charge Stars for messages.";
|
||||||
|
"lng_payment_confirm_users#other" = "You selected **{count}** users who charge Stars for messages.";
|
||||||
|
"lng_payment_confirm_chats#one" = "You selected **{count}** chat where you pay Stars for messages.";
|
||||||
|
"lng_payment_confirm_chats#other" = "You selected **{count}** chats where you pay Stars for messages.";
|
||||||
|
"lng_payment_confirm_sure#one" = "Would you like to pay {amount} to send **{count}** message?";
|
||||||
|
"lng_payment_confirm_sure#other" = "Would you like to pay {amount} to send **{count}** messages?";
|
||||||
|
"lng_payment_confirm_dont_ask" = "Don't ask me again";
|
||||||
|
"lng_payment_confirm_button#one" = "Pay for {count} Message";
|
||||||
|
"lng_payment_confirm_button#other" = "Pay for {count} Messages";
|
||||||
|
"lng_payment_bar_text" = "{name} must pay {cost} for each message to you.";
|
||||||
|
"lng_payment_bar_button" = "Remove Fee";
|
||||||
|
"lng_payment_refund_title" = "Remove Fee";
|
||||||
|
"lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?";
|
||||||
|
"lng_payment_refund_also#one" = "Refund already paid {count} Star";
|
||||||
|
"lng_payment_refund_also#other" = "Refund already paid {count} Stars";
|
||||||
|
"lng_payment_refund_confirm" = "Confirm";
|
||||||
|
|
||||||
"lng_rights_gigagroup_title" = "Broadcast group";
|
"lng_rights_gigagroup_title" = "Broadcast group";
|
||||||
"lng_rights_gigagroup_convert" = "Convert to Broadcast Group";
|
"lng_rights_gigagroup_convert" = "Convert to Broadcast Group";
|
||||||
"lng_rights_gigagroup_about" = "Broadcast groups can have over 200,000 members, but only admins can send messages in them.";
|
"lng_rights_gigagroup_about" = "Broadcast groups can have over 200,000 members, but only admins can send messages in them.";
|
||||||
|
@ -4936,6 +5012,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
|
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
|
||||||
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
|
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
|
||||||
|
|
||||||
|
"lng_send_charges_stars_text" = "{user} charges {amount} for each message.";
|
||||||
|
"lng_send_charges_stars_go" = "Buy Stars";
|
||||||
|
|
||||||
"lng_exceptions_list_title" = "Exceptions";
|
"lng_exceptions_list_title" = "Exceptions";
|
||||||
"lng_removed_list_title" = "Removed users";
|
"lng_removed_list_title" = "Removed users";
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||||
ProcessorArchitecture="ARCHITECTURE"
|
ProcessorArchitecture="ARCHITECTURE"
|
||||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||||
Version="5.11.1.0" />
|
Version="5.12.3.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 5,11,1,0
|
FILEVERSION 5,12,3,0
|
||||||
PRODUCTVERSION 5,11,1,0
|
PRODUCTVERSION 5,12,3,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -62,10 +62,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop"
|
VALUE "FileDescription", "AyuGram Desktop"
|
||||||
VALUE "FileVersion", "5.11.1.0"
|
VALUE "FileVersion", "5.12.3.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "5.11.1.0"
|
VALUE "ProductVersion", "5.12.3.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 5,11,1,0
|
FILEVERSION 5,12,3,0
|
||||||
PRODUCTVERSION 5,11,1,0
|
PRODUCTVERSION 5,12,3,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -53,10 +53,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||||
VALUE "FileVersion", "5.11.1.0"
|
VALUE "FileVersion", "5.12.3.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "5.11.1.0"
|
VALUE "ProductVersion", "5.12.3.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -149,18 +149,14 @@ void InitFilterLinkHeader(
|
||||||
iconEmoji
|
iconEmoji
|
||||||
).value_or(Ui::FilterIcon::Custom)).active;
|
).value_or(Ui::FilterIcon::Custom)).active;
|
||||||
const auto isStatic = title.isStatic;
|
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, {
|
auto header = Ui::MakeFilterLinkHeader(box, {
|
||||||
.type = type,
|
.type = type,
|
||||||
.title = TitleText(type)(tr::now),
|
.title = TitleText(type)(tr::now),
|
||||||
.about = AboutText(type, title.text),
|
.about = AboutText(type, title.text),
|
||||||
.makeAboutContext = makeContext,
|
.aboutContext = Core::TextContext({
|
||||||
|
.session = &box->peerListUiShow()->session(),
|
||||||
|
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||||
|
}),
|
||||||
.folderTitle = title.text,
|
.folderTitle = title.text,
|
||||||
.folderIcon = icon,
|
.folderIcon = icon,
|
||||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||||
|
@ -560,16 +556,12 @@ void ShowImportToast(
|
||||||
text.append('\n').append(phrase(tr::now, lt_count, added));
|
text.append('\n').append(phrase(tr::now, lt_count, added));
|
||||||
}
|
}
|
||||||
const auto isStatic = title.isStatic;
|
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({
|
strong->showToast({
|
||||||
.text = std::move(text),
|
.text = std::move(text),
|
||||||
.textContext = makeContext,
|
.textContext = Core::TextContext({
|
||||||
|
.session = &strong->session(),
|
||||||
|
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,18 +632,14 @@ void ProcessFilterInvite(
|
||||||
raw->setRealContentHeight(box->heightValue());
|
raw->setRealContentHeight(box->heightValue());
|
||||||
|
|
||||||
const auto isStatic = title.isStatic;
|
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(
|
auto owned = Ui::FilterLinkProcessButton(
|
||||||
box,
|
box,
|
||||||
type,
|
type,
|
||||||
title.text,
|
title.text,
|
||||||
makeContext,
|
Core::TextContext({
|
||||||
|
.session = &strong->session(),
|
||||||
|
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||||
|
}),
|
||||||
std::move(badge));
|
std::move(badge));
|
||||||
|
|
||||||
const auto button = owned.data();
|
const auto button = owned.data();
|
||||||
|
@ -873,18 +861,14 @@ void ProcessFilterRemove(
|
||||||
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
|
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
|
||||||
|
|
||||||
const auto isStatic = title.isStatic;
|
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(
|
auto owned = Ui::FilterLinkProcessButton(
|
||||||
box,
|
box,
|
||||||
type,
|
type,
|
||||||
title.text,
|
title.text,
|
||||||
makeContext,
|
Core::TextContext({
|
||||||
|
.session = &strong->session(),
|
||||||
|
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||||
|
}),
|
||||||
std::move(badge));
|
std::move(badge));
|
||||||
|
|
||||||
const auto button = owned.data();
|
const auto button = owned.data();
|
||||||
|
|
|
@ -25,6 +25,7 @@ struct SendOptions {
|
||||||
TimeId scheduled = 0;
|
TimeId scheduled = 0;
|
||||||
BusinessShortcutId shortcutId = 0;
|
BusinessShortcutId shortcutId = 0;
|
||||||
EffectId effectId = 0;
|
EffectId effectId = 0;
|
||||||
|
int starsApproved = 0;
|
||||||
bool silent = false;
|
bool silent = false;
|
||||||
bool handleSupportSwitch = false;
|
bool handleSupportSwitch = false;
|
||||||
bool invertCaption = false;
|
bool invertCaption = false;
|
||||||
|
|
|
@ -90,7 +90,13 @@ constexpr auto kTransactionsLimit = 100;
|
||||||
? peerFromMTP(*tl.data().vstarref_peer()).value
|
? peerFromMTP(*tl.data().vstarref_peer()).value
|
||||||
: 0;
|
: 0;
|
||||||
const auto incoming = (amount >= StarsAmount());
|
const auto incoming = (amount >= StarsAmount());
|
||||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
const auto paidMessagesCount
|
||||||
|
= tl.data().vpaid_messages().value_or_empty();
|
||||||
|
const auto premiumMonthsForStars
|
||||||
|
= tl.data().vpremium_gift_months().value_or_empty();
|
||||||
|
const auto saveActorId = (reaction
|
||||||
|
|| !extended.empty()
|
||||||
|
|| paidMessagesCount) && incoming;
|
||||||
const auto parsedGift = stargift
|
const auto parsedGift = stargift
|
||||||
? FromTL(&peer->session(), *stargift)
|
? FromTL(&peer->session(), *stargift)
|
||||||
: std::optional<Data::StarGift>();
|
: std::optional<Data::StarGift>();
|
||||||
|
@ -110,9 +116,9 @@ constexpr auto kTransactionsLimit = 100;
|
||||||
.bareGiftStickerId = giftStickerId,
|
.bareGiftStickerId = giftStickerId,
|
||||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||||
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
|
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
|
||||||
.starrefAmount = starrefAmount,
|
.starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount,
|
||||||
.starrefCommission = starrefCommission,
|
.starrefCommission = paidMessagesCount ? 0 : starrefCommission,
|
||||||
.starrefRecipientId = starrefBarePeerId,
|
.starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
|
||||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||||
|
@ -138,9 +144,15 @@ constexpr auto kTransactionsLimit = 100;
|
||||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||||
: QDateTime(),
|
: QDateTime(),
|
||||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||||
|
.paidMessagesCount = paidMessagesCount,
|
||||||
|
.paidMessagesAmount = (paidMessagesCount
|
||||||
|
? starrefAmount
|
||||||
|
: StarsAmount()),
|
||||||
|
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
|
||||||
.starsConverted = int(nonUniqueGift
|
.starsConverted = int(nonUniqueGift
|
||||||
? nonUniqueGift->vconvert_stars().v
|
? nonUniqueGift->vconvert_stars().v
|
||||||
: 0),
|
: 0),
|
||||||
|
.premiumMonthsForStars = premiumMonthsForStars,
|
||||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||||
.converted = stargift && incoming,
|
.converted = stargift && incoming,
|
||||||
.stargift = stargift.has_value(),
|
.stargift = stargift.has_value(),
|
||||||
|
|
|
@ -114,7 +114,8 @@ void GlobalPrivacy::updateHideReadTime(bool hide) {
|
||||||
archiveAndMuteCurrent(),
|
archiveAndMuteCurrent(),
|
||||||
unarchiveOnNewMessageCurrent(),
|
unarchiveOnNewMessageCurrent(),
|
||||||
hide,
|
hide,
|
||||||
newRequirePremiumCurrent());
|
newRequirePremiumCurrent(),
|
||||||
|
newChargeStarsCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GlobalPrivacy::hideReadTimeCurrent() const {
|
bool GlobalPrivacy::hideReadTimeCurrent() const {
|
||||||
|
@ -125,14 +126,6 @@ rpl::producer<bool> GlobalPrivacy::hideReadTime() const {
|
||||||
return _hideReadTime.value();
|
return _hideReadTime.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalPrivacy::updateNewRequirePremium(bool value) {
|
|
||||||
update(
|
|
||||||
archiveAndMuteCurrent(),
|
|
||||||
unarchiveOnNewMessageCurrent(),
|
|
||||||
hideReadTimeCurrent(),
|
|
||||||
value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GlobalPrivacy::newRequirePremiumCurrent() const {
|
bool GlobalPrivacy::newRequirePremiumCurrent() const {
|
||||||
return _newRequirePremium.current();
|
return _newRequirePremium.current();
|
||||||
}
|
}
|
||||||
|
@ -141,6 +134,25 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
||||||
return _newRequirePremium.value();
|
return _newRequirePremium.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int GlobalPrivacy::newChargeStarsCurrent() const {
|
||||||
|
return _newChargeStars.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> GlobalPrivacy::newChargeStars() const {
|
||||||
|
return _newChargeStars.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalPrivacy::updateMessagesPrivacy(
|
||||||
|
bool requirePremium,
|
||||||
|
int chargeStars) {
|
||||||
|
update(
|
||||||
|
archiveAndMuteCurrent(),
|
||||||
|
unarchiveOnNewMessageCurrent(),
|
||||||
|
hideReadTimeCurrent(),
|
||||||
|
requirePremium,
|
||||||
|
chargeStars);
|
||||||
|
}
|
||||||
|
|
||||||
void GlobalPrivacy::loadPaidReactionShownPeer() {
|
void GlobalPrivacy::loadPaidReactionShownPeer() {
|
||||||
if (_paidReactionShownPeerLoaded) {
|
if (_paidReactionShownPeerLoaded) {
|
||||||
return;
|
return;
|
||||||
|
@ -169,7 +181,8 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||||
value,
|
value,
|
||||||
unarchiveOnNewMessageCurrent(),
|
unarchiveOnNewMessageCurrent(),
|
||||||
hideReadTimeCurrent(),
|
hideReadTimeCurrent(),
|
||||||
newRequirePremiumCurrent());
|
newRequirePremiumCurrent(),
|
||||||
|
newChargeStarsCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalPrivacy::updateUnarchiveOnNewMessage(
|
void GlobalPrivacy::updateUnarchiveOnNewMessage(
|
||||||
|
@ -178,14 +191,16 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage(
|
||||||
archiveAndMuteCurrent(),
|
archiveAndMuteCurrent(),
|
||||||
value,
|
value,
|
||||||
hideReadTimeCurrent(),
|
hideReadTimeCurrent(),
|
||||||
newRequirePremiumCurrent());
|
newRequirePremiumCurrent(),
|
||||||
|
newChargeStarsCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalPrivacy::update(
|
void GlobalPrivacy::update(
|
||||||
bool archiveAndMute,
|
bool archiveAndMute,
|
||||||
UnarchiveOnNewMessage unarchiveOnNewMessage,
|
UnarchiveOnNewMessage unarchiveOnNewMessage,
|
||||||
bool hideReadTime,
|
bool hideReadTime,
|
||||||
bool newRequirePremium) {
|
bool newRequirePremium,
|
||||||
|
int newChargeStars) {
|
||||||
using Flag = MTPDglobalPrivacySettings::Flag;
|
using Flag = MTPDglobalPrivacySettings::Flag;
|
||||||
|
|
||||||
_api.request(_requestId).cancel();
|
_api.request(_requestId).cancel();
|
||||||
|
@ -204,35 +219,44 @@ void GlobalPrivacy::update(
|
||||||
| (hideReadTime ? Flag::f_hide_read_marks : Flag())
|
| (hideReadTime ? Flag::f_hide_read_marks : Flag())
|
||||||
| ((newRequirePremium && newRequirePremiumAllowed)
|
| ((newRequirePremium && newRequirePremiumAllowed)
|
||||||
? Flag::f_new_noncontact_peers_require_premium
|
? Flag::f_new_noncontact_peers_require_premium
|
||||||
: Flag());
|
: Flag())
|
||||||
|
| Flag::f_noncontact_peers_paid_stars;
|
||||||
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
|
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
|
||||||
MTP_globalPrivacySettings(MTP_flags(flags))
|
MTP_globalPrivacySettings(
|
||||||
|
MTP_flags(flags),
|
||||||
|
MTP_long(newChargeStars))
|
||||||
)).done([=](const MTPGlobalPrivacySettings &result) {
|
)).done([=](const MTPGlobalPrivacySettings &result) {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
apply(result);
|
apply(result);
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
|
if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
|
||||||
update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {});
|
update(
|
||||||
|
archiveAndMute,
|
||||||
|
unarchiveOnNewMessage,
|
||||||
|
hideReadTime,
|
||||||
|
false,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
_archiveAndMute = archiveAndMute;
|
_archiveAndMute = archiveAndMute;
|
||||||
_unarchiveOnNewMessage = unarchiveOnNewMessage;
|
_unarchiveOnNewMessage = unarchiveOnNewMessage;
|
||||||
_hideReadTime = hideReadTime;
|
_hideReadTime = hideReadTime;
|
||||||
_newRequirePremium = newRequirePremium;
|
_newRequirePremium = newRequirePremium;
|
||||||
|
_newChargeStars = newChargeStars;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) {
|
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) {
|
||||||
data.match([&](const MTPDglobalPrivacySettings &data) {
|
const auto &data = settings.data();
|
||||||
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
|
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
|
||||||
_unarchiveOnNewMessage = data.is_keep_archived_unmuted()
|
_unarchiveOnNewMessage = data.is_keep_archived_unmuted()
|
||||||
? UnarchiveOnNewMessage::None
|
? UnarchiveOnNewMessage::None
|
||||||
: data.is_keep_archived_folders()
|
: data.is_keep_archived_folders()
|
||||||
? UnarchiveOnNewMessage::NotInFoldersUnmuted
|
? UnarchiveOnNewMessage::NotInFoldersUnmuted
|
||||||
: UnarchiveOnNewMessage::AnyUnmuted;
|
: UnarchiveOnNewMessage::AnyUnmuted;
|
||||||
_hideReadTime = data.is_hide_read_marks();
|
_hideReadTime = data.is_hide_read_marks();
|
||||||
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
|
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
|
||||||
});
|
_newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -49,23 +49,28 @@ public:
|
||||||
[[nodiscard]] bool hideReadTimeCurrent() const;
|
[[nodiscard]] bool hideReadTimeCurrent() const;
|
||||||
[[nodiscard]] rpl::producer<bool> hideReadTime() const;
|
[[nodiscard]] rpl::producer<bool> hideReadTime() const;
|
||||||
|
|
||||||
void updateNewRequirePremium(bool value);
|
|
||||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||||
|
|
||||||
|
[[nodiscard]] int newChargeStarsCurrent() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> newChargeStars() const;
|
||||||
|
|
||||||
|
void updateMessagesPrivacy(bool requirePremium, int chargeStars);
|
||||||
|
|
||||||
void loadPaidReactionShownPeer();
|
void loadPaidReactionShownPeer();
|
||||||
void updatePaidReactionShownPeer(PeerId shownPeer);
|
void updatePaidReactionShownPeer(PeerId shownPeer);
|
||||||
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
|
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
|
||||||
[[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
|
[[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void apply(const MTPGlobalPrivacySettings &data);
|
void apply(const MTPGlobalPrivacySettings &settings);
|
||||||
|
|
||||||
void update(
|
void update(
|
||||||
bool archiveAndMute,
|
bool archiveAndMute,
|
||||||
UnarchiveOnNewMessage unarchiveOnNewMessage,
|
UnarchiveOnNewMessage unarchiveOnNewMessage,
|
||||||
bool hideReadTime,
|
bool hideReadTime,
|
||||||
bool newRequirePremium);
|
bool newRequirePremium,
|
||||||
|
int newChargeStars);
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
@ -76,6 +81,7 @@ private:
|
||||||
rpl::variable<bool> _showArchiveAndMute = false;
|
rpl::variable<bool> _showArchiveAndMute = false;
|
||||||
rpl::variable<bool> _hideReadTime = false;
|
rpl::variable<bool> _hideReadTime = false;
|
||||||
rpl::variable<bool> _newRequirePremium = false;
|
rpl::variable<bool> _newRequirePremium = false;
|
||||||
|
rpl::variable<int> _newChargeStars = 0;
|
||||||
rpl::variable<PeerId> _paidReactionShownPeer = false;
|
rpl::variable<PeerId> _paidReactionShownPeer = false;
|
||||||
std::vector<Fn<void()>> _callbacks;
|
std::vector<Fn<void()>> _callbacks;
|
||||||
bool _paidReactionShownPeerLoaded = false;
|
bool _paidReactionShownPeerLoaded = false;
|
||||||
|
|
|
@ -42,7 +42,7 @@ Polls::Polls(not_null<ApiWrap*> api)
|
||||||
|
|
||||||
void Polls::create(
|
void Polls::create(
|
||||||
const PollData &data,
|
const PollData &data,
|
||||||
const SendAction &action,
|
SendAction action,
|
||||||
Fn<void()> done,
|
Fn<void()> done,
|
||||||
Fn<void()> fail) {
|
Fn<void()> fail) {
|
||||||
_session->api().sendAction(action);
|
_session->api().sendAction(action);
|
||||||
|
@ -64,6 +64,9 @@ void Polls::create(
|
||||||
history->startSavingCloudDraft(topicRootId);
|
history->startSavingCloudDraft(topicRootId);
|
||||||
}
|
}
|
||||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
action.options.starsApproved);
|
||||||
if (silentPost) {
|
if (silentPost) {
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||||
}
|
}
|
||||||
|
@ -76,6 +79,10 @@ void Polls::create(
|
||||||
if (action.options.effectId) {
|
if (action.options.effectId) {
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||||
}
|
}
|
||||||
|
if (starsPaid) {
|
||||||
|
action.options.starsApproved -= starsPaid;
|
||||||
|
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||||
|
}
|
||||||
const auto sendAs = action.options.sendAs;
|
const auto sendAs = action.options.sendAs;
|
||||||
if (sendAs) {
|
if (sendAs) {
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||||
|
@ -98,7 +105,8 @@ void Polls::create(
|
||||||
MTP_int(action.options.scheduled),
|
MTP_int(action.options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||||
MTP_long(action.options.effectId)
|
MTP_long(action.options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||||
if (clearCloudDraft) {
|
if (clearCloudDraft) {
|
||||||
history->finishSavingCloudDraft(
|
history->finishSavingCloudDraft(
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
|
|
||||||
void create(
|
void create(
|
||||||
const PollData &data,
|
const PollData &data,
|
||||||
const SendAction &action,
|
SendAction action,
|
||||||
Fn<void()> done,
|
Fn<void()> done,
|
||||||
Fn<void()> fail);
|
Fn<void()> fail);
|
||||||
void sendVotes(
|
void sendVotes(
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "base/random.h"
|
#include "base/random.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_peer_values.h"
|
#include "data/data_peer_values.h"
|
||||||
|
@ -377,15 +378,15 @@ const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const {
|
||||||
return _subscriptionOptions;
|
return _subscriptionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> Premium::somePremiumRequiredResolved() const {
|
rpl::producer<> Premium::someMessageMoneyRestrictionsResolved() const {
|
||||||
return _somePremiumRequiredResolved.events();
|
return _someMessageMoneyRestrictionsResolved.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Premium::resolvePremiumRequired(not_null<UserData*> user) {
|
void Premium::resolveMessageMoneyRestrictions(not_null<UserData*> user) {
|
||||||
_resolvePremiumRequiredUsers.emplace(user);
|
_resolveMessageMoneyRequiredUsers.emplace(user);
|
||||||
if (!_premiumRequiredRequestScheduled
|
if (!_messageMoneyRequestScheduled
|
||||||
&& _resolvePremiumRequestedUsers.empty()) {
|
&& _resolveMessageMoneyRequestedUsers.empty()) {
|
||||||
_premiumRequiredRequestScheduled = true;
|
_messageMoneyRequestScheduled = true;
|
||||||
crl::on_main(_session, [=] {
|
crl::on_main(_session, [=] {
|
||||||
requestPremiumRequiredSlice();
|
requestPremiumRequiredSlice();
|
||||||
});
|
});
|
||||||
|
@ -393,50 +394,65 @@ void Premium::resolvePremiumRequired(not_null<UserData*> user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Premium::requestPremiumRequiredSlice() {
|
void Premium::requestPremiumRequiredSlice() {
|
||||||
_premiumRequiredRequestScheduled = false;
|
_messageMoneyRequestScheduled = false;
|
||||||
if (!_resolvePremiumRequestedUsers.empty()
|
if (!_resolveMessageMoneyRequestedUsers.empty()
|
||||||
|| _resolvePremiumRequiredUsers.empty()) {
|
|| _resolveMessageMoneyRequiredUsers.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
constexpr auto kPerRequest = 100;
|
constexpr auto kPerRequest = 100;
|
||||||
auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers
|
auto users = MTP_vector_from_range(_resolveMessageMoneyRequiredUsers
|
||||||
| ranges::views::transform(&UserData::inputUser));
|
| ranges::views::transform(&UserData::inputUser));
|
||||||
if (users.v.size() > kPerRequest) {
|
if (users.v.size() > kPerRequest) {
|
||||||
auto shortened = users.v;
|
auto shortened = users.v;
|
||||||
shortened.resize(kPerRequest);
|
shortened.resize(kPerRequest);
|
||||||
users = MTP_vector<MTPInputUser>(std::move(shortened));
|
users = MTP_vector<MTPInputUser>(std::move(shortened));
|
||||||
const auto from = begin(_resolvePremiumRequiredUsers);
|
const auto from = begin(_resolveMessageMoneyRequiredUsers);
|
||||||
_resolvePremiumRequestedUsers = { from, from + kPerRequest };
|
_resolveMessageMoneyRequestedUsers = { from, from + kPerRequest };
|
||||||
_resolvePremiumRequiredUsers.erase(from, from + kPerRequest);
|
_resolveMessageMoneyRequiredUsers.erase(from, from + kPerRequest);
|
||||||
} else {
|
} else {
|
||||||
_resolvePremiumRequestedUsers
|
_resolveMessageMoneyRequestedUsers
|
||||||
= base::take(_resolvePremiumRequiredUsers);
|
= base::take(_resolveMessageMoneyRequiredUsers);
|
||||||
}
|
}
|
||||||
const auto finish = [=](const QVector<MTPBool> &list) {
|
const auto finish = [=](const QVector<MTPRequirementToContact> &list) {
|
||||||
constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite;
|
|
||||||
constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown;
|
|
||||||
constexpr auto mask = me | known;
|
|
||||||
|
|
||||||
auto index = 0;
|
auto index = 0;
|
||||||
for (const auto &user : base::take(_resolvePremiumRequestedUsers)) {
|
for (const auto &user : base::take(_resolveMessageMoneyRequestedUsers)) {
|
||||||
const auto require = (index < list.size())
|
const auto set = [&](bool requirePremium, int stars) {
|
||||||
&& mtpIsTrue(list[index++]);
|
using Flag = UserDataFlag;
|
||||||
user->setFlags((user->flags() & ~mask)
|
constexpr auto me = Flag::RequiresPremiumToWrite;
|
||||||
| known
|
constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
|
||||||
| (require ? me : UserDataFlag()));
|
constexpr auto hasPrem = Flag::HasRequirePremiumToWrite;
|
||||||
|
constexpr auto hasStars = Flag::HasStarsPerMessage;
|
||||||
|
user->setStarsPerMessage(stars);
|
||||||
|
user->setFlags((user->flags() & ~(me | hasPrem | hasStars))
|
||||||
|
| known
|
||||||
|
| (requirePremium ? (me | hasPrem) : Flag())
|
||||||
|
| (stars ? hasStars : Flag()));
|
||||||
|
};
|
||||||
|
if (index >= list.size()) {
|
||||||
|
set(false, 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list[index++].match([&](const MTPDrequirementToContactEmpty &) {
|
||||||
|
set(false, 0);
|
||||||
|
}, [&](const MTPDrequirementToContactPremium &) {
|
||||||
|
set(true, 0);
|
||||||
|
}, [&](const MTPDrequirementToContactPaidMessages &data) {
|
||||||
|
set(false, data.vstars_amount().v);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!_premiumRequiredRequestScheduled
|
if (!_messageMoneyRequestScheduled
|
||||||
&& !_resolvePremiumRequiredUsers.empty()) {
|
&& !_resolveMessageMoneyRequiredUsers.empty()) {
|
||||||
_premiumRequiredRequestScheduled = true;
|
_messageMoneyRequestScheduled = true;
|
||||||
crl::on_main(_session, [=] {
|
crl::on_main(_session, [=] {
|
||||||
requestPremiumRequiredSlice();
|
requestPremiumRequiredSlice();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_somePremiumRequiredResolved.fire({});
|
_someMessageMoneyRestrictionsResolved.fire({});
|
||||||
};
|
};
|
||||||
_session->api().request(
|
_session->api().request(
|
||||||
MTPusers_GetIsPremiumRequiredToContact(std::move(users))
|
MTPusers_GetRequirementsToContact(std::move(users))
|
||||||
).done([=](const MTPVector<MTPBool> &result) {
|
).done([=](const MTPVector<MTPRequirementToContact> &result) {
|
||||||
finish(result.v);
|
finish(result.v);
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
finish({});
|
finish({});
|
||||||
|
@ -463,10 +479,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
|
||||||
for (const auto &tlOption : result.v) {
|
for (const auto &tlOption : result.v) {
|
||||||
const auto &data = tlOption.data();
|
const auto &data = tlOption.data();
|
||||||
tlMapOptions[data.vusers().v].push_back(tlOption);
|
tlMapOptions[data.vusers().v].push_back(tlOption);
|
||||||
|
if (qs(data.vcurrency()) == Ui::kCreditsCurrency) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const auto token = Token{ data.vusers().v, data.vmonths().v };
|
const auto token = Token{ data.vusers().v, data.vmonths().v };
|
||||||
_stores[token] = Store{
|
_stores[token] = Store{
|
||||||
.amount = data.vamount().v,
|
.amount = data.vamount().v,
|
||||||
|
.currency = qs(data.vcurrency()),
|
||||||
.product = qs(data.vstore_product().value_or_empty()),
|
.product = qs(data.vstore_product().value_or_empty()),
|
||||||
.quantity = data.vstore_quantity().value_or_empty(),
|
.quantity = data.vstore_quantity().value_or_empty(),
|
||||||
};
|
};
|
||||||
|
@ -475,14 +495,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto &[amount, tlOptions] : tlMapOptions) {
|
for (const auto &[amount, tlOptions] : tlMapOptions) {
|
||||||
if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) {
|
if (amount == 1 && _optionsForOnePerson.currencies.empty()) {
|
||||||
_optionsForOnePerson.currency = qs(
|
|
||||||
tlOptions.front().data().vcurrency());
|
|
||||||
for (const auto &option : tlOptions) {
|
for (const auto &option : tlOptions) {
|
||||||
_optionsForOnePerson.months.push_back(
|
_optionsForOnePerson.months.push_back(
|
||||||
option.data().vmonths().v);
|
option.data().vmonths().v);
|
||||||
_optionsForOnePerson.totalCosts.push_back(
|
_optionsForOnePerson.totalCosts.push_back(
|
||||||
option.data().vamount().v);
|
option.data().vamount().v);
|
||||||
|
_optionsForOnePerson.currencies.push_back(
|
||||||
|
qs(option.data().vcurrency()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
||||||
|
@ -509,7 +529,7 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
||||||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||||
_peer->input,
|
_peer->input,
|
||||||
MTP_long(prepaidId),
|
MTP_long(prepaidId),
|
||||||
invoice.creditsAmount
|
invoice.giveawayCredits
|
||||||
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
||||||
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||||
)).done([=](const MTPUpdates &result) {
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
@ -540,7 +560,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||||
const auto token = Token{ users, months };
|
const auto token = Token{ users, months };
|
||||||
const auto &store = _stores[token];
|
const auto &store = _stores[token];
|
||||||
return Payments::InvoicePremiumGiftCode{
|
return Payments::InvoicePremiumGiftCode{
|
||||||
.currency = _optionsForOnePerson.currency,
|
.currency = store.currency,
|
||||||
.storeProduct = store.product,
|
.storeProduct = store.product,
|
||||||
.randomId = randomId,
|
.randomId = randomId,
|
||||||
.amount = store.amount,
|
.amount = store.amount,
|
||||||
|
@ -553,14 +573,15 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||||
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
|
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
|
||||||
auto result = std::vector<GiftOptionData>();
|
auto result = std::vector<GiftOptionData>();
|
||||||
|
|
||||||
if (!_optionsForOnePerson.currency.isEmpty()) {
|
if (!_optionsForOnePerson.currencies.empty()) {
|
||||||
const auto count = int(_optionsForOnePerson.months.size());
|
const auto count = int(_optionsForOnePerson.months.size());
|
||||||
result.reserve(count);
|
result.reserve(count);
|
||||||
for (auto i = 0; i != count; ++i) {
|
for (auto i = 0; i != count; ++i) {
|
||||||
Assert(i < _optionsForOnePerson.totalCosts.size());
|
Assert(i < _optionsForOnePerson.totalCosts.size());
|
||||||
|
Assert(i < _optionsForOnePerson.currencies.size());
|
||||||
result.push_back({
|
result.push_back({
|
||||||
.cost = _optionsForOnePerson.totalCosts[i],
|
.cost = _optionsForOnePerson.totalCosts[i],
|
||||||
.currency = _optionsForOnePerson.currency,
|
.currency = _optionsForOnePerson.currencies[i],
|
||||||
.months = _optionsForOnePerson.months[i],
|
.months = _optionsForOnePerson.months[i],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -581,7 +602,7 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||||
MTP_int(_optionsForOnePerson.months[i]),
|
MTP_int(_optionsForOnePerson.months[i]),
|
||||||
MTPstring(),
|
MTPstring(),
|
||||||
MTPint(),
|
MTPint(),
|
||||||
MTP_string(_optionsForOnePerson.currency),
|
MTP_string(_optionsForOnePerson.currencies[i]),
|
||||||
MTP_long(_optionsForOnePerson.totalCosts[i] * amount)));
|
MTP_long(_optionsForOnePerson.totalCosts[i] * amount)));
|
||||||
}
|
}
|
||||||
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
||||||
|
@ -694,28 +715,38 @@ rpl::producer<rpl::no_value, QString> SponsoredToggle::setToggled(bool v) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RequirePremiumState ResolveRequiresPremiumToWrite(
|
MessageMoneyRestriction ResolveMessageMoneyRestrictions(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
History *maybeHistory) {
|
History *maybeHistory) {
|
||||||
const auto user = peer->asUser();
|
if (const auto channel = peer->asChannel()) {
|
||||||
if (!user
|
return {
|
||||||
|| !user->someRequirePremiumToWrite()
|
.starsPerMessage = channel->starsPerMessageChecked(),
|
||||||
|| user->session().premium()) {
|
.known = true,
|
||||||
return RequirePremiumState::No;
|
};
|
||||||
} else if (user->requirePremiumToWriteKnown()) {
|
}
|
||||||
return user->meRequiresPremiumToWrite()
|
const auto user = peer->asUser();
|
||||||
? RequirePremiumState::Yes
|
if (!user) {
|
||||||
: RequirePremiumState::No;
|
return { .known = true };
|
||||||
} else if (user->flags() & UserDataFlag::MutualContact) {
|
} else if (user->messageMoneyRestrictionsKnown()) {
|
||||||
return RequirePremiumState::No;
|
return {
|
||||||
} else if (!maybeHistory) {
|
.starsPerMessage = user->starsPerMessageChecked(),
|
||||||
return RequirePremiumState::Unknown;
|
.premiumRequired = (user->requiresPremiumToWrite()
|
||||||
|
&& !user->session().premium()),
|
||||||
|
.known = true,
|
||||||
|
};
|
||||||
|
} else if (user->hasStarsPerMessage()) {
|
||||||
|
return {};
|
||||||
|
} else if (!user->hasRequirePremiumToWrite()) {
|
||||||
|
return { .known = true };
|
||||||
|
} else if (user->flags() & UserDataFlag::MutualContact) {
|
||||||
|
return { .known = true };
|
||||||
|
} else if (!maybeHistory) {
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto update = [&](bool require) {
|
const auto update = [&](bool require) {
|
||||||
using Flag = UserDataFlag;
|
using Flag = UserDataFlag;
|
||||||
constexpr auto known = Flag::RequirePremiumToWriteKnown;
|
constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
|
||||||
constexpr auto me = Flag::MeRequiresPremiumToWrite;
|
constexpr auto me = Flag::RequiresPremiumToWrite;
|
||||||
user->setFlags((user->flags() & ~me)
|
user->setFlags((user->flags() & ~me)
|
||||||
| known
|
| known
|
||||||
| (require ? me : Flag()));
|
| (require ? me : Flag()));
|
||||||
|
@ -727,16 +758,19 @@ RequirePremiumState ResolveRequiresPremiumToWrite(
|
||||||
const auto item = view->data();
|
const auto item = view->data();
|
||||||
if (!item->out() && !item->isService()) {
|
if (!item->out() && !item->isService()) {
|
||||||
update(false);
|
update(false);
|
||||||
return RequirePremiumState::No;
|
return { .known = true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user->isContact() // Here we know, that we're not in his contacts.
|
if (user->isContact() // Here we know, that we're not in his contacts.
|
||||||
&& maybeHistory->loadedAtTop() // And no incoming messages.
|
&& maybeHistory->loadedAtTop() // And no incoming messages.
|
||||||
&& maybeHistory->loadedAtBottom()) {
|
&& maybeHistory->loadedAtBottom()) {
|
||||||
update(true);
|
return {
|
||||||
|
.premiumRequired = !user->session().premium(),
|
||||||
|
.known = true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return RequirePremiumState::Unknown;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<DocumentData*> RandomHelloStickerValue(
|
rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||||
|
@ -870,6 +904,7 @@ std::optional<Data::SavedStarGift> FromTL(
|
||||||
.date = data.vdate().v,
|
.date = data.vdate().v,
|
||||||
.upgradable = data.is_can_upgrade(),
|
.upgradable = data.is_can_upgrade(),
|
||||||
.anonymous = data.is_name_hidden(),
|
.anonymous = data.is_name_hidden(),
|
||||||
|
.pinned = data.is_pinned_to_top(),
|
||||||
.hidden = data.is_unsaved(),
|
.hidden = data.is_unsaved(),
|
||||||
.mine = to->isSelf(),
|
.mine = to->isSelf(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -116,8 +116,9 @@ public:
|
||||||
[[nodiscard]] auto subscriptionOptions() const
|
[[nodiscard]] auto subscriptionOptions() const
|
||||||
-> const Data::PremiumSubscriptionOptions &;
|
-> const Data::PremiumSubscriptionOptions &;
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const;
|
[[nodiscard]] auto someMessageMoneyRestrictionsResolved() const
|
||||||
void resolvePremiumRequired(not_null<UserData*> user);
|
-> rpl::producer<>;
|
||||||
|
void resolveMessageMoneyRestrictions(not_null<UserData*> user);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void reloadPromo();
|
void reloadPromo();
|
||||||
|
@ -166,10 +167,10 @@ private:
|
||||||
|
|
||||||
Data::PremiumSubscriptionOptions _subscriptionOptions;
|
Data::PremiumSubscriptionOptions _subscriptionOptions;
|
||||||
|
|
||||||
rpl::event_stream<> _somePremiumRequiredResolved;
|
rpl::event_stream<> _someMessageMoneyRestrictionsResolved;
|
||||||
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers;
|
base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequiredUsers;
|
||||||
base::flat_set<not_null<UserData*>> _resolvePremiumRequestedUsers;
|
base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequestedUsers;
|
||||||
bool _premiumRequiredRequestScheduled = false;
|
bool _messageMoneyRequestScheduled = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -208,6 +209,7 @@ private:
|
||||||
};
|
};
|
||||||
struct Store final {
|
struct Store final {
|
||||||
uint64 amount = 0;
|
uint64 amount = 0;
|
||||||
|
QString currency;
|
||||||
QString product;
|
QString product;
|
||||||
int quantity = 0;
|
int quantity = 0;
|
||||||
};
|
};
|
||||||
|
@ -218,7 +220,7 @@ private:
|
||||||
struct {
|
struct {
|
||||||
std::vector<int> months;
|
std::vector<int> months;
|
||||||
std::vector<int64> totalCosts;
|
std::vector<int64> totalCosts;
|
||||||
QString currency;
|
std::vector<QString> currencies;
|
||||||
} _optionsForOnePerson;
|
} _optionsForOnePerson;
|
||||||
|
|
||||||
std::vector<int> _availablePresets;
|
std::vector<int> _availablePresets;
|
||||||
|
@ -244,12 +246,20 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class RequirePremiumState {
|
struct MessageMoneyRestriction {
|
||||||
Unknown,
|
int starsPerMessage = 0;
|
||||||
Yes,
|
bool premiumRequired = false;
|
||||||
No,
|
bool known = false;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return starsPerMessage != 0 || premiumRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend inline bool operator==(
|
||||||
|
const MessageMoneyRestriction &,
|
||||||
|
const MessageMoneyRestriction &) = default;
|
||||||
};
|
};
|
||||||
[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite(
|
[[nodiscard]] MessageMoneyRestriction ResolveMessageMoneyRestrictions(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
History *maybeHistory);
|
History *maybeHistory);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ Data::PremiumSubscriptionOption CreateSubscriptionOption(
|
||||||
}();
|
}();
|
||||||
return {
|
return {
|
||||||
.duration = Ui::FormatTTL(months * 86400 * 31),
|
.duration = Ui::FormatTTL(months * 86400 * 31),
|
||||||
.discount = discount
|
.discount = (discount > 0)
|
||||||
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
|
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
|
||||||
: QString(),
|
: QString(),
|
||||||
.costPerMonth = Ui::FillAmountAndCurrency(
|
.costPerMonth = Ui::FillAmountAndCurrency(
|
||||||
|
|
|
@ -24,15 +24,26 @@ template<typename Option>
|
||||||
if (tlOpts.isEmpty()) {
|
if (tlOpts.isEmpty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
auto monthlyAmountPerCurrency = base::flat_map<QString, int>();
|
||||||
auto result = Data::PremiumSubscriptionOptions();
|
auto result = Data::PremiumSubscriptionOptions();
|
||||||
const auto monthlyAmount = [&] {
|
const auto monthlyAmount = [&](const QString ¤cy) -> int {
|
||||||
|
const auto it = monthlyAmountPerCurrency.find(currency);
|
||||||
|
if (it != end(monthlyAmountPerCurrency)) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
const auto &min = ranges::min_element(
|
const auto &min = ranges::min_element(
|
||||||
tlOpts,
|
tlOpts,
|
||||||
ranges::less(),
|
ranges::less(),
|
||||||
[](const Option &o) { return o.data().vamount().v; }
|
[&](const Option &o) {
|
||||||
|
return currency == qs(o.data().vcurrency())
|
||||||
|
? o.data().vamount().v
|
||||||
|
: std::numeric_limits<int64_t>::max();
|
||||||
|
}
|
||||||
)->data();
|
)->data();
|
||||||
return min.vamount().v / float64(min.vmonths().v);
|
const auto monthly = min.vamount().v / float64(min.vmonths().v);
|
||||||
}();
|
monthlyAmountPerCurrency.emplace(currency, monthly);
|
||||||
|
return monthly;
|
||||||
|
};
|
||||||
result.reserve(tlOpts.size());
|
result.reserve(tlOpts.size());
|
||||||
for (const auto &tlOption : tlOpts) {
|
for (const auto &tlOption : tlOpts) {
|
||||||
const auto &option = tlOption.data();
|
const auto &option = tlOption.data();
|
||||||
|
@ -45,7 +56,7 @@ template<typename Option>
|
||||||
const auto currency = qs(option.vcurrency());
|
const auto currency = qs(option.vcurrency());
|
||||||
result.push_back(CreateSubscriptionOption(
|
result.push_back(CreateSubscriptionOption(
|
||||||
months,
|
months,
|
||||||
monthlyAmount,
|
monthlyAmount(currency),
|
||||||
amount,
|
amount,
|
||||||
currency,
|
currency,
|
||||||
botUrl));
|
botUrl));
|
||||||
|
|
|
@ -95,7 +95,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||||
const auto messagePostAuthor = peer->isBroadcast()
|
const auto messagePostAuthor = peer->isBroadcast()
|
||||||
? session->user()->name()
|
? session->user()->name()
|
||||||
: QString();
|
: QString();
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
action.options.starsApproved);
|
||||||
if (action.options.scheduled) {
|
if (action.options.scheduled) {
|
||||||
flags |= MessageFlag::IsOrWasScheduled;
|
flags |= MessageFlag::IsOrWasScheduled;
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||||
|
@ -111,6 +113,10 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||||
flags |= MessageFlag::InvertMedia;
|
flags |= MessageFlag::InvertMedia;
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||||
}
|
}
|
||||||
|
if (starsPaid) {
|
||||||
|
action.options.starsApproved -= starsPaid;
|
||||||
|
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||||
|
}
|
||||||
|
|
||||||
auto &histories = history->owner().histories();
|
auto &histories = history->owner().histories();
|
||||||
histories.sendPreparedMessage(
|
histories.sendPreparedMessage(
|
||||||
|
@ -129,7 +135,8 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||||
MTP_int(action.options.scheduled),
|
MTP_int(action.options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||||
MTP_long(action.options.effectId)
|
MTP_long(action.options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||||
api->sendMessageFail(error, peer, randomId);
|
api->sendMessageFail(error, peer, randomId);
|
||||||
|
@ -160,7 +167,7 @@ void SendExistingMedia(
|
||||||
? (*localMessageId)
|
? (*localMessageId)
|
||||||
: session->data().nextLocalMessageId());
|
: session->data().nextLocalMessageId());
|
||||||
const auto randomId = base::RandomValue<uint64>();
|
const auto randomId = base::RandomValue<uint64>();
|
||||||
const auto &action = message.action;
|
auto &action = message.action;
|
||||||
|
|
||||||
auto flags = NewMessageFlags(peer);
|
auto flags = NewMessageFlags(peer);
|
||||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||||
|
@ -190,7 +197,9 @@ void SendExistingMedia(
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
||||||
}
|
}
|
||||||
const auto captionText = caption.text;
|
const auto captionText = caption.text;
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
action.options.starsApproved);
|
||||||
if (action.options.scheduled) {
|
if (action.options.scheduled) {
|
||||||
flags |= MessageFlag::IsOrWasScheduled;
|
flags |= MessageFlag::IsOrWasScheduled;
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||||
|
@ -206,6 +215,10 @@ void SendExistingMedia(
|
||||||
flags |= MessageFlag::InvertMedia;
|
flags |= MessageFlag::InvertMedia;
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||||
}
|
}
|
||||||
|
if (starsPaid) {
|
||||||
|
action.options.starsApproved -= starsPaid;
|
||||||
|
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||||
|
}
|
||||||
|
|
||||||
session->data().registerMessageRandomId(randomId, newId);
|
session->data().registerMessageRandomId(randomId, newId);
|
||||||
|
|
||||||
|
@ -216,6 +229,7 @@ void SendExistingMedia(
|
||||||
.replyTo = action.replyTo,
|
.replyTo = action.replyTo,
|
||||||
.date = NewMessageDate(action.options),
|
.date = NewMessageDate(action.options),
|
||||||
.shortcutId = action.options.shortcutId,
|
.shortcutId = action.options.shortcutId,
|
||||||
|
.starsPaid = starsPaid,
|
||||||
.postAuthor = NewMessagePostAuthor(action),
|
.postAuthor = NewMessagePostAuthor(action),
|
||||||
.effectId = action.options.effectId,
|
.effectId = action.options.effectId,
|
||||||
}, media, caption);
|
}, media, caption);
|
||||||
|
@ -240,7 +254,8 @@ void SendExistingMedia(
|
||||||
MTP_int(action.options.scheduled),
|
MTP_int(action.options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||||
MTP_long(action.options.effectId)
|
MTP_long(action.options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||||
if (error.code() == 400
|
if (error.code() == 400
|
||||||
|
@ -341,7 +356,7 @@ bool SendDice(MessageToSend &message) {
|
||||||
message.action.generateLocal = true;
|
message.action.generateLocal = true;
|
||||||
|
|
||||||
|
|
||||||
const auto &action = message.action;
|
auto &action = message.action;
|
||||||
api->sendAction(action);
|
api->sendAction(action);
|
||||||
|
|
||||||
const auto newId = FullMsgId(
|
const auto newId = FullMsgId(
|
||||||
|
@ -380,6 +395,13 @@ bool SendDice(MessageToSend &message) {
|
||||||
flags |= MessageFlag::InvertMedia;
|
flags |= MessageFlag::InvertMedia;
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||||
}
|
}
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
action.options.starsApproved);
|
||||||
|
if (starsPaid) {
|
||||||
|
action.options.starsApproved -= starsPaid;
|
||||||
|
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||||
|
}
|
||||||
|
|
||||||
session->data().registerMessageRandomId(randomId, newId);
|
session->data().registerMessageRandomId(randomId, newId);
|
||||||
|
|
||||||
|
@ -390,6 +412,7 @@ bool SendDice(MessageToSend &message) {
|
||||||
.replyTo = action.replyTo,
|
.replyTo = action.replyTo,
|
||||||
.date = NewMessageDate(action.options),
|
.date = NewMessageDate(action.options),
|
||||||
.shortcutId = action.options.shortcutId,
|
.shortcutId = action.options.shortcutId,
|
||||||
|
.starsPaid = starsPaid,
|
||||||
.postAuthor = NewMessagePostAuthor(action),
|
.postAuthor = NewMessagePostAuthor(action),
|
||||||
.effectId = action.options.effectId,
|
.effectId = action.options.effectId,
|
||||||
}, TextWithEntities(), MTP_messageMediaDice(
|
}, TextWithEntities(), MTP_messageMediaDice(
|
||||||
|
@ -411,7 +434,8 @@ bool SendDice(MessageToSend &message) {
|
||||||
MTP_int(action.options.scheduled),
|
MTP_int(action.options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||||
MTP_long(action.options.effectId)
|
MTP_long(action.options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||||
api->sendMessageFail(error, peer, randomId, newId);
|
api->sendMessageFail(error, peer, randomId, newId);
|
||||||
|
@ -610,6 +634,9 @@ void SendConfirmedFile(
|
||||||
.replyTo = file->to.replyTo,
|
.replyTo = file->to.replyTo,
|
||||||
.date = NewMessageDate(file->to.options),
|
.date = NewMessageDate(file->to.options),
|
||||||
.shortcutId = file->to.options.shortcutId,
|
.shortcutId = file->to.options.shortcutId,
|
||||||
|
.starsPaid = std::min(
|
||||||
|
history->peer->starsPerMessageChecked(),
|
||||||
|
file->to.options.starsApproved),
|
||||||
.postAuthor = NewMessagePostAuthor(action),
|
.postAuthor = NewMessagePostAuthor(action),
|
||||||
.groupedId = groupId,
|
.groupedId = groupId,
|
||||||
.effectId = file->to.options.effectId,
|
.effectId = file->to.options.effectId,
|
||||||
|
|
|
@ -1227,7 +1227,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||||
MTPint(), // quick_reply_shortcut_id
|
MTPint(), // quick_reply_shortcut_id
|
||||||
MTPlong(), // effect
|
MTPlong(), // effect
|
||||||
MTPFactCheck(),
|
MTPFactCheck(),
|
||||||
MTPint()), // report_delivery_until_date
|
MTPint(), // report_delivery_until_date
|
||||||
|
MTPlong()), // paid_message_stars
|
||||||
MessageFlags(),
|
MessageFlags(),
|
||||||
NewMessageType::Unread);
|
NewMessageType::Unread);
|
||||||
} break;
|
} break;
|
||||||
|
@ -1265,7 +1266,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||||
MTPint(), // quick_reply_shortcut_id
|
MTPint(), // quick_reply_shortcut_id
|
||||||
MTPlong(), // effect
|
MTPlong(), // effect
|
||||||
MTPFactCheck(),
|
MTPFactCheck(),
|
||||||
MTPint()), // report_delivery_until_date
|
MTPint(), // report_delivery_until_date
|
||||||
|
MTPlong()), // paid_message_stars
|
||||||
MessageFlags(),
|
MessageFlags(),
|
||||||
NewMessageType::Unread);
|
NewMessageType::Unread);
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -210,6 +210,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
|
||||||
case Key::About: return MTP_inputPrivacyKeyAbout();
|
case Key::About: return MTP_inputPrivacyKeyAbout();
|
||||||
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
||||||
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
||||||
|
case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages();
|
||||||
}
|
}
|
||||||
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
||||||
}
|
}
|
||||||
|
@ -241,6 +242,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
|
||||||
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
|
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
|
||||||
case mtpc_privacyKeyStarGiftsAutoSave:
|
case mtpc_privacyKeyStarGiftsAutoSave:
|
||||||
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
||||||
|
case mtpc_privacyKeyNoPaidMessages:
|
||||||
|
case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ public:
|
||||||
About,
|
About,
|
||||||
Birthday,
|
Birthday,
|
||||||
GiftsAutoSave,
|
GiftsAutoSave,
|
||||||
|
NoPaidMessages,
|
||||||
};
|
};
|
||||||
enum class Option {
|
enum class Option {
|
||||||
Everyone,
|
Everyone,
|
||||||
|
|
|
@ -546,6 +546,7 @@ void ApiWrap::sendMessageFail(
|
||||||
uint64 randomId,
|
uint64 randomId,
|
||||||
FullMsgId itemId) {
|
FullMsgId itemId) {
|
||||||
const auto show = ShowForPeer(peer);
|
const auto show = ShowForPeer(peer);
|
||||||
|
const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q;
|
||||||
if (show && error == u"PEER_FLOOD"_q) {
|
if (show && error == u"PEER_FLOOD"_q) {
|
||||||
show->showBox(
|
show->showBox(
|
||||||
Ui::MakeInformBox(
|
Ui::MakeInformBox(
|
||||||
|
@ -600,6 +601,19 @@ void ApiWrap::sendMessageFail(
|
||||||
if (show) {
|
if (show) {
|
||||||
show->showToast(tr::lng_error_schedule_limit(tr::now));
|
show->showToast(tr::lng_error_schedule_limit(tr::now));
|
||||||
}
|
}
|
||||||
|
} else if (error.startsWith(paidStarsPrefix)) {
|
||||||
|
if (show) {
|
||||||
|
show->showToast(
|
||||||
|
u"Payment requirements changed. Please, try again."_q);
|
||||||
|
}
|
||||||
|
if (const auto stars = error.mid(paidStarsPrefix.size()).toInt()) {
|
||||||
|
if (const auto user = peer->asUser()) {
|
||||||
|
user->setStarsPerMessage(stars);
|
||||||
|
} else if (const auto channel = peer->asChannel()) {
|
||||||
|
channel->setStarsPerMessage(stars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peer->updateFull();
|
||||||
}
|
}
|
||||||
if (const auto item = _session->data().message(itemId)) {
|
if (const auto item = _session->data().message(itemId)) {
|
||||||
Assert(randomId != 0);
|
Assert(randomId != 0);
|
||||||
|
@ -3342,7 +3356,7 @@ void ApiWrap::finishForwarding(const SendAction &action) {
|
||||||
|
|
||||||
void ApiWrap::forwardMessages(
|
void ApiWrap::forwardMessages(
|
||||||
Data::ResolvedForwardDraft &&draft,
|
Data::ResolvedForwardDraft &&draft,
|
||||||
const SendAction &action,
|
SendAction action,
|
||||||
FnMut<void()> &&successCallback) {
|
FnMut<void()> &&successCallback) {
|
||||||
Expects(!draft.items.empty());
|
Expects(!draft.items.empty());
|
||||||
|
|
||||||
|
@ -3417,9 +3431,17 @@ void ApiWrap::forwardMessages(
|
||||||
const auto requestType = Data::Histories::RequestType::Send;
|
const auto requestType = Data::Histories::RequestType::Send;
|
||||||
const auto idsCopy = localIds;
|
const auto idsCopy = localIds;
|
||||||
const auto scheduled = action.options.scheduled;
|
const auto scheduled = action.options.scheduled;
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
action.options.starsApproved,
|
||||||
|
int(ids.size() * peer->starsPerMessageChecked()));
|
||||||
|
auto oneFlags = sendFlags;
|
||||||
|
if (starsPaid) {
|
||||||
|
action.options.starsApproved -= starsPaid;
|
||||||
|
oneFlags |= SendFlag::f_allow_paid_stars;
|
||||||
|
}
|
||||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||||
MTP_flags(sendFlags),
|
MTP_flags(oneFlags),
|
||||||
forwardFrom->input,
|
forwardFrom->input,
|
||||||
MTP_vector<MTPint>(ids),
|
MTP_vector<MTPint>(ids),
|
||||||
MTP_vector<MTPlong>(randomIds),
|
MTP_vector<MTPlong>(randomIds),
|
||||||
|
@ -3428,7 +3450,8 @@ void ApiWrap::forwardMessages(
|
||||||
MTP_int(action.options.scheduled),
|
MTP_int(action.options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||||
MTPint() // video_timestamp
|
MTPint(), // video_timestamp
|
||||||
|
MTP_long(starsPaid)
|
||||||
)).done([=](const MTPUpdates &result) {
|
)).done([=](const MTPUpdates &result) {
|
||||||
if (!scheduled) {
|
if (!scheduled) {
|
||||||
this->updates().checkForSentToScheduled(result);
|
this->updates().checkForSentToScheduled(result);
|
||||||
|
@ -3480,6 +3503,7 @@ void ApiWrap::forwardMessages(
|
||||||
.replyTo = { .topicRootId = topMsgId },
|
.replyTo = { .topicRootId = topMsgId },
|
||||||
.date = NewMessageDate(action.options),
|
.date = NewMessageDate(action.options),
|
||||||
.shortcutId = action.options.shortcutId,
|
.shortcutId = action.options.shortcutId,
|
||||||
|
.starsPaid = action.options.starsApproved,
|
||||||
.postAuthor = NewMessagePostAuthor(action),
|
.postAuthor = NewMessagePostAuthor(action),
|
||||||
|
|
||||||
// forwarded messages don't have effects
|
// forwarded messages don't have effects
|
||||||
|
@ -3573,6 +3597,7 @@ void ApiWrap::sendSharedContact(
|
||||||
.replyTo = action.replyTo,
|
.replyTo = action.replyTo,
|
||||||
.date = NewMessageDate(action.options),
|
.date = NewMessageDate(action.options),
|
||||||
.shortcutId = action.options.shortcutId,
|
.shortcutId = action.options.shortcutId,
|
||||||
|
.starsPaid = action.options.starsApproved,
|
||||||
.postAuthor = NewMessagePostAuthor(action),
|
.postAuthor = NewMessagePostAuthor(action),
|
||||||
.effectId = action.options.effectId,
|
.effectId = action.options.effectId,
|
||||||
}, TextWithEntities(), MTP_messageMediaContact(
|
}, TextWithEntities(), MTP_messageMediaContact(
|
||||||
|
@ -3944,6 +3969,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
|
||||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||||
}
|
}
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
action.options.starsApproved);
|
||||||
|
if (starsPaid) {
|
||||||
|
action.options.starsApproved -= starsPaid;
|
||||||
|
sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars;
|
||||||
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||||
|
}
|
||||||
lastMessage = history->addNewLocalMessage({
|
lastMessage = history->addNewLocalMessage({
|
||||||
.id = newId.msg,
|
.id = newId.msg,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
|
@ -3951,6 +3984,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
.replyTo = action.replyTo,
|
.replyTo = action.replyTo,
|
||||||
.date = NewMessageDate(action.options),
|
.date = NewMessageDate(action.options),
|
||||||
.shortcutId = action.options.shortcutId,
|
.shortcutId = action.options.shortcutId,
|
||||||
|
.starsPaid = starsPaid,
|
||||||
.postAuthor = NewMessagePostAuthor(action),
|
.postAuthor = NewMessagePostAuthor(action),
|
||||||
.effectId = action.options.effectId,
|
.effectId = action.options.effectId,
|
||||||
}, sending, media);
|
}, sending, media);
|
||||||
|
@ -4001,7 +4035,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
MTP_int(action.options.scheduled),
|
MTP_int(action.options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
mtpShortcut,
|
mtpShortcut,
|
||||||
MTP_long(action.options.effectId)
|
MTP_long(action.options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), done, fail);
|
), done, fail);
|
||||||
} else {
|
} else {
|
||||||
histories.sendPreparedMessage(
|
histories.sendPreparedMessage(
|
||||||
|
@ -4019,7 +4054,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
MTP_int(action.options.scheduled),
|
MTP_int(action.options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
mtpShortcut,
|
mtpShortcut,
|
||||||
MTP_long(action.options.effectId)
|
MTP_long(action.options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), done, fail);
|
), done, fail);
|
||||||
}
|
}
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
|
@ -4084,7 +4120,7 @@ void ApiWrap::sendBotStart(
|
||||||
void ApiWrap::sendInlineResult(
|
void ApiWrap::sendInlineResult(
|
||||||
not_null<UserData*> bot,
|
not_null<UserData*> bot,
|
||||||
not_null<InlineBots::Result*> data,
|
not_null<InlineBots::Result*> data,
|
||||||
const SendAction &action,
|
SendAction action,
|
||||||
std::optional<MsgId> localMessageId,
|
std::optional<MsgId> localMessageId,
|
||||||
Fn<void(bool)> done) {
|
Fn<void(bool)> done) {
|
||||||
sendAction(action);
|
sendAction(action);
|
||||||
|
@ -4124,6 +4160,13 @@ void ApiWrap::sendInlineResult(
|
||||||
if (action.options.hideViaBot) {
|
if (action.options.hideViaBot) {
|
||||||
sendFlags |= SendFlag::f_hide_via;
|
sendFlags |= SendFlag::f_hide_via;
|
||||||
}
|
}
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
action.options.starsApproved);
|
||||||
|
if (starsPaid) {
|
||||||
|
action.options.starsApproved -= starsPaid;
|
||||||
|
sendFlags |= SendFlag::f_allow_paid_stars;
|
||||||
|
}
|
||||||
|
|
||||||
const auto sendAs = action.options.sendAs;
|
const auto sendAs = action.options.sendAs;
|
||||||
if (sendAs) {
|
if (sendAs) {
|
||||||
|
@ -4138,6 +4181,7 @@ void ApiWrap::sendInlineResult(
|
||||||
.replyTo = action.replyTo,
|
.replyTo = action.replyTo,
|
||||||
.date = NewMessageDate(action.options),
|
.date = NewMessageDate(action.options),
|
||||||
.shortcutId = action.options.shortcutId,
|
.shortcutId = action.options.shortcutId,
|
||||||
|
.starsPaid = starsPaid,
|
||||||
.viaBotId = ((bot && !action.options.hideViaBot)
|
.viaBotId = ((bot && !action.options.hideViaBot)
|
||||||
? peerToUser(bot->id)
|
? peerToUser(bot->id)
|
||||||
: UserId()),
|
: UserId()),
|
||||||
|
@ -4161,7 +4205,8 @@ void ApiWrap::sendInlineResult(
|
||||||
MTP_string(data->getId()),
|
MTP_string(data->getId()),
|
||||||
MTP_int(action.options.scheduled),
|
MTP_int(action.options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||||
history->finishSavingCloudDraft(
|
history->finishSavingCloudDraft(
|
||||||
topicRootId,
|
topicRootId,
|
||||||
|
@ -4298,6 +4343,7 @@ void ApiWrap::sendMediaWithRandomId(
|
||||||
|
|
||||||
const auto history = item->history();
|
const auto history = item->history();
|
||||||
const auto replyTo = item->replyTo();
|
const auto replyTo = item->replyTo();
|
||||||
|
const auto peer = history->peer;
|
||||||
|
|
||||||
auto caption = item->originalText();
|
auto caption = item->originalText();
|
||||||
TextUtilities::Trim(caption);
|
TextUtilities::Trim(caption);
|
||||||
|
@ -4307,6 +4353,12 @@ void ApiWrap::sendMediaWithRandomId(
|
||||||
Api::ConvertOption::SkipLocal);
|
Api::ConvertOption::SkipLocal);
|
||||||
|
|
||||||
const auto updateRecentStickers = Api::HasAttachedStickers(media);
|
const auto updateRecentStickers = Api::HasAttachedStickers(media);
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
options.starsApproved);
|
||||||
|
if (starsPaid) {
|
||||||
|
options.starsApproved -= starsPaid;
|
||||||
|
}
|
||||||
|
|
||||||
using Flag = MTPmessages_SendMedia::Flag;
|
using Flag = MTPmessages_SendMedia::Flag;
|
||||||
const auto flags = Flag(0)
|
const auto flags = Flag(0)
|
||||||
|
@ -4319,10 +4371,10 @@ void ApiWrap::sendMediaWithRandomId(
|
||||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
|
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||||
|
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||||
|
|
||||||
auto &histories = history->owner().histories();
|
auto &histories = history->owner().histories();
|
||||||
const auto peer = history->peer;
|
|
||||||
const auto itemId = item->fullId();
|
const auto itemId = item->fullId();
|
||||||
histories.sendPreparedMessage(
|
histories.sendPreparedMessage(
|
||||||
history,
|
history,
|
||||||
|
@ -4346,7 +4398,8 @@ void ApiWrap::sendMediaWithRandomId(
|
||||||
MTP_int(options.scheduled),
|
MTP_int(options.scheduled),
|
||||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||||
MTP_long(options.effectId)
|
MTP_long(options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||||
if (done) done(true);
|
if (done) done(true);
|
||||||
if (updateRecentStickers) {
|
if (updateRecentStickers) {
|
||||||
|
@ -4367,7 +4420,7 @@ void ApiWrap::sendMultiPaidMedia(
|
||||||
Expects(album->options.price > 0);
|
Expects(album->options.price > 0);
|
||||||
|
|
||||||
const auto groupId = album->groupId;
|
const auto groupId = album->groupId;
|
||||||
const auto &options = album->options;
|
auto &options = album->options;
|
||||||
const auto randomId = album->items.front().randomId;
|
const auto randomId = album->items.front().randomId;
|
||||||
auto medias = album->items | ranges::view::transform([](
|
auto medias = album->items | ranges::view::transform([](
|
||||||
const SendingAlbum::Item &part) {
|
const SendingAlbum::Item &part) {
|
||||||
|
@ -4377,6 +4430,7 @@ void ApiWrap::sendMultiPaidMedia(
|
||||||
|
|
||||||
const auto history = item->history();
|
const auto history = item->history();
|
||||||
const auto replyTo = item->replyTo();
|
const auto replyTo = item->replyTo();
|
||||||
|
const auto peer = history->peer;
|
||||||
|
|
||||||
auto caption = item->originalText();
|
auto caption = item->originalText();
|
||||||
TextUtilities::Trim(caption);
|
TextUtilities::Trim(caption);
|
||||||
|
@ -4384,6 +4438,12 @@ void ApiWrap::sendMultiPaidMedia(
|
||||||
_session,
|
_session,
|
||||||
caption.entities,
|
caption.entities,
|
||||||
Api::ConvertOption::SkipLocal);
|
Api::ConvertOption::SkipLocal);
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
options.starsApproved);
|
||||||
|
if (starsPaid) {
|
||||||
|
options.starsApproved -= starsPaid;
|
||||||
|
}
|
||||||
|
|
||||||
using Flag = MTPmessages_SendMedia::Flag;
|
using Flag = MTPmessages_SendMedia::Flag;
|
||||||
const auto flags = Flag(0)
|
const auto flags = Flag(0)
|
||||||
|
@ -4396,10 +4456,10 @@ void ApiWrap::sendMultiPaidMedia(
|
||||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
|
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||||
|
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||||
|
|
||||||
auto &histories = history->owner().histories();
|
auto &histories = history->owner().histories();
|
||||||
const auto peer = history->peer;
|
|
||||||
const auto itemId = item->fullId();
|
const auto itemId = item->fullId();
|
||||||
album->sent = true;
|
album->sent = true;
|
||||||
histories.sendPreparedMessage(
|
histories.sendPreparedMessage(
|
||||||
|
@ -4422,7 +4482,8 @@ void ApiWrap::sendMultiPaidMedia(
|
||||||
MTP_int(options.scheduled),
|
MTP_int(options.scheduled),
|
||||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||||
MTP_long(options.effectId)
|
MTP_long(options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||||
if (const auto album = _sendingAlbums.take(groupId)) {
|
if (const auto album = _sendingAlbums.take(groupId)) {
|
||||||
const auto copy = (*album)->items;
|
const auto copy = (*album)->items;
|
||||||
|
@ -4518,6 +4579,12 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||||
const auto history = sample->history();
|
const auto history = sample->history();
|
||||||
const auto replyTo = sample->replyTo();
|
const auto replyTo = sample->replyTo();
|
||||||
const auto sendAs = album->options.sendAs;
|
const auto sendAs = album->options.sendAs;
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
history->peer->starsPerMessageChecked() * int(medias.size()),
|
||||||
|
album->options.starsApproved);
|
||||||
|
if (starsPaid) {
|
||||||
|
album->options.starsApproved -= starsPaid;
|
||||||
|
}
|
||||||
using Flag = MTPmessages_SendMultiMedia::Flag;
|
using Flag = MTPmessages_SendMultiMedia::Flag;
|
||||||
const auto flags = Flag(0)
|
const auto flags = Flag(0)
|
||||||
| (replyTo ? Flag::f_reply_to : Flag(0))
|
| (replyTo ? Flag::f_reply_to : Flag(0))
|
||||||
|
@ -4530,7 +4597,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||||
? Flag::f_quick_reply_shortcut
|
? Flag::f_quick_reply_shortcut
|
||||||
: Flag(0))
|
: Flag(0))
|
||||||
| (album->options.effectId ? Flag::f_effect : Flag(0))
|
| (album->options.effectId ? Flag::f_effect : Flag(0))
|
||||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||||
|
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||||
auto &histories = history->owner().histories();
|
auto &histories = history->owner().histories();
|
||||||
const auto peer = history->peer;
|
const auto peer = history->peer;
|
||||||
album->sent = true;
|
album->sent = true;
|
||||||
|
@ -4546,7 +4614,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||||
MTP_int(album->options.scheduled),
|
MTP_int(album->options.scheduled),
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||||
Data::ShortcutIdToMTP(_session, album->options.shortcutId),
|
Data::ShortcutIdToMTP(_session, album->options.shortcutId),
|
||||||
MTP_long(album->options.effectId)
|
MTP_long(album->options.effectId),
|
||||||
|
MTP_long(starsPaid)
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||||
_sendingAlbums.remove(groupId);
|
_sendingAlbums.remove(groupId);
|
||||||
|
|
||||||
|
|
|
@ -306,7 +306,7 @@ public:
|
||||||
void finishForwarding(const SendAction &action);
|
void finishForwarding(const SendAction &action);
|
||||||
void forwardMessages(
|
void forwardMessages(
|
||||||
Data::ResolvedForwardDraft &&draft,
|
Data::ResolvedForwardDraft &&draft,
|
||||||
const SendAction &action,
|
SendAction action,
|
||||||
FnMut<void()> &&successCallback = nullptr);
|
FnMut<void()> &&successCallback = nullptr);
|
||||||
void shareContact(
|
void shareContact(
|
||||||
const QString &phone,
|
const QString &phone,
|
||||||
|
@ -368,7 +368,7 @@ public:
|
||||||
void sendInlineResult(
|
void sendInlineResult(
|
||||||
not_null<UserData*> bot,
|
not_null<UserData*> bot,
|
||||||
not_null<InlineBots::Result*> data,
|
not_null<InlineBots::Result*> data,
|
||||||
const SendAction &action,
|
SendAction action,
|
||||||
std::optional<MsgId> localMessageId,
|
std::optional<MsgId> localMessageId,
|
||||||
Fn<void(bool)> done = nullptr);
|
Fn<void(bool)> done = nullptr);
|
||||||
void sendMessageFail(
|
void sendMessageFail(
|
||||||
|
|
|
@ -43,18 +43,20 @@ bool ResolveUser(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchById(userId,
|
searchById(
|
||||||
&controller->session(),
|
userId,
|
||||||
[=](const QString &title, UserData *data)
|
&controller->session(),
|
||||||
{
|
[=](const QString &title, UserData *data)
|
||||||
if (data) {
|
{
|
||||||
controller->showPeerInfo(data);
|
if (data) {
|
||||||
return;
|
controller->showPeerInfo(data);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Core::App().hideMediaView();
|
Core::App().hideMediaView();
|
||||||
Ui::show(Ui::MakeInformBox(tr::ayu_UserNotFoundMessage()));
|
Ui::show(Ui::MakeInformBox(tr::ayu_UserNotFoundMessage()));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -146,9 +146,9 @@ extern "C" {
|
||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.48.0"
|
#define SQLITE_VERSION "3.49.1"
|
||||||
#define SQLITE_VERSION_NUMBER 3048000
|
#define SQLITE_VERSION_NUMBER 3049001
|
||||||
#define SQLITE_SOURCE_ID "2025-01-14 11:05:00 d2fe6b05f38d9d7cd78c5d252e99ac59f1aea071d669830c1ffe4e8966e84010"
|
#define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
|
@ -2211,7 +2211,15 @@ struct sqlite3_mem_methods {
|
||||||
** CAPI3REF: Database Connection Configuration Options
|
** CAPI3REF: Database Connection Configuration Options
|
||||||
**
|
**
|
||||||
** These constants are the available integer configuration options that
|
** These constants are the available integer configuration options that
|
||||||
** can be passed as the second argument to the [sqlite3_db_config()] interface.
|
** can be passed as the second parameter to the [sqlite3_db_config()] interface.
|
||||||
|
**
|
||||||
|
** The [sqlite3_db_config()] interface is a var-args functions. It takes a
|
||||||
|
** variable number of parameters, though always at least two. The number of
|
||||||
|
** parameters passed into sqlite3_db_config() depends on which of these
|
||||||
|
** constants is given as the second parameter. This documentation page
|
||||||
|
** refers to parameters beyond the second as "arguments". Thus, when this
|
||||||
|
** page says "the N-th argument" it means "the N-th parameter past the
|
||||||
|
** configuration option" or "the (N+2)-th parameter to sqlite3_db_config()".
|
||||||
**
|
**
|
||||||
** New configuration options may be added in future releases of SQLite.
|
** New configuration options may be added in future releases of SQLite.
|
||||||
** Existing configuration options might be discontinued. Applications
|
** Existing configuration options might be discontinued. Applications
|
||||||
|
@ -2223,8 +2231,14 @@ struct sqlite3_mem_methods {
|
||||||
** <dl>
|
** <dl>
|
||||||
** [[SQLITE_DBCONFIG_LOOKASIDE]]
|
** [[SQLITE_DBCONFIG_LOOKASIDE]]
|
||||||
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
|
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
|
||||||
** <dd> ^This option takes three additional arguments that determine the
|
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
|
||||||
** [lookaside memory allocator] configuration for the [database connection].
|
** configuration of the lookaside memory allocator within a database
|
||||||
|
** connection.
|
||||||
|
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
|
||||||
|
** in the [DBCONFIG arguments|usual format].
|
||||||
|
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
|
||||||
|
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
|
||||||
|
** should have a total of five parameters.
|
||||||
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
|
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
|
||||||
** pointer to a memory buffer to use for lookaside memory.
|
** pointer to a memory buffer to use for lookaside memory.
|
||||||
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
|
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
|
||||||
|
@ -2247,7 +2261,8 @@ struct sqlite3_mem_methods {
|
||||||
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
|
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
|
||||||
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
|
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
|
||||||
** <dd> ^This option is used to enable or disable the enforcement of
|
** <dd> ^This option is used to enable or disable the enforcement of
|
||||||
** [foreign key constraints]. There should be two additional arguments.
|
** [foreign key constraints]. This is the same setting that is
|
||||||
|
** enabled or disabled by the [PRAGMA foreign_keys] statement.
|
||||||
** The first argument is an integer which is 0 to disable FK enforcement,
|
** The first argument is an integer which is 0 to disable FK enforcement,
|
||||||
** positive to enable FK enforcement or negative to leave FK enforcement
|
** positive to enable FK enforcement or negative to leave FK enforcement
|
||||||
** unchanged. The second parameter is a pointer to an integer into which
|
** unchanged. The second parameter is a pointer to an integer into which
|
||||||
|
@ -2269,13 +2284,13 @@ struct sqlite3_mem_methods {
|
||||||
** <p>Originally this option disabled all triggers. ^(However, since
|
** <p>Originally this option disabled all triggers. ^(However, since
|
||||||
** SQLite version 3.35.0, TEMP triggers are still allowed even if
|
** SQLite version 3.35.0, TEMP triggers are still allowed even if
|
||||||
** this option is off. So, in other words, this option now only disables
|
** this option is off. So, in other words, this option now only disables
|
||||||
** triggers in the main database schema or in the schemas of ATTACH-ed
|
** triggers in the main database schema or in the schemas of [ATTACH]-ed
|
||||||
** databases.)^ </dd>
|
** databases.)^ </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
|
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
|
||||||
** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
|
** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
|
||||||
** <dd> ^This option is used to enable or disable [CREATE VIEW | views].
|
** <dd> ^This option is used to enable or disable [CREATE VIEW | views].
|
||||||
** There should be two additional arguments.
|
** There must be two additional arguments.
|
||||||
** The first argument is an integer which is 0 to disable views,
|
** The first argument is an integer which is 0 to disable views,
|
||||||
** positive to enable views or negative to leave the setting unchanged.
|
** positive to enable views or negative to leave the setting unchanged.
|
||||||
** The second parameter is a pointer to an integer into which
|
** The second parameter is a pointer to an integer into which
|
||||||
|
@ -2294,7 +2309,7 @@ struct sqlite3_mem_methods {
|
||||||
** <dd> ^This option is used to enable or disable the
|
** <dd> ^This option is used to enable or disable the
|
||||||
** [fts3_tokenizer()] function which is part of the
|
** [fts3_tokenizer()] function which is part of the
|
||||||
** [FTS3] full-text search engine extension.
|
** [FTS3] full-text search engine extension.
|
||||||
** There should be two additional arguments.
|
** There must be two additional arguments.
|
||||||
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
|
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
|
||||||
** positive to enable fts3_tokenizer() or negative to leave the setting
|
** positive to enable fts3_tokenizer() or negative to leave the setting
|
||||||
** unchanged.
|
** unchanged.
|
||||||
|
@ -2309,7 +2324,7 @@ struct sqlite3_mem_methods {
|
||||||
** interface independently of the [load_extension()] SQL function.
|
** interface independently of the [load_extension()] SQL function.
|
||||||
** The [sqlite3_enable_load_extension()] API enables or disables both the
|
** The [sqlite3_enable_load_extension()] API enables or disables both the
|
||||||
** C-API [sqlite3_load_extension()] and the SQL function [load_extension()].
|
** C-API [sqlite3_load_extension()] and the SQL function [load_extension()].
|
||||||
** There should be two additional arguments.
|
** There must be two additional arguments.
|
||||||
** When the first argument to this interface is 1, then only the C-API is
|
** When the first argument to this interface is 1, then only the C-API is
|
||||||
** enabled and the SQL function remains disabled. If the first argument to
|
** enabled and the SQL function remains disabled. If the first argument to
|
||||||
** this interface is 0, then both the C-API and the SQL function are disabled.
|
** this interface is 0, then both the C-API and the SQL function are disabled.
|
||||||
|
@ -2323,23 +2338,30 @@ struct sqlite3_mem_methods {
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
|
** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
|
||||||
** <dd> ^This option is used to change the name of the "main" database
|
** <dd> ^This option is used to change the name of the "main" database
|
||||||
** schema. ^The sole argument is a pointer to a constant UTF8 string
|
** schema. This option does not follow the
|
||||||
** which will become the new schema name in place of "main". ^SQLite
|
** [DBCONFIG arguments|usual SQLITE_DBCONFIG argument format].
|
||||||
** does not make a copy of the new main schema name string, so the application
|
** This option takes exactly one additional argument so that the
|
||||||
** must ensure that the argument passed into this DBCONFIG option is unchanged
|
** [sqlite3_db_config()] call has a total of three parameters. The
|
||||||
** until after the database connection closes.
|
** extra argument must be a pointer to a constant UTF8 string which
|
||||||
|
** will become the new schema name in place of "main". ^SQLite does
|
||||||
|
** not make a copy of the new main schema name string, so the application
|
||||||
|
** must ensure that the argument passed into SQLITE_DBCONFIG MAINDBNAME
|
||||||
|
** is unchanged until after the database connection closes.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
|
** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
|
||||||
** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
|
** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
|
||||||
** <dd> Usually, when a database in wal mode is closed or detached from a
|
** <dd> Usually, when a database in [WAL mode] is closed or detached from a
|
||||||
** database handle, SQLite checks if this will mean that there are now no
|
** database handle, SQLite checks if if there are other connections to the
|
||||||
** connections at all to the database. If so, it performs a checkpoint
|
** same database, and if there are no other database connection (if the
|
||||||
** operation before closing the connection. This option may be used to
|
** connection being closed is the last open connection to the database),
|
||||||
** override this behavior. The first parameter passed to this operation
|
** then SQLite performs a [checkpoint] before closing the connection and
|
||||||
** is an integer - positive to disable checkpoints-on-close, or zero (the
|
** deletes the WAL file. The SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE option can
|
||||||
** default) to enable them, and negative to leave the setting unchanged.
|
** be used to override that behavior. The first argument passed to this
|
||||||
** The second parameter is a pointer to an integer
|
** operation (the third parameter to [sqlite3_db_config()]) is an integer
|
||||||
|
** which is positive to disable checkpoints-on-close, or zero (the default)
|
||||||
|
** to enable them, and negative to leave the setting unchanged.
|
||||||
|
** The second argument (the fourth parameter) is a pointer to an integer
|
||||||
** into which is written 0 or 1 to indicate whether checkpoints-on-close
|
** into which is written 0 or 1 to indicate whether checkpoints-on-close
|
||||||
** have been disabled - 0 if they are not disabled, 1 if they are.
|
** have been disabled - 0 if they are not disabled, 1 if they are.
|
||||||
** </dd>
|
** </dd>
|
||||||
|
@ -2500,7 +2522,7 @@ struct sqlite3_mem_methods {
|
||||||
** statistics. For statistics to be collected, the flag must be set on
|
** statistics. For statistics to be collected, the flag must be set on
|
||||||
** the database handle both when the SQL statement is prepared and when it
|
** the database handle both when the SQL statement is prepared and when it
|
||||||
** is stepped. The flag is set (collection of statistics is enabled)
|
** is stepped. The flag is set (collection of statistics is enabled)
|
||||||
** by default. This option takes two arguments: an integer and a pointer to
|
** by default. <p>This option takes two arguments: an integer and a pointer to
|
||||||
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
|
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
|
||||||
** leave unchanged the statement scanstatus option. If the second argument
|
** leave unchanged the statement scanstatus option. If the second argument
|
||||||
** is not NULL, then the value of the statement scanstatus setting after
|
** is not NULL, then the value of the statement scanstatus setting after
|
||||||
|
@ -2514,7 +2536,7 @@ struct sqlite3_mem_methods {
|
||||||
** in which tables and indexes are scanned so that the scans start at the end
|
** in which tables and indexes are scanned so that the scans start at the end
|
||||||
** and work toward the beginning rather than starting at the beginning and
|
** and work toward the beginning rather than starting at the beginning and
|
||||||
** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the
|
** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the
|
||||||
** same as setting [PRAGMA reverse_unordered_selects]. This option takes
|
** same as setting [PRAGMA reverse_unordered_selects]. <p>This option takes
|
||||||
** two arguments which are an integer and a pointer to an integer. The first
|
** two arguments which are an integer and a pointer to an integer. The first
|
||||||
** argument is 1, 0, or -1 to enable, disable, or leave unchanged the
|
** argument is 1, 0, or -1 to enable, disable, or leave unchanged the
|
||||||
** reverse scan order flag, respectively. If the second argument is not NULL,
|
** reverse scan order flag, respectively. If the second argument is not NULL,
|
||||||
|
@ -2523,7 +2545,76 @@ struct sqlite3_mem_methods {
|
||||||
** first argument.
|
** first argument.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]]
|
||||||
|
** <dt>SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE</dt>
|
||||||
|
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE option enables or disables
|
||||||
|
** the ability of the [ATTACH DATABASE] SQL command to create a new database
|
||||||
|
** file if the database filed named in the ATTACH command does not already
|
||||||
|
** exist. This ability of ATTACH to create a new database is enabled by
|
||||||
|
** default. Applications can disable or reenable the ability for ATTACH to
|
||||||
|
** create new database files using this DBCONFIG option.<p>
|
||||||
|
** This option takes two arguments which are an integer and a pointer
|
||||||
|
** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
|
||||||
|
** leave unchanged the attach-create flag, respectively. If the second
|
||||||
|
** argument is not NULL, then 0 or 1 is written into the integer that the
|
||||||
|
** second argument points to depending on if the attach-create flag is set
|
||||||
|
** after processing the first argument.
|
||||||
|
** </dd>
|
||||||
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE]]
|
||||||
|
** <dt>SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE</dt>
|
||||||
|
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the
|
||||||
|
** ability of the [ATTACH DATABASE] SQL command to open a database for writing.
|
||||||
|
** This capability is enabled by default. Applications can disable or
|
||||||
|
** reenable this capability using the current DBCONFIG option. If the
|
||||||
|
** the this capability is disabled, the [ATTACH] command will still work,
|
||||||
|
** but the database will be opened read-only. If this option is disabled,
|
||||||
|
** then the ability to create a new database using [ATTACH] is also disabled,
|
||||||
|
** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]
|
||||||
|
** option.<p>
|
||||||
|
** This option takes two arguments which are an integer and a pointer
|
||||||
|
** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
|
||||||
|
** leave unchanged the ability to ATTACH another database for writing,
|
||||||
|
** respectively. If the second argument is not NULL, then 0 or 1 is written
|
||||||
|
** into the integer to which the second argument points, depending on whether
|
||||||
|
** the ability to ATTACH a read/write database is enabled or disabled
|
||||||
|
** after processing the first argument.
|
||||||
|
** </dd>
|
||||||
|
**
|
||||||
|
** [[SQLITE_DBCONFIG_ENABLE_COMMENTS]]
|
||||||
|
** <dt>SQLITE_DBCONFIG_ENABLE_COMMENTS</dt>
|
||||||
|
** <dd>The SQLITE_DBCONFIG_ENABLE_COMMENTS option enables or disables the
|
||||||
|
** ability to include comments in SQL text. Comments are enabled by default.
|
||||||
|
** An application can disable or reenable comments in SQL text using this
|
||||||
|
** DBCONFIG option.<p>
|
||||||
|
** This option takes two arguments which are an integer and a pointer
|
||||||
|
** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
|
||||||
|
** leave unchanged the ability to use comments in SQL text,
|
||||||
|
** respectively. If the second argument is not NULL, then 0 or 1 is written
|
||||||
|
** into the integer that the second argument points to depending on if
|
||||||
|
** comments are allowed in SQL text after processing the first argument.
|
||||||
|
** </dd>
|
||||||
|
**
|
||||||
** </dl>
|
** </dl>
|
||||||
|
**
|
||||||
|
** [[DBCONFIG arguments]] <h3>Arguments To SQLITE_DBCONFIG Options</h3>
|
||||||
|
**
|
||||||
|
** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the
|
||||||
|
** overall call to [sqlite3_db_config()] has a total of four parameters.
|
||||||
|
** The first argument (the third parameter to sqlite3_db_config()) is a integer.
|
||||||
|
** The second argument is a pointer to an integer. If the first argument is 1,
|
||||||
|
** then the option becomes enabled. If the first integer argument is 0, then the
|
||||||
|
** option is disabled. If the first argument is -1, then the option setting
|
||||||
|
** is unchanged. The second argument, the pointer to an integer, may be NULL.
|
||||||
|
** If the second argument is not NULL, then a value of 0 or 1 is written into
|
||||||
|
** the integer to which the second argument points, depending on whether the
|
||||||
|
** setting is disabled or enabled after applying any changes specified by
|
||||||
|
** the first argument.
|
||||||
|
**
|
||||||
|
** <p>While most SQLITE_DBCONFIG options use the argument format
|
||||||
|
** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME]
|
||||||
|
** and [SQLITE_DBCONFIG_LOOKASIDE] options are different. See the
|
||||||
|
** documentation of those exceptional options for details.
|
||||||
*/
|
*/
|
||||||
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
|
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
|
||||||
#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */
|
#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */
|
||||||
|
@ -2545,7 +2636,10 @@ struct sqlite3_mem_methods {
|
||||||
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
|
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */
|
#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */
|
#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */
|
||||||
#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */
|
#define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */
|
||||||
|
#define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */
|
||||||
|
#define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */
|
||||||
|
#define SQLITE_DBCONFIG_MAX 1022 /* Largest DBCONFIG */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Enable Or Disable Extended Result Codes
|
** CAPI3REF: Enable Or Disable Extended Result Codes
|
||||||
|
@ -10748,8 +10842,9 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Serialize a database
|
** CAPI3REF: Serialize a database
|
||||||
**
|
**
|
||||||
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory
|
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to
|
||||||
** that is a serialization of the S database on [database connection] D.
|
** memory that is a serialization of the S database on
|
||||||
|
** [database connection] D. If S is a NULL pointer, the main database is used.
|
||||||
** If P is not a NULL pointer, then the size of the database in bytes
|
** If P is not a NULL pointer, then the size of the database in bytes
|
||||||
** is written into *P.
|
** is written into *P.
|
||||||
**
|
**
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,6 +34,7 @@
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "history/view/history_view_context_menu.h"
|
#include "history/view/history_view_context_menu.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
|
#include "window/window_controller.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
|
||||||
namespace AyuUi {
|
namespace AyuUi {
|
||||||
|
@ -58,13 +59,14 @@ void AddDeletedMessagesActions(PeerData *peerData,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCallback(tr::ayu_ViewDeletedMenuText(tr::now),
|
addCallback(
|
||||||
[=]
|
tr::ayu_ViewDeletedMenuText(tr::now),
|
||||||
{
|
[=]
|
||||||
sessionController->session().tryResolveWindow()
|
{
|
||||||
->showSection(std::make_shared<MessageHistory::SectionMemento>(peerData, nullptr, topicId));
|
sessionController->session().tryResolveWindow()
|
||||||
},
|
->showSection(std::make_shared<MessageHistory::SectionMemento>(peerData, nullptr, topicId));
|
||||||
&st::menuIconArchive);
|
},
|
||||||
|
&st::menuIconArchive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddJumpToBeginningAction(PeerData *peerData,
|
void AddJumpToBeginningAction(PeerData *peerData,
|
||||||
|
@ -147,16 +149,38 @@ void AddJumpToBeginningAction(PeerData *peerData,
|
||||||
&st::ayuMenuIconToBeginning);
|
&st::ayuMenuIconToBeginning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddOpenChannelAction(PeerData *peerData,
|
||||||
|
not_null<Window::SessionController*> sessionController,
|
||||||
|
const Window::PeerMenuCallback &addCallback) {
|
||||||
|
if (!peerData || !peerData->isMegagroup()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto chat = peerData->asMegagroup()->linkedChat();
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addCallback(
|
||||||
|
tr::lng_context_open_channel(tr::now),
|
||||||
|
[=]
|
||||||
|
{
|
||||||
|
sessionController->showPeerHistory(chat, Window::SectionShow::Way::Forward);
|
||||||
|
},
|
||||||
|
&st::menuIconChannel);
|
||||||
|
}
|
||||||
|
|
||||||
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||||
if (AyuMessages::hasRevisions(item)) {
|
if (AyuMessages::hasRevisions(item)) {
|
||||||
menu->addAction(tr::ayu_EditsHistoryMenuText(tr::now),
|
menu->addAction(
|
||||||
[=]
|
tr::ayu_EditsHistoryMenuText(tr::now),
|
||||||
{
|
[=]
|
||||||
item->history()->session().tryResolveWindow()
|
{
|
||||||
->showSection(
|
item->history()->session().tryResolveWindow()
|
||||||
std::make_shared<MessageHistory::SectionMemento>(item->history()->peer, item, 0));
|
->showSection(
|
||||||
},
|
std::make_shared<MessageHistory::SectionMemento>(item->history()->peer, item, 0));
|
||||||
&st::ayuEditsHistoryIcon);
|
},
|
||||||
|
&st::ayuEditsHistoryIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,15 +195,16 @@ void AddHideMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto history = item->history();
|
const auto history = item->history();
|
||||||
menu->addAction(tr::ayu_ContextHideMessage(tr::now),
|
menu->addAction(
|
||||||
[=]()
|
tr::ayu_ContextHideMessage(tr::now),
|
||||||
{
|
[=]()
|
||||||
item->destroy();
|
{
|
||||||
history->requestChatListMessage();
|
item->destroy();
|
||||||
|
history->requestChatListMessage();
|
||||||
|
|
||||||
AyuState::hide(item);
|
AyuState::hide(item);
|
||||||
},
|
},
|
||||||
&st::menuIconClear);
|
&st::menuIconClear);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||||
|
@ -432,31 +457,77 @@ void AddReadUntilAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->addAction(tr::ayu_ReadUntilMenuText(tr::now),
|
menu->addAction(
|
||||||
[=]()
|
tr::ayu_ReadUntilMenuText(tr::now),
|
||||||
|
[=]()
|
||||||
|
{
|
||||||
|
readHistory(item);
|
||||||
|
if (item->media() && item->media()->ttlSeconds() <= 0 && item->unsupportedTTL() <= 0 && !item->out() && item
|
||||||
|
->isUnreadMedia()) {
|
||||||
|
const auto ids = MTP_vector<MTPint>(1, MTP_int(item->id));
|
||||||
|
if (const auto channel = item->history()->peer->asChannel()) {
|
||||||
|
item->history()->session().api().request(MTPchannels_ReadMessageContents(
|
||||||
|
channel->inputChannel,
|
||||||
|
ids
|
||||||
|
)).send();
|
||||||
|
} else {
|
||||||
|
item->history()->session().api().request(MTPmessages_ReadMessageContents(
|
||||||
|
ids
|
||||||
|
)).done([=](const MTPmessages_AffectedMessages &result)
|
||||||
{
|
{
|
||||||
readHistory(item);
|
item->history()->session().api().applyAffectedMessages(
|
||||||
if (item->media() && !item->media()->ttlSeconds()) {
|
item->history()->peer,
|
||||||
const auto ids = MTP_vector<MTPint>(1, MTP_int(item->id));
|
result);
|
||||||
if (const auto channel = item->history()->peer->asChannel()) {
|
}).send();
|
||||||
item->history()->session().api().request(MTPchannels_ReadMessageContents(
|
}
|
||||||
channel->inputChannel,
|
item->markContentsRead();
|
||||||
ids
|
}
|
||||||
)).send();
|
},
|
||||||
} else {
|
&st::menuIconShowInChat);
|
||||||
item->history()->session().api().request(MTPmessages_ReadMessageContents(
|
}
|
||||||
ids
|
|
||||||
)).done([=](const MTPmessages_AffectedMessages &result)
|
void AddBurnAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||||
{
|
if (!item->media() || item->media()->ttlSeconds() <= 0 && item->unsupportedTTL() <= 0 || item->out() ||
|
||||||
item->history()->session().api().applyAffectedMessages(
|
!item->isUnreadMedia()) {
|
||||||
item->history()->peer,
|
return;
|
||||||
result);
|
}
|
||||||
}).send();
|
|
||||||
}
|
menu->addAction(
|
||||||
item->markContentsRead();
|
tr::ayu_ExpireMediaContextMenuText(tr::now),
|
||||||
}
|
[=]()
|
||||||
},
|
{
|
||||||
&st::menuIconShowInChat);
|
const auto ids = MTP_vector<MTPint>(1, MTP_int(item->id));
|
||||||
|
const auto callback = [=]()
|
||||||
|
{
|
||||||
|
if (const auto window = Core::App().activeWindow()) {
|
||||||
|
if (const auto controller = window->sessionController()) {
|
||||||
|
controller->showToast(tr::lng_box_ok(tr::now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (const auto channel = item->history()->peer->asChannel()) {
|
||||||
|
item->history()->session().api().request(MTPchannels_ReadMessageContents(
|
||||||
|
channel->inputChannel,
|
||||||
|
ids
|
||||||
|
)).done([=]()
|
||||||
|
{
|
||||||
|
callback();
|
||||||
|
}).send();
|
||||||
|
} else {
|
||||||
|
item->history()->session().api().request(MTPmessages_ReadMessageContents(
|
||||||
|
ids
|
||||||
|
)).done([=](const MTPmessages_AffectedMessages &result)
|
||||||
|
{
|
||||||
|
item->history()->session().api().applyAffectedMessages(
|
||||||
|
item->history()->peer,
|
||||||
|
result);
|
||||||
|
callback();
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
item->markContentsRead();
|
||||||
|
},
|
||||||
|
&st::menuIconTTLAny);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AyuUi
|
} // namespace AyuUi
|
||||||
|
|
|
@ -25,10 +25,15 @@ void AddJumpToBeginningAction(PeerData *peerData,
|
||||||
not_null<Window::SessionController*> sessionController,
|
not_null<Window::SessionController*> sessionController,
|
||||||
const Window::PeerMenuCallback &addCallback);
|
const Window::PeerMenuCallback &addCallback);
|
||||||
|
|
||||||
|
void AddOpenChannelAction(PeerData *peerData,
|
||||||
|
not_null<Window::SessionController*> sessionController,
|
||||||
|
const Window::PeerMenuCallback &addCallback);
|
||||||
|
|
||||||
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
void AddHideMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
void AddHideMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
void AddReadUntilAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
void AddReadUntilAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
|
void AddBurnAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,59 +218,73 @@ ActionStickerPackAuthor::ActionStickerPackAuthor(not_null<Menu::Menu*> menu,
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionStickerPackAuthor::searchAuthor(ID authorId) {
|
void ActionStickerPackAuthor::searchAuthor(ID authorId) {
|
||||||
const auto pointer = Ui::MakeWeak(this);
|
const auto session = _session;
|
||||||
searchById(authorId,
|
const auto weak = Ui::MakeWeak(this);
|
||||||
_session,
|
|
||||||
[=](const QString &username, UserData *user)
|
|
||||||
{
|
|
||||||
if (!pointer) {
|
|
||||||
LOG(("ContextActionStickerAuthor: searchById callback after destruction"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (username.isEmpty() && !user) {
|
|
||||||
_subText = QString(tr::ayu_MessageDetailsPackOwnerNotFoundPC(tr::now));
|
|
||||||
setClickedCallback(
|
|
||||||
[=]
|
|
||||||
{
|
|
||||||
QGuiApplication::clipboard()->setText(QString::number(authorId));
|
|
||||||
if (const auto window = _session->tryResolveWindow()) {
|
|
||||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
|
||||||
mainWidget->showToast(tr::ayu_IDCopiedToast(tr::now));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
crl::on_main(
|
searchById(
|
||||||
[=]
|
authorId,
|
||||||
{
|
session,
|
||||||
update();
|
[session, weak, authorId](const QString &username, UserData *user)
|
||||||
});
|
{
|
||||||
return;
|
if (!weak) {
|
||||||
}
|
LOG(("ContextActionStickerAuthor: searchById callback after destruction"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto title = username.isEmpty() ? user ? user->name() : QString() : username;
|
const auto strong = weak.data();
|
||||||
const auto callback = [=]
|
if (!strong) {
|
||||||
{
|
LOG(("ContextActionStickerAuthor: weak.data() returned null"));
|
||||||
if (user) {
|
return;
|
||||||
if (const auto window = _session->tryResolveWindow()) {
|
}
|
||||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
|
||||||
mainWidget->showPeer(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QGuiApplication::clipboard()->setText(title);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setClickedCallback(callback);
|
if (username.isEmpty() && !user) {
|
||||||
|
strong->_subText = QString(tr::ayu_MessageDetailsPackOwnerNotFoundPC(tr::now));
|
||||||
|
strong->setClickedCallback(
|
||||||
|
[authorId, session]
|
||||||
|
{
|
||||||
|
QGuiApplication::clipboard()->setText(QString::number(authorId));
|
||||||
|
if (const auto window = session->tryResolveWindow()) {
|
||||||
|
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||||
|
mainWidget->showToast(tr::ayu_IDCopiedToast(tr::now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_subText = QString(title);
|
crl::on_main(
|
||||||
crl::on_main(
|
[weak]
|
||||||
[=]
|
{
|
||||||
{
|
if (const auto strongInner = weak.data()) {
|
||||||
update();
|
strongInner->update();
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto title = username.isEmpty() ? (user ? user->name() : QString()) : username;
|
||||||
|
const auto callback = [user, title, session]
|
||||||
|
{
|
||||||
|
if (user) {
|
||||||
|
if (const auto window = session->tryResolveWindow()) {
|
||||||
|
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||||
|
mainWidget->showPeer(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QGuiApplication::clipboard()->setText(title);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
strong->setClickedCallback(callback);
|
||||||
|
strong->_subText = QString(title);
|
||||||
|
crl::on_main(
|
||||||
|
[weak]
|
||||||
|
{
|
||||||
|
if (const auto strongInner = weak.data()) {
|
||||||
|
strongInner->update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -1078,7 +1078,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
|
||||||
? tr::lng_background_other_group(tr::now)
|
? tr::lng_background_other_group(tr::now)
|
||||||
: forChannel()
|
: forChannel()
|
||||||
? tr::lng_background_other_channel(tr::now)
|
? tr::lng_background_other_channel(tr::now)
|
||||||
: (_forPeer && !_fromMessageId)
|
: (_forPeer
|
||||||
|
&& !_fromMessageId
|
||||||
|
&& !_forPeer->starsPerMessageChecked())
|
||||||
? tr::lng_background_other_info(
|
? tr::lng_background_other_info(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_user,
|
lt_user,
|
||||||
|
|
|
@ -1122,3 +1122,10 @@ profileQrBackgroundRadius: 12px;
|
||||||
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
|
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
|
||||||
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
|
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
|
||||||
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);
|
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);
|
||||||
|
|
||||||
|
foldersMenu: PopupMenu(popupMenuWithIcons) {
|
||||||
|
maxHeight: 320px;
|
||||||
|
menu: Menu(menuWithIcons) {
|
||||||
|
itemPadding: margins(54px, 8px, 44px, 8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -172,13 +172,6 @@ void ChangeFilterById(
|
||||||
const auto account = not_null(&history->session().account());
|
const auto account = not_null(&history->session().account());
|
||||||
if (const auto controller = Core::App().windowFor(account)) {
|
if (const auto controller = Core::App().windowFor(account)) {
|
||||||
const auto isStatic = name.isStatic;
|
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({
|
controller->showToast({
|
||||||
.text = (add
|
.text = (add
|
||||||
? tr::lng_filters_toast_add
|
? tr::lng_filters_toast_add
|
||||||
|
@ -189,7 +182,10 @@ void ChangeFilterById(
|
||||||
lt_folder,
|
lt_folder,
|
||||||
Ui::Text::Wrapped(name.text, EntityType::Bold),
|
Ui::Text::Wrapped(name.text, EntityType::Bold),
|
||||||
Ui::Text::WithEntities),
|
Ui::Text::WithEntities),
|
||||||
.textContext = textContext,
|
.textContext = Core::TextContext({
|
||||||
|
.session = &history->session(),
|
||||||
|
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
@ -290,19 +286,17 @@ void FillChooseFilterMenu(
|
||||||
const auto title = filter.title();
|
const auto title = filter.title();
|
||||||
auto item = base::make_unique_q<FilterAction>(
|
auto item = base::make_unique_q<FilterAction>(
|
||||||
menu.get(),
|
menu.get(),
|
||||||
st::foldersMenu,
|
menu->st().menu,
|
||||||
Ui::Menu::CreateAction(
|
Ui::Menu::CreateAction(
|
||||||
menu.get(),
|
menu.get(),
|
||||||
Ui::Text::FixAmpersandInAction(title.text.text),
|
Ui::Text::FixAmpersandInAction(title.text.text),
|
||||||
std::move(callback)),
|
std::move(callback)),
|
||||||
contains ? &st::mediaPlayerMenuCheck : nullptr,
|
contains ? &st::mediaPlayerMenuCheck : nullptr,
|
||||||
contains ? &st::mediaPlayerMenuCheck : nullptr);
|
contains ? &st::mediaPlayerMenuCheck : nullptr);
|
||||||
const auto context = Core::MarkedTextContext{
|
item->setMarkedText(title.text, QString(), Core::TextContext({
|
||||||
.session = &history->session(),
|
.session = &history->session(),
|
||||||
.customEmojiRepaint = [raw = item.get()] { raw->update(); },
|
|
||||||
.customEmojiLoopLimit = title.isStatic ? -1 : 0,
|
.customEmojiLoopLimit = title.isStatic ? -1 : 0,
|
||||||
};
|
}));
|
||||||
item->setMarkedText(title.text, QString(), context);
|
|
||||||
|
|
||||||
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
|
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
|
||||||
const auto action = menu->addAction(std::move(item));
|
const auto action = menu->addAction(std::move(item));
|
||||||
|
|
|
@ -817,13 +817,15 @@ CreatePollBox::CreatePollBox(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
PollData::Flags chosen,
|
PollData::Flags chosen,
|
||||||
PollData::Flags disabled,
|
PollData::Flags disabled,
|
||||||
|
rpl::producer<int> starsRequired,
|
||||||
Api::SendType sendType,
|
Api::SendType sendType,
|
||||||
SendMenu::Details sendMenuDetails)
|
SendMenu::Details sendMenuDetails)
|
||||||
: _controller(controller)
|
: _controller(controller)
|
||||||
, _chosen(chosen)
|
, _chosen(chosen)
|
||||||
, _disabled(disabled)
|
, _disabled(disabled)
|
||||||
, _sendType(sendType)
|
, _sendType(sendType)
|
||||||
, _sendMenuDetails([result = sendMenuDetails] { return result; }) {
|
, _sendMenuDetails([result = sendMenuDetails] { return result; })
|
||||||
|
, _starsRequired(std::move(starsRequired)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
|
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
|
||||||
|
@ -1226,10 +1228,11 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||||
_sendMenuDetails());
|
_sendMenuDetails());
|
||||||
};
|
};
|
||||||
const auto submit = addButton(
|
const auto submit = addButton(
|
||||||
(isNormal
|
tr::lng_polls_create_button(),
|
||||||
? tr::lng_polls_create_button()
|
|
||||||
: tr::lng_schedule_button()),
|
|
||||||
[=] { isNormal ? send({}) : schedule(); });
|
[=] { isNormal ? send({}) : schedule(); });
|
||||||
|
submit->setText(PaidSendButtonText(_starsRequired.value(), isNormal
|
||||||
|
? tr::lng_polls_create_button()
|
||||||
|
: tr::lng_schedule_button()));
|
||||||
const auto sendMenuDetails = [=] {
|
const auto sendMenuDetails = [=] {
|
||||||
collectError();
|
collectError();
|
||||||
return (*error) ? SendMenu::Details() : _sendMenuDetails();
|
return (*error) ? SendMenu::Details() : _sendMenuDetails();
|
||||||
|
|
|
@ -42,6 +42,7 @@ public:
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
PollData::Flags chosen,
|
PollData::Flags chosen,
|
||||||
PollData::Flags disabled,
|
PollData::Flags disabled,
|
||||||
|
rpl::producer<int> starsRequired,
|
||||||
Api::SendType sendType,
|
Api::SendType sendType,
|
||||||
SendMenu::Details sendMenuDetails);
|
SendMenu::Details sendMenuDetails);
|
||||||
|
|
||||||
|
@ -76,6 +77,7 @@ private:
|
||||||
const PollData::Flags _disabled = PollData::Flags();
|
const PollData::Flags _disabled = PollData::Flags();
|
||||||
const Api::SendType _sendType = Api::SendType();
|
const Api::SendType _sendType = Api::SendType();
|
||||||
const Fn<SendMenu::Details()> _sendMenuDetails;
|
const Fn<SendMenu::Details()> _sendMenuDetails;
|
||||||
|
rpl::variable<int> _starsRequired;
|
||||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||||
Fn<void()> _setInnerFocus;
|
Fn<void()> _setInnerFocus;
|
||||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||||
|
|
|
@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/effects/premium_graphics.h"
|
#include "ui/effects/premium_graphics.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
|
#include "ui/widgets/continuous_sliders.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
@ -21,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "boxes/peer_list_controllers.h"
|
#include "boxes/peer_list_controllers.h"
|
||||||
#include "settings/settings_premium.h"
|
#include "settings/settings_premium.h"
|
||||||
|
#include "settings/settings_privacy_controllers.h"
|
||||||
#include "settings/settings_privacy_security.h"
|
#include "settings/settings_privacy_security.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
@ -42,6 +45,8 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
||||||
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
||||||
|
constexpr auto kStarsMin = 1;
|
||||||
|
constexpr auto kDefaultChargeStars = 10;
|
||||||
|
|
||||||
using Exceptions = Api::UserPrivacy::Exceptions;
|
using Exceptions = Api::UserPrivacy::Exceptions;
|
||||||
|
|
||||||
|
@ -452,6 +457,143 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<Ui::RpWidget> MakeChargeStarsSlider(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<const style::MediaSlider*> sliderStyle,
|
||||||
|
not_null<const style::FlatLabel*> labelStyle,
|
||||||
|
int valuesCount,
|
||||||
|
Fn<int(int)> valueByIndex,
|
||||||
|
int value,
|
||||||
|
int maxValue,
|
||||||
|
Fn<void(int)> valueProgress,
|
||||||
|
Fn<void(int)> valueFinished) {
|
||||||
|
auto result = object_ptr<Ui::VerticalLayout>(parent);
|
||||||
|
const auto raw = result.data();
|
||||||
|
|
||||||
|
const auto labels = raw->add(object_ptr<Ui::RpWidget>(raw));
|
||||||
|
const auto min = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
raw,
|
||||||
|
QString::number(kStarsMin),
|
||||||
|
*labelStyle);
|
||||||
|
const auto max = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
raw,
|
||||||
|
QString::number(maxValue),
|
||||||
|
*labelStyle);
|
||||||
|
const auto current = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
raw,
|
||||||
|
QString::number(value),
|
||||||
|
*labelStyle);
|
||||||
|
min->setTextColorOverride(st::windowSubTextFg->c);
|
||||||
|
max->setTextColorOverride(st::windowSubTextFg->c);
|
||||||
|
const auto slider = raw->add(object_ptr<Ui::MediaSliderWheelless>(
|
||||||
|
raw,
|
||||||
|
*sliderStyle));
|
||||||
|
labels->resize(
|
||||||
|
labels->width(),
|
||||||
|
current->height() + st::defaultVerticalListSkip);
|
||||||
|
struct State {
|
||||||
|
int indexMin = 0;
|
||||||
|
int index = 0;
|
||||||
|
};
|
||||||
|
const auto state = raw->lifetime().make_state<State>();
|
||||||
|
const auto updateByIndex = [=] {
|
||||||
|
const auto outer = labels->width();
|
||||||
|
const auto minWidth = min->width();
|
||||||
|
const auto maxWidth = max->width();
|
||||||
|
const auto currentWidth = current->width();
|
||||||
|
if (minWidth + maxWidth + currentWidth > outer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
min->moveToLeft(0, 0, outer);
|
||||||
|
max->moveToRight(0, 0, outer);
|
||||||
|
current->moveToLeft((outer - current->width()) / 2, 0, outer);
|
||||||
|
};
|
||||||
|
const auto updateByValue = [=](int value) {
|
||||||
|
current->setText(
|
||||||
|
tr::lng_action_gift_for_stars(tr::now, lt_count, value));
|
||||||
|
|
||||||
|
state->index = 0;
|
||||||
|
auto maxIndex = valuesCount - 1;
|
||||||
|
while (state->index < maxIndex) {
|
||||||
|
const auto mid = (state->index + maxIndex) / 2;
|
||||||
|
const auto midValue = valueByIndex(mid);
|
||||||
|
if (midValue == value) {
|
||||||
|
state->index = mid;
|
||||||
|
break;
|
||||||
|
} else if (midValue < value) {
|
||||||
|
state->index = mid + 1;
|
||||||
|
} else {
|
||||||
|
maxIndex = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateByIndex();
|
||||||
|
};
|
||||||
|
const auto progress = [=](int value) {
|
||||||
|
updateByValue(value);
|
||||||
|
valueProgress(value);
|
||||||
|
};
|
||||||
|
const auto finished = [=](int value) {
|
||||||
|
updateByValue(value);
|
||||||
|
valueFinished(value);
|
||||||
|
};
|
||||||
|
style::PaletteChanged() | rpl::start_with_next([=] {
|
||||||
|
min->setTextColorOverride(st::windowSubTextFg->c);
|
||||||
|
max->setTextColorOverride(st::windowSubTextFg->c);
|
||||||
|
}, raw->lifetime());
|
||||||
|
updateByValue(value);
|
||||||
|
state->indexMin = 0;
|
||||||
|
|
||||||
|
slider->setPseudoDiscrete(
|
||||||
|
valuesCount,
|
||||||
|
valueByIndex,
|
||||||
|
value,
|
||||||
|
progress,
|
||||||
|
finished,
|
||||||
|
state->indexMin);
|
||||||
|
slider->resize(slider->width(), sliderStyle->seekSize.height());
|
||||||
|
|
||||||
|
raw->widthValue() | rpl::start_with_next([=](int width) {
|
||||||
|
labels->resizeToWidth(width);
|
||||||
|
updateByIndex();
|
||||||
|
}, slider->lifetime());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditNoPaidMessagesExceptions(
|
||||||
|
not_null<Window::SessionController*> window,
|
||||||
|
const Api::UserPrivacy::Rule &value) {
|
||||||
|
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
|
||||||
|
&window->session(),
|
||||||
|
tr::lng_messages_privacy_remove_fee(),
|
||||||
|
value.always,
|
||||||
|
std::optional<SpecialRowType>());
|
||||||
|
auto initBox = [=, controller = controller.get()](
|
||||||
|
not_null<PeerListBox*> box) {
|
||||||
|
box->addButton(tr::lng_settings_save(), [=] {
|
||||||
|
auto copy = value;
|
||||||
|
auto &setTo = copy.always;
|
||||||
|
setTo.peers = box->collectSelectedRows();
|
||||||
|
setTo.premiums = false;
|
||||||
|
setTo.miniapps = false;
|
||||||
|
auto &removeFrom = copy.never;
|
||||||
|
for (const auto peer : setTo.peers) {
|
||||||
|
removeFrom.peers.erase(
|
||||||
|
ranges::remove(removeFrom.peers, peer),
|
||||||
|
end(removeFrom.peers));
|
||||||
|
}
|
||||||
|
window->session().api().userPrivacy().save(
|
||||||
|
Api::UserPrivacy::Key::NoPaidMessages,
|
||||||
|
copy);
|
||||||
|
box->closeBox();
|
||||||
|
});
|
||||||
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
|
};
|
||||||
|
window->show(
|
||||||
|
Box<PeerListBox>(std::move(controller), std::move(initBox)));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool EditPrivacyController::hasOption(Option option) const {
|
bool EditPrivacyController::hasOption(Option option) const {
|
||||||
|
@ -812,19 +954,27 @@ void EditMessagesPrivacyBox(
|
||||||
|
|
||||||
constexpr auto kOptionAll = 0;
|
constexpr auto kOptionAll = 0;
|
||||||
constexpr auto kOptionPremium = 1;
|
constexpr auto kOptionPremium = 1;
|
||||||
|
constexpr auto kOptionCharge = 2;
|
||||||
|
|
||||||
|
const auto session = &controller->session();
|
||||||
const auto allowed = [=] {
|
const auto allowed = [=] {
|
||||||
return controller->session().premium()
|
return session->premium()
|
||||||
|| controller->session().appConfig().newRequirePremiumFree();
|
|| session->appConfig().newRequirePremiumFree();
|
||||||
};
|
};
|
||||||
const auto privacy = &controller->session().api().globalPrivacy();
|
const auto privacy = &session->api().globalPrivacy();
|
||||||
const auto inner = box->verticalLayout();
|
const auto inner = box->verticalLayout();
|
||||||
inner->add(object_ptr<Ui::PlainShadow>(box));
|
inner->add(object_ptr<Ui::PlainShadow>(box));
|
||||||
|
|
||||||
Ui::AddSkip(inner, st::messagePrivacyTopSkip);
|
Ui::AddSkip(inner, st::messagePrivacyTopSkip);
|
||||||
Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle());
|
Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle());
|
||||||
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
|
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
|
||||||
privacy->newRequirePremiumCurrent() ? kOptionPremium : kOptionAll);
|
(!allowed()
|
||||||
|
? kOptionAll
|
||||||
|
: privacy->newRequirePremiumCurrent()
|
||||||
|
? kOptionPremium
|
||||||
|
: privacy->newChargeStarsCurrent()
|
||||||
|
? kOptionCharge
|
||||||
|
: kOptionAll));
|
||||||
inner->add(
|
inner->add(
|
||||||
object_ptr<Ui::Radiobutton>(
|
object_ptr<Ui::Radiobutton>(
|
||||||
inner,
|
inner,
|
||||||
|
@ -846,6 +996,92 @@ void EditMessagesPrivacyBox(
|
||||||
0,
|
0,
|
||||||
st::messagePrivacyBottomSkip));
|
st::messagePrivacyBottomSkip));
|
||||||
|
|
||||||
|
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
|
||||||
|
|
||||||
|
const auto available = session->appConfig().paidMessagesAvailable();
|
||||||
|
|
||||||
|
const auto charged = available
|
||||||
|
? inner->add(
|
||||||
|
object_ptr<Ui::Radiobutton>(
|
||||||
|
inner,
|
||||||
|
group,
|
||||||
|
kOptionCharge,
|
||||||
|
tr::lng_messages_privacy_charge(tr::now),
|
||||||
|
st::messagePrivacyCheck),
|
||||||
|
st::settingsSendTypePadding + style::margins(
|
||||||
|
0,
|
||||||
|
st::messagePrivacyBottomSkip,
|
||||||
|
0,
|
||||||
|
st::messagePrivacyBottomSkip))
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
rpl::variable<int> stars;
|
||||||
|
};
|
||||||
|
const auto state = std::make_shared<State>();
|
||||||
|
const auto savedValue = privacy->newChargeStarsCurrent();
|
||||||
|
|
||||||
|
if (available) {
|
||||||
|
Ui::AddDividerText(inner, tr::lng_messages_privacy_charge_about());
|
||||||
|
|
||||||
|
const auto chargeWrap = inner->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
inner,
|
||||||
|
object_ptr<Ui::VerticalLayout>(inner)));
|
||||||
|
const auto chargeInner = chargeWrap->entity();
|
||||||
|
|
||||||
|
Ui::AddSkip(chargeInner);
|
||||||
|
|
||||||
|
state->stars = SetupChargeSlider(
|
||||||
|
chargeInner,
|
||||||
|
session->user(),
|
||||||
|
savedValue);
|
||||||
|
|
||||||
|
Ui::AddSkip(chargeInner);
|
||||||
|
Ui::AddSubsectionTitle(
|
||||||
|
chargeInner,
|
||||||
|
tr::lng_messages_privacy_exceptions());
|
||||||
|
|
||||||
|
const auto key = Api::UserPrivacy::Key::NoPaidMessages;
|
||||||
|
session->api().userPrivacy().reload(key);
|
||||||
|
auto label = session->api().userPrivacy().value(
|
||||||
|
key
|
||||||
|
) | rpl::map([=](const Api::UserPrivacy::Rule &value) {
|
||||||
|
using namespace Settings;
|
||||||
|
const auto always = ExceptionUsersCount(value.always.peers);
|
||||||
|
return always
|
||||||
|
? tr::lng_edit_privacy_exceptions_count(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
always)
|
||||||
|
: QString();
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto exceptions = Settings::AddButtonWithLabel(
|
||||||
|
chargeInner,
|
||||||
|
tr::lng_messages_privacy_remove_fee(),
|
||||||
|
std::move(label),
|
||||||
|
st::settingsButtonNoIcon);
|
||||||
|
|
||||||
|
const auto shower = exceptions->lifetime().make_state<rpl::lifetime>();
|
||||||
|
exceptions->setClickedCallback([=] {
|
||||||
|
*shower = session->api().userPrivacy().value(
|
||||||
|
key
|
||||||
|
) | rpl::take(
|
||||||
|
1
|
||||||
|
) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) {
|
||||||
|
EditNoPaidMessagesExceptions(controller, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Ui::AddSkip(chargeInner);
|
||||||
|
Ui::AddDividerText(
|
||||||
|
chargeInner,
|
||||||
|
tr::lng_messages_privacy_remove_about());
|
||||||
|
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
chargeWrap->toggleOn(group->value() | rpl::map(_1 == kOptionCharge));
|
||||||
|
chargeWrap->finishAnimating();
|
||||||
|
}
|
||||||
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
|
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
|
||||||
const auto toast = std::make_shared<WeakToast>();
|
const auto toast = std::make_shared<WeakToast>();
|
||||||
const auto showToast = [=] {
|
const auto showToast = [=] {
|
||||||
|
@ -875,19 +1111,20 @@ void EditMessagesPrivacyBox(
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!allowed()) {
|
if (!allowed()) {
|
||||||
CreateRadiobuttonLock(restricted, st::messagePrivacyCheck);
|
CreateRadiobuttonLock(restricted, st::messagePrivacyCheck);
|
||||||
|
if (charged) {
|
||||||
|
CreateRadiobuttonLock(charged, st::messagePrivacyCheck);
|
||||||
|
}
|
||||||
|
|
||||||
group->setChangedCallback([=](int value) {
|
group->setChangedCallback([=](int value) {
|
||||||
if (value == kOptionPremium) {
|
if (value == kOptionPremium || value == kOptionCharge) {
|
||||||
group->setValue(kOptionAll);
|
group->setValue(kOptionAll);
|
||||||
showToast();
|
showToast();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
|
|
||||||
if (!allowed()) {
|
|
||||||
Ui::AddSkip(inner);
|
Ui::AddSkip(inner);
|
||||||
Settings::AddButtonWithIcon(
|
Settings::AddButtonWithIcon(
|
||||||
inner,
|
inner,
|
||||||
|
@ -907,8 +1144,12 @@ void EditMessagesPrivacyBox(
|
||||||
} else {
|
} else {
|
||||||
box->addButton(tr::lng_settings_save(), [=] {
|
box->addButton(tr::lng_settings_save(), [=] {
|
||||||
if (allowed()) {
|
if (allowed()) {
|
||||||
privacy->updateNewRequirePremium(
|
const auto value = group->current();
|
||||||
group->current() == kOptionPremium);
|
const auto premiumRequired = (value == kOptionPremium);
|
||||||
|
const auto chargeStars = (value == kOptionCharge)
|
||||||
|
? state->stars.current()
|
||||||
|
: 0;
|
||||||
|
privacy->updateMessagesPrivacy(premiumRequired, chargeStars);
|
||||||
box->closeBox();
|
box->closeBox();
|
||||||
} else {
|
} else {
|
||||||
showToast();
|
showToast();
|
||||||
|
@ -919,3 +1160,78 @@ void EditMessagesPrivacyBox(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> SetupChargeSlider(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
int savedValue) {
|
||||||
|
struct State {
|
||||||
|
rpl::variable<int> stars;
|
||||||
|
};
|
||||||
|
const auto group = !peer->isUser();
|
||||||
|
const auto state = container->lifetime().make_state<State>();
|
||||||
|
const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars;
|
||||||
|
state->stars = chargeStars;
|
||||||
|
|
||||||
|
Ui::AddSubsectionTitle(container, group
|
||||||
|
? tr::lng_rights_charge_price()
|
||||||
|
: tr::lng_messages_privacy_price());
|
||||||
|
|
||||||
|
auto values = std::vector<int>();
|
||||||
|
const auto maxStars = peer->session().appConfig().paidMessageStarsMax();
|
||||||
|
if (chargeStars < kStarsMin) {
|
||||||
|
values.push_back(chargeStars);
|
||||||
|
}
|
||||||
|
for (auto i = kStarsMin; i < std::min(100, maxStars); ++i) {
|
||||||
|
values.push_back(i);
|
||||||
|
}
|
||||||
|
for (auto i = 100; i < std::min(1000, maxStars); i += 10) {
|
||||||
|
if (i < chargeStars + 10 && chargeStars < i) {
|
||||||
|
values.push_back(chargeStars);
|
||||||
|
}
|
||||||
|
values.push_back(i);
|
||||||
|
}
|
||||||
|
for (auto i = 1000; i < maxStars + 1; i += 100) {
|
||||||
|
if (i < chargeStars + 100 && chargeStars < i) {
|
||||||
|
values.push_back(chargeStars);
|
||||||
|
}
|
||||||
|
values.push_back(i);
|
||||||
|
}
|
||||||
|
const auto valuesCount = int(values.size());
|
||||||
|
const auto setStars = [=](int value) {
|
||||||
|
state->stars = value;
|
||||||
|
};
|
||||||
|
container->add(
|
||||||
|
MakeChargeStarsSlider(
|
||||||
|
container,
|
||||||
|
&st::settingsScale,
|
||||||
|
&st::settingsScaleLabel,
|
||||||
|
valuesCount,
|
||||||
|
[=](int index) { return values[index]; },
|
||||||
|
chargeStars,
|
||||||
|
maxStars,
|
||||||
|
setStars,
|
||||||
|
setStars),
|
||||||
|
st::boxRowPadding);
|
||||||
|
|
||||||
|
const auto skip = 2 * st::defaultVerticalListSkip;
|
||||||
|
Ui::AddSkip(container, skip);
|
||||||
|
|
||||||
|
auto dollars = state->stars.value() | rpl::map([=](int stars) {
|
||||||
|
const auto ratio = peer->session().appConfig().starsWithdrawRate();
|
||||||
|
const auto dollars = int(base::SafeRound(stars * ratio));
|
||||||
|
return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q);
|
||||||
|
});
|
||||||
|
const auto percent = peer->session().appConfig().paidMessageCommission();
|
||||||
|
Ui::AddDividerText(
|
||||||
|
container,
|
||||||
|
(group
|
||||||
|
? tr::lng_rights_charge_price_about
|
||||||
|
: tr::lng_messages_privacy_price_about)(
|
||||||
|
lt_percent,
|
||||||
|
rpl::single(QString::number(percent / 10.) + '%'),
|
||||||
|
lt_amount,
|
||||||
|
std::move(dollars)));
|
||||||
|
|
||||||
|
return state->stars.value();
|
||||||
|
}
|
||||||
|
|
|
@ -169,3 +169,8 @@ private:
|
||||||
void EditMessagesPrivacyBox(
|
void EditMessagesPrivacyBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
not_null<Window::SessionController*> controller);
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<int> SetupChargeSlider(
|
||||||
|
not_null<Ui::VerticalLayout*> container,
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
int savedValue);
|
||||||
|
|
|
@ -441,13 +441,10 @@ void EditFilterBox(
|
||||||
using namespace Window;
|
using namespace Window;
|
||||||
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
|
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
|
||||||
};
|
};
|
||||||
name->setCustomTextContext([=](Fn<void()> repaint) {
|
name->setCustomTextContext(Core::TextContext({
|
||||||
return std::any(Core::MarkedTextContext{
|
.session = session,
|
||||||
.session = session,
|
.customEmojiLoopLimit = value ? -1 : 0,
|
||||||
.customEmojiRepaint = std::move(repaint),
|
}), [paused] {
|
||||||
.customEmojiLoopLimit = value ? -1 : 0,
|
|
||||||
});
|
|
||||||
}, [paused] {
|
|
||||||
return On(PowerSaving::kEmojiChat) || paused();
|
return On(PowerSaving::kEmojiChat) || paused();
|
||||||
}, [paused] {
|
}, [paused] {
|
||||||
return On(PowerSaving::kChatSpoiler) || paused();
|
return On(PowerSaving::kChatSpoiler) || paused();
|
||||||
|
@ -609,10 +606,7 @@ void EditFilterBox(
|
||||||
float64 alpha = 1.;
|
float64 alpha = 1.;
|
||||||
};
|
};
|
||||||
const auto tag = preview->lifetime().make_state<TagState>();
|
const auto tag = preview->lifetime().make_state<TagState>();
|
||||||
tag->context.textContext = Core::MarkedTextContext{
|
tag->context.textContext = Core::TextContext({ .session = session });
|
||||||
.session = session,
|
|
||||||
.customEmojiRepaint = [] {},
|
|
||||||
};
|
|
||||||
preview->paintRequest() | rpl::start_with_next([=] {
|
preview->paintRequest() | rpl::start_with_next([=] {
|
||||||
auto p = QPainter(preview);
|
auto p = QPainter(preview);
|
||||||
p.setOpacity(tag->alpha);
|
p.setOpacity(tag->alpha);
|
||||||
|
|
|
@ -163,10 +163,10 @@ ExceptionRow::ExceptionRow(
|
||||||
st::defaultTextStyle,
|
st::defaultTextStyle,
|
||||||
filters,
|
filters,
|
||||||
kMarkupTextOptions,
|
kMarkupTextOptions,
|
||||||
Core::MarkedTextContext{
|
Core::TextContext({
|
||||||
.session = &history->session(),
|
.session = &history->session(),
|
||||||
.customEmojiRepaint = repaint,
|
.repaint = repaint,
|
||||||
});
|
}));
|
||||||
} else if (peer()->isSelf()) {
|
} else if (peer()->isSelf()) {
|
||||||
setCustomStatus(tr::lng_saved_forward_here(tr::now));
|
setCustomStatus(tr::lng_saved_forward_here(tr::now));
|
||||||
}
|
}
|
||||||
|
|
|
@ -537,13 +537,6 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||||
verticalLayout->add(std::move(icon.widget));
|
verticalLayout->add(std::move(icon.widget));
|
||||||
|
|
||||||
const auto isStatic = _filterTitle.isStatic;
|
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(
|
verticalLayout->add(
|
||||||
object_ptr<Ui::CenterWrap<>>(
|
object_ptr<Ui::CenterWrap<>>(
|
||||||
verticalLayout,
|
verticalLayout,
|
||||||
|
@ -559,7 +552,10 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||||
Ui::Text::WithEntities)),
|
Ui::Text::WithEntities)),
|
||||||
st::settingsFilterDividerLabel,
|
st::settingsFilterDividerLabel,
|
||||||
st::defaultPopupMenu,
|
st::defaultPopupMenu,
|
||||||
makeContext)),
|
Core::TextContext({
|
||||||
|
.session = &_window->session(),
|
||||||
|
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||||
|
}))),
|
||||||
st::filterLinkDividerLabelPadding);
|
st::filterLinkDividerLabelPadding);
|
||||||
|
|
||||||
verticalLayout->geometryValue(
|
verticalLayout->geometryValue(
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "api/api_credits.h"
|
#include "api/api_credits.h"
|
||||||
#include "boxes/peer_list_controllers.h"
|
#include "boxes/peer_list_controllers.h"
|
||||||
|
#include "core/ui_integration.h" // TextContext.
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -67,14 +68,9 @@ void GiftCreditsBox(
|
||||||
2.);
|
2.);
|
||||||
{
|
{
|
||||||
Ui::AddSkip(content);
|
Ui::AddSkip(content);
|
||||||
const auto arrow = Ui::Text::SingleCustomEmoji(
|
|
||||||
peer->owner().customEmojiManager().registerInternalEmoji(
|
|
||||||
st::topicButtonArrow,
|
|
||||||
st::channelEarnLearnArrowMargins,
|
|
||||||
true));
|
|
||||||
auto link = tr::lng_credits_box_history_entry_gift_about_link(
|
auto link = tr::lng_credits_box_history_entry_gift_about_link(
|
||||||
lt_emoji,
|
lt_emoji,
|
||||||
rpl::single(arrow),
|
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
|
||||||
Ui::Text::RichLangValue
|
Ui::Text::RichLangValue
|
||||||
) | rpl::map([](TextWithEntities text) {
|
) | rpl::map([](TextWithEntities text) {
|
||||||
return Ui::Text::Link(
|
return Ui::Text::Link(
|
||||||
|
@ -92,7 +88,7 @@ void GiftCreditsBox(
|
||||||
lt_link,
|
lt_link,
|
||||||
std::move(link),
|
std::move(link),
|
||||||
Ui::Text::RichLangValue),
|
Ui::Text::RichLangValue),
|
||||||
{ .session = &peer->session() },
|
Core::TextContext({ .session = &peer->session() }),
|
||||||
st::creditsBoxAbout)),
|
st::creditsBoxAbout)),
|
||||||
st::boxRowPadding);
|
st::boxRowPadding);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
||||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||||
#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox.
|
#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox.
|
||||||
|
#include "core/ui_integration.h"
|
||||||
#include "data/data_boosts.h"
|
#include "data/data_boosts.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
@ -58,7 +59,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
#include "ui/widgets/gradient_round_button.h"
|
#include "ui/widgets/gradient_round_button.h"
|
||||||
#include "ui/widgets/label_with_custom_emoji.h"
|
|
||||||
#include "ui/widgets/tooltip.h"
|
#include "ui/widgets/tooltip.h"
|
||||||
#include "ui/wrap/padding_wrap.h"
|
#include "ui/wrap/padding_wrap.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
|
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
|
#include "styles/style_credits.h"
|
||||||
#include "styles/style_giveaway.h"
|
#include "styles/style_giveaway.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
|
@ -516,13 +517,13 @@ not_null<Ui::FlatLabel*> AddTableRow(
|
||||||
not_null<Ui::TableLayout*> table,
|
not_null<Ui::TableLayout*> table,
|
||||||
rpl::producer<QString> label,
|
rpl::producer<QString> label,
|
||||||
rpl::producer<TextWithEntities> value,
|
rpl::producer<TextWithEntities> value,
|
||||||
const Fn<std::any(Fn<void()>)> &makeContext = nullptr) {
|
const Ui::Text::MarkedContext &context = {}) {
|
||||||
auto widget = object_ptr<Ui::FlatLabel>(
|
auto widget = object_ptr<Ui::FlatLabel>(
|
||||||
table,
|
table,
|
||||||
std::move(value),
|
std::move(value),
|
||||||
table->st().defaultValue,
|
table->st().defaultValue,
|
||||||
st::defaultPopupMenu,
|
st::defaultPopupMenu,
|
||||||
std::move(makeContext));
|
context);
|
||||||
const auto result = widget.data();
|
const auto result = widget.data();
|
||||||
AddTableRow(
|
AddTableRow(
|
||||||
table,
|
table,
|
||||||
|
@ -1272,8 +1273,8 @@ void AddStarGiftTable(
|
||||||
const auto selfBareId = session->userPeerId().value;
|
const auto selfBareId = session->userPeerId().value;
|
||||||
const auto giftToSelf = (peerId == session->userPeerId())
|
const auto giftToSelf = (peerId == session->userPeerId())
|
||||||
&& (entry.in || entry.bareGiftOwnerId == selfBareId);
|
&& (entry.in || entry.bareGiftOwnerId == selfBareId);
|
||||||
const auto giftToChannel = entry.giftSavedId
|
const auto giftToChannel = entry.giftChannelSavedId
|
||||||
&& peerIsChannel(PeerId(entry.bareGiftListPeerId));
|
&& peerIsChannel(PeerId(entry.bareEntryOwnerId));
|
||||||
|
|
||||||
const auto raw = std::make_shared<Ui::ImportantTooltip*>(nullptr);
|
const auto raw = std::make_shared<Ui::ImportantTooltip*>(nullptr);
|
||||||
const auto showTooltip = [=](
|
const auto showTooltip = [=](
|
||||||
|
@ -1394,14 +1395,14 @@ void AddStarGiftTable(
|
||||||
? MakePeerTableValue(table, show, PeerId(entry.bareActorId))
|
? MakePeerTableValue(table, show, PeerId(entry.bareActorId))
|
||||||
: MakeHiddenPeerTableValue(table)),
|
: MakeHiddenPeerTableValue(table)),
|
||||||
st::giveawayGiftCodePeerMargin);
|
st::giveawayGiftCodePeerMargin);
|
||||||
if (entry.bareGiftListPeerId) {
|
if (entry.bareEntryOwnerId) {
|
||||||
AddTableRow(
|
AddTableRow(
|
||||||
table,
|
table,
|
||||||
tr::lng_credits_box_history_entry_peer(),
|
tr::lng_credits_box_history_entry_peer(),
|
||||||
MakePeerTableValue(
|
MakePeerTableValue(
|
||||||
table,
|
table,
|
||||||
show,
|
show,
|
||||||
PeerId(entry.bareGiftListPeerId)),
|
PeerId(entry.bareEntryOwnerId)),
|
||||||
st::giveawayGiftCodePeerMargin);
|
st::giveawayGiftCodePeerMargin);
|
||||||
}
|
}
|
||||||
} else if (peerId && !giftToSelf) {
|
} else if (peerId && !giftToSelf) {
|
||||||
|
@ -1526,12 +1527,6 @@ void AddStarGiftTable(
|
||||||
: nullptr;
|
: nullptr;
|
||||||
const auto date = base::unixtime::parse(original.date).date();
|
const auto date = base::unixtime::parse(original.date).date();
|
||||||
const auto dateText = TextWithEntities{ langDayOfMonth(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>(
|
auto label = object_ptr<Ui::FlatLabel>(
|
||||||
table,
|
table,
|
||||||
(from
|
(from
|
||||||
|
@ -1573,7 +1568,7 @@ void AddStarGiftTable(
|
||||||
? *st.tableValueMessage
|
? *st.tableValueMessage
|
||||||
: st::giveawayGiftMessage),
|
: st::giveawayGiftMessage),
|
||||||
st::defaultPopupMenu,
|
st::defaultPopupMenu,
|
||||||
makeContext);
|
Core::TextContext({ .session = session }));
|
||||||
const auto showBoxLink = [=](not_null<PeerData*> peer) {
|
const auto showBoxLink = [=](not_null<PeerData*> peer) {
|
||||||
return std::make_shared<LambdaClickHandler>([=] {
|
return std::make_shared<LambdaClickHandler>([=] {
|
||||||
show->showBox(PrepareShortInfoBox(peer, show));
|
show->showBox(PrepareShortInfoBox(peer, show));
|
||||||
|
@ -1591,12 +1586,6 @@ void AddStarGiftTable(
|
||||||
st::giveawayGiftCodeValueMargin);
|
st::giveawayGiftCodeValueMargin);
|
||||||
}
|
}
|
||||||
} else if (!entry.description.empty()) {
|
} else if (!entry.description.empty()) {
|
||||||
const auto makeContext = [=](Fn<void()> update) {
|
|
||||||
return Core::MarkedTextContext{
|
|
||||||
.session = session,
|
|
||||||
.customEmojiRepaint = std::move(update),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
auto label = object_ptr<Ui::FlatLabel>(
|
auto label = object_ptr<Ui::FlatLabel>(
|
||||||
table,
|
table,
|
||||||
rpl::single(entry.description),
|
rpl::single(entry.description),
|
||||||
|
@ -1604,7 +1593,7 @@ void AddStarGiftTable(
|
||||||
? *st.tableValueMessage
|
? *st.tableValueMessage
|
||||||
: st::giveawayGiftMessage),
|
: st::giveawayGiftMessage),
|
||||||
st::defaultPopupMenu,
|
st::defaultPopupMenu,
|
||||||
makeContext);
|
Core::TextContext({ .session = session }));
|
||||||
label->setSelectable(true);
|
label->setSelectable(true);
|
||||||
table->addRow(
|
table->addRow(
|
||||||
nullptr,
|
nullptr,
|
||||||
|
@ -1775,6 +1764,25 @@ void AddCreditsHistoryEntryTable(
|
||||||
tr::lng_credits_box_history_entry_subscription(
|
tr::lng_credits_box_history_entry_subscription(
|
||||||
Ui::Text::WithEntities));
|
Ui::Text::WithEntities));
|
||||||
}
|
}
|
||||||
|
if (entry.paidMessagesAmount) {
|
||||||
|
auto value = Ui::Text::IconEmoji(&st::starIconEmojiColored);
|
||||||
|
const auto full = (entry.in ? 1 : -1)
|
||||||
|
* (entry.credits + entry.paidMessagesAmount);
|
||||||
|
const auto starsText = Lang::FormatStarsAmountDecimal(full);
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
tr::lng_credits_paid_messages_full(),
|
||||||
|
rpl::single(value.append(' ' + starsText)));
|
||||||
|
}
|
||||||
|
if (const auto months = entry.premiumMonthsForStars) {
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
tr::lng_credits_premium_gift_duration(),
|
||||||
|
tr::lng_months(
|
||||||
|
lt_count,
|
||||||
|
rpl::single(1. * months),
|
||||||
|
Ui::Text::WithEntities));
|
||||||
|
}
|
||||||
if (!entry.id.isEmpty()) {
|
if (!entry.id.isEmpty()) {
|
||||||
auto label = MakeMaybeMultilineTokenValue(table, entry.id, st);
|
auto label = MakeMaybeMultilineTokenValue(table, entry.id, st);
|
||||||
label->setClickHandlerFilter([=](const auto &...) {
|
label->setClickHandlerFilter([=](const auto &...) {
|
||||||
|
|
|
@ -453,10 +453,7 @@ void CreateModerateMessagesBox(
|
||||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||||
raw->setMarkedText(
|
raw->setMarkedText(
|
||||||
Ui::Text::Link(text, u"internal:"_q),
|
Ui::Text::Link(text, u"internal:"_q),
|
||||||
Core::MarkedTextContext{
|
Core::TextContext({ .session = session }));
|
||||||
.session = session,
|
|
||||||
.customEmojiRepaint = [=] { raw->update(); },
|
|
||||||
});
|
|
||||||
}, label->lifetime());
|
}, label->lifetime());
|
||||||
|
|
||||||
Ui::AddSkip(inner);
|
Ui::AddSkip(inner);
|
||||||
|
|
|
@ -1154,8 +1154,7 @@ RecoverBox::RecoverBox(
|
||||||
rpl::single(Ui::Text::WrapEmailPattern(pattern)),
|
rpl::single(Ui::Text::WrapEmailPattern(pattern)),
|
||||||
Ui::Text::WithEntities),
|
Ui::Text::WithEntities),
|
||||||
st::termsContent,
|
st::termsContent,
|
||||||
st::defaultPopupMenu,
|
st::defaultPopupMenu)
|
||||||
[=](Fn<void()> update) { return CommonTextContext{ std::move(update) }; })
|
|
||||||
, _closeParent(std::move(closeParent)) {
|
, _closeParent(std::move(closeParent)) {
|
||||||
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
if (_cloudFields.pendingResetDate != 0 || !session) {
|
if (_cloudFields.pendingResetDate != 0 || !session) {
|
||||||
|
|
|
@ -883,6 +883,7 @@ void PeerListRow::paintUserpic(
|
||||||
} else if (const auto callback = generatePaintUserpicCallback(false)) {
|
} else if (const auto callback = generatePaintUserpicCallback(false)) {
|
||||||
callback(p, x, y, outerWidth, st.photoSize);
|
callback(p, x, y, outerWidth, st.photoSize);
|
||||||
}
|
}
|
||||||
|
paintUserpicOverlay(p, st, x, y, outerWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emulates Ui::RoundImageCheckbox::paint() in a checked state.
|
// Emulates Ui::RoundImageCheckbox::paint() in a checked state.
|
||||||
|
|
|
@ -95,6 +95,13 @@ public:
|
||||||
[[nodiscard]] virtual QString generateShortName();
|
[[nodiscard]] virtual QString generateShortName();
|
||||||
[[nodiscard]] virtual auto generatePaintUserpicCallback(
|
[[nodiscard]] virtual auto generatePaintUserpicCallback(
|
||||||
bool forceRound) -> PaintRoundImageCallback;
|
bool forceRound) -> PaintRoundImageCallback;
|
||||||
|
virtual void paintUserpicOverlay(
|
||||||
|
Painter &p,
|
||||||
|
const style::PeerListItem &st,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int outerWidth) {
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] virtual auto generateNameFirstLetters() const
|
[[nodiscard]] virtual auto generateNameFirstLetters() const
|
||||||
-> const base::flat_set<QChar> &;
|
-> const base::flat_set<QChar> &;
|
||||||
|
|
|
@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/peer_list_controllers.h"
|
#include "boxes/peer_list_controllers.h"
|
||||||
|
|
||||||
#include "api/api_chat_participants.h"
|
#include "api/api_chat_participants.h"
|
||||||
#include "api/api_premium.h"
|
#include "api/api_premium.h" // MessageMoneyRestriction.
|
||||||
#include "base/random.h"
|
#include "base/random.h"
|
||||||
#include "boxes/filters/edit_filter_chats_list.h"
|
#include "boxes/filters/edit_filter_chats_list.h"
|
||||||
#include "settings/settings_premium.h"
|
#include "settings/settings_premium.h"
|
||||||
|
@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "dialogs/dialogs_main_list.h"
|
#include "dialogs/dialogs_main_list.h"
|
||||||
|
#include "payments/ui/payments_reaction_box.h"
|
||||||
#include "ui/effects/outline_segments.h"
|
#include "ui/effects/outline_segments.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "window/window_separate_id.h"
|
#include "window/window_separate_id.h"
|
||||||
|
@ -275,40 +276,71 @@ bool PeerListGlobalSearchController::isLoading() {
|
||||||
return _timer.isActive() || _requestId;
|
return _timer.isActive() || _requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RecipientRow::Restriction {
|
||||||
|
Api::MessageMoneyRestriction value;
|
||||||
|
RestrictionBadgeCache cache;
|
||||||
|
};
|
||||||
|
|
||||||
RecipientRow::RecipientRow(
|
RecipientRow::RecipientRow(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const style::PeerListItem *maybeLockedSt,
|
const style::PeerListItem *maybeLockedSt,
|
||||||
History *maybeHistory)
|
History *maybeHistory)
|
||||||
: PeerListRow(peer)
|
: PeerListRow(peer)
|
||||||
, _maybeHistory(maybeHistory)
|
, _maybeHistory(maybeHistory)
|
||||||
, _resolvePremiumRequired(maybeLockedSt != nullptr) {
|
, _maybeLockedSt(maybeLockedSt) {
|
||||||
if (maybeLockedSt
|
if (_maybeLockedSt) {
|
||||||
&& (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory)
|
setRestriction(Api::ResolveMessageMoneyRestrictions(
|
||||||
== Api::RequirePremiumState::Yes)) {
|
peer,
|
||||||
_lockedSt = maybeLockedSt;
|
maybeHistory));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback(
|
Api::MessageMoneyRestriction RecipientRow::restriction() const {
|
||||||
bool forceRound) {
|
return _restriction
|
||||||
auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
|
? _restriction->value
|
||||||
if (const auto st = _lockedSt) {
|
: Api::MessageMoneyRestriction();
|
||||||
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
}
|
||||||
result(p, x, y, outerWidth, size);
|
|
||||||
PaintPremiumRequiredLock(p, st, x, y, outerWidth, size);
|
void RecipientRow::setRestriction(Api::MessageMoneyRestriction restriction) {
|
||||||
};
|
if (!restriction) {
|
||||||
|
_restriction = nullptr;
|
||||||
|
return;
|
||||||
|
} else if (!_restriction) {
|
||||||
|
_restriction = std::make_unique<Restriction>();
|
||||||
|
}
|
||||||
|
_restriction->value = restriction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecipientRow::paintUserpicOverlay(
|
||||||
|
Painter &p,
|
||||||
|
const style::PeerListItem &st,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int outerWidth) {
|
||||||
|
if (const auto &r = _restriction) {
|
||||||
|
PaintRestrictionBadge(
|
||||||
|
p,
|
||||||
|
_maybeLockedSt,
|
||||||
|
r->value.starsPerMessage,
|
||||||
|
r->cache,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
outerWidth,
|
||||||
|
st.photoSize);
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RecipientRow::refreshLock(
|
bool RecipientRow::refreshLock(
|
||||||
not_null<const style::PeerListItem*> maybeLockedSt) {
|
not_null<const style::PeerListItem*> maybeLockedSt) {
|
||||||
if (const auto user = peer()->asUser()) {
|
if (const auto user = peer()->asUser()) {
|
||||||
const auto locked = _resolvePremiumRequired
|
using Restriction = Api::MessageMoneyRestriction;
|
||||||
&& (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory)
|
const auto r = _maybeLockedSt
|
||||||
== Api::RequirePremiumState::Yes);
|
? Api::ResolveMessageMoneyRestrictions(
|
||||||
if (this->locked() != locked) {
|
user,
|
||||||
setLocked(locked ? maybeLockedSt.get() : nullptr);
|
_maybeHistory)
|
||||||
|
: Restriction();
|
||||||
|
if ((_restriction ? _restriction->value : Restriction()) != r) {
|
||||||
|
setRestriction(r);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,22 +350,30 @@ bool RecipientRow::refreshLock(
|
||||||
void RecipientRow::preloadUserpic() {
|
void RecipientRow::preloadUserpic() {
|
||||||
PeerListRow::preloadUserpic();
|
PeerListRow::preloadUserpic();
|
||||||
|
|
||||||
if (!_resolvePremiumRequired) {
|
if (!_maybeLockedSt) {
|
||||||
return;
|
return;
|
||||||
} else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory)
|
}
|
||||||
== Api::RequirePremiumState::Unknown) {
|
const auto peer = this->peer();
|
||||||
const auto user = peer()->asUser();
|
const auto known = Api::ResolveMessageMoneyRestrictions(
|
||||||
user->session().api().premium().resolvePremiumRequired(user);
|
peer,
|
||||||
|
_maybeHistory).known;
|
||||||
|
if (known) {
|
||||||
|
return;
|
||||||
|
} else if (const auto user = peer->asUser()) {
|
||||||
|
const auto api = &user->session().api();
|
||||||
|
api->premium().resolveMessageMoneyRestrictions(user);
|
||||||
|
} else if (const auto group = peer->asChannel()) {
|
||||||
|
group->updateFull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackPremiumRequiredChanges(
|
void TrackMessageMoneyRestrictionsChanges(
|
||||||
not_null<PeerListController*> controller,
|
not_null<PeerListController*> controller,
|
||||||
rpl::lifetime &lifetime) {
|
rpl::lifetime &lifetime) {
|
||||||
const auto session = &controller->session();
|
const auto session = &controller->session();
|
||||||
rpl::merge(
|
rpl::merge(
|
||||||
Data::AmPremiumValue(session) | rpl::to_empty,
|
Data::AmPremiumValue(session) | rpl::to_empty,
|
||||||
session->api().premium().somePremiumRequiredResolved()
|
session->api().premium().someMessageMoneyRestrictionsResolved()
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
const auto st = &controller->computeListSt().item;
|
const auto st = &controller->computeListSt().item;
|
||||||
const auto delegate = controller->delegate();
|
const auto delegate = controller->delegate();
|
||||||
|
@ -726,7 +766,7 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
|
||||||
return std::make_unique<PeerListRow>(user);
|
return std::make_unique<PeerListRow>(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
RecipientPremiumRequiredError WritePremiumRequiredError(
|
RecipientMoneyRestrictionError WriteMoneyRestrictionError(
|
||||||
not_null<UserData*> user) {
|
not_null<UserData*> user) {
|
||||||
return {
|
return {
|
||||||
.text = tr::lng_send_non_premium_message_toast(
|
.text = tr::lng_send_non_premium_message_toast(
|
||||||
|
@ -759,7 +799,7 @@ ChooseRecipientBoxController::ChooseRecipientBoxController(
|
||||||
, _session(args.session)
|
, _session(args.session)
|
||||||
, _callback(std::move(args.callback))
|
, _callback(std::move(args.callback))
|
||||||
, _filter(std::move(args.filter))
|
, _filter(std::move(args.filter))
|
||||||
, _premiumRequiredError(std::move(args.premiumRequiredError)) {
|
, _moneyRestrictionError(std::move(args.moneyRestrictionError)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Main::Session &ChooseRecipientBoxController::session() const {
|
Main::Session &ChooseRecipientBoxController::session() const {
|
||||||
|
@ -769,14 +809,17 @@ Main::Session &ChooseRecipientBoxController::session() const {
|
||||||
void ChooseRecipientBoxController::prepareViewHook() {
|
void ChooseRecipientBoxController::prepareViewHook() {
|
||||||
delegate()->peerListSetTitle(tr::lng_forward_choose());
|
delegate()->peerListSetTitle(tr::lng_forward_choose());
|
||||||
|
|
||||||
if (_premiumRequiredError) {
|
if (_moneyRestrictionError) {
|
||||||
TrackPremiumRequiredChanges(this, lifetime());
|
TrackMessageMoneyRestrictionsChanges(this, lifetime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChooseRecipientBoxController::showLockedError(
|
bool ChooseRecipientBoxController::showLockedError(
|
||||||
not_null<PeerListRow*> row) {
|
not_null<PeerListRow*> row) {
|
||||||
return RecipientRow::ShowLockedError(this, row, _premiumRequiredError);
|
return RecipientRow::ShowLockedError(
|
||||||
|
this,
|
||||||
|
row,
|
||||||
|
_moneyRestrictionError);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
|
@ -836,8 +879,9 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
bool RecipientRow::ShowLockedError(
|
bool RecipientRow::ShowLockedError(
|
||||||
not_null<PeerListController*> controller,
|
not_null<PeerListController*> controller,
|
||||||
not_null<PeerListRow*> row,
|
not_null<PeerListRow*> row,
|
||||||
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) {
|
Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error) {
|
||||||
if (!static_cast<RecipientRow*>(row.get())->locked()) {
|
const auto recipient = static_cast<RecipientRow*>(row.get());
|
||||||
|
if (!recipient->restriction().premiumRequired) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
::Settings::ShowPremiumPromoToast(
|
::Settings::ShowPremiumPromoToast(
|
||||||
|
@ -860,15 +904,15 @@ auto ChooseRecipientBoxController::createRow(
|
||||||
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|
||||||
|| peer->isRepliesChat()
|
|| peer->isRepliesChat()
|
||||||
|| peer->isVerifyCodes()
|
|| peer->isVerifyCodes()
|
||||||
|| (peer->isUser() && (_premiumRequiredError
|
|| (peer->isUser() && (_moneyRestrictionError
|
||||||
? !peer->asUser()->canSendIgnoreRequirePremium()
|
? !peer->asUser()->canSendIgnoreMoneyRestrictions()
|
||||||
: !Data::CanSendAnything(peer))));
|
: !Data::CanSendAnything(peer))));
|
||||||
if (skip) {
|
if (skip) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto result = std::make_unique<Row>(
|
auto result = std::make_unique<Row>(
|
||||||
history,
|
history,
|
||||||
_premiumRequiredError ? &computeListSt().item : nullptr);
|
_moneyRestrictionError ? &computeListSt().item : nullptr);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1093,25 +1137,61 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
|
||||||
return skip ? nullptr : std::make_unique<Row>(topic);
|
return skip ? nullptr : std::make_unique<Row>(topic);
|
||||||
};
|
};
|
||||||
|
|
||||||
void PaintPremiumRequiredLock(
|
void PaintRestrictionBadge(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
not_null<const style::PeerListItem*> st,
|
not_null<const style::PeerListItem*> st,
|
||||||
|
int stars,
|
||||||
|
RestrictionBadgeCache &cache,
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int outerWidth,
|
int outerWidth,
|
||||||
int size) {
|
int size) {
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
const auto paletteVersion = style::PaletteVersion();
|
||||||
|
const auto good = !cache.badge.isNull()
|
||||||
|
&& (cache.stars == stars)
|
||||||
|
&& (cache.paletteVersion == paletteVersion);
|
||||||
const auto &check = st->checkbox.check;
|
const auto &check = st->checkbox.check;
|
||||||
auto pen = check.border->p;
|
const auto add = check.width;
|
||||||
pen.setWidthF(check.width);
|
if (!good) {
|
||||||
p.setPen(pen);
|
cache.stars = stars;
|
||||||
p.setBrush(st::premiumButtonBg2);
|
cache.paletteVersion = paletteVersion;
|
||||||
const auto &icon = st::stickersPremiumLock;
|
if (stars) {
|
||||||
const auto width = icon.width();
|
const auto text = (stars >= 1000)
|
||||||
const auto height = icon.height();
|
? (QString::number(stars / 1000) + 'K')
|
||||||
const auto rect = QRect(
|
: QString::number(stars);
|
||||||
QPoint(x + size - width, y + size - height),
|
cache.badge = Ui::GenerateSmallBadgeImage(
|
||||||
icon.size());
|
text,
|
||||||
p.drawEllipse(rect);
|
st::paidReactTopStarIcon,
|
||||||
icon.paintInCenter(p, rect);
|
check.bgActive->c,
|
||||||
|
st::premiumButtonFg->c,
|
||||||
|
&check);
|
||||||
|
} else {
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
const auto &icon = st::stickersPremiumLock;
|
||||||
|
const auto width = icon.width();
|
||||||
|
const auto height = icon.height();
|
||||||
|
const auto rect = QRect(
|
||||||
|
QPoint(x + size - width, y + size - height),
|
||||||
|
icon.size());
|
||||||
|
const auto added = QMargins(add, add, add, add);
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
cache.badge = QImage(
|
||||||
|
(rect + added).size() * ratio,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
cache.badge.setDevicePixelRatio(ratio);
|
||||||
|
cache.badge.fill(Qt::transparent);
|
||||||
|
const auto inner = QRect(add, add, rect.width(), rect.height());
|
||||||
|
auto q = QPainter(&cache.badge);
|
||||||
|
auto pen = check.border->p;
|
||||||
|
pen.setWidthF(check.width);
|
||||||
|
q.setPen(pen);
|
||||||
|
q.setBrush(st::premiumButtonBg2);
|
||||||
|
q.drawEllipse(inner);
|
||||||
|
icon.paintInCenter(q, inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto cached = cache.badge.size() / cache.badge.devicePixelRatio();
|
||||||
|
const auto left = x + size + add - cached.width();
|
||||||
|
const auto top = stars ? (y - add) : (y + size + add - cached.height());
|
||||||
|
p.drawImage(left, top, cache.badge);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,10 @@ namespace style {
|
||||||
struct PeerListItem;
|
struct PeerListItem;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
struct MessageMoneyRestriction;
|
||||||
|
} // namespace Api
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Thread;
|
class Thread;
|
||||||
class Forum;
|
class Forum;
|
||||||
|
@ -93,13 +97,28 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RecipientPremiumRequiredError {
|
struct RecipientMoneyRestrictionError {
|
||||||
TextWithEntities text;
|
TextWithEntities text;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError(
|
[[nodiscard]] RecipientMoneyRestrictionError WriteMoneyRestrictionError(
|
||||||
not_null<UserData*> user);
|
not_null<UserData*> user);
|
||||||
|
|
||||||
|
struct RestrictionBadgeCache {
|
||||||
|
int paletteVersion = 0;
|
||||||
|
int stars = 0;
|
||||||
|
QImage badge;
|
||||||
|
};
|
||||||
|
void PaintRestrictionBadge(
|
||||||
|
Painter &p,
|
||||||
|
not_null<const style::PeerListItem*> st,
|
||||||
|
int stars,
|
||||||
|
RestrictionBadgeCache &cache,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int outerWidth,
|
||||||
|
int size);
|
||||||
|
|
||||||
class RecipientRow : public PeerListRow {
|
class RecipientRow : public PeerListRow {
|
||||||
public:
|
public:
|
||||||
explicit RecipientRow(
|
explicit RecipientRow(
|
||||||
|
@ -112,30 +131,33 @@ public:
|
||||||
[[nodiscard]] static bool ShowLockedError(
|
[[nodiscard]] static bool ShowLockedError(
|
||||||
not_null<PeerListController*> controller,
|
not_null<PeerListController*> controller,
|
||||||
not_null<PeerListRow*> row,
|
not_null<PeerListRow*> row,
|
||||||
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error);
|
Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error);
|
||||||
|
|
||||||
[[nodiscard]] History *maybeHistory() const {
|
[[nodiscard]] History *maybeHistory() const {
|
||||||
return _maybeHistory;
|
return _maybeHistory;
|
||||||
}
|
}
|
||||||
[[nodiscard]] bool locked() const {
|
void paintUserpicOverlay(
|
||||||
return _lockedSt != nullptr;
|
Painter &p,
|
||||||
}
|
const style::PeerListItem &st,
|
||||||
void setLocked(const style::PeerListItem *lockedSt) {
|
int x,
|
||||||
_lockedSt = lockedSt;
|
int y,
|
||||||
}
|
int outerWidth) override;
|
||||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
|
||||||
bool forceRound) override;
|
|
||||||
|
|
||||||
void preloadUserpic() override;
|
void preloadUserpic() override;
|
||||||
|
|
||||||
|
[[nodiscard]] Api::MessageMoneyRestriction restriction() const;
|
||||||
|
void setRestriction(Api::MessageMoneyRestriction restriction);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Restriction;
|
||||||
|
|
||||||
History *_maybeHistory = nullptr;
|
History *_maybeHistory = nullptr;
|
||||||
const style::PeerListItem *_lockedSt = nullptr;
|
const style::PeerListItem *_maybeLockedSt = nullptr;
|
||||||
bool _resolvePremiumRequired = false;
|
std::shared_ptr<Restriction> _restriction;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void TrackPremiumRequiredChanges(
|
void TrackMessageMoneyRestrictionsChanges(
|
||||||
not_null<PeerListController*> controller,
|
not_null<PeerListController*> controller,
|
||||||
rpl::lifetime &lifetime);
|
rpl::lifetime &lifetime);
|
||||||
|
|
||||||
|
@ -261,8 +283,8 @@ struct ChooseRecipientArgs {
|
||||||
FnMut<void(not_null<Data::Thread*>)> callback;
|
FnMut<void(not_null<Data::Thread*>)> callback;
|
||||||
Fn<bool(not_null<Data::Thread*>)> filter;
|
Fn<bool(not_null<Data::Thread*>)> filter;
|
||||||
|
|
||||||
using PremiumRequiredError = RecipientPremiumRequiredError;
|
using MoneyRestrictionError = RecipientMoneyRestrictionError;
|
||||||
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
|
Fn<MoneyRestrictionError(not_null<UserData*>)> moneyRestrictionError;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ChooseRecipientBoxController
|
class ChooseRecipientBoxController
|
||||||
|
@ -290,8 +312,8 @@ private:
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
FnMut<void(not_null<Data::Thread*>)> _callback;
|
FnMut<void(not_null<Data::Thread*>)> _callback;
|
||||||
Fn<bool(not_null<Data::Thread*>)> _filter;
|
Fn<bool(not_null<Data::Thread*>)> _filter;
|
||||||
Fn<RecipientPremiumRequiredError(
|
Fn<RecipientMoneyRestrictionError(
|
||||||
not_null<UserData*>)> _premiumRequiredError;
|
not_null<UserData*>)> _moneyRestrictionError;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -371,11 +393,3 @@ private:
|
||||||
Fn<bool(not_null<Data::ForumTopic*>)> _filter;
|
Fn<bool(not_null<Data::ForumTopic*>)> _filter;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void PaintPremiumRequiredLock(
|
|
||||||
Painter &p,
|
|
||||||
not_null<const style::PeerListItem*> st,
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int outerWidth,
|
|
||||||
int size);
|
|
||||||
|
|
|
@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "api/api_chat_participants.h"
|
#include "api/api_chat_participants.h"
|
||||||
#include "api/api_invite_links.h"
|
#include "api/api_invite_links.h"
|
||||||
|
#include "api/api_premium.h"
|
||||||
#include "boxes/peers/edit_participant_box.h"
|
#include "boxes/peers/edit_participant_box.h"
|
||||||
#include "boxes/peers/edit_peer_type_box.h"
|
#include "boxes/peers/edit_peer_type_box.h"
|
||||||
#include "boxes/peers/replace_boost_box.h"
|
#include "boxes/peers/replace_boost_box.h"
|
||||||
#include "boxes/max_invite_box.h"
|
#include "boxes/max_invite_box.h"
|
||||||
|
#include "chat_helpers/message_field.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
|
@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_peer_values.h"
|
#include "data/data_peer_values.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "history/history_item_helpers.h"
|
||||||
#include "dialogs/dialogs_indexed_list.h"
|
#include "dialogs/dialogs_indexed_list.h"
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "ui/boxes/show_or_premium_box.h"
|
#include "ui/boxes/show_or_premium_box.h"
|
||||||
|
@ -52,16 +55,39 @@ constexpr auto kUserpicsLimit = 3;
|
||||||
|
|
||||||
class ForbiddenRow final : public PeerListRow {
|
class ForbiddenRow final : public PeerListRow {
|
||||||
public:
|
public:
|
||||||
ForbiddenRow(not_null<PeerData*> peer, bool locked);
|
ForbiddenRow(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
not_null<const style::PeerListItem*> lockSt,
|
||||||
|
bool locked);
|
||||||
|
|
||||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||||
bool forceRound) override;
|
bool forceRound) override;
|
||||||
|
|
||||||
|
Api::MessageMoneyRestriction restriction() const;
|
||||||
|
void setRestriction(Api::MessageMoneyRestriction restriction);
|
||||||
|
|
||||||
|
void preloadUserpic() override;
|
||||||
|
void paintUserpicOverlay(
|
||||||
|
Painter &p,
|
||||||
|
const style::PeerListItem &st,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int outerWidth) override;
|
||||||
|
|
||||||
|
bool refreshLock();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Restriction {
|
||||||
|
Api::MessageMoneyRestriction value;
|
||||||
|
RestrictionBadgeCache cache;
|
||||||
|
};
|
||||||
|
|
||||||
const bool _locked = false;
|
const bool _locked = false;
|
||||||
|
const not_null<const style::PeerListItem*> _lockSt;
|
||||||
QImage _disabledFrame;
|
QImage _disabledFrame;
|
||||||
InMemoryKey _userpicKey;
|
InMemoryKey _userpicKey;
|
||||||
int _paletteVersion = 0;
|
int _paletteVersion = 0;
|
||||||
|
std::shared_ptr<Restriction> _restriction;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -81,6 +107,9 @@ public:
|
||||||
[[nodiscard]] rpl::producer<int> selectedValue() const {
|
[[nodiscard]] rpl::producer<int> selectedValue() const {
|
||||||
return _selected.value();
|
return _selected.value();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] rpl::producer<int> starsToSend() const {
|
||||||
|
return _starsToSend.value();
|
||||||
|
}
|
||||||
|
|
||||||
void send(
|
void send(
|
||||||
std::vector<not_null<PeerData*>> list,
|
std::vector<not_null<PeerData*>> list,
|
||||||
|
@ -89,10 +118,16 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void appendRow(not_null<UserData*> user);
|
void appendRow(not_null<UserData*> user);
|
||||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
[[nodiscard]] std::unique_ptr<ForbiddenRow> createRow(
|
||||||
not_null<UserData*> user) const;
|
not_null<UserData*> user) const;
|
||||||
[[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
|
[[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
|
||||||
|
|
||||||
|
void send(
|
||||||
|
std::vector<not_null<PeerData*>> list,
|
||||||
|
Ui::ShowPtr show,
|
||||||
|
Fn<void()> close,
|
||||||
|
Api::SendOptions options);
|
||||||
|
|
||||||
void setSimpleCover();
|
void setSimpleCover();
|
||||||
void setComplexCover();
|
void setComplexCover();
|
||||||
|
|
||||||
|
@ -101,8 +136,11 @@ private:
|
||||||
const std::vector<not_null<UserData*>> &_users;
|
const std::vector<not_null<UserData*>> &_users;
|
||||||
const bool _can = false;
|
const bool _can = false;
|
||||||
rpl::variable<int> _selected;
|
rpl::variable<int> _selected;
|
||||||
|
rpl::variable<int> _starsToSend;
|
||||||
bool _sending = false;
|
bool _sending = false;
|
||||||
|
|
||||||
|
rpl::lifetime _paymentCheckLifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
|
base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
|
||||||
|
@ -256,11 +294,17 @@ Main::Session &InviteForbiddenController::session() const {
|
||||||
return _peer->session();
|
return _peer->session();
|
||||||
}
|
}
|
||||||
|
|
||||||
ForbiddenRow::ForbiddenRow(not_null<PeerData*> peer, bool locked)
|
ForbiddenRow::ForbiddenRow(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
not_null<const style::PeerListItem*> lockSt,
|
||||||
|
bool locked)
|
||||||
: PeerListRow(peer)
|
: PeerListRow(peer)
|
||||||
, _locked(locked) {
|
, _locked(locked)
|
||||||
|
, _lockSt(lockSt) {
|
||||||
if (_locked) {
|
if (_locked) {
|
||||||
setCustomStatus(tr::lng_invite_status_disabled(tr::now));
|
setCustomStatus(tr::lng_invite_status_disabled(tr::now));
|
||||||
|
} else {
|
||||||
|
setRestriction(Api::ResolveMessageMoneyRestrictions(peer, nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,6 +383,76 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Api::MessageMoneyRestriction ForbiddenRow::restriction() const {
|
||||||
|
return _restriction
|
||||||
|
? _restriction->value
|
||||||
|
: Api::MessageMoneyRestriction();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForbiddenRow::setRestriction(Api::MessageMoneyRestriction restriction) {
|
||||||
|
if (!restriction || !restriction.starsPerMessage) {
|
||||||
|
_restriction = nullptr;
|
||||||
|
return;
|
||||||
|
} else if (!_restriction) {
|
||||||
|
_restriction = std::make_unique<Restriction>();
|
||||||
|
}
|
||||||
|
_restriction->value = restriction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForbiddenRow::paintUserpicOverlay(
|
||||||
|
Painter &p,
|
||||||
|
const style::PeerListItem &st,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int outerWidth) {
|
||||||
|
if (const auto &r = _restriction) {
|
||||||
|
PaintRestrictionBadge(
|
||||||
|
p,
|
||||||
|
_lockSt,
|
||||||
|
r->value.starsPerMessage,
|
||||||
|
r->cache,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
outerWidth,
|
||||||
|
st.photoSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ForbiddenRow::refreshLock() {
|
||||||
|
if (_locked) {
|
||||||
|
return false;
|
||||||
|
} else if (const auto user = peer()->asUser()) {
|
||||||
|
using Restriction = Api::MessageMoneyRestriction;
|
||||||
|
auto r = Api::ResolveMessageMoneyRestrictions(user, nullptr);
|
||||||
|
if (!r || !r.starsPerMessage) {
|
||||||
|
r = Restriction();
|
||||||
|
}
|
||||||
|
if ((_restriction ? _restriction->value : Restriction()) != r) {
|
||||||
|
setRestriction(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForbiddenRow::preloadUserpic() {
|
||||||
|
PeerListRow::preloadUserpic();
|
||||||
|
|
||||||
|
const auto peer = this->peer();
|
||||||
|
const auto known = Api::ResolveMessageMoneyRestrictions(
|
||||||
|
peer,
|
||||||
|
nullptr).known;
|
||||||
|
if (known) {
|
||||||
|
return;
|
||||||
|
} else if (const auto user = peer->asUser()) {
|
||||||
|
const auto api = &user->session().api();
|
||||||
|
api->premium().resolveMessageMoneyRestrictions(user);
|
||||||
|
} else if (const auto group = peer->asChannel()) {
|
||||||
|
group->updateFull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InviteForbiddenController::setSimpleCover() {
|
void InviteForbiddenController::setSimpleCover() {
|
||||||
delegate()->peerListSetTitle(
|
delegate()->peerListSetTitle(
|
||||||
_can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
|
_can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
|
||||||
|
@ -435,6 +549,30 @@ void InviteForbiddenController::setComplexCover() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InviteForbiddenController::prepare() {
|
void InviteForbiddenController::prepare() {
|
||||||
|
session().api().premium().someMessageMoneyRestrictionsResolved(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
auto stars = 0;
|
||||||
|
const auto process = [&](not_null<PeerListRow*> raw) {
|
||||||
|
const auto row = static_cast<ForbiddenRow*>(raw.get());
|
||||||
|
if (row->refreshLock()) {
|
||||||
|
delegate()->peerListUpdateRow(raw);
|
||||||
|
}
|
||||||
|
if (const auto r = row->restriction()) {
|
||||||
|
stars += r.starsPerMessage;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto count = delegate()->peerListFullRowsCount();
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
process(delegate()->peerListRowAt(i));
|
||||||
|
}
|
||||||
|
_starsToSend = stars;
|
||||||
|
|
||||||
|
count = delegate()->peerListSearchRowsCount();
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
process(delegate()->peerListSearchRowAt(i));
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
if (session().premium()
|
if (session().premium()
|
||||||
|| (_forbidden.premiumAllowsInvite.empty()
|
|| (_forbidden.premiumAllowsInvite.empty()
|
||||||
&& _forbidden.premiumAllowsWrite.empty())) {
|
&& _forbidden.premiumAllowsWrite.empty())) {
|
||||||
|
@ -464,6 +602,11 @@ void InviteForbiddenController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
const auto checked = row->checked();
|
const auto checked = row->checked();
|
||||||
delegate()->peerListSetRowChecked(row, !checked);
|
delegate()->peerListSetRowChecked(row, !checked);
|
||||||
_selected = _selected.current() + (checked ? -1 : 1);
|
_selected = _selected.current() + (checked ? -1 : 1);
|
||||||
|
const auto r = static_cast<ForbiddenRow*>(row.get())->restriction();
|
||||||
|
if (r.starsPerMessage) {
|
||||||
|
_starsToSend = _starsToSend.current()
|
||||||
|
+ (checked ? -r.starsPerMessage : r.starsPerMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InviteForbiddenController::appendRow(not_null<UserData*> user) {
|
void InviteForbiddenController::appendRow(not_null<UserData*> user) {
|
||||||
|
@ -473,6 +616,9 @@ void InviteForbiddenController::appendRow(not_null<UserData*> user) {
|
||||||
delegate()->peerListAppendRow(std::move(row));
|
delegate()->peerListAppendRow(std::move(row));
|
||||||
if (canInvite(user)) {
|
if (canInvite(user)) {
|
||||||
delegate()->peerListSetRowChecked(raw, true);
|
delegate()->peerListSetRowChecked(raw, true);
|
||||||
|
if (const auto r = raw->restriction()) {
|
||||||
|
_starsToSend = _starsToSend.current() + r.starsPerMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,7 +627,64 @@ void InviteForbiddenController::send(
|
||||||
std::vector<not_null<PeerData*>> list,
|
std::vector<not_null<PeerData*>> list,
|
||||||
Ui::ShowPtr show,
|
Ui::ShowPtr show,
|
||||||
Fn<void()> close) {
|
Fn<void()> close) {
|
||||||
if (_sending || list.empty()) {
|
send(list, show, close, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InviteForbiddenController::send(
|
||||||
|
std::vector<not_null<PeerData*>> list,
|
||||||
|
Ui::ShowPtr show,
|
||||||
|
Fn<void()> close,
|
||||||
|
Api::SendOptions options) {
|
||||||
|
if (list.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_paymentCheckLifetime.destroy();
|
||||||
|
|
||||||
|
const auto withPaymentApproved = [=](int approved) {
|
||||||
|
auto copy = options;
|
||||||
|
copy.starsApproved = approved;
|
||||||
|
send(list, show, close, copy);
|
||||||
|
};
|
||||||
|
const auto messagesCount = 1;
|
||||||
|
const auto alreadyApproved = options.starsApproved;
|
||||||
|
auto paid = std::vector<not_null<PeerData*>>();
|
||||||
|
auto waiting = base::flat_set<not_null<PeerData*>>();
|
||||||
|
auto totalStars = 0;
|
||||||
|
for (const auto &peer : list) {
|
||||||
|
const auto details = ComputePaymentDetails(peer, messagesCount);
|
||||||
|
if (!details) {
|
||||||
|
waiting.emplace(peer);
|
||||||
|
} else if (details->stars > 0) {
|
||||||
|
totalStars += details->stars;
|
||||||
|
paid.push_back(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!waiting.empty()) {
|
||||||
|
session().changes().peerUpdates(
|
||||||
|
Data::PeerUpdate::Flag::FullInfo
|
||||||
|
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||||
|
if (waiting.contains(update.peer)) {
|
||||||
|
withPaymentApproved(alreadyApproved);
|
||||||
|
}
|
||||||
|
}, _paymentCheckLifetime);
|
||||||
|
|
||||||
|
if (!session().credits().loaded()) {
|
||||||
|
session().credits().loadedValue(
|
||||||
|
) | rpl::filter(
|
||||||
|
rpl::mappers::_1
|
||||||
|
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||||
|
withPaymentApproved(alreadyApproved);
|
||||||
|
}, _paymentCheckLifetime);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (totalStars > alreadyApproved) {
|
||||||
|
const auto sessionShow = Main::MakeSessionShow(show, &session());
|
||||||
|
ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
|
||||||
|
.messages = messagesCount,
|
||||||
|
.stars = totalStars,
|
||||||
|
}, [=] { withPaymentApproved(totalStars); });
|
||||||
|
return;
|
||||||
|
} else if (_sending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_sending = true;
|
_sending = true;
|
||||||
|
@ -492,12 +695,18 @@ void InviteForbiddenController::send(
|
||||||
if (link.isEmpty()) {
|
if (link.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
auto full = options;
|
||||||
auto &api = _peer->session().api();
|
auto &api = _peer->session().api();
|
||||||
auto options = Api::SendOptions();
|
|
||||||
for (const auto &to : list) {
|
for (const auto &to : list) {
|
||||||
|
auto copy = full;
|
||||||
|
copy.starsApproved = std::min(
|
||||||
|
to->starsPerMessageChecked(),
|
||||||
|
full.starsApproved);
|
||||||
|
full.starsApproved -= copy.starsApproved;
|
||||||
|
|
||||||
const auto history = to->owner().history(to);
|
const auto history = to->owner().history(to);
|
||||||
auto message = Api::MessageToSend(
|
auto message = Api::MessageToSend(
|
||||||
Api::SendAction(history, options));
|
Api::SendAction(history, copy));
|
||||||
message.textWithTags = { link };
|
message.textWithTags = { link };
|
||||||
message.action.clearDraft = false;
|
message.action.clearDraft = false;
|
||||||
api.sendMessage(std::move(message));
|
api.sendMessage(std::move(message));
|
||||||
|
@ -542,10 +751,11 @@ void InviteForbiddenController::send(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PeerListRow> InviteForbiddenController::createRow(
|
std::unique_ptr<ForbiddenRow> InviteForbiddenController::createRow(
|
||||||
not_null<UserData*> user) const {
|
not_null<UserData*> user) const {
|
||||||
const auto locked = _can && !canInvite(user);
|
const auto locked = _can && !canInvite(user);
|
||||||
return std::make_unique<ForbiddenRow>(user, locked);
|
const auto lockSt = &computeListSt().item;
|
||||||
|
return std::make_unique<ForbiddenRow>(user, lockSt, locked);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -584,8 +794,8 @@ void AddParticipantsBoxController::subscribeToMigration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
const auto premiumRequiredError = WritePremiumRequiredError;
|
const auto moneyRestrictionError = WriteMoneyRestrictionError;
|
||||||
if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) {
|
if (RecipientRow::ShowLockedError(this, row, moneyRestrictionError)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto &serverConfig = session().serverConfig();
|
const auto &serverConfig = session().serverConfig();
|
||||||
|
@ -614,7 +824,7 @@ void AddParticipantsBoxController::itemDeselectedHook(
|
||||||
void AddParticipantsBoxController::prepareViewHook() {
|
void AddParticipantsBoxController::prepareViewHook() {
|
||||||
updateTitle();
|
updateTitle();
|
||||||
|
|
||||||
TrackPremiumRequiredChanges(this, lifetime());
|
TrackMessageMoneyRestrictionsChanges(this, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
int AddParticipantsBoxController::alreadyInCount() const {
|
int AddParticipantsBoxController::alreadyInCount() const {
|
||||||
|
@ -929,12 +1139,15 @@ bool ChatInviteForbidden(
|
||||||
) | rpl::start_with_next([=](bool has) {
|
) | rpl::start_with_next([=](bool has) {
|
||||||
box->clearButtons();
|
box->clearButtons();
|
||||||
if (has) {
|
if (has) {
|
||||||
box->addButton(tr::lng_via_link_send(), [=] {
|
const auto send = box->addButton(tr::lng_via_link_send(), [=] {
|
||||||
weak->send(
|
weak->send(
|
||||||
box->collectSelectedRows(),
|
box->collectSelectedRows(),
|
||||||
box->uiShow(),
|
box->uiShow(),
|
||||||
crl::guard(box, [=] { box->closeBox(); }));
|
crl::guard(box, [=] { box->closeBox(); }));
|
||||||
});
|
});
|
||||||
|
send->setText(PaidSendButtonText(
|
||||||
|
weak->starsToSend(),
|
||||||
|
tr::lng_via_link_send()));
|
||||||
}
|
}
|
||||||
box->addButton(tr::lng_create_group_skip(), [=] {
|
box->addButton(tr::lng_create_group_skip(), [=] {
|
||||||
box->closeBox();
|
box->closeBox();
|
||||||
|
|
|
@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/background_box.h"
|
#include "boxes/background_box.h"
|
||||||
#include "boxes/stickers_box.h"
|
#include "boxes/stickers_box.h"
|
||||||
#include "chat_helpers/compose/compose_show.h"
|
#include "chat_helpers/compose/compose_show.h"
|
||||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
#include "core/ui_integration.h" // TextContext
|
||||||
#include "data/stickers/data_custom_emoji.h"
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
@ -165,7 +165,7 @@ private:
|
||||||
|
|
||||||
const uint32 _level;
|
const uint32 _level;
|
||||||
const TextWithEntities _icon;
|
const TextWithEntities _icon;
|
||||||
const Core::MarkedTextContext _context;
|
const Ui::Text::MarkedContext _context;
|
||||||
Ui::Text::String _text;
|
Ui::Text::String _text;
|
||||||
bool _minimal = false;
|
bool _minimal = false;
|
||||||
|
|
||||||
|
@ -466,7 +466,10 @@ LevelBadge::LevelBadge(
|
||||||
st::settingsLevelBadgeLock,
|
st::settingsLevelBadgeLock,
|
||||||
QMargins(0, st::settingsLevelBadgeLockSkip, 0, 0),
|
QMargins(0, st::settingsLevelBadgeLockSkip, 0, 0),
|
||||||
false)))
|
false)))
|
||||||
, _context({ .session = session }) {
|
, _context(Core::TextContext({
|
||||||
|
.session = session,
|
||||||
|
.repaint = [this] { update(); },
|
||||||
|
})) {
|
||||||
updateText();
|
updateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,6 +219,33 @@ void SaveSlowmodeSeconds(
|
||||||
api->registerModifyRequest(key, requestId);
|
api->registerModifyRequest(key, requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SaveStarsPerMessage(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
int starsPerMessage,
|
||||||
|
Fn<void()> done) {
|
||||||
|
const auto api = &channel->session().api();
|
||||||
|
const auto key = Api::RequestKey("stars_per_message", channel->id);
|
||||||
|
|
||||||
|
const auto requestId = api->request(MTPchannels_UpdatePaidMessagesPrice(
|
||||||
|
channel->inputChannel,
|
||||||
|
MTP_long(starsPerMessage)
|
||||||
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
api->clearModifyRequest(key);
|
||||||
|
api->applyUpdates(result);
|
||||||
|
channel->setStarsPerMessage(starsPerMessage);
|
||||||
|
done();
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
api->clearModifyRequest(key);
|
||||||
|
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
channel->setStarsPerMessage(starsPerMessage);
|
||||||
|
done();
|
||||||
|
}).send();
|
||||||
|
|
||||||
|
api->registerModifyRequest(key, requestId);
|
||||||
|
}
|
||||||
|
|
||||||
void SaveBoostsUnrestrict(
|
void SaveBoostsUnrestrict(
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
int boostsUnrestrict,
|
int boostsUnrestrict,
|
||||||
|
@ -271,6 +298,7 @@ void ShowEditPermissions(
|
||||||
channel,
|
channel,
|
||||||
result.boostsUnrestrict,
|
result.boostsUnrestrict,
|
||||||
close);
|
close);
|
||||||
|
SaveStarsPerMessage(channel, result.starsPerMessage, close);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
auto done = [=](EditPeerPermissionsBoxResult result) {
|
auto done = [=](EditPeerPermissionsBoxResult result) {
|
||||||
|
@ -282,7 +310,9 @@ void ShowEditPermissions(
|
||||||
const auto saveFor = peer->migrateToOrMe();
|
const auto saveFor = peer->migrateToOrMe();
|
||||||
const auto chat = saveFor->asChat();
|
const auto chat = saveFor->asChat();
|
||||||
if (!chat
|
if (!chat
|
||||||
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
|
|| (!result.slowmodeSeconds
|
||||||
|
&& !result.boostsUnrestrict
|
||||||
|
&& !result.starsPerMessage)) {
|
||||||
save(saveFor, result);
|
save(saveFor, result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2689,3 +2719,9 @@ bool EditPeerInfoBox::Available(not_null<PeerData*> peer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShowEditChatPermissions(
|
||||||
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
|
not_null<PeerData*> peer) {
|
||||||
|
ShowEditPermissions(navigation, peer);
|
||||||
|
}
|
||||||
|
|
|
@ -56,3 +56,7 @@ private:
|
||||||
not_null<PeerData*> _peer;
|
not_null<PeerData*> _peer;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void ShowEditChatPermissions(
|
||||||
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
|
not_null<PeerData*> peer);
|
||||||
|
|
|
@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/peer_list_controllers.h"
|
#include "boxes/peer_list_controllers.h"
|
||||||
#include "boxes/share_box.h"
|
#include "boxes/share_box.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
#include "core/ui_integration.h" // TextContext
|
||||||
#include "data/components/credits.h"
|
#include "data/components/credits.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
@ -740,10 +740,10 @@ void Controller::setupAboveJoinedWidget() {
|
||||||
{ QString::number(current.subscription.credits) },
|
{ QString::number(current.subscription.credits) },
|
||||||
Ui::Text::WithEntities),
|
Ui::Text::WithEntities),
|
||||||
kMarkupTextOptions,
|
kMarkupTextOptions,
|
||||||
Core::MarkedTextContext{
|
Core::TextContext({
|
||||||
.session = &session(),
|
.session = &session(),
|
||||||
.customEmojiRepaint = [=] { widget->update(); },
|
.repaint = [=] { widget->update(); },
|
||||||
});
|
}));
|
||||||
auto &lifetime = widget->lifetime();
|
auto &lifetime = widget->lifetime();
|
||||||
const auto rateValue = lifetime.make_state<rpl::variable<float64>>(
|
const auto rateValue = lifetime.make_state<rpl::variable<float64>>(
|
||||||
session().credits().rateValue(_peer));
|
session().credits().rateValue(_peer));
|
||||||
|
@ -994,10 +994,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||||
lt_cost,
|
lt_cost,
|
||||||
{ QString::number(data.subscription.credits) },
|
{ QString::number(data.subscription.credits) },
|
||||||
Ui::Text::WithEntities),
|
Ui::Text::WithEntities),
|
||||||
Core::MarkedTextContext{
|
Core::TextContext({ .session = session }));
|
||||||
.session = session,
|
|
||||||
.customEmojiRepaint = [=] { subtitle1->update(); },
|
|
||||||
});
|
|
||||||
const auto subtitle2 = box->addRow(
|
const auto subtitle2 = box->addRow(
|
||||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||||
box,
|
box,
|
||||||
|
@ -1484,8 +1481,12 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
? tr::lng_group_invite_copied(tr::now)
|
? tr::lng_group_invite_copied(tr::now)
|
||||||
: copied);
|
: copied);
|
||||||
};
|
};
|
||||||
|
auto countMessagesCallback = [=](const TextWithTags &comment) {
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
auto submitCallback = [=](
|
auto submitCallback = [=](
|
||||||
std::vector<not_null<Data::Thread*>> &&result,
|
std::vector<not_null<Data::Thread*>> &&result,
|
||||||
|
Fn<bool()> checkPaid,
|
||||||
TextWithTags &&comment,
|
TextWithTags &&comment,
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
Data::ForwardOptions) {
|
Data::ForwardOptions) {
|
||||||
|
@ -1503,6 +1504,8 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
result.size() > 1));
|
result.size() > 1));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
} else if (!checkPaid()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
*sending = true;
|
*sending = true;
|
||||||
|
@ -1530,7 +1533,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
};
|
};
|
||||||
auto filterCallback = [](not_null<Data::Thread*> thread) {
|
auto filterCallback = [](not_null<Data::Thread*> thread) {
|
||||||
if (const auto user = thread->peer()->asUser()) {
|
if (const auto user = thread->peer()->asUser()) {
|
||||||
if (user->canSendIgnoreRequirePremium()) {
|
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1539,9 +1542,10 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
auto object = Box<ShareBox>(ShareBox::Descriptor{
|
auto object = Box<ShareBox>(ShareBox::Descriptor{
|
||||||
.session = session,
|
.session = session,
|
||||||
.copyCallback = std::move(copyCallback),
|
.copyCallback = std::move(copyCallback),
|
||||||
|
.countMessagesCallback = std::move(countMessagesCallback),
|
||||||
.submitCallback = std::move(submitCallback),
|
.submitCallback = std::move(submitCallback),
|
||||||
.filterCallback = std::move(filterCallback),
|
.filterCallback = std::move(filterCallback),
|
||||||
.premiumRequiredError = SharePremiumRequiredError(),
|
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||||
});
|
});
|
||||||
*box = Ui::MakeWeak(object.data());
|
*box = Ui::MakeWeak(object.data());
|
||||||
return object;
|
return object;
|
||||||
|
|
|
@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "info/profile/info_profile_values.h"
|
#include "info/profile/info_profile_values.h"
|
||||||
#include "boxes/peers/edit_participants_box.h"
|
#include "boxes/peers/edit_participants_box.h"
|
||||||
#include "boxes/peers/edit_peer_info_box.h"
|
#include "boxes/peers/edit_peer_info_box.h"
|
||||||
|
#include "boxes/edit_privacy_box.h"
|
||||||
#include "settings/settings_power_saving.h"
|
#include "settings/settings_power_saving.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
|
@ -891,11 +892,10 @@ void AddBoostsUnrestrictLabels(
|
||||||
manager->registerInternalEmoji(
|
manager->registerInternalEmoji(
|
||||||
st::boostsMessageIcon,
|
st::boostsMessageIcon,
|
||||||
st::boostsMessageIconPadding));
|
st::boostsMessageIconPadding));
|
||||||
const auto context = Core::MarkedTextContext{
|
const auto context = Core::TextContext({
|
||||||
.session = session,
|
.session = session,
|
||||||
.customEmojiRepaint = [] {},
|
|
||||||
.customEmojiLoopLimit = 1,
|
.customEmojiLoopLimit = 1,
|
||||||
};
|
});
|
||||||
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
|
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
|
||||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
labels,
|
labels,
|
||||||
|
@ -942,9 +942,7 @@ rpl::producer<int> AddBoostsUnrestrictSlider(
|
||||||
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
|
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
|
||||||
channel ? channel->boostsUnrestrict() : 0);
|
channel ? channel->boostsUnrestrict() : 0);
|
||||||
|
|
||||||
container->add(
|
Ui::AddSkip(container);
|
||||||
object_ptr<Ui::BoxContentDivider>(container),
|
|
||||||
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
|
|
||||||
|
|
||||||
auto enabled = boostsUnrestrict->value(
|
auto enabled = boostsUnrestrict->value(
|
||||||
) | rpl::map(_1 > 0);
|
) | rpl::map(_1 > 0);
|
||||||
|
@ -1008,19 +1006,20 @@ rpl::producer<int> AddBoostsUnrestrictWrapped(
|
||||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
container,
|
container,
|
||||||
object_ptr<Ui::VerticalLayout>(container)));
|
object_ptr<Ui::VerticalLayout>(container)));
|
||||||
wrap->toggleOn(rpl::duplicate(shown), anim::type::normal);
|
wrap->toggleOn(std::move(shown), anim::type::normal);
|
||||||
wrap->finishAnimating();
|
wrap->finishAnimating();
|
||||||
|
|
||||||
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer);
|
const auto inner = wrap->entity();
|
||||||
const auto divider = container->add(
|
|
||||||
|
auto result = AddBoostsUnrestrictSlider(inner, peer);
|
||||||
|
|
||||||
|
const auto skip = st::defaultVerticalListSkip;
|
||||||
|
const auto divider = inner->add(
|
||||||
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
|
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
|
||||||
container,
|
inner,
|
||||||
object_ptr<Ui::BoxContentDivider>(container),
|
object_ptr<Ui::BoxContentDivider>(inner),
|
||||||
QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip }));
|
QMargins{ 0, skip, 0, skip }));
|
||||||
divider->toggleOn(rpl::combine(
|
divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1));
|
||||||
std::move(shown),
|
|
||||||
rpl::duplicate(result),
|
|
||||||
!rpl::mappers::_1 || !rpl::mappers::_2));
|
|
||||||
divider->finishAnimating();
|
divider->finishAnimating();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -1159,7 +1158,43 @@ void ShowEditPeerPermissionsBox(
|
||||||
rpl::variable<int> slowmodeSeconds;
|
rpl::variable<int> slowmodeSeconds;
|
||||||
rpl::variable<int> boostsUnrestrict;
|
rpl::variable<int> boostsUnrestrict;
|
||||||
rpl::variable<bool> hasSendRestrictions;
|
rpl::variable<bool> hasSendRestrictions;
|
||||||
|
rpl::variable<int> starsPerMessage;
|
||||||
};
|
};
|
||||||
|
const auto state = inner->lifetime().make_state<State>();
|
||||||
|
const auto channel = peer->asChannel();
|
||||||
|
const auto available = channel && channel->paidMessagesAvailable();
|
||||||
|
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
Ui::AddDivider(inner);
|
||||||
|
auto charging = (Ui::SettingsButton*)nullptr;
|
||||||
|
if (available) {
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
const auto starsPerMessage = peer->isChannel()
|
||||||
|
? peer->asChannel()->starsPerMessage()
|
||||||
|
: 0;
|
||||||
|
charging = inner->add(object_ptr<Ui::SettingsButton>(
|
||||||
|
inner,
|
||||||
|
tr::lng_rights_charge_stars(),
|
||||||
|
st::settingsButtonNoIcon));
|
||||||
|
charging->toggleOn(rpl::single(starsPerMessage > 0));
|
||||||
|
Ui::AddSkip(inner);
|
||||||
|
Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about());
|
||||||
|
|
||||||
|
const auto chargeWrap = inner->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
inner,
|
||||||
|
object_ptr<Ui::VerticalLayout>(inner)));
|
||||||
|
chargeWrap->toggleOn(charging->toggledValue());
|
||||||
|
chargeWrap->finishAnimating();
|
||||||
|
const auto chargeInner = chargeWrap->entity();
|
||||||
|
|
||||||
|
Ui::AddSkip(chargeInner);
|
||||||
|
state->starsPerMessage = SetupChargeSlider(
|
||||||
|
chargeInner,
|
||||||
|
peer,
|
||||||
|
starsPerMessage);
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr auto kSendRestrictions = Flag::EmbedLinks
|
static constexpr auto kSendRestrictions = Flag::EmbedLinks
|
||||||
| Flag::SendGames
|
| Flag::SendGames
|
||||||
| Flag::SendGifs
|
| Flag::SendGifs
|
||||||
|
@ -1173,7 +1208,6 @@ void ShowEditPeerPermissionsBox(
|
||||||
| Flag::SendVoiceMessages
|
| Flag::SendVoiceMessages
|
||||||
| Flag::SendFiles
|
| Flag::SendFiles
|
||||||
| Flag::SendOther;
|
| Flag::SendOther;
|
||||||
const auto state = inner->lifetime().make_state<State>();
|
|
||||||
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|
||||||
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
|
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
|
||||||
state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
|
state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
|
||||||
|
@ -1214,10 +1248,14 @@ void ShowEditPeerPermissionsBox(
|
||||||
const auto boostsUnrestrict = hasRestrictions
|
const auto boostsUnrestrict = hasRestrictions
|
||||||
? state->boostsUnrestrict.current()
|
? state->boostsUnrestrict.current()
|
||||||
: 0;
|
: 0;
|
||||||
|
const auto starsPerMessage = (charging && charging->toggled())
|
||||||
|
? state->starsPerMessage.current()
|
||||||
|
: 0;
|
||||||
done({
|
done({
|
||||||
restrictions,
|
restrictions,
|
||||||
slowmodeSeconds,
|
slowmodeSeconds,
|
||||||
boostsUnrestrict,
|
boostsUnrestrict,
|
||||||
|
starsPerMessage,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
|
|
|
@ -39,6 +39,7 @@ struct EditPeerPermissionsBoxResult final {
|
||||||
ChatRestrictions rights;
|
ChatRestrictions rights;
|
||||||
int slowmodeSeconds = 0;
|
int slowmodeSeconds = 0;
|
||||||
int boostsUnrestrict = 0;
|
int boostsUnrestrict = 0;
|
||||||
|
int starsPerMessage = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ShowEditPeerPermissionsBox(
|
void ShowEditPeerPermissionsBox(
|
||||||
|
|
|
@ -363,12 +363,17 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||||
const auto customEmojiPaused = [controller = args.controller] {
|
const auto customEmojiPaused = [controller = args.controller] {
|
||||||
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
|
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
|
||||||
};
|
};
|
||||||
auto factory = [=](QStringView data, Fn<void()> update)
|
auto simpleContext = Core::TextContext({
|
||||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
.session = session,
|
||||||
|
.repaint = [=] { raw->update(); },
|
||||||
|
});
|
||||||
|
auto context = simpleContext;
|
||||||
|
context.customEmojiFactory = [=](
|
||||||
|
QStringView data,
|
||||||
|
const Ui::Text::MarkedContext &context
|
||||||
|
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||||
const auto id = Data::ParseCustomEmojiData(data);
|
const auto id = Data::ParseCustomEmojiData(data);
|
||||||
auto result = owner->customEmojiManager().create(
|
auto result = Ui::Text::MakeCustomEmoji(data, simpleContext);
|
||||||
data,
|
|
||||||
std::move(update));
|
|
||||||
if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) {
|
if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) {
|
||||||
return std::make_unique<MaybeDisabledEmoji>(
|
return std::make_unique<MaybeDisabledEmoji>(
|
||||||
std::move(result),
|
std::move(result),
|
||||||
|
@ -377,12 +382,10 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||||
using namespace Ui::Text;
|
using namespace Ui::Text;
|
||||||
return std::make_unique<FirstFrameEmoji>(std::move(result));
|
return std::make_unique<FirstFrameEmoji>(std::move(result));
|
||||||
};
|
};
|
||||||
raw->setCustomTextContext([=](Fn<void()> repaint) {
|
raw->setCustomTextContext(
|
||||||
return std::any(Core::MarkedTextContext{
|
std::move(context),
|
||||||
.session = session,
|
customEmojiPaused,
|
||||||
.customEmojiRepaint = std::move(repaint),
|
customEmojiPaused);
|
||||||
});
|
|
||||||
}, customEmojiPaused, customEmojiPaused, std::move(factory));
|
|
||||||
|
|
||||||
const auto callback = args.callback;
|
const auto callback = args.callback;
|
||||||
const auto isCustom = [=](DocumentId id) {
|
const auto isCustom = [=](DocumentId id) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "api/api_credits.h"
|
#include "api/api_credits.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
#include "core/ui_integration.h" // TextContext
|
||||||
#include "data/components/credits.h"
|
#include "data/components/credits.h"
|
||||||
#include "data/data_credits.h"
|
#include "data/data_credits.h"
|
||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
|
@ -511,32 +511,28 @@ TextWithEntities CreditsEmoji(not_null<Main::Session*> session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
|
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
|
||||||
return Ui::Text::SingleCustomEmoji(
|
return Ui::Text::IconEmoji(
|
||||||
session->data().customEmojiManager().registerInternalEmoji(
|
&st::starIconEmoji,
|
||||||
st::starIconSmall,
|
|
||||||
st::starIconSmallPadding,
|
|
||||||
true),
|
|
||||||
QString(QChar(0x2B50)));
|
QString(QChar(0x2B50)));
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||||
not_null<RpWidget*> button,
|
not_null<RpWidget*> button,
|
||||||
rpl::producer<TextWithEntities> text,
|
rpl::producer<TextWithEntities> text,
|
||||||
Fn<std::any(Fn<void()> update)> context,
|
Text::MarkedContext context,
|
||||||
const style::FlatLabel &st,
|
const style::FlatLabel &st,
|
||||||
const style::color *textFg) {
|
const style::color *textFg) {
|
||||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
button,
|
button,
|
||||||
rpl::single(QString()),
|
rpl::single(QString()),
|
||||||
st);
|
st);
|
||||||
|
context.repaint = [=] { buttonLabel->update(); };
|
||||||
rpl::duplicate(
|
rpl::duplicate(
|
||||||
text
|
text
|
||||||
) | rpl::filter([=](const TextWithEntities &text) {
|
) | rpl::filter([=](const TextWithEntities &text) {
|
||||||
return !text.text.isEmpty();
|
return !text.text.isEmpty();
|
||||||
}) | rpl::start_with_next([=](const TextWithEntities &text) {
|
}) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||||
buttonLabel->setMarkedText(
|
buttonLabel->setMarkedText(text, context);
|
||||||
text,
|
|
||||||
context([=] { buttonLabel->update(); }));
|
|
||||||
}, buttonLabel->lifetime());
|
}, buttonLabel->lifetime());
|
||||||
if (textFg) {
|
if (textFg) {
|
||||||
buttonLabel->setTextColorOverride((*textFg)->c);
|
buttonLabel->setTextColorOverride((*textFg)->c);
|
||||||
|
@ -565,15 +561,12 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const style::FlatLabel &st,
|
const style::FlatLabel &st,
|
||||||
const style::color *textFg) {
|
const style::color *textFg) {
|
||||||
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
|
return SetButtonMarkedLabel(button, text, Core::TextContext({
|
||||||
return Core::MarkedTextContext{
|
.session = session,
|
||||||
.session = session,
|
}), st, textFg);
|
||||||
.customEmojiRepaint = update,
|
|
||||||
};
|
|
||||||
}, st, textFg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendStarGift(
|
void SendStarsForm(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
std::shared_ptr<Payments::CreditsFormData> data,
|
std::shared_ptr<Payments::CreditsFormData> data,
|
||||||
Fn<void(std::optional<QString>)> done) {
|
Fn<void(std::optional<QString>)> done) {
|
||||||
|
|
|
@ -41,7 +41,7 @@ void SendCreditsBox(
|
||||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||||
not_null<RpWidget*> button,
|
not_null<RpWidget*> button,
|
||||||
rpl::producer<TextWithEntities> text,
|
rpl::producer<TextWithEntities> text,
|
||||||
Fn<std::any(Fn<void()> update)> context,
|
Text::MarkedContext context,
|
||||||
const style::FlatLabel &st,
|
const style::FlatLabel &st,
|
||||||
const style::color *textFg = nullptr);
|
const style::color *textFg = nullptr);
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||||
const style::FlatLabel &st,
|
const style::FlatLabel &st,
|
||||||
const style::color *textFg = nullptr);
|
const style::color *textFg = nullptr);
|
||||||
|
|
||||||
void SendStarGift(
|
void SendStarsForm(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
std::shared_ptr<Payments::CreditsFormData> data,
|
std::shared_ptr<Payments::CreditsFormData> data,
|
||||||
Fn<void(std::optional<QString>)> done);
|
Fn<void(std::optional<QString>)> done);
|
||||||
|
|
|
@ -59,9 +59,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/core_settings.h"
|
#include "core/core_settings.h"
|
||||||
#include "styles/style_layers.h"
|
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
#include <QtCore/QMimeData>
|
#include <QtCore/QMimeData>
|
||||||
|
|
||||||
|
@ -722,6 +722,18 @@ void SendFilesBox::openDialogToAddFileToAlbum() {
|
||||||
crl::guard(this, callback));
|
crl::guard(this, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SendFilesBox::refreshMessagesCount() {
|
||||||
|
const auto way = _sendWay.current();
|
||||||
|
const auto withCaption = _list.canAddCaption(
|
||||||
|
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||||
|
way.sendImagesAsPhotos());
|
||||||
|
const auto withComment = !withCaption
|
||||||
|
&& _caption
|
||||||
|
&& !_caption->isHidden()
|
||||||
|
&& !_caption->getTextWithTags().text.isEmpty();
|
||||||
|
_messagesCount = _list.files.size() + (withComment ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
void SendFilesBox::refreshButtons() {
|
void SendFilesBox::refreshButtons() {
|
||||||
clearButtons();
|
clearButtons();
|
||||||
|
|
||||||
|
@ -730,6 +742,15 @@ void SendFilesBox::refreshButtons() {
|
||||||
? tr::lng_send_button()
|
? tr::lng_send_button()
|
||||||
: tr::lng_create_group_next()),
|
: tr::lng_create_group_next()),
|
||||||
[=] { send({}); });
|
[=] { send({}); });
|
||||||
|
refreshMessagesCount();
|
||||||
|
|
||||||
|
const auto perMessage = _captionToPeer
|
||||||
|
? _captionToPeer->starsPerMessageChecked()
|
||||||
|
: 0;
|
||||||
|
if (perMessage > 0) {
|
||||||
|
_send->setText(PaidSendButtonText(_messagesCount.value(
|
||||||
|
) | rpl::map(rpl::mappers::_1 * perMessage)));
|
||||||
|
}
|
||||||
if (_sendType == Api::SendType::Normal) {
|
if (_sendType == Api::SendType::Normal) {
|
||||||
SendMenu::SetupMenuAndShortcuts(
|
SendMenu::SetupMenuAndShortcuts(
|
||||||
_send,
|
_send,
|
||||||
|
@ -846,10 +867,9 @@ void SendFilesBox::refreshPriceTag() {
|
||||||
QString(),
|
QString(),
|
||||||
st::paidTagLabel);
|
st::paidTagLabel);
|
||||||
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
|
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
|
||||||
label->setMarkedText(text, Core::MarkedTextContext{
|
label->setMarkedText(text, Core::TextContext({
|
||||||
.session = session,
|
.session = session,
|
||||||
.customEmojiRepaint = [=] { label->update(); },
|
}));
|
||||||
});
|
|
||||||
}, label->lifetime());
|
}, label->lifetime());
|
||||||
label->show();
|
label->show();
|
||||||
label->sizeValue() | rpl::start_with_next([=](QSize size) {
|
label->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||||
|
@ -1489,6 +1509,7 @@ void SendFilesBox::setupCaption() {
|
||||||
_caption->changes()
|
_caption->changes()
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
checkCharsLimitation();
|
checkCharsLimitation();
|
||||||
|
refreshMessagesCount();
|
||||||
}, _caption->lifetime());
|
}, _caption->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -246,6 +246,7 @@ private:
|
||||||
void addPreparedAsyncFile(Ui::PreparedFile &&file);
|
void addPreparedAsyncFile(Ui::PreparedFile &&file);
|
||||||
|
|
||||||
void checkCharsLimitation();
|
void checkCharsLimitation();
|
||||||
|
void refreshMessagesCount();
|
||||||
|
|
||||||
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
|
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
|
||||||
const SendFilesBoxDescriptor &descriptor);
|
const SendFilesBoxDescriptor &descriptor);
|
||||||
|
@ -261,6 +262,7 @@ private:
|
||||||
|
|
||||||
Ui::PreparedList _list;
|
Ui::PreparedList _list;
|
||||||
std::optional<int> _removingIndex;
|
std::optional<int> _removingIndex;
|
||||||
|
rpl::variable<int> _messagesCount;
|
||||||
|
|
||||||
SendFilesLimits _limits = {};
|
SendFilesLimits _limits = {};
|
||||||
Fn<MenuDetails()> _sendMenuDetails;
|
Fn<MenuDetails()> _sendMenuDetails;
|
||||||
|
|
|
@ -123,12 +123,13 @@ private:
|
||||||
Ui::RoundImageCheckbox checkbox;
|
Ui::RoundImageCheckbox checkbox;
|
||||||
Ui::Text::String name;
|
Ui::Text::String name;
|
||||||
Ui::Animations::Simple nameActive;
|
Ui::Animations::Simple nameActive;
|
||||||
bool locked = false;
|
Api::MessageMoneyRestriction restriction;
|
||||||
|
RestrictionBadgeCache badgeCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
void invalidateCache();
|
void invalidateCache();
|
||||||
bool showLockedError(not_null<Chat*> chat);
|
bool showLockedError(not_null<Chat*> chat);
|
||||||
void refreshLockedRows();
|
void refreshRestrictedRows();
|
||||||
|
|
||||||
[[nodiscard]] int displayedChatsCount() const;
|
[[nodiscard]] int displayedChatsCount() const;
|
||||||
[[nodiscard]] not_null<Data::Thread*> chatThread(
|
[[nodiscard]] not_null<Data::Thread*> chatThread(
|
||||||
|
@ -137,7 +138,7 @@ private:
|
||||||
void paintChat(Painter &p, not_null<Chat*> chat, int index);
|
void paintChat(Painter &p, not_null<Chat*> chat, int index);
|
||||||
void updateChat(not_null<PeerData*> peer);
|
void updateChat(not_null<PeerData*> peer);
|
||||||
void updateChatName(not_null<Chat*> chat);
|
void updateChatName(not_null<Chat*> chat);
|
||||||
void initChatLocked(not_null<Chat*> chat);
|
void initChatRestriction(not_null<Chat*> chat);
|
||||||
void repaintChat(not_null<PeerData*> peer);
|
void repaintChat(not_null<PeerData*> peer);
|
||||||
int chatIndex(not_null<PeerData*> peer) const;
|
int chatIndex(not_null<PeerData*> peer) const;
|
||||||
void repaintChatAtIndex(int index);
|
void repaintChatAtIndex(int index);
|
||||||
|
@ -517,9 +518,19 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
|
||||||
|
|
||||||
SendMenu::Details ShareBox::sendMenuDetails() const {
|
SendMenu::Details ShareBox::sendMenuDetails() const {
|
||||||
const auto selected = _inner->selected();
|
const auto selected = _inner->selected();
|
||||||
const auto type = ranges::all_of(
|
const auto hasPaid = [&] {
|
||||||
selected | ranges::views::transform(&Data::Thread::peer),
|
for (const auto &thread : selected) {
|
||||||
HistoryView::CanScheduleUntilOnline)
|
if (thread->peer()->starsPerMessageChecked()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}();
|
||||||
|
const auto type = hasPaid
|
||||||
|
? SendMenu::Type::SilentOnly
|
||||||
|
: ranges::all_of(
|
||||||
|
selected | ranges::views::transform(&Data::Thread::peer),
|
||||||
|
HistoryView::CanScheduleUntilOnline)
|
||||||
? SendMenu::Type::ScheduledToUser
|
? SendMenu::Type::ScheduledToUser
|
||||||
: (selected.size() == 1 && selected.front()->peer()->isSelf())
|
: (selected.size() == 1 && selected.front()->peer()->isSelf())
|
||||||
? SendMenu::Type::Reminder
|
? SendMenu::Type::Reminder
|
||||||
|
@ -614,6 +625,9 @@ void ShareBox::createButtons() {
|
||||||
showMenu(send);
|
showMenu(send);
|
||||||
}
|
}
|
||||||
}, send->lifetime());
|
}, send->lifetime());
|
||||||
|
send->setText(PaidSendButtonText(
|
||||||
|
_starsToSend.value(),
|
||||||
|
tr::lng_share_confirm()));
|
||||||
} else if (_descriptor.copyCallback) {
|
} else if (_descriptor.copyCallback) {
|
||||||
addButton(_copyLinkText.value(), [=] { copyLink(); });
|
addButton(_copyLinkText.value(), [=] { copyLink(); });
|
||||||
}
|
}
|
||||||
|
@ -657,6 +671,73 @@ void ShareBox::innerSelectedChanged(
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareBox::submit(Api::SendOptions options) {
|
void ShareBox::submit(Api::SendOptions options) {
|
||||||
|
_submitLifetime.destroy();
|
||||||
|
|
||||||
|
auto threads = _inner->selected();
|
||||||
|
const auto weak = Ui::MakeWeak(this);
|
||||||
|
const auto field = _comment->entity();
|
||||||
|
auto comment = field->getTextWithAppliedMarkdown();
|
||||||
|
const auto checkPaid = [=] {
|
||||||
|
if (!_descriptor.countMessagesCallback) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto withPaymentApproved = crl::guard(weak, [=](int approved) {
|
||||||
|
auto copy = options;
|
||||||
|
copy.starsApproved = approved;
|
||||||
|
submit(copy);
|
||||||
|
});
|
||||||
|
const auto messagesCount = _descriptor.countMessagesCallback(
|
||||||
|
comment);
|
||||||
|
const auto alreadyApproved = options.starsApproved;
|
||||||
|
auto paid = std::vector<not_null<PeerData*>>();
|
||||||
|
auto waiting = base::flat_set<not_null<PeerData*>>();
|
||||||
|
auto totalStars = 0;
|
||||||
|
for (const auto &thread : threads) {
|
||||||
|
const auto peer = thread->peer();
|
||||||
|
const auto details = ComputePaymentDetails(peer, messagesCount);
|
||||||
|
if (!details) {
|
||||||
|
waiting.emplace(peer);
|
||||||
|
} else if (details->stars > 0) {
|
||||||
|
totalStars += details->stars;
|
||||||
|
paid.push_back(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!waiting.empty()) {
|
||||||
|
_descriptor.session->changes().peerUpdates(
|
||||||
|
Data::PeerUpdate::Flag::FullInfo
|
||||||
|
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||||
|
if (waiting.contains(update.peer)) {
|
||||||
|
withPaymentApproved(alreadyApproved);
|
||||||
|
}
|
||||||
|
}, _submitLifetime);
|
||||||
|
|
||||||
|
if (!_descriptor.session->credits().loaded()) {
|
||||||
|
_descriptor.session->credits().loadedValue(
|
||||||
|
) | rpl::filter(
|
||||||
|
rpl::mappers::_1
|
||||||
|
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||||
|
withPaymentApproved(alreadyApproved);
|
||||||
|
}, _submitLifetime);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if (totalStars > alreadyApproved) {
|
||||||
|
const auto show = uiShow();
|
||||||
|
const auto session = _descriptor.session;
|
||||||
|
const auto sessionShow = Main::MakeSessionShow(show, session);
|
||||||
|
const auto scheduleBoxSt = _descriptor.st.scheduleBox.get();
|
||||||
|
ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
|
||||||
|
.messages = messagesCount,
|
||||||
|
.stars = totalStars,
|
||||||
|
}, [=] { withPaymentApproved(totalStars); }, PaidConfirmStyles{
|
||||||
|
.label = (scheduleBoxSt
|
||||||
|
? scheduleBoxSt->chooseDateTimeArgs.labelStyle
|
||||||
|
: nullptr),
|
||||||
|
.checkbox = _descriptor.st.checkbox,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
if (const auto onstack = _descriptor.submitCallback) {
|
if (const auto onstack = _descriptor.submitCallback) {
|
||||||
const auto forwardOptions = (_forwardOptions.captionsCount
|
const auto forwardOptions = (_forwardOptions.captionsCount
|
||||||
&& _forwardOptions.dropCaptions)
|
&& _forwardOptions.dropCaptions)
|
||||||
|
@ -665,8 +746,9 @@ void ShareBox::submit(Api::SendOptions options) {
|
||||||
? Data::ForwardOptions::NoSenderNames
|
? Data::ForwardOptions::NoSenderNames
|
||||||
: Data::ForwardOptions::PreserveInfo;
|
: Data::ForwardOptions::PreserveInfo;
|
||||||
onstack(
|
onstack(
|
||||||
_inner->selected(),
|
std::move(threads),
|
||||||
_comment->entity()->getTextWithAppliedMarkdown(),
|
checkPaid,
|
||||||
|
std::move(comment),
|
||||||
options,
|
options,
|
||||||
forwardOptions);
|
forwardOptions);
|
||||||
}
|
}
|
||||||
|
@ -686,9 +768,23 @@ void ShareBox::selectedChanged() {
|
||||||
_comment->toggle(_hasSelected, anim::type::normal);
|
_comment->toggle(_hasSelected, anim::type::normal);
|
||||||
_comment->resizeToWidth(st::boxWideWidth);
|
_comment->resizeToWidth(st::boxWideWidth);
|
||||||
}
|
}
|
||||||
|
computeStarsCount();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShareBox::computeStarsCount() {
|
||||||
|
auto perMessage = 0;
|
||||||
|
for (const auto &thread : _inner->selected()) {
|
||||||
|
perMessage += thread->peer()->starsPerMessageChecked();
|
||||||
|
}
|
||||||
|
const auto messagesCount = _descriptor.countMessagesCallback
|
||||||
|
? _descriptor.countMessagesCallback(_comment
|
||||||
|
? _comment->entity()->getTextWithTags()
|
||||||
|
: TextWithTags())
|
||||||
|
: 0;
|
||||||
|
_starsToSend = perMessage * messagesCount;
|
||||||
|
}
|
||||||
|
|
||||||
void ShareBox::scrollTo(Ui::ScrollToRequest request) {
|
void ShareBox::scrollTo(Ui::ScrollToRequest request) {
|
||||||
scrollToY(request.ymin, request.ymax);
|
scrollToY(request.ymin, request.ymax);
|
||||||
//auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height();
|
//auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height();
|
||||||
|
@ -726,13 +822,13 @@ ShareBox::Inner::Inner(
|
||||||
_rowHeight = st::shareRowHeight;
|
_rowHeight = st::shareRowHeight;
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
||||||
if (_descriptor.premiumRequiredError) {
|
if (_descriptor.moneyRestrictionError) {
|
||||||
const auto session = _descriptor.session;
|
const auto session = _descriptor.session;
|
||||||
rpl::merge(
|
rpl::merge(
|
||||||
Data::AmPremiumValue(session) | rpl::to_empty,
|
Data::AmPremiumValue(session) | rpl::to_empty,
|
||||||
session->api().premium().somePremiumRequiredResolved()
|
session->api().premium().someMessageMoneyRestrictionsResolved()
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
refreshLockedRows();
|
refreshRestrictedRows();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -793,38 +889,36 @@ void ShareBox::Inner::invalidateCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
|
bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
|
||||||
if (!chat->locked) {
|
if (!chat->restriction.premiumRequired) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
::Settings::ShowPremiumPromoToast(
|
::Settings::ShowPremiumPromoToast(
|
||||||
Main::MakeSessionShow(_show, _descriptor.session),
|
Main::MakeSessionShow(_show, _descriptor.session),
|
||||||
ChatHelpers::ResolveWindowDefault(),
|
ChatHelpers::ResolveWindowDefault(),
|
||||||
_descriptor.premiumRequiredError(chat->peer->asUser()).text,
|
_descriptor.moneyRestrictionError(chat->peer->asUser()).text,
|
||||||
u"require_premium"_q);
|
u"require_premium"_q);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareBox::Inner::refreshLockedRows() {
|
void ShareBox::Inner::refreshRestrictedRows() {
|
||||||
auto changed = false;
|
auto changed = false;
|
||||||
for (const auto &[peer, data] : _dataMap) {
|
for (const auto &[peer, data] : _dataMap) {
|
||||||
const auto history = data->history;
|
const auto history = data->history;
|
||||||
const auto locked = (Api::ResolveRequiresPremiumToWrite(
|
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||||
history->peer,
|
history->peer,
|
||||||
history
|
history);
|
||||||
) == Api::RequirePremiumState::Yes);
|
if (data->restriction != restriction) {
|
||||||
if (data->locked != locked) {
|
data->restriction = restriction;
|
||||||
data->locked = locked;
|
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto &data : d_byUsernameFiltered) {
|
for (const auto &data : d_byUsernameFiltered) {
|
||||||
const auto history = data->history;
|
const auto history = data->history;
|
||||||
const auto locked = (Api::ResolveRequiresPremiumToWrite(
|
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||||
history->peer,
|
history->peer,
|
||||||
history
|
history);
|
||||||
) == Api::RequirePremiumState::Yes);
|
if (data->restriction != restriction) {
|
||||||
if (data->locked != locked) {
|
data->restriction = restriction;
|
||||||
data->locked = locked;
|
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -891,14 +985,14 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
|
||||||
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
|
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) {
|
void ShareBox::Inner::initChatRestriction(not_null<Chat*> chat) {
|
||||||
if (_descriptor.premiumRequiredError) {
|
if (_descriptor.moneyRestrictionError) {
|
||||||
const auto history = chat->history;
|
const auto history = chat->history;
|
||||||
if (Api::ResolveRequiresPremiumToWrite(
|
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||||
history->peer,
|
history->peer,
|
||||||
history
|
history);
|
||||||
) == Api::RequirePremiumState::Yes) {
|
if (restriction || restriction.known) {
|
||||||
chat->locked = true;
|
chat->restriction = restriction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1024,14 +1118,15 @@ void ShareBox::Inner::loadProfilePhotos() {
|
||||||
void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
|
void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
|
||||||
entry->chatListPreloadData();
|
entry->chatListPreloadData();
|
||||||
const auto history = entry->asHistory();
|
const auto history = entry->asHistory();
|
||||||
if (!_descriptor.premiumRequiredError || !history) {
|
if (!_descriptor.moneyRestrictionError || !history) {
|
||||||
return;
|
return;
|
||||||
} else if (Api::ResolveRequiresPremiumToWrite(
|
} else if (!Api::ResolveMessageMoneyRestrictions(
|
||||||
history->peer,
|
history->peer,
|
||||||
history
|
history).known) {
|
||||||
) == Api::RequirePremiumState::Unknown) {
|
if (const auto user = history->peer->asUser()) {
|
||||||
const auto user = history->peer->asUser();
|
const auto api = &_descriptor.session->api();
|
||||||
_descriptor.session->api().premium().resolvePremiumRequired(user);
|
api->premium().resolveMessageMoneyRestrictions(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1054,7 +1149,7 @@ auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
|
||||||
repaintChat(peer);
|
repaintChat(peer);
|
||||||
}));
|
}));
|
||||||
updateChatName(i->second.get());
|
updateChatName(i->second.get());
|
||||||
initChatLocked(i->second.get());
|
initChatRestriction(i->second.get());
|
||||||
row->attached = i->second.get();
|
row->attached = i->second.get();
|
||||||
return i->second.get();
|
return i->second.get();
|
||||||
}
|
}
|
||||||
|
@ -1088,10 +1183,12 @@ void ShareBox::Inner::paintChat(
|
||||||
auto photoTop = st::sharePhotoTop;
|
auto photoTop = st::sharePhotoTop;
|
||||||
chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
|
chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
|
||||||
|
|
||||||
if (chat->locked) {
|
if (chat->restriction) {
|
||||||
PaintPremiumRequiredLock(
|
PaintRestrictionBadge(
|
||||||
p,
|
p,
|
||||||
&_st.item,
|
&_st.item,
|
||||||
|
chat->restriction.starsPerMessage,
|
||||||
|
chat->badgeCache,
|
||||||
x + photoLeft,
|
x + photoLeft,
|
||||||
y + photoTop,
|
y + photoTop,
|
||||||
outerWidth,
|
outerWidth,
|
||||||
|
@ -1446,7 +1543,7 @@ void ShareBox::Inner::peopleReceived(
|
||||||
_st.item,
|
_st.item,
|
||||||
[=] { repaintChat(peer); }));
|
[=] { repaintChat(peer); }));
|
||||||
updateChatName(d_byUsernameFiltered.back().get());
|
updateChatName(d_byUsernameFiltered.back().get());
|
||||||
initChatLocked(d_byUsernameFiltered.back().get());
|
initChatRestriction(d_byUsernameFiltered.back().get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1499,6 +1596,15 @@ ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShareBox::CountMessagesCallback ShareBox::DefaultForwardCountMessages(
|
||||||
|
not_null<History*> history,
|
||||||
|
MessageIdsList msgIds) {
|
||||||
|
return [=](const TextWithTags &comment) {
|
||||||
|
const auto items = history->owner().idsToItems(msgIds);
|
||||||
|
return int(items.size()) + (comment.empty() ? 0 : 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||||
std::shared_ptr<Ui::Show> show,
|
std::shared_ptr<Ui::Show> show,
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
|
@ -1510,12 +1616,14 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||||
const auto state = std::make_shared<State>();
|
const auto state = std::make_shared<State>();
|
||||||
return [=](
|
return [=](
|
||||||
std::vector<not_null<Data::Thread*>> &&result,
|
std::vector<not_null<Data::Thread*>> &&result,
|
||||||
TextWithTags &&comment,
|
Fn<bool()> checkPaid,
|
||||||
|
TextWithTags comment,
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
Data::ForwardOptions forwardOptions) {
|
Data::ForwardOptions forwardOptions) {
|
||||||
if (!state->requests.empty()) {
|
if (!state->requests.empty()) {
|
||||||
return; // Share clicked already.
|
return; // Share clicked already.
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto items = history->owner().idsToItems(msgIds);
|
const auto items = history->owner().idsToItems(msgIds);
|
||||||
const auto existingIds = history->owner().itemsToIds(items);
|
const auto existingIds = history->owner().itemsToIds(items);
|
||||||
if (existingIds.empty() || result.empty()) {
|
if (existingIds.empty() || result.empty()) {
|
||||||
|
@ -1528,6 +1636,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||||
if (error.error) {
|
if (error.error) {
|
||||||
show->showBox(MakeSendErrorBox(error, result.size() > 1));
|
show->showBox(MakeSendErrorBox(error, result.size() > 1));
|
||||||
return;
|
return;
|
||||||
|
} else if (!checkPaid()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using Flag = MTPmessages_ForwardMessages::Flag;
|
using Flag = MTPmessages_ForwardMessages::Flag;
|
||||||
|
@ -1576,6 +1686,12 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||||
: topicRootId;
|
: topicRootId;
|
||||||
const auto peer = thread->peer();
|
const auto peer = thread->peer();
|
||||||
const auto threadHistory = thread->owningHistory();
|
const auto threadHistory = thread->owningHistory();
|
||||||
|
const auto starsPaid = std::min(
|
||||||
|
peer->starsPerMessageChecked(),
|
||||||
|
options.starsApproved);
|
||||||
|
if (starsPaid) {
|
||||||
|
options.starsApproved -= starsPaid;
|
||||||
|
}
|
||||||
histories.sendRequest(threadHistory, requestType, [=](
|
histories.sendRequest(threadHistory, requestType, [=](
|
||||||
Fn<void()> finish) {
|
Fn<void()> finish) {
|
||||||
const auto session = &threadHistory->session();
|
const auto session = &threadHistory->session();
|
||||||
|
@ -1587,7 +1703,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||||
: Flag(0))
|
: Flag(0))
|
||||||
| (options.shortcutId
|
| (options.shortcutId
|
||||||
? Flag::f_quick_reply_shortcut
|
? Flag::f_quick_reply_shortcut
|
||||||
: Flag(0));
|
: Flag(0))
|
||||||
|
| (starsPaid ? Flag::f_allow_paid_stars : Flag());
|
||||||
threadHistory->sendRequestId = api.request(
|
threadHistory->sendRequestId = api.request(
|
||||||
MTPmessages_ForwardMessages(
|
MTPmessages_ForwardMessages(
|
||||||
MTP_flags(sendFlags),
|
MTP_flags(sendFlags),
|
||||||
|
@ -1599,7 +1716,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||||
MTP_int(options.scheduled),
|
MTP_int(options.scheduled),
|
||||||
MTP_inputPeerEmpty(), // send_as
|
MTP_inputPeerEmpty(), // send_as
|
||||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||||
MTP_int(videoTimestamp.value_or(0))
|
MTP_int(videoTimestamp.value_or(0)),
|
||||||
|
MTP_long(starsPaid)
|
||||||
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
|
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
|
||||||
threadHistory->session().api().applyUpdates(updates);
|
threadHistory->session().api().applyUpdates(updates);
|
||||||
state->requests.remove(reqId);
|
state->requests.remove(reqId);
|
||||||
|
@ -1621,7 +1739,11 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
if (error.type() == u"VOICE_MESSAGES_FORBIDDEN"_q) {
|
const auto type = error.type();
|
||||||
|
if (type.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) {
|
||||||
|
show->showToast(u"Payment requirements changed. "
|
||||||
|
"Please, try again."_q);
|
||||||
|
} else if (type == u"VOICE_MESSAGES_FORBIDDEN"_q) {
|
||||||
show->showToast(
|
show->showToast(
|
||||||
tr::lng_restricted_send_voice_messages(
|
tr::lng_restricted_send_voice_messages(
|
||||||
tr::now,
|
tr::now,
|
||||||
|
@ -1660,6 +1782,7 @@ ShareBoxStyleOverrides DarkShareBoxStyle() {
|
||||||
.comment = &st::groupCallShareBoxComment,
|
.comment = &st::groupCallShareBoxComment,
|
||||||
.peerList = &st::groupCallShareBoxList,
|
.peerList = &st::groupCallShareBoxList,
|
||||||
.label = &st::groupCallField,
|
.label = &st::groupCallField,
|
||||||
|
.checkbox = &st::groupCallCheckbox,
|
||||||
.scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
|
.scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1716,7 +1839,7 @@ void FastShareMessage(
|
||||||
const auto requiresInline = item->requiresSendInlineRight();
|
const auto requiresInline = item->requiresSendInlineRight();
|
||||||
auto filterCallback = [=](not_null<Data::Thread*> thread) {
|
auto filterCallback = [=](not_null<Data::Thread*> thread) {
|
||||||
if (const auto user = thread->peer()->asUser()) {
|
if (const auto user = thread->peer()->asUser()) {
|
||||||
if (user->canSendIgnoreRequirePremium()) {
|
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1731,6 +1854,9 @@ void FastShareMessage(
|
||||||
show->show(Box<ShareBox>(ShareBox::Descriptor{
|
show->show(Box<ShareBox>(ShareBox::Descriptor{
|
||||||
.session = session,
|
.session = session,
|
||||||
.copyCallback = std::move(copyLinkCallback),
|
.copyCallback = std::move(copyLinkCallback),
|
||||||
|
.countMessagesCallback = ShareBox::DefaultForwardCountMessages(
|
||||||
|
history,
|
||||||
|
msgIds),
|
||||||
.submitCallback = ShareBox::DefaultForwardCallback(
|
.submitCallback = ShareBox::DefaultForwardCallback(
|
||||||
show,
|
show,
|
||||||
history,
|
history,
|
||||||
|
@ -1742,7 +1868,7 @@ void FastShareMessage(
|
||||||
.captionsCount = ItemsForwardCaptionsCount(items),
|
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||||
.show = !hasOnlyForcedForwardedInfo,
|
.show = !hasOnlyForcedForwardedInfo,
|
||||||
},
|
},
|
||||||
.premiumRequiredError = SharePremiumRequiredError(),
|
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||||
}), Ui::LayerOption::CloseOther);
|
}), Ui::LayerOption::CloseOther);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1770,8 +1896,12 @@ void FastShareLink(
|
||||||
QGuiApplication::clipboard()->setText(url);
|
QGuiApplication::clipboard()->setText(url);
|
||||||
show->showToast(tr::lng_background_link_copied(tr::now));
|
show->showToast(tr::lng_background_link_copied(tr::now));
|
||||||
};
|
};
|
||||||
|
auto countMessagesCallback = [=](const TextWithTags &comment) {
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
auto submitCallback = [=](
|
auto submitCallback = [=](
|
||||||
std::vector<not_null<::Data::Thread*>> &&result,
|
std::vector<not_null<::Data::Thread*>> &&result,
|
||||||
|
Fn<bool()> checkPaid,
|
||||||
TextWithTags &&comment,
|
TextWithTags &&comment,
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
::Data::ForwardOptions) {
|
::Data::ForwardOptions) {
|
||||||
|
@ -1788,6 +1918,8 @@ void FastShareLink(
|
||||||
MakeSendErrorBox(error, result.size() > 1));
|
MakeSendErrorBox(error, result.size() > 1));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
} else if (!checkPaid()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
*sending = true;
|
*sending = true;
|
||||||
|
@ -1815,7 +1947,7 @@ void FastShareLink(
|
||||||
};
|
};
|
||||||
auto filterCallback = [](not_null<::Data::Thread*> thread) {
|
auto filterCallback = [](not_null<::Data::Thread*> thread) {
|
||||||
if (const auto user = thread->peer()->asUser()) {
|
if (const auto user = thread->peer()->asUser()) {
|
||||||
if (user->canSendIgnoreRequirePremium()) {
|
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1825,16 +1957,17 @@ void FastShareLink(
|
||||||
Box<ShareBox>(ShareBox::Descriptor{
|
Box<ShareBox>(ShareBox::Descriptor{
|
||||||
.session = &show->session(),
|
.session = &show->session(),
|
||||||
.copyCallback = std::move(copyCallback),
|
.copyCallback = std::move(copyCallback),
|
||||||
|
.countMessagesCallback = std::move(countMessagesCallback),
|
||||||
.submitCallback = std::move(submitCallback),
|
.submitCallback = std::move(submitCallback),
|
||||||
.filterCallback = std::move(filterCallback),
|
.filterCallback = std::move(filterCallback),
|
||||||
.st = st,
|
.st = st,
|
||||||
.premiumRequiredError = SharePremiumRequiredError(),
|
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||||
}),
|
}),
|
||||||
Ui::LayerOption::KeepOther,
|
Ui::LayerOption::KeepOther,
|
||||||
anim::type::normal);
|
anim::type::normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto SharePremiumRequiredError()
|
auto ShareMessageMoneyRestrictionError()
|
||||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
|
-> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> {
|
||||||
return WritePremiumRequiredError;
|
return WriteMoneyRestrictionError;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ struct ShareBoxStyleOverrides {
|
||||||
const style::InputField *comment = nullptr;
|
const style::InputField *comment = nullptr;
|
||||||
const style::PeerList *peerList = nullptr;
|
const style::PeerList *peerList = nullptr;
|
||||||
const style::InputField *label = nullptr;
|
const style::InputField *label = nullptr;
|
||||||
|
const style::Checkbox *checkbox = nullptr;
|
||||||
std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox;
|
std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox;
|
||||||
};
|
};
|
||||||
[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle();
|
[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle();
|
||||||
|
@ -87,20 +88,25 @@ void FastShareLink(
|
||||||
const QString &url,
|
const QString &url,
|
||||||
ShareBoxStyleOverrides st = {});
|
ShareBoxStyleOverrides st = {});
|
||||||
|
|
||||||
struct RecipientPremiumRequiredError;
|
struct RecipientMoneyRestrictionError;
|
||||||
[[nodiscard]] auto SharePremiumRequiredError()
|
[[nodiscard]] auto ShareMessageMoneyRestrictionError()
|
||||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)>;
|
-> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)>;
|
||||||
|
|
||||||
class ShareBox final : public Ui::BoxContent {
|
class ShareBox final : public Ui::BoxContent {
|
||||||
public:
|
public:
|
||||||
using CopyCallback = Fn<void()>;
|
using CopyCallback = Fn<void()>;
|
||||||
|
using CountMessagesCallback = Fn<int(const TextWithTags&)>;
|
||||||
using SubmitCallback = Fn<void(
|
using SubmitCallback = Fn<void(
|
||||||
std::vector<not_null<Data::Thread*>>&&,
|
std::vector<not_null<Data::Thread*>>&&,
|
||||||
|
Fn<bool()> checkPaid,
|
||||||
TextWithTags&&,
|
TextWithTags&&,
|
||||||
Api::SendOptions,
|
Api::SendOptions,
|
||||||
Data::ForwardOptions)>;
|
Data::ForwardOptions)>;
|
||||||
using FilterCallback = Fn<bool(not_null<Data::Thread*>)>;
|
using FilterCallback = Fn<bool(not_null<Data::Thread*>)>;
|
||||||
|
|
||||||
|
[[nodiscard]] static auto DefaultForwardCountMessages(
|
||||||
|
not_null<History*> history,
|
||||||
|
MessageIdsList msgIds) -> CountMessagesCallback;
|
||||||
[[nodiscard]] static SubmitCallback DefaultForwardCallback(
|
[[nodiscard]] static SubmitCallback DefaultForwardCallback(
|
||||||
std::shared_ptr<Ui::Show> show,
|
std::shared_ptr<Ui::Show> show,
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
|
@ -110,6 +116,7 @@ public:
|
||||||
struct Descriptor {
|
struct Descriptor {
|
||||||
not_null<Main::Session*> session;
|
not_null<Main::Session*> session;
|
||||||
CopyCallback copyCallback;
|
CopyCallback copyCallback;
|
||||||
|
CountMessagesCallback countMessagesCallback;
|
||||||
SubmitCallback submitCallback;
|
SubmitCallback submitCallback;
|
||||||
FilterCallback filterCallback;
|
FilterCallback filterCallback;
|
||||||
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
|
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
|
||||||
|
@ -123,8 +130,9 @@ public:
|
||||||
bool show = false;
|
bool show = false;
|
||||||
} forwardOptions;
|
} forwardOptions;
|
||||||
|
|
||||||
using PremiumRequiredError = RecipientPremiumRequiredError;
|
using MoneyRestrictionError = RecipientMoneyRestrictionError;
|
||||||
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
|
Fn<MoneyRestrictionError(
|
||||||
|
not_null<UserData*>)> moneyRestrictionError;
|
||||||
};
|
};
|
||||||
ShareBox(QWidget*, Descriptor &&descriptor);
|
ShareBox(QWidget*, Descriptor &&descriptor);
|
||||||
|
|
||||||
|
@ -149,6 +157,7 @@ private:
|
||||||
void needSearchByUsername();
|
void needSearchByUsername();
|
||||||
void applyFilterUpdate(const QString &query);
|
void applyFilterUpdate(const QString &query);
|
||||||
void selectedChanged();
|
void selectedChanged();
|
||||||
|
void computeStarsCount();
|
||||||
void createButtons();
|
void createButtons();
|
||||||
int getTopScrollSkip() const;
|
int getTopScrollSkip() const;
|
||||||
int getBottomScrollSkip() const;
|
int getBottomScrollSkip() const;
|
||||||
|
@ -180,6 +189,7 @@ private:
|
||||||
|
|
||||||
bool _hasSelected = false;
|
bool _hasSelected = false;
|
||||||
rpl::variable<QString> _copyLinkText;
|
rpl::variable<QString> _copyLinkText;
|
||||||
|
rpl::variable<int> _starsToSend;
|
||||||
|
|
||||||
base::Timer _searchTimer;
|
base::Timer _searchTimer;
|
||||||
QString _peopleQuery;
|
QString _peopleQuery;
|
||||||
|
@ -195,5 +205,6 @@ private:
|
||||||
PeopleQueries _peopleQueries;
|
PeopleQueries _peopleQueries;
|
||||||
|
|
||||||
Ui::Animations::Simple _scrollAnimation;
|
Ui::Animations::Simple _scrollAnimation;
|
||||||
|
rpl::lifetime _submitLifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "chat_helpers/tabbed_panel.h"
|
#include "chat_helpers/tabbed_panel.h"
|
||||||
#include "chat_helpers/tabbed_selector.h"
|
#include "chat_helpers/tabbed_selector.h"
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_credits.h"
|
#include "data/data_credits.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
@ -80,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "window/themes/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "window/section_widget.h"
|
#include "window/section_widget.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
@ -106,6 +108,7 @@ constexpr auto kSentToastDuration = 3 * crl::time(1000);
|
||||||
constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
|
constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
|
||||||
constexpr auto kCrossfadeDuration = crl::time(400);
|
constexpr auto kCrossfadeDuration = crl::time(400);
|
||||||
constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
|
constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
|
||||||
|
constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
|
||||||
|
|
||||||
using namespace HistoryView;
|
using namespace HistoryView;
|
||||||
using namespace Info::PeerGifts;
|
using namespace Info::PeerGifts;
|
||||||
|
@ -126,6 +129,7 @@ struct GiftDetails {
|
||||||
uint64 randomId = 0;
|
uint64 randomId = 0;
|
||||||
bool anonymous = false;
|
bool anonymous = false;
|
||||||
bool upgraded = false;
|
bool upgraded = false;
|
||||||
|
bool byStars = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PreviewDelegate final : public DefaultElementDelegate {
|
class PreviewDelegate final : public DefaultElementDelegate {
|
||||||
|
@ -227,7 +231,7 @@ auto GenerateGiftMedia(
|
||||||
TextWithEntities text,
|
TextWithEntities text,
|
||||||
QMargins margins = {},
|
QMargins margins = {},
|
||||||
const base::flat_map<uint16, ClickHandlerPtr> &links = {},
|
const base::flat_map<uint16, ClickHandlerPtr> &links = {},
|
||||||
const std::any &context = {}) {
|
Ui::Text::MarkedContext context = {}) {
|
||||||
if (text.empty()) {
|
if (text.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -236,7 +240,7 @@ auto GenerateGiftMedia(
|
||||||
margins,
|
margins,
|
||||||
st::defaultTextStyle,
|
st::defaultTextStyle,
|
||||||
links,
|
links,
|
||||||
context));
|
std::move(context)));
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto sticker = [=] {
|
const auto sticker = [=] {
|
||||||
|
@ -306,10 +310,10 @@ auto GenerateGiftMedia(
|
||||||
auto description = data.text.empty()
|
auto description = data.text.empty()
|
||||||
? std::move(textFallback)
|
? std::move(textFallback)
|
||||||
: data.text;
|
: data.text;
|
||||||
const auto context = Core::MarkedTextContext{
|
const auto context = Core::TextContext({
|
||||||
.session = &parent->history()->session(),
|
.session = &parent->history()->session(),
|
||||||
.customEmojiRepaint = [parent] { parent->repaint(); },
|
.repaint = [parent] { parent->repaint(); },
|
||||||
};
|
});
|
||||||
pushText(
|
pushText(
|
||||||
std::move(title),
|
std::move(title),
|
||||||
st::giftBoxPreviewTitlePadding,
|
st::giftBoxPreviewTitlePadding,
|
||||||
|
@ -495,7 +499,14 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
|
||||||
std::move(details) | rpl::start_with_next([=](GiftDetails details) {
|
std::move(details) | rpl::start_with_next([=](GiftDetails details) {
|
||||||
const auto &descriptor = details.descriptor;
|
const auto &descriptor = details.descriptor;
|
||||||
const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
|
const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
|
||||||
return FillAmountAndCurrency(data.cost, data.currency, true);
|
const auto stars = (details.byStars && data.stars)
|
||||||
|
? data.stars
|
||||||
|
: (data.currency == kCreditsCurrency)
|
||||||
|
? data.cost
|
||||||
|
: 0;
|
||||||
|
return stars
|
||||||
|
? tr::lng_gift_stars_title(tr::now, lt_count, stars)
|
||||||
|
: FillAmountAndCurrency(data.cost, data.currency, true);
|
||||||
}, [&](GiftTypeStars data) {
|
}, [&](GiftTypeStars data) {
|
||||||
const auto stars = data.info.stars
|
const auto stars = data.info.stars
|
||||||
+ (details.upgraded ? data.info.starsToUpgrade : 0);
|
+ (details.upgraded ? data.info.starsToUpgrade : 0);
|
||||||
|
@ -622,14 +633,27 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
||||||
list.reserve(options.size());
|
list.reserve(options.size());
|
||||||
auto minMonthsGift = GiftTypePremium();
|
auto minMonthsGift = GiftTypePremium();
|
||||||
for (const auto &option : options) {
|
for (const auto &option : options) {
|
||||||
list.push_back({
|
if (option.currency != kCreditsCurrency) {
|
||||||
.cost = option.cost,
|
list.push_back({
|
||||||
.currency = option.currency,
|
.cost = option.cost,
|
||||||
.months = option.months,
|
.currency = option.currency,
|
||||||
});
|
.months = option.months,
|
||||||
if (!minMonthsGift.months
|
});
|
||||||
|| option.months < minMonthsGift.months) {
|
if (!minMonthsGift.months
|
||||||
minMonthsGift = list.back();
|
|| option.months < minMonthsGift.months) {
|
||||||
|
minMonthsGift = list.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &option : options) {
|
||||||
|
if (option.currency == kCreditsCurrency) {
|
||||||
|
const auto i = ranges::find(
|
||||||
|
list,
|
||||||
|
option.months,
|
||||||
|
&GiftTypePremium::months);
|
||||||
|
if (i != end(list)) {
|
||||||
|
i->stars = option.cost;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto &gift : list) {
|
for (auto &gift : list) {
|
||||||
|
@ -735,15 +759,11 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
||||||
}
|
}
|
||||||
auto &manager = session->data().customEmojiManager();
|
auto &manager = session->data().customEmojiManager();
|
||||||
auto result = Text::String();
|
auto result = Text::String();
|
||||||
const auto context = Core::MarkedTextContext{
|
|
||||||
.session = session,
|
|
||||||
.customEmojiRepaint = [] {},
|
|
||||||
};
|
|
||||||
result.setMarkedText(
|
result.setMarkedText(
|
||||||
st::semiboldTextStyle,
|
st::semiboldTextStyle,
|
||||||
manager.creditsEmoji().append(QString::number(price)),
|
manager.creditsEmoji().append(QString::number(price)),
|
||||||
kMarkupTextOptions,
|
kMarkupTextOptions,
|
||||||
context);
|
Core::TextContext({ .session = session }));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1103,16 +1123,35 @@ void SendGift(
|
||||||
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
|
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
|
||||||
const GiftDetails &details,
|
const GiftDetails &details,
|
||||||
Fn<void(Payments::CheckoutResult)> done) {
|
Fn<void(Payments::CheckoutResult)> done) {
|
||||||
|
const auto processNonPanelPaymentFormFactory
|
||||||
|
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
|
||||||
v::match(details.descriptor, [&](const GiftTypePremium &gift) {
|
v::match(details.descriptor, [&](const GiftTypePremium &gift) {
|
||||||
auto invoice = api->invoice(1, gift.months);
|
if (details.byStars && gift.stars) {
|
||||||
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{
|
auto invoice = Payments::InvoicePremiumGiftCode{
|
||||||
.users = { peer->asUser() },
|
.purpose = Payments::InvoicePremiumGiftCodeUsers{
|
||||||
.message = details.text,
|
.users = { peer->asUser() },
|
||||||
};
|
.message = details.text,
|
||||||
Payments::CheckoutProcess::Start(std::move(invoice), done);
|
},
|
||||||
|
.currency = Ui::kCreditsCurrency,
|
||||||
|
.randomId = details.randomId,
|
||||||
|
.amount = uint64(gift.stars),
|
||||||
|
.storeQuantity = 1,
|
||||||
|
.users = 1,
|
||||||
|
.months = gift.months,
|
||||||
|
};
|
||||||
|
Payments::CheckoutProcess::Start(
|
||||||
|
std::move(invoice),
|
||||||
|
done,
|
||||||
|
processNonPanelPaymentFormFactory);
|
||||||
|
} else {
|
||||||
|
auto invoice = api->invoice(1, gift.months);
|
||||||
|
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{
|
||||||
|
.users = { peer->asUser() },
|
||||||
|
.message = details.text,
|
||||||
|
};
|
||||||
|
Payments::CheckoutProcess::Start(std::move(invoice), done);
|
||||||
|
}
|
||||||
}, [&](const GiftTypeStars &gift) {
|
}, [&](const GiftTypeStars &gift) {
|
||||||
const auto processNonPanelPaymentFormFactory
|
|
||||||
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
|
|
||||||
Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
|
Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
|
||||||
.giftId = gift.info.id,
|
.giftId = gift.info.id,
|
||||||
.randomId = details.randomId,
|
.randomId = details.randomId,
|
||||||
|
@ -1279,12 +1318,6 @@ void AddUpgradeButton(
|
||||||
button->toggleOn(rpl::single(false))->toggledValue(
|
button->toggleOn(rpl::single(false))->toggledValue(
|
||||||
) | rpl::start_with_next(toggled, button->lifetime());
|
) | rpl::start_with_next(toggled, button->lifetime());
|
||||||
|
|
||||||
const auto makeContext = [session](Fn<void()> update) {
|
|
||||||
return Core::MarkedTextContext{
|
|
||||||
.session = session,
|
|
||||||
.customEmojiRepaint = std::move(update),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
auto star = session->data().customEmojiManager().creditsEmoji();
|
auto star = session->data().customEmojiManager().creditsEmoji();
|
||||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
button,
|
button,
|
||||||
|
@ -1296,7 +1329,7 @@ void AddUpgradeButton(
|
||||||
Text::WithEntities),
|
Text::WithEntities),
|
||||||
st::boxLabel,
|
st::boxLabel,
|
||||||
st::defaultPopupMenu,
|
st::defaultPopupMenu,
|
||||||
std::move(makeContext));
|
Core::TextContext({ .session = session }));
|
||||||
label->show();
|
label->show();
|
||||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
button->widthValue() | rpl::start_with_next([=](int outer) {
|
button->widthValue() | rpl::start_with_next([=](int outer) {
|
||||||
|
@ -1424,6 +1457,7 @@ void SendGiftBox(
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
rpl::variable<GiftDetails> details;
|
rpl::variable<GiftDetails> details;
|
||||||
|
rpl::variable<bool> messageAllowed;
|
||||||
std::shared_ptr<Data::DocumentMedia> media;
|
std::shared_ptr<Data::DocumentMedia> media;
|
||||||
bool submitting = false;
|
bool submitting = false;
|
||||||
};
|
};
|
||||||
|
@ -1432,13 +1466,25 @@ void SendGiftBox(
|
||||||
.descriptor = descriptor,
|
.descriptor = descriptor,
|
||||||
.randomId = base::RandomValue<uint64>(),
|
.randomId = base::RandomValue<uint64>(),
|
||||||
};
|
};
|
||||||
|
peer->updateFull();
|
||||||
|
state->messageAllowed = peer->session().changes().peerFlagsValue(
|
||||||
|
peer,
|
||||||
|
Data::PeerUpdate::Flag::StarsPerMessage
|
||||||
|
) | rpl::map([=] {
|
||||||
|
return peer->starsPerMessageChecked() == 0;
|
||||||
|
});
|
||||||
|
|
||||||
auto cost = state->details.value(
|
auto cost = state->details.value(
|
||||||
) | rpl::map([session](const GiftDetails &details) {
|
) | rpl::map([session](const GiftDetails &details) {
|
||||||
return v::match(details.descriptor, [&](const GiftTypePremium &data) {
|
return v::match(details.descriptor, [&](const GiftTypePremium &data) {
|
||||||
if (data.currency == kCreditsCurrency) {
|
const auto stars = (details.byStars && data.stars)
|
||||||
|
? data.stars
|
||||||
|
: (data.currency == kCreditsCurrency)
|
||||||
|
? data.cost
|
||||||
|
: 0;
|
||||||
|
if (stars) {
|
||||||
return CreditsEmojiSmall(session).append(
|
return CreditsEmojiSmall(session).append(
|
||||||
Lang::FormatCountDecimal(std::abs(data.cost)));
|
Lang::FormatCountDecimal(std::abs(stars)));
|
||||||
}
|
}
|
||||||
return TextWithEntities{
|
return TextWithEntities{
|
||||||
FillAmountAndCurrency(data.cost, data.currency),
|
FillAmountAndCurrency(data.cost, data.currency),
|
||||||
|
@ -1462,10 +1508,17 @@ void SendGiftBox(
|
||||||
peer,
|
peer,
|
||||||
state->details.value()));
|
state->details.value()));
|
||||||
|
|
||||||
|
const auto messageWrap = container->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
|
container,
|
||||||
|
object_ptr<Ui::VerticalLayout>(container)));
|
||||||
|
messageWrap->toggleOn(state->messageAllowed.value());
|
||||||
|
messageWrap->finishAnimating();
|
||||||
|
const auto messageInner = messageWrap->entity();
|
||||||
const auto limit = StarGiftMessageLimit(session);
|
const auto limit = StarGiftMessageLimit(session);
|
||||||
const auto text = AddPartInput(
|
const auto text = AddPartInput(
|
||||||
window,
|
window,
|
||||||
container,
|
messageInner,
|
||||||
box->getDelegate()->outerContainer(),
|
box->getDelegate()->outerContainer(),
|
||||||
tr::lng_gift_send_message(),
|
tr::lng_gift_send_message(),
|
||||||
QString(),
|
QString(),
|
||||||
|
@ -1509,7 +1562,6 @@ void SendGiftBox(
|
||||||
text,
|
text,
|
||||||
session,
|
session,
|
||||||
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
|
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
|
||||||
|
|
||||||
if (stars) {
|
if (stars) {
|
||||||
const auto cost = stars->info.starsToUpgrade;
|
const auto cost = stars->info.starsToUpgrade;
|
||||||
if (cost > 0 && !peer->isSelf()) {
|
if (cost > 0 && !peer->isSelf()) {
|
||||||
|
@ -1551,20 +1603,73 @@ void SendGiftBox(
|
||||||
}, container->lifetime());
|
}, container->lifetime());
|
||||||
AddSkip(container);
|
AddSkip(container);
|
||||||
}
|
}
|
||||||
v::match(descriptor, [&](const GiftTypePremium &) {
|
v::match(descriptor, [&](const GiftTypePremium &data) {
|
||||||
AddDividerText(container, tr::lng_gift_send_premium_about(
|
AddDividerText(messageInner, tr::lng_gift_send_premium_about(
|
||||||
lt_user,
|
lt_user,
|
||||||
rpl::single(peer->shortName())));
|
rpl::single(peer->shortName())));
|
||||||
|
|
||||||
|
if (const auto byStars = data.stars) {
|
||||||
|
const auto star = Ui::Text::IconEmoji(&st::starIconEmojiColored);
|
||||||
|
AddSkip(container);
|
||||||
|
container->add(
|
||||||
|
object_ptr<SettingsButton>(
|
||||||
|
container,
|
||||||
|
tr::lng_gift_send_pay_with_stars(
|
||||||
|
lt_amount,
|
||||||
|
rpl::single(base::duplicate(star).append(Lang::FormatCountDecimal(byStars))),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
st::settingsButtonNoIcon)
|
||||||
|
)->toggleOn(rpl::single(false))->toggledValue(
|
||||||
|
) | rpl::start_with_next([=](bool toggled) {
|
||||||
|
auto now = state->details.current();
|
||||||
|
now.byStars = toggled;
|
||||||
|
state->details = std::move(now);
|
||||||
|
}, container->lifetime());
|
||||||
|
AddSkip(container);
|
||||||
|
|
||||||
|
const auto balance = AddDividerText(
|
||||||
|
container,
|
||||||
|
tr::lng_gift_send_stars_balance(
|
||||||
|
lt_amount,
|
||||||
|
peer->session().credits().balanceValue(
|
||||||
|
) | rpl::map([=](StarsAmount amount) {
|
||||||
|
return base::duplicate(star).append(
|
||||||
|
Lang::FormatStarsAmountDecimal(amount));
|
||||||
|
}),
|
||||||
|
lt_link,
|
||||||
|
tr::lng_gift_send_stars_balance_link(
|
||||||
|
) | Ui::Text::ToLink(),
|
||||||
|
Ui::Text::WithEntities));
|
||||||
|
struct State {
|
||||||
|
Settings::BuyStarsHandler buyStars;
|
||||||
|
rpl::variable<bool> loading;
|
||||||
|
};
|
||||||
|
const auto state = balance->lifetime().make_state<State>();
|
||||||
|
state->loading = state->buyStars.loadingValue();
|
||||||
|
balance->setClickHandlerFilter([=](const auto &...) {
|
||||||
|
if (!state->loading.current()) {
|
||||||
|
state->buyStars.handler(window->uiShow())();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [&](const GiftTypeStars &) {
|
}, [&](const GiftTypeStars &) {
|
||||||
AddDividerText(container, peer->isSelf()
|
AddDividerText(container, peer->isSelf()
|
||||||
? tr::lng_gift_send_anonymous_self()
|
? tr::lng_gift_send_anonymous_self()
|
||||||
: peer->isBroadcast()
|
: peer->isBroadcast()
|
||||||
? tr::lng_gift_send_anonymous_about_channel()
|
? tr::lng_gift_send_anonymous_about_channel()
|
||||||
: tr::lng_gift_send_anonymous_about(
|
: rpl::conditional(
|
||||||
lt_user,
|
state->messageAllowed.value(),
|
||||||
rpl::single(peer->shortName()),
|
tr::lng_gift_send_anonymous_about(
|
||||||
lt_recipient,
|
lt_user,
|
||||||
rpl::single(peer->shortName())));
|
rpl::single(peer->shortName()),
|
||||||
|
lt_recipient,
|
||||||
|
rpl::single(peer->shortName())),
|
||||||
|
tr::lng_gift_send_anonymous_about_paid(
|
||||||
|
lt_user,
|
||||||
|
rpl::single(peer->shortName()),
|
||||||
|
lt_recipient,
|
||||||
|
rpl::single(peer->shortName()))));
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto buttonWidth = st::boxWideWidth
|
const auto buttonWidth = st::boxWideWidth
|
||||||
|
@ -1575,13 +1680,20 @@ void SendGiftBox(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state->submitting = true;
|
state->submitting = true;
|
||||||
const auto details = state->details.current();
|
auto details = state->details.current();
|
||||||
|
if (!state->messageAllowed.current()) {
|
||||||
|
details.text = {};
|
||||||
|
}
|
||||||
const auto weak = MakeWeak(box);
|
const auto weak = MakeWeak(box);
|
||||||
const auto done = [=](Payments::CheckoutResult result) {
|
const auto done = [=](Payments::CheckoutResult result) {
|
||||||
if (result == Payments::CheckoutResult::Paid) {
|
if (result == Payments::CheckoutResult::Paid) {
|
||||||
|
if (details.byStars
|
||||||
|
|| v::is<GiftTypeStars>(details.descriptor)) {
|
||||||
|
window->session().credits().load(true);
|
||||||
|
}
|
||||||
const auto copy = state->media;
|
const auto copy = state->media;
|
||||||
window->showPeerHistory(peer);
|
window->showPeerHistory(peer);
|
||||||
ShowSentToast(window, descriptor, details);
|
ShowSentToast(window, details.descriptor, details);
|
||||||
}
|
}
|
||||||
if (const auto strong = weak.data()) {
|
if (const auto strong = weak.data()) {
|
||||||
box->closeBox();
|
box->closeBox();
|
||||||
|
@ -1814,6 +1926,8 @@ void GiftBox(
|
||||||
box->setCustomCornersFilling(RectPart::FullTop);
|
box->setCustomCornersFilling(RectPart::FullTop);
|
||||||
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
|
||||||
|
|
||||||
|
window->session().credits().load();
|
||||||
|
|
||||||
FillBg(box);
|
FillBg(box);
|
||||||
|
|
||||||
const auto &stUser = st::premiumGiftsUserpicButton;
|
const auto &stUser = st::premiumGiftsUserpicButton;
|
||||||
|
@ -2070,7 +2184,66 @@ void ChooseStarGiftRecipient(
|
||||||
void ShowStarGiftBox(
|
void ShowStarGiftBox(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<PeerData*> peer) {
|
not_null<PeerData*> peer) {
|
||||||
controller->show(Box(GiftBox, controller, peer));
|
struct Session {
|
||||||
|
PeerData *peer = nullptr;
|
||||||
|
bool premiumGiftsReady = false;
|
||||||
|
bool starsGiftsReady = false;
|
||||||
|
rpl::lifetime lifetime;
|
||||||
|
};
|
||||||
|
static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
|
||||||
|
|
||||||
|
const auto session = &controller->session();
|
||||||
|
auto i = Map.find(session);
|
||||||
|
if (i == end(Map)) {
|
||||||
|
i = Map.emplace(session).first;
|
||||||
|
session->lifetime().add([=] { Map.remove(session); });
|
||||||
|
} else if (i->second.peer == peer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i->second = Session{ .peer = peer };
|
||||||
|
|
||||||
|
const auto weak = base::make_weak(controller);
|
||||||
|
const auto show = [=] {
|
||||||
|
Map[session] = Session();
|
||||||
|
if (const auto strong = weak.get()) {
|
||||||
|
strong->show(Box(GiftBox, strong, peer));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
base::timer_once(
|
||||||
|
kGiftsPreloadTimeout
|
||||||
|
) | rpl::start_with_next(show, i->second.lifetime);
|
||||||
|
|
||||||
|
const auto user = peer->asUser();
|
||||||
|
if (user && !user->isSelf()) {
|
||||||
|
GiftsPremium(
|
||||||
|
session,
|
||||||
|
peer
|
||||||
|
) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) {
|
||||||
|
if (!gifts.list.empty()) {
|
||||||
|
auto &entry = Map[session];
|
||||||
|
entry.premiumGiftsReady = true;
|
||||||
|
if (entry.starsGiftsReady) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, i->second.lifetime);
|
||||||
|
} else {
|
||||||
|
i->second.premiumGiftsReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GiftsStars(
|
||||||
|
session,
|
||||||
|
peer
|
||||||
|
) | rpl::start_with_next([=](std::vector<GiftTypeStars> &&gifts) {
|
||||||
|
if (!gifts.empty()) {
|
||||||
|
auto &entry = Map[session];
|
||||||
|
entry.starsGiftsReady = true;
|
||||||
|
if (entry.premiumGiftsReady) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, i->second.lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddUniqueGiftCover(
|
void AddUniqueGiftCover(
|
||||||
|
|
|
@ -771,30 +771,43 @@ void StickerSetBox::updateButtons() {
|
||||||
const auto addPackOwner = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>> &menu)
|
const auto addPackOwner = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>> &menu)
|
||||||
{
|
{
|
||||||
if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) {
|
if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) {
|
||||||
const auto pointer = Ui::MakeWeak(this);
|
const auto weak = Ui::MakeWeak(this);
|
||||||
|
const auto session = _session;
|
||||||
|
const auto innerId = _inner->setId() >> 32;
|
||||||
|
|
||||||
(*menu)->addAction(
|
(*menu)->addAction(
|
||||||
tr::ayu_MessageDetailsPackOwnerPC(tr::now),
|
tr::ayu_MessageDetailsPackOwnerPC(tr::now),
|
||||||
[=]
|
[weak, session, innerId]
|
||||||
{
|
{
|
||||||
if (!pointer) {
|
if (!weak) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto strong = weak.data();
|
||||||
|
if (!strong) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchById(
|
searchById(
|
||||||
_inner->setId() >> 32,
|
innerId,
|
||||||
_session,
|
session,
|
||||||
[=](const QString &username, UserData *user)
|
[session, weak](const QString &username, UserData *user)
|
||||||
{
|
{
|
||||||
if (!pointer) {
|
if (!weak) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto strongInner = weak.data();
|
||||||
|
if (!strongInner) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
showToast(tr::ayu_UserNotFoundMessage(tr::now));
|
strongInner->showToast(tr::ayu_UserNotFoundMessage(tr::now));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto window = _session->tryResolveWindow()) {
|
if (const auto window = session->tryResolveWindow()) {
|
||||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||||
mainWidget->showPeer(user);
|
mainWidget->showPeer(user);
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,10 +150,7 @@ void TranslateBox(
|
||||||
original->entity()->setAnimationsPausedCallback(animationsPaused);
|
original->entity()->setAnimationsPausedCallback(animationsPaused);
|
||||||
original->entity()->setMarkedText(
|
original->entity()->setMarkedText(
|
||||||
text,
|
text,
|
||||||
Core::MarkedTextContext{
|
Core::TextContext({ .session = &peer->session() }));
|
||||||
.session = &peer->session(),
|
|
||||||
.customEmojiRepaint = [=] { original->entity()->update(); },
|
|
||||||
});
|
|
||||||
original->setMinimalHeight(lineHeight);
|
original->setMinimalHeight(lineHeight);
|
||||||
original->hide(anim::type::instant);
|
original->hide(anim::type::instant);
|
||||||
|
|
||||||
|
@ -221,10 +218,7 @@ void TranslateBox(
|
||||||
const auto label = translated->entity();
|
const auto label = translated->entity();
|
||||||
label->setMarkedText(
|
label->setMarkedText(
|
||||||
text,
|
text,
|
||||||
Core::MarkedTextContext{
|
Core::TextContext({ .session = &peer->session() }));
|
||||||
.session = &peer->session(),
|
|
||||||
.customEmojiRepaint = [=] { label->update(); },
|
|
||||||
});
|
|
||||||
translated->show(anim::type::instant);
|
translated->show(anim::type::instant);
|
||||||
loading->hide(anim::type::instant);
|
loading->hide(anim::type::instant);
|
||||||
};
|
};
|
||||||
|
|
|
@ -132,8 +132,12 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||||
QGuiApplication::clipboard()->setText(currentLink());
|
QGuiApplication::clipboard()->setText(currentLink());
|
||||||
show->showToast(tr::lng_group_invite_copied(tr::now));
|
show->showToast(tr::lng_group_invite_copied(tr::now));
|
||||||
};
|
};
|
||||||
|
auto countMessagesCallback = [=](const TextWithTags &comment) {
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
auto submitCallback = [=](
|
auto submitCallback = [=](
|
||||||
std::vector<not_null<Data::Thread*>> &&result,
|
std::vector<not_null<Data::Thread*>> &&result,
|
||||||
|
Fn<bool()> checkPaid,
|
||||||
TextWithTags &&comment,
|
TextWithTags &&comment,
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
Data::ForwardOptions) {
|
Data::ForwardOptions) {
|
||||||
|
@ -150,6 +154,8 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||||
MakeSendErrorBox(error, result.size() > 1));
|
MakeSendErrorBox(error, result.size() > 1));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
} else if (!checkPaid()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
*sending = true;
|
*sending = true;
|
||||||
|
@ -178,7 +184,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||||
};
|
};
|
||||||
auto filterCallback = [](not_null<Data::Thread*> thread) {
|
auto filterCallback = [](not_null<Data::Thread*> thread) {
|
||||||
if (const auto user = thread->peer()->asUser()) {
|
if (const auto user = thread->peer()->asUser()) {
|
||||||
if (user->canSendIgnoreRequirePremium()) {
|
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,6 +195,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||||
auto result = Box<ShareBox>(ShareBox::Descriptor{
|
auto result = Box<ShareBox>(ShareBox::Descriptor{
|
||||||
.session = &peer->session(),
|
.session = &peer->session(),
|
||||||
.copyCallback = std::move(copyCallback),
|
.copyCallback = std::move(copyCallback),
|
||||||
|
.countMessagesCallback = std::move(countMessagesCallback),
|
||||||
.submitCallback = std::move(submitCallback),
|
.submitCallback = std::move(submitCallback),
|
||||||
.filterCallback = std::move(filterCallback),
|
.filterCallback = std::move(filterCallback),
|
||||||
.bottomWidget = std::move(bottom),
|
.bottomWidget = std::move(bottom),
|
||||||
|
@ -199,7 +206,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||||
tr::lng_group_call_copy_speaker_link(),
|
tr::lng_group_call_copy_speaker_link(),
|
||||||
tr::lng_group_call_copy_listener_link()),
|
tr::lng_group_call_copy_listener_link()),
|
||||||
.st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
|
.st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
|
||||||
.premiumRequiredError = SharePremiumRequiredError(),
|
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||||
});
|
});
|
||||||
*box = result.data();
|
*box = result.data();
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -149,6 +149,7 @@ EmojiButton {
|
||||||
|
|
||||||
SendButton {
|
SendButton {
|
||||||
inner: IconButton;
|
inner: IconButton;
|
||||||
|
stars: RoundButton;
|
||||||
record: icon;
|
record: icon;
|
||||||
recordOver: icon;
|
recordOver: icon;
|
||||||
round: icon;
|
round: icon;
|
||||||
|
@ -855,6 +856,10 @@ historyComposeButton: FlatButton {
|
||||||
color: historyComposeButtonBgRipple;
|
color: historyComposeButtonBgRipple;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
historyComposeButtonText: FlatLabel(defaultFlatLabel) {
|
||||||
|
style: semiboldTextStyle;
|
||||||
|
textFg: windowActiveTextFg;
|
||||||
|
}
|
||||||
historyGiftToChannel: IconButton(defaultIconButton) {
|
historyGiftToChannel: IconButton(defaultIconButton) {
|
||||||
width: 46px;
|
width: 46px;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
|
@ -913,6 +918,10 @@ historyBusinessBotSettings: IconButton(defaultIconButton) {
|
||||||
height: 58px;
|
height: 58px;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
}
|
}
|
||||||
|
paysStatusLabel: FlatLabel(historyBusinessBotStatus) {
|
||||||
|
align: align(top);
|
||||||
|
minWidth: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }};
|
historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }};
|
||||||
historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }};
|
historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }};
|
||||||
|
@ -1289,6 +1298,12 @@ historySend: SendButton {
|
||||||
icon: historySendIcon;
|
icon: historySendIcon;
|
||||||
iconOver: historySendIconOver;
|
iconOver: historySendIconOver;
|
||||||
}
|
}
|
||||||
|
stars: RoundButton(defaultActiveButton) {
|
||||||
|
height: 28px;
|
||||||
|
padding: margins(0px, 0px, 6px, 0px);
|
||||||
|
textTop: 5px;
|
||||||
|
width: -8px;
|
||||||
|
}
|
||||||
record: historyRecordVoice;
|
record: historyRecordVoice;
|
||||||
recordOver: historyRecordVoiceOver;
|
recordOver: historyRecordVoiceOver;
|
||||||
round: historyRecordRound;
|
round: historyRecordRound;
|
||||||
|
|
|
@ -1747,7 +1747,8 @@ void InitFieldAutocomplete(
|
||||||
&& peer->isUser()
|
&& peer->isUser()
|
||||||
&& !peer->asUser()->isBot()
|
&& !peer->asUser()->isBot()
|
||||||
&& (!shortcutMessages
|
&& (!shortcutMessages
|
||||||
|| shortcutMessages->shortcuts().list.empty())) {
|
|| shortcutMessages->shortcuts().list.empty()
|
||||||
|
|| peer->starsPerMessageChecked() != 0)) {
|
||||||
parsed = {};
|
parsed = {};
|
||||||
}
|
}
|
||||||
raw->showFiltered(peer, parsed.query, parsed.fromStart);
|
raw->showFiltered(peer, parsed.query, parsed.fromStart);
|
||||||
|
|
|
@ -695,14 +695,15 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(
|
||||||
}
|
}
|
||||||
|
|
||||||
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(
|
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(
|
||||||
not_null<InlineResult*> result) {
|
std::shared_ptr<InlineResult> result) {
|
||||||
auto it = _inlineLayouts.find(result);
|
const auto raw = result.get();
|
||||||
|
auto it = _inlineLayouts.find(raw);
|
||||||
if (it == _inlineLayouts.cend()) {
|
if (it == _inlineLayouts.cend()) {
|
||||||
if (auto layout = LayoutItem::createLayout(
|
if (auto layout = LayoutItem::createLayout(
|
||||||
this,
|
this,
|
||||||
result,
|
std::move(result),
|
||||||
_inlineWithThumb)) {
|
_inlineWithThumb)) {
|
||||||
it = _inlineLayouts.emplace(result, std::move(layout)).first;
|
it = _inlineLayouts.emplace(raw, std::move(layout)).first;
|
||||||
it->second->initDimensions();
|
it->second->initDimensions();
|
||||||
} else {
|
} else {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -775,8 +776,8 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result
|
||||||
from,
|
from,
|
||||||
count
|
count
|
||||||
) | ranges::views::transform([&](
|
) | ranges::views::transform([&](
|
||||||
const std::unique_ptr<InlineBots::Result> &r) {
|
const std::shared_ptr<InlineBots::Result> &r) {
|
||||||
return layoutPrepareInlineResult(r.get());
|
return layoutPrepareInlineResult(r);
|
||||||
}) | ranges::views::filter([](const LayoutItem *item) {
|
}) | ranges::views::filter([](const LayoutItem *item) {
|
||||||
return item != nullptr;
|
return item != nullptr;
|
||||||
}) | ranges::to<std::vector<not_null<LayoutItem*>>>;
|
}) | ranges::to<std::vector<not_null<LayoutItem*>>>;
|
||||||
|
@ -799,7 +800,7 @@ int GifsListWidget::validateExistingInlineRows(const InlineResults &results) {
|
||||||
const auto until = _mosaic.validateExistingRows([&](
|
const auto until = _mosaic.validateExistingRows([&](
|
||||||
not_null<const LayoutItem*> item,
|
not_null<const LayoutItem*> item,
|
||||||
int untilIndex) {
|
int untilIndex) {
|
||||||
return item->getResult() != results[untilIndex].get();
|
return item->getResult().get() != results[untilIndex].get();
|
||||||
}, results.size());
|
}, results.size());
|
||||||
|
|
||||||
if (_mosaic.empty()) {
|
if (_mosaic.empty()) {
|
||||||
|
|
|
@ -131,7 +131,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
using InlineResult = InlineBots::Result;
|
using InlineResult = InlineBots::Result;
|
||||||
using InlineResults = std::vector<std::unique_ptr<InlineResult>>;
|
using InlineResults = std::vector<std::shared_ptr<InlineResult>>;
|
||||||
using LayoutItem = InlineBots::Layout::ItemBase;
|
using LayoutItem = InlineBots::Layout::ItemBase;
|
||||||
|
|
||||||
struct InlineCacheEntry {
|
struct InlineCacheEntry {
|
||||||
|
@ -162,7 +162,8 @@ private:
|
||||||
|
|
||||||
void clearInlineRows(bool resultsDeleted);
|
void clearInlineRows(bool resultsDeleted);
|
||||||
LayoutItem *layoutPrepareSavedGif(not_null<DocumentData*> document);
|
LayoutItem *layoutPrepareSavedGif(not_null<DocumentData*> document);
|
||||||
LayoutItem *layoutPrepareInlineResult(not_null<InlineResult*> result);
|
LayoutItem *layoutPrepareInlineResult(
|
||||||
|
std::shared_ptr<InlineResult> result);
|
||||||
|
|
||||||
void deleteUnusedGifLayouts();
|
void deleteUnusedGifLayouts();
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
#include "styles/style_credits.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
#include "base/qt/qt_common_adapters.h"
|
#include "base/qt/qt_common_adapters.h"
|
||||||
|
|
||||||
|
@ -432,12 +433,9 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
|
||||||
const auto session = args.session;
|
const auto session = args.session;
|
||||||
field->setTagMimeProcessor(
|
field->setTagMimeProcessor(
|
||||||
FieldTagMimeProcessor(session, args.allowPremiumEmoji));
|
FieldTagMimeProcessor(session, args.allowPremiumEmoji));
|
||||||
field->setCustomTextContext([=](Fn<void()> repaint) {
|
field->setCustomTextContext(Core::TextContext({
|
||||||
return std::any(Core::MarkedTextContext{
|
.session = session
|
||||||
.session = session,
|
}), [paused] {
|
||||||
.customEmojiRepaint = std::move(repaint),
|
|
||||||
});
|
|
||||||
}, [paused] {
|
|
||||||
return On(PowerSaving::kEmojiChat) || paused();
|
return On(PowerSaving::kEmojiChat) || paused();
|
||||||
}, [paused] {
|
}, [paused] {
|
||||||
return On(PowerSaving::kChatSpoiler) || paused();
|
return On(PowerSaving::kChatSpoiler) || paused();
|
||||||
|
@ -1280,3 +1278,26 @@ void SelectTextInFieldWithMargins(
|
||||||
textCursor.setPosition(selection.to, QTextCursor::KeepAnchor);
|
textCursor.setPosition(selection.to, QTextCursor::KeepAnchor);
|
||||||
field->setTextCursor(textCursor);
|
field->setTextCursor(textCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextWithEntities PaidSendButtonText(tr::now_t, int stars) {
|
||||||
|
return Ui::Text::IconEmoji(&st::starIconEmoji).append(
|
||||||
|
Lang::FormatCountToShort(stars).string);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<TextWithEntities> PaidSendButtonText(
|
||||||
|
rpl::producer<int> stars,
|
||||||
|
rpl::producer<QString> fallback) {
|
||||||
|
if (fallback) {
|
||||||
|
return rpl::combine(
|
||||||
|
std::move(fallback),
|
||||||
|
std::move(stars)
|
||||||
|
) | rpl::map([=](QString zero, int count) {
|
||||||
|
return count
|
||||||
|
? PaidSendButtonText(tr::now, count)
|
||||||
|
: TextWithEntities{ zero };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return std::move(stars) | rpl::map([=](int count) {
|
||||||
|
return PaidSendButtonText(tr::now, count);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include <QtGui/QClipboard>
|
#include <QtGui/QClipboard>
|
||||||
|
|
||||||
|
namespace tr {
|
||||||
|
struct now_t;
|
||||||
|
} // namespace tr
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
class SessionShow;
|
class SessionShow;
|
||||||
|
@ -169,3 +173,8 @@ private:
|
||||||
void SelectTextInFieldWithMargins(
|
void SelectTextInFieldWithMargins(
|
||||||
not_null<Ui::InputField*> field,
|
not_null<Ui::InputField*> field,
|
||||||
const TextSelection &selection);
|
const TextSelection &selection);
|
||||||
|
|
||||||
|
[[nodiscard]] TextWithEntities PaidSendButtonText(tr::now_t, int stars);
|
||||||
|
[[nodiscard]] rpl::producer<TextWithEntities> PaidSendButtonText(
|
||||||
|
rpl::producer<int> stars,
|
||||||
|
rpl::producer<QString> fallback = nullptr);
|
||||||
|
|
|
@ -37,7 +37,7 @@ std::map<int, const char*> BetaLogs() {
|
||||||
"- Nice looking code blocks with syntax highlight.\n"
|
"- Nice looking code blocks with syntax highlight.\n"
|
||||||
|
|
||||||
"- Copy full code block by click on its header.\n"
|
"- Copy full code block by click on its header.\n"
|
||||||
|
|
||||||
"- Send a highlighted code block using ```language syntax.\n"
|
"- Send a highlighted code block using ```language syntax.\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -205,13 +205,13 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if (_bot->isVerified()
|
if (_bot->isVerified()
|
||||||
|| _bot->session().local().isBotTrustedOpenGame(_bot->id)) {
|
|| _bot->session().local().isPeerTrustedOpenGame(_bot->id)) {
|
||||||
openGame();
|
openGame();
|
||||||
} else {
|
} else {
|
||||||
if (const auto controller = my.sessionWindow.get()) {
|
if (const auto controller = my.sessionWindow.get()) {
|
||||||
const auto callback = [=, bot = _bot](Fn<void()> close) {
|
const auto callback = [=, bot = _bot](Fn<void()> close) {
|
||||||
close();
|
close();
|
||||||
bot->session().local().markBotTrustedOpenGame(bot->id);
|
bot->session().local().markPeerTrustedOpenGame(bot->id);
|
||||||
openGame();
|
openGame();
|
||||||
};
|
};
|
||||||
controller->show(Ui::MakeConfirmBox({
|
controller->show(Ui::MakeConfirmBox({
|
||||||
|
|
|
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/boxes/edit_birthday_box.h"
|
#include "ui/boxes/edit_birthday_box.h"
|
||||||
#include "ui/integration.h"
|
#include "ui/integration.h"
|
||||||
#include "payments/payments_non_panel_process.h"
|
#include "payments/payments_non_panel_process.h"
|
||||||
|
#include "boxes/peers/edit_peer_info_box.h"
|
||||||
#include "boxes/share_box.h"
|
#include "boxes/share_box.h"
|
||||||
#include "boxes/connection_box.h"
|
#include "boxes/connection_box.h"
|
||||||
#include "boxes/gift_premium_box.h"
|
#include "boxes/gift_premium_box.h"
|
||||||
|
@ -68,6 +69,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_domain.h"
|
#include "main/main_domain.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "main/main_session_settings.h"
|
#include "main/main_session_settings.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
#include "info/info_memento.h"
|
||||||
#include "inline_bots/bot_attach_web_view.h"
|
#include "inline_bots/bot_attach_web_view.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
@ -1018,6 +1021,45 @@ bool CopyUsername(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EditPaidMessagesFee(
|
||||||
|
Window::SessionController *controller,
|
||||||
|
const Match &match,
|
||||||
|
const QVariant &context) {
|
||||||
|
if (!controller) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto peerId = PeerId(match->captured(1).toULongLong());
|
||||||
|
if (const auto id = peerToChannel(peerId)) {
|
||||||
|
const auto channel = controller->session().data().channelLoaded(id);
|
||||||
|
if (channel && channel->canEditPermissions()) {
|
||||||
|
ShowEditChatPermissions(controller, channel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
controller->show(Box(EditMessagesPrivacyBox, controller));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShowCommonGroups(
|
||||||
|
Window::SessionController *controller,
|
||||||
|
const Match &match,
|
||||||
|
const QVariant &context) {
|
||||||
|
if (!controller) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto peerId = PeerId(match->captured(1).toULongLong());
|
||||||
|
if (const auto id = peerToUser(peerId)) {
|
||||||
|
const auto user = controller->session().data().userLoaded(id);
|
||||||
|
if (user) {
|
||||||
|
controller->showSection(
|
||||||
|
std::make_shared<Info::Memento>(
|
||||||
|
user,
|
||||||
|
Info::Section::Type::CommonGroups));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ShowStarsExamples(
|
bool ShowStarsExamples(
|
||||||
Window::SessionController *controller,
|
Window::SessionController *controller,
|
||||||
const Match &match,
|
const Match &match,
|
||||||
|
@ -1529,6 +1571,14 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||||
u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||||
CopyUsername,
|
CopyUsername,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
u"^edit_paid_messages_fee/([0-9]+)$"_q,
|
||||||
|
EditPaidMessagesFee,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
u"^common_groups/([0-9]+)$"_q,
|
||||||
|
ShowCommonGroups,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
u"^stars_examples$"_q,
|
u"^stars_examples$"_q,
|
||||||
ShowStarsExamples,
|
ShowStarsExamples,
|
||||||
|
|
|
@ -127,16 +127,30 @@ ResolvePhoneAction::ResolvePhoneAction(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchById(possibleId,
|
const auto weak = Ui::MakeWeak(this);
|
||||||
&controller->session(),
|
const auto session = &controller->session();
|
||||||
[=](const QString &username, UserData *user)
|
|
||||||
{
|
|
||||||
if (user) {
|
|
||||||
_peer = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
_loaded.force_assign(true);
|
searchById(
|
||||||
});
|
possibleId,
|
||||||
|
session,
|
||||||
|
[session, weak, possibleId](const QString &username, UserData *user)
|
||||||
|
{
|
||||||
|
if (!weak) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto strong = weak.data();
|
||||||
|
if (!strong) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
strong->_peer = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong->_loaded.force_assign(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,14 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
||||||
{ u"first_chat"_q , Command::ChatFirst },
|
{ u"first_chat"_q , Command::ChatFirst },
|
||||||
{ u"last_chat"_q , Command::ChatLast },
|
{ u"last_chat"_q , Command::ChatLast },
|
||||||
{ u"self_chat"_q , Command::ChatSelf },
|
{ u"self_chat"_q , Command::ChatSelf },
|
||||||
|
{ u"pinned_chat1"_q , Command::ChatPinned1 },
|
||||||
|
{ u"pinned_chat2"_q , Command::ChatPinned2 },
|
||||||
|
{ u"pinned_chat3"_q , Command::ChatPinned3 },
|
||||||
|
{ u"pinned_chat4"_q , Command::ChatPinned4 },
|
||||||
|
{ u"pinned_chat5"_q , Command::ChatPinned5 },
|
||||||
|
{ u"pinned_chat6"_q , Command::ChatPinned6 },
|
||||||
|
{ u"pinned_chat7"_q , Command::ChatPinned7 },
|
||||||
|
{ u"pinned_chat8"_q , Command::ChatPinned8 },
|
||||||
|
|
||||||
{ u"previous_folder"_q , Command::FolderPrevious },
|
{ u"previous_folder"_q , Command::FolderPrevious },
|
||||||
{ u"next_folder"_q , Command::FolderNext },
|
{ u"next_folder"_q , Command::FolderNext },
|
||||||
|
@ -168,6 +176,7 @@ private:
|
||||||
void set(const QKeySequence &result, Command command, bool replace);
|
void set(const QKeySequence &result, Command command, bool replace);
|
||||||
void remove(const QString &keys);
|
void remove(const QString &keys);
|
||||||
void remove(const QKeySequence &keys);
|
void remove(const QKeySequence &keys);
|
||||||
|
void remove(const QKeySequence &keys, Command command);
|
||||||
void unregister(base::unique_qptr<QAction> shortcut);
|
void unregister(base::unique_qptr<QAction> shortcut);
|
||||||
|
|
||||||
void pruneListened();
|
void pruneListened();
|
||||||
|
@ -293,7 +302,7 @@ void Manager::change(
|
||||||
Command command,
|
Command command,
|
||||||
std::optional<Command> restore) {
|
std::optional<Command> restore) {
|
||||||
if (!was.isEmpty()) {
|
if (!was.isEmpty()) {
|
||||||
remove(was);
|
remove(was, command);
|
||||||
}
|
}
|
||||||
if (!now.isEmpty()) {
|
if (!now.isEmpty()) {
|
||||||
set(now, command, true);
|
set(now, command, true);
|
||||||
|
@ -397,6 +406,7 @@ bool Manager::readCustomFile() {
|
||||||
const auto entry = (*i).toObject();
|
const auto entry = (*i).toObject();
|
||||||
const auto keys = entry.constFind(u"keys"_q);
|
const auto keys = entry.constFind(u"keys"_q);
|
||||||
const auto command = entry.constFind(u"command"_q);
|
const auto command = entry.constFind(u"command"_q);
|
||||||
|
const auto removed = entry.constFind(u"removed"_q);
|
||||||
if (keys == entry.constEnd()
|
if (keys == entry.constEnd()
|
||||||
|| command == entry.constEnd()
|
|| command == entry.constEnd()
|
||||||
|| !(*keys).isString()
|
|| !(*keys).isString()
|
||||||
|
@ -410,7 +420,11 @@ bool Manager::readCustomFile() {
|
||||||
const auto name = (*command).toString();
|
const auto name = (*command).toString();
|
||||||
const auto i = CommandByName.find(name);
|
const auto i = CommandByName.find(name);
|
||||||
if (i != end(CommandByName)) {
|
if (i != end(CommandByName)) {
|
||||||
set((*keys).toString(), i->second, true);
|
if (removed != entry.constEnd() && removed->toBool()) {
|
||||||
|
remove((*keys).toString(), i->second);
|
||||||
|
} else {
|
||||||
|
set((*keys).toString(), i->second, true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG(("Shortcut Warning: "
|
LOG(("Shortcut Warning: "
|
||||||
"could not find shortcut command handler '%1'"
|
"could not find shortcut command handler '%1'"
|
||||||
|
@ -565,12 +579,36 @@ void Manager::writeCustomFile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto &[sequence, command] : _defaults) {
|
const auto has = [&](not_null<QObject*> shortcut, Command command) {
|
||||||
if (!_shortcuts.contains(sequence)) {
|
for (auto i = _commandByObject.findFirst(shortcut)
|
||||||
|
; i != end(_commandByObject) && i->first == shortcut
|
||||||
|
; ++i) {
|
||||||
|
if (i->second == command) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
for (const auto &[sequence, commands] : _defaults) {
|
||||||
|
const auto i = _shortcuts.find(sequence);
|
||||||
|
if (i == end(_shortcuts)) {
|
||||||
QJsonObject entry;
|
QJsonObject entry;
|
||||||
entry.insert(u"keys"_q, sequence.toString().toLower());
|
entry.insert(u"keys"_q, sequence.toString().toLower());
|
||||||
entry.insert(u"command"_q, QJsonValue());
|
entry.insert(u"command"_q, QJsonValue());
|
||||||
shortcuts.append(entry);
|
shortcuts.append(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const auto command : commands) {
|
||||||
|
if (!has(i->second.get(), command)) {
|
||||||
|
const auto j = CommandNames().find(command);
|
||||||
|
if (j != CommandNames().end()) {
|
||||||
|
QJsonObject entry;
|
||||||
|
entry.insert(u"keys"_q, sequence.toString().toLower());
|
||||||
|
entry.insert(u"command"_q, j->second);
|
||||||
|
entry.insert(u"removed"_q, true);
|
||||||
|
shortcuts.append(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,6 +707,17 @@ void Manager::remove(const QKeySequence &keys) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Manager::remove(const QKeySequence &keys, Command command) {
|
||||||
|
const auto i = _shortcuts.find(keys);
|
||||||
|
if (i != end(_shortcuts)) {
|
||||||
|
_commandByObject.remove(i->second.get(), command);
|
||||||
|
if (!_commandByObject.contains(i->second.get())) {
|
||||||
|
unregister(std::move(i->second));
|
||||||
|
_shortcuts.erase(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Manager::unregister(base::unique_qptr<QAction> shortcut) {
|
void Manager::unregister(base::unique_qptr<QAction> shortcut) {
|
||||||
if (shortcut) {
|
if (shortcut) {
|
||||||
_commandByObject.removeAll(shortcut.get());
|
_commandByObject.removeAll(shortcut.get());
|
||||||
|
|
|
@ -60,6 +60,11 @@ public:
|
||||||
normalize();
|
normalize();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
inline StarsAmount operator-() const {
|
||||||
|
auto result = *this;
|
||||||
|
result *= -1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
friend inline auto operator<=>(StarsAmount, StarsAmount) = default;
|
friend inline auto operator<=>(StarsAmount, StarsAmount) = default;
|
||||||
friend inline bool operator==(StarsAmount, StarsAmount) = default;
|
friend inline bool operator==(StarsAmount, StarsAmount) = default;
|
||||||
|
@ -97,3 +102,7 @@ private:
|
||||||
[[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) {
|
[[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) {
|
||||||
return a *= b;
|
return a *= b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline StarsAmount operator*(int64 a, StarsAmount b) {
|
||||||
|
return b *= a;
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "iv/iv_instance.h"
|
#include "iv/iv_instance.h"
|
||||||
#include "ui/text/text_custom_emoji.h"
|
#include "ui/text/text_custom_emoji.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/basic_click_handlers.h"
|
#include "ui/basic_click_handlers.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
@ -112,6 +113,40 @@ const auto kBadPrefix = u"http://"_q;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
Ui::Text::MarkedContext TextContext(TextContextArgs &&args) {
|
||||||
|
using Context = Ui::Text::MarkedContext;
|
||||||
|
using Factory = Ui::Text::CustomEmojiFactory;
|
||||||
|
|
||||||
|
const auto session = args.session;
|
||||||
|
auto simple = [session](QStringView data, const Context &context) {
|
||||||
|
return session->data().customEmojiManager().create(
|
||||||
|
data,
|
||||||
|
context.repaint);
|
||||||
|
};
|
||||||
|
auto factory = !args.customEmojiLoopLimit
|
||||||
|
? Factory(simple)
|
||||||
|
: (args.customEmojiLoopLimit > 0)
|
||||||
|
? Factory([simple, loop = args.customEmojiLoopLimit](
|
||||||
|
QStringView data,
|
||||||
|
const Context &context) {
|
||||||
|
return std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||||
|
simple(data, context),
|
||||||
|
loop);
|
||||||
|
})
|
||||||
|
: Factory([simple](
|
||||||
|
QStringView data,
|
||||||
|
const Context &context) {
|
||||||
|
return std::make_unique<Ui::Text::FirstFrameEmoji>(
|
||||||
|
simple(data, context));
|
||||||
|
});
|
||||||
|
args.details.session = session;
|
||||||
|
return {
|
||||||
|
.repaint = std::move(args.repaint),
|
||||||
|
.customEmojiFactory = std::move(factory),
|
||||||
|
.other = std::move(args.details),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void UiIntegration::postponeCall(FnMut<void()> &&callable) {
|
void UiIntegration::postponeCall(FnMut<void()> &&callable) {
|
||||||
Sandbox::Instance().postponeCall(std::move(callable));
|
Sandbox::Instance().postponeCall(std::move(callable));
|
||||||
}
|
}
|
||||||
|
@ -152,8 +187,8 @@ bool UiIntegration::screenIsLocked() {
|
||||||
|
|
||||||
std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
||||||
const EntityLinkData &data,
|
const EntityLinkData &data,
|
||||||
const std::any &context) {
|
const Ui::Text::MarkedContext &context) {
|
||||||
const auto my = std::any_cast<MarkedTextContext>(&context);
|
const auto my = std::any_cast<Core::TextContextDetails>(&context.other);
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case EntityType::Url:
|
case EntityType::Url:
|
||||||
return (!data.data.isEmpty()
|
return (!data.data.isEmpty()
|
||||||
|
@ -170,7 +205,7 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
||||||
return std::make_shared<BotCommandClickHandler>(data.data);
|
return std::make_shared<BotCommandClickHandler>(data.data);
|
||||||
|
|
||||||
case EntityType::Hashtag:
|
case EntityType::Hashtag:
|
||||||
using HashtagMentionType = MarkedTextContext::HashtagMentionType;
|
using HashtagMentionType = TextContextDetails::HashtagMentionType;
|
||||||
if (my && my->type == HashtagMentionType::Twitter) {
|
if (my && my->type == HashtagMentionType::Twitter) {
|
||||||
return std::make_shared<UrlClickHandler>(
|
return std::make_shared<UrlClickHandler>(
|
||||||
(u"https://twitter.com/hashtag/"_q
|
(u"https://twitter.com/hashtag/"_q
|
||||||
|
@ -190,7 +225,7 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
||||||
return std::make_shared<CashtagClickHandler>(data.data);
|
return std::make_shared<CashtagClickHandler>(data.data);
|
||||||
|
|
||||||
case EntityType::Mention:
|
case EntityType::Mention:
|
||||||
using HashtagMentionType = MarkedTextContext::HashtagMentionType;
|
using HashtagMentionType = TextContextDetails::HashtagMentionType;
|
||||||
if (my && my->type == HashtagMentionType::Twitter) {
|
if (my && my->type == HashtagMentionType::Twitter) {
|
||||||
return std::make_shared<UrlClickHandler>(
|
return std::make_shared<UrlClickHandler>(
|
||||||
u"https://twitter.com/"_q + data.data.mid(1),
|
u"https://twitter.com/"_q + data.data.mid(1),
|
||||||
|
@ -222,7 +257,9 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
||||||
case EntityType::Pre:
|
case EntityType::Pre:
|
||||||
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
||||||
case EntityType::Phone:
|
case EntityType::Phone:
|
||||||
return std::make_shared<PhoneClickHandler>(my->session, data.text);
|
return my->session
|
||||||
|
? std::make_shared<PhoneClickHandler>(my->session, data.text)
|
||||||
|
: nullptr;
|
||||||
}
|
}
|
||||||
return Integration::createLinkHandler(data, context);
|
return Integration::createLinkHandler(data, context);
|
||||||
}
|
}
|
||||||
|
@ -280,36 +317,6 @@ bool UiIntegration::copyPreOnClick(const QVariant &context) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
|
|
||||||
QStringView data,
|
|
||||||
const std::any &context) {
|
|
||||||
const auto my = std::any_cast<MarkedTextContext>(&context);
|
|
||||||
if (!my || !my->session) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
auto result = my->session->data().customEmojiManager().create(
|
|
||||||
data,
|
|
||||||
my->customEmojiRepaint);
|
|
||||||
if (my->customEmojiLoopLimit > 0) {
|
|
||||||
return std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
|
||||||
std::move(result),
|
|
||||||
my->customEmojiLoopLimit);
|
|
||||||
} else if (my->customEmojiLoopLimit) {
|
|
||||||
return std::make_unique<Ui::Text::FirstFrameEmoji>(
|
|
||||||
std::move(result));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Fn<void()> UiIntegration::createSpoilerRepaint(const std::any &context) {
|
|
||||||
const auto my = std::any_cast<MarkedTextContext>(&context);
|
|
||||||
if (my) {
|
|
||||||
return my->customEmojiRepaint;
|
|
||||||
}
|
|
||||||
const auto common = std::any_cast<CommonTextContext>(&context);
|
|
||||||
return common ? common->repaint : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
|
rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
|
||||||
return Core::App().passcodeLockChanges() | rpl::to_empty;
|
return Core::App().passcodeLockChanges() | rpl::to_empty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ class ElementDelegate;
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
struct MarkedTextContext {
|
struct TextContextDetails {
|
||||||
enum class HashtagMentionType : uchar {
|
enum class HashtagMentionType : uchar {
|
||||||
Telegram,
|
Telegram,
|
||||||
Twitter,
|
Twitter,
|
||||||
|
@ -28,9 +28,15 @@ struct MarkedTextContext {
|
||||||
|
|
||||||
Main::Session *session = nullptr;
|
Main::Session *session = nullptr;
|
||||||
HashtagMentionType type = HashtagMentionType::Telegram;
|
HashtagMentionType type = HashtagMentionType::Telegram;
|
||||||
Fn<void()> customEmojiRepaint;
|
};
|
||||||
|
|
||||||
|
struct TextContextArgs {
|
||||||
|
not_null<Main::Session*> session;
|
||||||
|
TextContextDetails details;
|
||||||
|
Fn<void()> repaint;
|
||||||
int customEmojiLoopLimit = 0;
|
int customEmojiLoopLimit = 0;
|
||||||
};
|
};
|
||||||
|
[[nodiscard]] Ui::Text::MarkedContext TextContext(TextContextArgs &&args);
|
||||||
|
|
||||||
class UiIntegration final : public Ui::Integration {
|
class UiIntegration final : public Ui::Integration {
|
||||||
public:
|
public:
|
||||||
|
@ -49,7 +55,7 @@ public:
|
||||||
|
|
||||||
std::shared_ptr<ClickHandler> createLinkHandler(
|
std::shared_ptr<ClickHandler> createLinkHandler(
|
||||||
const EntityLinkData &data,
|
const EntityLinkData &data,
|
||||||
const std::any &context) override;
|
const Ui::Text::MarkedContext &context) override;
|
||||||
bool handleUrlClick(
|
bool handleUrlClick(
|
||||||
const QString &url,
|
const QString &url,
|
||||||
const QVariant &context) override;
|
const QVariant &context) override;
|
||||||
|
@ -57,10 +63,6 @@ public:
|
||||||
rpl::producer<> forcePopupMenuHideRequests() override;
|
rpl::producer<> forcePopupMenuHideRequests() override;
|
||||||
const Ui::Emoji::One *defaultEmojiVariant(
|
const Ui::Emoji::One *defaultEmojiVariant(
|
||||||
const Ui::Emoji::One *emoji) override;
|
const Ui::Emoji::One *emoji) override;
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji> createCustomEmoji(
|
|
||||||
QStringView data,
|
|
||||||
const std::any &context) override;
|
|
||||||
Fn<void()> createSpoilerRepaint(const std::any &context) override;
|
|
||||||
|
|
||||||
QString phraseContextCopyText() override;
|
QString phraseContextCopyText() override;
|
||||||
QString phraseContextCopyEmail() override;
|
QString phraseContextCopyEmail() override;
|
||||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
||||||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||||
constexpr auto AppFile = "AyuGram"_cs;
|
constexpr auto AppFile = "AyuGram"_cs;
|
||||||
constexpr auto AppVersion = 5011001;
|
constexpr auto AppVersion = 5012003;
|
||||||
constexpr auto AppVersionStr = "5.11.1";
|
constexpr auto AppVersionStr = "5.12.3";
|
||||||
constexpr auto AppBetaVersion = false;
|
constexpr auto AppBetaVersion = false;
|
||||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||||
|
|
|
@ -322,6 +322,9 @@ QString CountriesInstance::flagEmojiByISO2(const QString &iso) const {
|
||||||
|| iso.back() < 'A'
|
|| iso.back() < 'A'
|
||||||
|| iso.back() > 'Z') {
|
|| iso.back() > 'Z') {
|
||||||
return QString();
|
return QString();
|
||||||
|
} else if (iso == u"FT"_q) {
|
||||||
|
return QString::fromUtf8(
|
||||||
|
"\xF0\x9F\x8F\xB4\xE2\x80\x8D\xE2\x98\xA0\xEF\xB8\x8F");
|
||||||
}
|
}
|
||||||
auto result = QString(4, QChar(0xD83C));
|
auto result = QString(4, QChar(0xD83C));
|
||||||
result[1] = QChar(iso.front().unicode() - 'A' + 0xDDE6);
|
result[1] = QChar(iso.front().unicode() - 'A' + 0xDDE6);
|
||||||
|
|
|
@ -91,7 +91,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||||
MTP_int(shortcutId),
|
MTP_int(shortcutId),
|
||||||
MTP_long(data.veffect().value_or_empty()),
|
MTP_long(data.veffect().value_or_empty()),
|
||||||
(data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()),
|
(data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()),
|
||||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()));
|
MTP_int(data.vreport_delivery_until_date().value_or_empty()),
|
||||||
|
MTP_long(data.vpaid_message_stars().value_or_empty()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,10 +37,7 @@ void Credits::apply(const MTPDupdateStarsBalance &data) {
|
||||||
|
|
||||||
rpl::producer<float64> Credits::rateValue(
|
rpl::producer<float64> Credits::rateValue(
|
||||||
not_null<PeerData*> ownedBotOrChannel) {
|
not_null<PeerData*> ownedBotOrChannel) {
|
||||||
return rpl::single(
|
return rpl::single(_session->appConfig().starsWithdrawRate());
|
||||||
_session->appConfig().get<float64>(
|
|
||||||
u"stars_usd_withdraw_rate_x1000"_q,
|
|
||||||
1200) / 1000.);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Credits::load(bool force) {
|
void Credits::load(bool force) {
|
||||||
|
|
|
@ -95,7 +95,8 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||||
MTPint(), // quick_reply_shortcut_id
|
MTPint(), // quick_reply_shortcut_id
|
||||||
MTP_long(data.veffect().value_or_empty()), // effect
|
MTP_long(data.veffect().value_or_empty()), // effect
|
||||||
data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck(),
|
data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck(),
|
||||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()));
|
MTP_int(data.vreport_delivery_until_date().value_or_empty()),
|
||||||
|
MTP_long(data.vpaid_message_stars().value_or_empty()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,7 +270,8 @@ void ScheduledMessages::sendNowSimpleMessage(
|
||||||
MTPint(), // quick_reply_shortcut_id
|
MTPint(), // quick_reply_shortcut_id
|
||||||
MTP_long(local->effectId()), // effect
|
MTP_long(local->effectId()), // effect
|
||||||
MTPFactCheck(),
|
MTPFactCheck(),
|
||||||
MTPint()), // report_delivery_until_date
|
MTPint(), // report_delivery_until_date
|
||||||
|
MTPlong()), // paid_message_stars
|
||||||
localFlags,
|
localFlags,
|
||||||
NewMessageType::Unread);
|
NewMessageType::Unread);
|
||||||
|
|
||||||
|
|
|
@ -75,46 +75,48 @@ struct PeerUpdate {
|
||||||
BackgroundEmoji = (1ULL << 15),
|
BackgroundEmoji = (1ULL << 15),
|
||||||
StoriesState = (1ULL << 16),
|
StoriesState = (1ULL << 16),
|
||||||
VerifyInfo = (1ULL << 17),
|
VerifyInfo = (1ULL << 17),
|
||||||
|
StarsPerMessage = (1ULL << 18),
|
||||||
|
|
||||||
// For users
|
// For users
|
||||||
CanShareContact = (1ULL << 18),
|
CanShareContact = (1ULL << 19),
|
||||||
IsContact = (1ULL << 19),
|
IsContact = (1ULL << 20),
|
||||||
PhoneNumber = (1ULL << 20),
|
PhoneNumber = (1ULL << 21),
|
||||||
OnlineStatus = (1ULL << 21),
|
OnlineStatus = (1ULL << 22),
|
||||||
BotCommands = (1ULL << 22),
|
BotCommands = (1ULL << 23),
|
||||||
BotCanBeInvited = (1ULL << 23),
|
BotCanBeInvited = (1ULL << 24),
|
||||||
BotStartToken = (1ULL << 24),
|
BotStartToken = (1ULL << 25),
|
||||||
CommonChats = (1ULL << 25),
|
CommonChats = (1ULL << 26),
|
||||||
PeerGifts = (1ULL << 26),
|
PeerGifts = (1ULL << 27),
|
||||||
HasCalls = (1ULL << 27),
|
HasCalls = (1ULL << 28),
|
||||||
SupportInfo = (1ULL << 28),
|
SupportInfo = (1ULL << 29),
|
||||||
IsBot = (1ULL << 29),
|
IsBot = (1ULL << 30),
|
||||||
EmojiStatus = (1ULL << 30),
|
EmojiStatus = (1ULL << 31),
|
||||||
BusinessDetails = (1ULL << 31),
|
BusinessDetails = (1ULL << 32),
|
||||||
Birthday = (1ULL << 32),
|
Birthday = (1ULL << 33),
|
||||||
PersonalChannel = (1ULL << 33),
|
PersonalChannel = (1ULL << 34),
|
||||||
StarRefProgram = (1ULL << 34),
|
StarRefProgram = (1ULL << 35),
|
||||||
|
PaysPerMessage = (1ULL << 36),
|
||||||
|
|
||||||
// For chats and channels
|
// For chats and channels
|
||||||
InviteLinks = (1ULL << 35),
|
InviteLinks = (1ULL << 37),
|
||||||
Members = (1ULL << 36),
|
Members = (1ULL << 38),
|
||||||
Admins = (1ULL << 37),
|
Admins = (1ULL << 39),
|
||||||
BannedUsers = (1ULL << 38),
|
BannedUsers = (1ULL << 40),
|
||||||
Rights = (1ULL << 39),
|
Rights = (1ULL << 41),
|
||||||
PendingRequests = (1ULL << 40),
|
PendingRequests = (1ULL << 42),
|
||||||
Reactions = (1ULL << 41),
|
Reactions = (1ULL << 43),
|
||||||
|
|
||||||
// For channels
|
// For channels
|
||||||
ChannelAmIn = (1ULL << 42),
|
ChannelAmIn = (1ULL << 44),
|
||||||
StickersSet = (1ULL << 43),
|
StickersSet = (1ULL << 45),
|
||||||
EmojiSet = (1ULL << 44),
|
EmojiSet = (1ULL << 46),
|
||||||
ChannelLinkedChat = (1ULL << 45),
|
ChannelLinkedChat = (1ULL << 47),
|
||||||
ChannelLocation = (1ULL << 46),
|
ChannelLocation = (1ULL << 48),
|
||||||
Slowmode = (1ULL << 47),
|
Slowmode = (1ULL << 49),
|
||||||
GroupCall = (1ULL << 48),
|
GroupCall = (1ULL << 50),
|
||||||
|
|
||||||
// For iteration
|
// For iteration
|
||||||
LastUsedBit = (1ULL << 48),
|
LastUsedBit = (1ULL << 50),
|
||||||
};
|
};
|
||||||
using Flags = base::flags<Flag>;
|
using Flags = base::flags<Flag>;
|
||||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
|
||||||
#include "api/api_global_privacy.h"
|
#include "api/api_global_privacy.h"
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_channel_admins.h"
|
#include "data/data_channel_admins.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_chat_invite.h"
|
#include "api/api_chat_invite.h"
|
||||||
#include "api/api_invite_links.h"
|
#include "api/api_invite_links.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "storage/storage_account.h"
|
||||||
#include "ui/unread_badge.h"
|
#include "ui/unread_badge.h"
|
||||||
#include "window/notifications_manager.h"
|
#include "window/notifications_manager.h"
|
||||||
|
|
||||||
|
@ -859,6 +861,21 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) {
|
||||||
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
|
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ChannelData::starsPerMessage() const {
|
||||||
|
if (const auto info = mgInfo.get()) {
|
||||||
|
return info->_starsPerMessage;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelData::setStarsPerMessage(int stars) {
|
||||||
|
if (mgInfo && starsPerMessage() != stars) {
|
||||||
|
mgInfo->_starsPerMessage = stars;
|
||||||
|
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
|
||||||
|
}
|
||||||
|
checkTrustedPayForMessage();
|
||||||
|
}
|
||||||
|
|
||||||
int ChannelData::peerGiftsCount() const {
|
int ChannelData::peerGiftsCount() const {
|
||||||
return _peerGiftsCount;
|
return _peerGiftsCount;
|
||||||
}
|
}
|
||||||
|
@ -1150,7 +1167,8 @@ void ApplyChannelUpdate(
|
||||||
| Flag::CanViewRevenue
|
| Flag::CanViewRevenue
|
||||||
| Flag::PaidMediaAllowed
|
| Flag::PaidMediaAllowed
|
||||||
| Flag::CanViewCreditsRevenue
|
| Flag::CanViewCreditsRevenue
|
||||||
| Flag::StargiftsAvailable;
|
| Flag::StargiftsAvailable
|
||||||
|
| Flag::PaidMessagesAvailable;
|
||||||
channel->setFlags((channel->flags() & ~mask)
|
channel->setFlags((channel->flags() & ~mask)
|
||||||
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
|
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
|
||||||
| (update.is_can_view_participants()
|
| (update.is_can_view_participants()
|
||||||
|
@ -1174,6 +1192,9 @@ void ApplyChannelUpdate(
|
||||||
: Flag())
|
: Flag())
|
||||||
| (update.is_stargifts_available()
|
| (update.is_stargifts_available()
|
||||||
? Flag::StargiftsAvailable
|
? Flag::StargiftsAvailable
|
||||||
|
: Flag())
|
||||||
|
| (update.is_paid_messages_available()
|
||||||
|
? Flag::PaidMessagesAvailable
|
||||||
: Flag()));
|
: Flag()));
|
||||||
channel->setUserpicPhoto(update.vchat_photo());
|
channel->setUserpicPhoto(update.vchat_photo());
|
||||||
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
|
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
|
||||||
|
|
|
@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_peer_bot_commands.h"
|
#include "data/data_peer_bot_commands.h"
|
||||||
#include "data/data_user_names.h"
|
#include "data/data_user_names.h"
|
||||||
|
|
||||||
|
class ChannelData;
|
||||||
|
|
||||||
struct ChannelLocation {
|
struct ChannelLocation {
|
||||||
QString address;
|
QString address;
|
||||||
Data::LocationPoint point;
|
Data::LocationPoint point;
|
||||||
|
@ -70,6 +72,7 @@ enum class ChannelDataFlag : uint64 {
|
||||||
CanViewCreditsRevenue = (1ULL << 34),
|
CanViewCreditsRevenue = (1ULL << 34),
|
||||||
SignatureProfiles = (1ULL << 35),
|
SignatureProfiles = (1ULL << 35),
|
||||||
StargiftsAvailable = (1ULL << 36),
|
StargiftsAvailable = (1ULL << 36),
|
||||||
|
PaidMessagesAvailable = (1ULL << 37),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||||
|
@ -150,6 +153,9 @@ private:
|
||||||
ChannelLocation _location;
|
ChannelLocation _location;
|
||||||
Data::ChatBotCommands _botCommands;
|
Data::ChatBotCommands _botCommands;
|
||||||
std::unique_ptr<Data::Forum> _forum;
|
std::unique_ptr<Data::Forum> _forum;
|
||||||
|
int _starsPerMessage = 0;
|
||||||
|
|
||||||
|
friend class ChannelData;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -257,6 +263,9 @@ public:
|
||||||
[[nodiscard]] bool stargiftsAvailable() const {
|
[[nodiscard]] bool stargiftsAvailable() const {
|
||||||
return flags() & Flag::StargiftsAvailable;
|
return flags() & Flag::StargiftsAvailable;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool paidMessagesAvailable() const {
|
||||||
|
return flags() & Flag::PaidMessagesAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights(
|
[[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights(
|
||||||
not_null<PeerData*> participant);
|
not_null<PeerData*> participant);
|
||||||
|
@ -456,6 +465,9 @@ public:
|
||||||
[[nodiscard]] TimeId slowmodeLastMessage() const;
|
[[nodiscard]] TimeId slowmodeLastMessage() const;
|
||||||
void growSlowmodeLastMessage(TimeId when);
|
void growSlowmodeLastMessage(TimeId when);
|
||||||
|
|
||||||
|
void setStarsPerMessage(int stars);
|
||||||
|
[[nodiscard]] int starsPerMessage() const;
|
||||||
|
|
||||||
[[nodiscard]] int peerGiftsCount() const;
|
[[nodiscard]] int peerGiftsCount() const;
|
||||||
void setPeerGiftsCount(int count);
|
void setPeerGiftsCount(int count);
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "ui/chat/attach/attach_prepare.h"
|
#include "ui/chat/attach/attach_prepare.h"
|
||||||
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
#include "styles/style_widgets.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -120,7 +123,7 @@ bool CanSendAnyOf(
|
||||||
|| user->isRepliesChat()
|
|| user->isRepliesChat()
|
||||||
|| user->isVerifyCodes()) {
|
|| user->isVerifyCodes()) {
|
||||||
return false;
|
return false;
|
||||||
} else if (user->meRequiresPremiumToWrite()
|
} else if (user->requiresPremiumToWrite()
|
||||||
&& !user->session().premium()) {
|
&& !user->session().premium()) {
|
||||||
return false;
|
return false;
|
||||||
} else if (rights
|
} else if (rights
|
||||||
|
@ -177,7 +180,7 @@ SendError RestrictionError(
|
||||||
using Flag = ChatRestriction;
|
using Flag = ChatRestriction;
|
||||||
if (const auto restricted = peer->amRestricted(restriction)) {
|
if (const auto restricted = peer->amRestricted(restriction)) {
|
||||||
if (const auto user = peer->asUser()) {
|
if (const auto user = peer->asUser()) {
|
||||||
if (user->meRequiresPremiumToWrite()
|
if (user->requiresPremiumToWrite()
|
||||||
&& !user->session().premium()) {
|
&& !user->session().premium()) {
|
||||||
return SendError({
|
return SendError({
|
||||||
.text = tr::lng_restricted_send_non_premium(
|
.text = tr::lng_restricted_send_non_premium(
|
||||||
|
|
|
@ -66,10 +66,11 @@ struct CreditsHistoryEntry final {
|
||||||
uint64 bareGiftStickerId = 0;
|
uint64 bareGiftStickerId = 0;
|
||||||
uint64 bareGiftOwnerId = 0;
|
uint64 bareGiftOwnerId = 0;
|
||||||
uint64 bareActorId = 0;
|
uint64 bareActorId = 0;
|
||||||
uint64 bareGiftListPeerId = 0;
|
uint64 bareEntryOwnerId = 0;
|
||||||
uint64 giftSavedId = 0;
|
uint64 giftChannelSavedId = 0;
|
||||||
uint64 stargiftId = 0;
|
uint64 stargiftId = 0;
|
||||||
std::shared_ptr<UniqueGift> uniqueGift;
|
std::shared_ptr<UniqueGift> uniqueGift;
|
||||||
|
Fn<std::vector<CreditsHistoryEntry>()> pinnedSavedGifts;
|
||||||
StarsAmount starrefAmount;
|
StarsAmount starrefAmount;
|
||||||
int starrefCommission = 0;
|
int starrefCommission = 0;
|
||||||
uint64 starrefRecipientId = 0;
|
uint64 starrefRecipientId = 0;
|
||||||
|
@ -77,11 +78,15 @@ struct CreditsHistoryEntry final {
|
||||||
QDateTime subscriptionUntil;
|
QDateTime subscriptionUntil;
|
||||||
QDateTime successDate;
|
QDateTime successDate;
|
||||||
QString successLink;
|
QString successLink;
|
||||||
|
int paidMessagesCount = 0;
|
||||||
|
StarsAmount paidMessagesAmount;
|
||||||
|
int paidMessagesCommission = 0;
|
||||||
int limitedCount = 0;
|
int limitedCount = 0;
|
||||||
int limitedLeft = 0;
|
int limitedLeft = 0;
|
||||||
int starsConverted = 0;
|
int starsConverted = 0;
|
||||||
int starsToUpgrade = 0;
|
int starsToUpgrade = 0;
|
||||||
int starsUpgradedBySender = 0;
|
int starsUpgradedBySender = 0;
|
||||||
|
int premiumMonthsForStars = 0;
|
||||||
int floodSkip = 0;
|
int floodSkip = 0;
|
||||||
bool converted : 1 = false;
|
bool converted : 1 = false;
|
||||||
bool anonymous : 1 = false;
|
bool anonymous : 1 = false;
|
||||||
|
@ -89,6 +94,7 @@ struct CreditsHistoryEntry final {
|
||||||
bool giftTransferred : 1 = false;
|
bool giftTransferred : 1 = false;
|
||||||
bool giftRefunded : 1 = false;
|
bool giftRefunded : 1 = false;
|
||||||
bool giftUpgraded : 1 = false;
|
bool giftUpgraded : 1 = false;
|
||||||
|
bool giftPinned : 1 = false;
|
||||||
bool savedToProfile : 1 = false;
|
bool savedToProfile : 1 = false;
|
||||||
bool fromGiftsList : 1 = false;
|
bool fromGiftsList : 1 = false;
|
||||||
bool fromGiftSlug : 1 = false;
|
bool fromGiftSlug : 1 = false;
|
||||||
|
|
|
@ -16,7 +16,10 @@ namespace Data {
|
||||||
|
|
||||||
struct CreditsEarnStatistics final {
|
struct CreditsEarnStatistics final {
|
||||||
explicit operator bool() const {
|
explicit operator bool() const {
|
||||||
return !!usdRate;
|
return usdRate
|
||||||
|
&& currentBalance
|
||||||
|
&& availableBalance
|
||||||
|
&& overallRevenue;
|
||||||
}
|
}
|
||||||
Data::StatisticalGraph revenueGraph;
|
Data::StatisticalGraph revenueGraph;
|
||||||
StarsAmount currentBalance;
|
StarsAmount currentBalance;
|
||||||
|
|
|
@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "storage/file_download.h"
|
#include "storage/file_download.h"
|
||||||
|
#include "storage/storage_account.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
#include "storage/storage_shared_media.h"
|
#include "storage/storage_shared_media.h"
|
||||||
|
|
||||||
|
@ -65,6 +66,28 @@ using UpdateFlag = Data::PeerUpdate::Flag;
|
||||||
return session->appConfig().ignoredRestrictionReasons();
|
return session->appConfig().ignoredRestrictionReasons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int ParseRegistrationDate(const QString &text) {
|
||||||
|
// MM.YYYY
|
||||||
|
if (text.size() != 7 || text[2] != '.') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const auto month = text.mid(0, 2).toInt();
|
||||||
|
const auto year = text.mid(3, 4).toInt();
|
||||||
|
return (year > 2012 && year < 2100 && month > 0 && month <= 12)
|
||||||
|
? (year * 100) + month
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int RegistrationYear(int date) {
|
||||||
|
const auto year = date / 100;
|
||||||
|
return (year > 2012 && year < 2100) ? year : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int RegistrationMonth(int date) {
|
||||||
|
const auto month = date % 100;
|
||||||
|
return (month > 0 && month <= 12) ? month : 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
@ -311,6 +334,17 @@ void PeerData::invalidateEmptyUserpic() {
|
||||||
_userpicEmpty = nullptr;
|
_userpicEmpty = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PeerData::checkTrustedPayForMessage() {
|
||||||
|
if (!_checkedTrustedPayForMessage
|
||||||
|
&& !starsPerMessage()
|
||||||
|
&& session().local().peerTrustedPayForMessageRead()) {
|
||||||
|
_checkedTrustedPayForMessage = 1;
|
||||||
|
if (session().local().hasPeerTrustedPayForMessageEntry(id)) {
|
||||||
|
session().local().clearPeerTrustedPayForMessage(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ClickHandlerPtr PeerData::createOpenLink() {
|
ClickHandlerPtr PeerData::createOpenLink() {
|
||||||
return std::make_shared<PeerClickHandler>(this);
|
return std::make_shared<PeerClickHandler>(this);
|
||||||
}
|
}
|
||||||
|
@ -692,7 +726,9 @@ void PeerData::checkFolder(FolderId folderId) {
|
||||||
|
|
||||||
void PeerData::clearBusinessBot() {
|
void PeerData::clearBusinessBot() {
|
||||||
if (const auto details = _barDetails.get()) {
|
if (const auto details = _barDetails.get()) {
|
||||||
if (details->requestChatDate) {
|
if (details->requestChatDate
|
||||||
|
|| details->paysPerMessage
|
||||||
|
|| !details->phoneCountryCode.isEmpty()) {
|
||||||
details->businessBot = nullptr;
|
details->businessBot = nullptr;
|
||||||
details->businessBotManageUrl = QString();
|
details->businessBotManageUrl = QString();
|
||||||
} else {
|
} else {
|
||||||
|
@ -735,12 +771,27 @@ void PeerData::saveTranslationDisabled(bool disabled) {
|
||||||
|
|
||||||
void PeerData::setBarSettings(const MTPPeerSettings &data) {
|
void PeerData::setBarSettings(const MTPPeerSettings &data) {
|
||||||
data.match([&](const MTPDpeerSettings &data) {
|
data.match([&](const MTPDpeerSettings &data) {
|
||||||
if (!data.vbusiness_bot_id() && !data.vrequest_chat_title()) {
|
const auto wasPaysPerMessage = paysPerMessage();
|
||||||
|
if (!data.vbusiness_bot_id()
|
||||||
|
&& !data.vrequest_chat_title()
|
||||||
|
&& !data.vcharge_paid_message_stars()
|
||||||
|
&& !data.vphone_country()
|
||||||
|
&& !data.vregistration_month()
|
||||||
|
&& !data.vname_change_date()
|
||||||
|
&& !data.vphoto_change_date()) {
|
||||||
_barDetails = nullptr;
|
_barDetails = nullptr;
|
||||||
} else if (!_barDetails) {
|
} else if (!_barDetails) {
|
||||||
_barDetails = std::make_unique<PeerBarDetails>();
|
_barDetails = std::make_unique<PeerBarDetails>();
|
||||||
}
|
}
|
||||||
if (_barDetails) {
|
if (_barDetails) {
|
||||||
|
_barDetails->phoneCountryCode
|
||||||
|
= qs(data.vphone_country().value_or_empty());
|
||||||
|
_barDetails->registrationDate = ParseRegistrationDate(
|
||||||
|
data.vregistration_month().value_or_empty());
|
||||||
|
_barDetails->nameChangeDate
|
||||||
|
= data.vname_change_date().value_or_empty();
|
||||||
|
_barDetails->photoChangeDate
|
||||||
|
= data.vphoto_change_date().value_or_empty();
|
||||||
_barDetails->requestChatTitle
|
_barDetails->requestChatTitle
|
||||||
= qs(data.vrequest_chat_title().value_or_empty());
|
= qs(data.vrequest_chat_title().value_or_empty());
|
||||||
_barDetails->requestChatDate
|
_barDetails->requestChatDate
|
||||||
|
@ -750,6 +801,8 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) {
|
||||||
: nullptr;
|
: nullptr;
|
||||||
_barDetails->businessBotManageUrl
|
_barDetails->businessBotManageUrl
|
||||||
= qs(data.vbusiness_bot_manage_url().value_or_empty());
|
= qs(data.vbusiness_bot_manage_url().value_or_empty());
|
||||||
|
_barDetails->paysPerMessage
|
||||||
|
= data.vcharge_paid_message_stars().value_or_empty();
|
||||||
}
|
}
|
||||||
using Flag = PeerBarSetting;
|
using Flag = PeerBarSetting;
|
||||||
setBarSettings((data.is_add_contact() ? Flag::AddContact : Flag())
|
setBarSettings((data.is_add_contact() ? Flag::AddContact : Flag())
|
||||||
|
@ -773,8 +826,35 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) {
|
||||||
| (data.is_business_bot_can_reply()
|
| (data.is_business_bot_can_reply()
|
||||||
? Flag::BusinessBotCanReply
|
? Flag::BusinessBotCanReply
|
||||||
: Flag()));
|
: Flag()));
|
||||||
|
if (wasPaysPerMessage != paysPerMessage()) {
|
||||||
|
session().changes().peerUpdated(
|
||||||
|
this,
|
||||||
|
UpdateFlag::PaysPerMessage);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int PeerData::paysPerMessage() const {
|
||||||
|
return _barDetails ? _barDetails->paysPerMessage : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeerData::clearPaysPerMessage() {
|
||||||
|
if (const auto details = _barDetails.get()) {
|
||||||
|
if (details->paysPerMessage) {
|
||||||
|
if (details->businessBot
|
||||||
|
|| details->requestChatDate
|
||||||
|
|| !details->phoneCountryCode.isEmpty()) {
|
||||||
|
details->paysPerMessage = 0;
|
||||||
|
} else {
|
||||||
|
_barDetails = nullptr;
|
||||||
|
}
|
||||||
|
session().changes().peerUpdated(
|
||||||
|
this,
|
||||||
|
UpdateFlag::PaysPerMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString PeerData::requestChatTitle() const {
|
QString PeerData::requestChatTitle() const {
|
||||||
return _barDetails ? _barDetails->requestChatTitle : QString();
|
return _barDetails ? _barDetails->requestChatTitle : QString();
|
||||||
}
|
}
|
||||||
|
@ -791,6 +871,28 @@ QString PeerData::businessBotManageUrl() const {
|
||||||
return _barDetails ? _barDetails->businessBotManageUrl : QString();
|
return _barDetails ? _barDetails->businessBotManageUrl : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString PeerData::phoneCountryCode() const {
|
||||||
|
return _barDetails ? _barDetails->phoneCountryCode : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
int PeerData::registrationMonth() const {
|
||||||
|
return _barDetails
|
||||||
|
? RegistrationMonth(_barDetails->registrationDate)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PeerData::registrationYear() const {
|
||||||
|
return _barDetails ? RegistrationYear(_barDetails->registrationDate) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeId PeerData::nameChangeDate() const {
|
||||||
|
return _barDetails ? _barDetails->nameChangeDate : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeId PeerData::photoChangeDate() const {
|
||||||
|
return _barDetails ? _barDetails->photoChangeDate : 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool PeerData::changeColorIndex(
|
bool PeerData::changeColorIndex(
|
||||||
const tl::conditional<MTPint> &cloudColorIndex) {
|
const tl::conditional<MTPint> &cloudColorIndex) {
|
||||||
return cloudColorIndex
|
return cloudColorIndex
|
||||||
|
@ -1301,7 +1403,7 @@ Data::RestrictionCheckResult PeerData::amRestricted(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (const auto user = asUser()) {
|
if (const auto user = asUser()) {
|
||||||
if (user->meRequiresPremiumToWrite() && !user->session().premium()) {
|
if (user->requiresPremiumToWrite() && !user->session().premium()) {
|
||||||
return Result::Explicit();
|
return Result::Explicit();
|
||||||
}
|
}
|
||||||
return (right == ChatRestriction::SendVoiceMessages
|
return (right == ChatRestriction::SendVoiceMessages
|
||||||
|
@ -1420,6 +1522,24 @@ bool PeerData::canManageGroupCall() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int PeerData::starsPerMessage() const {
|
||||||
|
if (const auto user = asUser()) {
|
||||||
|
return user->starsPerMessage();
|
||||||
|
} else if (const auto channel = asChannel()) {
|
||||||
|
return channel->starsPerMessage();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PeerData::starsPerMessageChecked() const {
|
||||||
|
if (const auto channel = asChannel()) {
|
||||||
|
return (channel->adminRights() || channel->amCreator())
|
||||||
|
? 0
|
||||||
|
: channel->starsPerMessage();
|
||||||
|
}
|
||||||
|
return starsPerMessage();
|
||||||
|
}
|
||||||
|
|
||||||
Data::GroupCall *PeerData::groupCall() const {
|
Data::GroupCall *PeerData::groupCall() const {
|
||||||
if (const auto chat = asChat()) {
|
if (const auto chat = asChat()) {
|
||||||
return chat->groupCall();
|
return chat->groupCall();
|
||||||
|
|
|
@ -173,10 +173,15 @@ inline constexpr bool is_flag_type(PeerBarSetting) { return true; };
|
||||||
using PeerBarSettings = base::flags<PeerBarSetting>;
|
using PeerBarSettings = base::flags<PeerBarSetting>;
|
||||||
|
|
||||||
struct PeerBarDetails {
|
struct PeerBarDetails {
|
||||||
|
QString phoneCountryCode;
|
||||||
|
int registrationDate = 0; // YYYYMM or 0, YYYY > 2012, MM > 0.
|
||||||
|
TimeId nameChangeDate = 0;
|
||||||
|
TimeId photoChangeDate = 0;
|
||||||
QString requestChatTitle;
|
QString requestChatTitle;
|
||||||
TimeId requestChatDate;
|
TimeId requestChatDate;
|
||||||
UserData *businessBot = nullptr;
|
UserData *businessBot = nullptr;
|
||||||
QString businessBotManageUrl;
|
QString businessBotManageUrl;
|
||||||
|
int paysPerMessage = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PeerData {
|
class PeerData {
|
||||||
|
@ -268,6 +273,9 @@ public:
|
||||||
[[nodiscard]] int slowmodeSecondsLeft() const;
|
[[nodiscard]] int slowmodeSecondsLeft() const;
|
||||||
[[nodiscard]] bool canManageGroupCall() const;
|
[[nodiscard]] bool canManageGroupCall() const;
|
||||||
|
|
||||||
|
[[nodiscard]] int starsPerMessage() const;
|
||||||
|
[[nodiscard]] int starsPerMessageChecked() const;
|
||||||
|
|
||||||
[[nodiscard]] UserData *asBot();
|
[[nodiscard]] UserData *asBot();
|
||||||
[[nodiscard]] const UserData *asBot() const;
|
[[nodiscard]] const UserData *asBot() const;
|
||||||
[[nodiscard]] UserData *asUser();
|
[[nodiscard]] UserData *asUser();
|
||||||
|
@ -409,11 +417,18 @@ public:
|
||||||
? _barSettings.changes()
|
? _barSettings.changes()
|
||||||
: (_barSettings.value() | rpl::type_erased());
|
: (_barSettings.value() | rpl::type_erased());
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] int paysPerMessage() const;
|
||||||
|
void clearPaysPerMessage();
|
||||||
[[nodiscard]] QString requestChatTitle() const;
|
[[nodiscard]] QString requestChatTitle() const;
|
||||||
[[nodiscard]] TimeId requestChatDate() const;
|
[[nodiscard]] TimeId requestChatDate() const;
|
||||||
[[nodiscard]] UserData *businessBot() const;
|
[[nodiscard]] UserData *businessBot() const;
|
||||||
[[nodiscard]] QString businessBotManageUrl() const;
|
[[nodiscard]] QString businessBotManageUrl() const;
|
||||||
void clearBusinessBot();
|
void clearBusinessBot();
|
||||||
|
[[nodiscard]] QString phoneCountryCode() const;
|
||||||
|
[[nodiscard]] int registrationMonth() const;
|
||||||
|
[[nodiscard]] int registrationYear() const;
|
||||||
|
[[nodiscard]] TimeId nameChangeDate() const;
|
||||||
|
[[nodiscard]] TimeId photoChangeDate() const;
|
||||||
|
|
||||||
enum class TranslationFlag : uchar {
|
enum class TranslationFlag : uchar {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -501,6 +516,7 @@ protected:
|
||||||
void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo);
|
void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo);
|
||||||
void clearUserpic();
|
void clearUserpic();
|
||||||
void invalidateEmptyUserpic();
|
void invalidateEmptyUserpic();
|
||||||
|
void checkTrustedPayForMessage();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void fillNames();
|
void fillNames();
|
||||||
|
@ -535,9 +551,10 @@ private:
|
||||||
crl::time _lastFullUpdate = 0;
|
crl::time _lastFullUpdate = 0;
|
||||||
|
|
||||||
QString _name;
|
QString _name;
|
||||||
uint32 _nameVersion : 30 = 1;
|
uint32 _nameVersion : 29 = 1;
|
||||||
uint32 _sensitiveContent : 1 = 0;
|
uint32 _sensitiveContent : 1 = 0;
|
||||||
uint32 _wallPaperOverriden : 1 = 0;
|
uint32 _wallPaperOverriden : 1 = 0;
|
||||||
|
uint32 _checkedTrustedPayForMessage : 1 = 0;
|
||||||
|
|
||||||
TimeId _ttlPeriod = 0;
|
TimeId _ttlPeriod = 0;
|
||||||
|
|
||||||
|
|
|
@ -228,11 +228,11 @@ inline auto DefaultRestrictionValue(
|
||||||
| ChatRestriction::SendVideoMessages);
|
| ChatRestriction::SendVideoMessages);
|
||||||
auto allowedAny = PeerFlagsValue(
|
auto allowedAny = PeerFlagsValue(
|
||||||
user,
|
user,
|
||||||
(UserDataFlag::Deleted | UserDataFlag::MeRequiresPremiumToWrite)
|
(UserDataFlag::Deleted | UserDataFlag::RequiresPremiumToWrite)
|
||||||
) | rpl::map([=](UserDataFlags flags) {
|
) | rpl::map([=](UserDataFlags flags) {
|
||||||
return (flags & UserDataFlag::Deleted)
|
return (flags & UserDataFlag::Deleted)
|
||||||
? rpl::single(false)
|
? rpl::single(false)
|
||||||
: !(flags & UserDataFlag::MeRequiresPremiumToWrite)
|
: !(flags & UserDataFlag::RequiresPremiumToWrite)
|
||||||
? rpl::single(true)
|
? rpl::single(true)
|
||||||
: AmPremiumValue(&user->session());
|
: AmPremiumValue(&user->session());
|
||||||
}) | rpl::flatten_latest();
|
}) | rpl::flatten_latest();
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue