Compare commits
129 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 | ||
|
|
7e2e510d8a | ||
|
|
1ed34c6fa0 | ||
|
|
78a0fa55b5 | ||
|
|
d37c040b36 | ||
|
|
e56bbf557d | ||
|
|
5abecec478 | ||
|
|
ccb41f778e | ||
|
|
059a4cf0d8 | ||
|
|
7a535a4554 | ||
|
|
f89167ef94 | ||
|
|
a77777f509 | ||
|
|
4a327ba584 | ||
|
|
a41e9bf67e | ||
|
|
6716973ce0 | ||
|
|
7cc81393d6 | ||
|
|
3e413a036f | ||
|
|
63a8fe7ee8 | ||
|
|
146409844d | ||
|
|
ba0da9f59e | ||
|
|
81aef519d4 | ||
|
|
bcd84518d1 | ||
|
|
f205952ff2 | ||
|
|
1d7622e0b5 | ||
|
|
4d9112283d | ||
|
|
dc49c788a8 | ||
|
|
36741ab780 | ||
|
|
53dffc5e88 | ||
|
|
607c7e7777 | ||
|
|
6f09e1699f | ||
|
|
8c35de48f3 | ||
|
|
b83d943841 | ||
|
|
b11b5caeb3 | ||
|
|
36924da59a | ||
|
|
f0a2c47613 | ||
|
|
5a4449f1a2 | ||
|
|
de3d7a7774 | ||
|
|
b06dbd1c00 | ||
|
|
1fa5e424e9 | ||
|
|
d81c7abf1a | ||
|
|
932215c91d | ||
|
|
7aa1141ba5 | ||
|
|
3699439506 | ||
|
|
76b1288f77 | ||
|
|
8fd9ae4e59 | ||
|
|
53abd2fe38 | ||
|
|
da8a4ba8ab | ||
|
|
9c3990c0c1 | ||
|
|
1eeb46d5fc |
@@ -112,6 +112,8 @@ PRIVATE
|
||||
api/api_bot.h
|
||||
api/api_chat_filters.cpp
|
||||
api/api_chat_filters.h
|
||||
api/api_chat_filters_remove_manager.cpp
|
||||
api/api_chat_filters_remove_manager.h
|
||||
api/api_chat_invite.cpp
|
||||
api/api_chat_invite.h
|
||||
api/api_chat_links.cpp
|
||||
@@ -636,6 +638,8 @@ PRIVATE
|
||||
data/data_thread.h
|
||||
data/data_types.cpp
|
||||
data/data_types.h
|
||||
data/data_unread_value.cpp
|
||||
data/data_unread_value.h
|
||||
data/data_user.cpp
|
||||
data/data_user.h
|
||||
data/data_user_photos.cpp
|
||||
@@ -982,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
|
||||
@@ -1032,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
|
||||
@@ -1537,6 +1549,8 @@ PRIVATE
|
||||
ui/widgets/expandable_peer_list.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/widgets/chat_filters_tabs_strip.cpp
|
||||
ui/widgets/chat_filters_tabs_strip.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/dynamic_thumbnails.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";
|
||||
@@ -5099,6 +5137,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_toast_add" = "{chat} added to {folder} folder";
|
||||
"lng_filters_toast_remove" = "{chat} removed from {folder} folder";
|
||||
"lng_filters_shareable_status" = "shareable folder";
|
||||
"lng_filters_view_subtitle" = "Tabs view";
|
||||
"lng_filters_vertical" = "Tabs on the left";
|
||||
"lng_filters_horizontal" = "Tabs at the top";
|
||||
|
||||
"lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder.";
|
||||
"lng_filters_link" = "Share Folder";
|
||||
|
||||
@@ -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.2.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,2,0
|
||||
PRODUCTVERSION 5,7,2,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.2.0"
|
||||
VALUE "FileVersion", "5.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.7.2.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,2,0
|
||||
PRODUCTVERSION 5,7,2,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.2.0"
|
||||
VALUE "FileVersion", "5.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.7.2.0"
|
||||
VALUE "ProductVersion", "5.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -152,6 +153,7 @@ void InitFilterLinkHeader(
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
: rpl::single(0)),
|
||||
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
|
||||
});
|
||||
const auto widget = header.widget;
|
||||
widget->resizeToWidth(st::boxWideWidth);
|
||||
|
||||
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal file
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_chat_filters_remove_manager.h"
|
||||
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
void RemoveChatFilter(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId,
|
||||
std::vector<not_null<PeerData*>> leave) {
|
||||
const auto api = &session->api();
|
||||
session->data().chatsFilters().apply(MTP_updateDialogFilter(
|
||||
MTP_flags(MTPDupdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()));
|
||||
if (leave.empty()) {
|
||||
api->request(MTPmessages_UpdateDialogFilter(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()
|
||||
)).send();
|
||||
} else {
|
||||
api->request(MTPchatlists_LeaveChatlist(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(ranges::views::all(
|
||||
leave
|
||||
) | ranges::views::transform([](not_null<PeerData*> peer) {
|
||||
return MTPInputPeer(peer->input);
|
||||
}) | ranges::to<QVector<MTPInputPeer>>())
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RemoveComplexChatFilter::RemoveComplexChatFilter() = default;
|
||||
|
||||
void RemoveComplexChatFilter::request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id) {
|
||||
const auto session = &weak->session();
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
const auto i = ranges::find(list, id, &Data::ChatFilter::id);
|
||||
const auto filter = (i != end(list)) ? *i : Data::ChatFilter();
|
||||
const auto has = filter.hasMyLinks();
|
||||
const auto confirm = [=](Fn<void()> action, bool onlyWhenHas = false) {
|
||||
if (!has && onlyWhenHas) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
weak->window().show(Ui::MakeConfirmBox({
|
||||
.text = (has
|
||||
? tr::lng_filters_delete_sure()
|
||||
: tr::lng_filters_remove_sure()),
|
||||
.confirmed = [=](Fn<void()> &&close) { close(); action(); },
|
||||
.confirmText = (has
|
||||
? tr::lng_box_delete()
|
||||
: tr::lng_filters_remove_yes()),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
const auto simple = [=] {
|
||||
confirm([=] { RemoveChatFilter(session, id, {}); });
|
||||
};
|
||||
const auto suggestRemoving = Api::ExtractSuggestRemoving(filter);
|
||||
if (suggestRemoving.empty()) {
|
||||
simple();
|
||||
return;
|
||||
} else if (_removingRequestId) {
|
||||
if (_removingId == id) {
|
||||
return;
|
||||
}
|
||||
session->api().request(_removingRequestId).cancel();
|
||||
}
|
||||
_removingId = id;
|
||||
_removingRequestId = session->api().request(
|
||||
MTPchatlists_GetLeaveChatlistSuggestions(
|
||||
MTP_inputChatlistDialogFilter(
|
||||
MTP_int(id)))
|
||||
).done(crl::guard(widget, [=, this](const MTPVector<MTPPeer> &result) {
|
||||
_removingRequestId = 0;
|
||||
const auto suggestRemovePeers = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([=](const MTPPeer &peer) {
|
||||
return session->data().peer(peerFromMTP(peer));
|
||||
}) | ranges::to_vector;
|
||||
const auto chosen = crl::guard(widget, [=](
|
||||
std::vector<not_null<PeerData*>> peers) {
|
||||
RemoveChatFilter(session, id, std::move(peers));
|
||||
});
|
||||
confirm(crl::guard(widget, [=] {
|
||||
Api::ProcessFilterRemove(
|
||||
weak,
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
suggestRemoving,
|
||||
suggestRemovePeers,
|
||||
chosen);
|
||||
}), true);
|
||||
})).fail(crl::guard(widget, [=, this] {
|
||||
_removingRequestId = 0;
|
||||
simple();
|
||||
})).send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal file
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
|
||||
class RemoveComplexChatFilter final {
|
||||
public:
|
||||
RemoveComplexChatFilter();
|
||||
|
||||
void request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id);
|
||||
|
||||
private:
|
||||
FilterId _removingId = 0;
|
||||
mtpRequestId _removingRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -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 {
|
||||
|
||||
@@ -206,7 +206,9 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
content()->selectSkipPage(height(), 1);
|
||||
} else if (e->key() == Qt::Key_PageUp) {
|
||||
content()->selectSkipPage(height(), -1);
|
||||
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
|
||||
} else if (e->key() == Qt::Key_Escape
|
||||
&& _select
|
||||
&& !_select->entity()->getQuery().isEmpty()) {
|
||||
_select->entity()->clearQuery();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
@@ -215,7 +217,19 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
void PeerListBox::searchQueryChanged(const QString &query) {
|
||||
scrollToY(0);
|
||||
content()->searchQueryChanged(query);
|
||||
const auto isEmpty = content()->searchQueryChanged(query);
|
||||
if (_specialTabsMode.enabled) {
|
||||
const auto was = _specialTabsMode.searchIsActive;
|
||||
_specialTabsMode.searchIsActive = !isEmpty;
|
||||
if (was != _specialTabsMode.searchIsActive) {
|
||||
if (_specialTabsMode.searchIsActive) {
|
||||
_specialTabsMode.topSkip = _addedTopScrollSkip;
|
||||
setAddedTopScrollSkip(0);
|
||||
} else {
|
||||
setAddedTopScrollSkip(_specialTabsMode.topSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::resizeEvent(QResizeEvent *e) {
|
||||
@@ -543,6 +557,19 @@ auto PeerListBox::collectSelectedRows()
|
||||
return result;
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerListBox::multiSelectHeightValue() const {
|
||||
return _select ? _select->heightValue() : rpl::single(0);
|
||||
}
|
||||
|
||||
void PeerListBox::setSpecialTabMode(bool value) {
|
||||
content()->setIgnoreHiddenRowsOnSearch(value);
|
||||
if (value) {
|
||||
_specialTabsMode.enabled = true;
|
||||
} else {
|
||||
_specialTabsMode = {};
|
||||
}
|
||||
}
|
||||
|
||||
PeerListRow::PeerListRow(not_null<PeerData*> peer)
|
||||
: PeerListRow(peer, peer->id.value) {
|
||||
}
|
||||
@@ -1385,10 +1412,12 @@ int PeerListContent::labelHeight() const {
|
||||
|
||||
void PeerListContent::refreshRows() {
|
||||
if (!_hiddenRows.empty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
if (!_ignoreHiddenRowsOnSearch || _normalizedSearchQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2050,13 +2079,16 @@ void PeerListContent::checkScrollForPreload() {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::searchQueryChanged(QString query) {
|
||||
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
|
||||
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
||||
const auto normalizedQuery = searchWordsList.join(' ');
|
||||
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
}
|
||||
if (_normalizedSearchQuery != normalizedQuery) {
|
||||
setSearchQuery(query, normalizedQuery);
|
||||
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
|
||||
Assert(_hiddenRows.empty());
|
||||
Assert(_hiddenRows.empty() || _ignoreHiddenRowsOnSearch);
|
||||
|
||||
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
|
||||
for (const auto &searchWord : searchWordsList) {
|
||||
@@ -2104,6 +2136,7 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||
}
|
||||
refreshRows();
|
||||
}
|
||||
return _normalizedSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
||||
@@ -2192,6 +2225,10 @@ void PeerListContent::dragLeft() {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
void PeerListContent::setIgnoreHiddenRowsOnSearch(bool value) {
|
||||
_ignoreHiddenRowsOnSearch = value;
|
||||
}
|
||||
|
||||
void PeerListContent::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
||||
@@ -652,12 +652,15 @@ public:
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
|
||||
void searchQueryChanged(QString query);
|
||||
using IsEmpty = bool;
|
||||
IsEmpty searchQueryChanged(QString query);
|
||||
bool submitted();
|
||||
|
||||
PeerListRowId updateFromParentDrag(QPoint globalPosition);
|
||||
void dragLeft();
|
||||
|
||||
void setIgnoreHiddenRowsOnSearch(bool value);
|
||||
|
||||
// Interface for the controller.
|
||||
void appendRow(std::unique_ptr<PeerListRow> row);
|
||||
void appendSearchRow(std::unique_ptr<PeerListRow> row);
|
||||
@@ -879,6 +882,7 @@ private:
|
||||
int _aboveHeight = 0;
|
||||
int _belowHeight = 0;
|
||||
bool _hideEmpty = false;
|
||||
bool _ignoreHiddenRowsOnSearch = false;
|
||||
object_ptr<Ui::RpWidget> _aboveWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _belowWidget = { nullptr };
|
||||
@@ -1102,6 +1106,9 @@ public:
|
||||
|
||||
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
||||
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
|
||||
|
||||
void setSpecialTabMode(bool value);
|
||||
|
||||
void peerListSetTitle(rpl::producer<QString> title) override {
|
||||
setTitle(std::move(title));
|
||||
@@ -1168,4 +1175,11 @@ private:
|
||||
bool _scrollBottomFixed = false;
|
||||
int _addedTopScrollSkip = 0;
|
||||
|
||||
struct SpecialTabsMode final {
|
||||
bool enabled = false;
|
||||
bool searchIsActive = false;
|
||||
int topSkip = 0;
|
||||
};
|
||||
SpecialTabsMode _specialTabsMode;
|
||||
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -79,7 +79,6 @@ void ShowReportMessageBox(
|
||||
auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
Data::ReportInput reportInput) -> void {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
report(reportInput, [=](const Api::ReportResult &result) {
|
||||
if (!result.error.isEmpty()) {
|
||||
if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
|
||||
@@ -199,6 +198,7 @@ void ShowReportMessageBox(
|
||||
}
|
||||
}));
|
||||
} else if (result.successful) {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
show->showToast(
|
||||
tr::lng_report_thanks(tr::now),
|
||||
kToastDuration);
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/widgets/chat_filters_tabs_strip.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
@@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/share_message_phrase_factory.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -81,11 +83,14 @@ public:
|
||||
void activateSkipColumn(int direction);
|
||||
void activateSkipPage(int pageHeight, int direction);
|
||||
void updateFilter(QString filter = QString());
|
||||
[[nodiscard]] bool isFilterEmpty() const;
|
||||
void selectActive();
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<> searchRequests() const;
|
||||
|
||||
void applyChatFilter(FilterId id);
|
||||
|
||||
protected:
|
||||
void visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
@@ -166,7 +171,9 @@ private:
|
||||
int _upon = -1;
|
||||
int _visibleTop = 0;
|
||||
|
||||
std::unique_ptr<Dialogs::IndexedList> _chatsIndexed;
|
||||
std::unique_ptr<Dialogs::IndexedList> _defaultChatsIndexed;
|
||||
std::unique_ptr<Dialogs::IndexedList> _customChatsIndexed;
|
||||
not_null<Dialogs::IndexedList*> _chatsIndexed;
|
||||
QString _filter;
|
||||
std::vector<not_null<Dialogs::Row*>> _filtered;
|
||||
|
||||
@@ -282,6 +289,10 @@ void ShareBox::prepare() {
|
||||
|
||||
_select->setQueryChangedCallback([=](const QString &query) {
|
||||
applyFilterUpdate(query);
|
||||
if (_chatsFilters) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}
|
||||
});
|
||||
_select->setItemRemovedCallback([=](uint64 itemId) {
|
||||
if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) {
|
||||
@@ -337,10 +348,32 @@ void ShareBox::prepare() {
|
||||
{ .suggestCustomEmoji = true });
|
||||
|
||||
_select->raise();
|
||||
|
||||
{
|
||||
const auto chatsFilters = AddChatFiltersTabsStrip(
|
||||
this,
|
||||
_descriptor.session,
|
||||
[this](FilterId id) {
|
||||
_inner->applyChatFilter(id);
|
||||
scrollToY(0);
|
||||
});
|
||||
chatsFilters->lower();
|
||||
chatsFilters->heightValue() | rpl::start_with_next([this](int h) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}, lifetime());
|
||||
_select->heightValue() | rpl::start_with_next([=](int h) {
|
||||
chatsFilters->moveToLeft(0, h);
|
||||
}, chatsFilters->lifetime());
|
||||
_chatsFilters = chatsFilters;
|
||||
}
|
||||
}
|
||||
|
||||
int ShareBox::getTopScrollSkip() const {
|
||||
return _select->isHidden() ? 0 : _select->height();
|
||||
return (_select->isHidden() ? 0 : _select->height())
|
||||
+ ((_chatsFilters && _inner && _inner->isFilterEmpty())
|
||||
? _chatsFilters->height()
|
||||
: 0);
|
||||
}
|
||||
|
||||
int ShareBox::getBottomScrollSkip() const {
|
||||
@@ -671,9 +704,10 @@ ShareBox::Inner::Inner(
|
||||
, _descriptor(descriptor)
|
||||
, _show(std::move(show))
|
||||
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
|
||||
, _chatsIndexed(
|
||||
, _defaultChatsIndexed(
|
||||
std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add)) {
|
||||
Dialogs::SortMode::Add))
|
||||
, _chatsIndexed(_defaultChatsIndexed.get()) {
|
||||
_rowsTop = st::shareRowsTop;
|
||||
_rowHeight = st::shareRowHeight;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
@@ -691,7 +725,7 @@ ShareBox::Inner::Inner(
|
||||
const auto self = _descriptor.session->user();
|
||||
const auto selfHistory = self->owner().history(self);
|
||||
if (_descriptor.filterCallback(selfHistory)) {
|
||||
_chatsIndexed->addToEnd(selfHistory);
|
||||
_defaultChatsIndexed->addToEnd(selfHistory);
|
||||
}
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
@@ -699,7 +733,7 @@ ShareBox::Inner::Inner(
|
||||
if (!history->peer->isSelf()
|
||||
&& (history->asForum()
|
||||
|| _descriptor.filterCallback(history))) {
|
||||
_chatsIndexed->addToEnd(history);
|
||||
_defaultChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -722,7 +756,7 @@ ShareBox::Inner::Inner(
|
||||
|
||||
_descriptor.session->changes().realtimeNameUpdates(
|
||||
) | rpl::start_with_next([=](const Data::NameUpdate &update) {
|
||||
_chatsIndexed->peerNameChanged(
|
||||
_defaultChatsIndexed->peerNameChanged(
|
||||
update.peer,
|
||||
update.oldFirstLetters);
|
||||
}, lifetime());
|
||||
@@ -1331,6 +1365,10 @@ void ShareBox::Inner::updateFilter(QString filter) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ShareBox::Inner::isFilterEmpty() const {
|
||||
return _filter.isEmpty();
|
||||
}
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> ShareBox::Inner::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
@@ -1339,6 +1377,30 @@ rpl::producer<> ShareBox::Inner::searchRequests() const {
|
||||
return _searchRequests.events();
|
||||
}
|
||||
|
||||
void ShareBox::Inner::applyChatFilter(FilterId id) {
|
||||
if (!id) {
|
||||
_chatsIndexed = _defaultChatsIndexed.get();
|
||||
} else {
|
||||
_customChatsIndexed = std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add);
|
||||
_chatsIndexed = _customChatsIndexed.get();
|
||||
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
if (const auto history = row->history()) {
|
||||
if (history->asForum()
|
||||
|| _descriptor.filterCallback(history)) {
|
||||
_customChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto &data = _descriptor.session->data();
|
||||
addList(data.chatsFilters().chatsList(id)->indexed());
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ShareBox::Inner::peopleReceived(
|
||||
const QString &query,
|
||||
const QVector<MTPPeer> &my,
|
||||
|
||||
@@ -174,6 +174,8 @@ private:
|
||||
bool _peopleFull = false;
|
||||
mtpRequestId _peopleRequest = 0;
|
||||
|
||||
RpWidget *_chatsFilters = nullptr;
|
||||
|
||||
using PeopleCache = QMap<QString, MTPcontacts_Found>;
|
||||
PeopleCache _peopleCache;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -33,6 +33,10 @@ base::options::toggle TabbedPanelShowOnClick({
|
||||
|
||||
const char kOptionTabbedPanelShowOnClick[] = "tabbed-panel-show-on-click";
|
||||
|
||||
bool ShowPanelOnClick() {
|
||||
return TabbedPanelShowOnClick.value();
|
||||
}
|
||||
|
||||
TabbedPanel::TabbedPanel(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace ChatHelpers {
|
||||
class TabbedSelector;
|
||||
|
||||
extern const char kOptionTabbedPanelShowOnClick[];
|
||||
[[nodiscard]] bool ShowPanelOnClick();
|
||||
|
||||
struct TabbedPanelDescriptor {
|
||||
Window::SessionController *regularWindow = nullptr;
|
||||
|
||||
@@ -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)
|
||||
@@ -396,11 +396,12 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
|
||||
<< _tonsiteStorageToken
|
||||
<< qint32(_includeMutedCounterFolders ? 1 : 0)
|
||||
<< qint32(0) // Old IV zoom
|
||||
<< qint32(_chatFiltersHorizontal.current() ? 1 : 0)
|
||||
<< 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);
|
||||
@@ -528,6 +529,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 skipToastsInFocus = _skipToastsInFocus ? 1 : 0;
|
||||
qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0;
|
||||
quint32 videoQuality = SerializeVideoQuality(_videoQuality);
|
||||
quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -609,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()) {
|
||||
@@ -838,8 +841,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
stream >> includeMutedCounterFolders;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
qint32 oldIvZoom = 0;
|
||||
stream >> oldIvZoom;
|
||||
stream >> chatFiltersHorizontal;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> skipToastsInFocus;
|
||||
@@ -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()"));
|
||||
@@ -1068,6 +1073,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_skipToastsInFocus = (skipToastsInFocus == 1);
|
||||
_recordVideoMessages = (recordVideoMessages == 1);
|
||||
_videoQuality = DeserializeVideoQuality(videoQuality);
|
||||
_chatFiltersHorizontal = (chatFiltersHorizontal == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
@@ -1452,13 +1458,13 @@ void Settings::resetOnLastLogout() {
|
||||
_thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
|
||||
_notifyFromAll = true;
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
_storiesClickTooltipHidden = false;
|
||||
_ttlVoiceClickTooltipHidden = false;
|
||||
_ivZoom = 100;
|
||||
_recordVideoMessages = false;
|
||||
_videoQuality = {};
|
||||
_chatFiltersHorizontal = false;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
@@ -1634,4 +1640,16 @@ void Settings::setVideoQuality(Media::VideoQuality value) {
|
||||
_videoQuality = value;
|
||||
}
|
||||
|
||||
bool Settings::chatFiltersHorizontal() const {
|
||||
return _chatFiltersHorizontal.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> Settings::chatFiltersHorizontalChanges() const {
|
||||
return _chatFiltersHorizontal.changes();
|
||||
}
|
||||
|
||||
void Settings::setChatFiltersHorizontal(bool value) {
|
||||
_chatFiltersHorizontal = value;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -929,6 +929,10 @@ public:
|
||||
[[nodiscard]] rpl::producer<int> ivZoomValue() const;
|
||||
void setIvZoom(int value);
|
||||
|
||||
[[nodiscard]] bool chatFiltersHorizontal() const;
|
||||
[[nodiscard]] rpl::producer<bool> chatFiltersHorizontalChanges() const;
|
||||
void setChatFiltersHorizontal(bool value);
|
||||
|
||||
[[nodiscard]] Media::VideoQuality videoQuality() const;
|
||||
void setVideoQuality(Media::VideoQuality quality);
|
||||
|
||||
@@ -1032,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;
|
||||
@@ -1070,6 +1074,7 @@ private:
|
||||
QByteArray _tonsiteStorageToken;
|
||||
rpl::variable<int> _ivZoom = 100;
|
||||
Media::VideoQuality _videoQuality;
|
||||
rpl::variable<bool> _chatFiltersHorizontal = false;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
@@ -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 = 5007002;
|
||||
constexpr auto AppVersionStr = "5.7.2";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ void SponsoredMessages::clearOldRequests() {
|
||||
|
||||
SponsoredMessages::AppendResult SponsoredMessages::append(
|
||||
not_null<History*> history) {
|
||||
if (isTopBarFor(history)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
|
||||
@@ -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;
|
||||
|
||||
68
Telegram/SourceFiles/data/data_unread_value.cpp
Normal file
68
Telegram/SourceFiles/data/data_unread_value.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_unread_value.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
rpl::producer<Dialogs::UnreadState> MainListUnreadState(
|
||||
not_null<Dialogs::MainList*> list) {
|
||||
return rpl::single(rpl::empty) | rpl::then(
|
||||
list->unreadStateChanges() | rpl::to_empty
|
||||
) | rpl::map([=] {
|
||||
return list->unreadState();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state) {
|
||||
const auto folderId = Data::Folder::kId;
|
||||
if (const auto folder = session->data().folderLoaded(folderId)) {
|
||||
return state - folder->chatsList()->unreadState();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId) {
|
||||
if (filterId > 0) {
|
||||
const auto filters = &session->data().chatsFilters();
|
||||
return MainListUnreadState(filters->chatsList(filterId));
|
||||
}
|
||||
return MainListUnreadState(
|
||||
session->data().chatsList()
|
||||
) | rpl::map([=](const Dialogs::UnreadState &state) {
|
||||
return MainListMapUnreadState(session, state);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<bool> IncludeMutedCounterFoldersValue() {
|
||||
using namespace Window::Notifications;
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
Core::App().notifications().settingsChanged(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == ChangeType::IncludeMuted
|
||||
) | rpl::to_empty
|
||||
) | rpl::map([] {
|
||||
return Core::App().settings().includeMutedCounterFolders();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
30
Telegram/SourceFiles/data/data_unread_value.h
Normal file
30
Telegram/SourceFiles/data/data_unread_value.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Dialogs {
|
||||
struct UnreadState;
|
||||
} // namespace Dialogs
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
[[nodiscard]] Dialogs::UnreadState MainListMapUnreadState(
|
||||
not_null<Main::Session*> session,
|
||||
const Dialogs::UnreadState &state);
|
||||
|
||||
[[nodiscard]] rpl::producer<Dialogs::UnreadState> UnreadStateValue(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> IncludeMutedCounterFoldersValue();
|
||||
|
||||
} // namespace Data
|
||||
@@ -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;
|
||||
|
||||
@@ -11,13 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/ui/chat_search_empty.h"
|
||||
#include "dialogs/ui/chat_search_in.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
#include "dialogs/ui/dialogs_video_userpic.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/dialogs_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "dialogs/dialogs_search_tags.h"
|
||||
#include "history/view/history_view_chat_preview.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -44,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
@@ -68,7 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/loading_element.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "boxes/peers/edit_forum_topic_box.h"
|
||||
|
||||
@@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_inner_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/history_view_top_bar_widget.h"
|
||||
@@ -26,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/chat_filters_tabs_strip.h"
|
||||
#include "ui/widgets/elastic_scroll.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
@@ -46,14 +46,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session_settings.h"
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_slide_animation.h"
|
||||
@@ -655,6 +651,12 @@ Widget::Widget(
|
||||
setupMoreChatsBar();
|
||||
setupDownloadBar();
|
||||
}
|
||||
|
||||
if (session().settings().dialogsFiltersEnabled()
|
||||
&& (Core::App().settings().chatFiltersHorizontal()
|
||||
|| !controller->enoughSpaceForFilters())) {
|
||||
toggleFiltersMenu(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::chosenRow(const ChosenRow &row) {
|
||||
@@ -1227,6 +1229,9 @@ void Widget::updateControlsVisibility(bool fast) {
|
||||
if (_moreChatsBar) {
|
||||
_moreChatsBar->show();
|
||||
}
|
||||
if (_chatFilters) {
|
||||
_chatFilters->show();
|
||||
}
|
||||
if (_openedFolder || _openedForum) {
|
||||
_subsectionTopBar->show();
|
||||
if (_forumTopShadow) {
|
||||
@@ -1296,6 +1301,60 @@ 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) {
|
||||
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);
|
||||
raw->resizeToWidth(width());
|
||||
const auto shadow = Ui::CreateChild<Ui::PlainShadow>(raw);
|
||||
shadow->show();
|
||||
inner->sizeValue() | rpl::start_with_next([=, this](const QSize &s) {
|
||||
raw->resize(s);
|
||||
shadow->setGeometry(
|
||||
0,
|
||||
s.height() - shadow->height(),
|
||||
s.width(),
|
||||
shadow->height());
|
||||
updateControlsGeometry();
|
||||
}, _chatFilters->lifetime());
|
||||
updateControlsGeometry();
|
||||
} else {
|
||||
_chatFilters = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Widget::cancelSearchByMouseBack() {
|
||||
return _searchHasFocus
|
||||
&& !_searchSuggestionsLocked
|
||||
@@ -1845,6 +1904,12 @@ void Widget::scrollToDefault(bool verytop) {
|
||||
this,
|
||||
QPoint(),
|
||||
QRect(0, top, wideGeometry.width(), skip));
|
||||
if (_chatFilters) {
|
||||
Ui::RenderWidget(
|
||||
p,
|
||||
_chatFilters,
|
||||
QPoint(0, skip - _chatFilters->height()));
|
||||
}
|
||||
Ui::RenderWidget(p, _scroll, QPoint(0, skip));
|
||||
}
|
||||
if (scrollGeometry != wideGeometry) {
|
||||
@@ -1860,6 +1925,9 @@ void Widget::startWidthAnimation() {
|
||||
}
|
||||
_widthAnimationCache = grabNonNarrowScrollFrame();
|
||||
_scroll->hide();
|
||||
if (_chatFilters) {
|
||||
_chatFilters->hide();
|
||||
}
|
||||
updateStoriesVisibility();
|
||||
}
|
||||
|
||||
@@ -1867,6 +1935,9 @@ void Widget::stopWidthAnimation() {
|
||||
_widthAnimationCache = QPixmap();
|
||||
if (!_showAnimation) {
|
||||
_scroll->setVisible(!_suggestions);
|
||||
if (_chatFilters) {
|
||||
_chatFilters->setVisible(!_suggestions);
|
||||
}
|
||||
}
|
||||
updateStoriesVisibility();
|
||||
update();
|
||||
@@ -1961,6 +2032,9 @@ void Widget::startSlideAnimation(
|
||||
if (_moreChatsBar) {
|
||||
_moreChatsBar->hide();
|
||||
}
|
||||
if (_chatFilters) {
|
||||
_chatFilters->hide();
|
||||
}
|
||||
if (_forumTopShadow) {
|
||||
_forumTopShadow->hide();
|
||||
}
|
||||
@@ -3085,6 +3159,9 @@ bool Widget::applySearchState(SearchState state) {
|
||||
const auto tagsChanged = (_searchState.tags != state.tags);
|
||||
const auto queryChanged = (_searchState.query != state.query);
|
||||
const auto tabChanged = (_searchState.tab != state.tab);
|
||||
const auto queryEmptyChanged = queryChanged
|
||||
? (_searchState.query.isEmpty() != state.query.isEmpty())
|
||||
: false;
|
||||
|
||||
if (forum) {
|
||||
if (_openedForum == forum) {
|
||||
@@ -3120,6 +3197,10 @@ bool Widget::applySearchState(SearchState state) {
|
||||
? peer->owner().history(migrateFrom).get()
|
||||
: nullptr;
|
||||
_searchState = state;
|
||||
if (_chatFilters && queryEmptyChanged) {
|
||||
_chatFilters->setVisible(_searchState.query.isEmpty());
|
||||
updateControlsGeometry();
|
||||
}
|
||||
_searchWithPostsPreview = computeSearchWithPostsPreview();
|
||||
if (queryChanged) {
|
||||
updateLockUnlockVisibility(anim::type::normal);
|
||||
@@ -3507,6 +3588,9 @@ void Widget::updateControlsGeometry() {
|
||||
if (_forumRequestsBar) {
|
||||
_forumRequestsBar->resizeToWidth(barw);
|
||||
}
|
||||
if (_chatFilters) {
|
||||
_chatFilters->resizeToWidth(barw);
|
||||
}
|
||||
_updateScrollGeometryCached = [=] {
|
||||
const auto moreChatsBarTop = expandedStoriesTop
|
||||
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
|
||||
@@ -3528,8 +3612,15 @@ void Widget::updateControlsGeometry() {
|
||||
if (_forumReportBar) {
|
||||
_forumReportBar->bar().move(0, forumReportTop);
|
||||
}
|
||||
const auto scrollTop = forumReportTop
|
||||
const auto chatFiltersTop = forumReportTop
|
||||
+ (_forumReportBar ? _forumReportBar->bar().height() : 0);
|
||||
if (_chatFilters) {
|
||||
_chatFilters->move(0, chatFiltersTop);
|
||||
}
|
||||
const auto scrollTop = chatFiltersTop
|
||||
+ ((_chatFilters && _searchState.query.isEmpty())
|
||||
? (_chatFilters->height() * (1. - narrowRatio))
|
||||
: 0);
|
||||
const auto scrollHeight = height() - scrollTop - bottomSkip;
|
||||
const auto wasScrollHeight = _scroll->height();
|
||||
_scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight);
|
||||
|
||||
@@ -129,6 +129,7 @@ public:
|
||||
[[nodiscard]] RowDescriptor resolveChatNext(RowDescriptor from = {}) const;
|
||||
[[nodiscard]] RowDescriptor resolveChatPrevious(RowDescriptor from = {}) const;
|
||||
void updateHasFocus(not_null<QWidget*> focused);
|
||||
void toggleFiltersMenu(bool value);
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e) override;
|
||||
@@ -295,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 };
|
||||
@@ -317,6 +318,8 @@ private:
|
||||
std::unique_ptr<Ui::RequestsBar> _forumRequestsBar;
|
||||
std::unique_ptr<HistoryView::ContactStatus> _forumReportBar;
|
||||
|
||||
base::unique_qptr<Ui::RpWidget> _chatFilters;
|
||||
|
||||
object_ptr<Ui::ElasticScroll> _scroll;
|
||||
QPointer<InnerWidget> _inner;
|
||||
std::unique_ptr<Suggestions> _suggestions;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -666,7 +666,8 @@ bool InnerWidget::elementUnderCursor(
|
||||
return (Element::Hovered() == view);
|
||||
}
|
||||
|
||||
HistoryView::SelectionModeResult InnerWidget::elementInSelectionMode() {
|
||||
HistoryView::SelectionModeResult InnerWidget::elementInSelectionMode(
|
||||
const HistoryView::Element *) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,8 @@ public:
|
||||
HistoryView::Context elementContext() override;
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
HistoryView::SelectionModeResult elementInSelectionMode() override;
|
||||
HistoryView::SelectionModeResult elementInSelectionMode(
|
||||
const HistoryView::Element *view) override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
int from,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -155,7 +155,11 @@ public:
|
||||
not_null<const Element*> view) override {
|
||||
return (Element::Moused() == view);
|
||||
}
|
||||
HistoryView::SelectionModeResult elementInSelectionMode() override {
|
||||
HistoryView::SelectionModeResult elementInSelectionMode(
|
||||
const Element *view) override {
|
||||
if (view && view->data()->isSponsored()) {
|
||||
return HistoryView::SelectionModeResult();
|
||||
}
|
||||
return _widget
|
||||
? _widget->inSelectionMode()
|
||||
: HistoryView::SelectionModeResult();
|
||||
@@ -2431,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),
|
||||
|
||||
@@ -198,7 +198,8 @@ void SetupSwipeHandler(
|
||||
const auto &touches = t->touchPoints();
|
||||
const auto released = [&](int index) {
|
||||
return (touches.size() > index)
|
||||
&& (touches.at(index).state() & Qt::TouchPointReleased);
|
||||
&& (int(touches.at(index).state())
|
||||
& int(Qt::TouchPointReleased));
|
||||
};
|
||||
const auto cancel = released(0)
|
||||
|| released(1)
|
||||
|
||||
@@ -421,8 +421,16 @@ HistoryWidget::HistoryWidget(
|
||||
initTabbedSelector();
|
||||
|
||||
_attachToggle->setClickedCallback([=] {
|
||||
const auto toggle = _attachBotsMenu && _attachBotsMenu->isHidden();
|
||||
base::call_delayed(st::historyAttach.ripple.hideDuration, this, [=] {
|
||||
chooseAttach();
|
||||
if (_attachBotsMenu && toggle) {
|
||||
_attachBotsMenu->showAnimated();
|
||||
} else {
|
||||
chooseAttach();
|
||||
if (_attachBotsMenu) {
|
||||
_attachBotsMenu->hideAnimated();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -861,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();
|
||||
}
|
||||
@@ -2574,6 +2582,8 @@ void HistoryWidget::showHistory(
|
||||
}
|
||||
}));
|
||||
checkState();
|
||||
} else {
|
||||
requestSponsoredMessageBar();
|
||||
}
|
||||
} else {
|
||||
_chooseForReport = nullptr;
|
||||
@@ -2611,7 +2621,7 @@ void HistoryWidget::setHistory(History *history) {
|
||||
if (was && !now) {
|
||||
_attachToggle->removeEventFilter(_attachBotsMenu.get());
|
||||
_attachBotsMenu->hideFast();
|
||||
} else if (now && !was) {
|
||||
} else if (now && !was && !ChatHelpers::ShowPanelOnClick()) {
|
||||
_attachToggle->installEventFilter(_attachBotsMenu.get());
|
||||
}
|
||||
|
||||
@@ -2699,7 +2709,9 @@ void HistoryWidget::refreshAttachBotsMenu() {
|
||||
}
|
||||
_attachBotsMenu->setOrigin(
|
||||
Ui::PanelAnimation::Origin::BottomLeft);
|
||||
_attachToggle->installEventFilter(_attachBotsMenu.get());
|
||||
if (!ChatHelpers::ShowPanelOnClick()) {
|
||||
_attachToggle->installEventFilter(_attachBotsMenu.get());
|
||||
}
|
||||
_attachBotsMenu->heightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
moveFieldControls();
|
||||
@@ -3428,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));
|
||||
}
|
||||
@@ -4521,7 +4533,6 @@ void HistoryWidget::showFinished() {
|
||||
_showAnimation = nullptr;
|
||||
doneShow();
|
||||
synteticScrollToY(_scroll->scrollTop());
|
||||
requestSponsoredMessageBar();
|
||||
}
|
||||
|
||||
void HistoryWidget::doneShow() {
|
||||
@@ -5100,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)) {
|
||||
@@ -7690,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();
|
||||
|
||||
@@ -111,7 +111,8 @@ bool DefaultElementDelegate::elementUnderCursor(
|
||||
return false;
|
||||
}
|
||||
|
||||
SelectionModeResult DefaultElementDelegate::elementInSelectionMode() {
|
||||
SelectionModeResult DefaultElementDelegate::elementInSelectionMode(
|
||||
const Element *view) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ class ElementDelegate {
|
||||
public:
|
||||
virtual Context elementContext() = 0;
|
||||
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
|
||||
virtual SelectionModeResult elementInSelectionMode() = 0;
|
||||
virtual SelectionModeResult elementInSelectionMode(
|
||||
const Element *view) = 0;
|
||||
virtual bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
int from,
|
||||
@@ -136,7 +137,7 @@ public:
|
||||
class DefaultElementDelegate : public ElementDelegate {
|
||||
public:
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
SelectionModeResult elementInSelectionMode() override;
|
||||
SelectionModeResult elementInSelectionMode(const Element *view) override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
int from,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1782,7 +1788,11 @@ bool ListWidget::elementUnderCursor(
|
||||
return (_overElement == view);
|
||||
}
|
||||
|
||||
SelectionModeResult ListWidget::elementInSelectionMode() {
|
||||
SelectionModeResult ListWidget::elementInSelectionMode(
|
||||
const HistoryView::Element *view) {
|
||||
if (view && !_delegate->listIsItemGoodForSelection(view->data())) {
|
||||
return {};
|
||||
}
|
||||
return inSelectionMode();
|
||||
}
|
||||
|
||||
@@ -2790,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
|
||||
@@ -2804,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());
|
||||
@@ -3564,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) {
|
||||
@@ -3625,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;
|
||||
@@ -3723,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;
|
||||
@@ -3734,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;
|
||||
@@ -391,7 +398,7 @@ public:
|
||||
// ElementDelegate interface.
|
||||
Context elementContext() override;
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
SelectionModeResult elementInSelectionMode() override;
|
||||
SelectionModeResult elementInSelectionMode(const Element *view) override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
int from,
|
||||
@@ -808,6 +815,7 @@ private:
|
||||
CursorState _mouseCursorState = CursorState();
|
||||
uint16 _mouseTextSymbol = 0;
|
||||
bool _pressWasInactive = false;
|
||||
bool _overSenderUserpic = false;
|
||||
|
||||
bool _selectEnabled = false;
|
||||
HistoryItem *_selectedTextItem = nullptr;
|
||||
|
||||
@@ -1100,7 +1100,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||
if (hasGesture) {
|
||||
p.translate(context.gestureHorizontal.translation, 0);
|
||||
}
|
||||
const auto selectionModeResult = delegate()->elementInSelectionMode();
|
||||
const auto selectionModeResult = delegate()->elementInSelectionMode(this);
|
||||
const auto selectionTranslation = (selectionModeResult.progress > 0)
|
||||
? (selectionModeResult.progress
|
||||
* AdditionalSpaceForSelectionCheckbox(this, g))
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -3868,7 +3876,7 @@ bool Message::displayFastReply() const {
|
||||
return hasFastReply()
|
||||
&& data()->isRegular()
|
||||
&& canSendAnything()
|
||||
&& !delegate()->elementInSelectionMode().inSelectionMode;
|
||||
&& !delegate()->elementInSelectionMode(this).inSelectionMode;
|
||||
}
|
||||
|
||||
bool Message::displayRightActionComments() const {
|
||||
@@ -4032,7 +4040,7 @@ void Message::drawRightAction(
|
||||
|
||||
ClickHandlerPtr Message::rightActionLink(
|
||||
std::optional<QPoint> pressPoint) const {
|
||||
if (delegate()->elementInSelectionMode().progress > 0) {
|
||||
if (delegate()->elementInSelectionMode(this).progress > 0) {
|
||||
return nullptr;
|
||||
}
|
||||
ensureRightAction();
|
||||
|
||||
@@ -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"
|
||||
@@ -898,7 +899,7 @@ void RepliesWidget::setupSwipeReply() {
|
||||
}
|
||||
}, [=, show = controller()->uiShow()](int cursorTop) {
|
||||
auto result = HistoryView::SwipeHandlerFinishData();
|
||||
if (_inner->elementInSelectionMode().inSelectionMode) {
|
||||
if (_inner->elementInSelectionMode(nullptr).inSelectionMode) {
|
||||
return result;
|
||||
}
|
||||
const auto view = _inner->lookupItemByY(cursorTop);
|
||||
@@ -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);
|
||||
@@ -663,8 +667,8 @@ QSize WebPage::countCurrentSize(int newWidth) {
|
||||
const auto stickerSet = stickerSetData();
|
||||
const auto factcheck = factcheckData();
|
||||
const auto sponsored = sponsoredData();
|
||||
const auto specialRightPix = ((sponsored && !sponsored->hasMedia)
|
||||
|| stickerSet);
|
||||
const auto specialRightPix = (stickerSet
|
||||
|| (sponsored && !sponsored->hasMedia && _data->photo));
|
||||
const auto lineHeight = UnitedLineHeight();
|
||||
const auto factcheckMetrics = factcheck
|
||||
? computeFactcheckMetrics(_description.countHeight(innerWidth))
|
||||
@@ -678,7 +682,7 @@ QSize WebPage::countCurrentSize(int newWidth) {
|
||||
}
|
||||
const auto linesMax = factcheck
|
||||
? (factcheckMetrics.lines + 1)
|
||||
: (specialRightPix || isLogEntryOriginal())
|
||||
: (sponsored || isLogEntryOriginal())
|
||||
? kMaxOriginalEntryLines
|
||||
: 5;
|
||||
const auto siteNameHeight = _siteNameLines ? lineHeight : 0;
|
||||
@@ -711,7 +715,9 @@ QSize WebPage::countCurrentSize(int newWidth) {
|
||||
newHeight += _titleLines * lineHeight;
|
||||
}
|
||||
|
||||
const auto descriptionHeight = _description.countHeight(wleft);
|
||||
const auto descriptionHeight = _description.countHeight(sponsored
|
||||
? innerWidth
|
||||
: wleft);
|
||||
const auto restLines = (linesMax - _siteNameLines - _titleLines);
|
||||
if (descriptionHeight < restLines * descriptionLineHeight) {
|
||||
// We have height for all the lines.
|
||||
@@ -1226,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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1510,7 +1517,7 @@ void WebPage::clickHandlerPressedChanged(
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (p == _openl) {
|
||||
if ((p == _openl) || (sponsoredData() && sponsoredData()->link == p)) {
|
||||
if (pressed) {
|
||||
if (!_ripple) {
|
||||
const auto full = Rect(currentSize());
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user