Compare commits

..

125 Commits

Author SHA1 Message Date
John Preston
1e6937a075 Version 4.3.3.
- Fix an issue with media auto-download on Windows.
- Fix switching accounts in maximized window.
- Fix collapsed archive row layout.
2022-11-23 23:09:31 +01:00
John Preston
d714c1edc0 Update submodules. 2022-11-23 23:01:05 +01:00
John Preston
ebf46e1270 Paint unread counter in expanded archive row. 2022-11-23 23:01:05 +01:00
John Preston
116a598508 Fix displaying of collapsed archive row. 2022-11-23 23:01:05 +01:00
23rd
f9a14fc6bc Fixed first check for skipping translation. 2022-11-23 23:01:05 +01:00
23rd
7cdc3eb2b2 Fixed translation of non-server messages. 2022-11-23 23:01:04 +01:00
23rd
294432ceed Differentiated replies in groups by color. 2022-11-23 23:01:04 +01:00
23rd
551bf4f9a7 Switched default language for skip translation to application language. 2022-11-23 23:01:04 +01:00
Ilya Fedin
4b2d8b0c53 Implement sonnet-based language detection backend 2022-11-23 23:00:06 +01:00
John Preston
58e35dec12 Fix auto-download with LTCG on Windows. 2022-11-23 10:07:16 +01:00
John Preston
ae90347c6c Revert "Fixed switching between accounts with filters at screen edge."
This reverts commit 8f3c3b2a54.

Regressions with account switch in a maximized window.
2022-11-23 00:44:31 +01:00
John Preston
1aece79a47 Version 4.3.2.
- Enable message translations in Settings > Language.
- Fast jump to the last updated topic.
- Bug fixes and other minor improvements.
2022-11-22 00:28:07 +00:00
John Preston
79a2d85287 Fix build with GCC. 2022-11-22 00:28:07 +00:00
23rd
937d243a4c Respected translation preferences in sections. 2022-11-22 00:28:07 +00:00
23rd
f82bae15f0 Added translation preferences. 2022-11-22 00:28:07 +00:00
23rd
7aede75e43 Added external cld3 library. 2022-11-22 00:28:03 +00:00
23rd
b72fce4894 Removed LanguageBox::createMultiSelect method. 2022-11-22 00:26:49 +00:00
23rd
32cebc0d9b Fixed position of show more button in translate box. 2022-11-22 00:26:49 +00:00
John Preston
2de76cb75b Highlight primary usernames in profiles like the additional ones. 2022-11-22 00:26:49 +00:00
John Preston
ab06574fd9 Add some margin for one-line profile values copying. 2022-11-22 00:26:49 +00:00
John Preston
473e190aeb Fix single-column forum-by-user search. 2022-11-22 00:26:49 +00:00
John Preston
4b5a0942b1 Open just topic in quick jump-to-last-topic click. 2022-11-22 00:26:49 +00:00
John Preston
106bdae9ce Always open first topic in two-column layout. 2022-11-22 00:26:49 +00:00
John Preston
f97e5d6307 Fix build with Xcode. 2022-11-22 00:26:49 +00:00
John Preston
bb106b07af Show small unread mark for non-opened topics. 2022-11-22 00:26:49 +00:00
John Preston
ce631436bf Show non-read non-opened topics as unread. 2022-11-22 00:26:49 +00:00
23rd
bc5aa7338e Removed inaccessible users from choosing recipients for forwards box. 2022-11-22 00:26:49 +00:00
23rd
6db7840fa7 Added translate ability to context menu. 2022-11-22 00:26:49 +00:00
23rd
921d2239c7 Added initial implementation of choosing translation language from list. 2022-11-22 00:26:49 +00:00
23rd
0feef675f7 Added loading text effect to translate box. 2022-11-22 00:26:49 +00:00
23rd
f16d30de37 Added initial implementation of loading element effect. 2022-11-22 00:26:49 +00:00
23rd
cf54d9fb12 Moved out glare effect to separate file. 2022-11-22 00:26:49 +00:00
23rd
b7647fbcc1 Added initial implementation of translate box. 2022-11-22 00:26:49 +00:00
23rd
95a1ab6b0b Fixed display of send as button above voice record bar. 2022-11-22 00:26:49 +00:00
23rd
823b4e6b98 Added icon for inaccessible users. 2022-11-22 00:26:49 +00:00
23rd
3467fe226f Added ability to send webp as compressed image. 2022-11-22 00:26:49 +00:00
23rd
57c50c8655 Optimized includes of attach_prepare.h. 2022-11-22 00:26:48 +00:00
23rd
8f3c3b2a54 Fixed switching between accounts with filters at screen edge. 2022-11-22 00:26:48 +00:00
23rd
efc0908ed8 Fixed forum closing with Back mouse button.
- Fixed #25330.
2022-11-22 00:26:48 +00:00
John Preston
561e3f4809 Handle clicks on topic jump area. 2022-11-22 00:26:48 +00:00
John Preston
ede34578da Display jump to last topic message bubble. 2022-11-22 00:26:48 +00:00
John Preston
97356032ac Fix build in Xcode. 2022-11-22 00:26:48 +00:00
John Preston
4c8187f623 Topics list in forum chats list entry. 2022-11-22 00:26:48 +00:00
John Preston
996b6bf46a Fix unread mentions / reactions button in topics. 2022-11-22 00:26:48 +00:00
John Preston
37308cde21 Support dialog rows with variable height. 2022-11-22 00:26:48 +00:00
John Preston
248337daf5 Implement topics pin limit box. 2022-11-22 00:26:48 +00:00
John Preston
8a288476b8 Implement pinned topics reordering. 2022-11-22 00:26:48 +00:00
John Preston
c7741cb62a Apply short topic info from channelMessages. 2022-11-22 00:26:48 +00:00
John Preston
983b6af0b4 Update API scheme to layer 149. 2022-11-22 00:26:48 +00:00
Ilya Fedin
a94dd22caa Rewrite GenerateDesktopFile using Glib::KeyFile and KShell 2022-11-21 21:30:17 +00:00
Ilya Fedin
899ab9a16a Fix range loop warnings in Snap build 2022-11-21 21:30:17 +00:00
Ilya Fedin
2f0d14bd35 Use customWorkingDir() outside of Core::Launcher
This allows to add -workdir to shortcuts only if the process was launched with -workdir
2022-11-21 21:30:17 +00:00
Ilya Fedin
9b66b76bac Use GNotification only in flatpak by default
The UX is not the best without sound, so there's no advantage in using GNotification on GNOME.

