Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4505a2bf2d | ||
|
|
a314380b08 | ||
|
|
0c07a015c6 | ||
|
|
a0c7697280 | ||
|
|
04023da723 | ||
|
|
13ea045055 | ||
|
|
f03351d112 | ||
|
|
48d9f10f5b | ||
|
|
2b53df98cd | ||
|
|
99a7a13218 | ||
|
|
2d2d4ac002 | ||
|
|
d12e8023e3 | ||
|
|
17181cee8f | ||
|
|
a74ee911b3 | ||
|
|
b0b37172ce | ||
|
|
cccf048e3f | ||
|
|
82e890746b | ||
|
|
188d65d700 | ||
|
|
4569f93e70 | ||
|
|
bcd1d8461f | ||
|
|
183a9139f9 | ||
|
|
80a1e6ecf3 | ||
|
|
aa1f8cfb8f | ||
|
|
8060691f3d | ||
|
|
bf26de495a | ||
|
|
73b3f7e298 | ||
|
|
22191649aa | ||
|
|
0557907310 | ||
|
|
0f283c484d | ||
|
|
e33ca9d316 | ||
|
|
f93f4c72f7 | ||
|
|
f0b9bc10c2 | ||
|
|
f583879aee | ||
|
|
7ea6c6c84b | ||
|
|
2532a0ff59 | ||
|
|
c3f354826d | ||
|
|
6d1e421ad7 | ||
|
|
29b0055e39 | ||
|
|
2cb20fe342 | ||
|
|
fc97fa4415 | ||
|
|
876a50f759 | ||
|
|
eb0d2868f5 | ||
|
|
e215d5bc64 | ||
|
|
3f0d687656 | ||
|
|
d59eb8e731 | ||
|
|
04e9eed88d | ||
|
|
5072e95f16 | ||
|
|
a2b8366477 | ||
|
|
e9a6bee046 | ||
|
|
080a8d7ee5 | ||
|
|
f94fd3118b | ||
|
|
8ddb13d6e2 | ||
|
|
c6cf8be8d4 | ||
|
|
e92270a9ab | ||
|
|
65d6636a41 | ||
|
|
4701badb2a | ||
|
|
3565215c81 | ||
|
|
3957fea5e4 | ||
|
|
10f1ae152d | ||
|
|
eb29b6bffe | ||
|
|
d157eb0b6e | ||
|
|
0045eb4598 | ||
|
|
b61c66c385 | ||
|
|
56d6c4eb30 | ||
|
|
a6030d708d | ||
|
|
683c3c4f36 | ||
|
|
bd084f9181 | ||
|
|
fef133bf0a | ||
|
|
15c226e6cf | ||
|
|
84f111d641 | ||
|
|
18c1e7ac60 | ||
|
|
ee6dbdced6 | ||
|
|
cb443d797d | ||
|
|
5a6497ec70 | ||
|
|
893ca8bcbd | ||
|
|
f91e4c8b69 | ||
|
|
f7c777d07d | ||
|
|
12a8e8616c | ||
|
|
2fbf7e8504 | ||
|
|
6864e6d5bf | ||
|
|
09b4e0e21b | ||
|
|
f381005184 | ||
|
|
42a2de4bf0 | ||
|
|
1fd1e34844 | ||
|
|
64dbbd7d09 | ||
|
|
c137e577dc | ||
|
|
f592a9202f | ||
|
|
cdc24d2e57 | ||
|
|
b8bf3f6520 | ||
|
|
82cec83d87 | ||
|
|
1e14667006 | ||
|
|
6539d14852 | ||
|
|
400df0f980 | ||
|
|
4cafb3f966 | ||
|
|
d8892c4eb4 | ||
|
|
1ebd25e76e | ||
|
|
ca89aa8377 | ||
|
|
ad6272bfe5 | ||
|
|
b401c37c39 | ||
|
|
552dd318cd | ||
|
|
a97880132a | ||
|
|
89058c63c8 | ||
|
|
747e417809 | ||
|
|
fd26e1618c | ||
|
|
86ea760011 | ||
|
|
824237deb3 | ||
|
|
ca8c70cc95 | ||
|
|
46fcc695a5 | ||
|
|
0e866a0266 | ||
|
|
63c36f5907 | ||
|
|
5299500d78 | ||
|
|
3b3d1aa9cc | ||
|
|
62d2346471 | ||
|
|
1e15764bb9 | ||
|
|
a6bfd35f1a | ||
|
|
3296efe46b | ||
|
|
51ddfbc340 | ||
|
|
42142d819a | ||
|
|
bbf9d523a6 | ||
|
|
721877e10a | ||
|
|
a9e95a128f | ||
|
|
2b920eaa87 | ||
|
|
a3ec759e62 | ||
|
|
3661442acd | ||
|
|
f232d329c5 | ||
|
|
ac5cf3bd80 | ||
|
|
ac13ac7a2c | ||
|
|
0bccb35cb0 | ||
|
|
18d9484ab1 | ||
|
|
fe7c06bc84 | ||
|
|
b6fb3bbf1d | ||
|
|
927d7a3aeb | ||
|
|
979973745b | ||
|
|
afab863f11 | ||
|
|
168162c174 | ||
|
|
2b122087c4 | ||
|
|
043d97cfdf | ||
|
|
794818953d | ||
|
|
783570fe9f | ||
|
|
b1e2a4243e | ||
|
|
b347308137 | ||
|
|
b3c8a79946 | ||
|
|
9822c56f1a | ||
|
|
cdd7ff5c6d | ||
|
|
5aba2f25cc | ||
|
|
96398daa78 | ||
|
|
61ceb66415 | ||
|
|
b4f173cdb3 | ||
|
|
03e4592082 | ||
|
|
cf2dbe50a1 | ||
|
|
e5bb5b75fe | ||
|
|
cffce47eb1 | ||
|
|
ca0adba6cf |
2
.github/workflows/mac_packaged.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew upgrade || true
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg@6 libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
|
||||
6
.github/workflows/snap.yml
vendored
@@ -57,14 +57,14 @@ jobs:
|
||||
sudo iptables -P FORWARD ACCEPT
|
||||
sudo snap install --classic snapcraft
|
||||
sudo usermod -aG lxd $USER
|
||||
sudo snap run lxd init --auto
|
||||
sudo snap run lxd waitready
|
||||
sudo lxd init --auto
|
||||
sudo lxd waitready
|
||||
|
||||
- name: Free up some disk space.
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
|
||||
- name: Telegram Desktop snap build.
|
||||
run: sg lxd -c 'snap run snapcraft --verbosity=debug'
|
||||
run: sudo -u $USER snap run snapcraft --verbosity=debug
|
||||
|
||||
- name: Move artifact.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
|
||||
@@ -58,7 +58,7 @@ Version **1.8.15** was the last that supports older systems
|
||||
* Guideline Support Library ([MIT License](https://github.com/Microsoft/GSL/blob/master/LICENSE))
|
||||
* Range-v3 ([Boost License](https://github.com/ericniebler/range-v3/blob/master/LICENSE.txt))
|
||||
* Open Sans font ([Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html))
|
||||
* Vazir font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazir-font/blob/master/OFL.txt))
|
||||
* Vazirmatn font ([SIL Open Font License 1.1](https://github.com/rastikerdar/vazirmatn/blob/master/OFL.txt))
|
||||
* Emoji alpha codes ([MIT License](https://github.com/emojione/emojione/blob/master/extras/alpha-codes/LICENSE.md))
|
||||
* xxHash ([BSD License](https://github.com/Cyan4973/xxHash/blob/dev/LICENSE))
|
||||
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))
|
||||
|
||||
@@ -322,8 +322,6 @@ PRIVATE
|
||||
boxes/send_gif_with_caption_box.h
|
||||
boxes/send_files_box.cpp
|
||||
boxes/send_files_box.h
|
||||
boxes/sessions_box.cpp
|
||||
boxes/sessions_box.h
|
||||
boxes/share_box.cpp
|
||||
boxes/share_box.h
|
||||
boxes/star_gift_box.cpp
|
||||
@@ -470,6 +468,7 @@ PRIVATE
|
||||
core/sandbox.h
|
||||
core/shortcuts.cpp
|
||||
core/shortcuts.h
|
||||
core/stars_amount.h
|
||||
core/ui_integration.cpp
|
||||
core/ui_integration.h
|
||||
core/update_checker.cpp
|
||||
@@ -919,6 +918,12 @@ PRIVATE
|
||||
info/bot/earn/info_bot_earn_list.h
|
||||
info/bot/earn/info_bot_earn_widget.cpp
|
||||
info/bot/earn/info_bot_earn_widget.h
|
||||
info/bot/starref/info_bot_starref_common.cpp
|
||||
info/bot/starref/info_bot_starref_common.h
|
||||
info/bot/starref/info_bot_starref_join_widget.cpp
|
||||
info/bot/starref/info_bot_starref_join_widget.h
|
||||
info/bot/starref/info_bot_starref_setup_widget.cpp
|
||||
info/bot/starref/info_bot_starref_setup_widget.h
|
||||
info/channel_statistics/boosts/create_giveaway_box.cpp
|
||||
info/channel_statistics/boosts/create_giveaway_box.h
|
||||
info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp
|
||||
@@ -941,6 +946,12 @@ PRIVATE
|
||||
info/downloads/info_downloads_provider.h
|
||||
info/downloads/info_downloads_widget.cpp
|
||||
info/downloads/info_downloads_widget.h
|
||||
info/global_media/info_global_media_widget.cpp
|
||||
info/global_media/info_global_media_widget.h
|
||||
info/global_media/info_global_media_inner_widget.cpp
|
||||
info/global_media/info_global_media_inner_widget.h
|
||||
info/global_media/info_global_media_provider.cpp
|
||||
info/global_media/info_global_media_provider.h
|
||||
info/media/info_media_buttons.h
|
||||
info/media/info_media_common.cpp
|
||||
info/media/info_media_common.h
|
||||
@@ -1405,6 +1416,8 @@ PRIVATE
|
||||
settings/cloud_password/settings_cloud_password_manage.h
|
||||
settings/cloud_password/settings_cloud_password_start.cpp
|
||||
settings/cloud_password/settings_cloud_password_start.h
|
||||
settings/settings_active_sessions.cpp
|
||||
settings/settings_active_sessions.h
|
||||
settings/settings_advanced.cpp
|
||||
settings/settings_advanced.h
|
||||
settings/settings_blocked_peers.cpp
|
||||
@@ -1566,8 +1579,6 @@ PRIVATE
|
||||
ui/item_text_options.cpp
|
||||
ui/item_text_options.h
|
||||
ui/resize_area.h
|
||||
ui/search_field_controller.cpp
|
||||
ui/search_field_controller.h
|
||||
ui/unread_badge.cpp
|
||||
ui/unread_badge.h
|
||||
window/main_window.cpp
|
||||
|
||||
BIN
Telegram/Resources/animations/starref_link.tgs
Normal file
BIN
Telegram/Resources/art/affiliate_logo.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
Telegram/Resources/icons/menu/affiliate_simple.png
Normal file
|
After Width: | Height: | Size: 655 B |
BIN
Telegram/Resources/icons/menu/affiliate_simple@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/affiliate_simple@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/affiliate_transparent.png
Normal file
|
After Width: | Height: | Size: 820 B |
BIN
Telegram/Resources/icons/menu/affiliate_transparent@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/affiliate_transparent@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/menu/bot.png
Normal file
|
After Width: | Height: | Size: 586 B |
BIN
Telegram/Resources/icons/menu/bot@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/menu/bot@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/menu/bot_add.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
Telegram/Resources/icons/menu/bot_add@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/bot_add@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/caption_hide.png
Normal file
|
After Width: | Height: | Size: 718 B |
BIN
Telegram/Resources/icons/menu/caption_hide@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/caption_hide@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/caption_show.png
Normal file
|
After Width: | Height: | Size: 505 B |
BIN
Telegram/Resources/icons/menu/caption_show@2x.png
Normal file
|
After Width: | Height: | Size: 763 B |
BIN
Telegram/Resources/icons/menu/caption_show@3x.png
Normal file
|
After Width: | Height: | Size: 963 B |
BIN
Telegram/Resources/icons/menu/forwarded_status.png
Normal file
|
After Width: | Height: | Size: 691 B |
BIN
Telegram/Resources/icons/menu/forwarded_status@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/menu/forwarded_status@3x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Telegram/Resources/icons/menu/name_hide.png
Normal file
|
After Width: | Height: | Size: 615 B |
BIN
Telegram/Resources/icons/menu/name_hide@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/menu/name_hide@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/menu/name_show.png
Normal file
|
After Width: | Height: | Size: 517 B |
BIN
Telegram/Resources/icons/menu/name_show@2x.png
Normal file
|
After Width: | Height: | Size: 959 B |
BIN
Telegram/Resources/icons/menu/name_show@3x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/menu/stars_share.png
Normal file
|
After Width: | Height: | Size: 791 B |
BIN
Telegram/Resources/icons/menu/stars_share@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/stars_share@3x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_menu_activate" = "Use this account";
|
||||
"lng_menu_set_status" = "Set Emoji Status";
|
||||
"lng_menu_change_status" = "Change Emoji Status";
|
||||
"lng_menu_my_profile" = "My Profile";
|
||||
"lng_menu_my_stories" = "My Stories";
|
||||
"lng_menu_my_groups" = "My Groups";
|
||||
"lng_menu_my_channels" = "My Channels";
|
||||
@@ -288,6 +289,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_error_cant_add_member" = "Sorry, you can't add the bot to this group. Ask a group admin to do it.";
|
||||
"lng_error_cant_add_bot" = "Sorry, this bot can't be added to groups.";
|
||||
"lng_error_cant_add_admin_invite" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
|
||||
"lng_error_you_blocked_user" = "Sorry, you can't add this user or bot to groups because you've blocked them. Please unblock to proceed.";
|
||||
"lng_error_add_admin_not_member" = "You can't add this user as an admin because they are not a member of this group and you are not allowed to add them.";
|
||||
"lng_error_user_admin_invalid" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
|
||||
"lng_error_channel_bots_too_much" = "Sorry, this channel has too many bots.";
|
||||
"lng_error_group_bots_too_much" = "There are too many bots in this group. Please remove some of the bots you're not using first.";
|
||||
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them.";
|
||||
"lng_error_cant_ban_admin" = "You can't ban this user because they are an admin in this group and you are not allowed to demote them.";
|
||||
"lng_error_cant_reply_other" = "This message can't be replied in another chat.";
|
||||
@@ -1545,6 +1551,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_channel_title" = "Manage Channel";
|
||||
"lng_manage_bot_title" = "Manage Bot";
|
||||
"lng_manage_peer_recent_actions" = "Recent actions";
|
||||
"lng_manage_peer_star_ref" = "Affiliate programs";
|
||||
"lng_manage_peer_members" = "Members";
|
||||
"lng_manage_peer_subscribers" = "Subscribers";
|
||||
"lng_manage_peer_administrators" = "Administrators";
|
||||
@@ -1618,11 +1625,99 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_bot_balance" = "Balance";
|
||||
"lng_manage_peer_bot_balance_currency" = "Toncoin";
|
||||
"lng_manage_peer_bot_balance_credits" = "Stars";
|
||||
"lng_manage_peer_bot_star_ref" = "Affiliate Program";
|
||||
"lng_manage_peer_bot_star_ref_off" = "Off";
|
||||
"lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there.";
|
||||
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
|
||||
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
|
||||
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
|
||||
"lng_manage_peer_bot_about" = "Use {bot} to manage this bot.";
|
||||
|
||||
"lng_star_ref_title" = "Affiliate Program";
|
||||
"lng_star_ref_about" = "Reward those who help grow your user base.";
|
||||
"lng_star_ref_share_title" = "Share revenue with affiliates";
|
||||
"lng_star_ref_share_about" = "Set the commission for revenue generated by users referred to you.";
|
||||
"lng_star_ref_launch_title" = "Launch your affiliate program";
|
||||
"lng_star_ref_launch_about" = "Telegram will feature your program for millions of potential affiliates.";
|
||||
"lng_star_ref_let_title" = "Let affiliate promote you";
|
||||
"lng_star_ref_let_about" = "Affiliates will share your referral link with their audience.";
|
||||
"lng_star_ref_commission_title" = "Commission";
|
||||
"lng_star_ref_commission_about" = "Define the percentage of star revenue your affiliates earn for referring users to your bot.";
|
||||
"lng_star_ref_duration_title" = "Duration";
|
||||
"lng_star_ref_duration_about" = "Set the duration for which affiliates will earn commissions from referred users.";
|
||||
"lng_star_ref_existing_title" = "View existing programs";
|
||||
"lng_star_ref_existing_about" = "Explore what other mini apps offer.";
|
||||
"lng_star_ref_add_bot" = "Add {bot}";
|
||||
"lng_star_ref_end" = "End Affiliate Program";
|
||||
"lng_star_ref_start" = "Start Affiliate Program";
|
||||
"lng_star_ref_start_disabled" = "Available in {time}";
|
||||
"lng_star_ref_start_info" = "By creating an affiliate program, you agree to the {terms} of Affiliate Programs.";
|
||||
"lng_star_ref_update" = "Update Affiliate Program";
|
||||
"lng_star_ref_update_info" = "By updating an affiliate program, you agree to the {terms} of Affiliate Programs.";
|
||||
"lng_star_ref_button_link" = "terms and conditions";
|
||||
"lng_star_ref_tos_url" = "https://telegram.org/tos/mini-apps";
|
||||
"lng_star_ref_warning_title" = "Warning";
|
||||
"lng_star_ref_warning_text" = "Once you start the affiliate program, you won't be able to decrease its commission or duration. You can only increase these parameters or end the program, which will disable all previously distributed referral links.";
|
||||
"lng_star_ref_warning_change" = "This change is irreversible. You won't be able to reduce commission or duration. You can only increase these parameters or end the program, which will disable all previously shared referral links.";
|
||||
"lng_star_ref_warning_start" = "Start";
|
||||
"lng_star_ref_warning_update" = "Update";
|
||||
"lng_star_ref_warning_if_end" = "If you end your affiliate program:";
|
||||
"lng_star_ref_warning_if_end1" = "Any referral links already shared will be disabled in **24** hours.";
|
||||
"lng_star_ref_warning_if_end2" = "All participating affiliates will be notified.";
|
||||
"lng_star_ref_warning_if_end3" = "You will be able to start a new affiliate program only in **24** hours.";
|
||||
"lng_star_ref_warning_end" = "End Anyway";
|
||||
"lng_star_ref_created_title" = "Affiliate program started";
|
||||
"lng_star_ref_created_text" = "Any Telegram user, channel owner or mini app developer can now join your program.";
|
||||
"lng_star_ref_updated_title" = "Affiliate program updated";
|
||||
"lng_star_ref_updated_text" = "Any Telegram user, channel owner or mini app developer can join your program.";
|
||||
"lng_star_ref_ended_title" = "Affiliate program ended";
|
||||
"lng_star_ref_ended_text" = "Participating affiliates have been notified. All referral links will be disabled in **24** hours.";
|
||||
"lng_star_ref_list_title" = "Affiliate Programs";
|
||||
"lng_star_ref_list_about_channel" = "Promote mini apps to your subscribers and earn a share of their revenue in Stars.";
|
||||
"lng_star_ref_list_text" = "Earn a commission each time a user who first accessed a mini app through your referral link spends **Stars** within it.";
|
||||
"lng_star_ref_list_my" = "My Programs";
|
||||
"lng_star_ref_list_my_open" = "Open App";
|
||||
"lng_star_ref_list_my_copy" = "Copy Link";
|
||||
"lng_star_ref_list_my_leave" = "Leave";
|
||||
"lng_star_ref_list_subtitle" = "Programs";
|
||||
"lng_star_ref_sort_text" = "Sort by {sort}";
|
||||
"lng_star_ref_sort_profitability" = "Profitability";
|
||||
"lng_star_ref_sort_date" = "Date";
|
||||
"lng_star_ref_sort_revenue" = "Revenue";
|
||||
"lng_star_ref_reliable_title" = "Reliable";
|
||||
"lng_star_ref_reliable_about" = "Receive guaranteed commissions for spending by users you refer.";
|
||||
"lng_star_ref_transparent_title" = "Transparent";
|
||||
"lng_star_ref_transparent_about" = "Track your commissions from referred users in real time.";
|
||||
"lng_star_ref_simple_title" = "Simple";
|
||||
"lng_star_ref_simple_about" = "Choose a mini app below, get your referral link, and start earning Stars.";
|
||||
"lng_star_ref_duration_forever" = "Forever";
|
||||
"lng_star_ref_one_about" = "{app} will share {amount} of the revenue from each user you refer to it {duration}.";
|
||||
"lng_star_ref_one_about_for_forever" = "for **lifetime**";
|
||||
"lng_star_ref_one_about_for_months#one" = "for **{count} month**";
|
||||
"lng_star_ref_one_about_for_months#other" = "for **{count} months**";
|
||||
"lng_star_ref_one_about_for_years#one" = "for **{count} year**";
|
||||
"lng_star_ref_one_about_for_years#other" = "for **{count} years**";
|
||||
"lng_star_ref_one_daily_revenue" = "Daily revenue per user: {amount}";
|
||||
"lng_star_ref_one_join" = "Join Program";
|
||||
"lng_star_ref_one_join_text" = "By joining this program, you agree to the {terms} of Affiliate Programs.";
|
||||
"lng_star_ref_joined_title" = "Program joined";
|
||||
"lng_star_ref_joined_text" = "You can now copy the referral link.";
|
||||
"lng_star_ref_link_title" = "Referral Link";
|
||||
"lng_star_ref_link_about_channel" = "Share this link with your subscribers to earn a {amount} commission on their spending in {app} {duration}.";
|
||||
"lng_star_ref_link_about_user" = "Share this link with your friends to earn a {amount} commission on their spending in {app} {duration}.";
|
||||
"lng_star_ref_link_about_bot" = "Share this link with your users to earn a {amount} commission on their spending in {app} {duration}.";
|
||||
"lng_star_ref_link_recipient" = "Commissions will be sent to:";
|
||||
"lng_star_ref_link_copy" = "Copy Link";
|
||||
"lng_star_ref_link_copy_none" = "No one have opened {app} through this link.";
|
||||
"lng_star_ref_link_copy_users#one" = "{count} user have opened {app} through this link.";
|
||||
"lng_star_ref_link_copy_users#other" = "{count} users have opened {app} through this link.";
|
||||
"lng_star_ref_link_copied_title" = "Link copied to clipboard";
|
||||
"lng_star_ref_link_copied_text" = "Share this link and earn {amount} of what people who use it spend in {app}!";
|
||||
"lng_star_ref_stopped" = "This affiliate link is no longer active.";
|
||||
"lng_star_ref_revoke_title" = "Revoke Link";
|
||||
"lng_star_ref_revoke_text" = "Are you sure you want to revoke the link of {bot}?";
|
||||
"lng_star_ref_revoked_title" = "Link removed";
|
||||
"lng_star_ref_revoked_text" = "It will no longer work.";
|
||||
|
||||
"lng_manage_discussion_group" = "Discussion";
|
||||
"lng_manage_discussion_group_add" = "Add a group";
|
||||
@@ -2435,11 +2530,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
|
||||
"lng_credits_summary_options_about_link" = "Terms and Conditions";
|
||||
"lng_credits_summary_options_about_url" = "https://telegram.org/tos/stars";
|
||||
"lng_credits_summary_earn_title" = "Earn Stars";
|
||||
"lng_credits_summary_earn_about" = "Distribute links to mini apps and earn a share of their revenue in Stars.";
|
||||
"lng_credits_summary_history_tab_full" = "All Transactions";
|
||||
"lng_credits_summary_history_tab_in" = "Incoming";
|
||||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_commission" = "{amount} commission";
|
||||
"lng_credits_more_options" = "More Options";
|
||||
"lng_credits_balance_me" = "your balance";
|
||||
"lng_credits_buy_button" = "Buy More Stars";
|
||||
@@ -2502,6 +2600,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
|
||||
"lng_credits_box_history_entry_id" = "Transaction ID";
|
||||
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
|
||||
"lng_credits_box_history_entry_reason_star_ref" = "Affiliate Program";
|
||||
"lng_credits_box_history_entry_affiliate" = "Affiliate";
|
||||
"lng_credits_box_history_entry_miniapp" = "Mini App";
|
||||
"lng_credits_box_history_entry_referred" = "Referred User";
|
||||
"lng_credits_box_history_entry_success_date" = "Transaction date";
|
||||
"lng_credits_box_history_entry_success_url" = "Transaction link";
|
||||
"lng_credits_box_history_entry_media" = "Media";
|
||||
@@ -3080,6 +3182,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_stars_sold_out" = "sold out";
|
||||
"lng_gift_stars_tabs_all" = "All Gifts";
|
||||
"lng_gift_stars_tabs_limited" = "Limited";
|
||||
"lng_gift_stars_tabs_in_stock" = "In Stock";
|
||||
"lng_gift_send_title" = "Send a Gift";
|
||||
"lng_gift_send_message" = "Enter Message";
|
||||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
@@ -3096,11 +3199,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
|
||||
"lng_gift_availability" = "Availability";
|
||||
"lng_gift_from_hidden" = "Hidden User";
|
||||
"lng_gift_visibility" = "Visibility";
|
||||
"lng_gift_visibility_shown" = "Visible on your page";
|
||||
"lng_gift_visibility_hidden" = "Not visible on your page";
|
||||
"lng_gift_visibility_show" = "show";
|
||||
"lng_gift_visibility_hide" = "hide";
|
||||
"lng_gift_availability_left#one" = "{count} of {amount} left";
|
||||
"lng_gift_availability_left#other" = "{count} of {amount} left";
|
||||
"lng_gift_availability_none" = "None of {amount} left";
|
||||
"lng_gift_display_on_page" = "Display on my Page";
|
||||
"lng_gift_display_on_page_hide" = "Hide from my Page";
|
||||
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
|
||||
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
|
||||
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
|
||||
@@ -3376,6 +3482,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_replies_no_comments" = "No comments here yet...";
|
||||
|
||||
"lng_verification_codes" = "Verification Codes";
|
||||
"lng_verification_codes_about" = "Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn't request any codes — don't worry! Most likely, someone made a mistake when entering their number.";
|
||||
|
||||
"lng_archived_name" = "Archived chats";
|
||||
"lng_archived_add" = "Archive";
|
||||
@@ -3550,6 +3657,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_mark_read_sure" = "Are you sure you want to mark all chats from this folder as read?";
|
||||
"lng_context_mark_read_all" = "Mark all chats as read";
|
||||
"lng_context_mark_read_all_sure" = "Are you sure you want to mark all chats as read?";
|
||||
"lng_context_mark_read_all_sure_2" = "**This action cannot be undone.**";
|
||||
"lng_context_mark_read_mentions_all" = "Mark all mentions as read";
|
||||
"lng_context_mark_read_reactions_all" = "Read all reactions";
|
||||
"lng_context_archive_expand" = "Expand";
|
||||
@@ -3709,6 +3817,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_paid_react_agree_link" = "Terms of Service";
|
||||
"lng_paid_react_toast#one" = "Star Sent!";
|
||||
"lng_paid_react_toast#other" = "Stars Sent!";
|
||||
"lng_paid_react_toast_anonymous#one" = "Star sent anonymously!";
|
||||
"lng_paid_react_toast_anonymous#other" = "Stars sent anonymously!";
|
||||
"lng_paid_react_toast_text#one" = "You reacted with **{count} Star**.";
|
||||
"lng_paid_react_toast_text#other" = "You reacted with **{count} Stars**.";
|
||||
"lng_paid_react_undo" = "Undo";
|
||||
@@ -4815,6 +4925,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forward_show_captions" = "Show captions";
|
||||
"lng_forward_change_recipient" = "Change recipient";
|
||||
"lng_forward_sender_names_removed" = "Sender names removed";
|
||||
"lng_forward_header_short" = "Forward";
|
||||
"lng_forward_action_show_sender" = "Show Sender Name";
|
||||
"lng_forward_action_show_senders" = "Show Sender Names";
|
||||
"lng_forward_action_hide_sender" = "Hide Sender Name";
|
||||
"lng_forward_action_hide_senders" = "Hide Sender Names";
|
||||
"lng_forward_action_show_caption" = "Show Caption";
|
||||
"lng_forward_action_show_captions" = "Show Captions";
|
||||
"lng_forward_action_hide_caption" = "Hide Caption";
|
||||
"lng_forward_action_hide_captions" = "Hide Captions";
|
||||
"lng_forward_action_change_recipient" = "Change Recipient";
|
||||
"lng_forward_action_remove" = "Do Not Forward";
|
||||
|
||||
"lng_passport_title" = "Telegram Passport";
|
||||
"lng_passport_request1" = "{bot} requests access to your personal data";
|
||||
@@ -5718,6 +5839,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_recent_chats" = "Chats";
|
||||
"lng_recent_channels" = "Channels";
|
||||
"lng_recent_apps" = "Apps";
|
||||
"lng_all_photos" = "Photos";
|
||||
"lng_all_videos" = "Videos";
|
||||
"lng_all_downloads" = "Downloads";
|
||||
"lng_all_links" = "Links";
|
||||
"lng_all_files" = "Files";
|
||||
"lng_all_music" = "Music";
|
||||
"lng_all_voice" = "Voice";
|
||||
"lng_channels_none_title" = "No channels yet...";
|
||||
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
|
||||
"lng_channels_your_title" = "Channels you joined";
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<file alias="search.tgs">../../animations/search.tgs</file>
|
||||
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
|
||||
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
|
||||
<file alias="starref_link.tgs">../../animations/starref_link.tgs</file>
|
||||
|
||||
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
||||
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<file alias="art/bg_thumbnail.png">../../art/bg_thumbnail.png</file>
|
||||
<file alias="art/bg_initial.jpg">../../art/bg_initial.jpg</file>
|
||||
<file alias="art/business_logo.png">../../art/business_logo.png</file>
|
||||
<file alias="art/affiliate_logo.png">../../art/affiliate_logo.png</file>
|
||||
<file alias="art/logo_256.png">../../art/logo_256.png</file>
|
||||
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
|
||||
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.8.4.0" />
|
||||
Version="5.9.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,8,4,0
|
||||
PRODUCTVERSION 5,8,4,0
|
||||
FILEVERSION 5,9,2,0
|
||||
PRODUCTVERSION 5,9,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.8.4.0"
|
||||
VALUE "FileVersion", "5.9.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.8.4.0"
|
||||
VALUE "ProductVersion", "5.9.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,8,4,0
|
||||
PRODUCTVERSION 5,8,4,0
|
||||
FILEVERSION 5,9,2,0
|
||||
PRODUCTVERSION 5,9,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.8.4.0"
|
||||
VALUE "FileVersion", "5.9.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.8.4.0"
|
||||
VALUE "ProductVersion", "5.9.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_chat_filters.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
@@ -139,7 +140,8 @@ void InitFilterLinkHeader(
|
||||
Ui::FilterLinkHeaderType type,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
rpl::producer<int> count) {
|
||||
rpl::producer<int> count,
|
||||
bool horizontalFilters) {
|
||||
const auto icon = Ui::LookupFilterIcon(
|
||||
Ui::LookupFilterIconByEmoji(
|
||||
iconEmoji
|
||||
@@ -153,7 +155,7 @@ void InitFilterLinkHeader(
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
: rpl::single(0)),
|
||||
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
|
||||
.horizontalFilters = horizontalFilters,
|
||||
});
|
||||
const auto widget = header.widget;
|
||||
widget->resizeToWidth(st::boxWideWidth);
|
||||
@@ -548,6 +550,26 @@ void ShowImportToast(
|
||||
strong->showToast(std::move(text));
|
||||
}
|
||||
|
||||
void HandleEnterInBox(not_null<Ui::BoxContent*> box) {
|
||||
const auto isEnter = [=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
if (const auto k = static_cast<QKeyEvent*>(event.get())) {
|
||||
return (k->key() == Qt::Key_Enter)
|
||||
|| (k->key() == Qt::Key_Return);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
base::install_event_filter(box, [=](not_null<QEvent*> event) {
|
||||
if (isEnter(event)) {
|
||||
box->triggerButton(0);
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void ProcessFilterInvite(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &slug,
|
||||
@@ -572,6 +594,8 @@ void ProcessFilterInvite(
|
||||
title,
|
||||
std::move(peers),
|
||||
std::move(already));
|
||||
const auto horizontalFilters = !strong->enoughSpaceForFilters()
|
||||
|| Core::App().settings().chatFiltersHorizontal();
|
||||
const auto raw = controller.get();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setStyle(st::filterInviteBox);
|
||||
@@ -588,7 +612,7 @@ void ProcessFilterInvite(
|
||||
});
|
||||
InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
|
||||
raw->adjust(min, max, addedTop);
|
||||
}, type, title, iconEmoji, rpl::duplicate(badge));
|
||||
}, type, title, iconEmoji, rpl::duplicate(badge), horizontalFilters);
|
||||
|
||||
raw->setRealContentHeight(box->heightValue());
|
||||
|
||||
@@ -610,6 +634,8 @@ void ProcessFilterInvite(
|
||||
|
||||
box->addButton(std::move(owned));
|
||||
|
||||
HandleEnterInBox(box);
|
||||
|
||||
struct State {
|
||||
bool importing = false;
|
||||
};
|
||||
@@ -798,6 +824,8 @@ void ProcessFilterRemove(
|
||||
title,
|
||||
std::move(suggest),
|
||||
std::move(all));
|
||||
const auto horizontalFilters = !strong->enoughSpaceForFilters()
|
||||
|| Core::App().settings().chatFiltersHorizontal();
|
||||
const auto raw = controller.get();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setStyle(st::filterInviteBox);
|
||||
@@ -809,7 +837,7 @@ void ProcessFilterRemove(
|
||||
});
|
||||
InitFilterLinkHeader(box, [=](int min, int max, int addedTop) {
|
||||
raw->adjust(min, max, addedTop);
|
||||
}, type, title, iconEmoji, rpl::single(0));
|
||||
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
|
||||
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
@@ -829,6 +857,8 @@ void ProcessFilterRemove(
|
||||
|
||||
box->addButton(std::move(owned));
|
||||
|
||||
HandleEnterInBox(box);
|
||||
|
||||
raw->selectedValue(
|
||||
) | rpl::start_with_next([=](
|
||||
base::flat_set<not_null<PeerData*>> &&peers) {
|
||||
|
||||
@@ -207,32 +207,12 @@ void ConfirmSubscriptionBox(
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
{
|
||||
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
|
||||
widget,
|
||||
false,
|
||||
Ui::Premium::MiniStars::Type::BiStars);
|
||||
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
||||
widget->resize(
|
||||
st::boxWideWidth - photoSize,
|
||||
photoSize * 2);
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
widget->moveToLeft(photoSize / 2, 0);
|
||||
const auto starsRect = Rect(widget->size());
|
||||
stars->setPosition(starsRect.topLeft());
|
||||
stars->setSize(starsRect.size());
|
||||
widget->lower();
|
||||
}, widget->lifetime());
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(widget);
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
Settings::AddMiniStars(
|
||||
content,
|
||||
Ui::CreateChild<Ui::RpWidget>(content),
|
||||
photoSize,
|
||||
box->width(),
|
||||
2.);
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
|
||||
@@ -74,7 +74,16 @@ constexpr auto kTransactionsLimit = 100;
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto reaction = tl.data().is_reaction();
|
||||
const auto incoming = (int64(tl.data().vstars().v) >= 0);
|
||||
const auto amount = Data::FromTL(tl.data().vstars());
|
||||
const auto starrefAmount = tl.data().vstarref_amount()
|
||||
? Data::FromTL(*tl.data().vstarref_amount())
|
||||
: StarsAmount();
|
||||
const auto starrefCommission
|
||||
= tl.data().vstarref_commission_permille().value_or_empty();
|
||||
const auto starrefBarePeerId = tl.data().vstarref_peer()
|
||||
? peerFromMTP(*tl.data().vstarref_peer()).value
|
||||
: 0;
|
||||
const auto incoming = (amount >= StarsAmount());
|
||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
@@ -83,7 +92,7 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.date = base::unixtime::parse(tl.data().vdate().v),
|
||||
.photoId = photo ? photo->id : 0,
|
||||
.extended = std::move(extended),
|
||||
.credits = tl.data().vstars().v,
|
||||
.credits = Data::FromTL(tl.data().vstars()),
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = saveActorId ? peer->id.value : barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
@@ -92,6 +101,9 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.starrefAmount = starrefAmount,
|
||||
.starrefCommission = starrefCommission,
|
||||
.starrefRecipientId = starrefBarePeerId,
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -181,7 +193,7 @@ constexpr auto kTransactionsLimit = 100;
|
||||
return Data::CreditsStatusSlice{
|
||||
.list = std::move(entries),
|
||||
.subscriptions = std::move(subscriptions),
|
||||
.balance = status.data().vbalance().v,
|
||||
.balance = Data::FromTL(status.data().vbalance()),
|
||||
.subscriptionsMissingBalance
|
||||
= status.data().vsubscriptions_missing_balance().value_or_empty(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value()
|
||||
@@ -268,8 +280,8 @@ void CreditsStatus::request(
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||
)).done([=](const TLResult &result) {
|
||||
_requestId = 0;
|
||||
const auto balance = result.data().vbalance().v;
|
||||
_peer->session().credits().apply(_peer->id, balance);
|
||||
const auto &balance = result.data().vbalance();
|
||||
_peer->session().credits().apply(_peer->id, Data::FromTL(balance));
|
||||
if (const auto onstack = done) {
|
||||
onstack(StatusFromTL(result, _peer));
|
||||
}
|
||||
@@ -348,7 +360,9 @@ rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
const auto api = lifetime.make_state<MTP::Sender>(&session->mtp());
|
||||
|
||||
api->request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
MTP_flags(0),
|
||||
MTP_string(username),
|
||||
MTP_string()
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
session->data().processUsers(result.data().vusers());
|
||||
session->data().processChats(result.data().vchats());
|
||||
@@ -380,12 +394,13 @@ rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
|
||||
)).done([=](const MTPpayments_StarsRevenueStats &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &status = data.vstatus().data();
|
||||
using Data::FromTL;
|
||||
_data = Data::CreditsEarnStatistics{
|
||||
.revenueGraph = StatisticalGraphFromTL(
|
||||
data.vrevenue_graph()),
|
||||
.currentBalance = status.vcurrent_balance().v,
|
||||
.availableBalance = status.vavailable_balance().v,
|
||||
.overallRevenue = status.voverall_revenue().v,
|
||||
.currentBalance = FromTL(status.vcurrent_balance()),
|
||||
.availableBalance = FromTL(status.vavailable_balance()),
|
||||
.overallRevenue = FromTL(status.voverall_revenue()),
|
||||
.usdRate = data.vusd_rate().v,
|
||||
.isWithdrawalEnabled = status.is_withdrawal_enabled(),
|
||||
.nextWithdrawalAt = status.vnext_withdrawal_at()
|
||||
|
||||
@@ -74,12 +74,17 @@ const FoundMessages &MessagesSearchMerged::messages() const {
|
||||
return _concatedFound;
|
||||
}
|
||||
|
||||
const MessagesSearch::Request &MessagesSearchMerged::request() const {
|
||||
return _request;
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::clear() {
|
||||
_concatedFound = {};
|
||||
_migratedFirstFound = {};
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::search(const Request &search) {
|
||||
_request = search;
|
||||
if (_migratedSearch) {
|
||||
_waitingForTotal = true;
|
||||
_migratedSearch->searchMessages(search);
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
void searchMore();
|
||||
|
||||
[[nodiscard]] const FoundMessages &messages() const;
|
||||
[[nodiscard]] const Request &request() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<> newFounds() const;
|
||||
[[nodiscard]] rpl::producer<> nextFounds() const;
|
||||
@@ -39,6 +40,7 @@ private:
|
||||
void addFound(const FoundMessages &data);
|
||||
|
||||
MessagesSearch _apiSearch;
|
||||
Request _request;
|
||||
|
||||
std::optional<MessagesSearch> _migratedSearch;
|
||||
FoundMessages _migratedFirstFound;
|
||||
|
||||
@@ -181,7 +181,9 @@ std::optional<HistoryItem*> SingleMessageSearch::performLookupByUsername(
|
||||
ready();
|
||||
};
|
||||
_requestId = _session->api().request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
MTP_flags(0),
|
||||
MTP_string(username),
|
||||
MTP_string()
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
result.match([&](const MTPDcontacts_resolvedPeer &data) {
|
||||
_session->data().processUsers(data.vusers());
|
||||
|
||||
@@ -756,19 +756,32 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
const style::WhoRead &st) {
|
||||
return WhoReacted(item, reaction, context, st, nullptr);
|
||||
}
|
||||
rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenDate(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date) {
|
||||
TimeId date,
|
||||
Ui::WhoReadType type) {
|
||||
return rpl::single(Ui::WhoReadContent{
|
||||
.participants = { Ui::WhoReadParticipant{
|
||||
.name = author->name(),
|
||||
.date = FormatReadDate(date, QDateTime::currentDateTime()),
|
||||
.id = author->id.value,
|
||||
} },
|
||||
.type = Ui::WhoReadType::Edited,
|
||||
.type = type,
|
||||
.fullReadCount = 1,
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date) {
|
||||
return WhenDate(author, date, Ui::WhoReadType::Edited);
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhenOriginal(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date) {
|
||||
return WhenDate(author, date, Ui::WhoReadType::Original);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -64,5 +64,8 @@ struct WhoReadList {
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenOriginal(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -3219,6 +3219,31 @@ void ApiWrap::sharedMediaDone(
|
||||
}
|
||||
}
|
||||
|
||||
mtpRequestId ApiWrap::requestGlobalMedia(
|
||||
Storage::SharedMediaType type,
|
||||
const QString &query,
|
||||
int32 offsetRate,
|
||||
Data::MessagePosition offsetPosition,
|
||||
Fn<void(Api::GlobalMediaResult)> done) {
|
||||
auto prepared = Api::PrepareGlobalMediaRequest(
|
||||
_session,
|
||||
offsetRate,
|
||||
offsetPosition,
|
||||
type,
|
||||
query);
|
||||
if (!prepared) {
|
||||
done({});
|
||||
return 0;
|
||||
}
|
||||
return request(
|
||||
std::move(*prepared)
|
||||
).done([=](const Api::SearchRequestResult &result) {
|
||||
done(Api::ParseGlobalMediaResult(_session, result));
|
||||
}).fail([=] {
|
||||
done({});
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::sendAction(const SendAction &action) {
|
||||
if (!action.options.scheduled
|
||||
&& !action.options.shortcutId
|
||||
|
||||
@@ -59,6 +59,7 @@ class Show;
|
||||
namespace Api {
|
||||
|
||||
struct SearchResult;
|
||||
struct GlobalMediaResult;
|
||||
|
||||
class Updates;
|
||||
class Authorizations;
|
||||
@@ -288,6 +289,12 @@ public:
|
||||
Storage::SharedMediaType type,
|
||||
MsgId messageId,
|
||||
SliceType slice);
|
||||
mtpRequestId requestGlobalMedia(
|
||||
Storage::SharedMediaType type,
|
||||
const QString &query,
|
||||
int32 offsetRate,
|
||||
Data::MessagePosition offsetPosition,
|
||||
Fn<void(Api::GlobalMediaResult)> done);
|
||||
|
||||
void readFeaturedSetDelayed(uint64 setId);
|
||||
|
||||
@@ -509,6 +516,10 @@ private:
|
||||
MsgId topicRootId,
|
||||
SharedMediaType type,
|
||||
Api::SearchResult &&parsed);
|
||||
void globalMediaDone(
|
||||
SharedMediaType type,
|
||||
FullMsgId messageId,
|
||||
Api::GlobalMediaResult &&parsed);
|
||||
|
||||
void sendSharedContact(
|
||||
const QString &phone,
|
||||
@@ -672,6 +683,17 @@ private:
|
||||
};
|
||||
base::flat_set<HistoryRequest> _historyRequests;
|
||||
|
||||
struct GlobalMediaRequest {
|
||||
SharedMediaType mediaType = {};
|
||||
FullMsgId aroundId;
|
||||
SliceType sliceType = {};
|
||||
|
||||
friend inline auto operator<=>(
|
||||
const GlobalMediaRequest&,
|
||||
const GlobalMediaRequest&) = default;
|
||||
};
|
||||
base::flat_set<GlobalMediaRequest> _globalMediaRequests;
|
||||
|
||||
std::unique_ptr<DialogsLoadState> _dialogsLoadState;
|
||||
TimeId _dialogsLoadTill = 0;
|
||||
rpl::variable<bool> _dialogsLoadMayBlockByDate = false;
|
||||
|
||||
@@ -262,10 +262,16 @@ void ShowAddParticipantsError(
|
||||
return tr::lng_bot_already_in_group(tr::now);
|
||||
} else if (error == u"BOT_GROUPS_BLOCKED"_q) {
|
||||
return tr::lng_error_cant_add_bot(tr::now);
|
||||
} else if (error == u"ADMINS_TOO_MUCH"_q) {
|
||||
return ((chat->isChat() || chat->isMegagroup())
|
||||
? tr::lng_error_admin_limit
|
||||
: tr::lng_error_admin_limit_channel)(tr::now);
|
||||
} else if (error == u"YOU_BLOCKED_USER"_q) {
|
||||
return tr::lng_error_you_blocked_user(tr::now);
|
||||
} else if (error == u"CHAT_ADMIN_INVITE_REQUIRED"_q) {
|
||||
return tr::lng_error_add_admin_not_member(tr::now);
|
||||
} else if (error == u"USER_ADMIN_INVALID"_q) {
|
||||
return tr::lng_error_user_admin_invalid(tr::now);
|
||||
} else if (error == u"BOTS_TOO_MUCH"_q) {
|
||||
return (chat->isChannel()
|
||||
? tr::lng_error_channel_bots_too_much
|
||||
: tr::lng_error_group_bots_too_much)(tr::now);
|
||||
}
|
||||
return tr::lng_failed_add_participant(tr::now);
|
||||
}();
|
||||
|
||||
@@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/chat/chats_filter_tag.h"
|
||||
#include "ui/effects/animation_value_f.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/panel_animation.h"
|
||||
@@ -544,31 +545,18 @@ void EditFilterBox(
|
||||
colors->width(),
|
||||
h);
|
||||
}, preview->lifetime());
|
||||
const auto previewColor = preview->lifetime().make_state<QColor>();
|
||||
const auto previewTag = preview->lifetime().make_state<QImage>();
|
||||
const auto previewAlpha = preview->lifetime().make_state<float64>(1);
|
||||
preview->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(preview);
|
||||
p.fillRect(preview->rect(), Qt::transparent);
|
||||
const auto &font = st::dialogRowFilterTagFont;
|
||||
const auto text = name->getLastText().toUpper();
|
||||
p.setFont(font);
|
||||
p.setOpacity(*previewAlpha);
|
||||
const auto roundedWidth = font->width(text) + font->spacew * 3;
|
||||
const auto size = previewTag->size() / style::DevicePixelRatio();
|
||||
const auto rect = QRect(
|
||||
preview->width() - roundedWidth - st::boxRowPadding.right(),
|
||||
(st::normalFont->height - font->height) / 2,
|
||||
roundedWidth,
|
||||
font->height);
|
||||
const auto pen = QPen(*previewColor);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(anim::with_alpha(pen.color(), .15));
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto radius = font->height / 3.;
|
||||
p.drawRoundedRect(rect, radius, radius);
|
||||
}
|
||||
p.setPen(pen);
|
||||
p.drawText(rect, text, style::al_center);
|
||||
preview->width() - size.width() - st::boxRowPadding.right(),
|
||||
(st::normalFont->height - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height());
|
||||
p.drawImage(rect.topLeft(), *previewTag);
|
||||
if (p.opacity() < 1) {
|
||||
p.setOpacity(1. - p.opacity());
|
||||
p.setFont(st::normalFont);
|
||||
@@ -580,10 +568,6 @@ void EditFilterBox(
|
||||
}
|
||||
}, preview->lifetime());
|
||||
|
||||
name->changes() | rpl::start_with_next([=] {
|
||||
preview->update();
|
||||
}, preview->lifetime());
|
||||
|
||||
const auto side = st::userpicBuilderEmojiAccentColorSize;
|
||||
const auto line = colors->add(
|
||||
Ui::CreateSkipWidget(colors, side),
|
||||
@@ -594,6 +578,13 @@ void EditFilterBox(
|
||||
const auto palette = [](int i) {
|
||||
return Ui::EmptyUserpic::UserpicColor(i).color2;
|
||||
};
|
||||
name->changes() | rpl::start_with_next([=] {
|
||||
*previewTag = Ui::ChatsFilterTag(
|
||||
name->getLastText().toUpper(),
|
||||
palette(state->colorIndex.current())->c,
|
||||
false);
|
||||
preview->update();
|
||||
}, preview->lifetime());
|
||||
for (auto i = 0; i < kColorsCount; ++i) {
|
||||
const auto button = Ui::CreateChild<UserpicBuilder::CircleButton>(
|
||||
line);
|
||||
@@ -605,7 +596,10 @@ void EditFilterBox(
|
||||
const auto color = palette(i);
|
||||
button->setBrush(color);
|
||||
if (progress == 1) {
|
||||
*previewColor = color->c;
|
||||
*previewTag = Ui::ChatsFilterTag(
|
||||
name->getLastText().toUpper(),
|
||||
color->c,
|
||||
false);
|
||||
if (i == kNoTag) {
|
||||
*previewAlpha = 0.;
|
||||
}
|
||||
@@ -628,7 +622,10 @@ void EditFilterBox(
|
||||
buttons[was]->setSelectedProgress(1. - progress);
|
||||
}
|
||||
buttons[now]->setSelectedProgress(progress);
|
||||
*previewColor = anim::color(c1, c2, progress);
|
||||
*previewTag = Ui::ChatsFilterTag(
|
||||
name->getLastText().toUpper(),
|
||||
anim::color(c1, c2, progress),
|
||||
false);
|
||||
*previewAlpha = anim::interpolateF(a1, a2, progress);
|
||||
preview->update();
|
||||
}, 0., 1., st::universalDuration);
|
||||
|
||||
@@ -47,6 +47,7 @@ void GiftCreditsBox(
|
||||
const auto content = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
const auto &stUser = st::premiumGiftsUserpicButton;
|
||||
@@ -58,39 +59,19 @@ void GiftCreditsBox(
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
{
|
||||
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
|
||||
widget,
|
||||
false,
|
||||
Ui::Premium::MiniStars::Type::BiStars);
|
||||
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
||||
widget->resize(
|
||||
st::boxWidth - stUser.photoSize,
|
||||
stUser.photoSize * 2);
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
widget->moveToLeft(stUser.photoSize / 2, 0);
|
||||
const auto starsRect = Rect(widget->size());
|
||||
stars->setPosition(starsRect.topLeft());
|
||||
stars->setSize(starsRect.size());
|
||||
widget->lower();
|
||||
}, widget->lifetime());
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(widget);
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
Settings::AddMiniStars(
|
||||
content,
|
||||
Ui::CreateChild<Ui::RpWidget>(content),
|
||||
stUser.photoSize,
|
||||
box->width(),
|
||||
2.);
|
||||
{
|
||||
Ui::AddSkip(content);
|
||||
const auto arrow = Ui::Text::SingleCustomEmoji(
|
||||
peer->owner().customEmojiManager().registerInternalEmoji(
|
||||
st::topicButtonArrow,
|
||||
st::channelEarnLearnArrowMargins,
|
||||
false));
|
||||
true));
|
||||
auto link = tr::lng_credits_box_history_entry_gift_about_link(
|
||||
lt_emoji,
|
||||
rpl::single(arrow),
|
||||
@@ -122,7 +103,7 @@ void GiftCreditsBox(
|
||||
Main::MakeSessionShow(box->uiShow(), &peer->session()),
|
||||
box->verticalLayout(),
|
||||
peer,
|
||||
0,
|
||||
StarsAmount(),
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); },
|
||||
tr::lng_credits_summary_options_subtitle(),
|
||||
{});
|
||||
|
||||
@@ -237,7 +237,7 @@ void AddTableRow(
|
||||
valueMargins);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
@@ -255,8 +255,8 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
|
||||
auto star = session->data().customEmojiManager().creditsEmoji();
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
rpl::single(
|
||||
star.append(' ' + Lang::FormatCountDecimal(entry.credits))),
|
||||
rpl::single(star.append(
|
||||
' ' + Lang::FormatStarsAmountDecimal(entry.credits))),
|
||||
st::giveawayGiftCodeValue,
|
||||
st::defaultPopupMenu,
|
||||
std::move(makeContext));
|
||||
@@ -302,6 +302,62 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeVisibilityTableValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
bool savedToProfile,
|
||||
Fn<void(bool)> toggleVisibility) {
|
||||
auto result = object_ptr<Ui::RpWidget>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
(savedToProfile
|
||||
? tr::lng_gift_visibility_shown()
|
||||
: tr::lng_gift_visibility_hidden()),
|
||||
st::giveawayGiftCodeValue,
|
||||
st::defaultPopupMenu);
|
||||
|
||||
const auto toggle = Ui::CreateChild<Ui::RoundButton>(
|
||||
raw,
|
||||
(savedToProfile
|
||||
? tr::lng_gift_visibility_hide()
|
||||
: tr::lng_gift_visibility_show()),
|
||||
st::starGiftSmallButton);
|
||||
toggle->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
toggle->setClickedCallback([=] {
|
||||
toggleVisibility(!savedToProfile);
|
||||
});
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
toggle->widthValue()
|
||||
) | rpl::start_with_next([=](int width, int toggleWidth) {
|
||||
const auto toggleSkip = toggleWidth
|
||||
? (st::normalFont->spacew + toggleWidth)
|
||||
: 0;
|
||||
label->resizeToNaturalWidth(width - toggleSkip);
|
||||
label->moveToLeft(0, 0, width);
|
||||
if (toggle) {
|
||||
toggle->moveToLeft(
|
||||
label->width() + st::normalFont->spacew,
|
||||
(st::giveawayGiftCodeValue.style.font->ascent
|
||||
- st::starGiftSmallButton.style.font->ascent),
|
||||
width);
|
||||
}
|
||||
}, label->lifetime());
|
||||
|
||||
label->heightValue() | rpl::start_with_next([=](int height) {
|
||||
raw->resize(
|
||||
raw->width(),
|
||||
height + st::giveawayGiftCodeValueMargin.bottom());
|
||||
}, raw->lifetime());
|
||||
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<Ui::FlatLabel*> AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
@@ -1035,6 +1091,7 @@ void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void(bool)> toggleVisibility,
|
||||
Fn<void()> convertToStars) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
@@ -1072,9 +1129,15 @@ void AddStarGiftTable(
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(entry.lastSaleDate))));
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
|
||||
}
|
||||
const auto marginWithButton = st::giveawayGiftCodeValueMargin
|
||||
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
|
||||
{
|
||||
const auto margin = st::giveawayGiftCodeValueMargin
|
||||
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_value(),
|
||||
@@ -1083,13 +1146,18 @@ void AddStarGiftTable(
|
||||
controller,
|
||||
entry,
|
||||
std::move(convertToStars)),
|
||||
margin);
|
||||
marginWithButton);
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
if (toggleVisibility) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
|
||||
tr::lng_gift_visibility(),
|
||||
MakeVisibilityTableValue(
|
||||
table,
|
||||
controller,
|
||||
entry.savedToProfile,
|
||||
std::move(toggleVisibility)),
|
||||
marginWithButton);
|
||||
}
|
||||
if (entry.limitedCount > 0) {
|
||||
auto amount = rpl::single(TextWithEntities{
|
||||
@@ -1146,9 +1214,43 @@ void AddCreditsHistoryEntryTable(
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto actorId = PeerId(entry.bareActorId);
|
||||
const auto starrefRecipientId = PeerId(entry.starrefRecipientId);
|
||||
const auto session = &controller->session();
|
||||
if (actorId || peerId) {
|
||||
auto text = entry.in
|
||||
if (entry.starrefCommission) {
|
||||
if (entry.starrefAmount) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_star_ref_commission_title(),
|
||||
rpl::single(TextWithEntities{
|
||||
QString::number(entry.starrefCommission / 10.) + '%' }));
|
||||
} else {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
tr::lng_credits_box_history_entry_reason_star_ref(
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
}
|
||||
if (starrefRecipientId && entry.starrefAmount) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_affiliate(),
|
||||
controller,
|
||||
starrefRecipientId);
|
||||
}
|
||||
if (peerId && entry.starrefCommission) {
|
||||
AddTableRow(
|
||||
table,
|
||||
(entry.starrefAmount
|
||||
? tr::lng_credits_box_history_entry_referred
|
||||
: tr::lng_credits_box_history_entry_miniapp)(),
|
||||
controller,
|
||||
peerId);
|
||||
}
|
||||
if (actorId || (!entry.starrefCommission && peerId)) {
|
||||
auto text = entry.starrefCommission
|
||||
? tr::lng_credits_box_history_entry_referred()
|
||||
: entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
AddTableRow(
|
||||
@@ -1229,7 +1331,7 @@ void AddCreditsHistoryEntryTable(
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_stars_title(
|
||||
lt_count,
|
||||
rpl::single(float64(entry.credits)),
|
||||
rpl::single(entry.credits.value()),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
{
|
||||
|
||||
@@ -58,6 +58,7 @@ void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void(bool)> toggleVisibility,
|
||||
Fn<void()> convertToStars);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/local_storage_box.h"
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
@@ -21,8 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
@@ -282,19 +281,19 @@ LocalStorageBox::LocalStorageBox(
|
||||
_timeLimit = settings.totalTimeLimit;
|
||||
}
|
||||
|
||||
void LocalStorageBox::Show(not_null<::Main::Session*> session) {
|
||||
void LocalStorageBox::Show(not_null<Window::SessionController*> controller) {
|
||||
auto shared = std::make_shared<object_ptr<LocalStorageBox>>(
|
||||
Box<LocalStorageBox>(session, CreateTag()));
|
||||
Box<LocalStorageBox>(&controller->session(), CreateTag()));
|
||||
const auto weak = shared->data();
|
||||
rpl::combine(
|
||||
session->data().cache().statsOnMain(),
|
||||
session->data().cacheBigFile().statsOnMain()
|
||||
controller->session().data().cache().statsOnMain(),
|
||||
controller->session().data().cacheBigFile().statsOnMain()
|
||||
) | rpl::start_with_next([=](
|
||||
Database::Stats &&stats,
|
||||
Database::Stats &&statsBig) {
|
||||
weak->update(std::move(stats), std::move(statsBig));
|
||||
if (auto &strong = *shared) {
|
||||
Ui::show(std::move(strong));
|
||||
controller->uiShow()->show(std::move(strong));
|
||||
}
|
||||
}, weak->lifetime());
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Storage {
|
||||
namespace Cache {
|
||||
class Database;
|
||||
@@ -40,7 +44,7 @@ public:
|
||||
not_null<Main::Session*> session,
|
||||
CreateTag);
|
||||
|
||||
static void Show(not_null<Main::Session*> session);
|
||||
static void Show(not_null<Window::SessionController*> controller);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
@@ -120,6 +120,9 @@ void PeerListBox::createMultiSelect() {
|
||||
content()->submitted();
|
||||
});
|
||||
_select->entity()->setQueryChangedCallback([=](const QString &query) {
|
||||
if (_customQueryChangedCallback) {
|
||||
_customQueryChangedCallback(query);
|
||||
}
|
||||
searchQueryChanged(query);
|
||||
});
|
||||
_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
|
||||
@@ -138,6 +141,10 @@ void PeerListBox::createMultiSelect() {
|
||||
_select->moveToLeft(0, 0);
|
||||
}
|
||||
|
||||
void PeerListBox::appendQueryChangedCallback(Fn<void(QString)> callback) {
|
||||
_customQueryChangedCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void PeerListBox::setAddedTopScrollSkip(int skip) {
|
||||
_addedTopScrollSkip = skip;
|
||||
_scrollBottomFixed = false;
|
||||
@@ -217,19 +224,7 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
void PeerListBox::searchQueryChanged(const QString &query) {
|
||||
scrollToY(0);
|
||||
const auto isEmpty = content()->searchQueryChanged(query);
|
||||
if (_specialTabsMode.enabled) {
|
||||
const auto was = _specialTabsMode.searchIsActive;
|
||||
_specialTabsMode.searchIsActive = !isEmpty;
|
||||
if (was != _specialTabsMode.searchIsActive) {
|
||||
if (_specialTabsMode.searchIsActive) {
|
||||
_specialTabsMode.topSkip = _addedTopScrollSkip;
|
||||
setAddedTopScrollSkip(0);
|
||||
} else {
|
||||
setAddedTopScrollSkip(_specialTabsMode.topSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
content()->searchQueryChanged(query);
|
||||
}
|
||||
|
||||
void PeerListBox::resizeEvent(QResizeEvent *e) {
|
||||
@@ -486,7 +481,7 @@ void PeerListBox::addSelectItem(
|
||||
void PeerListBox::addSelectItem(
|
||||
uint64 itemId,
|
||||
const QString &text,
|
||||
Ui::MultiSelect::PaintRoundImage paintUserpic,
|
||||
PaintRoundImageCallback paintUserpic,
|
||||
anim::type animated) {
|
||||
if (!_select) {
|
||||
createMultiSelect();
|
||||
@@ -561,13 +556,8 @@ rpl::producer<int> PeerListBox::multiSelectHeightValue() const {
|
||||
return _select ? _select->heightValue() : rpl::single(0);
|
||||
}
|
||||
|
||||
void PeerListBox::setSpecialTabMode(bool value) {
|
||||
content()->setIgnoreHiddenRowsOnSearch(value);
|
||||
if (value) {
|
||||
_specialTabsMode.enabled = true;
|
||||
} else {
|
||||
_specialTabsMode = {};
|
||||
}
|
||||
rpl::producer<> PeerListBox::noSearchSubmits() const {
|
||||
return content()->noSearchSubmits();
|
||||
}
|
||||
|
||||
PeerListRow::PeerListRow(not_null<PeerData*> peer)
|
||||
@@ -1291,6 +1281,9 @@ void PeerListContent::clearAllContent() {
|
||||
= _normalizedSearchQuery
|
||||
= _mentionHighlight
|
||||
= QString();
|
||||
if (_controller->hasComplexSearch()) {
|
||||
_controller->search(QString());
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::convertRowToSearchResult(not_null<PeerListRow*> row) {
|
||||
@@ -2079,7 +2072,7 @@ void PeerListContent::checkScrollForPreload() {
|
||||
}
|
||||
}
|
||||
|
||||
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
|
||||
void PeerListContent::searchQueryChanged(QString query) {
|
||||
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
||||
const auto normalizedQuery = searchWordsList.join(' ');
|
||||
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
|
||||
@@ -2136,7 +2129,6 @@ PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
|
||||
}
|
||||
refreshRows();
|
||||
}
|
||||
return _normalizedSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
||||
@@ -2211,6 +2203,9 @@ bool PeerListContent::submitted() {
|
||||
_controller->rowClicked(row);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
_noSearchSubmits.fire({});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -652,8 +652,7 @@ public:
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
|
||||
using IsEmpty = bool;
|
||||
IsEmpty searchQueryChanged(QString query);
|
||||
void searchQueryChanged(QString query);
|
||||
bool submitted();
|
||||
|
||||
PeerListRowId updateFromParentDrag(QPoint globalPosition);
|
||||
@@ -713,7 +712,7 @@ public:
|
||||
update();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> saveState() const;
|
||||
[[nodiscard]] std::unique_ptr<PeerListState> saveState() const;
|
||||
void restoreState(std::unique_ptr<PeerListState> state);
|
||||
|
||||
void showRowMenu(
|
||||
@@ -721,10 +720,14 @@ public:
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
|
||||
|
||||
auto scrollToRequests() const {
|
||||
[[nodiscard]] auto scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto noSearchSubmits() const {
|
||||
return _noSearchSubmits.events();
|
||||
}
|
||||
|
||||
~PeerListContent();
|
||||
|
||||
protected:
|
||||
@@ -891,6 +894,8 @@ private:
|
||||
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
|
||||
|
||||
rpl::event_stream<> _noSearchSubmits;
|
||||
|
||||
std::vector<std::unique_ptr<PeerListRow>> _searchRows;
|
||||
base::Timer _repaintByStatus;
|
||||
base::unique_qptr<Ui::PopupMenu> _contextMenu;
|
||||
@@ -1107,8 +1112,7 @@ public:
|
||||
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
||||
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
|
||||
|
||||
void setSpecialTabMode(bool value);
|
||||
[[nodiscard]] rpl::producer<> noSearchSubmits() const;
|
||||
|
||||
void peerListSetTitle(rpl::producer<QString> title) override {
|
||||
setTitle(std::move(title));
|
||||
@@ -1133,6 +1137,8 @@ public:
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
void appendQueryChangedCallback(Fn<void(QString)>);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
@@ -1170,16 +1176,10 @@ private:
|
||||
object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
|
||||
|
||||
const std::shared_ptr<Main::SessionShow> _show;
|
||||
Fn<void(QString)> _customQueryChangedCallback;
|
||||
std::unique_ptr<PeerListController> _controller;
|
||||
Fn<void(PeerListBox*)> _init;
|
||||
bool _scrollBottomFixed = false;
|
||||
int _addedTopScrollSkip = 0;
|
||||
|
||||
struct SpecialTabsMode final {
|
||||
bool enabled = false;
|
||||
bool searchIsActive = false;
|
||||
int topSkip = 0;
|
||||
};
|
||||
SpecialTabsMode _specialTabsMode;
|
||||
|
||||
};
|
||||
|
||||
@@ -46,6 +46,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/bot/earn/info_bot_earn_widget.h"
|
||||
#include "info/bot/starref/info_bot_starref_join_widget.h"
|
||||
#include "info/bot/starref/info_bot_starref_setup_widget.h"
|
||||
#include "info/channel_statistics/boosts/info_boosts_widget.h"
|
||||
#include "info/channel_statistics/earn/earn_format.h"
|
||||
#include "info/channel_statistics/earn/earn_icons.h"
|
||||
@@ -60,6 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/new_badges.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/vertical_list.h"
|
||||
@@ -78,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
@@ -358,6 +362,7 @@ private:
|
||||
void fillBotUsernamesButton();
|
||||
void fillBotCurrencyButton();
|
||||
void fillBotCreditsButton();
|
||||
void fillBotAffiliateProgram();
|
||||
void fillBotEditIntroButton();
|
||||
void fillBotEditCommandsButton();
|
||||
void fillBotEditSettingsButton();
|
||||
@@ -1181,6 +1186,7 @@ void Controller::fillManageSection() {
|
||||
fillBotUsernamesButton();
|
||||
fillBotCurrencyButton();
|
||||
fillBotCreditsButton();
|
||||
fillBotAffiliateProgram();
|
||||
fillBotEditIntroButton();
|
||||
fillBotEditCommandsButton();
|
||||
fillBotEditSettingsButton();
|
||||
@@ -1238,6 +1244,9 @@ void Controller::fillManageSection() {
|
||||
&& (channel->isBroadcast() || channel->isGigagroup());
|
||||
const auto hasRecentActions = isChannel
|
||||
&& (channel->hasAdminRights() || channel->amCreator());
|
||||
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)
|
||||
&& isChannel
|
||||
&& channel->canPostMessages();
|
||||
const auto canEditStickers = isChannel && channel->canEditStickers();
|
||||
const auto canDeleteChannel = isChannel && channel->canDelete();
|
||||
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
|
||||
@@ -1420,10 +1429,21 @@ void Controller::fillManageSection() {
|
||||
AddButtonWithCount(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_recent_actions(),
|
||||
rpl::single(QString()), //Empty count.
|
||||
rpl::single(QString()), // Empty count.
|
||||
std::move(callback),
|
||||
{ &st::menuIconGroupLog });
|
||||
}
|
||||
if (hasStarRef) {
|
||||
auto callback = [=] {
|
||||
_navigation->showSection(Info::BotStarRef::Join::Make(_peer));
|
||||
};
|
||||
AddButtonWithCount(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_star_ref(),
|
||||
rpl::single(QString()), // Empty count.
|
||||
std::move(callback),
|
||||
{ .icon = &st::menuIconStarRefShare, .newBadge = true });
|
||||
}
|
||||
|
||||
if (canEditStickers || canDeleteChannel) {
|
||||
::AddSkip(_controls.buttonsLayout);
|
||||
@@ -1664,7 +1684,7 @@ void Controller::fillBotCreditsButton() {
|
||||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
|
||||
state->balance = Lang::FormatCountDecimal(balance);
|
||||
state->balance = Lang::FormatStarsAmountDecimal(balance);
|
||||
}
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
@@ -1689,7 +1709,7 @@ void Controller::fillBotCreditsButton() {
|
||||
if (data.balance) {
|
||||
wrap->toggle(true, anim::type::normal);
|
||||
}
|
||||
state->balance = Lang::FormatCountDecimal(data.balance);
|
||||
state->balance = Lang::FormatStarsAmountDecimal(data.balance);
|
||||
});
|
||||
}
|
||||
{
|
||||
@@ -1711,6 +1731,35 @@ void Controller::fillBotCreditsButton() {
|
||||
|
||||
}
|
||||
|
||||
void Controller::fillBotAffiliateProgram() {
|
||||
Expects(_isBot);
|
||||
|
||||
if (!Info::BotStarRef::Setup::Allowed(_peer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto user = _peer->asUser();
|
||||
auto label = user->session().changes().peerFlagsValue(
|
||||
user,
|
||||
Data::PeerUpdate::Flag::StarRefProgram
|
||||
) | rpl::map([=] {
|
||||
const auto commission = user->botInfo
|
||||
? user->botInfo->starRefProgram.commission
|
||||
: 0;
|
||||
return commission
|
||||
? Info::BotStarRef::FormatCommission(commission)
|
||||
: tr::lng_manage_peer_bot_star_ref_off(tr::now);
|
||||
});
|
||||
AddButtonWithCount(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_bot_star_ref(),
|
||||
std::move(label),
|
||||
[controller = _navigation->parentController(), user] {
|
||||
controller->showSection(Info::BotStarRef::Setup::Make(user));
|
||||
},
|
||||
{ .icon = &st::menuIconSharing, .newBadge = true });
|
||||
}
|
||||
|
||||
void Controller::fillBotEditIntroButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
@@ -2227,7 +2276,9 @@ void Controller::saveHistoryVisibility() {
|
||||
void Controller::toggleBotManager(const QString &command) {
|
||||
const auto controller = _navigation->parentController();
|
||||
_api.request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(kBotManagerUsername.utf16())
|
||||
MTP_flags(0),
|
||||
MTP_string(kBotManagerUsername.utf16()),
|
||||
MTP_string()
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
_peer->owner().processUsers(result.data().vusers());
|
||||
_peer->owner().processChats(result.data().vchats());
|
||||
@@ -2501,6 +2552,13 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
|
||||
st.button);
|
||||
const auto button = result.data();
|
||||
button->addClickHandler(callback);
|
||||
|
||||
const auto badge = descriptor.newBadge
|
||||
? Ui::NewBadge::CreateNewBadge(
|
||||
button,
|
||||
tr::lng_premium_summary_new_badge()).get()
|
||||
: nullptr;
|
||||
|
||||
if (descriptor) {
|
||||
AddButtonIcon(
|
||||
button,
|
||||
@@ -2509,7 +2567,7 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
|
||||
}
|
||||
|
||||
auto labelText = rpl::combine(
|
||||
std::move(text),
|
||||
rpl::duplicate(text),
|
||||
std::move(count),
|
||||
button->widthValue()
|
||||
) | rpl::map([&st](const QString &text, const QString &count, int width) {
|
||||
@@ -2524,11 +2582,40 @@ object_ptr<Ui::SettingsButton> EditPeerInfoBox::CreateButton(
|
||||
: count;
|
||||
});
|
||||
|
||||
if (badge) {
|
||||
rpl::combine(
|
||||
std::move(text),
|
||||
rpl::duplicate(labelText),
|
||||
button->widthValue()
|
||||
) | rpl::start_with_next([=](
|
||||
const QString &text,
|
||||
const QString &label,
|
||||
int width) {
|
||||
const auto space = st.button.style.font->spacew;
|
||||
const auto left = st.button.padding.left()
|
||||
+ st.button.style.font->width(text)
|
||||
+ space;
|
||||
const auto right = st.labelPosition.x()
|
||||
+ st.label.style.font->width(label)
|
||||
+ (space * 2);
|
||||
const auto available = width - left - right;
|
||||
badge->setVisible(available >= badge->width());
|
||||
if (!badge->isHidden()) {
|
||||
const auto top = st.button.padding.top()
|
||||
+ st.button.style.font->ascent
|
||||
- st::settingsPremiumNewBadge.style.font->ascent
|
||||
- st::settingsPremiumNewBadgePadding.top();
|
||||
badge->moveToLeft(left, top, width);
|
||||
}
|
||||
}, badge->lifetime());
|
||||
}
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
std::move(labelText),
|
||||
st.label);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->show();
|
||||
|
||||
rpl::combine(
|
||||
button->widthValue(),
|
||||
|
||||
@@ -202,10 +202,11 @@ void Controller::prepare() {
|
||||
const auto session = &_to->session();
|
||||
auto above = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
above->add(
|
||||
CreateBoostReplaceUserpics(
|
||||
CreateUserpicsTransfer(
|
||||
above.data(),
|
||||
_selectedPeers.value(),
|
||||
_to),
|
||||
_to,
|
||||
UserpicsTransferType::BoostReplace),
|
||||
st::boxRowPadding + st::boostReplaceUserpicsPadding);
|
||||
above->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -366,10 +367,11 @@ object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
|
||||
});
|
||||
box->verticalLayout()->insert(
|
||||
0,
|
||||
CreateBoostReplaceUserpics(
|
||||
CreateUserpicsTransfer(
|
||||
box,
|
||||
rpl::single(std::vector{ peer }),
|
||||
to),
|
||||
to,
|
||||
UserpicsTransferType::BoostReplace),
|
||||
st::boxRowPadding + st::boostReplaceUserpicsPadding);
|
||||
});
|
||||
|
||||
@@ -536,10 +538,11 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
||||
return Box<PeerListBox>(std::move(controller), std::move(initBox));
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> from,
|
||||
not_null<PeerData*> to) {
|
||||
not_null<PeerData*> to,
|
||||
UserpicsTransferType type) {
|
||||
struct State {
|
||||
std::vector<not_null<PeerData*>> from;
|
||||
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
|
||||
@@ -640,13 +643,18 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
button->render(&q, position, QRegion(), QWidget::DrawChildren);
|
||||
}
|
||||
state->painting = false;
|
||||
const auto boosting = (type == UserpicsTransferType::BoostReplace);
|
||||
const auto last = state->buttons.back().get();
|
||||
const auto back = boosting ? last : right;
|
||||
const auto add = st::boostReplaceIconAdd;
|
||||
const auto skip = st::boostReplaceIconSkip;
|
||||
const auto w = st::boostReplaceIcon.width() + 2 * skip;
|
||||
const auto h = st::boostReplaceIcon.height() + 2 * skip;
|
||||
const auto x = last->x() + last->width() - w + add.x();
|
||||
const auto y = last->y() + last->height() - h + add.y();
|
||||
const auto &icon = boosting
|
||||
? st::boostReplaceIcon
|
||||
: st::starrefJoinIcon;
|
||||
const auto skip = boosting ? st::boostReplaceIconSkip : 0;
|
||||
const auto w = icon.width() + 2 * skip;
|
||||
const auto h = icon.height() + 2 * skip;
|
||||
const auto x = back->x() + back->width() - w + add.x();
|
||||
const auto y = back->y() + back->height() - h + add.y();
|
||||
|
||||
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
|
||||
brush.setStops(Ui::Premium::ButtonGradientStops());
|
||||
@@ -654,7 +662,7 @@ object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
pen.setWidthF(stroke);
|
||||
q.setPen(pen);
|
||||
q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
|
||||
st::boostReplaceIcon.paint(q, x + skip, y + skip, outerw);
|
||||
icon.paint(q, x + skip, y + skip, outerw);
|
||||
|
||||
const auto size = st::boostReplaceArrow.size();
|
||||
st::boostReplaceArrow.paint(
|
||||
|
||||
@@ -53,10 +53,15 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
||||
Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
|
||||
Fn<void()> cancel);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
enum class UserpicsTransferType {
|
||||
BoostReplace,
|
||||
StarRefJoin,
|
||||
};
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> from,
|
||||
not_null<PeerData*> to);
|
||||
not_null<PeerData*> to,
|
||||
UserpicsTransferType type);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsWithMoreBadge(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
|
||||
@@ -57,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -71,11 +72,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPriceTabAll = 0;
|
||||
constexpr auto kPriceTabLimited = -1;
|
||||
constexpr auto kPriceTabInStock = -2;
|
||||
constexpr auto kGiftMessageLimit = 255;
|
||||
constexpr auto kSentToastDuration = 3 * crl::time(1000);
|
||||
|
||||
@@ -156,6 +160,10 @@ private:
|
||||
return is(now) || is(now.addDays(1)) || is(now.addDays(-1));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsSoldOut(const Api::StarGift &info) {
|
||||
return info.limitedCount && !info.limitedLeft;
|
||||
}
|
||||
|
||||
PreviewDelegate::PreviewDelegate(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::ChatStyle*> st,
|
||||
@@ -554,6 +562,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
||||
return simple(tr::lng_gift_stars_tabs_all(tr::now));
|
||||
} else if (price == kPriceTabLimited) {
|
||||
return simple(tr::lng_gift_stars_tabs_limited(tr::now));
|
||||
} else if (price == kPriceTabInStock) {
|
||||
return simple(tr::lng_gift_stars_tabs_in_stock(tr::now));
|
||||
}
|
||||
auto &manager = session->data().customEmojiManager();
|
||||
auto result = Text::String();
|
||||
@@ -589,18 +599,33 @@ struct GiftPriceTabs {
|
||||
struct State {
|
||||
rpl::variable<std::vector<int>> prices;
|
||||
rpl::variable<int> priceTab = kPriceTabAll;
|
||||
rpl::variable<int> fullWidth;
|
||||
std::vector<Button> buttons;
|
||||
int dragx = 0;
|
||||
int pressx = 0;
|
||||
float64 dragscroll = 0.;
|
||||
float64 scroll = 0.;
|
||||
int scrollMax = 0;
|
||||
int selected = -1;
|
||||
int pressed = -1;
|
||||
int active = -1;
|
||||
};
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
const auto scroll = [=] {
|
||||
return QPoint(int(base::SafeRound(state->scroll)), 0);
|
||||
};
|
||||
|
||||
state->prices = std::move(
|
||||
gifts
|
||||
) | rpl::map([](const std::vector<GiftTypeStars> &gifts) {
|
||||
auto result = std::vector<int>();
|
||||
result.push_back(kPriceTabAll);
|
||||
auto special = 1;
|
||||
auto same = true;
|
||||
auto sameKey = 0;
|
||||
auto hasNonSoldOut = false;
|
||||
auto hasSoldOut = false;
|
||||
auto hasLimited = false;
|
||||
for (const auto &gift : gifts) {
|
||||
if (same) {
|
||||
const auto key = gift.info.stars
|
||||
@@ -611,10 +636,13 @@ struct GiftPriceTabs {
|
||||
same = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (gift.info.limitedCount
|
||||
&& (result.size() < 2 || result[1] != kPriceTabLimited)) {
|
||||
result.insert(begin(result) + 1, kPriceTabLimited);
|
||||
if (IsSoldOut(gift.info)) {
|
||||
hasSoldOut = true;
|
||||
} else {
|
||||
hasNonSoldOut = true;
|
||||
}
|
||||
if (gift.info.limitedCount) {
|
||||
hasLimited = true;
|
||||
}
|
||||
if (!ranges::contains(result, gift.info.stars)) {
|
||||
result.push_back(gift.info.stars);
|
||||
@@ -623,6 +651,12 @@ struct GiftPriceTabs {
|
||||
if (same) {
|
||||
return std::vector<int>();
|
||||
}
|
||||
if (hasSoldOut && hasNonSoldOut) {
|
||||
result.insert(begin(result) + (special++), kPriceTabInStock);
|
||||
}
|
||||
if (hasLimited) {
|
||||
result.insert(begin(result) + (special++), kPriceTabLimited);
|
||||
}
|
||||
ranges::sort(begin(result) + 1, end(result));
|
||||
return result;
|
||||
});
|
||||
@@ -681,6 +715,9 @@ struct GiftPriceTabs {
|
||||
button.geometry = QRect(QPoint(x, y), r.size());
|
||||
x += r.width() + st::giftBoxTabSkip;
|
||||
}
|
||||
state->fullWidth = x
|
||||
- st::giftBoxTabSkip
|
||||
+ st::giftBoxTabsMargin.right();
|
||||
const auto height = state->buttons.empty()
|
||||
? 0
|
||||
: (y
|
||||
@@ -690,13 +727,35 @@ struct GiftPriceTabs {
|
||||
raw->update();
|
||||
}, raw->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
state->fullWidth.value()
|
||||
) | rpl::start_with_next([=](int outer, int inner) {
|
||||
state->scrollMax = std::max(0, inner - outer);
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->setMouseTracking(true);
|
||||
raw->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
switch (type) {
|
||||
case QEvent::Leave: setSelected(-1); break;
|
||||
case QEvent::MouseMove: {
|
||||
const auto position = static_cast<QMouseEvent*>(e.get())->pos();
|
||||
const auto me = static_cast<QMouseEvent*>(e.get());
|
||||
const auto mousex = me->pos().x();
|
||||
const auto drag = QApplication::startDragDistance();
|
||||
if (state->dragx > 0) {
|
||||
state->scroll = std::clamp(
|
||||
state->dragscroll + state->dragx - mousex,
|
||||
0.,
|
||||
state->scrollMax * 1.);
|
||||
raw->update();
|
||||
break;
|
||||
} else if (state->pressx > 0
|
||||
&& std::abs(state->pressx - mousex) > drag) {
|
||||
state->dragx = state->pressx;
|
||||
state->dragscroll = state->scroll;
|
||||
}
|
||||
const auto position = me->pos() + scroll();
|
||||
for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
|
||||
if (state->buttons[i].geometry.contains(position)) {
|
||||
setSelected(i);
|
||||
@@ -704,17 +763,32 @@ struct GiftPriceTabs {
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case QEvent::Wheel: {
|
||||
const auto me = static_cast<QWheelEvent*>(e.get());
|
||||
state->scroll = std::clamp(
|
||||
state->scroll - Ui::ScrollDeltaF(me).x(),
|
||||
0.,
|
||||
state->scrollMax * 1.);
|
||||
raw->update();
|
||||
} break;
|
||||
case QEvent::MouseButtonPress: {
|
||||
const auto me = static_cast<QMouseEvent*>(e.get());
|
||||
if (me->button() != Qt::LeftButton) {
|
||||
break;
|
||||
}
|
||||
const auto position = me->pos();
|
||||
for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
|
||||
if (state->buttons[i].geometry.contains(position)) {
|
||||
setActive(i);
|
||||
break;
|
||||
}
|
||||
state->pressed = state->selected;
|
||||
state->pressx = me->pos().x();
|
||||
} break;
|
||||
case QEvent::MouseButtonRelease: {
|
||||
const auto me = static_cast<QMouseEvent*>(e.get());
|
||||
if (me->button() != Qt::LeftButton) {
|
||||
break;
|
||||
}
|
||||
const auto dragx = std::exchange(state->dragx, 0);
|
||||
const auto pressed = std::exchange(state->pressed, -1);
|
||||
state->pressx = 0;
|
||||
if (!dragx && pressed >= 0 && state->selected == pressed) {
|
||||
setActive(pressed);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
@@ -724,8 +798,9 @@ struct GiftPriceTabs {
|
||||
auto p = QPainter(raw);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto padding = st::giftBoxTabPadding;
|
||||
const auto shift = -scroll();
|
||||
for (const auto &button : state->buttons) {
|
||||
const auto geometry = button.geometry;
|
||||
const auto geometry = button.geometry.translated(shift);
|
||||
if (button.active) {
|
||||
p.setBrush(st::giftBoxTabBgActive);
|
||||
p.setPen(Qt::NoPen);
|
||||
@@ -740,6 +815,14 @@ struct GiftPriceTabs {
|
||||
.availableWidth = button.text.maxWidth(),
|
||||
});
|
||||
}
|
||||
{
|
||||
const auto &icon = st::defaultEmojiSuggestions;
|
||||
const auto w = icon.fadeRight.width();
|
||||
const auto &c = st::boxDividerBg->c;
|
||||
const auto r = QRect(0, 0, w, raw->height());
|
||||
icon.fadeRight.fill(p, r.translated(raw->width() - w, 0), c);
|
||||
icon.fadeLeft.fill(p, r, c);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
return {
|
||||
@@ -875,7 +958,7 @@ void SoldOutBox(
|
||||
Data::CreditsHistoryEntry{
|
||||
.firstSaleDate = base::unixtime::parse(gift.info.firstSaleDate),
|
||||
.lastSaleDate = base::unixtime::parse(gift.info.lastSaleDate),
|
||||
.credits = uint64(gift.info.stars),
|
||||
.credits = StarsAmount(gift.info.stars),
|
||||
.bareGiftStickerId = gift.info.document->id,
|
||||
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
|
||||
.limitedCount = gift.info.limitedCount,
|
||||
@@ -1128,9 +1211,7 @@ void SendGiftBox(
|
||||
|
||||
button->setClickedCallback([=] {
|
||||
const auto star = std::get_if<GiftTypeStars>(&descriptor);
|
||||
if (star
|
||||
&& star->info.limitedCount
|
||||
&& !star->info.limitedLeft) {
|
||||
if (star && IsSoldOut(star->info)) {
|
||||
window->show(Box(SoldOutBox, window, *star));
|
||||
} else {
|
||||
window->show(
|
||||
@@ -1235,6 +1316,8 @@ void AddBlock(
|
||||
gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) {
|
||||
return (price == kPriceTabLimited)
|
||||
? (!gift.info.limitedCount)
|
||||
: (price == kPriceTabInStock)
|
||||
? IsSoldOut(gift.info)
|
||||
: (price && gift.info.stars != price);
|
||||
}), end(gifts));
|
||||
return GiftsDescriptor{
|
||||
@@ -1270,32 +1353,12 @@ void GiftBox(
|
||||
AddSkip(content);
|
||||
AddSkip(content);
|
||||
|
||||
{
|
||||
const auto widget = CreateChild<RpWidget>(content);
|
||||
using ColoredMiniStars = Premium::ColoredMiniStars;
|
||||
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
|
||||
widget,
|
||||
false,
|
||||
Premium::MiniStars::Type::BiStars);
|
||||
stars->setColorOverride(Premium::CreditsIconGradientStops());
|
||||
widget->resize(
|
||||
st::boxWidth - stUser.photoSize,
|
||||
stUser.photoSize * 2);
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
widget->moveToLeft((size.width() - widget->width()) / 2, 0);
|
||||
const auto starsRect = Rect(widget->size());
|
||||
stars->setPosition(starsRect.topLeft());
|
||||
stars->setSize(starsRect.size());
|
||||
widget->lower();
|
||||
}, widget->lifetime());
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(widget);
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
Settings::AddMiniStars(
|
||||
content,
|
||||
Ui::CreateChild<Ui::RpWidget>(content),
|
||||
stUser.photoSize,
|
||||
box->width(),
|
||||
2.);
|
||||
AddSkip(content);
|
||||
AddSkip(box->verticalLayout());
|
||||
|
||||
|
||||
@@ -1612,7 +1612,7 @@ void Panel::initLayout() {
|
||||
#ifndef Q_OS_MAC
|
||||
_controls->wrap.raise();
|
||||
|
||||
Ui::Platform::TitleControlsLayoutChanged(
|
||||
_controls->controls.layout().changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
// _menuToggle geometry depends on _controls arrangement.
|
||||
crl::on_main(widget(), [=] { updateControlsGeometry(); });
|
||||
|
||||
@@ -899,7 +899,9 @@ void GifsListWidget::searchForGifs(const QString &query) {
|
||||
if (!_searchBot && !_searchBotRequestId) {
|
||||
const auto username = session().serverConfig().gifSearchUsername;
|
||||
_searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
MTP_flags(0),
|
||||
MTP_string(username),
|
||||
MTP_string()
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
auto &data = result.data();
|
||||
session().data().processUsers(data.vusers());
|
||||
|
||||
@@ -31,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/edit_privacy_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/sessions_box.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "boxes/language_box.h"
|
||||
#include "passport/passport_form_controller.h"
|
||||
@@ -51,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "window/themes/window_theme_editor_box.h" // GenerateSlug.
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "settings/settings_active_sessions.h"
|
||||
#include "settings/settings_credits.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "settings/settings_information.h"
|
||||
@@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_premium.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
@@ -556,8 +557,19 @@ bool ResolveUsernameOrPhone(
|
||||
? ResolveType::Profile
|
||||
: ResolveType::Default;
|
||||
auto startToken = params.value(u"start"_q);
|
||||
auto referral = params.value(u"ref"_q);
|
||||
if (!startToken.isEmpty()) {
|
||||
resolveType = ResolveType::BotStart;
|
||||
if (referral.isEmpty()) {
|
||||
const auto appConfig = &controller->session().appConfig();
|
||||
const auto &prefixes = appConfig->startRefPrefixes();
|
||||
for (const auto &prefix : prefixes) {
|
||||
if (startToken.startsWith(prefix)) {
|
||||
referral = startToken.mid(prefix.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (params.contains(u"startgroup"_q)) {
|
||||
resolveType = ResolveType::AddToGroup;
|
||||
startToken = params.value(u"startgroup"_q);
|
||||
@@ -613,6 +625,7 @@ bool ResolveUsernameOrPhone(
|
||||
}
|
||||
: Window::RepliesByLinkInfo{ v::null },
|
||||
.resolveType = resolveType,
|
||||
.referral = referral,
|
||||
.startToken = startToken,
|
||||
.startAdminRights = adminRights,
|
||||
.startAutoSubmit = myContext.botStartAutoSubmit,
|
||||
|
||||
99
Telegram/SourceFiles/core/stars_amount.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/basic_types.h"
|
||||
|
||||
inline constexpr auto kOneStarInNano = int64(1'000'000'000);
|
||||
|
||||
class StarsAmount {
|
||||
public:
|
||||
StarsAmount() = default;
|
||||
explicit StarsAmount(int64 whole) : _whole(whole) {}
|
||||
StarsAmount(int64 whole, int64 nano) : _whole(whole), _nano(nano) {
|
||||
normalize();
|
||||
}
|
||||
|
||||
[[nodiscard]] int64 whole() const {
|
||||
return _whole;
|
||||
}
|
||||
|
||||
[[nodiscard]] int64 nano() const {
|
||||
return _nano;
|
||||
}
|
||||
|
||||
[[nodiscard]] double value() const {
|
||||
return double(_whole) + double(_nano) / kOneStarInNano;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return !_whole && !_nano;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool operator!() const {
|
||||
return empty();
|
||||
}
|
||||
[[nodiscard]] inline explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
inline StarsAmount &operator+=(StarsAmount other) {
|
||||
_whole += other._whole;
|
||||
_nano += other._nano;
|
||||
normalize();
|
||||
return *this;
|
||||
}
|
||||
inline StarsAmount &operator-=(StarsAmount other) {
|
||||
_whole -= other._whole;
|
||||
_nano -= other._nano;
|
||||
normalize();
|
||||
return *this;
|
||||
}
|
||||
inline StarsAmount &operator*=(int64 multiplier) {
|
||||
_whole *= multiplier;
|
||||
_nano *= multiplier;
|
||||
normalize();
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend inline auto operator<=>(StarsAmount, StarsAmount) = default;
|
||||
friend inline bool operator==(StarsAmount, StarsAmount) = default;
|
||||
|
||||
[[nodiscard]] StarsAmount abs() const {
|
||||
return (_whole < 0) ? StarsAmount(-_whole, -_nano) : *this;
|
||||
}
|
||||
|
||||
private:
|
||||
int64 _whole = 0;
|
||||
int64 _nano = 0;
|
||||
|
||||
void normalize() {
|
||||
if (_nano < 0) {
|
||||
const auto shifts = (-_nano + kOneStarInNano - 1)
|
||||
/ kOneStarInNano;
|
||||
_nano += shifts * kOneStarInNano;
|
||||
_whole -= shifts;
|
||||
} else if (_nano >= kOneStarInNano) {
|
||||
const auto shifts = _nano / kOneStarInNano;
|
||||
_nano -= shifts * kOneStarInNano;
|
||||
_whole += shifts;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] inline StarsAmount operator+(StarsAmount a, StarsAmount b) {
|
||||
return a += b;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline StarsAmount operator-(StarsAmount a, StarsAmount b) {
|
||||
return a -= b;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline StarsAmount operator*(StarsAmount a, int64 b) {
|
||||
return a *= b;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 5008004;
|
||||
constexpr auto AppVersionStr = "5.8.4";
|
||||
constexpr auto AppVersion = 5009002;
|
||||
constexpr auto AppVersionStr = "5.9.2";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -19,6 +19,11 @@ constexpr auto kReloadThreshold = 60 * crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
StarsAmount FromTL(const MTPStarsAmount &value) {
|
||||
const auto &data = value.data();
|
||||
return StarsAmount(data.vamount().v, data.vnanos().v);
|
||||
}
|
||||
|
||||
Credits::Credits(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _reload([=] { load(true); }) {
|
||||
@@ -27,7 +32,7 @@ Credits::Credits(not_null<Main::Session*> session)
|
||||
Credits::~Credits() = default;
|
||||
|
||||
void Credits::apply(const MTPDupdateStarsBalance &data) {
|
||||
apply(data.vbalance().v);
|
||||
apply(FromTL(data.vbalance()));
|
||||
}
|
||||
|
||||
rpl::producer<float64> Credits::rateValue(
|
||||
@@ -65,13 +70,13 @@ rpl::producer<bool> Credits::loadedValue() const {
|
||||
) | rpl::then(_loadedChanges.events() | rpl::map_to(true));
|
||||
}
|
||||
|
||||
uint64 Credits::balance() const {
|
||||
StarsAmount Credits::balance() const {
|
||||
return _nonLockedBalance.current();
|
||||
}
|
||||
|
||||
uint64 Credits::balance(PeerId peerId) const {
|
||||
StarsAmount Credits::balance(PeerId peerId) const {
|
||||
const auto it = _cachedPeerBalances.find(peerId);
|
||||
return (it != _cachedPeerBalances.end()) ? it->second : 0;
|
||||
return (it != _cachedPeerBalances.end()) ? it->second : StarsAmount();
|
||||
}
|
||||
|
||||
uint64 Credits::balanceCurrency(PeerId peerId) const {
|
||||
@@ -79,17 +84,19 @@ uint64 Credits::balanceCurrency(PeerId peerId) const {
|
||||
return (it != _cachedPeerCurrencyBalances.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
rpl::producer<uint64> Credits::balanceValue() const {
|
||||
rpl::producer<StarsAmount> Credits::balanceValue() const {
|
||||
return _nonLockedBalance.value();
|
||||
}
|
||||
|
||||
void Credits::updateNonLockedValue() {
|
||||
_nonLockedBalance = (_balance >= _locked) ? (_balance - _locked) : 0;
|
||||
_nonLockedBalance = (_balance >= _locked)
|
||||
? (_balance - _locked)
|
||||
: StarsAmount();
|
||||
}
|
||||
|
||||
void Credits::lock(int count) {
|
||||
void Credits::lock(StarsAmount count) {
|
||||
Expects(loaded());
|
||||
Expects(count >= 0);
|
||||
Expects(count >= StarsAmount(0));
|
||||
Expects(_locked + count <= _balance);
|
||||
|
||||
_locked += count;
|
||||
@@ -97,8 +104,8 @@ void Credits::lock(int count) {
|
||||
updateNonLockedValue();
|
||||
}
|
||||
|
||||
void Credits::unlock(int count) {
|
||||
Expects(count >= 0);
|
||||
void Credits::unlock(StarsAmount count) {
|
||||
Expects(count >= StarsAmount(0));
|
||||
Expects(_locked >= count);
|
||||
|
||||
_locked -= count;
|
||||
@@ -106,12 +113,12 @@ void Credits::unlock(int count) {
|
||||
updateNonLockedValue();
|
||||
}
|
||||
|
||||
void Credits::withdrawLocked(int count) {
|
||||
Expects(count >= 0);
|
||||
void Credits::withdrawLocked(StarsAmount count) {
|
||||
Expects(count >= StarsAmount(0));
|
||||
Expects(_locked >= count);
|
||||
|
||||
_locked -= count;
|
||||
apply(_balance >= count ? (_balance - count) : 0);
|
||||
apply(_balance >= count ? (_balance - count) : StarsAmount(0));
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@@ -119,7 +126,7 @@ void Credits::invalidate() {
|
||||
_reload.call();
|
||||
}
|
||||
|
||||
void Credits::apply(uint64 balance) {
|
||||
void Credits::apply(StarsAmount balance) {
|
||||
_balance = balance;
|
||||
updateNonLockedValue();
|
||||
|
||||
@@ -129,7 +136,7 @@ void Credits::apply(uint64 balance) {
|
||||
}
|
||||
}
|
||||
|
||||
void Credits::apply(PeerId peerId, uint64 balance) {
|
||||
void Credits::apply(PeerId peerId, StarsAmount balance) {
|
||||
_cachedPeerBalances[peerId] = balance;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,30 +17,32 @@ class Session;
|
||||
|
||||
namespace Data {
|
||||
|
||||
[[nodiscard]] StarsAmount FromTL(const MTPStarsAmount &value);
|
||||
|
||||
class Credits final {
|
||||
public:
|
||||
explicit Credits(not_null<Main::Session*> session);
|
||||
~Credits();
|
||||
|
||||
void load(bool force = false);
|
||||
void apply(uint64 balance);
|
||||
void apply(PeerId peerId, uint64 balance);
|
||||
void apply(StarsAmount balance);
|
||||
void apply(PeerId peerId, StarsAmount balance);
|
||||
|
||||
[[nodiscard]] bool loaded() const;
|
||||
[[nodiscard]] rpl::producer<bool> loadedValue() const;
|
||||
|
||||
[[nodiscard]] uint64 balance() const;
|
||||
[[nodiscard]] uint64 balance(PeerId peerId) const;
|
||||
[[nodiscard]] rpl::producer<uint64> balanceValue() const;
|
||||
[[nodiscard]] StarsAmount balance() const;
|
||||
[[nodiscard]] StarsAmount balance(PeerId peerId) const;
|
||||
[[nodiscard]] rpl::producer<StarsAmount> balanceValue() const;
|
||||
[[nodiscard]] rpl::producer<float64> rateValue(
|
||||
not_null<PeerData*> ownedBotOrChannel);
|
||||
|
||||
void applyCurrency(PeerId peerId, uint64 balance);
|
||||
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
|
||||
|
||||
void lock(int count);
|
||||
void unlock(int count);
|
||||
void withdrawLocked(int count);
|
||||
void lock(StarsAmount count);
|
||||
void unlock(StarsAmount count);
|
||||
void withdrawLocked(StarsAmount count);
|
||||
void invalidate();
|
||||
|
||||
void apply(const MTPDupdateStarsBalance &data);
|
||||
@@ -52,12 +54,12 @@ private:
|
||||
|
||||
std::unique_ptr<Api::CreditsStatus> _loader;
|
||||
|
||||
base::flat_map<PeerId, uint64> _cachedPeerBalances;
|
||||
base::flat_map<PeerId, StarsAmount> _cachedPeerBalances;
|
||||
base::flat_map<PeerId, uint64> _cachedPeerCurrencyBalances;
|
||||
|
||||
uint64 _balance = 0;
|
||||
uint64 _locked = 0;
|
||||
rpl::variable<uint64> _nonLockedBalance;
|
||||
StarsAmount _balance;
|
||||
StarsAmount _locked;
|
||||
rpl::variable<StarsAmount> _nonLockedBalance;
|
||||
rpl::event_stream<> _loadedChanges;
|
||||
crl::time _lastLoaded = 0;
|
||||
float64 _rate = 0.;
|
||||
|
||||
@@ -85,7 +85,7 @@ QByteArray RecentPeers::serialize() const {
|
||||
auto stream = Serialize::ByteArrayWriter(size);
|
||||
stream
|
||||
<< quint32(AppVersion)
|
||||
<< quint32(_list.size());
|
||||
<< quint32(count);
|
||||
for (const auto &peer : list) {
|
||||
Serialize::writePeer(stream, peer);
|
||||
}
|
||||
|
||||
@@ -156,10 +156,11 @@ void SponsoredMessages::inject(
|
||||
if (blockIt == end(history->blocks)) {
|
||||
return;
|
||||
}
|
||||
const auto messages = [&]() -> const std::vector<ViewPtr> & {
|
||||
const auto messages = [&]() -> const std::vector<ViewPtr>& {
|
||||
return (*blockIt)->messages;
|
||||
};
|
||||
auto lastViewIt = ranges::find(messages(), lastView, &ViewPtr::get);
|
||||
auto appendAtLeastToEnd = false;
|
||||
while ((summaryBetween < list.postsBetween)
|
||||
|| (summaryHeight < betweenHeight)) {
|
||||
lastViewIt++;
|
||||
@@ -168,6 +169,10 @@ void SponsoredMessages::inject(
|
||||
if (blockIt != end(history->blocks)) {
|
||||
lastViewIt = begin(messages());
|
||||
} else {
|
||||
if (!list.injectedCount) {
|
||||
appendAtLeastToEnd = true;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -182,17 +187,24 @@ void SponsoredMessages::inject(
|
||||
entryIt->itemFullId = FullMsgId(
|
||||
history->peer->id,
|
||||
_session->data().nextLocalMessageId());
|
||||
const auto makedMessage = history->makeMessage(
|
||||
entryIt->itemFullId.msg,
|
||||
entryIt->sponsored.from,
|
||||
entryIt->sponsored.textWithEntities,
|
||||
(*lastViewIt)->data());
|
||||
entryIt->item.reset(makedMessage.get());
|
||||
history->addNewInTheMiddle(
|
||||
makedMessage.get(),
|
||||
std::distance(begin(history->blocks), blockIt),
|
||||
std::distance(begin(messages()), lastViewIt) + 1);
|
||||
messages().back().get()->setPendingResize();
|
||||
if (appendAtLeastToEnd) {
|
||||
entryIt->item.reset(history->addSponsoredMessage(
|
||||
entryIt->itemFullId.msg,
|
||||
entryIt->sponsored.from,
|
||||
entryIt->sponsored.textWithEntities));
|
||||
} else {
|
||||
const auto makedMessage = history->makeMessage(
|
||||
entryIt->itemFullId.msg,
|
||||
entryIt->sponsored.from,
|
||||
entryIt->sponsored.textWithEntities,
|
||||
(*lastViewIt)->data());
|
||||
entryIt->item.reset(makedMessage.get());
|
||||
history->addNewInTheMiddle(
|
||||
makedMessage.get(),
|
||||
std::distance(begin(history->blocks), blockIt),
|
||||
std::distance(begin(messages()), lastViewIt) + 1);
|
||||
messages().back().get()->setPendingResize();
|
||||
}
|
||||
list.injectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ QByteArray TopPeers::serialize() const {
|
||||
stream
|
||||
<< quint32(AppVersion)
|
||||
<< quint32(_disabled ? 1 : 0)
|
||||
<< quint32(_list.size());
|
||||
<< quint32(count);
|
||||
for (const auto &top : list) {
|
||||
Serialize::writePeer(stream, top.peer);
|
||||
stream << SerializeRating(top.rating);
|
||||
|
||||
@@ -92,27 +92,28 @@ struct PeerUpdate {
|
||||
BusinessDetails = (1ULL << 30),
|
||||
Birthday = (1ULL << 31),
|
||||
PersonalChannel = (1ULL << 32),
|
||||
StarRefProgram = (1ULL << 33),
|
||||
|
||||
// For chats and channels
|
||||
InviteLinks = (1ULL << 33),
|
||||
Members = (1ULL << 34),
|
||||
Admins = (1ULL << 35),
|
||||
BannedUsers = (1ULL << 36),
|
||||
Rights = (1ULL << 37),
|
||||
PendingRequests = (1ULL << 38),
|
||||
Reactions = (1ULL << 39),
|
||||
InviteLinks = (1ULL << 34),
|
||||
Members = (1ULL << 35),
|
||||
Admins = (1ULL << 36),
|
||||
BannedUsers = (1ULL << 37),
|
||||
Rights = (1ULL << 38),
|
||||
PendingRequests = (1ULL << 39),
|
||||
Reactions = (1ULL << 40),
|
||||
|
||||
// For channels
|
||||
ChannelAmIn = (1ULL << 40),
|
||||
StickersSet = (1ULL << 41),
|
||||
EmojiSet = (1ULL << 42),
|
||||
ChannelLinkedChat = (1ULL << 43),
|
||||
ChannelLocation = (1ULL << 44),
|
||||
Slowmode = (1ULL << 45),
|
||||
GroupCall = (1ULL << 46),
|
||||
ChannelAmIn = (1ULL << 41),
|
||||
StickersSet = (1ULL << 42),
|
||||
EmojiSet = (1ULL << 43),
|
||||
ChannelLinkedChat = (1ULL << 44),
|
||||
ChannelLocation = (1ULL << 45),
|
||||
Slowmode = (1ULL << 46),
|
||||
GroupCall = (1ULL << 47),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 46),
|
||||
LastUsedBit = (1ULL << 47),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
@@ -716,7 +716,7 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) {
|
||||
}
|
||||
if (pinnedChanged) {
|
||||
const auto filterList = _owner->chatsFilters().chatsList(id);
|
||||
filterList->pinned()->applyList(wasFilter.pinned());
|
||||
filterList->pinned()->applyList(filter.pinned());
|
||||
}
|
||||
if (chatlistChanged) {
|
||||
_isChatlistChanged.fire_copy(id);
|
||||
|
||||
@@ -57,12 +57,15 @@ struct CreditsHistoryEntry final {
|
||||
QDateTime lastSaleDate;
|
||||
PhotoId photoId = 0;
|
||||
std::vector<CreditsHistoryMedia> extended;
|
||||
uint64 credits = 0;
|
||||
StarsAmount credits;
|
||||
uint64 bareMsgId = 0;
|
||||
uint64 barePeerId = 0;
|
||||
uint64 bareGiveawayMsgId = 0;
|
||||
uint64 bareGiftStickerId = 0;
|
||||
uint64 bareActorId = 0;
|
||||
StarsAmount starrefAmount;
|
||||
int starrefCommission = 0;
|
||||
uint64 starrefRecipientId = 0;
|
||||
PeerType peerType;
|
||||
QDateTime subscriptionUntil;
|
||||
QDateTime successDate;
|
||||
@@ -89,7 +92,7 @@ struct CreditsStatusSlice final {
|
||||
using OffsetToken = QString;
|
||||
std::vector<CreditsHistoryEntry> list;
|
||||
std::vector<SubscriptionEntry> subscriptions;
|
||||
uint64 balance = 0;
|
||||
StarsAmount balance;
|
||||
uint64 subscriptionsMissingBalance = 0;
|
||||
bool allLoaded = false;
|
||||
OffsetToken token;
|
||||
|
||||
@@ -7,22 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/stars_amount.h"
|
||||
#include "data/data_statistics_chart.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
|
||||
namespace Data {
|
||||
|
||||
using CreditsEarnInt = uint64;
|
||||
|
||||
struct CreditsEarnStatistics final {
|
||||
explicit operator bool() const {
|
||||
return !!usdRate;
|
||||
}
|
||||
Data::StatisticalGraph revenueGraph;
|
||||
CreditsEarnInt currentBalance = 0;
|
||||
CreditsEarnInt availableBalance = 0;
|
||||
CreditsEarnInt overallRevenue = 0;
|
||||
StarsAmount currentBalance;
|
||||
StarsAmount availableBalance;
|
||||
StarsAmount overallRevenue;
|
||||
float64 usdRate = 0.;
|
||||
bool isWithdrawalEnabled = false;
|
||||
QDateTime nextWithdrawalAt;
|
||||
|
||||
@@ -55,7 +55,8 @@ MTPInputReplyTo ReplyToForMTP(
|
||||
? replyingToTopic->rootId()
|
||||
: Data::ForumTopic::kGeneralId)
|
||||
: (to ? to->topicRootId() : Data::ForumTopic::kGeneralId);
|
||||
const auto replyToTopicId = to
|
||||
const auto replyToTopicId = (to
|
||||
&& (to->history() != history || to->id != replyingToTopicId))
|
||||
? to->topicRootId()
|
||||
: replyingToTopicId;
|
||||
const auto external = replyTo.messageId
|
||||
|
||||
@@ -2220,7 +2220,7 @@ void MessageReactions::scheduleSendPaid(
|
||||
_paid->scheduledPrivacySet = anonymous.has_value();
|
||||
}
|
||||
if (count > 0) {
|
||||
_item->history()->session().credits().lock(count);
|
||||
_item->history()->session().credits().lock(StarsAmount(count));
|
||||
}
|
||||
_item->history()->owner().reactions().schedulePaid(_item);
|
||||
}
|
||||
@@ -2233,7 +2233,8 @@ void MessageReactions::cancelScheduledPaid() {
|
||||
if (_paid) {
|
||||
if (_paid->scheduledFlag) {
|
||||
if (const auto amount = int(_paid->scheduled)) {
|
||||
_item->history()->session().credits().unlock(amount);
|
||||
_item->history()->session().credits().unlock(
|
||||
StarsAmount(amount));
|
||||
}
|
||||
_paid->scheduled = 0;
|
||||
_paid->scheduledFlag = 0;
|
||||
@@ -2296,9 +2297,9 @@ void MessageReactions::finishPaidSending(
|
||||
if (const auto amount = send.count) {
|
||||
const auto credits = &_item->history()->session().credits();
|
||||
if (success) {
|
||||
credits->withdrawLocked(amount);
|
||||
credits->withdrawLocked(StarsAmount(amount));
|
||||
} else {
|
||||
credits->unlock(amount);
|
||||
credits->unlock(StarsAmount(amount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,9 @@ constexpr auto SpecialMsgIdShift = EndStoryMsgId.bare;
|
||||
constexpr auto ShowAtTheEndMsgId = MsgId(SpecialMsgIdShift + 1);
|
||||
constexpr auto SwitchAtTopMsgId = MsgId(SpecialMsgIdShift + 2);
|
||||
constexpr auto ShowAndStartBotMsgId = MsgId(SpecialMsgIdShift + 4);
|
||||
constexpr auto ShowAndMaybeStartBotMsgId = MsgId(SpecialMsgIdShift + 5);
|
||||
constexpr auto ShowForChooseMessagesMsgId = MsgId(SpecialMsgIdShift + 6);
|
||||
constexpr auto kSearchQueryOffsetHint = -1;
|
||||
|
||||
static_assert(SpecialMsgIdShift + 0xFF < 0);
|
||||
static_assert(-(SpecialMsgIdShift + 0xFF) > ServerMaxMsgId);
|
||||
@@ -221,4 +223,13 @@ struct hash<FullStoryId> {
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<FullMsgId> {
|
||||
size_t operator()(FullMsgId value) const {
|
||||
return QtPrivate::QHashCombine().operator()(
|
||||
std::hash<BareId>()(value.peer.value),
|
||||
value.msg.bare);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
@@ -26,6 +26,109 @@ constexpr auto kDefaultSearchTimeoutMs = crl::time(200);
|
||||
|
||||
} // namespace
|
||||
|
||||
MTPMessagesFilter PrepareSearchFilter(Storage::SharedMediaType type) {
|
||||
using Type = Storage::SharedMediaType;
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
return MTP_inputMessagesFilterPhotos();
|
||||
case Type::Video:
|
||||
return MTP_inputMessagesFilterVideo();
|
||||
case Type::PhotoVideo:
|
||||
return MTP_inputMessagesFilterPhotoVideo();
|
||||
case Type::MusicFile:
|
||||
return MTP_inputMessagesFilterMusic();
|
||||
case Type::File:
|
||||
return MTP_inputMessagesFilterDocument();
|
||||
case Type::VoiceFile:
|
||||
return MTP_inputMessagesFilterVoice();
|
||||
case Type::RoundVoiceFile:
|
||||
return MTP_inputMessagesFilterRoundVoice();
|
||||
case Type::RoundFile:
|
||||
return MTP_inputMessagesFilterRoundVideo();
|
||||
case Type::GIF:
|
||||
return MTP_inputMessagesFilterGif();
|
||||
case Type::Link:
|
||||
return MTP_inputMessagesFilterUrl();
|
||||
case Type::ChatPhoto:
|
||||
return MTP_inputMessagesFilterChatPhotos();
|
||||
case Type::Pinned:
|
||||
return MTP_inputMessagesFilterPinned();
|
||||
}
|
||||
return MTP_inputMessagesFilterEmpty();
|
||||
}
|
||||
|
||||
std::optional<GlobalMediaRequest> PrepareGlobalMediaRequest(
|
||||
not_null<Main::Session*> session,
|
||||
int32 offsetRate,
|
||||
Data::MessagePosition offsetPosition,
|
||||
Storage::SharedMediaType type,
|
||||
const QString &query) {
|
||||
const auto filter = PrepareSearchFilter(type);
|
||||
if (query.isEmpty() && filter.type() == mtpc_inputMessagesFilterEmpty) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto minDate = 0;
|
||||
const auto maxDate = 0;
|
||||
const auto folderId = 0;
|
||||
const auto limit = offsetPosition.fullId.peer
|
||||
? kSharedMediaLimit
|
||||
: kFirstSharedMediaLimit;
|
||||
return MTPmessages_SearchGlobal(
|
||||
MTP_flags(MTPmessages_SearchGlobal::Flag::f_folder_id), // No archive
|
||||
MTP_int(folderId),
|
||||
MTP_string(query),
|
||||
filter,
|
||||
MTP_int(minDate),
|
||||
MTP_int(maxDate),
|
||||
MTP_int(offsetRate),
|
||||
(offsetPosition.fullId.peer
|
||||
? session->data().peer(PeerId(offsetPosition.fullId.peer))->input
|
||||
: MTP_inputPeerEmpty()),
|
||||
MTP_int(offsetPosition.fullId.msg),
|
||||
MTP_int(limit));
|
||||
}
|
||||
|
||||
GlobalMediaResult ParseGlobalMediaResult(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_Messages &data) {
|
||||
auto result = GlobalMediaResult();
|
||||
|
||||
auto messages = (const QVector<MTPMessage>*)nullptr;
|
||||
data.match([&](const MTPDmessages_messagesNotModified &) {
|
||||
}, [&](const auto &data) {
|
||||
session->data().processUsers(data.vusers());
|
||||
session->data().processChats(data.vchats());
|
||||
messages = &data.vmessages().v;
|
||||
});
|
||||
data.match([&](const MTPDmessages_messagesNotModified &) {
|
||||
}, [&](const MTPDmessages_messages &data) {
|
||||
result.fullCount = data.vmessages().v.size();
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
result.fullCount = data.vcount().v;
|
||||
result.offsetRate = data.vnext_rate().value_or_empty();
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
result.fullCount = data.vcount().v;
|
||||
});
|
||||
data.match([&](const MTPDmessages_channelMessages &data) {
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (ParseSearchResult)"));
|
||||
}, [](const auto &) {});
|
||||
|
||||
const auto addType = NewMessageType::Existing;
|
||||
result.messageIds.reserve(messages->size());
|
||||
for (const auto &message : *messages) {
|
||||
const auto item = session->data().addNewMessage(
|
||||
message,
|
||||
MessageFlags(),
|
||||
addType);
|
||||
if (item) {
|
||||
result.messageIds.push_back(item->position());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<SearchRequest> PrepareSearchRequest(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
@@ -33,36 +136,7 @@ std::optional<SearchRequest> PrepareSearchRequest(
|
||||
const QString &query,
|
||||
MsgId messageId,
|
||||
Data::LoadDirection direction) {
|
||||
const auto filter = [&] {
|
||||
using Type = Storage::SharedMediaType;
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
return MTP_inputMessagesFilterPhotos();
|
||||
case Type::Video:
|
||||
return MTP_inputMessagesFilterVideo();
|
||||
case Type::PhotoVideo:
|
||||
return MTP_inputMessagesFilterPhotoVideo();
|
||||
case Type::MusicFile:
|
||||
return MTP_inputMessagesFilterMusic();
|
||||
case Type::File:
|
||||
return MTP_inputMessagesFilterDocument();
|
||||
case Type::VoiceFile:
|
||||
return MTP_inputMessagesFilterVoice();
|
||||
case Type::RoundVoiceFile:
|
||||
return MTP_inputMessagesFilterRoundVoice();
|
||||
case Type::RoundFile:
|
||||
return MTP_inputMessagesFilterRoundVideo();
|
||||
case Type::GIF:
|
||||
return MTP_inputMessagesFilterGif();
|
||||
case Type::Link:
|
||||
return MTP_inputMessagesFilterUrl();
|
||||
case Type::ChatPhoto:
|
||||
return MTP_inputMessagesFilterChatPhotos();
|
||||
case Type::Pinned:
|
||||
return MTP_inputMessagesFilterPinned();
|
||||
}
|
||||
return MTP_inputMessagesFilterEmpty();
|
||||
}();
|
||||
const auto filter = PrepareSearchFilter(type);
|
||||
if (query.isEmpty() && filter.type() == mtpc_inputMessagesFilterEmpty) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class Session;
|
||||
|
||||
namespace Data {
|
||||
enum class LoadDirection : char;
|
||||
struct MessagePosition;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
@@ -36,6 +37,27 @@ using HistoryResult = SearchResult;
|
||||
using HistoryRequest = MTPmessages_GetHistory;
|
||||
using HistoryRequestResult = MTPmessages_Messages;
|
||||
|
||||
using GlobalMediaRequest = MTPmessages_SearchGlobal;
|
||||
struct GlobalMediaResult {
|
||||
std::vector<Data::MessagePosition> messageIds;
|
||||
int32 offsetRate = 0;
|
||||
int fullCount = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] MTPMessagesFilter PrepareSearchFilter(
|
||||
Storage::SharedMediaType type);
|
||||
|
||||
[[nodiscard]] std::optional<GlobalMediaRequest> PrepareGlobalMediaRequest(
|
||||
not_null<Main::Session*> session,
|
||||
int32 offsetRate,
|
||||
Data::MessagePosition offsetPosition,
|
||||
Storage::SharedMediaType type,
|
||||
const QString &query);
|
||||
|
||||
[[nodiscard]] GlobalMediaResult ParseGlobalMediaResult(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_Messages &data);
|
||||
|
||||
[[nodiscard]] std::optional<SearchRequest> PrepareSearchRequest(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
|
||||
@@ -1404,10 +1404,6 @@ void Session::forgetPassportCredentials() {
|
||||
_passportCredentials = nullptr;
|
||||
}
|
||||
|
||||
QString Session::nameSortKey(const QString &name) const {
|
||||
return TextUtilities::RemoveAccents(name).toLower();
|
||||
}
|
||||
|
||||
void Session::setupMigrationViewer() {
|
||||
session().changes().peerUpdates(
|
||||
PeerUpdate::Flag::Migration
|
||||
|
||||
@@ -115,8 +115,6 @@ public:
|
||||
return *_session;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString nameSortKey(const QString &name) const;
|
||||
|
||||
[[nodiscard]] Groups &groups() {
|
||||
return _groups;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ using Options = base::flags<Option>;
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct FileOrigin;
|
||||
|
||||
struct UploadState {
|
||||
explicit UploadState(int64 size) : size(size) {
|
||||
}
|
||||
@@ -58,8 +60,6 @@ constexpr auto kVoiceMessageCacheTag = uint8(0x03);
|
||||
constexpr auto kVideoMessageCacheTag = uint8(0x04);
|
||||
constexpr auto kAnimationCacheTag = uint8(0x05);
|
||||
|
||||
struct FileOrigin;
|
||||
|
||||
} // namespace Data
|
||||
|
||||
struct MessageGroupId {
|
||||
@@ -342,3 +342,29 @@ enum class MediaWebPageFlag : uint8 {
|
||||
};
|
||||
inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; }
|
||||
using MediaWebPageFlags = base::flags<MediaWebPageFlag>;
|
||||
|
||||
namespace Data {
|
||||
|
||||
enum class ForwardOptions {
|
||||
PreserveInfo,
|
||||
NoSenderNames,
|
||||
NoNamesAndCaptions,
|
||||
};
|
||||
|
||||
struct ForwardDraft {
|
||||
MessageIdsList ids;
|
||||
ForwardOptions options = ForwardOptions::PreserveInfo;
|
||||
|
||||
friend inline auto operator<=>(
|
||||
const ForwardDraft&,
|
||||
const ForwardDraft&) = default;
|
||||
};
|
||||
|
||||
using ForwardDrafts = base::flat_map<MsgId, ForwardDraft>;
|
||||
|
||||
struct ResolvedForwardDraft {
|
||||
HistoryItemsList items;
|
||||
ForwardOptions options = ForwardOptions::PreserveInfo;
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -205,6 +205,16 @@ void UserData::setBusinessDetails(Data::BusinessDetails details) {
|
||||
session().changes().peerUpdated(this, UpdateFlag::BusinessDetails);
|
||||
}
|
||||
|
||||
void UserData::setStarRefProgram(StarRefProgram program) {
|
||||
const auto info = botInfo.get();
|
||||
if (info && info->starRefProgram != program) {
|
||||
info->starRefProgram = program;
|
||||
session().changes().peerUpdated(
|
||||
this,
|
||||
Data::PeerUpdate::Flag::StarRefProgram);
|
||||
}
|
||||
}
|
||||
|
||||
ChannelId UserData::personalChannelId() const {
|
||||
return _personalChannelId;
|
||||
}
|
||||
@@ -600,6 +610,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||
}
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
info->canManageEmojiStatus = update.is_bot_can_manage_emoji_status();
|
||||
user->setStarRefProgram(
|
||||
Data::ParseStarRefProgram(update.vstarref_program()));
|
||||
}
|
||||
if (const auto pinned = update.vpinned_msg_id()) {
|
||||
SetTopPinnedMessageId(user, pinned->v);
|
||||
@@ -719,4 +731,19 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||
user->fullUpdated();
|
||||
}
|
||||
|
||||
StarRefProgram ParseStarRefProgram(const MTPStarRefProgram *program) {
|
||||
if (!program) {
|
||||
return {};
|
||||
}
|
||||
auto result = StarRefProgram();
|
||||
const auto &data = program->data();
|
||||
result.commission = data.vcommission_permille().v;
|
||||
result.durationMonths = data.vduration_months().value_or_empty();
|
||||
result.revenuePerUser = data.vdaily_revenue_per_user()
|
||||
? Data::FromTL(*data.vdaily_revenue_per_user())
|
||||
: StarsAmount();
|
||||
result.endDate = data.vend_date().value_or_empty();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/stars_amount.h"
|
||||
#include "data/data_birthday.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_chat_participant_status.h"
|
||||
@@ -19,6 +20,17 @@ struct BotCommand;
|
||||
struct BusinessDetails;
|
||||
} // namespace Data
|
||||
|
||||
struct StarRefProgram {
|
||||
StarsAmount revenuePerUser;
|
||||
TimeId endDate = 0;
|
||||
ushort commission = 0;
|
||||
uint8 durationMonths = 0;
|
||||
|
||||
friend inline constexpr bool operator==(
|
||||
StarRefProgram,
|
||||
StarRefProgram) = default;
|
||||
};
|
||||
|
||||
struct BotInfo {
|
||||
BotInfo();
|
||||
|
||||
@@ -44,6 +56,8 @@ struct BotInfo {
|
||||
ChatAdminRights groupAdminRights;
|
||||
ChatAdminRights channelAdminRights;
|
||||
|
||||
StarRefProgram starRefProgram;
|
||||
|
||||
int version = 0;
|
||||
int descriptionVersion = 0;
|
||||
int activeUsers = 0;
|
||||
@@ -206,6 +220,8 @@ public:
|
||||
[[nodiscard]] const Data::BusinessDetails &businessDetails() const;
|
||||
void setBusinessDetails(Data::BusinessDetails details);
|
||||
|
||||
void setStarRefProgram(StarRefProgram program);
|
||||
|
||||
[[nodiscard]] ChannelId personalChannelId() const;
|
||||
[[nodiscard]] MsgId personalChannelMessageId() const;
|
||||
void setPersonalChannel(ChannelId channelId, MsgId messageId);
|
||||
@@ -253,4 +269,7 @@ namespace Data {
|
||||
|
||||
void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update);
|
||||
|
||||
[[nodiscard]] StarRefProgram ParseStarRefProgram(
|
||||
const MTPStarRefProgram *program);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -689,7 +689,11 @@ recentPeersSpecialName: PeerListItem(recentPeersItem) {
|
||||
namePosition: point(64px, 19px);
|
||||
}
|
||||
|
||||
dialogsTabsScroll: ScrollArea(defaultScrollArea) {
|
||||
barHidden: true;
|
||||
}
|
||||
dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) {
|
||||
padding: 8px;
|
||||
height: 33px;
|
||||
barTop: 30px;
|
||||
barSkip: 0px;
|
||||
@@ -707,7 +711,6 @@ dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) {
|
||||
rippleBgActive: lightButtonBgOver;
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
dialogsSearchTabsPadding: 8px;
|
||||
|
||||
chatsFiltersTabs: SettingsSlider(dialogsSearchTabs) {
|
||||
rippleBottomSkip: 0px;
|
||||
|
||||
@@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "ui/chat/chats_filter_tag.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/effects/loading_element.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
@@ -129,7 +130,9 @@ constexpr auto kPreviewPostsLimit = 3;
|
||||
if (const auto history = row->key().history()) {
|
||||
if (const auto user = history->peer->asUser()) {
|
||||
if (user->botInfo && user->botInfo->hasMainApp) {
|
||||
return user;
|
||||
if (!history->unreadCount() && !history->unreadMark()) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,6 +318,9 @@ InnerWidget::InnerWidget(
|
||||
session().settings().archiveCollapsedChanges() | rpl::map_to(false),
|
||||
session().data().chatsFilters().changed() | rpl::map_to(true)
|
||||
) | rpl::start_with_next([=](bool refreshHeight) {
|
||||
if (refreshHeight) {
|
||||
_chatsFilterTags.clear();
|
||||
}
|
||||
if (refreshHeight && _filterId) {
|
||||
// Height of the main list will be refreshed in other way.
|
||||
_shownList->updateHeights(_narrowRatio);
|
||||
@@ -1216,8 +1222,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||
const auto it = _rightButtons.find(user->id);
|
||||
if (it == _rightButtons.end()) {
|
||||
auto rightButton = RightButton();
|
||||
const auto text
|
||||
= tr::lng_profile_open_app_short(tr::now).toUpper();
|
||||
const auto text = tr::lng_profile_open_app_short(tr::now);
|
||||
rightButton.text.setText(st::dialogRowOpenBotTextStyle, text);
|
||||
const auto size = QSize(
|
||||
rightButton.text.maxWidth()
|
||||
@@ -1944,7 +1949,10 @@ const std::vector<Key> &InnerWidget::pinnedChatsOrder() const {
|
||||
}
|
||||
|
||||
void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
|
||||
if (!_pressed || _dragging || _state != WidgetState::Default) {
|
||||
if (!_pressed
|
||||
|| _dragging
|
||||
|| (_state != WidgetState::Default)
|
||||
|| _pressedBotApp) {
|
||||
return;
|
||||
} else if (qAbs(localPosition.y() - _dragStart.y())
|
||||
< style::ConvertScale(kStartReorderThreshold)) {
|
||||
@@ -4274,34 +4282,12 @@ QImage *InnerWidget::cacheChatsFilterTag(
|
||||
if (roundedText.isEmpty() || colorIndex < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto &roundedFont = st::dialogRowFilterTagFont;
|
||||
const auto roundedWidth = roundedFont->width(roundedText)
|
||||
+ roundedFont->spacew * 3;
|
||||
const auto rect = QRect(0, 0, roundedWidth, roundedFont->height);
|
||||
auto cache = QImage(
|
||||
rect.size() * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
cache.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
cache.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&cache);
|
||||
const auto pen = QPen(active
|
||||
? st::dialogsBgActive
|
||||
: Ui::EmptyUserpic::UserpicColor(colorIndex).color2);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(active
|
||||
? st::dialogsTextFgActive->c
|
||||
: anim::with_alpha(pen.color(), .15));
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto radius = roundedFont->height / 3.;
|
||||
p.drawRoundedRect(rect, radius, radius);
|
||||
}
|
||||
p.setPen(pen);
|
||||
p.setFont(roundedFont);
|
||||
p.drawText(rect, roundedText, style::al_center);
|
||||
}
|
||||
return &_chatsFilterTags.emplace(key, std::move(cache)).first->second;
|
||||
return &_chatsFilterTags.emplace(
|
||||
key,
|
||||
Ui::ChatsFilterTag(
|
||||
std::move(roundedText),
|
||||
Ui::EmptyUserpic::UserpicColor(colorIndex).color2->c,
|
||||
active)).first->second;
|
||||
}
|
||||
|
||||
bool InnerWidget::chooseHashtag() {
|
||||
|
||||
@@ -121,8 +121,12 @@ struct EntryState {
|
||||
FilterId filterId = 0;
|
||||
FullReplyTo currentReplyTo;
|
||||
|
||||
friend inline auto operator<=>(EntryState, EntryState) noexcept
|
||||
= default;
|
||||
friend inline auto operator<=>(
|
||||
const EntryState&,
|
||||
const EntryState&) = default;
|
||||
friend inline bool operator==(
|
||||
const EntryState&,
|
||||
const EntryState&) = default;
|
||||
};
|
||||
|
||||
struct SearchState {
|
||||
|
||||
@@ -696,16 +696,19 @@ void Widget::chosenRow(const ChosenRow &row) {
|
||||
}
|
||||
return;
|
||||
} else if (const auto topic = row.key.topic()) {
|
||||
auto params = Window::SectionShow(
|
||||
Window::SectionShow::Way::ClearStack);
|
||||
params.highlightPart.text = _searchState.query;
|
||||
if (!params.highlightPart.empty()) {
|
||||
params.highlightPartOffsetHint = kSearchQueryOffsetHint;
|
||||
}
|
||||
if (row.newWindow) {
|
||||
controller()->showInNewWindow(
|
||||
Window::SeparateId(topic),
|
||||
row.message.fullId.msg);
|
||||
} else {
|
||||
session().data().saveViewAsMessages(topic->forum(), false);
|
||||
controller()->showThread(
|
||||
topic,
|
||||
row.message.fullId.msg,
|
||||
Window::SectionShow::Way::ClearStack);
|
||||
controller()->showThread(topic, row.message.fullId.msg, params);
|
||||
}
|
||||
} else if (history
|
||||
&& row.userpicClick
|
||||
@@ -742,13 +745,16 @@ void Widget::chosenRow(const ChosenRow &row) {
|
||||
const auto showAtMsgId = controller()->uniqueChatsInSearchResults()
|
||||
? ShowAtUnreadMsgId
|
||||
: row.message.fullId.msg;
|
||||
auto params = Window::SectionShow(
|
||||
Window::SectionShow::Way::ClearStack);
|
||||
params.highlightPart.text = _searchState.query;
|
||||
if (!params.highlightPart.empty()) {
|
||||
params.highlightPartOffsetHint = kSearchQueryOffsetHint;
|
||||
}
|
||||
if (row.newWindow) {
|
||||
controller()->showInNewWindow(peer, showAtMsgId);
|
||||
} else {
|
||||
controller()->showThread(
|
||||
history,
|
||||
showAtMsgId,
|
||||
Window::SectionShow::Way::ClearStack);
|
||||
controller()->showThread(history, showAtMsgId, params);
|
||||
hideChildList();
|
||||
}
|
||||
} else if (const auto folder = row.key.folder()) {
|
||||
@@ -1421,6 +1427,9 @@ void Widget::updateSuggestions(anim::type animated) {
|
||||
controller(),
|
||||
TopPeersContent(&session()),
|
||||
RecentPeersContent(&session()));
|
||||
_suggestions->clearSearchQueryRequests() | rpl::start_with_next([=] {
|
||||
setSearchQuery(QString());
|
||||
}, _suggestions->lifetime());
|
||||
_searchSuggestionsLocked = false;
|
||||
|
||||
rpl::merge(
|
||||
@@ -2928,7 +2937,11 @@ void Widget::updateCancelSearch() {
|
||||
|
||||
QString Widget::validateSearchQuery() {
|
||||
const auto query = currentSearchQuery();
|
||||
if (_searchState.tab == ChatSearchTab::PublicPosts) {
|
||||
if (!_subsectionTopBar
|
||||
&& _suggestions
|
||||
&& _suggestions->consumeSearchQuery(query)) {
|
||||
return QString();
|
||||
} else if (_searchState.tab == ChatSearchTab::PublicPosts) {
|
||||
if (_searchHashOrCashtag == HashOrCashtag::None) {
|
||||
_searchHashOrCashtag = HashOrCashtag::Hashtag;
|
||||
}
|
||||
@@ -3265,6 +3278,9 @@ bool Widget::applySearchState(SearchState state) {
|
||||
_openedForum && _searchState.inChat);
|
||||
}
|
||||
if (!_searchState.inChat && _searchState.query.isEmpty()) {
|
||||
if (!_widthAnimationCache.isNull()) {
|
||||
stopWidthAnimation();
|
||||
}
|
||||
setInnerFocus();
|
||||
} else if (!_subsectionTopBar) {
|
||||
_search->setFocus();
|
||||
@@ -3923,9 +3939,18 @@ void Widget::setSearchQuery(const QString &query, int cursorPosition) {
|
||||
}
|
||||
|
||||
bool Widget::cancelSearch(CancelSearchOptions options) {
|
||||
const auto clearingSuggestionsQuery = _suggestions
|
||||
&& _suggestions->consumeSearchQuery(QString());
|
||||
if (clearingSuggestionsQuery) {
|
||||
setSearchQuery(QString());
|
||||
if (!options.forceFullCancel) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
cancelSearchRequest();
|
||||
auto updatedState = _searchState;
|
||||
const auto clearingQuery = !updatedState.query.isEmpty();
|
||||
const auto clearingQuery = clearingSuggestionsQuery
|
||||
|| !updatedState.query.isEmpty();
|
||||
const auto forceFullCancel = options.forceFullCancel;
|
||||
auto clearingInChat = (forceFullCancel || !clearingQuery)
|
||||
&& (updatedState.inChat
|
||||
|
||||
@@ -144,9 +144,7 @@ int PaintBadges(
|
||||
int pinnedIconTop,
|
||||
bool narrow) {
|
||||
auto initial = right;
|
||||
if (const auto used = PaintRightButton(p, context)) {
|
||||
return used - st::dialogsUnreadPadding;
|
||||
} else if (badgesState.unread
|
||||
if (badgesState.unread
|
||||
&& !badgesState.unreadCounter
|
||||
&& context.st->unreadMarkDiameter > 0) {
|
||||
const auto d = context.st->unreadMarkDiameter;
|
||||
@@ -188,6 +186,8 @@ int PaintBadges(
|
||||
: QString::number(badgesState.unreadCounter);
|
||||
const auto badge = PaintUnreadBadge(p, counter, right, top, st);
|
||||
right -= badge.width() + st.padding;
|
||||
} else if (const auto used = PaintRightButton(p, context)) {
|
||||
return used - st::dialogsUnreadPadding;
|
||||
} else if (displayPinnedIcon) {
|
||||
const auto &icon = ThreeStateIcon(
|
||||
st::dialogsPinnedIcon,
|
||||
|
||||
@@ -23,10 +23,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "dialogs/ui/chat_search_empty.h"
|
||||
#include "history/history.h"
|
||||
#include "info/downloads/info_downloads_widget.h"
|
||||
#include "info/media/info_media_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/info_wrap_widget.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
@@ -42,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/delayed_activation.h"
|
||||
#include "ui/dynamic_thumbnails.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "ui/unread_badge_paint.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_separate_id.h"
|
||||
@@ -60,6 +67,7 @@ constexpr auto kCollapsedChannelsCount = 5;
|
||||
constexpr auto kProbablyMaxChannels = 1000;
|
||||
constexpr auto kCollapsedAppsCount = 5;
|
||||
constexpr auto kProbablyMaxApps = 100;
|
||||
constexpr auto kSearchQueryDelay = crl::time(900);
|
||||
|
||||
class RecentRow final : public PeerListRow {
|
||||
public:
|
||||
@@ -194,7 +202,7 @@ RecentRow::RecentRow(not_null<PeerData*> peer)
|
||||
if (user->botInfo && user->botInfo->hasMainApp) {
|
||||
return std::make_unique<Ui::Text::String>(
|
||||
st::dialogRowOpenBotTextStyle,
|
||||
tr::lng_profile_open_app_short(tr::now).toUpper());
|
||||
tr::lng_profile_open_app_short(tr::now));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
@@ -261,7 +269,7 @@ bool RecentRow::refreshBadge() {
|
||||
}
|
||||
|
||||
QSize RecentRow::rightActionSize() const {
|
||||
if (_mainAppText) {
|
||||
if (_mainAppText && _badgeSize.isEmpty()) {
|
||||
return QSize(
|
||||
_mainAppText->maxWidth() + _mainAppText->minHeight(),
|
||||
st::dialogRowOpenBotHeight);
|
||||
@@ -270,7 +278,7 @@ QSize RecentRow::rightActionSize() const {
|
||||
}
|
||||
|
||||
QMargins RecentRow::rightActionMargins() const {
|
||||
if (_mainAppText) {
|
||||
if (_mainAppText && _badgeSize.isEmpty()) {
|
||||
return QMargins(
|
||||
0,
|
||||
st::dialogRowOpenBotRecentTop,
|
||||
@@ -292,13 +300,14 @@ void RecentRow::rightActionPaint(
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
if (_mainAppText) {
|
||||
if (_mainAppText && _badgeSize.isEmpty()) {
|
||||
const auto size = RecentRow::rightActionSize();
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(actionSelected
|
||||
? st::activeButtonBgOver
|
||||
: st::activeButtonBg);
|
||||
const auto radius = size.height() / 2;
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawRoundedRect(QRect(QPoint(x, y), size), radius, radius);
|
||||
if (_actionRipple) {
|
||||
_actionRipple->paint(p, x, y, outerWidth);
|
||||
@@ -335,13 +344,13 @@ void RecentRow::rightActionPaint(
|
||||
}
|
||||
|
||||
bool RecentRow::rightActionDisabled() const {
|
||||
return !_mainAppText;
|
||||
return !_mainAppText || !_badgeSize.isEmpty();
|
||||
}
|
||||
|
||||
void RecentRow::rightActionAddRipple(
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) {
|
||||
if (!_mainAppText) {
|
||||
if (!_mainAppText || !_badgeSize.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!_actionRipple) {
|
||||
@@ -1291,7 +1300,23 @@ Suggestions::Suggestions(
|
||||
RecentPeersList recentPeers)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _tabs(std::make_unique<Ui::SettingsSlider>(this, st::dialogsSearchTabs))
|
||||
, _tabsScroll(
|
||||
std::make_unique<Ui::ScrollArea>(this, st::dialogsTabsScroll, true))
|
||||
, _tabs(
|
||||
_tabsScroll->setOwnedWidget(
|
||||
object_ptr<Ui::SettingsSlider>(this, st::dialogsSearchTabs)))
|
||||
, _tabKeys{
|
||||
{ Tab::Chats },
|
||||
{ Tab::Channels },
|
||||
{ Tab::Apps },
|
||||
{ Tab::Media, MediaType::Photo },
|
||||
{ Tab::Media, MediaType::Video },
|
||||
{ Tab::Downloads },
|
||||
{ Tab::Media, MediaType::Link },
|
||||
{ Tab::Media, MediaType::File },
|
||||
{ Tab::Media, MediaType::MusicFile },
|
||||
{ Tab::Media, MediaType::RoundVoiceFile },
|
||||
}
|
||||
, _chatsScroll(std::make_unique<Ui::ElasticScroll>(this))
|
||||
, _chatsContent(
|
||||
_chatsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
||||
@@ -1312,8 +1337,8 @@ Suggestions::Suggestions(
|
||||
, _appsContent(
|
||||
_appsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
||||
, _recentApps(setupRecentApps())
|
||||
, _popularApps(setupPopularApps()) {
|
||||
|
||||
, _popularApps(setupPopularApps())
|
||||
, _searchQueryTimer([=] { applySearchQuery(); }) {
|
||||
setupTabs();
|
||||
setupChats();
|
||||
setupChannels();
|
||||
@@ -1323,10 +1348,46 @@ Suggestions::Suggestions(
|
||||
Suggestions::~Suggestions() = default;
|
||||
|
||||
void Suggestions::setupTabs() {
|
||||
_tabsScroll->setCustomWheelProcess([=](not_null<QWheelEvent*> e) {
|
||||
const auto pixelDelta = e->pixelDelta();
|
||||
const auto angleDelta = e->angleDelta();
|
||||
if (std::abs(pixelDelta.x()) + std::abs(angleDelta.x())) {
|
||||
return false;
|
||||
}
|
||||
const auto y = pixelDelta.y() ? pixelDelta.y() : angleDelta.y();
|
||||
_tabsScroll->scrollToX(_tabsScroll->scrollLeft() - y);
|
||||
return true;
|
||||
});
|
||||
|
||||
const auto scrollToIndex = [=](int index, anim::type type) {
|
||||
const auto to = index
|
||||
? (_tabs->centerOfSection(index) - _tabsScroll->width() / 2)
|
||||
: 0;
|
||||
_tabsScrollAnimation.stop();
|
||||
if (type == anim::type::instant) {
|
||||
_tabsScroll->scrollToX(to);
|
||||
} else {
|
||||
_tabsScrollAnimation.start(
|
||||
[=](float64 v) { _tabsScroll->scrollToX(v); },
|
||||
_tabsScroll->scrollLeft(),
|
||||
std::min(to, _tabsScroll->scrollLeftMax()),
|
||||
st::defaultTabsSlider.duration);
|
||||
}
|
||||
};
|
||||
rpl::single(-1) | rpl::then(
|
||||
_tabs->sectionActivated()
|
||||
) | rpl::combine_previous(
|
||||
) | rpl::start_with_next([=](int was, int index) {
|
||||
if (was != index) {
|
||||
scrollToIndex(index, anim::type::normal);
|
||||
}
|
||||
}, _tabs->lifetime());
|
||||
|
||||
const auto shadow = Ui::CreateChild<Ui::PlainShadow>(this);
|
||||
shadow->lower();
|
||||
|
||||
_tabs->move(st::dialogsSearchTabsPadding, 0);
|
||||
_tabsScroll->move(0, 0);
|
||||
_tabs->move(0, 0);
|
||||
rpl::combine(
|
||||
widthValue(),
|
||||
_tabs->heightValue()
|
||||
@@ -1335,20 +1396,37 @@ void Suggestions::setupTabs() {
|
||||
shadow->setGeometry(0, height - line, width, line);
|
||||
}, shadow->lifetime());
|
||||
|
||||
shadow->showOn(_tabs->shownValue());
|
||||
shadow->showOn(_tabsScroll->shownValue());
|
||||
|
||||
_tabs->setSections({
|
||||
tr::lng_recent_chats(tr::now),
|
||||
tr::lng_recent_channels(tr::now),
|
||||
tr::lng_recent_apps(tr::now),
|
||||
});
|
||||
const auto labels = base::flat_map<Key, QString>{
|
||||
{ Key{ Tab::Chats }, tr::lng_recent_chats(tr::now) },
|
||||
{ Key{ Tab::Channels }, tr::lng_recent_channels(tr::now) },
|
||||
{ Key{ Tab::Apps }, tr::lng_recent_apps(tr::now) },
|
||||
{ Key{ Tab::Media, MediaType::Photo }, tr::lng_all_photos(tr::now) },
|
||||
{ Key{ Tab::Media, MediaType::Video }, tr::lng_all_videos(tr::now) },
|
||||
{ Key{ Tab::Downloads }, tr::lng_all_downloads(tr::now) },
|
||||
{ Key{ Tab::Media, MediaType::Link }, tr::lng_all_links(tr::now) },
|
||||
{ Key{ Tab::Media, MediaType::File }, tr::lng_all_files(tr::now) },
|
||||
{
|
||||
Key{ Tab::Media, MediaType::MusicFile },
|
||||
tr::lng_all_music(tr::now),
|
||||
},
|
||||
{
|
||||
Key{ Tab::Media, MediaType::RoundVoiceFile },
|
||||
tr::lng_all_voice(tr::now),
|
||||
},
|
||||
};
|
||||
auto sections = std::vector<QString>();
|
||||
for (const auto key : _tabKeys) {
|
||||
const auto i = labels.find(key);
|
||||
Assert(i != end(labels));
|
||||
sections.push_back(i->second);
|
||||
}
|
||||
_tabs->setSections(sections);
|
||||
_tabs->sectionActivated(
|
||||
) | rpl::start_with_next([=](int section) {
|
||||
switchTab(section == 2
|
||||
? Tab::Apps
|
||||
: section
|
||||
? Tab::Channels
|
||||
: Tab::Chats);
|
||||
Assert(section >= 0 && section < _tabKeys.size());
|
||||
switchTab(_tabKeys[section]);
|
||||
}, _tabs->lifetime());
|
||||
}
|
||||
|
||||
@@ -1418,7 +1496,7 @@ void Suggestions::setupChats() {
|
||||
_chatsScroll->viewportEvent(e);
|
||||
}, _topPeers->lifetime());
|
||||
|
||||
_chatsScroll->setVisible(_tab.current() == Tab::Chats);
|
||||
_chatsScroll->setVisible(_key.current().tab == Tab::Chats);
|
||||
_chatsScroll->setCustomTouchProcess(_recent->processTouch);
|
||||
}
|
||||
|
||||
@@ -1452,7 +1530,7 @@ void Suggestions::setupChannels() {
|
||||
rpl::mappers::_1 + rpl::mappers::_2 == 0),
|
||||
anim::type::instant);
|
||||
|
||||
_channelsScroll->setVisible(_tab.current() == Tab::Channels);
|
||||
_channelsScroll->setVisible(_key.current().tab == Tab::Channels);
|
||||
_channelsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
|
||||
const auto myChannels = _myChannels->processTouch(e);
|
||||
const auto recommendations = _recommendations->processTouch(e);
|
||||
@@ -1469,7 +1547,7 @@ void Suggestions::setupApps() {
|
||||
_popularApps->wrap->toggle(count > 0, anim::type::instant);
|
||||
}, _popularApps->wrap->lifetime());
|
||||
|
||||
_appsScroll->setVisible(_tab.current() == Tab::Apps);
|
||||
_appsScroll->setVisible(_key.current().tab == Tab::Apps);
|
||||
_appsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
|
||||
const auto recentApps = _recentApps->processTouch(e);
|
||||
const auto popularApps = _popularApps->processTouch(e);
|
||||
@@ -1478,12 +1556,11 @@ void Suggestions::setupApps() {
|
||||
}
|
||||
|
||||
void Suggestions::selectJump(Qt::Key direction, int pageSize) {
|
||||
switch (_tab.current()) {
|
||||
switch (_key.current().tab) {
|
||||
case Tab::Chats: selectJumpChats(direction, pageSize); return;
|
||||
case Tab::Channels: selectJumpChannels(direction, pageSize); return;
|
||||
case Tab::Apps: selectJumpApps(direction, pageSize); return;
|
||||
}
|
||||
Unexpected("Tab in Suggestions::selectJump.");
|
||||
}
|
||||
|
||||
void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) {
|
||||
@@ -1661,7 +1738,7 @@ void Suggestions::selectJumpApps(Qt::Key direction, int pageSize) {
|
||||
}
|
||||
|
||||
void Suggestions::chooseRow() {
|
||||
switch (_tab.current()) {
|
||||
switch (_key.current().tab) {
|
||||
case Tab::Chats:
|
||||
if (!_topPeers->chooseRow()) {
|
||||
_recent->choose();
|
||||
@@ -1680,10 +1757,49 @@ void Suggestions::chooseRow() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Suggestions::consumeSearchQuery(const QString &query) {
|
||||
using Type = MediaType;
|
||||
const auto key = _key.current();
|
||||
const auto tab = key.tab;
|
||||
const auto type = (key.tab == Tab::Media) ? key.mediaType : Type::kCount;
|
||||
if (tab != Tab::Downloads
|
||||
&& type != Type::File
|
||||
&& type != Type::Link
|
||||
&& type != Type::MusicFile) {
|
||||
return false;
|
||||
} else if (_searchQuery == query) {
|
||||
return false;
|
||||
}
|
||||
_searchQuery = query;
|
||||
_persist = !_searchQuery.isEmpty();
|
||||
if (query.isEmpty() || tab == Tab::Downloads) {
|
||||
_searchQueryTimer.cancel();
|
||||
applySearchQuery();
|
||||
} else {
|
||||
_searchQueryTimer.callOnce(kSearchQueryDelay);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Suggestions::applySearchQuery() {
|
||||
const auto key = _key.current();
|
||||
const auto controller = _mediaLists[key].wrap->controller();
|
||||
const auto search = controller->searchFieldController();
|
||||
if (search->query() != _searchQuery) {
|
||||
search->setQuery(_searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> Suggestions::clearSearchQueryRequests() const {
|
||||
return _clearSearchQueryRequests.events();
|
||||
}
|
||||
|
||||
Data::Thread *Suggestions::updateFromParentDrag(QPoint globalPosition) {
|
||||
return (_tab.current() == Tab::Chats)
|
||||
? updateFromChatsDrag(globalPosition)
|
||||
: updateFromChannelsDrag(globalPosition);
|
||||
switch (_key.current().tab) {
|
||||
case Tab::Chats: return updateFromChatsDrag(globalPosition);
|
||||
case Tab::Channels: return updateFromChannelsDrag(globalPosition);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Data::Thread *Suggestions::updateFromChatsDrag(QPoint globalPosition) {
|
||||
@@ -1744,39 +1860,71 @@ void Suggestions::hide(anim::type animated, Fn<void()> finish) {
|
||||
}
|
||||
}
|
||||
|
||||
void Suggestions::switchTab(Tab tab) {
|
||||
const auto was = _tab.current();
|
||||
if (was == tab) {
|
||||
void Suggestions::switchTab(Key key) {
|
||||
const auto was = _key.current();
|
||||
if (was == key) {
|
||||
return;
|
||||
}
|
||||
_tab = tab;
|
||||
consumeSearchQuery(QString());
|
||||
_key = key;
|
||||
_persist = false;
|
||||
_clearSearchQueryRequests.fire({});
|
||||
if (_tabs->isHidden()) {
|
||||
return;
|
||||
}
|
||||
startSlideAnimation(was, tab);
|
||||
startSlideAnimation(was, key);
|
||||
}
|
||||
|
||||
void Suggestions::startSlideAnimation(Tab was, Tab now) {
|
||||
if (!_slideAnimation.animating()) {
|
||||
_slideLeft = (was == Tab::Chats || now == Tab::Chats)
|
||||
? Ui::GrabWidget(_chatsScroll.get())
|
||||
: Ui::GrabWidget(_channelsScroll.get());
|
||||
_slideLeftTop = (was == Tab::Chats || now == Tab::Chats)
|
||||
? _chatsScroll->y()
|
||||
: _channelsScroll->y();
|
||||
_slideRight = (was == Tab::Apps || now == Tab::Apps)
|
||||
? Ui::GrabWidget(_appsScroll.get())
|
||||
: Ui::GrabWidget(_channelsScroll.get());
|
||||
_slideRightTop = (was == Tab::Apps || now == Tab::Apps)
|
||||
? _appsScroll->y()
|
||||
: _channelsScroll->y();
|
||||
_chatsScroll->hide();
|
||||
_channelsScroll->hide();
|
||||
_appsScroll->hide();
|
||||
void Suggestions::ensureContent(Key key) {
|
||||
if (key.tab != Tab::Downloads && key.tab != Tab::Media) {
|
||||
return;
|
||||
}
|
||||
const auto from = (now > was) ? 0. : 1.;
|
||||
const auto to = (now > was) ? 1. : 0.;
|
||||
auto &list = _mediaLists[key];
|
||||
if (list.wrap) {
|
||||
return;
|
||||
}
|
||||
const auto self = _controller->session().user();
|
||||
const auto memento = (key.tab == Tab::Downloads)
|
||||
? Info::Downloads::Make(self)
|
||||
: std::make_shared<Info::Memento>(
|
||||
self,
|
||||
Info::Section(key.mediaType, Info::Section::Type::GlobalMedia));
|
||||
list.wrap = Ui::CreateChild<Info::WrapWidget>(
|
||||
this,
|
||||
_controller,
|
||||
Info::Wrap::Search,
|
||||
memento.get());
|
||||
list.wrap->show();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
void Suggestions::startSlideAnimation(Key was, Key now) {
|
||||
ensureContent(now);
|
||||
const auto wasIndex = ranges::find(_tabKeys, was);
|
||||
const auto nowIndex = ranges::find(_tabKeys, now);
|
||||
if (!_slideAnimation.animating()) {
|
||||
const auto find = [&](Key key) -> not_null<QWidget*> {
|
||||
switch (key.tab) {
|
||||
case Tab::Chats: return _chatsScroll.get();
|
||||
case Tab::Channels: return _channelsScroll.get();
|
||||
case Tab::Apps: return _appsScroll.get();
|
||||
}
|
||||
return _mediaLists[key].wrap;
|
||||
};
|
||||
auto left = find(was);
|
||||
auto right = find(now);
|
||||
if (wasIndex > nowIndex) {
|
||||
std::swap(left, right);
|
||||
}
|
||||
_slideLeft = Ui::GrabWidget(left);
|
||||
_slideLeftTop = left->y();
|
||||
_slideRight = Ui::GrabWidget(right);
|
||||
_slideRightTop = right->y();
|
||||
left->hide();
|
||||
right->hide();
|
||||
}
|
||||
const auto from = (nowIndex > wasIndex) ? 0. : 1.;
|
||||
const auto to = (nowIndex > wasIndex) ? 1. : 0.;
|
||||
_slideAnimation.start([=] {
|
||||
update();
|
||||
if (!_slideAnimation.animating() && !_shownAnimation.animating()) {
|
||||
@@ -1807,10 +1955,13 @@ void Suggestions::startShownAnimation(bool shown, Fn<void()> finish) {
|
||||
resize(now, height());
|
||||
}
|
||||
}
|
||||
_tabs->hide();
|
||||
_tabsScroll->hide();
|
||||
_chatsScroll->hide();
|
||||
_channelsScroll->hide();
|
||||
_appsScroll->hide();
|
||||
for (const auto &[key, list] : _mediaLists) {
|
||||
list.wrap->hide();
|
||||
}
|
||||
_slideAnimation.stop();
|
||||
}
|
||||
|
||||
@@ -1822,11 +1973,14 @@ void Suggestions::finishShow() {
|
||||
_shownAnimation.stop();
|
||||
_cache = QPixmap();
|
||||
|
||||
_tabs->show();
|
||||
const auto tab = _tab.current();
|
||||
_chatsScroll->setVisible(tab == Tab::Chats);
|
||||
_channelsScroll->setVisible(tab == Tab::Channels);
|
||||
_appsScroll->setVisible(tab == Tab::Apps);
|
||||
_tabsScroll->show();
|
||||
const auto key = _key.current();
|
||||
_chatsScroll->setVisible(key == Key{ Tab::Chats });
|
||||
_channelsScroll->setVisible(key == Key{ Tab::Channels });
|
||||
_appsScroll->setVisible(key == Key{ Tab::Apps });
|
||||
for (const auto &[mediaKey, list] : _mediaLists) {
|
||||
list.wrap->setVisible(key == mediaKey);
|
||||
}
|
||||
}
|
||||
|
||||
float64 Suggestions::shownOpacity() const {
|
||||
@@ -1846,7 +2000,7 @@ void Suggestions::paintEvent(QPaintEvent *e) {
|
||||
p.drawPixmap(0, (opacity - 1.) * slide, _cache);
|
||||
} else if (!_slideLeft.isNull()) {
|
||||
const auto slide = st::topPeers.height + st::searchedBarHeight;
|
||||
const auto right = (_tab.current() == Tab::Channels);
|
||||
const auto right = (_key.current().tab == Tab::Channels);
|
||||
const auto progress = _slideAnimation.value(right ? 1. : 0.);
|
||||
p.setOpacity(1. - progress);
|
||||
p.drawPixmap(
|
||||
@@ -1862,18 +2016,39 @@ void Suggestions::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
|
||||
void Suggestions::resizeEvent(QResizeEvent *e) {
|
||||
const auto w = std::max(width(), st::columnMinimalWidthLeft);
|
||||
_tabs->resizeToWidth(w);
|
||||
const auto tabs = _tabs->height();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
_chatsScroll->setGeometry(0, tabs, w, height() - tabs);
|
||||
void Suggestions::updateControlsGeometry() {
|
||||
const auto w = std::max(width(), st::columnMinimalWidthLeft);
|
||||
_tabs->fitWidthToSections();
|
||||
|
||||
const auto tabs = _tabs->height();
|
||||
_tabsScroll->setGeometry(0, 0, w, tabs);
|
||||
|
||||
const auto content = QRect(0, tabs, w, height() - tabs);
|
||||
|
||||
_chatsScroll->setGeometry(content);
|
||||
_chatsContent->resizeToWidth(w);
|
||||
|
||||
_channelsScroll->setGeometry(0, tabs, w, height() - tabs);
|
||||
_channelsScroll->setGeometry(content);
|
||||
_channelsContent->resizeToWidth(w);
|
||||
|
||||
_appsScroll->setGeometry(0, tabs, w, height() - tabs);
|
||||
_appsScroll->setGeometry(content);
|
||||
_appsContent->resizeToWidth(w);
|
||||
|
||||
const auto expanding = false;
|
||||
for (const auto &[key, list] : _mediaLists) {
|
||||
const auto full = !list.wrap->scrollBottomSkip();
|
||||
const auto additionalScroll = (full ? st::boxRadius : 0);
|
||||
const auto height = content.height() - (full ? 0 : st::boxRadius);
|
||||
const auto wrapGeometry = QRect{ 0, tabs, w, height};
|
||||
list.wrap->updateGeometry(
|
||||
wrapGeometry,
|
||||
expanding,
|
||||
additionalScroll,
|
||||
content.height());
|
||||
}
|
||||
}
|
||||
|
||||
auto Suggestions::setupRecentPeers(RecentPeersList recentPeers)
|
||||
@@ -2027,8 +2202,8 @@ auto Suggestions::setupRecommendations() -> std::unique_ptr<ObjectList> {
|
||||
_persist = true;
|
||||
}, list->lifetime());
|
||||
|
||||
_tab.value() | rpl::filter(
|
||||
rpl::mappers::_1 == Tab::Channels
|
||||
_key.value() | rpl::filter(
|
||||
rpl::mappers::_1 == Key{ Tab::Channels }
|
||||
) | rpl::start_with_next([=] {
|
||||
controller->load();
|
||||
}, list->lifetime());
|
||||
@@ -2142,8 +2317,8 @@ auto Suggestions::setupPopularApps() -> std::unique_ptr<ObjectList> {
|
||||
_persist = true;
|
||||
}, list->lifetime());
|
||||
|
||||
_tab.value() | rpl::filter(
|
||||
rpl::mappers::_1 == Tab::Apps
|
||||
_key.value() | rpl::filter(
|
||||
rpl::mappers::_1 == Key{ Tab::Apps }
|
||||
) | rpl::start_with_next([=] {
|
||||
controller->load();
|
||||
}, list->lifetime());
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/timer.h"
|
||||
#include "dialogs/ui/top_peers_strip.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/rp_widget.h"
|
||||
@@ -18,12 +19,21 @@ namespace Data {
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info {
|
||||
class WrapWidget;
|
||||
} // namespace Info
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Storage {
|
||||
enum class SharedMediaType : signed char;
|
||||
} // namespace Storage
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
class ScrollArea;
|
||||
class ElasticScroll;
|
||||
class SettingsSlider;
|
||||
class VerticalLayout;
|
||||
@@ -55,6 +65,9 @@ public:
|
||||
void selectJump(Qt::Key direction, int pageSize = 0);
|
||||
void chooseRow();
|
||||
|
||||
bool consumeSearchQuery(const QString &query);
|
||||
[[nodiscard]] rpl::producer<> clearSearchQueryRequests() const;
|
||||
|
||||
[[nodiscard]] Data::Thread *updateFromParentDrag(QPoint globalPosition);
|
||||
void dragLeft();
|
||||
|
||||
@@ -96,10 +109,13 @@ public:
|
||||
class ObjectListController;
|
||||
|
||||
private:
|
||||
using MediaType = Storage::SharedMediaType;
|
||||
enum class Tab : uchar {
|
||||
Chats,
|
||||
Channels,
|
||||
Apps,
|
||||
Media,
|
||||
Downloads,
|
||||
};
|
||||
enum class JumpResult : uchar {
|
||||
NotApplied,
|
||||
@@ -107,6 +123,14 @@ private:
|
||||
AppliedAndOut,
|
||||
};
|
||||
|
||||
struct Key {
|
||||
Tab tab = Tab::Chats;
|
||||
MediaType mediaType = {};
|
||||
|
||||
friend inline auto operator<=>(Key, Key) = default;
|
||||
friend inline bool operator==(Key, Key) = default;
|
||||
};
|
||||
|
||||
struct ObjectList {
|
||||
not_null<Ui::SlideWrap<PeerListContent>*> wrap;
|
||||
rpl::variable<int> count;
|
||||
@@ -118,6 +142,11 @@ private:
|
||||
rpl::event_stream<not_null<PeerData*>> chosen;
|
||||
};
|
||||
|
||||
struct MediaList {
|
||||
Info::WrapWidget *wrap = nullptr;
|
||||
rpl::variable<int> count;
|
||||
};
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
@@ -160,17 +189,23 @@ private:
|
||||
SearchEmptyIcon icon,
|
||||
rpl::producer<QString> text);
|
||||
|
||||
void switchTab(Tab tab);
|
||||
void switchTab(Key key);
|
||||
void startShownAnimation(bool shown, Fn<void()> finish);
|
||||
void startSlideAnimation(Tab was, Tab now);
|
||||
void startSlideAnimation(Key was, Key now);
|
||||
void ensureContent(Key key);
|
||||
void finishShow();
|
||||
|
||||
void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback);
|
||||
void updateControlsGeometry();
|
||||
void applySearchQuery();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
const std::unique_ptr<Ui::SettingsSlider> _tabs;
|
||||
rpl::variable<Tab> _tab = Tab::Chats;
|
||||
const std::unique_ptr<Ui::ScrollArea> _tabsScroll;
|
||||
const not_null<Ui::SettingsSlider*> _tabs;
|
||||
Ui::Animations::Simple _tabsScrollAnimation;
|
||||
const std::vector<Key> _tabKeys;
|
||||
rpl::variable<Key> _key;
|
||||
|
||||
const std::unique_ptr<Ui::ElasticScroll> _chatsScroll;
|
||||
const not_null<Ui::VerticalLayout*> _chatsContent;
|
||||
@@ -200,6 +235,11 @@ private:
|
||||
const std::unique_ptr<ObjectList> _recentApps;
|
||||
const std::unique_ptr<ObjectList> _popularApps;
|
||||
|
||||
base::flat_map<Key, MediaList> _mediaLists;
|
||||
rpl::event_stream<> _clearSearchQueryRequests;
|
||||
QString _searchQuery;
|
||||
base::Timer _searchQueryTimer;
|
||||
|
||||
Ui::Animations::Simple _shownAnimation;
|
||||
Fn<void()> _showFinished;
|
||||
bool _hidden = false;
|
||||
|
||||
@@ -920,6 +920,7 @@ StoriesSlice ParseStoriesSlice(
|
||||
auto content = photo
|
||||
? ParsePhoto(*photo, suggestedPath)
|
||||
: Photo();
|
||||
content.spoilered = data.is_spoiler();
|
||||
media.content = content;
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
const auto document = data.vdocument();
|
||||
@@ -948,6 +949,7 @@ StoriesSlice ParseStoriesSlice(
|
||||
date,
|
||||
extension);
|
||||
content.thumb.file.suggestedPath = path + "_thumb.jpg";
|
||||
content.spoilered = data.is_spoiler();
|
||||
media.content = content;
|
||||
}, [&](const auto &data) {
|
||||
media.content = UnsupportedMedia();
|
||||
@@ -1329,6 +1331,7 @@ Media ParseMedia(
|
||||
+ "photos/"
|
||||
+ PreparePhotoFileName(++context.photos, date))
|
||||
: Photo();
|
||||
content.spoilered = data.is_spoiler();
|
||||
if (const auto ttl = data.vttl_seconds()) {
|
||||
result.ttl = ttl->v;
|
||||
content.image.file = File();
|
||||
@@ -1349,6 +1352,7 @@ Media ParseMedia(
|
||||
result.ttl = ttl->v;
|
||||
content.file = File();
|
||||
}
|
||||
content.spoilered = data.is_spoiler();
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageMediaWebPage &data) {
|
||||
// Ignore web pages.
|
||||
|
||||