Compare commits

..

112 Commits

Author SHA1 Message Date
John Preston
4659d5db5d Version 5.15.
- Send Direct Messages to Channels.
- Enable New Tab Layout for Topics.
- Create Polls with Up To 12 Options.
2025-06-04 22:56:02 +04:00
Ilya Fedin
af061125dd Fix Docker build without LTO 2025-06-04 21:33:53 +04:00
John Preston
5c4b1f6638 Show message author to admins in monoforums. 2025-06-04 21:33:27 +04:00
John Preston
ee3d70f879 Fix glitching userpics in monoforum. 2025-06-04 21:05:08 +04:00
John Preston
7dadaa1b28 Save subsection tabs layout to disk. 2025-06-04 19:39:34 +04:00
John Preston
a72782e232 Use server provided default stars count for direct. 2025-06-04 18:42:17 +04:00
John Preston
8654ffb6fb Don't show "Who Viewed" in monoforums. 2025-06-04 18:26:48 +04:00
John Preston
90e445eec9 Don't show notifications from other admins. 2025-06-04 18:26:30 +04:00
John Preston
910b6d8879 Fix unread mark badge in new forums layout. 2025-06-04 18:13:21 +04:00
John Preston
8d1c2f832d Add "Create topic" to new forum view. 2025-06-04 17:59:25 +04:00
John Preston
158d2a4124 Fix possible stack overflow in subsection tabs. 2025-06-04 17:59:10 +04:00
John Preston
66473738d6 Add simple shadow to subsection tabs. 2025-06-04 17:26:23 +04:00
John Preston
6a43107bb2 Fix possible crash in subsection tabs. 2025-06-04 16:52:44 +04:00
John Preston
28e7afa412 Even nicer empty chat. 2025-06-04 16:48:37 +04:00
John Preston
8dc151e14d Fix build on Windows. 2025-06-04 16:21:21 +04:00
John Preston
a330a3f2eb Nicer empty monoforum for non-admins. 2025-06-04 15:45:22 +04:00
Ilya Fedin
8f7195d3b2 Fix macOS action 2025-06-04 11:17:35 +04:00
Ilya Fedin
a4e4502d50 Add missing dependencies to macOS packaged action 2025-06-04 11:17:35 +04:00
GitHub Action
902da90100 Update User-Agent for DNS to Chrome 136.0.0.0. 2025-06-04 10:28:54 +04:00
John Preston
d775760f98 Support nice monoforum userpics. 2025-06-04 10:00:24 +04:00
John Preston
dfb6600104 Fix loading of horizontal avatar strip. 2025-06-04 10:00:24 +04:00
John Preston
41ed487d5e Improve opening ChatWidget at the end. 2025-06-04 10:00:24 +04:00
John Preston
d156de05a5 Allow replying in monoforum while not in it. 2025-06-04 10:00:24 +04:00
John Preston
f4582ddf36 Correctly mark monoforum chats as read. 2025-06-04 10:00:24 +04:00
John Preston
7f7b764f7b Allow ton:// links in webapps. 2025-06-04 10:00:24 +04:00
John Preston
dd8fdfc3d4 Allow forwarding polls to monoforums. 2025-06-04 10:00:24 +04:00
John Preston
6c80d443b9 Better entry point for Direct Messages. 2025-06-04 10:00:24 +04:00
John Preston
cd05586d51 Fix display of pinned messages in sublists. 2025-06-04 10:00:24 +04:00
John Preston
dfc1ec3ccf Support shared media / pins for sublists. 2025-06-04 10:00:23 +04:00
John Preston
ffe6786ad1 Support unread reactions in monoforums. 2025-06-04 10:00:23 +04:00
John Preston
6068678fa1 Improve separate window support. 2025-06-04 10:00:23 +04:00
John Preston
50b761fab2 Remove showing monoforums inside dialogs widget. 2025-06-04 10:00:23 +04:00
John Preston
0d43f16db2 Remove unsupported actions from monoforum menu. 2025-06-04 10:00:23 +04:00
John Preston
3278de9ba1 Support mark as read/unread in sublists. 2025-06-04 10:00:23 +04:00
John Preston
abe1962002 Show context menu for topics in new tabs. 2025-06-04 10:00:23 +04:00
John Preston
5b15f377cd Improve set direct message price box. 2025-06-04 10:00:23 +04:00
John Preston
d0e5ea78a5 Improve topics layout management. 2025-06-04 10:00:23 +04:00
John Preston
853757e611 Fix tabs transfer between chat widgets. 2025-06-04 10:00:22 +04:00
John Preston
c3860cfe72 Improve monoforum top bar status. 2025-06-04 10:00:22 +04:00
John Preston
90b2c077a6 Don't flood with bot requests in monoforums. 2025-06-04 10:00:22 +04:00
John Preston
fdce4bada7 Implement nice topic mode editing. 2025-06-04 10:00:22 +04:00
John Preston
4c8ff1c7ec Disable polls in monoforums, enable in Saved Messages. 2025-06-04 10:00:22 +04:00
John Preston
2a153214f6 Support polls with 12 options. 2025-06-04 10:00:22 +04:00
John Preston
d7c964afc5 Show "Messages" badge for monoforum. 2025-06-04 10:00:22 +04:00
John Preston
5943052cd1 Show badges in new tabs. 2025-06-04 10:00:22 +04:00
John Preston
8512154b45 Implement better horizontal/vertical tabs. 2025-06-04 10:00:22 +04:00
John Preston
0e5419c60b Fix opening forums with tabs. 2025-06-04 10:00:22 +04:00
John Preston
1d26482298 Update API scheme on layer 204. 2025-06-04 10:00:21 +04:00
John Preston
126749f04c Fix build with new MSVC. 2025-06-04 10:00:21 +04:00
John Preston
e0e69ce740 Support vertical tabs somehow. 2025-06-04 10:00:21 +04:00
John Preston
72b57924b7 Correctly load tab slices. 2025-06-04 10:00:21 +04:00
John Preston
fdbdeeb956 Start new tabs for monoforums. 2025-06-04 10:00:21 +04:00
John Preston
3dbdecf73d Make monoforum sender badges float. 2025-06-04 10:00:21 +04:00
John Preston
7dc8943840 Improve monoforum opening. 2025-06-04 10:00:21 +04:00
John Preston
646b852717 Correct rights check in monoforums. 2025-06-04 10:00:21 +04:00
John Preston
075f754a71 Update API scheme on layer 204. 2025-06-04 10:00:21 +04:00
John Preston
f65556acb7 Support drafts in monoforum sublists. 2025-06-04 10:00:21 +04:00
John Preston
b2c01991a6 Support unread state in sublists. 2025-06-04 10:00:21 +04:00
John Preston
4bc5e81513 Update API scheme on layer 204. 2025-06-04 10:00:19 +04:00
John Preston
2b24fe95c2 Update API scheme on layer 204. 2025-06-04 10:00:19 +04:00
John Preston
358e64f2cc Show monoforums as forums in chats list. 2025-06-04 10:00:19 +04:00
John Preston
5dc50b6d96 Respect price of messages to channels. 2025-06-04 10:00:18 +04:00
John Preston
b91a040a32 Update API scheme on layer 204. 2025-06-04 10:00:18 +04:00
John Preston
76db55ff19 Support forwarding to monoforums. 2025-06-04 10:00:18 +04:00
John Preston
e17bf18350 Update API scheme on layer 204. 2025-06-04 10:00:18 +04:00
John Preston
43b4499125 Add monoforum sender bar divider. 2025-06-04 10:00:18 +04:00
John Preston
c6d43a802c Fix sending messages in monoforums. 2025-06-04 10:00:18 +04:00
John Preston
21f8403357 Merge SublistSection into ChatSection. 2025-06-04 10:00:18 +04:00
John Preston
40053e3388 Rename RepliesWidget/Memento to ChatWidget/Memento. 2025-06-04 10:00:14 +04:00
John Preston
abcf7e3a47 Update API scheme & fix monoforum send. 2025-06-04 10:00:14 +04:00
John Preston
f8913bf9b9 Show square userpics for monoforums. 2025-06-04 10:00:14 +04:00
John Preston
51878ab38e Allow opening monoforums. 2025-06-04 10:00:14 +04:00
John Preston
d3f9a84a0a Allow enabling direct messages in channels. 2025-06-04 10:00:14 +04:00
John Preston
23eedb468f Update API scheme to layer 204. 2025-06-04 10:00:14 +04:00
John Preston
9cb89fff45 Fix dropping filter in gifts. 2025-06-04 10:00:14 +04:00
23rd
79f0b22276 Compressed lottie for media forbidden. 2025-06-03 18:30:38 +03:00
23rd
8b2a728a0d Fixed display of loading animation from search in Saved Messages. 2025-06-03 18:28:11 +03:00
23rd
b4120b156e Added nice overlay to recorded round videos. 2025-06-03 17:55:37 +03:00
23rd
1ae3122c20 Added support of suggestion to validate cloud password to settings. 2025-06-03 17:55:37 +03:00
23rd
727acca217 Updated Qt to 5.15.17 on Windows. 2025-06-03 17:55:37 +03:00
23rd
5f8d662d67 Slightly improved of forward declarations in history item. 2025-06-03 17:55:18 +03:00
23rd
81b432140c Added ability to edit caption from box even when file is uploaded. 2025-06-03 17:55:18 +03:00
23rd
adc1ee71a9 Slightly improved box to edit caption of grouped file. 2025-06-03 17:55:18 +03:00
23rd
5ac373d4aa Added simple box to edit caption of single file while it's uploading. 2025-06-03 17:55:18 +03:00
23rd
5b9e24f3f4 Slightly improved box for writing captions to be more generic. 2025-06-03 17:55:18 +03:00
23rd
0e44de2fe3 Slightly improved style of exception button in notifications settings. 2025-06-03 17:55:18 +03:00
23rd
b0125e8165 Slightly improved display of numbers approaching zero in stats charts. 2025-06-03 17:55:18 +03:00
23rd
a532067a93 Fixed dismissing of custom pending suggestions. 2025-06-03 17:55:18 +03:00
Ilya Fedin
f456071c08 Switch Dockerfile to packaged mode 2025-06-03 18:25:15 +04:00
Ilya Fedin
3896f0995c Runtime Implib detection 2025-06-03 18:25:15 +04:00
Ilya Fedin
56ff5808a3 Unify packaged/non-packaged binary name 2025-06-03 18:25:15 +04:00
Ilya Fedin
15c817dd15 Update Qt 6.9.0 -> 6.9.1 2025-06-03 15:01:48 +04:00
Ilya Fedin
7246c3f304 Set cmake OpenGL default to legacy in Dockerfile 2025-06-03 14:11:25 +04:00
Ilya Fedin
e4f59f1ec4 Build only static libraries in Dockerfile 2025-06-03 14:11:25 +04:00
Ilya Fedin
5f0e9538cf Move Implib to Dockerfile 2025-06-03 10:16:58 +04:00
Ilya Fedin
73649128f3 Update cmake_helpers 2025-06-03 09:58:11 +04:00
Ilya Fedin
edc84731ac Change debug cmake flags according to Dockerfile options 2025-06-03 09:58:11 +04:00
Ilya Fedin
108b116b06 Use lld when building without LTO in Dockerfile 2025-06-03 09:58:11 +04:00
Ilya Fedin
dda587dc6f DESKTOP_APP_USE_PACKAGED_LAZY -> DESKTOP_APP_DISABLE_QT_PLUGINS 2025-06-03 09:50:07 +04:00
Ilya Fedin
4a9dd43598 Update tg_owt 2025-06-03 09:41:49 +04:00
Ilya Fedin
a64cfe661a Add missing deps to webrtc Docker layer 2025-06-03 09:41:33 +04:00
Ilya Fedin
7e418a16ae Fix packaged conditions in lib_ffmpeg and Packer 2025-06-03 09:41:22 +04:00
Ilya Fedin
ecf1fa2bbd Get rid of lib prefixes in Docker layers 2025-06-03 09:28:44 +04:00
Ilya Fedin
a6157a34bc Update variable syntax in Dockerfile 2025-06-03 09:28:44 +04:00
Ilya Fedin
8e37e66706 Make TOOLSET variable not dependent on jinja in Dockerfile 2025-06-03 09:28:44 +04:00
Ilya Fedin
dd6a4931e5 Make QT variable local to the Docker layer 2025-06-03 09:28:44 +04:00
Ilya Fedin
2d000e826b Remove GIT variable from Dockerfile 2025-06-03 09:28:44 +04:00
Ilya Fedin
c1028e7408 Remove GIT_FREEDESKTOP variable from Dockerfile 2025-06-03 09:28:44 +04:00
Ilya Fedin
28b54fac37 Revert GIT_UPDATE_M4 in Dockerfile
This partially reverts commit 9461095c88.
2025-06-03 09:28:44 +04:00
Ilya Fedin
845fddf5f2 Use enable_language 2025-06-03 09:28:31 +04:00
Ilya Fedin
e3e2a477c1 Proper check for multi-config generator 2025-06-03 09:20:45 +04:00
Ilya Fedin
bf4442ecf5 CMAKE_CONFIG_TYPE doesn't seem to work
This partially reverts commit ae45189436.
2025-06-03 09:20:24 +04:00
285 changed files with 11002 additions and 3618 deletions