Remove mention about being native to GNOME as it's not true anymore.
2022-11-20 08:52:36 +00:00
Ilya Fedin
a1e60a3f20 Ensure GNotification is not autodetected in snap
Snap reports it as present, but prevents the access and GNotification attempts to use portal that doesn't work in snap
2022-11-19 02:21:51 +04:00
Ilya Fedin
f7971733f4 Update msys2 base
It has new signatures and fixes the CI
2022-11-19 02:21:32 +04:00
Ilya Fedin
e33b62ad28 Update cmake_helpers 2022-11-18 20:40:57 +04:00
Ilya Fedin
82629dd3e5 Update Qt to 6.4.1 on Linux 2022-11-18 20:40:57 +04:00
Ilya Fedin
768fc9b8f6 Use the new Glib::Variant qint64/quint64 compatibility with old code 2022-11-18 20:40:33 +04:00
Ilya Fedin
b9b6a9e747 Follow desktop file naming specification
This is required for GApplication to enable all the features

https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#file-naming
2022-11-18 20:40:33 +04:00
Ilya Fedin
b4d310fd1e Don't subscribe to inhibiton changes when not supported 2022-11-18 20:40:33 +04:00
Ilya Fedin
f9dd2b4a0a Support GNotification
It's used if there's a gtk notification daemon or application is running sandboxed without access to the freedesktop protocol.

