Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4659d5db5d | ||
|
|
af061125dd | ||
|
|
5c4b1f6638 | ||
|
|
ee3d70f879 | ||
|
|
7dadaa1b28 | ||
|
|
a72782e232 | ||
|
|
8654ffb6fb | ||
|
|
90e445eec9 | ||
|
|
910b6d8879 | ||
|
|
8d1c2f832d | ||
|
|
158d2a4124 | ||
|
|
66473738d6 | ||
|
|
6a43107bb2 | ||
|
|
28e7afa412 | ||
|
|
8dc151e14d | ||
|
|
a330a3f2eb | ||
|
|
8f7195d3b2 | ||
|
|
a4e4502d50 | ||
|
|
902da90100 | ||
|
|
d775760f98 | ||
|
|
dfb6600104 | ||
|
|
41ed487d5e | ||
|
|
d156de05a5 | ||
|
|
f4582ddf36 | ||
|
|
7f7b764f7b | ||
|
|
dd8fdfc3d4 | ||
|
|
6c80d443b9 | ||
|
|
cd05586d51 | ||
|
|
dfc1ec3ccf | ||
|
|
ffe6786ad1 | ||
|
|
6068678fa1 | ||
|
|
50b761fab2 | ||
|
|
0d43f16db2 | ||
|
|
3278de9ba1 | ||
|
|
abe1962002 | ||
|
|
5b15f377cd | ||
|
|
d0e5ea78a5 | ||
|
|
853757e611 | ||
|
|
c3860cfe72 | ||
|
|
90b2c077a6 | ||
|
|
fdce4bada7 | ||
|
|
4c8ff1c7ec | ||
|
|
2a153214f6 | ||
|
|
d7c964afc5 | ||
|
|
5943052cd1 | ||
|
|
8512154b45 | ||
|
|
0e5419c60b | ||
|
|
1d26482298 | ||
|
|
126749f04c | ||
|
|
e0e69ce740 | ||
|
|
72b57924b7 | ||
|
|
fdbdeeb956 | ||
|
|
3dbdecf73d | ||
|
|
7dc8943840 | ||
|
|
646b852717 | ||
|
|
075f754a71 | ||
|
|
f65556acb7 | ||
|
|
b2c01991a6 | ||
|
|
4bc5e81513 | ||
|
|
2b24fe95c2 | ||
|
|
358e64f2cc | ||
|
|
5dc50b6d96 | ||
|
|
b91a040a32 | ||
|
|
76db55ff19 | ||
|
|
e17bf18350 | ||
|
|
43b4499125 | ||
|
|
c6d43a802c | ||
|
|
21f8403357 | ||
|
|
40053e3388 | ||
|
|
abcf7e3a47 | ||
|
|
f8913bf9b9 | ||
|
|
51878ab38e | ||
|
|
d3f9a84a0a | ||
|
|
23eedb468f | ||
|
|
9cb89fff45 | ||
|
|
79f0b22276 | ||
|
|
8b2a728a0d | ||
|
|
b4120b156e | ||
|
|
1ae3122c20 | ||
|
|
727acca217 | ||
|
|
5f8d662d67 | ||
|
|
81b432140c | ||
|
|
adc1ee71a9 | ||
|
|
5ac373d4aa | ||
|
|
5b9e24f3f4 | ||
|
|
0e44de2fe3 | ||
|
|
b0125e8165 | ||
|
|
a532067a93 | ||
|
|
f456071c08 | ||
|
|
3896f0995c | ||
|
|
56ff5808a3 | ||
|
|
15c817dd15 | ||
|
|
7246c3f304 | ||
|
|
e4f59f1ec4 | ||
|
|
5f0e9538cf | ||
|
|
73649128f3 | ||
|
|
edc84731ac | ||
|
|
108b116b06 | ||
|
|
dda587dc6f | ||
|
|
4a9dd43598 | ||
|
|
a64cfe661a | ||
|
|
7e418a16ae | ||
|
|
ecf1fa2bbd | ||
|
|
a6157a34bc | ||
|
|
8e37e66706 | ||
|
|
dd6a4931e5 | ||
|
|
2d000e826b | ||
|
|
c1028e7408 | ||
|
|
28b54fac37 | ||
|
|
845fddf5f2 | ||
|
|
e3e2a477c1 | ||
|
|
bf4442ecf5 |
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -104,7 +104,7 @@ jobs:
|
||||
docker run --rm \
|
||||
-u $(id -u) \
|
||||
-v $PWD:/usr/src/tdesktop \
|
||||
-e CMAKE_CONFIG_TYPE=Debug \
|
||||
-e CONFIG=Debug \
|
||||
$IMAGE_TAG \
|
||||
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
|
||||
-D CMAKE_CONFIGURATION_TYPES=Debug \
|
||||
|
||||
2
.github/workflows/mac.yml
vendored
2
.github/workflows/mac.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
macos:
|
||||
name: MacOS
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-13
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
3
.github/workflows/mac_packaged.yml
vendored
3
.github/workflows/mac_packaged.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew upgrade || true
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg jpeg-xl libavif libheif libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
@@ -168,7 +168,6 @@ jobs:
|
||||
-DCMAKE_CXX_FLAGS_DEBUG="" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-s" \
|
||||
-DTDESKTOP_API_TEST=ON \
|
||||
-DDESKTOP_APP_USE_PACKAGED_LAZY=ON \
|
||||
$DEFINE
|
||||
|
||||
cmake --build build --parallel
|
||||
|
||||
@@ -12,19 +12,17 @@ include(cmake/validate_special_target.cmake)
|
||||
include(cmake/version.cmake)
|
||||
desktop_app_parse_version(Telegram/build/version)
|
||||
|
||||
set(project_langs C CXX)
|
||||
if (APPLE)
|
||||
list(APPEND project_langs OBJC OBJCXX)
|
||||
elseif (LINUX)
|
||||
list(APPEND project_langs ASM)
|
||||
endif()
|
||||
|
||||
project(Telegram
|
||||
LANGUAGES ${project_langs}
|
||||
LANGUAGES C CXX
|
||||
VERSION ${desktop_app_version_cmake}
|
||||
DESCRIPTION "Official Telegram Desktop messenger"
|
||||
HOMEPAGE_URL "https://desktop.telegram.org"
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Telegram)
|
||||
|
||||
get_filename_component(third_party_loc "Telegram/ThirdParty" REALPATH)
|
||||
@@ -39,9 +37,7 @@ include(cmake/variables.cmake)
|
||||
include(cmake/nice_target_sources.cmake)
|
||||
include(cmake/target_compile_options_if_exists.cmake)
|
||||
include(cmake/target_link_frameworks.cmake)
|
||||
include(cmake/target_link_optional_libraries.cmake)
|
||||
include(cmake/target_link_options_if_exists.cmake)
|
||||
include(cmake/target_link_static_libraries.cmake)
|
||||
include(cmake/init_target.cmake)
|
||||
include(cmake/generate_target.cmake)
|
||||
include(cmake/nuget.cmake)
|
||||
|
||||
@@ -836,6 +836,8 @@ PRIVATE
|
||||
history/view/history_view_bottom_info.h
|
||||
history/view/history_view_chat_preview.cpp
|
||||
history/view/history_view_chat_preview.h
|
||||
history/view/history_view_chat_section.cpp
|
||||
history/view/history_view_chat_section.h
|
||||
history/view/history_view_contact_status.cpp
|
||||
history/view/history_view_contact_status.h
|
||||
history/view/history_view_context_menu.cpp
|
||||
@@ -870,8 +872,6 @@ PRIVATE
|
||||
history/view/history_view_pinned_tracker.h
|
||||
history/view/history_view_quick_action.cpp
|
||||
history/view/history_view_quick_action.h
|
||||
history/view/history_view_replies_section.cpp
|
||||
history/view/history_view_replies_section.h
|
||||
history/view/history_view_reply.cpp
|
||||
history/view/history_view_reply.h
|
||||
history/view/history_view_requests_bar.cpp
|
||||
@@ -888,8 +888,8 @@ PRIVATE
|
||||
history/view/history_view_sponsored_click_handler.h
|
||||
history/view/history_view_sticker_toast.cpp
|
||||
history/view/history_view_sticker_toast.h
|
||||
history/view/history_view_sublist_section.cpp
|
||||
history/view/history_view_sublist_section.h
|
||||
history/view/history_view_subsection_tabs.cpp
|
||||
history/view/history_view_subsection_tabs.h
|
||||
history/view/history_view_text_helper.cpp
|
||||
history/view/history_view_text_helper.h
|
||||
history/view/history_view_transcribe_button.cpp
|
||||
@@ -1446,6 +1446,8 @@ PRIVATE
|
||||
settings/cloud_password/settings_cloud_password_start.h
|
||||
settings/cloud_password/settings_cloud_password_step.cpp
|
||||
settings/cloud_password/settings_cloud_password_step.h
|
||||
settings/cloud_password/settings_cloud_password_validate_icon.cpp
|
||||
settings/cloud_password/settings_cloud_password_validate_icon.h
|
||||
settings/settings_active_sessions.cpp
|
||||
settings/settings_active_sessions.h
|
||||
settings/settings_advanced.cpp
|
||||
@@ -1864,11 +1866,7 @@ else()
|
||||
set(bundle_identifier "com.tdesktop.Telegram")
|
||||
endif()
|
||||
set(bundle_entitlements "Telegram.entitlements")
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
set(output_name "telegram-desktop")
|
||||
else()
|
||||
set(output_name "Telegram")
|
||||
endif()
|
||||
set(output_name "Telegram")
|
||||
endif()
|
||||
|
||||
if (CMAKE_GENERATOR STREQUAL Xcode)
|
||||
@@ -1910,8 +1908,9 @@ PRIVATE
|
||||
G_LOG_DOMAIN="Telegram"
|
||||
)
|
||||
|
||||
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if (APPLE
|
||||
OR "${CMAKE_GENERATOR}" STREQUAL "Ninja Multi-Config"
|
||||
OR is_multi_config
|
||||
OR NOT CMAKE_EXECUTABLE_SUFFIX STREQUAL ""
|
||||
OR NOT "${output_name}" STREQUAL "Telegram")
|
||||
set(output_folder ${CMAKE_BINARY_DIR})
|
||||
@@ -1988,6 +1987,10 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
|
||||
desktop-app::external_openssl
|
||||
)
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
target_compile_definitions(Packer PRIVATE PACKER_USE_PACKAGED)
|
||||
endif()
|
||||
|
||||
set_target_properties(Packer PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
endif()
|
||||
elseif (build_winstore)
|
||||
|
||||
BIN
Telegram/Resources/animations/cloud_password/validate.tgs
Normal file
BIN
Telegram/Resources/animations/cloud_password/validate.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/edit_peers/direct_messages.tgs
Normal file
BIN
Telegram/Resources/animations/edit_peers/direct_messages.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/edit_peers/topics.tgs
Normal file
BIN
Telegram/Resources/animations/edit_peers/topics.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/edit_peers/topics_list.tgs
Normal file
BIN
Telegram/Resources/animations/edit_peers/topics_list.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/edit_peers/topics_tabs.tgs
Normal file
BIN
Telegram/Resources/animations/edit_peers/topics_tabs.tgs
Normal file
Binary file not shown.
Binary file not shown.
BIN
Telegram/Resources/icons/chat/large_messages.png
Normal file
BIN
Telegram/Resources/icons/chat/large_messages.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/chat/large_messages@2x.png
Normal file
BIN
Telegram/Resources/icons/chat/large_messages@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/chat/large_messages@3x.png
Normal file
BIN
Telegram/Resources/icons/chat/large_messages@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -164,11 +164,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_chat_status_members_online" = "{members_count}, {online_count}";
|
||||
"lng_chat_status_subscribers#one" = "{count} subscriber";
|
||||
"lng_chat_status_subscribers#other" = "{count} subscribers";
|
||||
"lng_chat_status_direct" = "Direct messages";
|
||||
|
||||
"lng_channel_status" = "channel";
|
||||
"lng_group_status" = "group";
|
||||
"lng_scam_badge" = "SCAM";
|
||||
"lng_fake_badge" = "FAKE";
|
||||
"lng_direct_badge" = "DIRECT";
|
||||
|
||||
"lng_remember" = "Remember this choice";
|
||||
|
||||
@@ -840,6 +842,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_suggestion_phone_number_about" = "Keep your number up to date to ensure you can always log into Telegram. {link}";
|
||||
"lng_settings_suggestion_phone_number_about_link" = "https://telegram.org/faq#q-i-have-a-new-phone-number-what-do-i-do";
|
||||
"lng_settings_suggestion_phone_number_change" = "Please change your phone number in the official Telegram app on your phone as soon as possible. {emoji}";
|
||||
"lng_settings_suggestion_password_title" = "Your password";
|
||||
"lng_settings_suggestion_password_about" = "Your account is protected by 2-Step Veritifaction. Do you still remember your password?";
|
||||
"lng_settings_suggestion_password_yes" = "Yes, definitely";
|
||||
"lng_settings_suggestion_password_no" = "Not sure";
|
||||
"lng_settings_suggestion_password_step_input_title" = "Enter your password";
|
||||
"lng_settings_suggestion_password_step_input_about" = "Do you still remember your password?";
|
||||
"lng_settings_suggestion_password_step_finish_title" = "Perfect!";
|
||||
"lng_settings_suggestion_password_step_finish_about" = "You still remember your password.";
|
||||
|
||||
"lng_settings_power_menu" = "Battery and Animations";
|
||||
"lng_settings_power_title" = "Power Usage";
|
||||
@@ -1481,6 +1491,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_hide_participants_about" = "Switch this on to hide the list of members in this group. Admins will remain visible.";
|
||||
"lng_profile_view_channel" = "View Channel";
|
||||
"lng_profile_view_discussion" = "View discussion";
|
||||
"lng_profile_direct_messages" = "Direct messages";
|
||||
"lng_profile_join_channel" = "Join Channel";
|
||||
"lng_profile_join_group" = "Join Group";
|
||||
"lng_profile_apply_to_join_group" = "Apply to Join Group";
|
||||
@@ -1878,6 +1889,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_linked_channel_posted" = "All new posts from this channel are forwarded to the group.";
|
||||
"lng_manage_discussion_group_warning" = "\"Chat history for new members\" will be switched to **Visible**.";
|
||||
|
||||
"lng_manage_monoforum" = "Direct Messages";
|
||||
"lng_manage_monoforum_off" = "Off";
|
||||
"lng_manage_monoforum_free" = "Free";
|
||||
"lng_manage_monoforum_allow" = "Allow Channel Messages";
|
||||
"lng_manage_monoforum_price" = "Price for each message";
|
||||
"lng_manage_monoforum_about" = "Allow users to send messages to your channel, with the option to charge a fee for each message.";
|
||||
"lng_manage_monoforum_price_about" = "Your channel will receive {percent} of the selected fee ({amount}) for each incoming message.";
|
||||
|
||||
"lng_manage_history_visibility_title" = "Chat history for new members";
|
||||
"lng_manage_history_visibility_shown" = "Visible";
|
||||
"lng_manage_history_visibility_shown_about" = "New members will see messages that were sent before they joined.";
|
||||
@@ -2061,6 +2080,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_changed_title" = "{from} changed group name to «{title}»";
|
||||
"lng_action_changed_title_channel" = "Channel name was changed to «{title}»";
|
||||
"lng_action_created_chat" = "{from} created the group «{title}»";
|
||||
"lng_action_created_monoforum" = "Direct messages were enabled in this channel.";
|
||||
"lng_action_ttl_changed" = "{from} set messages to auto-delete in {duration}";
|
||||
"lng_action_ttl_changed_you" = "You set messages to auto-delete in {duration}";
|
||||
"lng_action_ttl_changed_channel" = "Messages in this channel will be automatically deleted after {duration}";
|
||||
@@ -2235,6 +2255,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_message_price_free" = "Messages are now free in this group.";
|
||||
"lng_action_message_price_paid#one" = "Messages now cost {count} Star each in this group.";
|
||||
"lng_action_message_price_paid#other" = "Messages now cost {count} Stars each in this group.";
|
||||
"lng_action_direct_messages_enabled" = "Channel enabled Direct Messages.";
|
||||
"lng_action_direct_messages_paid#one" = "Channel allows Direct Messages for {count} Star each.";
|
||||
"lng_action_direct_messages_paid#other" = "Channel allows Direct Messages for {count} Stars each";
|
||||
"lng_action_direct_messages_disabled" = "Channel disabled Direct Messages.";
|
||||
"lng_you_paid_stars#one" = "You paid {count} Star.";
|
||||
"lng_you_paid_stars#other" = "You paid {count} Stars.";
|
||||
|
||||
@@ -3196,6 +3220,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_feature_transcribe" = "Voice-to-Text Conversion";
|
||||
"lng_feature_autotranslate" = "Autotranslation of Messages";
|
||||
|
||||
"lng_edit_topics_enable" = "Enable Topics";
|
||||
"lng_edit_topics_about" = "The group chat will be divided into topics created by admins or users.";
|
||||
"lng_edit_topics_layout" = "Topics layout";
|
||||
"lng_edit_topics_layout_about" = "Choose how topics appear for all members.";
|
||||
"lng_edit_topics_tabs" = "Tabs";
|
||||
"lng_edit_topics_list" = "List";
|
||||
|
||||
"lng_giveaway_new_title" = "Boosts via Gifts";
|
||||
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
|
||||
"lng_giveaway_new_about_group" = "Get more boosts for your group by gifting Premium to your members.";
|
||||
@@ -4197,6 +4228,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_pin_msg" = "Pin";
|
||||
"lng_context_unpin_msg" = "Unpin";
|
||||
"lng_context_cancel_upload" = "Cancel Upload";
|
||||
"lng_context_upload_edit_caption" = "Edit Caption";
|
||||
"lng_context_copy_selected" = "Copy Selected Text";
|
||||
"lng_context_copy_selected_items" = "Copy Selected as Text";
|
||||
"lng_context_forward_selected" = "Forward Selected";
|
||||
@@ -4220,6 +4252,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_seen_reacted#other" = "{count} Reacted";
|
||||
"lng_context_seen_reacted_none" = "Nobody Reacted";
|
||||
"lng_context_seen_reacted_all" = "Show All Reactions";
|
||||
"lng_context_sent_by" = "Sent by {user}";
|
||||
"lng_context_set_as_quick" = "Set As Quick";
|
||||
"lng_context_filter_by_tag" = "Filter by Tag";
|
||||
"lng_context_tag_add_name" = "Add Name";
|
||||
@@ -5265,6 +5298,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
|
||||
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
|
||||
|
||||
"lng_send_charges_stars_channel" = "{channel} charges {amount} per message to its admin.";
|
||||
"lng_send_free_channel" = "Send a direct message to the administrator of {channel}.";
|
||||
"lng_send_charges_stars_text" = "{user} charges {amount} for each message.";
|
||||
"lng_send_charges_stars_go" = "Buy Stars";
|
||||
|
||||
@@ -6071,6 +6106,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forum_messages#other" = "{count} messages";
|
||||
"lng_forum_show_topics_list" = "Show Topics List";
|
||||
|
||||
"lng_monoforum_choose_to_reply" = "Choose a message to reply.";
|
||||
|
||||
"lng_request_peer_requirements" = "Requirements";
|
||||
"lng_request_peer_rights" = "You must have these admin rights: {rights}.";
|
||||
"lng_request_peer_rights_and" = "{rights} and {last}";
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<file alias="cloud_password/password_input.tgs">../../animations/cloud_password/password_input.tgs</file>
|
||||
<file alias="cloud_password/hint.tgs">../../animations/cloud_password/hint.tgs</file>
|
||||
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
|
||||
<file alias="cloud_password/validate.tgs">../../animations/cloud_password/validate.tgs</file>
|
||||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||
<file alias="stats.tgs">../../animations/stats.tgs</file>
|
||||
@@ -32,6 +33,10 @@
|
||||
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
|
||||
<file alias="starref_link.tgs">../../animations/starref_link.tgs</file>
|
||||
<file alias="media_forbidden.tgs">../../animations/media_forbidden.tgs</file>
|
||||
<file alias="topics.tgs">../../animations/edit_peers/topics.tgs</file>
|
||||
<file alias="topics_tabs.tgs">../../animations/edit_peers/topics_tabs.tgs</file>
|
||||
<file alias="topics_list.tgs">../../animations/edit_peers/topics_list.tgs</file>
|
||||
<file alias="direct_messages.tgs">../../animations/edit_peers/direct_messages.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.14.3.0" />
|
||||
Version="5.15.0.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,14,3,0
|
||||
PRODUCTVERSION 5,14,3,0
|
||||
FILEVERSION 5,15,0,0
|
||||
PRODUCTVERSION 5,15,0,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.14.3.0"
|
||||
VALUE "FileVersion", "5.15.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.14.3.0"
|
||||
VALUE "ProductVersion", "5.15.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,14,3,0
|
||||
PRODUCTVERSION 5,14,3,0
|
||||
FILEVERSION 5,15,0,0
|
||||
PRODUCTVERSION 5,15,0,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.14.3.0"
|
||||
VALUE "FileVersion", "5.15.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.14.3.0"
|
||||
VALUE "ProductVersion", "5.15.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -283,7 +283,7 @@ int main(int argc, char *argv[])
|
||||
cout << "Compression start, size: " << resultSize << "\n";
|
||||
|
||||
QByteArray compressed, resultCheck;
|
||||
#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win
|
||||
#if defined Q_OS_WIN && !defined PACKER_USE_PACKAGED // use Lzma SDK for win
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
|
||||
|
||||
compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size
|
||||
|
||||
@@ -27,7 +27,7 @@ extern "C" {
|
||||
#include <openssl/evp.h>
|
||||
} // extern "C"
|
||||
|
||||
#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win
|
||||
#if defined Q_OS_WIN && !defined PACKER_USE_PACKAGED // use Lzma SDK for win
|
||||
#include <LzmaLib.h>
|
||||
#else
|
||||
#include <lzma.h>
|
||||
|
||||
@@ -14,7 +14,7 @@ class ChannelData;
|
||||
|
||||
namespace Info::Profile {
|
||||
class Badge;
|
||||
enum class BadgeType;
|
||||
enum class BadgeType : uchar;
|
||||
} // namespace Info::Profile
|
||||
|
||||
namespace Main {
|
||||
|
||||
@@ -494,8 +494,15 @@ void ChatParticipants::requestBots(not_null<ChannelData*> channel) {
|
||||
LOG(("API Error: "
|
||||
"channels.channelParticipantsNotModified received!"));
|
||||
});
|
||||
}).fail([=] {
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_botsRequests.remove(channel);
|
||||
if (error.type() == u"CHANNEL_MONOFORUM_UNSUPPORTED"_q) {
|
||||
channel->mgInfo->bots.clear();
|
||||
channel->mgInfo->botStatus = -1;
|
||||
channel->session().changes().peerUpdated(
|
||||
channel,
|
||||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
}).send();
|
||||
|
||||
_botsRequests[channel] = requestId;
|
||||
|
||||
@@ -47,6 +47,7 @@ void Polls::create(
|
||||
const auto topicRootId = action.replyTo.messageId
|
||||
? action.replyTo.topicRootId
|
||||
: 0;
|
||||
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
if (action.replyTo) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||
@@ -54,9 +55,9 @@ void Polls::create(
|
||||
const auto clearCloudDraft = action.clearDraft;
|
||||
if (clearCloudDraft) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
||||
history->clearLocalDraft(topicRootId);
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->startSavingCloudDraft(topicRootId);
|
||||
history->clearLocalDraft(topicRootId, monoforumPeerId);
|
||||
history->clearCloudDraft(topicRootId, monoforumPeerId);
|
||||
history->startSavingCloudDraft(topicRootId, monoforumPeerId);
|
||||
}
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
const auto starsPaid = std::min(
|
||||
@@ -106,6 +107,7 @@ void Polls::create(
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
_session->changes().historyUpdated(
|
||||
@@ -118,6 +120,7 @@ void Polls::create(
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
fail();
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/history.h"
|
||||
@@ -31,7 +32,9 @@ UnreadThings::UnreadThings(not_null<ApiWrap*> api) : _api(api) {
|
||||
|
||||
bool UnreadThings::trackMentions(Data::Thread *thread) const {
|
||||
const auto peer = thread ? thread->peer().get() : nullptr;
|
||||
return peer && (peer->isChat() || peer->isMegagroup());
|
||||
return peer
|
||||
&& (peer->isChat() || peer->isMegagroup())
|
||||
&& !peer->isMonoforum();
|
||||
}
|
||||
|
||||
bool UnreadThings::trackReactions(Data::Thread *thread) const {
|
||||
@@ -93,7 +96,7 @@ void UnreadThings::cancelRequests(not_null<Data::Thread*> thread) {
|
||||
void UnreadThings::requestMentions(
|
||||
not_null<Data::Thread*> thread,
|
||||
int loaded) {
|
||||
if (_mentionsRequests.contains(thread)) {
|
||||
if (_mentionsRequests.contains(thread) || thread->asSublist()) {
|
||||
return;
|
||||
}
|
||||
const auto offsetId = std::max(
|
||||
@@ -138,12 +141,15 @@ void UnreadThings::requestReactions(
|
||||
const auto maxId = 0;
|
||||
const auto minId = 0;
|
||||
const auto history = thread->owningHistory();
|
||||
const auto sublist = thread->asSublist();
|
||||
const auto topic = thread->asTopic();
|
||||
using Flag = MTPmessages_GetUnreadReactions::Flag;
|
||||
const auto requestId = _api->request(MTPmessages_GetUnreadReactions(
|
||||
MTP_flags(topic ? Flag::f_top_msg_id : Flag()),
|
||||
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
|
||||
| (sublist ? Flag::f_saved_peer_id : Flag())),
|
||||
history->peer->input,
|
||||
MTP_int(topic ? topic->rootId() : 0),
|
||||
(sublist ? sublist->sublistPeer()->input : MTPInputPeer()),
|
||||
MTP_int(offsetId),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
|
||||
@@ -2442,6 +2442,32 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
session().data().updateRepliesReadTill({ id, readTillId, true });
|
||||
} break;
|
||||
|
||||
case mtpc_updateReadMonoForumInbox: {
|
||||
const auto &d = update.c_updateReadMonoForumInbox();
|
||||
const auto parentChatId = ChannelId(d.vchannel_id());
|
||||
const auto sublistPeerId = peerFromMTP(d.vsaved_peer_id());
|
||||
const auto readTillId = d.vread_max_id().v;
|
||||
session().data().updateSublistReadTill({
|
||||
parentChatId,
|
||||
sublistPeerId,
|
||||
readTillId,
|
||||
false,
|
||||
});
|
||||
} break;
|
||||
|
||||
case mtpc_updateReadMonoForumOutbox: {
|
||||
const auto &d = update.c_updateReadMonoForumOutbox();
|
||||
const auto parentChatId = ChannelId(d.vchannel_id());
|
||||
const auto sublistPeerId = peerFromMTP(d.vsaved_peer_id());
|
||||
const auto readTillId = d.vread_max_id().v;
|
||||
session().data().updateSublistReadTill({
|
||||
parentChatId,
|
||||
sublistPeerId,
|
||||
readTillId,
|
||||
true,
|
||||
});
|
||||
} break;
|
||||
|
||||
case mtpc_updateChannelAvailableMessages: {
|
||||
auto &d = update.c_updateChannelAvailableMessages();
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
|
||||
@@ -2661,13 +2687,22 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
const auto &data = update.c_updateDraftMessage();
|
||||
const auto peerId = peerFromMTP(data.vpeer());
|
||||
const auto topicRootId = data.vtop_msg_id().value_or_empty();
|
||||
const auto monoforumPeerId = data.vsaved_peer_id()
|
||||
? peerFromMTP(*data.vsaved_peer_id())
|
||||
: PeerId();
|
||||
data.vdraft().match([&](const MTPDdraftMessage &data) {
|
||||
Data::ApplyPeerCloudDraft(&session(), peerId, topicRootId, data);
|
||||
Data::ApplyPeerCloudDraft(
|
||||
&session(),
|
||||
peerId,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
data);
|
||||
}, [&](const MTPDdraftMessageEmpty &data) {
|
||||
Data::ClearPeerCloudDraft(
|
||||
&session(),
|
||||
peerId,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
data.vdate().value_or_empty());
|
||||
});
|
||||
} break;
|
||||
|
||||
@@ -712,7 +712,8 @@ bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||
const auto megagroup = peer->asMegagroup();
|
||||
if ((!chat && !megagroup)
|
||||
|| (megagroup
|
||||
&& (megagroup->flags() & ChannelDataFlag::ParticipantsHidden))) {
|
||||
&& (megagroup->flags() & ChannelDataFlag::ParticipantsHidden))
|
||||
|| megagroup->isMonoforum()) {
|
||||
return false;
|
||||
}
|
||||
const auto &appConfig = peer->session().appConfig();
|
||||
|
||||
@@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_search_controller.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -381,10 +382,13 @@ void ApiWrap::savePinnedOrder(not_null<Data::Forum*> forum) {
|
||||
}
|
||||
|
||||
void ApiWrap::savePinnedOrder(not_null<Data::SavedMessages*> saved) {
|
||||
if (saved->parentChat()) {
|
||||
return;
|
||||
}
|
||||
const auto &order = _session->data().pinnedChatsOrder(saved);
|
||||
const auto input = [](Dialogs::Key key) {
|
||||
if (const auto sublist = key.sublist()) {
|
||||
return MTP_inputDialogPeer(sublist->peer()->input);
|
||||
return MTP_inputDialogPeer(sublist->sublistPeer()->input);
|
||||
}
|
||||
Unexpected("Key type in pinnedDialogsOrder().");
|
||||
};
|
||||
@@ -1369,6 +1373,32 @@ void ApiWrap::deleteAllFromParticipantSend(
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::deleteSublistHistory(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> sublistPeer) {
|
||||
deleteSublistHistorySend(channel, sublistPeer);
|
||||
}
|
||||
|
||||
void ApiWrap::deleteSublistHistorySend(
|
||||
not_null<ChannelData*> parentChat,
|
||||
not_null<PeerData*> sublistPeer) {
|
||||
request(MTPmessages_DeleteSavedHistory(
|
||||
MTP_flags(MTPmessages_DeleteSavedHistory::Flag::f_parent_peer),
|
||||
parentChat->input,
|
||||
sublistPeer->input,
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_date
|
||||
MTP_int(0) // max_date
|
||||
)).done([=](const MTPmessages_AffectedHistory &result) {
|
||||
const auto offset = applyAffectedHistory(parentChat, result);
|
||||
if (offset > 0) {
|
||||
deleteSublistHistorySend(parentChat, sublistPeer);
|
||||
} else if (const auto monoforum = parentChat->monoforum()) {
|
||||
monoforum->applySublistDeleted(sublistPeer);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) {
|
||||
if (!_stickerSetRequests.contains(setId)) {
|
||||
_stickerSetRequests.emplace(setId, StickerSetRequest{ access });
|
||||
@@ -2060,8 +2090,13 @@ void ApiWrap::saveCurrentDraftToCloud() {
|
||||
_session->local().writeDrafts(history);
|
||||
|
||||
const auto topicRootId = thread->topicRootId();
|
||||
const auto localDraft = history->localDraft(topicRootId);
|
||||
const auto cloudDraft = history->cloudDraft(topicRootId);
|
||||
const auto monoforumPeerId = thread->monoforumPeerId();
|
||||
const auto localDraft = history->localDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId);
|
||||
const auto cloudDraft = history->cloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId);
|
||||
if (!Data::DraftsAreEqual(localDraft, cloudDraft)
|
||||
&& !_session->supportMode()) {
|
||||
saveDraftToCloudDelayed(thread);
|
||||
@@ -2084,15 +2119,22 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
|
||||
const auto history = thread->owningHistory();
|
||||
const auto topicRootId = thread->topicRootId();
|
||||
auto cloudDraft = history->cloudDraft(topicRootId);
|
||||
auto localDraft = history->localDraft(topicRootId);
|
||||
const auto monoforumPeerId = thread->monoforumPeerId();
|
||||
auto cloudDraft = history->cloudDraft(topicRootId, monoforumPeerId);
|
||||
auto localDraft = history->localDraft(topicRootId, monoforumPeerId);
|
||||
if (cloudDraft && cloudDraft->saveRequestId) {
|
||||
request(base::take(cloudDraft->saveRequestId)).cancel();
|
||||
}
|
||||
if (!_session->supportMode()) {
|
||||
cloudDraft = history->createCloudDraft(topicRootId, localDraft);
|
||||
cloudDraft = history->createCloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
localDraft);
|
||||
} else if (!cloudDraft) {
|
||||
cloudDraft = history->createCloudDraft(topicRootId, nullptr);
|
||||
cloudDraft = history->createCloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
auto flags = MTPmessages_SaveDraft::Flags(0);
|
||||
@@ -2102,7 +2144,9 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
} else if (!cloudDraft->webpage.url.isEmpty()) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_media;
|
||||
}
|
||||
if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) {
|
||||
if (cloudDraft->reply.messageId
|
||||
|| cloudDraft->reply.topicRootId
|
||||
|| cloudDraft->reply.monoforumPeerId) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
|
||||
}
|
||||
if (!textWithTags.tags.isEmpty()) {
|
||||
@@ -2113,7 +2157,7 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags),
|
||||
Api::ConvertOption::SkipLocal);
|
||||
|
||||
history->startSavingCloudDraft(topicRootId);
|
||||
history->startSavingCloudDraft(topicRootId, monoforumPeerId);
|
||||
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
|
||||
MTP_flags(flags),
|
||||
ReplyToForMTP(history, cloudDraft->reply),
|
||||
@@ -2128,11 +2172,15 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
const auto requestId = response.requestId;
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
|
||||
const auto cloudDraft = history->cloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId);
|
||||
if (cloudDraft) {
|
||||
if (cloudDraft->saveRequestId == requestId) {
|
||||
cloudDraft->saveRequestId = 0;
|
||||
history->draftSavedToCloud(topicRootId);
|
||||
history->draftSavedToCloud(topicRootId, monoforumPeerId);
|
||||
}
|
||||
}
|
||||
const auto i = _draftsSaveRequestIds.find(weak);
|
||||
@@ -2145,10 +2193,14 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
const auto requestId = response.requestId;
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
|
||||
const auto cloudDraft = history->cloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId);
|
||||
if (cloudDraft) {
|
||||
if (cloudDraft->saveRequestId == requestId) {
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->clearCloudDraft(topicRootId, monoforumPeerId);
|
||||
}
|
||||
}
|
||||
const auto i = _draftsSaveRequestIds.find(weak);
|
||||
@@ -2926,17 +2978,27 @@ void ApiWrap::resolveJumpToDate(
|
||||
Fn<void(not_null<PeerData*>, MsgId)> callback) {
|
||||
if (const auto peer = chat.peer()) {
|
||||
const auto topic = chat.topic();
|
||||
const auto rootId = topic ? topic->rootId() : 0;
|
||||
resolveJumpToHistoryDate(peer, rootId, date, std::move(callback));
|
||||
const auto sublist = chat.sublist();
|
||||
const auto rootId = topic ? topic->rootId() : MsgId();
|
||||
const auto monoforumPeerId = sublist
|
||||
? sublist->sublistPeer()->id
|
||||
: PeerId();
|
||||
resolveJumpToHistoryDate(
|
||||
peer,
|
||||
rootId,
|
||||
monoforumPeerId,
|
||||
date,
|
||||
std::move(callback));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void ApiWrap::requestMessageAfterDate(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
const QDate &date,
|
||||
Callback &&callback) {
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
const QDate &date,
|
||||
Callback &&callback) {
|
||||
// API returns a message with date <= offset_date.
|
||||
// So we request a message with offset_date = desired_date - 1 and add_offset = -1.
|
||||
// This should give us the first message with date >= desired_date.
|
||||
@@ -2959,7 +3021,7 @@ void ApiWrap::requestMessageAfterDate(
|
||||
return &messages.vmessages().v;
|
||||
};
|
||||
const auto list = result.match([&](
|
||||
const MTPDmessages_messages &data) {
|
||||
const MTPDmessages_messages &data) {
|
||||
return handleMessages(data);
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
return handleMessages(data);
|
||||
@@ -3002,6 +3064,18 @@ void ApiWrap::requestMessageAfterDate(
|
||||
MTP_int(maxId),
|
||||
MTP_int(minId),
|
||||
MTP_long(historyHash)));
|
||||
} else if (monoforumPeerId) {
|
||||
send(MTPmessages_GetSavedHistory(
|
||||
MTP_flags(MTPmessages_GetSavedHistory::Flag::f_parent_peer),
|
||||
peer->input,
|
||||
session().data().peer(monoforumPeerId)->input,
|
||||
MTP_int(offsetId),
|
||||
MTP_int(offsetDate),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
MTP_int(maxId),
|
||||
MTP_int(minId),
|
||||
MTP_long(historyHash)));
|
||||
} else {
|
||||
send(MTPmessages_GetHistory(
|
||||
peer->input,
|
||||
@@ -3018,28 +3092,41 @@ void ApiWrap::requestMessageAfterDate(
|
||||
void ApiWrap::resolveJumpToHistoryDate(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
const QDate &date,
|
||||
Fn<void(not_null<PeerData*>, MsgId)> callback) {
|
||||
if (const auto channel = peer->migrateTo()) {
|
||||
return resolveJumpToHistoryDate(
|
||||
channel,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
date,
|
||||
std::move(callback));
|
||||
}
|
||||
const auto jumpToDateInPeer = [=] {
|
||||
requestMessageAfterDate(peer, topicRootId, date, [=](MsgId itemId) {
|
||||
callback(peer, itemId);
|
||||
});
|
||||
requestMessageAfterDate(
|
||||
peer,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
date,
|
||||
[=](MsgId itemId) { callback(peer, itemId); });
|
||||
};
|
||||
if (const auto chat = topicRootId ? nullptr : peer->migrateFrom()) {
|
||||
requestMessageAfterDate(chat, 0, date, [=](MsgId itemId) {
|
||||
if (itemId) {
|
||||
callback(chat, itemId);
|
||||
} else {
|
||||
jumpToDateInPeer();
|
||||
}
|
||||
});
|
||||
const auto migrated = (topicRootId || monoforumPeerId)
|
||||
? nullptr
|
||||
: peer->migrateFrom();
|
||||
if (migrated) {
|
||||
requestMessageAfterDate(
|
||||
migrated,
|
||||
MsgId(),
|
||||
PeerId(),
|
||||
date,
|
||||
[=](MsgId itemId) {
|
||||
if (itemId) {
|
||||
callback(migrated, itemId);
|
||||
} else {
|
||||
jumpToDateInPeer();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
jumpToDateInPeer();
|
||||
}
|
||||
@@ -3088,12 +3175,14 @@ void ApiWrap::requestHistory(
|
||||
void ApiWrap::requestSharedMedia(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
SharedMediaType type,
|
||||
MsgId messageId,
|
||||
SliceType slice) {
|
||||
const auto key = SharedMediaRequest{
|
||||
peer,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
type,
|
||||
messageId,
|
||||
slice,
|
||||
@@ -3105,6 +3194,7 @@ void ApiWrap::requestSharedMedia(
|
||||
const auto prepared = Api::PrepareSearchRequest(
|
||||
peer,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
type,
|
||||
QString(),
|
||||
messageId,
|
||||
@@ -3127,7 +3217,12 @@ void ApiWrap::requestSharedMedia(
|
||||
messageId,
|
||||
slice,
|
||||
result);
|
||||
sharedMediaDone(peer, topicRootId, type, std::move(parsed));
|
||||
sharedMediaDone(
|
||||
peer,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
type,
|
||||
std::move(parsed));
|
||||
finish();
|
||||
}).fail([=] {
|
||||
_sharedMediaRequests.remove(key);
|
||||
@@ -3140,16 +3235,19 @@ void ApiWrap::requestSharedMedia(
|
||||
void ApiWrap::sharedMediaDone(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
SharedMediaType type,
|
||||
Api::SearchResult &&parsed) {
|
||||
const auto topic = peer->forumTopicFor(topicRootId);
|
||||
if (topicRootId && !topic) {
|
||||
const auto sublist = peer->monoforumSublistFor(monoforumPeerId);
|
||||
if ((topicRootId && !topic) || (monoforumPeerId && !sublist)) {
|
||||
return;
|
||||
}
|
||||
const auto hasMessages = !parsed.messageIds.empty();
|
||||
_session->storage().add(Storage::SharedMediaAddSlice(
|
||||
peer->id,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
type,
|
||||
std::move(parsed.messageIds),
|
||||
parsed.noSkipRange,
|
||||
@@ -3160,6 +3258,9 @@ void ApiWrap::sharedMediaDone(
|
||||
if (topic) {
|
||||
topic->setHasPinnedMessages(true);
|
||||
}
|
||||
if (sublist) {
|
||||
sublist->setHasPinnedMessages(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3196,8 +3297,14 @@ void ApiWrap::sendAction(const SendAction &action) {
|
||||
const auto topic = topicRootId
|
||||
? action.history->peer->forumTopicFor(topicRootId)
|
||||
: nullptr;
|
||||
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
|
||||
const auto sublist = monoforumPeerId
|
||||
? action.history->peer->monoforumSublistFor(monoforumPeerId)
|
||||
: nullptr;
|
||||
if (topic) {
|
||||
topic->readTillEnd();
|
||||
} else if (sublist) {
|
||||
sublist->readTillEnd();
|
||||
} else {
|
||||
_session->data().histories().readInbox(action.history);
|
||||
}
|
||||
@@ -3209,7 +3316,10 @@ void ApiWrap::sendAction(const SendAction &action) {
|
||||
void ApiWrap::finishForwarding(const SendAction &action) {
|
||||
const auto history = action.history;
|
||||
const auto topicRootId = action.replyTo.topicRootId;
|
||||
auto toForward = history->resolveForwardDraft(topicRootId);
|
||||
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
|
||||
auto toForward = history->resolveForwardDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId);
|
||||
if (!toForward.items.empty()) {
|
||||
const auto error = GetErrorForSending(
|
||||
history->peer,
|
||||
@@ -3222,7 +3332,7 @@ void ApiWrap::finishForwarding(const SendAction &action) {
|
||||
}
|
||||
|
||||
forwardMessages(std::move(toForward), action);
|
||||
history->setForwardDraft(topicRootId, {});
|
||||
history->setForwardDraft(topicRootId, monoforumPeerId, {});
|
||||
}
|
||||
|
||||
_session->data().sendHistoryChangeNotifications();
|
||||
@@ -3299,6 +3409,13 @@ void ApiWrap::forwardMessages(
|
||||
if (topMsgId) {
|
||||
sendFlags |= SendFlag::f_top_msg_id;
|
||||
}
|
||||
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
|
||||
const auto monoforumPeer = monoforumPeerId
|
||||
? session().data().peer(monoforumPeerId).get()
|
||||
: nullptr;
|
||||
if (monoforumPeer) {
|
||||
sendFlags |= SendFlag::f_reply_to;
|
||||
}
|
||||
|
||||
auto forwardFrom = draft.items.front()->history()->peer;
|
||||
auto ids = QVector<MTPint>();
|
||||
@@ -3328,6 +3445,9 @@ void ApiWrap::forwardMessages(
|
||||
MTP_vector<MTPlong>(randomIds),
|
||||
peer->input,
|
||||
MTP_int(topMsgId),
|
||||
(monoforumPeer
|
||||
? MTP_inputReplyToMonoForum(monoforumPeer->input)
|
||||
: MTPInputReplyTo()),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
@@ -3374,7 +3494,10 @@ void ApiWrap::forwardMessages(
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = { .topicRootId = topMsgId },
|
||||
.replyTo = {
|
||||
.topicRootId = topMsgId,
|
||||
.monoforumPeerId = monoforumPeerId,
|
||||
},
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = action.options.starsApproved,
|
||||
@@ -3701,6 +3824,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
|
||||
const auto clearCloudDraft = action.clearDraft;
|
||||
const auto draftTopicRootId = action.replyTo.topicRootId;
|
||||
const auto draftMonoforumPeerId = action.replyTo.monoforumPeerId;
|
||||
const auto replyTo = action.replyTo.messageId
|
||||
? peer->owner().message(action.replyTo.messageId)
|
||||
: nullptr;
|
||||
@@ -3810,8 +3934,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
if (clearCloudDraft) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
||||
history->clearCloudDraft(draftTopicRootId);
|
||||
history->startSavingCloudDraft(draftTopicRootId);
|
||||
history->clearCloudDraft(draftTopicRootId, draftMonoforumPeerId);
|
||||
history->startSavingCloudDraft(
|
||||
draftTopicRootId,
|
||||
draftMonoforumPeerId);
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
@@ -3857,6 +3983,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
draftTopicRootId,
|
||||
draftMonoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
};
|
||||
@@ -3871,6 +3998,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
draftTopicRootId,
|
||||
draftMonoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
};
|
||||
@@ -3989,6 +4117,7 @@ void ApiWrap::sendInlineResult(
|
||||
const auto topicRootId = action.replyTo.messageId
|
||||
? action.replyTo.topicRootId
|
||||
: 0;
|
||||
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
|
||||
|
||||
using SendFlag = MTPmessages_SendInlineBotResult::Flag;
|
||||
auto flags = NewMessageFlags(peer);
|
||||
@@ -4041,8 +4170,8 @@ void ApiWrap::sendInlineResult(
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
});
|
||||
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->startSavingCloudDraft(topicRootId);
|
||||
history->clearCloudDraft(topicRootId, monoforumPeerId);
|
||||
history->startSavingCloudDraft(topicRootId, monoforumPeerId);
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
histories.sendPreparedMessage(
|
||||
@@ -4063,6 +4192,7 @@ void ApiWrap::sendInlineResult(
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(true);
|
||||
@@ -4071,6 +4201,7 @@ void ApiWrap::sendInlineResult(
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(false);
|
||||
|
||||
@@ -231,6 +231,9 @@ public:
|
||||
void deleteAllFromParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> from);
|
||||
void deleteSublistHistory(
|
||||
not_null<ChannelData*> parentChat,
|
||||
not_null<PeerData*> sublistPeer);
|
||||
|
||||
void requestWebPageDelayed(not_null<WebPageData*> page);
|
||||
void clearWebPageRequest(not_null<WebPageData*> page);
|
||||
@@ -286,6 +289,7 @@ public:
|
||||
void requestSharedMedia(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
Storage::SharedMediaType type,
|
||||
MsgId messageId,
|
||||
SliceType slice);
|
||||
@@ -502,18 +506,21 @@ private:
|
||||
void resolveJumpToHistoryDate(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
const QDate &date,
|
||||
Fn<void(not_null<PeerData*>, MsgId)> callback);
|
||||
template <typename Callback>
|
||||
void requestMessageAfterDate(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
const QDate &date,
|
||||
Callback &&callback);
|
||||
|
||||
void sharedMediaDone(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
SharedMediaType type,
|
||||
Api::SearchResult &&parsed);
|
||||
void globalMediaDone(
|
||||
@@ -539,6 +546,9 @@ private:
|
||||
void deleteAllFromParticipantSend(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> from);
|
||||
void deleteSublistHistorySend(
|
||||
not_null<ChannelData*> parentChat,
|
||||
not_null<PeerData*> sublistPeer);
|
||||
|
||||
void uploadAlbumMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
@@ -659,6 +669,7 @@ private:
|
||||
struct SharedMediaRequest {
|
||||
not_null<PeerData*> peer;
|
||||
MsgId topicRootId = 0;
|
||||
PeerId monoforumPeerId = 0;
|
||||
SharedMediaType mediaType = {};
|
||||
MsgId aroundId = 0;
|
||||
SliceType sliceType = {};
|
||||
|
||||
@@ -559,7 +559,7 @@ void GroupInfoBox::prepare() {
|
||||
&_navigation->parentController()->window(),
|
||||
Ui::UserpicButton::Role::ChoosePhoto,
|
||||
st::defaultUserpicButton,
|
||||
(_type == Type::Forum));
|
||||
(_type == Type::Forum) ? Ui::PeerUserpicShape::Forum : Ui::PeerUserpicShape::Auto);
|
||||
_photo->showCustomOnChosen();
|
||||
_title.create(
|
||||
this,
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
@@ -510,7 +511,8 @@ Options::Options(
|
||||
}
|
||||
|
||||
bool Options::full() const {
|
||||
return (_list.size() == kMaxOptionsCount);
|
||||
const auto limit = _controller->session().appConfig().pollOptionsLimit();
|
||||
return (_list.size() >= limit);
|
||||
}
|
||||
|
||||
bool Options::hasOptions() const {
|
||||
@@ -1028,8 +1030,10 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
setCloseByEscape(!count);
|
||||
setCloseByOutsideClick(!count);
|
||||
}) | rpl::map([=](int count) {
|
||||
return (count < kMaxOptionsCount)
|
||||
? tr::lng_polls_create_limit(tr::now, lt_count, kMaxOptionsCount - count)
|
||||
const auto appConfig = &_controller->session().appConfig();
|
||||
const auto max = appConfig->pollOptionsLimit();
|
||||
return (count < max)
|
||||
? tr::lng_polls_create_limit(tr::now, lt_count, max - count)
|
||||
: tr::lng_polls_create_maximum(tr::now);
|
||||
}) | rpl::after_next([=] {
|
||||
container->resizeToWidth(container->widthNoMargins());
|
||||
|
||||
@@ -45,8 +45,7 @@ namespace {
|
||||
|
||||
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
||||
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
||||
constexpr auto kStarsMin = 1;
|
||||
constexpr auto kDefaultChargeStars = 10;
|
||||
constexpr auto kDefaultPrivateMessagesPrice = 10;
|
||||
|
||||
using Exceptions = Api::UserPrivacy::Exceptions;
|
||||
|
||||
@@ -464,6 +463,7 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
||||
int valuesCount,
|
||||
Fn<int(int)> valueByIndex,
|
||||
int value,
|
||||
int minValue,
|
||||
int maxValue,
|
||||
Fn<void(int)> valueProgress,
|
||||
Fn<void(int)> valueFinished) {
|
||||
@@ -473,7 +473,7 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
||||
const auto labels = raw->add(object_ptr<Ui::RpWidget>(raw));
|
||||
const auto min = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString::number(kStarsMin),
|
||||
QString::number(minValue),
|
||||
*labelStyle);
|
||||
const auto max = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
@@ -510,8 +510,9 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
||||
current->moveToLeft((outer - current->width()) / 2, 0, outer);
|
||||
};
|
||||
const auto updateByValue = [=](int value) {
|
||||
current->setText(
|
||||
tr::lng_action_gift_for_stars(tr::now, lt_count, value));
|
||||
current->setText(value > 0
|
||||
? tr::lng_action_gift_for_stars(tr::now, lt_count, value)
|
||||
: tr::lng_manage_monoforum_free(tr::now));
|
||||
|
||||
state->index = 0;
|
||||
auto maxIndex = valuesCount - 1;
|
||||
@@ -1035,7 +1036,8 @@ void EditMessagesPrivacyBox(
|
||||
state->stars = SetupChargeSlider(
|
||||
chargeInner,
|
||||
session->user(),
|
||||
savedValue);
|
||||
(savedValue > 0) ? savedValue : std::optional<int>(),
|
||||
kDefaultPrivateMessagesPrice);
|
||||
|
||||
Ui::AddSkip(chargeInner);
|
||||
Ui::AddSubsectionTitle(
|
||||
@@ -1164,25 +1166,31 @@ void EditMessagesPrivacyBox(
|
||||
rpl::producer<int> SetupChargeSlider(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
int savedValue) {
|
||||
std::optional<int> savedValue,
|
||||
int defaultValue,
|
||||
bool allowZero) {
|
||||
struct State {
|
||||
rpl::variable<int> stars;
|
||||
};
|
||||
const auto group = !peer->isUser();
|
||||
const auto broadcast = peer->isBroadcast();
|
||||
const auto group = !broadcast && !peer->isUser();
|
||||
const auto state = container->lifetime().make_state<State>();
|
||||
const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars;
|
||||
const auto chargeStars = savedValue.value_or(defaultValue);
|
||||
state->stars = chargeStars;
|
||||
|
||||
Ui::AddSubsectionTitle(container, group
|
||||
Ui::AddSubsectionTitle(container, broadcast
|
||||
? tr::lng_manage_monoforum_price()
|
||||
: group
|
||||
? tr::lng_rights_charge_price()
|
||||
: tr::lng_messages_privacy_price());
|
||||
|
||||
auto values = std::vector<int>();
|
||||
const auto minStars = allowZero ? 0 : 1;
|
||||
const auto maxStars = peer->session().appConfig().paidMessageStarsMax();
|
||||
if (chargeStars < kStarsMin) {
|
||||
if (chargeStars < minStars) {
|
||||
values.push_back(chargeStars);
|
||||
}
|
||||
for (auto i = kStarsMin; i < std::min(100, maxStars); ++i) {
|
||||
for (auto i = minStars; i < std::min(100, maxStars); ++i) {
|
||||
values.push_back(i);
|
||||
}
|
||||
for (auto i = 100; i < std::min(1000, maxStars); i += 10) {
|
||||
@@ -1209,6 +1217,7 @@ rpl::producer<int> SetupChargeSlider(
|
||||
valuesCount,
|
||||
[=](int index) { return values[index]; },
|
||||
chargeStars,
|
||||
minStars,
|
||||
maxStars,
|
||||
setStars,
|
||||
setStars),
|
||||
@@ -1217,21 +1226,100 @@ rpl::producer<int> SetupChargeSlider(
|
||||
const auto skip = 2 * st::defaultVerticalListSkip;
|
||||
Ui::AddSkip(container, skip);
|
||||
|
||||
auto dollars = state->stars.value() | rpl::map([=](int stars) {
|
||||
const auto ratio = peer->session().appConfig().starsWithdrawRate();
|
||||
const auto details = container->add(
|
||||
object_ptr<Ui::VerticalLayout>(container));
|
||||
state->stars.value() | rpl::start_with_next([=](int stars) {
|
||||
while (details->count()) {
|
||||
delete details->widgetAt(0);
|
||||
}
|
||||
if (!stars) {
|
||||
Ui::AddDivider(details);
|
||||
return;
|
||||
}
|
||||
const auto &appConfig = peer->session().appConfig();
|
||||
const auto percent = appConfig.paidMessageCommission();
|
||||
const auto ratio = appConfig.starsWithdrawRate();
|
||||
const auto dollars = int(base::SafeRound(stars * ratio));
|
||||
return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q);
|
||||
});
|
||||
const auto percent = peer->session().appConfig().paidMessageCommission();
|
||||
Ui::AddDividerText(
|
||||
container,
|
||||
(group
|
||||
? tr::lng_rights_charge_price_about
|
||||
: tr::lng_messages_privacy_price_about)(
|
||||
lt_percent,
|
||||
rpl::single(QString::number(percent / 10.) + '%'),
|
||||
lt_amount,
|
||||
std::move(dollars)));
|
||||
|
||||
const auto amount = Ui::FillAmountAndCurrency(dollars, u"USD"_q);
|
||||
Ui::AddDividerText(
|
||||
details,
|
||||
(broadcast
|
||||
? tr::lng_manage_monoforum_price_about
|
||||
: group
|
||||
? tr::lng_rights_charge_price_about
|
||||
: tr::lng_messages_privacy_price_about)(
|
||||
lt_percent,
|
||||
rpl::single(QString::number(percent / 10.) + '%'),
|
||||
lt_amount,
|
||||
rpl::single('~' + amount)));
|
||||
}, details->lifetime());
|
||||
return state->stars.value();
|
||||
}
|
||||
|
||||
void EditDirectMessagesPriceBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<ChannelData*> channel,
|
||||
std::optional<int> savedValue,
|
||||
Fn<void(std::optional<int>)> callback) {
|
||||
box->setTitle(tr::lng_manage_monoforum());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
Settings::AddDividerTextWithLottie(container, {
|
||||
.lottie = u"direct_messages"_q,
|
||||
.lottieSize = st::settingsFilterIconSize,
|
||||
.lottieMargins = st::settingsFilterIconPadding,
|
||||
.showFinished = box->showFinishes(),
|
||||
.about = tr::lng_manage_monoforum_about(
|
||||
Ui::Text::RichLangValue
|
||||
),
|
||||
.aboutMargins = st::settingsFilterDividerLabelPadding,
|
||||
});
|
||||
|
||||
Ui::AddSkip(container);
|
||||
|
||||
const auto toggle = container->add(object_ptr<Ui::SettingsButton>(
|
||||
box,
|
||||
tr::lng_manage_monoforum_allow(),
|
||||
st::settingsButtonNoIcon));
|
||||
toggle->toggleOn(rpl::single(savedValue.has_value()));
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
const auto wrap = box->addRow(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
box,
|
||||
object_ptr<Ui::VerticalLayout>(box)),
|
||||
{});
|
||||
wrap->toggle(savedValue.has_value(), anim::type::instant);
|
||||
wrap->toggleOn(toggle->toggledChanges());
|
||||
|
||||
const auto result = box->lifetime().make_state<int>(
|
||||
savedValue.value_or(0));
|
||||
|
||||
const auto inner = wrap->entity();
|
||||
Ui::AddSkip(inner);
|
||||
SetupChargeSlider(
|
||||
inner,
|
||||
channel,
|
||||
savedValue,
|
||||
channel->session().appConfig().paidMessageChannelStarsDefault(),
|
||||
true
|
||||
) | rpl::start_with_next([=](int stars) {
|
||||
*result = stars;
|
||||
}, box->lifetime());
|
||||
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
callback(toggle->toggled() ? *result : std::optional<int>());
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,4 +173,12 @@ void EditMessagesPrivacyBox(
|
||||
[[nodiscard]] rpl::producer<int> SetupChargeSlider(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
int savedValue);
|
||||
std::optional<int> savedValue,
|
||||
int defaultValue,
|
||||
bool allowZero = false);
|
||||
|
||||
void EditDirectMessagesPriceBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<ChannelData*> channel,
|
||||
std::optional<int> savedValue,
|
||||
Fn<void(std::optional<int>)> callback);
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat_participant_status.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
@@ -565,15 +566,7 @@ bool CanCreateModerateMessagesBox(const HistoryItemsList &items) {
|
||||
&& !options.participants.empty();
|
||||
}
|
||||
|
||||
void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
const auto maybeUser = peer->asUser();
|
||||
const auto isBot = maybeUser && maybeUser->isBot();
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
void SafeSubmitOnEnter(not_null<Ui::GenericBox*> box) {
|
||||
base::install_event_filter(box, [=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
if (const auto k = static_cast<QKeyEvent*>(event.get())) {
|
||||
@@ -587,17 +580,31 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
|
||||
},
|
||||
.confirmText = tr::lng_box_yes(),
|
||||
.cancelText = tr::lng_box_no(),
|
||||
}));
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
const auto userpicPeer = peer->userpicPaintingPeer();
|
||||
const auto maybeUser = peer->asUser();
|
||||
const auto isBot = maybeUser && maybeUser->isBot();
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
SafeSubmitOnEnter(box);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
container,
|
||||
peer,
|
||||
st::mainMenuUserpic);
|
||||
userpicPeer,
|
||||
st::mainMenuUserpic,
|
||||
peer->userpicShape());
|
||||
userpic->showSavedMessagesOnSelf(true);
|
||||
Ui::IconWithTitle(
|
||||
container,
|
||||
@@ -609,7 +616,7 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
|
||||
: maybeUser
|
||||
? tr::lng_profile_delete_conversation() | Ui::Text::ToBold()
|
||||
: rpl::single(
|
||||
peer->name()
|
||||
userpicPeer->name()
|
||||
) | Ui::Text::ToBold() | rpl::type_erased(),
|
||||
box->getDelegate()->style().title));
|
||||
|
||||
@@ -754,3 +761,54 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
|
||||
}, st::attentionBoxButton);
|
||||
box->addButton(tr::lng_cancel(), close);
|
||||
}
|
||||
|
||||
void DeleteSublistBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::SavedSublist*> sublist) {
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
const auto weak = base::make_weak(sublist.get());
|
||||
const auto peer = sublist->sublistPeer();
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
SafeSubmitOnEnter(box);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
container,
|
||||
peer,
|
||||
st::mainMenuUserpic);
|
||||
Ui::IconWithTitle(
|
||||
container,
|
||||
userpic,
|
||||
Ui::CreateChild<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_profile_delete_conversation() | Ui::Text::ToBold(),
|
||||
box->getDelegate()->style().title));
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_sure_delete_history(
|
||||
lt_contact,
|
||||
rpl::single(peer->name())),
|
||||
st::boxLabel));
|
||||
|
||||
Ui::AddSkip(container);
|
||||
|
||||
const auto close = crl::guard(box, [=] { box->closeBox(); });
|
||||
box->addButton(tr::lng_box_delete(), [=] {
|
||||
const auto strong = weak.get();
|
||||
const auto parentChat = strong ? strong->parentChat() : nullptr;
|
||||
if (!parentChat) {
|
||||
return;
|
||||
}
|
||||
peer->session().api().deleteSublistHistory(parentChat, peer);
|
||||
close();
|
||||
}, st::attentionBoxButton);
|
||||
box->addButton(tr::lng_cancel(), close);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace Data {
|
||||
class SavedSublist;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
@@ -21,3 +25,6 @@ void CreateModerateMessagesBox(
|
||||
[[nodiscard]] bool CanCreateModerateMessagesBox(const HistoryItemsList &);
|
||||
|
||||
void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer);
|
||||
void DeleteSublistBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::SavedSublist*> sublist);
|
||||
|
||||
@@ -714,7 +714,7 @@ void PeerListRow::elementsPaint(
|
||||
}
|
||||
|
||||
QString PeerListRow::generateName() {
|
||||
return peer()->name();
|
||||
return peer()->userpicPaintingPeer()->name();
|
||||
}
|
||||
|
||||
QString PeerListRow::generateShortName() {
|
||||
@@ -724,12 +724,12 @@ QString PeerListRow::generateShortName() {
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: _isVerifyCodesChat
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer()->shortName();
|
||||
: peer()->userpicPaintingPeer()->shortName();
|
||||
}
|
||||
|
||||
Ui::PeerUserpicView &PeerListRow::ensureUserpicView() {
|
||||
if (!_userpic.cloud && peer()->hasUserpic()) {
|
||||
_userpic = peer()->createUserpicView();
|
||||
if (!_userpic.cloud && peer()->userpicPaintingPeer()->hasUserpic()) {
|
||||
_userpic = peer()->userpicPaintingPeer()->createUserpicView();
|
||||
}
|
||||
return _userpic;
|
||||
}
|
||||
@@ -738,7 +738,7 @@ PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
const auto saved = !_savedMessagesStatus.isEmpty();
|
||||
const auto replies = _isRepliesMessagesChat;
|
||||
const auto peer = this->peer();
|
||||
const auto peer = this->peer()->userpicPaintingPeer();
|
||||
auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView();
|
||||
if (forceRound && peer->isForum()) {
|
||||
return ForceRoundUserpicCallback(peer);
|
||||
@@ -810,6 +810,9 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
? st::dialogsPremiumIcon.over
|
||||
: st::dialogsPremiumIcon.icon),
|
||||
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
|
||||
.direct = &(selected
|
||||
? st::windowSubTextFgOver
|
||||
: st::windowSubTextFg),
|
||||
.premiumFg = &(selected
|
||||
? st::dialogsVerifiedIconBgOver
|
||||
: st::dialogsVerifiedIconBg),
|
||||
|
||||
@@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/ui_utility.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -867,6 +869,45 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
*weak = owned.data();
|
||||
delegate()->peerListUiShow()->showBox(std::move(owned));
|
||||
return;
|
||||
} else if (const auto monoforum = peer->monoforum()) {
|
||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
auto callback = [=](not_null<Data::SavedSublist*> sublist) {
|
||||
const auto exists = guard.get();
|
||||
if (!exists) {
|
||||
if (*weak) {
|
||||
(*weak)->closeBox();
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto onstack = std::move(_callback);
|
||||
onstack(sublist);
|
||||
if (guard) {
|
||||
_callback = std::move(onstack);
|
||||
} else if (*weak) {
|
||||
(*weak)->closeBox();
|
||||
}
|
||||
};
|
||||
const auto filter = [=](not_null<Data::SavedSublist*> sublist) {
|
||||
return guard && (!_filter || _filter(sublist));
|
||||
};
|
||||
auto owned = Box<PeerListBox>(
|
||||
std::make_unique<ChooseSublistBoxController>(
|
||||
monoforum,
|
||||
std::move(callback),
|
||||
filter),
|
||||
[=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
monoforum->destroyed(
|
||||
) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
});
|
||||
*weak = owned.data();
|
||||
delegate()->peerListUiShow()->showBox(std::move(owned));
|
||||
return;
|
||||
}
|
||||
const auto history = peer->owner().history(peer);
|
||||
auto callback = std::move(_callback);
|
||||
@@ -1137,6 +1178,111 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
|
||||
return skip ? nullptr : std::make_unique<Row>(topic);
|
||||
};
|
||||
|
||||
ChooseSublistBoxController::ChooseSublistBoxController(
|
||||
not_null<Data::SavedMessages*> monoforum,
|
||||
FnMut<void(not_null<Data::SavedSublist*>)> callback,
|
||||
Fn<bool(not_null<Data::SavedSublist*>)> filter)
|
||||
: _monoforum(monoforum)
|
||||
, _callback(std::move(callback))
|
||||
, _filter(std::move(filter)) {
|
||||
setStyleOverrides(&st::chooseTopicList);
|
||||
|
||||
_monoforum->chatsListChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshRows();
|
||||
}, lifetime());
|
||||
|
||||
_monoforum->sublistDestroyed(
|
||||
) | rpl::start_with_next([=](not_null<Data::SavedSublist*> sublist) {
|
||||
const auto id = sublist->sublistPeer()->id.value;
|
||||
if (const auto row = delegate()->peerListFindRow(id)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
Main::Session &ChooseSublistBoxController::session() const {
|
||||
return _monoforum->session();
|
||||
}
|
||||
|
||||
void ChooseSublistBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto weak = base::make_weak(this);
|
||||
auto onstack = base::take(_callback);
|
||||
onstack(_monoforum->sublist(row->peer()));
|
||||
if (weak) {
|
||||
_callback = std::move(onstack);
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseSublistBoxController::prepare() {
|
||||
delegate()->peerListSetTitle(tr::lng_forward_choose());
|
||||
setSearchNoResultsText(tr::lng_topics_not_found(tr::now));
|
||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
||||
refreshRows(true);
|
||||
|
||||
session().changes().entryUpdates(
|
||||
Data::EntryUpdate::Flag::Repaint
|
||||
) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
|
||||
if (const auto sublist = update.entry->asSublist()) {
|
||||
if (sublist->parent() == _monoforum) {
|
||||
const auto id = sublist->sublistPeer()->id.value;
|
||||
if (const auto row = delegate()->peerListFindRow(id)) {
|
||||
delegate()->peerListUpdateRow(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void ChooseSublistBoxController::refreshRows(bool initial) {
|
||||
auto added = false;
|
||||
for (const auto &row : _monoforum->chatsList()->indexed()->all()) {
|
||||
if (const auto sublist = row->sublist()) {
|
||||
const auto id = sublist->sublistPeer()->id.value;
|
||||
auto already = delegate()->peerListFindRow(id);
|
||||
if (initial || !already) {
|
||||
if (auto created = createRow(sublist)) {
|
||||
delegate()->peerListAppendRow(std::move(created));
|
||||
added = true;
|
||||
}
|
||||
} else if (already->isSearchResult()) {
|
||||
delegate()->peerListAppendFoundRow(already);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (added) {
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseSublistBoxController::loadMoreRows() {
|
||||
_monoforum->loadMore();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ChooseSublistBoxController::createSearchRow(
|
||||
PeerListRowId id) {
|
||||
const auto peer = session().data().peer(PeerId(id));
|
||||
if (const auto sublist = _monoforum->sublistLoaded(peer)) {
|
||||
auto result = std::make_unique<PeerListRow>(sublist->sublistPeer());
|
||||
result->setCustomStatus(QString());
|
||||
return result;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ChooseSublistBoxController::createRow(
|
||||
not_null<Data::SavedSublist*> sublist)
|
||||
-> std::unique_ptr<PeerListRow> {
|
||||
if (_filter && !_filter(sublist)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<PeerListRow>(sublist->sublistPeer());
|
||||
result->setCustomStatus(QString());
|
||||
return result;
|
||||
};
|
||||
|
||||
void PaintRestrictionBadge(
|
||||
Painter &p,
|
||||
not_null<const style::PeerListItem*> st,
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace Data {
|
||||
class Thread;
|
||||
class Forum;
|
||||
class ForumTopic;
|
||||
class SavedSublist;
|
||||
class SavedMessages;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
@@ -393,3 +395,30 @@ private:
|
||||
Fn<bool(not_null<Data::ForumTopic*>)> _filter;
|
||||
|
||||
};
|
||||
|
||||
class ChooseSublistBoxController final
|
||||
: public PeerListController
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
ChooseSublistBoxController(
|
||||
not_null<Data::SavedMessages*> monoforum,
|
||||
FnMut<void(not_null<Data::SavedSublist*>)> callback,
|
||||
Fn<bool(not_null<Data::SavedSublist*>)> filter = nullptr);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override;
|
||||
|
||||
private:
|
||||
void refreshRows(bool initial = false);
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<Data::SavedSublist*> sublist);
|
||||
|
||||
const not_null<Data::SavedMessages*> _monoforum;
|
||||
FnMut<void(not_null<Data::SavedSublist*>)> _callback;
|
||||
Fn<bool(not_null<Data::SavedSublist*>)> _filter;
|
||||
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_replies_section.h"
|
||||
#include "history/view/history_view_chat_section.h"
|
||||
#include "history/view/history_view_sticker_toast.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
@@ -93,11 +93,12 @@ void DefaultIconEmoji::paint(QPainter &p, const Context &context) {
|
||||
const auto &st = (_tag == Data::CustomEmojiSizeTag::Normal)
|
||||
? st::normalForumTopicIcon
|
||||
: st::defaultForumTopicIcon;
|
||||
const auto general = Data::IsForumGeneralIconTitle(_icon.title);
|
||||
if (_image.isNull()) {
|
||||
_image = Data::IsForumGeneralIconTitle(_icon.title)
|
||||
_image = general
|
||||
? Data::ForumTopicGeneralIconFrame(
|
||||
st.size,
|
||||
Data::ParseForumGeneralIconColor(_icon.colorId))
|
||||
QColor(255, 255, 255))
|
||||
: Data::ForumTopicIconFrame(_icon.colorId, _icon.title, st);
|
||||
}
|
||||
const auto full = (_tag == Data::CustomEmojiSizeTag::Normal)
|
||||
@@ -106,7 +107,9 @@ void DefaultIconEmoji::paint(QPainter &p, const Context &context) {
|
||||
const auto esize = full / style::DevicePixelRatio();
|
||||
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
|
||||
const auto skip = (customSize - st.size) / 2;
|
||||
p.drawImage(context.position + QPoint(skip, skip), _image);
|
||||
p.drawImage(context.position + QPoint(skip, skip), general
|
||||
? style::colorizeImage(_image, context.textColor)
|
||||
: _image);
|
||||
}
|
||||
|
||||
void DefaultIconEmoji::unload() {
|
||||
@@ -518,13 +521,15 @@ void EditForumTopicBox(
|
||||
title->showError();
|
||||
return;
|
||||
}
|
||||
using namespace HistoryView;
|
||||
controller->showSection(
|
||||
std::make_shared<HistoryView::RepliesMemento>(
|
||||
forum,
|
||||
channel->forum()->reserveCreatingId(
|
||||
std::make_shared<ChatMemento>(ChatViewId{
|
||||
.history = forum,
|
||||
.repliesRootId = channel->forum()->reserveCreatingId(
|
||||
title->getLastText().trimmed(),
|
||||
state->defaultIcon.current().colorId,
|
||||
state->iconId.current())),
|
||||
state->iconId.current()),
|
||||
}),
|
||||
Window::SectionShow::Way::ClearStack);
|
||||
};
|
||||
|
||||
|
||||
@@ -26,8 +26,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/peers/toggle_topics_box.h"
|
||||
#include "boxes/peers/verify_peers_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/edit_privacy_box.h" // EditDirectMessagesPriceBox
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/username_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
@@ -220,27 +222,41 @@ void SaveSlowmodeSeconds(
|
||||
}
|
||||
|
||||
void SaveStarsPerMessage(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<ChannelData*> channel,
|
||||
int starsPerMessage,
|
||||
Fn<void()> done) {
|
||||
Fn<void(bool)> done) {
|
||||
const auto api = &channel->session().api();
|
||||
const auto key = Api::RequestKey("stars_per_message", channel->id);
|
||||
|
||||
const auto broadcast = channel->isBroadcast();
|
||||
|
||||
using Flag = MTPchannels_UpdatePaidMessagesPrice::Flag;
|
||||
const auto broadcastAllowed = broadcast && (starsPerMessage >= 0);
|
||||
const auto requestId = api->request(MTPchannels_UpdatePaidMessagesPrice(
|
||||
MTP_flags(broadcastAllowed
|
||||
? Flag::f_broadcast_messages_allowed
|
||||
: Flag(0)),
|
||||
channel->inputChannel,
|
||||
MTP_long(starsPerMessage)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->clearModifyRequest(key);
|
||||
api->applyUpdates(result);
|
||||
channel->setStarsPerMessage(starsPerMessage);
|
||||
done();
|
||||
if (!broadcast) {
|
||||
channel->setStarsPerMessage(starsPerMessage);
|
||||
}
|
||||
done(true);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
api->clearModifyRequest(key);
|
||||
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
|
||||
return;
|
||||
show->showToast(error.type());
|
||||
done(false);
|
||||
} else {
|
||||
if (!broadcast) {
|
||||
channel->setStarsPerMessage(starsPerMessage);
|
||||
}
|
||||
done(true);
|
||||
}
|
||||
channel->setStarsPerMessage(starsPerMessage);
|
||||
done();
|
||||
}).send();
|
||||
|
||||
api->registerModifyRequest(key, requestId);
|
||||
@@ -280,6 +296,7 @@ void SaveBoostsUnrestrict(
|
||||
void ShowEditPermissions(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto show = navigation->uiShow();
|
||||
auto createBox = [=](not_null<Ui::GenericBox*> box) {
|
||||
const auto saving = box->lifetime().make_state<int>(0);
|
||||
const auto save = [=](
|
||||
@@ -298,7 +315,10 @@ void ShowEditPermissions(
|
||||
channel,
|
||||
result.boostsUnrestrict,
|
||||
close);
|
||||
SaveStarsPerMessage(channel, result.starsPerMessage, close);
|
||||
const auto price = result.starsPerMessage;
|
||||
SaveStarsPerMessage(show, channel, price, [=](bool ok) {
|
||||
close();
|
||||
});
|
||||
}
|
||||
};
|
||||
auto done = [=](EditPeerPermissionsBoxResult result) {
|
||||
@@ -358,6 +378,7 @@ private:
|
||||
std::optional<QString> description;
|
||||
std::optional<bool> hiddenPreHistory;
|
||||
std::optional<bool> forum;
|
||||
std::optional<bool> forumTabs;
|
||||
std::optional<bool> autotranslate;
|
||||
std::optional<bool> signatures;
|
||||
std::optional<bool> signatureProfiles;
|
||||
@@ -365,6 +386,7 @@ private:
|
||||
std::optional<bool> joinToWrite;
|
||||
std::optional<bool> requestToJoin;
|
||||
std::optional<ChannelData*> discussionLink;
|
||||
std::optional<int> starsPerDirectMessage;
|
||||
};
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> createPhotoAndTitleEdit();
|
||||
@@ -381,8 +403,10 @@ private:
|
||||
void showEditPeerTypeBox(
|
||||
std::optional<rpl::producer<QString>> error = {});
|
||||
void showEditDiscussionLinkBox();
|
||||
void showEditDirectMessagesBox();
|
||||
void fillPrivacyTypeButton();
|
||||
void fillDiscussionLinkButton();
|
||||
void fillDirectMessagesButton();
|
||||
//void fillInviteLinkButton();
|
||||
void fillForumButton();
|
||||
void fillColorIndexButton();
|
||||
@@ -411,6 +435,7 @@ private:
|
||||
[[nodiscard]] bool validateUsernamesOrder(Saving &to) const;
|
||||
[[nodiscard]] bool validateUsername(Saving &to) const;
|
||||
[[nodiscard]] bool validateDiscussionLink(Saving &to) const;
|
||||
[[nodiscard]] bool validateDirectMessagesPrice(Saving &to) const;
|
||||
[[nodiscard]] bool validateTitle(Saving &to) const;
|
||||
[[nodiscard]] bool validateDescription(Saving &to) const;
|
||||
[[nodiscard]] bool validateHistoryVisibility(Saving &to) const;
|
||||
@@ -425,6 +450,7 @@ private:
|
||||
void saveUsernamesOrder();
|
||||
void saveUsername();
|
||||
void saveDiscussionLink();
|
||||
void saveDirectMessagesPrice();
|
||||
void saveTitle();
|
||||
void saveDescription();
|
||||
void saveHistoryVisibility();
|
||||
@@ -453,9 +479,11 @@ private:
|
||||
std::optional<ChannelData*> _discussionLinkSavedValue;
|
||||
ChannelData *_discussionLinkOriginalValue = nullptr;
|
||||
bool _channelHasLocationOriginalValue = false;
|
||||
std::optional<rpl::variable<int>> _starsPerDirectMessageSavedValue;
|
||||
std::optional<HistoryVisibility> _historyVisibilitySavedValue;
|
||||
std::optional<EditPeerTypeData> _typeDataSavedValue;
|
||||
std::optional<bool> _forumSavedValue;
|
||||
std::optional<bool> _forumTabsSavedValue;
|
||||
std::optional<bool> _autotranslateSavedValue;
|
||||
std::optional<bool> _signaturesSavedValue;
|
||||
std::optional<bool> _signatureProfilesSavedValue;
|
||||
@@ -917,6 +945,20 @@ void Controller::showEditDiscussionLinkBox() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Controller::showEditDirectMessagesBox() {
|
||||
Expects(_peer->isBroadcast());
|
||||
Expects(_starsPerDirectMessageSavedValue.has_value());
|
||||
|
||||
const auto stars = _starsPerDirectMessageSavedValue->current();
|
||||
_navigation->parentController()->show(Box(
|
||||
EditDirectMessagesPriceBox,
|
||||
_peer->asChannel(),
|
||||
(stars >= 0) ? stars : std::optional<int>(),
|
||||
[=](std::optional<int> value) {
|
||||
*_starsPerDirectMessageSavedValue = value.value_or(-1);
|
||||
}));
|
||||
}
|
||||
|
||||
void Controller::fillPrivacyTypeButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
@@ -982,9 +1024,11 @@ void Controller::fillPrivacyTypeButton() {
|
||||
void Controller::fillDiscussionLinkButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
_discussionLinkSavedValue = _discussionLinkOriginalValue = _peer->isChannel()
|
||||
? _peer->asChannel()->discussionLink()
|
||||
: nullptr;
|
||||
_discussionLinkSavedValue
|
||||
= _discussionLinkOriginalValue
|
||||
= (_peer->isChannel()
|
||||
? _peer->asChannel()->discussionLink()
|
||||
: nullptr);
|
||||
|
||||
const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
|
||||
auto text = !isGroup
|
||||
@@ -1018,6 +1062,33 @@ void Controller::fillDiscussionLinkButton() {
|
||||
{ isGroup ? &st::menuIconChannel : &st::menuIconGroups });
|
||||
_discussionLinkUpdates.fire_copy(*_discussionLinkSavedValue);
|
||||
}
|
||||
|
||||
void Controller::fillDirectMessagesButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
if (!_peer->isBroadcast() || !_peer->asChannel()->canEditInformation()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto monoforumLink = _peer->asChannel()->monoforumLink();
|
||||
_starsPerDirectMessageSavedValue = rpl::variable<int>(
|
||||
monoforumLink ? monoforumLink->starsPerMessage() : -1);
|
||||
|
||||
auto label = _starsPerDirectMessageSavedValue->value(
|
||||
) | rpl::map([](int starsPerMessage) {
|
||||
return (starsPerMessage < 0)
|
||||
? tr::lng_manage_monoforum_off()
|
||||
: !starsPerMessage
|
||||
? tr::lng_manage_monoforum_free()
|
||||
: rpl::single(Lang::FormatCountDecimal(starsPerMessage));
|
||||
}) | rpl::flatten_latest();
|
||||
AddButtonWithText(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_monoforum(),
|
||||
std::move(label),
|
||||
[=] { showEditDirectMessagesBox(); },
|
||||
{ .icon = &st::menuIconChats, .newBadge = true });
|
||||
}
|
||||
//
|
||||
//void Controller::fillInviteLinkButton() {
|
||||
// Expects(_controls.buttonsLayout != nullptr);
|
||||
@@ -1036,21 +1107,30 @@ void Controller::fillDiscussionLinkButton() {
|
||||
void Controller::fillForumButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
_forumSavedValue = _peer->isForum();
|
||||
_forumTabsSavedValue = !_peer->isChannel()
|
||||
|| !_peer->isForum()
|
||||
|| _peer->asChannel()->useSubsectionTabs();
|
||||
|
||||
const auto changes = std::make_shared<rpl::event_stream<>>();
|
||||
const auto label = [=] {
|
||||
return !*_forumSavedValue
|
||||
? tr::lng_manage_monoforum_off(tr::now)
|
||||
: *_forumTabsSavedValue
|
||||
? tr::lng_edit_topics_tabs(tr::now)
|
||||
: tr::lng_edit_topics_list(tr::now);
|
||||
};
|
||||
const auto button = _controls.forumToggle = _controls.buttonsLayout->add(
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_forum_topics_switch(),
|
||||
rpl::single(QString()),
|
||||
changes->events_starting_with({}) | rpl::map(label),
|
||||
[] {},
|
||||
st::manageGroupTopicsButton,
|
||||
{ &st::menuIconTopics }));
|
||||
const auto unlocks = std::make_shared<rpl::event_stream<bool>>();
|
||||
button->toggleOn(
|
||||
rpl::single(_peer->isForum()) | rpl::then(unlocks->events())
|
||||
)->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
if (_controls.forumToggleLocked && toggled) {
|
||||
unlocks->fire(false);
|
||||
{ .icon = &st::menuIconTopics, .newBadge = true }));
|
||||
|
||||
button->setClickedCallback(crl::guard(this, [=] {
|
||||
if (!*_forumSavedValue && _controls.forumToggleLocked) {
|
||||
if (_discussionLinkSavedValue && *_discussionLinkSavedValue) {
|
||||
ShowForumForDiscussionError(_navigation);
|
||||
} else {
|
||||
@@ -1062,13 +1142,21 @@ void Controller::fillForumButton() {
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
} else {
|
||||
_forumSavedValue = toggled;
|
||||
if (toggled) {
|
||||
_savingData.hiddenPreHistory = false;
|
||||
}
|
||||
refreshHistoryVisibility();
|
||||
_navigation->uiShow()->show(Box(
|
||||
Ui::ToggleTopicsBox,
|
||||
*_forumSavedValue,
|
||||
*_forumTabsSavedValue,
|
||||
crl::guard(this, [=](bool topics, bool topicsTabs) {
|
||||
_forumSavedValue = topics;
|
||||
_forumTabsSavedValue = !topics || topicsTabs;
|
||||
if (topics) {
|
||||
_savingData.hiddenPreHistory = false;
|
||||
}
|
||||
changes->fire({});
|
||||
refreshHistoryVisibility();
|
||||
})));
|
||||
}
|
||||
}, _controls.buttonsLayout->lifetime());
|
||||
}));
|
||||
refreshForumToggleLocked();
|
||||
}
|
||||
|
||||
@@ -1358,6 +1446,8 @@ void Controller::fillManageSection() {
|
||||
const auto canViewOrEditDiscussionLink = isChannel
|
||||
&& (channel->discussionLink()
|
||||
|| (channel->isBroadcast() && channel->canEditInformation()));
|
||||
const auto canEditDirectMessages = isChannel
|
||||
&& (channel->isBroadcast() && channel->canEditInformation());
|
||||
|
||||
::AddSkip(_controls.buttonsLayout, 0);
|
||||
|
||||
@@ -1369,6 +1459,9 @@ void Controller::fillManageSection() {
|
||||
if (canViewOrEditDiscussionLink) {
|
||||
fillDiscussionLinkButton();
|
||||
}
|
||||
if (canEditDirectMessages) {
|
||||
fillDirectMessagesButton();
|
||||
}
|
||||
if (canEditPreHistoryHidden) {
|
||||
fillHistoryVisibilityButton();
|
||||
}
|
||||
@@ -1972,6 +2065,7 @@ std::optional<Controller::Saving> Controller::validate() const {
|
||||
if (validateUsernamesOrder(result)
|
||||
&& validateUsername(result)
|
||||
&& validateDiscussionLink(result)
|
||||
&& validateDirectMessagesPrice(result)
|
||||
&& validateTitle(result)
|
||||
&& validateDescription(result)
|
||||
&& validateHistoryVisibility(result)
|
||||
@@ -2021,6 +2115,14 @@ bool Controller::validateDiscussionLink(Saving &to) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Controller::validateDirectMessagesPrice(Saving &to) const {
|
||||
if (!_starsPerDirectMessageSavedValue) {
|
||||
return true;
|
||||
}
|
||||
to.starsPerDirectMessage = _starsPerDirectMessageSavedValue->current();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Controller::validateTitle(Saving &to) const {
|
||||
if (!_controls.title) {
|
||||
return true;
|
||||
@@ -2061,6 +2163,7 @@ bool Controller::validateForum(Saving &to) const {
|
||||
return true;
|
||||
}
|
||||
to.forum = _forumSavedValue;
|
||||
to.forumTabs = _forumTabsSavedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2119,6 +2222,7 @@ void Controller::save() {
|
||||
pushSaveStage([=] { saveUsernamesOrder(); });
|
||||
pushSaveStage([=] { saveUsername(); });
|
||||
pushSaveStage([=] { saveDiscussionLink(); });
|
||||
pushSaveStage([=] { saveDirectMessagesPrice(); });
|
||||
pushSaveStage([=] { saveTitle(); });
|
||||
pushSaveStage([=] { saveDescription(); });
|
||||
pushSaveStage([=] { saveHistoryVisibility(); });
|
||||
@@ -2276,6 +2380,30 @@ void Controller::saveDiscussionLink() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Controller::saveDirectMessagesPrice() {
|
||||
const auto channel = _peer->asChannel();
|
||||
if (!channel) {
|
||||
return continueSave();
|
||||
}
|
||||
const auto monoforumLink = channel->monoforumLink();
|
||||
const auto current = monoforumLink ? monoforumLink->starsPerMessage() : -1;
|
||||
const auto desired = _savingData.starsPerDirectMessage
|
||||
? *_savingData.starsPerDirectMessage
|
||||
: current;
|
||||
if (desired == current) {
|
||||
return continueSave();
|
||||
}
|
||||
const auto show = _navigation->uiShow();
|
||||
const auto done = [=](bool ok) {
|
||||
if (ok) {
|
||||
continueSave();
|
||||
} else {
|
||||
cancelSave();
|
||||
}
|
||||
};
|
||||
SaveStarsPerMessage(show, channel, desired, crl::guard(this, done));
|
||||
}
|
||||
|
||||
void Controller::saveTitle() {
|
||||
if (!_savingData.title || *_savingData.title == _peer->name()) {
|
||||
return continueSave();
|
||||
@@ -2482,8 +2610,13 @@ void Controller::togglePreHistoryHidden(
|
||||
|
||||
void Controller::saveForum() {
|
||||
const auto channel = _peer->asChannel();
|
||||
const auto nowForum = _peer->isForum();
|
||||
const auto nowForumTabs = !channel
|
||||
|| !nowForum
|
||||
|| channel->useSubsectionTabs();
|
||||
if (!_savingData.forum
|
||||
|| *_savingData.forum == _peer->isForum()) {
|
||||
|| (*_savingData.forum == nowForum
|
||||
&& *_savingData.forumTabs == nowForumTabs)) {
|
||||
return continueSave();
|
||||
} else if (!channel) {
|
||||
const auto saveForChannel = [=](not_null<ChannelData*> channel) {
|
||||
@@ -2500,7 +2633,8 @@ void Controller::saveForum() {
|
||||
}
|
||||
_api.request(MTPchannels_ToggleForum(
|
||||
channel->inputChannel,
|
||||
MTP_bool(*_savingData.forum)
|
||||
MTP_bool(*_savingData.forum),
|
||||
MTP_bool(*_savingData.forum && *_savingData.forumTabs)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
const auto weak = base::make_weak(this);
|
||||
channel->session().api().applyUpdates(result);
|
||||
@@ -2829,7 +2963,9 @@ bool EditPeerInfoBox::Available(not_null<PeerData*> peer) {
|
||||
|
||||
// canViewAdmins() is removed, because in supergroups it is
|
||||
// always true and in channels it is equal to canViewBanned().
|
||||
|
||||
if (channel->isMonoforum()) {
|
||||
return false;
|
||||
}
|
||||
return false
|
||||
//|| channel->canViewMembers()
|
||||
//|| channel->canViewAdmins()
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
@@ -1163,8 +1164,11 @@ void SingleRowController::prepare() {
|
||||
return;
|
||||
}
|
||||
const auto topic = strong->asTopic();
|
||||
const auto sublist = strong->asSublist();
|
||||
auto row = topic
|
||||
? ChooseTopicBoxController::MakeRow(topic)
|
||||
: sublist
|
||||
? std::make_unique<PeerListRow>(sublist->sublistPeer())
|
||||
: std::make_unique<PeerListRow>(strong->peer());
|
||||
const auto raw = row.get();
|
||||
if (_status) {
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace {
|
||||
constexpr auto kSlowmodeValues = 7;
|
||||
constexpr auto kBoostsUnrestrictValues = 5;
|
||||
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
|
||||
constexpr auto kDefaultChargeStars = 10;
|
||||
|
||||
[[nodiscard]] auto Dependencies(PowerSaving::Flags)
|
||||
-> std::vector<std::pair<PowerSaving::Flag, PowerSaving::Flag>> {
|
||||
@@ -1196,7 +1197,8 @@ void ShowEditPeerPermissionsBox(
|
||||
state->starsPerMessage = SetupChargeSlider(
|
||||
chargeInner,
|
||||
peer,
|
||||
starsPerMessage);
|
||||
(starsPerMessage > 0) ? starsPerMessage : std::optional<int>(),
|
||||
kDefaultChargeStars);
|
||||
}
|
||||
|
||||
static constexpr auto kSendRestrictions = Flag::EmbedLinks
|
||||
|
||||
228
Telegram/SourceFiles/boxes/peers/toggle_topics_box.cpp
Normal file
228
Telegram/SourceFiles/boxes/peers/toggle_topics_box.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/toggle_topics_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
enum class LayoutType {
|
||||
Tabs,
|
||||
List
|
||||
};
|
||||
|
||||
class LayoutButton final : public Ui::RippleButton {
|
||||
public:
|
||||
LayoutButton(
|
||||
QWidget *parent,
|
||||
LayoutType type,
|
||||
std::shared_ptr<Ui::RadioenumGroup<LayoutType>> group);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
Ui::FlatLabel _text;
|
||||
Ui::Animations::Simple _activeAnimation;
|
||||
bool _active = false;
|
||||
|
||||
};
|
||||
|
||||
LayoutButton::LayoutButton(
|
||||
QWidget *parent,
|
||||
LayoutType type,
|
||||
std::shared_ptr<Ui::RadioenumGroup<LayoutType>> group)
|
||||
: RippleButton(parent, st::defaultRippleAnimationBgOver)
|
||||
, _text(this, st::topicsLayoutButtonLabel)
|
||||
, _active(group->current() == type) {
|
||||
_text.setText(type == LayoutType::Tabs
|
||||
? tr::lng_edit_topics_tabs(tr::now)
|
||||
: tr::lng_edit_topics_list(tr::now));
|
||||
const auto iconColorOverride = [=] {
|
||||
return anim::color(
|
||||
st::windowSubTextFg,
|
||||
st::windowActiveTextFg,
|
||||
_activeAnimation.value(_active ? 1. : 0.));
|
||||
};
|
||||
const auto iconSize = st::topicsLayoutButtonIconSize;
|
||||
auto [iconWidget, iconAnimate] = Settings::CreateLottieIcon(
|
||||
this,
|
||||
{
|
||||
.name = (type == LayoutType::Tabs
|
||||
? u"topics_tabs"_q
|
||||
: u"topics_list"_q),
|
||||
.color = &st::windowSubTextFg,
|
||||
.sizeOverride = { iconSize, iconSize },
|
||||
.colorizeUsingAlpha = true,
|
||||
},
|
||||
st::topicsLayoutButtonIconPadding,
|
||||
iconColorOverride);
|
||||
const auto icon = iconWidget.release();
|
||||
setClickedCallback([=] {
|
||||
group->setValue(type);
|
||||
iconAnimate(anim::repeat::once);
|
||||
});
|
||||
group->value() | rpl::start_with_next([=](LayoutType value) {
|
||||
const auto active = (value == type);
|
||||
_text.setTextColorOverride(active
|
||||
? st::windowFgActive->c
|
||||
: std::optional<QColor>());
|
||||
|
||||
if (_active == active) {
|
||||
return;
|
||||
}
|
||||
_active = active;
|
||||
_text.update();
|
||||
_activeAnimation.start([=] {
|
||||
icon->update();
|
||||
}, _active ? 0. : 1., _active ? 0. : 1., st::fadeWrapDuration);
|
||||
}, lifetime());
|
||||
|
||||
_text.paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||
if (_active) {
|
||||
auto p = QPainter(&_text);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto radius = _text.height() / 2.;
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBgActive);
|
||||
p.drawRoundedRect(_text.rect(), radius, radius);
|
||||
}
|
||||
}, _text.lifetime());
|
||||
|
||||
const auto padding = st::topicsLayoutButtonPadding;
|
||||
const auto skip = st::topicsLayoutButtonSkip;
|
||||
const auto text = _text.height();
|
||||
|
||||
resize(
|
||||
padding.left() + icon->width() + padding.right(),
|
||||
padding.top() + icon->height() + skip + text + padding.bottom());
|
||||
icon->move(padding.left(), padding.top());
|
||||
_text.move(
|
||||
(width() - _text.width()) / 2,
|
||||
padding.top() + icon->height() + skip);
|
||||
}
|
||||
|
||||
void LayoutButton::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
const auto rippleBg = anim::color(
|
||||
st::windowBgOver,
|
||||
st::lightButtonBgOver,
|
||||
_activeAnimation.value(_active ? 1. : 0.));
|
||||
paintRipple(p, QPoint(), &rippleBg);
|
||||
}
|
||||
|
||||
QImage LayoutButton::prepareRippleMask() const {
|
||||
return Ui::RippleAnimation::RoundRectMask(size(), st::boxRadius);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ToggleTopicsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
bool enabled,
|
||||
bool tabs,
|
||||
Fn<void(bool enabled, bool tabs)> callback) {
|
||||
box->setTitle(tr::lng_forum_topics_switch());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
Settings::AddDividerTextWithLottie(container, {
|
||||
.lottie = u"topics"_q,
|
||||
.lottieSize = st::settingsFilterIconSize,
|
||||
.lottieMargins = st::settingsFilterIconPadding,
|
||||
.showFinished = box->showFinishes(),
|
||||
.about = tr::lng_edit_topics_about(
|
||||
Ui::Text::RichLangValue
|
||||
),
|
||||
.aboutMargins = st::settingsFilterDividerLabelPadding,
|
||||
});
|
||||
|
||||
Ui::AddSkip(container);
|
||||
|
||||
const auto toggle = container->add(
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
container,
|
||||
tr::lng_edit_topics_enable(),
|
||||
st::settingsButtonNoIcon));
|
||||
toggle->toggleOn(rpl::single(enabled));
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
const auto group = std::make_shared<Ui::RadioenumGroup<LayoutType>>(tabs
|
||||
? LayoutType::Tabs
|
||||
: LayoutType::List);
|
||||
|
||||
const auto layoutWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
const auto layout = layoutWrap->entity();
|
||||
|
||||
Ui::AddSubsectionTitle(layout, tr::lng_edit_topics_layout());
|
||||
const auto buttons = layout->add(
|
||||
object_ptr<Ui::RpWidget>(layout),
|
||||
QMargins(0, 0, 0, st::defaultVerticalListSkip * 2));
|
||||
|
||||
const auto tabsButton = Ui::CreateChild<LayoutButton>(
|
||||
buttons,
|
||||
LayoutType::Tabs,
|
||||
group);
|
||||
const auto listButton = Ui::CreateChild<LayoutButton>(
|
||||
buttons,
|
||||
LayoutType::List,
|
||||
group);
|
||||
|
||||
buttons->resize(container->width(), tabsButton->height());
|
||||
buttons->widthValue() | rpl::start_with_next([=](int outer) {
|
||||
const auto skip = st::boxRowPadding.left() - st::boxRadius;
|
||||
tabsButton->moveToLeft(skip, 0, outer);
|
||||
listButton->moveToRight(skip, 0, outer);
|
||||
}, buttons->lifetime());
|
||||
|
||||
Ui::AddDividerText(
|
||||
layout,
|
||||
tr::lng_edit_topics_layout_about(Ui::Text::RichLangValue));
|
||||
|
||||
layoutWrap->toggle(enabled, anim::type::instant);
|
||||
toggle->toggledChanges(
|
||||
) | rpl::start_with_next([=](bool checked) {
|
||||
layoutWrap->toggle(checked, anim::type::normal);
|
||||
}, layoutWrap->lifetime());
|
||||
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
const auto enabledValue = toggle->toggled();
|
||||
const auto tabsValue = (group->current() == LayoutType::Tabs);
|
||||
callback(enabledValue, tabsValue);
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
20
Telegram/SourceFiles/boxes/peers/toggle_topics_box.h
Normal file
20
Telegram/SourceFiles/boxes/peers/toggle_topics_box.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void ToggleTopicsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
bool enabled,
|
||||
bool tabs,
|
||||
Fn<void(bool enabled, bool tabs)> callback);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -24,10 +24,15 @@ namespace {
|
||||
[[nodiscard]] bool IsOldForPin(
|
||||
MsgId id,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId) {
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId) {
|
||||
const auto normal = peer->migrateToOrMe();
|
||||
const auto migrated = normal->migrateFrom();
|
||||
const auto top = Data::ResolveTopPinnedId(normal, topicRootId, migrated);
|
||||
const auto top = Data::ResolveTopPinnedId(
|
||||
normal,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
migrated);
|
||||
if (!top) {
|
||||
return false;
|
||||
} else if (peer == migrated) {
|
||||
@@ -53,7 +58,14 @@ void PinMessageBox(
|
||||
const auto peer = item->history()->peer;
|
||||
const auto msgId = item->id;
|
||||
const auto topicRootId = item->topic() ? item->topicRootId() : MsgId();
|
||||
const auto pinningOld = IsOldForPin(msgId, peer, topicRootId);
|
||||
const auto monoforumPeerId = item->history()->peer->amMonoforumAdmin()
|
||||
? item->sublistPeerId()
|
||||
: PeerId();
|
||||
const auto pinningOld = IsOldForPin(
|
||||
msgId,
|
||||
peer,
|
||||
topicRootId,
|
||||
monoforumPeerId);
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
const auto api = box->lifetime().make_state<MTP::Sender>(
|
||||
&peer->session().mtp());
|
||||
@@ -71,7 +83,9 @@ void PinMessageBox(
|
||||
object->setAllowTextLines();
|
||||
state->pinForPeer = Ui::MakeWeak(object.data());
|
||||
return object;
|
||||
} else if (!pinningOld && (peer->isChat() || peer->isMegagroup())) {
|
||||
} else if (!pinningOld
|
||||
&& (peer->isChat() || peer->isMegagroup())
|
||||
&& !peer->isMonoforum()) {
|
||||
auto object = object_ptr<Ui::Checkbox>(
|
||||
box,
|
||||
tr::lng_pinned_notify(tr::now),
|
||||
|
||||
@@ -907,6 +907,7 @@ void PinsLimitBox(
|
||||
limits.dialogsPinnedPremium(),
|
||||
PinsCount(session->data().chatsList()));
|
||||
}
|
||||
|
||||
void SublistsPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session) {
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/send_gif_with_caption_box.h"
|
||||
|
||||
#include "api/api_editing.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
@@ -18,13 +19,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_groups.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/controls/history_view_characters_limit.h"
|
||||
#include "history/view/history_view_message.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
@@ -32,10 +37,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/controls/emoji_button_factory.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -224,11 +230,10 @@ namespace {
|
||||
return input;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SendGifWithCaptionBox(
|
||||
void CaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<DocumentData*> document,
|
||||
rpl::producer<QString> confirmText,
|
||||
TextWithTags initialText,
|
||||
not_null<PeerData*> peer,
|
||||
const SendMenu::Details &details,
|
||||
Fn<void(Api::SendOptions, TextWithTags)> done) {
|
||||
@@ -237,23 +242,15 @@ void SendGifWithCaptionBox(
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
box->setTitle(tr::lng_send_gif_with_caption());
|
||||
box->setWidth(st::boxWidth);
|
||||
box->getDelegate()->setStyle(st::sendGifBox);
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
[[maybe_unused]] const auto gifWidget = AddGifWidget(
|
||||
container,
|
||||
document,
|
||||
st::boxWidth);
|
||||
|
||||
Ui::AddSkip(container);
|
||||
|
||||
const auto input = AddInputField(box, controller);
|
||||
box->setFocusCallback([=] {
|
||||
input->setFocus();
|
||||
});
|
||||
|
||||
input->setTextWithTags(std::move(initialText));
|
||||
input->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
InitMessageField(controller, input, [=](not_null<DocumentData*>) {
|
||||
return true;
|
||||
@@ -318,7 +315,7 @@ void SendGifWithCaptionBox(
|
||||
done(std::move(options), input->getTextWithTags());
|
||||
};
|
||||
const auto confirm = box->addButton(
|
||||
tr::lng_send_button(),
|
||||
std::move(confirmText),
|
||||
[=] { send({}); });
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
confirm,
|
||||
@@ -339,4 +336,89 @@ void SendGifWithCaptionBox(
|
||||
) | rpl::start_with_next([=] { send({}); }, input->lifetime());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SendGifWithCaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<DocumentData*> document,
|
||||
not_null<PeerData*> peer,
|
||||
const SendMenu::Details &details,
|
||||
Fn<void(Api::SendOptions, TextWithTags)> c) {
|
||||
box->setTitle(tr::lng_send_gif_with_caption());
|
||||
[[maybe_unused]] const auto gifWidget = AddGifWidget(
|
||||
box->verticalLayout(),
|
||||
document,
|
||||
st::boxWidth);
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
CaptionBox(box, tr::lng_send_button(), {}, peer, details, std::move(c));
|
||||
}
|
||||
|
||||
void EditCaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<HistoryView::Element*> view) {
|
||||
using namespace TextUtilities;
|
||||
|
||||
box->setTitle(tr::lng_context_upload_edit_caption());
|
||||
|
||||
const auto data = &view->data()->history()->peer->owner();
|
||||
|
||||
struct State {
|
||||
FullMsgId fullId;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->fullId = view->data()->fullId();
|
||||
|
||||
data->itemIdChanged(
|
||||
) | rpl::start_with_next([=](Data::Session::IdChange event) {
|
||||
if (event.oldId == state->fullId.msg) {
|
||||
state->fullId = event.newId;
|
||||
}
|
||||
}, box->lifetime());
|
||||
|
||||
auto done = [=, show = box->uiShow()](
|
||||
Api::SendOptions,
|
||||
TextWithTags textWithTags) {
|
||||
const auto item = data->message(state->fullId);
|
||||
if (!item) {
|
||||
show->showToast(tr::lng_message_not_found(tr::now));
|
||||
return;
|
||||
}
|
||||
if (!(item->media() && item->media()->allowsEditCaption())) {
|
||||
show->showToast(tr::lng_edit_error(tr::now));
|
||||
return;
|
||||
}
|
||||
auto text = TextWithEntities{
|
||||
std::move(textWithTags.text),
|
||||
ConvertTextTagsToEntities(std::move(textWithTags.tags)),
|
||||
};
|
||||
if (item->isUploading()) {
|
||||
item->setText(std::move(text));
|
||||
data->requestViewResize(view);
|
||||
if (item->groupId()) {
|
||||
data->groups().refreshMessage(item, true);
|
||||
}
|
||||
box->closeBox();
|
||||
} else {
|
||||
Api::EditCaption(
|
||||
item,
|
||||
std::move(text),
|
||||
{ .invertCaption = item->invertMedia() },
|
||||
[=] { box->closeBox(); },
|
||||
[=](const QString &e) { box->uiShow()->showToast(e); });
|
||||
}
|
||||
};
|
||||
|
||||
const auto item = view->data();
|
||||
CaptionBox(
|
||||
box,
|
||||
tr::lng_settings_save(),
|
||||
TextWithTags{
|
||||
.text = item->originalText().text,
|
||||
.tags = ConvertEntitiesToTextTags(item->originalText().entities),
|
||||
},
|
||||
item->history()->peer,
|
||||
{},
|
||||
std::move(done));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -18,10 +18,18 @@ namespace SendMenu {
|
||||
struct Details;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace HistoryView {
|
||||
class Element;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
|
||||
void EditCaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<HistoryView::Element*> view);
|
||||
|
||||
void SendGifWithCaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<DocumentData*> document,
|
||||
|
||||
@@ -45,6 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_forum.h"
|
||||
@@ -114,7 +116,9 @@ private:
|
||||
not_null<History*> history;
|
||||
not_null<PeerData*> peer;
|
||||
Data::ForumTopic *topic = nullptr;
|
||||
Data::SavedSublist *sublist = nullptr;
|
||||
rpl::lifetime topicLifetime;
|
||||
rpl::lifetime sublistLifetime;
|
||||
Ui::RoundImageCheckbox checkbox;
|
||||
Ui::Text::String name;
|
||||
Ui::Animations::Simple nameActive;
|
||||
@@ -143,6 +147,7 @@ private:
|
||||
void preloadUserpic(not_null<Dialogs::Entry*> entry);
|
||||
void changeCheckState(Chat *chat);
|
||||
void chooseForumTopic(not_null<Data::Forum*> forum);
|
||||
void chooseMonoforumSublist(not_null<Data::SavedMessages*> monoforum);
|
||||
enum class ChangeStateWay {
|
||||
Default,
|
||||
SkipCallback,
|
||||
@@ -638,15 +643,18 @@ void ShareBox::addPeerToMultiSelect(not_null<Data::Thread*> thread) {
|
||||
auto addItemWay = Ui::MultiSelect::AddItemWay::Default;
|
||||
const auto peer = thread->peer();
|
||||
const auto topic = thread->asTopic();
|
||||
const auto sublist = thread->asSublist();
|
||||
_select->addItem(
|
||||
peer->id.value,
|
||||
(topic
|
||||
? topic->title()
|
||||
: sublist
|
||||
? sublist->sublistPeer()->shortName()
|
||||
: peer->isSelf()
|
||||
? tr::lng_saved_short(tr::now)
|
||||
: peer->shortName()),
|
||||
st::activeButtonBg,
|
||||
(topic
|
||||
((topic || sublist)
|
||||
? ForceRoundUserpicCallback(peer)
|
||||
: PaintUserpicCallback(peer, true)),
|
||||
addItemWay);
|
||||
@@ -970,6 +978,8 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
|
||||
const auto peer = chat->peer;
|
||||
const auto text = chat->topic
|
||||
? chat->topic->title()
|
||||
: chat->sublist
|
||||
? chat->sublist->sublistPeer()->name()
|
||||
: peer->isSelf()
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: peer->isRepliesChat()
|
||||
@@ -1209,7 +1219,7 @@ ShareBox::Inner::Chat::Chat(
|
||||
st.checkbox,
|
||||
updateCallback,
|
||||
PaintUserpicCallback(peer, true),
|
||||
[=](int size) { return peer->isForum()
|
||||
[=](int size) { return (peer->isForum() || peer->isMonoforum())
|
||||
? int(size * Ui::ForumUserpicRadiusMultiplier())
|
||||
: std::optional<int>(); })
|
||||
, name(st.checkbox.imageRadius * 2) {
|
||||
@@ -1350,10 +1360,13 @@ void ShareBox::Inner::changeCheckState(Chat *chat) {
|
||||
|
||||
const auto checked = chat->checkbox.checked();
|
||||
const auto forum = chat->peer->forum();
|
||||
if (checked || !forum) {
|
||||
const auto monoforum = chat->peer->monoforum();
|
||||
if (checked || (!forum && !monoforum)) {
|
||||
changePeerCheckState(chat, !checked);
|
||||
} else {
|
||||
chooseForumTopic(chat->peer->forum());
|
||||
} else if (forum) {
|
||||
chooseForumTopic(forum);
|
||||
} else if (monoforum) {
|
||||
chooseMonoforumSublist(monoforum);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1404,6 +1417,54 @@ void ShareBox::Inner::chooseForumTopic(not_null<Data::Forum*> forum) {
|
||||
_show->showBox(std::move(box));
|
||||
}
|
||||
|
||||
void ShareBox::Inner::chooseMonoforumSublist(
|
||||
not_null<Data::SavedMessages*> monoforum) {
|
||||
const auto guard = Ui::MakeWeak(this);
|
||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
auto chosen = [=](not_null<Data::SavedSublist*> sublist) {
|
||||
if (const auto strong = *weak) {
|
||||
strong->closeBox();
|
||||
}
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
const auto row = _chatsIndexed->getRow(sublist->owningHistory());
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const auto chat = getChat(row);
|
||||
Assert(!chat->sublist);
|
||||
chat->sublist = sublist;
|
||||
chat->sublist->destroyed(
|
||||
) | rpl::start_with_next([=] {
|
||||
changePeerCheckState(chat, false);
|
||||
}, chat->sublistLifetime);
|
||||
updateChatName(chat);
|
||||
changePeerCheckState(chat, true);
|
||||
};
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
monoforum->destroyed(
|
||||
) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
};
|
||||
auto filter = [=](not_null<Data::SavedSublist*> sublist) {
|
||||
return guard && _descriptor.filterCallback(sublist);
|
||||
};
|
||||
auto box = Box<PeerListBox>(
|
||||
std::make_unique<ChooseSublistBoxController>(
|
||||
monoforum,
|
||||
std::move(chosen),
|
||||
std::move(filter)),
|
||||
std::move(initBox));
|
||||
*weak = box.data();
|
||||
_show->showBox(std::move(box));
|
||||
}
|
||||
|
||||
void ShareBox::Inner::peerUnselected(not_null<PeerData*> peer) {
|
||||
if (const auto i = _dataMap.find(peer); i != end(_dataMap)) {
|
||||
changePeerCheckState(
|
||||
@@ -1434,6 +1495,11 @@ void ShareBox::Inner::changePeerCheckState(
|
||||
chat->topic = nullptr;
|
||||
updateChatName(chat);
|
||||
}
|
||||
if (chat->sublist) {
|
||||
chat->sublistLifetime.destroy();
|
||||
chat->sublist = nullptr;
|
||||
updateChatName(chat);
|
||||
}
|
||||
}
|
||||
if (useCallback != ChangeStateWay::SkipCallback
|
||||
&& _peerSelectedChangedCallback) {
|
||||
@@ -1565,6 +1631,8 @@ not_null<Data::Thread*> ShareBox::Inner::chatThread(
|
||||
not_null<Chat*> chat) const {
|
||||
return chat->topic
|
||||
? (Data::Thread*)chat->topic
|
||||
: chat->sublist
|
||||
? (Data::Thread*)chat->sublist
|
||||
: chat->peer->owner().history(chat->peer).get();
|
||||
}
|
||||
|
||||
@@ -1675,6 +1743,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
api.sendMessage(std::move(message));
|
||||
}
|
||||
const auto topicRootId = thread->topicRootId();
|
||||
const auto sublistPeer = thread->maybeSublistPeer();
|
||||
const auto kGeneralId = Data::ForumTopic::kGeneralId;
|
||||
const auto topMsgId = (topicRootId == kGeneralId)
|
||||
? MsgId(0)
|
||||
@@ -1699,7 +1768,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
| (options.shortcutId
|
||||
? Flag::f_quick_reply_shortcut
|
||||
: Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag());
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag())
|
||||
| (sublistPeer ? Flag::f_reply_to : Flag());
|
||||
threadHistory->sendRequestId = api.request(
|
||||
MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -1708,6 +1778,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
MTP_vector<MTPlong>(generateRandom()),
|
||||
peer->input,
|
||||
MTP_int(topMsgId),
|
||||
(sublistPeer
|
||||
? MTP_inputReplyToMonoForum(sublistPeer->input)
|
||||
: MTPInputReplyTo()),
|
||||
MTP_int(options.scheduled),
|
||||
MTP_inputPeerEmpty(), // send_as
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||
|
||||
@@ -269,6 +269,11 @@ whenReadPadding: margins(34px, 3px, 17px, 4px);
|
||||
whenReadIconPosition: point(8px, 0px);
|
||||
whenReadSkip: 3px;
|
||||
whenReadShowPadding: margins(6px, 0px, 6px, 2px);
|
||||
whoSentItem: Menu(defaultMenu) {
|
||||
itemPadding: margins(17px, 3px, 17px, 4px);
|
||||
itemRightSkip: 0px;
|
||||
itemStyle: whenReadStyle;
|
||||
}
|
||||
|
||||
switchPmButton: RoundButton(defaultBoxButton) {
|
||||
width: 320px;
|
||||
@@ -871,6 +876,10 @@ historyGiftToChannel: IconButton(defaultIconButton) {
|
||||
rippleAreaSize: 40px;
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
historyDirectMessage: IconButton(historyGiftToChannel) {
|
||||
icon: icon{{ "menu/chat_discuss", windowActiveTextFg }};
|
||||
iconOver: icon{{ "menu/chat_discuss", windowActiveTextFg }};
|
||||
}
|
||||
historyUnblock: FlatButton(historyComposeButton) {
|
||||
color: attentionButtonFg;
|
||||
overColor: attentionButtonFgOver;
|
||||
@@ -1594,3 +1603,5 @@ frozenInfoBox: Box(defaultBox) {
|
||||
}
|
||||
shadowIgnoreTopSkip: true;
|
||||
}
|
||||
|
||||
roundVideoFont: font(14px semibold);
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
bool elementAnimationsPaused() override;
|
||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
HistoryView::Context elementContext() override;
|
||||
bool elementIsChatWide() override;
|
||||
HistoryView::ElementChatMode elementChatMode() override;
|
||||
|
||||
private:
|
||||
const not_null<QWidget*> _parent;
|
||||
@@ -83,8 +83,9 @@ HistoryView::Context PreviewDelegate::elementContext() {
|
||||
return HistoryView::Context::TTLViewer;
|
||||
}
|
||||
|
||||
bool PreviewDelegate::elementIsChatWide() {
|
||||
return _chatWide.current();
|
||||
HistoryView::ElementChatMode PreviewDelegate::elementChatMode() {
|
||||
using Mode = HistoryView::ElementChatMode;
|
||||
return _chatWide.current() ? Mode::Wide : Mode::Default;
|
||||
}
|
||||
|
||||
class PreviewWrap final : public Ui::RpWidget {
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
|
||||
#include "data/data_abstract_structure.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -1377,8 +1378,9 @@ Window::Controller *Application::windowForShowingHistory(
|
||||
|
||||
Window::Controller *Application::windowForShowingForum(
|
||||
not_null<Data::Forum*> forum) const {
|
||||
const auto tabs = forum->channel()->useSubsectionTabs();
|
||||
const auto id = Window::SeparateId(
|
||||
Window::SeparateType::Forum,
|
||||
tabs ? Window::SeparateType::Chat : Window::SeparateType::Forum,
|
||||
forum->history());
|
||||
if (const auto separate = separateWindowFor(id)) {
|
||||
return separate;
|
||||
@@ -1386,9 +1388,15 @@ Window::Controller *Application::windowForShowingForum(
|
||||
auto result = (Window::Controller*)nullptr;
|
||||
enumerateWindows([&](not_null<Window::Controller*> window) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
const auto current = controller->shownForum().current();
|
||||
if (forum == current) {
|
||||
result = window;
|
||||
if (tabs) {
|
||||
if (controller->windowId() == id) {
|
||||
result = window;
|
||||
}
|
||||
} else {
|
||||
const auto current = controller->shownForum().current();
|
||||
if (forum == current) {
|
||||
result = window;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -847,6 +847,7 @@ bool OpenMediaTimestamp(
|
||||
document,
|
||||
context,
|
||||
context ? context->topicRootId() : MsgId(0),
|
||||
context ? context->sublistPeerId() : PeerId(0),
|
||||
false,
|
||||
time));
|
||||
} else if (document->isSong() || document->isVoiceMessage()) {
|
||||
|
||||
@@ -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 = 5014003;
|
||||
constexpr auto AppVersionStr = "5.14.3";
|
||||
constexpr auto AppVersion = 5015000;
|
||||
constexpr auto AppVersionStr = "5.15";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -237,7 +237,9 @@ void PromoSuggestions::invalidate() {
|
||||
}
|
||||
|
||||
std::optional<CustomSuggestion> PromoSuggestions::custom() const {
|
||||
return _custom;
|
||||
return (_custom && !_dismissedSuggestions.contains(_custom->suggestion))
|
||||
? _custom
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
void PromoSuggestions::requestContactBirthdays(Fn<void()> done, bool force) {
|
||||
@@ -304,4 +306,9 @@ std::optional<UserIds> PromoSuggestions::knownBirthdaysToday() const {
|
||||
return _contactBirthdaysToday;
|
||||
}
|
||||
|
||||
QString PromoSuggestions::SugValidatePassword() {
|
||||
static const auto key = u"VALIDATE_PASSWORD"_q;
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -51,6 +51,8 @@ public:
|
||||
[[nodiscard]] auto knownBirthdaysToday() const
|
||||
-> std::optional<std::vector<UserId>>;
|
||||
|
||||
[[nodiscard]] static QString SugValidatePassword();
|
||||
|
||||
private:
|
||||
void setTopPromoted(
|
||||
History *promoted,
|
||||
|
||||
@@ -204,6 +204,42 @@ void Changes::topicRemoved(not_null<ForumTopic*> topic) {
|
||||
_topicChanges.drop(topic);
|
||||
}
|
||||
|
||||
void Changes::sublistUpdated(
|
||||
not_null<SavedSublist*> sublist,
|
||||
SublistUpdate::Flags flags) {
|
||||
const auto drop = (flags & SublistUpdate::Flag::Destroyed);
|
||||
_sublistChanges.updated(sublist, flags, drop);
|
||||
if (!drop) {
|
||||
scheduleNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<SublistUpdate> Changes::sublistUpdates(
|
||||
SublistUpdate::Flags flags) const {
|
||||
return _sublistChanges.updates(flags);
|
||||
}
|
||||
|
||||
rpl::producer<SublistUpdate> Changes::sublistUpdates(
|
||||
not_null<SavedSublist*> sublist,
|
||||
SublistUpdate::Flags flags) const {
|
||||
return _sublistChanges.updates(sublist, flags);
|
||||
}
|
||||
|
||||
rpl::producer<SublistUpdate> Changes::sublistFlagsValue(
|
||||
not_null<SavedSublist*> sublist,
|
||||
SublistUpdate::Flags flags) const {
|
||||
return _sublistChanges.flagsValue(sublist, flags);
|
||||
}
|
||||
|
||||
rpl::producer<SublistUpdate> Changes::realtimeSublistUpdates(
|
||||
SublistUpdate::Flag flag) const {
|
||||
return _sublistChanges.realtimeUpdates(flag);
|
||||
}
|
||||
|
||||
void Changes::sublistRemoved(not_null<SavedSublist*> sublist) {
|
||||
_sublistChanges.drop(sublist);
|
||||
}
|
||||
|
||||
void Changes::messageUpdated(
|
||||
not_null<HistoryItem*> item,
|
||||
MessageUpdate::Flags flags) {
|
||||
@@ -323,6 +359,7 @@ void Changes::sendNotifications() {
|
||||
_messageChanges.sendNotifications();
|
||||
_entryChanges.sendNotifications();
|
||||
_topicChanges.sendNotifications();
|
||||
_sublistChanges.sendNotifications();
|
||||
_storyChanges.sendNotifications();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) {
|
||||
namespace Data {
|
||||
|
||||
class ForumTopic;
|
||||
class SavedSublist;
|
||||
class Story;
|
||||
|
||||
struct NameUpdate {
|
||||
@@ -112,12 +113,13 @@ struct PeerUpdate {
|
||||
StickersSet = (1ULL << 46),
|
||||
EmojiSet = (1ULL << 47),
|
||||
DiscussionLink = (1ULL << 48),
|
||||
ChannelLocation = (1ULL << 49),
|
||||
Slowmode = (1ULL << 50),
|
||||
GroupCall = (1ULL << 51),
|
||||
MonoforumLink = (1ULL << 49),
|
||||
ChannelLocation = (1ULL << 50),
|
||||
Slowmode = (1ULL << 51),
|
||||
GroupCall = (1ULL << 52),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 51),
|
||||
LastUsedBit = (1ULL << 52),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
@@ -183,6 +185,25 @@ struct TopicUpdate {
|
||||
|
||||
};
|
||||
|
||||
struct SublistUpdate {
|
||||
enum class Flag : uint32 {
|
||||
None = 0,
|
||||
|
||||
UnreadView = (1U << 1),
|
||||
UnreadReactions = (1U << 2),
|
||||
CloudDraft = (1U << 3),
|
||||
Destroyed = (1U << 4),
|
||||
|
||||
LastUsedBit = (1U << 4),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
not_null<SavedSublist*> sublist;
|
||||
Flags flags = 0;
|
||||
|
||||
};
|
||||
|
||||
struct MessageUpdate {
|
||||
enum class Flag : uint32 {
|
||||
None = 0,
|
||||
@@ -304,6 +325,21 @@ public:
|
||||
TopicUpdate::Flag flag) const;
|
||||
void topicRemoved(not_null<ForumTopic*> topic);
|
||||
|
||||
void sublistUpdated(
|
||||
not_null<SavedSublist*> sublist,
|
||||
SublistUpdate::Flags flags);
|
||||
[[nodiscard]] rpl::producer<SublistUpdate> sublistUpdates(
|
||||
SublistUpdate::Flags flags) const;
|
||||
[[nodiscard]] rpl::producer<SublistUpdate> sublistUpdates(
|
||||
not_null<SavedSublist*> sublist,
|
||||
SublistUpdate::Flags flags) const;
|
||||
[[nodiscard]] rpl::producer<SublistUpdate> sublistFlagsValue(
|
||||
not_null<SavedSublist*> sublist,
|
||||
SublistUpdate::Flags flags) const;
|
||||
[[nodiscard]] rpl::producer<SublistUpdate> realtimeSublistUpdates(
|
||||
SublistUpdate::Flag flag) const;
|
||||
void sublistRemoved(not_null<SavedSublist*> sublist);
|
||||
|
||||
void messageUpdated(
|
||||
not_null<HistoryItem*> item,
|
||||
MessageUpdate::Flags flags);
|
||||
@@ -395,6 +431,7 @@ private:
|
||||
Manager<PeerData, PeerUpdate> _peerChanges;
|
||||
Manager<History, HistoryUpdate> _historyChanges;
|
||||
Manager<ForumTopic, TopicUpdate> _topicChanges;
|
||||
Manager<SavedSublist, SublistUpdate> _sublistChanges;
|
||||
Manager<HistoryItem, MessageUpdate> _messageChanges;
|
||||
Manager<Dialogs::Entry, EntryUpdate> _entryChanges;
|
||||
Manager<Story, StoryUpdate> _storyChanges;
|
||||
|
||||
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_wall_paper.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -89,6 +90,29 @@ std::unique_ptr<Data::Forum> MegagroupInfo::takeForumData() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MegagroupInfo::ensureMonoforum(not_null<ChannelData*> that) {
|
||||
if (!_monoforum) {
|
||||
const auto history = that->owner().history(that);
|
||||
_monoforum = std::make_unique<Data::SavedMessages>(
|
||||
&that->owner(),
|
||||
that);
|
||||
history->monoforumChanged(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Data::SavedMessages *MegagroupInfo::monoforum() const {
|
||||
return _monoforum.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<Data::SavedMessages> MegagroupInfo::takeMonoforumData() {
|
||||
if (auto result = base::take(_monoforum)) {
|
||||
const auto history = result->owner().history(result->parentChat());
|
||||
history->monoforumChanged(result.get());
|
||||
return result;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
|
||||
: PeerData(owner, id)
|
||||
, inputChannel(
|
||||
@@ -161,6 +185,15 @@ void ChannelData::setAccessHash(uint64 accessHash) {
|
||||
}
|
||||
|
||||
void ChannelData::setFlags(ChannelDataFlags which) {
|
||||
if (which & Flag::MonoforumAdmin) {
|
||||
which |= Flag::Monoforum;
|
||||
}
|
||||
if (which & (Flag::Forum | Flag::Monoforum)) {
|
||||
which |= Flag::Megagroup;
|
||||
}
|
||||
if (which & Flag::Monoforum) {
|
||||
which &= ~Flag::Forum;
|
||||
}
|
||||
const auto diff = flags() ^ which;
|
||||
if ((which & Flag::Megagroup) && !mgInfo) {
|
||||
mgInfo = std::make_unique<MegagroupInfo>();
|
||||
@@ -169,11 +202,17 @@ void ChannelData::setFlags(ChannelDataFlags which) {
|
||||
// Let Data::Forum live till the end of _flags.set.
|
||||
// That way the data can be used in changes handler.
|
||||
// Example: render frame for forum auto-closing animation.
|
||||
const auto taken = ((diff & Flag::Forum) && !(which & Flag::Forum))
|
||||
const auto takenForum = ((diff & Flag::Forum) && !(which & Flag::Forum))
|
||||
? mgInfo->takeForumData()
|
||||
: nullptr;
|
||||
const auto takenMonoforum = ((diff & Flag::MonoforumAdmin)
|
||||
&& !(which & Flag::MonoforumAdmin))
|
||||
? mgInfo->takeMonoforumData()
|
||||
: nullptr;
|
||||
const auto wasIn = amIn();
|
||||
if ((diff & Flag::Forum) && (which & Flag::Forum)) {
|
||||
if ((diff & Flag::MonoforumAdmin) && (which & Flag::MonoforumAdmin)) {
|
||||
mgInfo->ensureMonoforum(this);
|
||||
} else if ((diff & Flag::Forum) && (which & Flag::Forum)) {
|
||||
mgInfo->ensureForum(this);
|
||||
}
|
||||
_flags.set(which);
|
||||
@@ -192,6 +231,7 @@ void ChannelData::setFlags(ChannelDataFlags which) {
|
||||
}
|
||||
}
|
||||
if (diff & (Flag::Forum
|
||||
| Flag::MonoforumAdmin
|
||||
| Flag::CallNotEmpty
|
||||
| Flag::SimilarExpanded
|
||||
| Flag::Signatures
|
||||
@@ -200,12 +240,14 @@ void ChannelData::setFlags(ChannelDataFlags which) {
|
||||
if (diff & Flag::CallNotEmpty) {
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
if (diff & Flag::Forum) {
|
||||
if (diff & (Flag::Forum | Flag::MonoforumAdmin)) {
|
||||
Core::App().notifications().clearFromHistory(history);
|
||||
history->updateChatListEntryHeight();
|
||||
if (history->inChatList()) {
|
||||
if (const auto forum = this->forum()) {
|
||||
forum->preloadTopics();
|
||||
} else if (const auto monoforum = this->monoforum()) {
|
||||
monoforum->preloadSublists();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +264,7 @@ void ChannelData::setFlags(ChannelDataFlags which) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto raw = taken.get()) {
|
||||
if (const auto raw = takenForum.get()) {
|
||||
owner().forumIcons().clearUserpicsReset(raw);
|
||||
}
|
||||
}
|
||||
@@ -276,8 +318,9 @@ const ChannelLocation *ChannelData::getLocation() const {
|
||||
}
|
||||
|
||||
void ChannelData::setDiscussionLink(ChannelData *linked) {
|
||||
if (_discussionLink != linked) {
|
||||
if (_discussionLink != linked || !_discussionLinkKnown) {
|
||||
_discussionLink = linked;
|
||||
_discussionLinkKnown = true;
|
||||
if (const auto history = owner().historyLoaded(this)) {
|
||||
history->forceFullResize();
|
||||
}
|
||||
@@ -286,11 +329,27 @@ void ChannelData::setDiscussionLink(ChannelData *linked) {
|
||||
}
|
||||
|
||||
ChannelData *ChannelData::discussionLink() const {
|
||||
return _discussionLink.value_or(nullptr);
|
||||
return _discussionLink;
|
||||
}
|
||||
|
||||
bool ChannelData::discussionLinkKnown() const {
|
||||
return _discussionLink.has_value();
|
||||
return _discussionLinkKnown;
|
||||
}
|
||||
|
||||
void ChannelData::setMonoforumLink(ChannelData *link) {
|
||||
if (_monoforumLink || !link) {
|
||||
return;
|
||||
}
|
||||
_monoforumLink = link;
|
||||
link->setMonoforumLink(this);
|
||||
session().changes().peerUpdated(this, UpdateFlag::MonoforumLink);
|
||||
if (isMegagroup() && link->canAccessMonoforum()) {
|
||||
setFlags(flags() | Flag::MonoforumAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
ChannelData *ChannelData::monoforumLink() const {
|
||||
return _monoforumLink;
|
||||
}
|
||||
|
||||
void ChannelData::setMembersCount(int newMembersCount) {
|
||||
@@ -349,6 +408,11 @@ void ChannelData::setPendingRequestsCount(
|
||||
}
|
||||
}
|
||||
|
||||
bool ChannelData::useSubsectionTabs() const {
|
||||
return isForum()
|
||||
&& (flags() & ChannelDataFlag::ForumTabs);
|
||||
}
|
||||
|
||||
ChatRestrictionsInfo ChannelData::KickedRestrictedRights(
|
||||
not_null<PeerData*> participant) {
|
||||
using Flag = ChatRestriction;
|
||||
@@ -588,6 +652,9 @@ bool ChannelData::canPostStories() const {
|
||||
}
|
||||
|
||||
bool ChannelData::canEditStories() const {
|
||||
if (isMonoforum()) {
|
||||
return false;
|
||||
}
|
||||
return amCreator()
|
||||
|| (adminRights() & AdminRight::EditStories);
|
||||
}
|
||||
@@ -597,6 +664,10 @@ bool ChannelData::canDeleteStories() const {
|
||||
|| (adminRights() & AdminRight::DeleteStories);
|
||||
}
|
||||
|
||||
bool ChannelData::canAccessMonoforum() const {
|
||||
return canPostMessages();
|
||||
}
|
||||
|
||||
bool ChannelData::canPostPaidMedia() const {
|
||||
return canPostMessages() && (flags() & Flag::PaidMediaAllowed);
|
||||
}
|
||||
@@ -610,7 +681,9 @@ bool ChannelData::hiddenPreHistory() const {
|
||||
}
|
||||
|
||||
bool ChannelData::canAddMembers() const {
|
||||
return isMegagroup()
|
||||
return isMonoforum()
|
||||
? false
|
||||
: isMegagroup()
|
||||
? !amRestricted(ChatRestriction::AddParticipants)
|
||||
: ((adminRights() & AdminRight::InviteByLinkOrAdd) || amCreator());
|
||||
}
|
||||
@@ -775,6 +848,11 @@ void ChannelData::setAdminRights(ChatAdminRights rights) {
|
||||
session().changes().peerUpdated(
|
||||
this,
|
||||
UpdateFlag::Rights | UpdateFlag::Admins | UpdateFlag::BannedUsers);
|
||||
if (isBroadcast() && _monoforumLink) {
|
||||
const auto flags = _monoforumLink->flags();
|
||||
_monoforumLink->setFlags((flags & ~Flag::MonoforumAdmin)
|
||||
| (canAccessMonoforum() ? Flag::MonoforumAdmin : Flag()));
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelData::setRestrictions(ChatRestrictionsInfo rights) {
|
||||
@@ -869,15 +947,12 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) {
|
||||
}
|
||||
|
||||
int ChannelData::starsPerMessage() const {
|
||||
if (const auto info = mgInfo.get()) {
|
||||
return info->_starsPerMessage;
|
||||
}
|
||||
return 0;
|
||||
return _starsPerMessage;
|
||||
}
|
||||
|
||||
void ChannelData::setStarsPerMessage(int stars) {
|
||||
if (mgInfo && starsPerMessage() != stars) {
|
||||
mgInfo->_starsPerMessage = stars;
|
||||
if (_starsPerMessage != stars) {
|
||||
_starsPerMessage = stars;
|
||||
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
|
||||
}
|
||||
checkTrustedPayForMessage();
|
||||
|
||||
@@ -16,6 +16,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class ChannelData;
|
||||
|
||||
namespace Data {
|
||||
class Forum;
|
||||
class SavedMessages;
|
||||
} // namespace Data
|
||||
|
||||
struct ChannelLocation {
|
||||
QString address;
|
||||
Data::LocationPoint point;
|
||||
@@ -74,6 +79,9 @@ enum class ChannelDataFlag : uint64 {
|
||||
StargiftsAvailable = (1ULL << 36),
|
||||
PaidMessagesAvailable = (1ULL << 37),
|
||||
AutoTranslation = (1ULL << 38),
|
||||
Monoforum = (1ULL << 39),
|
||||
MonoforumAdmin = (1ULL << 40),
|
||||
ForumTabs = (1ULL << 41),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||
@@ -118,6 +126,10 @@ public:
|
||||
[[nodiscard]] Data::Forum *forum() const;
|
||||
[[nodiscard]] std::unique_ptr<Data::Forum> takeForumData();
|
||||
|
||||
void ensureMonoforum(not_null<ChannelData*> that);
|
||||
[[nodiscard]] Data::SavedMessages *monoforum() const;
|
||||
[[nodiscard]] std::unique_ptr<Data::SavedMessages> takeMonoforumData();
|
||||
|
||||
std::deque<not_null<UserData*>> lastParticipants;
|
||||
base::flat_map<not_null<UserData*>, Admin> lastAdmins;
|
||||
base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
|
||||
@@ -154,7 +166,7 @@ private:
|
||||
ChannelLocation _location;
|
||||
Data::ChatBotCommands _botCommands;
|
||||
std::unique_ptr<Data::Forum> _forum;
|
||||
int _starsPerMessage = 0;
|
||||
std::unique_ptr<Data::SavedMessages> _monoforum;
|
||||
|
||||
friend class ChannelData;
|
||||
|
||||
@@ -267,6 +279,7 @@ public:
|
||||
[[nodiscard]] bool paidMessagesAvailable() const {
|
||||
return flags() & Flag::PaidMessagesAvailable;
|
||||
}
|
||||
[[nodiscard]] bool useSubsectionTabs() const;
|
||||
|
||||
[[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights(
|
||||
not_null<PeerData*> participant);
|
||||
@@ -301,6 +314,9 @@ public:
|
||||
[[nodiscard]] bool isForum() const {
|
||||
return flags() & Flag::Forum;
|
||||
}
|
||||
[[nodiscard]] bool isMonoforum() const {
|
||||
return flags() & Flag::Monoforum;
|
||||
}
|
||||
[[nodiscard]] bool hasUsername() const {
|
||||
return flags() & Flag::Username;
|
||||
}
|
||||
@@ -381,6 +397,7 @@ public:
|
||||
[[nodiscard]] bool canEditStories() const;
|
||||
[[nodiscard]] bool canDeleteStories() const;
|
||||
[[nodiscard]] bool canPostPaidMedia() const;
|
||||
[[nodiscard]] bool canAccessMonoforum() const;
|
||||
[[nodiscard]] bool hiddenPreHistory() const;
|
||||
[[nodiscard]] bool canViewMembers() const;
|
||||
[[nodiscard]] bool canViewAdmins() const;
|
||||
@@ -413,6 +430,9 @@ public:
|
||||
[[nodiscard]] ChannelData *discussionLink() const;
|
||||
[[nodiscard]] bool discussionLinkKnown() const;
|
||||
|
||||
void setMonoforumLink(ChannelData *link);
|
||||
[[nodiscard]] ChannelData *monoforumLink() const;
|
||||
|
||||
void ptsInit(int32 pts) {
|
||||
_ptsWaiter.init(pts);
|
||||
}
|
||||
@@ -510,6 +530,9 @@ public:
|
||||
[[nodiscard]] Data::Forum *forum() const {
|
||||
return mgInfo ? mgInfo->forum() : nullptr;
|
||||
}
|
||||
[[nodiscard]] Data::SavedMessages *monoforum() const {
|
||||
return mgInfo ? mgInfo->monoforum() : nullptr;
|
||||
}
|
||||
|
||||
void processTopics(const MTPVector<MTPForumTopic> &topics);
|
||||
|
||||
@@ -546,18 +569,11 @@ private:
|
||||
std::vector<Data::UnavailableReason> &&reasons) override;
|
||||
|
||||
Flags _flags = ChannelDataFlags(Flag::Forbidden);
|
||||
int _peerGiftsCount = 0;
|
||||
|
||||
PtsWaiter _ptsWaiter;
|
||||
|
||||
Data::UsernamesInfo _username;
|
||||
|
||||
int _membersCount = -1;
|
||||
int _adminsCount = 1;
|
||||
int _restrictedCount = 0;
|
||||
int _kickedCount = 0;
|
||||
int _pendingRequestsCount = 0;
|
||||
int _levelHint = 0;
|
||||
std::vector<UserId> _recentRequesters;
|
||||
MsgId _availableMinId = 0;
|
||||
|
||||
@@ -570,7 +586,19 @@ private:
|
||||
std::vector<Data::UnavailableReason> _unavailableReasons;
|
||||
std::unique_ptr<InvitePeek> _invitePeek;
|
||||
QString _inviteLink;
|
||||
std::optional<ChannelData*> _discussionLink;
|
||||
|
||||
ChannelData *_discussionLink = nullptr;
|
||||
ChannelData *_monoforumLink = nullptr;
|
||||
bool _discussionLinkKnown = false;
|
||||
|
||||
int _peerGiftsCount = 0;
|
||||
int _membersCount = -1;
|
||||
int _adminsCount = 1;
|
||||
int _restrictedCount = 0;
|
||||
int _kickedCount = 0;
|
||||
int _pendingRequestsCount = 0;
|
||||
int _levelHint = 0;
|
||||
int _starsPerMessage = 0;
|
||||
|
||||
Data::AllowedReactions _allowedReactions;
|
||||
|
||||
|
||||
@@ -461,6 +461,10 @@ rpl::producer<bool> ChatFilters::tagsEnabledValue() const {
|
||||
return _tagsEnabled.value();
|
||||
}
|
||||
|
||||
rpl::producer<bool> ChatFilters::tagsEnabledChanges() const {
|
||||
return _tagsEnabled.changes();
|
||||
}
|
||||
|
||||
void ChatFilters::requestToggleTags(bool value, Fn<void()> fail) {
|
||||
if (_toggleTagsRequestId) {
|
||||
return;
|
||||
|
||||
@@ -213,6 +213,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool tagsEnabled() const;
|
||||
[[nodiscard]] rpl::producer<bool> tagsEnabledValue() const;
|
||||
[[nodiscard]] rpl::producer<bool> tagsEnabledChanges() const;
|
||||
void requestToggleTags(bool value, Fn<void()> fail);
|
||||
|
||||
private:
|
||||
|
||||
@@ -159,7 +159,8 @@ bool CanSendAnyOf(
|
||||
using Flag = ChannelDataFlag;
|
||||
const auto allowed = channel->amIn()
|
||||
|| ((channel->flags() & Flag::HasLink)
|
||||
&& !(channel->flags() & Flag::JoinToWrite));
|
||||
&& !(channel->flags() & Flag::JoinToWrite))
|
||||
|| channel->isMonoforum();
|
||||
if (!allowed || (forbidInForums && channel->isForum())) {
|
||||
return false;
|
||||
} else if (channel->canPostMessages()) {
|
||||
|
||||
@@ -190,18 +190,21 @@ struct SendError {
|
||||
struct Args {
|
||||
QString text;
|
||||
int boostsToLift = 0;
|
||||
bool monoforumAdmin = false;
|
||||
bool premiumToLift = false;
|
||||
bool frozen = false;
|
||||
};
|
||||
SendError(Args &&args)
|
||||
: text(std::move(args.text))
|
||||
, boostsToLift(args.boostsToLift)
|
||||
, monoforumAdmin(args.monoforumAdmin)
|
||||
, premiumToLift(args.premiumToLift)
|
||||
, frozen(args.frozen) {
|
||||
}
|
||||
|
||||
QString text;
|
||||
int boostsToLift = 0;
|
||||
bool monoforumAdmin = false;
|
||||
bool premiumToLift = false;
|
||||
bool frozen = false;
|
||||
|
||||
@@ -210,7 +213,7 @@ struct SendError {
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return !text.isEmpty();
|
||||
return monoforumAdmin || !text.isEmpty();
|
||||
}
|
||||
[[nodiscard]] bool has_value() const {
|
||||
return !text.isEmpty();
|
||||
|
||||
@@ -187,7 +187,8 @@ void ResolveDocument(
|
||||
Window::SessionController *controller,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item,
|
||||
MsgId topicRootId) {
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId) {
|
||||
if (document->isNull()) {
|
||||
return;
|
||||
}
|
||||
@@ -202,7 +203,7 @@ void ResolveDocument(
|
||||
controller->openDocument(
|
||||
document,
|
||||
true,
|
||||
{ msgId, topicRootId });
|
||||
{ msgId, topicRootId, monoforumPeerId });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ void ResolveDocument(
|
||||
Window::SessionController *controller,
|
||||
not_null<DocumentData*> document,
|
||||
HistoryItem *item,
|
||||
MsgId topicRootId);
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -70,10 +70,11 @@ void ApplyPeerCloudDraft(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
const MTPDdraftMessage &draft) {
|
||||
const auto history = session->data().history(peerId);
|
||||
const auto date = draft.vdate().v;
|
||||
if (history->skipCloudDraftUpdate(topicRootId, date)) {
|
||||
if (history->skipCloudDraftUpdate(topicRootId, monoforumPeerId, date)) {
|
||||
return;
|
||||
}
|
||||
const auto textWithTags = TextWithTags{
|
||||
@@ -87,6 +88,7 @@ void ApplyPeerCloudDraft(
|
||||
? ReplyToFromMTP(history, *draft.vreply_to())
|
||||
: FullReplyTo();
|
||||
replyTo.topicRootId = topicRootId;
|
||||
replyTo.monoforumPeerId = monoforumPeerId;
|
||||
auto webpage = WebPageDraft{
|
||||
.invert = draft.is_invert_media(),
|
||||
.removed = draft.is_no_webpage(),
|
||||
@@ -112,21 +114,22 @@ void ApplyPeerCloudDraft(
|
||||
cloudDraft->date = date;
|
||||
|
||||
history->setCloudDraft(std::move(cloudDraft));
|
||||
history->applyCloudDraft(topicRootId);
|
||||
history->applyCloudDraft(topicRootId, monoforumPeerId);
|
||||
}
|
||||
|
||||
void ClearPeerCloudDraft(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
TimeId date) {
|
||||
const auto history = session->data().history(peerId);
|
||||
if (history->skipCloudDraftUpdate(topicRootId, date)) {
|
||||
if (history->skipCloudDraftUpdate(topicRootId, monoforumPeerId, date)) {
|
||||
return;
|
||||
}
|
||||
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->applyCloudDraft(topicRootId);
|
||||
history->clearCloudDraft(topicRootId, monoforumPeerId);
|
||||
history->applyCloudDraft(topicRootId, monoforumPeerId);
|
||||
}
|
||||
|
||||
void SetChatLinkDraft(not_null<PeerData*> peer, TextWithEntities draft) {
|
||||
@@ -146,12 +149,16 @@ void SetChatLinkDraft(not_null<PeerData*> peer, TextWithEntities draft) {
|
||||
};
|
||||
const auto history = peer->owner().history(peer->id);
|
||||
const auto topicRootId = MsgId();
|
||||
const auto monoforumPeerId = PeerId();
|
||||
history->setLocalDraft(std::make_unique<Data::Draft>(
|
||||
textWithTags,
|
||||
FullReplyTo{ .topicRootId = topicRootId },
|
||||
FullReplyTo{
|
||||
.topicRootId = topicRootId,
|
||||
.monoforumPeerId = monoforumPeerId,
|
||||
},
|
||||
cursor,
|
||||
Data::WebPageDraft()));
|
||||
history->clearLocalEditDraft(topicRootId);
|
||||
history->clearLocalEditDraft(topicRootId, monoforumPeerId);
|
||||
history->session().changes().entryUpdated(
|
||||
history,
|
||||
Data::EntryUpdate::Flag::LocalDraftSet);
|
||||
|
||||
@@ -23,11 +23,13 @@ void ApplyPeerCloudDraft(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
const MTPDdraftMessage &draft);
|
||||
void ClearPeerCloudDraft(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
TimeId date);
|
||||
|
||||
struct WebPageDraft {
|
||||
@@ -72,22 +74,38 @@ public:
|
||||
[[nodiscard]] static constexpr DraftKey None() {
|
||||
return 0;
|
||||
}
|
||||
[[nodiscard]] static constexpr DraftKey Local(MsgId topicRootId) {
|
||||
return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
|
||||
[[nodiscard]] static constexpr DraftKey Local(
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId) {
|
||||
return Invalid(topicRootId, monoforumPeerId)
|
||||
? None()
|
||||
: (topicRootId ? topicRootId.bare : kLocalDraftIndex);
|
||||
: (topicRootId
|
||||
? topicRootId.bare
|
||||
: monoforumPeerId
|
||||
? (monoforumPeerId.value + kMonoforumDraftBit)
|
||||
: kLocalDraftIndex);
|
||||
}
|
||||
[[nodiscard]] static constexpr DraftKey LocalEdit(MsgId topicRootId) {
|
||||
return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
|
||||
[[nodiscard]] static constexpr DraftKey LocalEdit(
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId) {
|
||||
return Invalid(topicRootId, monoforumPeerId)
|
||||
? None()
|
||||
: ((topicRootId ? topicRootId.bare : kLocalDraftIndex)
|
||||
+ kEditDraftShift);
|
||||
: (kEditDraftShift
|
||||
+ (topicRootId
|
||||
? topicRootId.bare
|
||||
: monoforumPeerId
|
||||
? (monoforumPeerId.value + kMonoforumDraftBit)
|
||||
: kLocalDraftIndex));
|
||||
}
|
||||
[[nodiscard]] static constexpr DraftKey Cloud(MsgId topicRootId) {
|
||||
return (topicRootId < 0 || topicRootId >= ServerMaxMsgId)
|
||||
[[nodiscard]] static constexpr DraftKey Cloud(
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId) {
|
||||
return Invalid(topicRootId, monoforumPeerId)
|
||||
? None()
|
||||
: topicRootId
|
||||
? (kCloudDraftShift + topicRootId.bare)
|
||||
: monoforumPeerId
|
||||
? (kCloudDraftShift + monoforumPeerId.value + kMonoforumDraftBit)
|
||||
: kCloudDraftIndex;
|
||||
}
|
||||
[[nodiscard]] static constexpr DraftKey Scheduled() {
|
||||
@@ -120,40 +138,62 @@ public:
|
||||
return !value
|
||||
? None()
|
||||
: (value == kLocalDraftIndex + kEditDraftShiftOld)
|
||||
? LocalEdit(0)
|
||||
? LocalEdit(MsgId(), PeerId())
|
||||
: (value == kScheduledDraftIndex + kEditDraftShiftOld)
|
||||
? ScheduledEdit()
|
||||
: (value > 0 && value < 0x4000'0000)
|
||||
? Local(MsgId(value))
|
||||
? Local(MsgId(value), PeerId())
|
||||
: (value > kEditDraftShiftOld
|
||||
&& value < kEditDraftShiftOld + 0x4000'000)
|
||||
? LocalEdit(int64(value - kEditDraftShiftOld))
|
||||
? LocalEdit(MsgId(int64(value - kEditDraftShiftOld)), PeerId())
|
||||
: None();
|
||||
}
|
||||
[[nodiscard]] constexpr bool isLocal() const {
|
||||
return (_value == kLocalDraftIndex)
|
||||
|| (_value > 0 && _value < ServerMaxMsgId.bare);
|
||||
|| (_value > 0
|
||||
&& (_value & kMonoforumDraftMask) < ServerMaxMsgId.bare);
|
||||
}
|
||||
[[nodiscard]] constexpr bool isCloud() const {
|
||||
return (_value == kCloudDraftIndex)
|
||||
|| (_value > kCloudDraftShift
|
||||
&& _value < kCloudDraftShift + ServerMaxMsgId.bare);
|
||||
|| ((_value & kMonoforumDraftMask) > kCloudDraftShift
|
||||
&& ((_value & kMonoforumDraftMask)
|
||||
< kCloudDraftShift + ServerMaxMsgId.bare));
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr MsgId topicRootId() const {
|
||||
const auto max = ServerMaxMsgId.bare;
|
||||
if (_value > kCloudDraftShift && _value < kCloudDraftShift + max) {
|
||||
if (_value & kMonoforumDraftBit) {
|
||||
return 0;
|
||||
} else if ((_value > kCloudDraftShift)
|
||||
&& (_value < kCloudDraftShift + max)) {
|
||||
return (_value - kCloudDraftShift);
|
||||
} else if (_value > kEditDraftShift && _value < kEditDraftShift + max) {
|
||||
} else if ((_value > kEditDraftShift)
|
||||
&& (_value < kEditDraftShift + max)) {
|
||||
return (_value - kEditDraftShift);
|
||||
} else if (_value > 0 && _value < max) {
|
||||
return _value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr PeerId monoforumPeerId() const {
|
||||
const auto max = ServerMaxMsgId.bare;
|
||||
const auto value = _value & kMonoforumDraftMask;
|
||||
if (!(_value & kMonoforumDraftBit)) {
|
||||
return 0;
|
||||
} else if ((value > kCloudDraftShift)
|
||||
&& (value < kCloudDraftShift + max)) {
|
||||
return PeerId(UserId(value - kCloudDraftShift));
|
||||
} else if ((value > kEditDraftShift)
|
||||
&& (value < kEditDraftShift + max)) {
|
||||
return PeerId(UserId(value - kEditDraftShift));
|
||||
} else if (value > 0 && value < max) {
|
||||
return PeerId(UserId(value));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
friend inline constexpr auto operator<=>(DraftKey, DraftKey) = default;
|
||||
friend inline constexpr bool operator==(DraftKey, DraftKey) = default;
|
||||
|
||||
inline explicit operator bool() const {
|
||||
return _value != 0;
|
||||
@@ -163,9 +203,20 @@ private:
|
||||
constexpr DraftKey(int64 value) : _value(value) {
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr bool Invalid(
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId) {
|
||||
return (topicRootId < 0)
|
||||
|| (topicRootId >= ServerMaxMsgId)
|
||||
|| !peerIsUser(monoforumPeerId)
|
||||
|| (monoforumPeerId.value >= ServerMaxMsgId);
|
||||
}
|
||||
|
||||
static constexpr auto kLocalDraftIndex = -1;
|
||||
static constexpr auto kCloudDraftIndex = -2;
|
||||
static constexpr auto kScheduledDraftIndex = -3;
|
||||
static constexpr auto kMonoforumDraftBit = (int64(1) << 60);
|
||||
static constexpr auto kMonoforumDraftMask = (kMonoforumDraftBit - 1);
|
||||
static constexpr auto kEditDraftShift = ServerMaxMsgId.bare;
|
||||
static constexpr auto kCloudDraftShift = 2 * ServerMaxMsgId.bare;
|
||||
static constexpr auto kShortcutDraftShift = 3 * ServerMaxMsgId.bare;
|
||||
|
||||
@@ -48,7 +48,6 @@ Forum::Forum(not_null<History*> history)
|
||||
, _topicsList(&session(), {}, owner().maxPinnedChatsLimitValue(this)) {
|
||||
Expects(_history->peer->isChannel());
|
||||
|
||||
|
||||
if (_history->inChatList()) {
|
||||
preloadTopics();
|
||||
}
|
||||
@@ -73,8 +72,11 @@ Forum::~Forum() {
|
||||
auto &changes = session().changes();
|
||||
const auto peerId = _history->peer->id;
|
||||
for (const auto &[rootId, topic] : _topics) {
|
||||
storage.unload(Storage::SharedMediaUnloadThread(peerId, rootId));
|
||||
_history->setForwardDraft(rootId, {});
|
||||
storage.unload(Storage::SharedMediaUnloadThread(
|
||||
peerId,
|
||||
rootId,
|
||||
PeerId()));
|
||||
_history->setForwardDraft(rootId, PeerId(), {});
|
||||
|
||||
const auto raw = topic.get();
|
||||
changes.topicRemoved(raw);
|
||||
@@ -176,34 +178,36 @@ void Forum::applyTopicDeleted(MsgId rootId) {
|
||||
_topicsDeleted.emplace(rootId);
|
||||
|
||||
const auto i = _topics.find(rootId);
|
||||
if (i != end(_topics)) {
|
||||
const auto raw = i->second.get();
|
||||
Core::App().notifications().clearFromTopic(raw);
|
||||
owner().removeChatListEntry(raw);
|
||||
|
||||
if (ranges::contains(_lastTopics, not_null(raw))) {
|
||||
reorderLastTopics();
|
||||
}
|
||||
|
||||
_topicDestroyed.fire(raw);
|
||||
session().changes().topicUpdated(
|
||||
raw,
|
||||
Data::TopicUpdate::Flag::Destroyed);
|
||||
session().changes().entryUpdated(
|
||||
raw,
|
||||
Data::EntryUpdate::Flag::Destroyed);
|
||||
_topics.erase(i);
|
||||
|
||||
_history->destroyMessagesByTopic(rootId);
|
||||
session().storage().unload(Storage::SharedMediaUnloadThread(
|
||||
_history->peer->id,
|
||||
rootId));
|
||||
_history->setForwardDraft(rootId, {});
|
||||
if (i == end(_topics)) {
|
||||
return;
|
||||
}
|
||||
const auto raw = i->second.get();
|
||||
Core::App().notifications().clearFromTopic(raw);
|
||||
owner().removeChatListEntry(raw);
|
||||
|
||||
if (ranges::contains(_lastTopics, not_null(raw))) {
|
||||
reorderLastTopics();
|
||||
}
|
||||
|
||||
_topicDestroyed.fire(raw);
|
||||
session().changes().topicUpdated(
|
||||
raw,
|
||||
Data::TopicUpdate::Flag::Destroyed);
|
||||
session().changes().entryUpdated(
|
||||
raw,
|
||||
Data::EntryUpdate::Flag::Destroyed);
|
||||
_topics.erase(i);
|
||||
|
||||
_history->destroyMessagesByTopic(rootId);
|
||||
session().storage().unload(Storage::SharedMediaUnloadThread(
|
||||
_history->peer->id,
|
||||
rootId,
|
||||
PeerId()));
|
||||
_history->setForwardDraft(rootId, PeerId(), {});
|
||||
}
|
||||
|
||||
void Forum::reorderLastTopics() {
|
||||
// We want first kShowChatNamesCount histories, by last message date.
|
||||
// We want first kShowTopicNamesCount histories, by last message date.
|
||||
const auto pred = [](not_null<ForumTopic*> a, not_null<ForumTopic*> b) {
|
||||
const auto aItem = a->chatListMessage();
|
||||
const auto bItem = b->chatListMessage();
|
||||
|
||||
@@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "history/view/history_view_item_preview.h"
|
||||
#include "history/view/history_view_replies_section.h"
|
||||
#include "history/view/history_view_chat_section.h"
|
||||
#include "main/main_session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "ui/painter.h"
|
||||
@@ -152,10 +152,10 @@ QImage ForumTopicGeneralIconFrame(int size, const QColor &color) {
|
||||
result.setDevicePixelRatio(ratio);
|
||||
result.fill(Qt::transparent);
|
||||
|
||||
const auto use = size * 0.8;
|
||||
const auto skip = size * 0.1;
|
||||
const auto use = size * 1.;
|
||||
const auto skip = size * 0.;
|
||||
auto p = QPainter(&result);
|
||||
svg.render(&p, QRectF(skip, 0, use, use));
|
||||
svg.render(&p, QRectF(skip, skip, use, use));
|
||||
p.end();
|
||||
|
||||
return style::colorizeImage(result, color);
|
||||
@@ -362,8 +362,8 @@ void ForumTopic::subscribeToUnreadChanges() {
|
||||
) | rpl::filter([=] {
|
||||
return inChatList();
|
||||
}) | rpl::start_with_next([=](
|
||||
std::optional<int> previous,
|
||||
std::optional<int> now) {
|
||||
std::optional<int> previous,
|
||||
std::optional<int> now) {
|
||||
if (previous.value_or(0) != now.value_or(0)) {
|
||||
_forum->recentTopicsInvalidate(this);
|
||||
}
|
||||
@@ -406,6 +406,7 @@ void ForumTopic::applyTopic(const MTPDforumTopic &data) {
|
||||
&session(),
|
||||
channel()->id,
|
||||
_rootId,
|
||||
PeerId(),
|
||||
data);
|
||||
}, [](const MTPDdraftMessageEmpty&) {});
|
||||
}
|
||||
@@ -709,7 +710,7 @@ void ForumTopic::requestChatListMessage() {
|
||||
|
||||
TimeId ForumTopic::adjustedChatListTimeId() const {
|
||||
const auto result = chatListTimeId();
|
||||
if (const auto draft = history()->cloudDraft(_rootId)) {
|
||||
if (const auto draft = history()->cloudDraft(_rootId, PeerId())) {
|
||||
if (!Data::DraftIsNull(draft) && !session().supportMode()) {
|
||||
return std::max(result, draft->date);
|
||||
}
|
||||
@@ -867,7 +868,7 @@ void ForumTopic::setMuted(bool muted) {
|
||||
session().changes().topicUpdated(this, UpdateFlag::Notifications);
|
||||
}
|
||||
|
||||
not_null<HistoryView::SendActionPainter*> ForumTopic::sendActionPainter() {
|
||||
HistoryView::SendActionPainter *ForumTopic::sendActionPainter() {
|
||||
return _sendActionPainter.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ public:
|
||||
void setMuted(bool muted) override;
|
||||
|
||||
[[nodiscard]] auto sendActionPainter()
|
||||
->not_null<HistoryView::SendActionPainter*> override;
|
||||
-> HistoryView::SendActionPainter* override;
|
||||
|
||||
private:
|
||||
enum class Flag : uchar {
|
||||
|
||||
@@ -84,7 +84,7 @@ void Groups::refreshMessage(
|
||||
_data->requestItemViewRefresh(item);
|
||||
return;
|
||||
}
|
||||
if (!item->isRegular() && !item->isScheduled()) {
|
||||
if (!item->isRegular() && !item->isScheduled() && !item->isUploading()) {
|
||||
return;
|
||||
}
|
||||
const auto groupId = item->groupId();
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_text_entities.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
@@ -60,6 +61,14 @@ MTPInputReplyTo ReplyToForMTP(
|
||||
&& (to->history() != history || to->id != replyingToTopicId))
|
||||
? to->topicRootId()
|
||||
: replyingToTopicId;
|
||||
const auto possibleMonoforumPeerId = (to && to->sublistPeerId())
|
||||
? to->sublistPeerId()
|
||||
: replyTo.monoforumPeerId
|
||||
? replyTo.monoforumPeerId
|
||||
: history->session().user()->id;
|
||||
const auto replyToMonoforumPeerId = history->peer->amMonoforumAdmin()
|
||||
? possibleMonoforumPeerId
|
||||
: PeerId();
|
||||
const auto external = replyTo.messageId
|
||||
&& (replyTo.messageId.peer != history->peer->id
|
||||
|| replyingToTopicId != replyToTopicId);
|
||||
@@ -74,6 +83,9 @@ MTPInputReplyTo ReplyToForMTP(
|
||||
| (replyTo.quote.text.isEmpty()
|
||||
? Flag()
|
||||
: (Flag::f_quote_text | Flag::f_quote_offset))
|
||||
| (replyToMonoforumPeerId
|
||||
? Flag::f_monoforum_peer_id
|
||||
: Flag())
|
||||
| (quoteEntities.v.isEmpty()
|
||||
? Flag()
|
||||
: Flag::f_quote_entities)),
|
||||
@@ -84,7 +96,16 @@ MTPInputReplyTo ReplyToForMTP(
|
||||
: MTPInputPeer()),
|
||||
MTP_string(replyTo.quote.text),
|
||||
quoteEntities,
|
||||
MTP_int(replyTo.quoteOffset));
|
||||
MTP_int(replyTo.quoteOffset),
|
||||
(replyToMonoforumPeerId
|
||||
? history->owner().peer(replyToMonoforumPeerId)->input
|
||||
: MTPInputPeer()));
|
||||
} else if (history->peer->amMonoforumAdmin()
|
||||
&& replyTo.monoforumPeerId) {
|
||||
const auto replyToMonoforumPeer = replyTo.monoforumPeerId
|
||||
? history->owner().peer(replyTo.monoforumPeerId)
|
||||
: history->session().user();
|
||||
return MTP_inputReplyToMonoForum(replyToMonoforumPeer->input);
|
||||
}
|
||||
return MTPInputReplyTo();
|
||||
}
|
||||
@@ -475,10 +496,29 @@ void Histories::changeDialogUnreadMark(
|
||||
using Flag = MTPmessages_MarkDialogUnread::Flag;
|
||||
session().api().request(MTPmessages_MarkDialogUnread(
|
||||
MTP_flags(unread ? Flag::f_unread : Flag(0)),
|
||||
MTPInputPeer(), // parent_peer
|
||||
MTP_inputDialogPeer(history->peer->input)
|
||||
)).send();
|
||||
}
|
||||
|
||||
void Histories::changeSublistUnreadMark(
|
||||
not_null<Data::SavedSublist*> sublist,
|
||||
bool unread) {
|
||||
const auto parent = sublist->parentChat();
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
sublist->setUnreadMark(unread);
|
||||
|
||||
using Flag = MTPmessages_MarkDialogUnread::Flag;
|
||||
session().api().request(MTPmessages_MarkDialogUnread(
|
||||
MTP_flags(Flag::f_parent_peer
|
||||
| (unread ? Flag::f_unread : Flag(0))),
|
||||
parent->input,
|
||||
MTP_inputDialogPeer(sublist->sublistPeer()->input)
|
||||
)).send();
|
||||
}
|
||||
|
||||
void Histories::requestFakeChatListMessage(
|
||||
not_null<History*> history) {
|
||||
if (_fakeChatListRequests.contains(history)) {
|
||||
@@ -671,6 +711,7 @@ void Histories::sendReadRequest(not_null<History*> history, State &state) {
|
||||
} else {
|
||||
Assert(!state->sentReadTill || state->sentReadTill > tillId);
|
||||
}
|
||||
history->validateMonoforumUnread(tillId);
|
||||
sendReadRequests();
|
||||
finish();
|
||||
};
|
||||
@@ -1054,13 +1095,12 @@ int Histories::sendPreparedMessage(
|
||||
_creatingTopicRequests.emplace(id);
|
||||
return id;
|
||||
}
|
||||
const auto realReplyTo = FullReplyTo{
|
||||
.messageId = convertTopicReplyToId(history, replyTo.messageId),
|
||||
.quote = replyTo.quote,
|
||||
.storyId = replyTo.storyId,
|
||||
.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
|
||||
.quoteOffset = replyTo.quoteOffset,
|
||||
auto realReplyTo = replyTo;
|
||||
const auto topicReplyToId = [&](const auto &id) {
|
||||
return convertTopicReplyToId(history, id);
|
||||
};
|
||||
realReplyTo.messageId = topicReplyToId(replyTo.messageId);
|
||||
realReplyTo.topicRootId = topicReplyToId(replyTo.topicRootId);
|
||||
return v::match(message(history, realReplyTo), [&](const auto &request) {
|
||||
const auto type = RequestType::Send;
|
||||
return sendRequest(history, type, [=](Fn<void()> finish) {
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Data {
|
||||
class Session;
|
||||
class Folder;
|
||||
struct WebPageDraft;
|
||||
class SavedSublist;
|
||||
|
||||
[[nodiscard]] MTPInputReplyTo ReplyToForMTP(
|
||||
not_null<History*> history,
|
||||
@@ -71,6 +72,9 @@ public:
|
||||
Fn<void()> callback = nullptr);
|
||||
void dialogEntryApplied(not_null<History*> history);
|
||||
void changeDialogUnreadMark(not_null<History*> history, bool unread);
|
||||
void changeSublistUnreadMark(
|
||||
not_null<Data::SavedSublist*> sublist,
|
||||
bool unread);
|
||||
void requestFakeChatListMessage(not_null<History*> history);
|
||||
|
||||
void requestGroupAround(not_null<HistoryItem*> item);
|
||||
|
||||
@@ -152,6 +152,7 @@ rpl::producer<SparseIdsMergedSlice> HistoryMergedViewer(
|
||||
auto createSimpleViewer = [=](
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
SparseIdsSlice::Key simpleKey,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
@@ -161,11 +162,10 @@ rpl::producer<SparseIdsMergedSlice> HistoryMergedViewer(
|
||||
return HistoryViewer(chosen, simpleKey, limitBefore, limitAfter);
|
||||
};
|
||||
const auto peerId = history->peer->id;
|
||||
const auto topicRootId = MsgId();
|
||||
const auto migratedPeerId = migrateFrom ? migrateFrom->id : PeerId(0);
|
||||
using Key = SparseIdsMergedSlice::Key;
|
||||
return SparseIdsMergedSlice::CreateViewer(
|
||||
Key(peerId, topicRootId, migratedPeerId, universalAroundId),
|
||||
Key(peerId, MsgId(), PeerId(), migratedPeerId, universalAroundId),
|
||||
limitBefore,
|
||||
limitAfter,
|
||||
std::move(createSimpleViewer));
|
||||
|
||||
@@ -1028,7 +1028,7 @@ void Reactions::requestMyTags(SavedSublist *sublist) {
|
||||
using Flag = MTPmessages_GetSavedReactionTags::Flag;
|
||||
my.requestId = api.request(MTPmessages_GetSavedReactionTags(
|
||||
MTP_flags(sublist ? Flag::f_peer : Flag()),
|
||||
(sublist ? sublist->peer()->input : MTP_inputPeerEmpty()),
|
||||
(sublist ? sublist->sublistPeer()->input : MTP_inputPeerEmpty()),
|
||||
MTP_long(my.hash)
|
||||
)).done([=](const MTPmessages_SavedReactionTags &result) {
|
||||
auto &my = _myTags[sublist];
|
||||
|
||||
@@ -177,13 +177,14 @@ struct FullReplyTo {
|
||||
TextWithEntities quote;
|
||||
FullStoryId storyId;
|
||||
MsgId topicRootId = 0;
|
||||
PeerId monoforumPeerId = 0;
|
||||
int quoteOffset = 0;
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
[[nodiscard]] bool replying() const {
|
||||
return messageId || (storyId && storyId.peer);
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return valid();
|
||||
return replying() || monoforumPeerId;
|
||||
}
|
||||
friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default;
|
||||
friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
|
||||
|
||||
@@ -427,19 +427,31 @@ QImage *PeerData::userpicCloudImage(Ui::PeerUserpicView &view) const {
|
||||
void PeerData::paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
bool forceCircle) const {
|
||||
PaintUserpicContext context) const {
|
||||
if (const auto broadcast = monoforumBroadcast()) {
|
||||
if (context.shape == Ui::PeerUserpicShape::Auto) {
|
||||
context.shape = Ui::PeerUserpicShape::Monoforum;
|
||||
}
|
||||
broadcast->paintUserpic(p, view, context);
|
||||
return;
|
||||
}
|
||||
const auto size = context.size;
|
||||
const auto cloud = userpicCloudImage(view);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (context.shape == Ui::PeerUserpicShape::Auto) {
|
||||
context.shape = isForum()
|
||||
? Ui::PeerUserpicShape::Forum
|
||||
: isMonoforum()
|
||||
? Ui::PeerUserpicShape::Monoforum
|
||||
: Ui::PeerUserpicShape::Circle;
|
||||
}
|
||||
Ui::ValidateUserpicCache(
|
||||
view,
|
||||
cloud,
|
||||
cloud ? nullptr : ensureEmptyUserpic().get(),
|
||||
size * ratio,
|
||||
!forceCircle && isForum());
|
||||
p.drawImage(QRect(x, y, size, size), view.cached);
|
||||
context.shape);
|
||||
p.drawImage(QRect(context.position, QSize(size, size)), view.cached);
|
||||
}
|
||||
|
||||
void PeerData::loadUserpic() {
|
||||
@@ -661,10 +673,13 @@ bool PeerData::canPinMessages() const {
|
||||
|
||||
bool PeerData::canCreatePolls() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->isBot()
|
||||
&& !user->isSupport()
|
||||
&& !user->isRepliesChat()
|
||||
&& !user->isVerifyCodes();
|
||||
return user->isSelf()
|
||||
|| (user->isBot()
|
||||
&& !user->isSupport()
|
||||
&& !user->isRepliesChat()
|
||||
&& !user->isVerifyCodes());
|
||||
} else if (isMonoforum()) {
|
||||
return false;
|
||||
}
|
||||
return Data::CanSend(this, ChatRestriction::SendPolls);
|
||||
}
|
||||
@@ -1118,6 +1133,16 @@ const ChannelData *PeerData::asChannelOrMigrated() const {
|
||||
return migrateTo();
|
||||
}
|
||||
|
||||
ChannelData *PeerData::asMonoforum() {
|
||||
const auto channel = asMegagroup();
|
||||
return (channel && channel->isMonoforum()) ? channel : nullptr;
|
||||
}
|
||||
|
||||
const ChannelData *PeerData::asMonoforum() const {
|
||||
const auto channel = asMegagroup();
|
||||
return (channel && channel->isMonoforum()) ? channel : nullptr;
|
||||
}
|
||||
|
||||
ChatData *PeerData::migrateFrom() const {
|
||||
if (const auto megagroup = asMegagroup()) {
|
||||
return megagroup->amIn()
|
||||
@@ -1150,6 +1175,35 @@ not_null<const PeerData*> PeerData::migrateToOrMe() const {
|
||||
return this;
|
||||
}
|
||||
|
||||
not_null<PeerData*> PeerData::userpicPaintingPeer() {
|
||||
if (const auto broadcast = monoforumBroadcast()) {
|
||||
return broadcast;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
not_null<const PeerData*> PeerData::userpicPaintingPeer() const {
|
||||
return const_cast<PeerData*>(this)->userpicPaintingPeer();
|
||||
}
|
||||
|
||||
Ui::PeerUserpicShape PeerData::userpicShape() const {
|
||||
return isForum()
|
||||
? Ui::PeerUserpicShape::Forum
|
||||
: isMonoforum()
|
||||
? Ui::PeerUserpicShape::Monoforum
|
||||
: Ui::PeerUserpicShape::Circle;
|
||||
}
|
||||
|
||||
ChannelData *PeerData::monoforumBroadcast() const {
|
||||
const auto monoforum = asMonoforum();
|
||||
return monoforum ? monoforum->monoforumLink() : nullptr;
|
||||
}
|
||||
|
||||
ChannelData *PeerData::broadcastMonoforum() const {
|
||||
const auto broadcast = asBroadcast();
|
||||
return broadcast ? broadcast->monoforumLink() : nullptr;
|
||||
}
|
||||
|
||||
const QString &PeerData::topBarNameText() const {
|
||||
if (const auto to = migrateTo()) {
|
||||
return to->topBarNameText();
|
||||
@@ -1168,6 +1222,8 @@ int PeerData::nameVersion() const {
|
||||
const QString &PeerData::name() const {
|
||||
if (const auto to = migrateTo()) {
|
||||
return to->name();
|
||||
} else if (const auto broadcast = monoforumBroadcast()) {
|
||||
return broadcast->name();
|
||||
}
|
||||
return _name;
|
||||
}
|
||||
@@ -1175,6 +1231,10 @@ const QString &PeerData::name() const {
|
||||
const QString &PeerData::shortName() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->firstName.isEmpty() ? user->lastName : user->firstName;
|
||||
} else if (const auto to = migrateTo()) {
|
||||
return to->shortName();
|
||||
} else if (const auto broadcast = monoforumBroadcast()) {
|
||||
return broadcast->shortName();
|
||||
}
|
||||
return _name;
|
||||
}
|
||||
@@ -1333,6 +1393,13 @@ bool PeerData::isForum() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::isMonoforum() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->isMonoforum();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::isGigagroup() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->isGigagroup();
|
||||
@@ -1416,6 +1483,23 @@ Data::ForumTopic *PeerData::forumTopicFor(MsgId rootId) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Data::SavedMessages *PeerData::monoforum() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->monoforum();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Data::SavedSublist *PeerData::monoforumSublistFor(
|
||||
PeerId sublistPeerId) const {
|
||||
if (!sublistPeerId) {
|
||||
return nullptr;
|
||||
} else if (const auto monoforum = this->monoforum()) {
|
||||
return monoforum->sublistLoaded(owner().peer(sublistPeerId));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PeerData::allowsForwarding() const {
|
||||
if (isUser()) {
|
||||
return true;
|
||||
@@ -1502,7 +1586,8 @@ bool PeerData::canRevokeFullHistory() const {
|
||||
} else if (const auto megagroup = asMegagroup()) {
|
||||
return megagroup->amCreator()
|
||||
&& megagroup->membersCountKnown()
|
||||
&& megagroup->canDelete();
|
||||
&& megagroup->canDelete()
|
||||
&& !megagroup->isMonoforum();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1558,12 +1643,22 @@ bool PeerData::canManageGroupCall() const {
|
||||
return chat->amCreator()
|
||||
|| (chat->adminRights() & ChatAdminRight::ManageCall);
|
||||
} else if (const auto group = asChannel()) {
|
||||
if (group->isMonoforum()) {
|
||||
return false;
|
||||
}
|
||||
return group->amCreator()
|
||||
|| (group->adminRights() & ChatAdminRight::ManageCall);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::amMonoforumAdmin() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->flags() & ChannelDataFlag::MonoforumAdmin;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int PeerData::starsPerMessage() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->starsPerMessage();
|
||||
@@ -1575,9 +1670,9 @@ int PeerData::starsPerMessage() const {
|
||||
|
||||
int PeerData::starsPerMessageChecked() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return (channel->adminRights() || channel->amCreator())
|
||||
? 0
|
||||
: channel->starsPerMessage();
|
||||
if (channel->adminRights() || channel->amCreator()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return starsPerMessage();
|
||||
}
|
||||
@@ -1738,12 +1833,14 @@ void SetTopPinnedMessageId(
|
||||
session.settings().setHiddenPinnedMessageId(
|
||||
peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
PeerId(0), // monoforumPeerId
|
||||
0);
|
||||
session.saveSettingsDelayed();
|
||||
}
|
||||
session.storage().add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
PeerId(0), // monoforumPeerId
|
||||
Storage::SharedMediaType::Pinned,
|
||||
messageId,
|
||||
{ messageId, ServerMaxMsgId }));
|
||||
@@ -1753,22 +1850,25 @@ void SetTopPinnedMessageId(
|
||||
FullMsgId ResolveTopPinnedId(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
PeerData *migrated) {
|
||||
const auto slice = peer->session().storage().snapshot(
|
||||
Storage::SharedMediaQuery(
|
||||
Storage::SharedMediaKey(
|
||||
peer->id,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
Storage::SharedMediaType::Pinned,
|
||||
ServerMaxMsgId - 1),
|
||||
1,
|
||||
1));
|
||||
const auto old = (!topicRootId && migrated)
|
||||
const auto old = (!topicRootId && !monoforumPeerId && migrated)
|
||||
? migrated->session().storage().snapshot(
|
||||
Storage::SharedMediaQuery(
|
||||
Storage::SharedMediaKey(
|
||||
migrated->id,
|
||||
MsgId(0), // topicRootId
|
||||
PeerId(0), // monoforumPeerId
|
||||
Storage::SharedMediaType::Pinned,
|
||||
ServerMaxMsgId - 1),
|
||||
1,
|
||||
@@ -1790,22 +1890,25 @@ FullMsgId ResolveTopPinnedId(
|
||||
FullMsgId ResolveMinPinnedId(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
PeerData *migrated) {
|
||||
const auto slice = peer->session().storage().snapshot(
|
||||
Storage::SharedMediaQuery(
|
||||
Storage::SharedMediaKey(
|
||||
peer->id,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
Storage::SharedMediaType::Pinned,
|
||||
1),
|
||||
1,
|
||||
1));
|
||||
const auto old = (!topicRootId && migrated)
|
||||
const auto old = (!topicRootId && !monoforumPeerId && migrated)
|
||||
? migrated->session().storage().snapshot(
|
||||
Storage::SharedMediaQuery(
|
||||
Storage::SharedMediaKey(
|
||||
migrated->id,
|
||||
MsgId(0), // topicRootId
|
||||
PeerId(0), // monoforumPeerId
|
||||
Storage::SharedMediaType::Pinned,
|
||||
1),
|
||||
1,
|
||||
|
||||
@@ -37,6 +37,8 @@ class Forum;
|
||||
class ForumTopic;
|
||||
class Session;
|
||||
class GroupCall;
|
||||
class SavedMessages;
|
||||
class SavedSublist;
|
||||
struct ReactionId;
|
||||
class WallPaper;
|
||||
|
||||
@@ -184,6 +186,12 @@ struct PeerBarDetails {
|
||||
int paysPerMessage = 0;
|
||||
};
|
||||
|
||||
struct PaintUserpicContext {
|
||||
QPoint position;
|
||||
int size = 0;
|
||||
Ui::PeerUserpicShape shape = Ui::PeerUserpicShape::Auto;
|
||||
};
|
||||
|
||||
class PeerData {
|
||||
protected:
|
||||
PeerData(not_null<Data::Session*> owner, PeerId id);
|
||||
@@ -232,6 +240,7 @@ public:
|
||||
[[nodiscard]] bool isMegagroup() const;
|
||||
[[nodiscard]] bool isBroadcast() const;
|
||||
[[nodiscard]] bool isForum() const;
|
||||
[[nodiscard]] bool isMonoforum() const;
|
||||
[[nodiscard]] bool isGigagroup() const;
|
||||
[[nodiscard]] bool isRepliesChat() const;
|
||||
[[nodiscard]] bool isVerifyCodes() const;
|
||||
@@ -257,6 +266,10 @@ public:
|
||||
[[nodiscard]] Data::Forum *forum() const;
|
||||
[[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const;
|
||||
|
||||
[[nodiscard]] Data::SavedMessages *monoforum() const;
|
||||
[[nodiscard]] Data::SavedSublist *monoforumSublistFor(
|
||||
PeerId sublistPeerId) const;
|
||||
|
||||
[[nodiscard]] Data::PeerNotifySettings ¬ify() {
|
||||
return _notify;
|
||||
}
|
||||
@@ -273,6 +286,7 @@ public:
|
||||
[[nodiscard]] rpl::producer<bool> slowmodeAppliedValue() const;
|
||||
[[nodiscard]] int slowmodeSecondsLeft() const;
|
||||
[[nodiscard]] bool canManageGroupCall() const;
|
||||
[[nodiscard]] bool amMonoforumAdmin() const;
|
||||
|
||||
[[nodiscard]] int starsPerMessage() const;
|
||||
[[nodiscard]] int starsPerMessageChecked() const;
|
||||
@@ -293,11 +307,22 @@ public:
|
||||
[[nodiscard]] const ChatData *asChatNotMigrated() const;
|
||||
[[nodiscard]] ChannelData *asChannelOrMigrated();
|
||||
[[nodiscard]] const ChannelData *asChannelOrMigrated() const;
|
||||
[[nodiscard]] ChannelData *asMonoforum();
|
||||
[[nodiscard]] const ChannelData *asMonoforum() const;
|
||||
|
||||
[[nodiscard]] ChatData *migrateFrom() const;
|
||||
[[nodiscard]] ChannelData *migrateTo() const;
|
||||
[[nodiscard]] not_null<PeerData*> migrateToOrMe();
|
||||
[[nodiscard]] not_null<const PeerData*> migrateToOrMe() const;
|
||||
[[nodiscard]] not_null<PeerData*> userpicPaintingPeer();
|
||||
[[nodiscard]] not_null<const PeerData*> userpicPaintingPeer() const;
|
||||
[[nodiscard]] Ui::PeerUserpicShape userpicShape() const;
|
||||
|
||||
// isMonoforum() ? monoforumLink() : nullptr
|
||||
[[nodiscard]] ChannelData *monoforumBroadcast() const;
|
||||
|
||||
// isMonoforum() ? nullptr : monoforumLink()
|
||||
[[nodiscard]] ChannelData *broadcastMonoforum() const;
|
||||
|
||||
void updateFull();
|
||||
void updateFullForced();
|
||||
@@ -328,13 +353,26 @@ public:
|
||||
const ImageLocation &location,
|
||||
bool hasVideo);
|
||||
void setUserpicPhoto(const MTPPhoto &data);
|
||||
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
bool forceCircle = false) const;
|
||||
PaintUserpicContext context) const;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
int x,
|
||||
int y,
|
||||
int size,
|
||||
bool forceCircle = false) const {
|
||||
paintUserpic(p, view, {
|
||||
.position = { x, y },
|
||||
.size = size,
|
||||
.shape = (forceCircle
|
||||
? Ui::PeerUserpicShape::Circle
|
||||
: Ui::PeerUserpicShape::Auto),
|
||||
});
|
||||
}
|
||||
void paintUserpicLeft(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
@@ -584,10 +622,12 @@ void SetTopPinnedMessageId(
|
||||
[[nodiscard]] FullMsgId ResolveTopPinnedId(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
PeerData *migrated = nullptr);
|
||||
[[nodiscard]] FullMsgId ResolveMinPinnedId(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
PeerData *migrated = nullptr);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -269,6 +269,7 @@ inline auto DefaultRestrictionValue(
|
||||
| Flag::Left
|
||||
| Flag::Forum
|
||||
| Flag::JoinToWrite
|
||||
| Flag::Monoforum
|
||||
| Flag::HasLink
|
||||
| Flag::Forbidden
|
||||
| Flag::Creator
|
||||
@@ -292,7 +293,8 @@ inline auto DefaultRestrictionValue(
|
||||
&& (flags & Flag::Forum);
|
||||
const auto allowed = !(flags & notAmInFlags)
|
||||
|| ((flags & Flag::HasLink)
|
||||
&& !(flags & Flag::JoinToWrite));
|
||||
&& !(flags & Flag::JoinToWrite))
|
||||
|| (flags & Flag::Monoforum);
|
||||
const auto restricted = sendRestriction
|
||||
| (defaultSendRestriction && !unrestrictedByBoosts);
|
||||
return allowed
|
||||
|
||||
@@ -75,7 +75,7 @@ struct PollData {
|
||||
int totalVoters = 0;
|
||||
int version = 0;
|
||||
|
||||
static constexpr auto kMaxOptions = 10;
|
||||
static constexpr auto kMaxOptions = 32;
|
||||
|
||||
private:
|
||||
bool applyResultToAnswers(
|
||||
|
||||
@@ -58,8 +58,6 @@ public:
|
||||
[[nodiscard]] bool isServerSideUnread(
|
||||
not_null<const HistoryItem*> item) const;
|
||||
|
||||
[[nodiscard]] std::optional<int> computeUnreadCountLocally(
|
||||
MsgId afterId) const;
|
||||
void requestUnreadCount();
|
||||
|
||||
void readTill(not_null<HistoryItem*> item);
|
||||
@@ -79,6 +77,8 @@ private:
|
||||
|
||||
void subscribeToUpdates();
|
||||
void appendClientSideMessages(MessagesSlice &slice);
|
||||
[[nodiscard]] std::optional<int> computeUnreadCountLocally(
|
||||
MsgId afterId) const;
|
||||
|
||||
[[nodiscard]] bool buildFromData(not_null<Viewer*> viewer);
|
||||
[[nodiscard]] bool applyItemDestroyed(
|
||||
|
||||
@@ -8,12 +8,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_saved_messages.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "main/main_session.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "window/notifications_manager.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
@@ -22,24 +30,82 @@ constexpr auto kPerPage = 50;
|
||||
constexpr auto kFirstPerPage = 10;
|
||||
constexpr auto kListPerPage = 100;
|
||||
constexpr auto kListFirstPerPage = 20;
|
||||
constexpr auto kLoadedSublistsMinCount = 20;
|
||||
constexpr auto kShowSublistNamesCount = 5;
|
||||
constexpr auto kStalePerRequest = 100;
|
||||
|
||||
} // namespace
|
||||
|
||||
SavedMessages::SavedMessages(not_null<Session*> owner)
|
||||
SavedMessages::SavedMessages(
|
||||
not_null<Session*> owner,
|
||||
ChannelData *parentChat)
|
||||
: _owner(owner)
|
||||
, _parentChat(parentChat)
|
||||
, _owningHistory(parentChat ? owner->history(parentChat).get() : nullptr)
|
||||
, _chatsList(
|
||||
&owner->session(),
|
||||
&_owner->session(),
|
||||
FilterId(),
|
||||
owner->maxPinnedChatsLimitValue(this))
|
||||
_owner->maxPinnedChatsLimitValue(this))
|
||||
, _loadMore([=] { sendLoadMoreRequests(); }) {
|
||||
// We don't assign _owningHistory for my Saved Messages here,
|
||||
// because the data structures are not ready yet.
|
||||
if (_owningHistory && _owningHistory->inChatList()) {
|
||||
preloadSublists();
|
||||
}
|
||||
}
|
||||
|
||||
SavedMessages::~SavedMessages() = default;
|
||||
void SavedMessages::clear() {
|
||||
for (const auto &request : base::take(_sublistRequests)) {
|
||||
if (request.second.id != _staleRequestId) {
|
||||
owner().histories().cancelRequest(request.second.id);
|
||||
}
|
||||
}
|
||||
if (const auto requestId = base::take(_staleRequestId)) {
|
||||
session().api().request(requestId).cancel();
|
||||
}
|
||||
|
||||
auto &storage = session().storage();
|
||||
auto &changes = session().changes();
|
||||
if (_owningHistory) {
|
||||
for (const auto &[peer, sublist] : base::take(_sublists)) {
|
||||
storage.unload(Storage::SharedMediaUnloadThread(
|
||||
_owningHistory->peer->id,
|
||||
MsgId(),
|
||||
peer->id));
|
||||
_owningHistory->setForwardDraft(MsgId(), peer->id, {});
|
||||
|
||||
const auto raw = sublist.get();
|
||||
changes.sublistRemoved(raw);
|
||||
changes.entryRemoved(raw);
|
||||
}
|
||||
}
|
||||
_owningHistory = nullptr;
|
||||
}
|
||||
|
||||
SavedMessages::~SavedMessages() {
|
||||
clear();
|
||||
}
|
||||
|
||||
bool SavedMessages::supported() const {
|
||||
return !_unsupported;
|
||||
}
|
||||
|
||||
void SavedMessages::markUnsupported() {
|
||||
_unsupported = true;
|
||||
}
|
||||
|
||||
ChannelData *SavedMessages::parentChat() const {
|
||||
return _parentChat;
|
||||
}
|
||||
|
||||
not_null<History*> SavedMessages::owningHistory() const {
|
||||
if (!_owningHistory) {
|
||||
const_cast<SavedMessages*>(this)->_owningHistory
|
||||
= _owner->history(_owner->session().user());
|
||||
}
|
||||
return _owningHistory;
|
||||
}
|
||||
|
||||
Session &SavedMessages::owner() const {
|
||||
return *_owner;
|
||||
}
|
||||
@@ -53,13 +119,116 @@ not_null<Dialogs::MainList*> SavedMessages::chatsList() {
|
||||
}
|
||||
|
||||
not_null<SavedSublist*> SavedMessages::sublist(not_null<PeerData*> peer) {
|
||||
const auto i = _sublists.find(peer);
|
||||
if (i != end(_sublists)) {
|
||||
return i->second.get();
|
||||
if (const auto loaded = sublistLoaded(peer)) {
|
||||
return loaded;
|
||||
}
|
||||
return _sublists.emplace(
|
||||
peer,
|
||||
std::make_unique<SavedSublist>(peer)).first->second.get();
|
||||
std::make_unique<SavedSublist>(this, peer)).first->second.get();
|
||||
}
|
||||
|
||||
SavedSublist *SavedMessages::sublistLoaded(not_null<PeerData*> peer) {
|
||||
const auto i = _sublists.find(peer);
|
||||
return (i != end(_sublists)) ? i->second.get() : nullptr;
|
||||
}
|
||||
|
||||
void SavedMessages::requestSomeStale() {
|
||||
if (_staleRequestId
|
||||
|| (!_offset.id && _loadMoreRequestId)
|
||||
|| _stalePeers.empty()
|
||||
|| !_parentChat) {
|
||||
return;
|
||||
}
|
||||
const auto type = Histories::RequestType::History;
|
||||
auto peers = std::vector<not_null<PeerData*>>();
|
||||
auto peerIds = QVector<MTPInputPeer>();
|
||||
peers.reserve(std::min(int(_stalePeers.size()), kStalePerRequest));
|
||||
peerIds.reserve(std::min(int(_stalePeers.size()), kStalePerRequest));
|
||||
for (auto i = begin(_stalePeers); i != end(_stalePeers);) {
|
||||
const auto peer = *i;
|
||||
i = _stalePeers.erase(i);
|
||||
|
||||
peers.push_back(peer);
|
||||
peerIds.push_back(peer->input);
|
||||
if (peerIds.size() == kStalePerRequest) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (peerIds.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto call = [=] {
|
||||
for (const auto &peer : peers) {
|
||||
finishSublistRequest(peer);
|
||||
}
|
||||
};
|
||||
auto &histories = owner().histories();
|
||||
_staleRequestId = histories.sendRequest(_owningHistory, type, [=](
|
||||
Fn<void()> finish) {
|
||||
using Flag = MTPmessages_GetSavedDialogsByID::Flag;
|
||||
return session().api().request(
|
||||
MTPmessages_GetSavedDialogsByID(
|
||||
MTP_flags(Flag::f_parent_peer),
|
||||
_parentChat->input,
|
||||
MTP_vector<MTPInputPeer>(peerIds))
|
||||
).done([=](const MTPmessages_SavedDialogs &result) {
|
||||
_staleRequestId = 0;
|
||||
applyReceivedSublists(result);
|
||||
call();
|
||||
finish();
|
||||
}).fail([=] {
|
||||
_staleRequestId = 0;
|
||||
call();
|
||||
finish();
|
||||
}).send();
|
||||
});
|
||||
for (const auto &peer : peers) {
|
||||
_sublistRequests[peer].id = _staleRequestId;
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::finishSublistRequest(not_null<PeerData*> peer) {
|
||||
if (const auto request = _sublistRequests.take(peer)) {
|
||||
for (const auto &callback : request->callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::requestSublist(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void()> done) {
|
||||
if (!_parentChat) {
|
||||
return;
|
||||
}
|
||||
auto &request = _sublistRequests[peer];
|
||||
if (done) {
|
||||
request.callbacks.push_back(std::move(done));
|
||||
}
|
||||
if (!request.id
|
||||
&& _stalePeers.emplace(peer).second
|
||||
&& (_stalePeers.size() == 1)) {
|
||||
crl::on_main(&session(), [peer = _parentChat] {
|
||||
if (const auto monoforum = peer->monoforum()) {
|
||||
monoforum->requestSomeStale();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SavedMessages::chatsListChanges() const {
|
||||
return _chatsListChanges.events();
|
||||
}
|
||||
|
||||
rpl::producer<> SavedMessages::chatsListLoadedEvents() const {
|
||||
return _chatsListLoadedEvents.events();
|
||||
}
|
||||
|
||||
void SavedMessages::preloadSublists() {
|
||||
if (parentChat()
|
||||
&& chatsList()->indexed()->size() < kLoadedSublistsMinCount) {
|
||||
loadMore();
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::loadMore() {
|
||||
@@ -67,9 +236,10 @@ void SavedMessages::loadMore() {
|
||||
_loadMore.call();
|
||||
}
|
||||
|
||||
void SavedMessages::loadMore(not_null<SavedSublist*> sublist) {
|
||||
_loadMoreSublistsScheduled.emplace(sublist);
|
||||
_loadMore.call();
|
||||
void SavedMessages::clearAllUnreadReactions() {
|
||||
for (const auto &[peer, sublist] : _sublists) {
|
||||
sublist->unreadReactions().clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMore() {
|
||||
@@ -78,19 +248,37 @@ void SavedMessages::sendLoadMore() {
|
||||
} else if (!_pinnedLoaded) {
|
||||
loadPinned();
|
||||
}
|
||||
using Flag = MTPmessages_GetSavedDialogs::Flag;
|
||||
_loadMoreRequestId = _owner->session().api().request(
|
||||
MTPmessages_GetSavedDialogs(
|
||||
MTP_flags(MTPmessages_GetSavedDialogs::Flag::f_exclude_pinned),
|
||||
MTP_int(_offsetDate),
|
||||
MTP_int(_offsetId),
|
||||
_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
|
||||
MTP_int(_offsetId ? kListPerPage : kListFirstPerPage),
|
||||
MTP_flags(Flag::f_exclude_pinned
|
||||
| (_parentChat ? Flag::f_parent_peer : Flag(0))),
|
||||
_parentChat ? _parentChat->input : MTPInputPeer(),
|
||||
MTP_int(_offset.date),
|
||||
MTP_int(_offset.id),
|
||||
_offset.peer ? _offset.peer->input : MTP_inputPeerEmpty(),
|
||||
MTP_int(_offset.id ? kListPerPage : kListFirstPerPage),
|
||||
MTP_long(0)) // hash
|
||||
).done([=](const MTPmessages_SavedDialogs &result) {
|
||||
apply(result, false);
|
||||
const auto applied = applyReceivedSublists(result);
|
||||
if (applied.allLoaded || _offset == applied.offset) {
|
||||
_chatsList.setLoaded();
|
||||
} else if (_offset.date > 0 && applied.offset.date > _offset.date) {
|
||||
LOG(("API Error: Bad order in messages.savedDialogs."));
|
||||
_chatsList.setLoaded();
|
||||
} else {
|
||||
_offset = applied.offset;
|
||||
}
|
||||
_loadMoreRequestId = 0;
|
||||
_chatsListChanges.fire({});
|
||||
if (_chatsList.loaded()) {
|
||||
_chatsListLoadedEvents.fire({});
|
||||
}
|
||||
reorderLastSublists();
|
||||
requestSomeStale();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
markUnsupported();
|
||||
}
|
||||
_chatsList.setLoaded();
|
||||
_loadMoreRequestId = 0;
|
||||
@@ -98,16 +286,19 @@ void SavedMessages::sendLoadMore() {
|
||||
}
|
||||
|
||||
void SavedMessages::loadPinned() {
|
||||
if (_pinnedRequestId) {
|
||||
if (_pinnedRequestId || parentChat()) {
|
||||
return;
|
||||
}
|
||||
_pinnedRequestId = _owner->session().api().request(
|
||||
MTPmessages_GetPinnedSavedDialogs()
|
||||
).done([=](const MTPmessages_SavedDialogs &result) {
|
||||
apply(result, true);
|
||||
_pinnedRequestId = 0;
|
||||
_pinnedLoaded = true;
|
||||
applyReceivedSublists(result, true);
|
||||
_chatsListChanges.fire({});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
markUnsupported();
|
||||
} else {
|
||||
_pinnedLoaded = true;
|
||||
}
|
||||
@@ -115,77 +306,11 @@ void SavedMessages::loadPinned() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMore(not_null<SavedSublist*> sublist) {
|
||||
if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) {
|
||||
return;
|
||||
}
|
||||
const auto &list = sublist->messages();
|
||||
const auto offsetId = list.empty() ? MsgId(0) : list.back()->id;
|
||||
const auto offsetDate = list.empty() ? MsgId(0) : list.back()->date();
|
||||
const auto limit = offsetId ? kPerPage : kFirstPerPage;
|
||||
const auto requestId = _owner->session().api().request(
|
||||
MTPmessages_GetSavedHistory(
|
||||
sublist->peer()->input,
|
||||
MTP_int(offsetId),
|
||||
MTP_int(offsetDate),
|
||||
MTP_int(0), // add_offset
|
||||
MTP_int(limit),
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_long(0)) // hash
|
||||
).done([=](const MTPmessages_Messages &result) {
|
||||
auto count = 0;
|
||||
auto list = (const QVector<MTPMessage>*)nullptr;
|
||||
result.match([](const MTPDmessages_channelMessages &) {
|
||||
LOG(("API Error: messages.channelMessages in sublist."));
|
||||
}, [](const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: messages.messagesNotModified in sublist."));
|
||||
}, [&](const auto &data) {
|
||||
owner().processUsers(data.vusers());
|
||||
owner().processChats(data.vchats());
|
||||
list = &data.vmessages().v;
|
||||
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
|
||||
count = int(list->size());
|
||||
} else {
|
||||
count = data.vcount().v;
|
||||
}
|
||||
});
|
||||
|
||||
_loadMoreRequests.remove(sublist);
|
||||
if (!list) {
|
||||
sublist->setFullLoaded();
|
||||
return;
|
||||
}
|
||||
auto items = std::vector<not_null<HistoryItem*>>();
|
||||
items.reserve(list->size());
|
||||
for (const auto &message : *list) {
|
||||
const auto item = owner().addNewMessage(
|
||||
message,
|
||||
{},
|
||||
NewMessageType::Existing);
|
||||
if (item) {
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
sublist->append(std::move(items), count);
|
||||
if (result.type() == mtpc_messages_messages) {
|
||||
sublist->setFullLoaded();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
}
|
||||
sublist->setFullLoaded();
|
||||
_loadMoreRequests.remove(sublist);
|
||||
}).send();
|
||||
_loadMoreRequests[sublist] = requestId;
|
||||
}
|
||||
|
||||
void SavedMessages::apply(
|
||||
const MTPmessages_SavedDialogs &result,
|
||||
SavedMessages::ApplyResult SavedMessages::applyReceivedSublists(
|
||||
const MTPmessages_SavedDialogs &dialogs,
|
||||
bool pinned) {
|
||||
auto list = (const QVector<MTPSavedDialog>*)nullptr;
|
||||
result.match([](const MTPDmessages_savedDialogsNotModified &) {
|
||||
dialogs.match([](const MTPDmessages_savedDialogsNotModified &) {
|
||||
LOG(("API Error: messages.savedDialogsNotModified."));
|
||||
}, [&](const auto &data) {
|
||||
_owner->processUsers(data.vusers());
|
||||
@@ -195,69 +320,66 @@ void SavedMessages::apply(
|
||||
NewMessageType::Existing);
|
||||
list = &data.vdialogs().v;
|
||||
});
|
||||
if (pinned) {
|
||||
_pinnedRequestId = 0;
|
||||
_pinnedLoaded = true;
|
||||
} else {
|
||||
_loadMoreRequestId = 0;
|
||||
}
|
||||
if (!list) {
|
||||
if (!pinned) {
|
||||
_chatsList.setLoaded();
|
||||
}
|
||||
return;
|
||||
return { .allLoaded = true };
|
||||
}
|
||||
auto lastValid = false;
|
||||
auto offsetDate = TimeId();
|
||||
auto offsetId = MsgId();
|
||||
auto offsetPeer = (PeerData*)nullptr;
|
||||
const auto selfId = _owner->session().userPeerId();
|
||||
auto result = ApplyResult();
|
||||
const auto parentPeerId = _parentChat
|
||||
? _parentChat->id
|
||||
: _owner->session().userPeerId();
|
||||
for (const auto &dialog : *list) {
|
||||
const auto &data = dialog.data();
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
const auto topId = MsgId(data.vtop_message().v);
|
||||
if (const auto item = _owner->message(selfId, topId)) {
|
||||
offsetPeer = peer;
|
||||
offsetDate = item->date();
|
||||
offsetId = topId;
|
||||
lastValid = true;
|
||||
const auto entry = sublist(peer);
|
||||
const auto entryPinned = pinned || data.is_pinned();
|
||||
entry->applyMaybeLast(item);
|
||||
_owner->setPinnedFromEntryList(entry, entryPinned);
|
||||
} else {
|
||||
lastValid = false;
|
||||
}
|
||||
dialog.match([&](const MTPDsavedDialog &data) {
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
const auto topId = MsgId(data.vtop_message().v);
|
||||
if (const auto item = _owner->message(parentPeerId, topId)) {
|
||||
result.offset.peer = peer;
|
||||
result.offset.date = item->date();
|
||||
result.offset.id = topId;
|
||||
lastValid = true;
|
||||
const auto entry = sublist(peer);
|
||||
const auto entryPinned = pinned || data.is_pinned();
|
||||
entry->applyMaybeLast(item);
|
||||
_owner->setPinnedFromEntryList(entry, entryPinned);
|
||||
} else {
|
||||
lastValid = false;
|
||||
}
|
||||
}, [&](const MTPDmonoForumDialog &data) {
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
const auto topId = MsgId(data.vtop_message().v);
|
||||
if (const auto item = _owner->message(parentPeerId, topId)) {
|
||||
result.offset.peer = peer;
|
||||
result.offset.date = item->date();
|
||||
result.offset.id = topId;
|
||||
lastValid = true;
|
||||
sublist(peer)->applyMonoforumDialog(data, item);
|
||||
} else {
|
||||
lastValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (pinned) {
|
||||
} else if (!lastValid) {
|
||||
LOG(("API Error: Unknown message in the end of a slice."));
|
||||
_chatsList.setLoaded();
|
||||
} else if (result.type() == mtpc_messages_savedDialogs) {
|
||||
_chatsList.setLoaded();
|
||||
} else if ((_offsetDate > 0 && offsetDate > _offsetDate)
|
||||
|| (offsetDate == _offsetDate
|
||||
&& offsetId == _offsetId
|
||||
&& offsetPeer == _offsetPeer)) {
|
||||
LOG(("API Error: Bad order in messages.savedDialogs."));
|
||||
_chatsList.setLoaded();
|
||||
} else {
|
||||
_offsetDate = offsetDate;
|
||||
_offsetId = offsetId;
|
||||
_offsetPeer = offsetPeer;
|
||||
result.allLoaded = true;
|
||||
} else if (dialogs.type() == mtpc_messages_savedDialogs) {
|
||||
result.allLoaded = true;
|
||||
}
|
||||
if (!_stalePeers.empty()) {
|
||||
requestSomeStale();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMoreRequests() {
|
||||
if (_loadMoreScheduled) {
|
||||
sendLoadMore();
|
||||
}
|
||||
for (const auto sublist : base::take(_loadMoreSublistsScheduled)) {
|
||||
sendLoadMore(sublist);
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) {
|
||||
Expects(!parentChat());
|
||||
|
||||
const auto list = update.vorder();
|
||||
if (!list) {
|
||||
loadPinned();
|
||||
@@ -283,6 +405,8 @@ void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) {
|
||||
}
|
||||
|
||||
void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
|
||||
Expects(!parentChat());
|
||||
|
||||
update.vpeer().match([&](const MTPDdialogPeer &data) {
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
const auto i = _sublists.find(peer);
|
||||
@@ -297,4 +421,146 @@ void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
|
||||
});
|
||||
}
|
||||
|
||||
void SavedMessages::applySublistDeleted(not_null<PeerData*> sublistPeer) {
|
||||
const auto i = _sublists.find(sublistPeer);
|
||||
if (i == end(_sublists)) {
|
||||
return;
|
||||
}
|
||||
const auto raw = i->second.get();
|
||||
Core::App().notifications().clearFromSublist(raw);
|
||||
owner().removeChatListEntry(raw);
|
||||
|
||||
if (ranges::contains(_lastSublists, not_null(raw))) {
|
||||
reorderLastSublists();
|
||||
}
|
||||
|
||||
_sublistDestroyed.fire(raw);
|
||||
session().changes().sublistUpdated(
|
||||
raw,
|
||||
Data::SublistUpdate::Flag::Destroyed);
|
||||
session().changes().entryUpdated(
|
||||
raw,
|
||||
Data::EntryUpdate::Flag::Destroyed);
|
||||
_sublists.erase(i);
|
||||
|
||||
const auto history = owningHistory();
|
||||
history->destroyMessagesBySublist(sublistPeer);
|
||||
session().storage().unload(Storage::SharedMediaUnloadThread(
|
||||
_owningHistory->peer->id,
|
||||
MsgId(),
|
||||
sublistPeer->id));
|
||||
history->setForwardDraft(MsgId(), sublistPeer->id, {});
|
||||
}
|
||||
|
||||
void SavedMessages::reorderLastSublists() {
|
||||
if (!_parentChat) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We want first kShowChatNamesCount histories, by last message date.
|
||||
const auto pred = [](
|
||||
not_null<SavedSublist*> a,
|
||||
not_null<SavedSublist*> b) {
|
||||
const auto aItem = a->chatListMessage();
|
||||
const auto bItem = b->chatListMessage();
|
||||
const auto aDate = aItem ? aItem->date() : TimeId(0);
|
||||
const auto bDate = bItem ? bItem->date() : TimeId(0);
|
||||
return aDate > bDate;
|
||||
};
|
||||
_lastSublists.clear();
|
||||
_lastSublists.reserve(kShowSublistNamesCount + 1);
|
||||
auto &&sublists = ranges::views::all(
|
||||
*_chatsList.indexed()
|
||||
) | ranges::views::transform([](not_null<Dialogs::Row*> row) {
|
||||
return row->sublist();
|
||||
});
|
||||
auto nonPinnedChecked = 0;
|
||||
for (const auto sublist : sublists) {
|
||||
const auto i = ranges::upper_bound(
|
||||
_lastSublists,
|
||||
not_null(sublist),
|
||||
pred);
|
||||
if (size(_lastSublists) < kShowSublistNamesCount
|
||||
|| i != end(_lastSublists)) {
|
||||
_lastSublists.insert(i, sublist);
|
||||
}
|
||||
if (size(_lastSublists) > kShowSublistNamesCount) {
|
||||
_lastSublists.pop_back();
|
||||
}
|
||||
if (!sublist->isPinnedDialog(FilterId())
|
||||
&& ++nonPinnedChecked >= kShowSublistNamesCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
++_lastSublistsVersion;
|
||||
owningHistory()->updateChatListEntry();
|
||||
}
|
||||
|
||||
void SavedMessages::listMessageChanged(HistoryItem *from, HistoryItem *to) {
|
||||
if (from || to) {
|
||||
reorderLastSublists();
|
||||
}
|
||||
}
|
||||
|
||||
int SavedMessages::recentSublistsListVersion() const {
|
||||
return _lastSublistsVersion;
|
||||
}
|
||||
|
||||
void SavedMessages::recentSublistsInvalidate(
|
||||
not_null<SavedSublist*> sublist) {
|
||||
Expects(_parentChat != nullptr);
|
||||
|
||||
if (ranges::contains(_lastSublists, sublist)) {
|
||||
++_lastSublistsVersion;
|
||||
owningHistory()->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
|
||||
auto SavedMessages::recentSublists() const
|
||||
-> const std::vector<not_null<SavedSublist*>> & {
|
||||
return _lastSublists;
|
||||
}
|
||||
|
||||
void SavedMessages::markUnreadCountsUnknown(MsgId readTillId) {
|
||||
for (const auto &[peer, sublist] : _sublists) {
|
||||
if (sublist->unreadCountCurrent() > 0) {
|
||||
sublist->setInboxReadTill(readTillId, std::nullopt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::updateUnreadCounts(
|
||||
MsgId readTillId,
|
||||
const base::flat_map<not_null<SavedSublist*>, int> &counts) {
|
||||
for (const auto &[peer, sublist] : _sublists) {
|
||||
const auto raw = sublist.get();
|
||||
const auto i = counts.find(raw);
|
||||
const auto count = (i != end(counts)) ? i->second : 0;
|
||||
if (raw->unreadCountCurrent() != count) {
|
||||
raw->setInboxReadTill(readTillId, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SavedMessages::destroyed() const {
|
||||
if (!_parentChat) {
|
||||
return rpl::never<>();
|
||||
}
|
||||
return _parentChat->flagsValue(
|
||||
) | rpl::filter([=](const ChannelData::Flags::Change &update) {
|
||||
using Flag = ChannelData::Flag;
|
||||
return (update.diff & Flag::MonoforumAdmin)
|
||||
&& !(update.value & Flag::MonoforumAdmin);
|
||||
}) | rpl::take(1) | rpl::to_empty;
|
||||
}
|
||||
|
||||
auto SavedMessages::sublistDestroyed() const
|
||||
-> rpl::producer<not_null<SavedSublist*>> {
|
||||
return _sublistDestroyed.events();
|
||||
}
|
||||
|
||||
rpl::lifetime &SavedMessages::lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -18,55 +18,125 @@ namespace Data {
|
||||
class Session;
|
||||
class SavedSublist;
|
||||
|
||||
struct SavedMessagesOffsets {
|
||||
TimeId date = 0;
|
||||
MsgId id = 0;
|
||||
PeerData *peer = nullptr;
|
||||
|
||||
friend inline constexpr auto operator<=>(
|
||||
SavedMessagesOffsets,
|
||||
SavedMessagesOffsets) = default;
|
||||
};
|
||||
|
||||
class SavedMessages final {
|
||||
public:
|
||||
explicit SavedMessages(not_null<Session*> owner);
|
||||
explicit SavedMessages(
|
||||
not_null<Session*> owner,
|
||||
ChannelData *parentChat = nullptr);
|
||||
~SavedMessages();
|
||||
|
||||
[[nodiscard]] bool supported() const;
|
||||
void markUnsupported();
|
||||
|
||||
[[nodiscard]] ChannelData *parentChat() const;
|
||||
[[nodiscard]] not_null<History*> owningHistory() const;
|
||||
|
||||
[[nodiscard]] Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
[[nodiscard]] not_null<Dialogs::MainList*> chatsList();
|
||||
[[nodiscard]] not_null<SavedSublist*> sublist(not_null<PeerData*> peer);
|
||||
[[nodiscard]] SavedSublist *sublistLoaded(not_null<PeerData*> peer);
|
||||
void requestSublist(not_null<PeerData*> peer, Fn<void()> done = nullptr);
|
||||
|
||||
[[nodiscard]] rpl::producer<> chatsListChanges() const;
|
||||
[[nodiscard]] rpl::producer<> chatsListLoadedEvents() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<> destroyed() const;
|
||||
[[nodiscard]] auto sublistDestroyed() const
|
||||
-> rpl::producer<not_null<SavedSublist*>>;
|
||||
|
||||
void preloadSublists();
|
||||
void loadMore();
|
||||
void loadMore(not_null<SavedSublist*> sublist);
|
||||
void clearAllUnreadReactions();
|
||||
|
||||
void apply(const MTPDupdatePinnedSavedDialogs &update);
|
||||
void apply(const MTPDupdateSavedDialogPinned &update);
|
||||
void applySublistDeleted(not_null<PeerData*> sublistPeer);
|
||||
|
||||
void listMessageChanged(HistoryItem *from, HistoryItem *to);
|
||||
[[nodiscard]] int recentSublistsListVersion() const;
|
||||
void recentSublistsInvalidate(not_null<SavedSublist*> sublist);
|
||||
[[nodiscard]] auto recentSublists() const
|
||||
-> const std::vector<not_null<SavedSublist*>> &;
|
||||
|
||||
void markUnreadCountsUnknown(MsgId readTillId);
|
||||
void updateUnreadCounts(
|
||||
MsgId readTillId,
|
||||
const base::flat_map<not_null<SavedSublist*>, int> &counts);
|
||||
|
||||
void clear();
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
struct SublistRequest {
|
||||
mtpRequestId id = 0;
|
||||
std::vector<Fn<void()>> callbacks;
|
||||
};
|
||||
struct ApplyResult {
|
||||
SavedMessagesOffsets offset;
|
||||
bool allLoaded = false;
|
||||
};
|
||||
|
||||
void loadPinned();
|
||||
void apply(const MTPmessages_SavedDialogs &result, bool pinned);
|
||||
ApplyResult applyReceivedSublists(
|
||||
const MTPmessages_SavedDialogs &result,
|
||||
SavedMessagesOffsets &updateOffsets);
|
||||
ApplyResult applyReceivedSublists(
|
||||
const MTPmessages_SavedDialogs &result,
|
||||
bool pinned = false);
|
||||
|
||||
void reorderLastSublists();
|
||||
void requestSomeStale();
|
||||
void finishSublistRequest(not_null<PeerData*> peer);
|
||||
|
||||
void sendLoadMore();
|
||||
void sendLoadMore(not_null<SavedSublist*> sublist);
|
||||
void sendLoadMoreRequests();
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
ChannelData *_parentChat = nullptr;
|
||||
History *_owningHistory = nullptr;
|
||||
|
||||
rpl::event_stream<not_null<SavedSublist*>> _sublistDestroyed;
|
||||
|
||||
Dialogs::MainList _chatsList;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
std::unique_ptr<SavedSublist>> _sublists;
|
||||
base::flat_map<not_null<PeerData*>, SublistRequest> _sublistRequests;
|
||||
base::flat_set<not_null<PeerData*>> _stalePeers;
|
||||
mtpRequestId _staleRequestId = 0;
|
||||
|
||||
base::flat_map<not_null<SavedSublist*>, mtpRequestId> _loadMoreRequests;
|
||||
mtpRequestId _loadMoreRequestId = 0;
|
||||
mtpRequestId _pinnedRequestId = 0;
|
||||
|
||||
TimeId _offsetDate = 0;
|
||||
MsgId _offsetId = 0;
|
||||
PeerData *_offsetPeer = nullptr;
|
||||
SavedMessagesOffsets _offset;
|
||||
|
||||
SingleQueuedInvokation _loadMore;
|
||||
base::flat_set<not_null<SavedSublist*>> _loadMoreSublistsScheduled;
|
||||
bool _loadMoreScheduled = false;
|
||||
|
||||
std::vector<not_null<SavedSublist*>> _lastSublists;
|
||||
int _lastSublistsVersion = 0;
|
||||
|
||||
rpl::event_stream<> _chatsListChanges;
|
||||
rpl::event_stream<> _chatsListLoadedEvents;
|
||||
|
||||
bool _pinnedLoaded = false;
|
||||
bool _unsupported = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "data/data_thread.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
|
||||
class PeerData;
|
||||
class History;
|
||||
@@ -16,31 +17,62 @@ class History;
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
class Histories;
|
||||
class SavedMessages;
|
||||
struct MessagePosition;
|
||||
struct MessageUpdate;
|
||||
struct SublistReadTillUpdate;
|
||||
struct MessagesSlice;
|
||||
|
||||
class SavedSublist final : public Dialogs::Entry {
|
||||
class SavedSublist final : public Data::Thread {
|
||||
public:
|
||||
explicit SavedSublist(not_null<PeerData*> peer);
|
||||
SavedSublist(
|
||||
not_null<SavedMessages*> parent,
|
||||
not_null<PeerData*> sublistPeer);
|
||||
~SavedSublist();
|
||||
|
||||
[[nodiscard]] not_null<History*> history() const;
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
[[nodiscard]] bool isHiddenAuthor() const;
|
||||
[[nodiscard]] bool isFullLoaded() const;
|
||||
[[nodiscard]] bool inMonoforum() const;
|
||||
|
||||
[[nodiscard]] auto messages() const
|
||||
-> const std::vector<not_null<HistoryItem*>> &;
|
||||
void apply(const SublistReadTillUpdate &update);
|
||||
void apply(const MessageUpdate &update);
|
||||
void applyDifferenceTooLong();
|
||||
bool removeOne(not_null<HistoryItem*> item);
|
||||
|
||||
[[nodiscard]] rpl::producer<MessagesSlice> source(
|
||||
MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
|
||||
[[nodiscard]] not_null<SavedMessages*> parent() const;
|
||||
[[nodiscard]] not_null<History*> owningHistory() override;
|
||||
[[nodiscard]] ChannelData *parentChat() const;
|
||||
[[nodiscard]] not_null<PeerData*> sublistPeer() const;
|
||||
[[nodiscard]] bool isHiddenAuthor() const;
|
||||
[[nodiscard]] rpl::producer<> destroyed() const;
|
||||
|
||||
void growLastKnownServerMessageId(MsgId id);
|
||||
void applyMaybeLast(not_null<HistoryItem*> item, bool added = false);
|
||||
void removeOne(not_null<HistoryItem*> item);
|
||||
void append(std::vector<not_null<HistoryItem*>> &&items, int fullCount);
|
||||
void setFullLoaded(bool loaded = true);
|
||||
void applyItemAdded(not_null<HistoryItem*> item);
|
||||
void applyItemRemoved(MsgId id);
|
||||
|
||||
[[nodiscard]] rpl::producer<> changes() const;
|
||||
[[nodiscard]] std::optional<int> fullCount() const;
|
||||
[[nodiscard]] rpl::producer<int> fullCountValue() const;
|
||||
void loadFullCount();
|
||||
|
||||
[[nodiscard]] Dialogs::Ui::MessageView &lastItemDialogsView() {
|
||||
return _lastItemDialogsView;
|
||||
}
|
||||
[[nodiscard]] bool unreadCountKnown() const;
|
||||
[[nodiscard]] int unreadCountCurrent() const;
|
||||
[[nodiscard]] int displayedUnreadCount() const;
|
||||
[[nodiscard]] rpl::producer<std::optional<int>> unreadCountValue() const;
|
||||
void setUnreadMark(bool unread);
|
||||
|
||||
void applyMonoforumDialog(
|
||||
const MTPDmonoForumDialog &dialog,
|
||||
not_null<HistoryItem*> topItem);
|
||||
void readTillEnd();
|
||||
void requestChatListMessage();
|
||||
|
||||
TimeId adjustedChatListTimeId() const override;
|
||||
|
||||
int fixedOnTopIndex() const override;
|
||||
bool shouldBeInChatList() const override;
|
||||
@@ -54,32 +86,110 @@ public:
|
||||
const base::flat_set<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
|
||||
void hasUnreadMentionChanged(bool has) override;
|
||||
void hasUnreadReactionChanged(bool has) override;
|
||||
|
||||
[[nodiscard]] HistoryItem *lastMessage() const;
|
||||
[[nodiscard]] HistoryItem *lastServerMessage() const;
|
||||
[[nodiscard]] bool lastMessageKnown() const;
|
||||
[[nodiscard]] bool lastServerMessageKnown() const;
|
||||
[[nodiscard]] MsgId lastKnownServerMessageId() const;
|
||||
|
||||
void setInboxReadTill(MsgId readTillId, std::optional<int> unreadCount);
|
||||
[[nodiscard]] MsgId inboxReadTillId() const;
|
||||
[[nodiscard]] MsgId computeInboxReadTillFull() const;
|
||||
|
||||
void setOutboxReadTill(MsgId readTillId);
|
||||
[[nodiscard]] MsgId computeOutboxReadTillFull() const;
|
||||
|
||||
[[nodiscard]] bool isServerSideUnread(
|
||||
not_null<const HistoryItem*> item) const override;
|
||||
|
||||
void requestUnreadCount();
|
||||
|
||||
void readTill(not_null<HistoryItem*> item);
|
||||
void readTill(MsgId tillId);
|
||||
|
||||
void chatListPreloadData() override;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
const Dialogs::Ui::PaintContext &context) const override;
|
||||
|
||||
[[nodiscard]] auto sendActionPainter()
|
||||
-> HistoryView::SendActionPainter* override;
|
||||
|
||||
private:
|
||||
struct Viewer;
|
||||
|
||||
enum class Flag : uchar {
|
||||
ResolveChatListMessage = (1 << 0),
|
||||
FullLoaded = (1 << 1),
|
||||
InMonoforum = (1 << 1),
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; }
|
||||
using Flags = base::flags<Flag>;
|
||||
|
||||
bool hasOrphanMediaGroupPart() const;
|
||||
[[nodiscard]] Histories &histories();
|
||||
|
||||
void subscribeToUnreadChanges();
|
||||
[[nodiscard]] Dialogs::UnreadState unreadStateFor(
|
||||
int count,
|
||||
bool known) const;
|
||||
void setLastMessage(HistoryItem *item);
|
||||
void setLastServerMessage(HistoryItem *item);
|
||||
void setChatListMessage(HistoryItem *item);
|
||||
void allowChatListMessageResolve();
|
||||
void resolveChatListMessageGroup();
|
||||
|
||||
const not_null<History*> _history;
|
||||
void changeUnreadCountByMessage(MsgId id, int delta);
|
||||
void setUnreadCount(std::optional<int> count);
|
||||
void readTill(MsgId tillId, HistoryItem *tillIdItem);
|
||||
void checkReadTillEnd();
|
||||
void sendReadTillRequest();
|
||||
void reloadUnreadCountIfNeeded();
|
||||
|
||||
std::vector<not_null<HistoryItem*>> _items;
|
||||
std::optional<int> _fullCount;
|
||||
rpl::event_stream<> _changed;
|
||||
Dialogs::Ui::MessageView _lastItemDialogsView;
|
||||
[[nodiscard]] bool buildFromData(not_null<Viewer*> viewer);
|
||||
[[nodiscard]] bool applyUpdate(const MessageUpdate &update);
|
||||
void appendClientSideMessages(MessagesSlice &slice);
|
||||
[[nodiscard]] std::optional<int> computeUnreadCountLocally(
|
||||
MsgId afterId) const;
|
||||
bool processMessagesIsEmpty(const MTPmessages_Messages &result);
|
||||
void loadAround(MsgId id);
|
||||
void loadBefore();
|
||||
void loadAfter();
|
||||
|
||||
const not_null<SavedMessages*> _parent;
|
||||
const not_null<History*> _sublistHistory;
|
||||
|
||||
MsgId _lastKnownServerMessageId = 0;
|
||||
|
||||
std::vector<MsgId> _list;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
rpl::variable<std::optional<int>> _fullCount;
|
||||
rpl::event_stream<> _listChanges;
|
||||
rpl::event_stream<> _instantChanges;
|
||||
std::optional<MsgId> _loadingAround;
|
||||
rpl::variable<std::optional<int>> _unreadCount;
|
||||
MsgId _inboxReadTillId = 0;
|
||||
MsgId _outboxReadTillId = 0;
|
||||
Flags _flags;
|
||||
|
||||
std::optional<HistoryItem*> _lastMessage;
|
||||
std::optional<HistoryItem*> _lastServerMessage;
|
||||
std::optional<HistoryItem*> _chatListMessage;
|
||||
base::flat_set<FullMsgId> _requestedGroups;
|
||||
int _beforeId = 0;
|
||||
int _afterId = 0;
|
||||
|
||||
base::Timer _readRequestTimer;
|
||||
mtpRequestId _readRequestId = 0;
|
||||
MsgId _sentReadTill = 0;
|
||||
|
||||
mtpRequestId _reloadUnreadCountRequestId = 0;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -132,6 +132,7 @@ GlobalMediaResult ParseGlobalMediaResult(
|
||||
std::optional<SearchRequest> PrepareSearchRequest(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
Storage::SharedMediaType type,
|
||||
const QString &query,
|
||||
MsgId messageId,
|
||||
@@ -168,11 +169,14 @@ std::optional<SearchRequest> PrepareSearchRequest(
|
||||
int64(0x3FFFFFFF)));
|
||||
using Flag = MTPmessages_Search::Flag;
|
||||
return MTPmessages_Search(
|
||||
MTP_flags(topicRootId ? Flag::f_top_msg_id : Flag(0)),
|
||||
MTP_flags((topicRootId ? Flag::f_top_msg_id : Flag(0))
|
||||
| (monoforumPeerId ? Flag::f_saved_peer_id : Flag(0))),
|
||||
peer->input,
|
||||
MTP_string(query),
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPInputPeer(), // saved_peer_id
|
||||
(monoforumPeerId
|
||||
? peer->owner().peer(monoforumPeerId)->input
|
||||
: MTPInputPeer()),
|
||||
MTPVector<MTPReaction>(), // saved_reaction
|
||||
MTP_int(topicRootId),
|
||||
filter,
|
||||
@@ -369,12 +373,14 @@ rpl::producer<SparseIdsMergedSlice> SearchController::idsSlice(
|
||||
auto createSimpleViewer = [=](
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
SparseIdsSlice::Key simpleKey,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return simpleIdsSlice(
|
||||
peerId,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
simpleKey,
|
||||
query,
|
||||
limitBefore,
|
||||
@@ -384,6 +390,7 @@ rpl::producer<SparseIdsMergedSlice> SearchController::idsSlice(
|
||||
SparseIdsMergedSlice::Key(
|
||||
query.peerId,
|
||||
query.topicRootId,
|
||||
query.monoforumPeerId,
|
||||
query.migratedPeerId,
|
||||
aroundId),
|
||||
limitBefore,
|
||||
@@ -394,6 +401,7 @@ rpl::producer<SparseIdsMergedSlice> SearchController::idsSlice(
|
||||
rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice(
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
MsgId aroundId,
|
||||
const Query &query,
|
||||
int limitBefore,
|
||||
@@ -402,8 +410,12 @@ rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice(
|
||||
Expects(IsServerMsgId(aroundId) || (aroundId == 0));
|
||||
Expects((aroundId != 0)
|
||||
|| (limitBefore == 0 && limitAfter == 0));
|
||||
Expects((query.peerId == peerId && query.topicRootId == topicRootId)
|
||||
|| (query.migratedPeerId == peerId && MsgId(0) == topicRootId));
|
||||
Expects((query.peerId == peerId
|
||||
&& query.topicRootId == topicRootId
|
||||
&& query.monoforumPeerId == monoforumPeerId)
|
||||
|| (query.migratedPeerId == peerId
|
||||
&& MsgId(0) == topicRootId
|
||||
&& PeerId(0) == monoforumPeerId));
|
||||
|
||||
auto it = _cache.find(query);
|
||||
if (it == _cache.end()) {
|
||||
@@ -437,7 +449,9 @@ rpl::producer<SparseIdsSlice> SearchController::simpleIdsSlice(
|
||||
_session->data().itemRemoved(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (item->history()->peer->id == peerId)
|
||||
&& (!topicRootId || item->topicRootId() == topicRootId);
|
||||
&& (!topicRootId || item->topicRootId() == topicRootId)
|
||||
&& (!monoforumPeerId
|
||||
|| item->sublistPeerId() == monoforumPeerId);
|
||||
}) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return builder->removeOne(item->id);
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
@@ -510,6 +524,7 @@ void SearchController::requestMore(
|
||||
auto prepared = PrepareSearchRequest(
|
||||
listData->peer,
|
||||
query.topicRootId,
|
||||
query.monoforumPeerId,
|
||||
query.type,
|
||||
query.query,
|
||||
key.aroundId,
|
||||
|
||||
@@ -61,6 +61,7 @@ struct GlobalMediaResult {
|
||||
[[nodiscard]] std::optional<SearchRequest> PrepareSearchRequest(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
Storage::SharedMediaType type,
|
||||
const QString &query,
|
||||
MsgId messageId,
|
||||
@@ -92,6 +93,7 @@ public:
|
||||
|
||||
PeerId peerId = 0;
|
||||
MsgId topicRootId = 0;
|
||||
PeerId monoforumPeerId = 0;
|
||||
PeerId migratedPeerId = 0;
|
||||
MediaType type = MediaType::kCount;
|
||||
QString query;
|
||||
@@ -151,6 +153,7 @@ private:
|
||||
rpl::producer<SparseIdsSlice> simpleIdsSlice(
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
MsgId aroundId,
|
||||
const Query &query,
|
||||
int limitBefore,
|
||||
|
||||
@@ -341,6 +341,19 @@ void Session::subscribeForTopicRepliesLists() {
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
sublistReadTillUpdates(
|
||||
) | rpl::start_with_next([=](const SublistReadTillUpdate &update) {
|
||||
if (const auto parentChat = channelLoaded(update.parentChatId)) {
|
||||
if (const auto monoforum = parentChat->monoforum()) {
|
||||
const auto sublistPeerId = update.sublistPeerId;
|
||||
const auto peer = monoforum->owner().peer(sublistPeerId);
|
||||
if (const auto sublist = monoforum->sublistLoaded(peer)) {
|
||||
sublist->apply(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
session().changes().messageUpdates(
|
||||
MessageUpdate::Flag::NewAdded
|
||||
| MessageUpdate::Flag::NewMaybeAdded
|
||||
@@ -349,6 +362,11 @@ void Session::subscribeForTopicRepliesLists() {
|
||||
) | rpl::start_with_next([=](const MessageUpdate &update) {
|
||||
if (const auto topic = update.item->topic()) {
|
||||
topic->replies()->apply(update);
|
||||
} else if (update.flags == MessageUpdate::Flag::ReplyToTopAdded) {
|
||||
// Not interested in this one for sublist.
|
||||
return;
|
||||
} else if (const auto sublist = update.item->savedSublist()) {
|
||||
sublist->apply(update);
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
@@ -372,19 +390,21 @@ void Session::clear() {
|
||||
// Optimization: clear notifications before destroying items.
|
||||
Core::App().notifications().clearFromSession(_session);
|
||||
|
||||
// We must clear all forums before clearing customEmojiManager.
|
||||
// We must clear all [mono]forums before clearing customEmojiManager.
|
||||
// Because in Data::ForumTopic an Ui::Text::CustomEmoji is cached.
|
||||
auto forums = base::flat_set<not_null<ChannelData*>>();
|
||||
for (const auto &[peerId, peer] : _peers) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (channel->isForum()) {
|
||||
if (channel->isForum() || channel->amMonoforumAdmin()) {
|
||||
forums.emplace(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &channel : forums) {
|
||||
channel->setFlags(channel->flags() & ~ChannelDataFlag::Forum);
|
||||
channel->setFlags(channel->flags()
|
||||
& ~(ChannelDataFlag::Forum | ChannelDataFlag::MonoforumAdmin));
|
||||
}
|
||||
_savedMessages->clear();
|
||||
|
||||
_sendActionManager->clear();
|
||||
|
||||
@@ -959,15 +979,19 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
| Flag::CallActive
|
||||
| Flag::CallNotEmpty
|
||||
| Flag::Forbidden
|
||||
| (!minimal ? (Flag::Left | Flag::Creator) : Flag())
|
||||
| (!minimal
|
||||
? (Flag::Left | Flag::Creator)
|
||||
: Flag())
|
||||
| Flag::NoForwards
|
||||
| Flag::JoinToWrite
|
||||
| Flag::RequestToJoin
|
||||
| Flag::Forum
|
||||
| Flag::ForumTabs
|
||||
| ((!minimal && !data.is_stories_hidden_min())
|
||||
? Flag::StoriesHidden
|
||||
: Flag())
|
||||
| Flag::AutoTranslation;
|
||||
| Flag::AutoTranslation
|
||||
| Flag::Monoforum;
|
||||
const auto storiesState = minimal
|
||||
? std::optional<Data::Stories::PeerSourceState>()
|
||||
: data.is_stories_unavailable()
|
||||
@@ -993,8 +1017,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
? Flag::CallNotEmpty
|
||||
: Flag())
|
||||
| (!minimal
|
||||
? (data.is_left() ? Flag::Left : Flag())
|
||||
| (data.is_creator() ? Flag::Creator : Flag())
|
||||
? ((data.is_left() ? Flag::Left : Flag())
|
||||
| (data.is_creator() ? Flag::Creator : Flag()))
|
||||
: Flag())
|
||||
| (data.is_noforwards() ? Flag::NoForwards : Flag())
|
||||
| (data.is_join_to_send() ? Flag::JoinToWrite : Flag())
|
||||
@@ -1002,12 +1026,14 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
| ((data.is_forum() && data.is_megagroup())
|
||||
? Flag::Forum
|
||||
: Flag())
|
||||
| (data.is_forum_tabs() ? Flag::ForumTabs : Flag())
|
||||
| ((!minimal
|
||||
&& !data.is_stories_hidden_min()
|
||||
&& data.is_stories_hidden())
|
||||
? Flag::StoriesHidden
|
||||
: Flag())
|
||||
| (data.is_autotranslation() ? Flag::AutoTranslation : Flag());
|
||||
| (data.is_autotranslation() ? Flag::AutoTranslation : Flag())
|
||||
| (data.is_monoforum() ? Flag::Monoforum : Flag());
|
||||
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
|
||||
channel->setBotVerifyDetailsIcon(
|
||||
data.vbot_verification_icon().value_or_empty());
|
||||
@@ -1023,6 +1049,16 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||
channel->setStarsPerMessage(
|
||||
data.vsend_paid_messages_stars().value_or_empty());
|
||||
|
||||
if (const auto monoforum = data.vlinked_monoforum_id()) {
|
||||
if (const auto linked = channelLoaded(monoforum->v)) {
|
||||
channel->setMonoforumLink(linked);
|
||||
} else {
|
||||
channel->updateFull();
|
||||
}
|
||||
} else {
|
||||
channel->setMonoforumLink(nullptr);
|
||||
}
|
||||
|
||||
if (wasInChannel != channel->amIn()) {
|
||||
flags |= UpdateFlag::ChannelAmIn;
|
||||
}
|
||||
@@ -2310,6 +2346,9 @@ void Session::applyDialog(
|
||||
|
||||
bool Session::pinnedCanPin(not_null<Dialogs::Entry*> entry) const {
|
||||
if ([[maybe_unused]] const auto sublist = entry->asSublist()) {
|
||||
if (sublist->parentChat()) {
|
||||
return false;
|
||||
}
|
||||
const auto saved = &savedMessages();
|
||||
return pinnedChatsOrder(saved).size() < pinnedChatsLimit(saved);
|
||||
} else if (const auto topic = entry->asTopic()) {
|
||||
@@ -2351,6 +2390,9 @@ int Session::pinnedChatsLimit(not_null<Data::Forum*> forum) const {
|
||||
}
|
||||
|
||||
int Session::pinnedChatsLimit(not_null<Data::SavedMessages*> saved) const {
|
||||
if (saved->parentChat()) {
|
||||
return 0;
|
||||
}
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.savedSublistsPinnedCurrent();
|
||||
}
|
||||
@@ -2391,6 +2433,9 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
not_null<SavedMessages*> saved) const {
|
||||
if (saved->parentChat()) {
|
||||
return rpl::single(0);
|
||||
}
|
||||
// Premium limit from appconfig.
|
||||
// We always use premium limit in the MainList limit producer,
|
||||
// because it slices the list to that limit. We don't want to slice
|
||||
@@ -2889,6 +2934,15 @@ auto Session::repliesReadTillUpdates() const
|
||||
return _repliesReadTillUpdates.events();
|
||||
}
|
||||
|
||||
void Session::updateSublistReadTill(SublistReadTillUpdate update) {
|
||||
_sublistReadTillUpdates.fire(std::move(update));
|
||||
}
|
||||
|
||||
auto Session::sublistReadTillUpdates() const
|
||||
-> rpl::producer<SublistReadTillUpdate> {
|
||||
return _sublistReadTillUpdates.events();
|
||||
}
|
||||
|
||||
int Session::computeUnreadBadge(const Dialogs::UnreadState &state) const {
|
||||
const auto all = Core::App().settings().includeMutedCounter();
|
||||
return std::max(state.marks - (all ? 0 : state.marksMuted), 0)
|
||||
@@ -4563,12 +4617,12 @@ not_null<Folder*> Session::processFolder(const MTPDfolder &data) {
|
||||
|
||||
not_null<Dialogs::MainList*> Session::chatsListFor(
|
||||
not_null<Dialogs::Entry*> entry) {
|
||||
const auto topic = entry->asTopic();
|
||||
return topic
|
||||
? topic->forum()->topicsList()
|
||||
: entry->asSublist()
|
||||
? _savedMessages->chatsList()
|
||||
: chatsList(entry->folder());
|
||||
if (const auto topic = entry->asTopic()) {
|
||||
return topic->forum()->topicsList();
|
||||
} else if (const auto sublist = entry->asSublist()) {
|
||||
return sublist->parent()->chatsList();
|
||||
}
|
||||
return chatsList(entry->folder());
|
||||
}
|
||||
|
||||
not_null<Dialogs::MainList*> Session::chatsList(Data::Folder *folder) {
|
||||
@@ -4644,6 +4698,13 @@ void Session::refreshChatListEntry(Dialogs::Key key) {
|
||||
}
|
||||
if (const auto forum = history->peer->forum()) {
|
||||
forum->preloadTopics();
|
||||
} else if (const auto monoforum = history->peer->monoforum()) {
|
||||
monoforum->preloadSublists();
|
||||
}
|
||||
if (const auto broadcast = history->peer->monoforumBroadcast()) {
|
||||
if (!broadcast->isFullLoaded()) {
|
||||
broadcast->updateFull();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,13 @@ struct RepliesReadTillUpdate {
|
||||
bool out = false;
|
||||
};
|
||||
|
||||
struct SublistReadTillUpdate {
|
||||
ChannelId parentChatId;
|
||||
PeerId sublistPeerId;
|
||||
MsgId readTillId;
|
||||
bool out = false;
|
||||
};
|
||||
|
||||
struct GiftUpdate {
|
||||
enum class Action : uchar {
|
||||
Save,
|
||||
@@ -565,6 +572,10 @@ public:
|
||||
[[nodiscard]] auto repliesReadTillUpdates() const
|
||||
-> rpl::producer<RepliesReadTillUpdate>;
|
||||
|
||||
void updateSublistReadTill(SublistReadTillUpdate update);
|
||||
[[nodiscard]] auto sublistReadTillUpdates() const
|
||||
-> rpl::producer<SublistReadTillUpdate>;
|
||||
|
||||
void selfDestructIn(not_null<HistoryItem*> item, crl::time delay);
|
||||
|
||||
[[nodiscard]] not_null<PhotoData*> photo(PhotoId id);
|
||||
@@ -1004,6 +1015,7 @@ private:
|
||||
rpl::event_stream<ChatListEntryRefresh> _chatListEntryRefreshes;
|
||||
rpl::event_stream<> _unreadBadgeChanges;
|
||||
rpl::event_stream<RepliesReadTillUpdate> _repliesReadTillUpdates;
|
||||
rpl::event_stream<SublistReadTillUpdate> _sublistReadTillUpdates;
|
||||
rpl::event_stream<SentToScheduled> _sentToScheduled;
|
||||
rpl::event_stream<SentFromScheduled> _sentFromScheduled;
|
||||
|
||||
|
||||
@@ -110,11 +110,13 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
auto requestMediaAround = [
|
||||
peer = session->data().peer(key.peerId),
|
||||
topicRootId = key.topicRootId,
|
||||
monoforumPeerId = key.monoforumPeerId,
|
||||
type = key.type
|
||||
](const SparseIdsSliceBuilder::AroundData &data) {
|
||||
peer->session().api().requestSharedMedia(
|
||||
peer,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
type,
|
||||
data.aroundId,
|
||||
data.direction);
|
||||
@@ -131,6 +133,7 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
) | rpl::filter([=](const SliceUpdate &update) {
|
||||
return (update.peerId == key.peerId)
|
||||
&& (update.topicRootId == key.topicRootId)
|
||||
&& (update.monoforumPeerId == key.monoforumPeerId)
|
||||
&& (update.type == key.type);
|
||||
}) | rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update.data);
|
||||
@@ -151,6 +154,8 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
return (update.peerId == key.peerId)
|
||||
&& (!update.topicRootId
|
||||
|| update.topicRootId == key.topicRootId)
|
||||
&& (!update.monoforumPeerId
|
||||
|| update.monoforumPeerId == key.monoforumPeerId)
|
||||
&& update.types.test(key.type);
|
||||
}) | rpl::filter([=] {
|
||||
return builder->removeAll();
|
||||
@@ -236,6 +241,7 @@ rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
||||
auto createSimpleViewer = [=](
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
SparseIdsSlice::Key simpleKey,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
@@ -244,6 +250,7 @@ rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
||||
Storage::SharedMediaKey(
|
||||
peerId,
|
||||
topicRootId,
|
||||
monoforumPeerId,
|
||||
key.type,
|
||||
simpleKey),
|
||||
limitBefore,
|
||||
|
||||
@@ -74,11 +74,13 @@ public:
|
||||
Key(
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
PeerId migratedPeerId,
|
||||
Type type,
|
||||
UniversalMsgId universalId)
|
||||
: peerId(peerId)
|
||||
, topicRootId(topicRootId)
|
||||
, monoforumPeerId(monoforumPeerId)
|
||||
, migratedPeerId(migratedPeerId)
|
||||
, type(type)
|
||||
, universalId(universalId) {
|
||||
@@ -91,6 +93,7 @@ public:
|
||||
|
||||
PeerId peerId = 0;
|
||||
MsgId topicRootId = 0;
|
||||
PeerId monoforumPeerId = 0;
|
||||
PeerId migratedPeerId = 0;
|
||||
Type type = Type::kCount;
|
||||
UniversalMsgId universalId;
|
||||
@@ -120,6 +123,7 @@ public:
|
||||
return {
|
||||
key.peerId,
|
||||
key.topicRootId,
|
||||
key.monoforumPeerId,
|
||||
key.migratedPeerId,
|
||||
v::is<MessageId>(key.universalId)
|
||||
? v::get<MessageId>(key.universalId)
|
||||
@@ -130,6 +134,7 @@ public:
|
||||
return {
|
||||
key.peerId,
|
||||
key.topicRootId,
|
||||
key.monoforumPeerId,
|
||||
key.migratedPeerId,
|
||||
ServerMaxMsgId - 1
|
||||
};
|
||||
|
||||
@@ -377,7 +377,10 @@ rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
int limitBefore,
|
||||
int limitAfter,
|
||||
Fn<SimpleViewerFunction> simpleViewer) {
|
||||
Expects(!key.topicRootId || !key.migratedPeerId);
|
||||
Expects(!key.topicRootId
|
||||
|| (!key.monoforumPeerId && !key.migratedPeerId));
|
||||
Expects(!key.monoforumPeerId
|
||||
|| (!key.topicRootId && !key.migratedPeerId));
|
||||
Expects(IsServerMsgId(key.universalId)
|
||||
|| (key.universalId == 0)
|
||||
|| (IsServerMsgId(ServerMaxMsgId + key.universalId) && key.migratedPeerId != 0));
|
||||
@@ -388,6 +391,7 @@ rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
auto partViewer = simpleViewer(
|
||||
key.peerId,
|
||||
key.topicRootId,
|
||||
key.monoforumPeerId,
|
||||
SparseIdsMergedSlice::PartKey(key),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
@@ -405,6 +409,7 @@ rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
auto migratedViewer = simpleViewer(
|
||||
key.migratedPeerId,
|
||||
MsgId(0), // topicRootId
|
||||
PeerId(0), // monoforumPeerId
|
||||
SparseIdsMergedSlice::MigratedKey(key),
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
|
||||
@@ -33,11 +33,13 @@ public:
|
||||
Key(
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
PeerId migratedPeerId,
|
||||
UniversalMsgId universalId)
|
||||
: peerId(peerId)
|
||||
, topicRootId(topicRootId)
|
||||
, migratedPeerId(topicRootId ? 0 : migratedPeerId)
|
||||
, monoforumPeerId(monoforumPeerId)
|
||||
, migratedPeerId((topicRootId || monoforumPeerId) ? 0 : migratedPeerId)
|
||||
, universalId(universalId) {
|
||||
}
|
||||
|
||||
@@ -47,6 +49,7 @@ public:
|
||||
|
||||
PeerId peerId = 0;
|
||||
MsgId topicRootId = 0;
|
||||
PeerId monoforumPeerId = 0;
|
||||
PeerId migratedPeerId = 0;
|
||||
UniversalMsgId universalId = 0;
|
||||
};
|
||||
@@ -72,6 +75,7 @@ public:
|
||||
using SimpleViewerFunction = rpl::producer<SparseIdsSlice>(
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
PeerId monoforumPeerId,
|
||||
SparseIdsSlice::Key simpleKey,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_unread_things.h"
|
||||
@@ -31,6 +32,20 @@ MsgId Thread::topicRootId() const {
|
||||
return MsgId();
|
||||
}
|
||||
|
||||
PeerId Thread::monoforumPeerId() const {
|
||||
if (const auto sublist = asSublist()) {
|
||||
return sublist->sublistPeer()->id;
|
||||
}
|
||||
return PeerId();
|
||||
}
|
||||
|
||||
PeerData *Thread::maybeSublistPeer() const {
|
||||
if (const auto sublist = asSublist()) {
|
||||
return sublist->sublistPeer();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
not_null<PeerData*> Thread::peer() const {
|
||||
return owningHistory()->peer;
|
||||
}
|
||||
@@ -80,6 +95,17 @@ HistoryUnreadThings::ConstProxy Thread::unreadReactions() const {
|
||||
};
|
||||
}
|
||||
|
||||
bool Thread::canToggleUnread(bool nowUnread) const {
|
||||
if ((asTopic() || asForum()) && !nowUnread) {
|
||||
return false;
|
||||
} else if (asSublist() && owningHistory()->peer->isSelf()) {
|
||||
return false;
|
||||
} else if (asHistory() && peer()->amMonoforumAdmin()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const base::flat_set<MsgId> &Thread::unreadMentionsIds() const {
|
||||
if (!_unreadThings) {
|
||||
static const auto Result = base::flat_set<MsgId>();
|
||||
|
||||
@@ -67,6 +67,8 @@ public:
|
||||
return const_cast<Thread*>(this)->owningHistory();
|
||||
}
|
||||
[[nodiscard]] MsgId topicRootId() const;
|
||||
[[nodiscard]] PeerId monoforumPeerId() const;
|
||||
[[nodiscard]] PeerData *maybeSublistPeer() const;
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
[[nodiscard]] PeerNotifySettings ¬ify();
|
||||
[[nodiscard]] const PeerNotifySettings ¬ify() const;
|
||||
@@ -78,6 +80,7 @@ public:
|
||||
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const;
|
||||
virtual void hasUnreadMentionChanged(bool has) = 0;
|
||||
virtual void hasUnreadReactionChanged(bool has) = 0;
|
||||
bool canToggleUnread(bool nowUnread) const;
|
||||
|
||||
void removeNotification(not_null<HistoryItem*> item);
|
||||
void clearNotifications();
|
||||
@@ -112,7 +115,7 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual auto sendActionPainter()
|
||||
-> not_null<HistoryView::SendActionPainter*> = 0;
|
||||
-> HistoryView::SendActionPainter* = 0;
|
||||
|
||||
[[nodiscard]] bool hasPinnedMessages() const;
|
||||
void setHasPinnedMessages(bool has);
|
||||
|
||||
@@ -382,8 +382,6 @@ struct ForwardDraft {
|
||||
const ForwardDraft&) = default;
|
||||
};
|
||||
|
||||
using ForwardDrafts = base::flat_map<MsgId, ForwardDraft>;
|
||||
|
||||
struct ResolvedForwardDraft {
|
||||
HistoryItemsList items;
|
||||
ForwardOptions options = ForwardOptions::PreserveInfo;
|
||||
|
||||
@@ -500,8 +500,8 @@ dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation)
|
||||
}
|
||||
|
||||
dialogsSearchInHeight: 38px;
|
||||
dialogsSearchInPhotoSize: 26px;
|
||||
dialogsSearchInPhotoPadding: 12px;
|
||||
dialogsSearchInPhotoSize: 28px;
|
||||
dialogsSearchInPhotoPadding: 10px;
|
||||
dialogsSearchInSkip: 10px;
|
||||
dialogsSearchInNameTop: 9px;
|
||||
dialogsSearchInDownTop: 15px;
|
||||
|
||||
@@ -93,6 +93,9 @@ struct BadgesState {
|
||||
friend inline constexpr auto operator<=>(
|
||||
BadgesState,
|
||||
BadgesState) = default;
|
||||
friend inline constexpr bool operator==(
|
||||
BadgesState,
|
||||
BadgesState) = default;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return !unread && !mention && !reaction;
|
||||
|
||||
@@ -84,9 +84,9 @@ Entry::Entry(not_null<Data::Session*> owner, Type type)
|
||||
, _flags((type == Type::History)
|
||||
? (Flag::IsThread | Flag::IsHistory)
|
||||
: (type == Type::ForumTopic)
|
||||
? Flag::IsThread
|
||||
? (Flag::IsThread | Flag::IsForumTopic)
|
||||
: (type == Type::SavedSublist)
|
||||
? Flag::IsSavedSublist
|
||||
? (Flag::IsThread | Flag::IsSavedSublist)
|
||||
: Flag(0)) {
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ Data::Forum *Entry::asForum() {
|
||||
}
|
||||
|
||||
Data::Folder *Entry::asFolder() {
|
||||
return (_flags & (Flag::IsThread | Flag::IsSavedSublist))
|
||||
return (_flags & Flag::IsThread)
|
||||
? nullptr
|
||||
: static_cast<Data::Folder*>(this);
|
||||
}
|
||||
@@ -125,7 +125,7 @@ Data::Thread *Entry::asThread() {
|
||||
}
|
||||
|
||||
Data::ForumTopic *Entry::asTopic() {
|
||||
return ((_flags & Flag::IsThread) && !(_flags & Flag::IsHistory))
|
||||
return (_flags & Flag::IsForumTopic)
|
||||
? static_cast<Data::ForumTopic*>(this)
|
||||
: nullptr;
|
||||
}
|
||||
@@ -229,6 +229,13 @@ uint64 Entry::computeSortPosition(FilterId filterId) const {
|
||||
}
|
||||
|
||||
void Entry::updateChatListExistence() {
|
||||
if (const auto history = asHistory()) {
|
||||
if (const auto channel = history->peer->asMonoforum()) {
|
||||
if (!folderKnown()) {
|
||||
history->clearFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
setChatListExistence(shouldBeInChatList());
|
||||
}
|
||||
|
||||
@@ -280,6 +287,10 @@ void Entry::notifyUnreadStateChange(const UnreadState &wasState) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (const auto sublist = asSublist()) {
|
||||
session().changes().sublistUpdated(
|
||||
sublist,
|
||||
Data::SublistUpdate::Flag::UnreadView);
|
||||
}
|
||||
updateChatListEntryPostponed();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user