Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e6937a075 | ||
|
|
d714c1edc0 | ||
|
|
ebf46e1270 | ||
|
|
116a598508 | ||
|
|
f9a14fc6bc | ||
|
|
7cdc3eb2b2 | ||
|
|
294432ceed | ||
|
|
551bf4f9a7 | ||
|
|
4b2d8b0c53 | ||
|
|
58e35dec12 | ||
|
|
ae90347c6c | ||
|
|
1aece79a47 | ||
|
|
79a2d85287 | ||
|
|
937d243a4c | ||
|
|
f82bae15f0 | ||
|
|
7aede75e43 | ||
|
|
b72fce4894 | ||
|
|
32cebc0d9b | ||
|
|
2de76cb75b | ||
|
|
ab06574fd9 | ||
|
|
473e190aeb | ||
|
|
4b5a0942b1 | ||
|
|
106bdae9ce | ||
|
|
f97e5d6307 | ||
|
|
bb106b07af | ||
|
|
ce631436bf | ||
|
|
bc5aa7338e | ||
|
|
6db7840fa7 | ||
|
|
921d2239c7 | ||
|
|
0feef675f7 | ||
|
|
f16d30de37 | ||
|
|
cf54d9fb12 | ||
|
|
b7647fbcc1 | ||
|
|
95a1ab6b0b | ||
|
|
823b4e6b98 | ||
|
|
3467fe226f | ||
|
|
57c50c8655 | ||
|
|
8f3c3b2a54 | ||
|
|
efc0908ed8 | ||
|
|
561e3f4809 | ||
|
|
ede34578da | ||
|
|
97356032ac | ||
|
|
4c8187f623 | ||
|
|
996b6bf46a | ||
|
|
37308cde21 | ||
|
|
248337daf5 | ||
|
|
8a288476b8 | ||
|
|
c7741cb62a | ||
|
|
983b6af0b4 | ||
|
|
a94dd22caa | ||
|
|
899ab9a16a | ||
|
|
2f0d14bd35 | ||
|
|
9b66b76bac | ||
|
|
a1e60a3f20 | ||
|
|
f7971733f4 | ||
|
|
e33b62ad28 | ||
|
|
82629dd3e5 | ||
|
|
768fc9b8f6 | ||
|
|
b9b6a9e747 | ||
|
|
b4d310fd1e | ||
|
|
f9dd2b4a0a | ||
|
|
afaad155a0 | ||
|
|
9726b3c298 | ||
|
|
85b94bc6fd | ||
|
|
f0645753d4 | ||
|
|
8b570f2e8f | ||
|
|
2b7b278b52 | ||
|
|
a02c01cce7 | ||
|
|
9601207b2c | ||
|
|
c06f0b3ea1 | ||
|
|
b514496546 | ||
|
|
ca460dab6d | ||
|
|
644ec1f599 | ||
|
|
73e2cc96d1 | ||
|
|
d756ecc609 | ||
|
|
0cd0ad7a5b | ||
|
|
9229c57e7a | ||
|
|
5b17416177 | ||
|
|
0b7a2c18a2 | ||
|
|
187f5fa4f3 | ||
|
|
3fad69d3c8 | ||
|
|
7e2a49c1f9 | ||
|
|
66435d5269 | ||
|
|
4a8b5c3015 | ||
|
|
d0d2a4f488 | ||
|
|
8ee28f6665 | ||
|
|
a2b0c551c2 | ||
|
|
bff641c217 | ||
|
|
a9c0b817d1 | ||
|
|
0f38dabd84 | ||
|
|
8552047210 | ||
|
|
092923fe6e | ||
|
|
ab5792f59f | ||
|
|
9d59e42b52 | ||
|
|
e675dc1ef1 | ||
|
|
8cb980a791 | ||
|
|
785372f5d0 | ||
|
|
aa6495a257 | ||
|
|
7993c6207a | ||
|
|
6af93b3497 | ||
|
|
153fb3e579 | ||
|
|
80d4c3affe | ||
|
|
9a1d9deea5 | ||
|
|
3b7cdb5748 | ||
|
|
f542a026ec | ||
|
|
629314cfa2 | ||
|
|
c320917069 | ||
|
|
9276f3dab8 | ||
|
|
1316d14f7a | ||
|
|
0737034ea6 | ||
|
|
9a54473e03 | ||
|
|
991fe491c5 | ||
|
|
6e606f3bb6 | ||
|
|
d8a0497a7e | ||
|
|
fc4682d77e | ||
|
|
a507edb67a | ||
|
|
bd8b90055e | ||
|
|
371ba40a50 | ||
|
|
a8d8b5be28 | ||
|
|
3e428faa2e | ||
|
|
3887fbc437 | ||
|
|
a4b0443047 | ||
|
|
a73ff8f5d7 | ||
|
|
0c8400212e | ||
|
|
dcfc3431f5 |
2
.github/workflows/docker.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
|
||||
6
.github/workflows/mac.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
|
||||
- name: ThirdParty cache.
|
||||
id: cache-third-party
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3.0.11
|
||||
with:
|
||||
path: ThirdParty
|
||||
key: ${{ runner.OS }}-third-party-${{ hashFiles(format('{0}/{1}', env.REPO_NAME, env.PREPARE_PATH)) }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3.0.11
|
||||
with:
|
||||
path: Libraries
|
||||
key: ${{ runner.OS }}-libs-${{ hashFiles(format('{0}/{1}', env.REPO_NAME, env.PREPARE_PATH)) }}
|
||||
|
||||
2
.github/workflows/master_updater.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
SKIP: "0"
|
||||
to_branch: "master"
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3.1.0
|
||||
if: env.SKIP == '0'
|
||||
- name: Push the code to the master branch.
|
||||
if: env.SKIP == '0'
|
||||
|
||||
2
.github/workflows/snap.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
12
.github/workflows/win.yml
vendored
@@ -60,21 +60,24 @@ jobs:
|
||||
steps:
|
||||
- name: Prepare directories.
|
||||
run: |
|
||||
mkdir %userprofile%\TBuild
|
||||
mklink /d %GITHUB_WORKSPACE%\TBuild %userprofile%\TBuild
|
||||
echo TBUILD=%GITHUB_WORKSPACE%\TBuild>>%GITHUB_ENV%
|
||||
|
||||
mkdir %userprofile%\TBuild Libraries
|
||||
mklink /d %userprofile%\TBuild\Libraries %GITHUB_WORKSPACE%\Libraries
|
||||
echo TBUILD=%userprofile%\TBuild>>%GITHUB_ENV%
|
||||
|
||||
- name: Get repository name.
|
||||
shell: bash
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1.10.0
|
||||
- uses: ilammy/msvc-dev-cmd@v1.12.0
|
||||
name: Native Tools Command Prompt.
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
- name: Clone.
|
||||
uses: LebedevRI/checkout@issue197
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.TBUILD }}\${{ env.REPO_NAME }}
|
||||
@@ -98,7 +101,7 @@ jobs:
|
||||
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3.0.11
|
||||
with:
|
||||
path: Libraries
|
||||
key: ${{ runner.OS }}-${{ matrix.arch }}-libs-${{ env.CACHE_KEY }}
|
||||
@@ -132,7 +135,6 @@ jobs:
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: |
|
||||
C:
|
||||
cd %TBUILD%\%REPO_NAME%\Telegram
|
||||
|
||||
call configure.bat ^
|
||||
|
||||
6
.gitmodules
vendored
@@ -97,3 +97,9 @@
|
||||
[submodule "Telegram/ThirdParty/kcoreaddons"]
|
||||
path = Telegram/ThirdParty/kcoreaddons
|
||||
url = https://github.com/KDE/kcoreaddons.git
|
||||
[submodule "Telegram/ThirdParty/cld3"]
|
||||
path = Telegram/ThirdParty/cld3
|
||||
url = https://github.com/google/cld3.git
|
||||
[submodule "Telegram/ThirdParty/sonnet"]
|
||||
path = Telegram/ThirdParty/sonnet
|
||||
url = https://github.com/KDE/sonnet.git
|
||||
|
||||
@@ -56,7 +56,7 @@ if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.3.1)
|
||||
else()
|
||||
set(qt_version 6.4.0)
|
||||
set(qt_version 6.4.1)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
@@ -287,6 +287,8 @@ PRIVATE
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
boxes/stickers_box.h
|
||||
boxes/translate_box.cpp
|
||||
boxes/translate_box.h
|
||||
boxes/url_auth_box.cpp
|
||||
boxes/url_auth_box.h
|
||||
boxes/username_box.cpp
|
||||
@@ -581,6 +583,8 @@ PRIVATE
|
||||
dialogs/ui/dialogs_layout.h
|
||||
dialogs/ui/dialogs_message_view.cpp
|
||||
dialogs/ui/dialogs_message_view.h
|
||||
dialogs/ui/dialogs_topics_view.cpp
|
||||
dialogs/ui/dialogs_topics_view.h
|
||||
dialogs/ui/dialogs_video_userpic.cpp
|
||||
dialogs/ui/dialogs_video_userpic.h
|
||||
editor/color_picker.cpp
|
||||
@@ -1125,6 +1129,7 @@ PRIVATE
|
||||
platform/platform_integration.h
|
||||
platform/platform_main_window.h
|
||||
platform/platform_notifications_manager.h
|
||||
platform/platform_specific.cpp
|
||||
platform/platform_specific.h
|
||||
platform/platform_tray.h
|
||||
platform/platform_window_title.h
|
||||
@@ -1680,8 +1685,8 @@ endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
include(GNUInstallDirs)
|
||||
configure_file("../lib/xdg/telegramdesktop.metainfo.xml.in" "${CMAKE_CURRENT_BINARY_DIR}/telegramdesktop.metainfo.xml" @ONLY)
|
||||
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/telegramdesktop.metainfo.xml")
|
||||
configure_file("../lib/xdg/org.telegram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" @ONLY)
|
||||
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml")
|
||||
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png")
|
||||
@@ -1690,6 +1695,6 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png")
|
||||
install(FILES "../lib/xdg/telegramdesktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.desktop")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/telegramdesktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.metainfo.xml")
|
||||
install(FILES "../lib/xdg/org.telegram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
|
||||
endif()
|
||||
|
||||
BIN
Telegram/Resources/icons/dialogs/inaccessible_userpic.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/dialogs/inaccessible_userpic@2x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Telegram/Resources/icons/dialogs/inaccessible_userpic@3x.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Telegram/Resources/icons/info/inaccessible_userpic.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Telegram/Resources/icons/info/inaccessible_userpic@2x.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Telegram/Resources/icons/info/inaccessible_userpic@3x.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
Telegram/Resources/icons/menu/translate.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
Telegram/Resources/icons/menu/translate@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/menu/translate@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@@ -204,6 +204,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filter_pin_limit2#one" = "Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **{count}** chat.";
|
||||
"lng_filter_pin_limit2#other" = "Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **{count}** chats.";
|
||||
|
||||
"lng_forum_pin_limit#one" = "Sorry, you can't pin more than **{count}** topic to the top.";
|
||||
"lng_forum_pin_limit#other" = "Sorry, you can't pin more than **{count}** topics to the top.";
|
||||
|
||||
"lng_fave_sticker_limit_title#one" = "The Limit of {count} Stickers Reached";
|
||||
"lng_fave_sticker_limit_title#other" = "The Limit of {count} Stickers Reached";
|
||||
"lng_fave_sticker_limit_more#one" = "An older sticker was replaced with this one.\nYou can {link} to {count} sticker.";
|
||||
@@ -1936,6 +1939,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_in_dlg_sticker" = "Sticker";
|
||||
"lng_in_dlg_sticker_emoji" = "{emoji} Sticker";
|
||||
"lng_in_dlg_poll" = "Poll";
|
||||
"lng_in_dlg_media_count#one" = "{count} media";
|
||||
"lng_in_dlg_media_count#other" = "{count} media";
|
||||
"lng_in_dlg_photo_count#one" = "{count} photo";
|
||||
"lng_in_dlg_photo_count#other" = "{count} photos";
|
||||
"lng_in_dlg_video_count#one" = "{count} video";
|
||||
"lng_in_dlg_video_count#other" = "{count} videos";
|
||||
"lng_in_dlg_file_count#one" = "{count} file";
|
||||
"lng_in_dlg_file_count#other" = "{count} files";
|
||||
"lng_in_dlg_audio_count#one" = "{count} audio";
|
||||
"lng_in_dlg_audio_count#other" = "{count} audio";
|
||||
|
||||
"lng_ban_user" = "Ban User";
|
||||
"lng_delete_all_from_user" = "Delete all from {user}";
|
||||
@@ -2245,6 +2258,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_delete_from_disk" = "Delete from disk";
|
||||
"lng_context_delete_all_files" = "Delete all files";
|
||||
"lng_context_save_custom_sound" = "Save for notifications";
|
||||
"lng_context_translate" = "Translate";
|
||||
"lng_context_translate_selected" = "Translate Selected Text";
|
||||
|
||||
"lng_context_animated_emoji" = "This message contains emoji from **{name} pack**.";
|
||||
"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**.";
|
||||
@@ -3372,6 +3387,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_language_not_ready_about" = "Unfortunately, this custom language pack ({lang_name}) doesn't contain data for Telegram Desktop. You can contribute to this language pack using the {link}.";
|
||||
"lng_language_not_ready_link" = "translations platform";
|
||||
|
||||
"lng_translate_box_original" = "Original";
|
||||
"lng_translate_box_error" = "Translate failed.";
|
||||
|
||||
"lng_translate_settings_subtitle" = "Translate Messages";
|
||||
"lng_translate_settings_show" = "Show Translate Button";
|
||||
"lng_translate_settings_choose" = "Do Not Translate";
|
||||
"lng_translate_settings_about" = "The 'Translate' button will appear when you open a context menu on a text message.";
|
||||
|
||||
"lng_launch_exe_warning" = "This file has a {extension} extension.\nAre you sure you want to run it?";
|
||||
"lng_launch_svg_warning" = "Opening this file can potentially expose your IP address to its sender. Continue?";
|
||||
"lng_launch_exe_sure" = "Run";
|
||||
@@ -3556,6 +3579,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forum_create_topic" = "Create topic";
|
||||
"lng_forum_discard_sure" = "Are you sure you want to discard this topic?";
|
||||
"lng_forum_view_as_messages" = "View as Messages";
|
||||
"lng_forum_no_messages" = "No messages";
|
||||
"lng_forum_messages#one" = "{count} message";
|
||||
"lng_forum_messages#other" = "{count} messages";
|
||||
"lng_forum_show_topics_list" = "Show Topics List";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
</qresource>
|
||||
<qresource prefix="/misc">
|
||||
<file alias="default_shortcuts-custom.json">../../default_shortcuts-custom.json</file>
|
||||
<file alias="telegramdesktop.desktop">../../../../lib/xdg/telegramdesktop.desktop</file>
|
||||
<file alias="org.telegram.desktop.desktop">../../../../lib/xdg/org.telegram.desktop.desktop</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -266,7 +266,7 @@ messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs;
|
||||
|
||||
messages.messages#8c718e87 messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.messagesSlice#3a54685e flags:# inexact:flags.1?true count:int next_rate:flags.0?int offset_id_offset:flags.2?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.channelMessages#64479808 flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.channelMessages#c776ba4e flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector<Message> topics:Vector<ForumTopic> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.messagesNotModified#74535f21 count:int = messages.Messages;
|
||||
|
||||
messages.chats#64ff9fd5 chats:Vector<Chat> = messages.Chats;
|
||||
@@ -401,7 +401,8 @@ updateRecentEmojiStatuses#30f443db = Update;
|
||||
updateRecentReactions#6f7863f4 = Update;
|
||||
updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update;
|
||||
updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update;
|
||||
updateChannelPinnedTopic#f694b0ae flags:# channel_id:long topic_id:flags.0?int = Update;
|
||||
updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long topic_id:int = Update;
|
||||
updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector<int> = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -1466,7 +1467,7 @@ stickerKeyword#fcfeb29c document_id:long keyword:Vector<string> = StickerKeyword
|
||||
username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username;
|
||||
|
||||
forumTopicDeleted#23f109b id:int = ForumTopic;
|
||||
forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
|
||||
forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true short:flags.5?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
|
||||
|
||||
messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector<ForumTopic> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> pts:int = messages.ForumTopics;
|
||||
|
||||
@@ -1880,6 +1881,7 @@ channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector<int> = m
|
||||
channels.editForumTopic#6c883e2d flags:# channel:InputChannel topic_id:int title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool = Updates;
|
||||
channels.updatePinnedForumTopic#6c2d9026 channel:InputChannel topic_id:int pinned:Bool = Updates;
|
||||
channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory;
|
||||
channels.reorderPinnedForumTopics#2950a18f flags:# force:flags.0?true channel:InputChannel order:Vector<int> = Updates;
|
||||
|
||||
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
|
||||
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
|
||||
@@ -1958,4 +1960,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 148
|
||||
// LAYER 149
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.3.0.0" />
|
||||
Version="4.3.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,3,0,0
|
||||
PRODUCTVERSION 4,3,0,0
|
||||
FILEVERSION 4,3,3,0
|
||||
PRODUCTVERSION 4,3,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "4.3.0.0"
|
||||
VALUE "FileVersion", "4.3.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.3.0.0"
|
||||
VALUE "ProductVersion", "4.3.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,3,0,0
|
||||
PRODUCTVERSION 4,3,0,0
|
||||
FILEVERSION 4,3,3,0
|
||||
PRODUCTVERSION 4,3,3,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", "4.3.0.0"
|
||||
VALUE "FileVersion", "4.3.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.3.0.0"
|
||||
VALUE "ProductVersion", "4.3.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -17,9 +17,7 @@ namespace Api {
|
||||
void SaveNewFilterPinned(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId) {
|
||||
const auto &order = session->data().pinnedChatsOrder(
|
||||
nullptr,
|
||||
filterId);
|
||||
const auto &order = session->data().pinnedChatsOrder(filterId);
|
||||
auto &filters = session->data().chatsFilters();
|
||||
const auto &filter = filters.applyUpdatedPinned(filterId, order);
|
||||
session->api().request(MTPmessages_UpdateDialogFilter(
|
||||
|
||||
@@ -152,18 +152,22 @@ void MessagesSearch::searchReceived(
|
||||
const auto total = int(data.vcount().v);
|
||||
return FoundMessages{ total, std::move(items), nextToken };
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
if (const auto channel = _history->peer->asChannel()) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
} else {
|
||||
LOG(("API Error: "
|
||||
"received messages.channelMessages when no channel "
|
||||
"was passed!"));
|
||||
}
|
||||
if (_requestId != 0) {
|
||||
// Don't apply cached data!
|
||||
owner.processUsers(data.vusers());
|
||||
owner.processChats(data.vchats());
|
||||
}
|
||||
if (const auto channel = _history->peer->asChannel()) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
if (_requestId != 0) {
|
||||
// Don't apply cached data!
|
||||
channel->processTopics(data.vtopics());
|
||||
}
|
||||
} else {
|
||||
LOG(("API Error: "
|
||||
"received messages.channelMessages when no channel "
|
||||
"was passed!"));
|
||||
}
|
||||
auto items = HistoryItemsFromTL(&owner, data.vmessages().v);
|
||||
const auto total = int(data.vcount().v);
|
||||
return FoundMessages{ total, std::move(items), nextToken };
|
||||
|
||||
@@ -141,7 +141,7 @@ void SendProgressManager::send(const Key &key, int progress) {
|
||||
MTP_int(key.topMsgId),
|
||||
action
|
||||
)).done([=](const MTPBool &result, mtpRequestId requestId) {
|
||||
done(result, requestId);
|
||||
done(requestId);
|
||||
}).send();
|
||||
_requests.emplace(key, requestId);
|
||||
|
||||
@@ -171,9 +171,7 @@ bool SendProgressManager::skipRequest(const Key &key) const {
|
||||
}
|
||||
}
|
||||
|
||||
void SendProgressManager::done(
|
||||
const MTPBool &result,
|
||||
mtpRequestId requestId) {
|
||||
void SendProgressManager::done(mtpRequestId requestId) {
|
||||
for (auto i = _requests.begin(), e = _requests.end(); i != e; ++i) {
|
||||
if (i->second == requestId) {
|
||||
_requests.erase(i);
|
||||
|
||||
@@ -90,7 +90,7 @@ private:
|
||||
bool updated(const Key &key, bool doing);
|
||||
|
||||
void send(const Key &key, int progress);
|
||||
void done(const MTPBool &result, mtpRequestId requestId);
|
||||
void done(mtpRequestId requestId);
|
||||
|
||||
[[nodiscard]] bool skipRequest(const Key &key) const;
|
||||
|
||||
|
||||
@@ -2297,14 +2297,37 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
const auto &d = update.c_updateChannelPinnedTopic();
|
||||
const auto peerId = peerFromChannel(d.vchannel_id());
|
||||
if (const auto peer = session().data().peerLoaded(peerId)) {
|
||||
const auto rootId = d.vtopic_id().value_or_empty();
|
||||
const auto rootId = d.vtopic_id().v;
|
||||
if (const auto topic = peer->forumTopicFor(rootId)) {
|
||||
session().data().setChatPinned(topic, 0, true);
|
||||
session().data().setChatPinned(topic, 0, d.is_pinned());
|
||||
} else if (const auto forum = peer->forum()) {
|
||||
if (rootId) {
|
||||
forum->requestTopic(rootId);
|
||||
} else {
|
||||
forum->unpinTopic();
|
||||
forum->requestTopic(rootId);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateChannelPinnedTopics: {
|
||||
const auto &d = update.c_updateChannelPinnedTopics();
|
||||
const auto peerId = peerFromChannel(d.vchannel_id());
|
||||
if (const auto peer = session().data().peerLoaded(peerId)) {
|
||||
if (const auto forum = peer->forum()) {
|
||||
const auto done = [&] {
|
||||
const auto list = d.vorder();
|
||||
if (!list) {
|
||||
return false;
|
||||
}
|
||||
const auto &order = list->v;
|
||||
const auto notLoaded = [&](const MTPint &topicId) {
|
||||
return !forum->topicFor(topicId.v);
|
||||
};
|
||||
if (!ranges::none_of(order, notLoaded)) {
|
||||
return false;
|
||||
}
|
||||
session().data().applyPinnedTopics(forum, order);
|
||||
return true;
|
||||
}();
|
||||
if (!done) {
|
||||
forum->reloadTopics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_sparse_ids.h"
|
||||
#include "data/data_search_controller.h"
|
||||
@@ -384,10 +385,8 @@ void ApiWrap::checkChatInvite(
|
||||
}
|
||||
|
||||
void ApiWrap::savePinnedOrder(Data::Folder *folder) {
|
||||
const auto &order = _session->data().pinnedChatsOrder(
|
||||
folder,
|
||||
FilterId());
|
||||
const auto input = [](const Dialogs::Key &key) {
|
||||
const auto &order = _session->data().pinnedChatsOrder(folder);
|
||||
const auto input = [](Dialogs::Key key) {
|
||||
if (const auto history = key.history()) {
|
||||
return MTP_inputDialogPeer(history->peer->input);
|
||||
} else if (const auto folder = key.folder()) {
|
||||
@@ -408,6 +407,29 @@ void ApiWrap::savePinnedOrder(Data::Folder *folder) {
|
||||
)).send();
|
||||
}
|
||||
|
||||
void ApiWrap::savePinnedOrder(not_null<Data::Forum*> forum) {
|
||||
const auto &order = _session->data().pinnedChatsOrder(forum);
|
||||
const auto input = [](Dialogs::Key key) {
|
||||
if (const auto topic = key.topic()) {
|
||||
return MTP_int(topic->rootId().bare);
|
||||
}
|
||||
Unexpected("Key type in pinnedDialogsOrder().");
|
||||
};
|
||||
auto topics = QVector<MTPint>();
|
||||
topics.reserve(order.size());
|
||||
ranges::transform(
|
||||
order,
|
||||
ranges::back_inserter(topics),
|
||||
input);
|
||||
request(MTPchannels_ReorderPinnedForumTopics(
|
||||
MTP_flags(MTPchannels_ReorderPinnedForumTopics::Flag::f_force),
|
||||
forum->channel()->inputChannel,
|
||||
MTP_vector(topics)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::toggleHistoryArchived(
|
||||
not_null<History*> history,
|
||||
bool archived,
|
||||
@@ -2870,18 +2892,18 @@ void ApiWrap::requestMessageAfterDate(
|
||||
};
|
||||
const auto list = result.match([&](
|
||||
const MTPDmessages_messages &data) {
|
||||
return handleMessages(result.c_messages_messages());
|
||||
return handleMessages(data);
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
return handleMessages(result.c_messages_messagesSlice());
|
||||
return handleMessages(data);
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
const auto &messages = result.c_messages_channelMessages();
|
||||
if (peer && peer->isChannel()) {
|
||||
peer->asChannel()->ptsReceived(messages.vpts().v);
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
channel->processTopics(data.vtopics());
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (ApiWrap::jumpToDate)"));
|
||||
}
|
||||
return handleMessages(messages);
|
||||
return handleMessages(data);
|
||||
}, [&](const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(ApiWrap::jumpToDate)"));
|
||||
@@ -3352,7 +3374,10 @@ void ApiWrap::sendFiles(
|
||||
std::shared_ptr<SendingAlbum> album,
|
||||
const SendAction &action) {
|
||||
const auto haveCaption = !caption.text.isEmpty();
|
||||
if (haveCaption && !list.canAddCaption(album != nullptr)) {
|
||||
if (haveCaption
|
||||
&& !list.canAddCaption(
|
||||
album != nullptr,
|
||||
type == SendMediaType::Photo)) {
|
||||
auto message = MessageToSend(action);
|
||||
message.textWithTags = base::take(caption);
|
||||
message.action.clearDraft = false;
|
||||
|
||||
@@ -32,6 +32,7 @@ class WallPaper;
|
||||
struct ResolvedForwardDraft;
|
||||
enum class DefaultNotify;
|
||||
enum class StickersType : uchar;
|
||||
class Forum;
|
||||
class ForumTopic;
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
@@ -149,6 +150,7 @@ public:
|
||||
void saveCurrentDraftToCloud();
|
||||
|
||||
void savePinnedOrder(Data::Folder *folder);
|
||||
void savePinnedOrder(not_null<Data::Forum*> forum);
|
||||
void toggleHistoryArchived(
|
||||
not_null<History*> history,
|
||||
bool archived,
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/about_sponsored_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
@@ -7,10 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -127,7 +127,7 @@ ChooseFilterValidator::LimitData ChooseFilterValidator::limitReached(
|
||||
|
||||
const auto list = _history->owner().chatsFilters().list();
|
||||
const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
|
||||
const auto limit = _history->owner().pinnedChatsLimit(nullptr, filterId);
|
||||
const auto limit = _history->owner().pinnedChatsLimit(filterId);
|
||||
return {
|
||||
.reached = (i != end(list))
|
||||
&& !ranges::contains(i->always(), _history)
|
||||
|
||||
@@ -232,7 +232,7 @@ void DeleteMessagesBox::prepare() {
|
||||
_revoke.create(
|
||||
this,
|
||||
revoke->checkbox,
|
||||
false,
|
||||
true,
|
||||
st::defaultBoxCheckbox);
|
||||
appendDetails(std::move(revoke->description));
|
||||
} else if (peer->isChannel()) {
|
||||
@@ -249,6 +249,9 @@ void DeleteMessagesBox::prepare() {
|
||||
tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
|
||||
});
|
||||
} else if (!peer->isSelf()) {
|
||||
if (const auto user = peer->asUser(); user && user->isBot()) {
|
||||
_revokeForBot = true;
|
||||
}
|
||||
appendDetails({
|
||||
tr::lng_delete_for_me_hint(tr::now, lt_count, count)
|
||||
});
|
||||
@@ -466,7 +469,7 @@ void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::deleteAndClear() {
|
||||
const auto revoke = _revoke ? _revoke->checked() : false;
|
||||
const auto revoke = _revoke ? _revoke->checked() : _revokeForBot;
|
||||
const auto session = _session;
|
||||
const auto invokeCallbackAndClose = [&] {
|
||||
// deleteMessages can initiate closing of the current section,
|
||||
|
||||
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "ui/layers/box_content.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
@@ -69,6 +69,8 @@ private:
|
||||
bool _moderateBan = false;
|
||||
bool _moderateDeleteAll = false;
|
||||
|
||||
bool _revokeForBot = false;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _text = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _revoke = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _banUser = { nullptr };
|
||||
|
||||
@@ -23,18 +23,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/painter.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "boxes/translate_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_instance.h"
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_passport.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
@@ -1095,7 +1098,66 @@ void LanguageBox::prepare() {
|
||||
|
||||
setTitle(tr::lng_languages());
|
||||
|
||||
const auto select = createMultiSelect();
|
||||
const auto topContainer = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
Settings::AddSubsectionTitle(
|
||||
topContainer,
|
||||
tr::lng_translate_settings_subtitle());
|
||||
|
||||
const auto translateEnabled = Settings::AddButton(
|
||||
topContainer,
|
||||
tr::lng_translate_settings_show(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(rpl::single(Core::App().settings().translateButtonEnabled()));
|
||||
|
||||
translateEnabled->toggledValue(
|
||||
) | rpl::filter([](bool checked) {
|
||||
return (checked != Core::App().settings().translateButtonEnabled());
|
||||
}) | rpl::start_with_next([=](bool checked) {
|
||||
Core::App().settings().setTranslateButtonEnabled(checked);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}, translateEnabled->lifetime());
|
||||
|
||||
const auto label = lifetime().make_state<rpl::event_stream<QLocale>>();
|
||||
const auto translateSkip = Settings::AddButtonWithLabel(
|
||||
topContainer,
|
||||
tr::lng_translate_settings_choose(),
|
||||
label->events() | rpl::map(Ui::LanguageName),
|
||||
st::settingsButtonNoIcon);
|
||||
|
||||
{
|
||||
const auto settingsLang =
|
||||
Core::App().settings().skipTranslationForLanguage();
|
||||
const auto locale = (settingsLang == QLocale::English)
|
||||
? QLocale(Lang::LanguageIdOrDefault(Lang::Id()))
|
||||
: (settingsLang == QLocale::C)
|
||||
? QLocale(QLocale::English)
|
||||
: QLocale(settingsLang);
|
||||
label->fire_copy(locale);
|
||||
}
|
||||
translateSkip->setClickedCallback([=] {
|
||||
Ui::BoxShow(this).showBox(
|
||||
Box(Ui::ChooseLanguageBox, [=](QLocale locale) {
|
||||
label->fire_copy(locale);
|
||||
const auto result = (locale.language() == QLocale::English)
|
||||
? QLocale::c()
|
||||
: locale;
|
||||
Core::App().settings().setSkipTranslationForLanguage(
|
||||
result.language());
|
||||
Core::App().saveSettingsDelayed();
|
||||
}),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
Settings::AddSkip(topContainer);
|
||||
Settings::AddDividerText(
|
||||
topContainer,
|
||||
tr::lng_translate_settings_about());
|
||||
|
||||
const auto select = topContainer->add(
|
||||
object_ptr<Ui::MultiSelect>(
|
||||
topContainer,
|
||||
st::defaultMultiSelect,
|
||||
tr::lng_participant_filter()));
|
||||
topContainer->resizeToWidth(st::boxWidth);
|
||||
|
||||
using namespace rpl::mappers;
|
||||
|
||||
@@ -1103,13 +1165,13 @@ void LanguageBox::prepare() {
|
||||
const auto inner = setInnerWidget(
|
||||
object_ptr<Content>(this, recent, official),
|
||||
st::boxScroll,
|
||||
select->height());
|
||||
topContainer->height());
|
||||
inner->resizeToWidth(st::boxWidth);
|
||||
|
||||
const auto max = lifetime().make_state<int>(0);
|
||||
rpl::combine(
|
||||
inner->heightValue(),
|
||||
select->heightValue(),
|
||||
topContainer->heightValue(),
|
||||
_1 + _2
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
accumulate_max(*max, height);
|
||||
@@ -1180,16 +1242,6 @@ void LanguageBox::setInnerFocus() {
|
||||
_setInnerFocus();
|
||||
}
|
||||
|
||||
not_null<Ui::MultiSelect*> LanguageBox::createMultiSelect() {
|
||||
const auto result = Ui::CreateChild<Ui::MultiSelect>(
|
||||
this,
|
||||
st::defaultMultiSelect,
|
||||
tr::lng_participant_filter());
|
||||
result->resizeToWidth(st::boxWidth);
|
||||
result->moveToLeft(0, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
base::binary_guard LanguageBox::Show() {
|
||||
auto result = base::binary_guard();
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ protected:
|
||||
private:
|
||||
using Languages = Lang::CloudManager::Languages;
|
||||
|
||||
not_null<Ui::MultiSelect*> createMultiSelect();
|
||||
int rowsInPage() const;
|
||||
|
||||
Fn<void()> _setInnerFocus;
|
||||
|
||||
@@ -847,10 +847,16 @@ void PeerListRow::lazyInitialize(const style::PeerListItem &st) {
|
||||
void PeerListRow::createCheckbox(
|
||||
const style::RoundImageCheckbox &st,
|
||||
Fn<void()> updateCallback) {
|
||||
const auto generateRadius = [=] {
|
||||
return (!special() && peer()->isForum())
|
||||
? ImageRoundRadius::Large
|
||||
: ImageRoundRadius::Ellipse;
|
||||
};
|
||||
_checkbox = std::make_unique<Ui::RoundImageCheckbox>(
|
||||
st,
|
||||
std::move(updateCallback),
|
||||
generatePaintUserpicCallback());
|
||||
generatePaintUserpicCallback(),
|
||||
generateRadius);
|
||||
}
|
||||
|
||||
void PeerListRow::setCheckedInternal(bool checked, anim::type animated) {
|
||||
|
||||
@@ -533,6 +533,7 @@ auto ChooseRecipientBoxController::createRow(
|
||||
const auto skip = _filter
|
||||
? !_filter(history)
|
||||
: ((peer->isBroadcast() && !peer->canWrite())
|
||||
|| (peer->isUser() && !peer->canWrite())
|
||||
|| peer->isRepliesChat());
|
||||
return skip ? nullptr : std::make_unique<Row>(history);
|
||||
}
|
||||
|
||||
@@ -1189,16 +1189,7 @@ void Controller::fillManageSection() {
|
||||
Ui::LayerOption::KeepOther);
|
||||
},
|
||||
{ &st::infoRoundedIconInviteLinks, Settings::kIconLightOrange });
|
||||
|
||||
if (_typeDataSavedValue) {
|
||||
_privacyTypeUpdates.events_starting_with_copy(
|
||||
_typeDataSavedValue->privacy
|
||||
) | rpl::start_with_next([=](Privacy flag) {
|
||||
wrap->toggle(
|
||||
flag != Privacy::HasUsername,
|
||||
anim::type::instant);
|
||||
}, wrap->lifetime());
|
||||
}
|
||||
wrap->toggle(true, anim::type::instant);
|
||||
}
|
||||
if (canViewAdmins) {
|
||||
AddButtonWithCount(
|
||||
|
||||
@@ -397,7 +397,9 @@ QString Controller::getUsernameInput() const {
|
||||
}
|
||||
|
||||
std::vector<QString> Controller::usernamesOrder() const {
|
||||
return _controls.usernamesList->order();
|
||||
return _controls.usernamesList
|
||||
? _controls.usernamesList->order()
|
||||
: std::vector<QString>();
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
|
||||
@@ -452,12 +454,17 @@ object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
|
||||
container,
|
||||
tr::lng_create_channel_link_about());
|
||||
|
||||
const auto focusCallback = [=] {
|
||||
_scrollToRequests.fire(container->y());
|
||||
_controls.usernameInput->setFocusFast();
|
||||
};
|
||||
_controls.usernamesList = container->add(
|
||||
object_ptr<UsernamesList>(container, channel, _show, focusCallback));
|
||||
if (channel) {
|
||||
const auto focusCallback = [=] {
|
||||
_scrollToRequests.fire(container->y());
|
||||
_controls.usernameInput->setFocusFast();
|
||||
};
|
||||
_controls.usernamesList = container->add(object_ptr<UsernamesList>(
|
||||
container,
|
||||
channel,
|
||||
_show,
|
||||
focusCallback));
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
_controls.usernameInput,
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
@@ -800,6 +801,25 @@ void PinsLimitBox(
|
||||
PinsCount(session->data().chatsList()));
|
||||
}
|
||||
|
||||
void ForumPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::Forum*> forum) {
|
||||
const auto current = forum->owner().pinnedChatsLimit(forum) * 1.;
|
||||
|
||||
auto text = tr::lng_forum_pin_limit(
|
||||
lt_count,
|
||||
rpl::single(current),
|
||||
Ui::Text::RichLangValue);
|
||||
SimpleLimitBox(
|
||||
box,
|
||||
&forum->session(),
|
||||
false,
|
||||
tr::lng_filter_pin_limit_title(),
|
||||
std::move(text),
|
||||
QString(),
|
||||
{ current, current, current * 2, &st::premiumIconPins });
|
||||
}
|
||||
|
||||
void CaptionLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
||||
namespace Data {
|
||||
class Forum;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -41,6 +45,9 @@ void FolderPinsLimitBox(
|
||||
void PinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session);
|
||||
void ForumPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::Forum*> forum);
|
||||
void CaptionLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
|
||||
@@ -102,7 +102,9 @@ void FileDialogCallback(
|
||||
rpl::producer<QString> FieldPlaceholder(
|
||||
const Ui::PreparedList &list,
|
||||
SendFilesWay way) {
|
||||
return list.canAddCaption(way.groupFiles() && way.sendImagesAsPhotos())
|
||||
return list.canAddCaption(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos())
|
||||
? tr::lng_photo_caption()
|
||||
: tr::lng_photos_comment();
|
||||
}
|
||||
@@ -390,6 +392,11 @@ void SendFilesBox::refreshAllAfterChanges(int fromItem) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto sendWay = _sendWay.current();
|
||||
sendWay.setHasCompressedStickers(_list.hasSticker());
|
||||
_sendWay = sendWay;
|
||||
}
|
||||
generatePreviewFrom(fromBlock);
|
||||
_inner->resizeToWidth(st::boxWideWidth);
|
||||
refreshControls();
|
||||
@@ -427,6 +434,7 @@ void SendFilesBox::openDialogToAddFileToAlbum() {
|
||||
void SendFilesBox::initSendWay() {
|
||||
_sendWay = [&] {
|
||||
auto result = Core::App().settings().sendFilesWay();
|
||||
result.setHasCompressedStickers(_list.hasSticker());
|
||||
if (_sendLimit == SendLimit::One) {
|
||||
result.setGroupFiles(true);
|
||||
return result;
|
||||
@@ -455,7 +463,9 @@ void SendFilesBox::updateCaptionPlaceholder() {
|
||||
return;
|
||||
}
|
||||
const auto way = _sendWay.current();
|
||||
if (!_list.canAddCaption(way.groupFiles() && way.sendImagesAsPhotos())
|
||||
if (!_list.canAddCaption(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos())
|
||||
&& _sendLimit == SendLimit::One) {
|
||||
_caption->hide();
|
||||
if (_emojiToggle) {
|
||||
@@ -668,7 +678,7 @@ void SendFilesBox::updateSendWayControlsVisibility() {
|
||||
|
||||
_hintLabel->setVisible(
|
||||
_controller->session().settings().photoEditorHintShown()
|
||||
? _list.hasSendImagesAsPhotosOption(false)
|
||||
? _list.canHaveEditorHintLabel()
|
||||
: false);
|
||||
}
|
||||
|
||||
@@ -1019,7 +1029,8 @@ bool SendFilesBox::validateLength(const QString &text) const {
|
||||
const auto way = _sendWay.current();
|
||||
if (remove <= 0
|
||||
|| !_list.canAddCaption(
|
||||
way.groupFiles() && way.sendImagesAsPhotos())) {
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos())) {
|
||||
return true;
|
||||
}
|
||||
_controller->show(Box(CaptionLimitReachedBox, session, remove));
|
||||
|
||||
@@ -795,7 +795,9 @@ ShareBox::Inner::Chat *ShareBox::Inner::getChatAtIndex(int index) {
|
||||
}
|
||||
const auto row = [=] {
|
||||
if (_filter.isEmpty()) {
|
||||
return _chatsIndexed->rowAtY(index, 1);
|
||||
return (index < _chatsIndexed->size())
|
||||
? (_chatsIndexed->begin() + index)->get()
|
||||
: nullptr;
|
||||
}
|
||||
return (index < _filtered.size())
|
||||
? _filtered[index].get()
|
||||
@@ -865,9 +867,11 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
|
||||
|
||||
if (_filter.isEmpty()) {
|
||||
if (!_chatsIndexed->empty()) {
|
||||
auto i = _chatsIndexed->cfind(yFrom, _rowHeight);
|
||||
const auto index = yFrom / _rowHeight;
|
||||
auto i = _chatsIndexed->begin()
|
||||
+ std::min(index, _chatsIndexed->size());;
|
||||
for (auto end = _chatsIndexed->cend(); i != end; ++i) {
|
||||
if (((*i)->pos() * _rowHeight) >= yTo) {
|
||||
if (((*i)->index() * _rowHeight) >= yTo) {
|
||||
break;
|
||||
}
|
||||
(*i)->entry()->loadUserpic();
|
||||
@@ -973,7 +977,8 @@ void ShareBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
auto indexTo = rowTo * _columnCount;
|
||||
if (_filter.isEmpty()) {
|
||||
if (!_chatsIndexed->empty()) {
|
||||
auto i = _chatsIndexed->cfind(indexFrom, 1);
|
||||
auto i = _chatsIndexed->begin()
|
||||
+ std::min(indexFrom, _chatsIndexed->size());
|
||||
for (auto end = _chatsIndexed->cend(); i != end; ++i) {
|
||||
if (indexFrom >= indexTo) {
|
||||
break;
|
||||
|
||||
348
Telegram/SourceFiles/boxes/translate_box.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
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/translate_box.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "lang/lang_instance.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "spellcheck/platform/platform_language.h"
|
||||
#include "ui/effects/loading_element.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h" // settingsSubsectionTitlePadding.
|
||||
|
||||
#include <QLocale>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] std::vector<QLocale::Language> Languages() {
|
||||
return std::vector<QLocale::Language>{
|
||||
QLocale::English,
|
||||
QLocale::Albanian,
|
||||
QLocale::Armenian,
|
||||
QLocale::Bulgarian,
|
||||
QLocale::Catalan,
|
||||
QLocale::Chinese,
|
||||
QLocale::Croatian,
|
||||
QLocale::Czech,
|
||||
QLocale::Danish,
|
||||
QLocale::Dutch,
|
||||
QLocale::Estonian,
|
||||
QLocale::French,
|
||||
QLocale::German,
|
||||
QLocale::Greek,
|
||||
QLocale::Hebrew,
|
||||
QLocale::Hindi,
|
||||
QLocale::Hungarian,
|
||||
QLocale::Indonesian,
|
||||
QLocale::Italian,
|
||||
QLocale::Japanese,
|
||||
QLocale::Korean,
|
||||
QLocale::Latvian,
|
||||
QLocale::Lithuanian,
|
||||
QLocale::Persian,
|
||||
QLocale::Polish,
|
||||
QLocale::Portuguese,
|
||||
QLocale::Romanian,
|
||||
QLocale::Russian,
|
||||
QLocale::Serbian,
|
||||
QLocale::Slovak,
|
||||
QLocale::Slovenian,
|
||||
QLocale::Spanish,
|
||||
QLocale::Swedish,
|
||||
QLocale::Tajik,
|
||||
QLocale::Tamil,
|
||||
QLocale::Turkish,
|
||||
QLocale::Ukrainian,
|
||||
QLocale::Vietnamese,
|
||||
QLocale::Welsh,
|
||||
};
|
||||
}
|
||||
|
||||
class ShowButton : public RpWidget {
|
||||
public:
|
||||
ShowButton(not_null<Ui::RpWidget*> parent);
|
||||
|
||||
[[nodiscard]] rpl::producer<Qt::MouseButton> clicks() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
LinkButton _button;
|
||||
|
||||
};
|
||||
|
||||
ShowButton::ShowButton(not_null<Ui::RpWidget*> parent)
|
||||
: RpWidget(parent)
|
||||
, _button(this, tr::lng_usernames_activate_confirm(tr::now)) {
|
||||
_button.sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
resize(
|
||||
s.width() + st::emojiSuggestionsFadeRight.width(),
|
||||
s.height());
|
||||
_button.moveToRight(0, 0);
|
||||
}, lifetime());
|
||||
_button.show();
|
||||
}
|
||||
|
||||
void ShowButton::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
const auto clip = e->rect();
|
||||
|
||||
const auto &icon = st::emojiSuggestionsFadeRight;
|
||||
const auto fade = QRect(0, 0, icon.width(), height());
|
||||
if (fade.intersects(clip)) {
|
||||
icon.fill(p, fade);
|
||||
}
|
||||
const auto fill = clip.intersected(
|
||||
{ icon.width(), 0, width() - icon.width(), height() });
|
||||
if (!fill.isEmpty()) {
|
||||
p.fillRect(fill, st::boxBg);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<Qt::MouseButton> ShowButton::clicks() const {
|
||||
return _button.clicks();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString LanguageName(const QLocale &locale) {
|
||||
if (locale.language() == QLocale::English
|
||||
&& (locale.country() == QLocale::UnitedStates
|
||||
|| locale.country() == QLocale::AnyCountry)) {
|
||||
return u"English"_q;
|
||||
} else {
|
||||
const auto name = locale.nativeLanguageName();
|
||||
return name.left(1).toUpper() + name.mid(1);
|
||||
}
|
||||
}
|
||||
|
||||
void TranslateBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
TextWithEntities text) {
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
const auto container = box->verticalLayout();
|
||||
const auto settingsLang =
|
||||
Core::App().settings().skipTranslationForLanguage();
|
||||
const auto defaultId = (settingsLang == QLocale::English)
|
||||
? Lang::LanguageIdOrDefault(Lang::Id())
|
||||
: (settingsLang == QLocale::C)
|
||||
? u"en"_q
|
||||
: QLocale(settingsLang).name().mid(0, 2);
|
||||
|
||||
const auto api = box->lifetime().make_state<MTP::Sender>(
|
||||
&peer->session().mtp());
|
||||
struct State {
|
||||
rpl::event_stream<QLocale> locale;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
if (!IsServerMsgId(msgId)) {
|
||||
msgId = 0;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_translateText::Flag;
|
||||
const auto flags = msgId
|
||||
? (Flag::f_peer | Flag::f_msg_id)
|
||||
: !text.text.isEmpty()
|
||||
? Flag::f_text
|
||||
: Flag(0);
|
||||
|
||||
const auto &stLabel = st::aboutLabel;
|
||||
const auto lineHeight = stLabel.style.lineHeight;
|
||||
|
||||
Settings::AddSkip(container);
|
||||
// Settings::AddSubsectionTitle(
|
||||
// container,
|
||||
// tr::lng_translate_box_original());
|
||||
|
||||
const auto original = box->addRow(object_ptr<SlideWrap<FlatLabel>>(
|
||||
box,
|
||||
object_ptr<FlatLabel>(box, stLabel)));
|
||||
{
|
||||
original->entity()->setMarkedText(text);
|
||||
original->setMinimalHeight(lineHeight);
|
||||
original->hide(anim::type::instant);
|
||||
|
||||
const auto show = Ui::CreateChild<FadeWrap<ShowButton>>(
|
||||
container.get(),
|
||||
object_ptr<ShowButton>(container));
|
||||
show->hide(anim::type::instant);
|
||||
rpl::combine(
|
||||
container->widthValue(),
|
||||
original->geometryValue()
|
||||
) | rpl::start_with_next([=](int width, const QRect &rect) {
|
||||
show->moveToLeft(
|
||||
width - show->width() - st::boxRowPadding.right(),
|
||||
rect.y() + std::abs(lineHeight - show->height()) / 2);
|
||||
}, show->lifetime());
|
||||
original->entity()->heightValue(
|
||||
) | rpl::filter([](int height) {
|
||||
return height > 0;
|
||||
}) | rpl::take(1) | rpl::start_with_next([=](int height) {
|
||||
if (height > lineHeight) {
|
||||
show->show(anim::type::instant);
|
||||
}
|
||||
}, show->lifetime());
|
||||
show->toggleOn(show->entity()->clicks() | rpl::map_to(false));
|
||||
original->toggleOn(show->entity()->clicks() | rpl::map_to(true));
|
||||
}
|
||||
Settings::AddSkip(container);
|
||||
Settings::AddSkip(container);
|
||||
Settings::AddDivider(container);
|
||||
Settings::AddSkip(container);
|
||||
|
||||
{
|
||||
const auto padding = st::settingsSubsectionTitlePadding;
|
||||
const auto subtitle = Settings::AddSubsectionTitle(
|
||||
container,
|
||||
state->locale.events() | rpl::map(LanguageName));
|
||||
|
||||
// Workaround.
|
||||
state->locale.events(
|
||||
) | rpl::start_with_next([=] {
|
||||
subtitle->resizeToWidth(container->width()
|
||||
- padding.left()
|
||||
- padding.right());
|
||||
}, subtitle->lifetime());
|
||||
}
|
||||
|
||||
const auto translated = box->addRow(object_ptr<SlideWrap<FlatLabel>>(
|
||||
box,
|
||||
object_ptr<FlatLabel>(box, stLabel)));
|
||||
translated->entity()->setSelectable(true);
|
||||
translated->hide(anim::type::instant);
|
||||
|
||||
constexpr auto kMaxLines = 3;
|
||||
container->resizeToWidth(box->width());
|
||||
const auto loading = box->addRow(object_ptr<SlideWrap<RpWidget>>(
|
||||
box,
|
||||
CreateLoadingTextWidget(
|
||||
box,
|
||||
st::aboutLabel,
|
||||
std::min(original->entity()->height() / lineHeight, kMaxLines),
|
||||
state->locale.events() | rpl::map([=](const QLocale &locale) {
|
||||
return locale.textDirection() == Qt::RightToLeft;
|
||||
}))));
|
||||
loading->show(anim::type::instant);
|
||||
|
||||
const auto showText = [=](const QString &text) {
|
||||
translated->entity()->setText(text);
|
||||
translated->show(anim::type::instant);
|
||||
loading->hide(anim::type::instant);
|
||||
};
|
||||
|
||||
const auto send = [=](const QString &toLang) {
|
||||
api->request(MTPmessages_TranslateText(
|
||||
MTP_flags(flags),
|
||||
msgId ? peer->input : MTP_inputPeerEmpty(),
|
||||
MTP_int(msgId),
|
||||
MTP_string(text.text),
|
||||
MTPstring(),
|
||||
MTP_string(toLang)
|
||||
)).done([=](const MTPmessages_TranslatedText &result) {
|
||||
const auto text = result.match([](
|
||||
const MTPDmessages_translateNoResult &data) {
|
||||
return tr::lng_translate_box_error(tr::now);
|
||||
}, [](const MTPDmessages_translateResultText &data) {
|
||||
return qs(data.vtext());
|
||||
});
|
||||
showText(text);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
showText(tr::lng_translate_box_error(tr::now));
|
||||
}).send();
|
||||
};
|
||||
send(defaultId);
|
||||
state->locale.fire(QLocale(defaultId));
|
||||
|
||||
box->addLeftButton(tr::lng_settings_language(), [=] {
|
||||
if (loading->toggled()) {
|
||||
return;
|
||||
}
|
||||
Ui::BoxShow(box).showBox(Box(ChooseLanguageBox, [=](QLocale locale) {
|
||||
state->locale.fire_copy(locale);
|
||||
loading->show(anim::type::instant);
|
||||
translated->hide(anim::type::instant);
|
||||
send(locale.name().mid(0, 2));
|
||||
}));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void ChooseLanguageBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Fn<void(QLocale)> callback) {
|
||||
box->setTitle(tr::lng_languages());
|
||||
for (const auto &lang : Languages()) {
|
||||
const auto locale = QLocale(lang);
|
||||
const auto button = Settings::AddButton(
|
||||
box->verticalLayout(),
|
||||
rpl::single(LanguageName(locale)),
|
||||
st::defaultSettingsButton);
|
||||
button->setClickedCallback([=] {
|
||||
callback(locale);
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
bool SkipTranslate(TextWithEntities textWithEntities) {
|
||||
const auto &text = textWithEntities.text;
|
||||
if (text.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (!Core::App().settings().translateButtonEnabled()) {
|
||||
return true;
|
||||
}
|
||||
constexpr auto kFirstChunk = size_t(100);
|
||||
auto hasLetters = (text.size() >= kFirstChunk);
|
||||
for (auto i = 0; i < kFirstChunk; i++) {
|
||||
if (i >= text.size()) {
|
||||
break;
|
||||
}
|
||||
if (text.at(i).isLetter()) {
|
||||
hasLetters = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasLetters) {
|
||||
return true;
|
||||
}
|
||||
const auto result = Platform::Language::Recognize(text);
|
||||
if (result.unknown) {
|
||||
return false;
|
||||
}
|
||||
const auto settingsLang =
|
||||
Core::App().settings().skipTranslationForLanguage();
|
||||
const auto skip = (settingsLang == QLocale::English)
|
||||
? QLocale(Lang::LanguageIdOrDefault(Lang::Id())).language()
|
||||
: (settingsLang == QLocale::C)
|
||||
? QLocale::English
|
||||
: settingsLang;
|
||||
return (result.locale.language() == skip);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
30
Telegram/SourceFiles/boxes/translate_box.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
|
||||
[[nodiscard]] QString LanguageName(const QLocale &locale);
|
||||
|
||||
void TranslateBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
TextWithEntities text);
|
||||
|
||||
[[nodiscard]] bool SkipTranslate(TextWithEntities textWithEntities);
|
||||
|
||||
void ChooseLanguageBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Fn<void(QLocale)> callback);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "base/unixtime.h"
|
||||
|
||||
@@ -587,6 +587,12 @@ void Application::saveSettings() {
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
bool Application::canSaveFileWithoutAskingForPath() const {
|
||||
return !Core::App().settings().askDownloadPath()
|
||||
&& (!KSandbox::isInside()
|
||||
|| !Core::App().settings().downloadPath().isEmpty());
|
||||
}
|
||||
|
||||
MTP::Config &Application::fallbackProductionConfig() const {
|
||||
if (!_fallbackProductionConfig) {
|
||||
_fallbackProductionConfig = std::make_unique<MTP::Config>(
|
||||
@@ -823,10 +829,6 @@ rpl::producer<bool> Application::appDeactivatedValue() const {
|
||||
});
|
||||
}
|
||||
|
||||
void Application::call_handleObservables() {
|
||||
base::HandleObservables();
|
||||
}
|
||||
|
||||
void Application::switchDebugMode() {
|
||||
if (Logs::DebugEnabled()) {
|
||||
Logs::SetDebugEnabled(false);
|
||||
@@ -1444,7 +1446,9 @@ void Application::startShortcuts() {
|
||||
void Application::RegisterUrlScheme() {
|
||||
base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{
|
||||
.executable = cExeDir() + cExeName(),
|
||||
.arguments = qsl("-workdir \"%1\"").arg(cWorkingDir()),
|
||||
.arguments = Sandbox::Instance().customWorkingDir()
|
||||
? qsl("-workdir \"%1\"").arg(cWorkingDir())
|
||||
: QString(),
|
||||
.protocol = qsl("tg"),
|
||||
.protocolName = qsl("Telegram Link"),
|
||||
.shortAppName = qsl("tdesktop"),
|
||||
|
||||
@@ -178,6 +178,7 @@ public:
|
||||
[[nodiscard]] Settings &settings();
|
||||
void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay);
|
||||
void saveSettings();
|
||||
[[nodiscard]] bool canSaveFileWithoutAskingForPath() const;
|
||||
|
||||
// Fallback config and proxy.
|
||||
[[nodiscard]] MTP::Config &fallbackProductionConfig() const;
|
||||
@@ -296,8 +297,6 @@ public:
|
||||
|
||||
void preventOrInvoke(Fn<void()> &&callback);
|
||||
|
||||
void call_handleObservables();
|
||||
|
||||
// Global runtime variables.
|
||||
void setScreenIsLocked(bool locked);
|
||||
bool screenIsLocked() const;
|
||||
|
||||
@@ -267,7 +267,8 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_chatQuickAction)
|
||||
<< qint32(_hardwareAcceleratedVideo ? 1 : 0)
|
||||
<< qint32(_suggestAnimatedEmoji ? 1 : 0)
|
||||
<< qint32(_cornerReaction.current() ? 1 : 0);
|
||||
<< qint32(_cornerReaction.current() ? 1 : 0)
|
||||
<< qint32(_skipTranslationForLanguage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -361,6 +362,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 chatQuickAction = static_cast<qint32>(_chatQuickAction);
|
||||
qint32 suggestAnimatedEmoji = _suggestAnimatedEmoji ? 1 : 0;
|
||||
qint32 cornerReaction = _cornerReaction.current() ? 1 : 0;
|
||||
qint32 skipTranslationForLanguage = _skipTranslationForLanguage;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -558,6 +560,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> cornerReaction;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> skipTranslationForLanguage;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -727,6 +732,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
}
|
||||
_suggestAnimatedEmoji = (suggestAnimatedEmoji == 1);
|
||||
_cornerReaction = (cornerReaction == 1);
|
||||
_skipTranslationForLanguage = skipTranslationForLanguage;
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
@@ -1036,4 +1042,19 @@ float64 Settings::DefaultDialogsWidthRatio() {
|
||||
: kDefaultDialogsWidthRatio;
|
||||
}
|
||||
|
||||
void Settings::setTranslateButtonEnabled(bool value) {
|
||||
_skipTranslationForLanguage = std::abs(_skipTranslationForLanguage)
|
||||
* (value ? 1 : -1);
|
||||
}
|
||||
bool Settings::translateButtonEnabled() const {
|
||||
return _skipTranslationForLanguage > 0;
|
||||
}
|
||||
void Settings::setSkipTranslationForLanguage(QLocale::Language language) {
|
||||
const auto enabled = translateButtonEnabled();
|
||||
_skipTranslationForLanguage = language * (enabled ? 1 : -1);
|
||||
}
|
||||
QLocale::Language Settings::skipTranslationForLanguage() const {
|
||||
return QLocale::Language(std::abs(_skipTranslationForLanguage));
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -722,6 +722,11 @@ public:
|
||||
return _chatQuickAction;
|
||||
}
|
||||
|
||||
void setTranslateButtonEnabled(bool value);
|
||||
[[nodiscard]] bool translateButtonEnabled() const;
|
||||
void setSkipTranslationForLanguage(QLocale::Language language);
|
||||
[[nodiscard]] QLocale::Language skipTranslationForLanguage() const;
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
|
||||
@@ -836,6 +841,7 @@ private:
|
||||
#endif // Q_OS_MAC
|
||||
HistoryView::DoubleClickQuickAction _chatQuickAction =
|
||||
HistoryView::DoubleClickQuickAction();
|
||||
int _skipTranslationForLanguage = -int(QLocale::English);
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
@@ -173,6 +173,11 @@ QString DefaultDownloadPathFolder(not_null<Main::Session*> session) {
|
||||
}
|
||||
|
||||
QString DefaultDownloadPath(not_null<Main::Session*> session) {
|
||||
const auto realDefaultPath = QStandardPaths::writableLocation(
|
||||
QStandardPaths::DownloadLocation)
|
||||
+ '/'
|
||||
+ DefaultDownloadPathFolder(session)
|
||||
+ '/';
|
||||
if (KSandbox::isInside() && Core::App().settings().downloadPath().isEmpty()) {
|
||||
QStringList files;
|
||||
QByteArray remoteContent;
|
||||
@@ -183,19 +188,16 @@ QString DefaultDownloadPath(not_null<Main::Session*> session) {
|
||||
tr::lng_download_path_choose(tr::now),
|
||||
QString(),
|
||||
FileDialog::internal::Type::ReadFolder,
|
||||
QString());
|
||||
realDefaultPath);
|
||||
if (success && !files.isEmpty() && !files[0].isEmpty()) {
|
||||
const auto result = files[0].endsWith('/') ? files[0] : (files[0] + '/');
|
||||
Core::App().settings().setDownloadPath(result);
|
||||
Core::App().saveSettings();
|
||||
return result;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
return QStandardPaths::writableLocation(
|
||||
QStandardPaths::DownloadLocation)
|
||||
+ '/'
|
||||
+ DefaultDownloadPathFolder(session)
|
||||
+ '/';
|
||||
return realDefaultPath;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
@@ -85,11 +85,6 @@ Sandbox::Sandbox(
|
||||
char **argv)
|
||||
: QApplication(argc, argv)
|
||||
, _mainThreadId(QThread::currentThreadId())
|
||||
, _handleObservables([=] {
|
||||
if (_application) {
|
||||
_application->call_handleObservables();
|
||||
}
|
||||
})
|
||||
, _launcher(launcher) {
|
||||
setQuitOnLastWindowClosed(false);
|
||||
}
|
||||
@@ -201,10 +196,6 @@ void Sandbox::launchApplication() {
|
||||
}
|
||||
setupScreenScale();
|
||||
|
||||
base::InitObservables([] {
|
||||
Instance()._handleObservables.call();
|
||||
});
|
||||
|
||||
_application = std::make_unique<Application>(_launcher);
|
||||
|
||||
// Ideally this should go to constructor.
|
||||
@@ -520,6 +511,10 @@ void Sandbox::refreshGlobalProxy() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Sandbox::customWorkingDir() const {
|
||||
return _launcher->customWorkingDir();
|
||||
}
|
||||
|
||||
uint64 Sandbox::installationTag() const {
|
||||
return _launcher->installationTag();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public:
|
||||
int start();
|
||||
|
||||
void refreshGlobalProxy();
|
||||
bool customWorkingDir() const;
|
||||
uint64 installationTag() const;
|
||||
|
||||
void postponeCall(FnMut<void()> &&callable);
|
||||
@@ -112,7 +113,6 @@ private:
|
||||
int _loopNestingLevel = 0;
|
||||
std::vector<int> _previousLoopNestingLevels;
|
||||
std::vector<PostponedCall> _postponedCalls;
|
||||
SingleQueuedInvokation _handleObservables;
|
||||
|
||||
not_null<Launcher*> _launcher;
|
||||
std::unique_ptr<Application> _application;
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_session.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -249,9 +250,15 @@ std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
|
||||
if (!my || !my->session) {
|
||||
return nullptr;
|
||||
}
|
||||
return my->session->data().customEmojiManager().create(
|
||||
auto result = my->session->data().customEmojiManager().create(
|
||||
data,
|
||||
my->customEmojiRepaint);
|
||||
if (my->customEmojiLoopLimit > 0) {
|
||||
return std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||
std::move(result),
|
||||
my->customEmojiLoopLimit);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Fn<void()> UiIntegration::createSpoilerRepaint(const std::any &context) {
|
||||
|
||||
@@ -29,6 +29,7 @@ struct MarkedTextContext {
|
||||
Main::Session *session = nullptr;
|
||||
HashtagMentionType type = HashtagMentionType::Telegram;
|
||||
Fn<void()> customEmojiRepaint;
|
||||
int customEmojiLoopLimit = 0;
|
||||
};
|
||||
|
||||
class UiIntegration final : public Ui::Integration {
|
||||
|
||||
@@ -51,22 +51,10 @@ inline QList<QUrl> GetMimeUrls(const QMimeData *data) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __has_include(<ksandbox.h>) && defined DeclareReadSetting
|
||||
inline QString FlatpakID() {
|
||||
static const auto Result = [] {
|
||||
if (!qEnvironmentVariableIsEmpty("FLATPAK_ID")) {
|
||||
return qEnvironmentVariable("FLATPAK_ID");
|
||||
} else {
|
||||
return cExeName();
|
||||
}
|
||||
}();
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
#if __has_include(<ksandbox.h>)
|
||||
inline QString IconName() {
|
||||
static const auto Result = KSandbox::isFlatpak()
|
||||
? FlatpakID()
|
||||
? qEnvironmentVariable("FLATPAK_ID")
|
||||
: qsl("telegram");
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -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 = 4003000;
|
||||
constexpr auto AppVersionStr = "4.3";
|
||||
constexpr auto AppVersion = 4003003;
|
||||
constexpr auto AppVersionStr = "4.3.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_forum_icons.h"
|
||||
#include "data/data_location.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_group_call.h"
|
||||
@@ -82,7 +83,6 @@ std::unique_ptr<Data::Forum> MegagroupInfo::takeForumData() {
|
||||
return result;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
}
|
||||
|
||||
ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
|
||||
@@ -164,8 +164,9 @@ void ChannelData::setFlags(ChannelDataFlags which) {
|
||||
const auto taken = ((diff & Flag::Forum) && !(which & Flag::Forum))
|
||||
? mgInfo->takeForumData()
|
||||
: nullptr;
|
||||
|
||||
if ((diff & Flag::Forum) && (which & Flag::Forum)) {
|
||||
if (const auto raw = taken.get()) {
|
||||
owner().forumIcons().clearUserpicsReset(taken.get());
|
||||
} else if ((diff & Flag::Forum) && (which & Flag::Forum)) {
|
||||
mgInfo->ensureForum(this);
|
||||
}
|
||||
_flags.set(which);
|
||||
@@ -874,6 +875,12 @@ const Data::AllowedReactions &ChannelData::allowedReactions() const {
|
||||
return _allowedReactions;
|
||||
}
|
||||
|
||||
void ChannelData::processTopics(const MTPVector<MTPForumTopic> &topics) {
|
||||
if (const auto forum = this->forum()) {
|
||||
forum->applyReceivedTopics(topics);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Data {
|
||||
|
||||
void ApplyMigration(
|
||||
|
||||
@@ -437,6 +437,8 @@ public:
|
||||
return mgInfo ? mgInfo->forum() : nullptr;
|
||||
}
|
||||
|
||||
void processTopics(const MTPVector<MTPForumTopic> &topics);
|
||||
|
||||
// Still public data members.
|
||||
uint64 access = 0;
|
||||
|
||||
|
||||
@@ -236,12 +236,12 @@ not_null<Dialogs::MainList*> ChatFilters::chatsList(FilterId filterId) {
|
||||
auto limit = rpl::single(rpl::empty_value()) | rpl::then(
|
||||
_owner->session().account().appConfig().refreshed()
|
||||
) | rpl::map([=] {
|
||||
return _owner->pinnedChatsLimit(nullptr, filterId);
|
||||
return _owner->pinnedChatsLimit(filterId);
|
||||
});
|
||||
pointer = std::make_unique<Dialogs::MainList>(
|
||||
&_owner->session(),
|
||||
filterId,
|
||||
_owner->maxPinnedChatsLimitValue(nullptr, filterId));
|
||||
_owner->maxPinnedChatsLimitValue(filterId));
|
||||
}
|
||||
return pointer.get();
|
||||
}
|
||||
@@ -476,7 +476,7 @@ const ChatFilter &ChatFilters::applyUpdatedPinned(
|
||||
const auto i = ranges::find(_list, id, &ChatFilter::id);
|
||||
Assert(i != end(_list));
|
||||
|
||||
const auto limit = _owner->pinnedChatsLimit(nullptr, id);
|
||||
const auto limit = _owner->pinnedChatsLimit(id);
|
||||
auto always = i->always();
|
||||
auto pinned = std::vector<not_null<History*>>();
|
||||
pinned.reserve(dialogs.size());
|
||||
|
||||
@@ -155,6 +155,7 @@ QString FileNameUnsafe(
|
||||
return path;
|
||||
}
|
||||
}();
|
||||
if (path.isEmpty()) return QString();
|
||||
if (name.isEmpty()) name = qsl(".unknown");
|
||||
if (name.at(0) == QChar::fromLatin1('.')) {
|
||||
if (!QDir().exists(path)) QDir().mkpath(path);
|
||||
@@ -1118,7 +1119,7 @@ bool DocumentData::saveFromData() {
|
||||
|
||||
bool DocumentData::saveFromDataSilent() {
|
||||
return !filepath(true).isEmpty()
|
||||
|| (!Core::App().settings().askDownloadPath()
|
||||
|| (Core::App().canSaveFileWithoutAskingForPath()
|
||||
&& saveFromDataChecked());
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/core_settings.h"
|
||||
#include "core/application.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/image/image.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
@@ -60,7 +61,8 @@ enum class FileType {
|
||||
QByteArray data,
|
||||
FileType type) {
|
||||
if (type == FileType::Video || type == FileType::VideoSticker) {
|
||||
auto result = ::Media::Clip::PrepareForSending(path, data);
|
||||
auto result = v::get<Ui::PreparedFileInformation::Video>(
|
||||
::Media::Clip::PrepareForSending(path, data).media);
|
||||
if (result.isWebmSticker && type == FileType::Video) {
|
||||
result.thumbnail = Images::Opaque(std::move(result.thumbnail));
|
||||
}
|
||||
@@ -291,7 +293,7 @@ void DocumentMedia::automaticLoad(
|
||||
return;
|
||||
}
|
||||
const auto toCache = _owner->saveToCache();
|
||||
if (!toCache && Core::App().settings().askDownloadPath()) {
|
||||
if (!toCache && !Core::App().canSaveFileWithoutAskingForPath()) {
|
||||
// We need a filename, but we're supposed to ask user for it.
|
||||
// No automatic download in this case.
|
||||
return;
|
||||
|
||||
@@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "core/application.h"
|
||||
@@ -33,15 +35,73 @@ namespace {
|
||||
constexpr auto kLoadedChatsMinCount = 20;
|
||||
constexpr auto kShowChatNamesCount = 8;
|
||||
|
||||
[[nodiscard]] TextWithEntities ComposeFolderListEntryText(
|
||||
not_null<Folder*> folder) {
|
||||
const auto &list = folder->lastHistories();
|
||||
if (list.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto count = std::max(
|
||||
int(list.size()),
|
||||
folder->chatsList()->fullSize().current());
|
||||
|
||||
const auto throwAwayLastName = (list.size() > 1)
|
||||
&& (count == list.size() + 1);
|
||||
auto &&peers = ranges::views::all(
|
||||
list
|
||||
) | ranges::views::take(
|
||||
list.size() - (throwAwayLastName ? 1 : 0)
|
||||
);
|
||||
const auto wrapName = [](not_null<History*> history) {
|
||||
const auto name = history->peer->name();
|
||||
return TextWithEntities{
|
||||
.text = name,
|
||||
.entities = (history->chatListBadgesState().unread
|
||||
? EntitiesInText{
|
||||
{ EntityType::Semibold, 0, int(name.size()), QString() },
|
||||
{ EntityType::PlainLink, 0, int(name.size()), QString() },
|
||||
}
|
||||
: EntitiesInText{}),
|
||||
};
|
||||
};
|
||||
const auto shown = int(peers.size());
|
||||
const auto accumulated = [&] {
|
||||
Expects(shown > 0);
|
||||
|
||||
auto i = peers.begin();
|
||||
auto result = wrapName(*i);
|
||||
for (++i; i != peers.end(); ++i) {
|
||||
result = tr::lng_archived_last_list(
|
||||
tr::now,
|
||||
lt_accumulated,
|
||||
result,
|
||||
lt_chat,
|
||||
wrapName(*i),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
return (shown < count)
|
||||
? tr::lng_archived_last(
|
||||
tr::now,
|
||||
lt_count,
|
||||
(count - shown),
|
||||
lt_chats,
|
||||
accumulated,
|
||||
Ui::Text::WithEntities)
|
||||
: accumulated;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Folder::Folder(not_null<Data::Session*> owner, FolderId id)
|
||||
Folder::Folder(not_null<Session*> owner, FolderId id)
|
||||
: Entry(owner, Type::Folder)
|
||||
, _id(id)
|
||||
, _chatsList(
|
||||
&owner->session(),
|
||||
FilterId(),
|
||||
owner->maxPinnedChatsLimitValue(this, FilterId()))
|
||||
owner->maxPinnedChatsLimitValue(this))
|
||||
, _name(tr::lng_archived_name(tr::now)) {
|
||||
indexNameParts();
|
||||
|
||||
@@ -76,29 +136,6 @@ FolderId Folder::id() const {
|
||||
|
||||
void Folder::indexNameParts() {
|
||||
// We don't want archive to be filtered in the chats list.
|
||||
//_nameWords.clear();
|
||||
//_nameFirstLetters.clear();
|
||||
//auto toIndexList = QStringList();
|
||||
//auto appendToIndex = [&](const QString &value) {
|
||||
// if (!value.isEmpty()) {
|
||||
// toIndexList.push_back(TextUtilities::RemoveAccents(value));
|
||||
// }
|
||||
//};
|
||||
|
||||
//appendToIndex(_name);
|
||||
//const auto appendTranslit = !toIndexList.isEmpty()
|
||||
// && cRussianLetters().match(toIndexList.front()).hasMatch();
|
||||
//if (appendTranslit) {
|
||||
// appendToIndex(translitRusEng(toIndexList.front()));
|
||||
//}
|
||||
//auto toIndex = toIndexList.join(' ');
|
||||
//toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex);
|
||||
|
||||
//const auto namesList = TextUtilities::PrepareSearchWords(toIndex);
|
||||
//for (const auto &name : namesList) {
|
||||
// _nameWords.insert(name);
|
||||
// _nameFirstLetters.insert(name[0]);
|
||||
//}
|
||||
}
|
||||
|
||||
void Folder::registerOne(not_null<History*> history) {
|
||||
@@ -110,7 +147,6 @@ void Folder::registerOne(not_null<History*> history) {
|
||||
} else {
|
||||
updateChatListEntry();
|
||||
}
|
||||
applyChatListMessage(history->chatListMessage());
|
||||
reorderLastHistories();
|
||||
}
|
||||
|
||||
@@ -118,9 +154,6 @@ void Folder::unregisterOne(not_null<History*> history) {
|
||||
if (_chatsList.empty()) {
|
||||
updateChatListExistence();
|
||||
}
|
||||
if (_chatListMessage && _chatListMessage->history() == history) {
|
||||
computeChatListMessage();
|
||||
}
|
||||
reorderLastHistories();
|
||||
}
|
||||
|
||||
@@ -129,47 +162,11 @@ int Folder::chatListNameVersion() const {
|
||||
}
|
||||
|
||||
void Folder::oneListMessageChanged(HistoryItem *from, HistoryItem *to) {
|
||||
if (!applyChatListMessage(to) && _chatListMessage == from) {
|
||||
computeChatListMessage();
|
||||
}
|
||||
if (from || to) {
|
||||
reorderLastHistories();
|
||||
}
|
||||
}
|
||||
|
||||
bool Folder::applyChatListMessage(HistoryItem *item) {
|
||||
if (!item) {
|
||||
return false;
|
||||
} else if (_chatListMessage
|
||||
&& _chatListMessage->date() >= item->date()) {
|
||||
return false;
|
||||
}
|
||||
_chatListMessage = item;
|
||||
updateChatListEntry();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Folder::computeChatListMessage() {
|
||||
auto &&items = ranges::views::all(
|
||||
*_chatsList.indexed()
|
||||
) | ranges::views::filter([](not_null<Dialogs::Row*> row) {
|
||||
return row->entry()->chatListMessage() != nullptr;
|
||||
});
|
||||
const auto chatListDate = [](not_null<Dialogs::Row*> row) {
|
||||
return row->entry()->chatListMessage()->date();
|
||||
};
|
||||
const auto top = ranges::max_element(
|
||||
items,
|
||||
ranges::less(),
|
||||
chatListDate);
|
||||
if (top == items.end()) {
|
||||
_chatListMessage = nullptr;
|
||||
} else {
|
||||
_chatListMessage = (*top)->entry()->chatListMessage();
|
||||
}
|
||||
updateChatListEntry();
|
||||
}
|
||||
|
||||
void Folder::reorderLastHistories() {
|
||||
// We want first kShowChatNamesCount histories, by last message date.
|
||||
const auto pred = [](not_null<History*> a, not_null<History*> b) {
|
||||
@@ -179,7 +176,7 @@ void Folder::reorderLastHistories() {
|
||||
const auto bDate = bItem ? bItem->date() : TimeId(0);
|
||||
return aDate > bDate;
|
||||
};
|
||||
_lastHistories.erase(_lastHistories.begin(), _lastHistories.end());
|
||||
_lastHistories.clear();
|
||||
_lastHistories.reserve(kShowChatNamesCount + 1);
|
||||
auto &&histories = ranges::views::all(
|
||||
*_chatsList.indexed()
|
||||
@@ -187,11 +184,13 @@ void Folder::reorderLastHistories() {
|
||||
return row->history();
|
||||
}) | ranges::views::filter([](History *history) {
|
||||
return (history != nullptr);
|
||||
}) | ranges::views::transform([](History *history) {
|
||||
return not_null<History*>(history);
|
||||
});
|
||||
auto nonPinnedChecked = 0;
|
||||
for (const auto history : histories) {
|
||||
const auto i = ranges::upper_bound(_lastHistories, history, pred);
|
||||
const auto i = ranges::upper_bound(
|
||||
_lastHistories,
|
||||
not_null(history),
|
||||
pred);
|
||||
if (size(_lastHistories) < kShowChatNamesCount
|
||||
|| i != end(_lastHistories)) {
|
||||
_lastHistories.insert(i, history);
|
||||
@@ -199,6 +198,10 @@ void Folder::reorderLastHistories() {
|
||||
if (size(_lastHistories) > kShowChatNamesCount) {
|
||||
_lastHistories.pop_back();
|
||||
}
|
||||
if (!history->isPinnedDialog(FilterId())
|
||||
&& ++nonPinnedChecked >= kShowChatNamesCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
++_chatListViewVersion;
|
||||
updateChatListEntry();
|
||||
@@ -213,7 +216,7 @@ void Folder::loadUserpic() {
|
||||
|
||||
void Folder::paintUserpic(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
std::shared_ptr<CloudImageView> &view,
|
||||
const Dialogs::Ui::PaintContext &context) const {
|
||||
paintUserpic(
|
||||
p,
|
||||
@@ -282,8 +285,16 @@ const std::vector<not_null<History*>> &Folder::lastHistories() const {
|
||||
return _lastHistories;
|
||||
}
|
||||
|
||||
uint32 Folder::chatListViewVersion() const {
|
||||
return _chatListViewVersion;
|
||||
void Folder::validateListEntryCache() {
|
||||
if (_listEntryCacheVersion == _chatListViewVersion) {
|
||||
return;
|
||||
}
|
||||
_listEntryCacheVersion = _chatListViewVersion;
|
||||
_listEntryCache.setMarkedText(
|
||||
st::dialogsTextStyle,
|
||||
ComposeFolderListEntryText(this),
|
||||
// Use rich options as long as the entry text does not have user text.
|
||||
Ui::ItemTextDefaultOptions());
|
||||
}
|
||||
|
||||
void Folder::requestChatListMessage() {
|
||||
@@ -293,7 +304,7 @@ void Folder::requestChatListMessage() {
|
||||
}
|
||||
|
||||
TimeId Folder::adjustedChatListTimeId() const {
|
||||
return _chatListMessage ? _chatListMessage->date() : chatListTimeId();
|
||||
return chatListTimeId();
|
||||
}
|
||||
|
||||
void Folder::applyDialog(const MTPDdialogFolder &data) {
|
||||
@@ -344,7 +355,7 @@ Dialogs::BadgesState Folder::chatListBadgesState() const {
|
||||
}
|
||||
|
||||
HistoryItem *Folder::chatListMessage() const {
|
||||
return _chatListMessage;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Folder::chatListMessageKnown() const {
|
||||
|
||||
@@ -69,12 +69,13 @@ public:
|
||||
const style::color &overrideFg) const;
|
||||
|
||||
const std::vector<not_null<History*>> &lastHistories() const;
|
||||
uint32 chatListViewVersion() const;
|
||||
void validateListEntryCache();
|
||||
[[nodiscard]] const Ui::Text::String &listEntryCache() const {
|
||||
return _listEntryCache;
|
||||
}
|
||||
|
||||
private:
|
||||
void indexNameParts();
|
||||
bool applyChatListMessage(HistoryItem *item);
|
||||
void computeChatListMessage();
|
||||
|
||||
int chatListNameVersion() const override;
|
||||
|
||||
@@ -96,8 +97,10 @@ private:
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
|
||||
std::vector<not_null<History*>> _lastHistories;
|
||||
HistoryItem *_chatListMessage = nullptr;
|
||||
uint32 _chatListViewVersion = 0;
|
||||
|
||||
Ui::Text::String _listEntryCache;
|
||||
int _listEntryCacheVersion = 0;
|
||||
int _chatListViewVersion = 0;
|
||||
//rpl::variable<MessagePosition> _unreadPosition;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
@@ -37,15 +37,17 @@ constexpr auto kTopicsFirstLoad = 20;
|
||||
constexpr auto kLoadedTopicsMinCount = 20;
|
||||
constexpr auto kTopicsPerPage = 500;
|
||||
constexpr auto kStalePerRequest = 100;
|
||||
constexpr auto kShowTopicNamesCount = 8;
|
||||
// constexpr auto kGeneralColorId = 0xA9A9A9;
|
||||
|
||||
} // namespace
|
||||
|
||||
Forum::Forum(not_null<History*> history)
|
||||
: _history(history)
|
||||
, _topicsList(&session(), FilterId(0), rpl::single(1)) {
|
||||
, _topicsList(&session(), {}, owner().maxPinnedChatsLimitValue(this)) {
|
||||
Expects(_history->peer->isChannel());
|
||||
|
||||
|
||||
if (_history->inChatList()) {
|
||||
preloadTopics();
|
||||
}
|
||||
@@ -95,13 +97,6 @@ not_null<Dialogs::MainList*> Forum::topicsList() {
|
||||
return &_topicsList;
|
||||
}
|
||||
|
||||
void Forum::unpinTopic() {
|
||||
const auto list = _topicsList.pinned();
|
||||
while (!list->order().empty()) {
|
||||
list->setPinned(list->order().front(), false);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> Forum::destroyed() const {
|
||||
return channel()->flagsValue(
|
||||
) | rpl::filter([=](const ChannelData::Flags::Change &update) {
|
||||
@@ -147,9 +142,12 @@ void Forum::requestTopics() {
|
||||
MTP_int(_offset.topicId),
|
||||
MTP_int(loadCount)
|
||||
)).done([=](const MTPmessages_ForumTopics &result) {
|
||||
const auto previousOffset = _offset;
|
||||
applyReceivedTopics(result, _offset);
|
||||
const auto &list = result.data().vtopics().v;
|
||||
if (list.isEmpty() || list.size() == result.data().vcount().v) {
|
||||
if (list.isEmpty()
|
||||
|| list.size() == result.data().vcount().v
|
||||
|| (_offset == previousOffset)) {
|
||||
_topicsList.setLoaded();
|
||||
}
|
||||
_requestId = 0;
|
||||
@@ -157,6 +155,7 @@ void Forum::requestTopics() {
|
||||
if (_topicsList.loaded()) {
|
||||
_chatsListLoadedEvents.fire({});
|
||||
}
|
||||
reorderLastTopics();
|
||||
requestSomeStale();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
@@ -176,6 +175,11 @@ void Forum::applyTopicDeleted(MsgId rootId) {
|
||||
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);
|
||||
_topics.erase(i);
|
||||
|
||||
@@ -187,6 +191,65 @@ void Forum::applyTopicDeleted(MsgId rootId) {
|
||||
}
|
||||
}
|
||||
|
||||
void Forum::reorderLastTopics() {
|
||||
// We want first kShowChatNamesCount 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();
|
||||
const auto aDate = aItem ? aItem->date() : TimeId(0);
|
||||
const auto bDate = bItem ? bItem->date() : TimeId(0);
|
||||
return aDate > bDate;
|
||||
};
|
||||
_lastTopics.clear();
|
||||
_lastTopics.reserve(kShowTopicNamesCount + 1);
|
||||
auto &&topics = ranges::views::all(
|
||||
*_topicsList.indexed()
|
||||
) | ranges::views::transform([](not_null<Dialogs::Row*> row) {
|
||||
return row->topic();
|
||||
});
|
||||
auto nonPinnedChecked = 0;
|
||||
for (const auto topic : topics) {
|
||||
const auto i = ranges::upper_bound(
|
||||
_lastTopics,
|
||||
not_null(topic),
|
||||
pred);
|
||||
if (size(_lastTopics) < kShowTopicNamesCount
|
||||
|| i != end(_lastTopics)) {
|
||||
_lastTopics.insert(i, topic);
|
||||
}
|
||||
if (size(_lastTopics) > kShowTopicNamesCount) {
|
||||
_lastTopics.pop_back();
|
||||
}
|
||||
if (!topic->isPinnedDialog(FilterId())
|
||||
&& ++nonPinnedChecked >= kShowTopicNamesCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
++_lastTopicsVersion;
|
||||
_history->updateChatListEntry();
|
||||
}
|
||||
|
||||
int Forum::recentTopicsListVersion() const {
|
||||
return _lastTopicsVersion;
|
||||
}
|
||||
|
||||
void Forum::recentTopicsInvalidate(not_null<ForumTopic*> topic) {
|
||||
if (ranges::contains(_lastTopics, topic)) {
|
||||
++_lastTopicsVersion;
|
||||
_history->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<not_null<ForumTopic*>> &Forum::recentTopics() const {
|
||||
return _lastTopics;
|
||||
}
|
||||
|
||||
void Forum::listMessageChanged(HistoryItem *from, HistoryItem *to) {
|
||||
if (from || to) {
|
||||
reorderLastTopics();
|
||||
}
|
||||
}
|
||||
|
||||
void Forum::applyReceivedTopics(
|
||||
const MTPmessages_ForumTopics &topics,
|
||||
ForumOffsets &updateOffsets) {
|
||||
@@ -207,7 +270,16 @@ void Forum::applyReceivedTopics(
|
||||
owner().processChats(data.vchats());
|
||||
owner().processMessages(data.vmessages(), NewMessageType::Existing);
|
||||
channel()->ptsReceived(data.vpts().v);
|
||||
const auto &list = data.vtopics().v;
|
||||
applyReceivedTopics(data.vtopics(), std::move(callback));
|
||||
if (!_staleRootIds.empty()) {
|
||||
requestSomeStale();
|
||||
}
|
||||
}
|
||||
|
||||
void Forum::applyReceivedTopics(
|
||||
const MTPVector<MTPForumTopic> &topics,
|
||||
Fn<void(not_null<ForumTopic*>)> callback) {
|
||||
const auto &list = topics.v;
|
||||
for (const auto &topic : list) {
|
||||
const auto rootId = topic.match([&](const auto &data) {
|
||||
return data.vid().v;
|
||||
@@ -238,9 +310,6 @@ void Forum::applyReceivedTopics(
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!_staleRootIds.empty()) {
|
||||
requestSomeStale();
|
||||
}
|
||||
}
|
||||
|
||||
void Forum::requestSomeStale() {
|
||||
@@ -342,6 +411,7 @@ ForumTopic *Forum::applyTopicAdded(
|
||||
if (!creating(rootId)) {
|
||||
raw->addToChatList(FilterId(), topicsList());
|
||||
_chatsListChanges.fire({});
|
||||
reorderLastTopics();
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
@@ -393,6 +463,8 @@ void Forum::created(MsgId rootId, MsgId realId) {
|
||||
realId,
|
||||
std::move(topic)
|
||||
).first->second->setRealRootId(realId);
|
||||
|
||||
reorderLastTopics();
|
||||
}
|
||||
owner().notifyItemIdChange({ id, rootId });
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ public:
|
||||
void requestTopics();
|
||||
[[nodiscard]] rpl::producer<> chatsListChanges() const;
|
||||
[[nodiscard]] rpl::producer<> chatsListLoadedEvents() const;
|
||||
void unpinTopic();
|
||||
|
||||
void requestTopic(MsgId rootId, Fn<void()> done = nullptr);
|
||||
ForumTopic *applyTopicAdded(
|
||||
@@ -75,6 +74,9 @@ public:
|
||||
void applyReceivedTopics(
|
||||
const MTPmessages_ForumTopics &topics,
|
||||
Fn<void(not_null<ForumTopic*>)> callback = nullptr);
|
||||
void applyReceivedTopics(
|
||||
const MTPVector<MTPForumTopic> &topics,
|
||||
Fn<void(not_null<ForumTopic*>)> callback = nullptr);
|
||||
|
||||
[[nodiscard]] MsgId reserveCreatingId(
|
||||
const QString &title,
|
||||
@@ -88,6 +90,12 @@ public:
|
||||
void clearAllUnreadReactions();
|
||||
void enumerateTopics(Fn<void(not_null<ForumTopic*>)> action) const;
|
||||
|
||||
void listMessageChanged(HistoryItem *from, HistoryItem *to);
|
||||
[[nodiscard]] int recentTopicsListVersion() const;
|
||||
void recentTopicsInvalidate(not_null<ForumTopic*> topic);
|
||||
[[nodiscard]] auto recentTopics() const
|
||||
-> const std::vector<not_null<ForumTopic*>> &;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
@@ -98,6 +106,7 @@ private:
|
||||
std::vector<Fn<void()>> callbacks;
|
||||
};
|
||||
|
||||
void reorderLastTopics();
|
||||
void requestSomeStale();
|
||||
void finishTopicRequest(MsgId rootId);
|
||||
|
||||
@@ -117,6 +126,9 @@ private:
|
||||
|
||||
base::flat_set<MsgId> _creatingRootIds;
|
||||
|
||||
std::vector<not_null<ForumTopic*>> _lastTopics;
|
||||
int _lastTopicsVersion = 0;
|
||||
|
||||
rpl::event_stream<> _chatsListChanges;
|
||||
rpl::event_stream<> _chatsListLoadedEvents;
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Data {
|
||||
@@ -22,7 +24,8 @@ constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000);
|
||||
} // namespace
|
||||
|
||||
ForumIcons::ForumIcons(not_null<Session*> owner)
|
||||
: _owner(owner) {
|
||||
: _owner(owner)
|
||||
, _resetUserpicsTimer([=] { resetUserpics(); }) {
|
||||
}
|
||||
|
||||
ForumIcons::~ForumIcons() = default;
|
||||
@@ -79,4 +82,45 @@ void ForumIcons::updateDefault(const MTPDmessages_stickerSet &data) {
|
||||
_defaultUpdated.fire({});
|
||||
}
|
||||
|
||||
void ForumIcons::scheduleUserpicsReset(not_null<Forum*> forum) {
|
||||
const auto duration = crl::time(st::slideDuration);
|
||||
_resetUserpicsWhen[forum] = crl::now() + duration;
|
||||
if (!_resetUserpicsTimer.isActive()) {
|
||||
_resetUserpicsTimer.callOnce(duration);
|
||||
}
|
||||
}
|
||||
|
||||
void ForumIcons::clearUserpicsReset(not_null<Forum*> forum) {
|
||||
_resetUserpicsWhen.remove(forum);
|
||||
}
|
||||
|
||||
void ForumIcons::resetUserpics() {
|
||||
auto nearest = crl::time();
|
||||
auto now = crl::now();
|
||||
for (auto i = begin(_resetUserpicsWhen); i != end(_resetUserpicsWhen);) {
|
||||
if (i->second > now) {
|
||||
if (!nearest || nearest > i->second) {
|
||||
nearest = i->second;
|
||||
}
|
||||
++i;
|
||||
} else {
|
||||
const auto forum = i->first;
|
||||
i = _resetUserpicsWhen.erase(i);
|
||||
resetUserpicsFor(forum);
|
||||
}
|
||||
}
|
||||
if (nearest) {
|
||||
_resetUserpicsTimer.callOnce(
|
||||
std::min(nearest - now, 86400 * crl::time(1000)));
|
||||
} else {
|
||||
_resetUserpicsTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void ForumIcons::resetUserpicsFor(not_null<Forum*> forum) {
|
||||
forum->enumerateTopics([](not_null<ForumTopic*> topic) {
|
||||
topic->clearUserpicLoops();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -15,6 +17,7 @@ namespace Data {
|
||||
|
||||
class DocumentMedia;
|
||||
class Session;
|
||||
class Forum;
|
||||
|
||||
class ForumIcons final {
|
||||
public:
|
||||
@@ -33,8 +36,13 @@ public:
|
||||
|
||||
[[nodiscard]] rpl::producer<> defaultUpdates() const;
|
||||
|
||||
void scheduleUserpicsReset(not_null<Forum*> forum);
|
||||
void clearUserpicsReset(not_null<Forum*> forum);
|
||||
|
||||
private:
|
||||
void requestDefault();
|
||||
void resetUserpics();
|
||||
void resetUserpicsFor(not_null<Forum*> forum);
|
||||
|
||||
void updateDefault(const MTPDmessages_stickerSet &data);
|
||||
|
||||
@@ -45,6 +53,9 @@ private:
|
||||
|
||||
mtpRequestId _defaultRequestId = 0;
|
||||
|
||||
base::flat_map<not_null<Forum*>, crl::time> _resetUserpicsWhen;
|
||||
base::Timer _resetUserpicsTimer;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
@@ -41,6 +42,8 @@ namespace {
|
||||
|
||||
using UpdateFlag = TopicUpdate::Flag;
|
||||
|
||||
constexpr auto kUserpicLoopsCount = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
const base::flat_map<int32, QString> &ForumTopicIcons() {
|
||||
@@ -175,6 +178,9 @@ ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId)
|
||||
}) | rpl::start_with_next([=](
|
||||
std::optional<int> previous,
|
||||
std::optional<int> now) {
|
||||
if (previous.value_or(0) != now.value_or(0)) {
|
||||
_forum->recentTopicsInvalidate(this);
|
||||
}
|
||||
notifyUnreadStateChange(unreadStateFor(
|
||||
previous.value_or(0),
|
||||
previous.has_value()));
|
||||
@@ -293,6 +299,8 @@ void ForumTopic::readTillEnd() {
|
||||
void ForumTopic::applyTopic(const MTPDforumTopic &data) {
|
||||
Expects(_rootId == data.vid().v);
|
||||
|
||||
const auto min = data.is_short();
|
||||
|
||||
applyCreator(peerFromMTP(data.vfrom_id()));
|
||||
applyCreationDate(data.vdate().v);
|
||||
|
||||
@@ -304,32 +312,31 @@ void ForumTopic::applyTopic(const MTPDforumTopic &data) {
|
||||
}
|
||||
applyColorId(data.vicon_color().v);
|
||||
|
||||
if (data.is_pinned()) {
|
||||
owner().setChatPinned(this, 0, true);
|
||||
} else {
|
||||
_list->pinned()->setPinned(this, false);
|
||||
}
|
||||
|
||||
owner().notifySettings().apply(this, data.vnotify_settings());
|
||||
|
||||
const auto draft = data.vdraft();
|
||||
if (draft && draft->type() == mtpc_draftMessage) {
|
||||
Data::ApplyPeerCloudDraft(
|
||||
&session(),
|
||||
channel()->id,
|
||||
_rootId,
|
||||
draft->c_draftMessage());
|
||||
}
|
||||
applyIsMy(data.is_my());
|
||||
setClosed(data.is_closed());
|
||||
|
||||
_replies->setInboxReadTill(
|
||||
data.vread_inbox_max_id().v,
|
||||
data.vunread_count().v);
|
||||
_replies->setOutboxReadTill(data.vread_outbox_max_id().v);
|
||||
applyTopicTopMessage(data.vtop_message().v);
|
||||
unreadMentions().setCount(data.vunread_mentions_count().v);
|
||||
unreadReactions().setCount(data.vunread_reactions_count().v);
|
||||
if (!min) {
|
||||
owner().setPinnedFromEntryList(this, data.is_pinned());
|
||||
owner().notifySettings().apply(this, data.vnotify_settings());
|
||||
|
||||
if (const auto draft = data.vdraft()) {
|
||||
draft->match([&](const MTPDdraftMessage &data) {
|
||||
Data::ApplyPeerCloudDraft(
|
||||
&session(),
|
||||
channel()->id,
|
||||
_rootId,
|
||||
data);
|
||||
}, [](const MTPDdraftMessageEmpty&) {});
|
||||
}
|
||||
|
||||
_replies->setInboxReadTill(
|
||||
data.vread_inbox_max_id().v,
|
||||
data.vunread_count().v);
|
||||
_replies->setOutboxReadTill(data.vread_outbox_max_id().v);
|
||||
applyTopicTopMessage(data.vtop_message().v);
|
||||
unreadMentions().setCount(data.vunread_mentions_count().v);
|
||||
unreadReactions().setCount(data.vunread_reactions_count().v);
|
||||
}
|
||||
}
|
||||
|
||||
void ForumTopic::applyCreator(PeerId creatorId) {
|
||||
@@ -483,7 +490,9 @@ void ForumTopic::setLastMessage(HistoryItem *item) {
|
||||
void ForumTopic::setChatListMessage(HistoryItem *item) {
|
||||
if (_chatListMessage && *_chatListMessage == item) {
|
||||
return;
|
||||
} else if (item) {
|
||||
}
|
||||
const auto was = _chatListMessage.value_or(nullptr);
|
||||
if (item) {
|
||||
if (item->isSponsored()) {
|
||||
return;
|
||||
}
|
||||
@@ -499,6 +508,7 @@ void ForumTopic::setChatListMessage(HistoryItem *item) {
|
||||
_chatListMessage = nullptr;
|
||||
updateChatListEntry();
|
||||
}
|
||||
_forum->listMessageChanged(was, item);
|
||||
}
|
||||
|
||||
void ForumTopic::loadUserpic() {
|
||||
@@ -544,6 +554,12 @@ void ForumTopic::paintUserpic(
|
||||
}
|
||||
}
|
||||
|
||||
void ForumTopic::clearUserpicLoops() {
|
||||
if (_icon) {
|
||||
_icon->unload();
|
||||
}
|
||||
}
|
||||
|
||||
void ForumTopic::validateDefaultIcon() const {
|
||||
if (_defaultIcon.isNull()) {
|
||||
_defaultIcon = ForumTopicIconFrame(
|
||||
@@ -607,12 +623,17 @@ TextWithEntities ForumTopic::titleWithIcon() const {
|
||||
return ForumTopicIconWithTitle(_iconId, _title);
|
||||
}
|
||||
|
||||
int ForumTopic::titleVersion() const {
|
||||
return _titleVersion;
|
||||
}
|
||||
|
||||
void ForumTopic::applyTitle(const QString &title) {
|
||||
if (_title == title) {
|
||||
return;
|
||||
}
|
||||
_title = title;
|
||||
++_titleVersion;
|
||||
_forum->recentTopicsInvalidate(this);
|
||||
_defaultIcon = QImage();
|
||||
indexTitleParts();
|
||||
updateChatListEntry();
|
||||
@@ -628,11 +649,14 @@ void ForumTopic::applyIconId(DocumentId iconId) {
|
||||
return;
|
||||
}
|
||||
_iconId = iconId;
|
||||
++_titleVersion;
|
||||
_icon = iconId
|
||||
? owner().customEmojiManager().create(
|
||||
_iconId,
|
||||
[=] { updateChatListEntry(); },
|
||||
Data::CustomEmojiManager::SizeTag::Normal)
|
||||
? std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||
owner().customEmojiManager().create(
|
||||
_iconId,
|
||||
[=] { updateChatListEntry(); },
|
||||
Data::CustomEmojiManager::SizeTag::Normal),
|
||||
kUserpicLoopsCount)
|
||||
: nullptr;
|
||||
if (iconId) {
|
||||
_defaultIcon = QImage();
|
||||
@@ -717,10 +741,16 @@ Dialogs::UnreadState ForumTopic::chatListUnreadState() const {
|
||||
}
|
||||
|
||||
Dialogs::BadgesState ForumTopic::chatListBadgesState() const {
|
||||
return Dialogs::BadgesForUnread(
|
||||
auto result = Dialogs::BadgesForUnread(
|
||||
chatListUnreadState(),
|
||||
Dialogs::CountInBadge::Messages,
|
||||
Dialogs::IncludeInBadge::All);
|
||||
if (!result.unread && _replies->inboxReadTillId() < 2) {
|
||||
result.unread = channel()->amIn()
|
||||
&& (_lastKnownServerMessageId > history()->inboxReadTillId());
|
||||
result.unreadMuted = muted();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Dialogs::UnreadState ForumTopic::unreadStateFor(
|
||||
|
||||
@@ -114,6 +114,7 @@ public:
|
||||
|
||||
[[nodiscard]] QString title() const;
|
||||
[[nodiscard]] TextWithEntities titleWithIcon() const;
|
||||
[[nodiscard]] int titleVersion() const;
|
||||
void applyTitle(const QString &title);
|
||||
[[nodiscard]] DocumentId iconId() const;
|
||||
void applyIconId(DocumentId iconId);
|
||||
@@ -138,6 +139,7 @@ public:
|
||||
Painter &p,
|
||||
std::shared_ptr<CloudImageView> &view,
|
||||
const Dialogs::Ui::PaintContext &context) const override;
|
||||
void clearUserpicLoops();
|
||||
|
||||
[[nodiscard]] bool isServerSideUnread(
|
||||
not_null<const HistoryItem*> item) const override;
|
||||
|
||||
@@ -462,12 +462,23 @@ std::unique_ptr<HistoryView::Media> Media::createView(
|
||||
ItemPreview Media::toGroupPreview(
|
||||
const HistoryItemsList &items,
|
||||
ToPreviewOptions options) const {
|
||||
const auto genericText = Ui::Text::PlainLink(
|
||||
tr::lng_in_dlg_album(tr::now));
|
||||
auto result = ItemPreview();
|
||||
auto loadingContext = std::vector<std::any>();
|
||||
auto photoCount = 0;
|
||||
auto videoCount = 0;
|
||||
auto audioCount = 0;
|
||||
auto fileCount = 0;
|
||||
for (const auto &item : items) {
|
||||
if (const auto media = item->media()) {
|
||||
if (media->photo()) {
|
||||
photoCount++;
|
||||
} else if (const auto document = media->document()) {
|
||||
(document->isVideoFile()
|
||||
? videoCount
|
||||
: document->isAudioFile()
|
||||
? audioCount
|
||||
: fileCount)++;
|
||||
}
|
||||
auto copy = options;
|
||||
copy.ignoreGroup = true;
|
||||
const auto already = int(result.images.size());
|
||||
@@ -490,13 +501,25 @@ ItemPreview Media::toGroupPreview(
|
||||
if (result.text.text.isEmpty()) {
|
||||
result.text = original;
|
||||
} else {
|
||||
result.text = genericText;
|
||||
result.text = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.text.text.isEmpty()) {
|
||||
result.text = genericText;
|
||||
const auto mediaCount = photoCount + videoCount;
|
||||
auto genericText = (photoCount && videoCount)
|
||||
? tr::lng_in_dlg_media_count(tr::now, lt_count, mediaCount)
|
||||
: photoCount
|
||||
? tr::lng_in_dlg_photo_count(tr::now, lt_count, photoCount)
|
||||
: videoCount
|
||||
? tr::lng_in_dlg_video_count(tr::now, lt_count, videoCount)
|
||||
: audioCount
|
||||
? tr::lng_in_dlg_audio_count(tr::now, lt_count, audioCount)
|
||||
: fileCount
|
||||
? tr::lng_in_dlg_file_count(tr::now, lt_count, fileCount)
|
||||
: tr::lng_in_dlg_album(tr::now);
|
||||
result.text = Ui::Text::PlainLink(genericText);
|
||||
}
|
||||
if (!loadingContext.empty()) {
|
||||
result.loadingContext = std::move(loadingContext);
|
||||
|
||||
@@ -248,9 +248,12 @@ void PeerData::updateNameDelayed(
|
||||
|
||||
not_null<Ui::EmptyUserpic*> PeerData::ensureEmptyUserpic() const {
|
||||
if (!_userpicEmpty) {
|
||||
const auto user = asUser();
|
||||
_userpicEmpty = std::make_unique<Ui::EmptyUserpic>(
|
||||
Data::PeerUserpicColor(id),
|
||||
name());
|
||||
user && user->isInaccessible()
|
||||
? Ui::EmptyUserpic::InaccessibleName()
|
||||
: name());
|
||||
}
|
||||
return _userpicEmpty.get();
|
||||
}
|
||||
|
||||
@@ -276,6 +276,10 @@ rpl::producer<int> RepliesList::fullCount() const {
|
||||
return _fullCount.value() | rpl::filter_optional();
|
||||
}
|
||||
|
||||
rpl::producer<std::optional<int>> RepliesList::maybeFullCount() const {
|
||||
return _fullCount.value();
|
||||
}
|
||||
|
||||
bool RepliesList::unreadCountKnown() const {
|
||||
return _unreadCount.current().has_value();
|
||||
}
|
||||
@@ -657,25 +661,6 @@ void RepliesList::loadAfter() {
|
||||
bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
|
||||
const auto guard = gsl::finally([&] { _listChanges.fire({}); });
|
||||
|
||||
const auto fullCount = result.match([&](
|
||||
const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(HistoryWidget::messagesReceived)"));
|
||||
return 0;
|
||||
}, [&](const MTPDmessages_messages &data) {
|
||||
return int(data.vmessages().v.size());
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
return data.vcount().v;
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
if (_history->peer->isChannel()) {
|
||||
_history->peer->asChannel()->ptsReceived(data.vpts().v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (HistoryWidget::messagesReceived)"));
|
||||
}
|
||||
return data.vcount().v;
|
||||
});
|
||||
|
||||
auto &owner = _history->owner();
|
||||
const auto list = result.match([&](
|
||||
const MTPDmessages_messagesNotModified &) {
|
||||
@@ -687,6 +672,27 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
|
||||
owner.processChats(data.vchats());
|
||||
return data.vmessages().v;
|
||||
});
|
||||
|
||||
const auto fullCount = result.match([&](
|
||||
const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(HistoryWidget::messagesReceived)"));
|
||||
return 0;
|
||||
}, [&](const MTPDmessages_messages &data) {
|
||||
return int(data.vmessages().v.size());
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
return data.vcount().v;
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
if (const auto channel = _history->peer->asChannel()) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
channel->processTopics(data.vtopics());
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (HistoryWidget::messagesReceived)"));
|
||||
}
|
||||
return data.vcount().v;
|
||||
});
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -935,15 +941,15 @@ void RepliesList::readTill(not_null<HistoryItem*> item) {
|
||||
}
|
||||
|
||||
void RepliesList::readTill(MsgId tillId) {
|
||||
if (!IsServerMsgId(tillId)) {
|
||||
return;
|
||||
}
|
||||
readTill(tillId, _history->owner().message(_history->peer->id, tillId));
|
||||
}
|
||||
|
||||
void RepliesList::readTill(
|
||||
MsgId tillId,
|
||||
HistoryItem *tillIdItem) {
|
||||
if (!IsServerMsgId(tillId)) {
|
||||
return;
|
||||
}
|
||||
const auto was = computeInboxReadTillFull();
|
||||
const auto now = tillId;
|
||||
if (now < was) {
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
int limitAfter);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> fullCount() const;
|
||||
[[nodiscard]] rpl::producer<std::optional<int>> maybeFullCount() const;
|
||||
|
||||
[[nodiscard]] bool unreadCountKnown() const;
|
||||
[[nodiscard]] int unreadCountCurrent() const;
|
||||
|
||||
@@ -137,9 +137,10 @@ SearchResult ParseSearchResult(
|
||||
} break;
|
||||
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = data.c_messages_channelMessages();
|
||||
if (auto channel = peer->asChannel()) {
|
||||
const auto &d = data.c_messages_channelMessages();
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
channel->ptsReceived(d.vpts().v);
|
||||
channel->processTopics(d.vtopics());
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (ParseSearchResult)"));
|
||||
|
||||
@@ -85,6 +85,8 @@ namespace {
|
||||
|
||||
using ViewElement = HistoryView::Element;
|
||||
|
||||
constexpr auto kTopicsPinLimit = 5;
|
||||
|
||||
// s: box 100x100
|
||||
// m: box 320x320
|
||||
// x: box 800x800
|
||||
@@ -241,7 +243,7 @@ Session::Session(not_null<Main::Session*> session)
|
||||
, _chatsList(
|
||||
session,
|
||||
FilterId(),
|
||||
maxPinnedChatsLimitValue(nullptr, FilterId()))
|
||||
maxPinnedChatsLimitValue(nullptr))
|
||||
, _contactsList(Dialogs::SortMode::Name)
|
||||
, _contactsNoChatsList(Dialogs::SortMode::Name)
|
||||
, _ttlCheckTimer([=] { checkTTLs(); })
|
||||
@@ -1379,8 +1381,8 @@ void Session::setupUserIsContactViewer() {
|
||||
_contactsNoChatsList.addByName(history);
|
||||
}
|
||||
} else if (const auto history = historyLoaded(user)) {
|
||||
_contactsNoChatsList.del(history);
|
||||
_contactsList.del(history);
|
||||
_contactsNoChatsList.remove(history);
|
||||
_contactsList.remove(history);
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
@@ -1913,7 +1915,7 @@ MessageIdsList Session::itemOrItsGroup(not_null<HistoryItem*> item) const {
|
||||
}
|
||||
|
||||
void Session::setChatPinned(
|
||||
const Dialogs::Key &key,
|
||||
Dialogs::Key key,
|
||||
FilterId filterId,
|
||||
bool pinned) {
|
||||
Expects(key.entry()->folderKnown());
|
||||
@@ -1921,17 +1923,14 @@ void Session::setChatPinned(
|
||||
const auto list = (filterId
|
||||
? chatsFilters().chatsList(filterId)
|
||||
: chatsListFor(key.entry()))->pinned();
|
||||
if (const auto topic = key.topic()) {
|
||||
topic->forum()->unpinTopic();
|
||||
}
|
||||
list->setPinned(key, pinned);
|
||||
notifyPinnedDialogsOrderUpdated();
|
||||
}
|
||||
|
||||
void Session::setPinnedFromDialog(const Dialogs::Key &key, bool pinned) {
|
||||
void Session::setPinnedFromEntryList(Dialogs::Key key, bool pinned) {
|
||||
Expects(key.entry()->folderKnown());
|
||||
|
||||
const auto list = chatsList(key.entry()->folder())->pinned();
|
||||
const auto list = chatsListFor(key.entry())->pinned();
|
||||
if (pinned) {
|
||||
list->addPinned(key);
|
||||
} else {
|
||||
@@ -1960,6 +1959,13 @@ void Session::applyPinnedChats(
|
||||
notifyPinnedDialogsOrderUpdated();
|
||||
}
|
||||
|
||||
void Session::applyPinnedTopics(
|
||||
not_null<Data::Forum*> forum,
|
||||
const QVector<MTPint> &list) {
|
||||
forum->topicsList()->pinned()->applyList(forum, list);
|
||||
notifyPinnedDialogsOrderUpdated();
|
||||
}
|
||||
|
||||
void Session::applyDialogs(
|
||||
Data::Folder *requestFolder,
|
||||
const QVector<MTPMessage> &messages,
|
||||
@@ -1986,7 +1992,7 @@ void Session::applyDialog(
|
||||
|
||||
const auto history = this->history(peerId);
|
||||
history->applyDialog(requestFolder, data);
|
||||
setPinnedFromDialog(history, data.is_pinned());
|
||||
setPinnedFromEntryList(history, data.is_pinned());
|
||||
|
||||
if (const auto from = history->peer->migrateFrom()) {
|
||||
if (const auto historyFrom = historyLoaded(from)) {
|
||||
@@ -2007,37 +2013,63 @@ void Session::applyDialog(
|
||||
}
|
||||
const auto folder = processFolder(data.vfolder());
|
||||
folder->applyDialog(data);
|
||||
setPinnedFromDialog(folder, data.is_pinned());
|
||||
setPinnedFromEntryList(folder, data.is_pinned());
|
||||
}
|
||||
|
||||
int Session::pinnedCanPin(
|
||||
Data::Folder *folder,
|
||||
bool Session::pinnedCanPin(not_null<Data::Thread*> thread) const {
|
||||
if (const auto topic = thread->asTopic()) {
|
||||
const auto forum = topic->forum();
|
||||
return pinnedChatsOrder(forum).size() < pinnedChatsLimit(forum);
|
||||
}
|
||||
const auto folder = thread->folder();
|
||||
return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder);
|
||||
}
|
||||
|
||||
bool Session::pinnedCanPin(
|
||||
FilterId filterId,
|
||||
not_null<History*> history) const {
|
||||
if (!filterId) {
|
||||
const auto limit = pinnedChatsLimit(folder, filterId);
|
||||
return pinnedChatsOrder(folder, FilterId()).size() < limit;
|
||||
}
|
||||
Expects(filterId != 0);
|
||||
|
||||
const auto &list = chatsFilters().list();
|
||||
const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
|
||||
return (i == end(list))
|
||||
|| (i->always().contains(history))
|
||||
|| (i->always().size() < pinnedChatsLimit(folder, filterId));
|
||||
|| (i->always().size() < pinnedChatsLimit(filterId));
|
||||
}
|
||||
|
||||
int Session::pinnedChatsLimit(
|
||||
Data::Folder *folder,
|
||||
FilterId filterId) const {
|
||||
int Session::pinnedChatsLimit(Data::Folder *folder) const {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return filterId
|
||||
? limits.dialogFiltersChatsCurrent()
|
||||
: folder
|
||||
return folder
|
||||
? limits.dialogsFolderPinnedCurrent()
|
||||
: limits.dialogsPinnedCurrent();
|
||||
}
|
||||
|
||||
int Session::pinnedChatsLimit(FilterId filterId) const {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.dialogFiltersChatsCurrent();
|
||||
}
|
||||
|
||||
int Session::pinnedChatsLimit(not_null<Data::Forum*> forum) const {
|
||||
return kTopicsPinLimit;
|
||||
}
|
||||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
Data::Folder *folder) const {
|
||||
// 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
|
||||
// premium-ly added chats from the pinned list because of sync issues.
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
_session->account().appConfig().refreshed()
|
||||
) | rpl::map([=] {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return folder
|
||||
? limits.dialogsFolderPinnedPremium()
|
||||
: limits.dialogsPinnedPremium();
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
Data::Folder *folder,
|
||||
FilterId filterId) const {
|
||||
// Premium limit from appconfig.
|
||||
// We always use premium limit in the MainList limit producer,
|
||||
@@ -2047,21 +2079,28 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
_session->account().appConfig().refreshed()
|
||||
) | rpl::map([=] {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return filterId
|
||||
? limits.dialogFiltersChatsPremium()
|
||||
: folder
|
||||
? limits.dialogsFolderPinnedPremium()
|
||||
: limits.dialogsPinnedPremium();
|
||||
return limits.dialogFiltersChatsPremium();
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
not_null<Data::Forum*> forum) const {
|
||||
return rpl::single(pinnedChatsLimit(forum));
|
||||
}
|
||||
|
||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
Data::Folder *folder) const {
|
||||
return chatsList(folder)->pinned()->order();
|
||||
}
|
||||
|
||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
Data::Folder *folder,
|
||||
FilterId filterId) const {
|
||||
const auto list = filterId
|
||||
? chatsFilters().chatsList(filterId)
|
||||
: chatsList(folder);
|
||||
return list->pinned()->order();
|
||||
return chatsFilters().chatsList(filterId)->pinned()->order();
|
||||
}
|
||||
|
||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
not_null<Data::Forum*> forum) const {
|
||||
return forum->topicsList()->pinned()->order();
|
||||
}
|
||||
|
||||
void Session::clearPinnedChats(Data::Folder *folder) {
|
||||
@@ -2070,12 +2109,15 @@ void Session::clearPinnedChats(Data::Folder *folder) {
|
||||
|
||||
void Session::reorderTwoPinnedChats(
|
||||
FilterId filterId,
|
||||
const Dialogs::Key &key1,
|
||||
const Dialogs::Key &key2) {
|
||||
Dialogs::Key key1,
|
||||
Dialogs::Key key2) {
|
||||
Expects(key1.entry()->folderKnown() && key2.entry()->folderKnown());
|
||||
Expects(filterId || (key1.entry()->folder() == key2.entry()->folder()));
|
||||
|
||||
const auto list = filterId
|
||||
const auto topic = key1.topic();
|
||||
const auto list = topic
|
||||
? topic->forum()->topicsList()
|
||||
: filterId
|
||||
? chatsFilters().chatsList(filterId)
|
||||
: chatsList(key1.entry()->folder());
|
||||
list->pinned()->reorder(key1, key2);
|
||||
@@ -2158,6 +2200,7 @@ void Session::processExistingMessages(
|
||||
data.match([&](const MTPDmessages_channelMessages &data) {
|
||||
if (channel) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
channel->processTopics(data.vtopics());
|
||||
} else {
|
||||
LOG(("App Error: received messages.channelMessages!"));
|
||||
}
|
||||
@@ -2467,14 +2510,14 @@ bool Session::unreadBadgeMuted() const {
|
||||
return computeUnreadBadgeMuted(_chatsList.unreadState());
|
||||
}
|
||||
|
||||
int Session::unreadBadgeIgnoreOne(const Dialogs::Key &key) const {
|
||||
int Session::unreadBadgeIgnoreOne(Dialogs::Key key) const {
|
||||
const auto remove = (key && key.entry()->inChatList())
|
||||
? key.entry()->chatListUnreadState()
|
||||
: Dialogs::UnreadState();
|
||||
return computeUnreadBadge(_chatsList.unreadState() - remove);
|
||||
}
|
||||
|
||||
bool Session::unreadBadgeMutedIgnoreOne(const Dialogs::Key &key) const {
|
||||
bool Session::unreadBadgeMutedIgnoreOne(Dialogs::Key key) const {
|
||||
if (!Core::App().settings().includeMutedCounter()) {
|
||||
return false;
|
||||
}
|
||||
@@ -3954,7 +3997,7 @@ void Session::refreshChatListEntry(Dialogs::Key key) {
|
||||
return;
|
||||
} else if (event.existenceChanged) {
|
||||
const auto mainRow = entry->addToChatList(0, mainList);
|
||||
_contactsNoChatsList.del(key, mainRow);
|
||||
_contactsNoChatsList.remove(key, mainRow);
|
||||
} else {
|
||||
event.moved = entry->adjustByPosInChatList(0, mainList);
|
||||
}
|
||||
|
||||
@@ -230,16 +230,16 @@ public:
|
||||
[[nodiscard]] rpl::variable<bool> &contactsLoaded() {
|
||||
return _contactsLoaded;
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Data::Folder*> chatsListChanges() const {
|
||||
[[nodiscard]] rpl::producer<Folder*> chatsListChanges() const {
|
||||
return _chatsListChanged.events();
|
||||
}
|
||||
[[nodiscard]] bool chatsListLoaded(Data::Folder *folder = nullptr);
|
||||
[[nodiscard]] rpl::producer<Data::Folder*> chatsListLoadedEvents() const {
|
||||
[[nodiscard]] bool chatsListLoaded(Folder *folder = nullptr);
|
||||
[[nodiscard]] rpl::producer<Folder*> chatsListLoadedEvents() const {
|
||||
return _chatsListLoadedEvents.events();
|
||||
}
|
||||
void chatsListChanged(FolderId folderId);
|
||||
void chatsListChanged(Data::Folder *folder);
|
||||
void chatsListDone(Data::Folder *folder);
|
||||
void chatsListChanged(Folder *folder);
|
||||
void chatsListDone(Folder *folder);
|
||||
|
||||
void userIsBotChanged(not_null<UserData*> user);
|
||||
[[nodiscard]] rpl::producer<not_null<UserData*>> userIsBotChanges() const;
|
||||
@@ -339,36 +339,43 @@ public:
|
||||
void applyUpdate(const MTPDupdateChatDefaultBannedRights &update);
|
||||
|
||||
void applyDialogs(
|
||||
Data::Folder *requestFolder,
|
||||
Folder *requestFolder,
|
||||
const QVector<MTPMessage> &messages,
|
||||
const QVector<MTPDialog> &dialogs,
|
||||
std::optional<int> count = std::nullopt);
|
||||
|
||||
int pinnedCanPin(
|
||||
Data::Folder *folder,
|
||||
[[nodiscard]] bool pinnedCanPin(not_null<Thread*> thread) const;
|
||||
[[nodiscard]] bool pinnedCanPin(
|
||||
FilterId filterId,
|
||||
not_null<History*> history) const;
|
||||
int pinnedChatsLimit(
|
||||
Data::Folder *folder,
|
||||
[[nodiscard]] int pinnedChatsLimit(Folder *folder) const;
|
||||
[[nodiscard]] int pinnedChatsLimit(FilterId filterId) const;
|
||||
[[nodiscard]] int pinnedChatsLimit(not_null<Forum*> forum) const;
|
||||
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
Folder *folder) const;
|
||||
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
FilterId filterId) const;
|
||||
rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
Data::Folder *folder,
|
||||
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
not_null<Forum*> forum) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
Folder *folder) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
not_null<Forum*> forum) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
FilterId filterId) const;
|
||||
const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
Data::Folder *folder,
|
||||
FilterId filterId) const;
|
||||
void setChatPinned(
|
||||
const Dialogs::Key &key,
|
||||
FilterId filterId,
|
||||
bool pinned);
|
||||
void clearPinnedChats(Data::Folder *folder);
|
||||
void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned);
|
||||
void setPinnedFromEntryList(Dialogs::Key key, bool pinned);
|
||||
void clearPinnedChats(Folder *folder);
|
||||
void applyPinnedChats(
|
||||
Data::Folder *folder,
|
||||
Folder *folder,
|
||||
const QVector<MTPDialogPeer> &list);
|
||||
void applyPinnedTopics(
|
||||
not_null<Data::Forum*> forum,
|
||||
const QVector<MTPint> &list);
|
||||
void reorderTwoPinnedChats(
|
||||
FilterId filterId,
|
||||
const Dialogs::Key &key1,
|
||||
const Dialogs::Key &key2);
|
||||
Dialogs::Key key1,
|
||||
Dialogs::Key key2);
|
||||
|
||||
void setSuggestToGigagroup(not_null<ChannelData*> group, bool suggest);
|
||||
[[nodiscard]] bool suggestToGigagroup(
|
||||
@@ -464,9 +471,8 @@ public:
|
||||
|
||||
[[nodiscard]] int unreadBadge() const;
|
||||
[[nodiscard]] bool unreadBadgeMuted() const;
|
||||
[[nodiscard]] int unreadBadgeIgnoreOne(const Dialogs::Key &key) const;
|
||||
[[nodiscard]] bool unreadBadgeMutedIgnoreOne(
|
||||
const Dialogs::Key &key) const;
|
||||
[[nodiscard]] int unreadBadgeIgnoreOne(Dialogs::Key key) const;
|
||||
[[nodiscard]] bool unreadBadgeMutedIgnoreOne(Dialogs::Key key) const;
|
||||
[[nodiscard]] int unreadOnlyMutedBadge() const;
|
||||
[[nodiscard]] rpl::producer<> unreadBadgeChanges() const;
|
||||
void notifyUnreadBadgeChanged();
|
||||
@@ -572,7 +578,7 @@ public:
|
||||
not_null<PollData*> processPoll(const MTPPoll &data);
|
||||
not_null<PollData*> processPoll(const MTPDmessageMediaPoll &data);
|
||||
|
||||
[[nodiscard]] not_null<Data::CloudImage*> location(
|
||||
[[nodiscard]] not_null<CloudImage*> location(
|
||||
const LocationPoint &point);
|
||||
|
||||
void registerPhotoItem(
|
||||
@@ -655,9 +661,9 @@ public:
|
||||
[[nodiscard]] not_null<Dialogs::MainList*> chatsListFor(
|
||||
not_null<Dialogs::Entry*> entry);
|
||||
[[nodiscard]] not_null<Dialogs::MainList*> chatsList(
|
||||
Data::Folder *folder = nullptr);
|
||||
Folder *folder = nullptr);
|
||||
[[nodiscard]] not_null<const Dialogs::MainList*> chatsList(
|
||||
Data::Folder *folder = nullptr) const;
|
||||
Folder *folder = nullptr) const;
|
||||
[[nodiscard]] not_null<Dialogs::IndexedList*> contactsList();
|
||||
[[nodiscard]] not_null<Dialogs::IndexedList*> contactsNoChatsList();
|
||||
|
||||
@@ -727,9 +733,9 @@ private:
|
||||
int computeUnreadBadge(const Dialogs::UnreadState &state) const;
|
||||
bool computeUnreadBadgeMuted(const Dialogs::UnreadState &state) const;
|
||||
|
||||
void applyDialog(Data::Folder *requestFolder, const MTPDdialog &data);
|
||||
void applyDialog(Folder *requestFolder, const MTPDdialog &data);
|
||||
void applyDialog(
|
||||
Data::Folder *requestFolder,
|
||||
Folder *requestFolder,
|
||||
const MTPDdialogFolder &data);
|
||||
|
||||
const Messages *messagesList(PeerId peerId) const;
|
||||
@@ -818,8 +824,6 @@ private:
|
||||
PhotoData *photo,
|
||||
DocumentData *document);
|
||||
|
||||
void setPinnedFromDialog(const Dialogs::Key &key, bool pinned);
|
||||
|
||||
template <typename Method>
|
||||
void enumerateItemViews(
|
||||
not_null<const HistoryItem*> item,
|
||||
@@ -843,8 +847,8 @@ private:
|
||||
QPointer<Ui::BoxContent> _exportSuggestion;
|
||||
|
||||
rpl::variable<bool> _contactsLoaded = false;
|
||||
rpl::event_stream<Data::Folder*> _chatsListLoadedEvents;
|
||||
rpl::event_stream<Data::Folder*> _chatsListChanged;
|
||||
rpl::event_stream<Folder*> _chatsListLoadedEvents;
|
||||
rpl::event_stream<Folder*> _chatsListChanged;
|
||||
rpl::event_stream<not_null<UserData*>> _userIsBotChanges;
|
||||
rpl::event_stream<not_null<PeerData*>> _botCommandsChanges;
|
||||
rpl::event_stream<ItemVisibilityQuery> _itemVisibilityQueries;
|
||||
@@ -916,7 +920,7 @@ private:
|
||||
base::flat_set<not_null<ViewElement*>>> _webpageViews;
|
||||
std::unordered_map<
|
||||
LocationPoint,
|
||||
std::unique_ptr<Data::CloudImage>> _locations;
|
||||
std::unique_ptr<CloudImage>> _locations;
|
||||
std::unordered_map<
|
||||
PollId,
|
||||
std::unique_ptr<PollData>> _polls;
|
||||
|
||||
@@ -295,6 +295,7 @@ void WebPageData::ApplyChanges(
|
||||
const MTPDmessages_channelMessages &data) {
|
||||
if (channel) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
channel->processTopics(data.vtopics());
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages "
|
||||
"when no channel was passed! (WebPageData::ApplyChanges)"));
|
||||
|
||||
@@ -499,7 +499,9 @@ bool NotifySettings::silentPosts(not_null<const PeerData*> peer) const {
|
||||
}
|
||||
|
||||
NotifySound NotifySettings::sound(not_null<const PeerData*> peer) const {
|
||||
if (const auto sound = peer->notify().sound()) {
|
||||
// Explicitly ignore a notify sound for Saved Messages
|
||||
// to follow the global notify sound.
|
||||
if (const auto sound = peer->notify().sound(); !peer->isSelf() && sound) {
|
||||
return *sound;
|
||||
} else if (const auto sound = defaultSettings(peer).sound()) {
|
||||
return *sound;
|
||||
|
||||
@@ -54,10 +54,12 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
int MuteValue::until() const {
|
||||
constexpr auto kMax = std::numeric_limits<int>::max();
|
||||
|
||||
return forever
|
||||
? std::numeric_limits<int>::max()
|
||||
? kMax
|
||||
: (period > 0)
|
||||
? (base::unixtime::now() + period)
|
||||
? int(std::min(int64(base::unixtime::now()) + period, int64(kMax)))
|
||||
: unmute
|
||||
? 0
|
||||
: -1;
|
||||
|
||||
@@ -17,6 +17,10 @@ DialogRow {
|
||||
nameTop: pixels;
|
||||
textLeft: pixels;
|
||||
textTop: pixels;
|
||||
topicsSkip: pixels;
|
||||
topicsSkipBig: pixels;
|
||||
topicsHeight: pixels;
|
||||
unreadMarkDiameter: pixels;
|
||||
}
|
||||
|
||||
ForumTopicIcon {
|
||||
@@ -71,6 +75,21 @@ defaultDialogRow: DialogRow {
|
||||
textLeft: 68px;
|
||||
textTop: 34px;
|
||||
}
|
||||
forumDialogRow: DialogRow(defaultDialogRow) {
|
||||
height: 80px;
|
||||
textTop: 32px;
|
||||
topicsSkip: 8px;
|
||||
topicsSkipBig: 14px;
|
||||
topicsHeight: 21px;
|
||||
}
|
||||
|
||||
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
|
||||
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};
|
||||
forumDialogJumpArrowSkip: 8px;
|
||||
forumDialogJumpArrowLeft: 3px;
|
||||
forumDialogJumpArrowTop: 3px;
|
||||
forumDialogJumpPadding: margins(8px, 3px, 8px, 3px);
|
||||
forumDialogJumpRadius: 11px;
|
||||
|
||||
dialogsOnlineBadgeStroke: 2px;
|
||||
dialogsOnlineBadgeSize: 10px;
|
||||
@@ -148,6 +167,21 @@ dialogsTextPaletteArchiveActive: TextPalette(defaultTextPalette) {
|
||||
monoFg: dialogsTextFgActive;
|
||||
spoilerFg: dialogsTextFgActive;
|
||||
}
|
||||
dialogsTextPaletteInTopic: TextPalette(defaultTextPalette) {
|
||||
linkFg: dialogsNameFg;
|
||||
monoFg: dialogsTextFg;
|
||||
spoilerFg: dialogsTextFg;
|
||||
}
|
||||
dialogsTextPaletteInTopicOver: TextPalette(defaultTextPalette) {
|
||||
linkFg: dialogsNameFgOver;
|
||||
monoFg: dialogsTextFgOver;
|
||||
spoilerFg: dialogsTextFgOver;
|
||||
}
|
||||
dialogsTextPaletteInTopicActive: TextPalette(defaultTextPalette) {
|
||||
linkFg: dialogsNameFgActive;
|
||||
monoFg: dialogsTextFgActive;
|
||||
spoilerFg: dialogsTextFgActive;
|
||||
}
|
||||
|
||||
dialogsEmptyHeight: 160px;
|
||||
dialogsEmptySkip: 2px;
|
||||
@@ -286,6 +320,7 @@ dialogsForumIconOver: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgOver, po
|
||||
dialogsForumIconActive: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgActive, point(1px, 4px) }};
|
||||
dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }};
|
||||
dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }};
|
||||
dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }};
|
||||
|
||||
dialogsSendStateSkip: 20px;
|
||||
dialogsSendingIcon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }};
|
||||
@@ -440,6 +475,7 @@ forumTopicRow: DialogRow(defaultDialogRow) {
|
||||
nameTop: 7px;
|
||||
textLeft: 68px;
|
||||
textTop: 29px;
|
||||
unreadMarkDiameter: 8px;
|
||||
}
|
||||
forumTopicIconPosition: point(2px, 0px);
|
||||
editTopicTitleMargin: margins(70px, 2px, 22px, 18px);
|
||||
@@ -460,7 +496,3 @@ chooseTopicListItem: PeerListItem(defaultPeerListItem) {
|
||||
chooseTopicList: PeerList(defaultPeerList) {
|
||||
item: chooseTopicListItem;
|
||||
}
|
||||
|
||||
dialogsTopicArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgService }};
|
||||
dialogsTopicArrowSkip: 13px;
|
||||
dialogsTopicArrowTop: 4px;
|
||||
|
||||
@@ -300,10 +300,10 @@ PositionChange Entry::adjustByPosInChatList(
|
||||
not_null<MainList*> list) {
|
||||
const auto links = chatListLinks(filterId);
|
||||
Assert(links != nullptr);
|
||||
const auto from = links->main->pos();
|
||||
const auto from = links->main->top();
|
||||
list->indexed()->adjustByDate(*links);
|
||||
const auto to = links->main->pos();
|
||||
return { from, to };
|
||||
const auto to = links->main->top();
|
||||
return { .from = from, .to = to, .height = links->main->height() };
|
||||
}
|
||||
|
||||
void Entry::setChatListTimeId(TimeId date) {
|
||||
@@ -315,7 +315,7 @@ void Entry::setChatListTimeId(TimeId date) {
|
||||
}
|
||||
|
||||
int Entry::posInChatList(FilterId filterId) const {
|
||||
return mainChatListLink(filterId)->pos();
|
||||
return mainChatListLink(filterId)->index();
|
||||
}
|
||||
|
||||
not_null<Row*> Entry::addToChatList(
|
||||
|
||||
@@ -56,6 +56,7 @@ enum class SortMode {
|
||||
struct PositionChange {
|
||||
int from = -1;
|
||||
int to = -1;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
struct UnreadState {
|
||||
|
||||
@@ -134,7 +134,7 @@ void IndexedList::adjustByName(
|
||||
}
|
||||
for (auto ch : toRemove) {
|
||||
if (auto it = _index.find(ch); it != _index.cend()) {
|
||||
it->second.del(key, mainRow);
|
||||
it->second.remove(key, mainRow);
|
||||
}
|
||||
}
|
||||
if (!toAdd.empty()) {
|
||||
@@ -171,7 +171,7 @@ void IndexedList::adjustNames(
|
||||
history->removeChatListEntryByLetter(filterId, ch);
|
||||
}
|
||||
if (auto it = _index.find(ch); it != _index.cend()) {
|
||||
it->second.del(key, mainRow);
|
||||
it->second.remove(key, mainRow);
|
||||
}
|
||||
}
|
||||
for (auto ch : toAdd) {
|
||||
@@ -186,11 +186,11 @@ void IndexedList::adjustNames(
|
||||
}
|
||||
}
|
||||
|
||||
void IndexedList::del(Key key, Row *replacedBy) {
|
||||
if (_list.del(key, replacedBy)) {
|
||||
void IndexedList::remove(Key key, Row *replacedBy) {
|
||||
if (_list.remove(key, replacedBy)) {
|
||||
for (const auto &ch : key.entry()->chatListFirstLetters()) {
|
||||
if (auto it = _index.find(ch); it != _index.cend()) {
|
||||
it->second.del(key, replacedBy);
|
||||
if (const auto it = _index.find(ch); it != _index.cend()) {
|
||||
it->second.remove(key, replacedBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,39 +37,48 @@ public:
|
||||
not_null<PeerData*> peer,
|
||||
const base::flat_set<QChar> &oldChars);
|
||||
|
||||
void del(Key key, Row *replacedBy = nullptr);
|
||||
void remove(Key key, Row *replacedBy = nullptr);
|
||||
void clear();
|
||||
|
||||
const List &all() const {
|
||||
[[nodiscard]] const List &all() const {
|
||||
return _list;
|
||||
}
|
||||
const List *filtered(QChar ch) const {
|
||||
[[nodiscard]] const List *filtered(QChar ch) const {
|
||||
const auto i = _index.find(ch);
|
||||
return (i != _index.end()) ? &i->second : nullptr;
|
||||
}
|
||||
std::vector<not_null<Row*>> filtered(const QStringList &words) const;
|
||||
[[nodiscard]] std::vector<not_null<Row*>> filtered(
|
||||
const QStringList &words) const;
|
||||
|
||||
// Part of List interface is duplicated here for all() list.
|
||||
int size() const { return all().size(); }
|
||||
bool empty() const { return all().empty(); }
|
||||
bool contains(Key key) const { return all().contains(key); }
|
||||
Row *getRow(Key key) const { return all().getRow(key); }
|
||||
Row *rowAtY(int32 y, int32 h) const { return all().rowAtY(y, h); }
|
||||
[[nodiscard]] int size() const { return all().size(); }
|
||||
[[nodiscard]] bool empty() const { return all().empty(); }
|
||||
[[nodiscard]] int height() const { return all().height(); }
|
||||
[[nodiscard]] bool contains(Key key) const {
|
||||
return all().contains(key);
|
||||
}
|
||||
[[nodiscard]] Row *getRow(Key key) const { return all().getRow(key); }
|
||||
[[nodiscard]] Row *rowAtY(int y) const { return all().rowAtY(y); }
|
||||
|
||||
using iterator = List::iterator;
|
||||
using const_iterator = List::const_iterator;
|
||||
const_iterator cbegin() const { return all().cbegin(); }
|
||||
const_iterator cend() const { return all().cend(); }
|
||||
const_iterator begin() const { return all().cbegin(); }
|
||||
const_iterator end() const { return all().cend(); }
|
||||
iterator begin() { return all().begin(); }
|
||||
iterator end() { return all().end(); }
|
||||
const_iterator cfind(Row *value) const { return all().cfind(value); }
|
||||
const_iterator find(Row *value) const { return all().cfind(value); }
|
||||
iterator find(Row *value) { return all().find(value); }
|
||||
const_iterator cfind(int y, int h) const { return all().cfind(y, h); }
|
||||
const_iterator find(int y, int h) const { return all().cfind(y, h); }
|
||||
iterator find(int y, int h) { return all().find(y, h); }
|
||||
[[nodiscard]] const_iterator cbegin() const { return all().cbegin(); }
|
||||
[[nodiscard]] const_iterator cend() const { return all().cend(); }
|
||||
[[nodiscard]] const_iterator begin() const { return all().cbegin(); }
|
||||
[[nodiscard]] const_iterator end() const { return all().cend(); }
|
||||
[[nodiscard]] iterator begin() { return all().begin(); }
|
||||
[[nodiscard]] iterator end() { return all().end(); }
|
||||
[[nodiscard]] const_iterator cfind(Row *value) const {
|
||||
return all().cfind(value);
|
||||
}
|
||||
[[nodiscard]] const_iterator find(Row *value) const {
|
||||
return all().cfind(value);
|
||||
}
|
||||
[[nodiscard]] iterator find(Row *value) { return all().find(value); }
|
||||
[[nodiscard]] const_iterator findByY(int y) const {
|
||||
return all().findByY(y);
|
||||
}
|
||||
[[nodiscard]] iterator findByY(int y) { return all().findByY(y); }
|
||||
|
||||
private:
|
||||
void adjustByName(
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Dialogs::Ui {
|
||||
using namespace ::Ui;
|
||||
class VideoUserpic;
|
||||
struct PaintContext;
|
||||
struct TopicJumpCache;
|
||||
} // namespace Dialogs::Ui
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -112,7 +113,9 @@ public:
|
||||
void refreshEmptyLabel();
|
||||
void resizeEmptyLabel();
|
||||
|
||||
bool chooseRow(Qt::KeyboardModifiers modifiers = {});
|
||||
bool chooseRow(
|
||||
Qt::KeyboardModifiers modifiers = {},
|
||||
MsgId pressedTopicRootId = {});
|
||||
|
||||
void scrollToEntry(const RowDescriptor &entry);
|
||||
|
||||
@@ -192,6 +195,22 @@ private:
|
||||
EmptyForum,
|
||||
};
|
||||
|
||||
struct PinnedRow {
|
||||
anim::value yadd;
|
||||
crl::time animStartTime = 0;
|
||||
};
|
||||
|
||||
struct FilterResult {
|
||||
FilterResult(not_null<Row*> row) : row(row) {
|
||||
}
|
||||
|
||||
not_null<Row*> row;
|
||||
int top = 0;
|
||||
|
||||
[[nodiscard]] Key key() const;
|
||||
[[nodiscard]] int bottom() const;
|
||||
};
|
||||
|
||||
Main::Session &session() const;
|
||||
|
||||
void dialogRowReplaced(Row *oldRow, Row *newRow);
|
||||
@@ -220,8 +239,11 @@ private:
|
||||
void clearIrrelevantState();
|
||||
void selectByMouse(QPoint globalPosition);
|
||||
void loadPeerPhotos();
|
||||
void scrollToItem(int top, int height);
|
||||
void scrollToDefaultSelected();
|
||||
void setCollapsedPressed(int pressed);
|
||||
void setPressed(Row *pressed);
|
||||
void setPressed(Row *pressed, bool pressedTopicJump);
|
||||
void clearPressed();
|
||||
void setHashtagPressed(int pressed);
|
||||
void setFilteredPressed(int pressed);
|
||||
void setPeerSearchPressed(int pressed);
|
||||
@@ -283,13 +305,18 @@ private:
|
||||
void fillSupportSearchMenu(not_null<Ui::PopupMenu*> menu);
|
||||
void fillArchiveSearchMenu(not_null<Ui::PopupMenu*> menu);
|
||||
|
||||
int dialogsOffset() const;
|
||||
int fixedOnTopCount() const;
|
||||
int pinnedOffset() const;
|
||||
int filteredOffset() const;
|
||||
int peerSearchOffset() const;
|
||||
int searchedOffset() const;
|
||||
int searchInChatSkip() const;
|
||||
void refreshShownList();
|
||||
[[nodiscard]] int skipTopHeight() const;
|
||||
[[nodiscard]] int dialogsOffset() const;
|
||||
[[nodiscard]] int shownHeight(int till = -1) const;
|
||||
[[nodiscard]] int fixedOnTopCount() const;
|
||||
[[nodiscard]] int pinnedOffset() const;
|
||||
[[nodiscard]] int filteredOffset() const;
|
||||
[[nodiscard]] int filteredIndex(int y) const;
|
||||
[[nodiscard]] int filteredHeight(int till = -1) const;
|
||||
[[nodiscard]] int peerSearchOffset() const;
|
||||
[[nodiscard]] int searchedOffset() const;
|
||||
[[nodiscard]] int searchInChatSkip() const;
|
||||
|
||||
void paintCollapsedRows(
|
||||
Painter &p,
|
||||
@@ -339,13 +366,13 @@ private:
|
||||
Ui::VideoUserpic *validateVideoUserpic(not_null<Row*> row);
|
||||
Ui::VideoUserpic *validateVideoUserpic(not_null<History*> history);
|
||||
|
||||
Row *shownRowByKey(Key key);
|
||||
void clearSearchResults(bool clearPeerSearchResults = true);
|
||||
void updateSelectedRow(Key key = Key());
|
||||
void trackSearchResultsHistory(not_null<History*> history);
|
||||
void trackSearchResultsForum(Data::Forum *forum);
|
||||
|
||||
[[nodiscard]] not_null<IndexedList*> shownDialogs() const;
|
||||
|
||||
[[nodiscard]] const std::vector<Key> &pinnedChatsOrder() const;
|
||||
void checkReorderPinnedStart(QPoint localPosition);
|
||||
int updateReorderIndexGetCount();
|
||||
bool updateReorderPinned(QPoint localPosition);
|
||||
@@ -357,8 +384,12 @@ private:
|
||||
void handleChatListEntryRefreshes();
|
||||
void moveCancelSearchButtons();
|
||||
|
||||
void saveChatsFilterScrollState(FilterId filterId);
|
||||
void restoreChatsFilterScrollState(FilterId filterId);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
not_null<IndexedList*> _shownList;
|
||||
FilterId _filterId = 0;
|
||||
bool _mouseSelection = false;
|
||||
std::optional<QPoint> _lastMousePosition;
|
||||
@@ -370,20 +401,20 @@ private:
|
||||
|
||||
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
|
||||
not_null<const style::DialogRow*> _st;
|
||||
mutable std::unique_ptr<Ui::TopicJumpCache> _topicJumpCache;
|
||||
int _collapsedSelected = -1;
|
||||
int _collapsedPressed = -1;
|
||||
int _skipTopDialogs = 0;
|
||||
bool _skipTopDialog = false;
|
||||
Row *_selected = nullptr;
|
||||
Row *_pressed = nullptr;
|
||||
MsgId _pressedTopicJumpRootId;
|
||||
bool _selectedTopicJump = false;
|
||||
bool _pressedTopicJump = false;
|
||||
|
||||
Row *_dragging = nullptr;
|
||||
int _draggingIndex = -1;
|
||||
int _aboveIndex = -1;
|
||||
QPoint _dragStart;
|
||||
struct PinnedRow {
|
||||
anim::value yadd;
|
||||
crl::time animStartTime = 0;
|
||||
};
|
||||
std::vector<PinnedRow> _pinnedRows;
|
||||
Ui::Animations::Basic _pinnedShiftAnimation;
|
||||
base::flat_set<Key> _pinnedOnDragStart;
|
||||
@@ -401,7 +432,7 @@ private:
|
||||
bool _hashtagDeleteSelected = false;
|
||||
bool _hashtagDeletePressed = false;
|
||||
|
||||
std::vector<not_null<Row*>> _filterResults;
|
||||
std::vector<FilterResult> _filterResults;
|
||||
base::flat_map<Key, std::unique_ptr<Row>> _filterResultsGlobal;
|
||||
int _filteredSelected = -1;
|
||||
int _filteredPressed = -1;
|
||||
|
||||
@@ -39,13 +39,13 @@ public:
|
||||
explicit operator bool() const {
|
||||
return (_value != nullptr);
|
||||
}
|
||||
not_null<Entry*> entry() const;
|
||||
History *history() const;
|
||||
Data::Folder *folder() const;
|
||||
Data::ForumTopic *topic() const;
|
||||
Data::Thread *thread() const;
|
||||
History *owningHistory() const;
|
||||
PeerData *peer() const;
|
||||
[[nodiscard]] not_null<Entry*> entry() const;
|
||||
[[nodiscard]] History *history() const;
|
||||
[[nodiscard]] Data::Folder *folder() const;
|
||||
[[nodiscard]] Data::ForumTopic *topic() const;
|
||||
[[nodiscard]] Data::Thread *thread() const;
|
||||
[[nodiscard]] History *owningHistory() const;
|
||||
[[nodiscard]] PeerData *peer() const;
|
||||
|
||||
friend inline constexpr auto operator<=>(Key, Key) noexcept = default;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ List::List(SortMode sortMode, FilterId filterId)
|
||||
|
||||
List::const_iterator List::cfind(Row *value) const {
|
||||
return value
|
||||
? (cbegin() + value->pos())
|
||||
? (cbegin() + value->index())
|
||||
: cend();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ not_null<Row*> List::addToEnd(Key key) {
|
||||
}
|
||||
const auto result = _rowByKey.emplace(
|
||||
key,
|
||||
std::make_unique<Row>(key, _rows.size())
|
||||
std::make_unique<Row>(key, _rows.size(), height())
|
||||
).first->second.get();
|
||||
_rows.emplace_back(result);
|
||||
if (_sortMode == SortMode::Date) {
|
||||
@@ -60,10 +60,10 @@ not_null<Row*> List::addByName(Key key) {
|
||||
}
|
||||
|
||||
void List::adjustByName(not_null<Row*> row) {
|
||||
Expects(row->pos() >= 0 && row->pos() < _rows.size());
|
||||
Expects(row->index() >= 0 && row->index() < _rows.size());
|
||||
|
||||
const auto &key = row->entry()->chatListNameSortKey();
|
||||
const auto index = row->pos();
|
||||
const auto index = row->index();
|
||||
const auto i = _rows.begin() + index;
|
||||
const auto before = std::find_if(i + 1, _rows.end(), [&](Row *row) {
|
||||
return row->entry()->chatListNameSortKey().compare(key) >= 0;
|
||||
@@ -85,7 +85,7 @@ void List::adjustByDate(not_null<Row*> row) {
|
||||
Expects(_sortMode == SortMode::Date);
|
||||
|
||||
const auto key = row->sortKey(_filterId);
|
||||
const auto index = row->pos();
|
||||
const auto index = row->index();
|
||||
const auto i = _rows.begin() + index;
|
||||
const auto before = std::find_if(i + 1, _rows.end(), [&](Row *row) {
|
||||
return (row->sortKey(_filterId) <= key);
|
||||
@@ -108,7 +108,7 @@ bool List::moveToTop(Key key) {
|
||||
if (i == _rowByKey.cend()) {
|
||||
return false;
|
||||
}
|
||||
const auto index = i->second->pos();
|
||||
const auto index = i->second->index();
|
||||
const auto begin = _rows.begin();
|
||||
rotate(begin, begin + index, begin + index + 1);
|
||||
return true;
|
||||
@@ -118,16 +118,20 @@ void List::rotate(
|
||||
std::vector<not_null<Row*>>::iterator first,
|
||||
std::vector<not_null<Row*>>::iterator middle,
|
||||
std::vector<not_null<Row*>>::iterator last) {
|
||||
auto top = (*first)->top();
|
||||
std::rotate(first, middle, last);
|
||||
|
||||
auto count = (last - first);
|
||||
auto index = (first - _rows.begin());
|
||||
while (count--) {
|
||||
(*first++)->_pos = index++;
|
||||
const auto row = *first++;
|
||||
row->_index = index++;
|
||||
row->_top = top;
|
||||
top += row->height();
|
||||
}
|
||||
}
|
||||
|
||||
bool List::del(Key key, Row *replacedBy) {
|
||||
bool List::remove(Key key, Row *replacedBy) {
|
||||
auto i = _rowByKey.find(key);
|
||||
if (i == _rowByKey.cend()) {
|
||||
return false;
|
||||
@@ -136,13 +140,34 @@ bool List::del(Key key, Row *replacedBy) {
|
||||
const auto row = i->second.get();
|
||||
row->entry()->owner().dialogsRowReplaced({ row, replacedBy });
|
||||
|
||||
const auto index = row->pos();
|
||||
auto top = row->top();
|
||||
const auto index = row->index();
|
||||
_rows.erase(_rows.begin() + index);
|
||||
for (auto i = index, count = int(_rows.size()); i != count; ++i) {
|
||||
_rows[i]->_pos = i;
|
||||
const auto row = _rows[i];
|
||||
row->_index = i;
|
||||
row->_top = top;
|
||||
top += row->height();
|
||||
}
|
||||
_rowByKey.erase(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
Row *List::rowAtY(int y) const {
|
||||
const auto i = findByY(y);
|
||||
if (i == cend()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto row = *i;
|
||||
const auto top = row->top();
|
||||
const auto bottom = top + row->height();
|
||||
return (top <= y && bottom > y) ? row.get() : nullptr;
|
||||
}
|
||||
|
||||
List::iterator List::findByY(int y) const {
|
||||
return ranges::lower_bound(_rows, y, ranges::less(), [](const Row *row) {
|
||||
return row->top() + row->height();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
|
||||
@@ -23,52 +23,48 @@ public:
|
||||
List &operator=(List &&other) = default;
|
||||
~List() = default;
|
||||
|
||||
int size() const {
|
||||
[[nodiscard]] int size() const {
|
||||
return _rows.size();
|
||||
}
|
||||
bool empty() const {
|
||||
[[nodiscard]] bool empty() const {
|
||||
return _rows.empty();
|
||||
}
|
||||
bool contains(Key key) const {
|
||||
[[nodiscard]] int height() const {
|
||||
return _rows.empty()
|
||||
? 0
|
||||
: (_rows.back()->top() + _rows.back()->height());
|
||||
}
|
||||
[[nodiscard]] bool contains(Key key) const {
|
||||
return _rowByKey.find(key) != _rowByKey.end();
|
||||
}
|
||||
Row *getRow(Key key) const {
|
||||
[[nodiscard]] Row *getRow(Key key) const {
|
||||
const auto i = _rowByKey.find(key);
|
||||
return (i != _rowByKey.end()) ? i->second.get() : nullptr;
|
||||
}
|
||||
Row *rowAtY(int y, int h) const {
|
||||
const auto i = cfind(y, h);
|
||||
if (i == cend() || (*i)->pos() != ((y > 0) ? (y / h) : 0)) {
|
||||
return nullptr;
|
||||
}
|
||||
return *i;
|
||||
}
|
||||
[[nodiscard]] Row *rowAtY(int y) const;
|
||||
|
||||
not_null<Row*> addToEnd(Key key);
|
||||
Row *adjustByName(Key key);
|
||||
not_null<Row*> addByName(Key key);
|
||||
bool moveToTop(Key key);
|
||||
void adjustByDate(not_null<Row*> row);
|
||||
bool del(Key key, Row *replacedBy = nullptr);
|
||||
bool remove(Key key, Row *replacedBy = nullptr);
|
||||
|
||||
using const_iterator = std::vector<not_null<Row*>>::const_iterator;
|
||||
using iterator = const_iterator;
|
||||
|
||||
const_iterator cbegin() const { return _rows.cbegin(); }
|
||||
const_iterator cend() const { return _rows.cend(); }
|
||||
const_iterator begin() const { return cbegin(); }
|
||||
const_iterator end() const { return cend(); }
|
||||
iterator begin() { return cbegin(); }
|
||||
iterator end() { return cend(); }
|
||||
const_iterator cfind(Row *value) const;
|
||||
const_iterator find(Row *value) const { return cfind(value); }
|
||||
iterator find(Row *value) { return cfind(value); }
|
||||
const_iterator cfind(int y, int h) const {
|
||||
const auto index = std::max(y, 0) / h;
|
||||
return _rows.begin() + std::min(index, size());
|
||||
[[nodiscard]] const_iterator cbegin() const { return _rows.cbegin(); }
|
||||
[[nodiscard]] const_iterator cend() const { return _rows.cend(); }
|
||||
[[nodiscard]] const_iterator begin() const { return cbegin(); }
|
||||
[[nodiscard]] const_iterator end() const { return cend(); }
|
||||
[[nodiscard]] iterator begin() { return cbegin(); }
|
||||
[[nodiscard]] iterator end() { return cend(); }
|
||||
[[nodiscard]] const_iterator cfind(Row *value) const;
|
||||
[[nodiscard]] const_iterator find(Row *value) const {
|
||||
return cfind(value);
|
||||
}
|
||||
const_iterator find(int y, int h) const { return cfind(y, h); }
|
||||
iterator find(int y, int h) { return cfind(y, h); }
|
||||
[[nodiscard]] iterator find(Row *value) { return cfind(value); }
|
||||
[[nodiscard]] iterator findByY(int y) const;
|
||||
|
||||
private:
|
||||
void adjustByName(not_null<Row*> row);
|
||||
|
||||
@@ -89,7 +89,7 @@ void MainList::clear() {
|
||||
_cloudListSize = 0;
|
||||
}
|
||||
|
||||
RowsByLetter MainList::addEntry(const Key &key) {
|
||||
RowsByLetter MainList::addEntry(Key key) {
|
||||
const auto result = _all.addToEnd(key);
|
||||
|
||||
const auto unread = key.entry()->chatListUnreadState();
|
||||
@@ -99,8 +99,8 @@ RowsByLetter MainList::addEntry(const Key &key) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void MainList::removeEntry(const Key &key) {
|
||||
_all.del(key);
|
||||
void MainList::removeEntry(Key key) {
|
||||
_all.remove(key);
|
||||
|
||||
const auto unread = key.entry()->chatListUnreadState();
|
||||
unreadEntryChanged(unread, false);
|
||||
|
||||
@@ -33,8 +33,8 @@ public:
|
||||
void setAllAreMuted(bool allAreMuted = true);
|
||||
void clear();
|
||||
|
||||
RowsByLetter addEntry(const Key &key);
|
||||
void removeEntry(const Key &key);
|
||||
RowsByLetter addEntry(Key key);
|
||||
void removeEntry(Key key);
|
||||
|
||||
void unreadStateChanged(
|
||||
const UnreadState &wasState,
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
#include "history/history.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_forum.h"
|
||||
|
||||
namespace Dialogs {
|
||||
|
||||
@@ -30,13 +31,13 @@ void PinnedList::setLimit(int limit) {
|
||||
applyLimit(_limit);
|
||||
}
|
||||
|
||||
void PinnedList::addPinned(const Key &key) {
|
||||
void PinnedList::addPinned(Key key) {
|
||||
Expects(key.entry()->folderKnown());
|
||||
|
||||
addPinnedGetPosition(key);
|
||||
}
|
||||
|
||||
int PinnedList::addPinnedGetPosition(const Key &key) {
|
||||
int PinnedList::addPinnedGetPosition(Key key) {
|
||||
const auto already = ranges::find(_data, key);
|
||||
if (already != end(_data)) {
|
||||
return already - begin(_data);
|
||||
@@ -48,7 +49,7 @@ int PinnedList::addPinnedGetPosition(const Key &key) {
|
||||
return position;
|
||||
}
|
||||
|
||||
void PinnedList::setPinned(const Key &key, bool pinned) {
|
||||
void PinnedList::setPinned(Key key, bool pinned) {
|
||||
Expects(key.entry()->folderKnown() || _filterId != 0);
|
||||
|
||||
if (pinned) {
|
||||
@@ -97,6 +98,15 @@ void PinnedList::applyList(
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedList::applyList(
|
||||
not_null<Data::Forum*> forum,
|
||||
const QVector<MTPint> &list) {
|
||||
clear();
|
||||
for (const auto &topicId : list) {
|
||||
addPinned(forum->topicFor(topicId.v));
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedList::applyList(const std::vector<not_null<History*>> &list) {
|
||||
Expects(_filterId != 0);
|
||||
|
||||
@@ -118,7 +128,7 @@ void PinnedList::applyList(const std::vector<not_null<History*>> &list) {
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedList::reorder(const Key &key1, const Key &key2) {
|
||||
void PinnedList::reorder(Key key1, Key key2) {
|
||||
const auto index1 = ranges::find(_data, key1) - begin(_data);
|
||||
const auto index2 = ranges::find(_data, key2) - begin(_data);
|
||||
Assert(index1 >= 0 && index1 < _data.size());
|
||||
|
||||
@@ -11,6 +11,7 @@ class History;
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
class Forum;
|
||||
} // namespace Data
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -25,25 +26,28 @@ public:
|
||||
|
||||
// Places on the last place in the list otherwise.
|
||||
// Does nothing if already pinned.
|
||||
void addPinned(const Key &key);
|
||||
void addPinned(Key key);
|
||||
|
||||
// if (pinned) places on the first place in the list.
|
||||
void setPinned(const Key &key, bool pinned);
|
||||
void setPinned(Key key, bool pinned);
|
||||
|
||||
void clear();
|
||||
|
||||
void applyList(
|
||||
not_null<Data::Session*> owner,
|
||||
const QVector<MTPDialogPeer> &list);
|
||||
void applyList(
|
||||
not_null<Data::Forum*> forum,
|
||||
const QVector<MTPint> &list);
|
||||
void applyList(const std::vector<not_null<History*>> &list);
|
||||
void reorder(const Key &key1, const Key &key2);
|
||||
void reorder(Key key1, Key key2);
|
||||
|
||||
const std::vector<Key> &order() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
private:
|
||||
int addPinnedGetPosition(const Key &key);
|
||||
int addPinnedGetPosition(Key key);
|
||||
void applyLimit(int limit);
|
||||
|
||||
FilterId _filterId = 0;
|
||||
|
||||
@@ -26,67 +26,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace Dialogs {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] TextWithEntities ComposeFolderListEntryText(
|
||||
not_null<Data::Folder*> folder) {
|
||||
const auto &list = folder->lastHistories();
|
||||
if (list.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto count = std::max(
|
||||
int(list.size()),
|
||||
folder->chatsList()->fullSize().current());
|
||||
|
||||
const auto throwAwayLastName = (list.size() > 1)
|
||||
&& (count == list.size() + 1);
|
||||
auto &&peers = ranges::views::all(
|
||||
list
|
||||
) | ranges::views::take(
|
||||
list.size() - (throwAwayLastName ? 1 : 0)
|
||||
);
|
||||
const auto wrapName = [](not_null<History*> history) {
|
||||
const auto name = history->peer->name();
|
||||
return TextWithEntities{
|
||||
.text = name,
|
||||
.entities = (history->chatListBadgesState().unread
|
||||
? EntitiesInText{
|
||||
{ EntityType::Semibold, 0, int(name.size()), QString() },
|
||||
{ EntityType::PlainLink, 0, int(name.size()), QString() },
|
||||
}
|
||||
: EntitiesInText{}),
|
||||
};
|
||||
};
|
||||
const auto shown = int(peers.size());
|
||||
const auto accumulated = [&] {
|
||||
Expects(shown > 0);
|
||||
|
||||
auto i = peers.begin();
|
||||
auto result = wrapName(*i);
|
||||
for (++i; i != peers.end(); ++i) {
|
||||
result = tr::lng_archived_last_list(
|
||||
tr::now,
|
||||
lt_accumulated,
|
||||
result,
|
||||
lt_chat,
|
||||
wrapName(*i),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
return (shown < count)
|
||||
? tr::lng_archived_last(
|
||||
tr::now,
|
||||
lt_count,
|
||||
(count - shown),
|
||||
lt_chats,
|
||||
accumulated,
|
||||
Ui::Text::WithEntities)
|
||||
: accumulated;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BasicRow::BasicRow() = default;
|
||||
BasicRow::~BasicRow() = default;
|
||||
@@ -96,15 +35,30 @@ void BasicRow::addRipple(
|
||||
QSize size,
|
||||
Fn<void()> updateCallback) {
|
||||
if (!_ripple) {
|
||||
auto mask = Ui::RippleAnimation::RectMask(size);
|
||||
_ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::dialogsRipple,
|
||||
std::move(mask),
|
||||
addRippleWithMask(
|
||||
origin,
|
||||
Ui::RippleAnimation::RectMask(size),
|
||||
std::move(updateCallback));
|
||||
} else {
|
||||
_ripple->add(origin);
|
||||
}
|
||||
}
|
||||
|
||||
void BasicRow::addRippleWithMask(
|
||||
QPoint origin,
|
||||
QImage mask,
|
||||
Fn<void()> updateCallback) {
|
||||
_ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::dialogsRipple,
|
||||
std::move(mask),
|
||||
std::move(updateCallback));
|
||||
_ripple->add(origin);
|
||||
}
|
||||
|
||||
void BasicRow::clearRipple() {
|
||||
_ripple = nullptr;
|
||||
}
|
||||
|
||||
void BasicRow::stopLastRipple() {
|
||||
if (_ripple) {
|
||||
_ripple->lastStop();
|
||||
@@ -143,9 +97,16 @@ void BasicRow::paintUserpic(
|
||||
context.paused);
|
||||
}
|
||||
|
||||
Row::Row(Key key, int pos) : _id(key), _pos(pos) {
|
||||
Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) {
|
||||
if (const auto history = key.history()) {
|
||||
updateCornerBadgeShown(history->peer);
|
||||
_height = history->peer->isForum()
|
||||
? st::forumDialogRow.height
|
||||
: st::defaultDialogRow.height;
|
||||
} else if (key.folder()) {
|
||||
_height = st::defaultDialogRow.height;
|
||||
} else {
|
||||
_height = st::forumTopicRow.height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,30 +114,14 @@ uint64 Row::sortKey(FilterId filterId) const {
|
||||
return _id.entry()->sortKeyInChatList(filterId);
|
||||
}
|
||||
|
||||
void Row::validateListEntryCache() const {
|
||||
const auto folder = _id.folder();
|
||||
if (!folder) {
|
||||
return;
|
||||
}
|
||||
const auto version = folder->chatListViewVersion();
|
||||
if (_listEntryCacheVersion == version) {
|
||||
return;
|
||||
}
|
||||
_listEntryCacheVersion = version;
|
||||
_listEntryCache.setMarkedText(
|
||||
st::dialogsTextStyle,
|
||||
ComposeFolderListEntryText(folder),
|
||||
// Use rich options as long as the entry text does not have user text.
|
||||
Ui::ItemTextDefaultOptions());
|
||||
}
|
||||
|
||||
void Row::setCornerBadgeShown(
|
||||
bool shown,
|
||||
Fn<void()> updateCallback) const {
|
||||
if (_cornerBadgeShown == shown) {
|
||||
const auto value = (shown ? 1 : 0);
|
||||
if (_cornerBadgeShown == value) {
|
||||
return;
|
||||
}
|
||||
_cornerBadgeShown = shown;
|
||||
const_cast<Row*>(this)->_cornerBadgeShown = value;
|
||||
if (_cornerBadgeUserpic && _cornerBadgeUserpic->animation.animating()) {
|
||||
_cornerBadgeUserpic->animation.change(
|
||||
_cornerBadgeShown ? 1. : 0.,
|
||||
@@ -350,6 +295,46 @@ void Row::paintUserpic(
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
bool Row::lookupIsInTopicJump(int x, int y) const {
|
||||
const auto history = this->history();
|
||||
return history && history->lastItemDialogsView().isInTopicJump(x, y);
|
||||
}
|
||||
|
||||
void Row::stopLastRipple() {
|
||||
BasicRow::stopLastRipple();
|
||||
const auto history = this->history();
|
||||
const auto view = history ? &history->lastItemDialogsView() : nullptr;
|
||||
if (view) {
|
||||
view->stopLastRipple();
|
||||
}
|
||||
}
|
||||
|
||||
void Row::addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
Fn<void()> updateCallback) {
|
||||
const auto history = this->history();
|
||||
const auto view = history ? &history->lastItemDialogsView() : nullptr;
|
||||
if (view) {
|
||||
view->addTopicJumpRipple(
|
||||
origin,
|
||||
topicJumpCache,
|
||||
std::move(updateCallback));
|
||||
_topicJumpRipple = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Row::clearTopicJumpRipple() {
|
||||
if (_topicJumpRipple) {
|
||||
clearRipple();
|
||||
_topicJumpRipple = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool Row::topicJumpRipple() const {
|
||||
return _topicJumpRipple != 0;
|
||||
}
|
||||
|
||||
FakeRow::FakeRow(
|
||||
Key searchInChat,
|
||||
not_null<HistoryItem*> item,
|
||||
|
||||
@@ -16,6 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
class History;
|
||||
class HistoryItem;
|
||||
|
||||
namespace style {
|
||||
struct DialogRow;
|
||||
} // namespace style
|
||||
|
||||
namespace Data {
|
||||
class CloudImageView;
|
||||
} // namespace Data
|
||||
@@ -29,6 +33,7 @@ using namespace ::Ui;
|
||||
class RowPainter;
|
||||
class VideoUserpic;
|
||||
struct PaintContext;
|
||||
struct TopicJumpCache;
|
||||
} // namespace Dialogs::Ui
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -48,7 +53,12 @@ public:
|
||||
const Ui::PaintContext &context) const;
|
||||
|
||||
void addRipple(QPoint origin, QSize size, Fn<void()> updateCallback);
|
||||
void stopLastRipple();
|
||||
virtual void stopLastRipple();
|
||||
void addRippleWithMask(
|
||||
QPoint origin,
|
||||
QImage mask,
|
||||
Fn<void()> updateCallback);
|
||||
void clearRipple();
|
||||
|
||||
void paintRipple(
|
||||
QPainter &p,
|
||||
@@ -57,7 +67,8 @@ public:
|
||||
int outerWidth,
|
||||
const QColor *colorOverride = nullptr) const;
|
||||
|
||||
std::shared_ptr<Data::CloudImageView> &userpicView() const {
|
||||
[[nodiscard]] auto userpicView() const
|
||||
-> std::shared_ptr<Data::CloudImageView> & {
|
||||
return _userpic;
|
||||
}
|
||||
|
||||
@@ -68,11 +79,18 @@ private:
|
||||
};
|
||||
|
||||
class List;
|
||||
class Row : public BasicRow {
|
||||
class Row final : public BasicRow {
|
||||
public:
|
||||
explicit Row(std::nullptr_t) {
|
||||
}
|
||||
Row(Key key, int pos);
|
||||
Row(Key key, int index, int top);
|
||||
|
||||
[[nodiscard]] int top() const {
|
||||
return _top;
|
||||
}
|
||||
[[nodiscard]] int height() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
void updateCornerBadgeShown(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -84,6 +102,15 @@ public:
|
||||
History *historyForCornerBadge,
|
||||
const Ui::PaintContext &context) const final override;
|
||||
|
||||
[[nodiscard]] bool lookupIsInTopicJump(int x, int y) const;
|
||||
void stopLastRipple() override;
|
||||
void addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
Fn<void()> updateCallback);
|
||||
void clearTopicJumpRipple();
|
||||
[[nodiscard]] bool topicJumpRipple() const;
|
||||
|
||||
[[nodiscard]] Key key() const {
|
||||
return _id;
|
||||
}
|
||||
@@ -102,16 +129,11 @@ public:
|
||||
[[nodiscard]] not_null<Entry*> entry() const {
|
||||
return _id.entry();
|
||||
}
|
||||
[[nodiscard]] int pos() const {
|
||||
return _pos;
|
||||
[[nodiscard]] int index() const {
|
||||
return _index;
|
||||
}
|
||||
[[nodiscard]] uint64 sortKey(FilterId filterId) const;
|
||||
|
||||
void validateListEntryCache() const;
|
||||
[[nodiscard]] const Ui::Text::String &listEntryCache() const {
|
||||
return _listEntryCache;
|
||||
}
|
||||
|
||||
// for any attached data, for example View in contacts list
|
||||
void *attached = nullptr;
|
||||
|
||||
@@ -139,11 +161,12 @@ private:
|
||||
const Ui::PaintContext &context);
|
||||
|
||||
Key _id;
|
||||
int _pos = 0;
|
||||
mutable uint32 _listEntryCacheVersion = 0;
|
||||
mutable Ui::Text::String _listEntryCache;
|
||||
mutable std::unique_ptr<CornerBadgeUserpic> _cornerBadgeUserpic;
|
||||
mutable bool _cornerBadgeShown = false;
|
||||
int _top = 0;
|
||||
int _height = 0;
|
||||
int _index : 30 = 0;
|
||||
int _cornerBadgeShown : 1 = 0;
|
||||
int _topicJumpRipple : 1 = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -415,8 +415,21 @@ void Widget::chosenRow(const ChosenRow &row) {
|
||||
const auto openSearchResult = !controller()->selectingPeer()
|
||||
&& row.filteredRow;
|
||||
const auto history = row.key.history();
|
||||
if (const auto topic = row.key.topic()) {
|
||||
controller()->content()->chooseThread(topic, row.message.fullId.msg);
|
||||
const auto topicJump = history
|
||||
? history->peer->forumTopicFor(row.message.fullId.msg)
|
||||
: nullptr;
|
||||
if (topicJump) {
|
||||
if (!controller()->adaptive().isOneColumn()) {
|
||||
controller()->openForum(history->peer->asChannel());
|
||||
}
|
||||
controller()->content()->chooseThread(
|
||||
topicJump,
|
||||
ShowAtUnreadMsgId);
|
||||
return;
|
||||
} else if (const auto topic = row.key.topic()) {
|
||||
controller()->content()->chooseThread(
|
||||
topic,
|
||||
row.message.fullId.msg);
|
||||
} else if (history && history->peer->isForum() && !row.message.fullId) {
|
||||
controller()->openForum(history->peer->asChannel());
|
||||
return;
|
||||
@@ -716,19 +729,18 @@ void Widget::changeOpenedSubsection(
|
||||
FnMut<void()> change,
|
||||
bool fromRight,
|
||||
anim::type animated) {
|
||||
_a_show.stop();
|
||||
|
||||
if (isHidden()) {
|
||||
animated = anim::type::instant;
|
||||
}
|
||||
if (animated == anim::type::normal) {
|
||||
_connecting->setForceHidden(true);
|
||||
_cacheUnder = grabForFolderSlideAnimation();
|
||||
_showDirection = fromRight
|
||||
? Window::SlideDirection::FromRight
|
||||
: Window::SlideDirection::FromLeft;
|
||||
_showAnimationType = ShowAnimation::Internal;
|
||||
_connecting->setForceHidden(true);
|
||||
_cacheUnder = grabForFolderSlideAnimation();
|
||||
}
|
||||
_a_show.stop();
|
||||
change();
|
||||
refreshTopBars();
|
||||
updateControlsVisibility(true);
|
||||
@@ -751,9 +763,7 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
|
||||
|
||||
void Widget::changeOpenedForum(ChannelData *forum, anim::type animated) {
|
||||
changeOpenedSubsection([&] {
|
||||
if (forum) {
|
||||
cancelSearch();
|
||||
}
|
||||
cancelSearch();
|
||||
_openedForum = forum;
|
||||
_api.request(base::take(_topicSearchRequest)).cancel();
|
||||
_inner->changeOpenedForum(forum);
|
||||
@@ -1103,10 +1113,10 @@ void Widget::animationCallback() {
|
||||
|
||||
void Widget::escape() {
|
||||
if (!cancelSearch()) {
|
||||
if (controller()->openedFolder().current()) {
|
||||
controller()->closeFolder();
|
||||
} else if (controller()->openedForum().current()) {
|
||||
if (controller()->openedForum().current()) {
|
||||
controller()->closeForum();
|
||||
} else if (controller()->openedFolder().current()) {
|
||||
controller()->closeFolder();
|
||||
} else if (controller()->activeChatEntryCurrent().key) {
|
||||
controller()->content()->dialogsCancelled();
|
||||
} else {
|
||||
@@ -1646,6 +1656,7 @@ void Widget::searchReceived(
|
||||
if (const auto peer = searchInPeer()) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
channel->processTopics(data.vtopics());
|
||||
} else {
|
||||
LOG(("API Error: "
|
||||
"received messages.channelMessages when no channel "
|
||||
@@ -2337,7 +2348,7 @@ bool Widget::cancelSearch() {
|
||||
auto clearingInChat = false;
|
||||
cancelSearchRequest();
|
||||
if (!clearingQuery && (_searchInChat || _searchFromAuthor)) {
|
||||
if (controller()->adaptive().isOneColumn()) {
|
||||
if (_searchInChat && controller()->adaptive().isOneColumn()) {
|
||||
if (const auto thread = _searchInChat.thread()) {
|
||||
controller()->showThread(thread);
|
||||
} else {
|
||||
|
||||