GNotification API is poor, but should feel native on environments using GNOME technologies.
2022-11-18 20:40:33 +04:00
Ilya Fedin
afaad155a0 Update to changes in MPRIS SystemMediaControlsManager backend
And let it use service name provided by flatpak by default
2022-11-11 11:47:13 +04:00
Ilya Fedin
9726b3c298 Avoid race condition when registering Wayland interfaces 2022-11-11 11:47:01 +04:00
23rd
85b94bc6fd Changed behavior to always delete messages for bots.
Fixed #17033.
2022-11-11 11:45:13 +04:00
23rd
f0645753d4 Toggled checkbox of deleting messages for both participants. 2022-11-11 11:45:13 +04:00
23rd
8b570f2e8f Fixed duplicated transcribe buttons for video messages.
Fixed #25327.
2022-11-11 11:45:13 +04:00
23rd
2b7b278b52 Respected global notify sound for Saved Messages.
Fixed #25315.
2022-11-11 11:45:13 +04:00
23rd
a02c01cce7 Added phrases for various types of albums in chats list. 2022-11-11 11:45:13 +04:00
23rd
9601207b2c Fixed warnings from Github CI. 2022-11-11 11:45:12 +04:00
John Preston
c06f0b3ea1 Fix contact status bar in single column layout. 2022-11-11 11:44:10 +04:00
John Preston
b514496546 Simplify SendProgressManager::done prototype. 2022-11-11 11:44:03 +04:00
John Preston
ca460dab6d Fix mute value casting. 2022-11-11 11:43:47 +04:00
John Preston
644ec1f599 Remove base::Observable / base::Variable. 2022-11-11 11:43:24 +04:00
John Preston
73e2cc96d1 Fix music player with RTL song names. 2022-11-11 11:43:06 +04:00
Ilya Fedin
d756ecc609 Handle cancel in menu_item_download_files, too 2022-11-10 17:43:48 +04:00
Ilya Fedin
0cd0ad7a5b Handle cancel for default download path in Linux sandbox 2022-11-10 00:24:37 +04:00
John Preston
9229c57e7a Fix monospace-wide comments thread root message.
Fixes #16343.
2022-11-09 12:51:44 +04:00
John Preston
5b17416177 Fix spoiler glitches in reply previews. 2022-11-09 12:40:28 +04:00
John Preston
0b7a2c18a2 Pause spoilers in reply / edit panels. 2022-11-09 12:40:27 +04:00
John Preston
187f5fa4f3 Fix emoji panel section appearance in topics. 2022-11-09 11:00:24 +04:00
John Preston
3fad69d3c8 Hide pinned bar in topics for a single pin. 2022-11-09 11:00:22 +04:00
John Preston
7e2a49c1f9 Fix possible crash in pinned bar destruction. 2022-11-09 10:22:48 +04:00
John Preston
66435d5269 Better folder / chat closing by escape. 2022-11-09 10:22:44 +04:00
John Preston
4a8b5c3015 Add "Show Topics List" button to topic profile. 2022-11-09 10:22:33 +04:00
John Preston
d0d2a4f488 Show messages count in forum. 2022-11-09 10:22:23 +04:00
John Preston
8ee28f6665 Skip forum send action painting. 2022-11-09 10:22:07 +04:00
John Preston
a2b0c551c2 Pass default download path in sandboxed environments. 2022-11-09 10:21:52 +04:00
Ilya Fedin
bff641c217 Update cmake_helpers 2022-11-08 23:37:58 +04:00
Ilya Fedin
a9c0b817d1 Explain how to build a backward compatible binary on Linux 2022-11-08 23:37:58 +04:00
Ilya Fedin
0f38dabd84 Update cmake for -flto=auto support 2022-11-08 23:37:58 +04:00
Ilya Fedin
8552047210 Always build glibmm with LTO to be able to build backward compatible binary 2022-11-08 23:37:58 +04:00
John Preston
092923fe6e Don't autodownload files if sandbox prevents it.
Partially fixes #25308.
2022-11-08 18:26:02 +04:00
John Preston
ab5792f59f Revert "Fix crash when asking download path in sandbox environment"
This reverts commit a4b0443047.
2022-11-08 18:26:01 +04:00
Dragoon Aethis
9d59e42b52 Add an experimental "small message radius" toggle (#25305)
Add an experimental "small message radius" toggle.

This toggle allows switching to the pre-4.3.0, smaller message bubble
radius after an app restart. The message bubble radius styles now have
to be referenced via the Ui::BubbleRadius* and Ui::MsgFileThumbRadius*
wrappers to use the appropriate value.
2022-11-08 14:19:17 +04:00
Ilya Fedin
e675dc1ef1 Fix appdata filename in snapcraft.yaml 2022-11-08 01:13:08 +04:00
John Preston
8cb980a791 Version 4.3.1.
- Critical bug fixes.
2022-11-07 23:43:04 +04:00
John Preston
785372f5d0 Remove redundant updateSize. 2022-11-07 23:40:41 +04:00
John Preston
aa6495a257 Fix a crash in sharing contact.
Fixes #25287.
2022-11-07 23:19:52 +04:00
23rd
7993c6207a Fixed crash on checking sponsored state with closed dialog. 2022-11-07 23:11:30 +04:00
Ilya Fedin
6af93b3497 Fix appdata changelog generation 2022-11-07 23:11:17 +04:00
John Preston
153fb3e579 Fix search in topic cancel on forum closing. 2022-11-07 20:01:18 +04:00
23rd
80d4c3affe Added button for invite links management even to public channels. 2022-11-07 19:17:21 +04:00
John Preston
9a1d9deea5 Fix peer list row selecting for forums.
The blobs in group calls are still round for them :(
2022-11-07 19:15:57 +04:00
Ilya Fedin
3b7cdb5748 Update lib_base 2022-11-07 18:16:49 +04:00
Ilya Fedin
f542a026ec Add _GTK_APPLICATION_ID support 2022-11-07 18:16:49 +04:00
23rd
629314cfa2 Added saving of scroll state in dialogs widget for restoring from forum. 2022-11-07 15:39:03 +03:00
23rd
c320917069 Fixed Escape shortcut for opened archived forums. 2022-11-07 14:46:41 +03:00
John Preston
9276f3dab8 Fix reply button in View as Messages. 2022-11-07 15:44:37 +04:00
John Preston
1316d14f7a Fix reading of comments.
Fixes #25276.
2022-11-07 15:27:09 +04:00
John Preston
0737034ea6 Fix pinning between topics. 2022-11-07 15:12:47 +04:00
John Preston
9a54473e03 Fix dialog row updating on user online status change.
Regression was introduced in ed895ace66.

Fixes #6410. Again. I hope.
2022-11-07 15:11:15 +04:00
John Preston
991fe491c5 Animate emoji only twice in chats list. 2022-11-07 14:32:06 +04:00
John Preston
6e606f3bb6 Animate topic icons only twice in topics list. 2022-11-07 14:32:06 +04:00
John Preston
d8a0497a7e Don't jump with focus to chats list. 2022-11-07 14:32:06 +04:00
Ilya Fedin
fc4682d77e Get rid of TDESKTOP_LAUNCHER_BASENAME
This key was mainly used to let flatpak and snap provide right desktop file name.
Now, we can compute it from the environment in runtime for both flatpak and snap.
There's no more need in this option. Desktop filename override by downstreams is highly discouraged.
2022-11-07 13:47:58 +04:00
Ilya Fedin
a507edb67a Get rid of workarounds for old flatpak versions
The minimal supported flatpak version since Qt 6.x is 1.14.0.
Flatpak shares temp directory and runtime directory between launches since 1.11.1.
FLATPAK_ID is defined since 1.1.2.
2022-11-07 13:47:58 +04:00
John Preston
bd8b90055e Fix showing forum in a single-column layout. 2022-11-07 13:19:40 +04:00
23rd
371ba40a50 Added volume button to media player for voices. 2022-11-06 18:55:11 +03:00
23rd
a8d8b5be28 Removed wrapping of message texts for mime data. 2022-11-06 18:18:24 +03:00
23rd
3e428faa2e Fixed crash on creating of callback for adding items to non-exist menu. 2022-11-06 18:18:24 +03:00
23rd
3887fbc437 Fixed crash from type box for groups. 2022-11-06 16:56:45 +03:00
Ilya Fedin
a4b0443047 Fix crash when asking download path in sandbox environment
This makes first download to go to temp directory, but I can't think of better solution without changing download path getting architecture.
2022-11-06 17:38:20 +04:00
Ilya Fedin
a73ff8f5d7 Get rid of legacy themes in snap
gtk-common-themes contains all the themes now
2022-11-06 08:08:42 +04:00
Ilya Fedin
0c8400212e Add curl to fix snap build on Launchpad 2022-11-06 00:08:25 +04:00
Ilya Fedin
dcfc3431f5 Disable unneeded validation for libsigc++ 2022-11-06 00:08:25 +04:00
217 changed files with 4819 additions and 1726 deletions

View File

@@ -20,7 +20,7 @@ jobs:
steps:
- name: Clone.
uses: actions/checkout@v2
uses: actions/checkout@v3.1.0
with:
submodules: recursive

View File

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

View File

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

View File

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

View File

@@ -47,7 +47,7 @@ jobs:
steps:
- name: Clone.
uses: actions/checkout@v2
uses: actions/checkout@v3.1.0
with:
fetch-depth: 0
submodules: recursive

View File

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

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,7 +33,6 @@ protected:
private:
using Languages = Lang::CloudManager::Languages;
not_null<Ui::MultiSelect*> createMultiSelect();
int rowsInPage() const;
Fn<void()> _setInnerFocus;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 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;

View File

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

View File

@@ -437,6 +437,8 @@ public:
return mgInfo ? mgInfo->forum() : nullptr;
}
void processTopics(const MTPVector<MTPForumTopic> &topics);
// Still public data members.
uint64 access = 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,7 @@ enum class SortMode {
struct PositionChange {
int from = -1;
int to = -1;
int height = 0;
};
struct UnreadState {

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More