View File

@@ -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 \

View File

@@ -40,7 +40,7 @@ jobs:
macos:
name: MacOS
runs-on: macos-latest
runs-on: macos-13
strategy:
matrix:

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -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}";

View File

@@ -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>

View 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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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>

View File

@@ -14,7 +14,7 @@ class ChannelData;
namespace Info::Profile {
class Badge;
enum class BadgeType;
enum class BadgeType : uchar;
} // namespace Info::Profile
namespace Main {

View File

@@ -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;

View File

@@ -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();

View File

@@ -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),

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);

View File

@@ -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 = {};

View File

@@ -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,

View File

@@ -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());

View File

@@ -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();
});
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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),

View File

@@ -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,

View File

@@ -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;
};

View File

@@ -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);
};

View File

@@ -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()

View File

@@ -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) {

View File

@@ -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

View 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

View 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

View File

@@ -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),

View File

@@ -907,6 +907,7 @@ void PinsLimitBox(
limits.dialogsPinnedPremium(),
PinsCount(session->data().chatsList()));
}
void SublistsPinsLimitBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session) {

View File

@@ -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

View File

@@ -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,

View File

@@ -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),

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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;
}
}
}
});

View File

@@ -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()) {

View File

@@ -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;

View File

@@ -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

View File

@@ -51,6 +51,8 @@ public:
[[nodiscard]] auto knownBirthdaysToday() const
-> std::optional<std::vector<UserId>>;
[[nodiscard]] static QString SugValidatePassword();
private:
void setTopPromoted(
History *promoted,

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -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:

View File

@@ -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()) {

View File

@@ -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();

View File

@@ -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 });
}
};

