Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c04f68f25c | ||
|
|
e4902efefc | ||
|
|
1267bcd255 | ||
|
|
0659ccc3f0 | ||
|
|
bd2ae03ab4 | ||
|
|
244696ae24 | ||
|
|
1438046dd4 | ||
|
|
5c62ba0835 | ||
|
|
20fadfef7f | ||
|
|
eed9541f9f | ||
|
|
1594afa389 | ||
|
|
9d74d93ed7 | ||
|
|
e4e2f47f8e | ||
|
|
be53bec9b7 | ||
|
|
bb32c546d4 | ||
|
|
ecb4ceec7b | ||
|
|
c080bd4c4d | ||
|
|
39780f49bf | ||
|
|
73349c3c89 | ||
|
|
42a70ff7d0 | ||
|
|
fa8262cbe9 | ||
|
|
7552328cdd | ||
|
|
60f4587d95 | ||
|
|
572c074c42 | ||
|
|
3cfbd6a93b | ||
|
|
d0911b6a45 | ||
|
|
06b85442f8 | ||
|
|
762592daff | ||
|
|
338122793c | ||
|
|
ef521624a0 | ||
|
|
341ab781b2 | ||
|
|
2fed657940 | ||
|
|
7bf78b3317 | ||
|
|
2d1fb0562d | ||
|
|
3d77bff0c9 | ||
|
|
4198203a7f | ||
|
|
21487641c1 | ||
|
|
c987872be8 | ||
|
|
07e367e1a0 | ||
|
|
db2e45c56e | ||
|
|
67bbdbfc70 | ||
|
|
83df3cba66 | ||
|
|
f4e2b4bcbd | ||
|
|
6c64c22f83 | ||
|
|
702aa944dd | ||
|
|
df45edd816 | ||
|
|
3f3143514e | ||
|
|
3e89910749 | ||
|
|
721a642a2f | ||
|
|
d16ccc9dc5 | ||
|
|
77e7796b3f | ||
|
|
983c949e8c | ||
|
|
e3f4f60e2d | ||
|
|
33aa904cb7 | ||
|
|
0248be5543 | ||
|
|
f7ca8212aa | ||
|
|
5eb59a1a43 | ||
|
|
7dd1e9bfbe | ||
|
|
067fd25a34 | ||
|
|
7cb26ba104 | ||
|
|
a4212cc865 | ||
|
|
0445f7d6e8 | ||
|
|
efe99b3f62 | ||
|
|
7f2c98f17a | ||
|
|
8f1d215851 | ||
|
|
013b58f6f6 | ||
|
|
9d3b3476c2 | ||
|
|
715874a98f | ||
|
|
d2109dd2cb | ||
|
|
ddaf11ed6a | ||
|
|
2b5f68003d | ||
|
|
1a759cc4e7 | ||
|
|
9e83562bf4 | ||
|
|
c03c19d26f | ||
|
|
ad9ebdf8e6 | ||
|
|
9c1701c62a | ||
|
|
edf6c42e9d | ||
|
|
f0f2a71a87 | ||
|
|
cf270bd9ce | ||
|
|
49223a4688 | ||
|
|
074bb1e66e |
@@ -986,6 +986,10 @@ PRIVATE
|
||||
info/profile/info_profile_values.h
|
||||
info/profile/info_profile_widget.cpp
|
||||
info/profile/info_profile_widget.h
|
||||
info/reactions_list/info_reactions_list_widget.cpp
|
||||
info/reactions_list/info_reactions_list_widget.h
|
||||
info/requests_list/info_requests_list_widget.cpp
|
||||
info/requests_list/info_requests_list_widget.h
|
||||
info/saved/info_saved_sublists_widget.cpp
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
@@ -1036,6 +1040,10 @@ PRIVATE
|
||||
info/info_wrap_widget.h
|
||||
inline_bots/bot_attach_web_view.cpp
|
||||
inline_bots/bot_attach_web_view.h
|
||||
inline_bots/inline_bot_confirm_prepared.cpp
|
||||
inline_bots/inline_bot_confirm_prepared.h
|
||||
inline_bots/inline_bot_downloads.cpp
|
||||
inline_bots/inline_bot_downloads.h
|
||||
inline_bots/inline_bot_layout_internal.cpp
|
||||
inline_bots/inline_bot_layout_internal.h
|
||||
inline_bots/inline_bot_layout_item.cpp
|
||||
|
||||
BIN
Telegram/Resources/animations/hello_status.tgs
Normal file
BIN
Telegram/Resources/animations/hello_status.tgs
Normal file
Binary file not shown.
@@ -682,6 +682,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_messages_privacy" = "Messages";
|
||||
"lng_settings_voices_privacy" = "Voice messages";
|
||||
"lng_settings_bio_privacy" = "Bio";
|
||||
"lng_settings_gifts_privacy" = "Gifts";
|
||||
"lng_settings_birthday_privacy" = "Date of Birth";
|
||||
"lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
|
||||
"lng_settings_privacy_premium_link" = "Telegram Premium";
|
||||
@@ -1162,19 +1163,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_blocked_list_subtitle#other" = "{count} blocked users";
|
||||
|
||||
"lng_edit_privacy_everyone" = "Everybody";
|
||||
"lng_edit_privacy_no_miniapps" = "Not Mini Apps";
|
||||
"lng_edit_privacy_contacts" = "My contacts";
|
||||
"lng_edit_privacy_close_friends" = "Close friends";
|
||||
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
|
||||
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
|
||||
"lng_edit_privacy_nobody" = "Nobody";
|
||||
"lng_edit_privacy_premium" = "Premium users";
|
||||
"lng_edit_privacy_miniapps" = "Mini Apps";
|
||||
"lng_edit_privacy_exceptions" = "Add exceptions";
|
||||
"lng_edit_privacy_user_types" = "User types";
|
||||
"lng_edit_privacy_users_and_groups" = "Users and groups";
|
||||
"lng_edit_privacy_premium_status" = "all Telegram Premium subscribers";
|
||||
"lng_edit_privacy_miniapps_status" = "web mini apps that you use";
|
||||
|
||||
"lng_edit_privacy_exceptions_count#one" = "{count} user";
|
||||
"lng_edit_privacy_exceptions_count#other" = "{count} users";
|
||||
"lng_edit_privacy_exceptions_premium_and" = "Premium & {users}";
|
||||
"lng_edit_privacy_exceptions_miniapps_and" = "Mini Apps & {users}";
|
||||
"lng_edit_privacy_exceptions_add" = "Add users";
|
||||
|
||||
"lng_edit_privacy_phone_number_title" = "Phone number privacy";
|
||||
@@ -1228,6 +1234,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edit_privacy_birthday_yet" = "You haven't entered your date of birth yet.\n{link}";
|
||||
"lng_edit_privacy_birthday_yet_link" = "Add my birthday >";
|
||||
|
||||
"lng_edit_privacy_gifts_title" = "Gifts";
|
||||
"lng_edit_privacy_gifts_header" = "Who can display gifts on my profile";
|
||||
"lng_edit_privacy_gifts_always_empty" = "Always allow";
|
||||
"lng_edit_privacy_gifts_never_empty" = "Never allow";
|
||||
"lng_edit_privacy_gifts_exceptions" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile.";
|
||||
"lng_edit_privacy_gifts_always_title" = "Always allow";
|
||||
"lng_edit_privacy_gifts_never_title" = "Never allow";
|
||||
|
||||
"lng_edit_privacy_calls_title" = "Calls";
|
||||
"lng_edit_privacy_calls_header" = "Who can call me";
|
||||
"lng_edit_privacy_calls_always_empty" = "Always allow";
|
||||
@@ -1461,11 +1475,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_open_app" = "Open App";
|
||||
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
|
||||
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
|
||||
"lng_profile_bot_permissions_title" = "Allow access to";
|
||||
"lng_profile_bot_emoji_status_access" = "Emoji Status";
|
||||
"lng_info_add_as_contact" = "Add to contacts";
|
||||
"lng_profile_shared_media" = "Shared media";
|
||||
"lng_profile_suggest_photo" = "Suggest Profile Photo";
|
||||
"lng_profile_suggest_photo_from_clipboard" = "Suggest From Clipboard";
|
||||
"lng_profile_set_photo_for" = "Set Profile Photo";
|
||||
"lng_profile_set_photo_for_from_clipboard" = "Set From Clipboard";
|
||||
"lng_profile_photo_reset" = "Reset to Original";
|
||||
"lng_profile_photo_from_clipboard" = "From clipboard";
|
||||
"lng_profile_suggest_sure" = "You can suggest {user} to set this photo for their Telegram profile.";
|
||||
"lng_profile_suggest_button" = "Suggest";
|
||||
"lng_profile_set_personal_sure" = "Only you will see this photo and it will replace any photo {user} sets for themselves.";
|
||||
@@ -1874,6 +1893,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
|
||||
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
|
||||
"lng_action_gift_sent_subtitle" = "Gift for {user}";
|
||||
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
|
||||
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
|
||||
@@ -2413,6 +2433,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_more_options" = "More Options";
|
||||
"lng_credits_balance_me" = "your balance";
|
||||
"lng_credits_buy_button" = "Buy More Stars";
|
||||
"lng_credits_gift_button" = "Gift Stars to Friends";
|
||||
"lng_credits_box_out_title" = "Confirm Your Purchase";
|
||||
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
|
||||
@@ -3412,6 +3435,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
|
||||
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_emoji_status_confirm" = "Confirm";
|
||||
"lng_bot_emoji_status_title" = "Set Emoji Status";
|
||||
"lng_bot_emoji_status_text" = "Do you want to set this emoji status suggested by {bot}?";
|
||||
"lng_bot_emoji_status_access_text" = "{bot} requests access to set your **emoji status**. You will be able to revoke this access in the profile page of {name}.";
|
||||
"lng_bot_emoji_status_access_allow" = "Allow";
|
||||
"lng_bot_share_prepared_title" = "Share Message";
|
||||
"lng_bot_share_prepared_about" = "{bot} mini app suggests you to send this message to a chat you select.";
|
||||
"lng_bot_share_prepared_button" = "Share With...";
|
||||
"lng_bot_download_file" = "Download File";
|
||||
"lng_bot_download_file_sure" = "{bot} suggests you download the following file:";
|
||||
"lng_bot_download_file_button" = "Download";
|
||||
"lng_bot_download_starting" = "Starting...";
|
||||
"lng_bot_download_failed" = "Failed. {retry}";
|
||||
"lng_bot_download_retry" = "Retry";
|
||||
|
||||
"lng_bot_status_users#one" = "{count} monthly user";
|
||||
"lng_bot_status_users#other" = "{count} monthly users";
|
||||
@@ -4356,7 +4393,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_rights_edit_admin_rank_about" = "A title that members will see instead of '{title}'.";
|
||||
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with equal or fewer rights.";
|
||||
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
|
||||
"lng_rights_about_by" = "This admin promoted by {user} at {date}.";
|
||||
"lng_rights_about_by" = "This admin promoted by {user} on {date}.";
|
||||
|
||||
"lng_rights_about_admin_cant_edit" = "You can't edit the rights of this admin.";
|
||||
"lng_rights_about_restriction_cant_edit" = "You cannot change the restrictions for this user.";
|
||||
@@ -4435,8 +4472,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_rights_chat_files" = "Files";
|
||||
"lng_rights_chat_voice_messages" = "Voice messages";
|
||||
"lng_rights_chat_video_messages" = "Video messages";
|
||||
"lng_rights_chat_restricted_by" = "Restricted by {user} at {date}.";
|
||||
"lng_rights_chat_banned_by" = "Banned by {user} at {date}.";
|
||||
"lng_rights_chat_restricted_by" = "Restricted by {user} on {date}.";
|
||||
"lng_rights_chat_banned_by" = "Banned by {user} on {date}.";
|
||||
"lng_rights_chat_banned_until_header" = "Restricted until";
|
||||
"lng_rights_chat_banned_forever" = "Forever";
|
||||
"lng_rights_chat_banned_day#one" = "For {count} day";
|
||||
@@ -5045,6 +5082,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_outdated_now" = "So Telegram Desktop can update to newer versions.";
|
||||
|
||||
"lng_filters_all" = "All chats";
|
||||
"lng_filters_all_short" = "All";
|
||||
"lng_filters_setup" = "Edit";
|
||||
"lng_filters_title" = "Folders";
|
||||
"lng_filters_subtitle" = "My folders";
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
||||
<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="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
||||
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.7.3.0" />
|
||||
Version="5.8.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,7,3,0
|
||||
PRODUCTVERSION 5,7,3,0
|
||||
FILEVERSION 5,8,2,0
|
||||
PRODUCTVERSION 5,8,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.7.3.0"
|
||||
VALUE "FileVersion", "5.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.7.3.0"
|
||||
VALUE "ProductVersion", "5.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,7,3,0
|
||||
PRODUCTVERSION 5,7,3,0
|
||||
FILEVERSION 5,8,2,0
|
||||
PRODUCTVERSION 5,8,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.7.3.0"
|
||||
VALUE "FileVersion", "5.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.7.3.0"
|
||||
VALUE "ProductVersion", "5.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -117,11 +117,12 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.convertStars = int(stargift
|
||||
.starsConverted = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
: 0),
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.stargift = stargift.has_value(),
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
@@ -174,7 +175,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.balance = status.data().vbalance().v,
|
||||
.subscriptionsMissingBalance
|
||||
= status.data().vsubscriptions_missing_balance().value_or_empty(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value()
|
||||
&& !status.data().vsubscriptions_next_offset().has_value(),
|
||||
.token = qs(status.data().vnext_offset().value_or_empty()),
|
||||
.tokenSubscriptions = qs(
|
||||
status.data().vsubscriptions_next_offset().value_or_empty()),
|
||||
|
||||
@@ -40,6 +40,7 @@ void HandleWithdrawalButton(
|
||||
std::shared_ptr<Ui::Show> show) {
|
||||
Expects(receiver.currencyReceiver
|
||||
|| (receiver.creditsReceiver && receiver.creditsAmount));
|
||||
|
||||
struct State {
|
||||
rpl::lifetime lifetime;
|
||||
bool loading = false;
|
||||
@@ -58,8 +59,7 @@ void HandleWithdrawalButton(
|
||||
const auto processOut = [=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
}
|
||||
if (peer && !receiver.creditsAmount()) {
|
||||
} else if (peer && !receiver.creditsAmount()) {
|
||||
return;
|
||||
}
|
||||
state->loading = true;
|
||||
|
||||
@@ -772,12 +772,13 @@ std::optional<StarGift> FromTL(
|
||||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.convertStars = int64(data.vconvert_stars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
.birthday = data.is_birthday(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -800,7 +801,7 @@ std::optional<UserStarGift> FromTL(
|
||||
data.vmessage()->data().ventities().v),
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.convertStars = int64(data.vconvert_stars().value_or_empty()),
|
||||
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
|
||||
@@ -76,12 +76,13 @@ struct GiftOptionData {
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 0;
|
||||
int64 convertStars = 0;
|
||||
int64 starsConverted = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
bool birthday = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarGift &,
|
||||
@@ -91,7 +92,7 @@ struct StarGift {
|
||||
struct UserStarGift {
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 convertStars = 0;
|
||||
int64 starsConverted = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
|
||||
@@ -69,6 +69,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
|
||||
if (rule.always.premiums && (rule.option != Option::Everyone)) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowPremium());
|
||||
}
|
||||
if (rule.always.miniapps && (rule.option != Option::Everyone)) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowBots());
|
||||
}
|
||||
}
|
||||
if (!rule.ignoreNever) {
|
||||
const auto users = collectInputUsers(rule.never);
|
||||
@@ -83,6 +86,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
|
||||
MTP_inputPrivacyValueDisallowChatParticipants(
|
||||
MTP_vector<MTPlong>(chats)));
|
||||
}
|
||||
if (rule.never.miniapps && (rule.option != Option::Nobody)) {
|
||||
result.push_back(MTP_inputPrivacyValueDisallowBots());
|
||||
}
|
||||
}
|
||||
result.push_back([&] {
|
||||
switch (rule.option) {
|
||||
@@ -124,6 +130,10 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) {
|
||||
setOption(Option::CloseFriends);
|
||||
}, [&](const MTPDprivacyValueAllowPremium &) {
|
||||
result.always.premiums = true;
|
||||
}, [&](const MTPDprivacyValueAllowBots &) {
|
||||
result.always.miniapps = true;
|
||||
}, [&](const MTPDprivacyValueDisallowBots &) {
|
||||
result.never.miniapps = true;
|
||||
}, [&](const MTPDprivacyValueAllowUsers &data) {
|
||||
const auto &users = data.vusers().v;
|
||||
always.reserve(always.size() + users.size());
|
||||
@@ -199,6 +209,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
|
||||
case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages();
|
||||
case Key::About: return MTP_inputPrivacyKeyAbout();
|
||||
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
||||
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
||||
}
|
||||
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
||||
}
|
||||
@@ -228,6 +239,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
|
||||
case mtpc_inputPrivacyKeyAbout: return Key::About;
|
||||
case mtpc_privacyKeyBirthday:
|
||||
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
|
||||
case mtpc_privacyKeyStarGiftsAutoSave:
|
||||
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
Voices,
|
||||
About,
|
||||
Birthday,
|
||||
GiftsAutoSave,
|
||||
};
|
||||
enum class Option {
|
||||
Everyone,
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
struct Exceptions {
|
||||
std::vector<not_null<PeerData*>> peers;
|
||||
bool premiums = false;
|
||||
bool miniapps = false;
|
||||
};
|
||||
struct Rule {
|
||||
Option option = Option::Everyone;
|
||||
|
||||
@@ -3947,7 +3947,8 @@ void ApiWrap::sendInlineResult(
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
std::optional<MsgId> localMessageId) {
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done) {
|
||||
sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
@@ -4027,11 +4028,17 @@ void ApiWrap::sendInlineResult(
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(true);
|
||||
}
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(false);
|
||||
}
|
||||
});
|
||||
finishForwarding(action);
|
||||
}
|
||||
|
||||
@@ -361,7 +361,8 @@ public:
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
std::optional<MsgId> localMessageId);
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
void sendMessageFail(
|
||||
const MTP::Error &error,
|
||||
not_null<PeerData*> peer,
|
||||
|
||||
@@ -96,6 +96,9 @@ void ChangeFilterById(
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: failed to %1 a dialog to a folder. %2")
|
||||
.arg(add ? u"add"_q : u"remove"_q)
|
||||
.arg(error.type()));
|
||||
// Revert filter on fail.
|
||||
history->owner().chatsFilters().set(was);
|
||||
}).send();
|
||||
|
||||
@@ -36,13 +36,63 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
||||
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
||||
|
||||
using Exceptions = Api::UserPrivacy::Exceptions;
|
||||
|
||||
enum class SpecialRowType {
|
||||
Premiums,
|
||||
MiniApps,
|
||||
};
|
||||
|
||||
[[nodiscard]] PaintRoundImageCallback GeneratePremiumsUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(x, y),
|
||||
QPointF(x + size, y + size));
|
||||
gradient.setStops(Ui::Premium::ButtonGradientStops());
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] PaintRoundImageCallback GenerateMiniAppsUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
const auto &color1 = st::historyPeer6UserpicBg;
|
||||
const auto &color2 = st::historyPeer6UserpicBg2;
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto gradient = QLinearGradient(x, y, x, y + size);
|
||||
gradient.setStops({ { 0., color1->c }, { 1., color2->c } });
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::windowFilterTypeBots.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
void CreateRadiobuttonLock(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
const style::Checkbox &st) {
|
||||
@@ -102,7 +152,7 @@ public:
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const Exceptions &selected,
|
||||
bool allowChoosePremiums);
|
||||
std::optional<SpecialRowType> allowChooseSpecial);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
@@ -110,18 +160,20 @@ public:
|
||||
bool handleDeselectForeignRow(PeerListRowId itemId) override;
|
||||
|
||||
[[nodiscard]] bool premiumsSelected() const;
|
||||
[[nodiscard]] bool miniAppsSelected() const;
|
||||
|
||||
protected:
|
||||
void prepareViewHook() override;
|
||||
std::unique_ptr<Row> createRow(not_null<History*> history) override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> preparePremiumsRowList();
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> prepareSpecialRowList(
|
||||
SpecialRowType type);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::producer<QString> _title;
|
||||
Exceptions _selected;
|
||||
bool _allowChoosePremiums = false;
|
||||
std::optional<SpecialRowType> _allowChooseSpecial;
|
||||
|
||||
PeerListContentDelegate *_typesDelegate = nullptr;
|
||||
Fn<void(PeerListRowId)> _deselectOption;
|
||||
@@ -133,9 +185,9 @@ struct RowSelectionChange {
|
||||
bool checked = false;
|
||||
};
|
||||
|
||||
class PremiumsRow final : public PeerListRow {
|
||||
class SpecialRow final : public PeerListRow {
|
||||
public:
|
||||
PremiumsRow();
|
||||
explicit SpecialRow(SpecialRowType type);
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
@@ -147,66 +199,61 @@ public:
|
||||
|
||||
class TypesController final : public PeerListController {
|
||||
public:
|
||||
TypesController(not_null<Main::Session*> session, bool premiums);
|
||||
TypesController(not_null<Main::Session*> session, SpecialRowType type);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] bool premiumsSelected() const;
|
||||
[[nodiscard]] rpl::producer<bool> premiumsChanges() const;
|
||||
[[nodiscard]] bool specialSelected() const;
|
||||
[[nodiscard]] rpl::producer<bool> specialChanges() const;
|
||||
[[nodiscard]] auto rowSelectionChanges() const
|
||||
-> rpl::producer<RowSelectionChange>;
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
const SpecialRowType _type;
|
||||
|
||||
rpl::event_stream<> _selectionChanged;
|
||||
rpl::event_stream<RowSelectionChange> _rowSelectionChanges;
|
||||
|
||||
};
|
||||
|
||||
PremiumsRow::PremiumsRow() : PeerListRow(kPremiumsRowId) {
|
||||
setCustomStatus(tr::lng_edit_privacy_premium_status(tr::now));
|
||||
SpecialRow::SpecialRow(SpecialRowType type)
|
||||
: PeerListRow((type == SpecialRowType::Premiums)
|
||||
? kPremiumsRowId
|
||||
: kMiniAppsRowId) {
|
||||
setCustomStatus((id() == kPremiumsRowId)
|
||||
? tr::lng_edit_privacy_premium_status(tr::now)
|
||||
: tr::lng_edit_privacy_miniapps_status(tr::now));
|
||||
}
|
||||
|
||||
QString PremiumsRow::generateName() {
|
||||
return tr::lng_edit_privacy_premium(tr::now);
|
||||
QString SpecialRow::generateName() {
|
||||
return (id() == kPremiumsRowId)
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_miniapps(tr::now);
|
||||
}
|
||||
|
||||
QString PremiumsRow::generateShortName() {
|
||||
QString SpecialRow::generateShortName() {
|
||||
return generateName();
|
||||
}
|
||||
|
||||
PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
|
||||
PaintRoundImageCallback SpecialRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(x, y),
|
||||
QPointF(x + size, y + size));
|
||||
gradient.setStops(Ui::Premium::ButtonGradientStops());
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
return (id() == kPremiumsRowId)
|
||||
? GeneratePremiumsUserpicCallback(forceRound)
|
||||
: GenerateMiniAppsUserpicCallback(forceRound);
|
||||
}
|
||||
|
||||
bool PremiumsRow::useForumLikeUserpic() const {
|
||||
bool SpecialRow::useForumLikeUserpic() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
TypesController::TypesController(
|
||||
not_null<Main::Session*> session,
|
||||
bool premiums)
|
||||
: _session(session) {
|
||||
SpecialRowType type)
|
||||
: _session(session)
|
||||
, _type(type) {
|
||||
}
|
||||
|
||||
Main::Session &TypesController::session() const {
|
||||
@@ -214,12 +261,15 @@ Main::Session &TypesController::session() const {
|
||||
}
|
||||
|
||||
void TypesController::prepare() {
|
||||
delegate()->peerListAppendRow(std::make_unique<PremiumsRow>());
|
||||
delegate()->peerListAppendRow(std::make_unique<SpecialRow>(_type));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
bool TypesController::premiumsSelected() const {
|
||||
const auto row = delegate()->peerListFindRow(kPremiumsRowId);
|
||||
bool TypesController::specialSelected() const {
|
||||
const auto premiums = (_type == SpecialRowType::Premiums);
|
||||
const auto row = delegate()->peerListFindRow(premiums
|
||||
? kPremiumsRowId
|
||||
: kMiniAppsRowId);
|
||||
Assert(row != nullptr);
|
||||
|
||||
return row->checked();
|
||||
@@ -231,10 +281,10 @@ void TypesController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_rowSelectionChanges.fire({ row, checked });
|
||||
}
|
||||
|
||||
rpl::producer<bool> TypesController::premiumsChanges() const {
|
||||
rpl::producer<bool> TypesController::specialChanges() const {
|
||||
return _rowSelectionChanges.events(
|
||||
) | rpl::map([=] {
|
||||
return premiumsSelected();
|
||||
return specialSelected();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -247,12 +297,12 @@ PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const Exceptions &selected,
|
||||
bool allowChoosePremiums)
|
||||
std::optional<SpecialRowType> allowChooseSpecial)
|
||||
: ChatsListBoxController(session)
|
||||
, _session(session)
|
||||
, _title(std::move(title))
|
||||
, _selected(selected)
|
||||
, _allowChoosePremiums(allowChoosePremiums) {
|
||||
, _allowChooseSpecial(allowChooseSpecial) {
|
||||
}
|
||||
|
||||
Main::Session &PrivacyExceptionsBoxController::session() const {
|
||||
@@ -261,14 +311,18 @@ Main::Session &PrivacyExceptionsBoxController::session() const {
|
||||
|
||||
void PrivacyExceptionsBoxController::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(std::move(_title));
|
||||
if (_allowChoosePremiums || _selected.premiums) {
|
||||
delegate()->peerListSetAboveWidget(preparePremiumsRowList());
|
||||
if (_allowChooseSpecial || _selected.premiums || _selected.miniapps) {
|
||||
delegate()->peerListSetAboveWidget(prepareSpecialRowList(
|
||||
_allowChooseSpecial.value_or(_selected.premiums
|
||||
? SpecialRowType::Premiums
|
||||
: SpecialRowType::MiniApps)));
|
||||
}
|
||||
delegate()->peerListAddSelectedPeers(_selected.peers);
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::isForeignRow(PeerListRowId itemId) {
|
||||
return (itemId == kPremiumsRowId);
|
||||
return (itemId == kPremiumsRowId)
|
||||
|| (itemId == kMiniAppsRowId);
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
|
||||
@@ -280,7 +334,8 @@ bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
|
||||
return false;
|
||||
}
|
||||
|
||||
auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
auto PrivacyExceptionsBoxController::prepareSpecialRowList(
|
||||
SpecialRowType type)
|
||||
-> object_ptr<Ui::RpWidget> {
|
||||
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
const auto container = result.data();
|
||||
@@ -291,30 +346,39 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
_typesDelegate = lifetime.make_state<PeerListContentDelegateSimple>();
|
||||
const auto controller = lifetime.make_state<TypesController>(
|
||||
&session(),
|
||||
_selected.premiums);
|
||||
type);
|
||||
const auto content = result->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
_typesDelegate->setContent(content);
|
||||
controller->setDelegate(_typesDelegate);
|
||||
|
||||
const auto selectType = [&](PeerListRowId id) {
|
||||
const auto row = _typesDelegate->peerListFindRow(id);
|
||||
if (row) {
|
||||
content->changeCheckState(row, true, anim::type::instant);
|
||||
this->delegate()->peerListSetForeignRowChecked(
|
||||
row,
|
||||
true,
|
||||
anim::type::instant);
|
||||
}
|
||||
};
|
||||
if (_selected.premiums) {
|
||||
const auto row = _typesDelegate->peerListFindRow(kPremiumsRowId);
|
||||
Assert(row != nullptr);
|
||||
|
||||
content->changeCheckState(row, true, anim::type::instant);
|
||||
this->delegate()->peerListSetForeignRowChecked(
|
||||
row,
|
||||
true,
|
||||
anim::type::instant);
|
||||
selectType(kPremiumsRowId);
|
||||
} else if (_selected.miniapps) {
|
||||
selectType(kMiniAppsRowId);
|
||||
}
|
||||
container->add(CreatePeerListSectionSubtitle(
|
||||
container,
|
||||
tr::lng_edit_privacy_users_and_groups()));
|
||||
|
||||
controller->premiumsChanges(
|
||||
) | rpl::start_with_next([=](bool premiums) {
|
||||
_selected.premiums = premiums;
|
||||
controller->specialChanges(
|
||||
) | rpl::start_with_next([=](bool chosen) {
|
||||
if (type == SpecialRowType::Premiums) {
|
||||
_selected.premiums = chosen;
|
||||
} else {
|
||||
_selected.miniapps = chosen;
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
controller->rowSelectionChanges(
|
||||
@@ -329,6 +393,8 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
|
||||
if (itemId == kPremiumsRowId) {
|
||||
_selected.premiums = false;
|
||||
} else if (itemId == kMiniAppsRowId) {
|
||||
_selected.miniapps = false;
|
||||
}
|
||||
_typesDelegate->peerListSetRowChecked(row, false);
|
||||
}
|
||||
@@ -337,10 +403,14 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool PrivacyExceptionsBoxController::premiumsSelected() const {
|
||||
bool PrivacyExceptionsBoxController::premiumsSelected() const {
|
||||
return _selected.premiums;
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::miniAppsSelected() const {
|
||||
return _selected.miniapps;
|
||||
}
|
||||
|
||||
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto peer = row->peer();
|
||||
|
||||
@@ -412,6 +482,11 @@ EditPrivacyBox::EditPrivacyBox(
|
||||
// If we switch from Everyone to Contacts or Nobody suggest Premiums.
|
||||
_value.always.premiums = true;
|
||||
}
|
||||
if (_controller->allowMiniAppsToggle(Exception::Always)
|
||||
&& _value.option == Option::Everyone) {
|
||||
// If we switch from Everyone to Contacts or Nobody suggest MiniApps.
|
||||
_value.always.miniapps = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EditPrivacyBox::prepare() {
|
||||
@@ -427,12 +502,18 @@ void EditPrivacyBox::editExceptions(
|
||||
&_window->session(),
|
||||
_controller->exceptionBoxTitle(exception),
|
||||
exceptions(exception),
|
||||
_controller->allowPremiumsToggle(exception));
|
||||
(_controller->allowPremiumsToggle(exception)
|
||||
? SpecialRowType::Premiums
|
||||
: _controller->allowMiniAppsToggle(exception)
|
||||
? SpecialRowType::MiniApps
|
||||
: std::optional<SpecialRowType>()));
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
|
||||
exceptions(exception).peers = box->collectSelectedRows();
|
||||
exceptions(exception).premiums = controller->premiumsSelected();
|
||||
auto &setTo = exceptions(exception);
|
||||
setTo.peers = box->collectSelectedRows();
|
||||
setTo.premiums = controller->premiumsSelected();
|
||||
setTo.miniapps = controller->miniAppsSelected();
|
||||
const auto type = [&] {
|
||||
switch (exception) {
|
||||
case Exception::Always: return Exception::Never;
|
||||
@@ -440,11 +521,17 @@ void EditPrivacyBox::editExceptions(
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}();
|
||||
auto &removeFrom = exceptions(type).peers;
|
||||
auto &removeFrom = exceptions(type);
|
||||
for (const auto peer : exceptions(exception).peers) {
|
||||
removeFrom.erase(
|
||||
ranges::remove(removeFrom, peer),
|
||||
end(removeFrom));
|
||||
removeFrom.peers.erase(
|
||||
ranges::remove(removeFrom.peers, peer),
|
||||
end(removeFrom.peers));
|
||||
}
|
||||
if (setTo.premiums) {
|
||||
removeFrom.premiums = false;
|
||||
}
|
||||
if (setTo.miniapps) {
|
||||
removeFrom.miniapps = false;
|
||||
}
|
||||
done();
|
||||
box->closeBox();
|
||||
@@ -566,14 +653,21 @@ void EditPrivacyBox::setupContent() {
|
||||
lt_count,
|
||||
count)
|
||||
: tr::lng_edit_privacy_exceptions_add(tr::now);
|
||||
return !value.premiums
|
||||
? users
|
||||
: !count
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_premium_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users);
|
||||
return value.premiums
|
||||
? (!count
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_premium_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users))
|
||||
: value.miniapps
|
||||
? (!count
|
||||
? tr::lng_edit_privacy_miniapps(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_miniapps_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users))
|
||||
: users;
|
||||
});
|
||||
_controller->handleExceptionsChange(
|
||||
exception,
|
||||
|
||||
@@ -61,6 +61,10 @@ public:
|
||||
Exception exception) const {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] virtual bool allowMiniAppsToggle(
|
||||
Exception exception) const {
|
||||
return false;
|
||||
}
|
||||
virtual void handleExceptionsChange(
|
||||
Exception exception,
|
||||
rpl::producer<int> value) {
|
||||
|
||||
@@ -123,7 +123,9 @@ void GiftCreditsBox(
|
||||
box->verticalLayout(),
|
||||
peer,
|
||||
0,
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); });
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); },
|
||||
tr::lng_credits_summary_options_subtitle(),
|
||||
{});
|
||||
|
||||
box->setPinnedToBottomContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
||||
@@ -266,7 +266,7 @@ object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
|
||||
raw,
|
||||
tr::lng_gift_sell_small(
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.convertStars * 1.)),
|
||||
rpl::single(entry.starsConverted * 1.)),
|
||||
st::starGiftSmallButton)
|
||||
: nullptr;
|
||||
if (convert) {
|
||||
@@ -1044,7 +1044,8 @@ void AddStarGiftTable(
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
const auto withSendButton = entry.in;
|
||||
const auto user = session->data().peer(peerId)->asUser();
|
||||
const auto withSendButton = entry.in && user && !user->isBot();
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
@@ -1144,12 +1145,17 @@ void AddCreditsHistoryEntryTable(
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto actorId = PeerId(entry.bareActorId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
if (actorId || peerId) {
|
||||
auto text = entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
AddTableRow(
|
||||
table,
|
||||
std::move(text),
|
||||
controller,
|
||||
actorId ? actorId : peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto peer = session->data().peer(peerId);
|
||||
@@ -1242,12 +1248,24 @@ void AddCreditsHistoryEntryTable(
|
||||
}
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
const auto oneLine = entry.id.length() <= kOneLineCount;
|
||||
constexpr auto kOneLineCount = 22;
|
||||
const auto oneLine = entry.id.size() <= kOneLineCount;
|
||||
auto multiLine = QString();
|
||||
if (!oneLine) {
|
||||
for (auto i = 0; i < entry.id.size(); ++i) {
|
||||
multiLine.append(entry.id[i]);
|
||||
if ((i + 1) % kOneLineCount == 0) {
|
||||
multiLine.append('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(
|
||||
Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
|
||||
Ui::Text::Wrapped(
|
||||
{ oneLine ? entry.id : std::move(multiLine) },
|
||||
EntityType::Code,
|
||||
{})),
|
||||
oneLine
|
||||
? st::giveawayGiftCodeValue
|
||||
: st::giveawayGiftCodeValueMultiline);
|
||||
|
||||
@@ -240,7 +240,8 @@ int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
|
||||
}
|
||||
|
||||
void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
|
||||
if (!_progress || true) {
|
||||
#if 0 // not used
|
||||
if (!_progress) {
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(this);
|
||||
@@ -254,6 +255,7 @@ void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
|
||||
st::proxyCheckingPosition.y() + bottom
|
||||
},
|
||||
width());
|
||||
#endif
|
||||
}
|
||||
|
||||
QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {
|
||||
|
||||
@@ -219,12 +219,15 @@ 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 (_specialTabsMode.searchIsActive) {
|
||||
_specialTabsMode.topSkip = _addedTopScrollSkip;
|
||||
setAddedTopScrollSkip(0);
|
||||
} else {
|
||||
setAddedTopScrollSkip(_specialTabsMode.topSkip);
|
||||
if (was != _specialTabsMode.searchIsActive) {
|
||||
if (_specialTabsMode.searchIsActive) {
|
||||
_specialTabsMode.topSkip = _addedTopScrollSkip;
|
||||
setAddedTopScrollSkip(0);
|
||||
} else {
|
||||
setAddedTopScrollSkip(_specialTabsMode.topSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1065,6 +1065,11 @@ std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ChooseTopicBoxController::MakeRow(
|
||||
not_null<Data::ForumTopic*> topic) {
|
||||
return std::make_unique<Row>(topic);
|
||||
}
|
||||
|
||||
auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
|
||||
-> std::unique_ptr<Row> {
|
||||
const auto skip = _filter && !_filter(topic);
|
||||
|
||||
@@ -335,6 +335,9 @@ public:
|
||||
void loadMoreRows() override;
|
||||
std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override;
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<PeerListRow> MakeRow(
|
||||
not_null<Data::ForumTopic*> topic);
|
||||
|
||||
private:
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
|
||||
@@ -30,10 +30,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_changes.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "ui/effects/outline_segments.h"
|
||||
#include "ui/widgets/menu/menu_multiline_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "history/history.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace {
|
||||
@@ -1645,6 +1648,51 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
const auto addToEnd = gsl::finally([&] {
|
||||
const auto addInfoAction = [&](
|
||||
not_null<PeerData*> by,
|
||||
tr::phrase<lngtag_user, lngtag_date> phrase,
|
||||
TimeId since) {
|
||||
auto text = phrase(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(by->name()),
|
||||
lt_date,
|
||||
Ui::Text::Bold(
|
||||
langDateTimeFull(base::unixtime::parse(since))),
|
||||
Ui::Text::WithEntities);
|
||||
auto button = base::make_unique_q<Ui::Menu::MultilineAction>(
|
||||
result->menu(),
|
||||
result->st().menu,
|
||||
st::historyHasCustomEmoji,
|
||||
st::historyHasCustomEmojiPosition,
|
||||
std::move(text));
|
||||
if (const auto n = _navigation) {
|
||||
button->setClickedCallback([=] {
|
||||
n->parentController()->show(PrepareShortInfoBox(by, n));
|
||||
});
|
||||
}
|
||||
result->addSeparator();
|
||||
result->addAction(std::move(button));
|
||||
};
|
||||
|
||||
if (const auto by = _additional.restrictedBy(participant)) {
|
||||
if (const auto since = _additional.restrictedSince(participant)) {
|
||||
addInfoAction(
|
||||
by,
|
||||
_additional.isKicked(participant)
|
||||
? tr::lng_rights_chat_banned_by
|
||||
: tr::lng_rights_chat_restricted_by,
|
||||
since);
|
||||
}
|
||||
} else if (user) {
|
||||
if (const auto by = _additional.adminPromotedBy(user)) {
|
||||
if (const auto since = _additional.adminPromotedSince(user)) {
|
||||
addInfoAction(by, tr::lng_rights_about_by, since);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (_navigation) {
|
||||
result->addAction(
|
||||
(participant->isUser()
|
||||
@@ -1652,39 +1700,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
: participant->isBroadcast()
|
||||
? tr::lng_context_view_channel
|
||||
: tr::lng_context_view_group)(tr::now),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->showPeerInfo(participant); }),
|
||||
crl::guard(this, [=, this] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(participant, _navigation));
|
||||
}),
|
||||
(participant->isUser()
|
||||
? &st::menuIconProfile
|
||||
: &st::menuIconInfo));
|
||||
}
|
||||
if (const auto by = _additional.restrictedBy(participant)) {
|
||||
result->addAction(
|
||||
(_role == Role::Kicked
|
||||
? tr::lng_channel_banned_status_removed_by
|
||||
: tr::lng_channel_banned_status_restricted_by)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
by->name()),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(by, _navigation));
|
||||
}),
|
||||
&st::menuIconAdmin);
|
||||
} else if (user) {
|
||||
if (const auto by = _additional.adminPromotedBy(user)) {
|
||||
result->addAction(
|
||||
tr::lng_channel_admin_status_promoted_by(
|
||||
tr::now,
|
||||
lt_user,
|
||||
by->name()),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(by, _navigation));
|
||||
}),
|
||||
&st::menuIconAdmin);
|
||||
}
|
||||
}
|
||||
if (_role == Role::Kicked) {
|
||||
if (_peer->isMegagroup()
|
||||
&& _additional.canRestrictParticipant(participant)) {
|
||||
|
||||
@@ -12,12 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -51,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h" // st::boxDividerLabel.
|
||||
@@ -264,8 +267,9 @@ private:
|
||||
class SingleRowController final : public PeerListController {
|
||||
public:
|
||||
SingleRowController(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status);
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked);
|
||||
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
@@ -273,8 +277,10 @@ public:
|
||||
Main::Session &session() const override;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<Main::Session*> _session;
|
||||
const base::weak_ptr<Data::Thread> _thread;
|
||||
rpl::producer<QString> _status;
|
||||
Fn<void()> _clicked;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
@@ -1144,36 +1150,59 @@ int Controller::descriptionTopSkipMin() const {
|
||||
}
|
||||
|
||||
SingleRowController::SingleRowController(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status)
|
||||
: _peer(peer)
|
||||
, _status(std::move(status)) {
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked)
|
||||
: _session(&thread->session())
|
||||
, _thread(thread)
|
||||
, _status(std::move(status))
|
||||
, _clicked(std::move(clicked)) {
|
||||
}
|
||||
|
||||
void SingleRowController::prepare() {
|
||||
auto row = std::make_unique<PeerListRow>(_peer);
|
||||
|
||||
const auto strong = _thread.get();
|
||||
if (!strong) {
|
||||
return;
|
||||
}
|
||||
const auto topic = strong->asTopic();
|
||||
auto row = topic
|
||||
? ChooseTopicBoxController::MakeRow(topic)
|
||||
: std::make_unique<PeerListRow>(strong->peer());
|
||||
const auto raw = row.get();
|
||||
std::move(
|
||||
_status
|
||||
) | rpl::start_with_next([=](const QString &status) {
|
||||
raw->setCustomStatus(status);
|
||||
delegate()->peerListUpdateRow(raw);
|
||||
}, _lifetime);
|
||||
|
||||
if (_status) {
|
||||
std::move(
|
||||
_status
|
||||
) | rpl::start_with_next([=](const QString &status) {
|
||||
raw->setCustomStatus(status);
|
||||
delegate()->peerListUpdateRow(raw);
|
||||
}, _lifetime);
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListRefreshRows();
|
||||
|
||||
if (topic) {
|
||||
topic->destroyed() | rpl::start_with_next([=] {
|
||||
while (delegate()->peerListFullRowsCount()) {
|
||||
delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}, _lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
void SingleRowController::loadMoreRows() {
|
||||
}
|
||||
|
||||
void SingleRowController::rowClicked(not_null<PeerListRow*> row) {
|
||||
ShowPeerInfoSync(row->peer());
|
||||
if (const auto onstack = _clicked) {
|
||||
onstack();
|
||||
} else {
|
||||
ShowPeerInfoSync(row->peer());
|
||||
}
|
||||
}
|
||||
|
||||
Main::Session &SingleRowController::session() const {
|
||||
return _peer->session();
|
||||
return *_session;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1186,14 +1215,29 @@ bool IsExpiredLink(const Api::InviteLink &data, TimeId now) {
|
||||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status) {
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked) {
|
||||
AddSinglePeerRow(
|
||||
container,
|
||||
peer->owner().history(peer),
|
||||
std::move(status),
|
||||
std::move(clicked));
|
||||
}
|
||||
|
||||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked) {
|
||||
const auto delegate = container->lifetime().make_state<
|
||||
PeerListContentDelegateSimple
|
||||
>();
|
||||
const auto controller = container->lifetime().make_state<
|
||||
SingleRowController
|
||||
>(peer, std::move(status));
|
||||
controller->setStyleOverrides(&st::peerListSingleRow);
|
||||
>(thread, std::move(status), std::move(clicked));
|
||||
controller->setStyleOverrides(thread->asTopic()
|
||||
? &st::chooseTopicList
|
||||
: &st::peerListSingleRow);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
|
||||
@@ -16,6 +16,10 @@ namespace Api {
|
||||
struct InviteLink;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -31,7 +35,14 @@ class BoxContent;
|
||||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status);
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked = nullptr);
|
||||
|
||||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked = nullptr);
|
||||
|
||||
void AddPermanentLinkBlock(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
|
||||
@@ -7,27 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
|
||||
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
|
||||
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
|
||||
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/requests_list/info_requests_list_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
@@ -262,14 +265,10 @@ RequestsBoxController::~RequestsBoxController() = default;
|
||||
void RequestsBoxController::Start(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
auto controller = std::make_unique<RequestsBoxController>(
|
||||
navigation,
|
||||
peer->migrateToOrMe());
|
||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
};
|
||||
navigation->parentController()->show(
|
||||
Box<PeerListBox>(std::move(controller), initBox));
|
||||
navigation->showSection(
|
||||
std::make_shared<Info::Memento>(
|
||||
peer->migrateToOrMe(),
|
||||
Info::Section::Type::RequestsList));
|
||||
}
|
||||
|
||||
Main::Session &RequestsBoxController::session() const {
|
||||
@@ -289,6 +288,58 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createSearchRow(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> RequestsBoxController::createRestoredRow(
|
||||
not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
return createRow(user, _dates[user]);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto RequestsBoxController::saveState() const
|
||||
-> std::unique_ptr<PeerListState> {
|
||||
auto result = PeerListController::saveState();
|
||||
|
||||
auto my = std::make_unique<SavedState>();
|
||||
my->dates = _dates;
|
||||
my->offsetDate = _offsetDate;
|
||||
my->offsetUser = _offsetUser;
|
||||
my->allLoaded = _allLoaded;
|
||||
my->wasLoading = (_loadRequestId != 0);
|
||||
if (const auto search = searchController()) {
|
||||
my->searchState = search->saveState();
|
||||
}
|
||||
result->controllerState = std::move(my);
|
||||
return result;
|
||||
}
|
||||
|
||||
void RequestsBoxController::restoreState(
|
||||
std::unique_ptr<PeerListState> state) {
|
||||
auto typeErasedState = state
|
||||
? state->controllerState.get()
|
||||
: nullptr;
|
||||
if (const auto my = dynamic_cast<SavedState*>(typeErasedState)) {
|
||||
if (const auto requestId = base::take(_loadRequestId)) {
|
||||
_api.request(requestId).cancel();
|
||||
}
|
||||
_dates = std::move(my->dates);
|
||||
_offsetDate = my->offsetDate;
|
||||
_offsetUser = my->offsetUser;
|
||||
_allLoaded = my->allLoaded;
|
||||
if (const auto search = searchController()) {
|
||||
search->restoreState(std::move(my->searchState));
|
||||
}
|
||||
if (my->wasLoading) {
|
||||
loadMoreRows();
|
||||
}
|
||||
PeerListController::restoreState(std::move(state));
|
||||
if (delegate()->peerListFullRowsCount() || _allLoaded) {
|
||||
refreshDescription();
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RequestsBoxController::prepare() {
|
||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
||||
delegate()->peerListSetTitle(_peer->isBroadcast()
|
||||
@@ -356,9 +407,7 @@ void RequestsBoxController::refreshDescription() {
|
||||
}
|
||||
|
||||
void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_navigation->parentController()->show(PrepareShortInfoBox(
|
||||
row->peer(),
|
||||
_navigation));
|
||||
_navigation->showPeerInfo(row->peer());
|
||||
}
|
||||
|
||||
void RequestsBoxController::rowElementClicked(
|
||||
@@ -405,6 +454,7 @@ void RequestsBoxController::appendRow(
|
||||
not_null<UserData*> user,
|
||||
TimeId date) {
|
||||
if (!delegate()->peerListFindRow(user->id.value)) {
|
||||
_dates.emplace(user, date);
|
||||
if (auto row = createRow(user, date)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
setDescriptionText(QString());
|
||||
@@ -503,6 +553,7 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createRow(
|
||||
const auto search = static_cast<RequestsBoxSearchController*>(
|
||||
searchController());
|
||||
date = search->dateForUser(user);
|
||||
_dates.emplace(user, date);
|
||||
}
|
||||
return std::make_unique<Row>(_helper.get(), user, date);
|
||||
}
|
||||
@@ -574,6 +625,36 @@ TimeId RequestsBoxSearchController::dateForUser(not_null<UserData*> user) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto RequestsBoxSearchController::saveState() const
|
||||
-> std::unique_ptr<SavedStateBase> {
|
||||
auto result = std::make_unique<SavedState>();
|
||||
result->query = _query;
|
||||
result->offsetDate = _offsetDate;
|
||||
result->offsetUser = _offsetUser;
|
||||
result->allLoaded = _allLoaded;
|
||||
result->wasLoading = (_requestId != 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
void RequestsBoxSearchController::restoreState(
|
||||
std::unique_ptr<SavedStateBase> state) {
|
||||
if (auto my = dynamic_cast<SavedState*>(state.get())) {
|
||||
if (auto requestId = base::take(_requestId)) {
|
||||
_api.request(requestId).cancel();
|
||||
}
|
||||
_cache.clear();
|
||||
_queries.clear();
|
||||
|
||||
_allLoaded = my->allLoaded;
|
||||
_offsetDate = my->offsetDate;
|
||||
_offsetUser = my->offsetUser;
|
||||
_query = my->query;
|
||||
if (my->wasLoading) {
|
||||
searchOnServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RequestsBoxSearchController::searchInCache() {
|
||||
const auto i = _cache.find(_query);
|
||||
if (i != _cache.cend()) {
|
||||
|
||||
@@ -35,15 +35,32 @@ public:
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowElementClicked(not_null<PeerListRow*> row, int element) override;
|
||||
void rowElementClicked(
|
||||
not_null<PeerListRow*> row,
|
||||
int element) override;
|
||||
void loadMoreRows() override;
|
||||
|
||||
std::unique_ptr<PeerListRow> createSearchRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
std::unique_ptr<PeerListRow> createRestoredRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
|
||||
std::unique_ptr<PeerListState> saveState() const override;
|
||||
void restoreState(std::unique_ptr<PeerListState> state) override;
|
||||
|
||||
private:
|
||||
class RowHelper;
|
||||
|
||||
struct SavedState : SavedStateBase {
|
||||
using SearchStateBase = PeerListSearchController::SavedStateBase;
|
||||
std::unique_ptr<SearchStateBase> searchState;
|
||||
base::flat_map<not_null<UserData*>, TimeId> dates;
|
||||
TimeId offsetDate = 0;
|
||||
UserData *offsetUser = nullptr;
|
||||
bool allLoaded = false;
|
||||
bool wasLoading = false;
|
||||
};
|
||||
|
||||
static std::unique_ptr<PeerListSearchController> CreateSearchController(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
@@ -63,6 +80,8 @@ private:
|
||||
not_null<PeerData*> _peer;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<not_null<UserData*>, TimeId> _dates;
|
||||
|
||||
TimeId _offsetDate = 0;
|
||||
UserData *_offsetUser = nullptr;
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
@@ -82,7 +101,17 @@ public:
|
||||
void removeFromCache(not_null<UserData*> user);
|
||||
[[nodiscard]] TimeId dateForUser(not_null<UserData*> user);
|
||||
|
||||
std::unique_ptr<SavedStateBase> saveState() const override;
|
||||
void restoreState(std::unique_ptr<SavedStateBase> state) override;
|
||||
|
||||
private:
|
||||
struct SavedState : SavedStateBase {
|
||||
QString query;
|
||||
TimeId offsetDate = 0;
|
||||
UserData *offsetUser = nullptr;
|
||||
bool allLoaded = false;
|
||||
bool wasLoading = false;
|
||||
};
|
||||
struct Item {
|
||||
not_null<UserData*> user;
|
||||
TimeId date = 0;
|
||||
|
||||
@@ -210,7 +210,7 @@ void ProcessFullPhoto(
|
||||
) | rpl::map([=] {
|
||||
const auto user = peer->asUser();
|
||||
const auto username = peer->username();
|
||||
const auto channelId = user->personalChannelId();
|
||||
const auto channelId = user ? user->personalChannelId() : 0;
|
||||
const auto channel = channelId
|
||||
? user->owner().channel(channelId).get()
|
||||
: nullptr;
|
||||
|
||||
@@ -77,7 +77,7 @@ bool operator==(const Descriptor &a, const Descriptor &b) {
|
||||
struct Preload {
|
||||
Descriptor descriptor;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::weak_ptr<ChatHelpers::Show> show;
|
||||
std::weak_ptr<Main::SessionShow> show;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<Preload> &Preloads() {
|
||||
|
||||
@@ -1388,7 +1388,8 @@ void ShareBox::Inner::applyChatFilter(FilterId id) {
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
if (const auto history = row->history()) {
|
||||
if (_descriptor.filterCallback(history)) {
|
||||
if (history->asForum()
|
||||
|| _descriptor.filterCallback(history)) {
|
||||
_customChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,23 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool SortForBirthday(not_null<PeerData*> peer) {
|
||||
const auto user = peer->asUser();
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
const auto birthday = user->birthday();
|
||||
if (!birthday) {
|
||||
return false;
|
||||
}
|
||||
const auto is = [&](const QDate &date) {
|
||||
return (date.day() == birthday.day())
|
||||
&& (date.month() == birthday.month());
|
||||
};
|
||||
const auto now = QDate::currentDate();
|
||||
return is(now) || is(now.addDays(1)) || is(now.addDays(-1));
|
||||
}
|
||||
|
||||
PreviewDelegate::PreviewDelegate(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::ChatStyle*> st,
|
||||
@@ -216,7 +233,7 @@ auto GenerateGiftMedia(
|
||||
return tr::lng_action_gift_got_stars_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
gift.info.convertStars,
|
||||
gift.info.starsConverted,
|
||||
Ui::Text::RichLangValue);
|
||||
});
|
||||
auto description = data.text.empty()
|
||||
@@ -1068,10 +1085,23 @@ void SendGiftBox(
|
||||
const auto padding = st::giftBoxPadding;
|
||||
const auto available = width - padding.left() - padding.right();
|
||||
const auto perRow = available / single.width();
|
||||
const auto count = int(gifts.list.size());
|
||||
|
||||
auto order = ranges::views::ints
|
||||
| ranges::views::take(count)
|
||||
| ranges::to_vector;
|
||||
|
||||
if (SortForBirthday(peer)) {
|
||||
ranges::stable_partition(order, [&](int i) {
|
||||
const auto &gift = gifts.list[i];
|
||||
const auto stars = std::get_if<GiftTypeStars>(&gift);
|
||||
return stars && stars->info.birthday;
|
||||
});
|
||||
}
|
||||
|
||||
auto x = padding.left();
|
||||
auto y = padding.top();
|
||||
state->buttons.resize(gifts.list.size());
|
||||
state->buttons.resize(count);
|
||||
for (auto &button : state->buttons) {
|
||||
if (!button) {
|
||||
button = std::make_unique<GiftButton>(raw, &state->delegate);
|
||||
@@ -1079,9 +1109,9 @@ void SendGiftBox(
|
||||
}
|
||||
}
|
||||
const auto api = gifts.api;
|
||||
for (auto i = 0, count = int(gifts.list.size()); i != count; ++i) {
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto button = state->buttons[i].get();
|
||||
const auto &descriptor = gifts.list[i];
|
||||
const auto &descriptor = gifts.list[order[i]];
|
||||
button->setDescriptor(descriptor);
|
||||
|
||||
const auto last = !((i + 1) % perRow);
|
||||
@@ -1108,12 +1138,12 @@ void SendGiftBox(
|
||||
}
|
||||
});
|
||||
}
|
||||
if (gifts.list.size() % perRow) {
|
||||
if (count % perRow) {
|
||||
y += padding.bottom() + single.height();
|
||||
} else {
|
||||
y += padding.bottom() - st::giftBoxGiftSkip.y();
|
||||
}
|
||||
raw->resize(raw->width(), gifts.list.empty() ? 0 : y);
|
||||
raw->resize(raw->width(), count ? y : 0);
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
|
||||
@@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -169,7 +171,12 @@ void StartRtmpProcess::finish(JoinInfo info) {
|
||||
void StartRtmpProcess::createBox() {
|
||||
auto done = [=] {
|
||||
const auto peer = _request->peer;
|
||||
finish({ .peer = peer, .joinAs = peer, .rtmp = true });
|
||||
const auto joinAs = (peer->isChat() && peer->asChat()->amCreator())
|
||||
? peer
|
||||
: (peer->isChannel() && peer->asChannel()->amCreator())
|
||||
? peer
|
||||
: peer->session().user();
|
||||
finish({ .peer = peer, .joinAs = joinAs, .rtmp = true });
|
||||
};
|
||||
auto revoke = [=] {
|
||||
const auto guard = base::make_weak(&_request->guard);
|
||||
|
||||
@@ -822,7 +822,7 @@ void StickersListFooter::mousePressEvent(QMouseEvent *e) {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
_iconsMousePos = e ? e->globalPos() : QCursor::pos();
|
||||
_iconsMousePos = e->globalPos();
|
||||
updateSelected();
|
||||
|
||||
if (_selected == SpecialOver::Settings) {
|
||||
|
||||
@@ -192,9 +192,7 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
||||
};
|
||||
const auto session = thumb
|
||||
? &thumb->owner()->session()
|
||||
: media
|
||||
? &media->owner()->session()
|
||||
: nullptr;
|
||||
: &media->owner()->session();
|
||||
return LottieCachedFromContent(
|
||||
method,
|
||||
baseKey,
|
||||
|
||||
@@ -189,6 +189,7 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
||||
const auto game = media ? media->game() : nullptr;
|
||||
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) {
|
||||
openLink();
|
||||
return;
|
||||
}
|
||||
const auto bot = _bot;
|
||||
const auto title = game->title;
|
||||
|
||||
@@ -240,7 +240,7 @@ QByteArray Settings::serialize() const {
|
||||
+ Serialize::stringSize(_customFontFamily)
|
||||
+ sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken)
|
||||
+ sizeof(qint32) * 6;
|
||||
+ sizeof(qint32) * 7;
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -309,7 +309,7 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_thirdSectionExtendedBy)
|
||||
<< qint32(_notifyFromAll ? 1 : 0)
|
||||
<< qint32(_nativeWindowFrame.current() ? 1 : 0)
|
||||
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0)
|
||||
<< qint32(0) // Legacy system dark mode
|
||||
<< _cameraDeviceId.current()
|
||||
<< qint32(_ipRevealWarning ? 1 : 0)
|
||||
<< qint32(_groupCallPushToTalk ? 1 : 0)
|
||||
@@ -400,7 +400,8 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_skipToastsInFocus ? 1 : 0)
|
||||
<< qint32(_recordVideoMessages ? 1 : 0)
|
||||
<< SerializeVideoQuality(_videoQuality)
|
||||
<< qint32(_ivZoom.current());
|
||||
<< qint32(_ivZoom.current())
|
||||
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0);
|
||||
}
|
||||
|
||||
Ensures(result.size() == size);
|
||||
@@ -610,6 +611,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
stream >> nativeWindowFrame;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
// Read over this one below, if was in the file.
|
||||
stream >> systemDarkModeEnabled;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
@@ -853,6 +855,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> ivZoom;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> systemDarkModeEnabled;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -1453,7 +1458,6 @@ void Settings::resetOnLastLogout() {
|
||||
_thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
|
||||
_notifyFromAll = true;
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
_storiesClickTooltipHidden = false;
|
||||
_ttlVoiceClickTooltipHidden = false;
|
||||
|
||||
@@ -1036,7 +1036,7 @@ private:
|
||||
bool _notifyFromAll = true;
|
||||
rpl::variable<bool> _nativeWindowFrame = false;
|
||||
rpl::variable<std::optional<bool>> _systemDarkMode = std::nullopt;
|
||||
rpl::variable<bool> _systemDarkModeEnabled = false;
|
||||
rpl::variable<bool> _systemDarkModeEnabled = true;
|
||||
rpl::variable<WindowTitleContent> _windowTitleContent;
|
||||
WindowPosition _windowPosition; // per-window
|
||||
bool _disableOpenGL = false;
|
||||
|
||||
@@ -618,6 +618,7 @@ bool ResolveUsernameOrPhone(
|
||||
.startAutoSubmit = myContext.botStartAutoSubmit,
|
||||
.botAppName = (appname.isEmpty() ? postParam : appname),
|
||||
.botAppForceConfirmation = myContext.mayShowConfirmation,
|
||||
.botAppFullScreen = (params.value(u"mode"_q) == u"fullscreen"_q),
|
||||
.attachBotUsername = params.value(u"attach"_q),
|
||||
.attachBotToggleCommand = (params.contains(u"startattach"_q)
|
||||
? params.value(u"startattach"_q)
|
||||
|
||||
@@ -340,12 +340,12 @@ void PhoneClickHandler::onClick(ClickContext context) const {
|
||||
if (Trim(phone) != Trim(controller->session().user()->phone())) {
|
||||
menu->addAction(
|
||||
tr::lng_info_add_as_contact(tr::now),
|
||||
[=, raw = resolvePhoneAction.get()] {
|
||||
[=, raw = Ui::MakeWeak(resolvePhoneAction.get())] {
|
||||
controller->show(
|
||||
Box<AddContactBox>(
|
||||
_session,
|
||||
raw->firstName(),
|
||||
raw->lastName(),
|
||||
&controller->session(),
|
||||
raw ? raw->firstName() : QString(),
|
||||
raw ? raw->lastName() : QString(),
|
||||
Trim(phone)));
|
||||
},
|
||||
&st::menuIconInvite);
|
||||
|
||||
@@ -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 = 5007003;
|
||||
constexpr auto AppVersionStr = "5.7.3";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 5008002;
|
||||
constexpr auto AppVersionStr = "5.8.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -126,7 +126,6 @@ void RecentPeers::applyLocal(QByteArray serialized) {
|
||||
).arg(count));
|
||||
DEBUG_LOG(("Failed bytes: %1.").arg(
|
||||
QString::fromUtf8(serialized.mid(streamPosition).toHex())));
|
||||
_list.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,19 +69,20 @@ struct CreditsHistoryEntry final {
|
||||
QString successLink;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int convertStars = 0;
|
||||
int starsConverted = 0;
|
||||
int floodSkip = 0;
|
||||
bool converted = false;
|
||||
bool anonymous = false;
|
||||
bool savedToProfile = false;
|
||||
bool fromGiftsList = false;
|
||||
bool soldOutInfo = false;
|
||||
bool reaction = false;
|
||||
bool refunded = false;
|
||||
bool pending = false;
|
||||
bool failed = false;
|
||||
bool in = false;
|
||||
bool gift = false;
|
||||
bool converted : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
bool stargift : 1 = false;
|
||||
bool savedToProfile : 1 = false;
|
||||
bool fromGiftsList : 1 = false;
|
||||
bool soldOutInfo : 1 = false;
|
||||
bool reaction : 1 = false;
|
||||
bool refunded : 1 = false;
|
||||
bool pending : 1 = false;
|
||||
bool failed : 1 = false;
|
||||
bool in : 1 = false;
|
||||
bool gift : 1 = false;
|
||||
};
|
||||
|
||||
struct CreditsStatusSlice final {
|
||||
|
||||
@@ -745,6 +745,14 @@ bool DocumentData::emojiUsesTextColor() const {
|
||||
return (_flags & Flag::UseTextColor);
|
||||
}
|
||||
|
||||
void DocumentData::overrideEmojiUsesTextColor(bool value) {
|
||||
if (value) {
|
||||
_flags |= Flag::UseTextColor;
|
||||
} else {
|
||||
_flags &= ~Flag::UseTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
bool DocumentData::hasThumbnail() const {
|
||||
return _thumbnail.location.valid()
|
||||
&& !thumbnailFailed()
|
||||
|
||||
@@ -206,6 +206,7 @@ public:
|
||||
[[nodiscard]] bool isPremiumSticker() const;
|
||||
[[nodiscard]] bool isPremiumEmoji() const;
|
||||
[[nodiscard]] bool emojiUsesTextColor() const;
|
||||
void overrideEmojiUsesTextColor(bool value);
|
||||
|
||||
[[nodiscard]] bool hasThumbnail() const;
|
||||
[[nodiscard]] bool thumbnailLoading() const;
|
||||
|
||||
@@ -139,7 +139,7 @@ struct GiftCode {
|
||||
TextWithEntities message;
|
||||
ChannelData *channel = nullptr;
|
||||
MsgId giveawayMsgId = 0;
|
||||
int convertStars = 0;
|
||||
int starsConverted = 0;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int count = 0;
|
||||
|
||||
@@ -1225,6 +1225,9 @@ not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
|
||||
}
|
||||
|
||||
void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
|
||||
if (!document->sticker()) {
|
||||
return;
|
||||
}
|
||||
const auto id = ReactionId{ { document->id } };
|
||||
const auto favorite = (_unresolvedFavoriteId == id);
|
||||
const auto i = _unresolvedTop.find(id);
|
||||
|
||||
@@ -17,11 +17,16 @@ ChatBotCommands::Changed ChatBotCommands::update(
|
||||
clear();
|
||||
} else {
|
||||
for (const auto &commands : list) {
|
||||
auto &value = operator[](commands.userId);
|
||||
changed |= commands.commands.empty()
|
||||
? remove(commands.userId)
|
||||
: !ranges::equal(value, commands.commands);
|
||||
value = commands.commands;
|
||||
if (commands.commands.empty()) {
|
||||
changed |= remove(commands.userId);
|
||||
} else {
|
||||
auto &value = operator[](commands.userId);
|
||||
const auto isEqual = ranges::equal(value, commands.commands);
|
||||
changed |= !isEqual;
|
||||
if (!isEqual) {
|
||||
value = commands.commands;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
|
||||
@@ -345,6 +345,24 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
|
||||
const auto privacyChanged = (botInfo->privacyPolicyUrl != privacy);
|
||||
botInfo->privacyPolicyUrl = privacy;
|
||||
|
||||
if (const auto settings = d.vapp_settings()) {
|
||||
const auto &data = settings->data();
|
||||
botInfo->botAppColorTitleDay = Ui::MaybeColorFromSerialized(
|
||||
data.vheader_color()).value_or(QColor(0, 0, 0, 0));
|
||||
botInfo->botAppColorTitleNight = Ui::MaybeColorFromSerialized(
|
||||
data.vheader_dark_color()).value_or(QColor(0, 0, 0, 0));
|
||||
botInfo->botAppColorBodyDay = Ui::MaybeColorFromSerialized(
|
||||
data.vbackground_color()).value_or(QColor(0, 0, 0, 0));
|
||||
botInfo->botAppColorBodyNight = Ui::MaybeColorFromSerialized(
|
||||
data.vbackground_dark_color()).value_or(QColor(0, 0, 0, 0));
|
||||
} else {
|
||||
botInfo->botAppColorTitleDay
|
||||
= botInfo->botAppColorTitleNight
|
||||
= botInfo->botAppColorBodyDay
|
||||
= botInfo->botAppColorBodyNight
|
||||
= QColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
if (changedCommands || changedButton || privacyChanged) {
|
||||
owner().botCommandsChanged(this);
|
||||
}
|
||||
@@ -580,6 +598,9 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||
} else {
|
||||
user->setBotInfoVersion(-1);
|
||||
}
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
info->canManageEmojiStatus = update.is_bot_can_manage_emoji_status();
|
||||
}
|
||||
if (const auto pinned = update.vpinned_msg_id()) {
|
||||
SetTopPinnedMessageId(user, pinned->v);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ struct BotInfo {
|
||||
QString botMenuButtonUrl;
|
||||
QString privacyPolicyUrl;
|
||||
|
||||
QColor botAppColorTitleDay = QColor(0, 0, 0, 0);
|
||||
QColor botAppColorTitleNight = QColor(0, 0, 0, 0);
|
||||
QColor botAppColorBodyDay = QColor(0, 0, 0, 0);
|
||||
QColor botAppColorBodyNight = QColor(0, 0, 0, 0);
|
||||
|
||||
QString startToken;
|
||||
Dialogs::EntryState inlineReturnTo;
|
||||
|
||||
@@ -47,6 +52,7 @@ struct BotInfo {
|
||||
bool cantJoinGroups : 1 = false;
|
||||
bool supportsAttachMenu : 1 = false;
|
||||
bool canEditInformation : 1 = false;
|
||||
bool canManageEmojiStatus : 1 = false;
|
||||
bool supportsBusiness : 1 = false;
|
||||
bool hasMainApp : 1 = false;
|
||||
};
|
||||
|
||||
@@ -652,22 +652,27 @@ void CustomEmojiManager::unregisterListener(not_null<Listener*> listener) {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<DocumentData*>> CustomEmojiManager::resolve(
|
||||
DocumentId documentId) {
|
||||
auto CustomEmojiManager::resolve(DocumentId documentId)
|
||||
-> rpl::producer<not_null<DocumentData*>, rpl::empty_error> {
|
||||
return [=](auto consumer) {
|
||||
auto result = rpl::lifetime();
|
||||
const auto put = [=](not_null<DocumentData*> document) {
|
||||
const auto put = [=](
|
||||
not_null<DocumentData*> document,
|
||||
bool resolved = true) {
|
||||
if (!document->sticker()) {
|
||||
if (resolved) {
|
||||
consumer.put_error({});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
consumer.put_next_copy(document);
|
||||
return true;
|
||||
};
|
||||
if (!put(owner().document(documentId))) {
|
||||
const auto listener = new CallbackListener(put);
|
||||
if (!put(owner().document(documentId), false)) {
|
||||
const auto listener = result.make_state<CallbackListener>(put);
|
||||
resolve(documentId, listener);
|
||||
result.add([=] {
|
||||
unregisterListener(listener);
|
||||
delete listener;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
@@ -763,6 +768,9 @@ void CustomEmojiManager::request() {
|
||||
requestFinished();
|
||||
}).fail([=] {
|
||||
LOG(("API Error: Failed to get documents for emoji."));
|
||||
for (const auto &id : ids) {
|
||||
processListeners(_owner->document(id.v));
|
||||
}
|
||||
requestFinished();
|
||||
}).send();
|
||||
}
|
||||
@@ -792,7 +800,8 @@ void CustomEmojiManager::processLoaders(not_null<DocumentData*> document) {
|
||||
}
|
||||
}
|
||||
|
||||
void CustomEmojiManager::processListeners(not_null<DocumentData*> document) {
|
||||
void CustomEmojiManager::processListeners(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto id = document->id;
|
||||
if (const auto listeners = _resolvers.take(id)) {
|
||||
for (const auto &listener : *listeners) {
|
||||
|
||||
@@ -66,8 +66,8 @@ public:
|
||||
void resolve(DocumentId documentId, not_null<Listener*> listener);
|
||||
void unregisterListener(not_null<Listener*> listener);
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<DocumentData*>> resolve(
|
||||
DocumentId documentId);
|
||||
[[nodiscard]] auto resolve(DocumentId documentId)
|
||||
-> rpl::producer<not_null<DocumentData*>, rpl::empty_error>;
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
|
||||
not_null<DocumentData*> document,
|
||||
|
||||
@@ -206,22 +206,20 @@ void Stickers::incrementSticker(not_null<DocumentData*> document) {
|
||||
auto &sets = setsRef();
|
||||
auto it = sets.find(Data::Stickers::CloudRecentSetId);
|
||||
if (it == sets.cend()) {
|
||||
if (it == sets.cend()) {
|
||||
it = sets.emplace(
|
||||
it = sets.emplace(
|
||||
Data::Stickers::CloudRecentSetId,
|
||||
std::make_unique<Data::StickersSet>(
|
||||
&session().data(),
|
||||
Data::Stickers::CloudRecentSetId,
|
||||
std::make_unique<Data::StickersSet>(
|
||||
&session().data(),
|
||||
Data::Stickers::CloudRecentSetId,
|
||||
uint64(0), // accessHash
|
||||
uint64(0), // hash
|
||||
tr::lng_recent_stickers(tr::now),
|
||||
QString(),
|
||||
0, // count
|
||||
SetFlag::Special,
|
||||
TimeId(0))).first;
|
||||
} else {
|
||||
it->second->title = tr::lng_recent_stickers(tr::now);
|
||||
}
|
||||
uint64(0), // accessHash
|
||||
uint64(0), // hash
|
||||
tr::lng_recent_stickers(tr::now),
|
||||
QString(),
|
||||
0, // count
|
||||
SetFlag::Special,
|
||||
TimeId(0))).first;
|
||||
} else {
|
||||
it->second->title = tr::lng_recent_stickers(tr::now);
|
||||
}
|
||||
const auto set = it->second.get();
|
||||
auto removedFromEmoji = std::vector<not_null<EmojiPtr>>();
|
||||
|
||||
@@ -692,6 +692,10 @@ dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) {
|
||||
}
|
||||
dialogsSearchTabsPadding: 8px;
|
||||
|
||||
chatsFiltersTabs: SettingsSlider(dialogsSearchTabs) {
|
||||
rippleBottomSkip: 0px;
|
||||
}
|
||||
|
||||
dialogsStoriesList: DialogsStoriesList {
|
||||
small: dialogsStories;
|
||||
full: dialogsStoriesFull;
|
||||
|
||||
@@ -653,7 +653,8 @@ Widget::Widget(
|
||||
}
|
||||
|
||||
if (session().settings().dialogsFiltersEnabled()
|
||||
&& Core::App().settings().chatFiltersHorizontal()) {
|
||||
&& (Core::App().settings().chatFiltersHorizontal()
|
||||
|| !controller->enoughSpaceForFilters())) {
|
||||
toggleFiltersMenu(true);
|
||||
}
|
||||
}
|
||||
@@ -1301,19 +1302,38 @@ void Widget::updateHasFocus(not_null<QWidget*> focused) {
|
||||
}
|
||||
|
||||
void Widget::toggleFiltersMenu(bool enabled) {
|
||||
if (_layout == Layout::Child) {
|
||||
enabled = false;
|
||||
}
|
||||
if (!enabled == !_chatFilters) {
|
||||
return;
|
||||
} else if (enabled) {
|
||||
_chatFilters = base::make_unique_q<Ui::RpWidget>(this);
|
||||
class NoScrollPropagationWidget final : public Ui::RpWidget {
|
||||
public:
|
||||
using Ui::RpWidget::RpWidget;
|
||||
|
||||
protected:
|
||||
void touchEvent(QTouchEvent *e) {
|
||||
e->accept();
|
||||
}
|
||||
void wheelEvent(QWheelEvent *e) override final {
|
||||
e->accept();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
_chatFilters = base::make_unique_q<NoScrollPropagationWidget>(this);
|
||||
const auto raw = _chatFilters.get();
|
||||
const auto inner = Ui::AddChatFiltersTabsStrip(
|
||||
_chatFilters.get(),
|
||||
&session(),
|
||||
[this](FilterId id) {
|
||||
_scroll->scrollToY(0);
|
||||
if (controller()->activeChatsFilterCurrent() != id) {
|
||||
controller()->setActiveChatsFilter(id);
|
||||
}
|
||||
},
|
||||
controller(),
|
||||
true);
|
||||
raw->show();
|
||||
raw->stackUnder(_scroll);
|
||||
|
||||
@@ -296,7 +296,7 @@ private:
|
||||
bool _dragForward = false;
|
||||
base::Timer _chooseByDragTimer;
|
||||
|
||||
Layout _layout = Layout::Main;
|
||||
const Layout _layout = Layout::Main;
|
||||
int _narrowWidth = 0;
|
||||
object_ptr<Ui::RpWidget> _searchControls;
|
||||
object_ptr<HistoryView::TopBarWidget> _subsectionTopBar = { nullptr };
|
||||
|
||||
@@ -438,8 +438,6 @@ void ApiWrap::startExport(
|
||||
}
|
||||
if (_settings->types & Settings::Type::AnyChatsMask) {
|
||||
_startProcess->steps.push_back(Step::SplitRanges);
|
||||
}
|
||||
if (_settings->types & Settings::Type::AnyChatsMask) {
|
||||
_startProcess->steps.push_back(Step::DialogsCount);
|
||||
}
|
||||
if (_settings->types & Settings::Type::GroupsChannelsMask) {
|
||||
|
||||
@@ -712,6 +712,14 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
true);
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> History::addNewLocalMessage(
|
||||
not_null<HistoryItem*> item) {
|
||||
Expects(item->history() == this);
|
||||
Expects(item->isLocal());
|
||||
|
||||
return addNewItem(item, true);
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> History::addSponsoredMessage(
|
||||
MsgId id,
|
||||
Data::SponsoredFrom from,
|
||||
|
||||
@@ -180,6 +180,7 @@ public:
|
||||
not_null<HistoryItem*> addNewLocalMessage(
|
||||
HistoryItemCommonFields &&fields,
|
||||
not_null<GameData*> game);
|
||||
not_null<HistoryItem*> addNewLocalMessage(not_null<HistoryItem*> item);
|
||||
|
||||
not_null<HistoryItem*> addSponsoredMessage(
|
||||
MsgId id,
|
||||
|
||||
@@ -2435,6 +2435,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
if (const auto sticker = emojiStickers->stickerForEmoji(
|
||||
isolated)) {
|
||||
addDocumentActions(sticker.document, item);
|
||||
} else if (v::is<QString>(isolated.items.front())
|
||||
&& v::is_null(isolated.items[1])) {
|
||||
const auto id = v::get<QString>(isolated.items.front());
|
||||
const auto docId = id.toULongLong();
|
||||
const auto document = session->data().document(docId);
|
||||
if (document->sticker()) {
|
||||
addDocumentActions(document, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5530,7 +5530,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
||||
data.vmessage()->data().ventities().v),
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.convertStars = int(data.vconvert_stars().v),
|
||||
.starsConverted = int(data.vconvert_stars().value_or_empty()),
|
||||
.limitedCount = gift.vavailability_total().value_or_empty(),
|
||||
.limitedLeft = gift.vavailability_remains().value_or_empty(),
|
||||
.count = int(gift.vstars().v),
|
||||
|
||||
@@ -14,28 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] InlineBots::PeerTypes PeerTypesFromMTP(
|
||||
const MTPvector<MTPInlineQueryPeerType> &types) {
|
||||
using namespace InlineBots;
|
||||
auto result = PeerTypes(0);
|
||||
for (const auto &type : types.v) {
|
||||
result |= type.match([&](const MTPDinlineQueryPeerTypePM &data) {
|
||||
return PeerType::User;
|
||||
}, [&](const MTPDinlineQueryPeerTypeChat &data) {
|
||||
return PeerType::Group;
|
||||
}, [&](const MTPDinlineQueryPeerTypeMegagroup &data) {
|
||||
return PeerType::Group;
|
||||
}, [&](const MTPDinlineQueryPeerTypeBroadcast &data) {
|
||||
return PeerType::Broadcast;
|
||||
}, [&](const MTPDinlineQueryPeerTypeBotPM &data) {
|
||||
return PeerType::Bot;
|
||||
}, [&](const MTPDinlineQueryPeerTypeSameBotPM &data) {
|
||||
return PeerType();
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL(
|
||||
const MTPDkeyboardButtonRequestPeer &query) {
|
||||
using Type = RequestPeerQuery::Type;
|
||||
@@ -76,6 +54,28 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
InlineBots::PeerTypes PeerTypesFromMTP(
|
||||
const MTPvector<MTPInlineQueryPeerType> &types) {
|
||||
using namespace InlineBots;
|
||||
auto result = PeerTypes(0);
|
||||
for (const auto &type : types.v) {
|
||||
result |= type.match([&](const MTPDinlineQueryPeerTypePM &data) {
|
||||
return PeerType::User;
|
||||
}, [&](const MTPDinlineQueryPeerTypeChat &data) {
|
||||
return PeerType::Group;
|
||||
}, [&](const MTPDinlineQueryPeerTypeMegagroup &data) {
|
||||
return PeerType::Group;
|
||||
}, [&](const MTPDinlineQueryPeerTypeBroadcast &data) {
|
||||
return PeerType::Broadcast;
|
||||
}, [&](const MTPDinlineQueryPeerTypeBotPM &data) {
|
||||
return PeerType::Bot;
|
||||
}, [&](const MTPDinlineQueryPeerTypeSameBotPM &data) {
|
||||
return PeerType();
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HistoryMessageMarkupButton::HistoryMessageMarkupButton(
|
||||
Type type,
|
||||
const QString &text,
|
||||
|
||||
@@ -19,6 +19,9 @@ enum class PeerType : uint8;
|
||||
using PeerTypes = base::flags<PeerType>;
|
||||
} // namespace InlineBots
|
||||
|
||||
[[nodiscard]] InlineBots::PeerTypes PeerTypesFromMTP(
|
||||
const MTPvector<MTPInlineQueryPeerType> &types);
|
||||
|
||||
enum class ReplyMarkupFlag : uint32 {
|
||||
None = (1U << 0),
|
||||
ForceReply = (1U << 1),
|
||||
|
||||
@@ -869,7 +869,7 @@ HistoryWidget::HistoryWidget(
|
||||
}
|
||||
if (flags & PeerUpdateFlag::FullInfo) {
|
||||
fullInfoUpdated();
|
||||
if (const auto channel = _peer ? _peer->asChannel() : nullptr) {
|
||||
if (const auto channel = _peer->asChannel()) {
|
||||
if (channel->allowedReactions().paidEnabled) {
|
||||
session().credits().load();
|
||||
}
|
||||
@@ -3440,7 +3440,7 @@ void HistoryWidget::messagesFailed(const MTP::Error &error, int requestId) {
|
||||
closeCurrent();
|
||||
const auto wasAccount = not_null(&was->account());
|
||||
if (const auto primary = Core::App().windowFor(wasAccount)) {
|
||||
primary->showToast((was && was->isMegagroup())
|
||||
primary->showToast(was->isMegagroup()
|
||||
? tr::lng_group_not_accessible(tr::now)
|
||||
: tr::lng_channel_not_accessible(tr::now));
|
||||
}
|
||||
@@ -5111,7 +5111,7 @@ bool HistoryWidget::updateCmdStartShown() {
|
||||
const auto textSmall = _fieldCharsCountManager.count() > kSmallMenuAfter;
|
||||
const auto textChanged = _botMenu.button
|
||||
&& ((_botMenu.text != bot->botInfo->botMenuButtonText)
|
||||
|| (_botMenu.small != textSmall));
|
||||
|| (_botMenu.small != textSmall));
|
||||
if (textChanged) {
|
||||
_botMenu.text = bot->botInfo->botMenuButtonText;
|
||||
if ((_botMenu.small = textSmall)) {
|
||||
@@ -7701,12 +7701,12 @@ void HistoryWidget::createSponsoredMessageBar() {
|
||||
session().sponsoredMessages().itemRemoved(
|
||||
maybeFullId
|
||||
) | rpl::start_with_next([this] {
|
||||
_sponsoredMessageBar->toggle(false, anim::type::normal);
|
||||
_sponsoredMessageBar->shownValue() | rpl::filter(
|
||||
!rpl::mappers::_1
|
||||
) | rpl::start_with_next([this] {
|
||||
_sponsoredMessageBar = nullptr;
|
||||
}, _sponsoredMessageBar->lifetime());
|
||||
_sponsoredMessageBar->toggle(false, anim::type::normal);
|
||||
}, _sponsoredMessageBar->lifetime());
|
||||
|
||||
if (maybeFullId) {
|
||||
|
||||
@@ -381,7 +381,7 @@ void FieldHeader::init() {
|
||||
return;
|
||||
}
|
||||
const auto e = static_cast<QMouseEvent*>(event.get());
|
||||
const auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos());
|
||||
const auto pos = e->pos();
|
||||
const auto inPreviewRect = _clickableRect.contains(pos);
|
||||
const auto inPhotoEdit = _shownMessageHasPreview
|
||||
&& _photoEditAllowed
|
||||
@@ -1191,9 +1191,7 @@ void ComposeControls::showStarted() {
|
||||
if (_attachBotsMenu) {
|
||||
_attachBotsMenu->hideFast();
|
||||
}
|
||||
if (_voiceRecordBar) {
|
||||
_voiceRecordBar->hideFast();
|
||||
}
|
||||
_voiceRecordBar->hideFast();
|
||||
if (_autocomplete) {
|
||||
_autocomplete->hideFast();
|
||||
}
|
||||
@@ -1213,9 +1211,7 @@ void ComposeControls::showFinished() {
|
||||
if (_attachBotsMenu) {
|
||||
_attachBotsMenu->hideFast();
|
||||
}
|
||||
if (_voiceRecordBar) {
|
||||
_voiceRecordBar->hideFast();
|
||||
}
|
||||
_voiceRecordBar->hideFast();
|
||||
if (_autocomplete) {
|
||||
_autocomplete->hideFast();
|
||||
}
|
||||
@@ -3383,6 +3379,10 @@ Fn<void()> ComposeControls::restoreTextCallback(
|
||||
});
|
||||
}
|
||||
|
||||
Ui::InputField *ComposeControls::fieldForMention() const {
|
||||
return _writeRestriction.current() ? nullptr : _field.get();
|
||||
}
|
||||
|
||||
TextWithEntities ComposeControls::prepareTextForEditMsg() const {
|
||||
if (!_history) {
|
||||
return {};
|
||||
|
||||
@@ -249,6 +249,8 @@ public:
|
||||
|
||||
Fn<void()> restoreTextCallback(const QString &insertTextOnCancel) const;
|
||||
|
||||
[[nodiscard]] Ui::InputField *fieldForMention() const;
|
||||
|
||||
private:
|
||||
enum class TextUpdateEvent {
|
||||
SaveDraft = (1 << 0),
|
||||
|
||||
@@ -474,7 +474,7 @@ TTLButton::TTLButton(
|
||||
) | rpl::start_with_next([=](bool toHide) {
|
||||
const auto isFirstTooltip
|
||||
= !Core::App().settings().ttlVoiceClickTooltipHidden();
|
||||
if (isFirstTooltip || (!isFirstTooltip && toHide)) {
|
||||
if (isFirstTooltip || toHide) {
|
||||
_tooltip->toggleAnimated(!toHide);
|
||||
}
|
||||
}, _tooltip->lifetime());
|
||||
|
||||
@@ -161,6 +161,8 @@ private:
|
||||
bool listShowReactPremiumError(
|
||||
not_null<HistoryItem*> item,
|
||||
const Data::ReactionId &id) override;
|
||||
base::unique_qptr<Ui::PopupMenu> listFillSenderUserpicMenu(
|
||||
PeerId userpicPeerId) override;
|
||||
void listWindowSetInnerFocus() override;
|
||||
bool listAllowsDragForward() override;
|
||||
void listLaunchDrag(
|
||||
@@ -828,6 +830,11 @@ bool Item::listShowReactPremiumError(
|
||||
return false;
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> Item::listFillSenderUserpicMenu(
|
||||
PeerId userpicPeerId) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Item::listWindowSetInnerFocus() {
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace {
|
||||
text.size(),
|
||||
Data::SerializeCustomEmojiId(document)) },
|
||||
};
|
||||
});
|
||||
}) | rpl::map_error_to_done();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> PeerCustomStatus(
|
||||
|
||||
@@ -1510,11 +1510,13 @@ void AddWhoReactedAction(
|
||||
strong->hideMenu();
|
||||
}
|
||||
if (const auto item = controller->session().data().message(itemId)) {
|
||||
controller->window().show(Reactions::FullListBox(
|
||||
controller,
|
||||
item,
|
||||
{},
|
||||
whoReadIds));
|
||||
controller->showSection(
|
||||
std::make_shared<Info::Memento>(
|
||||
whoReadIds,
|
||||
itemId,
|
||||
HistoryView::Reactions::DefaultSelectedTab(
|
||||
item,
|
||||
whoReadIds)));
|
||||
}
|
||||
};
|
||||
if (!menu->empty()) {
|
||||
@@ -1685,10 +1687,10 @@ void ShowWhoReactedMenu(
|
||||
};
|
||||
const auto showAllChosen = [=, itemId = item->fullId()]{
|
||||
if (const auto item = controller->session().data().message(itemId)) {
|
||||
controller->window().show(Reactions::FullListBox(
|
||||
controller,
|
||||
item,
|
||||
id));
|
||||
controller->showSection(std::make_shared<Info::Memento>(
|
||||
nullptr,
|
||||
itemId,
|
||||
HistoryView::Reactions::DefaultSelectedTab(item, id)));
|
||||
}
|
||||
};
|
||||
const auto owner = &controller->session().data();
|
||||
|
||||
@@ -30,9 +30,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/phone_click_handler.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_who_reacted.h"
|
||||
#include "api/api_views.h"
|
||||
@@ -171,6 +172,11 @@ bool WindowListDelegate::listShowReactPremiumError(
|
||||
return Window::ShowReactPremiumError(_window, item, id);
|
||||
}
|
||||
|
||||
auto WindowListDelegate::listFillSenderUserpicMenu(PeerId userpicPeerId)
|
||||
-> base::unique_qptr<Ui::PopupMenu> {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void WindowListDelegate::listWindowSetInnerFocus() {
|
||||
_window->widget()->setInnerFocus();
|
||||
}
|
||||
@@ -2794,6 +2800,12 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
? _overElement->data().get()
|
||||
: nullptr;
|
||||
const auto clickedReaction = Reactions::ReactionIdOfLink(link);
|
||||
const auto linkPhoneNumber = link
|
||||
? link->property(kPhoneNumberLinkProperty).toString()
|
||||
: QString();
|
||||
const auto linkUserpicPeerId = (link && _overSenderUserpic)
|
||||
? PeerId(link->property(kPeerLinkPeerIdProperty).toULongLong())
|
||||
: PeerId();
|
||||
_whoReactedMenuLifetime.destroy();
|
||||
if (!clickedReaction.empty()
|
||||
&& overItem
|
||||
@@ -2808,6 +2820,19 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
_whoReactedMenuLifetime);
|
||||
e->accept();
|
||||
return;
|
||||
} else if (!linkPhoneNumber.isEmpty()) {
|
||||
PhoneClickHandler(&session(), linkPhoneNumber).onClick(
|
||||
prepareClickContext(
|
||||
Qt::LeftButton,
|
||||
_overItemExact ? _overItemExact->fullId() : FullMsgId()));
|
||||
return;
|
||||
} else if (linkUserpicPeerId) {
|
||||
_menu = _delegate->listFillSenderUserpicMenu(linkUserpicPeerId);
|
||||
if (_menu) {
|
||||
_menu->popup(e->globalPos());
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto request = ContextMenuRequest(controller());
|
||||
@@ -3568,6 +3593,15 @@ ClickHandlerContext ListWidget::prepareClickHandlerContext(FullMsgId id) {
|
||||
};
|
||||
}
|
||||
|
||||
ClickContext ListWidget::prepareClickContext(
|
||||
Qt::MouseButton button,
|
||||
FullMsgId itemId) {
|
||||
return {
|
||||
button,
|
||||
QVariant::fromValue(prepareClickHandlerContext(itemId)),
|
||||
};
|
||||
}
|
||||
|
||||
int ListWidget::SelectionViewOffset(
|
||||
not_null<const ListWidget*> inner,
|
||||
not_null<const Element*> view) {
|
||||
@@ -3629,6 +3663,7 @@ void ListWidget::mouseActionUpdate() {
|
||||
auto inTextSelection = (_overState.pointState != PointState::Outside)
|
||||
&& (_overState.itemId == _pressState.itemId)
|
||||
&& hasSelectedText();
|
||||
auto dragStateUserpic = false;
|
||||
const auto overReaction = reactionView && reactionState.link;
|
||||
if (overReaction) {
|
||||
dragState = reactionState;
|
||||
@@ -3727,6 +3762,7 @@ void ListWidget::mouseActionUpdate() {
|
||||
// stop enumeration if we've found a userpic under the cursor
|
||||
if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
|
||||
dragState = TextState(nullptr, view->fromPhotoLink());
|
||||
dragStateUserpic = true;
|
||||
_overItemExact = nullptr;
|
||||
lnkhost = view;
|
||||
return false;
|
||||
@@ -3738,6 +3774,7 @@ void ListWidget::mouseActionUpdate() {
|
||||
}
|
||||
}
|
||||
const auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost);
|
||||
_overSenderUserpic = dragStateUserpic;
|
||||
if (lnkChanged || dragState.cursor != _mouseCursorState) {
|
||||
Ui::Tooltip::Hide();
|
||||
}
|
||||
|
||||
@@ -185,6 +185,8 @@ public:
|
||||
virtual bool listShowReactPremiumError(
|
||||
not_null<HistoryItem*> item,
|
||||
const Data::ReactionId &id) = 0;
|
||||
virtual base::unique_qptr<Ui::PopupMenu> listFillSenderUserpicMenu(
|
||||
PeerId userpicPeerId) = 0;
|
||||
virtual void listWindowSetInnerFocus() = 0;
|
||||
virtual bool listAllowsDragForward() = 0;
|
||||
virtual void listLaunchDrag(
|
||||
@@ -218,6 +220,8 @@ public:
|
||||
bool listShowReactPremiumError(
|
||||
not_null<HistoryItem*> item,
|
||||
const Data::ReactionId &id) override;
|
||||
base::unique_qptr<Ui::PopupMenu> listFillSenderUserpicMenu(
|
||||
PeerId userpicPeerId) override;
|
||||
void listWindowSetInnerFocus() override;
|
||||
bool listAllowsDragForward() override;
|
||||
void listLaunchDrag(
|
||||
@@ -356,6 +360,9 @@ public:
|
||||
int top) const;
|
||||
[[nodiscard]] ClickHandlerContext prepareClickHandlerContext(
|
||||
FullMsgId id);
|
||||
[[nodiscard]] ClickContext prepareClickContext(
|
||||
Qt::MouseButton button,
|
||||
FullMsgId itemId);
|
||||
|
||||
// AbstractTooltipShower interface
|
||||
QString tooltipText() const override;
|
||||
@@ -808,6 +815,7 @@ private:
|
||||
CursorState _mouseCursorState = CursorState();
|
||||
uint16 _mouseTextSymbol = 0;
|
||||
bool _pressWasInactive = false;
|
||||
bool _overSenderUserpic = false;
|
||||
|
||||
bool _selectEnabled = false;
|
||||
HistoryItem *_selectedTextItem = nullptr;
|
||||
|
||||
@@ -2120,6 +2120,7 @@ PointState Message::pointState(QPoint point) const {
|
||||
|
||||
// Entry page is always a bubble bottom.
|
||||
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
|
||||
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
|
||||
|
||||
if (item->repliesAreComments() || item->externalReply()) {
|
||||
g.setHeight(g.height() - st::historyCommentsButtonHeight);
|
||||
@@ -2160,11 +2161,18 @@ PointState Message::pointState(QPoint point) const {
|
||||
trect.setHeight(trect.height() - entryHeight);
|
||||
}
|
||||
|
||||
auto mediaHeight = media->height();
|
||||
auto mediaLeft = trect.x() - st::msgPadding.left();
|
||||
auto mediaTop = (trect.y() + trect.height() - mediaHeight);
|
||||
|
||||
if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) {
|
||||
const auto mediaHeight = mediaDisplayed ? media->height() : 0;
|
||||
const auto mediaLeft = trect.x() - st::msgPadding.left();
|
||||
const auto mediaTop = (!mediaDisplayed || _invertMedia)
|
||||
? (trect.y() + (mediaOnTop ? 0 : st::mediaInBubbleSkip))
|
||||
: (trect.y() + trect.height() - mediaHeight);
|
||||
if (mediaDisplayed && _invertMedia) {
|
||||
trect.setY(mediaTop
|
||||
+ mediaHeight
|
||||
+ (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
|
||||
}
|
||||
if (point.y() >= mediaTop
|
||||
&& point.y() < mediaTop + mediaHeight) {
|
||||
return media->pointState(point - QPoint(mediaLeft, mediaTop));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_view_swipe.h"
|
||||
#include "ui/chat/pinned_bar.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
@@ -915,8 +916,9 @@ void RepliesWidget::setupSwipeReply() {
|
||||
result.callback = [=, itemId = view->data()->fullId()] {
|
||||
const auto still = show->session().data().message(itemId);
|
||||
const auto view = _inner->viewByPosition(still->position());
|
||||
const auto selected = view->selectedQuote(
|
||||
_inner->getSelectedTextRange(still));
|
||||
const auto selected = view
|
||||
? view->selectedQuote(_inner->getSelectedTextRange(still))
|
||||
: SelectedQuote();
|
||||
const auto replyToItemId = (selected.item
|
||||
? selected.item
|
||||
: still)->fullId();
|
||||
@@ -2705,6 +2707,23 @@ Ui::ChatPaintContext RepliesWidget::listPreparePaintContext(
|
||||
return context;
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> RepliesWidget::listFillSenderUserpicMenu(
|
||||
PeerId userpicPeerId) {
|
||||
const auto searchInEntry = _topic
|
||||
? Dialogs::Key(_topic)
|
||||
: Dialogs::Key(_history);
|
||||
auto menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
Window::FillSenderUserpicMenu(
|
||||
controller(),
|
||||
_history->owner().peer(userpicPeerId),
|
||||
_composeControls->fieldForMention(),
|
||||
searchInEntry,
|
||||
Ui::Menu::CreateAddActionCallback(menu.get()));
|
||||
return menu->empty() ? nullptr : std::move(menu);
|
||||
}
|
||||
|
||||
void RepliesWidget::setupEmptyPainter() {
|
||||
Expects(_topic != nullptr);
|
||||
|
||||
|
||||
@@ -183,6 +183,8 @@ public:
|
||||
not_null<TranslateTracker*> tracker) override;
|
||||
Ui::ChatPaintContext listPreparePaintContext(
|
||||
Ui::ChatPaintContextArgs &&args) override;
|
||||
base::unique_qptr<Ui::PopupMenu> listFillSenderUserpicMenu(
|
||||
PeerId userpicPeerId) override;
|
||||
|
||||
// CornerButtonsDelegate delegate.
|
||||
void cornerButtonsShowAtPosition(
|
||||
|
||||
@@ -72,10 +72,8 @@ namespace {
|
||||
|
||||
constexpr auto kEmojiInteractionSeenDuration = 3 * crl::time(1000);
|
||||
|
||||
inline bool HasGroupCallMenu(const not_null<PeerData*> &peer) {
|
||||
return !peer->groupCall()
|
||||
&& ((peer->isChannel() && peer->asChannel()->amCreator())
|
||||
|| (peer->isChat() && peer->asChat()->amCreator()));
|
||||
[[nodiscard]] inline bool HasGroupCallMenu(not_null<PeerData*> peer) {
|
||||
return !peer->groupCall() && peer->canManageGroupCall();
|
||||
}
|
||||
|
||||
QString TopBarNameText(
|
||||
|
||||
@@ -108,6 +108,9 @@ CustomEmoji::CustomEmoji(
|
||||
}
|
||||
|
||||
void CustomEmoji::customEmojiResolveDone(not_null<DocumentData*> document) {
|
||||
if (!document->sticker()) {
|
||||
return;
|
||||
}
|
||||
_resolving = false;
|
||||
const auto id = document->id;
|
||||
for (auto &line : _lines) {
|
||||
|
||||
@@ -80,7 +80,7 @@ TextWithEntities PremiumGift::subtitle() {
|
||||
? tr::lng_action_gift_sent_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
_data.convertStars,
|
||||
_data.starsConverted,
|
||||
lt_user,
|
||||
Ui::Text::Bold(_parent->history()->peer->shortName()),
|
||||
Ui::Text::RichLangValue)
|
||||
@@ -89,7 +89,7 @@ TextWithEntities PremiumGift::subtitle() {
|
||||
: tr::lng_action_gift_got_stars_text)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
_data.convertStars,
|
||||
_data.starsConverted,
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
const auto isCreditsPrize = creditsPrize();
|
||||
|
||||
@@ -91,7 +91,8 @@ ThemeDocument::ThemeDocument(
|
||||
: File(parent, parent->data())
|
||||
, _data(document)
|
||||
, _serviceWidth(serviceWidth) {
|
||||
Expects(params.has_value() || _data->hasThumbnail() || _data->isTheme());
|
||||
Expects(params.has_value()
|
||||
|| (_data && (_data->hasThumbnail() || _data->isTheme())));
|
||||
|
||||
if (params) {
|
||||
_background = params->backgroundColors();
|
||||
|
||||
@@ -560,7 +560,9 @@ QSize WebPage::countOptimalSize() {
|
||||
}
|
||||
|
||||
// init dimensions
|
||||
const auto skipBlockWidth = _parent->skipBlockWidth();
|
||||
const auto skipBlockWidth = (sponsored && sponsored->hasMedia)
|
||||
? 0
|
||||
: _parent->skipBlockWidth();
|
||||
auto maxWidth = skipBlockWidth;
|
||||
auto minHeight = 0;
|
||||
|
||||
@@ -628,8 +630,10 @@ QSize WebPage::countOptimalSize() {
|
||||
_durationWidth = st::msgDateFont->width(_duration);
|
||||
}
|
||||
if (!_openButton.isEmpty()) {
|
||||
maxWidth += rect::m::sum::h(st::historyPageButtonPadding)
|
||||
+ _openButton.maxWidth();
|
||||
accumulate_max(
|
||||
maxWidth,
|
||||
rect::m::sum::h(st::historyPageButtonPadding)
|
||||
+ _openButton.maxWidth());
|
||||
}
|
||||
maxWidth += rect::m::sum::h(padding);
|
||||
minHeight += rect::m::sum::v(padding);
|
||||
@@ -1228,8 +1232,9 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
||||
.position = QPoint(
|
||||
inner.x() + (inner.width() - _openButton.maxWidth()) / 2,
|
||||
end + st::historyPageButtonPadding.top()),
|
||||
.availableWidth = paintw,
|
||||
.availableWidth = inner.width(),
|
||||
.now = context.now,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ private:
|
||||
class Controller final : public PeerListController {
|
||||
public:
|
||||
Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<Window::SessionNavigation*> window,
|
||||
FullMsgId itemId,
|
||||
const ReactionId &selected,
|
||||
rpl::producer<ReactionId> switches,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds);
|
||||
@@ -73,9 +73,26 @@ public:
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void loadMoreRows() override;
|
||||
|
||||
std::unique_ptr<PeerListRow> createRestoredRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
|
||||
std::unique_ptr<PeerListState> saveState() const override;
|
||||
void restoreState(std::unique_ptr<PeerListState> state) override;
|
||||
|
||||
private:
|
||||
using AllEntry = std::pair<not_null<PeerData*>, Data::ReactionId>;
|
||||
|
||||
struct SavedState : SavedStateBase {
|
||||
ReactionId shownReaction;
|
||||
base::flat_map<std::pair<PeerId, ReactionId>, uint64> idsMap;
|
||||
uint64 idsCounter = 0;
|
||||
std::vector<AllEntry> all;
|
||||
QString allOffset;
|
||||
std::vector<not_null<PeerData*>> filtered;
|
||||
QString filteredOffset;
|
||||
bool wasLoading = false;
|
||||
};
|
||||
|
||||
void fillWhoRead();
|
||||
void loadMore(const ReactionId &reaction);
|
||||
bool appendRow(not_null<PeerData*> peer, ReactionId reaction);
|
||||
@@ -88,14 +105,15 @@ private:
|
||||
not_null<PeerData*> peer,
|
||||
const ReactionId &reaction) const;
|
||||
|
||||
const not_null<Window::SessionController*> _window;
|
||||
const not_null<HistoryItem*> _item;
|
||||
const not_null<Window::SessionNavigation*> _window;
|
||||
const not_null<PeerData*> _peer;
|
||||
const FullMsgId _itemId;
|
||||
const Ui::Text::CustomEmojiFactory _factory;
|
||||
const std::shared_ptr<Api::WhoReadList> _whoReadIds;
|
||||
const std::vector<not_null<PeerData*>> _whoRead;
|
||||
MTP::Sender _api;
|
||||
|
||||
ReactionId _shownReaction;
|
||||
std::shared_ptr<Api::WhoReadList> _whoReadIds;
|
||||
std::vector<not_null<PeerData*>> _whoRead;
|
||||
|
||||
mutable base::flat_map<std::pair<PeerId, ReactionId>, uint64> _idsMap;
|
||||
mutable uint64 _idsCounter = 0;
|
||||
@@ -110,6 +128,22 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> ResolveWhoRead(
|
||||
not_null<Window::SessionNavigation*> window,
|
||||
const std::shared_ptr<Api::WhoReadList> &whoReadIds) {
|
||||
if (!whoReadIds || whoReadIds->list.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto result = std::vector<not_null<PeerData*>>();
|
||||
auto &owner = window->session().data();
|
||||
for (const auto &peerWithDate : whoReadIds->list) {
|
||||
if (const auto peer = owner.peerLoaded(peerWithDate.peer)) {
|
||||
result.push_back(peer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Row::Row(
|
||||
uint64 id,
|
||||
not_null<PeerData*> peer,
|
||||
@@ -166,17 +200,19 @@ void Row::rightActionPaint(
|
||||
}
|
||||
|
||||
Controller::Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<Window::SessionNavigation*> window,
|
||||
FullMsgId itemId,
|
||||
const ReactionId &selected,
|
||||
rpl::producer<ReactionId> switches,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds)
|
||||
: _window(window)
|
||||
, _item(item)
|
||||
, _peer(window->session().data().peer(itemId.peer))
|
||||
, _itemId(itemId)
|
||||
, _factory(Data::ReactedMenuFactory(&window->session()))
|
||||
, _whoReadIds(whoReadIds)
|
||||
, _whoRead(ResolveWhoRead(window, _whoReadIds))
|
||||
, _api(&window->session().mtp())
|
||||
, _shownReaction(selected)
|
||||
, _whoReadIds(whoReadIds) {
|
||||
, _shownReaction(selected) {
|
||||
std::move(
|
||||
switches
|
||||
) | rpl::filter([=](const ReactionId &reaction) {
|
||||
@@ -248,14 +284,6 @@ uint64 Controller::id(
|
||||
}
|
||||
|
||||
void Controller::fillWhoRead() {
|
||||
if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
|
||||
auto &owner = _window->session().data();
|
||||
for (const auto &peerWithDate : _whoReadIds->list) {
|
||||
if (const auto peer = owner.peerLoaded(peerWithDate.peer)) {
|
||||
_whoRead.push_back(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &peer : _whoRead) {
|
||||
appendRow(peer, ReactionId());
|
||||
}
|
||||
@@ -271,6 +299,60 @@ void Controller::loadMoreRows() {
|
||||
loadMore(_shownReaction);
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> Controller::createRestoredRow(
|
||||
not_null<PeerData*> peer) {
|
||||
if (_shownReaction.emoji() == u"read"_q) {
|
||||
return createRow(peer, Data::ReactionId());
|
||||
} else if (_shownReaction.empty()) {
|
||||
const auto i = ranges::find(_all, peer, &AllEntry::first);
|
||||
const auto reaction = (i != end(_all)) ? i->second : _shownReaction;
|
||||
return createRow(peer, reaction);
|
||||
}
|
||||
return createRow(peer, _shownReaction);
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> Controller::saveState() const {
|
||||
auto result = PeerListController::saveState();
|
||||
|
||||
auto my = std::make_unique<SavedState>();
|
||||
my->shownReaction = _shownReaction;
|
||||
my->idsMap = _idsMap;
|
||||
my->idsCounter = _idsCounter;
|
||||
my->all = _all;
|
||||
my->allOffset = _allOffset;
|
||||
my->filtered = _filtered;
|
||||
my->filteredOffset = _filteredOffset;
|
||||
my->wasLoading = (_loadRequestId != 0);
|
||||
result->controllerState = std::move(my);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Controller::restoreState(std::unique_ptr<PeerListState> state) {
|
||||
auto typeErasedState = state
|
||||
? state->controllerState.get()
|
||||
: nullptr;
|
||||
if (const auto my = dynamic_cast<SavedState*>(typeErasedState)) {
|
||||
if (const auto requestId = base::take(_loadRequestId)) {
|
||||
_api.request(requestId).cancel();
|
||||
}
|
||||
_shownReaction = my->shownReaction;
|
||||
_idsMap = std::move(my->idsMap);
|
||||
_idsCounter = my->idsCounter;
|
||||
_all = std::move(my->all);
|
||||
_allOffset = std::move(my->allOffset);
|
||||
_filtered = std::move(my->filtered);
|
||||
_filteredOffset = std::move(my->filteredOffset);
|
||||
if (my->wasLoading) {
|
||||
loadMoreRows();
|
||||
}
|
||||
PeerListController::restoreState(std::move(state));
|
||||
if (delegate()->peerListFullRowsCount()) {
|
||||
setDescriptionText(QString());
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::loadMore(const ReactionId &reaction) {
|
||||
if (reaction.emoji() == u"read"_q) {
|
||||
loadMore(ReactionId());
|
||||
@@ -290,8 +372,8 @@ void Controller::loadMore(const ReactionId &reaction) {
|
||||
| (reaction.empty() ? Flag(0) : Flag::f_reaction);
|
||||
_loadRequestId = _api.request(MTPmessages_GetMessageReactionsList(
|
||||
MTP_flags(flags),
|
||||
_item->history()->peer->input,
|
||||
MTP_int(_item->id),
|
||||
_peer->input,
|
||||
MTP_int(_itemId.msg),
|
||||
Data::ReactionToMTP(reaction),
|
||||
MTP_string(offset),
|
||||
MTP_int(offset.isEmpty() ? kPerPageFirst : kPerPage)
|
||||
@@ -332,7 +414,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto window = _window;
|
||||
const auto peer = row->peer();
|
||||
crl::on_main(window, [=] {
|
||||
window->show(PrepareShortInfoBox(peer, window));
|
||||
window->showPeerInfo(peer);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -353,72 +435,75 @@ std::unique_ptr<PeerListRow> Controller::createRow(
|
||||
_factory,
|
||||
Data::ReactionEntityData(reaction),
|
||||
[=](Row *row) { delegate()->peerListUpdateRow(row); },
|
||||
[=] { return _window->isGifPausedAtLeastFor(
|
||||
[=] { return _window->parentController()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
object_ptr<Ui::BoxContent> FullListBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
Data::ReactionId DefaultSelectedTab(
|
||||
not_null<HistoryItem*> item,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds) {
|
||||
return DefaultSelectedTab(item, {}, std::move(whoReadIds));
|
||||
}
|
||||
|
||||
Data::ReactionId DefaultSelectedTab(
|
||||
not_null<HistoryItem*> item,
|
||||
Data::ReactionId selected,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds) {
|
||||
Expects(IsServerMsgId(item->id));
|
||||
|
||||
if (!ranges::contains(
|
||||
item->reactions(),
|
||||
selected,
|
||||
&Data::MessageReaction::id)) {
|
||||
const auto proj = &Data::MessageReaction::id;
|
||||
if (!ranges::contains(item->reactions(), selected, proj)) {
|
||||
selected = {};
|
||||
}
|
||||
if (selected.empty() && whoReadIds && !whoReadIds->list.empty()) {
|
||||
selected = Data::ReactionId{ u"read"_q };
|
||||
}
|
||||
const auto tabRequests = std::make_shared<
|
||||
rpl::event_stream<Data::ReactionId>>();
|
||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setNoContentMargin(true);
|
||||
return (selected.empty() && whoReadIds && !whoReadIds->list.empty())
|
||||
? Data::ReactionId{ u"read"_q }
|
||||
: selected;
|
||||
}
|
||||
|
||||
auto map = item->reactions();
|
||||
if (whoReadIds && !whoReadIds->list.empty()) {
|
||||
map.push_back({
|
||||
.id = Data::ReactionId{ u"read"_q },
|
||||
.count = int(whoReadIds->list.size()),
|
||||
});
|
||||
}
|
||||
const auto tabs = CreateTabs(
|
||||
box,
|
||||
Data::ReactedMenuFactory(&item->history()->session()),
|
||||
[=] { return window->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer); },
|
||||
map,
|
||||
selected,
|
||||
whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
|
||||
tabs->changes(
|
||||
) | rpl::start_to_stream(*tabRequests, box->lifetime());
|
||||
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
tabs->resizeToWidth(width);
|
||||
tabs->move(0, 0);
|
||||
}, box->lifetime());
|
||||
tabs->heightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
box->setAddedTopScrollSkip(height);
|
||||
}, box->lifetime());
|
||||
box->addButton(tr::lng_close(), [=] {
|
||||
box->closeBox();
|
||||
not_null<Tabs*> CreateReactionsTabs(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> window,
|
||||
FullMsgId itemId,
|
||||
Data::ReactionId selected,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds) {
|
||||
const auto item = window->session().data().message(itemId);
|
||||
auto map = item
|
||||
? item->reactions()
|
||||
: std::vector<Data::MessageReaction>();
|
||||
if (whoReadIds && !whoReadIds->list.empty()) {
|
||||
map.push_back({
|
||||
.id = Data::ReactionId{ u"read"_q },
|
||||
.count = int(whoReadIds->list.size()),
|
||||
});
|
||||
};
|
||||
return Box<PeerListBox>(
|
||||
std::make_unique<Controller>(
|
||||
}
|
||||
return CreateTabs(
|
||||
parent,
|
||||
Data::ReactedMenuFactory(&window->session()),
|
||||
[=] { return window->parentController()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer); },
|
||||
map,
|
||||
selected,
|
||||
whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
|
||||
}
|
||||
|
||||
PreparedFullList FullListController(
|
||||
not_null<Window::SessionNavigation*> window,
|
||||
FullMsgId itemId,
|
||||
Data::ReactionId selected,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds) {
|
||||
Expects(IsServerMsgId(itemId.msg));
|
||||
|
||||
const auto tab = std::make_shared<
|
||||
rpl::event_stream<Data::ReactionId>>();
|
||||
return {
|
||||
.controller = std::make_unique<Controller>(
|
||||
window,
|
||||
item,
|
||||
itemId,
|
||||
selected,
|
||||
tabRequests->events(),
|
||||
tab->events(),
|
||||
whoReadIds),
|
||||
initBox);
|
||||
.switchTab = [=](Data::ReactionId id) { tab->fire_copy(id); },
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace HistoryView::Reactions
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
class HistoryItem;
|
||||
class PeerListController;
|
||||
|
||||
namespace Data {
|
||||
struct ReactionId;
|
||||
@@ -21,6 +22,7 @@ struct WhoReadList;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
@@ -29,10 +31,31 @@ class BoxContent;
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
||||
object_ptr<Ui::BoxContent> FullListBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
[[nodiscard]] Data::ReactionId DefaultSelectedTab(
|
||||
not_null<HistoryItem*> item,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds);
|
||||
|
||||
[[nodiscard]] Data::ReactionId DefaultSelectedTab(
|
||||
not_null<HistoryItem*> item,
|
||||
Data::ReactionId selected,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds = nullptr);
|
||||
|
||||
struct Tabs;
|
||||
[[nodiscard]] not_null<Tabs*> CreateReactionsTabs(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> window,
|
||||
FullMsgId itemId,
|
||||
Data::ReactionId selected,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds);
|
||||
|
||||
struct PreparedFullList {
|
||||
std::unique_ptr<PeerListController> controller;
|
||||
Fn<void(Data::ReactionId)> switchTab;
|
||||
};
|
||||
[[nodiscard]] PreparedFullList FullListController(
|
||||
not_null<Window::SessionNavigation*> window,
|
||||
FullMsgId itemId,
|
||||
Data::ReactionId selected,
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds = nullptr);
|
||||
|
||||
} // namespace HistoryView::Reactions
|
||||
|
||||
@@ -27,7 +27,7 @@ struct Tabs {
|
||||
Fn<rpl::producer<int>()> heightValue;
|
||||
};
|
||||
|
||||
not_null<Tabs*> CreateTabs(
|
||||
[[nodiscard]] not_null<Tabs*> CreateTabs(
|
||||
not_null<QWidget*> parent,
|
||||
Ui::Text::CustomEmojiFactory factory,
|
||||
Fn<bool()> paused,
|
||||
|
||||
@@ -240,7 +240,7 @@ void InnerWidget::fill() {
|
||||
),
|
||||
rpl::duplicate(availableBalanceValue),
|
||||
rpl::duplicate(dateValue),
|
||||
std::move(dateValue) | rpl::map([=](const QDateTime &dt) {
|
||||
rpl::duplicate(dateValue) | rpl::map([=](const QDateTime &dt) {
|
||||
return !dt.isNull() || (!_state.isWithdrawalEnabled);
|
||||
}),
|
||||
rpl::duplicate(availableBalanceValue) | rpl::map([=](uint64 v) {
|
||||
|
||||
@@ -96,6 +96,10 @@ giveawayGiftCodeValue: FlatLabel(defaultFlatLabel) {
|
||||
giveawayGiftCodeValueMultiline: FlatLabel(giveawayGiftCodeValue) {
|
||||
minWidth: 128px;
|
||||
maxHeight: 100px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(10px);
|
||||
linkUnderline: kLinkUnderlineNever;
|
||||
}
|
||||
}
|
||||
giveawayGiftMessage: FlatLabel(giveawayGiftCodeValue) {
|
||||
minWidth: 128px;
|
||||
|
||||
@@ -35,9 +35,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/slider_natural_width.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_dialogs.h" // dialogsSearchTabs
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_premium.h"
|
||||
@@ -430,7 +432,7 @@ void InnerWidget::fill() {
|
||||
#else
|
||||
const auto hasOneTab = (hasBoosts != hasGifts);
|
||||
#endif
|
||||
const auto boostsTabText = tr::lng_boosts_list_title(
|
||||
const auto boostsTabText = tr::lng_giveaway_quantity(
|
||||
tr::now,
|
||||
lt_count,
|
||||
status.firstSliceBoosts.multipliedTotal);
|
||||
@@ -454,8 +456,19 @@ void InnerWidget::fill() {
|
||||
inner,
|
||||
object_ptr<Ui::CustomWidthSlider>(
|
||||
inner,
|
||||
st::defaultTabsSlider)),
|
||||
st::dialogsSearchTabs)),
|
||||
st::boxRowPadding);
|
||||
if (const auto shadow = Ui::CreateChild<Ui::PlainShadow>(inner)) {
|
||||
shadow->show();
|
||||
slider->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
shadow->setGeometry(
|
||||
inner->x(),
|
||||
rect::bottom(r) - shadow->height(),
|
||||
inner->width(),
|
||||
shadow->height());
|
||||
}, shadow->lifetime());
|
||||
}
|
||||
slider->toggle(!hasOneTab, anim::type::instant);
|
||||
|
||||
slider->entity()->addSection(boostsTabText);
|
||||
|
||||
@@ -119,27 +119,6 @@ void ShowMenu(not_null<Ui::GenericBox*> box, const QString &text) {
|
||||
});
|
||||
}
|
||||
|
||||
void AddArrow(not_null<Ui::RpWidget*> parent) {
|
||||
const auto arrow = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
arrow->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(arrow);
|
||||
|
||||
const auto path = Ui::ToggleUpDownArrowPath(
|
||||
st::statisticsShowMoreButtonArrowSize,
|
||||
st::statisticsShowMoreButtonArrowSize,
|
||||
st::statisticsShowMoreButtonArrowSize,
|
||||
st::mainMenuToggleFourStrokes,
|
||||
0.);
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.fillPath(path, st::lightButtonFg);
|
||||
}, arrow->lifetime());
|
||||
arrow->resize(Size(st::statisticsShowMoreButtonArrowSize * 2));
|
||||
arrow->move(st::statisticsShowMoreButtonArrowPosition);
|
||||
arrow->show();
|
||||
}
|
||||
|
||||
void AddHeader(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
tr::phrase<> text) {
|
||||
@@ -833,7 +812,7 @@ void InnerWidget::fill() {
|
||||
Ui::AddSkip(container);
|
||||
}
|
||||
#ifndef _DEBUG
|
||||
if (!channel->amCreator()) {
|
||||
if (channel && !channel->amCreator()) {
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
return;
|
||||
@@ -1346,8 +1325,8 @@ void InnerWidget::fill() {
|
||||
handleSlice(firstSlice);
|
||||
if (!firstSlice.allLoaded) {
|
||||
struct ShowMoreState final {
|
||||
ShowMoreState(not_null<ChannelData*> channel)
|
||||
: api(channel) {
|
||||
ShowMoreState(not_null<PeerData*> peer)
|
||||
: api(peer) {
|
||||
}
|
||||
Api::EarnStatistics api;
|
||||
bool loading = false;
|
||||
@@ -1355,7 +1334,7 @@ void InnerWidget::fill() {
|
||||
rpl::variable<int> showed = 0;
|
||||
};
|
||||
const auto state
|
||||
= lifetime().make_state<ShowMoreState>(channel);
|
||||
= lifetime().make_state<ShowMoreState>(_peer);
|
||||
state->token = firstSlice.token;
|
||||
state->showed = firstSlice.list.size();
|
||||
const auto max = firstSlice.total;
|
||||
@@ -1372,7 +1351,7 @@ void InnerWidget::fill() {
|
||||
) | tr::to_count()),
|
||||
st::statisticsShowMoreButton)));
|
||||
const auto button = wrap->entity();
|
||||
AddArrow(button);
|
||||
Ui::AddToggleUpDownArrowToMoreButton(button);
|
||||
|
||||
wrap->toggle(true, anim::type::instant);
|
||||
const auto handleReceived = [=](
|
||||
|
||||
@@ -470,6 +470,7 @@ infoIconMediaSaved: icon {{ "info/info_media_saved", infoIconFg }};
|
||||
infoIconMediaStoriesArchive: icon {{ "info/info_stories_archive", infoIconFg }};
|
||||
infoIconMediaStoriesRecent: icon {{ "info/info_stories_recent", infoIconFg }};
|
||||
infoIconMediaGifts: icon {{ "menu/gift_premium", infoIconFg, point(4px, 4px) }};
|
||||
infoIconEmojiStatusAccess: icon {{ "menu/read_reactions", infoIconFg, point(4px, 4px) }};
|
||||
|
||||
infoIconShare: icon {{ "info/info_share", infoIconFg }};
|
||||
infoIconEdit: icon {{ "info/info_edit", infoIconFg }};
|
||||
|
||||
@@ -7,27 +7,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "info/info_content_widget.h"
|
||||
|
||||
#include "window/window_session_controller.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "info/profile/info_profile_widget.h"
|
||||
#include "info/media/info_media_widget.h"
|
||||
#include "info/common_groups/info_common_groups_widget.h"
|
||||
#include "info/info_layer_widget.h"
|
||||
#include "info/info_section_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "api/api_who_reacted.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "info/profile/info_profile_widget.h"
|
||||
#include "info/media/info_media_widget.h"
|
||||
#include "info/common_groups/info_common_groups_widget.h"
|
||||
#include "info/info_layer_widget.h"
|
||||
#include "info/info_section_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_profile.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -377,6 +378,8 @@ Key ContentMemento::key() const {
|
||||
return Stories::Tag{ peer, storiesTab() };
|
||||
} else if (const auto peer = statisticsTag().peer) {
|
||||
return statisticsTag();
|
||||
} else if (const auto who = reactionsWhoReadIds()) {
|
||||
return Key(who, _reactionsSelected, _pollReactionsContextId);
|
||||
} else {
|
||||
return Downloads::Tag();
|
||||
}
|
||||
@@ -417,4 +420,15 @@ ContentMemento::ContentMemento(Statistics::Tag statistics)
|
||||
: _statisticsTag(statistics) {
|
||||
}
|
||||
|
||||
ContentMemento::ContentMemento(
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||
FullMsgId contextId,
|
||||
Data::ReactionId selected)
|
||||
: _reactionsWhoReadIds(whoReadIds
|
||||
? whoReadIds
|
||||
: std::make_shared<Api::WhoReadList>())
|
||||
, _reactionsSelected(selected)
|
||||
, _pollReactionsContextId(contextId) {
|
||||
}
|
||||
|
||||
} // namespace Info
|
||||
|
||||
@@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/info_wrap_widget.h"
|
||||
#include "info/statistics/info_statistics_tag.h"
|
||||
|
||||
namespace Api {
|
||||
struct WhoReadList;
|
||||
} // namespace Api
|
||||
|
||||
namespace Dialogs::Stories {
|
||||
struct Content;
|
||||
} // namespace Dialogs::Stories
|
||||
@@ -189,8 +193,12 @@ public:
|
||||
explicit ContentMemento(Statistics::Tag statistics);
|
||||
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
|
||||
: _poll(poll)
|
||||
, _pollContextId(contextId) {
|
||||
, _pollReactionsContextId(contextId) {
|
||||
}
|
||||
ContentMemento(
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||
FullMsgId contextId,
|
||||
Data::ReactionId selected);
|
||||
|
||||
virtual object_ptr<ContentWidget> createWidget(
|
||||
QWidget *parent,
|
||||
@@ -222,7 +230,16 @@ public:
|
||||
return _poll;
|
||||
}
|
||||
FullMsgId pollContextId() const {
|
||||
return _pollContextId;
|
||||
return _poll ? _pollReactionsContextId : FullMsgId();
|
||||
}
|
||||
std::shared_ptr<Api::WhoReadList> reactionsWhoReadIds() const {
|
||||
return _reactionsWhoReadIds;
|
||||
}
|
||||
Data::ReactionId reactionsSelected() const {
|
||||
return _reactionsSelected;
|
||||
}
|
||||
FullMsgId reactionsContextId() const {
|
||||
return _reactionsWhoReadIds ? _pollReactionsContextId : FullMsgId();
|
||||
}
|
||||
Key key() const;
|
||||
|
||||
@@ -264,7 +281,9 @@ private:
|
||||
Stories::Tab _storiesTab = {};
|
||||
Statistics::Tag _statisticsTag;
|
||||
PollData * const _poll = nullptr;
|
||||
const FullMsgId _pollContextId;
|
||||
std::shared_ptr<Api::WhoReadList> _reactionsWhoReadIds;
|
||||
Data::ReactionId _reactionsSelected;
|
||||
const FullMsgId _pollReactionsContextId;
|
||||
|
||||
int _scrollTop = 0;
|
||||
QString _searchFieldQuery;
|
||||
|
||||
@@ -50,6 +50,13 @@ Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
||||
: _value(PollKey{ poll, contextId }) {
|
||||
}
|
||||
|
||||
Key::Key(
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||
Data::ReactionId selected,
|
||||
FullMsgId contextId)
|
||||
: _value(ReactionsKey{ whoReadIds, selected, contextId }) {
|
||||
}
|
||||
|
||||
PeerData *Key::peer() const {
|
||||
if (const auto peer = std::get_if<not_null<PeerData*>>(&_value)) {
|
||||
return *peer;
|
||||
@@ -113,6 +120,27 @@ FullMsgId Key::pollContextId() const {
|
||||
return FullMsgId();
|
||||
}
|
||||
|
||||
std::shared_ptr<Api::WhoReadList> Key::reactionsWhoReadIds() const {
|
||||
if (const auto data = std::get_if<ReactionsKey>(&_value)) {
|
||||
return data->whoReadIds;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Data::ReactionId Key::reactionsSelected() const {
|
||||
if (const auto data = std::get_if<ReactionsKey>(&_value)) {
|
||||
return data->selected;
|
||||
}
|
||||
return Data::ReactionId();
|
||||
}
|
||||
|
||||
FullMsgId Key::reactionsContextId() const {
|
||||
if (const auto data = std::get_if<ReactionsKey>(&_value)) {
|
||||
return data->contextId;
|
||||
}
|
||||
return FullMsgId();
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> AbstractController::mediaSource(
|
||||
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
||||
int limitBefore,
|
||||
@@ -183,6 +211,19 @@ PollData *AbstractController::poll() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto AbstractController::reactionsWhoReadIds() const
|
||||
-> std::shared_ptr<Api::WhoReadList> {
|
||||
return key().reactionsWhoReadIds();
|
||||
}
|
||||
|
||||
Data::ReactionId AbstractController::reactionsSelected() const {
|
||||
return key().reactionsSelected();
|
||||
}
|
||||
|
||||
FullMsgId AbstractController::reactionsContextId() const {
|
||||
return key().reactionsContextId();
|
||||
}
|
||||
|
||||
void AbstractController::showSection(
|
||||
std::shared_ptr<Window::SectionMemento> memento,
|
||||
const Window::SectionShow ¶ms) {
|
||||
@@ -296,6 +337,7 @@ void Controller::updateSearchControllers(
|
||||
: Section::MediaType::kCount;
|
||||
const auto hasMediaSearch = isMedia
|
||||
&& SharedMediaAllowSearch(mediaType);
|
||||
const auto hasRequestsListSearch = (type == Type::RequestsList);
|
||||
const auto hasCommonGroupsSearch = (type == Type::CommonGroups);
|
||||
const auto hasDownloadsSearch = (type == Type::Downloads);
|
||||
const auto hasMembersSearch = (type == Type::Members)
|
||||
@@ -312,6 +354,7 @@ void Controller::updateSearchControllers(
|
||||
_searchController = nullptr;
|
||||
}
|
||||
if (hasMediaSearch
|
||||
|| hasRequestsListSearch
|
||||
|| hasCommonGroupsSearch
|
||||
|| hasDownloadsSearch
|
||||
|| hasMembersSearch) {
|
||||
|
||||
@@ -7,10 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_message_reaction_id.h"
|
||||
#include "data/data_search_controller.h"
|
||||
#include "info/statistics/info_statistics_tag.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace Api {
|
||||
struct WhoReadList;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
class ForumTopic;
|
||||
} // namespace Data
|
||||
@@ -67,6 +72,10 @@ public:
|
||||
Key(Stories::Tag stories);
|
||||
Key(Statistics::Tag statistics);
|
||||
Key(not_null<PollData*> poll, FullMsgId contextId);
|
||||
Key(
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||
Data::ReactionId selected,
|
||||
FullMsgId contextId);
|
||||
|
||||
PeerData *peer() const;
|
||||
Data::ForumTopic *topic() const;
|
||||
@@ -77,12 +86,20 @@ public:
|
||||
Statistics::Tag statisticsTag() const;
|
||||
PollData *poll() const;
|
||||
FullMsgId pollContextId() const;
|
||||
std::shared_ptr<Api::WhoReadList> reactionsWhoReadIds() const;
|
||||
Data::ReactionId reactionsSelected() const;
|
||||
FullMsgId reactionsContextId() const;
|
||||
|
||||
private:
|
||||
struct PollKey {
|
||||
not_null<PollData*> poll;
|
||||
FullMsgId contextId;
|
||||
};
|
||||
struct ReactionsKey {
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds;
|
||||
Data::ReactionId selected;
|
||||
FullMsgId contextId;
|
||||
};
|
||||
std::variant<
|
||||
not_null<PeerData*>,
|
||||
not_null<Data::ForumTopic*>,
|
||||
@@ -90,7 +107,8 @@ private:
|
||||
Downloads::Tag,
|
||||
Stories::Tag,
|
||||
Statistics::Tag,
|
||||
PollKey> _value;
|
||||
PollKey,
|
||||
ReactionsKey> _value;
|
||||
|
||||
};
|
||||
|
||||
@@ -106,6 +124,8 @@ public:
|
||||
Media,
|
||||
CommonGroups,
|
||||
SimilarChannels,
|
||||
RequestsList,
|
||||
ReactionsList,
|
||||
SavedSublists,
|
||||
PeerGifts,
|
||||
Members,
|
||||
@@ -186,6 +206,10 @@ public:
|
||||
[[nodiscard]] FullMsgId pollContextId() const {
|
||||
return key().pollContextId();
|
||||
}
|
||||
[[nodiscard]] auto reactionsWhoReadIds() const
|
||||
-> std::shared_ptr<Api::WhoReadList>;
|
||||
[[nodiscard]] Data::ReactionId reactionsSelected() const;
|
||||
[[nodiscard]] FullMsgId reactionsContextId() const;
|
||||
|
||||
virtual void setSearchEnabledByContent(bool enabled) {
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/saved/info_saved_sublists_widget.h"
|
||||
#include "info/settings/info_settings_widget.h"
|
||||
#include "info/similar_channels/info_similar_channels_widget.h"
|
||||
#include "info/reactions_list/info_reactions_list_widget.h"
|
||||
#include "info/requests_list/info_requests_list_widget.h"
|
||||
#include "info/peer_gifts/info_peer_gifts_widget.h"
|
||||
#include "info/polls/info_polls_results_widget.h"
|
||||
#include "info/info_section_widget.h"
|
||||
@@ -53,6 +55,13 @@ Memento::Memento(not_null<PollData*> poll, FullMsgId contextId)
|
||||
: Memento(DefaultStack(poll, contextId)) {
|
||||
}
|
||||
|
||||
Memento::Memento(
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||
FullMsgId contextId,
|
||||
Data::ReactionId selected)
|
||||
: Memento(DefaultStack(std::move(whoReadIds), contextId, selected)) {
|
||||
}
|
||||
|
||||
Memento::Memento(std::vector<std::shared_ptr<ContentMemento>> stack)
|
||||
: _stack(std::move(stack)) {
|
||||
auto topics = base::flat_set<not_null<Data::ForumTopic*>>();
|
||||
@@ -112,6 +121,18 @@ std::vector<std::shared_ptr<ContentMemento>> Memento::DefaultStack(
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ContentMemento>> Memento::DefaultStack(
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||
FullMsgId contextId,
|
||||
Data::ReactionId selected) {
|
||||
auto result = std::vector<std::shared_ptr<ContentMemento>>();
|
||||
result.push_back(std::make_shared<ReactionsList::Memento>(
|
||||
std::move(whoReadIds),
|
||||
contextId,
|
||||
selected));
|
||||
return result;
|
||||
}
|
||||
|
||||
Section Memento::DefaultSection(not_null<PeerData*> peer) {
|
||||
if (peer->savedSublistsInfo()) {
|
||||
return Section(Section::Type::SavedSublists);
|
||||
@@ -149,6 +170,8 @@ std::shared_ptr<ContentMemento> Memento::DefaultContent(
|
||||
case Section::Type::SimilarChannels:
|
||||
return std::make_shared<SimilarChannels::Memento>(
|
||||
peer->asChannel());
|
||||
case Section::Type::RequestsList:
|
||||
return std::make_shared<RequestsList::Memento>(peer->asChannel());
|
||||
case Section::Type::PeerGifts:
|
||||
return std::make_shared<PeerGifts::Memento>(peer->asUser());
|
||||
case Section::Type::SavedSublists:
|
||||
|
||||
@@ -13,12 +13,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/section_memento.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
namespace Api {
|
||||
struct WhoReadList;
|
||||
} // namespace Api
|
||||
|
||||
namespace Storage {
|
||||
enum class SharedMediaType : signed char;
|
||||
} // namespace Storage
|
||||
|
||||
namespace Data {
|
||||
class ForumTopic;
|
||||
struct ReactionId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
@@ -46,6 +51,10 @@ public:
|
||||
Memento(not_null<Data::ForumTopic*> topic, Section section);
|
||||
Memento(Settings::Tag settings, Section section);
|
||||
Memento(not_null<PollData*> poll, FullMsgId contextId);
|
||||
Memento(
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||
FullMsgId contextId,
|
||||
Data::ReactionId selected);
|
||||
explicit Memento(std::vector<std::shared_ptr<ContentMemento>> stack);
|
||||
|
||||
object_ptr<Window::SectionWidget> createWidget(
|
||||
@@ -91,6 +100,10 @@ private:
|
||||
static std::vector<std::shared_ptr<ContentMemento>> DefaultStack(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId contextId);
|
||||
static std::vector<std::shared_ptr<ContentMemento>> DefaultStack(
|
||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||
FullMsgId contextId,
|
||||
Data::ReactionId selected);
|
||||
|
||||
static std::shared_ptr<ContentMemento> DefaultContent(
|
||||
not_null<PeerData*> peer,
|
||||
|
||||
@@ -24,8 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info {
|
||||
namespace Polls {
|
||||
namespace Info::Polls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kFirstPage = 15;
|
||||
@@ -659,6 +658,4 @@ auto InnerWidget::showPeerInfoRequests() const
|
||||
return _showPeerInfoRequests.events();
|
||||
}
|
||||
|
||||
} // namespace Polls
|
||||
} // namespace Info
|
||||
|
||||
} // namespace Info::Polls
|
||||
|
||||
@@ -16,10 +16,10 @@ class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
|
||||
class Controller;
|
||||
} // namespace Info
|
||||
|
||||
namespace Polls {
|
||||
namespace Info::Polls {
|
||||
|
||||
class Memento;
|
||||
class ListController;
|
||||
@@ -70,5 +70,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace Polls
|
||||
} // namespace Info
|
||||
} // namespace Info::Polls
|
||||
|
||||
@@ -13,8 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_poll.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
namespace Info {
|
||||
namespace Polls {
|
||||
namespace Info::Polls {
|
||||
|
||||
Memento::Memento(not_null<PollData*> poll, FullMsgId contextId)
|
||||
: ContentMemento(poll, contextId) {
|
||||
@@ -113,5 +112,4 @@ void Widget::restoreState(not_null<Memento*> memento) {
|
||||
scrollTopRestore(memento->scrollTop());
|
||||
}
|
||||
|
||||
} // namespace Polls
|
||||
} // namespace Info
|
||||
} // namespace Info::Polls
|
||||
|
||||
@@ -12,8 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
struct PeerListState;
|
||||
|
||||
namespace Info {
|
||||
namespace Polls {
|
||||
namespace Info::Polls {
|
||||
|
||||
class InnerWidget;
|
||||
|
||||
@@ -68,5 +67,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace Polls
|
||||
} // namespace Info
|
||||
} // namespace Info::Polls
|
||||
|
||||
@@ -958,6 +958,7 @@ private:
|
||||
object_ptr<Ui::RpWidget> setupInfo();
|
||||
object_ptr<Ui::RpWidget> setupMuteToggle();
|
||||
void setupMainApp();
|
||||
void setupBotPermissions();
|
||||
void setupMainButtons();
|
||||
Ui::MultiSlideTracker fillTopicButtons();
|
||||
Ui::MultiSlideTracker fillUserButtons(
|
||||
@@ -1865,6 +1866,37 @@ void DetailsFiller::setupMainApp() {
|
||||
Ui::AddSkip(_wrap);
|
||||
}
|
||||
|
||||
void DetailsFiller::setupBotPermissions() {
|
||||
AddSkip(_wrap);
|
||||
AddSubsectionTitle(_wrap, tr::lng_profile_bot_permissions_title());
|
||||
const auto emoji = _wrap->add(
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
_wrap,
|
||||
tr::lng_profile_bot_emoji_status_access(),
|
||||
st::infoSharedMediaButton));
|
||||
object_ptr<Profile::FloatingIcon>(
|
||||
emoji,
|
||||
st::infoIconEmojiStatusAccess,
|
||||
st::infoSharedMediaButtonIconPosition);
|
||||
|
||||
const auto user = _peer->asUser();
|
||||
emoji->toggleOn(
|
||||
rpl::single(bool(user->botInfo->canManageEmojiStatus))
|
||||
)->toggledValue() | rpl::filter([=](bool allowed) {
|
||||
return allowed != user->botInfo->canManageEmojiStatus;
|
||||
}) | rpl::start_with_next([=](bool allowed) {
|
||||
user->botInfo->canManageEmojiStatus = allowed;
|
||||
const auto session = &user->session();
|
||||
session->api().request(MTPbots_ToggleUserEmojiStatusPermission(
|
||||
user->inputUser,
|
||||
MTP_bool(allowed)
|
||||
)).send();
|
||||
}, emoji->lifetime());
|
||||
AddSkip(_wrap);
|
||||
AddDivider(_wrap);
|
||||
AddSkip(_wrap);
|
||||
}
|
||||
|
||||
void DetailsFiller::setupMainButtons() {
|
||||
auto wrapButtons = [=](auto &&callback) {
|
||||
auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap));
|
||||
@@ -2059,6 +2091,9 @@ object_ptr<Ui::RpWidget> DetailsFiller::fill() {
|
||||
if (info->hasMainApp) {
|
||||
setupMainApp();
|
||||
}
|
||||
if (info->canManageEmojiStatus) {
|
||||
setupBotPermissions();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_peer->isSelf()) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user