View File

@@ -31,6 +31,7 @@ void ResolveDocument(
Window::SessionController *controller,
not_null<DocumentData*> document,
HistoryItem *item,
MsgId topicRootId);
MsgId topicRootId,
PeerId monoforumPeerId);
} // namespace Data

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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));

View File

@@ -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];

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 &notify() {
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

View File

@@ -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

View File

@@ -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(

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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();
}
}
}
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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
};

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>();

View File

@@ -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 &notify();
[[nodiscard]] const PeerNotifySettings &notify() 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);

View File

@@ -382,8 +382,6 @@ struct ForwardDraft {
const ForwardDraft&) = default;
};
using ForwardDrafts = base::flat_map<MsgId, ForwardDraft>;
struct ResolvedForwardDraft {
HistoryItemsList items;
ForwardOptions options = ForwardOptions::PreserveInfo;

View File

@@ -500,8 +500,8 @@ dialogsLoadMoreLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation)
}
dialogsSearchInHeight: 38px;
dialogsSearchInPhotoSize: 26px;
dialogsSearchInPhotoPadding: 12px;
dialogsSearchInPhotoSize: 28px;
dialogsSearchInPhotoPadding: 10px;
dialogsSearchInSkip: 10px;
dialogsSearchInNameTop: 9px;
dialogsSearchInDownTop: 15px;

View File

@@ -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;

View File

@@ -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