From e5ad35125cfd33cf2c59ab4087ae1fa0d33b906a Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 17 Mar 2020 13:08:13 +0400 Subject: [PATCH 001/115] Fix artifact uploading in Windows action --- .github/workflows/win.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 6118a4c86..e6113fe3f 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -350,6 +350,7 @@ jobs: - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' + shell: cmd run: | cd %REPO_NAME%\out\Debug mkdir artifact From d2291f5b17e0c169083a34e51053a99546207ec8 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 17 Mar 2020 23:13:11 +0400 Subject: [PATCH 002/115] Fix panel icon forcing --- .../platform/linux/main_window_linux.cpp | 20 +++++++++++-------- .../platform/linux/main_window_linux.h | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 347395ab3..3d30ef755 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -78,7 +78,8 @@ QString GetTrayIconName(int counter, bool muted) { const auto iconName = GetIconName(); const auto panelIconName = GetPanelIconName(counter, muted); - if (QIcon::hasThemeIcon(panelIconName)) { + if (QIcon::hasThemeIcon(panelIconName) + || qEnvironmentVariableIsSet(kForcePanelIcon.utf8())) { return panelIconName; } else if (QIcon::hasThemeIcon(iconName)) { return iconName; @@ -158,7 +159,7 @@ QIcon TrayIconGen(int counter, bool muted) { || iconThemeName != TrayIconThemeName || iconName != TrayIconName) { if (!iconName.isEmpty()) { - if(systemIcon.isNull()) { + if (systemIcon.isNull()) { systemIcon = QIcon::fromTheme(iconName); } @@ -467,11 +468,12 @@ void MainWindow::psTrayMenuUpdated() { } #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -void MainWindow::setSNITrayIcon(int counter, bool muted, bool firstShow) { +void MainWindow::setSNITrayIcon(int counter, bool muted) { const auto iconName = GetTrayIconName(counter, muted); if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) - && ((!iconName.isEmpty() && !InSnap()) + && !iconName.isEmpty() + && (!InSnap() || qEnvironmentVariableIsSet(kForcePanelIcon.utf8()))) { if (_sniTrayIcon->iconName() == iconName) { return; @@ -480,7 +482,8 @@ void MainWindow::setSNITrayIcon(int counter, bool muted, bool firstShow) { _sniTrayIcon->setIconByName(iconName); _sniTrayIcon->setToolTipIconByName(iconName); } else if (IsIndicatorApplication()) { - if(!IsIconRegenerationNeeded(counter, muted) && !firstShow) { + if (!IsIconRegenerationNeeded(counter, muted) + && !_sniTrayIcon->iconName().isEmpty()) { return; } @@ -492,7 +495,8 @@ void MainWindow::setSNITrayIcon(int counter, bool muted, bool firstShow) { _sniTrayIcon->setIconByName(_trayIconFile->fileName()); } } else { - if(!IsIconRegenerationNeeded(counter, muted) && !firstShow) { + if (!IsIconRegenerationNeeded(counter, muted) + && !_sniTrayIcon->iconPixmap().isEmpty()) { return; } @@ -554,7 +558,7 @@ void MainWindow::onSNIOwnerChanged( cSetSupportTray(trayAvailable); - if(cSupportTray()) { + if (cSupportTray()) { psSetupTrayIcon(); } else { LOG(("System tray is not available.")); @@ -575,7 +579,7 @@ void MainWindow::psSetupTrayIcon() { this); _sniTrayIcon->setTitle(AppName.utf16()); - setSNITrayIcon(counter, muted, true); + setSNITrayIcon(counter, muted); attachToSNITrayIcon(); } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index 8602c5380..43ab72284 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -123,7 +123,7 @@ private: QAction *psMonospace = nullptr; QAction *psClearFormat = nullptr; - void setSNITrayIcon(int counter, bool muted, bool firstShow = false); + void setSNITrayIcon(int counter, bool muted); void attachToSNITrayIcon(); #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION From ea854e5be3c89157c282f7e224bee2e14791208a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D0=BF=D0=BE=D1=80=D1=81=D0=BA=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Fri, 20 Mar 2020 10:02:36 +0500 Subject: [PATCH 003/115] bugfix: incorrect erase element --- Telegram/SourceFiles/mtproto/session_private.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/mtproto/session_private.cpp b/Telegram/SourceFiles/mtproto/session_private.cpp index e35a4959d..808af4543 100644 --- a/Telegram/SourceFiles/mtproto/session_private.cpp +++ b/Telegram/SourceFiles/mtproto/session_private.cpp @@ -2007,9 +2007,10 @@ void SessionPrivate::requestsAcked(const QVector &ids, bool byResponse) } else { DEBUG_LOG(("Message Info: acked msgId %1 that was prepared to resend, requestId %2").arg(msgId).arg(requestId)); } - toSend.erase(j); - + _ackedIds.emplace(msgId, j->second->requestId); + + toSend.erase(j); continue; } DEBUG_LOG(("Message Info: msgId %1 was not found in recent resent either").arg(msgId)); From 6c46194009a0bf26c1f383060a9d14378efa4ded Mon Sep 17 00:00:00 2001 From: Aleksey Kiporskiy Date: Tue, 24 Mar 2020 13:58:39 +0500 Subject: [PATCH 004/115] bugfix: show chat description --- Telegram/SourceFiles/data/data_chat.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 9e7870b8e..d3dfbc9e9 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -350,6 +350,7 @@ void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) { } chat->checkFolder(update.vfolder_id().value_or_empty()); chat->fullUpdated(); + chat->setAbout(qs(update.vabout())); chat->session().api().applyNotifySettings( MTP_inputNotifyPeer(chat->input), From 13e8b60d6c396b128cfd7338656d91ffbec8d170 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 19 Mar 2020 03:12:36 +0400 Subject: [PATCH 005/115] Don't recreate tray icon if it is disabled --- Telegram/SourceFiles/platform/linux/main_window_linux.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 3d30ef755..7b2e5d9ef 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -531,6 +531,10 @@ void MainWindow::onSNIOwnerChanged( const QString &service, const QString &oldOwner, const QString &newOwner) { + if (Global::WorkMode().value() == dbiwmWindowOnly) { + return; + } + if (oldOwner.isEmpty() && !newOwner.isEmpty()) { LOG(("Switching to SNI tray icon...")); } else if (!oldOwner.isEmpty() && newOwner.isEmpty()) { From 9a78c94d7e505c977867e34b1ea699f5015e6c7e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 19 Mar 2020 14:07:29 +0300 Subject: [PATCH 006/115] Updated linux.yml. --- .github/workflows/linux.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index eb84dc942..3e26e8217 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -29,7 +29,7 @@ jobs: QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.5" OPENSSL_VER: "1_1_1" OPENSSL_PREFIX: "/usr/local/desktop-app/openssl-1.1.1" - CMAKE_VER: "3.16.3" + CMAKE_VER: "3.17.0" UPLOAD_ARTIFACT: "false" ONLY_CACHE: "false" MANUAL_CACHING: "6" @@ -127,6 +127,7 @@ jobs: sudo mkdir /opt/cmake sudo sh $file --prefix=/opt/cmake --skip-license sudo ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake + rm $file cmake --version @@ -500,11 +501,14 @@ jobs: echo Define from matrix: $DEFINE fi - ./configure.sh -D TDESKTOP_API_TEST=ON -D DESKTOP_APP_USE_PACKAGED=OFF $DEFINE + ./configure.sh \ + -D CMAKE_CXX_FLAGS="-s" \ + -D TDESKTOP_API_TEST=ON \ + -D DESKTOP_APP_USE_PACKAGED=OFF \ + $DEFINE cd ../out/Debug make -j$(nproc) - strip -s bin/Telegram - name: Check. if: env.ONLY_CACHE == 'false' From 1a5ee99c8a68868ca780af0755f8d83340d99674 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 20 Mar 2020 19:32:06 +0300 Subject: [PATCH 007/115] Updated checkout Github Action to v2. --- .github/workflows/linux.yml | 24 ++++++++++++------------ .github/workflows/mac.yml | 9 ++------- .github/workflows/snap.yml | 2 +- .github/workflows/win.yml | 9 ++------- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3e26e8217..67d9f1ab9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -40,11 +40,6 @@ jobs: - name: Get repository name. run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/} - - name: Clone. - uses: actions/checkout@v1 - with: - submodules: recursive - - name: Disable man for further package installs. run: | cfgFile="/etc/dpkg/dpkg.cfg.d/no_man" @@ -57,17 +52,13 @@ jobs: p locale p doc - - name: First set up. + - name: Apt install. shell: bash run: | - cd .. - mv $REPO_NAME temp - mkdir $REPO_NAME - mv temp $REPO_NAME/$REPO_NAME - cd $REPO_NAME - sudo apt-get update sudo apt-get install software-properties-common -y && \ + sudo add-apt-repository ppa:git-core/ppa -y && \ + sudo apt-get update && \ sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev \ libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \ autoconf automake build-essential libxml2-dev libass-dev libfreetype6-dev \ @@ -86,6 +77,15 @@ jobs: sudo update-alternatives --config gcc && \ sudo add-apt-repository --remove ppa:ubuntu-toolchain-r/test -y + - name: Clone. + uses: actions/checkout@v2 + with: + submodules: recursive + path: ${{ env.REPO_NAME }} + + - name: First set up. + shell: bash + run: | gcc --version gcc --version > CACHE_KEY.txt diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 7713139ed..9ded71821 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -42,18 +42,13 @@ jobs: run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/} - name: Clone. - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: submodules: recursive + path: ${{ env.REPO_NAME }} - name: First set up. run: | - cd .. - mv $REPO_NAME temp - mkdir $REPO_NAME - mv temp $REPO_NAME/$REPO_NAME - cd $REPO_NAME - brew install automake fdk-aac lame libass libtool libvorbis libvpx \ ninja opus sdl shtool texi2html theora x264 xvid yasm pkg-config diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index 8a5767e58..8ff2c7947 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Clone. - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: submodules: recursive diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index e6113fe3f..f28b58860 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -38,19 +38,14 @@ jobs: run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/} - name: Clone. - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: submodules: recursive + path: ${{ env.REPO_NAME }} - name: Set up environment variables. shell: cmd run: | - cd .. - move %REPO_NAME% temp - mkdir %REPO_NAME% - move temp %REPO_NAME%/%REPO_NAME% - cd %REPO_NAME% - echo ::add-path::C:\Strawberry\perl\bin\ echo ::add-path::"%programfiles%\NASM" From 98b947fbdf4d9d550c6092b1b150c6b5ba9be4e2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 20 Mar 2020 23:26:04 +0300 Subject: [PATCH 008/115] Slightly refactored snap.yml. --- .github/workflows/snap.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index 8ff2c7947..0b504c329 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -29,27 +29,34 @@ jobs: - name: First set up. run: | + # Workaround for Heroku + curl https://cli-assets.heroku.com/apt/release.key | sudo apt-key add - + sudo apt-get update sudo apt-get install gcc-8 g++-8 -y sudo snap install --classic snapcraft # Workaround for snapcraft - # See https://forum.snapcraft.io/t/permissions-problem-using-snapcraft-in-azure-pipelines/13258 + # See https://forum.snapcraft.io/t/13258 sudo chown root:root / + md5() { + md5cache=$(md5sum $1.txt | cut -c -32) + echo ::set-env name=$1::$md5cache + } + keyFor() { + keyName="${1^^}_CACHE_KEY" + awk -v RS="" -v ORS="\n\n" '/^ '"$1"':/' snap/snapcraft.yaml > $keyName.txt + md5 $keyName + } + snapcraft --version > CACHE_KEY.txt gcc-8 --version >> CACHE_KEY.txt echo $MANUAL_CACHING >> CACHE_KEY.txt - md5cache=$(md5sum CACHE_KEY.txt | cut -c -32) - echo ::set-env name=CACHE_KEY::$md5cache + md5 CACHE_KEY - awk -v RS="" -v ORS="\n\n" '/^ cmake:/' snap/snapcraft.yaml > CMAKE_CACHE_KEY.txt - md5cache=$(md5sum CMAKE_CACHE_KEY.txt | cut -c -32) - echo ::set-env name=CMAKE_CACHE_KEY::$md5cache - - awk -v RS="" -v ORS="\n\n" '/^ ffmpeg:/' snap/snapcraft.yaml > FFMPEG_CACHE_KEY.txt - md5cache=$(md5sum FFMPEG_CACHE_KEY.txt | cut -c -32) - echo ::set-env name=FFMPEG_CACHE_KEY::$md5cache + keyFor cmake + keyFor ffmpeg - name: CMake cache. id: cache-cmake From 71f7aae948661e57d7969a81a781c4f5f1191da5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Feb 2020 13:43:12 +0400 Subject: [PATCH 009/115] Replace dialogs mode with filters. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/boxes/share_box.cpp | 5 +- Telegram/SourceFiles/core/application.cpp | 4 +- .../SourceFiles/data/data_chat_filters.cpp | 53 +++++++++ Telegram/SourceFiles/data/data_chat_filters.h | 45 ++++++++ Telegram/SourceFiles/data/data_folder.cpp | 4 - Telegram/SourceFiles/data/data_folder.h | 1 - Telegram/SourceFiles/data/data_session.cpp | 40 +++---- Telegram/SourceFiles/data/data_types.h | 1 + .../SourceFiles/dialogs/dialogs_entry.cpp | 105 +++++++++--------- Telegram/SourceFiles/dialogs/dialogs_entry.h | 43 ++++--- .../dialogs/dialogs_indexed_list.cpp | 46 ++++---- .../dialogs/dialogs_indexed_list.h | 6 +- .../dialogs/dialogs_inner_widget.cpp | 88 ++++++++------- .../dialogs/dialogs_inner_widget.h | 5 +- .../SourceFiles/dialogs/dialogs_main_list.cpp | 26 +++-- .../SourceFiles/dialogs/dialogs_main_list.h | 8 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 4 +- Telegram/SourceFiles/dialogs/dialogs_widget.h | 3 +- Telegram/SourceFiles/facades.cpp | 8 +- Telegram/SourceFiles/facades.h | 8 +- Telegram/SourceFiles/history/history.cpp | 31 +++--- Telegram/SourceFiles/history/history.h | 1 - Telegram/SourceFiles/mainwidget.cpp | 4 +- Telegram/SourceFiles/mainwidget.h | 3 +- .../SourceFiles/settings/settings_codes.cpp | 2 +- Telegram/SourceFiles/storage/localstorage.cpp | 18 +-- .../window/window_session_controller.cpp | 2 +- 28 files changed, 330 insertions(+), 236 deletions(-) create mode 100644 Telegram/SourceFiles/data/data_chat_filters.cpp create mode 100644 Telegram/SourceFiles/data/data_chat_filters.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b7be40f6b..cb7a45ef9 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -320,6 +320,8 @@ PRIVATE data/data_auto_download.h data/data_chat.cpp data/data_chat.h + data/data_chat_filters.cpp + data/data_chat_filters.h data/data_channel.cpp data/data_channel.h data/data_channel_admins.cpp diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 9719bee0b..09bb042f0 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -937,10 +937,7 @@ void ShareBox::Inner::changeCheckState(Chat *chat) { const auto history = chat->peer->owner().history(chat->peer); auto row = _chatsIndexed->getRow(history); if (!row) { - const auto rowsByLetter = _chatsIndexed->addToEnd(history); - const auto it = rowsByLetter.find(0); - Assert(it != rowsByLetter.cend()); - row = it->second; + row = _chatsIndexed->addToEnd(history).main; } chat = getChat(row); if (!chat->checkbox.checked()) { diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index f3bbacb66..18f3bf4f2 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -503,8 +503,8 @@ void Application::switchDebugMode() { } void Application::switchWorkMode() { - Global::SetDialogsModeEnabled(!Global::DialogsModeEnabled()); - Global::SetDialogsMode(Dialogs::Mode::All); + Global::SetDialogsFiltersEnabled(!Global::DialogsFiltersEnabled()); + Global::SetDialogsFilterId(0); Local::writeUserSettings(); App::restart(); } diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp new file mode 100644 index 000000000..b71d77cbb --- /dev/null +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -0,0 +1,53 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_chat_filters.h" + +#include "history/history.h" +#include "data/data_peer.h" +#include "data/data_user.h" +#include "data/data_chat.h" +#include "data/data_channel.h" + +namespace Data { + +ChatFilter::ChatFilter( + const QString &title, + Flags flags, + base::flat_set> always) +: _title(title) +, _always(std::move(always)) +, _flags(flags) { +} + +bool ChatFilter::contains(not_null history) const { + const auto flag = [&] { + const auto peer = history->peer; + if (const auto user = peer->asUser()) { + return user->isBot() ? Flag::Bots : Flag::Users; + } else if (const auto chat = peer->asChat()) { + return Flag::PrivateGroups; + } else if (const auto channel = peer->asChannel()) { + if (channel->isBroadcast()) { + return Flag::Broadcasts; + } else if (channel->isPublic()) { + return Flag::PublicGroups; + } else { + return Flag::PrivateGroups; + } + } else { + Unexpected("Peer type in ChatFilter::contains."); + } + }(); + return false + || ((_flags & flag) + && (!(_flags & Flag::NoMuted) || !history->mute()) + && (!(_flags & Flag::NoRead) || history->unreadCountForBadge())) + || _always.contains(history); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h new file mode 100644 index 000000000..bc12246a0 --- /dev/null +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -0,0 +1,45 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/flags.h" + +class History; + +namespace Data { + +class ChatFilter final { +public: + enum class Flag : uchar { + Users = 0x01, + PrivateGroups = 0x02, + PublicGroups = 0x04, + Broadcasts = 0x08, + Bots = 0x10, + NoMuted = 0x20, + NoRead = 0x40, + }; + friend constexpr inline bool is_flag_type(Flag) { return true; }; + using Flags = base::flags; + + ChatFilter() = default; + ChatFilter( + const QString &title, + Flags flags, + base::flat_set> always); + + [[nodiscard]] bool contains(not_null history) const; + +private: + QString _title; + base::flat_set> _always; + Flags _flags; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index 5229de94b..df513684b 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -441,10 +441,6 @@ void Folder::unreadEntryChanged( // return _unreadPosition.changes(); //} -bool Folder::toImportant() const { - return false; -} - int Folder::fixedOnTopIndex() const { return kArchiveFixOnTopIndex; } diff --git a/Telegram/SourceFiles/data/data_folder.h b/Telegram/SourceFiles/data/data_folder.h index 26a9b8d58..f1898f522 100644 --- a/Telegram/SourceFiles/data/data_folder.h +++ b/Telegram/SourceFiles/data/data_folder.h @@ -58,7 +58,6 @@ public: TimeId adjustedChatListTimeId() const override; int fixedOnTopIndex() const override; - bool toImportant() const override; bool shouldBeInChatList() const override; int chatListUnreadCount() const override; bool chatListUnreadMark() const override; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 7fa9f0a06..3479bf6c6 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3363,25 +3363,25 @@ auto Session::refreshChatListEntry(Dialogs::Key key) auto result = RefreshChatListEntryResult(); result.changed = !entry->inChatList(); if (result.changed) { - const auto mainRow = entry->addToChatList(Mode::All); + const auto mainRow = entry->addToChatList(0); _contactsNoChatsList.del(key, mainRow); } else { - result.moved = entry->adjustByPosInChatList(Mode::All); - } - if (Global::DialogsModeEnabled()) { - if (entry->toImportant()) { - result.importantChanged = !entry->inChatList(Mode::Important); - if (result.importantChanged) { - entry->addToChatList(Mode::Important); - } else { - result.importantMoved = entry->adjustByPosInChatList( - Mode::Important); - } - } else if (entry->inChatList(Mode::Important)) { - entry->removeFromChatList(Mode::Important); - result.importantChanged = true; - } + result.moved = entry->adjustByPosInChatList(0); } + //if (Global::DialogsFiltersEnabled()) { // #TODO filters + // if (entry->toImportant()) { + // result.importantChanged = !entry->inChatList(Mode::Important); + // if (result.importantChanged) { + // entry->addToChatList(Mode::Important); + // } else { + // result.importantMoved = entry->adjustByPosInChatList( + // Mode::Important); + // } + // } else if (entry->inChatList(Mode::Important)) { + // entry->removeFromChatList(Mode::Important); + // result.importantChanged = true; + // } + //} return result; } @@ -3389,10 +3389,10 @@ void Session::removeChatListEntry(Dialogs::Key key) { using namespace Dialogs; const auto entry = key.entry(); - entry->removeFromChatList(Mode::All); - if (Global::DialogsModeEnabled()) { - entry->removeFromChatList(Mode::Important); - } + entry->removeFromChatList(0); + //if (Global::DialogsFiltersEnabled()) { // #TODO filters + // entry->removeFromChatList(Mode::Important); + //} if (_contactsList.contains(key)) { if (!_contactsNoChatsList.contains(key)) { _contactsNoChatsList.addByName(key); diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 2b452b3e4..66d95966b 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -128,6 +128,7 @@ using UserId = int32; using ChatId = int32; using ChannelId = int32; using FolderId = int32; +using FilterId = int32; constexpr auto NoChannel = ChannelId(0); diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index d56ae68d9..4db2f9ff2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -131,25 +131,33 @@ TimeId Entry::adjustedChatListTimeId() const { void Entry::changedChatListPinHook() { } -RowsByLetter &Entry::chatListLinks(Mode list) { - return _chatListLinks[static_cast(list)]; +RowsByLetter *Entry::chatListLinks(FilterId filterId) { + const auto i = _chatListLinks.find(filterId); + return (i != end(_chatListLinks)) ? &i->second : nullptr; } -const RowsByLetter &Entry::chatListLinks(Mode list) const { - return _chatListLinks[static_cast(list)]; +const RowsByLetter *Entry::chatListLinks(FilterId filterId) const { + const auto i = _chatListLinks.find(filterId); + return (i != end(_chatListLinks)) ? &i->second : nullptr; } -Row *Entry::mainChatListLink(Mode list) const { - auto it = chatListLinks(list).find(0); - Assert(it != chatListLinks(list).cend()); - return it->second; +not_null Entry::mainChatListLink(FilterId filterId) const { + const auto links = chatListLinks(filterId); + Assert(links != nullptr); + return links->main; } -PositionChange Entry::adjustByPosInChatList(Mode list) { - const auto lnk = mainChatListLink(list); - const auto from = lnk->pos(); - myChatsList(list)->adjustByDate(chatListLinks(list)); - const auto to = lnk->pos(); +Row *Entry::maybeMainChatListLink(FilterId filterId) const { + const auto links = chatListLinks(filterId); + return links ? links->main.get() : nullptr; +} + +PositionChange Entry::adjustByPosInChatList(FilterId filterId) { + const auto links = chatListLinks(filterId); + Assert(links != nullptr); + const auto from = links->main->pos(); + myChatsList(filterId)->adjustByDate(*links); + const auto to = links->main->pos(); return { from, to }; } @@ -161,60 +169,57 @@ void Entry::setChatListTimeId(TimeId date) { } } -int Entry::posInChatList(Dialogs::Mode list) const { - return mainChatListLink(list)->pos(); +int Entry::posInChatList(FilterId filterId) const { + return mainChatListLink(filterId)->pos(); } -not_null Entry::addToChatList(Mode list) { - if (!inChatList(list)) { - chatListLinks(list) = myChatsList(list)->addToEnd(_key); - if (list == Mode::All) { - owner().unreadEntryChanged(_key, true); - } +not_null Entry::addToChatList(FilterId filterId) { + if (const auto main = maybeMainChatListLink(filterId)) { + return main; } - return mainChatListLink(list); + const auto result = _chatListLinks.emplace( + filterId, + myChatsList(filterId)->addToEnd(_key) + ).first->second.main; + if (!filterId) { + owner().unreadEntryChanged(_key, true); + } + return result; } -void Entry::removeFromChatList(Dialogs::Mode list) { - if (inChatList(list)) { - myChatsList(list)->del(_key); - chatListLinks(list).clear(); - if (list == Mode::All) { - owner().unreadEntryChanged(_key, false); - } +void Entry::removeFromChatList(FilterId filterId) { + const auto i = _chatListLinks.find(filterId); + if (i == end(_chatListLinks)) { + return; + } + myChatsList(filterId)->del(_key); + _chatListLinks.erase(i); + if (!filterId) { + owner().unreadEntryChanged(_key, false); } } -void Entry::removeChatListEntryByLetter(Mode list, QChar letter) { - Expects(letter != 0); - - if (inChatList(list)) { - chatListLinks(list).remove(letter); +void Entry::removeChatListEntryByLetter(FilterId filterId, QChar letter) { + const auto i = _chatListLinks.find(filterId); + if (i != end(_chatListLinks)) { + i->second.letters.remove(letter); } } void Entry::addChatListEntryByLetter( - Mode list, + FilterId filterId, QChar letter, not_null row) { - Expects(letter != 0); - - if (inChatList(list)) { - chatListLinks(list).emplace(letter, row); + const auto i = _chatListLinks.find(filterId); + if (i != end(_chatListLinks)) { + i->second.letters.emplace(letter, row); } } void Entry::updateChatListEntry() const { if (const auto main = App::main()) { - if (inChatList()) { - main->repaintDialogRow( - Mode::All, - mainChatListLink(Mode::All)); - if (inChatList(Mode::Important)) { - main->repaintDialogRow( - Mode::Important, - mainChatListLink(Mode::Important)); - } + for (const auto &[filterId, links] : _chatListLinks) { + main->repaintDialogRow(filterId, links.main); } if (session().supportMode() && !session().settings().supportAllSearchResults()) { @@ -223,8 +228,8 @@ void Entry::updateChatListEntry() const { } } -not_null Entry::myChatsList(Mode list) const { - return owner().chatsList(folder())->indexed(list); +not_null Entry::myChatsList(FilterId filterId) const { + return owner().chatsList(folder())->indexed(filterId); } } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index ed0ec0191..0f8945693 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -24,7 +24,11 @@ namespace Dialogs { class Row; class IndexedList; -using RowsByLetter = base::flat_map>; + +struct RowsByLetter { + not_null main; + base::flat_map> letters; +}; enum class SortMode { Date = 0x00, @@ -32,11 +36,6 @@ enum class SortMode { Add = 0x02, }; -enum class Mode { - All = 0x00, - Important = 0x01, -}; - struct PositionChange { int from = -1; int to = -1; @@ -94,19 +93,19 @@ public: Entry &operator=(const Entry &other) = delete; virtual ~Entry() = default; - Data::Session &owner() const; - Main::Session &session() const; + [[nodiscard]] Data::Session &owner() const; + [[nodiscard]] Main::Session &session() const; - PositionChange adjustByPosInChatList(Mode list); - bool inChatList(Mode list = Mode::All) const { - return !chatListLinks(list).empty(); + PositionChange adjustByPosInChatList(FilterId filterId); + [[nodiscard]] bool inChatList(FilterId filterId = 0) const { + return _chatListLinks.contains(filterId); } - int posInChatList(Mode list) const; - not_null addToChatList(Mode list); - void removeFromChatList(Mode list); - void removeChatListEntryByLetter(Mode list, QChar letter); + [[nodiscard]] int posInChatList(FilterId filterId) const; + not_null addToChatList(FilterId filterId); + void removeFromChatList(FilterId filterId); + void removeChatListEntryByLetter(FilterId filterId, QChar letter); void addChatListEntryByLetter( - Mode list, + FilterId filterId, QChar letter, not_null row); void updateChatListEntry() const; @@ -131,7 +130,6 @@ public: static constexpr auto kArchiveFixOnTopIndex = 1; static constexpr auto kProxyPromotionFixOnTopIndex = 2; - virtual bool toImportant() const = 0; virtual bool shouldBeInChatList() const = 0; virtual int chatListUnreadCount() const = 0; virtual bool chatListUnreadMark() const = 0; @@ -190,15 +188,16 @@ private: void notifyUnreadStateChange(const UnreadState &wasState); void setChatListExistence(bool exists); - RowsByLetter &chatListLinks(Mode list); - const RowsByLetter &chatListLinks(Mode list) const; - Row *mainChatListLink(Mode list) const; + RowsByLetter *chatListLinks(FilterId filterId); + const RowsByLetter *chatListLinks(FilterId filterId) const; + not_null mainChatListLink(FilterId filterId) const; + Row *maybeMainChatListLink(FilterId filterId) const; - not_null myChatsList(Mode list) const; + not_null myChatsList(FilterId filterId) const; not_null _owner; Dialogs::Key _key; - RowsByLetter _chatListLinks[2]; + base::flat_map _chatListLinks; uint64 _sortKeyInChatList = 0; int _pinnedIndex = 0; bool _isProxyPromoted = false; diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp index b5bb69044..2ee60cebb 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -20,16 +20,17 @@ IndexedList::IndexedList(SortMode sortMode) } RowsByLetter IndexedList::addToEnd(Key key) { - RowsByLetter result; - if (!_list.contains(key)) { - result.emplace(0, _list.addToEnd(key)); - for (const auto ch : key.entry()->chatListFirstLetters()) { - auto j = _index.find(ch); - if (j == _index.cend()) { - j = _index.emplace(ch, _sortMode).first; - } - result.emplace(ch, j->second.addToEnd(key)); + if (const auto row = _list.getRow(key)) { + return { row }; + } + + auto result = RowsByLetter{ _list.addToEnd(key) }; + for (const auto ch : key.entry()->chatListFirstLetters()) { + auto j = _index.find(ch); + if (j == _index.cend()) { + j = _index.emplace(ch, _sortMode).first; } + result.letters.emplace(ch, j->second.addToEnd(key)); } return result; } @@ -51,13 +52,10 @@ Row *IndexedList::addByName(Key key) { } void IndexedList::adjustByDate(const RowsByLetter &links) { - for (const auto [ch, row] : links) { - if (ch == QChar(0)) { - _list.adjustByDate(row); - } else { - if (auto it = _index.find(ch); it != _index.cend()) { - it->second.adjustByDate(row); - } + _list.adjustByDate(links.main); + for (const auto [ch, row] : links.letters) { + if (auto it = _index.find(ch); it != _index.cend()) { + it->second.adjustByDate(row); } } } @@ -95,19 +93,19 @@ void IndexedList::peerNameChanged( if (_sortMode == SortMode::Name) { adjustByName(history, oldLetters); } else { - adjustNames(Dialogs::Mode::All, history, oldLetters); + adjustNames(FilterId(), history, oldLetters); } } } void IndexedList::peerNameChanged( - Mode list, + FilterId filterId, not_null peer, const base::flat_set &oldLetters) { Expects(_sortMode == SortMode::Date); if (const auto history = peer->owner().historyLoaded(peer)) { - adjustNames(list, history, oldLetters); + adjustNames(filterId, history, oldLetters); } } @@ -149,7 +147,7 @@ void IndexedList::adjustByName( } void IndexedList::adjustNames( - Mode list, + FilterId filterId, not_null history, const base::flat_set &oldLetters) { const auto key = Dialogs::Key(history); @@ -168,7 +166,7 @@ void IndexedList::adjustNames( } for (auto ch : toRemove) { if (_sortMode == SortMode::Date) { - history->removeChatListEntryByLetter(list, ch); + history->removeChatListEntryByLetter(filterId, ch); } if (auto it = _index.find(ch); it != _index.cend()) { it->second.del(key, mainRow); @@ -181,7 +179,7 @@ void IndexedList::adjustNames( } auto row = j->second.addToEnd(key); if (_sortMode == SortMode::Date) { - history->addChatListEntryByLetter(list, ch, row); + history->addChatListEntryByLetter(filterId, ch, row); } } } @@ -250,8 +248,4 @@ std::vector> IndexedList::filtered( return result; } -IndexedList::~IndexedList() { - clear(); -} - } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h index 05b4fb987..65bb30d89 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h @@ -33,7 +33,7 @@ public: //For sortMode == SortMode::Date void peerNameChanged( - Mode list, + FilterId filterId, not_null peer, const base::flat_set &oldChars); @@ -49,8 +49,6 @@ public: } std::vector> filtered(const QStringList &words) const; - ~IndexedList(); - // Part of List interface is duplicated here for all() list. int size() const { return all().size(); } bool empty() const { return all().empty(); } @@ -78,7 +76,7 @@ private: Key key, const base::flat_set &oldChars); void adjustNames( - Mode list, + FilterId filterId, not_null history, const base::flat_set &oldChars); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 5af51519d..f8b47ffd5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -118,9 +118,9 @@ InnerWidget::InnerWidget( setAttribute(Qt::WA_OpaquePaintEvent, true); #endif // OS_MAC_OLD - _mode = Global::DialogsModeEnabled() - ? Global::DialogsMode() - : Dialogs::Mode::All; + _filterId = Global::DialogsFiltersEnabled() + ? Global::DialogsFilterId() + : 0; _addContactLnk->addClickHandler([] { App::wnd()->onShowAddContact(); }); _cancelSearchInChat->setClickedCallback([=] { cancelSearchInChat(); }); @@ -280,7 +280,7 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { _collapsedSelected = -1; _collapsedRows.clear(); - if (!_openedFolder && Global::DialogsModeEnabled()) { + if (!_openedFolder && Global::DialogsFiltersEnabled()) { _collapsedRows.push_back(std::make_unique()); } const auto list = shownDialogs(); @@ -368,7 +368,7 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) { //const auto lastMousePosition = _lastMousePosition; clearSelection(); _openedFolder = folder; - _mode = _openedFolder ? Mode::All : Global::DialogsMode(); + _filterId = _openedFolder ? 0 : Global::DialogsFilterId(); refreshWithCollapsedRows(true); // This doesn't work, because we clear selection in leaveEvent on hide. //if (mouseSelection && lastMousePosition) { @@ -686,12 +686,12 @@ void InnerWidget::paintCollapsedRow( const auto narrow = (width() <= smallWidth); const auto text = row->folder ? row->folder->chatListName() - : (_mode == Dialogs::Mode::Important) + : _filterId // #TODO filters ? (narrow ? "Show" : tr::lng_dialogs_show_all_chats(tr::now)) : (narrow ? "Hide" : tr::lng_dialogs_hide_muted_chats(tr::now)); const auto unread = row->folder ? row->folder->chatListUnreadCount() - : (_mode == Dialogs::Mode::Important) + : _filterId // #TODO filters ? session().data().unreadOnlyMutedBadge() : 0; Layout::PaintCollapsedRow( @@ -1049,11 +1049,11 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { }); } else if (base::in_range(_filteredPressed, 0, _filterResults.size())) { const auto row = _filterResults[_filteredPressed]; - const auto list = _mode; + const auto filterId = _filterId; row->addRipple( e->pos() - QPoint(0, filteredOffset() + _filteredPressed * st::dialogsRowHeight), QSize(width(), st::dialogsRowHeight), - [=] { repaintDialogRow(list, row); }); + [=] { repaintDialogRow(filterId, row); }); } else if (base::in_range(_peerSearchPressed, 0, _peerSearchResults.size())) { auto &result = _peerSearchResults[_peerSearchPressed]; auto row = &result->row; @@ -1435,10 +1435,10 @@ void InnerWidget::refreshDialog(Key key) { } const auto result = session().data().refreshChatListEntry(key); - const auto changed = (_mode == Mode::Important) + const auto changed = _filterId // #TODO filters ? result.importantChanged : result.changed; - const auto moved = (_mode == Mode::Important) + const auto moved = _filterId // #TODO filters ? result.importantMoved : result.moved; @@ -1511,17 +1511,17 @@ int InnerWidget::defaultRowTop(not_null row) const { } void InnerWidget::repaintDialogRow( - Mode list, + FilterId filterId, not_null row) { if (_state == WidgetState::Default) { - if (_mode == list) { + if (_filterId == filterId) { if (const auto folder = row->folder()) { repaintCollapsedFolderRow(folder); } update(0, defaultRowTop(row), width(), st::dialogsRowHeight); } } else if (_state == WidgetState::Filtered) { - if (list == Mode::All) { + if (!filterId) { for (auto i = 0, l = int(_filterResults.size()); i != l; ++i) { if (_filterResults[i]->key() == row->key()) { update( @@ -1645,10 +1645,10 @@ void InnerWidget::updateSelectedRow(Key key) { if (_state == WidgetState::Default) { if (key) { const auto entry = key.entry(); - if (!entry->inChatList(_mode)) { + if (!entry->inChatList(_filterId)) { return; } - auto position = entry->posInChatList(_mode); + auto position = entry->posInChatList(_filterId); auto top = dialogsOffset(); if (base::in_range(position, 0, _pinnedRows.size())) { top += qRound(_pinnedRows[position].yadd.current()); @@ -1680,7 +1680,7 @@ void InnerWidget::updateSelectedRow(Key key) { } not_null InnerWidget::shownDialogs() const { - return session().data().chatsList(_openedFolder)->indexed(_mode); + return session().data().chatsList(_openedFolder)->indexed(_filterId); } void InnerWidget::leaveEventHook(QEvent *e) { @@ -2137,7 +2137,8 @@ void InnerWidget::peerSearchReceived( } void InnerWidget::notify_historyMuteUpdated(History *history) { - if (!Global::DialogsModeEnabled() || !history->inChatList()) { + // #TODO filters + if (!Global::DialogsFiltersEnabled() || !history->inChatList()) { return; } refreshDialog(history); @@ -2425,22 +2426,23 @@ void InnerWidget::scrollToEntry(const RowDescriptor &entry) { void InnerWidget::selectSkipPage(int32 pixels, int32 direction) { clearMouseSelection(); + const auto list = shownDialogs(); int toSkip = pixels / int(st::dialogsRowHeight); if (_state == WidgetState::Default) { if (!_selected) { - if (direction > 0 && shownDialogs()->size() > _skipTopDialogs) { - _selected = *(shownDialogs()->cbegin() + _skipTopDialogs); + if (direction > 0 && list->size() > _skipTopDialogs) { + _selected = *(list->cbegin() + _skipTopDialogs); _collapsedSelected = -1; } else { return; } } if (direction > 0) { - for (auto i = shownDialogs()->cfind(_selected), end = shownDialogs()->cend(); i != end && (toSkip--); ++i) { + for (auto i = list->cfind(_selected), end = list->cend(); i != end && (toSkip--); ++i) { _selected = *i; } } else { - for (auto i = shownDialogs()->cfind(_selected), b = shownDialogs()->cbegin(); i != b && (*i)->pos() > _skipTopDialogs && (toSkip--);) { + for (auto i = list->cfind(_selected), b = list->cbegin(); i != b && (*i)->pos() > _skipTopDialogs && (toSkip--);) { _selected = *(--i); } if (toSkip && !_collapsedRows.empty()) { @@ -2463,12 +2465,13 @@ void InnerWidget::selectSkipPage(int32 pixels, int32 direction) { void InnerWidget::loadPeerPhotos() { if (!parentWidget()) return; + const auto list = shownDialogs(); auto yFrom = _visibleTop; auto yTo = _visibleTop + (_visibleBottom - _visibleTop) * (PreloadHeightsCount + 1); if (_state == WidgetState::Default) { - auto otherStart = shownDialogs()->size() * st::dialogsRowHeight; + auto otherStart = list->size() * st::dialogsRowHeight; if (yFrom < otherStart) { - for (auto i = shownDialogs()->cfind(yFrom, st::dialogsRowHeight), end = shownDialogs()->cend(); i != end; ++i) { + for (auto i = list->cfind(yFrom, st::dialogsRowHeight), end = list->cend(); i != end; ++i) { if (((*i)->pos() * st::dialogsRowHeight) >= yTo) { break; } @@ -2532,12 +2535,13 @@ bool InnerWidget::chooseCollapsedRow() { void InnerWidget::switchImportantChats() { clearSelection(); - if (Global::DialogsMode() == Mode::All) { - Global::SetDialogsMode(Mode::Important); - } else { - Global::SetDialogsMode(Mode::All); - } - _mode = Global::DialogsMode(); + // #TODO filters + //if (Global::DialogsFilterId() == 0) { + // Global::SetDialogsMode(Mode::Important); + //} else { + // Global::SetDialogsMode(Mode::All); + //} + _filterId = Global::DialogsFilterId(); Local::writeUserSettings(); refreshWithCollapsedRows(true); _collapsedSelected = 0; @@ -2633,9 +2637,10 @@ RowDescriptor InnerWidget::chatListEntryBefore( return RowDescriptor(); } if (_state == WidgetState::Default) { - if (const auto row = shownDialogs()->getRow(which.key)) { - const auto i = shownDialogs()->cfind(row); - if (i != shownDialogs()->cbegin()) { + const auto list = shownDialogs(); + if (const auto row = list->getRow(which.key)) { + const auto i = list->cfind(row); + if (i != list->cbegin()) { return RowDescriptor( (*(i - 1))->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); @@ -2710,9 +2715,10 @@ RowDescriptor InnerWidget::chatListEntryAfter( return RowDescriptor(); } if (_state == WidgetState::Default) { - if (const auto row = shownDialogs()->getRow(which.key)) { - const auto i = shownDialogs()->cfind(row) + 1; - if (i != shownDialogs()->cend()) { + const auto list = shownDialogs(); + if (const auto row = list->getRow(which.key)) { + const auto i = list->cfind(row) + 1; + if (i != list->cend()) { return RowDescriptor( (*i)->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); @@ -2775,8 +2781,9 @@ RowDescriptor InnerWidget::chatListEntryAfter( RowDescriptor InnerWidget::chatListEntryFirst() const { if (_state == WidgetState::Default) { - const auto i = shownDialogs()->cbegin(); - if (i != shownDialogs()->cend()) { + const auto list = shownDialogs(); + const auto i = list->cbegin(); + if (i != list->cend()) { return RowDescriptor( (*i)->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); @@ -2800,8 +2807,9 @@ RowDescriptor InnerWidget::chatListEntryFirst() const { RowDescriptor InnerWidget::chatListEntryLast() const { if (_state == WidgetState::Default) { - const auto i = shownDialogs()->cend(); - if (i != shownDialogs()->cbegin()) { + const auto list = shownDialogs(); + const auto i = list->cend(); + if (i != list->cbegin()) { return RowDescriptor( (*(i - 1))->key(), FullMsgId(NoChannel, ShowAtUnreadMsgId)); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 0015d4501..ddd7ec09c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -38,7 +38,6 @@ namespace Dialogs { class Row; class FakeRow; class IndexedList; -enum class Mode; struct ChosenRow { Key key; @@ -89,7 +88,7 @@ public: void refreshDialog(Key key); void removeDialog(Key key); - void repaintDialogRow(Mode list, not_null row); + void repaintDialogRow(FilterId filterId, not_null row); void repaintDialogRow(RowDescriptor row); void dragLeft(); @@ -310,7 +309,7 @@ private: not_null _controller; - Mode _mode = Mode(); + FilterId _filterId = 0; bool _mouseSelection = false; std::optional _lastMousePosition; Qt::MouseButton _pressButton = Qt::LeftButton; diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index f0827f9b7..c8154578d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -14,7 +14,6 @@ namespace Dialogs { MainList::MainList(rpl::producer pinnedLimit) : _all(SortMode::Date) -, _important(SortMode::Date) , _pinned(1) { _unreadState.known = true; @@ -29,8 +28,10 @@ MainList::MainList(rpl::producer pinnedLimit) ) | rpl::start_with_next([=](const Notify::PeerUpdate &update) { const auto peer = update.peer; const auto &oldLetters = update.oldNameFirstLetters; - _all.peerNameChanged(Mode::All, peer, oldLetters); - _important.peerNameChanged(Mode::Important, peer, oldLetters); + _all.peerNameChanged(FilterId(), peer, oldLetters); + for (auto &[filterId, list] : _other) { + list.peerNameChanged(filterId, peer, oldLetters); + } }, _lifetime); } @@ -48,7 +49,9 @@ void MainList::setLoaded(bool loaded) { void MainList::clear() { _all.clear(); - _important.clear(); + for (auto &[filterId, list] : _other) { // #TODO filters _other.clear?.. + list.clear(); + } _unreadState = UnreadState(); } @@ -72,12 +75,19 @@ UnreadState MainList::unreadState() const { return _unreadState; } -not_null MainList::indexed(Mode list) { - return (list == Mode::All) ? &_all : &_important; +not_null MainList::indexed(FilterId filterId) { + if (!filterId) { + return &_all; + } + const auto i = _other.find(filterId); + if (i != end(_other)) { + return &i->second; + } + return &_other.emplace(filterId, SortMode::Date).first->second; } -not_null MainList::indexed(Mode list) const { - return (list == Mode::All) ? &_all : &_important; +not_null MainList::indexed() const { + return &_all; } not_null MainList::pinned() { diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.h b/Telegram/SourceFiles/dialogs/dialogs_main_list.h index ce5831105..4319b836a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.h @@ -27,16 +27,16 @@ public: void unreadEntryChanged( const Dialogs::UnreadState &state, bool added); - UnreadState unreadState() const; + [[nodiscard]] UnreadState unreadState() const; - not_null indexed(Mode list = Mode::All); - not_null indexed(Mode list = Mode::All) const; + not_null indexed(FilterId filterId = 0); + not_null indexed() const; not_null pinned(); not_null pinned() const; private: IndexedList _all; - IndexedList _important; + base::flat_map _other; PinnedList _pinned; UnreadState _unreadState; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 957baa25e..1f6e0bb9c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -528,9 +528,9 @@ void Widget::refreshDialog(Key key) { } void Widget::repaintDialogRow( - Mode list, + FilterId filterId, not_null row) { - _inner->repaintDialogRow(list, row); + _inner->repaintDialogRow(filterId, row); } void Widget::repaintDialogRow(RowDescriptor row) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 710f32c66..1fce349f1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -41,7 +41,6 @@ class ConnectionState; namespace Dialogs { -enum class Mode; struct RowDescriptor; class Row; class FakeRow; @@ -63,7 +62,7 @@ public: void refreshDialog(Key key); void removeDialog(Key key); - void repaintDialogRow(Mode list, not_null row); + void repaintDialogRow(FilterId filterId, not_null row); void repaintDialogRow(RowDescriptor row); void jumpToTop(); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index a38a0fa73..ed84a2a63 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -314,8 +314,8 @@ struct Data { bool AdaptiveForWide = true; base::Observable AdaptiveChanged; - bool DialogsModeEnabled = false; - Dialogs::Mode DialogsMode = Dialogs::Mode::All; + bool DialogsFiltersEnabled = false; + FilterId DialogsFilterId = 0; bool ModerateModeEnabled = false; bool ScreenIsLocked = false; @@ -444,8 +444,8 @@ DefineVar(Global, Adaptive::ChatLayout, AdaptiveChatLayout); DefineVar(Global, bool, AdaptiveForWide); DefineRefVar(Global, base::Observable, AdaptiveChanged); -DefineVar(Global, bool, DialogsModeEnabled); -DefineVar(Global, Dialogs::Mode, DialogsMode); +DefineVar(Global, bool, DialogsFiltersEnabled); +DefineVar(Global, FilterId, DialogsFilterId); DefineVar(Global, bool, ModerateModeEnabled); DefineVar(Global, bool, ScreenIsLocked); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 9c4f41615..ad81566ba 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -18,10 +18,6 @@ namespace Data { struct FileOrigin; } // namespace Data -namespace Dialogs { -enum class Mode; -} // namespace Dialogs - namespace InlineBots { namespace Layout { class ItemBase; @@ -154,8 +150,8 @@ DeclareVar(Adaptive::ChatLayout, AdaptiveChatLayout); DeclareVar(bool, AdaptiveForWide); DeclareRefVar(base::Observable, AdaptiveChanged); -DeclareVar(bool, DialogsModeEnabled); -DeclareVar(Dialogs::Mode, DialogsMode); +DeclareVar(bool, DialogsFiltersEnabled); +DeclareVar(FilterId, DialogsFilterId); DeclareVar(bool, ModerateModeEnabled); DeclareVar(bool, ScreenIsLocked); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 7b0767027..da213ff5e 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -838,7 +838,7 @@ void History::setUnreadMentionsCount(int count) { } _unreadMentionsCount = count; const auto has = (count > 0); - if (has != had && Global::DialogsModeEnabled()) { + if (has != had && Global::DialogsFiltersEnabled()) { // #TODO filters Notify::historyMuteUpdated(this); updateChatListEntry(); } @@ -1912,8 +1912,6 @@ void History::clearFolder() { } void History::setFolderPointer(Data::Folder *folder) { - using Mode = Dialogs::Mode; - if (_folder == folder) { return; } @@ -1922,23 +1920,24 @@ void History::setFolderPointer(Data::Folder *folder) { } const auto wasKnown = folderKnown(); const auto wasInList = inChatList(); - const auto wasInImportant = wasInList && inChatList(Mode::Important); - if (wasInList) { - removeFromChatList(Mode::All); - if (wasInImportant) { - removeFromChatList(Mode::Important); - } - } + // #TODO filters + //const auto wasInImportant = wasInList && inChatList(Mode::Important); + //if (wasInList) { + // removeFromChatList(Mode::All); + // if (wasInImportant) { + // removeFromChatList(Mode::Important); + // } + //} const auto was = _folder.value_or(nullptr); _folder = folder; if (was) { was->unregisterOne(this); } if (wasInList) { - addToChatList(Mode::All); - if (wasInImportant) { - addToChatList(Mode::Important); - } + addToChatList(0); + //if (wasInImportant) { // #TODO filters + // addToChatList(Mode::Important); + //} owner().chatsListChanged(was); owner().chatsListChanged(folder); } else if (!wasKnown) { @@ -2602,10 +2601,6 @@ bool History::shouldBeInChatList() const { || (lastMessage() != nullptr); } -bool History::toImportant() const { - return !mute() || hasUnreadMentions(); -} - void History::unknownMessageDeleted(MsgId messageId) { if (_inboxReadBefore && messageId >= *_inboxReadBefore) { owner().histories().requestDialogEntry(this); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 8ef86d070..9c321efaf 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -331,7 +331,6 @@ public: int fixedOnTopIndex() const override; void updateChatListExistence() override; bool shouldBeInChatList() const override; - bool toImportant() const override; int chatListUnreadCount() const override; bool chatListUnreadMark() const override; bool chatListMutedBadge() const override; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index b5a8dcfa9..68f38845a 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -2183,9 +2183,9 @@ QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶m } void MainWidget::repaintDialogRow( - Dialogs::Mode list, + FilterId filterId, not_null row) { - _dialogs->repaintDialogRow(list, row); + _dialogs->repaintDialogRow(filterId, row); } void MainWidget::repaintDialogRow(Dialogs::RowDescriptor row) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 61bbdab6e..928dd8aab 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -44,7 +44,6 @@ struct RowDescriptor; class Row; class Key; class Widget; -enum class Mode; } // namespace Dialogs namespace Media { @@ -136,7 +135,7 @@ public: void refreshDialog(Dialogs::Key key); void removeDialog(Dialogs::Key key); - void repaintDialogRow(Dialogs::Mode list, not_null row); + void repaintDialogRow(FilterId filterId, not_null row); void repaintDialogRow(Dialogs::RowDescriptor row); void windowShown(); diff --git a/Telegram/SourceFiles/settings/settings_codes.cpp b/Telegram/SourceFiles/settings/settings_codes.cpp index e50471fa8..b86179618 100644 --- a/Telegram/SourceFiles/settings/settings_codes.cpp +++ b/Telegram/SourceFiles/settings/settings_codes.cpp @@ -70,7 +70,7 @@ auto GenerateCodes() { Unexpected("Crashed in Settings!"); }); codes.emplace(qsl("workmode"), [](::Main::Session *session) { - auto text = Global::DialogsModeEnabled() ? qsl("Disable work mode?") : qsl("Enable work mode?"); + auto text = Global::DialogsFiltersEnabled() ? qsl("Disable filters?") : qsl("Enable filters?"); Ui::show(Box(text, [] { Core::App().switchWorkMode(); })); diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 0418ce73a..26ef1bf9f 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -613,7 +613,7 @@ enum { dbiHiddenPinnedMessages = 0x39, dbiRecentEmoji = 0x3a, dbiEmojiVariants = 0x3b, - dbiDialogsMode = 0x40, + dbiDialogsFilters = 0x40, dbiModerateMode = 0x41, dbiVideoVolume = 0x42, dbiStickersRecentLimit = 0x43, @@ -1167,20 +1167,20 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting } } break; - case dbiDialogsMode: { + case dbiDialogsFilters: { qint32 enabled, modeInt; stream >> enabled >> modeInt; if (!_checkStreamStatus(stream)) return false; - Global::SetDialogsModeEnabled(enabled == 1); - auto mode = Dialogs::Mode::All; + Global::SetDialogsFiltersEnabled(enabled == 1); + auto mode = FilterId(0); if (enabled) { - mode = static_cast(modeInt); - if (mode != Dialogs::Mode::All && mode != Dialogs::Mode::Important) { - mode = Dialogs::Mode::All; + mode = FilterId(modeInt); + if (mode == 1) { // #TODO filters + } } - Global::SetDialogsMode(mode); + Global::SetDialogsFilterId(mode); } break; case dbiModerateMode: { @@ -2140,7 +2140,7 @@ void _writeUserSettings() { data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); data.stream << quint32(dbiSongVolume) << qint32(qRound(Global::SongVolume() * 1e6)); data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6)); - data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast(Global::DialogsMode()); + data.stream << quint32(dbiDialogsFilters) << qint32(Global::DialogsFiltersEnabled() ? 1 : 0) << static_cast(Global::DialogsFilterId()); data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer()); data.stream << quint32(dbiCacheSettings) << qint64(_cacheTotalSizeLimit) << qint32(_cacheTotalTimeLimit) << qint64(_cacheBigFileTotalSizeLimit) << qint32(_cacheBigFileTotalTimeLimit); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 0856bd074..fe82914c7 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -129,7 +129,7 @@ SessionController::SessionController( ) | rpl::filter([=](Data::Folder *folder) { return (folder != nullptr) && (folder == _openedFolder.current()) - && folder->chatsList()->indexed(Global::DialogsMode())->empty(); + && folder->chatsList()->indexed()->empty(); }) | rpl::start_with_next([=](Data::Folder *folder) { folder->updateChatListSortPosition(); closeFolder(); From 2f1ee6f1fa0925e6778ad4492ddc71aaf0c1dd5d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Feb 2020 15:34:05 +0400 Subject: [PATCH 010/115] Show two hardcoded filters in Ui. --- .../SourceFiles/data/data_chat_filters.cpp | 37 ++++++++++ Telegram/SourceFiles/data/data_chat_filters.h | 35 +++++++-- Telegram/SourceFiles/data/data_session.cpp | 62 ++++++++++------ Telegram/SourceFiles/data/data_session.h | 19 +++-- .../dialogs/dialogs_inner_widget.cpp | 71 ++++++++++--------- .../dialogs/dialogs_inner_widget.h | 4 +- .../SourceFiles/dialogs/dialogs_main_list.cpp | 4 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 4 -- Telegram/SourceFiles/dialogs/dialogs_widget.h | 2 - Telegram/SourceFiles/facades.cpp | 4 -- Telegram/SourceFiles/facades.h | 1 - Telegram/SourceFiles/history/history.cpp | 33 +++++---- .../SourceFiles/history/history_widget.cpp | 3 + Telegram/SourceFiles/mainwidget.cpp | 4 -- Telegram/SourceFiles/mainwidget.h | 1 - 15 files changed, 179 insertions(+), 105 deletions(-) diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index b71d77cbb..d7c0df56b 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -24,6 +24,10 @@ ChatFilter::ChatFilter( , _flags(flags) { } +QString ChatFilter::title() const { + return _title; +} + bool ChatFilter::contains(not_null history) const { const auto flag = [&] { const auto peer = history->peer; @@ -43,6 +47,9 @@ bool ChatFilter::contains(not_null history) const { Unexpected("Peer type in ChatFilter::contains."); } }(); + if (history->folder()) { + return false; + } return false || ((_flags & flag) && (!(_flags & Flag::NoMuted) || !history->mute()) @@ -50,4 +57,34 @@ bool ChatFilter::contains(not_null history) const { || _always.contains(history); } +ChatFilters::ChatFilters(not_null owner) : _owner(owner) { + using Flag = ChatFilter::Flag; + const auto all = Flag::Users + | Flag::SecretChats + | Flag::PrivateGroups + | Flag::PublicGroups + | Flag::Broadcasts + | Flag::Bots; + _list.emplace( + 1, + ChatFilter("Unmuted Chats", all | ChatFilter::Flag::NoMuted, {})); + _list.emplace( + 2, + ChatFilter("Unread Chats", all | ChatFilter::Flag::NoRead, {})); +} + +const base::flat_map &ChatFilters::list() const { + return _list; +} + +void ChatFilters::refreshHistory(not_null history) { + _refreshHistoryRequests.fire_copy(history); +} + +auto ChatFilters::refreshHistoryRequests() const +-> rpl::producer> { + return _refreshHistoryRequests.events(); +} + + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index bc12246a0..348afd422 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -13,16 +13,19 @@ class History; namespace Data { +class Session; + class ChatFilter final { public: enum class Flag : uchar { Users = 0x01, - PrivateGroups = 0x02, - PublicGroups = 0x04, - Broadcasts = 0x08, - Bots = 0x10, - NoMuted = 0x20, - NoRead = 0x40, + SecretChats = 0x02, + PrivateGroups = 0x04, + PublicGroups = 0x08, + Broadcasts = 0x10, + Bots = 0x20, + NoMuted = 0x40, + NoRead = 0x80, }; friend constexpr inline bool is_flag_type(Flag) { return true; }; using Flags = base::flags; @@ -33,6 +36,8 @@ public: Flags flags, base::flat_set> always); + [[nodiscard]] QString title() const; + [[nodiscard]] bool contains(not_null history) const; private: @@ -42,4 +47,22 @@ private: }; +class ChatFilters final { +public: + explicit ChatFilters(not_null owner); + + [[nodiscard]] const base::flat_map &list() const; + + void refreshHistory(not_null history); + [[nodiscard]] auto refreshHistoryRequests() const + -> rpl::producer>; + +private: + const not_null _owner; + + base::flat_map _list; + rpl::event_stream> _refreshHistoryRequests; + +}; + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 3479bf6c6..28144cb8a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -184,6 +184,7 @@ Session::Session(not_null session) Local::cacheBigFilePath(), Local::cacheBigFileSettings())) , _chatsList(PinnedDialogsCountMaxValue(session)) +, _chatsFilters(this) , _contactsList(Dialogs::SortMode::Name) , _contactsNoChatsList(Dialogs::SortMode::Name) , _selfDestructTimer([=] { checkSelfDestructItems(); }) @@ -3347,6 +3348,14 @@ not_null Session::chatsList( return folder ? folder->chatsList() : &_chatsList; } +not_null Session::chatsFilters() { + return &_chatsFilters; +} + +not_null Session::chatsFilters() const { + return &_chatsFilters; +} + not_null Session::contactsList() { return &_contactsList; } @@ -3355,33 +3364,42 @@ not_null Session::contactsNoChatsList() { return &_contactsNoChatsList; } -auto Session::refreshChatListEntry(Dialogs::Key key) +auto Session::refreshChatListEntry( + Dialogs::Key key, + FilterId filterIdForResult) -> RefreshChatListEntryResult { using namespace Dialogs; const auto entry = key.entry(); - auto result = RefreshChatListEntryResult(); - result.changed = !entry->inChatList(); - if (result.changed) { + const auto history = key.history(); + auto mainListResult = RefreshChatListEntryResult(); + mainListResult.changed = !entry->inChatList(); + if (mainListResult.changed) { const auto mainRow = entry->addToChatList(0); _contactsNoChatsList.del(key, mainRow); } else { - result.moved = entry->adjustByPosInChatList(0); + mainListResult.moved = entry->adjustByPosInChatList(0); + } + auto result = filterIdForResult + ? RefreshChatListEntryResult() + : mainListResult; + for (const auto &[filterId, filter] : _chatsFilters.list()) { + auto filterResult = RefreshChatListEntryResult(); + if (history && filter.contains(history)) { + filterResult.changed = !entry->inChatList(filterId); + if (filterResult.changed) { + entry->addToChatList(filterId); + } else { + filterResult.moved = entry->adjustByPosInChatList(filterId); + } + } else if (entry->inChatList(filterId)) { + entry->removeFromChatList(filterId); + filterResult.changed = true; + } + if (filterId == filterIdForResult) { + result = filterResult; + } } - //if (Global::DialogsFiltersEnabled()) { // #TODO filters - // if (entry->toImportant()) { - // result.importantChanged = !entry->inChatList(Mode::Important); - // if (result.importantChanged) { - // entry->addToChatList(Mode::Important); - // } else { - // result.importantMoved = entry->adjustByPosInChatList( - // Mode::Important); - // } - // } else if (entry->inChatList(Mode::Important)) { - // entry->removeFromChatList(Mode::Important); - // result.importantChanged = true; - // } - //} return result; } @@ -3390,9 +3408,9 @@ void Session::removeChatListEntry(Dialogs::Key key) { const auto entry = key.entry(); entry->removeFromChatList(0); - //if (Global::DialogsFiltersEnabled()) { // #TODO filters - // entry->removeFromChatList(Mode::Important); - //} + for (const auto &[filterId, filter] : _chatsFilters.list()) { + entry->removeFromChatList(filterId); + } if (_contactsList.contains(key)) { if (!_contactsNoChatsList.contains(key)) { _contactsNoChatsList.addByName(key); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index f7d7a95f7..d3d2b0038 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_main_list.h" #include "data/data_groups.h" #include "data/data_notify_settings.h" +#include "data/data_chat_filters.h" #include "history/history_location_manager.h" #include "base/timer.h" #include "base/flags.h" @@ -621,19 +622,22 @@ public: //FeedId defaultFeedId() const; //rpl::producer defaultFeedIdValue() const; - not_null chatsList(Data::Folder *folder = nullptr); - not_null chatsList( + [[nodiscard]] not_null chatsList( + Data::Folder *folder = nullptr); + [[nodiscard]] not_null chatsList( Data::Folder *folder = nullptr) const; - not_null contactsList(); - not_null contactsNoChatsList(); + [[nodiscard]] not_null chatsFilters(); + [[nodiscard]] not_null chatsFilters() const; + [[nodiscard]] not_null contactsList(); + [[nodiscard]] not_null contactsNoChatsList(); struct RefreshChatListEntryResult { bool changed = false; - bool importantChanged = false; Dialogs::PositionChange moved; - Dialogs::PositionChange importantMoved; }; - RefreshChatListEntryResult refreshChatListEntry(Dialogs::Key key); + RefreshChatListEntryResult refreshChatListEntry( + Dialogs::Key key, + FilterId filterIdForResult); void removeChatListEntry(Dialogs::Key key); struct DialogsRowReplacement { @@ -870,6 +874,7 @@ private: Stickers::SavedGifs _savedGifs; Dialogs::MainList _chatsList; + ChatFilters _chatsFilters; Dialogs::IndexedList _contactsList; Dialogs::IndexedList _contactsNoChatsList; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f8b47ffd5..f5eb1d253 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -81,10 +81,12 @@ int PinnedDialogsCount(not_null list) { } // namespace struct InnerWidget::CollapsedRow { - explicit CollapsedRow(Data::Folder *folder = nullptr) : folder(folder) { + CollapsedRow(Data::Folder *folder, FilterId filterId) + : folder(folder), filterId(filterId) { } Data::Folder *folder = nullptr; + FilterId filterId = 0; BasicRow row; }; @@ -202,6 +204,14 @@ InnerWidget::InnerWidget( refresh(); }, lifetime()); + session().data().chatsFilters()->refreshHistoryRequests( + ) | rpl::start_with_next([=](not_null history) { + if (history->inChatList() + && !session().data().chatsFilters()->list().empty()) { + refreshDialog(history); + } + }, lifetime()); + subscribe(Window::Theme::Background(), [=](const Window::Theme::BackgroundUpdate &data) { if (data.paletteChanged()) { Layout::clearUnreadBadgesCache(); @@ -281,7 +291,13 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { _collapsedRows.clear(); if (!_openedFolder && Global::DialogsFiltersEnabled()) { - _collapsedRows.push_back(std::make_unique()); + if (_filterId) { + _collapsedRows.push_back(std::make_unique(nullptr, 0)); + } else { + for (const auto &[filterId, filter] : session().data().chatsFilters()->list()) { + _collapsedRows.push_back(std::make_unique(nullptr, filterId)); + } + } } const auto list = shownDialogs(); const auto archive = !list->empty() @@ -296,8 +312,8 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { setPressed(nullptr); } _skipTopDialogs = 1; - if (!inMainMenu) { - _collapsedRows.push_back(std::make_unique(archive)); + if (!inMainMenu && !_filterId) { + _collapsedRows.push_back(std::make_unique(archive, 0)); } } else { _skipTopDialogs = 0; @@ -686,12 +702,13 @@ void InnerWidget::paintCollapsedRow( const auto narrow = (width() <= smallWidth); const auto text = row->folder ? row->folder->chatListName() - : _filterId // #TODO filters + : _filterId ? (narrow ? "Show" : tr::lng_dialogs_show_all_chats(tr::now)) - : (narrow ? "Hide" : tr::lng_dialogs_hide_muted_chats(tr::now)); + : session().data().chatsFilters()->list().find( + row->filterId)->second.title(); const auto unread = row->folder ? row->folder->chatListUnreadCount() - : _filterId // #TODO filters + : _filterId ? session().data().unreadOnlyMutedBadge() : 0; Layout::PaintCollapsedRow( @@ -1434,17 +1451,12 @@ void InnerWidget::refreshDialog(Key key) { } } - const auto result = session().data().refreshChatListEntry(key); - const auto changed = _filterId // #TODO filters - ? result.importantChanged - : result.changed; - const auto moved = _filterId // #TODO filters - ? result.importantMoved - : result.moved; - + const auto result = session().data().refreshChatListEntry( + key, + _filterId); const auto rowHeight = st::dialogsRowHeight; - const auto from = dialogsOffset() + moved.from * rowHeight; - const auto to = dialogsOffset() + moved.to * rowHeight; + const auto from = dialogsOffset() + result.moved.from * rowHeight; + const auto to = dialogsOffset() + result.moved.to * rowHeight; if (!_dragging && (from != to) && (key.entry()->folder() == _openedFolder)) { @@ -1452,7 +1464,7 @@ void InnerWidget::refreshDialog(Key key) { emit dialogMoved(from, to); } - if (changed) { + if (result.changed) { refresh(); } else if (_state == WidgetState::Default && from != to) { update( @@ -2136,14 +2148,6 @@ void InnerWidget::peerSearchReceived( refresh(); } -void InnerWidget::notify_historyMuteUpdated(History *history) { - // #TODO filters - if (!Global::DialogsFiltersEnabled() || !history->inChatList()) { - return; - } - refreshDialog(history); -} - Data::Folder *InnerWidget::shownFolder() const { return _openedFolder; } @@ -2528,19 +2532,18 @@ bool InnerWidget::chooseCollapsedRow() { if (row->folder) { _controller->openFolder(row->folder); } else { - switchImportantChats(); + switchToFilter(row->filterId); } return true; } -void InnerWidget::switchImportantChats() { +void InnerWidget::switchToFilter(FilterId filterId) { clearSelection(); - // #TODO filters - //if (Global::DialogsFilterId() == 0) { - // Global::SetDialogsMode(Mode::Important); - //} else { - // Global::SetDialogsMode(Mode::All); - //} + if (!Global::DialogsFiltersEnabled() + || !session().data().chatsFilters()->list().contains(filterId)) { + filterId = 0; + } + Global::SetDialogsFilterId(filterId); _filterId = Global::DialogsFilterId(); Local::writeUserSettings(); refreshWithCollapsedRows(true); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index ddd7ec09c..12062a46a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -126,8 +126,6 @@ public: rpl::producer chosenRow() const; - void notify_historyMuteUpdated(History *history); - ~InnerWidget(); public slots: @@ -176,7 +174,7 @@ private: void refreshWithCollapsedRows(bool toTop = false); bool needCollapsedRowsRefresh() const; bool chooseCollapsedRow(); - void switchImportantChats(); + void switchToFilter(FilterId filterId); bool chooseHashtag(); ChosenRow computeChosenRow() const; bool isSearchResultActive( diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index c8154578d..26081a0d7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -49,9 +49,7 @@ void MainList::setLoaded(bool loaded) { void MainList::clear() { _all.clear(); - for (auto &[filterId, list] : _other) { // #TODO filters _other.clear?.. - list.clear(); - } + _other.clear(); _unreadState = UnreadState(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 1f6e0bb9c..e476aab2a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -678,10 +678,6 @@ void Widget::escape() { } } -void Widget::notify_historyMuteUpdated(History *history) { - _inner->notify_historyMuteUpdated(history); -} - void Widget::refreshLoadMoreButton(bool mayBlock, bool isBlocked) { if (!mayBlock) { _loadMoreChats.destroy(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 1fce349f1..700a5ab96 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -85,8 +85,6 @@ public: bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; - void notify_historyMuteUpdated(History *history); - ~Widget(); signals: diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index ed84a2a63..d4c9211fb 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -277,10 +277,6 @@ bool switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, return false; } -void historyMuteUpdated(History *history) { - if (MainWidget *m = App::main()) m->notify_historyMuteUpdated(history); -} - void unreadCounterUpdated() { Global::RefHandleUnreadCounterUpdate().call(); } diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index ad81566ba..1a76f42ca 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -89,7 +89,6 @@ void replyMarkupUpdated(const HistoryItem *item); void inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop); bool switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot = nullptr, MsgId samePeerReplyTo = 0); -void historyMuteUpdated(History *history); void unreadCounterUpdated(); enum class ScreenCorner { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index da213ff5e..94576e752 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -838,8 +838,8 @@ void History::setUnreadMentionsCount(int count) { } _unreadMentionsCount = count; const auto has = (count > 0); - if (has != had && Global::DialogsFiltersEnabled()) { // #TODO filters - Notify::historyMuteUpdated(this); + if (has != had) { + owner().chatsFilters()->refreshHistory(this); updateChatListEntry(); } } @@ -1782,7 +1782,11 @@ void History::setUnreadCount(int newUnreadCount) { } const auto notifier = unreadStateChangeNotifier(true); + const auto wasForBadge = (unreadCountForBadge() > 0); _unreadCount = newUnreadCount; + if (wasForBadge != (unreadCountForBadge() > 0)) { + owner().chatsFilters()->refreshHistory(this); + } if (newUnreadCount == 1) { if (loadedAtBottom()) { @@ -1819,6 +1823,7 @@ void History::setUnreadMark(bool unread) { _unreadMark = unread; if (inChatList() && noUnreadMessages) { + owner().chatsFilters()->refreshHistory(this); updateChatListEntry(); } Notify::peerUpdatedDelayed( @@ -1844,7 +1849,7 @@ bool History::changeMute(bool newMute) { _mute = newMute; if (inChatList()) { - Notify::historyMuteUpdated(this); + owner().chatsFilters()->refreshHistory(this); updateChatListEntry(); } Notify::peerUpdatedDelayed( @@ -1920,14 +1925,12 @@ void History::setFolderPointer(Data::Folder *folder) { } const auto wasKnown = folderKnown(); const auto wasInList = inChatList(); - // #TODO filters - //const auto wasInImportant = wasInList && inChatList(Mode::Important); - //if (wasInList) { - // removeFromChatList(Mode::All); - // if (wasInImportant) { - // removeFromChatList(Mode::Important); - // } - //} + if (wasInList) { + removeFromChatList(0); + for (const auto &[filterId, _] : owner().chatsFilters()->list()) { + removeFromChatList(filterId); + } + } const auto was = _folder.value_or(nullptr); _folder = folder; if (was) { @@ -1935,9 +1938,11 @@ void History::setFolderPointer(Data::Folder *folder) { } if (wasInList) { addToChatList(0); - //if (wasInImportant) { // #TODO filters - // addToChatList(Mode::Important); - //} + for (const auto &[filterId, filter] : owner().chatsFilters()->list()) { + if (filter.contains(this)) { + addToChatList(filterId); + } + } owner().chatsListChanged(was); owner().chatsListChanged(folder); } else if (!wasKnown) { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 7514da8b3..f999933c4 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1453,6 +1453,9 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U } void HistoryWidget::notify_userIsBotChanged(UserData *user) { + if (const auto history = session().data().history(user)) { + session().data().chatsFilters()->refreshHistory(history); + } if (_peer && _peer == user) { _list->notifyIsBotChanged(); _list->updateBotInfo(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 68f38845a..2fe3072db 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -783,10 +783,6 @@ void MainWidget::notify_userIsBotChanged(UserData *bot) { _history->notify_userIsBotChanged(bot); } -void MainWidget::notify_historyMuteUpdated(History *history) { - _dialogs->notify_historyMuteUpdated(history); -} - void MainWidget::clearHider(not_null instance) { if (_hider != instance) { return; diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 928dd8aab..a28e6d5da 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -287,7 +287,6 @@ public: void notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop); bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo); void notify_userIsBotChanged(UserData *bot); - void notify_historyMuteUpdated(History *history); void closeBothPlayers(); From ed715fb8100f84ac328e9e022a43869a47da8b8e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Feb 2020 20:06:35 +0400 Subject: [PATCH 011/115] Update API scheme to layer 111. --- Telegram/Resources/tl/api.tl | 51 ++++++++++++++++--- .../SourceFiles/api/api_text_entities.cpp | 1 + .../SourceFiles/boxes/sticker_set_box.cpp | 5 +- Telegram/SourceFiles/calls/calls_call.cpp | 9 ++-- Telegram/SourceFiles/data/data_document.cpp | 6 ++- .../export/data/export_data_types.cpp | 6 ++- .../export/data/export_data_types.h | 3 +- .../export/output/export_output_html.cpp | 2 + .../export/output/export_output_json.cpp | 1 + Telegram/SourceFiles/history/history_item.cpp | 2 + .../SourceFiles/history/history_message.cpp | 2 + .../SourceFiles/ui/image/image_location.cpp | 2 + 12 files changed, 75 insertions(+), 15 deletions(-) diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index a7cddc5c3..8a87a4424 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -72,6 +72,7 @@ inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector = InputMedia; +inputMediaDice#aeffa807 = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; @@ -128,7 +129,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#2d895c74 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int pts:int = ChatFull; +channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -156,6 +157,7 @@ messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; +messageMediaDice#638fe46b value:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#a6638b9a title:string users:Vector = MessageAction; @@ -352,6 +354,9 @@ updateTheme#8216fba3 theme:Theme = Update; updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update; updateLoginToken#564fe691 = Update; updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector = Update; +updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; +updateDialogFilterOrder#a5d72105 order:Vector = Update; +updateDialogFilters#3504914f = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -502,7 +507,7 @@ messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMess webPageEmpty#eb1477e8 id:long = WebPage; webPagePending#c586da1c id:long date:int = WebPage; webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; -webPageNotModified#85849473 = WebPage; +webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; @@ -528,6 +533,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; +inputStickerSetDice#79e21a53 = InputStickerSet; stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; @@ -574,6 +580,7 @@ messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity; messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; +messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#afeb712e channel_id:int access_hash:long = InputChannel; @@ -822,7 +829,7 @@ phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3? phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; -phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int = PhoneCallProtocol; +phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector = PhoneCallProtocol; phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector = phone.PhoneCall; @@ -1008,7 +1015,7 @@ pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector = PageLis pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle; -page#ae891bec flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector photos:Vector documents:Vector = Page; +page#98657f0d flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector photos:Vector documents:Vector views:flags.3?int = Page; help.supportName#8c05f1c9 name:string = help.SupportName; @@ -1073,6 +1080,7 @@ channelLocationEmpty#bfb5ad8b = ChannelLocation; channelLocation#209b82db geo_point:GeoPoint address:string = ChannelLocation; peerLocated#ca461b5d peer:Peer expires:int distance:int = PeerLocated; +peerSelfLocated#f8ec284b expires:int = PeerLocated; restrictionReason#d072acb4 platform:string reason:string text:string = RestrictionReason; @@ -1110,6 +1118,28 @@ messageUserVoteMultiple#e8fe0de user_id:int options:Vector date:int = Mes messages.votesList#823f649 flags:# count:int votes:Vector users:Vector next_offset:flags.0?string = messages.VotesList; +bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; + +payments.bankCardData#3e24e573 title:string open_urls:Vector = payments.BankCardData; + +dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; + +dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; + +statsDateRangeDays#b637edaf min_date:int max_date:int = StatsDateRangeDays; + +statsAbsValueAndPrev#cb43acde current:double previous:double = StatsAbsValueAndPrev; + +statsPercentValue#cbce2fe0 part:double total:double = StatsPercentValue; + +statsGraphAsync#4a27eb2d token:string = StatsGraph; +statsGraphError#bedc9822 error:string = StatsGraph; +statsGraph#8ea464b6 flags:# json:DataJSON zoom_token:flags.0?string = StatsGraph; + +messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageInteractionCounters; + +stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector = stats.BroadcastStats; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1227,7 +1257,7 @@ contacts.getSaved#82f1e39f = Vector; contacts.toggleTopPeers#8514bdda enabled:Bool = Bool; contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates; contacts.acceptContact#f831a20f id:InputUser = Updates; -contacts.getLocated#a356056 geo_point:InputGeoPoint = Updates; +contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; @@ -1347,6 +1377,11 @@ messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector = messages. messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector = Updates; messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector = Updates; messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList; +messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector = Bool; +messages.getDialogFilters#f19ed96d = Vector; +messages.getSuggestedDialogFilters#a29cd42c = Vector; +messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; +messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1431,6 +1466,7 @@ payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int inf payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; +payments.getBankCardData#2e79d779 number:string = payments.BankCardData; stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -1455,4 +1491,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -// LAYER 109 +stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; +stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; + +// LAYER 111 diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index e67b86dcc..eb3f0ebf1 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -65,6 +65,7 @@ EntitiesInText EntitiesFromMTP(const QVector &entities) { case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, Clean(qs(d.vlanguage())) }); } break; + case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities } } diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index ca7130b2b..478cf8ebc 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -229,8 +229,9 @@ StickerSetBox::Inner::Inner( _setAccess = data.vaccess_hash().v; }, [&](const MTPDinputStickerSetShortName &data) { _setShortName = qs(data.vshort_name()); - }, [&](const MTPDinputStickerSetEmpty &) { - }, [&](const MTPDinputStickerSetAnimatedEmoji &) { + }, [](const MTPDinputStickerSetEmpty &) { + }, [](const MTPDinputStickerSetAnimatedEmoji &) { + }, [](const MTPDinputStickerSetDice &) { }); _api.request(MTPmessages_GetStickerSet( diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index bf57bdca4..732c516c2 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -199,7 +199,8 @@ void Call::startOutgoing() { MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), - MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer())) + MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()), + MTP_vector(1, MTP_string("2.4.4"))) )).done([=](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); @@ -277,7 +278,8 @@ void Call::actuallyAnswer() { MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), - MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer())) + MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()), + MTP_vector(1, MTP_string("2.4.4"))) )).done([=](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); auto &call = result.c_phone_phoneCall(); @@ -515,7 +517,8 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), - MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer())) + MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()), + MTP_vector(1, MTP_string("2.4.4"))) )).done([this](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 9603ba960..97d6eb670 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -1144,9 +1144,11 @@ bool DocumentData::isStickerSetInstalled() const { } } return false; - }, [&](const MTPDinputStickerSetEmpty &) { + }, [](const MTPDinputStickerSetEmpty &) { return false; - }, [&](const MTPDinputStickerSetAnimatedEmoji &) { + }, [](const MTPDinputStickerSetAnimatedEmoji &) { + return false; + }, [](const MTPDinputStickerSetDice &) { return false; }); } diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 768ef6a90..a6d5afaba 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -178,7 +178,9 @@ std::vector ParseText( [](const MTPDmessageEntityCashtag&) { return Type::Cashtag; }, [](const MTPDmessageEntityUnderline&) { return Type::Underline; }, [](const MTPDmessageEntityStrike&) { return Type::Strike; }, - [](const MTPDmessageEntityBlockquote&) { return Type::Blockquote; }); + [](const MTPDmessageEntityBlockquote&) { + return Type::Blockquote; }, + [](const MTPDmessageEntityBankCard&) { return Type::BankCard; }); part.text = mid(start, length); part.additional = entity.match( [](const MTPDmessageEntityPre &data) { @@ -931,6 +933,8 @@ Media ParseMedia( result.ttl = data.vperiod().v; }, [&](const MTPDmessageMediaPoll &data) { result.content = ParsePoll(data); + }, [](const MTPDmessageMediaDice &data) { + // #TODO dice }, [](const MTPDmessageMediaEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index ee4a14182..573f7c0af 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -495,7 +495,8 @@ struct TextPart { Cashtag, Underline, Strike, - Blockquote + Blockquote, + BankCard, }; Type type = Type::Text; Utf8String text; diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 17dc6d19e..6817d59e5 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -287,6 +287,8 @@ QByteArray FormatText( case Type::Strike: return "" + text + ""; case Type::Blockquote: return "
" + text + "
"; + case Type::BankCard: + return text; } Unexpected("Type in text entities serialization."); }) | ranges::to_vector); diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index fbdbf82ea..c8dc01463 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -175,6 +175,7 @@ QByteArray SerializeText( case Type::Underline: return "underline"; case Type::Strike: return "strikethrough"; case Type::Blockquote: return "blockquote"; + case Type::BankCard: return "bank_card"; } Unexpected("Type in SerializeText."); }(); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ce7883a71..0503a60d5 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -153,6 +153,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { return Result::Good; }, [](const MTPDmessageMediaPoll &) { return Result::Good; + }, [](const MTPDmessageMediaDice &) { + return Result::Unsupported; // #TODO dice }, [](const MTPDmessageMediaUnsupported &) { return Result::Unsupported; }); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 7e2974d66..d2b0fdfe1 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1024,6 +1024,8 @@ std::unique_ptr HistoryMessage::CreateMedia( return std::make_unique( item, item->history()->owner().processPoll(media)); + }, [](const MTPDmessageMediaDice &media) -> Result { + return nullptr; // #TODO dice }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { diff --git a/Telegram/SourceFiles/ui/image/image_location.cpp b/Telegram/SourceFiles/ui/image/image_location.cpp index ccd7ac2bb..1dfe3cf40 100644 --- a/Telegram/SourceFiles/ui/image/image_location.cpp +++ b/Telegram/SourceFiles/ui/image/image_location.cpp @@ -169,6 +169,8 @@ StorageFileLocation::StorageFileLocation( }, [&](const MTPDinputStickerSetAnimatedEmoji &data) { Unexpected( "inputStickerSetAnimatedEmoji in StorageFileLocation."); + }, [&](const MTPDinputStickerSetDice &data) { + Unexpected("inputStickerSetDice in StorageFileLocation."); }); _volumeId = data.vvolume_id().v; _localId = data.vlocal_id().v; From 11d31ffc8458a36d453cc97a97e67aa138a73762 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Feb 2020 20:07:21 +0400 Subject: [PATCH 012/115] Parse and apply cloud filters. --- Telegram/SourceFiles/data/data_channel.cpp | 6 + Telegram/SourceFiles/data/data_channel.h | 1 + .../SourceFiles/data/data_chat_filters.cpp | 319 ++++++++++++++++-- Telegram/SourceFiles/data/data_chat_filters.h | 48 ++- Telegram/SourceFiles/data/data_session.cpp | 52 +-- Telegram/SourceFiles/data/data_session.h | 9 +- .../dialogs/dialogs_inner_widget.cpp | 41 ++- .../dialogs/dialogs_inner_widget.h | 3 + .../SourceFiles/dialogs/dialogs_widget.cpp | 2 + Telegram/SourceFiles/history/history.cpp | 17 +- .../SourceFiles/history/history_widget.cpp | 3 +- Telegram/SourceFiles/mainwidget.cpp | 7 + Telegram/lib_base | 2 +- 13 files changed, 420 insertions(+), 90 deletions(-) diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 7c8755f69..9b9b96f84 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -87,6 +87,12 @@ void ChannelData::setName(const QString &newName, const QString &newUsername) { updateNameDelayed(newName.isEmpty() ? name : newName, QString(), newUsername); } +void ChannelData::setAccessHash(uint64 accessHash) { + access = accessHash; + input = MTP_inputPeerChannel(MTP_int(bareId()), MTP_long(accessHash)); + inputChannel = MTP_inputChannel(MTP_int(bareId()), MTP_long(accessHash)); +} + void ChannelData::setInviteLink(const QString &newInviteLink) { if (newInviteLink != _inviteLink) { _inviteLink = newInviteLink; diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 78c360f17..4816ba5cf 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -127,6 +127,7 @@ public: void setPhoto(PhotoId photoId, const MTPChatPhoto &photo); void setName(const QString &name, const QString &username); + void setAccessHash(uint64 accessHash); void setFlags(MTPDchannel::Flags which) { _flags.set(which); diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index d7c0df56b..fff8b2224 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -12,71 +12,348 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_chat.h" #include "data/data_channel.h" +#include "data/data_session.h" +#include "main/main_session.h" +#include "apiwrap.h" namespace Data { ChatFilter::ChatFilter( + FilterId id, const QString &title, Flags flags, - base::flat_set> always) -: _title(title) + base::flat_set> always, + base::flat_set> never) +: _id(id) +, _title(title) , _always(std::move(always)) +, _never(std::move(never)) , _flags(flags) { } +ChatFilter ChatFilter::FromTL( + const MTPDialogFilter &data, + not_null owner) { + return data.match([&](const MTPDdialogFilter &data) { + const auto flags = (data.is_contacts() ? Flag::Contacts : Flag(0)) + | (data.is_non_contacts() ? Flag::NonContacts : Flag(0)) + | (data.is_groups() ? Flag::Groups : Flag(0)) + | (data.is_broadcasts() ? Flag::Broadcasts : Flag(0)) + | (data.is_bots() ? Flag::Bots : Flag(0)) + | (data.is_exclude_muted() ? Flag::NoMuted : Flag(0)) + | (data.is_exclude_read() ? Flag::NoRead : Flag(0)) + | (data.is_exclude_archived() ? Flag::NoArchive : Flag(0)); + auto &&to_histories = ranges::view::transform([&]( + const MTPInputPeer &data) { + const auto peer = data.match([&](const MTPDinputPeerUser &data) { + const auto user = owner->user(data.vuser_id().v); + user->setAccessHash(data.vaccess_hash().v); + return (PeerData*)user; + }, [&](const MTPDinputPeerChat &data) { + return (PeerData*)owner->chat(data.vchat_id().v); + }, [&](const MTPDinputPeerChannel &data) { + const auto channel = owner->channel(data.vchannel_id().v); + channel->setAccessHash(data.vaccess_hash().v); + return (PeerData*)channel; + }, [&](const auto &data) { + return (PeerData*)nullptr; + }); + return peer ? owner->history(peer).get() : nullptr; + }) | ranges::view::filter([](History *history) { + return history != nullptr; + }) | ranges::view::transform([](History *history) { + return not_null(history); + }); + auto &&always = ranges::view::all( + data.vinclude_peers().v + ) | to_histories; + auto &&never = ranges::view::all( + data.vexclude_peers().v + ) | to_histories; + return ChatFilter( + data.vid().v, + qs(data.vtitle()), + flags, + { always.begin(), always.end() }, + { never.begin(), never.end() }); + }); +} + +MTPDialogFilter ChatFilter::tl() const { + using TLFlag = MTPDdialogFilter::Flag; + const auto flags = TLFlag(0) + | ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0)) + | ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0)) + | ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0)) + | ((_flags & Flag::Broadcasts) ? TLFlag::f_broadcasts : TLFlag(0)) + | ((_flags & Flag::Bots) ? TLFlag::f_bots : TLFlag(0)) + | ((_flags & Flag::NoMuted) ? TLFlag::f_exclude_muted : TLFlag(0)) + | ((_flags & Flag::NoRead) ? TLFlag::f_exclude_read : TLFlag(0)) + | ((_flags & Flag::NoArchive) + ? TLFlag::f_exclude_archived + : TLFlag(0)); + auto always = QVector(); + always.reserve(_always.size()); + for (const auto history : _always) { + always.push_back(history->peer->input); + } + auto never = QVector(); + never.reserve(_never.size()); + for (const auto history : _never) { + never.push_back(history->peer->input); + } + return MTP_dialogFilter( + MTP_flags(flags), + MTP_int(_id), + MTP_string(_title), + MTPstring(), // emoticon + MTP_vector(), + MTP_vector(always), + MTP_vector(never)); +} + +FilterId ChatFilter::id() const { + return _id; +} + QString ChatFilter::title() const { return _title; } +ChatFilter::Flags ChatFilter::flags() const { + return _flags; +} + +const base::flat_set> &ChatFilter::always() const { + return _always; +} + +const base::flat_set> &ChatFilter::never() const { + return _never; +} + bool ChatFilter::contains(not_null history) const { const auto flag = [&] { const auto peer = history->peer; if (const auto user = peer->asUser()) { - return user->isBot() ? Flag::Bots : Flag::Users; + return user->isBot() + ? Flag::Bots + : user->isContact() + ? Flag::Contacts + : Flag::NonContacts; } else if (const auto chat = peer->asChat()) { - return Flag::PrivateGroups; + return Flag::Groups; } else if (const auto channel = peer->asChannel()) { if (channel->isBroadcast()) { return Flag::Broadcasts; - } else if (channel->isPublic()) { - return Flag::PublicGroups; } else { - return Flag::PrivateGroups; + return Flag::Groups; } } else { Unexpected("Peer type in ChatFilter::contains."); } }(); - if (history->folder()) { + if (_never.contains(history)) { return false; } return false || ((_flags & flag) && (!(_flags & Flag::NoMuted) || !history->mute()) - && (!(_flags & Flag::NoRead) || history->unreadCountForBadge())) + && (!(_flags & Flag::NoRead) || history->unreadCountForBadge()) + && (!(_flags & Flag::NoArchive) + || (history->folderKnown() && !history->folder()))) || _always.contains(history); } ChatFilters::ChatFilters(not_null owner) : _owner(owner) { using Flag = ChatFilter::Flag; - const auto all = Flag::Users - | Flag::SecretChats - | Flag::PrivateGroups - | Flag::PublicGroups + const auto all = Flag::Contacts + | Flag::NonContacts + | Flag::Groups | Flag::Broadcasts - | Flag::Bots; - _list.emplace( - 1, - ChatFilter("Unmuted Chats", all | ChatFilter::Flag::NoMuted, {})); - _list.emplace( - 2, - ChatFilter("Unread Chats", all | ChatFilter::Flag::NoRead, {})); + | Flag::Bots + | Flag::NoArchive; + _list.push_back( + ChatFilter(1, "Unmuted", all | Flag::NoMuted, {}, {})); + _list.push_back( + ChatFilter(2, "Unread", all | Flag::NoRead, {}, {})); } -const base::flat_map &ChatFilters::list() const { +void ChatFilters::load() { + load(false); +} + +void ChatFilters::load(bool force) { + if (_loadRequestId && !force) { + return; + } + auto &api = _owner->session().api(); + api.request(_loadRequestId).cancel(); + _loadRequestId = api.request(MTPmessages_GetDialogFilters( + )).done([=](const MTPVector &result) { + auto position = 0; + auto changed = false; + for (const auto &filter : result.v) { + auto parsed = ChatFilter::FromTL(filter, _owner); + const auto b = begin(_list) + position, e = end(_list); + const auto i = ranges::find(b, e, parsed.id(), &ChatFilter::id); + if (i == e) { + applyInsert(std::move(parsed), position); + changed = true; + } else if (i == b) { + if (applyChange(*b, std::move(parsed))) { + changed = true; + } + } else { + std::swap(*i, *b); + applyChange(*b, std::move(parsed)); + changed = true; + } + ++position; + } + while (position < _list.size()) { + applyRemove(position); + changed = true; + } + if (changed) { + _listChanged.fire({}); + } + _loadRequestId = 0; + }).fail([=](const RPCError &error) { + _loadRequestId = 0; + }).send(); +} + +void ChatFilters::apply(const MTPUpdate &update) { + update.match([&](const MTPDupdateDialogFilter &data) { + if (const auto filter = data.vfilter()) { + set(ChatFilter::FromTL(*filter, _owner)); + } else { + remove(data.vid().v); + } + }, [&](const MTPDupdateDialogFilters &data) { + load(true); + }, [&](const MTPDupdateDialogFilterOrder &data) { + if (applyOrder(data.vorder().v)) { + _listChanged.fire({}); + } else { + load(true); + } + }, [](auto&&) { + Unexpected("Update in ChatFilters::apply."); + }); +} + +void ChatFilters::set(ChatFilter filter) { + if (!filter.id()) { + return; + } + const auto i = ranges::find(_list, filter.id(), &ChatFilter::id); + if (i == end(_list)) { + applyInsert(std::move(filter), _list.size()); + _listChanged.fire({}); + } else if (applyChange(*i, std::move(filter))) { + _listChanged.fire({}); + } +} + +void ChatFilters::applyInsert(ChatFilter filter, int position) { + Expects(position >= 0 && position <= _list.size()); + + _list.insert( + begin(_list) + position, + ChatFilter(filter.id(), {}, {}, {}, {})); + applyChange(*(begin(_list) + position), std::move(filter)); +} + +void ChatFilters::remove(FilterId id) { + const auto i = ranges::find(_list, id, &ChatFilter::id); + if (i == end(_list)) { + return; + } + applyRemove(i - begin(_list)); + _listChanged.fire({}); +} + +void ChatFilters::applyRemove(int position) { + Expects(position >= 0 && position < _list.size()); + + const auto i = begin(_list) + position; + applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {})); + _list.erase(i); +} + +bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { + const auto rulesChanged = (filter.flags() != updated.flags()) + || (filter.always() != updated.always()); + if (rulesChanged) { + const auto list = _owner->chatsList()->indexed(); + for (const auto &entry : *list) { + if (const auto history = entry->history()) { + const auto now = updated.contains(history); + const auto was = filter.contains(history); + if (now != was) { + if (now) { + history->addToChatList(filter.id()); + } else { + history->removeFromChatList(filter.id()); + } + } + } + } + } else if (filter.title() == updated.title()) { + return false; + } + filter = std::move(updated); + return true; +} + +bool ChatFilters::applyOrder(const QVector &order) { + if (order.size() != _list.size()) { + return false; + } else if (_list.empty()) { + return true; + } + auto indices = ranges::view::all( + _list + ) | ranges::view::transform( + &ChatFilter::id + ) | ranges::to_vector; + auto b = indices.begin(), e = indices.end(); + for (const auto id : order) { + const auto i = ranges::find(b, e, id.v); + if (i == e) { + return false; + } else if (i != b) { + std::swap(*i, *b); + } + ++b; + } + auto changed = false; + auto begin = _list.begin(), end = _list.end(); + for (const auto id : order) { + const auto i = ranges::find(begin, end, id.v, &ChatFilter::id); + Assert(i != end); + if (i != begin) { + changed = true; + std::swap(*i, *begin); + } + ++begin; + } + if (changed) { + _listChanged.fire({}); + } + return true; +} + +const std::vector &ChatFilters::list() const { return _list; } +rpl::producer<> ChatFilters::changed() const { + return _listChanged.events(); +} + void ChatFilters::refreshHistory(not_null history) { _refreshHistoryRequests.fire_copy(history); } diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 348afd422..a933e834e 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -18,31 +18,44 @@ class Session; class ChatFilter final { public: enum class Flag : uchar { - Users = 0x01, - SecretChats = 0x02, - PrivateGroups = 0x04, - PublicGroups = 0x08, - Broadcasts = 0x10, - Bots = 0x20, - NoMuted = 0x40, - NoRead = 0x80, + Contacts = 0x01, + NonContacts = 0x02, + Groups = 0x04, + Broadcasts = 0x08, + Bots = 0x10, + NoMuted = 0x20, + NoRead = 0x40, + NoArchive = 0x80, }; friend constexpr inline bool is_flag_type(Flag) { return true; }; using Flags = base::flags; ChatFilter() = default; ChatFilter( + FilterId id, const QString &title, Flags flags, - base::flat_set> always); + base::flat_set> always, + base::flat_set> never); + [[nodiscard]] static ChatFilter FromTL( + const MTPDialogFilter &data, + not_null owner); + [[nodiscard]] MTPDialogFilter tl() const; + + [[nodiscard]] FilterId id() const; [[nodiscard]] QString title() const; + [[nodiscard]] Flags flags() const; + [[nodiscard]] const base::flat_set> &always() const; + [[nodiscard]] const base::flat_set> &never() const; [[nodiscard]] bool contains(not_null history) const; private: + FilterId _id = 0; QString _title; base::flat_set> _always; + base::flat_set> _never; Flags _flags; }; @@ -51,17 +64,30 @@ class ChatFilters final { public: explicit ChatFilters(not_null owner); - [[nodiscard]] const base::flat_map &list() const; + void load(); + void apply(const MTPUpdate &update); + void set(ChatFilter filter); + void remove(FilterId id); + [[nodiscard]] const std::vector &list() const; + [[nodiscard]] rpl::producer<> changed() const; void refreshHistory(not_null history); [[nodiscard]] auto refreshHistoryRequests() const -> rpl::producer>; private: + void load(bool force); + bool applyOrder(const QVector &order); + bool applyChange(ChatFilter &filter, ChatFilter &&updated); + void applyInsert(ChatFilter filter, int position); + void applyRemove(int position); + const not_null _owner; - base::flat_map _list; + std::vector _list; + rpl::event_stream<> _listChanged; rpl::event_stream> _refreshHistoryRequests; + mtpRequestId _loadRequestId = 0; }; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 28144cb8a..01d4b7a42 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "data/data_game.h" #include "data/data_poll.h" +#include "data/data_chat_filters.h" #include "data/data_scheduled_messages.h" #include "data/data_cloud_themes.h" #include "data/data_streaming.h" @@ -184,7 +185,6 @@ Session::Session(not_null session) Local::cacheBigFilePath(), Local::cacheBigFileSettings())) , _chatsList(PinnedDialogsCountMaxValue(session)) -, _chatsFilters(this) , _contactsList(Dialogs::SortMode::Name) , _contactsNoChatsList(Dialogs::SortMode::Name) , _selfDestructTimer([=] { checkSelfDestructItems(); }) @@ -193,6 +193,7 @@ Session::Session(not_null session) }) , _unmuteByFinishedTimer([=] { unmuteByFinished(); }) , _groups(this) +, _chatsFilters(std::make_unique(this)) , _scheduledMessages(std::make_unique(this)) , _cloudThemes(std::make_unique(session)) , _streaming(std::make_unique(this)) @@ -520,11 +521,7 @@ not_null Session::processChat(const MTPChat &data) { const auto channel = this->channel(input.vchannel_id().v); channel->addFlags(MTPDchannel::Flag::f_megagroup); if (!channel->access) { - channel->input = MTP_inputPeerChannel( - input.vchannel_id(), - input.vaccess_hash()); - channel->inputChannel = *migratedTo; - channel->access = input.vaccess_hash().v; + channel->setAccessHash(input.vaccess_hash().v); } ApplyMigration(chat, channel); }, [](const MTPDinputChannelFromMessage &) { @@ -567,10 +564,6 @@ not_null Session::processChat(const MTPChat &data) { if (result->loadedStatus != PeerData::FullLoaded) { LOG(("API Warning: not loaded minimal channel applied.")); } - } else { - channel->input = MTP_inputPeerChannel( - data.vid(), - MTP_long(data.vaccess_hash().value_or_empty())); } const auto wasInChannel = channel->amIn(); @@ -606,11 +599,8 @@ not_null Session::processChat(const MTPChat &data) { channel->setRestrictions( MTP_chatBannedRights(MTP_flags(0), MTP_int(0))); } - const auto hash = data.vaccess_hash().value_or(channel->access); - channel->inputChannel = MTP_inputChannel( - data.vid(), - MTP_long(hash)); - channel->access = hash; + channel->setAccessHash( + data.vaccess_hash().value_or(channel->access)); channel->date = data.vdate().v; if (channel->version() < data.vversion().v) { channel->setVersion(data.vversion().v); @@ -645,15 +635,12 @@ not_null Session::processChat(const MTPChat &data) { } }, [&](const MTPDchannelForbidden &data) { const auto channel = result->asChannel(); - channel->input = MTP_inputPeerChannel(data.vid(), data.vaccess_hash()); auto wasInChannel = channel->amIn(); auto canViewAdmins = channel->canViewAdmins(); auto canViewMembers = channel->canViewMembers(); auto canAddMembers = channel->canAddMembers(); - channel->inputChannel = MTP_inputChannel(data.vid(), data.vaccess_hash()); - auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup); channel->setFlags((channel->flags() & ~mask) | (mtpCastFlags(data.vflags()) & mask) | MTPDchannel_ClientFlag::f_forbidden); @@ -666,7 +653,7 @@ not_null Session::processChat(const MTPChat &data) { channel->setName(qs(data.vtitle()), QString()); - channel->access = data.vaccess_hash().v; + channel->setAccessHash(data.vaccess_hash().v); channel->setPhoto(MTP_chatPhotoEmpty()); channel->date = 0; channel->setMembersCount(0); @@ -3348,14 +3335,6 @@ not_null Session::chatsList( return folder ? folder->chatsList() : &_chatsList; } -not_null Session::chatsFilters() { - return &_chatsFilters; -} - -not_null Session::chatsFilters() const { - return &_chatsFilters; -} - not_null Session::contactsList() { return &_contactsList; } @@ -3383,20 +3362,21 @@ auto Session::refreshChatListEntry( auto result = filterIdForResult ? RefreshChatListEntryResult() : mainListResult; - for (const auto &[filterId, filter] : _chatsFilters.list()) { + for (const auto &filter : _chatsFilters->list()) { + const auto id = filter.id(); auto filterResult = RefreshChatListEntryResult(); if (history && filter.contains(history)) { - filterResult.changed = !entry->inChatList(filterId); + filterResult.changed = !entry->inChatList(id); if (filterResult.changed) { - entry->addToChatList(filterId); + entry->addToChatList(id); } else { - filterResult.moved = entry->adjustByPosInChatList(filterId); + filterResult.moved = entry->adjustByPosInChatList(id); } - } else if (entry->inChatList(filterId)) { - entry->removeFromChatList(filterId); + } else if (entry->inChatList(id)) { + entry->removeFromChatList(id); filterResult.changed = true; } - if (filterId == filterIdForResult) { + if (id == filterIdForResult) { result = filterResult; } } @@ -3408,8 +3388,8 @@ void Session::removeChatListEntry(Dialogs::Key key) { const auto entry = key.entry(); entry->removeFromChatList(0); - for (const auto &[filterId, filter] : _chatsFilters.list()) { - entry->removeFromChatList(filterId); + for (const auto &filter : _chatsFilters->list()) { + entry->removeFromChatList(filter.id()); } if (_contactsList.contains(key)) { if (!_contactsNoChatsList.contains(key)) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index d3d2b0038..f4ac5d7b8 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_main_list.h" #include "data/data_groups.h" #include "data/data_notify_settings.h" -#include "data/data_chat_filters.h" #include "history/history_location_manager.h" #include "base/timer.h" #include "base/flags.h" @@ -59,6 +58,7 @@ class Folder; class LocationPoint; class WallPaper; class ScheduledMessages; +class ChatFilters; class CloudThemes; class Streaming; class MediaRotation; @@ -86,6 +86,9 @@ public: [[nodiscard]] const Groups &groups() const { return _groups; } + [[nodiscard]] ChatFilters &chatsFilters() const { + return *_chatsFilters; + } [[nodiscard]] ScheduledMessages &scheduledMessages() const { return *_scheduledMessages; } @@ -626,8 +629,6 @@ public: Data::Folder *folder = nullptr); [[nodiscard]] not_null chatsList( Data::Folder *folder = nullptr) const; - [[nodiscard]] not_null chatsFilters(); - [[nodiscard]] not_null chatsFilters() const; [[nodiscard]] not_null contactsList(); [[nodiscard]] not_null contactsNoChatsList(); @@ -874,7 +875,6 @@ private: Stickers::SavedGifs _savedGifs; Dialogs::MainList _chatsList; - ChatFilters _chatsFilters; Dialogs::IndexedList _contactsList; Dialogs::IndexedList _contactsNoChatsList; @@ -980,6 +980,7 @@ private: int32 _wallpapersHash = 0; Groups _groups; + std::unique_ptr _chatsFilters; std::unique_ptr _scheduledMessages; std::unique_ptr _cloudThemes; std::unique_ptr _streaming; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f5eb1d253..baae406e7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_peer_values.h" #include "data/data_histories.h" +#include "data/data_chat_filters.h" #include "base/unixtime.h" #include "lang/lang_keys.h" #include "mainwindow.h" @@ -194,7 +195,10 @@ InnerWidget::InnerWidget( refresh(); }, lifetime()); - session().settings().archiveCollapsedChanges( + rpl::merge( + session().settings().archiveCollapsedChanges( + ) | rpl::map([] { return rpl::empty_value(); }), + session().data().chatsFilters().changed() ) | rpl::start_with_next([=] { refreshWithCollapsedRows(); }, lifetime()); @@ -204,10 +208,10 @@ InnerWidget::InnerWidget( refresh(); }, lifetime()); - session().data().chatsFilters()->refreshHistoryRequests( + session().data().chatsFilters().refreshHistoryRequests( ) | rpl::start_with_next([=](not_null history) { if (history->inChatList() - && !session().data().chatsFilters()->list().empty()) { + && !session().data().chatsFilters().list().empty()) { refreshDialog(history); } }, lifetime()); @@ -291,11 +295,17 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { _collapsedRows.clear(); if (!_openedFolder && Global::DialogsFiltersEnabled()) { + const auto &list = session().data().chatsFilters().list(); + if (_filterId + && ranges::find(list, _filterId, &Data::ChatFilter::id) == end(list)) { + switchToFilter(0); + } if (_filterId) { _collapsedRows.push_back(std::make_unique(nullptr, 0)); } else { - for (const auto &[filterId, filter] : session().data().chatsFilters()->list()) { - _collapsedRows.push_back(std::make_unique(nullptr, filterId)); + for (const auto &filter : session().data().chatsFilters().list()) { + _collapsedRows.push_back( + std::make_unique(nullptr, filter.id())); } } } @@ -704,8 +714,10 @@ void InnerWidget::paintCollapsedRow( ? row->folder->chatListName() : _filterId ? (narrow ? "Show" : tr::lng_dialogs_show_all_chats(tr::now)) - : session().data().chatsFilters()->list().find( - row->filterId)->second.title(); + : ranges::find( + session().data().chatsFilters().list(), + row->filterId, + &Data::ChatFilter::id)->title(); const auto unread = row->folder ? row->folder->chatListUnreadCount() : _filterId @@ -1705,6 +1717,16 @@ void InnerWidget::dragLeft() { clearSelection(); } +FilterId InnerWidget::filterId() const { + return _filterId; +} + +void InnerWidget::closeFilter() { + if (_filterId) { + switchToFilter(0); + } +} + void InnerWidget::clearSelection() { _mouseSelection = false; _lastMousePosition = std::nullopt; @@ -2540,7 +2562,10 @@ bool InnerWidget::chooseCollapsedRow() { void InnerWidget::switchToFilter(FilterId filterId) { clearSelection(); if (!Global::DialogsFiltersEnabled() - || !session().data().chatsFilters()->list().contains(filterId)) { + || !ranges::contains( + session().data().chatsFilters().list(), + filterId, + &Data::ChatFilter::id)) { filterId = 0; } Global::SetDialogsFilterId(filterId); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 12062a46a..b68598739 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -80,6 +80,9 @@ public: const QVector &my, const QVector &result); + [[nodiscard]] FilterId filterId() const; + void closeFilter(); + void clearSelection(); void changeOpenedFolder(Data::Folder *folder); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index e476aab2a..4faa07a12 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1574,6 +1574,8 @@ void Widget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { if (_openedFolder) { controller()->closeFolder(); + } else if (_inner->filterId()) { + _inner->closeFilter(); } else { e->ignore(); } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 94576e752..96148c10d 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_channel_admins.h" +#include "data/data_chat_filters.h" #include "data/data_scheduled_messages.h" #include "data/data_folder.h" #include "data/data_photo.h" @@ -839,7 +840,7 @@ void History::setUnreadMentionsCount(int count) { _unreadMentionsCount = count; const auto has = (count > 0); if (has != had) { - owner().chatsFilters()->refreshHistory(this); + owner().chatsFilters().refreshHistory(this); updateChatListEntry(); } } @@ -1785,7 +1786,7 @@ void History::setUnreadCount(int newUnreadCount) { const auto wasForBadge = (unreadCountForBadge() > 0); _unreadCount = newUnreadCount; if (wasForBadge != (unreadCountForBadge() > 0)) { - owner().chatsFilters()->refreshHistory(this); + owner().chatsFilters().refreshHistory(this); } if (newUnreadCount == 1) { @@ -1823,7 +1824,7 @@ void History::setUnreadMark(bool unread) { _unreadMark = unread; if (inChatList() && noUnreadMessages) { - owner().chatsFilters()->refreshHistory(this); + owner().chatsFilters().refreshHistory(this); updateChatListEntry(); } Notify::peerUpdatedDelayed( @@ -1849,7 +1850,7 @@ bool History::changeMute(bool newMute) { _mute = newMute; if (inChatList()) { - owner().chatsFilters()->refreshHistory(this); + owner().chatsFilters().refreshHistory(this); updateChatListEntry(); } Notify::peerUpdatedDelayed( @@ -1927,8 +1928,8 @@ void History::setFolderPointer(Data::Folder *folder) { const auto wasInList = inChatList(); if (wasInList) { removeFromChatList(0); - for (const auto &[filterId, _] : owner().chatsFilters()->list()) { - removeFromChatList(filterId); + for (const auto &filter : owner().chatsFilters().list()) { + removeFromChatList(filter.id()); } } const auto was = _folder.value_or(nullptr); @@ -1938,9 +1939,9 @@ void History::setFolderPointer(Data::Folder *folder) { } if (wasInList) { addToChatList(0); - for (const auto &[filterId, filter] : owner().chatsFilters()->list()) { + for (const auto &filter : owner().chatsFilters().list()) { if (filter.contains(this)) { - addToChatList(filterId); + addToChatList(filter.id()); } } owner().chatsListChanged(was); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index f999933c4..8cbf6e522 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" +#include "data/data_chat_filters.h" #include "data/data_scheduled_messages.h" #include "data/data_file_origin.h" #include "data/data_histories.h" @@ -1454,7 +1455,7 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U void HistoryWidget::notify_userIsBotChanged(UserData *user) { if (const auto history = session().data().history(user)) { - session().data().chatsFilters()->refreshHistory(history); + session().data().chatsFilters().refreshHistory(history); } if (_peer && _peer == user) { _list->notifyIsBotChanged(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 2fe3072db..6ed8477da 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" +#include "data/data_chat_filters.h" #include "data/data_scheduled_messages.h" #include "data/data_file_origin.h" #include "data/data_histories.h" @@ -4051,6 +4052,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { ptsUpdateAndApply(data.vpts().v, data.vpts_count().v, update); } break; + case mtpc_updateDialogFilter: + case mtpc_updateDialogFilterOrder: + case mtpc_updateDialogFilters: { + session().data().chatsFilters().apply(update); + } break; + // Deleted messages. case mtpc_updateDeleteMessages: { auto &d = update.c_updateDeleteMessages(); diff --git a/Telegram/lib_base b/Telegram/lib_base index 1720a5b4e..62d4145ba 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 1720a5b4eebc794ff05d7223d79d42e00e39062e +Subproject commit 62d4145ba04d8b6205fe0413318bc5a0f19f9410 From d4b9b65724469304ffe8f207f4e84ddef17d1904 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 3 Mar 2020 16:07:22 +0400 Subject: [PATCH 013/115] First version of the filters side bar. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/icons/filters_all.png | Bin 0 -> 1160 bytes Telegram/Resources/icons/filters_all@2x.png | Bin 0 -> 2398 bytes Telegram/Resources/icons/filters_all@3x.png | Bin 0 -> 3522 bytes .../Resources/icons/filters_all_active.png | Bin 0 -> 877 bytes .../Resources/icons/filters_all_active@2x.png | Bin 0 -> 1767 bytes .../Resources/icons/filters_all_active@3x.png | Bin 0 -> 2592 bytes Telegram/Resources/icons/filters_custom.png | Bin 0 -> 492 bytes .../Resources/icons/filters_custom@2x.png | Bin 0 -> 938 bytes .../Resources/icons/filters_custom@3x.png | Bin 0 -> 1463 bytes .../Resources/icons/filters_custom_active.png | Bin 0 -> 382 bytes .../icons/filters_custom_active@2x.png | Bin 0 -> 750 bytes .../icons/filters_custom_active@3x.png | Bin 0 -> 1219 bytes Telegram/Resources/icons/filters_setup.png | Bin 0 -> 232 bytes Telegram/Resources/icons/filters_setup@2x.png | Bin 0 -> 493 bytes Telegram/Resources/icons/filters_setup@3x.png | Bin 0 -> 916 bytes Telegram/Resources/icons/filters_unmuted.png | Bin 0 -> 683 bytes .../Resources/icons/filters_unmuted@2x.png | Bin 0 -> 1393 bytes .../Resources/icons/filters_unmuted@3x.png | Bin 0 -> 2128 bytes .../icons/filters_unmuted_active.png | Bin 0 -> 470 bytes .../icons/filters_unmuted_active@2x.png | Bin 0 -> 936 bytes .../icons/filters_unmuted_active@3x.png | Bin 0 -> 1482 bytes Telegram/Resources/icons/filters_unread.png | Bin 0 -> 864 bytes .../Resources/icons/filters_unread@2x.png | Bin 0 -> 1836 bytes .../Resources/icons/filters_unread@3x.png | Bin 0 -> 2735 bytes .../Resources/icons/filters_unread_active.png | Bin 0 -> 677 bytes .../icons/filters_unread_active@2x.png | Bin 0 -> 1402 bytes .../icons/filters_unread_active@3x.png | Bin 0 -> 2108 bytes .../dialogs/dialogs_inner_widget.cpp | 67 +++---------- .../dialogs/dialogs_inner_widget.h | 1 - .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- Telegram/SourceFiles/mainwindow.h | 4 +- Telegram/SourceFiles/window/main_window.cpp | 26 ++++- Telegram/SourceFiles/window/main_window.h | 13 +-- Telegram/SourceFiles/window/window.style | 12 +++ .../SourceFiles/window/window_controller.cpp | 10 ++ .../SourceFiles/window/window_controller.h | 1 + .../window/window_filters_menu.cpp | 92 ++++++++++++++++++ .../SourceFiles/window/window_filters_menu.h | 29 ++++++ .../window/window_session_controller.cpp | 28 ++++++ .../window/window_session_controller.h | 11 +++ Telegram/lib_ui | 2 +- 42 files changed, 235 insertions(+), 65 deletions(-) create mode 100644 Telegram/Resources/icons/filters_all.png create mode 100644 Telegram/Resources/icons/filters_all@2x.png create mode 100644 Telegram/Resources/icons/filters_all@3x.png create mode 100644 Telegram/Resources/icons/filters_all_active.png create mode 100644 Telegram/Resources/icons/filters_all_active@2x.png create mode 100644 Telegram/Resources/icons/filters_all_active@3x.png create mode 100644 Telegram/Resources/icons/filters_custom.png create mode 100644 Telegram/Resources/icons/filters_custom@2x.png create mode 100644 Telegram/Resources/icons/filters_custom@3x.png create mode 100644 Telegram/Resources/icons/filters_custom_active.png create mode 100644 Telegram/Resources/icons/filters_custom_active@2x.png create mode 100644 Telegram/Resources/icons/filters_custom_active@3x.png create mode 100644 Telegram/Resources/icons/filters_setup.png create mode 100644 Telegram/Resources/icons/filters_setup@2x.png create mode 100644 Telegram/Resources/icons/filters_setup@3x.png create mode 100644 Telegram/Resources/icons/filters_unmuted.png create mode 100644 Telegram/Resources/icons/filters_unmuted@2x.png create mode 100644 Telegram/Resources/icons/filters_unmuted@3x.png create mode 100644 Telegram/Resources/icons/filters_unmuted_active.png create mode 100644 Telegram/Resources/icons/filters_unmuted_active@2x.png create mode 100644 Telegram/Resources/icons/filters_unmuted_active@3x.png create mode 100644 Telegram/Resources/icons/filters_unread.png create mode 100644 Telegram/Resources/icons/filters_unread@2x.png create mode 100644 Telegram/Resources/icons/filters_unread@3x.png create mode 100644 Telegram/Resources/icons/filters_unread_active.png create mode 100644 Telegram/Resources/icons/filters_unread_active@2x.png create mode 100644 Telegram/Resources/icons/filters_unread_active@3x.png create mode 100644 Telegram/SourceFiles/window/window_filters_menu.cpp create mode 100644 Telegram/SourceFiles/window/window_filters_menu.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index cb7a45ef9..e0acb5c18 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -937,6 +937,8 @@ PRIVATE window/window_connecting_widget.h window/window_controller.cpp window/window_controller.h + window/window_filters_menu.cpp + window/window_filters_menu.h window/window_history_hider.cpp window/window_history_hider.h window/window_lock_widgets.cpp diff --git a/Telegram/Resources/icons/filters_all.png b/Telegram/Resources/icons/filters_all.png new file mode 100644 index 0000000000000000000000000000000000000000..9cf16f5e0f7bd3b78e891ab9f39af44512f778c9 GIT binary patch literal 1160 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 QtN;K207*qoM6N<$f}RbF6#xJL literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_all@3x.png b/Telegram/Resources/icons/filters_all@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e1cc8aa779bb5dfc876d9cc3c5ace258b5208442 GIT binary patch literal 3522 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_all_active.png b/Telegram/Resources/icons/filters_all_active.png new file mode 100644 index 0000000000000000000000000000000000000000..133c37a7aea95402a6c3c72c329a2dfc6cabd07a GIT binary patch literal 877 zcmV-z1CsoSP)Fk1rA(h&g*x6_uQE?*FXpX z`uv(<{54FfL|TOOzDTP=f38AiJ;t)#Znu-g#YHkVH%De>X2`(605KYk0o}T28rf2L!nvca|fwi?YD3{BiH(p;~VRLg6IyyQu@#^KO+O)|G4-Z2s zmD1zE+0W0h5P0%|)?D3M5fHm|1t+1Z(t(Cv0ZAP@kX&88-xmTL+1^z=Zb zQqfG%jKSe>NJ+1*t^hau$jFGA1e3X|g9c0eg>J@g8&0QF!a6xQA=A^-Bp#0wS}7qu zN2tHQU+DN1WnW*P6n=MiC(UDHV`OVHU8O+ks5*ZpAVzRv8 z2+{9MK0ZEV`?t)+Vo}18D}{pDY$o(HbNCz~tWYwUnAjlIV`F23 zo#ue*Dc;`RAQ%inS67!rhMpt{hr?V5-UME+S5zI;i3!m@ z!2!|J0{i>>kk9Ag<>dw5-{0Z>{vM*yDER$;7#tjAN!r`n;o#ta!$5`4=VN|?{5+xE z-CcH{(VhgjIoH?M0h1^J@N+0-T;m_+%E}5{UtbH!pmKhGo{Ll9@PyFV+1UxS#RFcp z3g^1((a}-JWHKBPe4)d`L#EGWvw)vd16NE)!&wWYMvte8HbI!2oP_S~Zf09uT?N`G z0e?wmXJ?sSnafZW==3{=uv7mewH6`0>bENN=PL9M@l7*&OcRJ*00000NkvXXu0mjf DXF`_8 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_all_active@2x.png b/Telegram/Resources/icons/filters_all_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..762dc383de750e2e314bcc134d4519894eff7fe9 GIT binary patch literal 1767 zcmVKE9AQIFF(uJBI>Bdc= zCJ~hj69QMc5nGffkj8wENDO5BwrHz`}r%mTvdhh@4*PFTT`!6$ad2h}+ zGjrzWnfvCRxq1;oC|siG5b)NK3-Lb!0uUn<1Rw#3L4cta5hMUH2r$$lf&?H20ft&c zkO0IWz)*__5`Y*47-|tg3_!jNyocFheNO2++5n#)kVL2`9h7BuV25?$B!S= z!oou8<>lp2zigbRz11>Hu(*67QI~x)|Ap8~av~ug#EzbZH5D-9DR#rTXkF?z5;^JhG;1Neww{Zt8 z^}+qHiqBKAv9XSR%(1&a_`{Ln;$m4p4Gj&XqN0M}^PVFL7ww*&9*>J2lS8=E1`y^e zEiFw_y?y(Z9zTBEEVr~V@cqAz^%~)g5O=bkTn-;TECYeiuzdgiokE1kbb9mDl5;Jw z?CflE;J^WB@44mV$&)hYo;-OX)%We&M;<(QKyKc=Ngz8Qv*D?50fL(#PhW1eQTjMM zJS_9)+O=y0HkUFPPlXH6p+kpw`f{s{(#O}YU#t9qEdhahl1gq(ux9~bwi1$Wvbp>E z`jYPMZW0z2CX-vMZ~?;9sizk$AJ^B{Rc^<}$CIX}CY4-Iz%}zvpFZie_Za!dj~`XO zY;SL?WGE;rDPd=D`fuQIh?fua0na(STm7yT!7HBudk2UnY#=gK76Pe z`qZgYDw$Fc85yY*Y1Wxx2CXPAGfzx+z$q+EOiWbG;nk~GR+ktm&zw1|R2wx&KTKKTJ*B5G=CiZ!mw^cc~>!NICY z;;9Eufp-N|Z7sl=1J1UAy^LuR=FfkS<)f z;Ox0G`-k*}8H(j!y``mv*%l2RJ$j_s-m5(HxbSHJ^&K>R{@SP*A z2-E;$8qYRBFJHdocFI=6xw$!7US3Wi(PeU0^?CjJb?)x*$Q*x#t-#RI83&Y?m&fgt zQA2!)eD>@aEiEmz3I^{v_wL=}?*8}h-?pCR8v_I<9CUMYlj#_~H-yK+_$CPc^ge(7 zoGvUZ(2b1^X_t#TU3`ZTa>sOVaFE`;dsmu3P*9N8Ur{DYNJyaLPgPnVCuN-@mVEjt{qxr#9rbp$O6=>})ya zm*MPPy6{XZBqW5u&0cy#|MTb11YV?)>Lx$%h`G7BnOwbk)g&^1!iJJS+QwXGefeJi zVogF%kFV^&uNT2nSGY$KJidZ-4S|g%_rE*rX+V15jL5lj(ndPnOSS?Kp%xJ&05J$K)FOfeAO-=3T11cl#2~;>iwI%>`U_U&p-@w@gKPi*002ov JPDHLkV1k+8T~h!6 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_all_active@3x.png b/Telegram/Resources/icons/filters_all_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8a610c42fdacf909f46d6a212b39e589827c3f7b GIT binary patch literal 2592 zcmb7G_d6SG7fx)kXN?*~YqnzZB5E~7?OjDwjnv*%UP6snwTU(oN~AR+Myonvw)Sq& z+Ej^AYPD8<`Tm6Whx43Y&ULPHp8LM;`=nV}K-oY-AOHZsW^&uehMN8VA509?H{08B zh??kp^v(4FfQD?AGY@(IfO*QqNZ;-eaN8A%B-TJSxts%Zf|D~0adoFi3sO*)(M2i);BJ1pvoQElm)k#s&%6m( z=}P5T!vBx%ik|Rf|+{d4@grlFU2jtP*`vLSDEdwrm zh*r7ywUUZ|Q6Z!G_&{c7tRj`t?!`_7@yYGt%!>DR7TTZ*&~-@E&ioK%&Q~tD1Xyc} zw>YCr;L8hzB0h~^_4buKKckrL9w3I$S}H3O?oRDfwa#5;1u`Do!DB!NQfxHIZ;C*C zH*oi3jbr`Ztt_Dun!Bn9a{xO_2KqVmCa>_rHjIS1_g>z?h(7aT1Fz!LvjAaF5;m%A;b`)jY_--oQ zTPqhFm%9Hn*x{88&R^$&AYKbTUKy*}w>;8&_Spgp8CpNhSA&mBxtGc)Cvje5usQs_ zvw(d}lmB<+aC7Q%N<#c6a8S ze?HwhF`RDjggS~3mueT|H~R9WwK5kZPFP(WADeDbS6;p(T-mMjD*obc^v|#$->SZI z%2{$ClhNdd=zX2ze*!Cko1^7V*iUX9j*O-Rg4_7jl`PiYtUjve@zDE0Rkxb~8zqIt%58jN)o^UzW;WeK@s6ziovo z%$*d=ysVhJ>K57-e*v*_(+nR(DM=DB?)30>NZi@_RM7o_S%)W_lk$45Tut6X_nYHU z7?Be*WOVGd_bgf+74=Rq3@=dsV?LCsXKhS(aUT~x&DsR zzE>VMOgnNbSm)S_>G`Dh;RYF_F!4$*<#*ogy+#v;!*V(S1I3)KC{DJOf%4SbjY?5fV`KG$r{(xfAF_Ys$QaDW8egB!1IMKk4E+ZMQcZcG?R zX4g_oe@@`D3lv5$y8GK2rNer^HxL~Oiv3Q)HbP^g>r^f$_^@+`K@^Oc=anuH;(IP4 zU`|o3&`REwkiaBPzBp4{kSzRYir~6A)577Zi%4kiFOZ$7YdJX&R`M_t%*WKgTPD>LmYg|f>c4dU z@(MaBu6^-q-ezKjlt)t5QZOXuXzONKXo69*9a%Mc@B8RMDCON5nPYl=tis+aqghwt za5T=&ceP{9Os!NeexBSmNS`B4q7M3F8MUjV^Po3JJo&pURwD@0DB}U)qeD=)G-&=T z>tWAeX)OS!FIaZcQV;l6sQ{8t2>&_jjEn?ePXJxNFV)gDGGEzUd{+I8xCIIQ&V*I= z0^ytZJNpwae>bS?PvQ9z){X#n9-IRv_*+C1>}8mgD}q>GNMnK}(mIQppxL5Ipyk0{ zU21Ny6m<*Ri1Oqv0-BQ4NH$aiF?)DLp%6%lP_F)G*bv*Ux-d zLcJGNn>1N)V2^qD)q#eWV^{L`3U1#KDB(=2b8hm^ycu2<{^)omSQ@>(+lo*rZpE@9 zrRniu8hBmJYTNaD>Jb}u?z&c-O1EY-ND-a<5A`ufzV$d_4(K1O$&Q7W2;Vdp{}G)3 z6=Oxm+mIDBn1iXBh7r^#`487Z@6$dapyQ)yBjS~>B1=Hw@T5<|0*&Q3jrX)94A_Ja ztO9mTzm^=N@kTMQdQLqBv!FbLz?rVvoeHoNcsn?9o9Vw4opZb+Dy<)}G-^^$K7fg_ Kg%QC3miRw^1^E8} literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_custom.png b/Telegram/Resources/icons/filters_custom.png new file mode 100644 index 0000000000000000000000000000000000000000..499de7caa17cd02f276af422908fb777cf257f41 GIT binary patch literal 492 zcmV2BI>WO&Ep>)nX^~ zJWthit*WY0o6UyJ=}F=_)@ldf=eIo`51?gRE|)-k$+9eiD2m{Izr*czlhUAR8aSO! zkmorRMIq)<;K}Z_k0i80k~&?k>$>WCz4lw%Hp};Yc22?+6{3nP%hD<#4(7_XTCD(y z$q5)CwYS?X?Du;(9*?kIui5-j2(ieFbFo+e?m7L(5<$**FPAxjuiJ~Hyk!4MNFwp- zpAMl;R?~)lt!|9|bQBt++P`4T3Yk=!jzZ`2Sr-!ofj;(4m6_snG7}bAhb@g^FS^}s zH|W9~l1$i0MrwS3Zh3q$>UR-dv_s}&a|wgqScFN~5+U(&mE`=ie>$7`(@IGAvqF+G ioqtxSQ$qTFokBn9UF;~z9-r#~0000!lvNA9*CwjU# zhEy=Vo#mTf=qPghc=I)9mN^OGRj!WqS9- zGijUm%M0kVpZ)UXi^}4QE4JKz+osO$w{z#tiT(Zkvtv)5Oi|h!xBg+BkB;;y2^03O zziV@Y9u~|nnh|#W{rA%^OICdiUmLd7>OM>2`x)E#8DE`^m~}So(94o_+VS!6+#1#< zPm3%ko`1ge+1jwzf7h)L6*4;gG>E6&`QpnGm!(011;~9n{U6(s*Rc%81MMx?%lbPy}Jdz`Ldl_%f?dM=A^iC-L9!IWxtFHnSWF| zFsuyGdh2uQRMYkA;gTFIC;XJ&J9Zy+T9LZ&*%$qls%i;mXZz-Dk7j9h+cJ|s+ zQy=~N7gu33uXO&?qR14ZnWgt$unUExlI*T1EbpP#?=k7nw&t-WrGo1aYl_cGr^>el3@ zxb^DF6FFXfXbSR_nj9rRZ%c#Pg>5q(theebcx6rTQ8PdOCv?7}b?Lqo2ZI9}=QqhU zTV>cW@NHRW$?@v2;GXvO_QkSGHVFyH2(zqR@N>dWIhGg23o2~%t}6O*s%!Ms{%Bz0 z1)0evW&c&~lV9B?E*E{xiTR(Wv#>uq)i6Q(T!Vyw69ebwW+Es7hkC}D-)BzcJa9J# QnE4nyUHx3vIVCg!0KLwelmGw# literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_custom@3x.png b/Telegram/Resources/icons/filters_custom@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4e0b2a126e56f8391968b21831dbc9764467f920 GIT binary patch literal 1463 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_setup@3x.png b/Telegram/Resources/icons/filters_setup@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..87ab4420c260ce6fd62e4b22cea2dc05fbd79df6 GIT binary patch literal 916 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p{OJmUKs7M+SzC{oH>NSs56Zy**tV zLn;{G&UW;B>>%LEE0sEPamw7wPi!pRryNe1>-VYuKabVA?f#UAyaZ7${3uAd#FQ}x`! zdTW2l!#T%i`yMt~8G3&+CeRYh(s0?IA+he* pB_58+kG`-w6L1j^y_J4oDwTPWk@3Da37E|oJYD@<);T3K0RU<+sVD#d literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_unmuted.png b/Telegram/Resources/icons/filters_unmuted.png new file mode 100644 index 0000000000000000000000000000000000000000..819d3f7ec696109a8e88808071f98ebf24e7b6c0 GIT binary patch literal 683 zcmV;c0#yBpP)fEV9w!inC9a=m^XvPnM{VfEEWq0hr={* zj*-n~KWE70azU+DgX{H5;)Oy%6~{RSu2rj5a5|mp8rK0)PQF8azaM6^8HwI*H|X_x z5Rb=I0i0tF*D$8jDPWBWGl-QpG#ZUaXt`WMBocWl6pcn+|`f@QfW0i-EOxEi-I)5IY$M<0XL2h9&Z0wodw%$y<02 zX&DmrPpU(~U{J&(TsOmE`1Iq4bFw03lM-{=j zUSMsxTvl;<8eC3nhy$yn?RJ~AJDm=o5ggramvXIEOU3Ec3UaR*3WY*&KA#^Nf;n6h zh#IZGW(WmQ7(2Dw?Py?U-ZTT!UwdZYBO%=OA92BnScbS}=uWK;>9&PSegh;tk>Y^I Rm;L|%002ovPDHLkV1n73IDh~E literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_unmuted@2x.png b/Telegram/Resources/icons/filters_unmuted@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3244112e856ddb7c79d72dbfb976dbd96e573102 GIT binary patch literal 1393 zcmV-%1&;cOP)(zKmy{Vf&?T1aR_j_Wd=z=90HtfnL!c|hXAKrW{?ELA;9UD8T8!& z#l^+B1_uXSJ3BkB`}=#>!^4BSjBVKW9Rz=@I&#(5*8>3pMuL3}4GoU?Mlo>)PGe)E z5f@E+Q&W?Zkn9JPn3xDJFE1JpMh!SNDJjV*NcIDoo}Ok{JU>4}cXv0$$H%*`uC6Y? zHdIFoVOa~Pudfe2KR=lO>B{Qrs$rljD=YL+ri?m!dwUJ%3WQ`Gpw7-tCMczH zdV1(A*`{Dil zo#BL;R9sx_vpyf7i3b!C5&}m@M@%5Nz~<-Yn+~L)pg^O`1>H+X;sJGZbTDu} zK0ctNq{K8FC7`mhGC-ZQw6(Pf)34b1`V+9>To12?|4T4JKY21anX;Isz z`}{2n2b7bO!}zkfxyjT;R@A!Hwnf+y4k$G>mGNtPdz-0?tf+OTrKO2f@75(85cxsL zIC*@0%+y6z)VkHSMc5J!C^9mV@e8+6t&+RDJ4UtIHq+^6C4kJsm0y^Uy9%Q#AOKBR zCsx}NoG*4iDojjF=$^HC(&M4ed;lXOBb0l8iny6RFfd>#91IFK z^7W3DMn^|Q*)|6VH__D&K0ZD|O-+p`2f2ExtE)8|`RG$&VIg;}hs;stngcXDJIiFx z$;k<%r>85*ZTlp9K|r=Y7+>b*=0w;x2S|OOS5;Ld!YRLWR903pfY3KDGK4qA93ZkM z!#L&88yM~&@udb@Zf|dS!E^gf-{#euRv8%?MQE9ypBLeWaq!+aj3=hwRmd@r^MDZI^78VxW3;%q zC;|@ZFqc6%KQuHHHa0f=f<)ehHVg#&{E@Y_HNY1rM8qZpkm5*PT^(#~ZNd5Zxz};w zty!6`{=`QHZ{_Ul49Gzu)YjG->Qn3YvTiC`&@281kM6%{JPX+@00000NkvXXu0mjfynKF7 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_unmuted@3x.png b/Telegram/Resources/icons/filters_unmuted@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..113455c3e6d1a424a5ce705b6382ec73aedcf3db GIT binary patch literal 2128 zcma);c{~%28^>4X$kAnKzD2po)HJ#8=BU`nHTq&BqdAKlbFAM=$iBv;_|ZZtw-x0+ z=3345b<9y%Qo}|vQRe8o-~YeY>-YQPd7i(Y=l%M8K2Mqx!cIo=4@m$3AaltcepNU_ z{!(8|#Y0C+`~+QEnd021Pt;8w1&`&PUe=^!Mi+B*_~-mj|!KB5wP z*v1u_UM)`F6_K0iNuMhbul?L}PuwikK*SH;S{jcs%N5Zif;%K^T}c}iH8;Cw$|hQ2 zqi(El!YUmKW3k-W+im!n3oN$_0hhHxef%82nt?UKz1!*eBiN&B!7YMpQ>RvLO&`-nPmE3oil z%0b+FiW>J_mI`-ocbhxUAah4wBDwUt;?7_K;sXk~}83`y+FpRik8vg71 zT--v#52fVSad1@MBb^PZS$t);MI0ZV&-PSZTSS{C?M&j>~ENhnFfyAyVT_w}V?l zrq1s!PryJBW|<;l7ajHtBLk744R5os#$M6wdj!slNAWj;=B&9w|)?8-d6Q3n=jj4ck?x2z|FELm&m z$JY3?;#|pNx||5&TpPO9w>@cTZJuFrOg&@lfUNo=yPfc{@CGTj`w4FjWk@fj7Uem} z{u`9IJgHlO4je2v@Rtq=4G!AfT*=RQI8_^%qOSIbOWnUjI33TnF~BahA#4NkItD9i zYwOdV6neLmk)d#+(n>Vn-Z*U9FtTyT0469*+S|z^bVM@Arz}9UNm6H6Uw*U~2MBBJ zs!N14nZjUw`07Su)M%orm~18C_n!N2O-f~+g2vUqQaZnZ5v~bPdcQ;p%K}CTb3Bw4 z^gI>ZZ43|Rdz0m1y#_uOK*uM>ZiFp-0-wiwBW~*A>qPi#;wM^im_7Bw?d=o5q!ESS$~Zl z!7i4TI!N2=Go^2^jD_Y1o~ZP^c$RX9OvM75k4fiKCzp8>HTb#CWE)u&ZVVJ}?p_0E z9?%b)UhFzmPjt?YtMYooOo>ErQLp@x9czNTJzvQb-C3WOv<)@7r!XN2$SZ4U#c#Zp z6!Z1o{<4!&JLTN_D>HnR-9Ci8PO%>FC`oV})mb3sv9;V=RO~Y9UPRiTynuUnjI%KG zv});wy7BR4{?|VD7LFp;aUrt-zk$>4Z)!3Q#(zjd)HF|yXT09s{>e+)6&O{qGjqJQ zM8u?1XCSrZIs#OyMgk|>{Tjb8u}*ROmQ>WVd9 zb?u39R&wWnqT*l=b8E?zq+1uc!FA&@-=|-1BCk_9ucWM;u zNIjCR&it99?cw#l*H%NwZoueboBvQePUSdBMvkZ!;iE{Dkw4#k27GjwI}g8$O2sn8 z4k<#2jX}v8h=M{xa~h?Nu@jzFgKQuRpIQBd$~?JaAY)j>O+%&WnHs6N(oV`Pr}dnZnfhRS=Ol`UDFG29wGBlV8Ts>Ifh?RaVjeJWko>E&{vRaJeG(>}o$ zMG=Jp+@pvo?aS64%$zA|hL%!%KCSlQb$^mQ3}0S8oNp|T;=+yDRo M07*qoM6N<$f_FmFNB{r; literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_unmuted_active@2x.png b/Telegram/Resources/icons/filters_unmuted_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a1ed122a89c9179de210815d6046e8677dba79fb GIT binary patch literal 936 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*$9uXs zhEy=Vops);%2DL_aeLOZFm7?7mYd8|61J>NKe6=EqNV-;PTDUO*Mz_1=g=)loxP|` zaLGlj30|zs=kxtS7F5{YyPtpV%z6uE^Sz(vp0oTsv-teg*DOEl)`b@$r!>+alQ)pMB-{d#=$ zSZ3V+F`to$?wr$&No^RQ?^QR%(@m)D#>pwqznrdNX)l|0oZ*2Ao^Zfk$ z0KMsoGkv0ZL-o0SZ4O@Iyih z9q|i;RvtKWrbpLfkEDsME${Q^&+8;7JofkVOUTMP#ikPHz|PM8B;|jZ!^Vg|Hygyn z#cM^A_b=E`p)SN3=CNk`&%1YHmu9nGvz#W@>n8f^L0*n_L2d0@bIqFm{kwK0b;mci zGyHoP(fIRc<<+dM7C!tpuDD!1bf~GSvQqMedFi3D&ZU<#gAMz(3UjcCEY~-4SQ(P_ z=7>+1wzjs~S>78-%qgE6#h0&-XYBYKZ7;o`z`|z2syXVvgiU8xnmObMMw{t0a6Dl0 zU=U>Ch%~}VbuhGS`lw#y?d_fLIP=l<$L?QR?JS=q$y_*gjLpW@w)SY=ThZD@`D`A~ zj5Ma7cHDk@D_iOf!C&W|e_p0IlX1)D&CGx6>MhdLpSL<4R8cW*c=ztzoC6Y1L-HRR zvz@pRyXNxC1l!;Gw?2Q~e9@xk!nOT64DYo6Ous9=c+)|D`CrHW_N`vM+U?mA&Ch@J z(n>gGrHsouE$1+=&SrFZP|Eh=-@mw*zl<(6CdZnlH=H|nPHo=jmoHx)Nw#E@TK&Un zM`!NQ$AuXtQcrweJ$%SmutxrN+i^cT&BJ|+_ELB5-K%oiwR30V)~%+~3odN8c6cHE z-QeB(_lKW8Ro%01U!Ba?D%o$}zU91il-bRY^SgP%pZtt-U0?hg)f+md3OO)!C^(#$ fgTe~DWM4fV@pkE zUfNs}zUz5u9u;XO@+cX4xVr8ianJXibH4AKU(a{wp6<>HP)#TR08ntjIbJyM`2Q#i zIna_Qzu^M|M`O=p0f3egxxG+n06;p&#S!bB4qRss8{32&>mNdOtZ99-Iope+>BAX+gjRxvu+FR{LA^|U|#aaJJ&{?ITxqc ztjkMV8Jyj?ks%hB?38czqOTCDEOWDT*?=_4zZ&pW9UmOL>1iZUzqbJK#pBnaRg$0A)`D%2k{Us4tGU?WGKo~56#VeWxRk4l zi`R~&g@q5-bh2PM2WufLYigD~6N%`o?+p9IkByC;9vf3Ss-+bb5D;*#9${=8fW-nY zXJ_wQdt3}WL_C3#dAGI}Z);)EC(b_=!|m!?WH1jM$=I7@ZPKBsXfBEt=g+iGcHjL`( z=&-W2wuTzfr>{L}OHtxiPDCu%C0{3Bg=3iS|oC8MYF(IYto%AXIFlPi)p z)kAvEi-`tycG`LenNTS7wy_{2rl#Ja{*$KZ6%-VA%)aYTLqh`wE0s!WsH=0nt4>3V zp>nUHljM>Ji@7tat8s?zM0VUJw3Ek+&dA7E6N~%)n4UqEYYAs(p)h8xUQ=r`lc^R> z-J|yaRQjPjOXOB*dk)Z-K!8C?LvUx$=2C97Wgz>TD=UvO84S@-{=Bkl0+-9>`Lyi7 zWr-vb$?eX=z_=7WdhLt4x;jVSm%qdEgZ0{PEO@{i#&P`Y>aAXL(2|*S0bI+)i;;c#GtTq5%vk-csF|aaoW?ihrr(FcODSZC64{Gjx1(iKQ_{F}g&kA-2Ju zuQTpBX3tl3qj~32vDH=ga;YcF!Szt!Z;lRFIp9U)p$kVq5PBX>4fKGhT#Eq9D=SEw zfZciwa6Ymc2Aa@741mGI(Fi)EluWWyR+bH~ySr%jf%5A1eW!Y#c>ku54X|W;``3&R zGp63tbHl||GcwGwGQs=z?^`Yv{LO7{=Jzf%8(&Jxp@#$KXJ-q{fv;%m>j|c>-reh% zkz%q~gJd$frG;KkPD~u_W2CGBg9wD;ne;~O#>Phf^+?~uJSBY-u}lV5d&yby=FOXc z1jfrPq)`XkX-K$f%8`$5^Ot_s-X77G;G1M(Y8p5wIvTG0cZ0UwF(TiR(7DG~AE+erKbV@C(c76*K+saER1XU102nmhWLV1ob7%D&bCL3QxA`wQD_Dpwt+iOGF%F0SA@wIgrSnUQ8 zkP=j}M=^c}b$H#4Xr-mkK~5nM_EQ@l3Gnz`$#FrZNUTn$Gw0?AtC<$xuBw!yFt(}R zMIr}QWh2_sG7rw2ELJa{oSU2!loS{5tIsYh=qAR*@L{Wo_zLBaskWV+w1Zi{SgiRh z=)EP*8xZjJh-frAK0I8sdLnarDrtza9H?iWX>@IS;EWJ)n@V~*aRmvAnhSpnmo4{2 l56kEimu!R;Isa?%T`?jOT?b<4e>^y6fXi8T$0mCs?O*=Du_*um literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_unread.png b/Telegram/Resources/icons/filters_unread.png new file mode 100644 index 0000000000000000000000000000000000000000..1ed90a352775862c096f8b02d8a30aaf6c07cb87 GIT binary patch literal 864 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
  • c z3)Bvy?+7W1SR;LxV@XlT`btbO>#~(&z?2r6%x=Z^{p6(X{J|FtB%74S{(n67U&!tc ZxHvhM@{gX_eFlL7j1A268+5VJ{{j}X9lihn literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_unread_active.png b/Telegram/Resources/icons/filters_unread_active.png new file mode 100644 index 0000000000000000000000000000000000000000..556111486ae50afdc40a8331a7c32322264fa5d4 GIT binary patch literal 677 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 AkN^Mx literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index baae406e7..f5e592af4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -82,12 +82,10 @@ int PinnedDialogsCount(not_null list) { } // namespace struct InnerWidget::CollapsedRow { - CollapsedRow(Data::Folder *folder, FilterId filterId) - : folder(folder), filterId(filterId) { + CollapsedRow(Data::Folder *folder) : folder(folder) { } Data::Folder *folder = nullptr; - FilterId filterId = 0; BasicRow row; }; @@ -121,10 +119,6 @@ InnerWidget::InnerWidget( setAttribute(Qt::WA_OpaquePaintEvent, true); #endif // OS_MAC_OLD - _filterId = Global::DialogsFiltersEnabled() - ? Global::DialogsFilterId() - : 0; - _addContactLnk->addClickHandler([] { App::wnd()->onShowAddContact(); }); _cancelSearchInChat->setClickedCallback([=] { cancelSearchInChat(); }); _cancelSearchInChat->hide(); @@ -262,6 +256,11 @@ InnerWidget::InnerWidget( updateDialogRow(next); }, lifetime()); + _controller->activeChatsFilter( + ) | rpl::start_with_next([=](FilterId filterId) { + switchToFilter(filterId); + }, lifetime()); + refreshWithCollapsedRows(true); setupShortcuts(); @@ -294,21 +293,6 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { _collapsedSelected = -1; _collapsedRows.clear(); - if (!_openedFolder && Global::DialogsFiltersEnabled()) { - const auto &list = session().data().chatsFilters().list(); - if (_filterId - && ranges::find(list, _filterId, &Data::ChatFilter::id) == end(list)) { - switchToFilter(0); - } - if (_filterId) { - _collapsedRows.push_back(std::make_unique(nullptr, 0)); - } else { - for (const auto &filter : session().data().chatsFilters().list()) { - _collapsedRows.push_back( - std::make_unique(nullptr, filter.id())); - } - } - } const auto list = shownDialogs(); const auto archive = !list->empty() ? (*list->begin())->folder() @@ -323,7 +307,8 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { } _skipTopDialogs = 1; if (!inMainMenu && !_filterId) { - _collapsedRows.push_back(std::make_unique(archive, 0)); + _collapsedRows.push_back( + std::make_unique(archive)); } } else { _skipTopDialogs = 0; @@ -394,7 +379,7 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) { //const auto lastMousePosition = _lastMousePosition; clearSelection(); _openedFolder = folder; - _filterId = _openedFolder ? 0 : Global::DialogsFilterId(); + _filterId = _openedFolder ? 0 : _controller->activeChatsFilterCurrent(); refreshWithCollapsedRows(true); // This doesn't work, because we clear selection in leaveEvent on hide. //if (mouseSelection && lastMousePosition) { @@ -706,23 +691,14 @@ void InnerWidget::paintCollapsedRow( Painter &p, not_null row, bool selected) const { + Expects(row->folder != nullptr); + const auto smallWidth = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; const auto narrow = (width() <= smallWidth); - const auto text = row->folder - ? row->folder->chatListName() - : _filterId - ? (narrow ? "Show" : tr::lng_dialogs_show_all_chats(tr::now)) - : ranges::find( - session().data().chatsFilters().list(), - row->filterId, - &Data::ChatFilter::id)->title(); - const auto unread = row->folder - ? row->folder->chatListUnreadCount() - : _filterId - ? session().data().unreadOnlyMutedBadge() - : 0; + const auto text = row->folder->chatListName(); + const auto unread = row->folder->chatListUnreadCount(); Layout::PaintCollapsedRow( p, row->row, @@ -1721,12 +1697,6 @@ FilterId InnerWidget::filterId() const { return _filterId; } -void InnerWidget::closeFilter() { - if (_filterId) { - switchToFilter(0); - } -} - void InnerWidget::clearSelection() { _mouseSelection = false; _lastMousePosition = std::nullopt; @@ -2551,11 +2521,8 @@ bool InnerWidget::chooseCollapsedRow() { return false; } const auto &row = _collapsedRows[_collapsedSelected]; - if (row->folder) { - _controller->openFolder(row->folder); - } else { - switchToFilter(row->filterId); - } + Assert(row->folder != nullptr); + _controller->openFolder(row->folder); return true; } @@ -2568,9 +2535,7 @@ void InnerWidget::switchToFilter(FilterId filterId) { &Data::ChatFilter::id)) { filterId = 0; } - Global::SetDialogsFilterId(filterId); - _filterId = Global::DialogsFilterId(); - Local::writeUserSettings(); + _filterId = filterId; refreshWithCollapsedRows(true); _collapsedSelected = 0; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index b68598739..a6427cf66 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -81,7 +81,6 @@ public: const QVector &result); [[nodiscard]] FilterId filterId() const; - void closeFilter(); void clearSelection(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 4faa07a12..d93369251 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1575,7 +1575,7 @@ void Widget::keyPressEvent(QKeyEvent *e) { if (_openedFolder) { controller()->closeFolder(); } else if (_inner->filterId()) { - _inner->closeFilter(); + controller()->setActiveChatsFilter(0); } else { e->ignore(); } diff --git a/Telegram/SourceFiles/mainwindow.h b/Telegram/SourceFiles/mainwindow.h index d9b6fae0c..75aef8eca 100644 --- a/Telegram/SourceFiles/mainwindow.h +++ b/Telegram/SourceFiles/mainwindow.h @@ -118,6 +118,8 @@ public: not_null photo); void hideMediaPreview(); + void updateControlsGeometry() override; + protected: bool eventFilter(QObject *o, QEvent *e) override; void closeEvent(QCloseEvent *e) override; @@ -126,8 +128,6 @@ protected: void updateIsActiveHook() override; void clearWidgetsHook() override; - void updateControlsGeometry() override; - public slots: void showSettings(); void setInnerFocus(); diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index a220b0369..d78970a0e 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -373,6 +373,19 @@ HitTestResult MainWindow::hitTest(const QPoint &p) const { return Window::HitTestResult::None; } +int MainWindow::computeMinWidth() const { + auto result = st::windowMinWidth; + if (const auto session = _controller->sessionController()) { + if (const auto add = session->filtersWidth()) { + result += add; + } + } + if (_rightColumn) { + result += _rightColumn->width(); + } + return result; +} + int MainWindow::computeMinHeight() const { const auto title = _title ? _title->height() : 0; const auto outdated = [&] { @@ -386,7 +399,7 @@ int MainWindow::computeMinHeight() const { } void MainWindow::initSize() { - setMinimumWidth(st::windowMinWidth); + setMinimumWidth(computeMinWidth()); setMinimumHeight(computeMinHeight()); auto position = cWindowPos(); @@ -492,6 +505,7 @@ void MainWindow::leaveEventHook(QEvent *e) { } void MainWindow::updateControlsGeometry() { + auto bodyLeft = 0; auto bodyTop = 0; auto bodyWidth = width(); if (_title && !_title->isHidden()) { @@ -508,7 +522,13 @@ void MainWindow::updateControlsGeometry() { bodyWidth -= _rightColumn->width(); _rightColumn->setGeometry(bodyWidth, bodyTop, width() - bodyWidth, height() - bodyTop); } - _body->setGeometry(0, bodyTop, bodyWidth, height() - bodyTop); + if (const auto session = _controller->sessionController()) { + if (const auto skip = session->filtersWidth()) { + bodyLeft += skip; + bodyWidth -= skip; + } + } + _body->setGeometry(bodyLeft, bodyTop, bodyWidth, height() - bodyTop); } void MainWindow::updateUnreadCounter() { @@ -621,7 +641,7 @@ void MainWindow::showRightColumn(object_ptr widget) { const auto nowRightWidth = _rightColumn ? _rightColumn->width() : 0; const auto wasMaximized = isMaximized(); const auto wasMinimumWidth = minimumWidth(); - const auto nowMinimumWidth = st::windowMinWidth + nowRightWidth; + const auto nowMinimumWidth = computeMinWidth(); const auto firstResize = (nowMinimumWidth < wasMinimumWidth); if (firstResize) { setMinimumWidth(nowMinimumWidth); diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h index 3282483f0..1580dfbde 100644 --- a/Telegram/SourceFiles/window/main_window.h +++ b/Telegram/SourceFiles/window/main_window.h @@ -80,7 +80,7 @@ public: virtual ~MainWindow(); - TWidget *bodyWidget() { + Ui::RpWidget *bodyWidget() { return _body.data(); } @@ -95,6 +95,11 @@ public: void clearWidgets(); + int computeMinWidth() const; + int computeMinHeight() const; + + virtual void updateControlsGeometry(); + public slots: bool minimizeToTray(); void updateGlobalMenu() { @@ -148,8 +153,6 @@ protected: virtual void workmodeUpdated(DBIWorkMode mode) { } - virtual void updateControlsGeometry(); - virtual void createGlobalMenu() { } virtual void initShadows() { @@ -175,8 +178,6 @@ private: void showTermsDecline(); void showTermsDelete(); - int computeMinHeight() const; - not_null _controller; base::Timer _positionUpdatedTimer; @@ -184,7 +185,7 @@ private: object_ptr _title = { nullptr }; object_ptr _outdated; - object_ptr _body; + object_ptr _body; object_ptr _rightColumn = { nullptr }; QPointer _termsBox; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 140f0140a..df4300a30 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -245,6 +245,18 @@ createThemeLink: InputField(defaultInputField) { font: boxTextFont; } +windowFiltersWidth: 72px; +windowFiltersIconTop: 8px; +windowFiltersAll: icon {{ "filters_all", sideBarIconFg }}; +windowFiltersAllActive: icon {{ "filters_all_active", sideBarIconFgActive }}; +windowFiltersUnread: icon {{ "filters_unread", sideBarIconFg }}; +windowFiltersUnreadActive: icon {{ "filters_unread_active", sideBarIconFgActive }}; +windowFiltersUnmuted: icon {{ "filters_unmuted", sideBarIconFg }}; +windowFiltersUnmutedActive: icon {{ "filters_unmuted_active", sideBarIconFgActive }}; +windowFiltersCustom: icon {{ "filters_custom", sideBarIconFg }}; +windowFiltersCustomActive: icon {{ "filters_custom_active", sideBarIconFgActive }}; +windowFiltersSetup: icon {{ "filters_setup", sideBarIconFg }}; + // Mac specific macAccessoryWidth: 450.; diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index 69afd5e88..766d25a2f 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -31,6 +31,11 @@ Controller::Controller(not_null account) _sessionController = session ? std::make_unique(session, this) : nullptr; + if (_sessionController && Global::DialogsFiltersEnabled()) { + _sessionController->toggleFiltersMenu(true); + } else { + sideBarChanged(); + } _widget.updateWindowIcon(); }, _lifetime); @@ -86,6 +91,11 @@ void Controller::showRightColumn(object_ptr widget) { _widget.showRightColumn(std::move(widget)); } +void Controller::sideBarChanged() { + _widget.setMinimumWidth(_widget.computeMinWidth()); + _widget.updateControlsGeometry(); +} + void Controller::activate() { _widget.activate(); } diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h index 7ce23ca04..289d6d390 100644 --- a/Telegram/SourceFiles/window/window_controller.h +++ b/Telegram/SourceFiles/window/window_controller.h @@ -54,6 +54,7 @@ public: } void showRightColumn(object_ptr widget); + void sideBarChanged(); void activate(); void reActivate(); diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp new file mode 100644 index 000000000..8128cb31d --- /dev/null +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -0,0 +1,92 @@ +/* +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 "window/window_filters_menu.h" + +#include "mainwindow.h" +#include "window/window_session_controller.h" +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_chat_filters.h" +#include "styles/style_widgets.h" +#include "styles/style_window.h" + +namespace Window { + +FiltersMenu::FiltersMenu(not_null session) +: _session(session) +, _widget(session->widget(), st::defaultSideBarMenu) { + setup(); +} + +void FiltersMenu::setup() { + const auto body = _session->widget()->bodyWidget(); + rpl::combine( + body->topValue(), + body->heightValue() + ) | rpl::start_with_next([=](int top, int height) { + _widget.setGeometry({ 0, top, st::windowFiltersWidth, height }); + }, _widget.lifetime()); + + const auto filters = &_session->session().data().chatsFilters(); + rpl::single( + rpl::empty_value() + ) | rpl::then( + filters->changed() + ) | rpl::start_with_next([=] { + refresh(); + }, _widget.lifetime()); + + _session->activeChatsFilter( + ) | rpl::start_with_next([=](FilterId id) { + _widget.setActive(QString::number(id)); + }, _widget.lifetime()); + + _widget.activateRequests( + ) | rpl::start_with_next([=](const QString &id) { + if (id == "setup") { + } else if (const auto filterId = id.toInt()) { + _session->setActiveChatsFilter(filterId); + } else { + _session->setActiveChatsFilter(0); + } + }, _widget.lifetime()); +} + +void FiltersMenu::refresh() { + auto items = std::vector(); + items.push_back({ + QString::number(0), + "All Chats", + QString(), + &st::windowFiltersCustom, + &st::windowFiltersCustomActive, + st::windowFiltersIconTop + }); + const auto filters = &_session->session().data().chatsFilters(); + for (const auto &filter : filters->list()) { + items.push_back({ + QString::number(filter.id()), + filter.title(), + QString(), + &st::windowFiltersCustom, + &st::windowFiltersCustomActive, + st::windowFiltersIconTop + }); + } + items.push_back({ + "setup", + "Setup", + QString(), + &st::windowFiltersSetup, + &st::windowFiltersSetup, + st::windowFiltersIconTop + }); + _widget.setItems(items); +} + +} // namespace Window diff --git a/Telegram/SourceFiles/window/window_filters_menu.h b/Telegram/SourceFiles/window/window_filters_menu.h new file mode 100644 index 000000000..b4c70ba52 --- /dev/null +++ b/Telegram/SourceFiles/window/window_filters_menu.h @@ -0,0 +1,29 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/widgets/side_bar_menu.h" + +namespace Window { + +class SessionController; + +class FiltersMenu final { +public: + explicit FiltersMenu(not_null session); + +private: + void setup(); + void refresh(); + + const not_null _session; + Ui::SideBarMenu _widget; + +}; + +} // namespace Window diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index fe82914c7..04dd32108 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_info_box.h" #include "window/window_controller.h" #include "window/main_window.h" +#include "window/window_filters_menu.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "history/history.h" @@ -189,6 +190,17 @@ void SessionController::initSupportMode() { }, lifetime()); } +void SessionController::toggleFiltersMenu(bool enabled) { + if (!enabled == !_filters) { + return; + } else if (enabled) { + _filters = std::make_unique(this); + } else { + _filters = nullptr; + } + _window->sideBarChanged(); +} + bool SessionController::uniqueChatsInSearchResults() const { return session().supportMode() && !session().settings().supportAllSearchResults() @@ -713,6 +725,22 @@ rpl::producer SessionController::floatPlayerClosed() const { return _floatPlayers->closeEvents(); } +int SessionController::filtersWidth() const { + return _filters ? st::windowFiltersWidth : 0; +} + +rpl::producer SessionController::activeChatsFilter() const { + return _activeChatsFilter.value(); +} + +FilterId SessionController::activeChatsFilterCurrent() const { + return _activeChatsFilter.current(); +} + +void SessionController::setActiveChatsFilter(FilterId id) { + _activeChatsFilter = id; +} + SessionController::~SessionController() = default; } // namespace Window diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 245b5edde..5daa506e0 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -56,6 +56,7 @@ namespace Window { class MainWindow; class SectionMemento; class Controller; +class FiltersMenu; enum class GifPauseReason { Any = 0, @@ -292,6 +293,13 @@ public: not_null replacement); rpl::producer floatPlayerClosed() const; + [[nodiscard]] int filtersWidth() const; + [[nodiscard]] rpl::producer activeChatsFilter() const; + [[nodiscard]] FilterId activeChatsFilterCurrent() const; + void setActiveChatsFilter(FilterId id); + + void toggleFiltersMenu(bool enabled); + rpl::lifetime &lifetime() { return _lifetime; } @@ -321,6 +329,7 @@ private: const not_null _window; std::unique_ptr _passportForm; + std::unique_ptr _filters; GifPauseReasons _gifPauseReasons = 0; base::Observable _gifPauseLevelChanged; @@ -335,6 +344,8 @@ private: std::deque _chatEntryHistory; int _chatEntryHistoryPosition = -1; + rpl::variable _activeChatsFilter; + std::unique_ptr _floatPlayers; Media::Player::FloatDelegate *_defaultFloatPlayerDelegate = nullptr; Media::Player::FloatDelegate *_replacementFloatPlayerDelegate = nullptr; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ccc12ce3d..81e9a8083 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ccc12ce3da8f9ac30f30228c225585f732f74d5f +Subproject commit 81e9a80831555ae7908ee86fb15ea791fadd0891 From c83e29755472b4a17ed42f5d46e3567259e9cb85 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 4 Mar 2020 13:21:19 +0400 Subject: [PATCH 014/115] Implement dice media display. --- Telegram/CMakeLists.txt | 4 + Telegram/Resources/art/dice_idle.tgs | Bin 0 -> 3939 bytes Telegram/Resources/qrc/telegram/telegram.qrc | 1 + .../chat_helpers/stickers_dice_pack.cpp | 96 ++++++++++++++++++ .../chat_helpers/stickers_dice_pack.h | 37 +++++++ .../SourceFiles/data/data_media_types.cpp | 46 +++++++++ Telegram/SourceFiles/data/data_media_types.h | 40 ++++++-- Telegram/SourceFiles/history/history_item.cpp | 2 +- .../SourceFiles/history/history_message.cpp | 4 +- .../history/view/media/history_view_dice.cpp | 60 +++++++++++ .../history/view/media/history_view_dice.h | 43 ++++++++ .../view/media/history_view_sticker.cpp | 67 +++++++++--- .../history/view/media/history_view_sticker.h | 11 ++ Telegram/SourceFiles/main/main_session.cpp | 2 + Telegram/SourceFiles/main/main_session.h | 7 +- .../SourceFiles/storage/localimageloader.cpp | 4 + .../SourceFiles/storage/localimageloader.h | 2 + 17 files changed, 401 insertions(+), 25 deletions(-) create mode 100644 Telegram/Resources/art/dice_idle.tgs create mode 100644 Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp create mode 100644 Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h create mode 100644 Telegram/SourceFiles/history/view/media/history_view_dice.cpp create mode 100644 Telegram/SourceFiles/history/view/media/history_view_dice.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e0acb5c18..06d89a81a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -270,6 +270,8 @@ PRIVATE chat_helpers/stickers.h chat_helpers/stickers_emoji_pack.cpp chat_helpers/stickers_emoji_pack.h + chat_helpers/stickers_dice_pack.cpp + chat_helpers/stickers_dice_pack.h chat_helpers/stickers_list_widget.cpp chat_helpers/stickers_list_widget.h chat_helpers/tabbed_panel.cpp @@ -436,6 +438,8 @@ PRIVATE history/view/media/history_view_call.cpp history/view/media/history_view_contact.h history/view/media/history_view_contact.cpp + history/view/media/history_view_dice.h + history/view/media/history_view_dice.cpp history/view/media/history_view_document.h history/view/media/history_view_document.cpp history/view/media/history_view_file.h diff --git a/Telegram/Resources/art/dice_idle.tgs b/Telegram/Resources/art/dice_idle.tgs new file mode 100644 index 0000000000000000000000000000000000000000..aad33bffe1c91c36b22ec59e80be5d9dafa23ab2 GIT binary patch literal 3939 zcmV-p51jBHiwFP!000021MOYiZ{xTT|5pM(O9F@A%-glS76-Iwad&S)u<&l2O}dF4 z*xtR{F7m&3W=PqREID>oiL!}aY)PcZ`Qnc=LrVHKS$|wjP9d9onVe2cVG23PCYQ^} zsTJAe`fhTHe~Y_#`p4wdfXpUW>3@EU_xv)yyq;gLXD9C$pXVQDc=yG{1>ZJZt!8U% z{OsH08m~+~EN<`K39N84{W@F5g0k-Q{DP@if1RA_)bi@?dj198;SH-L7kI@%W+Aj^#`tV>l^^(r|CQGhfV z{RS|IluxTBuV8t`(kPMsU))X%I z^Xv??D`Jo4{2awkm2#xP3M|YGAY)ivnL;>C6~1rTdgOo=BnwUy#_B-kWzuQ+5S`5r2$F1=P~~*@_bBNHnt^QQ>s47 z^adq@Fm>}^{(FDRoUuTETQ3)%W?e&o#&82pL@R)ZR?I-bMVQDMSS31Sy!y0LZ8hDw zlq_L7UtKPix0Czxk{5y?W+idB<>)*@xQY-NB}(%D9){X(zNg$anI+7w;}#7zsCR#n)w9)Z+22G!h% z!U5u{vS@{N3%yQ7v{hWq~XBQ9q0{DjJ$nPuN&|i-YEL^DO1x#-!Ou2 z3;4&ORjE)O|k1sISBBBW3TAnRq zdt&VElX}^}UiU_^SM)!hZL!yl*xQ1=DRb;f>@9_HA9b)v3B#BYep@MF+v8Y-UDsGX zbxzp!pVICo{zn98sg6pHCwZFVZlc_6J^z z+adl9Ag}BPdHoLX+7WoW*Wg$>`xoKHH1L=P?topf3%mLUP6Y1|{{~=J^aH!Q`_A+T zyWI(3md@S`uskjU{PpDh;yO1si^biEY+`d0gf*I$V}X4)YVKGdTMOF;A63kTtuB?| zQO*1k?1n6YCC&jBT$YDIX+mFge$i=-Wx3-M`b}q$;dBF35C5@D6)Kj2EP^nZ6opoj zG8dGmsAew0rctbl@C=;6D1>oX&jA|w8TXbtV>mB7A9GK6*_9<)O{Bn@Km}DFoYLoD zY$BP`BX}0OjqOMm&v5?0GoNUpt}>iNtmGMlY6ggnVA#Q-N3J0iQ%plbQdA+%GVA5% z*?nzbAE_N^*N#G%!mbQz2b$Wkk*rV%AEJIF8as+5=*W~H7IFILGo;z#1da@Ycb+&7ssHmMClr|D;nJb*aE=z;O~g6!Fxm zQf0)hk0*}HG;31^>NoQubjm~DC-f2yWY%DQ^i zpo%`JAZ;Lc2+|GPn`ZP-ad3sg3(YF_J`Faqew{>1+7?dZU;)EIkz9;oOAH~$(r1)_ zfSTVM4IxQVM!85;Dg-rbs8J4B)h}$$Q5!Po2HV? zfT$$WrA@jNuC!iJYMk|pu?_k0F4iJwK%Ywc0(krx)-R3DNQ&^>vQbHxfTNje(28Lx z5Lra@@O>#**N3bnVzQD8h#4r71}hLJIT}zpPIWi{ouhNG477xGe6x(eOQOXvs*u)3 z9QGkYoAtyjqBDXr4erW_MQ}vJKy%s%)^X}9{&AR7SYc@rfDjuY>xdwXy(>g-9BOg6 zKr{;?kFgQW!>S0p!N&0)fmuq}YFBWFIEF$Jd5&t9L=-4rngju`kK;hZ85>Z|L^@)O zS4>S2RU2tB(aM}G<(fd-Zd45k??MFUrgFDCUPeFV3H_Aw1LEQKn94Y-oj{nAiY8bq zoY!cMP8vRS+LUqC!!cEPEd?zQgBJX|z2Wit=3|#l#z-BwL>gQECj~Pn_MzB>HltX0 zfw2iGDdXRASk|tj=}F?4(s2p`I;!7o90Cko=2^W=(J1C&Ma_9%PvrJCi>NZkx%dH;qk~9>c|%m z@^laoEKY}b8a7a<4n{s0(={ECcj$8}Rlswv9Pl(bXK^s&ksT85(66IVBE|%kqt`(J zFZc*}(wBf|65uH@FyN7IU#2Z2?}&_EU=N6=Qp<7GgJWVjBHn95Jl&0W=9MF!P+1Iz zgUXZ}67Yh~B)fOcX0$*#B-oh#P<0ABAeh$9*d3yAi1EsD6aB9~;taH>9ao4r$VtgEu{kFff=y)Jlj-S8$~JG@a?off|{ z2!kSCo zuV8@PuCI>Z_Bz1LcY_+|O-uzbibE@&cbG<5c zvz+C()75&mYz82^kEP!=-(6#ICbb>1q8HYf=35i04<~!^K)fXDg#PQN-+ur3S5o10 zIh!X(_b`HY10Ke7I55ybbD&s#vR+hAt~V!#&LuuxPj14d@tiYe`!*n;1S<-;UCl0wV_x&0T*2Z;I` zGU1_Q+RNo6sYE7~K&MVXyl_@zQi@7Sk)28bc`XKb!$bv zbXJm9bkd6I){1`NtmtxPOj_xF;TgR2IbJ8Nn4}fmt(BKP&6~=%RC#{v*2;^Y>62D0 zBv!h$(mmN%oqg3{1)h%;*I>nUvJ&bJJ}sTaptTma$b-H7p5GIP5uEMi-Ndkj*M)b?j^K!t|PYP&pjO4{-M;{rzPKS$p>*rXui>r$V~XI;9NGG zL9%pO9j?OGq8y!naa{gyJczzrZmY^C7H@@nYv*q zRY|5Q$y9aA)DKK)lBrHIRoyZT!&0h~Om&i}?v$w-cy`iBrY6Z$cgj=^JUy8tQ40X5{u9_c5xSC$f>I(;Z9dKy*I1Rd9Xm&u~(Gu++2Fqz94{v8L4ANdpytldW zK+Earr+i0o23gB{inGdZ)fq3Z*M0n;vXI^azT@a;mEWqtw%)*r0J&@7H$7L@_VM!PYaKwi%sj_=2>{$_~_5aBtY7M;7#VAiA(kTTtA7**@!lB>X5&WV!3pzb}m7T@Y&BIvq+o+K@sx+VAdZ2o?uhZ?bUs%b- zmj;N84;(sE{2GCLd$cLcw>MC69?Q$4$`;K>@6YVx6EfeuKQjk@koh~dX!gC-1J0OU z*Ltio{d^{={7#ZGyJ5yrU+dWf5qa6xx?H=UFLj%?&ldHu!a*s zTd*%_U^$So8)@u@V2Z<|%9pO1`GKwJ_15y{%U6gLUiaCud^)1llM$_-j%c%&=w`5d zOv`pZ(R9t_)@$Bre`mJ%&*Z2GefK-#c=h}&XwOC&N x4_%q*%@e{>Wd<|P_Kd+lmW$7Kd!I4L%Pn)8wQG8)bMu_R{{iIG8NA1s000C5s+|A; literal 0 HcmV?d00001 diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index 2a9d64d3c..68b67373b 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -47,6 +47,7 @@ ../../art/logo_256.png ../../art/logo_256_no_margin.png ../../art/sunrise.jpg + ../../art/dice_idle.tgs ../../day-blue.tdesktop-theme ../../night.tdesktop-theme ../../night-green.tdesktop-theme diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp new file mode 100644 index 000000000..1e9c6a7f6 --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -0,0 +1,96 @@ +/* +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 "chat_helpers/stickers_dice_pack.h" + +#include "main/main_session.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "storage/localimageloader.h" +#include "base/unixtime.h" +#include "apiwrap.h" + +#include +#include + +namespace Stickers { +namespace { + +constexpr auto kZeroDiceDocumentId = 0xa3b83c9f84fa9e83ULL; + +} // namespace + +DicePack::DicePack(not_null session) +: _session(session) { +} + +DicePack::~DicePack() = default; + +DocumentData *DicePack::lookup(int value) { + if (!_requestId) { + load(); + } + if (!value) { + ensureZeroGenerated(); + return _zero; + } + const auto i = _map.find(value); + return (i != end(_map)) ? i->second.get() : nullptr; +} + +void DicePack::load() { + if (_requestId) { + return; + } + _requestId = _session->api().request(MTPmessages_GetStickerSet( + MTP_inputStickerSetDice() + )).done([=](const MTPmessages_StickerSet &result) { + result.match([&](const MTPDmessages_stickerSet &data) { + applySet(data); + }); + }).fail([=](const RPCError &error) { + _requestId = 0; + }).send(); +} + +void DicePack::applySet(const MTPDmessages_stickerSet &data) { + auto index = 0; + for (const auto &sticker : data.vdocuments().v) { + const auto document = _session->data().processDocument( + sticker); + if (document->sticker()) { + _map.emplace(++index, document); + } + } +} + +void DicePack::ensureZeroGenerated() { + if (_zero) { + return; + } + + const auto path = qsl(":/gui/art/dice_idle.tgs"); + auto task = FileLoadTask( + path, + QByteArray(), + nullptr, + SendMediaType::File, + FileLoadTo(0, {}, 0), + {}); + task.process(); + const auto result = task.peekResult(); + Assert(result != nullptr); + _zero = _session->data().processDocument( + result->document, + std::move(result->thumb)); + _zero->setLocation(FileLocation(path)); + + Ensures(_zero->sticker()); + Ensures(_zero->sticker()->animated); +} + +} // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h new file mode 100644 index 000000000..59a22a11a --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h @@ -0,0 +1,37 @@ +/* +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 DocumentData; + +namespace Main { +class Session; +} // namespace Main + +namespace Stickers { + +class DicePack final { +public: + explicit DicePack(not_null session); + ~DicePack(); + + DocumentData *lookup(int value); + +private: + void load(); + void applySet(const MTPDmessages_stickerSet &data); + void ensureZeroGenerated(); + + not_null _session; + base::flat_map> _map; + DocumentData *_zero = nullptr; + mtpRequestId _requestId = 0; + +}; + +} // namespace Stickers diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index e0f6bd6f7..5e6a9d9c0 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_web_page.h" #include "history/view/media/history_view_poll.h" #include "history/view/media/history_view_theme_document.h" +#include "history/view/media/history_view_dice.h" #include "ui/image/image.h" #include "ui/image/image_source.h" #include "ui/text_options.h" @@ -1325,4 +1326,49 @@ std::unique_ptr MediaPoll::createView( return std::make_unique(message, _poll); } +MediaDice::MediaDice(not_null parent, int value) +: Media(parent) +, _value(value) { +} + +std::unique_ptr MediaDice::clone(not_null parent) { + return std::make_unique(parent, _value); +} + +int MediaDice::diceValue() const { + return _value; +} + +QString MediaDice::notificationText() const { + return QString::fromUtf8("\xF0\x9F\x8E\xB2"); +} + +QString MediaDice::pinnedTextSubstring() const { + return QChar(171) + notificationText() + QChar(187); +} + +TextForMimeData MediaDice::clipboardText() const { + return { notificationText() }; +} + +bool MediaDice::updateInlineResultMedia(const MTPMessageMedia &media) { + return updateSentMedia(media); +} + +bool MediaDice::updateSentMedia(const MTPMessageMedia &media) { + if (media.type() != mtpc_messageMediaDice) { + return false; + } + _value = media.c_messageMediaDice().vvalue().v; + return true; +} + +std::unique_ptr MediaDice::createView( + not_null message, + not_null realParent) { + return std::make_unique( + message, + std::make_unique(message, _value)); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 7803e503d..6b77e7255 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -118,7 +118,7 @@ private: }; -class MediaPhoto : public Media { +class MediaPhoto final : public Media { public: MediaPhoto( not_null parent, @@ -158,7 +158,7 @@ private: }; -class MediaFile : public Media { +class MediaFile final : public Media { public: MediaFile( not_null parent, @@ -195,7 +195,7 @@ private: }; -class MediaContact : public Media { +class MediaContact final : public Media { public: MediaContact( not_null parent, @@ -223,7 +223,7 @@ private: }; -class MediaLocation : public Media { +class MediaLocation final : public Media { public: MediaLocation( not_null parent, @@ -255,7 +255,7 @@ private: }; -class MediaCall : public Media { +class MediaCall final : public Media { public: MediaCall( not_null parent, @@ -284,7 +284,7 @@ private: }; -class MediaWebPage : public Media { +class MediaWebPage final : public Media { public: MediaWebPage( not_null parent, @@ -316,7 +316,7 @@ private: }; -class MediaGame : public Media { +class MediaGame final : public Media { public: MediaGame( not_null parent, @@ -348,7 +348,7 @@ private: }; -class MediaInvoice : public Media { +class MediaInvoice final : public Media { public: MediaInvoice( not_null parent, @@ -378,7 +378,7 @@ private: }; -class MediaPoll : public Media { +class MediaPoll final : public Media { public: MediaPoll( not_null parent, @@ -405,6 +405,28 @@ private: }; +class MediaDice final : public Media { +public: + MediaDice(not_null parent, int value); + + std::unique_ptr clone(not_null parent) override; + + int diceValue() const; + + QString notificationText() const override; + QString pinnedTextSubstring() const override; + TextForMimeData clipboardText() const override; + bool updateInlineResultMedia(const MTPMessageMedia &media) override; + bool updateSentMedia(const MTPMessageMedia &media) override; + std::unique_ptr createView( + not_null message, + not_null realParent) override; + +private: + int _value = 0; + +}; + TextForMimeData WithCaptionClipboardText( const QString &attachType, TextForMimeData &&caption); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 0503a60d5..4bc32948a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -154,7 +154,7 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { }, [](const MTPDmessageMediaPoll &) { return Result::Good; }, [](const MTPDmessageMediaDice &) { - return Result::Unsupported; // #TODO dice + return Result::Good; }, [](const MTPDmessageMediaUnsupported &) { return Result::Unsupported; }); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index d2b0fdfe1..c461982b4 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1024,8 +1024,8 @@ std::unique_ptr HistoryMessage::CreateMedia( return std::make_unique( item, item->history()->owner().processPoll(media)); - }, [](const MTPDmessageMediaDice &media) -> Result { - return nullptr; // #TODO dice + }, [&](const MTPDmessageMediaDice &media) -> Result { + return std::make_unique(item, media.vvalue().v); }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp new file mode 100644 index 000000000..0f780802d --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -0,0 +1,60 @@ +/* +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 "history/view/media/history_view_dice.h" + +#include "data/data_session.h" +#include "chat_helpers/stickers_dice_pack.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "main/main_session.h" + +namespace HistoryView { +namespace { + +DocumentData *Lookup(not_null view, int value) { + const auto &session = view->data()->history()->session(); + return session.diceStickersPack().lookup(value); +} + +} // namespace + +Dice::Dice(not_null parent, int value) +: _parent(parent) +, _start(parent, Lookup(parent, 0)) +, _value(value) { + _start.setDiceIndex(0); +} + +Dice::~Dice() = default; + +QSize Dice::size() { + return _start.size(); +} + +void Dice::draw(Painter &p, const QRect &r, bool selected) { + Expects(_end.has_value() || !_drawingEnd); + + if (_drawingEnd) { + _end->draw(p, r, selected); + } else { + _start.draw(p, r, selected); + if (!_end && _value) { + if (const auto document = Lookup(_parent, _value)) { + _end.emplace(_parent, document); + _end->setDiceIndex(_value); + _end->initSize(); + } + } + if (_end && _end->readyToDrawLottie() && _start.atTheEnd()) { + _drawingEnd = true; + } + } +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.h b/Telegram/SourceFiles/history/view/media/history_view_dice.h new file mode 100644 index 000000000..5d0d7c4a9 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.h @@ -0,0 +1,43 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/view/media/history_view_media_unwrapped.h" +#include "history/view/media/history_view_sticker.h" + +namespace HistoryView { + +class Dice final : public UnwrappedMedia::Content { +public: + Dice(not_null parent, int value); + ~Dice(); + + QSize size() override; + void draw(Painter &p, const QRect &r, bool selected) override; + + void clearStickerLoopPlayed() override { + _lottieOncePlayed = false; + } + void unloadHeavyPart() override { + _start.unloadHeavyPart(); + if (_end) { + _end->unloadHeavyPart(); + } + } + +private: + const not_null _parent; + std::optional _end; + Sticker _start; + int _value = 0; + mutable bool _lottieOncePlayed = false; + mutable bool _drawingEnd = false; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index 717a0bf78..00e2b9bce 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -57,7 +57,7 @@ bool Sticker::isEmojiSticker() const { return (_parent->data()->media() == nullptr); } -QSize Sticker::size() { +void Sticker::initSize() { _size = _document->dimensions; if (isEmojiSticker()) { constexpr auto kIdealStickerSize = 512; @@ -70,14 +70,22 @@ QSize Sticker::size() { _size = DownscaledSize( _size, { st::maxStickerSize, st::maxStickerSize }); + [[maybe_unused]] bool result = readyToDrawLottie(); } +} + +QSize Sticker::size() { + initSize(); return _size; } -void Sticker::draw(Painter &p, const QRect &r, bool selected) { +bool Sticker::readyToDrawLottie() { + if (!_lastDiceFrame.isNull()) { + return true; + } const auto sticker = _document->sticker(); if (!sticker) { - return; + return false; } _document->checkStickerLarge(); @@ -85,10 +93,14 @@ void Sticker::draw(Painter &p, const QRect &r, bool selected) { if (sticker->animated && !_lottie && loaded) { setupLottie(); } + return (_lottie && _lottie->ready()); +} - if (_lottie && _lottie->ready()) { +void Sticker::draw(Painter &p, const QRect &r, bool selected) { + if (readyToDrawLottie()) { paintLottie(p, r, selected); - } else if (!sticker->animated || !_replacements) { + } else if (_document->sticker() + && (!_document->sticker()->animated || !_replacements)) { paintPixmap(p, r, selected); } } @@ -96,24 +108,51 @@ void Sticker::draw(Painter &p, const QRect &r, bool selected) { void Sticker::paintLottie(Painter &p, const QRect &r, bool selected) { auto request = Lottie::FrameRequest(); request.box = _size * cIntRetinaFactor(); - if (selected) { + if (selected && !_nextLastDiceFrame) { request.colored = st::msgStickerOverlay->c; } - const auto frame = _lottie->frameInfo(request); - const auto size = frame.image.size() / cIntRetinaFactor(); + const auto frame = _lottie + ? _lottie->frameInfo(request) + : Lottie::Animation::FrameInfo(); + if (_nextLastDiceFrame) { + _nextLastDiceFrame = false; + _lastDiceFrame = frame.image; + } + const auto &image = _lastDiceFrame.isNull() + ? frame.image + : _lastDiceFrame; + const auto prepared = (!_lastDiceFrame.isNull() && selected) + ? Images::prepareColored(st::msgStickerOverlay->c, image) + : image; + const auto size = prepared.size() / cIntRetinaFactor(); p.drawImage( QRect( QPoint( r.x() + (r.width() - size.width()) / 2, r.y() + (r.height() - size.height()) / 2), size), - frame.image); + prepared); + if (!_lastDiceFrame.isNull()) { + return; + } const auto paused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any); - const auto playOnce = isEmojiSticker() - || !_document->session().settings().loopAnimatedStickers(); + const auto playOnce = (_diceIndex > 0) + ? true + : (_diceIndex == 0) + ? false + : (isEmojiSticker() + || !_document->session().settings().loopAnimatedStickers()); + const auto count = _lottie->information().framesCount; + _atTheEnd = (frame.index + 1 == count); + _nextLastDiceFrame = !paused + && (_diceIndex > 0) + && (frame.index + 2 == count); + const auto lastDiceFrame = (_diceIndex > 0) && _atTheEnd; + const auto switchToNext = !playOnce + || (!lastDiceFrame && (frame.index != 0 || !_lottieOncePlayed)); if (!paused - && (!playOnce || frame.index != 0 || !_lottieOncePlayed) + && switchToNext && _lottie->markFrameShown() && playOnce && !_lottieOncePlayed) { @@ -188,6 +227,10 @@ void Sticker::refreshLink() { } } +void Sticker::setDiceIndex(int index) { + _diceIndex = index; +} + void Sticker::setupLottie() { _lottie = Stickers::LottiePlayerFromDocument( _document, diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.h b/Telegram/SourceFiles/history/view/media/history_view_sticker.h index 6ee7370e4..6c9f4d0a6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.h +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.h @@ -31,6 +31,7 @@ public: const Lottie::ColorReplacements *replacements = nullptr); ~Sticker(); + void initSize(); QSize size() override; void draw(Painter &p, const QRect &r, bool selected) override; ClickHandlerPtr link() override { @@ -48,6 +49,12 @@ public: } void refreshLink() override; + void setDiceIndex(int index); + [[nodiscard]] bool atTheEnd() const { + return _atTheEnd; + } + [[nodiscard]] bool readyToDrawLottie(); + private: [[nodiscard]] bool isEmojiSticker() const; void paintLottie(Painter &p, const QRect &r, bool selected); @@ -63,7 +70,11 @@ private: std::unique_ptr _lottie; ClickHandlerPtr _link; QSize _size; + QImage _lastDiceFrame; + int _diceIndex = -1; mutable bool _lottieOncePlayed = false; + mutable bool _atTheEnd = false; + mutable bool _nextLastDiceFrame = false; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 90de3897d..303be0c59 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/changelogs.h" #include "main/main_account.h" #include "chat_helpers/stickers_emoji_pack.h" +#include "chat_helpers/stickers_dice_pack.h" #include "storage/file_download.h" #include "storage/download_manager_mtproto.h" #include "storage/file_upload.h" @@ -56,6 +57,7 @@ Session::Session( , _data(std::make_unique(this)) , _user(_data->processUser(user)) , _emojiStickersPack(std::make_unique(this)) +, _diceStickersPack(std::make_unique(this)) , _changelogs(Core::Changelogs::Create(this)) , _supportHelper(Support::Helper::Create(this)) { Core::App().passcodeLockChanges( diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 10ef88afb..552bd7630 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -46,6 +46,7 @@ class Instance; namespace Stickers { class EmojiPack; +class DicePack; } // namespace Stickers; namespace Core { @@ -89,9 +90,12 @@ public: [[nodiscard]] Storage::Facade &storage() { return *_storage; } - [[nodiscard]] Stickers::EmojiPack &emojiStickersPack() { + [[nodiscard]] Stickers::EmojiPack &emojiStickersPack() const { return *_emojiStickersPack; } + [[nodiscard]] Stickers::DicePack &diceStickersPack() const { + return *_diceStickersPack; + } [[nodiscard]] base::Observable &downloaderTaskFinished(); @@ -157,6 +161,7 @@ private: // _emojiStickersPack depends on _data. const std::unique_ptr _emojiStickersPack; + const std::unique_ptr _diceStickersPack; // _changelogs depends on _data, subscribes on chats loading event. const std::unique_ptr _changelogs; diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 53de99d95..5d497fe99 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -967,6 +967,10 @@ void FileLoadTask::finish() { } } +FileLoadResult *FileLoadTask::peekResult() const { + return _result.get(); +} + void FileLoadTask::removeFromAlbum() { if (!_album) { return; diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index 1f4ada86b..029b71637 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -292,6 +292,8 @@ public: void process(); void finish(); + FileLoadResult *peekResult() const; + private: static bool CheckForSong( const QString &filepath, From e7ca405e8c48f915a19f2a7de0a84f61fc9c93a4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Mar 2020 11:53:59 +0400 Subject: [PATCH 015/115] Show forwarded info in Dice media. --- .../history/history_inner_widget.cpp | 2 +- .../history/view/media/history_view_dice.h | 3 + .../media/history_view_media_unwrapped.cpp | 125 ++++++++++++++---- .../view/media/history_view_media_unwrapped.h | 28 +++- 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 3b195f368..67ae34198 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -3245,7 +3245,7 @@ QString HistoryInner::tooltipText() const { } } else if (_mouseCursorState == CursorState::Forwarded && _mouseAction == MouseAction::None) { - if (const auto view = App::hoveredItem()) { + if (const auto view = App::mousedItem()) { if (const auto forwarded = view->data()->Get()) { return forwarded->text.toString(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.h b/Telegram/SourceFiles/history/view/media/history_view_dice.h index 5d0d7c4a9..1afb1c08e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.h +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.h @@ -29,6 +29,9 @@ public: _end->unloadHeavyPart(); } } + bool hidesForwardedInfo() override { + return false; + } private: const not_null _parent; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 7b49cd2b1..f1b5554e4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -18,6 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_history.h" namespace HistoryView { +namespace { + +constexpr auto kMaxForwardedBarLines = 4; + +} // namespace UnwrappedMedia::Content::~Content() = default; @@ -40,11 +45,16 @@ QSize UnwrappedMedia::countOptimalSize() { const auto item = _parent->data(); const auto via = item->Get(); const auto reply = item->Get(); - maxWidth += additionalWidth(via, reply); - if (const auto surrounding = surroundingHeight(via, reply)) { + const auto forwarded = getDisplayedForwardedInfo(); + if (forwarded) { + forwarded->create(via); + } + const auto additional = additionalWidth(via, reply, forwarded); + maxWidth += additional; + if (const auto surrounding = surroundingInfo(via, reply, forwarded, additional - st::msgReplyPadding.left())) { const auto infoHeight = st::msgDateImgPadding.y() * 2 + st::msgDateFont->height; - const auto minimal = surrounding + const auto minimal = surrounding.height + st::msgDateImgDelta + infoHeight; minHeight = std::max(minHeight, minimal); @@ -60,8 +70,9 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { const auto infoWidth = _parent->infoWidth() + 2 * st::msgDateImgPadding.x(); const auto via = item->Get(); const auto reply = item->Get(); - if (via || reply) { - int usew = maxWidth() - additionalWidth(via, reply); + const auto forwarded = getDisplayedForwardedInfo(); + if (via || reply || forwarded) { + int usew = maxWidth() - additionalWidth(via, reply, forwarded); int availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left(); if (via) { via->resize(availw); @@ -99,10 +110,11 @@ void UnwrappedMedia::draw( const auto item = _parent->data(); const auto via = inWebPage ? nullptr : item->Get(); const auto reply = inWebPage ? nullptr : item->Get(); + const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { - usew -= additionalWidth(via, reply); + usew -= additionalWidth(via, reply, forwarded); if (rightAligned) { usex = width() - usew; } @@ -121,25 +133,37 @@ void UnwrappedMedia::draw( _content->draw(p, inner, selected); if (!inWebPage) { - drawSurrounding(p, inner, selected, via, reply); + drawSurrounding(p, inner, selected, via, reply, forwarded); } } -int UnwrappedMedia::surroundingHeight( +UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo( const HistoryMessageVia *via, - const HistoryMessageReply *reply) const { - if (!via && !reply) { - return 0; + const HistoryMessageReply *reply, + const HistoryMessageForwarded *forwarded, + int outerw) const { + if (!via && !reply && !forwarded) { + return {}; } - auto result = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); - if (via) { - result += st::msgServiceNameFont->height + auto height = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + const auto innerw = outerw - st::msgReplyPadding.left() - st::msgReplyPadding.right(); + auto forwardedHeightReal = forwarded + ? forwarded->text.countHeight(innerw) + : 0; + auto forwardedHeight = std::min( + forwardedHeightReal, + kMaxForwardedBarLines * st::msgServiceNameFont->height); + const auto breakEverywhere = (forwardedHeightReal > forwardedHeight); + if (forwarded) { + height += forwardedHeight; + } else if (via) { + height += st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); } if (reply) { - result += st::msgReplyBarSize.height(); + height += st::msgReplyBarSize.height(); } - return result; + return { height, forwardedHeight, breakEverywhere }; } void UnwrappedMedia::drawSurrounding( @@ -147,7 +171,8 @@ void UnwrappedMedia::drawSurrounding( const QRect &inner, bool selected, const HistoryMessageVia *via, - const HistoryMessageReply *reply) const { + const HistoryMessageReply *reply, + const HistoryMessageForwarded *forwarded) const { const auto rightAligned = _parent->hasOutLayout() && !Adaptive::ChatWide(); const auto rightAction = _parent->displayRightAction(); const auto fullRight = calculateFullRight(inner); @@ -162,8 +187,9 @@ void UnwrappedMedia::drawSurrounding( InfoDisplayType::Background); } auto replyRight = 0; - if (const auto recth = surroundingHeight(via, reply)) { - int rectw = width() - inner.width() - st::msgReplyPadding.left(); + auto rectw = width() - inner.width() - st::msgReplyPadding.left(); + if (const auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) { + auto recth = surrounding.height; int rectx = rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left()); int recty = 0; if (rtl()) rectx = width() - rectx - rectw; @@ -172,6 +198,16 @@ void UnwrappedMedia::drawSurrounding( p.setPen(st::msgServiceFg); rectx += st::msgReplyPadding.left(); rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); + if (forwarded) { + p.setTextPalette(st::serviceTextPalette); + forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxForwardedBarLines, style::al_left, 0, -1, 0, surrounding.forwardedBreakEverywhere); + p.restoreTextPalette(); + } else if (via) { + p.setFont(st::msgDateFont); + p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); + int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0); + recty += skip; + } if (via) { p.setFont(st::msgDateFont); p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text); @@ -207,10 +243,11 @@ PointState UnwrappedMedia::pointState(QPoint point) const { const auto item = _parent->data(); const auto via = inWebPage ? nullptr : item->Get(); const auto reply = inWebPage ? nullptr : item->Get(); + const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { - usew -= additionalWidth(via, reply); + usew -= additionalWidth(via, reply, forwarded); if (rightAligned) { usex = width() - usew; } @@ -246,10 +283,11 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { const auto item = _parent->data(); const auto via = inWebPage ? nullptr : item->Get(); const auto reply = inWebPage ? nullptr : item->Get(); + const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { - usew -= additionalWidth(via, reply); + usew -= additionalWidth(via, reply, forwarded); if (rightAligned) { usex = width() - usew; } @@ -268,13 +306,36 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { if (_parent->media() == this) { auto replyRight = 0; - if (auto recth = surroundingHeight(via, reply)) { - int rectw = width() - inner.width() - st::msgReplyPadding.left(); + auto rectw = width() - inner.width() - st::msgReplyPadding.left(); + if (auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) { + auto recth = surrounding.height; int rectx = rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left()); int recty = 0; if (rtl()) rectx = width() - rectx - rectw; - if (via) { + if (forwarded) { + if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + surrounding.forwardedHeight).contains(point)) { + auto textRequest = request.forText(); + if (surrounding.forwardedBreakEverywhere) { + textRequest.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere; + } + const auto innerw = rectw - st::msgReplyPadding.left() - st::msgReplyPadding.right(); + result = TextState(_parent, forwarded->text.getState( + point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()), + innerw, + textRequest)); + result.symbol = 0; + result.afterSymbol = false; + if (surrounding.forwardedBreakEverywhere) { + result.cursor = CursorState::Forwarded; + } else { + result.cursor = CursorState::None; + } + return result; + } + recty += surrounding.forwardedHeight; + recth -= surrounding.forwardedHeight; + } else if (via) { int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom()); if (QRect(rectx, recty, rectw, viah).contains(point)) { result.link = via->link; @@ -373,9 +434,14 @@ bool UnwrappedMedia::needInfoDisplay() const { && _content->alwaysShowOutTimestamp()); } -int UnwrappedMedia::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const { +int UnwrappedMedia::additionalWidth( + const HistoryMessageVia *via, + const HistoryMessageReply *reply, + const HistoryMessageForwarded *forwarded) const { auto result = st::msgReplyPadding.left() + _parent->infoWidth() + 2 * st::msgDateImgPadding.x(); - if (via) { + if (forwarded) { + accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right()); + } else if (via) { accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left()); } if (reply) { @@ -384,4 +450,11 @@ int UnwrappedMedia::additionalWidth(const HistoryMessageVia *via, const HistoryM return result; } +auto UnwrappedMedia::getDisplayedForwardedInfo() const +-> const HistoryMessageForwarded * { + return _content->hidesForwardedInfo() + ? nullptr + : _parent->data()->Get(); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index de27f2d0f..71d3cb2b0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -38,6 +38,9 @@ public: } virtual void refreshLink() { } + [[nodiscard]] virtual bool hidesForwardedInfo() { + return true; + } [[nodiscard]] virtual bool alwaysShowOutTimestamp() { return false; } @@ -73,7 +76,7 @@ public: return true; } bool hidesForwardedInfo() const override { - return true; + return _content->hidesForwardedInfo(); } void clearStickerLoopPlayed() override { _content->clearStickerLoopPlayed(); @@ -84,15 +87,27 @@ public: } private: - int surroundingHeight( + struct SurroundingInfo { + int height = 0; + int forwardedHeight = 0; + bool forwardedBreakEverywhere = false; + + explicit operator bool() const { + return (height > 0); + } + }; + [[nodiscard]] SurroundingInfo surroundingInfo( const HistoryMessageVia *via, - const HistoryMessageReply *reply) const; + const HistoryMessageReply *reply, + const HistoryMessageForwarded *forwarded, + int outerw) const; void drawSurrounding( Painter &p, const QRect &inner, bool selected, const HistoryMessageVia *via, - const HistoryMessageReply *reply) const; + const HistoryMessageReply *reply, + const HistoryMessageForwarded *forwarded) const; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; @@ -100,7 +115,8 @@ private: bool needInfoDisplay() const; int additionalWidth( const HistoryMessageVia *via, - const HistoryMessageReply *reply) const; + const HistoryMessageReply *reply, + const HistoryMessageForwarded *forwarded) const; inline int calculateFullRight(const QRect &inner) const; inline QPoint calculateFastActionPosition( @@ -108,6 +124,8 @@ private: int replyRight, int fullRight) const; + const HistoryMessageForwarded *getDisplayedForwardedInfo() const; + std::unique_ptr _content; QSize _contentSize; From 2cefccc6ebd6ee4b1466f495cc597aabfcc60ac9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Mar 2020 13:48:29 +0400 Subject: [PATCH 016/115] Start with the end Dice animation in forwarded. --- .../history/view/media/history_view_dice.cpp | 24 +++++++++++-------- .../history/view/media/history_view_dice.h | 3 +-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 0f780802d..dd23adf02 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers_dice_pack.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_components.h" #include "history/view/history_view_element.h" #include "main/main_session.h" @@ -28,7 +29,12 @@ Dice::Dice(not_null parent, int value) : _parent(parent) , _start(parent, Lookup(parent, 0)) , _value(value) { - _start.setDiceIndex(0); + _showLastFrame = _parent->data()->Has(); + if (_showLastFrame) { + _drawingEnd = true; + } else { + _start.setDiceIndex(0); + } } Dice::~Dice() = default; @@ -38,19 +44,17 @@ QSize Dice::size() { } void Dice::draw(Painter &p, const QRect &r, bool selected) { - Expects(_end.has_value() || !_drawingEnd); - + if (!_end && _value) { + if (const auto document = Lookup(_parent, _value)) { + _end.emplace(_parent, document); + _end->setDiceIndex(_value); + _end->initSize(); + } + } if (_drawingEnd) { _end->draw(p, r, selected); } else { _start.draw(p, r, selected); - if (!_end && _value) { - if (const auto document = Lookup(_parent, _value)) { - _end.emplace(_parent, document); - _end->setDiceIndex(_value); - _end->initSize(); - } - } if (_end && _end->readyToDrawLottie() && _start.atTheEnd()) { _drawingEnd = true; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.h b/Telegram/SourceFiles/history/view/media/history_view_dice.h index 1afb1c08e..e61da58b1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.h +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.h @@ -21,7 +21,6 @@ public: void draw(Painter &p, const QRect &r, bool selected) override; void clearStickerLoopPlayed() override { - _lottieOncePlayed = false; } void unloadHeavyPart() override { _start.unloadHeavyPart(); @@ -38,7 +37,7 @@ private: std::optional _end; Sticker _start; int _value = 0; - mutable bool _lottieOncePlayed = false; + mutable bool _showLastFrame = false; mutable bool _drawingEnd = false; }; From c27998649364241c3cf0535ce26924f2bb2527bc Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Mar 2020 15:37:48 +0400 Subject: [PATCH 017/115] Move filters side bar inside bodyWidget. --- Telegram/SourceFiles/mainwindow.cpp | 16 +++++++++++++++- Telegram/SourceFiles/window/main_window.cpp | 6 ------ .../SourceFiles/window/window_filters_menu.cpp | 16 ++++++++-------- .../SourceFiles/window/window_filters_menu.h | 5 ++++- .../window/window_session_controller.cpp | 8 +++++--- .../window/window_session_controller.h | 2 +- 6 files changed, 33 insertions(+), 20 deletions(-) diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 6df70cd0d..592d4dbd1 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -799,7 +799,21 @@ void MainWindow::updateControlsGeometry() { auto body = bodyWidget()->rect(); if (_passcodeLock) _passcodeLock->setGeometry(body); - if (_main) _main->setGeometry(body); + auto mainLeft = 0; + auto mainWidth = body.width(); + if (const auto session = sessionController()) { + if (const auto skip = session->filtersWidth()) { + mainLeft += skip; + mainWidth -= skip; + } + } + if (_main) { + _main->setGeometry({ + body.x() + mainLeft, + body.y(), + mainWidth, + body.height() }); + } if (_intro) _intro->setGeometry(body); if (_layer) _layer->setGeometry(body); if (_mediaPreview) _mediaPreview->setGeometry(body); diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index d78970a0e..88ffe6e48 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -522,12 +522,6 @@ void MainWindow::updateControlsGeometry() { bodyWidth -= _rightColumn->width(); _rightColumn->setGeometry(bodyWidth, bodyTop, width() - bodyWidth, height() - bodyTop); } - if (const auto session = _controller->sessionController()) { - if (const auto skip = session->filtersWidth()) { - bodyLeft += skip; - bodyWidth -= skip; - } - } _body->setGeometry(bodyLeft, bodyTop, bodyWidth, height() - bodyTop); } diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 8128cb31d..9a8105048 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -17,19 +17,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Window { -FiltersMenu::FiltersMenu(not_null session) +FiltersMenu::FiltersMenu( + not_null parent, + not_null session) : _session(session) -, _widget(session->widget(), st::defaultSideBarMenu) { +, _parent(parent) +, _widget(_parent, st::defaultSideBarMenu) { setup(); } void FiltersMenu::setup() { - const auto body = _session->widget()->bodyWidget(); - rpl::combine( - body->topValue(), - body->heightValue() - ) | rpl::start_with_next([=](int top, int height) { - _widget.setGeometry({ 0, top, st::windowFiltersWidth, height }); + _parent->heightValue( + ) | rpl::start_with_next([=](int height) { + _widget.setGeometry({ 0, 0, st::windowFiltersWidth, height }); }, _widget.lifetime()); const auto filters = &_session->session().data().chatsFilters(); diff --git a/Telegram/SourceFiles/window/window_filters_menu.h b/Telegram/SourceFiles/window/window_filters_menu.h index b4c70ba52..0555da74f 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.h +++ b/Telegram/SourceFiles/window/window_filters_menu.h @@ -15,13 +15,16 @@ class SessionController; class FiltersMenu final { public: - explicit FiltersMenu(not_null session); + FiltersMenu( + not_null parent, + not_null session); private: void setup(); void refresh(); const not_null _session; + const not_null _parent; Ui::SideBarMenu _widget; }; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 04dd32108..ad4df58b6 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -194,7 +194,9 @@ void SessionController::toggleFiltersMenu(bool enabled) { if (!enabled == !_filters) { return; } else if (enabled) { - _filters = std::make_unique(this); + _filters = std::make_unique( + widget()->bodyWidget(), + this); } else { _filters = nullptr; } @@ -346,10 +348,10 @@ bool SessionController::forceWideDialogs() const { return !App::main()->isMainSectionShown(); } -SessionController::ColumnLayout SessionController::computeColumnLayout() const { +auto SessionController::computeColumnLayout() const -> ColumnLayout { auto layout = Adaptive::WindowLayout::OneColumn; - auto bodyWidth = widget()->bodyWidget()->width(); + auto bodyWidth = widget()->bodyWidget()->width() - filtersWidth(); auto dialogsWidth = 0, chatWidth = 0, thirdWidth = 0; auto useOneColumnLayout = [&] { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 5daa506e0..8568b7b23 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -215,7 +215,7 @@ public: int thirdWidth; Adaptive::WindowLayout windowLayout; }; - ColumnLayout computeColumnLayout() const; + [[nodiscard]] ColumnLayout computeColumnLayout() const; int dialogsSmallColumnWidth() const; bool forceWideDialogs() const; void updateColumnLayout(); From f8cc134bd6cc7a4f183d2ce21ac15dd3aa7fb5ac Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Mar 2020 16:12:03 +0400 Subject: [PATCH 018/115] Send dice emoji as Dice media. --- Telegram/SourceFiles/api/api_sending.cpp | 105 ++++++++++++++++++ Telegram/SourceFiles/api/api_sending.h | 3 + Telegram/SourceFiles/apiwrap.cpp | 3 +- .../SourceFiles/data/data_media_types.cpp | 3 +- .../history/view/media/history_view_dice.cpp | 12 +- .../history/view/media/history_view_dice.h | 8 +- 6 files changed, 124 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 082826664..d0c5315ac 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -198,4 +198,109 @@ void SendExistingPhoto( Data::FileOrigin()); } +bool SendDice(Api::MessageToSend &message) { + static const auto kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2"); + if (message.textWithTags.text != kDiceString) { + return false; + } + const auto history = message.action.history; + const auto peer = history->peer; + const auto session = &history->session(); + const auto api = &session->api(); + + message.textWithTags = TextWithTags(); + message.action.clearDraft = false; + message.action.generateLocal = true; + api->sendAction(message.action); + + const auto newId = FullMsgId( + peerToChannel(peer->id), + session->data().nextLocalMessageId()); + const auto randomId = rand_value(); + + auto &histories = history->owner().histories(); + auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; + auto clientFlags = NewMessageClientFlags(); + auto sendFlags = MTPmessages_SendMedia::Flags(0); + if (message.action.replyTo) { + flags |= MTPDmessage::Flag::f_reply_to_msg_id; + sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; + } + const auto channelPost = peer->isChannel() && !peer->isMegagroup(); + const auto silentPost = message.action.options.silent + || (channelPost && session->data().notifySilentPosts(peer)); + if (channelPost) { + flags |= MTPDmessage::Flag::f_views; + flags |= MTPDmessage::Flag::f_post; + } + if (!channelPost) { + flags |= MTPDmessage::Flag::f_from_id; + } else if (peer->asChannel()->addsSignature()) { + flags |= MTPDmessage::Flag::f_post_author; + } + if (silentPost) { + sendFlags |= MTPmessages_SendMedia::Flag::f_silent; + } + auto messageFromId = channelPost ? 0 : session->userId(); + auto messagePostAuthor = channelPost ? session->user()->name : QString(); + const auto replyTo = message.action.replyTo; + + if (message.action.options.scheduled) { + flags |= MTPDmessage::Flag::f_from_scheduled; + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + } else { + clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + } + + session->data().registerMessageRandomId(randomId, newId); + + history->addNewMessage( + MTP_message( + MTP_flags(flags), + MTP_int(newId.msg), + MTP_int(messageFromId), + peerToMTP(history->peer->id), + MTPMessageFwdHeader(), + MTP_int(0), + MTP_int(replyTo), + MTP_int(HistoryItem::NewMessageDate( + message.action.options.scheduled)), + MTP_string(), + MTP_messageMediaDice(MTP_int(0)), + MTPReplyMarkup(), + MTP_vector(), + MTP_int(1), + MTPint(), + MTP_string(messagePostAuthor), + MTPlong(), + //MTPMessageReactions(), + MTPVector()), + clientFlags, + NewMessageType::Unread); + + const auto requestType = Data::Histories::RequestType::Send; + histories.sendRequest(history, requestType, [=](Fn finish) { + history->sendRequestId = api->request(MTPmessages_SendMedia( + MTP_flags(sendFlags), + peer->input, + MTP_int(replyTo), + MTP_inputMediaDice(), + MTP_string(), + MTP_long(randomId), + MTPReplyMarkup(), + MTP_vector(), + MTP_int(message.action.options.scheduled) + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result, randomId); + finish(); + }).fail([=](const RPCError &error) { + api->sendMessageFail(error, peer, randomId, newId); + finish(); + }).afterRequest(history->sendRequestId + ).send(); + return history->sendRequestId; + }); + return true; +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h index ce4c33bb7..c6232eeba 100644 --- a/Telegram/SourceFiles/api/api_sending.h +++ b/Telegram/SourceFiles/api/api_sending.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once class History; +class PhotoData; class DocumentData; namespace Api { @@ -22,4 +23,6 @@ void SendExistingPhoto( Api::MessageToSend &&message, not_null photo); +[[nodiscard]] bool SendDice(Api::MessageToSend &message); + } // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 5dbea9760..ac5ba4ede 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "apiwrap.h" +#include "api/api_sending.h" #include "api/api_text_entities.h" #include "api/api_self_destruct.h" #include "api/api_sensitive_content.h" @@ -4793,7 +4794,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { action.generateLocal = true; sendAction(action); - if (!peer->canWrite()) { + if (!peer->canWrite() || Api::SendDice(message)) { return; } Local::saveRecentSentHashtags(textWithTags.text); diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 5e6a9d9c0..9fd380bbe 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -1360,6 +1360,7 @@ bool MediaDice::updateSentMedia(const MTPMessageMedia &media) { return false; } _value = media.c_messageMediaDice().vvalue().v; + parent()->history()->owner().requestItemRepaint(parent()); return true; } @@ -1368,7 +1369,7 @@ std::unique_ptr MediaDice::createView( not_null realParent) { return std::make_unique( message, - std::make_unique(message, _value)); + std::make_unique(message, this)); } } // namespace Data diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index dd23adf02..90200d1f7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -25,10 +25,10 @@ DocumentData *Lookup(not_null view, int value) { } // namespace -Dice::Dice(not_null parent, int value) +Dice::Dice(not_null parent, not_null dice) : _parent(parent) -, _start(parent, Lookup(parent, 0)) -, _value(value) { +, _dice(dice) +, _start(parent, Lookup(parent, 0)) { _showLastFrame = _parent->data()->Has(); if (_showLastFrame) { _drawingEnd = true; @@ -44,10 +44,10 @@ QSize Dice::size() { } void Dice::draw(Painter &p, const QRect &r, bool selected) { - if (!_end && _value) { - if (const auto document = Lookup(_parent, _value)) { + if (const auto value = _end ? 0 : _dice->diceValue()) { + if (const auto document = Lookup(_parent, value)) { _end.emplace(_parent, document); - _end->setDiceIndex(_value); + _end->setDiceIndex(value); _end->initSize(); } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.h b/Telegram/SourceFiles/history/view/media/history_view_dice.h index e61da58b1..d8967412f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.h +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.h @@ -10,11 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media_unwrapped.h" #include "history/view/media/history_view_sticker.h" +namespace Data { +class MediaDice; +} // namespace Data + namespace HistoryView { class Dice final : public UnwrappedMedia::Content { public: - Dice(not_null parent, int value); + Dice(not_null parent, not_null dice); ~Dice(); QSize size() override; @@ -34,9 +38,9 @@ public: private: const not_null _parent; + const not_null _dice; std::optional _end; Sticker _start; - int _value = 0; mutable bool _showLastFrame = false; mutable bool _drawingEnd = false; From 48d790de5efff36053719609604daeed0f1fb31d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Mar 2020 16:20:50 +0400 Subject: [PATCH 019/115] Move main menu button to the side bar. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 3 ++- Telegram/SourceFiles/window/window.style | 2 ++ Telegram/SourceFiles/window/window_filters_menu.cpp | 12 +++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index d93369251..925d7ec24 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -259,6 +259,7 @@ Widget::Widget( Core::App().lockByPasscode(); _lockUnlock->setIconOverride(nullptr); }); + _mainMenuToggle->setVisible(!controller->filtersWidth()); _mainMenuToggle->setClickedCallback([this] { showMainMenu(); }); _chooseByDragTimer.setSingleShot(true); @@ -1500,7 +1501,7 @@ void Widget::updateControlsGeometry() { } auto smallLayoutWidth = (st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPadding.x()); auto smallLayoutRatio = (width() < st::columnMinimalWidthLeft) ? (st::columnMinimalWidthLeft - width()) / float64(st::columnMinimalWidthLeft - smallLayoutWidth) : 0.; - auto filterLeft = st::dialogsFilterPadding.x() + _mainMenuToggle->width() + st::dialogsFilterPadding.x(); + auto filterLeft = (controller()->filtersWidth() ? 0 : st::dialogsFilterPadding.x() + _mainMenuToggle->width()) + st::dialogsFilterPadding.x(); auto filterRight = (Global::LocalPasscode() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x(); auto filterWidth = qMax(width(), st::columnMinimalWidthLeft) - filterLeft - filterRight; auto filterAreaHeight = st::topBarHeight; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index df4300a30..8762e62a8 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -246,6 +246,8 @@ createThemeLink: InputField(defaultInputField) { } windowFiltersWidth: 72px; +windowFiltersMainMenu: icon {{ "dialogs_menu", sideBarIconFg }}; +windowFiltersMainMenuIconTop: 12px; windowFiltersIconTop: 8px; windowFiltersAll: icon {{ "filters_all", sideBarIconFg }}; windowFiltersAllActive: icon {{ "filters_all_active", sideBarIconFgActive }}; diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 9a8105048..71c77dbc8 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -48,7 +48,9 @@ void FiltersMenu::setup() { _widget.activateRequests( ) | rpl::start_with_next([=](const QString &id) { - if (id == "setup") { + if (id == "main_menu") { + _session->widget()->showMainMenu(); + } else if (id == "setup") { } else if (const auto filterId = id.toInt()) { _session->setActiveChatsFilter(filterId); } else { @@ -59,6 +61,14 @@ void FiltersMenu::setup() { void FiltersMenu::refresh() { auto items = std::vector(); + items.push_back({ + "main_menu", + QString(), + QString(), + &st::windowFiltersMainMenu, + &st::windowFiltersMainMenu, + st::windowFiltersMainMenuIconTop + }); items.push_back({ QString::number(0), "All Chats", From fcfb26867772326d249701114cb135f2cded085e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 6 Mar 2020 17:12:22 +0400 Subject: [PATCH 020/115] Show some special filter icons. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- .../window/window_filters_menu.cpp | 59 +++++++++++++++++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 925d7ec24..658bcd857 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1501,7 +1501,7 @@ void Widget::updateControlsGeometry() { } auto smallLayoutWidth = (st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPadding.x()); auto smallLayoutRatio = (width() < st::columnMinimalWidthLeft) ? (st::columnMinimalWidthLeft - width()) / float64(st::columnMinimalWidthLeft - smallLayoutWidth) : 0.; - auto filterLeft = (controller()->filtersWidth() ? 0 : st::dialogsFilterPadding.x() + _mainMenuToggle->width()) + st::dialogsFilterPadding.x(); + auto filterLeft = (controller()->filtersWidth() ? st::dialogsFilterSkip : st::dialogsFilterPadding.x() + _mainMenuToggle->width()) + st::dialogsFilterPadding.x(); auto filterRight = (Global::LocalPasscode() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x(); auto filterWidth = qMax(width(), st::columnMinimalWidthLeft) - filterLeft - filterRight; auto filterAreaHeight = st::topBarHeight; diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 71c77dbc8..63aa4ddea 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -16,6 +16,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_window.h" namespace Window { +namespace { + +enum class Type { + Unread, + Unmuted, + Custom, +}; + +[[nodiscard]] Type ComputeType(const Data::ChatFilter &filter) { + using Flag = Data::ChatFilter::Flag; + + const auto all = Flag::Contacts + | Flag::NonContacts + | Flag::Groups + | Flag::Broadcasts + | Flag::Bots + | Flag::NoArchive; + if (!filter.always().empty()) { + return Type::Custom; + } else if (filter.flags() == (all | Flag::NoRead)) { + return Type::Unread; + } else if (filter.flags() == (all | Flag::NoMuted)) { + return Type::Unmuted; + } + return Type::Custom; +} + +[[nodiscard]] std::array ComputeIcons(Type type) { + switch (type) { + case Type::Unread: + return { + &st::windowFiltersUnread, + &st::windowFiltersUnreadActive + }; + case Type::Unmuted: + return { + &st::windowFiltersUnmuted, + &st::windowFiltersUnmutedActive + }; + case Type::Custom: + return { + &st::windowFiltersCustom, + &st::windowFiltersCustomActive + }; + } + Unexpected("Filter type in FiltersMenu::refresh."); +} + +} // namespace FiltersMenu::FiltersMenu( not_null parent, @@ -73,18 +122,20 @@ void FiltersMenu::refresh() { QString::number(0), "All Chats", QString(), - &st::windowFiltersCustom, - &st::windowFiltersCustomActive, + &st::windowFiltersAll, + &st::windowFiltersAllActive, st::windowFiltersIconTop }); const auto filters = &_session->session().data().chatsFilters(); for (const auto &filter : filters->list()) { + const auto type = ComputeType(filter); + const auto icons = ComputeIcons(type); items.push_back({ QString::number(filter.id()), filter.title(), QString(), - &st::windowFiltersCustom, - &st::windowFiltersCustomActive, + icons[0], + icons[1], st::windowFiltersIconTop }); } From c305246d211c4be54eb1195eb96b7a44a92e1aae Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 9 Mar 2020 13:04:00 +0400 Subject: [PATCH 021/115] Move sidebar mainmenu button outside the scroll. --- Telegram/SourceFiles/window/window.style | 49 ++++-- .../window/window_filters_menu.cpp | 156 ++++++++++-------- .../SourceFiles/window/window_filters_menu.h | 11 +- Telegram/lib_ui | 2 +- 4 files changed, 133 insertions(+), 85 deletions(-) diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 8762e62a8..58080fa88 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -246,18 +246,43 @@ createThemeLink: InputField(defaultInputField) { } windowFiltersWidth: 72px; -windowFiltersMainMenu: icon {{ "dialogs_menu", sideBarIconFg }}; -windowFiltersMainMenuIconTop: 12px; -windowFiltersIconTop: 8px; -windowFiltersAll: icon {{ "filters_all", sideBarIconFg }}; -windowFiltersAllActive: icon {{ "filters_all_active", sideBarIconFgActive }}; -windowFiltersUnread: icon {{ "filters_unread", sideBarIconFg }}; -windowFiltersUnreadActive: icon {{ "filters_unread_active", sideBarIconFgActive }}; -windowFiltersUnmuted: icon {{ "filters_unmuted", sideBarIconFg }}; -windowFiltersUnmutedActive: icon {{ "filters_unmuted_active", sideBarIconFgActive }}; -windowFiltersCustom: icon {{ "filters_custom", sideBarIconFg }}; -windowFiltersCustomActive: icon {{ "filters_custom_active", sideBarIconFgActive }}; -windowFiltersSetup: icon {{ "filters_setup", sideBarIconFg }}; +windowFiltersButton: SideBarButton(defaultSideBarButton) { + textTop: 40px; + textSkip: 6px; + minHeight: 62px; + minTextWidth: 48px; + style: TextStyle(defaultTextStyle) { + font: font(11px semibold); + } + badgeStyle: TextStyle(defaultTextStyle) { + font: font(11px semibold); + } + iconPosition: point(-1px, 6px); +} +windowFiltersMainMenu: SideBarButton(windowFiltersButton) { + icon: icon {{ "dialogs_menu", sideBarIconFg }}; + iconPosition: point(-1px, -1px); + minHeight: 54px; +} +windowFiltersAll: SideBarButton(windowFiltersButton) { + icon: icon {{ "filters_all", sideBarIconFg }}; + iconActive: icon {{ "filters_all_active", sideBarIconFgActive }}; +} +windowFiltersUnread: SideBarButton(windowFiltersButton) { + icon: icon {{ "filters_unread", sideBarIconFg }}; + iconActive: icon {{ "filters_unread_active", sideBarIconFgActive }}; +} +windowFiltersUnmuted: SideBarButton(windowFiltersButton) { + icon: icon {{ "filters_unmuted", sideBarIconFg }}; + iconActive: icon {{ "filters_unmuted_active", sideBarIconFgActive }}; +} +windowFiltersCustom: SideBarButton(windowFiltersButton) { + icon: icon {{ "filters_custom", sideBarIconFg }}; + iconActive: icon {{ "filters_custom_active", sideBarIconFgActive }}; +} +windowFiltersSetup: SideBarButton(windowFiltersButton) { + icon: icon {{ "filters_setup", sideBarIconFg }}; +} // Mac specific diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 63aa4ddea..5a0d4b194 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -43,23 +43,11 @@ enum class Type { return Type::Custom; } -[[nodiscard]] std::array ComputeIcons(Type type) { +[[nodiscard]] const style::SideBarButton &ComputeStyle(Type type) { switch (type) { - case Type::Unread: - return { - &st::windowFiltersUnread, - &st::windowFiltersUnreadActive - }; - case Type::Unmuted: - return { - &st::windowFiltersUnmuted, - &st::windowFiltersUnmutedActive - }; - case Type::Custom: - return { - &st::windowFiltersCustom, - &st::windowFiltersCustomActive - }; + case Type::Unread: return st::windowFiltersUnread; + case Type::Unmuted: return st::windowFiltersUnmuted; + case Type::Custom: return st::windowFiltersCustom; } Unexpected("Filter type in FiltersMenu::refresh."); } @@ -71,15 +59,46 @@ FiltersMenu::FiltersMenu( not_null session) : _session(session) , _parent(parent) -, _widget(_parent, st::defaultSideBarMenu) { +, _outer(_parent) +, _menu(&_outer, QString(), st::windowFiltersMainMenu) +, _scroll(&_outer) +, _container( + _scroll.setOwnedWidget( + object_ptr(&_scroll))) { setup(); } void FiltersMenu::setup() { + _outer.setAttribute(Qt::WA_OpaquePaintEvent); + _outer.show(); + _outer.paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + const auto bottom = _scroll.y() + _container->height(); + const auto height = _outer.height() - bottom; + if (height <= 0) { + return; + } + const auto fill = clip.intersected( + QRect(0, bottom, _outer.width(), height)); + if (!fill.isEmpty()) { + auto p = QPainter(&_outer); + p.setPen(Qt::NoPen); + p.setBrush(st::windowFiltersAll.textBg); + p.drawRect(fill); + } + }, _outer.lifetime()); + _parent->heightValue( ) | rpl::start_with_next([=](int height) { - _widget.setGeometry({ 0, 0, st::windowFiltersWidth, height }); - }, _widget.lifetime()); + const auto width = st::windowFiltersWidth; + _outer.setGeometry({ 0, 0, width, height }); + _menu.resizeToWidth(width); + _menu.move(0, 0); + _scroll.setGeometry( + { 0, _menu.height(), width, height - _menu.height() }); + _container->resizeToWidth(width); + _container->move(0, 0); + }, _outer.lifetime()); const auto filters = &_session->session().data().chatsFilters(); rpl::single( @@ -88,66 +107,63 @@ void FiltersMenu::setup() { filters->changed() ) | rpl::start_with_next([=] { refresh(); - }, _widget.lifetime()); + }, _outer.lifetime()); + _activeFilterId = _session->activeChatsFilterCurrent(); _session->activeChatsFilter( - ) | rpl::start_with_next([=](FilterId id) { - _widget.setActive(QString::number(id)); - }, _widget.lifetime()); - - _widget.activateRequests( - ) | rpl::start_with_next([=](const QString &id) { - if (id == "main_menu") { - _session->widget()->showMainMenu(); - } else if (id == "setup") { - } else if (const auto filterId = id.toInt()) { - _session->setActiveChatsFilter(filterId); - } else { - _session->setActiveChatsFilter(0); + ) | rpl::filter([=](FilterId id) { + return id != _activeFilterId; + }) | rpl::start_with_next([=](FilterId id) { + const auto i = _filters.find(_activeFilterId); + if (i != end(_filters)) { + i->second->setActive(false); } - }, _widget.lifetime()); + _activeFilterId = id; + const auto j = _filters.find(_activeFilterId); + if (j != end(_filters)) { + j->second->setActive(true); + } + }, _outer.lifetime()); + + _menu.setClickedCallback([=] { + _session->widget()->showMainMenu(); + }); } void FiltersMenu::refresh() { - auto items = std::vector(); - items.push_back({ - "main_menu", - QString(), - QString(), - &st::windowFiltersMainMenu, - &st::windowFiltersMainMenu, - st::windowFiltersMainMenuIconTop - }); - items.push_back({ - QString::number(0), - "All Chats", - QString(), - &st::windowFiltersAll, - &st::windowFiltersAllActive, - st::windowFiltersIconTop - }); const auto filters = &_session->session().data().chatsFilters(); - for (const auto &filter : filters->list()) { - const auto type = ComputeType(filter); - const auto icons = ComputeIcons(type); - items.push_back({ - QString::number(filter.id()), - filter.title(), - QString(), - icons[0], - icons[1], - st::windowFiltersIconTop + auto now = base::flat_map>(); + const auto prepare = [&]( + FilterId id, + const QString &title, + const style::SideBarButton &st, + const QString &badge) { + auto button = base::unique_qptr(_container->add( + object_ptr( + _container, + title, + st))); + button->setBadge(badge); + button->setActive(_session->activeChatsFilterCurrent() == id); + button->setClickedCallback([=] { + if (id >= 0) { + _session->setActiveChatsFilter(id); + } else { + // #TODO filters + } }); + now.emplace(id, std::move(button)); + }; + prepare(0, "All chats", st::windowFiltersAll, QString()); + for (const auto filter : filters->list()) { + prepare( + filter.id(), + filter.title(), + ComputeStyle(ComputeType(filter)), + QString()); } - items.push_back({ - "setup", - "Setup", - QString(), - &st::windowFiltersSetup, - &st::windowFiltersSetup, - st::windowFiltersIconTop - }); - _widget.setItems(items); + prepare(-1, "Setup", st::windowFiltersSetup, QString()); + _filters = std::move(now); } } // namespace Window diff --git a/Telegram/SourceFiles/window/window_filters_menu.h b/Telegram/SourceFiles/window/window_filters_menu.h index 0555da74f..985fc9af2 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.h +++ b/Telegram/SourceFiles/window/window_filters_menu.h @@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/widgets/side_bar_menu.h" +#include "ui/widgets/side_bar_button.h" +#include "ui/widgets/scroll_area.h" +#include "ui/wrap/vertical_layout.h" namespace Window { @@ -25,7 +27,12 @@ private: const not_null _session; const not_null _parent; - Ui::SideBarMenu _widget; + Ui::RpWidget _outer; + Ui::SideBarButton _menu; + Ui::ScrollArea _scroll; + not_null _container; + base::flat_map> _filters; + FilterId _activeFilterId = 0; }; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 81e9a8083..1b673b7e4 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 81e9a80831555ae7908ee86fb15ea791fadd0891 +Subproject commit 1b673b7e406af46e931fd37feabaf9e277ada93b From ca3419ef2452604524a67157a3d10fd5c16a67cd Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 9 Mar 2020 15:17:56 +0400 Subject: [PATCH 022/115] Make filtered lists independent from folders. --- .../SourceFiles/data/data_chat_filters.cpp | 48 ++++++++++++++----- Telegram/SourceFiles/data/data_chat_filters.h | 8 ++++ Telegram/SourceFiles/data/data_folder.cpp | 2 +- .../data/data_scheduled_messages.cpp | 4 +- Telegram/SourceFiles/data/data_session.cpp | 2 +- .../SourceFiles/dialogs/dialogs_entry.cpp | 10 ++-- Telegram/SourceFiles/dialogs/dialogs_entry.h | 11 +++-- .../dialogs/dialogs_indexed_list.cpp | 8 ++-- .../dialogs/dialogs_indexed_list.h | 4 +- .../dialogs/dialogs_inner_widget.cpp | 6 ++- .../SourceFiles/dialogs/dialogs_layout.cpp | 2 + Telegram/SourceFiles/dialogs/dialogs_layout.h | 1 + Telegram/SourceFiles/dialogs/dialogs_list.cpp | 10 ++-- .../SourceFiles/dialogs/dialogs_main_list.cpp | 22 +++------ .../SourceFiles/dialogs/dialogs_main_list.h | 6 +-- Telegram/SourceFiles/dialogs/dialogs_row.cpp | 6 ++- Telegram/SourceFiles/dialogs/dialogs_row.h | 4 +- 17 files changed, 98 insertions(+), 56 deletions(-) diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index fff8b2224..32e1b5382 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_channel.h" #include "data/data_session.h" +#include "data/data_folder.h" +#include "dialogs/dialogs_main_list.h" #include "main/main_session.h" #include "apiwrap.h" @@ -179,6 +181,18 @@ ChatFilters::ChatFilters(not_null owner) : _owner(owner) { ChatFilter(2, "Unread", all | Flag::NoRead, {}, {})); } +ChatFilters::~ChatFilters() = default; + +not_null ChatFilters::chatsList(FilterId filterId) { + auto &pointer = _chatsLists[filterId]; + if (!pointer) { + pointer = std::make_unique( + filterId, + rpl::single(1)); + } + return pointer.get(); +} + void ChatFilters::load() { load(false); } @@ -285,21 +299,31 @@ void ChatFilters::applyRemove(int position) { bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { const auto rulesChanged = (filter.flags() != updated.flags()) - || (filter.always() != updated.always()); + || (filter.always() != updated.always()) + || (filter.never() != updated.never()); if (rulesChanged) { - const auto list = _owner->chatsList()->indexed(); - for (const auto &entry : *list) { - if (const auto history = entry->history()) { - const auto now = updated.contains(history); - const auto was = filter.contains(history); - if (now != was) { - if (now) { - history->addToChatList(filter.id()); - } else { - history->removeFromChatList(filter.id()); - } + const auto feedHistory = [&](not_null history) { + const auto now = updated.contains(history); + const auto was = filter.contains(history); + if (now != was) { + if (now) { + history->addToChatList(filter.id()); + } else { + history->removeFromChatList(filter.id()); } } + }; + const auto feedList = [&](not_null list) { + for (const auto &entry : *list->indexed()) { + if (const auto history = entry->history()) { + feedHistory(history); + } + } + }; + feedList(_owner->chatsList()); + const auto id = Data::Folder::kId; + if (const auto folder = _owner->folderLoaded(id)) { + feedList(folder->chatsList()); } } else if (filter.title() == updated.title()) { return false; diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index a933e834e..ca9735200 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; +namespace Dialogs { +class MainList; +} // namespace Dialogs + namespace Data { class Session; @@ -63,6 +67,7 @@ private: class ChatFilters final { public: explicit ChatFilters(not_null owner); + ~ChatFilters(); void load(); void apply(const MTPUpdate &update); @@ -75,6 +80,8 @@ public: [[nodiscard]] auto refreshHistoryRequests() const -> rpl::producer>; + [[nodiscard]] not_null chatsList(FilterId filterId); + private: void load(bool force); bool applyOrder(const QVector &order); @@ -85,6 +92,7 @@ private: const not_null _owner; std::vector _list; + base::flat_map> _chatsLists; rpl::event_stream<> _listChanged; rpl::event_stream> _refreshHistoryRequests; mtpRequestId _loadRequestId = 0; diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index df513684b..41d3a5282 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -57,7 +57,7 @@ rpl::producer PinnedDialogsInFolderMaxValue( Folder::Folder(not_null owner, FolderId id) : Entry(owner, this) , _id(id) -, _chatsList(PinnedDialogsInFolderMaxValue(&owner->session())) +, _chatsList(FilterId(), PinnedDialogsInFolderMaxValue(&owner->session())) , _name(tr::lng_archived_name(tr::now)) { indexNameParts(); diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index 7bbce72fa..8d6831675 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -139,8 +139,8 @@ int ScheduledMessages::count(not_null history) const { } void ScheduledMessages::sendNowSimpleMessage( - const MTPDupdateShortSentMessage &update, - not_null local) { + const MTPDupdateShortSentMessage &update, + not_null local) { Expects(local->isSending()); Expects(local->isScheduled()); Expects(local->date() == kScheduledUntilOnlineTimestamp); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 01d4b7a42..1f134b8e0 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -184,7 +184,7 @@ Session::Session(not_null session) , _bigFileCache(Core::App().databases().get( Local::cacheBigFilePath(), Local::cacheBigFileSettings())) -, _chatsList(PinnedDialogsCountMaxValue(session)) +, _chatsList(FilterId(), PinnedDialogsCountMaxValue(session)) , _contactsList(Dialogs::SortMode::Name) , _contactsNoChatsList(Dialogs::SortMode::Name) , _selfDestructTimer([=] { checkSelfDestructItems(); }) diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 4db2f9ff2..05cb676ab 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_indexed_list.h" #include "data/data_session.h" #include "data/data_folder.h" +#include "data/data_chat_filters.h" #include "mainwidget.h" #include "main/main_session.h" #include "history/history_item.h" @@ -92,16 +93,17 @@ void Entry::updateChatListSortPosition() { updateChatListEntry(); return; } + _sortKeyByDate = DialogPosFromDate(adjustedChatListTimeId()); const auto fixedIndex = fixedOnTopIndex(); _sortKeyInChatList = fixedIndex ? FixedOnTopDialogPos(fixedIndex) : isPinnedDialog() ? PinnedDialogPos(_pinnedIndex) - : DialogPosFromDate(adjustedChatListTimeId()); + : _sortKeyByDate; if (needUpdateInChatList()) { setChatListExistence(true); } else { - _sortKeyInChatList = 0; + _sortKeyInChatList = _sortKeyByDate = 0; } } @@ -229,7 +231,9 @@ void Entry::updateChatListEntry() const { } not_null Entry::myChatsList(FilterId filterId) const { - return owner().chatsList(folder())->indexed(filterId); + return filterId + ? owner().chatsFilters().chatsList(filterId)->indexed() + : owner().chatsList(folder())->indexed(); } } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index 0f8945693..05be48d33 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -31,9 +31,10 @@ struct RowsByLetter { }; enum class SortMode { - Date = 0x00, - Name = 0x01, - Add = 0x02, + Complex = 0x00, + Date = 0x01, + Name = 0x02, + Add = 0x04, }; struct PositionChange { @@ -120,6 +121,9 @@ public: uint64 sortKeyInChatList() const { return _sortKeyInChatList; } + uint64 sortKeyByDate() const { + return _sortKeyByDate; + } void updateChatListSortPosition(); void setChatListTimeId(TimeId date); virtual void updateChatListExistence(); @@ -199,6 +203,7 @@ private: Dialogs::Key _key; base::flat_map _chatListLinks; uint64 _sortKeyInChatList = 0; + uint64 _sortKeyByDate = 0; int _pinnedIndex = 0; bool _isProxyPromoted = false; TimeId _timeId = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp index 2ee60cebb..5af4ca39c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -87,7 +87,7 @@ void IndexedList::movePinned(Row *row, int deltaSign) { void IndexedList::peerNameChanged( not_null peer, const base::flat_set &oldLetters) { - Expects(_sortMode != SortMode::Date); + Expects(_sortMode != SortMode::Date && _sortMode != SortMode::Complex); if (const auto history = peer->owner().historyLoaded(peer)) { if (_sortMode == SortMode::Name) { @@ -102,7 +102,7 @@ void IndexedList::peerNameChanged( FilterId filterId, not_null peer, const base::flat_set &oldLetters) { - Expects(_sortMode == SortMode::Date); + Expects(_sortMode == SortMode::Date || _sortMode == SortMode::Complex); if (const auto history = peer->owner().historyLoaded(peer)) { adjustNames(filterId, history, oldLetters); @@ -165,7 +165,7 @@ void IndexedList::adjustNames( } } for (auto ch : toRemove) { - if (_sortMode == SortMode::Date) { + if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) { history->removeChatListEntryByLetter(filterId, ch); } if (auto it = _index.find(ch); it != _index.cend()) { @@ -178,7 +178,7 @@ void IndexedList::adjustNames( j = _index.emplace(ch, _sortMode).first; } auto row = j->second.addToEnd(key); - if (_sortMode == SortMode::Date) { + if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) { history->addChatListEntryByLetter(filterId, ch, row); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h index 65bb30d89..e2ae1346f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h @@ -26,12 +26,12 @@ public: // row must belong to this indexed list all(). void movePinned(Row *row, int deltaSign); - // For sortMode != SortMode::Date + // For sortMode != SortMode::Date && != Complex void peerNameChanged( not_null peer, const base::flat_set &oldChars); - //For sortMode == SortMode::Date + //For sortMode == SortMode::Date || == Complex void peerNameChanged( FilterId filterId, not_null peer, diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f5e592af4..ee702a6f3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -440,6 +440,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { Layout::RowPainter::paint( p, row, + _filterId, fullWidth, isActive, isSelected, @@ -565,6 +566,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { Layout::RowPainter::paint( p, _filterResults[from], + _filterId, fullWidth, active, selected, @@ -1680,7 +1682,9 @@ void InnerWidget::updateSelectedRow(Key key) { } not_null InnerWidget::shownDialogs() const { - return session().data().chatsList(_openedFolder)->indexed(_filterId); + return _filterId + ? session().data().chatsFilters().chatsList(_filterId)->indexed() + : session().data().chatsList(_openedFolder)->indexed(); } void InnerWidget::leaveEventHook(QEvent *e) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index 4af73bf69..879cf8e3c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -613,6 +613,7 @@ void paintUnreadCount( void RowPainter::paint( Painter &p, not_null row, + int filterId, int fullWidth, bool active, bool selected, @@ -668,6 +669,7 @@ void RowPainter::paint( const auto displayPinnedIcon = !displayUnreadCounter && !displayMentionBadge && !displayUnreadMark + && !filterId && entry->isPinnedDialog() && !entry->fixedOnTopIndex(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h index c8b03e792..6745569de 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -29,6 +29,7 @@ public: static void paint( Painter &p, not_null row, + int filterId, int fullWidth, bool active, bool selected, diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp index 37302ace8..7d3c196b4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp @@ -32,7 +32,7 @@ not_null List::addToEnd(Key key) { std::make_unique(key, _rows.size()) ).first->second.get(); _rows.emplace_back(result); - if (_sortMode == SortMode::Date) { + if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) { adjustByDate(result); } return result; @@ -82,20 +82,20 @@ void List::adjustByName(not_null row) { } void List::adjustByDate(not_null row) { - Expects(_sortMode == SortMode::Date); + Expects(_sortMode == SortMode::Date || _sortMode == SortMode::Complex); - const auto key = row->sortKey(); + const auto key = row->sortKey(_sortMode); const auto index = row->pos(); const auto i = _rows.begin() + index; const auto before = std::find_if(i + 1, _rows.end(), [&](Row *row) { - return (row->sortKey() <= key); + return (row->sortKey(_sortMode) <= key); }); if (before != i + 1) { rotate(i, i + 1, before); } else { const auto from = std::make_reverse_iterator(i); const auto after = std::find_if(from, _rows.rend(), [&](Row *row) { - return (row->sortKey() >= key); + return (row->sortKey(_sortMode) >= key); }).base(); if (after != i) { rotate(after, i, i + 1); diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index 26081a0d7..c6f01db27 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -12,8 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Dialogs { -MainList::MainList(rpl::producer pinnedLimit) -: _all(SortMode::Date) +MainList::MainList(FilterId filterId, rpl::producer pinnedLimit) +: _filterId(filterId) +, _all(filterId ? SortMode::Date : SortMode::Complex) , _pinned(1) { _unreadState.known = true; @@ -28,10 +29,7 @@ MainList::MainList(rpl::producer pinnedLimit) ) | rpl::start_with_next([=](const Notify::PeerUpdate &update) { const auto peer = update.peer; const auto &oldLetters = update.oldNameFirstLetters; - _all.peerNameChanged(FilterId(), peer, oldLetters); - for (auto &[filterId, list] : _other) { - list.peerNameChanged(filterId, peer, oldLetters); - } + _all.peerNameChanged(_filterId, peer, oldLetters); }, _lifetime); } @@ -49,7 +47,6 @@ void MainList::setLoaded(bool loaded) { void MainList::clear() { _all.clear(); - _other.clear(); _unreadState = UnreadState(); } @@ -73,15 +70,8 @@ UnreadState MainList::unreadState() const { return _unreadState; } -not_null MainList::indexed(FilterId filterId) { - if (!filterId) { - return &_all; - } - const auto i = _other.find(filterId); - if (i != end(_other)) { - return &i->second; - } - return &_other.emplace(filterId, SortMode::Date).first->second; +not_null MainList::indexed() { + return &_all; } not_null MainList::indexed() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.h b/Telegram/SourceFiles/dialogs/dialogs_main_list.h index 4319b836a..5bc69ba0e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.h @@ -14,7 +14,7 @@ namespace Dialogs { class MainList final { public: - explicit MainList(rpl::producer pinnedLimit); + MainList(FilterId filterId, rpl::producer pinnedLimit); bool empty() const; bool loaded() const; @@ -29,14 +29,14 @@ public: bool added); [[nodiscard]] UnreadState unreadState() const; - not_null indexed(FilterId filterId = 0); + not_null indexed(); not_null indexed() const; not_null pinned(); not_null pinned() const; private: + FilterId _filterId = 0; IndexedList _all; - base::flat_map _other; PinnedList _pinned; UnreadState _unreadState; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 7ce16d30f..1953ad5db 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -220,8 +220,10 @@ Row::Row(Key key, int pos) : _id(key), _pos(pos) { } } -uint64 Row::sortKey() const { - return _id.entry()->sortKeyInChatList(); +uint64 Row::sortKey(SortMode mode) const { + return (mode == SortMode::Complex) + ? _id.entry()->sortKeyInChatList() + : _id.entry()->sortKeyByDate(); } void Row::validateListEntryCache() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index e8caf1d63..8fc58f74a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -23,6 +23,8 @@ namespace Layout { class RowPainter; } // namespace Layout +enum class SortMode; + class BasicRow { public: BasicRow(); @@ -88,7 +90,7 @@ public: int pos() const { return _pos; } - uint64 sortKey() const; + uint64 sortKey(SortMode mode) const; void validateListEntryCache() const; const Ui::Text::String &listEntryCache() const { From b8c11f3d8c4ea308836e2271343319363690feca Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Mar 2020 16:41:12 +0400 Subject: [PATCH 023/115] Manage filters: delete, add suggested. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 33 ++ .../SourceFiles/boxes/manage_filters_box.cpp | 465 ++++++++++++++++++ .../SourceFiles/boxes/manage_filters_box.h | 49 ++ .../chat_helpers/chat_helpers.style | 4 + .../SourceFiles/data/data_chat_filters.cpp | 23 +- .../window/window_filters_menu.cpp | 20 +- Telegram/lib_ui | 2 +- 8 files changed, 580 insertions(+), 18 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/manage_filters_box.cpp create mode 100644 Telegram/SourceFiles/boxes/manage_filters_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 06d89a81a..6cee0790b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -204,6 +204,8 @@ PRIVATE boxes/language_box.h boxes/local_storage_box.cpp boxes/local_storage_box.h + boxes/manage_filters_box.cpp + boxes/manage_filters_box.h boxes/mute_settings_box.cpp boxes/mute_settings_box.h boxes/peer_list_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 849fbcb02..f3f815b62 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2228,6 +2228,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}."; "lng_outdated_now" = "So that Telegram Desktop can update to newer versions."; +"lng_filters_all" = "All chats"; +"lng_filters_setup" = "Setup"; +"lng_filters_title" = "Folders"; +"lng_filters_subtitle" = "My folders"; +"lng_filters_no_chats" = "No chats"; +"lng_filters_chats_count#one" = "{count} chat"; +"lng_filters_chats_count#other" = "{count} chats"; +"lng_filters_create" = "Add a custom folder"; +"lng_filters_about" = "Create folders for different groups of chats and quickly switch between them."; +"lng_filters_recommended" = "Recommended"; +"lng_filters_recommended_add" = "Add"; +"lng_filters_restore" = "Undo"; +"lng_filters_new" = "New folder"; +"lng_filters_new_name" = "Folder name"; +"lng_filters_add_chats" = "Add chats"; +"lng_filters_include" = "Include"; +"lng_filters_include_about" = "Choose chats and types of chats that will appear in this folder."; +"lng_filters_exclude" = "Exclude"; +"lng_filters_exclude_about" = "Choose chats and types of chats that will never appear in this folder."; +"lng_filters_add_title" = "Add Chats"; +"lng_filters_edit_types" = "Chat types"; +"lng_filters_edit_chats" = "Chats"; +"lng_filters_include_contacts" = "Contacts"; +"lng_filters_include_non_contacts" = "Non-Contacts"; +"lng_filters_include_groups" = "Groups"; +"lng_filters_include_channels" = "Channels"; +"lng_filters_include_bots" = "Bots"; +"lng_filters_exclude_muted" = "Muted"; +"lng_filters_exclude_read" = "Read"; +"lng_filters_exclude_archived" = "Archived"; +"lng_filters_add" = "Done"; +"lng_filters_limit" = "Sorry, you have reached folders limit."; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/manage_filters_box.cpp new file mode 100644 index 000000000..4373e171d --- /dev/null +++ b/Telegram/SourceFiles/boxes/manage_filters_box.cpp @@ -0,0 +1,465 @@ +/* +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/manage_filters_box.h" + +#include "data/data_session.h" +#include "data/data_chat_filters.h" +#include "data/data_folder.h" +#include "main/main_session.h" +#include "window/window_session_controller.h" +#include "window/window_controller.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/text/text_utilities.h" +#include "settings/settings_common.h" +#include "lang/lang_keys.h" +#include "apiwrap.h" +#include "styles/style_settings.h" +#include "styles/style_layers.h" +#include "styles/style_boxes.h" +#include "styles/style_chat_helpers.h" + +namespace { + +constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000); +constexpr auto kFiltersLimit = 10; + +class FilterRowButton final : public Ui::RippleButton { +public: + FilterRowButton( + not_null parent, + not_null session, + const Data::ChatFilter &filter); + FilterRowButton( + not_null parent, + const Data::ChatFilter &filter, + const QString &description); + + void setRemoved(bool removed); + + [[nodiscard]] rpl::producer<> removeRequests() const; + [[nodiscard]] rpl::producer<> restoreRequests() const; + [[nodiscard]] rpl::producer<> addRequests() const; + +private: + enum class State { + Suggested, + Removed, + Normal, + }; + + FilterRowButton( + not_null parent, + const Data::ChatFilter &filter, + const QString &description, + State state); + + void paintEvent(QPaintEvent *e) override; + + void setup(const Data::ChatFilter &filter, const QString &status); + void setState(State state, bool force = false); + void updateButtonsVisibility(); + + Ui::IconButton _remove; + Ui::RoundButton _restore; + Ui::RoundButton _add; + + Ui::Text::String _title; + QString _status; + + State _state = State::Normal; + +}; + +[[nodiscard]] int CountFilterChats( + not_null session, + const Data::ChatFilter &filter) { + auto result = 0; + const auto addList = [&](not_null list) { + for (const auto &entry : list->indexed()->all()) { + if (const auto history = entry->history()) { + if (filter.contains(history)) { + ++result; + } + } + } + }; + addList(session->data().chatsList()); + const auto folderId = Data::Folder::kId; + if (const auto folder = session->data().folderLoaded(folderId)) { + addList(folder->chatsList()); + } + return result; +} + +[[nodiscard]] int ComputeCount( + not_null session, + const Data::ChatFilter &filter) { + const auto &list = session->data().chatsFilters().list(); + const auto id = filter.id(); + if (ranges::contains(list, id, &Data::ChatFilter::id)) { + const auto chats = session->data().chatsFilters().chatsList(id); + return chats->indexed()->size(); + } + return CountFilterChats(session, filter); +} + +[[nodiscard]] QString ComputeCountString( + not_null session, + const Data::ChatFilter &filter) { + const auto count = ComputeCount(session, filter); + return count + ? tr::lng_filters_chats_count(tr::now, lt_count_short, count) + : tr::lng_filters_no_chats(tr::now); +} + +FilterRowButton::FilterRowButton( + not_null parent, + not_null session, + const Data::ChatFilter &filter) +: FilterRowButton( + parent, + filter, + ComputeCountString(session, filter), + State::Normal) { +} + +FilterRowButton::FilterRowButton( + not_null parent, + const Data::ChatFilter &filter, + const QString &description) +: FilterRowButton(parent, filter, description, State::Suggested) { +} + +FilterRowButton::FilterRowButton( + not_null parent, + const Data::ChatFilter &filter, + const QString &status, + State state) +: RippleButton(parent, st::defaultRippleAnimation) +, _remove(this, st::filtersRemove) +, _restore(this, tr::lng_filters_restore(), st::stickersUndoRemove) +, _add(this, tr::lng_filters_recommended_add(), st::stickersTrendingAdd) +, _state(state) { + setup(filter, status); +} + +void FilterRowButton::setRemoved(bool removed) { + setState(removed ? State::Removed : State::Normal); +} + +void FilterRowButton::setState(State state, bool force) { + if (!force && _state == state) { + return; + } + _state = state; + setPointerCursor(_state == State::Normal); + setDisabled(_state != State::Normal); + updateButtonsVisibility(); + update(); +} + +void FilterRowButton::setup( + const Data::ChatFilter &filter, + const QString &status) { + resize(width(), st::defaultPeerListItem.height); + + _title.setText(st::contactsNameStyle, filter.title()); + _status = status; + + setState(_state, true); + + sizeValue( + ) | rpl::start_with_next([=](QSize size) { + const auto right = st::contactsPadding.right() + + st::contactsCheckPosition.x(); + const auto width = size.width(); + const auto height = size.height(); + _restore.moveToRight(right, (height - _restore.height()) / 2, width); + _add.moveToRight(right, (height - _add.height()) / 2, width); + const auto skipped = right - st::stickersRemoveSkip; + _remove.moveToRight(skipped, (height - _remove.height()) / 2, width); + }, lifetime()); +} + +void FilterRowButton::updateButtonsVisibility() { + _remove.setVisible(_state == State::Normal); + _restore.setVisible(_state == State::Removed); + _add.setVisible(_state == State::Suggested); +} + +rpl::producer<> FilterRowButton::removeRequests() const { + return _remove.clicks() | rpl::map([] { return rpl::empty_value(); }); +} + +rpl::producer<> FilterRowButton::restoreRequests() const { + return _restore.clicks() | rpl::map([] { return rpl::empty_value(); }); +} + +rpl::producer<> FilterRowButton::addRequests() const { + return _add.clicks() | rpl::map([] { return rpl::empty_value(); }); +} + +void FilterRowButton::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + + if (_state == State::Normal) { + if (isOver() || isDown()) { + p.fillRect(e->rect(), st::windowBgOver); + } + RippleButton::paintRipple(p, 0, 0); + } else if (_state == State::Removed) { + p.setOpacity(st::stickersRowDisabledOpacity); + } + + const auto left = st::settingsSubsectionTitlePadding.left(); + const auto buttonsLeft = std::min( + _add.x(), + std::min(_remove.x(), _restore.x())); + const auto availableWidth = buttonsLeft - left; + + p.setPen(st::contactsNameFg); + _title.drawLeftElided( + p, + left, + st::contactsPadding.top() + st::contactsNameTop, + availableWidth, + width()); + + p.setFont(st::contactsStatusFont); + p.setPen(st::contactsStatusFg); + p.drawTextLeft( + left, + st::contactsPadding.top() + st::contactsStatusTop, + width(), + _status); +} + +} // namespace + +ManageFiltersPrepare::ManageFiltersPrepare( + not_null window) +: _window(window) +, _api(&_window->session().api()) { +} + +ManageFiltersPrepare::~ManageFiltersPrepare() { + if (_requestId) { + _api->request(_requestId).cancel(); + } +} + +void ManageFiltersPrepare::showBox() { + if (_requestId) { + return; + } + if (_suggestedLastReceived > 0 + && crl::now() - _suggestedLastReceived < kRefreshSuggestedTimeout) { + showBoxWithSuggested(); + return; + } + _requestId = _api->request(MTPmessages_GetSuggestedDialogFilters( + )).done([=](const MTPVector &data) { + _requestId = 0; + _suggestedLastReceived = crl::now(); + + const auto owner = &_api->session().data(); + _suggested = ranges::view::all( + data.v + ) | ranges::view::transform([&](const MTPDialogFilterSuggested &f) { + return f.match([&](const MTPDdialogFilterSuggested &data) { + return Suggested{ + Data::ChatFilter::FromTL(data.vfilter(), owner), + qs(data.vdescription()) + }; + }); + }) | ranges::to_vector; + + showBoxWithSuggested(); + }).fail([=](const RPCError &error) { + _requestId = 0; + _suggestedLastReceived = crl::now() + kRefreshSuggestedTimeout / 2; + + showBoxWithSuggested(); + }).send(); +} + +void ManageFiltersPrepare::showBoxWithSuggested() { + _window->window().show(Box(CreateBox, _window, _suggested)); +} + +void ManageFiltersPrepare::CreateBox( + not_null box, + not_null window, + const std::vector &suggested) { + struct FilterRow { + not_null button; + Data::ChatFilter filter; + bool removed = false; + bool added = false; + }; + + box->setTitle(tr::lng_filters_title()); + + const auto session = &window->session(); + const auto content = box->verticalLayout(); + Settings::AddSubsectionTitle(content, tr::lng_filters_subtitle()); + + const auto rows = box->lifetime().make_state>(); + const auto find = [=](not_null button) { + const auto i = ranges::find(*rows, button, &FilterRow::button); + Assert(i != end(*rows)); + return &*i; + }; + const auto countNonRemoved = [=] { + const auto removed = ranges::count_if(*rows, &FilterRow::removed); + return rows->size() - removed; + }; + const auto wrap = content->add(object_ptr(content)); + const auto addFilter = [=](const Data::ChatFilter &filter) { + const auto button = wrap->add( + object_ptr(wrap, session, filter)); + button->removeRequests( + ) | rpl::start_with_next([=] { + button->setRemoved(true); + find(button)->removed = true; + }, button->lifetime()); + button->restoreRequests( + ) | rpl::start_with_next([=] { + if (countNonRemoved() < kFiltersLimit) { + button->setRemoved(false); + find(button)->removed = false; + } + }, button->lifetime()); + rows->push_back({ button, filter }); + }; + const auto &list = session->data().chatsFilters().list(); + for (const auto &filter : list) { + addFilter(filter); + } + + Settings::AddButton( + content, + tr::lng_filters_create() | Ui::Text::ToUpper(), + st::settingsUpdate); + Settings::AddSkip(content); + if (suggested.empty()) { + content->add( + object_ptr( + content, + tr::lng_filters_about(), + st::boxDividerLabel), + st::settingsDividerLabelPadding); + } else { + Settings::AddDividerText(content, tr::lng_filters_about()); + Settings::AddSkip(content); + Settings::AddSubsectionTitle(content, tr::lng_filters_recommended()); + + for (const auto &suggestion : suggested) { + const auto filter = suggestion.filter; + const auto already = [&] { + for (const auto &entry : list) { + if (entry.flags() == filter.flags() + && entry.always() == filter.always() + && entry.never() == filter.never()) { + return true; + } + } + return false; + }(); + if (already) { + continue; + } + const auto button = content->add(object_ptr( + content, + filter, + suggestion.description)); + button->addRequests( + ) | rpl::start_with_next([=] { + addFilter(filter); + delete button; + }, button->lifetime()); + } + } + + const auto prepareGoodIdsForNewFilters = [=] { + const auto &list = session->data().chatsFilters().list(); + + auto localId = 2; + const auto chooseNextId = [&] { + while (ranges::contains(list, localId, &Data::ChatFilter::id)) { + ++localId; + } + return localId; + }; + auto result = base::flat_map(); + for (auto &row : *rows) { + const auto id = row.filter.id(); + if (row.removed) { + continue; + } else if (!ranges::contains(list, id, &Data::ChatFilter::id)) { + result.emplace(row.filter.id(), chooseNextId()); + } + } + return result; + }; + + const auto save = [=] { + auto ids = prepareGoodIdsForNewFilters(); + + auto requests = std::deque(); + auto &realFilters = session->data().chatsFilters(); + const auto &list = realFilters.list(); + auto order = QVector(); + for (const auto &row : *rows) { + const auto id = row.filter.id(); + const auto removed = row.removed; + if (removed + && !ranges::contains(list, id, &Data::ChatFilter::id)) { + continue; + } + const auto newId = ids.take(id).value_or(id); + const auto tl = removed ? MTPDialogFilter() : row.filter.tl(); + const auto request = MTPmessages_UpdateDialogFilter( + MTP_flags(removed + ? MTPmessages_UpdateDialogFilter::Flag(0) + : MTPmessages_UpdateDialogFilter::Flag::f_filter), + MTP_int(newId), + tl); + if (removed) { + requests.push_front(request); + } else { + requests.push_back(request); + order.push_back(MTP_int(newId)); + } + realFilters.apply(MTP_updateDialogFilter( + MTP_flags(removed + ? MTPDupdateDialogFilter::Flag(0) + : MTPDupdateDialogFilter::Flag::f_filter), + MTP_int(newId), + tl)); + } + auto previousId = mtpRequestId(0); + for (auto &request : requests) { + previousId = session->api().request( + std::move(request) + ).afterRequest(previousId).send(); + } + if (!order.isEmpty()) { + realFilters.apply( + MTP_updateDialogFilterOrder(MTP_vector(order))); + session->api().request(MTPmessages_UpdateDialogFiltersOrder( + MTP_vector(order) + )).afterRequest(previousId).send(); + } + box->closeBox(); + }; + box->addButton(tr::lng_settings_save(), save); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.h b/Telegram/SourceFiles/boxes/manage_filters_box.h new file mode 100644 index 000000000..1b4bd94e5 --- /dev/null +++ b/Telegram/SourceFiles/boxes/manage_filters_box.h @@ -0,0 +1,49 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "data/data_chat_filters.h" + +class ApiWrap; + +namespace Ui { +class GenericBox; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +class ManageFiltersPrepare final { +public: + explicit ManageFiltersPrepare( + not_null window); + ~ManageFiltersPrepare(); + + void showBox(); + +private: + struct Suggested { + Data::ChatFilter filter; + QString description; + }; + + void showBoxWithSuggested(); + static void CreateBox( + not_null box, + not_null window, + const std::vector &suggested); + + const not_null _window; + const not_null _api; + + mtpRequestId _requestId = 0; + std::vector _suggested; + crl::time _suggestedLastReceived = 0; + +}; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 7aa5580d9..c21ae2aff 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -95,6 +95,10 @@ stickersSearch: icon {{ "stickers_search", emojiIconFg, point(0px, 1px) }}; stickersSettingsUnreadSize: 17px; stickersSettingsUnreadPosition: point(4px, 5px); +filtersRemove: IconButton(stickersRemove) { + ripple: defaultRippleAnimation; +} + emojiPanMargins: margins(10px, 10px, 10px, 10px); emojiTabs: SettingsSlider(defaultTabsSlider) { diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 32e1b5382..7dfe88416 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -168,17 +168,18 @@ bool ChatFilter::contains(not_null history) const { } ChatFilters::ChatFilters(not_null owner) : _owner(owner) { - using Flag = ChatFilter::Flag; - const auto all = Flag::Contacts - | Flag::NonContacts - | Flag::Groups - | Flag::Broadcasts - | Flag::Bots - | Flag::NoArchive; - _list.push_back( - ChatFilter(1, "Unmuted", all | Flag::NoMuted, {}, {})); - _list.push_back( - ChatFilter(2, "Unread", all | Flag::NoRead, {}, {})); + //using Flag = ChatFilter::Flag; + //const auto all = Flag::Contacts + // | Flag::NonContacts + // | Flag::Groups + // | Flag::Broadcasts + // | Flag::Bots + // | Flag::NoArchive; + //_list.push_back( + // ChatFilter(1, "Unmuted", all | Flag::NoMuted, {}, {})); + //_list.push_back( + // ChatFilter(2, "Unread", all | Flag::NoRead, {}, {})); + load(); } ChatFilters::~ChatFilters() = default; diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 5a0d4b194..31a5abea6 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -9,9 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "window/window_session_controller.h" +#include "window/window_controller.h" #include "main/main_session.h" #include "data/data_session.h" #include "data/data_chat_filters.h" +#include "boxes/manage_filters_box.h" +#include "lang/lang_keys.h" #include "styles/style_widgets.h" #include "styles/style_window.h" @@ -31,9 +34,9 @@ enum class Type { | Flag::NonContacts | Flag::Groups | Flag::Broadcasts - | Flag::Bots - | Flag::NoArchive; - if (!filter.always().empty()) { + | Flag::Bots; + const auto allNoArchive = all | Flag::NoArchive; + if (!filter.always().empty() || !filter.never().empty()) { return Type::Custom; } else if (filter.flags() == (all | Flag::NoRead)) { return Type::Unread; @@ -132,6 +135,11 @@ void FiltersMenu::setup() { void FiltersMenu::refresh() { const auto filters = &_session->session().data().chatsFilters(); + if (filters->list().empty()) { + return; + } + const auto manage = _outer.lifetime().make_state( + _session); auto now = base::flat_map>(); const auto prepare = [&]( FilterId id, @@ -149,12 +157,12 @@ void FiltersMenu::refresh() { if (id >= 0) { _session->setActiveChatsFilter(id); } else { - // #TODO filters + manage->showBox(); } }); now.emplace(id, std::move(button)); }; - prepare(0, "All chats", st::windowFiltersAll, QString()); + prepare(0, tr::lng_filters_all(tr::now), st::windowFiltersAll, {}); for (const auto filter : filters->list()) { prepare( filter.id(), @@ -162,7 +170,7 @@ void FiltersMenu::refresh() { ComputeStyle(ComputeType(filter)), QString()); } - prepare(-1, "Setup", st::windowFiltersSetup, QString()); + prepare(-1, tr::lng_filters_setup(tr::now), st::windowFiltersSetup, {}); _filters = std::move(now); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 1b673b7e4..c96119dcd 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 1b673b7e406af46e931fd37feabaf9e277ada93b +Subproject commit c96119dcd18bff5974348524b1e15b3d396426dc From 2fb2fa966101d287656a486b3a96f514665d94b8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Mar 2020 16:51:23 +0400 Subject: [PATCH 024/115] Handle last suggestion being added. --- .../SourceFiles/boxes/manage_filters_box.cpp | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/manage_filters_box.cpp index 4373e171d..b0726e85e 100644 --- a/Telegram/SourceFiles/boxes/manage_filters_box.cpp +++ b/Telegram/SourceFiles/boxes/manage_filters_box.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/text/text_utilities.h" +#include "ui/wrap/slide_wrap.h" #include "settings/settings_common.h" #include "lang/lang_keys.h" #include "apiwrap.h" @@ -297,7 +298,7 @@ void ManageFiltersPrepare::showBoxWithSuggested() { void ManageFiltersPrepare::CreateBox( not_null box, not_null window, - const std::vector &suggested) { + const std::vector &suggestions) { struct FilterRow { not_null button; Data::ChatFilter filter; @@ -349,45 +350,58 @@ void ManageFiltersPrepare::CreateBox( tr::lng_filters_create() | Ui::Text::ToUpper(), st::settingsUpdate); Settings::AddSkip(content); - if (suggested.empty()) { - content->add( + const auto emptyAbout = content->add( + object_ptr>( + content, object_ptr( content, tr::lng_filters_about(), st::boxDividerLabel), - st::settingsDividerLabelPadding); - } else { - Settings::AddDividerText(content, tr::lng_filters_about()); - Settings::AddSkip(content); - Settings::AddSubsectionTitle(content, tr::lng_filters_recommended()); + st::settingsDividerLabelPadding) + )->setDuration(0); + const auto nonEmptyAbout = content->add( + object_ptr>( + content, + object_ptr(content)) + )->setDuration(0); + const auto aboutRows = nonEmptyAbout->entity(); + Settings::AddDividerText(aboutRows, tr::lng_filters_about()); + Settings::AddSkip(aboutRows); + Settings::AddSubsectionTitle(aboutRows, tr::lng_filters_recommended()); - for (const auto &suggestion : suggested) { - const auto filter = suggestion.filter; - const auto already = [&] { - for (const auto &entry : list) { - if (entry.flags() == filter.flags() - && entry.always() == filter.always() - && entry.never() == filter.never()) { - return true; - } + const auto suggested = box->lifetime().make_state>(); + for (const auto &suggestion : suggestions) { + const auto filter = suggestion.filter; + const auto already = [&] { + for (const auto &entry : list) { + if (entry.flags() == filter.flags() + && entry.always() == filter.always() + && entry.never() == filter.never()) { + return true; } - return false; - }(); - if (already) { - continue; } - const auto button = content->add(object_ptr( - content, - filter, - suggestion.description)); - button->addRequests( - ) | rpl::start_with_next([=] { - addFilter(filter); - delete button; - }, button->lifetime()); + return false; + }(); + if (already) { + continue; } + *suggested = suggested->current() + 1; + const auto button = aboutRows->add(object_ptr( + aboutRows, + filter, + suggestion.description)); + button->addRequests( + ) | rpl::start_with_next([=] { + addFilter(filter); + *suggested = suggested->current() - 1; + delete button; + }, button->lifetime()); } + using namespace rpl::mappers; + emptyAbout->toggleOn(suggested->value() | rpl::map(_1 == 0)); + nonEmptyAbout->toggleOn(suggested->value() | rpl::map(_1 > 0)); + const auto prepareGoodIdsForNewFilters = [=] { const auto &list = session->data().chatsFilters().list(); From a091e736862d2158e8167a7624f96285f782684c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Mar 2020 17:02:23 +0400 Subject: [PATCH 025/115] Show different filter icons. --- Telegram/Resources/icons/filters_bots.png | Bin 0 -> 684 bytes Telegram/Resources/icons/filters_bots@2x.png | Bin 0 -> 1434 bytes Telegram/Resources/icons/filters_bots@3x.png | Bin 0 -> 2277 bytes .../Resources/icons/filters_bots_active.png | Bin 0 -> 615 bytes .../icons/filters_bots_active@2x.png | Bin 0 -> 1289 bytes .../icons/filters_bots_active@3x.png | Bin 0 -> 2129 bytes Telegram/Resources/icons/filters_channels.png | Bin 0 -> 897 bytes .../Resources/icons/filters_channels@2x.png | Bin 0 -> 1796 bytes .../Resources/icons/filters_channels@3x.png | Bin 0 -> 2645 bytes .../icons/filters_channels_active.png | Bin 0 -> 633 bytes .../icons/filters_channels_active@2x.png | Bin 0 -> 1099 bytes .../icons/filters_channels_active@3x.png | Bin 0 -> 1775 bytes Telegram/Resources/icons/filters_groups.png | Bin 0 -> 757 bytes .../Resources/icons/filters_groups@2x.png | Bin 0 -> 1574 bytes .../Resources/icons/filters_groups@3x.png | Bin 0 -> 2392 bytes .../Resources/icons/filters_groups_active.png | Bin 0 -> 553 bytes .../icons/filters_groups_active@2x.png | Bin 0 -> 1166 bytes .../icons/filters_groups_active@3x.png | Bin 0 -> 1917 bytes Telegram/Resources/icons/filters_private.png | Bin 0 -> 627 bytes .../Resources/icons/filters_private@2x.png | Bin 0 -> 1151 bytes .../Resources/icons/filters_private@3x.png | Bin 0 -> 1883 bytes .../icons/filters_private_active.png | Bin 0 -> 521 bytes .../icons/filters_private_active@2x.png | Bin 0 -> 913 bytes .../icons/filters_private_active@3x.png | Bin 0 -> 1489 bytes Telegram/SourceFiles/window/window.style | 16 ++++++++++ .../window/window_filters_menu.cpp | 28 ++++++++++++++++-- 26 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 Telegram/Resources/icons/filters_bots.png create mode 100644 Telegram/Resources/icons/filters_bots@2x.png create mode 100644 Telegram/Resources/icons/filters_bots@3x.png create mode 100644 Telegram/Resources/icons/filters_bots_active.png create mode 100644 Telegram/Resources/icons/filters_bots_active@2x.png create mode 100644 Telegram/Resources/icons/filters_bots_active@3x.png create mode 100644 Telegram/Resources/icons/filters_channels.png create mode 100644 Telegram/Resources/icons/filters_channels@2x.png create mode 100644 Telegram/Resources/icons/filters_channels@3x.png create mode 100644 Telegram/Resources/icons/filters_channels_active.png create mode 100644 Telegram/Resources/icons/filters_channels_active@2x.png create mode 100644 Telegram/Resources/icons/filters_channels_active@3x.png create mode 100644 Telegram/Resources/icons/filters_groups.png create mode 100644 Telegram/Resources/icons/filters_groups@2x.png create mode 100644 Telegram/Resources/icons/filters_groups@3x.png create mode 100644 Telegram/Resources/icons/filters_groups_active.png create mode 100644 Telegram/Resources/icons/filters_groups_active@2x.png create mode 100644 Telegram/Resources/icons/filters_groups_active@3x.png create mode 100644 Telegram/Resources/icons/filters_private.png create mode 100644 Telegram/Resources/icons/filters_private@2x.png create mode 100644 Telegram/Resources/icons/filters_private@3x.png create mode 100644 Telegram/Resources/icons/filters_private_active.png create mode 100644 Telegram/Resources/icons/filters_private_active@2x.png create mode 100644 Telegram/Resources/icons/filters_private_active@3x.png diff --git a/Telegram/Resources/icons/filters_bots.png b/Telegram/Resources/icons/filters_bots.png new file mode 100644 index 0000000000000000000000000000000000000000..590b15279e7dd07c012533586535b711eb1dcfe1 GIT binary patch literal 684 zcmV;d0#p5oP)T*^ZdOh>xD8}Cd3p2lGRcN@gb*O>pDD3FA?MdmmXdhr_rnRd16R4Vnvm4YuZJV&Z1 zuBoqZ_x&tr3aEa)UVk~cAxzvxqoKletO7W`#Ow|2_j{;TtB^<}v^W=!7;^BUJRA;c zejTF#F4l2D`T8j`fI9MqrMK$;Fy3pKdaCbL3-v$LYPH0Ay%vwh!>hrzk%Qc3v&oM8 zfuSnN)c(d>2hRTpP@KbH}9y0He-<~{3U!Q(CG27nlb~4KVrBaC<_lrXu zW3ZscE1nv&+OoxBA^AcK1_L1{ptj8exfp{*Y1qUCI?raaa6BGqrSCj27B!?9n`%WO z!QpU7skb$S+k`P8%mDk@+obhV9IG?*smVFr$PkwmFBWCUn+>iVWr)j)7yAZ>RDE?9 S4NboQ0000`#Tc~0NpU;b$v2@kwqNYohN3oeln?CI_*Zl+T{o$PRo$vf~&JTy?=YvvL(N_Tg0P5ae2>*Tc z`WMQI`y3={N9~IO&i$M_0MK?zbte)80D#VWBisX%fuf4h<1S}3Un-R%oOR48npq%| zhvtTb1`STay^6=!s^yk&?>}4#<=(DWEEiCFMT@SEnI|tc^_a$XdZKP zSY!A7RmC`)6nt(f2{t<+KCMSzAWNsUJ&f(@=dSOWVt|l<0XY^~S1xawYpj}M(qAT0 z`wy!)j?Qz@CIkc&**QC}CMG7HT3TB22@Nd|@b{m`VzEPCfn@Ra_VUc!oKsR#5_e!A zqrIm`z+f;COseY5$4;huON)#BJf3b?L`3s~Z%bHM*w7`+!iS&>w=^D^+$yk1~QNeKe^ zw6#^UtE-FRkLbKM3LG50IzFX>3Z7la$+vwJR9RUG^D1+qTm(3}CD*`5*Vb&X(a|>t z5_}E9UXP89#q{t}Zwt*jiscbk|UEOSeol&IdXr$z5wP29SpPrtsolKZrgH65?L3v0eWlz!i`U^hanC}s5%o#zY zT2SihO!`ChfBDr$fD%QSRH{z|^&h_}Gwyd;Dj+du(8t?BF#g|x)fYrS zvV_KZJLt^%#;N<)qz0|d-RtYyOER0kfJWC?%1_Nh?vHEMxwyE9^oC1I!HrE#TC^7W zQG1Qg{b59RYuY#OKXs~|%iW5Li^+StZ&g){4j<0nHL)rWv3Q=W5*4SSN1;xGUH`DwZ$o1 z!{Q2Hzdh05U!tw&u2_>54Dx7g5b8$7LU{(qSA1$pcFS`Ww(OgY147L3z)&|eASVu2 l02gUJo3O${sGndw!#1a literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_bots@3x.png b/Telegram/Resources/icons/filters_bots@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..867e63a7d739f1deebf2712f369295937dd73efd GIT binary patch literal 2277 zcmc&$`#Td37hm6LHupAd_K>)e%jqpT0&U@002lM zo$NeC7Wa>UK_Xocc5YB)#KIA72mqj=KvIa=4FG`4k#>kPmv^oD_p_xDa&-l=4cgx) zHWwu!@(Mw24DcahuGUuhj3%?Y52>k274DMh?N{?%;1YAe2&Z1MdiMJgun%V~pd#7- zSJfQtePB_y zn}NRkUT=^_#vPyk6l(d?sD1x*(gX?nPA2M0safm;Nh^zWT=_K< zDHLug!tp}zL3nEV<5PE;9VwgKS2t8*1pG(_VF#Z#A5N=^SUfW{Mcgep+L^XJNm9{0&OQC_YbE@x1hdhdQU)kjN6>!aBKDclBuEmIlgrqffkWM4GuZyg%t> zZ>Bxk(ec`3TT-3i+XqKfc~g^1Cj^Lr6f$DKJ%|i*yzufHlC!SdUgjQ!$OD9z+=E@A z!w-=$-#;QtjW3+VsLyo~#M?Js_T@t71wTHoE%fE$QntQp@mEH~9r9Hn|8mGne^=?) zL)LN$?8{L~pYg|RKfL^T%mCe%Xkc-L&w3hg>-8*KUDE~AC}~=*caB~ytzvRm^|f#C z!9TcA^I6UHXN&NcKpiTct!tZvUW?WhE!%K;YPYryVtEXC` zq2Jy10aw(1$vZ%QXpGmGM$cAPRH*6LE&4(dsS^NT+c>3HehdK8_zkWsLet__hP90G z!k^A8tY-ALg+5+)hIlPEFaDz2**7K)03r}Iu?P8Bz07Mov*XN9FKwB3jQlU`4EjFq|*&EWv{!* zrNLj3bq4sjwfnb?Oq-sRStd)B~O6n@~JmeOqcw}HKD2`|q-odQk#tCQ?1}H9Lha1%DqSSi> zxm)X>+=8ezNL`&F&b$6N4)@DUTe3%UL41f$GRnq-X%-~9+;Jm#JHvEkImFHR#8uHm z6dK6vLLoGC+t1Y})X}CvyLQ#}N!+ z`So6&r^rlB&VB4JKJl~0U`kjTHxpBn#>7S2v}Qkcr@jWw07hvt^rZ#vGX`$m3pTa^ zdDa10)Cg#bkXK3QRzFhLmAVxt1OGf_Vr3s>V7xpu6T=Gu_o8uQ8p_hB%k>BHUidAC znAE$-hkal@Y)f7b8<|(n&mUqSTAD^+a?a8=5oI%(q zXd5E)GrzTcd{LGD!sd(kfG1_J?^=l$-E@%SrxjSHY*;5HB~`wk`l=2( z@H}^|@Q`ib;_F)zdQbUSJ=JALm- zmKFO!Exxn!qSaw$0>G9$p%6$ruQ{gvqF2MZ`0ZCIO{T_4MTP%T{BIp*!so4oMpoQ{ z6x|SNUUs9$)4;W*;*`#_UHqdfytwnnyS$QvxY1UsyNuAIO0}F9a%0!!e^uIY@<+Cx-G@eU$t*9&zqk5jxMRkqdiR zeP@&sJ(z2@ev@X+(R1|wz5eLQCgeCR#K@GHhlw)72kHTKr-R%Rt z@}xkJCd8&+nkhVDffw!z8z`L*9YzWD%PdoxnLA^F=E)gwhxfW5i>s-)fbuYZ_q>Pl zVLU_2B76KVIS2fT{v%Fj!yYEDA(Z2;Kp@FS0zpzNkjC`#F)<^Eil`xttyrF#D{DUV zqkRa|t_FK~8q zP(*amMR2_D-b)ipyftloyeGT{(j=$no}ApIp|0aNfc0aF_c2a8*bGcj`0N_e}!ss1wk`}s$B30LQq1Wrd{eDLeuM2UAOEFRPeO`1klgUK8 zxL&V3VWZK2VzCJ2av55!77PXhxZQ4=M=>!?MKeUJRb@OLvx%^kS1J{l&1Q2P* zZa0MIL2uJ^It9=3tlN;uWMH*gu_@X<(hw$>FuNfY(h4h)#6e-VZ@<}@^Cn)`E`r} zq*&Jnv3V2>-~ryS+^X?|k*sIxsc}#({T~WuL+XEAhQxcwyi25&yo1^F_36h+#Qtu# zQy)g+mNdfjgLtGZTP~OHB!pw*Xjq+>N+mcRkBPHGv7`~q#EZVXO=bWIjz%Mhvwg&+ zm>6b2ycjvhfAolShJMpqYRH!DG(%~IY#Fe<{|3k7hle_WPB{Po002ovPDHLkV1h4f BBA5UG literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_bots_active@2x.png b/Telegram/Resources/icons/filters_bots_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6788eb7953135df0803a2bf02eef4e5469d9d88f GIT binary patch literal 1289 zcmV+k1@`)hP)DTUm1QZaH3JOR8F$5U1W{?762ry>NAO*w_V9c693Wy=Vm^FhG5JP}5YX&iZ z{xJ4L%-!AHW@2K(3=R&Orluy-*w|?5>+4M_m2%?&fO&p?Hjj^w=HcPNoSdAP&CN}7 zadDA|9?mH~?nwMoR#pa6Q&Vt#eeKLYw{mrL<@!Khh0y#jM)GhbCnw?N<|a2P5x+uT z=(9kOk^@v)S_&&GD-lza=|bNaBVka<0IIF6g`=aRSP~x}A8>GR05dZ)FfcFxt*xz4 zUS1BBm6g!m-VP%pBQQ5N2WMwzu|Cj%F)&twkmLd3QH_K1{{9Zj%gfN#)@D7a-rio= z*w}!tudhg07%Ra{k_QxbTAiPtLq|u4H5i%|!^6XHdwUxR31ianLTy<o zqg-8G4g351Q6Z9Axa9!h_Ck;_b^==fIY7A2q2=)K5Yp+i z7##1k!NvUY@{)#g_etIup>H`rbQAdU@&e7x&EjypiTCyO0bULL1vPkfp>G*L&h3L= zr(2(%o|YCH^0&IW8j6crgy5hb89=kMvjL%)J$M>W|D&U$aCdj-ey~pMw9SpjJ=5ps zXCO9ep?>mc%K+Ni+6stVT3U(}iEzAvbxyd=_??{{&!zi~S~$P9cCp7DcvtQ(ySuyo zW~T0}Ns;wTC#l$sr?IFO;_}B$5!z2rPl2OYQBe`;H!#R)IBK(gY{qwWbp?i@7S8Vv z?AZm{I{NP>bJ3o^=^Oet}r8s;Y_<$t}8cGB)Ee7A@3I z9&I~7oKPyTuC6W;P#!{K{WUZ+(D0$Loc=7j>;Q48P8D``c7{Y|5s}x}o}L~WKID(n zAE(XwDrR#vae`z@e}8|bo^)ksXowB*Q~iF2A=-{H)JG2y9YgJ>r>9U-QWEk*f0u6? z7ifHpLJRfF+39#+I}F2T%uJ9AK<(IGSXdCfi>JEqCU|jiF)DOMxAz-5$m_TGP7V-? zIzB#zv9U40h15cf)fneB5sZ(I17>Mlp|R*VYL9E!fzCzgXX# zK5CECX}w6tIr#1U3Wzzv3P=Gl1Q@eskOE=|FlNmlDM0u4_Ze6IdZzO~ulP0l>o&5j zcCXyy0=iQzcH5bFU|yVlI^Jtr@1!}>udlDBrly7)RuKE!+naIz@gWjRYz=Z=`-%vX za3ZNi4AA!Ww#3{-d`N2voU4w)oZ2ldEr9?1DRw~(s3q5VMEou{AQ6s$zFjQ_EaA7R z2wDyh{>5c|eZ3f5+?W^}HAv1$6c7qSfp`=PdAeaNjF|u&*$na@Ia~+k=jYw$#m*K$ zJgd+rK8wZ}!fQ?-1I6}1n2Nu#h+XD?BOIKQ0%DG=0#ZN>0miHuq<|O#j9D{C0Wky^ zvu2P2VhAv1%^(HD5Ma!jK?;Z=z?e0I7(jmkQ3Tu-Vmk)f00000NkvXXu0mjf8J}AG literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_bots_active@3x.png b/Telegram/Resources/icons/filters_bots_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..7bf3a689dc8b6b8a753ca0531145e240a9fb1a42 GIT binary patch literal 2129 zcmcgu`9BkkAK#L4&5&arRAz*t0|;0(dNj_9!DF? zHfW@uiBKzgfu%cf*AwpDvH_0pi=3-`t$KHc6m$Bzi%V982UCuwl!B&3 zZ;WzLKVHFtqt0ipnhO-O+4-jJoBKh}V1_0riA(k#&9?758to$tyW7QTk=dt2v>~Fh z@&eiPIYk!sGRrzJqUA{)GTT5x-us82wnw0t?XleW{~v7WDyyrDSz?#Tm~6u^r>BXr2fLBD zMU4hh#JpP{mzAzCmwH0u+oR*)K&l8O3FSXtynsm3yHiX-jjP*Txw7;|a#Wnv;cAqH-^gAP}g&y&ZAL+azl{8IChYQh;?wHN7ul=|mgf zcd-U|xVw0Yph1$_txkmgT37fV;o)+*dI;I&;hJ91x1H6gBh{9(^F0N#9|`8OTg$`j zjrkr0g8-ITU1+~*UBt|#x$x_~ZmAh~T~rQ|C$euq(L8P6CXUrS$lzWDv8 z(2+W|EJE8?F)*0>bARV5grg9EXuVr*aqqunClwU9r@(b8S0q#(GxdZl=5QGq^Qxx>H7aPAA84K`lGGeRn|ayiap=QQNYav*53w+O1Y z{&_AfM^IEYu&_|FVPW40iVd2SE;7eBI~Q3^=6m3eGY@#1XDq@#ZyPbJ7FTC#;^`67 zL@ha8?={sSHC|O!m6W2?vhrR6Zq8SKvVp$PTf~jqoab3!@gqJyJ{!X|h|Xv`+#=mv{Z; zHc7 zu5tK1R2Qu-(Fu7b11&&Lo1RHkh&E)x`=$s;DwWD%tdBKC%{icEHQ}wq9;3JdwD`%3 zS>isUR_O0}2FF~x&pyCl36}AcXQ5l1mGQsN<5`8zbwjV7!MC6WoLyb<2kT$$%o6I; zP4oRVXQ44Hkw&**y72jmsGoZ=&*RcSi0sm*oEO`$@XDpKJA+%++L8R}yzLr9;FQn8 z7i7p-9R$r=SgG*6){2V!S)7}5kUwVV4WHy&i>m1p(f05*O)acJrSH9Vl)3&JXU{GE zFtH$?@*F9I?b^|X5CO{Av%;$$%PNdY_nOjU!DsQ^1u_=zc{z#vuUSIuz0{FSF zgO%=hm^*N5NuZ%cb5~8*^?ua+ZN0ojC(Y;U`~_l{MytEr`^neRb&WkjI{3zJ>LdOh z(^*~*lt3BeKP^L*F$l(x8{TDi)N4hBgQe^j;@75aCVwbzCq2&o{;4AetrU|%Tyi-K z?J~u>Sx#-Aw#)br5#Lxs->#thV7X)kj=QoZwU3UmvEUzyWmC0A<7GiB6B& z50Dfw-JRNBcw_}H;hb0CtIwe7G|}fhYRFJqcJJSlWGL zwL9r0zPi_Zam6ohq}-Lf^vL4jO?|mse?N|MJmeTdX=^4<@&KUtcgW;nHYUutY!QqXv=+{=bry6ToK5enE5 z?~`pNJc>m^wRgu8^~>3w^&D&YD;KR9{EI1QMem?XehKkuL-+7Pv#|0wq_j2VT2RMg z;>hQ|cR`&1(csRPU1r*Ob{nWm4M^j+ac?`ZacWcQM*nu=m%tmu#5d#IQ%wGk0DxU~ KwynN|#Qp<`wi4?A literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_channels.png b/Telegram/Resources/icons/filters_channels.png new file mode 100644 index 0000000000000000000000000000000000000000..1c0c0601de65e15b020a10ae7d701f8a259e451c GIT binary patch literal 897 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 Dva}nf literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_channels_active.png b/Telegram/Resources/icons/filters_channels_active.png new file mode 100644 index 0000000000000000000000000000000000000000..c76a77f8b6aeecaacdb82f3506ad904806426a45 GIT binary patch literal 633 zcmV-<0*3vGP)m)9HjpqXEn15*VQLnayU9OeXnKM59q?Hk;t_ zc);Osa65I}ZZ`x1fjgUMAY{zQXtUI3XZB{KI7X>d!u@_P45wwQR;xUgk$LD#70T!H z+AKvg_|p~)2D!=S^MT9d;^S94QbJy@_f;${-e$9z=BfNrLZ+xDPAVuXYUa}+{|X@; z{5x+cq19^j9Xw4PnL-3;<|Dt9P`lj*_O(!qvd!mn9&zQ9ii*>ilm$X^c~ytgs3q6r8u2Vno9Jx+wJcZ z7v;W2$nW=Kzu(8{bc%&SK?L}gjV>Mn_?RX=hyE2qi;zlrOCd`kl>#dN$L*otma<%4 T|A9lr00000NkvXXu0mjfQ|&4$ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_channels_active@2x.png b/Telegram/Resources/icons/filters_channels_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..87b26cd9de100e6b022f523d1768363e70791b11 GIT binary patch literal 1099 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 YKZ)$AxoG_r0ssI207*qoM6N<$g4LYc;Q#;t literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_groups@3x.png b/Telegram/Resources/icons/filters_groups@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9f3ff16e58978e1c320e81b3d2d2049abdc4da76 GIT binary patch literal 2392 zcmcgu`#Teg8z*Y!vK5JJ=F~JoTbvWZa^^N9msZGa79Gi@oR!N$(V99$!kOYI<~GDe z6yJtsVzP{6X>`SKu@3668&R4W|F4I>u;}Q9X2D|1!708o$I( zAQDZUx|m!~o^4&441OZa79!S*CqmV19B(moRg>22=7WFUwYv*EJ5OsR8xtPd|71*T zO!=P|#~eoQ_(urIKs63O$B--86miQvXdGT9ocjWEk2C)II=9lRhSNnwaZAopIdg+$ z+$=q}rm+d1ZylHc=bFe*O`R$bQ{L9Puc<}n-r&as}4caS`C)upjo=Fh%>cc+p|L(}> zf|x}*=Fs_4*1489*wY2H0EGmY=CXosL-^BEJqG8f(q*E5hpxJRMqE+Nl$G1ZOW0cL zMB$r9FGvz7r1Fet7zQEf$c;L2vPBy6NA^V%f#oljRk zXu(kT1c;D}@<@KMC4UHmv4n*7{iF4^p~18Titzid~VTIrv8Jo7h+A%fEiQ0EucHE0U67HwFm z6RWPAF%2AVi0~cJNXQ6Bp&r{8;6B1v#&0~pqYG%TW38y~XOqO!z_xg< zZmyU5Nu$@uC6_(h*|7J|%ZIdel@FVc!^hJek`9KpW|+GvxoINu5buiLe4MM@mFv~* zb0Y)|gsCbM}lYVEJRd1dI?={ z*@JF~Ztg9bH?KqmTyd)m3^FNpkIhc}R$vuZ+fP>0bJgq;Dbk~}3#{J0o%K*?b7eX? zWWXW}){wL%{cU%$c0^XW5RHq9JUpB}kfoH1JEBM!9Wm>i;ijJBPF^_(E7AEQo>bEP zYlRt(6z+}eE!t1{Srg%b=agO+nkZ%rfb9$ATn^aIEk`%d2}`IBi<+D6IFQR97hM$b zS3^|ChuBy*T;7a>=_!PE8tc!El{p+Pe+87(-8@uhC-8-`zc=~xicTgkH7%wrI|DG9 zBxLm6i1Rl)o@(ubSop={$DQc6@zL(Rxpa+npV3tCY-anUQs^x-m1o&=XrOP<{WU|X|*qG0PyrYo9p=vS3f-oJ|C&3dff zANr|Sl3S*J47A$cv(CxsM(7^p!RX!Hu155{Y76SGJnCL0PXK?TuRBc2n%ZGZB zP+wX$Y>ucpg{Vi;CG}u5X=OjI)a8KVEabx|Gl<)oBzND3t@=GhwJ1h7IU}0wooPz} z&qz%;^XWe^HriuDH3{y960wkVd!6&422Yds(#{$rLBz~YyHdluQlF_Q4psUTm95&Q zfQE%rxj%=b=UT>8=Nbo5+-HGjw>G!8Vxs;TIT0n`t4FPVzqpOekkU}w_uhS`F*_sK zShk&LBx3zq68-TEIh_iF;<%4+-fmEPTBh&o$Q%t+fUaBnZ(bn&}fh?dXq^rn9ziU2PLt#vBTH$OPI6@Zj!HtAPtY*V0 z(}*$MWhZA$px*qgkLeEuz1?|c0``3+|A)pSB4>;CM-aVqZOhUug;n}-Bi_p5B+C$H zomhpMfUDVhQqI04rv|V{b7==I!9ih1Y5C?+Ma`wIe^m&T3_VpIL#upWyN1MHxUFl? z^M-t25ZX8!%YW|%n45ub$M0p1*Ir4D-Q8hnkTad`2OHrTftcxOVLuJ~qp#3bynTot z2S=7<7yv-&Rk9iz_VR7Ik{1krJ^&1>x>MK~BMgXJ7^`Pnx7_c}LY(`-eCLwZ`OQA{ z&>S`GjXbv7_e%l#_Lr5p!Gtt}iG8FOV3NPQwNtV+!a9LZ0qYgQ8eRpeXHNUJg8LJB-4q?4)z;g&bJRU<3CjU zJ6KQQFRsC?V*9SjQ6jOBA3z)tPJ7~hyopv=4a!^%tZ|Yyn7Us-<4!cugXAN6_)Y@1 z%JQ_TddAmdx0XuMRNf|C5`n!(tPnl-t!88_7L_Sz%QmywL_Ndq~)iKR@p}? h9c1qRZA7vwI}K&K&YafyEPa(^oa|lgs%-+3{{`cRk81z` literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_groups_active.png b/Telegram/Resources/icons/filters_groups_active.png new file mode 100644 index 0000000000000000000000000000000000000000..a5a143946e2f3192ab48b7e002aa10d07f752992 GIT binary patch literal 553 zcmV+^0@nSBP)h(o|JVBrRcflDy3!T}gr zU;+jP1_b$j{cV0q>xP0r{ZcGNaqMU3$&TX@k>Zsb`N|EGfQTbx>xiq6tB|b%Hvj*8 zLxn60?F5d6=F(_;gl1eAUZzh)9G{A2XLNz3T&%_FP zI-#r;LYpLkPavV~*0G<>W}HJ7n_rHUJ(?XuVtUpodwa`35~MIqXdm7TcAS$&{_T%C zscJSGNTE{CjJ+2m=Pe&%ebk?(`7K>oEEcKJXi%%w@>jiHr)srIrBaE`=QHhgJ6f;T zv{)?s#qWwPmy1rQY>ZH9WJYI5sMG0KBSS3q$dqk18@9bJW**PO&mxP71{xeD1TVDtX~4Sg`As&O@x00000NkvXXu0mjfd(HWg literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_groups_active@2x.png b/Telegram/Resources/icons/filters_groups_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6f87b49e499122e4f3b071d15a370921a9e1606b GIT binary patch literal 1166 zcmV;91abR`P)cfUH7)$9n z*!=*hHY!L4k?oG4-v zn6<&z_8N5KyekO5;MCNVczk@sp;Lnod`)d*W5Z-N+Sb?Cjdok3H)RzlT6nsw$7~m? zKmD2q#2|p6Q>@P&B|TA01B|s5mJgUA#|ugvqt9mDmDDWhrAG2%iggZ1s3g;@`d2Pi ziv>id``w>Aa$kw49k()>hHn z+$wWzA9iZotcUZVfUuCA`c`T4mxJv|kNhlk?e;6R+6oms`fps}$rF*7qG zYHMqST7n4ZK0Q5&%gf717h2HW-`^J(7Z=vLrh(2A#fF9kIWaLIS5{W!{r!E`rBdDx z8qk6!v{kakt*56)s*BJ2>ke*jZ>3rY%klAXSzljo)HK92ClfnXR8+{};bFPGy{*e9 zSU+MQ7GlQnlY3REQdNz0%+JqHxw*M1hlYlXy2&`|oCY*HIx25&Zmg3DH4rfoTbE;d zd%JvneRU3}KG4aLkrAzKBAy-)%5rCC$2oz}4#+`wi%d^XXAXx-)#(;MN(WR|S0_(S zPBI4%?TB2EQ$ga{I!5=#sBqjT0m;v z@awK!ms2i>j}qCNP8W(=Kx=Dj;{E+Sd(^<5$OSp2wes@vv~H_DlP(1Kdk}$9TS0{1 ze`97q#81tD7!W~#FmnYlAc6p4<_cm!1OdX#6~urD0)&|>hyf7<2s2j@10o0zX09Lx gL=Yg%TtNh&zbNE|f(3FHHvj+t07*qoM6N<$f_BCp7ytkO literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_groups_active@3x.png b/Telegram/Resources/icons/filters_groups_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..342825ff2993083365cdbc7838679c4fc1448d48 GIT binary patch literal 1917 zcmcIlSvZ@E7L5`@Qx(x`NZX)AmCCPDbD}h*hMI@AK@x7M9zz|CnHoxsMXSU`jWypA zGc_cFY8w>2=a@p(!R=Kf#vrNVyxrIPa3A(s-(KJT_QU$t{&uphwb>~#IWZ6jbjth| z&i!oUvo5z?$o*PY^oAne{&oOR60K=?=JO@Eq z?c)p_-~|QZ)iD1D0q^eH~j`H+};JJNB`vAZP@sfi*hhC6KQE_0b?~i z5=hNrym~@!MM;Umy?ggA>pCllcg;Hkom)QadeNkAAChB0K>aj}HGC3Iwv}Fyl5C0z zTGaWXTopaqc)oMxzFPT-L|&3-yh_va=p%>TXucTdY)?c~l-t6r)e}+PuG%F3aF2ms zV?(~-lvc;e%)l5I4m2U7D)408hD$>(ilCpZG<)V7PT@p1V?Z8)m0b6U2IuGHiF!{h z!1}3Yprb{)ZFHc$9izNswS~pkBTB=z1MI#%CX->yfZNnmleZ}LC9uIbSX=YcC;umV zua&pI^~|~mTc{(RxK8MaENN_P>>`&rPR`7f)wff?OU)!qBpCqu?sxh_G<`5 zJ|?1fcUE0$s;U6Z!dyHa?}*3$@+#{$Mp)&Aenz^n>DddWMa$jKN?<7WF7{L+t#@rx zV8+|+EDall9Q5S(RokREN&E2IWtSUrotlu*W$Fl-bLXyA>Z2A6G|XC8!Zt|lS2GVn zxoicWMD|1;9mz8_|EK)TZ=WI$uxV3=P9^bR&GF2@-%umYKG{{Q(>~H%ZNHs6gYSk1 z1hR9kog4D*(B`Hw%+rEiz?m^>yEUp9o|nOXt)7)T(BFT(gt98B)Gg@{`uCKqNF4U0 zsw4*O9NwpUiGw%{H1s#s`yH$*%A}MoDgJ9ubGuo$g9?9#qvym{m)+9)5 zGM=4A8yHxI%cWLSRQ!9UEr7~eT;%rjU~6h?n|5PI3u6RkwztvUQHR>x;<;(#w3LzY z6XAZZANu(@u~;mN_>jh6jD?)g>MTr}6Dt^sMrLXAkP6>A0NNPxxzO6<#V9;&XwDkCqE4-4Vbu|AtN9Q>PibayWS9WkW;6uqEcA=JMyS{ zWW+l6!!S4QJ3dKHR@Sb)X55!1Qb0o}+b{q8_|al#on_!XY+`y3HQ>+|FyA_unf(20 z$Yc6;(~ys60o7vxq6m5dku}?0jmzpRZ@6Wt+0qlcMK}Wp;=_@qx{9yz1i6UouqpA?#8}H zkg`IeprGK~b?FJsX>wAU=TS6|OLa-&^ZD-wPtjiy8s3F$go9muR~`@`di0-(iNYHa zTO{45s9qu*4xbZ{)}nE_q1NI9ObR_hSg9Ch8+p79| z?&jx34e>u1M2|b76)2R$QGbqvQmzu+7z?GTH0^XaS}bV7)Dueoh`SiKI-@RgIHXrerJ1z)PT3FeX4Rm!eZ3_lYUQL{a58kU`ufL5Kf+^(HETslG!qf8Z)~qtqxw zCj^xc3YndA<83yZ@#ej^yVq?JNKSBbLP(AAtE80YwX1}OzuRUgmfqJROo*x z6p2Kr*=*8mHlyS5$UVmJf5q?)*>1&RQEazcp|Z_pBMOCrZOl6l`(&g7?_~P@zQ|^? zA{Y#Ek1-6S5a(dC_6fCGEnc+O>nRsBNXg?<&TB~8CnRfz2|*bZIQ%fgspd64`-Efx z^MW`g6?iBV;(5fW=FNPL9>&FDAu)+ksTB90Kgnd0T}IsJd9$%2LX*jaG3)iZ61&l8 zu-}MN2AMc*%lh$_p|F4bmdVWtSG_|Zga)N>wQ-wJ!00h{`2H}a=MFAAcbKE1{z>Yt zBlPNIx6x>n%H=Yp(`kyuV%%ii(0;$C^?J>1KA%&!+oj!ZXL|`t*WTyziCV2DMx&9q z-ERC6>pqA>Jo1n)nQ5D)qR!=V)ai66kx1}vuh%P$$731{2DDnOSdqiwK;dwh)r!aC zR4SDypU;!u@8_K5a!Hj+g{IS~Rxwl7nM_8U&u11x9)qY>t0E8xnC2KtAP(`!gPl$% z0X$>=L2oYOa5!YJcDpUSUat;JIgdQ#!+`gIh~YOUbh%s@=+Q5`2HaSL|G)tVcxE}; z8j=_?pUC4u=-12&miF4CK)=hZU}+WlE;3eeT=!Bx3F(gRsnDNO=nX(4)}?Nt7cT$+ N002ovPDHLkV1jSrDo+3a literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_private@2x.png b/Telegram/Resources/icons/filters_private@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c39dc8d74beadc6dfbb3bd92c82bb29c71b0302e GIT binary patch literal 1151 zcmV-_1c3XAP)VAPU90*E2Ns3n6KK))G% z#np|CjWjzuYs%2j5Vf_n8K1Yew{(7fPTSktraV7CS3H(N4shHNywudxsO9Bl_44wf zI6)oMMVky_4G*Wiy&fgyzjgbv=^+~jq>)Ej6U7*aSu7$QlT^Y-?p z*4Eb4$jFGQt*td>bad3*gU%M%2sqrr0m3|D%l`hp>g?<+IBcLp9a~^4&`=8lNWZP} zCS-Ixm&>soMI9RwCT`da04WThxw*Mm5@6^Q%N)DosA~^5*tF|a;5rPTt*xyXAj~GU|r`ba{Ch?VFj9U4z9W9u?ShwNXa>Fo53Q-=lpqZz|Vd@rH{EY`WTT ztuK2qO{|y%1BAU*RC;@Rqn9Q9?(6G|)w6BJlqp>V1B8udRIovHm6@5DSQFc3Oqt?E z#mBLZRmp3fIT4VBA_E9nBwGyzlFMclYaT2N?bS10^Z)H;6XF+&@g;`14elXlHC;jvz*W7pD0F{aS^h5j10cG-39GnJQNjt>!qpAuZ=5Yuh z0mKks)RI9Apv?Z8Z)j+s!NEc5@9#IIwY8P%>+4NH%zBGq%H7=^U0+|Da&d7%udlC6 zm(o=S1Ju;iMAOsLG&wm*R$vdV2B#;L;er;1j;n z1eNMaTJNt&A5qZK(h^lyR~OCZ&(BXfJ3FJRt1DCVM+v0=r7-gj8e%@GtE;1qjt&|a z7%*jcc(^3Ho12@ozP?T$A0I_yC{>vX@$T+!r8`{op`Vi+&dF<$)^Qmd=0s;jHZH_*KHunF7nktB!-v3WxB15{O2 zm6$mX4-aa7e!i?saqSQ?3%=mf7Gx4)bhR0&?+0jMVZn|`?dwM_R{T5-u{m3b}#hymoiS)H1iVq9dSPAe|&IVhX%hw~~!jA1Q+ z1Q0`jQA-91Acg>=mJAX=3;{+h86tXZH`7tp R{TToN002ovPDHLkV1o0^5aj>> literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_private@3x.png b/Telegram/Resources/icons/filters_private@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..42ce50320a4104cc9b47dbf1c5b786e3ced79d25 GIT binary patch literal 1883 zcmc(g`8OMg7RM9&78#{wP+DD-I;CPMBk53UD`F{XOo_H)i8WWwy>_5i>ulJb6tEC3*X#LdzEdJ=G@wD+1XLaVE*(A+ZW zu}pEs16 zT)zGtQtDxVOi}ZBsC&xAYyc#&I$hBWrG}iRY?=W(O{6%JPxQ{Rikwo48N4>sgo0jr(&$H2UoHbF7vgjHsyRUSnj1v)YRo&#lY0FtamK%_9$1$%?43$syto&wY^2QW^r zs)tROX^lQIlwTC}x&g3&GE$B;p-i-=mlhGw8? ziEL3JRpevL+VA+3jRMY}sXxenovf~!uwrFtX;dGNjB42JD>APW72m#pzhQi8%9n{3 zdoz;{xw@}L`t8=QM@2=|uIg>4^XVu*R%BF^uLR5wPEM8*QsGZg@b&mQgMC{V%x}~O zoTRL*yx5iwF7E5AxO{5pLXaBonopr=uwPkO8N2@Xy2i#vl@l|SQmp1?o6sO&WzCBh z!(49ey}VajVWv}>V#e441&hUQwYR5EPfTQsp0{7K8BNz;m&X@(d+$7oaUfTEkaBYu z#bTW4hdzI!{a=&UY2C9~%F7>qlgHrku$iP|bl85{Ob5W99yL0}NE{|x-@ESO;<9|v zj?p?W@L06HLjEZz$nP?KzndfTTfj+FMv;*m&6#szX=!PAW#WzR?b}txrhWIgmOuPZ zR}RJ26ciM^zWMc_(qgJKma5~@e_`f8 z9L-%XV?Zh?e%k)6R)WApr(Le5 zM*ZLeVrS_HeWdCho-jv@!(u1K-*DIDsgKiOt=Ou;^*RT%DKffb!u$u9-ce?1#I@!(7Uz>L-YMmqH}1C*ruGE z>BbLy@GqrK_jOfHDX#LulYG`D^JE&j3ay4r;41v@qW7-tI;3`PZ*4i9>tMK^oZ#bl wA9Q4)YhSdTNtuJEfb%Ng7I)zPr{)|28saK@+hXizCEpU@=H%f>aX|k1H&Fte0{{R3 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_private_active.png b/Telegram/Resources/icons/filters_private_active.png new file mode 100644 index 0000000000000000000000000000000000000000..912ea878d65405f3de05a95a6676cbff707c5b1c GIT binary patch literal 521 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 Date: Fri, 13 Mar 2020 10:56:03 +0400 Subject: [PATCH 026/115] View filter box, removing chats and chat types. --- .../Resources/icons/filters_type_archived.png | Bin 0 -> 528 bytes .../icons/filters_type_archived@2x.png | Bin 0 -> 1052 bytes .../icons/filters_type_archived@3x.png | Bin 0 -> 1576 bytes .../Resources/icons/filters_type_bots.png | Bin 0 -> 503 bytes .../Resources/icons/filters_type_bots@2x.png | Bin 0 -> 1077 bytes .../Resources/icons/filters_type_bots@3x.png | Bin 0 -> 1712 bytes .../Resources/icons/filters_type_channels.png | Bin 0 -> 454 bytes .../icons/filters_type_channels@2x.png | Bin 0 -> 913 bytes .../icons/filters_type_channels@3x.png | Bin 0 -> 1402 bytes .../Resources/icons/filters_type_contacts.png | Bin 0 -> 372 bytes .../icons/filters_type_contacts@2x.png | Bin 0 -> 699 bytes .../icons/filters_type_contacts@3x.png | Bin 0 -> 1223 bytes .../Resources/icons/filters_type_groups.png | Bin 0 -> 508 bytes .../icons/filters_type_groups@2x.png | Bin 0 -> 984 bytes .../icons/filters_type_groups@3x.png | Bin 0 -> 1712 bytes .../Resources/icons/filters_type_muted.png | Bin 0 -> 576 bytes .../Resources/icons/filters_type_muted@2x.png | Bin 0 -> 1318 bytes .../Resources/icons/filters_type_muted@3x.png | Bin 0 -> 2101 bytes .../icons/filters_type_noncontacts.png | Bin 0 -> 573 bytes .../icons/filters_type_noncontacts@2x.png | Bin 0 -> 1252 bytes .../icons/filters_type_noncontacts@3x.png | Bin 0 -> 1951 bytes .../Resources/icons/filters_type_read.png | Bin 0 -> 672 bytes .../Resources/icons/filters_type_read@2x.png | Bin 0 -> 1420 bytes .../Resources/icons/filters_type_read@3x.png | Bin 0 -> 2180 bytes Telegram/Resources/langs/lang.strings | 20 +- .../SourceFiles/boxes/manage_filters_box.cpp | 510 ++++++++++++++++-- .../SourceFiles/boxes/manage_filters_box.h | 7 +- .../SourceFiles/data/data_chat_filters.cpp | 16 +- Telegram/SourceFiles/data/data_chat_filters.h | 15 +- Telegram/SourceFiles/storage/localstorage.cpp | 8 - Telegram/SourceFiles/window/window.style | 17 + .../SourceFiles/window/window_controller.cpp | 5 + .../SourceFiles/window/window_controller.h | 1 + .../window/window_filters_menu.cpp | 6 +- 34 files changed, 540 insertions(+), 65 deletions(-) create mode 100644 Telegram/Resources/icons/filters_type_archived.png create mode 100644 Telegram/Resources/icons/filters_type_archived@2x.png create mode 100644 Telegram/Resources/icons/filters_type_archived@3x.png create mode 100644 Telegram/Resources/icons/filters_type_bots.png create mode 100644 Telegram/Resources/icons/filters_type_bots@2x.png create mode 100644 Telegram/Resources/icons/filters_type_bots@3x.png create mode 100644 Telegram/Resources/icons/filters_type_channels.png create mode 100644 Telegram/Resources/icons/filters_type_channels@2x.png create mode 100644 Telegram/Resources/icons/filters_type_channels@3x.png create mode 100644 Telegram/Resources/icons/filters_type_contacts.png create mode 100644 Telegram/Resources/icons/filters_type_contacts@2x.png create mode 100644 Telegram/Resources/icons/filters_type_contacts@3x.png create mode 100644 Telegram/Resources/icons/filters_type_groups.png create mode 100644 Telegram/Resources/icons/filters_type_groups@2x.png create mode 100644 Telegram/Resources/icons/filters_type_groups@3x.png create mode 100644 Telegram/Resources/icons/filters_type_muted.png create mode 100644 Telegram/Resources/icons/filters_type_muted@2x.png create mode 100644 Telegram/Resources/icons/filters_type_muted@3x.png create mode 100644 Telegram/Resources/icons/filters_type_noncontacts.png create mode 100644 Telegram/Resources/icons/filters_type_noncontacts@2x.png create mode 100644 Telegram/Resources/icons/filters_type_noncontacts@3x.png create mode 100644 Telegram/Resources/icons/filters_type_read.png create mode 100644 Telegram/Resources/icons/filters_type_read@2x.png create mode 100644 Telegram/Resources/icons/filters_type_read@3x.png diff --git a/Telegram/Resources/icons/filters_type_archived.png b/Telegram/Resources/icons/filters_type_archived.png new file mode 100644 index 0000000000000000000000000000000000000000..790d50335ecfb394b84a8e2c2aa962bbd4c80447 GIT binary patch literal 528 zcmV+r0`L8aP)4`0aERVsgS9Vssbvz zIYYiyC?1caQmJ4x8euw}Vm6y$KA&T;SYWwaVzpXfyGV(!1OjL@8b~A($Ye4Y3Sd_keZp=|sNInr*oSu&ZtwqP*m%q86(Z#J8b zKPfsnZa!)la&*i2d}ifxS(>C!D6qrf;GQD&eE{kmh%#L+7shYxyF~eXp7DE1fT7NV z#Imv1fb5LbYSjsx&1Ttdx3f*c_hUemwN|S!g5mF%0O9WV4m2K*-SIu?G}m{aQmNz# z!tGov7X1gJ6;fa?l_Fn{FiiU`<3Jygw{L2{DGiXSqNYNoLaGX=?B)!qD)bNWOsL-n SFtK3(0000!lvNA9*U-ooy z45?szJJZ_3*iqoPx?|v?4CM}vE*F6{thra*e1vt5TngK`Y1@XYZX3kbjbuuIc}3^Nqfg$x&7Co0Mni;7*jK^+%-jv+BckkYzn>QtEYHJ-e!_AKtz_yScckN-9LXh48mz;Z~|MKO_Oyh0;Up{!iFzMuy;vK1z z(uExhN=jHJPoC_zJ*7H3+xt?><>?wK0#anL)#%F~{oj87+ea)IR47&VW zm!CdOeV7{f@6{`=Gi7dpGjHB%*t*s9#fujjTKu1!O0wcID?k4H*|~V};?mnva&q79 zMjI`7wJq>cdrtdQhCs`;?|^}pU6zoM!67dv_s8eTopR18hw7x*J8X?W zUmxDR+ghwATG6QCgyJpUIrHbQ=UBOYyLf(HUd;Wq=O!|_$X&kQ+1lD_soy~ zti)&O2}Y&k_4W0Im6bP(*X`ady?*`r+?gU*yHymfJOAo;i`O=GEa_fl=glm1KX8ep z@!7J&9%)8g&$drqk@<0}#NU$~+N`O!tH1Romp;$b{P5>b%-6+%`OKRyj)zT zp(^|6&6_o!AFaKfdHdU!N$RtgJ{H(DjopQLF{8QCq!Vrp1sqid4J+cge(v3Co1U2{ z&AaVu#!jbN_Ae2Y7d8HgDnAPJdKAA=9~p>^o$H6B08WVY&8{u!C@%-_9S<5AXZ_^7-w3pZ9?SjSN>rDuO^D`1x~0$Ngme zCk4p9r}?_Q*$+8C0+j#))uchcGr%Aa_``W3!8uZ9g==wc%|@li@AOw2IVC7vj-UdC z(ovm!lHfuGk?;j%H^k?6^QT;i*!nMl7!0xAK?Q?hS=$VepipR+N1G(C>GsQOW_eW8 zyxF;4l)EX-;M?xz&bLsOFHAxA*%n*ZRwt1|d$L&wLXnmoyny${OgT>P9LyWwDCrnv z<5J-Iq`Ntn2aHWq5HieAiysK+`?}{Au5U=C$pr=4N(yqUJC2Ty%2N?OK6WW-Y1?XW zirMO$7sbW4hv4vDE?2Xqtqo%$+}?h|nSd@ao!uvZ>k&y~U z-q_o*vAva**zu8(S=7vdz5KKGdl?zZHBC)uVP9W3GA=Go@afYFhrbg(e0?%gS=YKF zl{S@DR;mI3aDY}aaT13+Qn9zQGwS*j^Hl5f!otGp+M08=jl8^kZF4h%I`;ZChjzrd ze{k?=fFqf7G$K;hd3szb5d3ziUo6hY7#glp?m>ve{<5-T`KF}^P0bX4zxr3n>*h(S zYHAmno5|UYjixM}&VLpMoOwJRsSg?mSvi4ZzB5Z@h{a+{#PIO2FZ1-3!t`|A(RG&> zb>-!KPFfT9WLGRIcs$wy2g9B*Ha0$d@xk}8_4NiCAvX4v`=v{7M`P&mzV6}S4Z(XP zGC4dvu7Xn{KAxX0<$2n<+z<+d)+rL!QOb({Afu|PO3%tlbTLDFePd(8U<7C<|D^{M zX*}o6reEOffIC8%D)ml!-2=OcIV^;U`uP9q4>>qFImyY*MGiL!haeMnU@uJ$w`omGJ z1Swr467lDodg|(M>i~mXJuuP%440NE@rtnx zp@X<9^cwboB_#o`rL{Gqq@;ug^bQVY_w@AC*a=D>CCswQvh~~>q0`pUksT;-dCRV?t)2b+Irz5Q$BI+&x%KVl zJHsbo2s|Dy?CvIs9QFq_H!wicwCZ>3k_q(4?!qr#r zEQU{YFHDRJKtik;rMH5D%6*O+Z!*2T0fAyrIreTt#qrMec_hSt)SodvH5IIg#$u_w zsw$5#z_wgYH*e9t=*&ctj7Lu7*!XyW!Y`*zx$r70FIIO(XUc(4i?!qOu>hzp_d7Z! zCMKLcBCsy&?)13DXWHjghi|dgzL~*f2LdCWIoF{92QT6Nu_V|*m4kd`Z@Rl>h<3FA z+t+9`x+M4Eadma|vo5K3&yi7lGK(_LI+YlW3yW|r^_B!4KdIqp1W4PKo zkyKeK-W(o?LrEVaPQu!!nc7=u23wJDco?-y*YGE_cMc&{FOy=}u8`BXX};lFTcv%j{pDw literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_bots.png b/Telegram/Resources/icons/filters_type_bots.png new file mode 100644 index 0000000000000000000000000000000000000000..34d2ed00a682157eb7fc616c1e2b70883a965067 GIT binary patch literal 503 zcmV^w{p^|&DW!S4uuX$9gzHCh6d}< zWHM3f^;%sn7j?Z}^}-p?;QNo^ufuyqQAEq-lA5NWE`tvkVEe>=VEx5_fJDZss-mJO zC`l4Z)0F1(IUSEjWV6|LV_#zV z3*w>GNv6}O7PQ@N35iX>_Wf)&(^znf-)9%UL!3lm!A7Hz-cP5K-d&f&;h^1ygBUE{ zMX^3-1-eYWFxnhxJRUnaz~*^w@og^7!g2#l)OOI_Zbw;`bq0oSzuz0`Q!#=ViXH&o z-Wx~N4*sT~Mejkv(>51pmx(jvA`jvFIzc~Dm33X)S9QPNRUG%L0UTI6j+C==IGOn5 zJD<;XP<;>Jyv7mkpOd=4i^alMhsA;8iuuOpF9>8LK-IU~%_e<4zz579#K=E;7V%N* tH=!Sp76eI64jl>|k~$#y!wg9s`UL`=rZ?kkAaMWy002ovPDHLkV1jDd=7RtL literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_bots@2x.png b/Telegram/Resources/icons/filters_type_bots@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1f37ff794076b3dba76f95df6c7732d0fae022cc GIT binary patch literal 1077 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*zwvZ& z45?szJ7d3>iJ`!OVtM&RN@7Y?U9IIZ9Q#^0#Ig$icJe1Ark2|^t^LlRRFR|_aU<#n z$E}FY`hc7xP9man^*8T0t(Km*Y4+^D4r&{6CY}6qQh)Q!H`AsBEzsBGW2%?=d+D#Q z1B2j7g$5P@#|8m!Hco{m2E}PSLJlm9PCAk*4IE4@5k?*if-D>xQzkGdaR?-xRA6)w zP&hKlk*Pz$p+mKq!=awja&|>gQP9=brCayxS)((I9oCp@0K|M)TR?%lg9PoMTK zt*Kel*VnhmEZU$Y@lR&_jnK7WiS_mJ`}XZSu>C>A700_u$$!6v%FIdI%=!NP`+YH1 z$M){s%XmveLdbr>zkhWZ+i%MXpS#_-?LwO4f**`KAK2L19{l#r>_sidvSrH_tY6RH z*4DNlZ1urcue4-jWDXoZe!O4i;N${rvq8KYX|_S1C*U z%Z`t!0(q;x&HMc44-Yptw~BBPQ9>m=g#rO-VXDywrGyg?!6!O zvZ%0dVjkO7e}8}8zgzUfZQ7iUxe3%>_mcKrBhAdr+$L`;=n%VS!@6X%b$Oo-9(252 zv*>l#@on=L=CVz5G~_zfXL4=(@9N69gOj(##?(Fjx99rr@M*n@B1zNugCw2i)Tc3Jv@lqV*RbS*DXo*xbA$&dxVChE5w%*>}2|KG_F5-8y$e7JP;U8a@LSc%~ zEf i5;6^HZc}3T%iMED_v6Q2fl6RGz~JfX=d#Wzp$PzSH2Ew5 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_bots@3x.png b/Telegram/Resources/icons/filters_type_bots@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..afc773a3c86e54081e136405509a34b1adcc8371 GIT binary patch literal 1712 zcmd5-`!^E`93LxOk9p*k8%Yd#E=-KFv90A1x-+>m+f=w0CDtgJF?m~MYD$DWPF_C6<3} zd+wAt+Wu!-#qJFcQQ568ui8gFmM+gVx+RXjdX`vh%hfsm0nta7<$OF<8Ub>2BxfrE zBZ+-`Ou;wNhQR%c07$z>uc0ACIcG?yJ}=fB)16p2-5K^$zL@kO20^@ zsSp^TRt<(ry$A)yL;YvOc6?z2c)YRh&D!gHN33mr6S$@C%Fd zmb?Cf`E!a~E_W9U221c4!vJp?EzJmDi4Hj8cij}$k8*_g`ugI7s8lL%ak2K<9ut$W z*49@2$sWJvrlzrSH>iut$<@_W{Z7P}4YB9W+{p>)fv z`6wX)kogqUk-tz^XBiY8Zp=>b_V(`J_wg8?i&Q{FCnu+{i7VOJz)Zc}E;cqcZ^a*= zA?BbS8xXPMQs&IeOcy!i!iAQ$_VyqIFiyn_>CDjdo1@^2c9x8cjA&QVyNU`6V<_n; z^RZrKC5Jtb+uPf7 zlVWJ}q);{@>86>P9!KBs=YFbi zZpP0#JFS?X3JTO~pv7h5H`gxHbTlid_Z5wHjV!+#%@)aEi{1xub*2?V%STUZO$z0^ z5@wbW4|t`&YwC8NT;7zKA9i)cSq_%GzZc=|DGS|e>lnUvr-}m6)6=srF8j9Q2?2|( zDdvw?si_{rnV+4nAU&#N_{!t%{RZxcl%wkDU&CqjbV^c^H1E6DsNU#_H_wMUey-;l zr_Ms%C?CmppK~}9IL~H@C5>u zvpgR7%i3BbCLv~Qr=-#%0Q9cAn>{*uyt%E-s-vUBCn)F|x9!lK{M~Bprc2}F*I(0c z&p39|-xp~_`Dv}tWh!Vf@>7vL1~az#zA8VrlPR3?;W&X+X6r4D;YsX^0ZOO>(|bIAQ)kqU z^bZicswdQn)7<#mVVY7xm99~1S$TPwP$*2`_o^R)!8iifdD5|_8IY6}J?^b4yG=*# zS0%2jtbp;}g*{c;{npy$=qnTCn3$OQz@$QM)WqkBeNqnS%7VnPBm?or#L&4+PDd8dPmJ~VBcrP|-l8rT zNtLq68URrG4Uw4T3WhWZwcMMvBBAHQu3Q6LEBnd&X7up$=|Z| z**L4SG76xdV)(+5nocjhlx23EUfE5w&-fqr3!1p#UpXFN1X%up02pL|&qD+;<8Ox3 BE5#|) zQU2e0NY`}|1Od6-Zp3w6k&aoOJ+7NAP1C?}94N~YhPdDFYwB+&h8--5qCv-OUvX%h zAj2>qj$^2*3fi`PT3MEH7rL&S2^s!uF6ekX4z*wIjPzAt1t~p{ZttUM8ltM|TacnC zOwl5S}HibFAVjy z!Z73$`*1icVx~1Gzt;<5j?g!Xi(2_CV1%s~#7<+|HWWnxm&;}L$tEYokUwG2gOL(_ wCD7OYD{3f-6eMz5IwTzuIUvf*9uhh900^lm16mxMzyJUM07*qoM6N<$f!lvNA9*dw9Ay zhEy=Vo$2q*5LZT723LnY`>#DCD-+|c9Xl3$|6b1AcJ-vxytcPx0R|FF{FWyl zJ$&W=dxoo7rot_$vg;T{dfiqAt-NsgvT*P5Eo|oSqw!&>c{hJ7c5?^yxjD!MV$T@_d?I*a?4)@|MdFxU7) z4R0Sy3jAo3xUh`zYn5$*clpUvvR^)Yc%a9m5dE~F>!{L>rN2|2p8o8#a6`)4?xhzq zQl5mYzg~FY4oBntj?j{IHHRXbjvqf>QeN)eRQDISm(|-;!Anp|dUG@|!3f-Y+RR_3z%l57wJL`)%Hd z@9x*HU*EEQd%AUq&xT*mly=TlzPt6+o$RjowGZyAi1M|+{P%C( zlcoKhN-OaELUe-fC- O7(8A5T-G@yGywowJ_oK2){Ri^!mhJcx$a6_nl8iOHvR@_ePwa@Fth3 z%hP7gP5V8cTjmbkN1+mMcxN@_x^dxQSKs|h4qKr8i%|stvebGDjbZk`(zfeicu96n zPGU+*8JEkQ|NNQVFO@cQc8)({x z>%{=mmQ0}_L?V%=XD3e3^pO|u-o{1dWMpKh42FsKmIER~e}8dn4gbhxgRA7t;vy*7Ixs$3i^VCJ@&&W{iPsOR|yh2TiP=-!)q^neZi^Lt9LXVU;o9Jj!FiA{Y;@7R>8fjo}Fdq zLW1~wK37?Az&_--L9ahqQ&Uqv1fWd@~Vf_3!zjZayyPZA4l}hF4@GwrN({a-_ zKWa=Y5S)*h>#Y0pbjLQ!#4|6_tavgI=4$#8n=)-r@+dLm1U}2agS=_oe4T_jmq_H=x>cN9s z_Xro<+}+bEhBz9n_C|Jg_Ky!A7OJlU6P6h(DwUI#iOAY9^{lTiP=9Q=eJi^Jz%1L@ z*@Y)1!G%KMohrHwys5dle?X;K??kwe%33av$>ijNte>kBXf$?R$a3ChRC>L>gCSun z&P7LaDn#b$d4vIv-)&+7z5Oj>;G47M;0U6`0tSPr78gAyYM~!w_Mc|FJwN4f{AxcDrd#!<=seCvSj=aAVyHm0@qr3(%>B`5 z-I?yh*r`3`RbltR?#Stbfb!xU%KcXcI&LYhhnyprIT`oBzkZv?Qq<+h$nx8W055{Q zR4V0)>>aIgO(K8sfW!MnM=LtTV$=NPmY~$Z!9kqA&}dK8kbs`!;5zN(a=buZ3oE3q lQmhIAS@O02OeQjuRS0GKEI89`wR=;hppZZ!z8M!+^bfpAdQ$)Z literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_contacts.png b/Telegram/Resources/icons/filters_type_contacts.png new file mode 100644 index 0000000000000000000000000000000000000000..1cfb2a57518a49122229b5b16b35ddef4f0299e6 GIT binary patch literal 372 zcmV-)0gL{LP)R66F*`$EPQENB0aaD$cN_N|raVKQArk{8yYHc&W9S17nuYLC SAj7x-0000!lvNA9*6?wWi zhEy=VooU#2$Uwk#?$t03K1bJsiVh|wGMrrJ9R2y#8`@^IF|joDF*j(OtP=1P6sc=6 zl3Vh3+9Hf_6juFqW=myW&qH?Q#RgFElP+n8nU-D21#A~&JF^Lia;*9FgUwgit)2t4^v5YPA`Ltk_*m;4+Gu|XAs)dE}Ffplcaxna2 ztuojdwtDINs88$4cHeC}S|@&jC1CZ{qPc#@OLxa!&)Xim`KHf96?5$gea9Ey&N0i| z7_sAN(aUYObCuX_mq*X)UsWXQ8VL03-eUq zoJcU}DBm5M$l)q|YKBpnko(6q%pUt5o=gcU*(bk!vc##pON=H`y>|0TZuT917;xFs zs^L$*UF!3nh1H!)zjmzLoAu-7uWG*VKgr94dODR3tYXX98r68=>d9FfugGnDwJhIy t(yZp3<)N`9OiVhQ#Kk4H=x4Yf-`cuzN4&z$IACgI@O1TaS?83{1ONkrB3%Fg literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_contacts@3x.png b/Telegram/Resources/icons/filters_type_contacts@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ac2f12a2609356cf61050b4109bbc14bf19008 GIT binary patch literal 1223 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p{OJmUKs7M+SzC{oH>NS%KU-PZ!6K z3dXmy?e$FpMUI`{=kaH4hK35)!o>@O*19?F-}{5XuJePahlrEvM2{7kFFH5^F9awm z{<~9tF719w^VZ@sbMJnB^ZQOp(%E^{?~3Q&m#cn$PNT!agU?KW{a?e2C1*A&$3Z{OywxN72-yp{h|&FNi+iK?Q)X6EME zg}*-S`*v~PfeRlRwr<_p+T7f%Z*Xbejr;fcXEBRN$!)lL_3Dj%QD+?NU`S{$voAuAR=o+Td!gTHS z?Px{YbEj>JjsD>xNCgZ*6V8&ClK?TW^W#-~B6})NP#3Ba+MWuEfQ}SZ&7qqC$N^9;x!z2OBcK zeyo#F*z*5lpYfGVb}QH9&(7mIfByXGJ9p;v^!A=SaKJ(H-Lq$Ietv!_)z#74wrx`g zZ<()tN92H=|Eihw?YCZLqzPm`cT32eTO{P3;ODSpsv`#b`YV^?>r=jb`EugJhYdWu zyi@!B{i}QV?wy$Jdf)jQ>uYMJJTc7Oxo3}#?DOZ(mww(ab-__xBVqC4#YV=)%9AHg zHZn9+R904=^rmb^j*ncW_TR%tE?%4{BPVz2-Me*j=FOW`WMXA;Yi)mu(#2w5U*FcJ zPoEYP76!ikc<9V?)9rl=lb;&4pRjy9droLVdU|?9LQ~FEJy`*|mNVz6uIEpXm6c7bt+n0tF;aMy+SO+qF^?O!ZrReZ zckkXc_nuvg-@4LG{el#KPG+X2`Q~KRuwIt1xKOpzuOjw6o91p>#qjJ=-HqF~XRFi~ z3EBVI_WgT#W^V4E)YaSxo{>LPLl>T@nizM{^p^KKRkzKX>^fr@s%vZ4p3gDvdE~m7 zJ0Uwe`_rdSlMbG%*}cl_>WhUtfAD=yVCiBL7i~+L#y*$jS!0tUbED*$PXb;EavBmQ j0y7Tk@Gl(1!hcxRGC3OOX+?blmP`zuu6{1-oD!M9>B&%@B$W=f{kae^cJ2%ixdG{L2Pmb zMG!2kgaidebbj9AhL{h|u3E@v2iBRLdColFE{QoJ;&^?H{d^6RI}t}nIuWOkQ%F)k zs=GV%{}syT^E4a|X}jI3g&4U{8s25bYcLpa){wL5Az;nPpwViz%JNY;J)~SNv+w&H zhN1G<>zbjrBZNU&LrxEyOePQI@pzonG)C}tgr?K!!$541(?ha^(_}S9@OFf1wHohs zyMz>CEr1SxHd<0000KTy literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_groups@2x.png b/Telegram/Resources/icons/filters_type_groups@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..114156e547ff75bb975e716bbc744f601c7ba27b GIT binary patch literal 984 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*_j|fH zhEy=Vow?ha*-_w_dQ#@X-Xuk#jRGzcM4~dKg1vW%Y}@uv`+pE#J*)9R!$G}g`Ake4 zQ<@lr92yuVENx;DP+&+|sldp>!4RqC!NADmFpZ0_RGsjG7(Msr&!2z!@#DsxA2W0J z@BO&Hc21nLvvXE{zW+?A>^G0AGGz{{`0vDWS@&&xO8dKuRnis z8VzhW@c;hjKC<;HU3b4mAg z7wiz-vUziI%VW+r$$}mF(?9nnEeyC&+<&d9MqErRp+H1_hRTtH?moXG-yW~+wzapf zZcW;=x&LkD_NSY0OxHRY_$1&lqqeg1Yi_Hoy72e@{-aG|cUQlSf8Siyn)vDe%DE+vs-1qR>2@4Q zm~e4hZc0jv(M+DRXU|^z^2MZZe)Qst3$nI)JuRB)=jS)8_IC2|v(I@h967?0x;Zi` zHg@ULr>RmhJo2v|KW;vE?%azPFFN@6`90;mYO}r7CYzX4w{T@RL?<~LNp?l-L>ni!R7Z>{FpSgH3aH;*IV^gkQzn+zxeE8e?$z=>@ z&zxzQx2tzG6Vv=BmfP7g3kwBZG)jw$7k?>_(>~Vy_H!Qlg^wQ%=Tz+Ho3p#NaQX7( z2BP&o;bCDlg>NmZDz-6u?4S5x$?wt!ciwW}x^t&z%BxqePKjTir~GR3*7??L?d@M@ zDQ~lE&08K7DeWe-yJ>kPk7|h8lDsPB z5z*L{qGU|9JjO_6g%mco>;4h<{_y#J&iQ;l=X}rkobNdwn)eYm2uK42001B!?wF&S z9QQ92w`|Vbh=74jl8Z!pp#gx#TqP+%9st-9>48D}rphdYc3g zbN~+{`nz8DU@QnsEL_tQ`;x@O>iHB_Qe}YpS^FG(p!x2F1 zke#q#+dB89schxT+(C(e{OTYyOZn(Yfr-zaA!r$PnJpVv63&v<EUP2Q5p$F zhGFWVWrR%~6zEzU3D=A$l`R?)ssFbYezv;*v17ViF1M$<`%>!p^PsqI>N)6%U$xyA z!!%k&Gc$eF)rRWTXmHOB+ifI##0@`RU%lL%9K^X5)^4JXLXg(Z#g|AEOG`hoSp2tz z^H1Q6qjRK5B@Ov?wt{n#p)dyxt^MqrX;;gwuz5Heu~54@uLhZVH)5}TWAd@k^0uw)*9fi zWj2)6wq9wONDJ#B&4@&ISyWp_`c&^BQ&X?_goMwI@6gw0uOHp+}rc2R)7!*`0l}hO#s8DCW)?QK4wCXNCpC2P_wA@b@ zi{tZ4;2#=vw3o0^Lly>MPZ}F{oQCbE7Zs@bFWu4S5@Gi&Cjc*zEm@P+9Gvdn44?@w2<7&DXtC zQku0iGzwx;TIzV_+hnbH>A{CTK5dptFH}d|iB(UPfW}YXDk;gcv$Nx(y0Wf`>e1c{ zY&P37*{VGomYeCV6l41rtJ46;{K!Mu+ZS;-oF#t!aUGi@0vqwL2D5CuKR{1f#Lq%8-kCZR;T|qV8)My}d4MGu{bTfz)N!&~^n~2EhU6SuoW| z&^s^p=A$k8CW`RetGniQ7)R;Z>c0*T*Y z1^(R=s%J&VL_9&n!OM#D+~eT1m#Hpe<|tE-yfJfV>0)*RAvL&Q&;NR0T}^gKM6hSI RJ=?4xfQQQwOr29$#y`BtDVqQQ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_muted.png b/Telegram/Resources/icons/filters_type_muted.png new file mode 100644 index 0000000000000000000000000000000000000000..92e3b1adc70b7559fb98d11171307b0e7050f54e GIT binary patch literal 576 zcmV-G0>Axh-#+PcV--3^ptli*z^~ zXti2VBoetwTq>1lx7!iS>2zNC1Yz*ZV?c<{=kv7R?-?oopAq1DQj)>J9{`bUMu?LJ7Z{ECm&@^l zU6Q$6E`|yO0yLY=*yZ#2M3j0S2$1L-IWe+djy*v7{XW@jHvNDA1&GOH!hK`6+eMPA z#r%yzaw)A=E4kfnwM0VqOy`YGm-n#G|4;pD9Sw!F6gCtx6w*>a%YOu?(!>=}(J%!7 O0000qTaRTu8>oldW4vyphjY)S;;uF z+>o@$R18zJrR&P&k4t1YoQG&?8EURZnjj{Y&HmpX`{TWL@9zEi?o+~uiSmH^!9gI9 zM>H*xxt6rgg1N4Ff<+Ux77z~kTQUgLT;}$H<+4`Cqa(@jIbciWv*10kUY$#mrO>T` zQ?g?+;F?nT@WFIJ3BQynqcc~X=K>!tLzPoKDuR@T+YuBEYmWk!-Z)aKbjv9Nx@P_U zl--*}!~0iS>=PQ}T%%@K=LcoGmzQnl!rr%Uy4E!Z0nksp0ra|JG^zMDG@1kfQ??(V zIeiI3CjCv`fFz5Nbc9R{b1FJ2ltLnr==cXv*H$eA-|iZ5NV39G1r5{U$^kjv}x8vNFamvadsbE!(${zP-RLJL29@eUAQB@y;vWl6pT-c0imY-$PM@`My zM2@HLVnosb*i|YGfk4P>L?V$?Dy_z|>iqrvJx*jw-oE8_C6=C@UGhP|Jc5FQB`YhU z^@FRcL;#C@?OnLKT96|WnP(cRHoL(v-abBE` zh>=Vd3o|w`!56TVbUIyPFvN1l8m(q92AH0Jak<>^l|_!%wxLsx{G{{#RF~o!onGIv zu)wb+@%zf>Eosl{--U>gV*Zh?Lv;Gc;$mA>j_;{uwr?|!RFc?u|9-`}^XEGRUjzoG z%#x3yP^jyCo~FJYhYASzCqQGqhSA{<6$l$uDtZ?GL;6ebWoVn-Z)9YImz%qMau9mA zi71(en#8+@HXFS0(`XXPC8P*&Wkg0obUIzCWxoEhR45dN(+mc~z4rD#3ukRm z2)Ifc5@|G8H?ApnEZ^1DHT>)GY=^@UPSet9CX?w!E!!)D%bl627v|kWxjo(36A=+1 z$0#&LrKB&J~LreZyE)UyneA9fA-i{}^Cue5IVNo!s%aF2z}+5_UhK1VP?)Sp0W7m~6RD=0DEt?gRbE^G literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_muted@3x.png b/Telegram/Resources/icons/filters_type_muted@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0f0f81dbb4e36ed1580fcb9f10f1134d49996e96 GIT binary patch literal 2101 zcmchZ>08p<7RP^J&Kj9GAGIu;$q^gXNafVM(#blQsUd`t1KNNXnkZRe&d2mPrJ}dg z98t;%O-NI+D9r&&%Q-a3)Es)8>i*7ua9`aQd#@LJth)x zJ$2IcENChJ90fV>P9vXx1DYL|(O5J9)TJqJ5oG~D{_81M^q+Lde7-OV{s&wbGBk+d z7@1<=YW{B$A$I1*aJTnvXs>7V#Yyz8&WG~bGOn&_$L<+n;GOwrY?4eRfFz$9#ou__ zV?ZvgjB2?NRkpd>78k&))DcK z;P{^`q>p4U%*jEvO3#Y05pdfnW-A8~DCurvY(6G-uu!`jeHz2UfBoDRN&19In#c-| z2awL4QwB>=IS5Nd=Y*kC^vR^$n^(sLkl=yG9z}0qy7u?*&!YYw`Ew(Bez^YDa1xM7 z^4nsfRTQ?qeQJ4H)GQwlGc4^&(K}kOxRrk_(w4FQ9nqLjwM>(WBOBEA7WuYJDyl$L zwXK<>ZHxy9iln=aBz$}-!?;=h)e-j58#SwL#!rxtwOr9Xk`iN~MrVLLaTXreC}6*w`5y}#l1 z2!bKnMYM&{f9Ors|*D#fzA)NK8!Rx{o{!%HYwYo}Pt;TJ9HPS7tp1 zo2fHZ-2LoFLMvWIy1tcHEfXvcnL(tleZ4S;eCJOpJ6$k4`s}8d7o*hF8a|$+t+Rh- zG)KCeVZF11E-KGnFYi#CeOqueCeH@HPXLWpOEq*3gTKp6&`ATs^or|4hEiAf*mL#4 z%R9UP0^5mE;n^!v#`9yaIV91F^-NRfNJIEkSBmI0L!S!KOlov>jI3N;V|&*Jm#lnG z?0d3a^RJBy*-@YQG26UKOGTYJz*Bwi>ixDO;j@4BNy#;H;A>pO>KyL8&JUFa!JcTa zmj<5Smj4@H9C59;=Lm`{(x!cWrEy+oMnx>x(~9@>IO2a?RnI}xiFTHr?LFYv_SJkU zpBPP-?$odx8}dP;v?e+OreCIG9;m6&er`qfXf}j>3a;`wsUJOBd6htsi!|3MT3nv# zd1S&$X_v;b!$Gw|Lj3qKB?gXB+S1{#siw|{1;=HcJCKB_D5W%95NDN}ZRK~CgMXGx z+ZRXTA}$C3)pNd6!nA|5tUOc*#^jH}7Xe*&Z17`kj1(FPii{K-pWOVhU?0@C|KXy- z@KWAIbT16*O@f*ZrTRmuMc7g@bwq70WV$MrvYPL?{vu8-3 z?iIPg+=Z9DWR)v{ksNaiin=DP4l_}Qx-rraD)u2_{698b>*C#IvQBD!Jmzo;M!{tt zxaf;MCEVj9q zEIIR{mu*NMsrchfFwYK-V&3>0-Mn|KsMWdj)B&zT!mY!XJlf}6m=W!2NP%idNd`Ui zMBseOR z4c+Bu%wnv5f_sR#%0}!)-dvmCzNg=X>XwnaRY&-O+Q+VprU`xvEG;OQBzj*}$XqZa zTh*>Cn5;u2@1IaU*tP?|Dc-7?}eb%;{1*!gPFG{z9#8HEFS16SGUa-A*xy$l&v+_m9CJ+x1w(RJ}A5 ntv#DEcmJQZ;6ID><1fIBvb#Pr!N?u_2LPwsPP^8eAjJI_rTg-Y literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_noncontacts.png b/Telegram/Resources/icons/filters_type_noncontacts.png new file mode 100644 index 0000000000000000000000000000000000000000..213633ac0ba338618801089897a2c0a455aa2701 GIT binary patch literal 573 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 BtKI+r literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_type_read.png b/Telegram/Resources/icons/filters_type_read.png new file mode 100644 index 0000000000000000000000000000000000000000..689da4ca766231dd4f6c30bf3caef608c9abf0cc GIT binary patch literal 672 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f3f815b62..7984173cc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2240,14 +2240,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_recommended" = "Recommended"; "lng_filters_recommended_add" = "Add"; "lng_filters_restore" = "Undo"; -"lng_filters_new" = "New folder"; +"lng_filters_new" = "New Folder"; +"lng_filters_edit" = "Edit Folder"; "lng_filters_new_name" = "Folder name"; "lng_filters_add_chats" = "Add chats"; -"lng_filters_include" = "Include"; +"lng_filters_include" = "Included Chats"; "lng_filters_include_about" = "Choose chats and types of chats that will appear in this folder."; -"lng_filters_exclude" = "Exclude"; +"lng_filters_exclude" = "Excluded Chats"; "lng_filters_exclude_about" = "Choose chats and types of chats that will never appear in this folder."; +"lng_filters_create_button" = "Create"; "lng_filters_add_title" = "Add Chats"; +"lng_filters_include_title" = "Include Chats"; +"lng_filters_exclude_title" = "Exclude Chats"; "lng_filters_edit_types" = "Chat types"; "lng_filters_edit_chats" = "Chats"; "lng_filters_include_contacts" = "Contacts"; @@ -2260,6 +2264,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_exclude_archived" = "Archived"; "lng_filters_add" = "Done"; "lng_filters_limit" = "Sorry, you have reached folders limit."; +"lng_filters_empty" = "Please choose at least one chat for this folder."; +"lng_filters_default" = "Please change at least one rule for this folder."; +"lng_filters_type_contacts" = "Contacts"; +"lng_filters_type_non_contacts" = "Non-Contacts"; +"lng_filters_type_groups" = "Groups"; +"lng_filters_type_channels" = "Channels"; +"lng_filters_type_bots" = "Bots"; +"lng_filters_type_no_archived" = "No Archived"; +"lng_filters_type_no_muted" = "No Muted"; +"lng_filters_type_no_read" = "No Read"; // Wnd specific diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/manage_filters_box.cpp index b0726e85e..3f194157d 100644 --- a/Telegram/SourceFiles/boxes/manage_filters_box.cpp +++ b/Telegram/SourceFiles/boxes/manage_filters_box.cpp @@ -10,14 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_chat_filters.h" #include "data/data_folder.h" +#include "data/data_peer.h" +#include "history/history.h" #include "main/main_session.h" #include "window/window_session_controller.h" #include "window/window_controller.h" #include "ui/layers/generic_box.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "ui/text/text_utilities.h" #include "ui/wrap/slide_wrap.h" +#include "ui/painter.h" #include "settings/settings_common.h" #include "lang/lang_keys.h" #include "apiwrap.h" @@ -25,11 +29,72 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" +#include "styles/style_window.h" namespace { constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000); constexpr auto kFiltersLimit = 10; +constexpr auto kMaxFilterTitleLength = 20; + +using namespace Settings; + +using Flag = Data::ChatFilter::Flag; +using Flags = Data::ChatFilter::Flags; +using ExceptionPeersRef = const base::flat_set> &; +using ExceptionPeersGetter = ExceptionPeersRef(Data::ChatFilter::*)() const; + +constexpr auto kAllTypes = { + Flag::Contacts, + Flag::NonContacts, + Flag::Groups, + Flag::Channels, + Flag::Bots, + Flag::NoMuted, + Flag::NoArchived, + Flag::NoRead +}; + +class FilterChatsPreview final : public Ui::RpWidget { +public: + FilterChatsPreview( + not_null parent, + Flags flags, + const base::flat_set> &peers); + + [[nodiscard]] rpl::producer flagRemoved() const; + [[nodiscard]] rpl::producer> peerRemoved() const; + + int resizeGetHeight(int newWidth) override; + +private: + using Button = base::unique_qptr; + struct FlagButton { + Flag flag = Flag(); + Button button; + }; + struct PeerButton { + not_null history; + Button button; + }; + + void paintEvent(QPaintEvent *e) override; + + void setup( + Flags flags, + const base::flat_set> &peers); + void refresh(); + void removeFlag(Flag flag); + void removePeer(not_null history); + void paintFlagIcon(QPainter &p, int left, int top, Flag flag) const; + + std::vector _removeFlag; + std::vector _removePeer; + + rpl::event_stream _flagRemoved; + rpl::event_stream> _peerRemoved; + +}; class FilterRowButton final : public Ui::RippleButton { public: @@ -43,6 +108,7 @@ public: const QString &description); void setRemoved(bool removed); + void updateData(const Data::ChatFilter &filter); [[nodiscard]] rpl::producer<> removeRequests() const; [[nodiscard]] rpl::producer<> restoreRequests() const; @@ -57,6 +123,7 @@ private: FilterRowButton( not_null parent, + Main::Session *session, const Data::ChatFilter &filter, const QString &description, State state); @@ -67,6 +134,8 @@ private: void setState(State state, bool force = false); void updateButtonsVisibility(); + Main::Session *_session = nullptr; + Ui::IconButton _remove; Ui::RoundButton _restore; Ui::RoundButton _add; @@ -101,10 +170,16 @@ private: [[nodiscard]] int ComputeCount( not_null session, - const Data::ChatFilter &filter) { + const Data::ChatFilter &filter, + bool check = false) { const auto &list = session->data().chatsFilters().list(); const auto id = filter.id(); - if (ranges::contains(list, id, &Data::ChatFilter::id)) { + const auto i = ranges::find(list, id, &Data::ChatFilter::id); + if (i != end(list) + && (!check + || (i->flags() == filter.flags() + && i->always() == filter.always() + && i->never() == filter.never()))) { const auto chats = session->data().chatsFilters().chatsList(id); return chats->indexed()->size(); } @@ -113,19 +188,188 @@ private: [[nodiscard]] QString ComputeCountString( not_null session, - const Data::ChatFilter &filter) { - const auto count = ComputeCount(session, filter); + const Data::ChatFilter &filter, + bool check = false) { + const auto count = ComputeCount(session, filter, check); return count ? tr::lng_filters_chats_count(tr::now, lt_count_short, count) : tr::lng_filters_no_chats(tr::now); } +FilterChatsPreview::FilterChatsPreview( + not_null parent, + Flags flags, + const base::flat_set> &peers) +: RpWidget(parent) { + setup(flags, peers); +} + +void FilterChatsPreview::setup( + Flags flags, + const base::flat_set> &peers) { + const auto makeButton = [&](Fn handler) { + auto result = base::make_unique_q( + this, + st::windowFilterSmallRemove); + result->setClickedCallback(std::move(handler)); + return result; + }; + for (const auto flag : kAllTypes) { + if (flags & flag) { + _removeFlag.push_back({ + flag, + makeButton([=] { removeFlag(flag); }) }); + } + } + for (const auto history : peers) { + _removePeer.push_back({ + history, + makeButton([=] { removePeer(history); }) }); + } + refresh(); +} + +void FilterChatsPreview::refresh() { + resizeToWidth(width()); +} + +int FilterChatsPreview::resizeGetHeight(int newWidth) { + const auto right = st::windowFilterSmallRemoveRight; + const auto add = (st::windowFilterSmallItem.height + - st::windowFilterSmallRemove.height) / 2; + auto top = 0; + const auto moveNextButton = [&](not_null button) { + button->moveToRight(right, top + add, newWidth); + top += st::windowFilterSmallItem.height; + }; + for (const auto &[flag, button] : _removeFlag) { + moveNextButton(button.get()); + } + for (const auto &[history, button] : _removePeer) { + moveNextButton(button.get()); + } + return top; +} + +[[nodiscard]] QString TypeName(Flag flag) { + switch (flag) { + case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now); + case Flag::NonContacts: + return tr::lng_filters_type_non_contacts(tr::now); + case Flag::Groups: return tr::lng_filters_type_groups(tr::now); + case Flag::Channels: return tr::lng_filters_type_channels(tr::now); + case Flag::Bots: return tr::lng_filters_type_bots(tr::now); + case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now); + case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now); + case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now); + } + Unexpected("Flag in TypeName."); +} + +void FilterChatsPreview::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + auto top = 0; + const auto &st = st::windowFilterSmallItem; + const auto iconLeft = st.photoPosition.x(); + const auto iconTop = st.photoPosition.y(); + const auto nameLeft = st.namePosition.x(); + p.setFont(st::windowFilterSmallItem.nameStyle.font); + const auto nameTop = st.namePosition.y(); + for (const auto &[flag, button] : _removeFlag) { + paintFlagIcon(p, iconLeft, top + iconTop, flag); + + p.setPen(st::contactsNameFg); + p.drawTextLeft(nameLeft, top + nameTop, width(), TypeName(flag)); + top += st.height; + } + for (const auto &[history, button] : _removePeer) { + history->peer->paintUserpicLeft( + p, + iconLeft, + top + iconTop, + width(), + st.photoSize); + history->peer->nameText().drawLeftElided( + p, + nameLeft, + top + nameTop, + button->x() - nameLeft, + width()); + top += st.height; + } +} + +void FilterChatsPreview::paintFlagIcon( + QPainter &p, + int left, + int top, + Flag flag) const { + const auto &color = [&]() -> const style::color& { + switch (flag) { + case Flag::Contacts: return st::historyPeer4UserpicBg; + case Flag::NonContacts: return st::historyPeer7UserpicBg; + case Flag::Groups: return st::historyPeer2UserpicBg; + case Flag::Channels: return st::historyPeer1UserpicBg; + case Flag::Bots: return st::historyPeer6UserpicBg; + case Flag::NoMuted: return st::historyPeer6UserpicBg; + case Flag::NoArchived: return st::historyPeer4UserpicBg; + case Flag::NoRead: return st::historyPeer7UserpicBg; + } + Unexpected("Flag in color paintFlagIcon."); + }(); + const auto &icon = [&]() -> const style::icon& { + switch (flag) { + case Flag::Contacts: return st::windowFilterTypeContacts; + case Flag::NonContacts: return st::windowFilterTypeNonContacts; + case Flag::Groups: return st::windowFilterTypeGroups; + case Flag::Channels: return st::windowFilterTypeChannels; + case Flag::Bots: return st::windowFilterTypeBots; + case Flag::NoMuted: return st::windowFilterTypeNoMuted; + case Flag::NoArchived: return st::windowFilterTypeNoArchived; + case Flag::NoRead: return st::windowFilterTypeNoRead; + } + Unexpected("Flag in icon paintFlagIcon."); + }(); + const auto size = st::windowFilterSmallItem.photoSize; + const auto rect = QRect(left, top, size, size); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(color->b); + p.setPen(Qt::NoPen); + p.drawEllipse(rect); + icon.paintInCenter(p, rect); +} + +void FilterChatsPreview::removeFlag(Flag flag) { + const auto i = ranges::find(_removeFlag, flag, &FlagButton::flag); + Assert(i != end(_removeFlag)); + _removeFlag.erase(i); + refresh(); + _flagRemoved.fire_copy(flag); +} + +void FilterChatsPreview::removePeer(not_null history) { + const auto i = ranges::find(_removePeer, history, &PeerButton::history); + Assert(i != end(_removePeer)); + _removePeer.erase(i); + refresh(); + _peerRemoved.fire_copy(history); +} + +rpl::producer FilterChatsPreview::flagRemoved() const { + return _flagRemoved.events(); +} + +rpl::producer> FilterChatsPreview::peerRemoved() const { + return _peerRemoved.events(); +} + FilterRowButton::FilterRowButton( not_null parent, not_null session, const Data::ChatFilter &filter) : FilterRowButton( parent, + session, filter, ComputeCountString(session, filter), State::Normal) { @@ -135,15 +379,17 @@ FilterRowButton::FilterRowButton( not_null parent, const Data::ChatFilter &filter, const QString &description) -: FilterRowButton(parent, filter, description, State::Suggested) { +: FilterRowButton(parent, nullptr, filter, description, State::Suggested) { } FilterRowButton::FilterRowButton( not_null parent, + Main::Session *session, const Data::ChatFilter &filter, const QString &status, State state) : RippleButton(parent, st::defaultRippleAnimation) +, _session(session) , _remove(this, st::filtersRemove) , _restore(this, tr::lng_filters_restore(), st::stickersUndoRemove) , _add(this, tr::lng_filters_recommended_add(), st::stickersTrendingAdd) @@ -155,6 +401,14 @@ void FilterRowButton::setRemoved(bool removed) { setState(removed ? State::Removed : State::Normal); } +void FilterRowButton::updateData(const Data::ChatFilter &filter) { + Expects(_session != nullptr); + + _title.setText(st::contactsNameStyle, filter.title()); + _status = ComputeCountString(_session, filter, true); + update(); +} + void FilterRowButton::setState(State state, bool force) { if (!force && _state == state) { return; @@ -292,13 +546,15 @@ void ManageFiltersPrepare::showBox() { } void ManageFiltersPrepare::showBoxWithSuggested() { - _window->window().show(Box(CreateBox, _window, _suggested)); + _window->window().show(Box(SetupBox, _window, _suggested)); } -void ManageFiltersPrepare::CreateBox( +void ManageFiltersPrepare::SetupBox( not_null box, not_null window, const std::vector &suggestions) { + box->setTitle(tr::lng_filters_title()); + struct FilterRow { not_null button; Data::ChatFilter filter; @@ -306,11 +562,9 @@ void ManageFiltersPrepare::CreateBox( bool added = false; }; - box->setTitle(tr::lng_filters_title()); - const auto session = &window->session(); const auto content = box->verticalLayout(); - Settings::AddSubsectionTitle(content, tr::lng_filters_subtitle()); + AddSubsectionTitle(content, tr::lng_filters_subtitle()); const auto rows = box->lifetime().make_state>(); const auto find = [=](not_null button) { @@ -319,8 +573,14 @@ void ManageFiltersPrepare::CreateBox( return &*i; }; const auto countNonRemoved = [=] { + }; + const auto showLimitReached = [=] { const auto removed = ranges::count_if(*rows, &FilterRow::removed); - return rows->size() - removed; + if (rows->size() < kFiltersLimit + removed) { + return false; + } + window->window().showToast(tr::lng_filters_limit(tr::now)); + return true; }; const auto wrap = content->add(object_ptr(content)); const auto addFilter = [=](const Data::ChatFilter &filter) { @@ -333,11 +593,27 @@ void ManageFiltersPrepare::CreateBox( }, button->lifetime()); button->restoreRequests( ) | rpl::start_with_next([=] { - if (countNonRemoved() < kFiltersLimit) { - button->setRemoved(false); - find(button)->removed = false; + if (showLimitReached()) { + return; } + button->setRemoved(false); + find(button)->removed = false; }, button->lifetime()); + button->setClickedCallback([=] { + const auto found = find(button); + if (found->removed) { + return; + } + const auto doneCallback = [=](const Data::ChatFilter &result) { + find(button)->filter = result; + button->updateData(result); + }; + window->window().show(Box( + EditBox, + window, + found->filter, + crl::guard(button, doneCallback))); + }); rows->push_back({ button, filter }); }; const auto &list = session->data().chatsFilters().list(); @@ -345,11 +621,24 @@ void ManageFiltersPrepare::CreateBox( addFilter(filter); } - Settings::AddButton( + AddButton( content, tr::lng_filters_create() | Ui::Text::ToUpper(), - st::settingsUpdate); - Settings::AddSkip(content); + st::settingsUpdate + )->setClickedCallback([=] { + if (showLimitReached()) { + return; + } + const auto doneCallback = [=](const Data::ChatFilter &result) { + addFilter(result); + }; + window->window().show(Box( + EditBox, + window, + Data::ChatFilter(), + crl::guard(box, doneCallback))); + }); + AddSkip(content); const auto emptyAbout = content->add( object_ptr>( content, @@ -365,24 +654,15 @@ void ManageFiltersPrepare::CreateBox( object_ptr(content)) )->setDuration(0); const auto aboutRows = nonEmptyAbout->entity(); - Settings::AddDividerText(aboutRows, tr::lng_filters_about()); - Settings::AddSkip(aboutRows); - Settings::AddSubsectionTitle(aboutRows, tr::lng_filters_recommended()); + AddDividerText(aboutRows, tr::lng_filters_about()); + AddSkip(aboutRows); + AddSubsectionTitle(aboutRows, tr::lng_filters_recommended()); + const auto changed = box->lifetime().make_state(); const auto suggested = box->lifetime().make_state>(); for (const auto &suggestion : suggestions) { - const auto filter = suggestion.filter; - const auto already = [&] { - for (const auto &entry : list) { - if (entry.flags() == filter.flags() - && entry.always() == filter.always() - && entry.never() == filter.never()) { - return true; - } - } - return false; - }(); - if (already) { + const auto &filter = suggestion.filter; + if (ranges::contains(list, filter)) { continue; } *suggested = suggested->current() + 1; @@ -392,6 +672,9 @@ void ManageFiltersPrepare::CreateBox( suggestion.description)); button->addRequests( ) | rpl::start_with_next([=] { + if (showLimitReached()) { + return; + } addFilter(filter); *suggested = suggested->current() - 1; delete button; @@ -427,15 +710,19 @@ void ManageFiltersPrepare::CreateBox( const auto save = [=] { auto ids = prepareGoodIdsForNewFilters(); - auto requests = std::deque(); + using Requests = std::vector; + auto addRequests = Requests(), removeRequests = Requests(); auto &realFilters = session->data().chatsFilters(); const auto &list = realFilters.list(); auto order = QVector(); for (const auto &row : *rows) { const auto id = row.filter.id(); const auto removed = row.removed; - if (removed - && !ranges::contains(list, id, &Data::ChatFilter::id)) { + const auto i = ranges::find(list, id, &Data::ChatFilter::id); + if (removed && i == end(list)) { + continue; + } else if (!removed && i != end(list) && *i == row.filter) { + order.push_back(MTP_int(id)); continue; } const auto newId = ids.take(id).value_or(id); @@ -447,9 +734,9 @@ void ManageFiltersPrepare::CreateBox( MTP_int(newId), tl); if (removed) { - requests.push_front(request); + removeRequests.push_back(request); } else { - requests.push_back(request); + addRequests.push_back(request); order.push_back(MTP_int(newId)); } realFilters.apply(MTP_updateDialogFilter( @@ -460,12 +747,13 @@ void ManageFiltersPrepare::CreateBox( tl)); } auto previousId = mtpRequestId(0); + auto &&requests = ranges::view::concat(removeRequests, addRequests); for (auto &request : requests) { previousId = session->api().request( std::move(request) ).afterRequest(previousId).send(); } - if (!order.isEmpty()) { + if (!order.isEmpty() && !addRequests.empty()) { realFilters.apply( MTP_updateDialogFilterOrder(MTP_vector(order))); session->api().request(MTPmessages_UpdateDialogFiltersOrder( @@ -474,6 +762,148 @@ void ManageFiltersPrepare::CreateBox( } box->closeBox(); }; - box->addButton(tr::lng_settings_save(), save); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + box->boxClosing() | rpl::start_with_next(save, box->lifetime()); + box->addButton(tr::lng_about_done(), [=] { box->closeBox(); }); } + +void SetupChatsPreview( + not_null content, + not_null data, + Flags flags, + ExceptionPeersGetter peers) { + const auto preview = content->add(object_ptr( + content, + data->flags() & flags, + (data->*peers)())); + + preview->flagRemoved( + ) | rpl::start_with_next([=](Flag flag) { + *data = Data::ChatFilter( + data->id(), + data->title(), + (data->flags() & ~flag), + data->always(), + data->never()); + }, preview->lifetime()); + + preview->peerRemoved( + ) | rpl::start_with_next([=](not_null history) { + auto always = data->always(); + auto never = data->never(); + always.remove(history); + never.remove(history); + *data = Data::ChatFilter( + data->id(), + data->title(), + data->flags(), + std::move(always), + std::move(never)); + }, preview->lifetime()); +} + +void ManageFiltersPrepare::EditBox( + not_null box, + not_null window, + const Data::ChatFilter &filter, + Fn doneCallback) { + const auto creating = filter.title().isEmpty(); + box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit()); + + const auto content = box->verticalLayout(); + const auto name = content->add( + object_ptr( + box, + st::defaultInputField, + tr::lng_filters_new_name(), + filter.title()), + st::markdownLinkFieldPadding); + name->setMaxLength(kMaxFilterTitleLength); + + const auto data = box->lifetime().make_state(filter); + + constexpr auto kTypes = Flag::Contacts + | Flag::NonContacts + | Flag::Groups + | Flag::Channels + | Flag::Bots; + constexpr auto kExcludeTypes = Flag::NoMuted + | Flag::NoArchived + | Flag::NoRead; + + box->setFocusCallback([=] { + name->setFocusFast(); + }); + + AddSkip(content); + AddDivider(content); + AddSkip(content); + AddSubsectionTitle(content, tr::lng_filters_include()); + + SetupChatsPreview( + content, + data, + kTypes, + &Data::ChatFilter::always); + + AddButton( + content, + tr::lng_filters_add_chats() | Ui::Text::ToUpper(), + st::settingsUpdate + )->setClickedCallback([=] { + }); + + AddSkip(content); + AddDividerText(content, tr::lng_filters_include_about()); + AddSkip(content); + + AddSubsectionTitle(content, tr::lng_filters_exclude()); + + SetupChatsPreview( + content, + data, + kExcludeTypes, + &Data::ChatFilter::never); + + AddButton( + content, + tr::lng_filters_add_chats() | Ui::Text::ToUpper(), + st::settingsUpdate + )->setClickedCallback([=] { + }); + AddSkip(content); + content->add( + object_ptr( + content, + tr::lng_filters_exclude_about(), + st::boxDividerLabel), + st::settingsDividerLabelPadding); + + const auto save = [=] { + const auto title = name->getLastText().trimmed(); + if (title.isEmpty()) { + name->showError(); + return; + } else if (!(data->flags() & kTypes) && data->always().empty()) { + window->window().showToast(tr::lng_filters_empty(tr::now)); + return; + } else if ((data->flags() == (kTypes | Flag::NoArchived)) + && data->always().empty() + && data->never().empty()) { + window->window().showToast(tr::lng_filters_default(tr::now)); + return; + } + const auto result = Data::ChatFilter( + data->id(), + title, + data->flags(), + data->always(), + data->never()); + box->closeBox(); + + doneCallback(result); + }; + box->addButton( + creating ? tr::lng_filters_create_button() : tr::lng_settings_save(), + save); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} \ No newline at end of file diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.h b/Telegram/SourceFiles/boxes/manage_filters_box.h index 1b4bd94e5..9900927e5 100644 --- a/Telegram/SourceFiles/boxes/manage_filters_box.h +++ b/Telegram/SourceFiles/boxes/manage_filters_box.h @@ -34,10 +34,15 @@ private: }; void showBoxWithSuggested(); - static void CreateBox( + static void SetupBox( not_null box, not_null window, const std::vector &suggested); + static void EditBox( + not_null box, + not_null window, + const Data::ChatFilter &filter, + Fn doneCallback); const not_null _window; const not_null _api; diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 7dfe88416..c015de468 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -40,11 +40,11 @@ ChatFilter ChatFilter::FromTL( const auto flags = (data.is_contacts() ? Flag::Contacts : Flag(0)) | (data.is_non_contacts() ? Flag::NonContacts : Flag(0)) | (data.is_groups() ? Flag::Groups : Flag(0)) - | (data.is_broadcasts() ? Flag::Broadcasts : Flag(0)) + | (data.is_broadcasts() ? Flag::Channels : Flag(0)) | (data.is_bots() ? Flag::Bots : Flag(0)) | (data.is_exclude_muted() ? Flag::NoMuted : Flag(0)) | (data.is_exclude_read() ? Flag::NoRead : Flag(0)) - | (data.is_exclude_archived() ? Flag::NoArchive : Flag(0)); + | (data.is_exclude_archived() ? Flag::NoArchived : Flag(0)); auto &&to_histories = ranges::view::transform([&]( const MTPInputPeer &data) { const auto peer = data.match([&](const MTPDinputPeerUser &data) { @@ -87,11 +87,11 @@ MTPDialogFilter ChatFilter::tl() const { | ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0)) | ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0)) | ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0)) - | ((_flags & Flag::Broadcasts) ? TLFlag::f_broadcasts : TLFlag(0)) + | ((_flags & Flag::Channels) ? TLFlag::f_broadcasts : TLFlag(0)) | ((_flags & Flag::Bots) ? TLFlag::f_bots : TLFlag(0)) | ((_flags & Flag::NoMuted) ? TLFlag::f_exclude_muted : TLFlag(0)) | ((_flags & Flag::NoRead) ? TLFlag::f_exclude_read : TLFlag(0)) - | ((_flags & Flag::NoArchive) + | ((_flags & Flag::NoArchived) ? TLFlag::f_exclude_archived : TLFlag(0)); auto always = QVector(); @@ -147,7 +147,7 @@ bool ChatFilter::contains(not_null history) const { return Flag::Groups; } else if (const auto channel = peer->asChannel()) { if (channel->isBroadcast()) { - return Flag::Broadcasts; + return Flag::Channels; } else { return Flag::Groups; } @@ -162,7 +162,7 @@ bool ChatFilter::contains(not_null history) const { || ((_flags & flag) && (!(_flags & Flag::NoMuted) || !history->mute()) && (!(_flags & Flag::NoRead) || history->unreadCountForBadge()) - && (!(_flags & Flag::NoArchive) + && (!(_flags & Flag::NoArchived) || (history->folderKnown() && !history->folder()))) || _always.contains(history); } @@ -172,9 +172,9 @@ ChatFilters::ChatFilters(not_null owner) : _owner(owner) { //const auto all = Flag::Contacts // | Flag::NonContacts // | Flag::Groups - // | Flag::Broadcasts + // | Flag::Channels // | Flag::Bots - // | Flag::NoArchive; + // | Flag::NoArchived; //_list.push_back( // ChatFilter(1, "Unmuted", all | Flag::NoMuted, {}, {})); //_list.push_back( diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index ca9735200..7e6b2775d 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -25,11 +25,11 @@ public: Contacts = 0x01, NonContacts = 0x02, Groups = 0x04, - Broadcasts = 0x08, + Channels = 0x08, Bots = 0x10, NoMuted = 0x20, NoRead = 0x40, - NoArchive = 0x80, + NoArchived = 0x80, }; friend constexpr inline bool is_flag_type(Flag) { return true; }; using Flags = base::flags; @@ -64,6 +64,17 @@ private: }; +inline bool operator==(const ChatFilter &a, const ChatFilter &b) { + return (a.title() == b.title()) + && (a.flags() == b.flags()) + && (a.always() == b.always()) + && (a.never() == b.never()); +} + +inline bool operator!=(const ChatFilter &a, const ChatFilter &b) { + return !(a == b); +} + class ChatFilters final { public: explicit ChatFilters(not_null owner); diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 26ef1bf9f..7fb7c92e1 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -1173,14 +1173,6 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting if (!_checkStreamStatus(stream)) return false; Global::SetDialogsFiltersEnabled(enabled == 1); - auto mode = FilterId(0); - if (enabled) { - mode = FilterId(modeInt); - if (mode == 1) { // #TODO filters - - } - } - Global::SetDialogsFilterId(mode); } break; case dbiModerateMode: { diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index bbbd6c1fd..aaabe5800 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -299,6 +299,23 @@ windowFiltersCustom: SideBarButton(windowFiltersButton) { windowFiltersSetup: SideBarButton(windowFiltersButton) { icon: icon {{ "filters_setup", sideBarIconFg }}; } +windowFilterSmallItem: PeerListItem(defaultPeerListItem) { + height: 44px; + photoPosition: point(15px, 5px); + namePosition: point(62px, 14px); + photoSize: 34px; +} +windowFilterSmallRemove: IconButton(notifyClose) { +} +windowFilterSmallRemoveRight: 10px; +windowFilterTypeContacts: icon {{ "filters_type_contacts", historyPeerUserpicFg }}; +windowFilterTypeNonContacts: icon {{ "filters_type_noncontacts", historyPeerUserpicFg }}; +windowFilterTypeGroups: icon {{ "filters_type_groups", historyPeerUserpicFg }}; +windowFilterTypeChannels: icon {{ "filters_type_channels", historyPeerUserpicFg }}; +windowFilterTypeBots: icon {{ "filters_type_bots", historyPeerUserpicFg }}; +windowFilterTypeNoMuted: icon {{ "filters_type_muted", historyPeerUserpicFg }}; +windowFilterTypeNoArchived: icon {{ "filters_type_archived", historyPeerUserpicFg }}; +windowFilterTypeNoRead: icon {{ "filters_type_read", historyPeerUserpicFg }}; // Mac specific diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index 766d25a2f..b754c9a2f 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_account.h" #include "ui/layers/box_content.h" #include "ui/layers/layer_widget.h" +#include "ui/toast/toast.h" #include "window/window_session_controller.h" #include "window/themes/window_theme.h" #include "window/themes/window_theme_editor.h" @@ -80,6 +81,10 @@ void Controller::showSettings() { _widget.showSettings(); } +void Controller::showToast(const QString &text) { + Ui::Toast::Show(_widget.bodyWidget(), text); +} + void Controller::showBox( object_ptr content, Ui::LayerOptions options, diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h index 289d6d390..75953a07c 100644 --- a/Telegram/SourceFiles/window/window_controller.h +++ b/Telegram/SourceFiles/window/window_controller.h @@ -52,6 +52,7 @@ public: showBox(std::move(content), options, animated); return result; } + void showToast(const QString &text); void showRightColumn(object_ptr widget); void sideBarChanged(); diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 844bb2604..7e9bd6141 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -37,11 +37,11 @@ enum class Type { const auto all = Flag::Contacts | Flag::NonContacts | Flag::Groups - | Flag::Broadcasts + | Flag::Channels | Flag::Bots; const auto removed = Flag::NoRead | Flag::NoMuted; const auto people = Flag::Contacts | Flag::NonContacts; - const auto allNoArchive = all | Flag::NoArchive; + const auto allNoArchive = all | Flag::NoArchived; if (!filter.always().empty() || !filter.never().empty() || !(filter.flags() & all)) { @@ -52,7 +52,7 @@ enum class Type { return Type::People; } else if ((filter.flags() & all) == Flag::Groups) { return Type::Groups; - } else if ((filter.flags() & all) == Flag::Broadcasts) { + } else if ((filter.flags() & all) == Flag::Channels) { return Type::Channels; } else if ((filter.flags() & all) == Flag::Bots) { return Type::Bots; From 13fe0b6272cff90d93f7c9fe63e5eac1e821bd22 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Mar 2020 15:07:27 +0400 Subject: [PATCH 027/115] Allow adding chats to filter exceptions. --- Telegram/CMakeLists.txt | 8 +- .../boxes/filters/edit_filter_box.cpp | 466 ++++++++++++++++++ .../boxes/filters/edit_filter_box.h | 26 + .../boxes/filters/edit_filter_chats_list.cpp | 66 +++ .../boxes/filters/edit_filter_chats_list.h | 55 +++ .../{ => filters}/manage_filters_box.cpp | 372 +------------- .../boxes/{ => filters}/manage_filters_box.h | 5 - .../window/window_filters_menu.cpp | 2 +- 8 files changed, 624 insertions(+), 376 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp create mode 100644 Telegram/SourceFiles/boxes/filters/edit_filter_box.h create mode 100644 Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp create mode 100644 Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h rename Telegram/SourceFiles/boxes/{ => filters}/manage_filters_box.cpp (60%) rename Telegram/SourceFiles/boxes/{ => filters}/manage_filters_box.h (84%) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6cee0790b..b8c24968d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -146,6 +146,12 @@ PRIVATE api/api_single_message_search.h api/api_text_entities.cpp api/api_text_entities.h + boxes/filters/edit_filter_box.cpp + boxes/filters/edit_filter_box.h + boxes/filters/edit_filter_chats_list.cpp + boxes/filters/edit_filter_chats_list.h + boxes/filters/manage_filters_box.cpp + boxes/filters/manage_filters_box.h boxes/peers/add_participants_box.cpp boxes/peers/add_participants_box.h boxes/peers/edit_contact_box.cpp @@ -204,8 +210,6 @@ PRIVATE boxes/language_box.h boxes/local_storage_box.cpp boxes/local_storage_box.h - boxes/manage_filters_box.cpp - boxes/manage_filters_box.h boxes/mute_settings_box.cpp boxes/mute_settings_box.h boxes/peer_list_box.cpp diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp new file mode 100644 index 000000000..027968e11 --- /dev/null +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -0,0 +1,466 @@ +/* +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/filters/edit_filter_box.h" + +#include "boxes/filters/edit_filter_chats_list.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "data/data_chat_filters.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "settings/settings_common.h" +#include "lang/lang_keys.h" +#include "history/history.h" +#include "main/main_session.h" +#include "window/window_session_controller.h" +#include "window/window_controller.h" +#include "styles/style_settings.h" +#include "styles/style_boxes.h" +#include "styles/style_layers.h" +#include "styles/style_window.h" + +namespace { + +using namespace Settings; + +constexpr auto kMaxFilterTitleLength = 20; + +using Flag = Data::ChatFilter::Flag; +using Flags = Data::ChatFilter::Flags; +using ExceptionPeersRef = const base::flat_set> &; +using ExceptionPeersGetter = ExceptionPeersRef(Data::ChatFilter::*)() const; + +constexpr auto kAllTypes = { + Flag::Contacts, + Flag::NonContacts, + Flag::Groups, + Flag::Channels, + Flag::Bots, + Flag::NoMuted, + Flag::NoArchived, + Flag::NoRead +}; + +class FilterChatsPreview final : public Ui::RpWidget { +public: + FilterChatsPreview( + not_null parent, + Flags flags, + const base::flat_set> &peers); + + [[nodiscard]] rpl::producer flagRemoved() const; + [[nodiscard]] rpl::producer> peerRemoved() const; + + void updateData( + Flags flags, + const base::flat_set> &peers); + + int resizeGetHeight(int newWidth) override; + +private: + using Button = base::unique_qptr; + struct FlagButton { + Flag flag = Flag(); + Button button; + }; + struct PeerButton { + not_null history; + Button button; + }; + + void paintEvent(QPaintEvent *e) override; + + void setup( + Flags flags, + const base::flat_set> &peers); + void refresh(); + void removeFlag(Flag flag); + void removePeer(not_null history); + void paintFlagIcon(QPainter &p, int left, int top, Flag flag) const; + + std::vector _removeFlag; + std::vector _removePeer; + + rpl::event_stream _flagRemoved; + rpl::event_stream> _peerRemoved; + +}; + +not_null SetupChatsPreview( + not_null content, + not_null data, + Flags flags, + ExceptionPeersGetter peers) { + const auto preview = content->add(object_ptr( + content, + data->flags() & flags, + (data->*peers)())); + + preview->flagRemoved( + ) | rpl::start_with_next([=](Flag flag) { + *data = Data::ChatFilter( + data->id(), + data->title(), + (data->flags() & ~flag), + data->always(), + data->never()); + }, preview->lifetime()); + + preview->peerRemoved( + ) | rpl::start_with_next([=](not_null history) { + auto always = data->always(); + auto never = data->never(); + always.remove(history); + never.remove(history); + *data = Data::ChatFilter( + data->id(), + data->title(), + data->flags(), + std::move(always), + std::move(never)); + }, preview->lifetime()); + + return preview; +} + +FilterChatsPreview::FilterChatsPreview( + not_null parent, + Flags flags, + const base::flat_set> &peers) +: RpWidget(parent) { + updateData(flags, peers); +} + +void FilterChatsPreview::refresh() { + resizeToWidth(width()); +} + +void FilterChatsPreview::updateData( + Flags flags, + const base::flat_set> &peers) { + _removeFlag.clear(); + _removePeer.clear(); + const auto makeButton = [&](Fn handler) { + auto result = base::make_unique_q( + this, + st::windowFilterSmallRemove); + result->setClickedCallback(std::move(handler)); + return result; + }; + for (const auto flag : kAllTypes) { + if (flags & flag) { + _removeFlag.push_back({ + flag, + makeButton([=] { removeFlag(flag); }) }); + } + } + for (const auto history : peers) { + _removePeer.push_back({ + history, + makeButton([=] { removePeer(history); }) }); + } + refresh(); +} + +int FilterChatsPreview::resizeGetHeight(int newWidth) { + const auto right = st::windowFilterSmallRemoveRight; + const auto add = (st::windowFilterSmallItem.height + - st::windowFilterSmallRemove.height) / 2; + auto top = 0; + const auto moveNextButton = [&](not_null button) { + button->moveToRight(right, top + add, newWidth); + top += st::windowFilterSmallItem.height; + }; + for (const auto &[flag, button] : _removeFlag) { + moveNextButton(button.get()); + } + for (const auto &[history, button] : _removePeer) { + moveNextButton(button.get()); + } + return top; +} + +[[nodiscard]] QString TypeName(Flag flag) { + switch (flag) { + case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now); + case Flag::NonContacts: + return tr::lng_filters_type_non_contacts(tr::now); + case Flag::Groups: return tr::lng_filters_type_groups(tr::now); + case Flag::Channels: return tr::lng_filters_type_channels(tr::now); + case Flag::Bots: return tr::lng_filters_type_bots(tr::now); + case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now); + case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now); + case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now); + } + Unexpected("Flag in TypeName."); +} + +void FilterChatsPreview::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + auto top = 0; + const auto &st = st::windowFilterSmallItem; + const auto iconLeft = st.photoPosition.x(); + const auto iconTop = st.photoPosition.y(); + const auto nameLeft = st.namePosition.x(); + p.setFont(st::windowFilterSmallItem.nameStyle.font); + const auto nameTop = st.namePosition.y(); + for (const auto &[flag, button] : _removeFlag) { + paintFlagIcon(p, iconLeft, top + iconTop, flag); + + p.setPen(st::contactsNameFg); + p.drawTextLeft(nameLeft, top + nameTop, width(), TypeName(flag)); + top += st.height; + } + for (const auto &[history, button] : _removePeer) { + history->peer->paintUserpicLeft( + p, + iconLeft, + top + iconTop, + width(), + st.photoSize); + p.setPen(st::contactsNameFg); + history->peer->nameText().drawLeftElided( + p, + nameLeft, + top + nameTop, + button->x() - nameLeft, + width()); + top += st.height; + } +} + +void FilterChatsPreview::paintFlagIcon( + QPainter &p, + int left, + int top, + Flag flag) const { + const auto &color = [&]() -> const style::color& { + switch (flag) { + case Flag::Contacts: return st::historyPeer4UserpicBg; + case Flag::NonContacts: return st::historyPeer7UserpicBg; + case Flag::Groups: return st::historyPeer2UserpicBg; + case Flag::Channels: return st::historyPeer1UserpicBg; + case Flag::Bots: return st::historyPeer6UserpicBg; + case Flag::NoMuted: return st::historyPeer6UserpicBg; + case Flag::NoArchived: return st::historyPeer4UserpicBg; + case Flag::NoRead: return st::historyPeer7UserpicBg; + } + Unexpected("Flag in color paintFlagIcon."); + }(); + const auto &icon = [&]() -> const style::icon& { + switch (flag) { + case Flag::Contacts: return st::windowFilterTypeContacts; + case Flag::NonContacts: return st::windowFilterTypeNonContacts; + case Flag::Groups: return st::windowFilterTypeGroups; + case Flag::Channels: return st::windowFilterTypeChannels; + case Flag::Bots: return st::windowFilterTypeBots; + case Flag::NoMuted: return st::windowFilterTypeNoMuted; + case Flag::NoArchived: return st::windowFilterTypeNoArchived; + case Flag::NoRead: return st::windowFilterTypeNoRead; + } + Unexpected("Flag in icon paintFlagIcon."); + }(); + const auto size = st::windowFilterSmallItem.photoSize; + const auto rect = QRect(left, top, size, size); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(color->b); + p.setPen(Qt::NoPen); + p.drawEllipse(rect); + icon.paintInCenter(p, rect); +} + +void FilterChatsPreview::removeFlag(Flag flag) { + const auto i = ranges::find(_removeFlag, flag, &FlagButton::flag); + Assert(i != end(_removeFlag)); + _removeFlag.erase(i); + refresh(); + _flagRemoved.fire_copy(flag); +} + +void FilterChatsPreview::removePeer(not_null history) { + const auto i = ranges::find(_removePeer, history, &PeerButton::history); + Assert(i != end(_removePeer)); + _removePeer.erase(i); + refresh(); + _peerRemoved.fire_copy(history); +} + +rpl::producer FilterChatsPreview::flagRemoved() const { + return _flagRemoved.events(); +} + +rpl::producer> FilterChatsPreview::peerRemoved() const { + return _peerRemoved.events(); +} + +void EditExceptions( + not_null window, + not_null context, + Flags options, + not_null data, + Fn refresh) { + const auto include = (options & Flag::Contacts) != Flags(0); + const auto initBox = [=](not_null box) { + box->addButton(tr::lng_settings_save(), crl::guard(context, [=] { + const auto peers = box->peerListCollectSelectedRows(); + auto &&histories = ranges::view::all( + peers + ) | ranges::view::transform([=](not_null peer) { + return window->session().data().history(peer); + }); + auto changed = base::flat_set>{ + histories.begin(), + histories.end() + }; + auto removeFrom = include ? data->never() : data->always(); + for (const auto &history : changed) { + removeFrom.remove(history); + } + *data = Data::ChatFilter( + data->id(), + data->title(), + data->flags(), + include ? std::move(changed) : std::move(removeFrom), + include ? std::move(removeFrom) : std::move(changed)); + refresh(); + box->closeBox(); + })); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }; + window->window().show( + Box( + std::make_unique( + window, + (include + ? tr::lng_filters_include_title() + : tr::lng_filters_exclude_title()), + options, + data->flags() & options, + include ? data->always() : data->never()), + std::move(initBox)), + Ui::LayerOption::KeepOther); +} + +} // namespace + +void EditFilterBox( + not_null box, + not_null window, + const Data::ChatFilter &filter, + Fn doneCallback) { + const auto creating = filter.title().isEmpty(); + box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit()); + + const auto content = box->verticalLayout(); + const auto name = content->add( + object_ptr( + box, + st::defaultInputField, + tr::lng_filters_new_name(), + filter.title()), + st::markdownLinkFieldPadding); + name->setMaxLength(kMaxFilterTitleLength); + + const auto data = box->lifetime().make_state(filter); + + constexpr auto kTypes = Flag::Contacts + | Flag::NonContacts + | Flag::Groups + | Flag::Channels + | Flag::Bots; + constexpr auto kExcludeTypes = Flag::NoMuted + | Flag::NoArchived + | Flag::NoRead; + + box->setFocusCallback([=] { + name->setFocusFast(); + }); + + AddSkip(content); + AddDivider(content); + AddSkip(content); + AddSubsectionTitle(content, tr::lng_filters_include()); + + const auto include = SetupChatsPreview( + content, + data, + kTypes, + &Data::ChatFilter::always); + + const auto includeAdd = AddButton( + content, + tr::lng_filters_add_chats() | Ui::Text::ToUpper(), + st::settingsUpdate); + + AddSkip(content); + AddDividerText(content, tr::lng_filters_include_about()); + AddSkip(content); + + AddSubsectionTitle(content, tr::lng_filters_exclude()); + + const auto exclude = SetupChatsPreview( + content, + data, + kExcludeTypes, + &Data::ChatFilter::never); + + const auto excludeAdd = AddButton( + content, + tr::lng_filters_add_chats() | Ui::Text::ToUpper(), + st::settingsUpdate); + + AddSkip(content); + content->add( + object_ptr( + content, + tr::lng_filters_exclude_about(), + st::boxDividerLabel), + st::settingsDividerLabelPadding); + + const auto refreshPreviews = [=] { + include->updateData(data->flags() & kTypes, data->always()); + exclude->updateData(data->flags() & kExcludeTypes, data->never()); + }; + includeAdd->setClickedCallback([=] { + EditExceptions(window, box, kTypes, data, refreshPreviews); + }); + excludeAdd->setClickedCallback([=] { + EditExceptions(window, box, kExcludeTypes, data, refreshPreviews); + }); + + const auto save = [=] { + const auto title = name->getLastText().trimmed(); + if (title.isEmpty()) { + name->showError(); + return; + } else if (!(data->flags() & kTypes) && data->always().empty()) { + window->window().showToast(tr::lng_filters_empty(tr::now)); + return; + } else if ((data->flags() == (kTypes | Flag::NoArchived)) + && data->always().empty() + && data->never().empty()) { + window->window().showToast(tr::lng_filters_default(tr::now)); + return; + } + const auto result = Data::ChatFilter( + data->id(), + title, + data->flags(), + data->always(), + data->never()); + box->closeBox(); + + doneCallback(result); + }; + box->addButton( + creating ? tr::lng_filters_create_button() : tr::lng_settings_save(), + save); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} \ No newline at end of file diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.h b/Telegram/SourceFiles/boxes/filters/edit_filter_box.h new file mode 100644 index 000000000..8cd87f8d2 --- /dev/null +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.h @@ -0,0 +1,26 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { +class GenericBox; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace Data { +class ChatFilter; +} // namespace Data + +void EditFilterBox( + not_null box, + not_null window, + const Data::ChatFilter &filter, + Fn doneCallback); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp new file mode 100644 index 000000000..c2e8e96d4 --- /dev/null +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -0,0 +1,66 @@ +/* +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/filters/edit_filter_chats_list.h" + +#include "history/history.h" +#include "window/window_session_controller.h" +#include "lang/lang_keys.h" + +namespace { + +constexpr auto kMaxExceptions = 100; + +} // namespace + +EditFilterChatsListController::EditFilterChatsListController( + not_null navigation, + rpl::producer title, + Flags options, + Flags selected, + const base::flat_set> &peers) +: ChatsListBoxController(navigation) +, _navigation(navigation) +, _title(std::move(title)) +, _peers(peers) { +} + +Main::Session &EditFilterChatsListController::session() const { + return _navigation->session(); +} + +void EditFilterChatsListController::rowClicked(not_null row) { + const auto count = delegate()->peerListSelectedRowsCount(); + if (count < kMaxExceptions || row->checked()) { + delegate()->peerListSetRowChecked(row, !row->checked()); + updateTitle(); + } +} + +void EditFilterChatsListController::itemDeselectedHook( + not_null peer) { + updateTitle(); +} + +void EditFilterChatsListController::prepareViewHook() { + delegate()->peerListSetTitle(std::move(_title)); + delegate()->peerListAddSelectedRows( + _peers | ranges::view::transform(&History::peer)); +} + +auto EditFilterChatsListController::createRow(not_null history) +-> std::unique_ptr { + return std::make_unique(history); +} + +void EditFilterChatsListController::updateTitle() { + const auto count = delegate()->peerListSelectedRowsCount(); + const auto additional = qsl("%1 / %2").arg(count).arg(kMaxExceptions); + delegate()->peerListSetTitle(tr::lng_profile_add_participant()); + delegate()->peerListSetAdditionalTitle(rpl::single(additional)); + +} diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h new file mode 100644 index 000000000..6840b2b56 --- /dev/null +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h @@ -0,0 +1,55 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "boxes/peer_list_controllers.h" +#include "data/data_chat_filters.h" + +class History; + +namespace Ui { +class GenericBox; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace Main { +class Session; +} // namespace Main + +class EditFilterChatsListController final : public ChatsListBoxController { +public: + using Flag = Data::ChatFilter::Flag; + using Flags = Data::ChatFilter::Flags; + + EditFilterChatsListController( + not_null navigation, + rpl::producer title, + Flags options, + Flags selected, + const base::flat_set> &peers); + + Main::Session &session() const override; + + void rowClicked(not_null row) override; + void itemDeselectedHook(not_null peer) override; + +protected: + void prepareViewHook() override; + std::unique_ptr createRow(not_null history) override; + +private: + void updateTitle(); + + const not_null _navigation; + rpl::producer _title; + base::flat_set> _peers; + +}; diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp similarity index 60% rename from Telegram/SourceFiles/boxes/manage_filters_box.cpp rename to Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp index 3f194157d..a6bd98d8e 100644 --- a/Telegram/SourceFiles/boxes/manage_filters_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp @@ -5,10 +5,10 @@ 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/manage_filters_box.h" +#include "boxes/filters/manage_filters_box.h" +#include "boxes/filters/edit_filter_box.h" #include "data/data_session.h" -#include "data/data_chat_filters.h" #include "data/data_folder.h" #include "data/data_peer.h" #include "history/history.h" @@ -35,66 +35,11 @@ namespace { constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000); constexpr auto kFiltersLimit = 10; -constexpr auto kMaxFilterTitleLength = 20; using namespace Settings; using Flag = Data::ChatFilter::Flag; using Flags = Data::ChatFilter::Flags; -using ExceptionPeersRef = const base::flat_set> &; -using ExceptionPeersGetter = ExceptionPeersRef(Data::ChatFilter::*)() const; - -constexpr auto kAllTypes = { - Flag::Contacts, - Flag::NonContacts, - Flag::Groups, - Flag::Channels, - Flag::Bots, - Flag::NoMuted, - Flag::NoArchived, - Flag::NoRead -}; - -class FilterChatsPreview final : public Ui::RpWidget { -public: - FilterChatsPreview( - not_null parent, - Flags flags, - const base::flat_set> &peers); - - [[nodiscard]] rpl::producer flagRemoved() const; - [[nodiscard]] rpl::producer> peerRemoved() const; - - int resizeGetHeight(int newWidth) override; - -private: - using Button = base::unique_qptr; - struct FlagButton { - Flag flag = Flag(); - Button button; - }; - struct PeerButton { - not_null history; - Button button; - }; - - void paintEvent(QPaintEvent *e) override; - - void setup( - Flags flags, - const base::flat_set> &peers); - void refresh(); - void removeFlag(Flag flag); - void removePeer(not_null history); - void paintFlagIcon(QPainter &p, int left, int top, Flag flag) const; - - std::vector _removeFlag; - std::vector _removePeer; - - rpl::event_stream _flagRemoved; - rpl::event_stream> _peerRemoved; - -}; class FilterRowButton final : public Ui::RippleButton { public: @@ -196,173 +141,6 @@ private: : tr::lng_filters_no_chats(tr::now); } -FilterChatsPreview::FilterChatsPreview( - not_null parent, - Flags flags, - const base::flat_set> &peers) -: RpWidget(parent) { - setup(flags, peers); -} - -void FilterChatsPreview::setup( - Flags flags, - const base::flat_set> &peers) { - const auto makeButton = [&](Fn handler) { - auto result = base::make_unique_q( - this, - st::windowFilterSmallRemove); - result->setClickedCallback(std::move(handler)); - return result; - }; - for (const auto flag : kAllTypes) { - if (flags & flag) { - _removeFlag.push_back({ - flag, - makeButton([=] { removeFlag(flag); }) }); - } - } - for (const auto history : peers) { - _removePeer.push_back({ - history, - makeButton([=] { removePeer(history); }) }); - } - refresh(); -} - -void FilterChatsPreview::refresh() { - resizeToWidth(width()); -} - -int FilterChatsPreview::resizeGetHeight(int newWidth) { - const auto right = st::windowFilterSmallRemoveRight; - const auto add = (st::windowFilterSmallItem.height - - st::windowFilterSmallRemove.height) / 2; - auto top = 0; - const auto moveNextButton = [&](not_null button) { - button->moveToRight(right, top + add, newWidth); - top += st::windowFilterSmallItem.height; - }; - for (const auto &[flag, button] : _removeFlag) { - moveNextButton(button.get()); - } - for (const auto &[history, button] : _removePeer) { - moveNextButton(button.get()); - } - return top; -} - -[[nodiscard]] QString TypeName(Flag flag) { - switch (flag) { - case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now); - case Flag::NonContacts: - return tr::lng_filters_type_non_contacts(tr::now); - case Flag::Groups: return tr::lng_filters_type_groups(tr::now); - case Flag::Channels: return tr::lng_filters_type_channels(tr::now); - case Flag::Bots: return tr::lng_filters_type_bots(tr::now); - case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now); - case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now); - case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now); - } - Unexpected("Flag in TypeName."); -} - -void FilterChatsPreview::paintEvent(QPaintEvent *e) { - auto p = Painter(this); - auto top = 0; - const auto &st = st::windowFilterSmallItem; - const auto iconLeft = st.photoPosition.x(); - const auto iconTop = st.photoPosition.y(); - const auto nameLeft = st.namePosition.x(); - p.setFont(st::windowFilterSmallItem.nameStyle.font); - const auto nameTop = st.namePosition.y(); - for (const auto &[flag, button] : _removeFlag) { - paintFlagIcon(p, iconLeft, top + iconTop, flag); - - p.setPen(st::contactsNameFg); - p.drawTextLeft(nameLeft, top + nameTop, width(), TypeName(flag)); - top += st.height; - } - for (const auto &[history, button] : _removePeer) { - history->peer->paintUserpicLeft( - p, - iconLeft, - top + iconTop, - width(), - st.photoSize); - history->peer->nameText().drawLeftElided( - p, - nameLeft, - top + nameTop, - button->x() - nameLeft, - width()); - top += st.height; - } -} - -void FilterChatsPreview::paintFlagIcon( - QPainter &p, - int left, - int top, - Flag flag) const { - const auto &color = [&]() -> const style::color& { - switch (flag) { - case Flag::Contacts: return st::historyPeer4UserpicBg; - case Flag::NonContacts: return st::historyPeer7UserpicBg; - case Flag::Groups: return st::historyPeer2UserpicBg; - case Flag::Channels: return st::historyPeer1UserpicBg; - case Flag::Bots: return st::historyPeer6UserpicBg; - case Flag::NoMuted: return st::historyPeer6UserpicBg; - case Flag::NoArchived: return st::historyPeer4UserpicBg; - case Flag::NoRead: return st::historyPeer7UserpicBg; - } - Unexpected("Flag in color paintFlagIcon."); - }(); - const auto &icon = [&]() -> const style::icon& { - switch (flag) { - case Flag::Contacts: return st::windowFilterTypeContacts; - case Flag::NonContacts: return st::windowFilterTypeNonContacts; - case Flag::Groups: return st::windowFilterTypeGroups; - case Flag::Channels: return st::windowFilterTypeChannels; - case Flag::Bots: return st::windowFilterTypeBots; - case Flag::NoMuted: return st::windowFilterTypeNoMuted; - case Flag::NoArchived: return st::windowFilterTypeNoArchived; - case Flag::NoRead: return st::windowFilterTypeNoRead; - } - Unexpected("Flag in icon paintFlagIcon."); - }(); - const auto size = st::windowFilterSmallItem.photoSize; - const auto rect = QRect(left, top, size, size); - auto hq = PainterHighQualityEnabler(p); - p.setBrush(color->b); - p.setPen(Qt::NoPen); - p.drawEllipse(rect); - icon.paintInCenter(p, rect); -} - -void FilterChatsPreview::removeFlag(Flag flag) { - const auto i = ranges::find(_removeFlag, flag, &FlagButton::flag); - Assert(i != end(_removeFlag)); - _removeFlag.erase(i); - refresh(); - _flagRemoved.fire_copy(flag); -} - -void FilterChatsPreview::removePeer(not_null history) { - const auto i = ranges::find(_removePeer, history, &PeerButton::history); - Assert(i != end(_removePeer)); - _removePeer.erase(i); - refresh(); - _peerRemoved.fire_copy(history); -} - -rpl::producer FilterChatsPreview::flagRemoved() const { - return _flagRemoved.events(); -} - -rpl::producer> FilterChatsPreview::peerRemoved() const { - return _peerRemoved.events(); -} - FilterRowButton::FilterRowButton( not_null parent, not_null session, @@ -609,7 +387,7 @@ void ManageFiltersPrepare::SetupBox( button->updateData(result); }; window->window().show(Box( - EditBox, + EditFilterBox, window, found->filter, crl::guard(button, doneCallback))); @@ -633,7 +411,7 @@ void ManageFiltersPrepare::SetupBox( addFilter(result); }; window->window().show(Box( - EditBox, + EditFilterBox, window, Data::ChatFilter(), crl::guard(box, doneCallback))); @@ -765,145 +543,3 @@ void ManageFiltersPrepare::SetupBox( box->boxClosing() | rpl::start_with_next(save, box->lifetime()); box->addButton(tr::lng_about_done(), [=] { box->closeBox(); }); } - -void SetupChatsPreview( - not_null content, - not_null data, - Flags flags, - ExceptionPeersGetter peers) { - const auto preview = content->add(object_ptr( - content, - data->flags() & flags, - (data->*peers)())); - - preview->flagRemoved( - ) | rpl::start_with_next([=](Flag flag) { - *data = Data::ChatFilter( - data->id(), - data->title(), - (data->flags() & ~flag), - data->always(), - data->never()); - }, preview->lifetime()); - - preview->peerRemoved( - ) | rpl::start_with_next([=](not_null history) { - auto always = data->always(); - auto never = data->never(); - always.remove(history); - never.remove(history); - *data = Data::ChatFilter( - data->id(), - data->title(), - data->flags(), - std::move(always), - std::move(never)); - }, preview->lifetime()); -} - -void ManageFiltersPrepare::EditBox( - not_null box, - not_null window, - const Data::ChatFilter &filter, - Fn doneCallback) { - const auto creating = filter.title().isEmpty(); - box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit()); - - const auto content = box->verticalLayout(); - const auto name = content->add( - object_ptr( - box, - st::defaultInputField, - tr::lng_filters_new_name(), - filter.title()), - st::markdownLinkFieldPadding); - name->setMaxLength(kMaxFilterTitleLength); - - const auto data = box->lifetime().make_state(filter); - - constexpr auto kTypes = Flag::Contacts - | Flag::NonContacts - | Flag::Groups - | Flag::Channels - | Flag::Bots; - constexpr auto kExcludeTypes = Flag::NoMuted - | Flag::NoArchived - | Flag::NoRead; - - box->setFocusCallback([=] { - name->setFocusFast(); - }); - - AddSkip(content); - AddDivider(content); - AddSkip(content); - AddSubsectionTitle(content, tr::lng_filters_include()); - - SetupChatsPreview( - content, - data, - kTypes, - &Data::ChatFilter::always); - - AddButton( - content, - tr::lng_filters_add_chats() | Ui::Text::ToUpper(), - st::settingsUpdate - )->setClickedCallback([=] { - }); - - AddSkip(content); - AddDividerText(content, tr::lng_filters_include_about()); - AddSkip(content); - - AddSubsectionTitle(content, tr::lng_filters_exclude()); - - SetupChatsPreview( - content, - data, - kExcludeTypes, - &Data::ChatFilter::never); - - AddButton( - content, - tr::lng_filters_add_chats() | Ui::Text::ToUpper(), - st::settingsUpdate - )->setClickedCallback([=] { - }); - AddSkip(content); - content->add( - object_ptr( - content, - tr::lng_filters_exclude_about(), - st::boxDividerLabel), - st::settingsDividerLabelPadding); - - const auto save = [=] { - const auto title = name->getLastText().trimmed(); - if (title.isEmpty()) { - name->showError(); - return; - } else if (!(data->flags() & kTypes) && data->always().empty()) { - window->window().showToast(tr::lng_filters_empty(tr::now)); - return; - } else if ((data->flags() == (kTypes | Flag::NoArchived)) - && data->always().empty() - && data->never().empty()) { - window->window().showToast(tr::lng_filters_default(tr::now)); - return; - } - const auto result = Data::ChatFilter( - data->id(), - title, - data->flags(), - data->always(), - data->never()); - box->closeBox(); - - doneCallback(result); - }; - box->addButton( - creating ? tr::lng_filters_create_button() : tr::lng_settings_save(), - save); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); -} \ No newline at end of file diff --git a/Telegram/SourceFiles/boxes/manage_filters_box.h b/Telegram/SourceFiles/boxes/filters/manage_filters_box.h similarity index 84% rename from Telegram/SourceFiles/boxes/manage_filters_box.h rename to Telegram/SourceFiles/boxes/filters/manage_filters_box.h index 9900927e5..955ecf311 100644 --- a/Telegram/SourceFiles/boxes/manage_filters_box.h +++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.h @@ -38,11 +38,6 @@ private: not_null box, not_null window, const std::vector &suggested); - static void EditBox( - not_null box, - not_null window, - const Data::ChatFilter &filter, - Fn doneCallback); const not_null _window; const not_null _api; diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 7e9bd6141..1ab31de89 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "data/data_session.h" #include "data/data_chat_filters.h" -#include "boxes/manage_filters_box.h" +#include "boxes/filters/manage_filters_box.h" #include "lang/lang_keys.h" #include "styles/style_widgets.h" #include "styles/style_window.h" From b88f0108adba6049835920fe1b329fa406c030d4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Mar 2020 17:05:21 +0400 Subject: [PATCH 028/115] Edit chat types in filters. --- Telegram/SourceFiles/boxes/background_box.cpp | 2 +- Telegram/SourceFiles/boxes/boxes.style | 23 +- .../SourceFiles/boxes/edit_privacy_box.cpp | 2 +- .../boxes/filters/edit_filter_box.cpp | 95 ++---- .../boxes/filters/edit_filter_chats_list.cpp | 323 +++++++++++++++++- .../boxes/filters/edit_filter_chats_list.h | 24 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 210 ++++++++---- Telegram/SourceFiles/boxes/peer_list_box.h | 118 +++++-- Telegram/SourceFiles/data/data_types.h | 3 +- .../info_common_groups_inner_widget.cpp | 8 +- .../info_common_groups_inner_widget.h | 6 +- .../polls/info_polls_results_inner_widget.cpp | 14 +- .../info/profile/info_profile_members.cpp | 8 +- .../info/profile/info_profile_members.h | 6 +- .../SourceFiles/ui/effects/round_checkbox.cpp | 14 +- .../SourceFiles/ui/effects/round_checkbox.h | 13 +- Telegram/SourceFiles/window/window.style | 14 + Telegram/lib_ui | 2 +- 18 files changed, 654 insertions(+), 231 deletions(-) diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index f69aac5db..6882c74b2 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -185,7 +185,7 @@ BackgroundBox::Inner::Inner( , _session(session) , _api(_session->api().instance()) , _check(std::make_unique(st::overviewCheck, [=] { update(); })) { - _check->setChecked(true, Ui::RoundCheckbox::SetStyle::Fast); + _check->setChecked(true, anim::type::instant); if (_session->data().wallpapers().empty()) { resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); } else { diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 74eb4df04..4bb710fa6 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -216,26 +216,9 @@ contactsMultiSelect: MultiSelect { fieldCancel: contactsSearchCancel; fieldCancelSkip: 40px; } -contactsPhotoCheckIcon: icon {{ - "default_checkbox_check", - overviewCheckFgActive, - point(3px, 6px) -}}; -contactsPhotoCheck: RoundCheckbox(defaultRoundCheckbox) { - size: 20px; - sizeSmall: 0.3; - bgInactive: overviewCheckBg; - bgActive: overviewCheckBgActive; - check: contactsPhotoCheckIcon; -} -contactsPhotoCheckbox: RoundImageCheckbox { - imageRadius: 21px; - imageSmallRadius: 18px; - selectWidth: 2px; - selectFg: windowBgActive; - selectDuration: 150; - check: contactsPhotoCheck; -} +contactsPhotoCheckIcon: defaultPeerListCheckIcon; +contactsPhotoCheck: defaultPeerListCheck; +contactsPhotoCheckbox: defaultPeerListCheckbox; contactsPhotoDisabledCheckFg: menuIconFg; contactsNameCheckedFg: windowActiveTextFg; contactsRipple: defaultRippleAnimation; diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 03667da4e..8b634248f 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -69,7 +69,7 @@ Main::Session &PrivacyExceptionsBoxController::session() const { void PrivacyExceptionsBoxController::prepareViewHook() { delegate()->peerListSetTitle(std::move(_title)); - delegate()->peerListAddSelectedRows(_selected); + delegate()->peerListAddSelectedPeers(_selected); } std::vector> PrivacyExceptionsBoxController::getResult() const { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 027968e11..5a09f640c 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -77,13 +77,9 @@ private: void paintEvent(QPaintEvent *e) override; - void setup( - Flags flags, - const base::flat_set> &peers); void refresh(); void removeFlag(Flag flag); void removePeer(not_null history); - void paintFlagIcon(QPainter &p, int left, int top, Flag flag) const; std::vector _removeFlag; std::vector _removePeer; @@ -187,21 +183,6 @@ int FilterChatsPreview::resizeGetHeight(int newWidth) { return top; } -[[nodiscard]] QString TypeName(Flag flag) { - switch (flag) { - case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now); - case Flag::NonContacts: - return tr::lng_filters_type_non_contacts(tr::now); - case Flag::Groups: return tr::lng_filters_type_groups(tr::now); - case Flag::Channels: return tr::lng_filters_type_channels(tr::now); - case Flag::Bots: return tr::lng_filters_type_bots(tr::now); - case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now); - case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now); - case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now); - } - Unexpected("Flag in TypeName."); -} - void FilterChatsPreview::paintEvent(QPaintEvent *e) { auto p = Painter(this); auto top = 0; @@ -212,10 +193,20 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) { p.setFont(st::windowFilterSmallItem.nameStyle.font); const auto nameTop = st.namePosition.y(); for (const auto &[flag, button] : _removeFlag) { - paintFlagIcon(p, iconLeft, top + iconTop, flag); + PaintFilterChatsTypeIcon( + p, + flag, + iconLeft, + top + iconTop, + width(), + st.photoSize); p.setPen(st::contactsNameFg); - p.drawTextLeft(nameLeft, top + nameTop, width(), TypeName(flag)); + p.drawTextLeft( + nameLeft, + top + nameTop, + width(), + FilterChatsTypeName(flag)); top += st.height; } for (const auto &[history, button] : _removePeer) { @@ -236,46 +227,6 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) { } } -void FilterChatsPreview::paintFlagIcon( - QPainter &p, - int left, - int top, - Flag flag) const { - const auto &color = [&]() -> const style::color& { - switch (flag) { - case Flag::Contacts: return st::historyPeer4UserpicBg; - case Flag::NonContacts: return st::historyPeer7UserpicBg; - case Flag::Groups: return st::historyPeer2UserpicBg; - case Flag::Channels: return st::historyPeer1UserpicBg; - case Flag::Bots: return st::historyPeer6UserpicBg; - case Flag::NoMuted: return st::historyPeer6UserpicBg; - case Flag::NoArchived: return st::historyPeer4UserpicBg; - case Flag::NoRead: return st::historyPeer7UserpicBg; - } - Unexpected("Flag in color paintFlagIcon."); - }(); - const auto &icon = [&]() -> const style::icon& { - switch (flag) { - case Flag::Contacts: return st::windowFilterTypeContacts; - case Flag::NonContacts: return st::windowFilterTypeNonContacts; - case Flag::Groups: return st::windowFilterTypeGroups; - case Flag::Channels: return st::windowFilterTypeChannels; - case Flag::Bots: return st::windowFilterTypeBots; - case Flag::NoMuted: return st::windowFilterTypeNoMuted; - case Flag::NoArchived: return st::windowFilterTypeNoArchived; - case Flag::NoRead: return st::windowFilterTypeNoRead; - } - Unexpected("Flag in icon paintFlagIcon."); - }(); - const auto size = st::windowFilterSmallItem.photoSize; - const auto rect = QRect(left, top, size, size); - auto hq = PainterHighQualityEnabler(p); - p.setBrush(color->b); - p.setPen(Qt::NoPen); - p.drawEllipse(rect); - icon.paintInCenter(p, rect); -} - void FilterChatsPreview::removeFlag(Flag flag) { const auto i = ranges::find(_removeFlag, flag, &FlagButton::flag); Assert(i != end(_removeFlag)); @@ -307,7 +258,16 @@ void EditExceptions( not_null data, Fn refresh) { const auto include = (options & Flag::Contacts) != Flags(0); - const auto initBox = [=](not_null box) { + auto controller = std::make_unique( + window, + (include + ? tr::lng_filters_include_title() + : tr::lng_filters_exclude_title()), + options, + data->flags() & options, + include ? data->always() : data->never()); + const auto rawController = controller.get(); + auto initBox = [=](not_null box) { box->addButton(tr::lng_settings_save(), crl::guard(context, [=] { const auto peers = box->peerListCollectSelectedRows(); auto &&histories = ranges::view::all( @@ -326,7 +286,7 @@ void EditExceptions( *data = Data::ChatFilter( data->id(), data->title(), - data->flags(), + (data->flags() & ~options) | rawController->chosenOptions(), include ? std::move(changed) : std::move(removeFrom), include ? std::move(removeFrom) : std::move(changed)); refresh(); @@ -336,14 +296,7 @@ void EditExceptions( }; window->window().show( Box( - std::make_unique( - window, - (include - ? tr::lng_filters_include_title() - : tr::lng_filters_exclude_title()), - options, - data->flags() & options, - include ? data->always() : data->never()), + std::move(controller), std::move(initBox)), Ui::LayerOption::KeepOther); } diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index c2e8e96d4..c9d5d80d5 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -10,13 +10,291 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "window/window_session_controller.h" #include "lang/lang_keys.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "base/object_ptr.h" +#include "styles/style_window.h" namespace { constexpr auto kMaxExceptions = 100; +using Flag = Data::ChatFilter::Flag; +using Flags = Data::ChatFilter::Flags; + +constexpr auto kAllTypes = { + Flag::Contacts, + Flag::NonContacts, + Flag::Groups, + Flag::Channels, + Flag::Bots, + Flag::NoMuted, + Flag::NoArchived, + Flag::NoRead +}; + +class TypeRow final : public PeerListRow { +public: + explicit TypeRow(Flag flag); + + QString generateName() override; + QString generateShortName() override; + void paintEntityUserpicLeft( + Painter &p, + int x, + int y, + int outerWidth, + int size) override; + +private: + [[nodiscard]] Flag flag() const; + +}; + +class TypeDelegate final : public PeerListContentDelegate { +public: + void peerListSetTitle(rpl::producer title) override; + void peerListSetAdditionalTitle(rpl::producer title) override; + bool peerListIsRowChecked(not_null row) override; + int peerListSelectedRowsCount() override; + std::vector> peerListCollectSelectedRows() override; + void peerListScrollToTop() override; + void peerListAddSelectedPeerInBunch( + not_null peer) override; + void peerListAddSelectedRowInBunch(not_null row) override; + void peerListFinishSelectedRowsBunch() override; + void peerListSetDescription( + object_ptr description) override; + +}; + +class TypeController final : public PeerListController { +public: + TypeController( + not_null session, + Flags options, + Flags selected); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + + [[nodiscard]] rpl::producer selectedOptions() const; + +private: + [[nodiscard]] std::unique_ptr createRow(Flag flag) const; + [[nodiscard]] Flags collectSelectedOptions() const; + + const not_null _session; + Flags _options; + + rpl::event_stream<> _selectionChanged; + +}; + +[[nodiscard]] object_ptr CreateSectionSubtitle( + not_null parent, + rpl::producer text) { + auto result = object_ptr( + parent, + st::searchedBarHeight); + + const auto raw = result.data(); + raw->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(raw); + p.fillRect(clip, st::searchedBarBg); + }, raw->lifetime()); + + const auto label = Ui::CreateChild( + raw, + std::move(text), + st::windowFilterChatsSectionSubtitle); + raw->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto padding = st::windowFilterChatsSectionSubtitlePadding; + const auto available = width - padding.left() - padding.right(); + label->resizeToNaturalWidth(available); + label->moveToLeft(padding.left(), padding.top(), width); + }, label->lifetime()); + + return result; +} + +[[nodiscard]] uint64 TypeId(Flag flag) { + return PeerIdFakeShift | static_cast(flag); +} + +TypeRow::TypeRow(Flag flag) : PeerListRow(TypeId(flag)) { +} + +QString TypeRow::generateName() { + return FilterChatsTypeName(flag()); +} + +QString TypeRow::generateShortName() { + return generateName(); +} + +void TypeRow::paintEntityUserpicLeft( + Painter &p, + int x, + int y, + int outerWidth, + int size) { + PaintFilterChatsTypeIcon(p, flag(), x, y, outerWidth, size); +} + +Flag TypeRow::flag() const { + return static_cast(id() & 0xFF); +} + +void TypeDelegate::peerListSetTitle(rpl::producer title) { +} + +void TypeDelegate::peerListSetAdditionalTitle(rpl::producer title) { +} + +bool TypeDelegate::peerListIsRowChecked(not_null row) { + return false; +} + +int TypeDelegate::peerListSelectedRowsCount() { + return 0; +} + +auto TypeDelegate::peerListCollectSelectedRows() +-> std::vector> { + return {}; +} + +void TypeDelegate::peerListScrollToTop() { +} + +void TypeDelegate::peerListAddSelectedPeerInBunch(not_null peer) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void TypeDelegate::peerListAddSelectedRowInBunch(not_null row) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void TypeDelegate::peerListFinishSelectedRowsBunch() { +} + +void TypeDelegate::peerListSetDescription( + object_ptr description) { + description.destroy(); +} + +TypeController::TypeController( + not_null session, + Flags options, + Flags selected) +: _session(session) +, _options(options) { +} + +Main::Session &TypeController::session() const { + return *_session; +} + +void TypeController::prepare() { + for (const auto flag : kAllTypes) { + if (_options & flag) { + delegate()->peerListAppendRow(createRow(flag)); + } + } + delegate()->peerListRefreshRows(); +} + +Flags TypeController::collectSelectedOptions() const { + auto result = Flags(); + for (const auto flag : kAllTypes) { + if (const auto row = delegate()->peerListFindRow(TypeId(flag))) { + if (row->checked()) { + result |= flag; + } + } + } + return result; +} + +void TypeController::rowClicked(not_null row) { + delegate()->peerListSetRowChecked(row, !row->checked()); + _selectionChanged.fire({}); +} + +std::unique_ptr TypeController::createRow(Flag flag) const { + return std::make_unique(flag); +} + +rpl::producer TypeController::selectedOptions() const { + return _selectionChanged.events_starting_with( + {} + ) | rpl::map([=] { + return collectSelectedOptions(); + }); +} + } // namespace +[[nodiscard]] QString FilterChatsTypeName(Flag flag) { + switch (flag) { + case Flag::Contacts: return tr::lng_filters_type_contacts(tr::now); + case Flag::NonContacts: + return tr::lng_filters_type_non_contacts(tr::now); + case Flag::Groups: return tr::lng_filters_type_groups(tr::now); + case Flag::Channels: return tr::lng_filters_type_channels(tr::now); + case Flag::Bots: return tr::lng_filters_type_bots(tr::now); + case Flag::NoMuted: return tr::lng_filters_type_no_muted(tr::now); + case Flag::NoArchived: return tr::lng_filters_type_no_archived(tr::now); + case Flag::NoRead: return tr::lng_filters_type_no_read(tr::now); + } + Unexpected("Flag in TypeName."); +} + +void PaintFilterChatsTypeIcon( + Painter &p, + Data::ChatFilter::Flag flag, + int x, + int y, + int outerWidth, + int size) { + const auto &color = [&]() -> const style::color& { + switch (flag) { + case Flag::Contacts: return st::historyPeer4UserpicBg; + case Flag::NonContacts: return st::historyPeer7UserpicBg; + case Flag::Groups: return st::historyPeer2UserpicBg; + case Flag::Channels: return st::historyPeer1UserpicBg; + case Flag::Bots: return st::historyPeer6UserpicBg; + case Flag::NoMuted: return st::historyPeer6UserpicBg; + case Flag::NoArchived: return st::historyPeer4UserpicBg; + case Flag::NoRead: return st::historyPeer7UserpicBg; + } + Unexpected("Flag in color paintFlagIcon."); + }(); + const auto &icon = [&]() -> const style::icon& { + switch (flag) { + case Flag::Contacts: return st::windowFilterTypeContacts; + case Flag::NonContacts: return st::windowFilterTypeNonContacts; + case Flag::Groups: return st::windowFilterTypeGroups; + case Flag::Channels: return st::windowFilterTypeChannels; + case Flag::Bots: return st::windowFilterTypeBots; + case Flag::NoMuted: return st::windowFilterTypeNoMuted; + case Flag::NoArchived: return st::windowFilterTypeNoArchived; + case Flag::NoRead: return st::windowFilterTypeNoRead; + } + Unexpected("Flag in icon paintFlagIcon."); + }(); + const auto rect = style::rtlrect(x, y, size, size, outerWidth); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(color->b); + p.setPen(Qt::NoPen); + p.drawEllipse(rect); + icon.paintInCenter(p, rect); +} + EditFilterChatsListController::EditFilterChatsListController( not_null navigation, rpl::producer title, @@ -26,7 +304,9 @@ EditFilterChatsListController::EditFilterChatsListController( : ChatsListBoxController(navigation) , _navigation(navigation) , _title(std::move(title)) -, _peers(peers) { +, _peers(peers) +, _options(options) +, _selected(selected) { } Main::Session &EditFilterChatsListController::session() const { @@ -48,8 +328,45 @@ void EditFilterChatsListController::itemDeselectedHook( void EditFilterChatsListController::prepareViewHook() { delegate()->peerListSetTitle(std::move(_title)); - delegate()->peerListAddSelectedRows( + delegate()->peerListAddSelectedPeers( _peers | ranges::view::transform(&History::peer)); + delegate()->peerListSetAboveWidget(prepareTypesList()); +} + +object_ptr EditFilterChatsListController::prepareTypesList() { + auto result = object_ptr((QWidget*)nullptr); + const auto container = result.data(); + container->add(CreateSectionSubtitle( + container, + tr::lng_filters_edit_types())); + const auto delegate = container->lifetime().make_state(); + const auto controller = container->lifetime().make_state( + &session(), + _options, + _selected); + const auto content = result->add(object_ptr( + container, + controller, + st::windowFilterSmallList)); + delegate->setContent(content); + controller->setDelegate(delegate); + for (const auto flag : kAllTypes) { + if (_selected & flag) { + if (const auto row = delegate->peerListFindRow(TypeId(flag))) { + content->changeCheckState(row, true, anim::type::instant); + } + } + } + container->add(CreateSectionSubtitle( + container, + tr::lng_filters_edit_chats())); + + controller->selectedOptions( + ) | rpl::start_with_next([=](Flags selected) { + _selected = selected; + }, _lifetime); + + return result; } auto EditFilterChatsListController::createRow(not_null history) @@ -60,7 +377,5 @@ auto EditFilterChatsListController::createRow(not_null history) void EditFilterChatsListController::updateTitle() { const auto count = delegate()->peerListSelectedRowsCount(); const auto additional = qsl("%1 / %2").arg(count).arg(kMaxExceptions); - delegate()->peerListSetTitle(tr::lng_profile_add_participant()); delegate()->peerListSetAdditionalTitle(rpl::single(additional)); - } diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h index 6840b2b56..a4eae9fb1 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h @@ -24,6 +24,17 @@ namespace Main { class Session; } // namespace Main +class Painter; + +[[nodiscard]] QString FilterChatsTypeName(Data::ChatFilter::Flag flag); +void PaintFilterChatsTypeIcon( + Painter &p, + Data::ChatFilter::Flag flag, + int x, + int y, + int outerWidth, + int size); + class EditFilterChatsListController final : public ChatsListBoxController { public: using Flag = Data::ChatFilter::Flag; @@ -36,20 +47,27 @@ public: Flags selected, const base::flat_set> &peers); - Main::Session &session() const override; + [[nodiscard]] Main::Session &session() const override; + [[nodiscard]] Flags chosenOptions() const { + return _selected; + } void rowClicked(not_null row) override; void itemDeselectedHook(not_null peer) override; -protected: +private: void prepareViewHook() override; std::unique_ptr createRow(not_null history) override; + [[nodiscard]] object_ptr prepareTypesList(); -private: void updateTitle(); const not_null _navigation; rpl::producer _title; base::flat_set> _peers; + Flags _options; + Flags _selected; + + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 821c7641f..b66588509 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -33,10 +33,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include -auto PaintUserpicCallback( - not_null peer, - bool respectSavedMessagesChat) --> Fn { +PaintRoundImageCallback PaintUserpicCallback( + not_null peer, + bool respectSavedMessagesChat) { if (respectSavedMessagesChat && peer->isSelf()) { return [](Painter &p, int x, int y, int outerWidth, int size) { Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size); @@ -77,7 +76,7 @@ void PeerListBox::createMultiSelect() { _select->entity()->setItemRemovedCallback([=](uint64 itemId) { if (const auto peer = _controller->session().data().peerLoaded(itemId)) { if (const auto row = peerListFindRow(peer->id)) { - content()->changeCheckState(row, false, PeerListRow::SetStyle::Animated); + content()->changeCheckState(row, false, anim::type::normal); update(); } _controller->itemDeselectedHook(peer); @@ -185,9 +184,8 @@ void PeerListBox::setInnerFocus() { void PeerListBox::peerListSetRowChecked( not_null row, bool checked) { - auto peer = row->peer(); if (checked) { - addSelectItem(peer, PeerListRow::SetStyle::Animated); + addSelectItem(row, anim::type::normal); PeerListContentDelegate::peerListSetRowChecked(row, checked); peerListUpdateRow(row); @@ -195,7 +193,7 @@ void PeerListBox::peerListSetRowChecked( _select->entity()->clearQuery(); } else { // The itemRemovedCallback will call changeCheckState() here. - _select->entity()->removeItem(peer->id); + _select->entity()->removeItem(row->id()); peerListUpdateRow(row); } } @@ -295,38 +293,62 @@ int PeerListController::contentWidth() const { return st::boxWideWidth; } -void PeerListBox::addSelectItem(not_null peer, PeerListRow::SetStyle style) { - if (!_select) { - createMultiSelect(); - _select->hide(anim::type::instant); - } +void PeerListBox::addSelectItem( + not_null peer, + anim::type animated) { const auto respect = _controller->respectSavedMessagesChat(); const auto text = (respect && peer->isSelf()) ? tr::lng_saved_short(tr::now) : peer->shortName(); - const auto callback = PaintUserpicCallback(peer, respect); - if (style == PeerListRow::SetStyle::Fast) { + addSelectItem( + peer->id, + text, + PaintUserpicCallback(peer, respect), + animated); +} + +void PeerListBox::addSelectItem( + not_null row, + anim::type animated) { + addSelectItem( + row->id(), + row->generateShortName(), + row->generatePaintUserpicCallback(), + animated); +} + +void PeerListBox::addSelectItem( + uint64 itemId, + const QString &text, + Ui::MultiSelect::PaintRoundImage paintUserpic, + anim::type animated) { + if (!_select) { + createMultiSelect(); + _select->hide(anim::type::instant); + } + if (animated == anim::type::instant) { _select->entity()->addItemInBunch( - peer->id, + itemId, text, st::activeButtonBg, - std::move(callback)); + std::move(paintUserpic)); } else { _select->entity()->addItem( - peer->id, + itemId, text, st::activeButtonBg, - std::move(callback)); + std::move(paintUserpic)); } } void PeerListBox::peerListFinishSelectedRowsBunch() { Expects(_select != nullptr); + _select->entity()->finishItemsBunch(); } -bool PeerListBox::peerListIsRowSelected(not_null peer) { - return _select ? _select->entity()->hasItem(peer->id) : false; +bool PeerListBox::peerListIsRowChecked(not_null row) { + return _select ? _select->entity()->hasItem(row->id()) : false; } int PeerListBox::peerListSelectedRowsCount() { @@ -348,7 +370,8 @@ auto PeerListBox::peerListCollectSelectedRows() return result; } -PeerListRow::PeerListRow(not_null peer) : PeerListRow(peer, peer->id) { +PeerListRow::PeerListRow(not_null peer) +: PeerListRow(peer, peer->id) { } PeerListRow::PeerListRow(not_null peer, PeerListRowId id) @@ -359,6 +382,15 @@ PeerListRow::PeerListRow(not_null peer, PeerListRowId id) , _isSavedMessagesChat(false) { } +PeerListRow::PeerListRow(PeerListRowId id) +: _id(id) +, _initialized(false) +, _isSearchResult(false) +, _isSavedMessagesChat(false) { +} + +PeerListRow::~PeerListRow() = default; + bool PeerListRow::checked() const { return _checkbox && _checkbox->checked(); } @@ -375,7 +407,7 @@ void PeerListRow::clearCustomStatus() { } void PeerListRow::refreshStatus() { - if (!_initialized || _statusType == StatusType::Custom) { + if (!_initialized || special() || _statusType == StatusType::Custom) { return; } _statusType = StatusType::LastSeen; @@ -417,11 +449,38 @@ void PeerListRow::refreshName(const style::PeerListItem &st) { } const auto text = _isSavedMessagesChat ? tr::lng_saved_messages(tr::now) - : peer()->name; + : generateName(); _name.setText(st.nameStyle, text, Ui::NameTextOptions()); } -PeerListRow::~PeerListRow() = default; +QString PeerListRow::generateName() { + return peer()->name; +} + +QString PeerListRow::generateShortName() { + return _isSavedMessagesChat + ? tr::lng_saved_short(tr::now) + : peer()->shortName(); +} + +PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback() { + return [=](Painter &p, int x, int y, int outerWidth, int size) { + paintEntityUserpicLeft(p, x, y, outerWidth, size); + }; +} + +void PeerListRow::paintEntityUserpicLeft( + Painter &p, + int x, + int y, + int outerWidth, + int size) { + if (_isSavedMessagesChat) { + Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size); + } else { + peer()->paintUserpicLeft(p, x, y, outerWidth, size); + } +} void PeerListRow::invalidatePixmapsCache() { if (_checkbox) { @@ -430,7 +489,9 @@ void PeerListRow::invalidatePixmapsCache() { } int PeerListRow::nameIconWidth() const { - return _peer->isVerified() ? st::dialogsVerifiedIcon.width() : 0; + return (special() || !_peer->isVerified()) + ? 0 + : st::dialogsVerifiedIcon.width(); } void PeerListRow::paintNameIcon( @@ -490,10 +551,8 @@ void PeerListRow::paintUserpic( paintDisabledCheckUserpic(p, st, x, y, outerWidth); } else if (_checkbox) { _checkbox->paint(p, x, y, outerWidth); - } else if (_isSavedMessagesChat) { - Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, st.photoSize); } else { - peer()->paintUserpicLeft(p, x, y, outerWidth, st.photoSize); + paintEntityUserpicLeft(p, x, y, outerWidth, st.photoSize); } } @@ -558,18 +617,19 @@ void PeerListRow::lazyInitialize(const style::PeerListItem &st) { refreshStatus(); } -void PeerListRow::createCheckbox(Fn updateCallback) { +void PeerListRow::createCheckbox( + const style::RoundImageCheckbox &st, + Fn updateCallback) { _checkbox = std::make_unique( - st::contactsPhotoCheckbox, + st, std::move(updateCallback), - PaintUserpicCallback(_peer, _isSavedMessagesChat)); + generatePaintUserpicCallback()); } -void PeerListRow::setCheckedInternal(bool checked, SetStyle style) { +void PeerListRow::setCheckedInternal(bool checked, anim::type animated) { Expects(_checkbox != nullptr); - using CheckboxStyle = Ui::RoundCheckbox::SetStyle; - auto speed = (style == SetStyle::Animated) ? CheckboxStyle::Animated : CheckboxStyle::Fast; - _checkbox->setChecked(checked, speed); + + _checkbox->setChecked(checked, animated); } PeerListContent::PeerListContent( @@ -636,25 +696,30 @@ void PeerListContent::appendFoundRow(not_null row) { void PeerListContent::changeCheckState( not_null row, bool checked, - PeerListRow::SetStyle style) { + anim::type animated) { row->setChecked( checked, - style, - [this, row] { updateRow(row); }); + _st.item.checkbox, + animated, + [=] { updateRow(row); }); } void PeerListContent::addRowEntry(not_null row) { - if (_controller->respectSavedMessagesChat() && row->peer()->isSelf()) { + if (_controller->respectSavedMessagesChat() + && !row->special() + && row->peer()->isSelf()) { row->setIsSavedMessagesChat(true); } _rowsById.emplace(row->id(), row); - _rowsByPeer[row->peer()].push_back(row); + if (!row->special()) { + _rowsByPeer[row->peer()].push_back(row); + } if (addingToSearchIndex()) { addToSearchIndex(row); } - if (_controller->isRowSelected(row->peer())) { - Assert(row->id() == row->peer()->id); - changeCheckState(row, true, PeerListRow::SetStyle::Fast); + if (_controller->isRowSelected(row)) { + Assert(row->special() || row->id() == row->peer()->id); + changeCheckState(row, true, anim::type::instant); } } @@ -670,7 +735,7 @@ bool PeerListContent::addingToSearchIndex() const { } void PeerListContent::addToSearchIndex(not_null row) { - if (row->isSearchResult()) { + if (row->isSearchResult() || row->special()) { return; } @@ -763,8 +828,10 @@ void PeerListContent::removeRow(not_null row) { setContexted(Selected()); _rowsById.erase(row->id()); - auto &byPeer = _rowsByPeer[row->peer()]; - byPeer.erase(ranges::remove(byPeer, row), end(byPeer)); + if (!row->special()) { + auto &byPeer = _rowsByPeer[row->peer()]; + byPeer.erase(ranges::remove(byPeer, row), end(byPeer)); + } removeFromSearchIndex(row); _filterResults.erase( ranges::remove(_filterResults, row), @@ -1107,8 +1174,11 @@ void PeerListContent::setPressed(Selected pressed) { _pressed = pressed; } -crl::time PeerListContent::paintRow(Painter &p, crl::time ms, RowIndex index) { - auto row = getRow(index); +crl::time PeerListContent::paintRow( + Painter &p, + crl::time ms, + RowIndex index) { + const auto row = getRow(index); Assert(row != nullptr); row->lazyInitialize(_st.item); @@ -1119,17 +1189,17 @@ crl::time PeerListContent::paintRow(Painter &p, crl::time ms, RowIndex index) { refreshStatusAt = row->refreshStatusTime(); } - auto peer = row->peer(); - auto user = peer->asUser(); - auto active = (_contexted.index.value >= 0) + const auto peer = row->special() ? nullptr : row->peer().get(); + const auto user = peer ? peer->asUser() : nullptr; + const auto active = (_contexted.index.value >= 0) ? _contexted : (_pressed.index.value >= 0) ? _pressed : _selected; - auto selected = (active.index == index); - auto actionSelected = (selected && active.action); + const auto selected = (active.index == index); + const auto actionSelected = (selected && active.action); - auto &bg = selected + const auto &bg = selected ? _st.item.button.textBgOver : _st.item.button.textBg; p.fillRect(0, 0, width(), _rowHeight, bg); @@ -1184,12 +1254,17 @@ crl::time PeerListContent::paintRow(Painter &p, crl::time ms, RowIndex index) { } p.setFont(st::contactsStatusFont); - if (row->isSearchResult() && !_mentionHighlight.isEmpty() && peer->userName().startsWith(_mentionHighlight, Qt::CaseInsensitive)) { - auto username = peer->userName(); - auto availableWidth = statusw; + if (row->isSearchResult() + && !_mentionHighlight.isEmpty() + && peer + && peer->userName().startsWith( + _mentionHighlight, + Qt::CaseInsensitive)) { + const auto username = peer->userName(); + const auto availableWidth = statusw; auto highlightedPart = '@' + username.mid(0, _mentionHighlight.size()); auto grayedPart = username.mid(_mentionHighlight.size()); - auto highlightedWidth = st::contactsStatusFont->width(highlightedPart); + const auto highlightedWidth = st::contactsStatusFont->width(highlightedPart); if (highlightedWidth >= availableWidth || grayedPart.isEmpty()) { if (highlightedWidth > availableWidth) { highlightedPart = st::contactsStatusFont->elided(highlightedPart, availableWidth); @@ -1300,7 +1375,10 @@ void PeerListContent::loadProfilePhotos() { if (to > rowsCount) to = rowsCount; for (auto index = from; index != to; ++index) { - getRow(RowIndex(index))->peer()->loadUserpic(); + const auto row = getRow(RowIndex(index)); + if (!row->special()) { + row->peer()->loadUserpic(); + } } } } @@ -1313,13 +1391,13 @@ void PeerListContent::checkScrollForPreload() { } void PeerListContent::searchQueryChanged(QString query) { - auto searchWordsList = TextUtilities::PrepareSearchWords(query); - auto normalizedQuery = searchWordsList.join(' '); + const auto searchWordsList = TextUtilities::PrepareSearchWords(query); + const auto normalizedQuery = searchWordsList.join(' '); if (_normalizedSearchQuery != normalizedQuery) { setSearchQuery(query, normalizedQuery); if (_controller->searchInLocal() && !searchWordsList.isEmpty()) { auto minimalList = (const std::vector>*)nullptr; - for_const (auto &searchWord, searchWordsList) { + for (const auto &searchWord : searchWordsList) { auto searchWordStart = searchWord[0].toLower(); auto it = _searchIndex.find(searchWordStart); if (it == _searchIndex.cend()) { @@ -1343,7 +1421,7 @@ void PeerListContent::searchQueryChanged(QString query) { }; auto allSearchWordsInNames = [&]( not_null peer) { - for_const (auto &searchWord, searchWordsList) { + for (const auto &searchWord : searchWordsList) { if (!searchWordInNames(peer, searchWord)) { return false; } @@ -1352,8 +1430,8 @@ void PeerListContent::searchQueryChanged(QString query) { }; _filterResults.reserve(minimalList->size()); - for_const (auto row, *minimalList) { - if (allSearchWordsInNames(row->peer())) { + for (const auto row : *minimalList) { + if (!row->special() && allSearchWordsInNames(row->peer())) { _filterResults.push_back(row); } } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 3c6b84f6d..df5027fb2 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -38,22 +38,31 @@ namespace Notify { struct PeerUpdate; } // namespace Notify -auto PaintUserpicCallback( +using PaintRoundImageCallback = Fn; + +[[nodiscard]] PaintRoundImageCallback PaintUserpicCallback( not_null peer, - bool respectSavedMessagesChat) --> Fn; + bool respectSavedMessagesChat); using PeerListRowId = uint64; class PeerListRow { public: - PeerListRow(not_null peer); - PeerListRow(not_null peer, PeerListRowId id); - enum class State { Active, Disabled, DisabledChecked, }; + + explicit PeerListRow(not_null peer); + PeerListRow(not_null peer, PeerListRowId id); + + virtual ~PeerListRow(); + void setDisabledState(State state) { _disabledState = state; } @@ -62,20 +71,27 @@ public: // not by the row itself, so there is no setChecked() method. // We can query the checked state from row, but before it is // added to the box it is always false. - bool checked() const; + [[nodiscard]] bool checked() const; + + [[nodiscard]] bool special() const { + return !_peer; + } + [[nodiscard]] not_null peer() const { + Expects(!special()); - not_null peer() const { return _peer; } - PeerListRowId id() const { + [[nodiscard]] PeerListRowId id() const { return _id; } + [[nodiscard]] virtual QString generateName(); + [[nodiscard]] virtual QString generateShortName(); + [[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback(); + void setCustomStatus(const QString &status); void clearCustomStatus(); - virtual ~PeerListRow(); - // Box interface. virtual int nameIconWidth() const; virtual void paintNameIcon( @@ -138,19 +154,16 @@ public: _isSavedMessagesChat = isSavedMessagesChat; } - enum class SetStyle { - Animated, - Fast, - }; template void setChecked( bool checked, - SetStyle style, + const style::RoundImageCheckbox &st, + anim::type animated, UpdateCallback callback) { if (checked && !_checkbox) { - createCheckbox(std::move(callback)); + createCheckbox(st, std::move(callback)); } - setCheckedInternal(checked, style); + setCheckedInternal(checked, animated); } void invalidatePixmapsCache(); @@ -192,9 +205,20 @@ protected: return _initialized; } + explicit PeerListRow(PeerListRowId id); + + virtual void paintEntityUserpicLeft( + Painter &p, + int x, + int y, + int outerWidth, + int size); + private: - void createCheckbox(Fn updateCallback); - void setCheckedInternal(bool checked, SetStyle style); + void createCheckbox( + const style::RoundImageCheckbox &st, + Fn updateCallback); + void setCheckedInternal(bool checked, anim::type animated); void paintDisabledCheckUserpic( Painter &p, const style::PeerListItem &st, @@ -204,7 +228,7 @@ private: void setStatusText(const QString &text); PeerListRowId _id = 0; - not_null _peer; + PeerData *_peer = nullptr; std::unique_ptr _ripple; std::unique_ptr _checkbox; Ui::Text::String _name; @@ -245,7 +269,7 @@ public: virtual void peerListUpdateRow(not_null row) = 0; virtual void peerListRemoveRow(not_null row) = 0; virtual void peerListConvertRowToSearchResult(not_null row) = 0; - virtual bool peerListIsRowSelected(not_null peer) = 0; + virtual bool peerListIsRowChecked(not_null row) = 0; virtual void peerListSetRowChecked(not_null row, bool checked) = 0; virtual not_null peerListRowAt(int index) = 0; virtual void peerListRefreshRows() = 0; @@ -256,9 +280,17 @@ public: virtual int peerListPartitionRows(Fn border) = 0; template - void peerListAddSelectedRows(PeerDataRange &&range) { - for (auto peer : range) { - peerListAddSelectedRowInBunch(peer); + void peerListAddSelectedPeers(PeerDataRange &&range) { + for (const auto peer : range) { + peerListAddSelectedPeerInBunch(peer); + } + peerListFinishSelectedRowsBunch(); + } + + template + void peerListAddSelectedRows(PeerListRowRange &&range) { + for (const auto row : range) { + peerListAddSelectedRowInBunch(row); } peerListFinishSelectedRowsBunch(); } @@ -271,7 +303,8 @@ public: virtual ~PeerListDelegate() = default; private: - virtual void peerListAddSelectedRowInBunch(not_null peer) = 0; + virtual void peerListAddSelectedPeerInBunch(not_null peer) = 0; + virtual void peerListAddSelectedRowInBunch(not_null row) = 0; virtual void peerListFinishSelectedRowsBunch() = 0; }; @@ -366,8 +399,8 @@ public: virtual int contentWidth() const; - bool isRowSelected(not_null peer) { - return delegate()->peerListIsRowSelected(peer); + bool isRowSelected(not_null row) { + return delegate()->peerListIsRowChecked(row); } virtual bool searchInLocal() { @@ -470,7 +503,10 @@ public: void refreshRows(); void setSearchMode(PeerListSearchMode mode); - void changeCheckState(not_null row, bool checked, PeerListRow::SetStyle style); + void changeCheckState( + not_null row, + bool checked, + anim::type animated); template void reorderRows(ReorderCallback &&callback) { @@ -677,10 +713,7 @@ public: void peerListSetRowChecked( not_null row, bool checked) override { - _content->changeCheckState( - row, - checked, - PeerListRow::SetStyle::Animated); + _content->changeCheckState(row, checked, anim::type::normal); } int peerListFullRowsCount() override { return _content->fullRowsCount(); @@ -770,7 +803,7 @@ public: void peerListSetRowChecked( not_null row, bool checked) override; - bool peerListIsRowSelected(not_null peer) override; + bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; @@ -784,15 +817,26 @@ protected: void paintEvent(QPaintEvent *e) override; private: - void peerListAddSelectedRowInBunch( + void peerListAddSelectedPeerInBunch( not_null peer) override { - addSelectItem(peer, PeerListRow::SetStyle::Fast); + addSelectItem(peer, anim::type::instant); + } + void peerListAddSelectedRowInBunch(not_null row) override { + addSelectItem(row, anim::type::instant); } void peerListFinishSelectedRowsBunch() override; void addSelectItem( not_null peer, - PeerListRow::SetStyle style); + anim::type animated); + void addSelectItem( + not_null row, + anim::type animated); + void addSelectItem( + uint64 itemId, + const QString &text, + PaintRoundImageCallback paintUserpic, + anim::type animated); void createMultiSelect(); int getTopScrollSkip() const; void updateScrollSkips(); diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 66d95966b..dd523426d 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -135,10 +135,11 @@ constexpr auto NoChannel = ChannelId(0); using PeerId = uint64; constexpr auto PeerIdMask = PeerId(0xFFFFFFFFULL); -constexpr auto PeerIdTypeMask = PeerId(0x300000000ULL); +constexpr auto PeerIdTypeMask = PeerId(0xF00000000ULL); constexpr auto PeerIdUserShift = PeerId(0x000000000ULL); constexpr auto PeerIdChatShift = PeerId(0x100000000ULL); constexpr auto PeerIdChannelShift = PeerId(0x200000000ULL); +constexpr auto PeerIdFakeShift = PeerId(0xF00000000ULL); inline constexpr bool peerIsUser(const PeerId &id) { return (id & PeerIdTypeMask) == PeerIdUserShift; diff --git a/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp b/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp index 065ef7857..608922e21 100644 --- a/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp +++ b/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.cpp @@ -251,7 +251,7 @@ void InnerWidget::peerListSetTitle(rpl::producer title) { void InnerWidget::peerListSetAdditionalTitle(rpl::producer title) { } -bool InnerWidget::peerListIsRowSelected(not_null peer) { +bool InnerWidget::peerListIsRowChecked(not_null row) { return false; } @@ -267,7 +267,11 @@ void InnerWidget::peerListScrollToTop() { _scrollToRequests.fire({ -1, -1 }); } -void InnerWidget::peerListAddSelectedRowInBunch(not_null peer) { +void InnerWidget::peerListAddSelectedPeerInBunch(not_null peer) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void InnerWidget::peerListAddSelectedRowInBunch(not_null row) { Unexpected("Item selection in Info::Profile::Members."); } diff --git a/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h b/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h index 07b57eda4..f6f731400 100644 --- a/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h +++ b/Telegram/SourceFiles/info/common_groups/info_common_groups_inner_widget.h @@ -50,12 +50,14 @@ private: // PeerListContentDelegate interface. void peerListSetTitle(rpl::producer title) override; void peerListSetAdditionalTitle(rpl::producer title) override; - bool peerListIsRowSelected(not_null peer) override; + bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; - void peerListAddSelectedRowInBunch( + void peerListAddSelectedPeerInBunch( not_null peer) override; + void peerListAddSelectedRowInBunch( + not_null row) override; void peerListFinishSelectedRowsBunch() override; void peerListSetDescription( object_ptr description) override; diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp index dc90ed808..c8ac40db5 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp +++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp @@ -56,12 +56,14 @@ class ListDelegate final : public PeerListContentDelegate { public: void peerListSetTitle(rpl::producer title) override; void peerListSetAdditionalTitle(rpl::producer title) override; - bool peerListIsRowSelected(not_null peer) override; + bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; - void peerListAddSelectedRowInBunch( + void peerListAddSelectedPeerInBunch( not_null peer) override; + void peerListAddSelectedRowInBunch( + not_null row) override; void peerListFinishSelectedRowsBunch() override; void peerListSetDescription( object_ptr description) override; @@ -121,7 +123,7 @@ void ListDelegate::peerListSetTitle(rpl::producer title) { void ListDelegate::peerListSetAdditionalTitle(rpl::producer title) { } -bool ListDelegate::peerListIsRowSelected(not_null peer) { +bool ListDelegate::peerListIsRowChecked(not_null row) { return false; } @@ -137,7 +139,11 @@ auto ListDelegate::peerListCollectSelectedRows() void ListDelegate::peerListScrollToTop() { } -void ListDelegate::peerListAddSelectedRowInBunch(not_null peer) { +void ListDelegate::peerListAddSelectedPeerInBunch(not_null peer) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void ListDelegate::peerListAddSelectedRowInBunch(not_null row) { Unexpected("Item selection in Info::Profile::Members."); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.cpp b/Telegram/SourceFiles/info/profile/info_profile_members.cpp index 30fdbd22d..0f3d57301 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_members.cpp @@ -415,7 +415,7 @@ void Members::peerListSetTitle(rpl::producer title) { void Members::peerListSetAdditionalTitle(rpl::producer title) { } -bool Members::peerListIsRowSelected(not_null peer) { +bool Members::peerListIsRowChecked(not_null row) { return false; } @@ -431,7 +431,11 @@ void Members::peerListScrollToTop() { _scrollToRequests.fire({ -1, -1 }); } -void Members::peerListAddSelectedRowInBunch(not_null peer) { +void Members::peerListAddSelectedPeerInBunch(not_null peer) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void Members::peerListAddSelectedRowInBunch(not_null row) { Unexpected("Item selection in Info::Profile::Members."); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.h b/Telegram/SourceFiles/info/profile/info_profile_members.h index 33af24859..cdfba90ac 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.h +++ b/Telegram/SourceFiles/info/profile/info_profile_members.h @@ -61,12 +61,14 @@ private: // PeerListContentDelegate interface. void peerListSetTitle(rpl::producer title) override; void peerListSetAdditionalTitle(rpl::producer title) override; - bool peerListIsRowSelected(not_null peer) override; + bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; - void peerListAddSelectedRowInBunch( + void peerListAddSelectedPeerInBunch( not_null peer) override; + void peerListAddSelectedRowInBunch( + not_null row) override; void peerListFinishSelectedRowsBunch() override; void peerListSetDescription( object_ptr description) override; diff --git a/Telegram/SourceFiles/ui/effects/round_checkbox.cpp b/Telegram/SourceFiles/ui/effects/round_checkbox.cpp index fb2b5f6d7..f018132b1 100644 --- a/Telegram/SourceFiles/ui/effects/round_checkbox.cpp +++ b/Telegram/SourceFiles/ui/effects/round_checkbox.cpp @@ -323,15 +323,15 @@ void RoundCheckbox::paint(Painter &p, int x, int y, int outerWidth, float64 mast } } -void RoundCheckbox::setChecked(bool newChecked, SetStyle speed) { +void RoundCheckbox::setChecked(bool newChecked, anim::type animated) { if (_checked == newChecked) { - if (speed != SetStyle::Animated) { + if (animated == anim::type::instant) { _checkedProgress.stop(); } return; } _checked = newChecked; - if (speed == SetStyle::Animated) { + if (animated == anim::type::normal) { _checkedProgress.start( _updateCallback, _checked ? 0. : 1., @@ -441,16 +441,16 @@ float64 RoundImageCheckbox::checkedAnimationRatio() const { return snap(_selection.value(checked() ? 1. : 0.), 0., 1.); } -void RoundImageCheckbox::setChecked(bool newChecked, SetStyle speed) { +void RoundImageCheckbox::setChecked(bool newChecked, anim::type animated) { auto changed = (checked() != newChecked); - _check.setChecked(newChecked, speed); + _check.setChecked(newChecked, animated); if (!changed) { - if (speed != SetStyle::Animated) { + if (animated == anim::type::instant) { _selection.stop(); } return; } - if (speed == SetStyle::Animated) { + if (animated == anim::type::normal) { prepareWideCache(); _selection.start(_updateCallback, checked() ? 0 : 1, checked() ? 1 : 0, _st.selectDuration, anim::bumpy(1.25)); } else { diff --git a/Telegram/SourceFiles/ui/effects/round_checkbox.h b/Telegram/SourceFiles/ui/effects/round_checkbox.h index 8a905196d..ec74fc506 100644 --- a/Telegram/SourceFiles/ui/effects/round_checkbox.h +++ b/Telegram/SourceFiles/ui/effects/round_checkbox.h @@ -22,11 +22,9 @@ public: bool checked() const { return _checked; } - enum class SetStyle { - Animated, - Fast, - }; - void setChecked(bool newChecked, SetStyle speed = SetStyle::Animated); + void setChecked( + bool newChecked, + anim::type animated = anim::type::normal); void invalidateCache(); @@ -55,8 +53,9 @@ public: bool checked() const { return _check.checked(); } - using SetStyle = RoundCheckbox::SetStyle; - void setChecked(bool newChecked, SetStyle speed = SetStyle::Animated); + void setChecked( + bool newChecked, + anim::type animated = anim::type::normal); void invalidateCache() { _check.invalidateCache(); diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index aaabe5800..79ca9003d 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -304,6 +304,13 @@ windowFilterSmallItem: PeerListItem(defaultPeerListItem) { photoPosition: point(15px, 5px); namePosition: point(62px, 14px); photoSize: 34px; + checkbox: RoundImageCheckbox(defaultPeerListCheckbox) { + imageRadius: 17px; + imageSmallRadius: 14px; + } +} +windowFilterSmallList: PeerList(defaultPeerList) { + item: windowFilterSmallItem; } windowFilterSmallRemove: IconButton(notifyClose) { } @@ -316,6 +323,13 @@ windowFilterTypeBots: icon {{ "filters_type_bots", historyPeerUserpicFg }}; windowFilterTypeNoMuted: icon {{ "filters_type_muted", historyPeerUserpicFg }}; windowFilterTypeNoArchived: icon {{ "filters_type_archived", historyPeerUserpicFg }}; windowFilterTypeNoRead: icon {{ "filters_type_read", historyPeerUserpicFg }}; +windowFilterChatsSectionSubtitle: FlatLabel(defaultFlatLabel) { + style: TextStyle(defaultTextStyle) { + font: searchedBarFont; + } + textFg: searchedBarFg; +} +windowFilterChatsSectionSubtitlePadding: margins(17px, 7px, 17px, 7px); // Mac specific diff --git a/Telegram/lib_ui b/Telegram/lib_ui index c96119dcd..d2611d7e8 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit c96119dcd18bff5974348524b1e15b3d396426dc +Subproject commit d2611d7e8588759c9ecc129ce70ef0d7b2e24d6c From 96366177988c6b79b27c973fb2e6e56f4d0a4f67 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Mar 2020 18:44:25 +0400 Subject: [PATCH 029/115] Show selected chats types in search input. --- .../boxes/filters/edit_filter_box.cpp | 2 +- .../boxes/filters/edit_filter_chats_list.cpp | 56 ++++++++++++++++--- .../boxes/filters/edit_filter_chats_list.h | 4 ++ Telegram/SourceFiles/boxes/peer_list_box.cpp | 21 ++++++- Telegram/SourceFiles/boxes/peer_list_box.h | 14 +++++ 5 files changed, 87 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 5a09f640c..9360b4732 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -30,7 +30,7 @@ namespace { using namespace Settings; -constexpr auto kMaxFilterTitleLength = 20; +constexpr auto kMaxFilterTitleLength = 12; using Flag = Data::ChatFilter::Flag; using Flags = Data::ChatFilter::Flags; diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index c9d5d80d5..5740dc545 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -33,6 +33,11 @@ constexpr auto kAllTypes = { Flag::NoRead }; +struct RowSelectionChange { + not_null row; + bool checked = false; +}; + class TypeRow final : public PeerListRow { public: explicit TypeRow(Flag flag); @@ -79,7 +84,9 @@ public: void prepare() override; void rowClicked(not_null row) override; - [[nodiscard]] rpl::producer selectedOptions() const; + [[nodiscard]] rpl::producer selectedChanges() const; + [[nodiscard]] auto rowSelectionChanges() const + -> rpl::producer; private: [[nodiscard]] std::unique_ptr createRow(Flag flag) const; @@ -89,6 +96,7 @@ private: Flags _options; rpl::event_stream<> _selectionChanged; + rpl::event_stream _rowSelectionChanges; }; @@ -221,22 +229,27 @@ Flags TypeController::collectSelectedOptions() const { } void TypeController::rowClicked(not_null row) { - delegate()->peerListSetRowChecked(row, !row->checked()); - _selectionChanged.fire({}); + const auto checked = !row->checked(); + delegate()->peerListSetRowChecked(row, checked); + _rowSelectionChanges.fire({ row, checked }); } std::unique_ptr TypeController::createRow(Flag flag) const { return std::make_unique(flag); } -rpl::producer TypeController::selectedOptions() const { - return _selectionChanged.events_starting_with( - {} +rpl::producer TypeController::selectedChanges() const { + return _rowSelectionChanges.events( ) | rpl::map([=] { return collectSelectedOptions(); }); } +auto TypeController::rowSelectionChanges() const +-> rpl::producer { + return _rowSelectionChanges.events(); +} + } // namespace [[nodiscard]] QString FilterChatsTypeName(Flag flag) { @@ -326,11 +339,24 @@ void EditFilterChatsListController::itemDeselectedHook( updateTitle(); } +bool EditFilterChatsListController::isForeignRow(PeerListRowId itemId) { + return ranges::contains(kAllTypes, itemId, TypeId); +} + +bool EditFilterChatsListController::handleDeselectForeignRow( + PeerListRowId itemId) { + if (isForeignRow(itemId)) { + _deselectOption(itemId); + return true; + } + return false; +} + void EditFilterChatsListController::prepareViewHook() { delegate()->peerListSetTitle(std::move(_title)); + delegate()->peerListSetAboveWidget(prepareTypesList()); delegate()->peerListAddSelectedPeers( _peers | ranges::view::transform(&History::peer)); - delegate()->peerListSetAboveWidget(prepareTypesList()); } object_ptr EditFilterChatsListController::prepareTypesList() { @@ -354,6 +380,7 @@ object_ptr EditFilterChatsListController::prepareTypesList() { if (_selected & flag) { if (const auto row = delegate->peerListFindRow(TypeId(flag))) { content->changeCheckState(row, true, anim::type::instant); + this->delegate()->peerListSetForeignRowChecked(row, true); } } } @@ -361,11 +388,24 @@ object_ptr EditFilterChatsListController::prepareTypesList() { container, tr::lng_filters_edit_chats())); - controller->selectedOptions( + controller->selectedChanges( ) | rpl::start_with_next([=](Flags selected) { _selected = selected; }, _lifetime); + controller->rowSelectionChanges( + ) | rpl::start_with_next([=](RowSelectionChange update) { + this->delegate()->peerListSetForeignRowChecked( + update.row, + update.checked); + }, _lifetime); + + _deselectOption = [=](PeerListRowId itemId) { + if (const auto row = delegate->peerListFindRow(itemId)) { + delegate->peerListSetRowChecked(row, false); + } + }; + return result; } diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h index a4eae9fb1..e993cafb7 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h @@ -54,6 +54,8 @@ public: void rowClicked(not_null row) override; void itemDeselectedHook(not_null peer) override; + bool isForeignRow(PeerListRowId itemId) override; + bool handleDeselectForeignRow(PeerListRowId itemId) override; private: void prepareViewHook() override; @@ -68,6 +70,8 @@ private: Flags _options; Flags _selected; + Fn _deselectOption; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index b66588509..3e1042993 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -74,6 +74,9 @@ void PeerListBox::createMultiSelect() { searchQueryChanged(query); }); _select->entity()->setItemRemovedCallback([=](uint64 itemId) { + if (_controller->handleDeselectForeignRow(itemId)) { + return; + } if (const auto peer = _controller->session().data().peerLoaded(itemId)) { if (const auto row = peerListFindRow(peer->id)) { content()->changeCheckState(row, false, anim::type::normal); @@ -198,6 +201,20 @@ void PeerListBox::peerListSetRowChecked( } } +void PeerListBox::peerListSetForeignRowChecked( + not_null row, + bool checked) { + if (checked) { + addSelectItem(row, anim::type::normal); + + // This call deletes row from _searchRows. + _select->entity()->clearQuery(); + } else { + // The itemRemovedCallback will call changeCheckState() here. + _select->entity()->removeItem(row->id()); + } +} + void PeerListBox::peerListScrollToTop() { onScrollToY(0); } @@ -364,7 +381,9 @@ auto PeerListBox::peerListCollectSelectedRows() if (!items.empty()) { result.reserve(items.size()); for (const auto itemId : items) { - result.push_back(_controller->session().data().peer(itemId)); + if (!_controller->isForeignRow(itemId)) { + result.push_back(_controller->session().data().peer(itemId)); + } } } return result; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index df5027fb2..f3ab5df0e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -271,6 +271,7 @@ public: virtual void peerListConvertRowToSearchResult(not_null row) = 0; virtual bool peerListIsRowChecked(not_null row) = 0; virtual void peerListSetRowChecked(not_null row, bool checked) = 0; + virtual void peerListSetForeignRowChecked(not_null row, bool checked) = 0; virtual not_null peerListRowAt(int index) = 0; virtual void peerListRefreshRows() = 0; virtual void peerListScrollToTop() = 0; @@ -378,6 +379,12 @@ public: } virtual void itemDeselectedHook(not_null peer) { } + virtual bool isForeignRow(PeerListRowId itemId) { + return false; + } + virtual bool handleDeselectForeignRow(PeerListRowId itemId) { + return false; + } virtual base::unique_qptr rowContextMenu( QWidget *parent, not_null row); @@ -715,6 +722,10 @@ public: bool checked) override { _content->changeCheckState(row, checked, anim::type::normal); } + void peerListSetForeignRowChecked( + not_null row, + bool checked) override { + } int peerListFullRowsCount() override { return _content->fullRowsCount(); } @@ -803,6 +814,9 @@ public: void peerListSetRowChecked( not_null row, bool checked) override; + void peerListSetForeignRowChecked( + not_null row, + bool checked) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; From 38a744fe5bbe0123d2634e67fba6644535f31437 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Mar 2020 14:20:18 +0400 Subject: [PATCH 030/115] Count unread messages in Dialogs::MainList. --- .../SourceFiles/data/data_chat_filters.cpp | 9 +- Telegram/SourceFiles/data/data_folder.cpp | 122 ++++-------------- Telegram/SourceFiles/data/data_folder.h | 18 --- Telegram/SourceFiles/data/data_session.cpp | 71 +++++----- Telegram/SourceFiles/data/data_session.h | 5 - .../SourceFiles/dialogs/dialogs_entry.cpp | 43 +++--- Telegram/SourceFiles/dialogs/dialogs_entry.h | 18 ++- .../dialogs/dialogs_inner_widget.cpp | 4 +- .../SourceFiles/dialogs/dialogs_main_list.cpp | 119 ++++++++++++++++- .../SourceFiles/dialogs/dialogs_main_list.h | 35 ++++- Telegram/SourceFiles/dialogs/dialogs_row.cpp | 2 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- Telegram/SourceFiles/history/history.cpp | 17 ++- 13 files changed, 257 insertions(+), 208 deletions(-) diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index c015de468..6e6fc98d4 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -303,14 +303,16 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { || (filter.always() != updated.always()) || (filter.never() != updated.never()); if (rulesChanged) { + const auto id = filter.id(); + const auto filterList = _owner->chatsFilters().chatsList(id); const auto feedHistory = [&](not_null history) { const auto now = updated.contains(history); const auto was = filter.contains(history); if (now != was) { if (now) { - history->addToChatList(filter.id()); + history->addToChatList(id, filterList); } else { - history->removeFromChatList(filter.id()); + history->removeFromChatList(id, filterList); } } }; @@ -322,8 +324,7 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { } }; feedList(_owner->chatsList()); - const auto id = Data::Folder::kId; - if (const auto folder = _owner->folderLoaded(id)) { + if (const auto folder = _owner->folderLoaded(Data::Folder::kId)) { feedList(folder->chatsList()); } } else if (filter.title() == updated.title()) { diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index 41d3a5282..4cbcbb214 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -72,6 +72,22 @@ Folder::Folder(not_null owner, FolderId id) } } }, _lifetime); + + _chatsList.setAllAreMuted(true); + + _chatsList.unreadStateChanges( + ) | rpl::filter([=] { + return inChatList(); + }) | rpl::start_with_next([=](const Dialogs::UnreadState &old) { + ++_chatListViewVersion; + notifyUnreadStateChange(old); + updateChatListEntry(); + }, _lifetime); + + _chatsList.fullSize().changes( + ) | rpl::start_with_next([=] { + updateChatListEntry(); + }, _lifetime); } FolderId Folder::id() const { @@ -108,7 +124,7 @@ void Folder::indexNameParts() { void Folder::registerOne(not_null history) { if (_chatsList.indexed()->size() == 1) { updateChatListSortPosition(); - if (!_cloudUnread.known) { + if (!_chatsList.cloudUnreadKnown()) { owner().histories().requestDialogEntry(this); } } else { @@ -291,29 +307,6 @@ void Folder::paintUserpic( //} } -bool Folder::chatsListLoaded() const { - return _chatsList.loaded(); -} - -void Folder::setChatsListLoaded(bool loaded) { - if (_chatsList.loaded() == loaded) { - return; - } - const auto notifier = unreadStateChangeNotifier(true); - _chatsList.setLoaded(loaded); -} - -void Folder::setCloudChatsListSize(int size) { - _cloudChatsListSize = size; - updateChatListEntry(); -} - -int Folder::chatsListSize() const { - return std::max( - _chatsList.indexed()->size(), - _chatsList.loaded() ? 0 : _cloudChatsListSize); -} - const std::vector> &Folder::lastHistories() const { return _lastHistories; } @@ -333,7 +326,7 @@ TimeId Folder::adjustedChatListTimeId() const { } void Folder::applyDialog(const MTPDdialogFolder &data) { - updateCloudUnread(data); + _chatsList.updateCloudUnread(data); if (const auto peerId = peerFromMTP(data.vpeer())) { const auto history = owner().history(peerId); const auto fullId = FullMsgId( @@ -349,39 +342,6 @@ void Folder::applyDialog(const MTPDdialogFolder &data) { } } -void Folder::updateCloudUnread(const MTPDdialogFolder &data) { - const auto notifier = unreadStateChangeNotifier(!_chatsList.loaded()); - - _cloudUnread.messages = data.vunread_muted_messages_count().v - + data.vunread_unmuted_messages_count().v; - _cloudUnread.chats = data.vunread_muted_peers_count().v - + data.vunread_unmuted_peers_count().v; - finalizeCloudUnread(); - - _cloudUnread.known = true; -} - -void Folder::finalizeCloudUnread() { - // Cloud state for archive folder always counts everything as muted. - _cloudUnread.messagesMuted = _cloudUnread.messages; - _cloudUnread.chatsMuted = _cloudUnread.chats; - - // We don't know the real value of marked chats counts in _cloudUnread. - _cloudUnread.marksMuted = _cloudUnread.marks = 0; -} - -Dialogs::UnreadState Folder::chatListUnreadState() const { - const auto localUnread = _chatsList.unreadState(); - auto result = _chatsList.loaded() ? localUnread : _cloudUnread; - result.messagesMuted = result.messages; - result.chatsMuted = result.chats; - - // We don't know the real value of marked chats counts in _cloudUnread. - result.marksMuted = result.marks = localUnread.marks; - - return result; -} - void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { const auto folderId = data.vfolder_id().value_or_empty(); if (folderId != 0) { @@ -390,48 +350,6 @@ void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { owner().setChatPinned(this, data.is_pinned()); } -void Folder::unreadStateChanged( - const Dialogs::Key &key, - const Dialogs::UnreadState &wasState, - const Dialogs::UnreadState &nowState) { - if (const auto history = key.history()) { - if (wasState.empty() != nowState.empty()) { - ++_chatListViewVersion; - updateChatListEntry(); - } - } - - const auto updateCloudUnread = _cloudUnread.known && wasState.known; - const auto notify = _chatsList.loaded() || updateCloudUnread; - const auto notifier = unreadStateChangeNotifier(notify); - - _chatsList.unreadStateChanged(wasState, nowState); - if (updateCloudUnread) { - Assert(nowState.known); - _cloudUnread += nowState - wasState; - finalizeCloudUnread(); - } -} - -void Folder::unreadEntryChanged( - const Dialogs::Key &key, - const Dialogs::UnreadState &state, - bool added) { - const auto updateCloudUnread = _cloudUnread.known && state.known; - const auto notify = _chatsList.loaded() || updateCloudUnread; - const auto notifier = unreadStateChangeNotifier(notify); - - _chatsList.unreadEntryChanged(state, added); - if (updateCloudUnread) { - if (added) { - _cloudUnread += state; - } else { - _cloudUnread -= state; - } - finalizeCloudUnread(); - } -} - // #feed //MessagePosition Folder::unreadPosition() const { // return _unreadPosition.current(); @@ -457,6 +375,10 @@ int Folder::chatListUnreadCount() const { : state.chats); } +Dialogs::UnreadState Folder::chatListUnreadState() const { + return _chatsList.unreadState(); +} + bool Folder::chatListUnreadMark() const { return false; // #feed unread mark } diff --git a/Telegram/SourceFiles/data/data_folder.h b/Telegram/SourceFiles/data/data_folder.h index f1898f522..cba736f97 100644 --- a/Telegram/SourceFiles/data/data_folder.h +++ b/Telegram/SourceFiles/data/data_folder.h @@ -45,16 +45,6 @@ public: //MessagePosition unreadPosition() const; // #feed //rpl::producer unreadPositionChanges() const; // #feed - void updateCloudUnread(const MTPDdialogFolder &data); - void unreadStateChanged( - const Dialogs::Key &key, - const Dialogs::UnreadState &wasState, - const Dialogs::UnreadState &nowState); - void unreadEntryChanged( - const Dialogs::Key &key, - const Dialogs::UnreadState &state, - bool added); - TimeId adjustedChatListTimeId() const override; int fixedOnTopIndex() const override; @@ -85,11 +75,6 @@ public: const style::color &overrideBg, const style::color &overrideFg) const; - bool chatsListLoaded() const; - void setChatsListLoaded(bool loaded = true); - void setCloudChatsListSize(int size); - - int chatsListSize() const; const std::vector> &lastHistories() const; uint32 chatListViewVersion() const; @@ -99,7 +84,6 @@ private: void computeChatListMessage(); void reorderLastHistories(); - void finalizeCloudUnread(); void paintUserpic( Painter &p, @@ -116,8 +100,6 @@ private: base::flat_set _nameWords; base::flat_set _nameFirstLetters; - Dialogs::UnreadState _cloudUnread; - int _cloudChatsListSize = 0; std::vector> _lastHistories; HistoryItem *_chatListMessage = nullptr; uint32 _chatListViewVersion = 0; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 1f134b8e0..b36cb50cc 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -214,6 +214,11 @@ Session::Session(not_null session) setupChannelLeavingViewer(); setupPeerNameViewer(); setupUserIsContactViewer(); + + _chatsList.unreadStateChanges( + ) | rpl::start_with_next([] { + Notify::unreadCounterUpdated(); + }, _lifetime); } void Session::clear() { @@ -833,7 +838,7 @@ void Session::chatsListChanged(Data::Folder *folder) { void Session::chatsListDone(Data::Folder *folder) { if (folder) { - folder->setChatsListLoaded(); + folder->chatsList()->setLoaded(); } else { _chatsList.setLoaded(); } @@ -1506,7 +1511,7 @@ void Session::applyDialogs( }); } if (requestFolder && count) { - requestFolder->setCloudChatsListSize(*count); + requestFolder->chatsList()->setCloudListSize(*count); } } @@ -2016,35 +2021,6 @@ bool Session::computeUnreadBadgeMuted( : (state.chatsMuted >= state.chats)); } -void Session::unreadStateChanged( - const Dialogs::Key &key, - const Dialogs::UnreadState &wasState) { - Expects(key.entry()->folderKnown()); - Expects(key.entry()->inChatList()); - - const auto nowState = key.entry()->chatListUnreadState(); - if (const auto folder = key.entry()->folder()) { - folder->unreadStateChanged(key, wasState, nowState); - } else { - _chatsList.unreadStateChanged(wasState, nowState); - } - Notify::unreadCounterUpdated(); -} - -void Session::unreadEntryChanged(const Dialogs::Key &key, bool added) { - Expects(key.entry()->folderKnown()); - - const auto state = key.entry()->chatListUnreadState(); - if (!state.empty()) { - if (const auto folder = key.entry()->folder()) { - folder->unreadEntryChanged(key, state, added); - } else { - _chatsList.unreadEntryChanged(state, added); - } - } - Notify::unreadCounterUpdated(); -} - void Session::selfDestructIn(not_null item, crl::time delay) { _selfDestructItems.push_back(item->fullId()); if (!_selfDestructTimer.isActive() @@ -3347,33 +3323,42 @@ auto Session::refreshChatListEntry( Dialogs::Key key, FilterId filterIdForResult) -> RefreshChatListEntryResult { + Expects(key.entry()->folderKnown()); + using namespace Dialogs; const auto entry = key.entry(); const auto history = key.history(); + const auto mainList = chatsList(entry->folder()); auto mainListResult = RefreshChatListEntryResult(); mainListResult.changed = !entry->inChatList(); if (mainListResult.changed) { - const auto mainRow = entry->addToChatList(0); + const auto mainRow = entry->addToChatList(0, mainList); _contactsNoChatsList.del(key, mainRow); } else { - mainListResult.moved = entry->adjustByPosInChatList(0); + mainListResult.moved = entry->adjustByPosInChatList(0, mainList); } auto result = filterIdForResult ? RefreshChatListEntryResult() : mainListResult; + if (!history) { + return result; + } for (const auto &filter : _chatsFilters->list()) { const auto id = filter.id(); + const auto filterList = chatsFilters().chatsList(id); auto filterResult = RefreshChatListEntryResult(); - if (history && filter.contains(history)) { + if (filter.contains(history)) { filterResult.changed = !entry->inChatList(id); if (filterResult.changed) { - entry->addToChatList(id); + entry->addToChatList(id, filterList); } else { - filterResult.moved = entry->adjustByPosInChatList(id); + filterResult.moved = entry->adjustByPosInChatList( + id, + filterList); } } else if (entry->inChatList(id)) { - entry->removeFromChatList(id); + entry->removeFromChatList(id, filterList); filterResult.changed = true; } if (id == filterIdForResult) { @@ -3387,9 +3372,17 @@ void Session::removeChatListEntry(Dialogs::Key key) { using namespace Dialogs; const auto entry = key.entry(); - entry->removeFromChatList(0); + if (!entry->inChatList()) { + return; + } + Assert(entry->folderKnown()); + const auto mainList = chatsList(entry->folder()); + entry->removeFromChatList(0, mainList); for (const auto &filter : _chatsFilters->list()) { - entry->removeFromChatList(filter.id()); + const auto id = filter.id(); + if (entry->inChatList(id)) { + entry->removeFromChatList(id, chatsFilters().chatsList(id)); + } } if (_contactsList.contains(key)) { if (!_contactsNoChatsList.contains(key)) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index f4ac5d7b8..8ff99c45e 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -449,11 +449,6 @@ public: bool unreadBadgeMutedIgnoreOne(const Dialogs::Key &key) const; int unreadOnlyMutedBadge() const; - void unreadStateChanged( - const Dialogs::Key &key, - const Dialogs::UnreadState &wasState); - void unreadEntryChanged(const Dialogs::Key &key, bool added); - void selfDestructIn(not_null item, crl::time delay); [[nodiscard]] not_null photo(PhotoId id); diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 05cb676ab..c94c07814 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -112,7 +112,15 @@ void Entry::updateChatListExistence() { } void Entry::notifyUnreadStateChange(const UnreadState &wasState) { - owner().unreadStateChanged(_key, wasState); + Expects(folderKnown()); + Expects(inChatList()); + + const auto nowState = chatListUnreadState(); + owner().chatsList(folder())->unreadStateChanged(wasState, nowState); + auto &filters = owner().chatsFilters(); + for (const auto &[filterId, links] : _chatListLinks) { + filters.chatsList(filterId)->unreadStateChanged(wasState, nowState); + } } void Entry::setChatListExistence(bool exists) { @@ -154,11 +162,13 @@ Row *Entry::maybeMainChatListLink(FilterId filterId) const { return links ? links->main.get() : nullptr; } -PositionChange Entry::adjustByPosInChatList(FilterId filterId) { +PositionChange Entry::adjustByPosInChatList( + FilterId filterId, + not_null list) { const auto links = chatListLinks(filterId); Assert(links != nullptr); const auto from = links->main->pos(); - myChatsList(filterId)->adjustByDate(*links); + list->indexed()->adjustByDate(*links); const auto to = links->main->pos(); return { from, to }; } @@ -175,30 +185,27 @@ int Entry::posInChatList(FilterId filterId) const { return mainChatListLink(filterId)->pos(); } -not_null Entry::addToChatList(FilterId filterId) { +not_null Entry::addToChatList( + FilterId filterId, + not_null list) { if (const auto main = maybeMainChatListLink(filterId)) { return main; } - const auto result = _chatListLinks.emplace( + return _chatListLinks.emplace( filterId, - myChatsList(filterId)->addToEnd(_key) + list->addEntry(_key) ).first->second.main; - if (!filterId) { - owner().unreadEntryChanged(_key, true); - } - return result; } -void Entry::removeFromChatList(FilterId filterId) { +void Entry::removeFromChatList( + FilterId filterId, + not_null list) { const auto i = _chatListLinks.find(filterId); if (i == end(_chatListLinks)) { return; } - myChatsList(filterId)->del(_key); _chatListLinks.erase(i); - if (!filterId) { - owner().unreadEntryChanged(_key, false); - } + list->removeEntry(_key); } void Entry::removeChatListEntryByLetter(FilterId filterId, QChar letter) { @@ -230,10 +237,4 @@ void Entry::updateChatListEntry() const { } } -not_null Entry::myChatsList(FilterId filterId) const { - return filterId - ? owner().chatsFilters().chatsList(filterId)->indexed() - : owner().chatsList(folder())->indexed(); -} - } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index 05be48d33..eb0bfe194 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -24,6 +24,7 @@ namespace Dialogs { class Row; class IndexedList; +class MainList; struct RowsByLetter { not_null main; @@ -97,13 +98,19 @@ public: [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; - PositionChange adjustByPosInChatList(FilterId filterId); + PositionChange adjustByPosInChatList( + FilterId filterId, + not_null list); [[nodiscard]] bool inChatList(FilterId filterId = 0) const { return _chatListLinks.contains(filterId); } [[nodiscard]] int posInChatList(FilterId filterId) const; - not_null addToChatList(FilterId filterId); - void removeFromChatList(FilterId filterId); + not_null addToChatList( + FilterId filterId, + not_null list); + void removeFromChatList( + FilterId filterId, + not_null list); void removeChatListEntryByLetter(FilterId filterId, QChar letter); void addChatListEntryByLetter( FilterId filterId, @@ -176,6 +183,7 @@ public: mutable Ui::Text::String lastItemTextCache; protected: + void notifyUnreadStateChange(const UnreadState &wasState); auto unreadStateChangeNotifier(bool required) { const auto notify = required && inChatList(); const auto wasState = notify ? chatListUnreadState() : UnreadState(); @@ -189,16 +197,12 @@ protected: private: virtual void changedChatListPinHook(); - void notifyUnreadStateChange(const UnreadState &wasState); - void setChatListExistence(bool exists); RowsByLetter *chatListLinks(FilterId filterId); const RowsByLetter *chatListLinks(FilterId filterId) const; not_null mainChatListLink(FilterId filterId) const; Row *maybeMainChatListLink(FilterId filterId) const; - not_null myChatsList(FilterId filterId) const; - not_null _owner; Dialogs::Key _key; base::flat_map _chatListLinks; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index ee702a6f3..5fe6aa593 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1728,7 +1728,9 @@ void InnerWidget::fillSupportSearchMenu(not_null menu) { void InnerWidget::fillArchiveSearchMenu(not_null menu) { const auto folder = session().data().folderLoaded(Data::Folder::kId); - if (!folder || !folder->chatsListSize() || _searchInChat) { + if (!folder + || !folder->chatsList()->fullSize().current() + || _searchInChat) { return; } const auto skip = session().settings().skipArchiveInSearch(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index c6f01db27..f1e54e957 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -42,32 +42,149 @@ bool MainList::loaded() const { } void MainList::setLoaded(bool loaded) { + if (_loaded == loaded) { + return; + } + const auto notifier = unreadStateChangeNotifier(true); _loaded = loaded; + recomputeFullListSize(); +} + +void MainList::setAllAreMuted(bool allAreMuted) { + if (_allAreMuted == allAreMuted) { + return; + } + const auto notifier = unreadStateChangeNotifier(true); + _allAreMuted = allAreMuted; +} + +void MainList::setCloudListSize(int size) { + if (_cloudListSize == size) { + return; + } + _cloudListSize = size; + recomputeFullListSize(); +} + +const rpl::variable &MainList::fullSize() const { + return _fullListSize; } void MainList::clear() { + const auto notifier = unreadStateChangeNotifier(true); _all.clear(); _unreadState = UnreadState(); + _cloudUnreadState = UnreadState(); + _unreadState.known = true; + _cloudUnreadState.known = true; + _cloudListSize = 0; + recomputeFullListSize(); +} + +RowsByLetter MainList::addEntry(const Key &key) { + const auto result = _all.addToEnd(key); + + const auto unread = key.entry()->chatListUnreadState(); + unreadEntryChanged(unread, true); + recomputeFullListSize(); + + return result; +} + +void MainList::removeEntry(const Key &key) { + _all.del(key); + + const auto unread = key.entry()->chatListUnreadState(); + unreadEntryChanged(unread, false); + recomputeFullListSize(); +} + +void MainList::recomputeFullListSize() { + _fullListSize = std::max(_all.size(), loaded() ? 0 : _cloudListSize); } void MainList::unreadStateChanged( const UnreadState &wasState, const UnreadState &nowState) { + const auto updateCloudUnread = _cloudUnreadState.known && wasState.known; + const auto notify = loaded() || updateCloudUnread; + const auto notifier = unreadStateChangeNotifier(notify); _unreadState += nowState - wasState; + if (updateCloudUnread) { + Assert(nowState.known); + _cloudUnreadState += nowState - wasState; + finalizeCloudUnread(); + } } void MainList::unreadEntryChanged( const Dialogs::UnreadState &state, bool added) { + if (state.empty()) { + return; + } + const auto updateCloudUnread = _cloudUnreadState.known && state.known; + const auto notify = loaded() || updateCloudUnread; + const auto notifier = unreadStateChangeNotifier(notify); if (added) { _unreadState += state; } else { _unreadState -= state; } + if (updateCloudUnread) { + if (added) { + _cloudUnreadState += state; + } else { + _cloudUnreadState -= state; + } + finalizeCloudUnread(); + } +} + +void MainList::updateCloudUnread(const MTPDdialogFolder &data) { + const auto notifier = unreadStateChangeNotifier(!loaded()); + + _cloudUnreadState.messages = data.vunread_muted_messages_count().v + + data.vunread_unmuted_messages_count().v; + _cloudUnreadState.chats = data.vunread_muted_peers_count().v + + data.vunread_unmuted_peers_count().v; + finalizeCloudUnread(); + + _cloudUnreadState.known = true; +} + +bool MainList::cloudUnreadKnown() const { + return _cloudUnreadState.known; +} + +void MainList::finalizeCloudUnread() { + // Cloud state for archive folder always counts everything as muted. + _cloudUnreadState.messagesMuted = _cloudUnreadState.messages; + _cloudUnreadState.chatsMuted = _cloudUnreadState.chats; + + // We don't know the real value of marked chats counts in cloud unread. + _cloudUnreadState.marksMuted = _cloudUnreadState.marks = 0; } UnreadState MainList::unreadState() const { - return _unreadState; + const auto useCloudState = _cloudUnreadState.known && !loaded(); + auto result = useCloudState ? _cloudUnreadState : _unreadState; + + // We don't know the real value of marked chats counts in cloud unread. + if (useCloudState) { + result.marks = _unreadState.marks; + result.marksMuted = _unreadState.marksMuted; + } + if (_allAreMuted) { + result.messagesMuted = result.messages; + result.chatsMuted = result.chats; + result.marksMuted = result.marks; + } + return result; +} + +rpl::producer MainList::unreadStateChanges() const { + return _unreadStateChanges.events(); } not_null MainList::indexed() { diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.h b/Telegram/SourceFiles/dialogs/dialogs_main_list.h index 5bc69ba0e..c9ac27480 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.h @@ -19,28 +19,55 @@ public: bool empty() const; bool loaded() const; void setLoaded(bool loaded = true); + void setAllAreMuted(bool allAreMuted = true); void clear(); + RowsByLetter addEntry(const Key &key); + void removeEntry(const Key &key); + void unreadStateChanged( const UnreadState &wasState, const UnreadState &nowState); void unreadEntryChanged( const Dialogs::UnreadState &state, bool added); + void updateCloudUnread(const MTPDdialogFolder &data); + [[nodiscard]] bool cloudUnreadKnown() const; [[nodiscard]] UnreadState unreadState() const; + [[nodiscard]] rpl::producer unreadStateChanges() const; - not_null indexed(); - not_null indexed() const; - not_null pinned(); - not_null pinned() const; + [[nodiscard]] not_null indexed(); + [[nodiscard]] not_null indexed() const; + [[nodiscard]] not_null pinned(); + [[nodiscard]] not_null pinned() const; + + void setCloudListSize(int size); + [[nodiscard]] const rpl::variable &fullSize() const; private: + void finalizeCloudUnread(); + void recomputeFullListSize(); + + auto unreadStateChangeNotifier(bool notify) { + const auto wasState = notify ? unreadState() : UnreadState(); + return gsl::finally([=] { + if (notify) { + _unreadStateChanges.fire_copy(wasState); + } + }); + } + FilterId _filterId = 0; IndexedList _all; PinnedList _pinned; UnreadState _unreadState; + UnreadState _cloudUnreadState; + rpl::event_stream _unreadStateChanges; + rpl::variable _fullListSize = 0; + int _cloudListSize = 0; bool _loaded = false; + bool _allAreMuted = false; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 1953ad5db..00b31be44 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -28,7 +28,7 @@ QString ComposeFolderListEntryText(not_null folder) { const auto count = std::max( int(list.size()), - folder->chatsListSize()); + folder->chatsList()->fullSize().current()); const auto throwAwayLastName = (list.size() > 1) && (count == list.size() + 1); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 658bcd857..406075ee2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -280,7 +280,7 @@ Widget::Widget( onSearchMore(); } else { const auto folder = _inner->shownFolder(); - if (!folder || !folder->chatsListLoaded()) { + if (!folder || !folder->chatsList()->loaded()) { session().api().requestDialogs(folder); } } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 96148c10d..0d9680c30 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1924,12 +1924,16 @@ void History::setFolderPointer(Data::Folder *folder) { if (isPinnedDialog()) { owner().setChatPinned(this, false); } + auto &filters = owner().chatsFilters(); const auto wasKnown = folderKnown(); const auto wasInList = inChatList(); if (wasInList) { - removeFromChatList(0); - for (const auto &filter : owner().chatsFilters().list()) { - removeFromChatList(filter.id()); + removeFromChatList(0, owner().chatsList(this->folder())); + for (const auto &filter : filters.list()) { + const auto id = filter.id(); + if (inChatList(id)) { + removeFromChatList(id, filters.chatsList(id)); + } } } const auto was = _folder.value_or(nullptr); @@ -1938,10 +1942,11 @@ void History::setFolderPointer(Data::Folder *folder) { was->unregisterOne(this); } if (wasInList) { - addToChatList(0); - for (const auto &filter : owner().chatsFilters().list()) { + addToChatList(0, owner().chatsList(folder)); + for (const auto &filter : filters.list()) { if (filter.contains(this)) { - addToChatList(filter.id()); + const auto id = filter.id(); + addToChatList(id, filters.chatsList(id)); } } owner().chatsListChanged(was); From 66c0d51f96929d97e39ac5998bc11049bbffa42e Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Mar 2020 16:12:40 +0400 Subject: [PATCH 031/115] Show unread badges in filters. --- .../SourceFiles/data/data_chat_filters.cpp | 11 +++++++++-- .../SourceFiles/dialogs/dialogs_main_list.cpp | 3 ++- Telegram/SourceFiles/window/window.style | 6 +++++- .../SourceFiles/window/window_filters_menu.cpp | 18 +++++++++++++++++- Telegram/lib_ui | 2 +- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 6e6fc98d4..97beb7e81 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -160,8 +160,15 @@ bool ChatFilter::contains(not_null history) const { } return false || ((_flags & flag) - && (!(_flags & Flag::NoMuted) || !history->mute()) - && (!(_flags & Flag::NoRead) || history->unreadCountForBadge()) + && (!(_flags & Flag::NoMuted) + || !history->mute() + || (history->hasUnreadMentions() + && history->folderKnown() + && !history->folder())) + && (!(_flags & Flag::NoRead) + || history->unreadCount() + || history->unreadMark() + || history->hasUnreadMentions()) && (!(_flags & Flag::NoArchived) || (history->folderKnown() && !history->folder()))) || _always.contains(history); diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index f1e54e957..d24dcf722 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -106,8 +106,9 @@ void MainList::recomputeFullListSize() { void MainList::unreadStateChanged( const UnreadState &wasState, const UnreadState &nowState) { + const auto useClouded = _cloudUnreadState.known && !loaded(); const auto updateCloudUnread = _cloudUnreadState.known && wasState.known; - const auto notify = loaded() || updateCloudUnread; + const auto notify = !useClouded || wasState.known; const auto notifier = unreadStateChangeNotifier(notify); _unreadState += nowState - wasState; if (updateCloudUnread) { diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 79ca9003d..2800c0832 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -255,8 +255,12 @@ windowFiltersButton: SideBarButton(defaultSideBarButton) { font: font(11px semibold); } badgeStyle: TextStyle(defaultTextStyle) { - font: font(11px semibold); + font: font(12px semibold); } + badgeSkip: 4px; + badgeHeight: 17px; + badgeStroke: 2px; + badgePosition: point(5px, 7px); iconPosition: point(-1px, 6px); } windowFiltersMainMenu: SideBarButton(windowFiltersButton) { diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 1ab31de89..d9045a768 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -173,7 +173,23 @@ void FiltersMenu::refresh() { _container, title, st))); - button->setBadge(badge); + if (id > 0) { + const auto list = filters->chatsList(id); + rpl::single(rpl::empty_value()) | rpl::then( + list->unreadStateChanges( + ) | rpl::map([] { return rpl::empty_value(); }) + ) | rpl::start_with_next([=, raw = button.get()] { + const auto &state = list->unreadState(); + const auto count = (state.chats + state.marks); + const auto muted = (state.chatsMuted + state.marksMuted); + const auto string = !count + ? QString() + : (count > 99) + ? "..." + : QString::number(count); + raw->setBadge(string, count == muted); + }, button->lifetime()); + } button->setActive(_session->activeChatsFilterCurrent() == id); button->setClickedCallback([=] { if (id >= 0) { diff --git a/Telegram/lib_ui b/Telegram/lib_ui index d2611d7e8..4b6b32813 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit d2611d7e8588759c9ecc129ce70ef0d7b2e24d6c +Subproject commit 4b6b328130d74e5c5b7de6d41603650f29277ad4 From 483d4e5a4e4442fe2fc41b624ec1ae8ee691fe67 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Mar 2020 17:37:35 +0400 Subject: [PATCH 032/115] Support filters side bar in built-in themes. --- Telegram/Resources/night-green.tdesktop-theme | Bin 8149 -> 8310 bytes Telegram/Resources/night.tdesktop-theme | Bin 8213 -> 8383 bytes Telegram/lib_ui | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/Resources/night-green.tdesktop-theme b/Telegram/Resources/night-green.tdesktop-theme index 2a47a1c144ae1bddcd278ab06983f824d9b77217..6158491ac871f732041247fcd94ae0d254fd25ec 100644 GIT binary patch delta 7924 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 delta 7781 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 diff --git a/Telegram/Resources/night.tdesktop-theme b/Telegram/Resources/night.tdesktop-theme index c9d188ec1e6c694dcd6350e0333b26cdb125e383..6a557e6311959105ab882658373f7cf6e0595d37 100644 GIT binary patch delta 8220 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 O0LK6T0OB720000^1mr9L delta 8029 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 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 4b6b32813..e4e8faaa7 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 4b6b328130d74e5c5b7de6d41603650f29277ad4 +Subproject commit e4e8faaa73d1f6f9b690d7322d069183799ac6c2 From e27a8fe05889bf18a43e84afb4f8a988395dea7c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Mar 2020 17:04:30 +0400 Subject: [PATCH 033/115] Support pinned locally in filters. --- Telegram/SourceFiles/apiwrap.cpp | 8 +- .../boxes/filters/edit_filter_box.cpp | 13 ++- .../boxes/filters/edit_filter_chats_list.cpp | 8 +- Telegram/SourceFiles/boxes/peer_list_box.cpp | 5 +- Telegram/SourceFiles/boxes/peer_list_box.h | 11 ++- .../SourceFiles/data/data_chat_filters.cpp | 59 ++++++++++--- Telegram/SourceFiles/data/data_chat_filters.h | 5 ++ Telegram/SourceFiles/data/data_folder.cpp | 2 +- Telegram/SourceFiles/data/data_session.cpp | 53 +++++++++--- Telegram/SourceFiles/data/data_session.h | 15 ++-- .../SourceFiles/dialogs/dialogs_entry.cpp | 63 ++++++++++---- Telegram/SourceFiles/dialogs/dialogs_entry.h | 28 +++--- .../dialogs/dialogs_indexed_list.cpp | 26 +++--- .../dialogs/dialogs_indexed_list.h | 3 +- .../dialogs/dialogs_inner_widget.cpp | 32 ++++--- .../SourceFiles/dialogs/dialogs_layout.cpp | 16 ++-- Telegram/SourceFiles/dialogs/dialogs_layout.h | 2 +- Telegram/SourceFiles/dialogs/dialogs_list.cpp | 14 +-- Telegram/SourceFiles/dialogs/dialogs_list.h | 3 +- .../SourceFiles/dialogs/dialogs_main_list.cpp | 4 +- .../dialogs/dialogs_pinned_list.cpp | 54 +++++++++--- .../SourceFiles/dialogs/dialogs_pinned_list.h | 11 ++- Telegram/SourceFiles/dialogs/dialogs_row.cpp | 6 +- Telegram/SourceFiles/dialogs/dialogs_row.h | 2 +- Telegram/SourceFiles/history/history.cpp | 28 ++++-- Telegram/SourceFiles/history/history.h | 5 ++ .../view/history_view_top_bar_widget.cpp | 1 + .../SourceFiles/info/info_wrap_widget.cpp | 1 + .../SourceFiles/window/window_peer_menu.cpp | 85 ++++++++++++++----- .../SourceFiles/window/window_peer_menu.h | 1 + 30 files changed, 409 insertions(+), 155 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index ac5ba4ede..2b91cbe5b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -484,7 +484,9 @@ void ApiWrap::applyUpdates( } void ApiWrap::savePinnedOrder(Data::Folder *folder) { - const auto &order = _session->data().pinnedChatsOrder(folder); + const auto &order = _session->data().pinnedChatsOrder( + folder, + FilterId()); const auto input = [](const Dialogs::Key &key) { if (const auto history = key.history()) { return MTP_inputDialogPeer(history->peer->input); @@ -513,7 +515,7 @@ void ApiWrap::toggleHistoryArchived( if (const auto already = _historyArchivedRequests.take(history)) { request(already->first).cancel(); } - const auto isPinned = history->isPinnedDialog(); + const auto isPinned = history->isPinnedDialog(0); const auto archiveId = Data::Folder::kId; const auto requestId = request(MTPfolders_EditPeerFolders( MTP_vector( @@ -980,7 +982,7 @@ void ApiWrap::requestPinnedDialogs(Data::Folder *folder) { result.match([&](const MTPDmessages_peerDialogs &data) { _session->data().processUsers(data.vusers()); _session->data().processChats(data.vchats()); - _session->data().clearPinnedChats(folder); + _session->data().clearPinnedChats(folder, FilterId()); _session->data().applyDialogs( folder, data.vmessages().v, diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 9360b4732..558c96b82 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -106,20 +106,24 @@ not_null SetupChatsPreview( data->title(), (data->flags() & ~flag), data->always(), + data->pinned(), data->never()); }, preview->lifetime()); preview->peerRemoved( ) | rpl::start_with_next([=](not_null history) { auto always = data->always(); + auto pinned = data->pinned(); auto never = data->never(); always.remove(history); + pinned.erase(ranges::remove(pinned, history), end(pinned)); never.remove(history); *data = Data::ChatFilter( data->id(), data->title(), data->flags(), std::move(always), + std::move(pinned), std::move(never)); }, preview->lifetime()); @@ -272,7 +276,7 @@ void EditExceptions( const auto peers = box->peerListCollectSelectedRows(); auto &&histories = ranges::view::all( peers - ) | ranges::view::transform([=](not_null peer) { + ) | ranges::view::transform([=](not_null peer) { return window->session().data().history(peer); }); auto changed = base::flat_set>{ @@ -283,11 +287,17 @@ void EditExceptions( for (const auto &history : changed) { removeFrom.remove(history); } + auto pinned = data->pinned(); + pinned.erase(ranges::remove_if(pinned, [&](not_null history) { + const auto contains = changed.contains(history); + return include ? !contains : contains; + }), end(pinned)); *data = Data::ChatFilter( data->id(), data->title(), (data->flags() & ~options) | rawController->chosenOptions(), include ? std::move(changed) : std::move(removeFrom), + std::move(pinned), include ? std::move(removeFrom) : std::move(changed)); refresh(); box->closeBox(); @@ -407,6 +417,7 @@ void EditFilterBox( title, data->flags(), data->always(), + data->pinned(), data->never()); box->closeBox(); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index 5740dc545..7650c00bf 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -380,7 +380,10 @@ object_ptr EditFilterChatsListController::prepareTypesList() { if (_selected & flag) { if (const auto row = delegate->peerListFindRow(TypeId(flag))) { content->changeCheckState(row, true, anim::type::instant); - this->delegate()->peerListSetForeignRowChecked(row, true); + this->delegate()->peerListSetForeignRowChecked( + row, + true, + anim::type::instant); } } } @@ -397,7 +400,8 @@ object_ptr EditFilterChatsListController::prepareTypesList() { ) | rpl::start_with_next([=](RowSelectionChange update) { this->delegate()->peerListSetForeignRowChecked( update.row, - update.checked); + update.checked, + anim::type::normal); }, _lifetime); _deselectOption = [=](PeerListRowId itemId) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 3e1042993..2f86bf5e1 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -203,9 +203,10 @@ void PeerListBox::peerListSetRowChecked( void PeerListBox::peerListSetForeignRowChecked( not_null row, - bool checked) { + bool checked, + anim::type animated) { if (checked) { - addSelectItem(row, anim::type::normal); + addSelectItem(row, animated); // This call deletes row from _searchRows. _select->entity()->clearQuery(); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index f3ab5df0e..005fa563a 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -271,7 +271,10 @@ public: virtual void peerListConvertRowToSearchResult(not_null row) = 0; virtual bool peerListIsRowChecked(not_null row) = 0; virtual void peerListSetRowChecked(not_null row, bool checked) = 0; - virtual void peerListSetForeignRowChecked(not_null row, bool checked) = 0; + virtual void peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) = 0; virtual not_null peerListRowAt(int index) = 0; virtual void peerListRefreshRows() = 0; virtual void peerListScrollToTop() = 0; @@ -724,7 +727,8 @@ public: } void peerListSetForeignRowChecked( not_null row, - bool checked) override { + bool checked, + anim::type animated) override { } int peerListFullRowsCount() override { return _content->fullRowsCount(); @@ -816,7 +820,8 @@ public: bool checked) override; void peerListSetForeignRowChecked( not_null row, - bool checked) override; + bool checked, + anim::type animated) override; bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 97beb7e81..d5e1c6aad 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -25,10 +25,12 @@ ChatFilter::ChatFilter( const QString &title, Flags flags, base::flat_set> always, + std::vector> pinned, base::flat_set> never) : _id(id) , _title(title) , _always(std::move(always)) +, _pinned(std::move(pinned)) , _never(std::move(never)) , _flags(flags) { } @@ -66,17 +68,26 @@ ChatFilter ChatFilter::FromTL( }) | ranges::view::transform([](History *history) { return not_null(history); }); - auto &&always = ranges::view::all( + auto &&always = ranges::view::concat( data.vinclude_peers().v ) | to_histories; + auto pinned = ranges::view::all( + data.vpinned_peers().v + ) | to_histories | ranges::to_vector; auto &&never = ranges::view::all( data.vexclude_peers().v ) | to_histories; + auto &&all = ranges::view::concat(always, pinned); + auto list = base::flat_set>{ + all.begin(), + all.end() + }; return ChatFilter( data.vid().v, qs(data.vtitle()), flags, - { always.begin(), always.end() }, + std::move(list), + std::move(pinned), { never.begin(), never.end() }); }); } @@ -94,10 +105,17 @@ MTPDialogFilter ChatFilter::tl() const { | ((_flags & Flag::NoArchived) ? TLFlag::f_exclude_archived : TLFlag(0)); - auto always = QVector(); - always.reserve(_always.size()); - for (const auto history : _always) { - always.push_back(history->peer->input); + auto always = _always; + auto pinned = QVector(); + pinned.reserve(_pinned.size()); + for (const auto history : _pinned) { + pinned.push_back(history->peer->input); + always.remove(history); + } + auto include = QVector(); + include.reserve(always.size()); + for (const auto history : always) { + include.push_back(history->peer->input); } auto never = QVector(); never.reserve(_never.size()); @@ -109,8 +127,8 @@ MTPDialogFilter ChatFilter::tl() const { MTP_int(_id), MTP_string(_title), MTPstring(), // emoticon - MTP_vector(), - MTP_vector(always), + MTP_vector(pinned), + MTP_vector(include), MTP_vector(never)); } @@ -130,6 +148,10 @@ const base::flat_set> &ChatFilter::always() const { return _always; } +const std::vector> &ChatFilter::pinned() const { + return _pinned; +} + const base::flat_set> &ChatFilter::never() const { return _never; } @@ -196,7 +218,7 @@ not_null ChatFilters::chatsList(FilterId filterId) { if (!pointer) { pointer = std::make_unique( filterId, - rpl::single(1)); + rpl::single(ChatFilter::kPinnedLimit)); } return pointer.get(); } @@ -284,7 +306,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) { _list.insert( begin(_list) + position, - ChatFilter(filter.id(), {}, {}, {}, {})); + ChatFilter(filter.id(), {}, {}, {}, {}, {})); applyChange(*(begin(_list) + position), std::move(filter)); } @@ -301,7 +323,7 @@ void ChatFilters::applyRemove(int position) { Expects(position >= 0 && position < _list.size()); const auto i = begin(_list) + position; - applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {})); + applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {})); _list.erase(i); } @@ -309,12 +331,21 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { const auto rulesChanged = (filter.flags() != updated.flags()) || (filter.always() != updated.always()) || (filter.never() != updated.never()); + const auto pinnedChanged = (filter.pinned() != updated.pinned()); + if (!rulesChanged + && !pinnedChanged + && filter.title() == updated.title()) { + return false; + } if (rulesChanged) { const auto id = filter.id(); const auto filterList = _owner->chatsFilters().chatsList(id); const auto feedHistory = [&](not_null history) { const auto now = updated.contains(history); const auto was = filter.contains(history); + if (now) { + history->applyFilterPinnedIndex(id, updated); + } if (now != was) { if (now) { history->addToChatList(id, filterList); @@ -334,8 +365,10 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { if (const auto folder = _owner->folderLoaded(Data::Folder::kId)) { feedList(folder->chatsList()); } - } else if (filter.title() == updated.title()) { - return false; + } else if (pinnedChanged) { + const auto id = filter.id(); + const auto filterList = _owner->chatsFilters().chatsList(id); + filterList->pinned()->applyList(updated.pinned()); } filter = std::move(updated); return true; diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 7e6b2775d..b5a6efbc2 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -34,12 +34,15 @@ public: friend constexpr inline bool is_flag_type(Flag) { return true; }; using Flags = base::flags; + static constexpr int kPinnedLimit = 100; + ChatFilter() = default; ChatFilter( FilterId id, const QString &title, Flags flags, base::flat_set> always, + std::vector> pinned, base::flat_set> never); [[nodiscard]] static ChatFilter FromTL( @@ -51,6 +54,7 @@ public: [[nodiscard]] QString title() const; [[nodiscard]] Flags flags() const; [[nodiscard]] const base::flat_set> &always() const; + [[nodiscard]] const std::vector> &pinned() const; [[nodiscard]] const base::flat_set> &never() const; [[nodiscard]] bool contains(not_null history) const; @@ -59,6 +63,7 @@ private: FilterId _id = 0; QString _title; base::flat_set> _always; + std::vector> _pinned; base::flat_set> _never; Flags _flags; diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index 4cbcbb214..0d07bd833 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -347,7 +347,7 @@ void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { if (folderId != 0) { LOG(("API Error: Nested folders detected.")); } - owner().setChatPinned(this, data.is_pinned()); + owner().setChatPinned(this, FilterId(), data.is_pinned()); } // #feed diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index b36cb50cc..20e9df135 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -780,7 +780,7 @@ void Session::deleteConversationLocally(not_null peer) { const auto history = historyLoaded(peer); if (history) { if (history->folderKnown()) { - setChatPinned(history, false); + setChatPinned(history, FilterId(), false); } App::main()->removeDialog(history); history->clear(peer->isChannel() @@ -1459,11 +1459,16 @@ MessageIdsList Session::itemOrItsGroup(not_null item) const { return { 1, item->fullId() }; } -void Session::setChatPinned(const Dialogs::Key &key, bool pinned) { +void Session::setChatPinned( + const Dialogs::Key &key, + FilterId filterId, + bool pinned) { Expects(key.entry()->folderKnown()); - const auto list = chatsList(key.entry()->folder())->pinned(); - list->setPinned(key, pinned); + const auto list = filterId + ? chatsFilters().chatsList(filterId) + : chatsList(key.entry()->folder()); + list->pinned()->setPinned(key, pinned); notifyPinnedDialogsOrderUpdated(); } @@ -1549,32 +1554,53 @@ void Session::applyDialog( setPinnedFromDialog(folder, data.is_pinned()); } -int Session::pinnedChatsCount(Data::Folder *folder) const { - return pinnedChatsOrder(folder).size(); +int Session::pinnedChatsCount( + Data::Folder *folder, + FilterId filterId) const { + Expects(!folder || !filterId); + + if (!filterId) { + return pinnedChatsOrder(folder, filterId).size(); + } + const auto &list = chatsFilters().list(); + const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); + return (i != end(list)) ? i->pinned().size() : 0; } -int Session::pinnedChatsLimit(Data::Folder *folder) const { - return folder +int Session::pinnedChatsLimit( + Data::Folder *folder, + FilterId filterId) const { + return filterId + ? Data::ChatFilter::kPinnedLimit + : folder ? Global::PinnedDialogsInFolderMax() : Global::PinnedDialogsCountMax(); } const std::vector &Session::pinnedChatsOrder( - Data::Folder *folder) const { - return chatsList(folder)->pinned()->order(); + Data::Folder *folder, + FilterId filterId) const { + const auto list = filterId + ? chatsFilters().chatsList(filterId) + : chatsList(folder); + return list->pinned()->order(); } -void Session::clearPinnedChats(Data::Folder *folder) { +void Session::clearPinnedChats(Data::Folder *folder, FilterId filterId) { chatsList(folder)->pinned()->clear(); } void Session::reorderTwoPinnedChats( + FilterId filterId, const Dialogs::Key &key1, const Dialogs::Key &key2) { Expects(key1.entry()->folderKnown() && key2.entry()->folderKnown()); - Expects(key1.entry()->folder() == key2.entry()->folder()); + Expects(filterId || (key1.entry()->folder() == key2.entry()->folder())); - chatsList(key1.entry()->folder())->pinned()->reorder(key1, key2); + const auto list = filterId + ? chatsFilters().chatsList(filterId) + : chatsList(key1.entry()->folder()); + list->pinned()->reorder(key1, key2); notifyPinnedDialogsOrderUpdated(); } @@ -3349,6 +3375,7 @@ auto Session::refreshChatListEntry( const auto filterList = chatsFilters().chatsList(id); auto filterResult = RefreshChatListEntryResult(); if (filter.contains(history)) { + history->applyFilterPinnedIndex(id, filter); filterResult.changed = !entry->inChatList(id); if (filterResult.changed) { entry->addToChatList(id, filterList); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 8ff99c45e..fecc1fc87 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -362,16 +362,21 @@ public: const QVector &dialogs, std::optional count = std::nullopt); - int pinnedChatsCount(Data::Folder *folder) const; - int pinnedChatsLimit(Data::Folder *folder) const; + int pinnedChatsCount(Data::Folder *folder, FilterId filterId) const; + int pinnedChatsLimit(Data::Folder *folder, FilterId filterId) const; const std::vector &pinnedChatsOrder( - Data::Folder *folder) const; - void setChatPinned(const Dialogs::Key &key, bool pinned); - void clearPinnedChats(Data::Folder *folder); + Data::Folder *folder, + FilterId filterId) const; + void setChatPinned( + const Dialogs::Key &key, + FilterId filterId, + bool pinned); + void clearPinnedChats(Data::Folder *folder, FilterId filterId); void applyPinnedChats( Data::Folder *folder, const QVector &list); void reorderTwoPinnedChats( + FilterId filterId, const Dialogs::Key &key1, const Dialogs::Key &key2); diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index c94c07814..ecf99794b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -55,19 +55,34 @@ Main::Session &Entry::session() const { return _owner->session(); } -void Entry::cachePinnedIndex(int index) { - if (_pinnedIndex != index) { - const auto wasPinned = isPinnedDialog(); - _pinnedIndex = index; - if (session().supportMode()) { - // Force reorder in support mode. - _sortKeyInChatList = 0; - } - updateChatListSortPosition(); - updateChatListEntry(); - if (wasPinned != isPinnedDialog()) { - changedChatListPinHook(); +void Entry::pinnedIndexChanged(int was, int now) { + if (session().supportMode()) { + // Force reorder in support mode. + _sortKeyInChatList = 0; + } + updateChatListSortPosition(); + updateChatListEntry(); + if ((was != 0) != (now != 0)) { + changedChatListPinHook(); + } +} + +void Entry::cachePinnedIndex(FilterId filterId, int index) { + const auto i = _pinnedIndex.find(filterId); + const auto was = (i != end(_pinnedIndex)) ? i->second : 0; + if (index == was) { + return; + } + if (!index) { + _pinnedIndex.erase(i); + pinnedIndexChanged(was, index); + } else { + if (!was) { + _pinnedIndex.emplace(filterId, index); + } else { + i->second = index; } + pinnedIndexChanged(was, index); } } @@ -97,9 +112,7 @@ void Entry::updateChatListSortPosition() { const auto fixedIndex = fixedOnTopIndex(); _sortKeyInChatList = fixedIndex ? FixedOnTopDialogPos(fixedIndex) - : isPinnedDialog() - ? PinnedDialogPos(_pinnedIndex) - : _sortKeyByDate; + : computeSortPosition(0); if (needUpdateInChatList()) { setChatListExistence(true); } else { @@ -107,6 +120,23 @@ void Entry::updateChatListSortPosition() { } } +int Entry::lookupPinnedIndex(FilterId filterId) const { + if (filterId) { + const auto i = _pinnedIndex.find(filterId); + return (i != end(_pinnedIndex)) ? i->second : 0; + } else if (!_pinnedIndex.empty()) { + return _pinnedIndex.front().first + ? 0 + : _pinnedIndex.front().second; + } + return 0; +} + +uint64 Entry::computeSortPosition(FilterId filterId) const { + const auto index = lookupPinnedIndex(filterId); + return index ? PinnedDialogPos(index) : _sortKeyByDate; +} + void Entry::updateChatListExistence() { setChatListExistence(shouldBeInChatList()); } @@ -205,6 +235,9 @@ void Entry::removeFromChatList( return; } _chatListLinks.erase(i); + if (isPinnedDialog(filterId)) { + owner().setChatPinned(_key, filterId, false); + } list->removeEntry(_key); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index eb0bfe194..b9e21a369 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -32,10 +32,9 @@ struct RowsByLetter { }; enum class SortMode { - Complex = 0x00, - Date = 0x01, - Name = 0x02, - Add = 0x04, + Date = 0x00, + Name = 0x01, + Add = 0x02, }; struct PositionChange { @@ -117,19 +116,18 @@ public: QChar letter, not_null row); void updateChatListEntry() const; - bool isPinnedDialog() const { - return _pinnedIndex > 0; + [[nodiscard]] bool isPinnedDialog(FilterId filterId) const { + return lookupPinnedIndex(filterId) != 0; } - void cachePinnedIndex(int index); + void cachePinnedIndex(FilterId filterId, int index); bool isProxyPromoted() const { return _isProxyPromoted; } void cacheProxyPromoted(bool promoted); - uint64 sortKeyInChatList() const { - return _sortKeyInChatList; - } - uint64 sortKeyByDate() const { - return _sortKeyByDate; + [[nodiscard]] uint64 sortKeyInChatList(FilterId filterId) const { + return filterId + ? computeSortPosition(filterId) + : _sortKeyInChatList; } void updateChatListSortPosition(); void setChatListTimeId(TimeId date); @@ -194,8 +192,12 @@ protected: }); } + [[nodiscard]] int lookupPinnedIndex(FilterId filterId) const; + private: virtual void changedChatListPinHook(); + void pinnedIndexChanged(int was, int now); + [[nodiscard]] uint64 computeSortPosition(FilterId filterId) const; void setChatListExistence(bool exists); RowsByLetter *chatListLinks(FilterId filterId); @@ -208,7 +210,7 @@ private: base::flat_map _chatListLinks; uint64 _sortKeyInChatList = 0; uint64 _sortKeyByDate = 0; - int _pinnedIndex = 0; + base::flat_map _pinnedIndex; bool _isProxyPromoted = false; TimeId _timeId = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp index 5af4ca39c..19a4957d1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -13,10 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Dialogs { -IndexedList::IndexedList(SortMode sortMode) +IndexedList::IndexedList(SortMode sortMode, FilterId filterId) : _sortMode(sortMode) -, _list(sortMode) -, _empty(sortMode) { +, _filterId(filterId) +, _list(sortMode, filterId) +, _empty(sortMode, filterId) { } RowsByLetter IndexedList::addToEnd(Key key) { @@ -28,7 +29,7 @@ RowsByLetter IndexedList::addToEnd(Key key) { for (const auto ch : key.entry()->chatListFirstLetters()) { auto j = _index.find(ch); if (j == _index.cend()) { - j = _index.emplace(ch, _sortMode).first; + j = _index.emplace(ch, _sortMode, _filterId).first; } result.letters.emplace(ch, j->second.addToEnd(key)); } @@ -44,7 +45,7 @@ Row *IndexedList::addByName(Key key) { for (const auto ch : key.entry()->chatListFirstLetters()) { auto j = _index.find(ch); if (j == _index.cend()) { - j = _index.emplace(ch, _sortMode).first; + j = _index.emplace(ch, _sortMode, _filterId).first; } j->second.addByName(key); } @@ -79,7 +80,8 @@ void IndexedList::movePinned(Row *row, int deltaSign) { Assert(swapPinnedIndexWith != cbegin()); --swapPinnedIndexWith; } - Auth().data().reorderTwoPinnedChats( + row->key().entry()->owner().reorderTwoPinnedChats( + _filterId, row->key(), (*swapPinnedIndexWith)->key()); } @@ -87,7 +89,7 @@ void IndexedList::movePinned(Row *row, int deltaSign) { void IndexedList::peerNameChanged( not_null peer, const base::flat_set &oldLetters) { - Expects(_sortMode != SortMode::Date && _sortMode != SortMode::Complex); + Expects(_sortMode != SortMode::Date); if (const auto history = peer->owner().historyLoaded(peer)) { if (_sortMode == SortMode::Name) { @@ -102,7 +104,7 @@ void IndexedList::peerNameChanged( FilterId filterId, not_null peer, const base::flat_set &oldLetters) { - Expects(_sortMode == SortMode::Date || _sortMode == SortMode::Complex); + Expects(_sortMode == SortMode::Date); if (const auto history = peer->owner().historyLoaded(peer)) { adjustNames(filterId, history, oldLetters); @@ -139,7 +141,7 @@ void IndexedList::adjustByName( for (auto ch : toAdd) { auto j = _index.find(ch); if (j == _index.cend()) { - j = _index.emplace(ch, _sortMode).first; + j = _index.emplace(ch, _sortMode, _filterId).first; } j->second.addByName(key); } @@ -165,7 +167,7 @@ void IndexedList::adjustNames( } } for (auto ch : toRemove) { - if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) { + if (_sortMode == SortMode::Date) { history->removeChatListEntryByLetter(filterId, ch); } if (auto it = _index.find(ch); it != _index.cend()) { @@ -175,10 +177,10 @@ void IndexedList::adjustNames( for (auto ch : toAdd) { auto j = _index.find(ch); if (j == _index.cend()) { - j = _index.emplace(ch, _sortMode).first; + j = _index.emplace(ch, _sortMode, _filterId).first; } auto row = j->second.addToEnd(key); - if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) { + if (_sortMode == SortMode::Date) { history->addChatListEntryByLetter(filterId, ch, row); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h index e2ae1346f..99c362571 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h @@ -16,7 +16,7 @@ namespace Dialogs { class IndexedList { public: - IndexedList(SortMode sortMode); + IndexedList(SortMode sortMode, FilterId filterId = 0); RowsByLetter addToEnd(Key key); Row *addByName(Key key); @@ -81,6 +81,7 @@ private: const base::flat_set &oldChars); SortMode _sortMode = SortMode(); + FilterId _filterId = 0; List _list, _empty; base::flat_map _index; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 5fe6aa593..f3bea9083 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -66,12 +66,14 @@ int FixedOnTopDialogsCount(not_null list) { return result; } -int PinnedDialogsCount(not_null list) { +int PinnedDialogsCount( + FilterId filterId, + not_null list) { auto result = 0; for (const auto row : *list) { if (row->entry()->fixedOnTopIndex()) { continue; - } else if (!row->entry()->isPinnedDialog()) { + } else if (!row->entry()->isPinnedDialog(filterId)) { break; } ++result; @@ -1075,7 +1077,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { }); } if (anim::Disabled() - && (!_pressed || !_pressed->entry()->isPinnedDialog())) { + && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { mousePressReleased(e->globalPos(), e->button()); } } @@ -1091,7 +1093,9 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) { if (updateReorderIndexGetCount() < 2) { _dragging = nullptr; } else { - const auto &order = session().data().pinnedChatsOrder(_openedFolder); + const auto &order = session().data().pinnedChatsOrder( + _openedFolder, + _filterId); _pinnedOnDragStart = base::flat_set{ order.begin(), order.end() @@ -1103,14 +1107,14 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) { } int InnerWidget::countPinnedIndex(Row *ofRow) { - if (!ofRow || !ofRow->entry()->isPinnedDialog()) { + if (!ofRow || !ofRow->entry()->isPinnedDialog(_filterId)) { return -1; } auto result = 0; for (const auto row : *shownDialogs()) { if (row->entry()->fixedOnTopIndex()) { continue; - } else if (!row->entry()->isPinnedDialog()) { + } else if (!row->entry()->isPinnedDialog(_filterId)) { break; } else if (row == ofRow) { return result; @@ -1121,7 +1125,9 @@ int InnerWidget::countPinnedIndex(Row *ofRow) { } void InnerWidget::savePinnedOrder() { - const auto &newOrder = session().data().pinnedChatsOrder(_openedFolder); + const auto &newOrder = session().data().pinnedChatsOrder( + _openedFolder, + _filterId); if (newOrder.size() != _pinnedOnDragStart.size()) { return; // Something has changed in the set of pinned chats. } @@ -1130,7 +1136,11 @@ void InnerWidget::savePinnedOrder() { return; // Something has changed in the set of pinned chats. } } - session().api().savePinnedOrder(_openedFolder); + if (_filterId) { + // #TODO pinned reorder data and to server + } else { + session().api().savePinnedOrder(_openedFolder); + } } void InnerWidget::finishReorderPinned() { @@ -1162,7 +1172,7 @@ int InnerWidget::updateReorderIndexGetCount() { return 0; } - const auto count = Dialogs::PinnedDialogsCount(shownDialogs()); + const auto count = Dialogs::PinnedDialogsCount(_filterId, shownDialogs()); Assert(index < count); if (count < 2) { stopReorderPinned(); @@ -1789,6 +1799,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { Window::FillPeerMenu( _controller, history->peer, + _filterId, [&](const QString &text, Fn callback) { return _menu->addAction(text, std::move(callback)); }, @@ -2541,6 +2552,7 @@ void InnerWidget::switchToFilter(FilterId filterId) { &Data::ChatFilter::id)) { filterId = 0; } + stopReorderPinned(); _filterId = filterId; refreshWithCollapsedRows(true); _collapsedSelected = 0; @@ -2983,7 +2995,7 @@ void InnerWidget::setupShortcuts() { for (const auto [command, index] : pinned) { request->check(command) && request->handle([=, index = index] { const auto list = session().data().chatsList()->indexed(); - const auto count = Dialogs::PinnedDialogsCount(list); + const auto count = Dialogs::PinnedDialogsCount(_filterId, list); if (index >= count) { return false; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index 879cf8e3c..f5e681e25 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -218,6 +218,7 @@ void paintRow( not_null row, not_null entry, Dialogs::Key chat, + FilterId filterId, PeerData *from, const HiddenSenderInfo *hiddenSenderInfo, HistoryItem *item, @@ -322,7 +323,7 @@ void paintRow( } auto availableWidth = namewidth; - if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) { + if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) { auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; @@ -349,7 +350,7 @@ void paintRow( } } else if (!item) { auto availableWidth = namewidth; - if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) { + if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) { auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; @@ -366,7 +367,7 @@ void paintRow( } paintItemCallback(nameleft, namewidth); - } else if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) { + } else if (entry->isPinnedDialog(filterId) && (filterId || !entry->fixedOnTopIndex())) { auto availableWidth = namewidth; auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); @@ -613,7 +614,7 @@ void paintUnreadCount( void RowPainter::paint( Painter &p, not_null row, - int filterId, + FilterId filterId, int fullWidth, bool active, bool selected, @@ -669,9 +670,8 @@ void RowPainter::paint( const auto displayPinnedIcon = !displayUnreadCounter && !displayMentionBadge && !displayUnreadMark - && !filterId - && entry->isPinnedDialog() - && !entry->fixedOnTopIndex(); + && entry->isPinnedDialog(filterId) + && (filterId || !entry->fixedOnTopIndex()); const auto from = history ? (history->peer->migrateTo() @@ -749,6 +749,7 @@ void RowPainter::paint( row, entry, row->key(), + filterId, from, nullptr, item, @@ -872,6 +873,7 @@ void RowPainter::paint( row, history, history, + FilterId(), from, hiddenSenderInfo, item, diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h index 6745569de..2ef8f29e9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -29,7 +29,7 @@ public: static void paint( Painter &p, not_null row, - int filterId, + FilterId filterId, int fullWidth, bool active, bool selected, diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp index 7d3c196b4..51d8f6734 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp @@ -14,7 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Dialogs { -List::List(SortMode sortMode) : _sortMode(sortMode) { +List::List(SortMode sortMode, FilterId filterId) +: _sortMode(sortMode) +, _filterId(filterId) { } List::const_iterator List::cfind(Row *value) const { @@ -32,7 +34,7 @@ not_null List::addToEnd(Key key) { std::make_unique(key, _rows.size()) ).first->second.get(); _rows.emplace_back(result); - if (_sortMode == SortMode::Date || _sortMode == SortMode::Complex) { + if (_sortMode == SortMode::Date) { adjustByDate(result); } return result; @@ -82,20 +84,20 @@ void List::adjustByName(not_null row) { } void List::adjustByDate(not_null row) { - Expects(_sortMode == SortMode::Date || _sortMode == SortMode::Complex); + Expects(_sortMode == SortMode::Date); - const auto key = row->sortKey(_sortMode); + const auto key = row->sortKey(_filterId); const auto index = row->pos(); const auto i = _rows.begin() + index; const auto before = std::find_if(i + 1, _rows.end(), [&](Row *row) { - return (row->sortKey(_sortMode) <= key); + return (row->sortKey(_filterId) <= key); }); if (before != i + 1) { rotate(i, i + 1, before); } else { const auto from = std::make_reverse_iterator(i); const auto after = std::find_if(from, _rows.rend(), [&](Row *row) { - return (row->sortKey(_sortMode) >= key); + return (row->sortKey(_filterId) >= key); }).base(); if (after != i) { rotate(after, i, i + 1); diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.h b/Telegram/SourceFiles/dialogs/dialogs_list.h index 34fb53d14..00e4985e6 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_list.h @@ -16,7 +16,7 @@ enum class SortMode; class List final { public: - List(SortMode sortMode); + List(SortMode sortMode, FilterId filterId = 0); List(const List &other) = delete; List &operator=(const List &other) = delete; List(List &&other) = default; @@ -78,6 +78,7 @@ private: std::vector>::iterator last); SortMode _sortMode = SortMode(); + FilterId _filterId = 0; std::vector> _rows; std::map> _rowByKey; diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index d24dcf722..ee552a265 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -14,8 +14,8 @@ namespace Dialogs { MainList::MainList(FilterId filterId, rpl::producer pinnedLimit) : _filterId(filterId) -, _all(filterId ? SortMode::Date : SortMode::Complex) -, _pinned(1) { +, _all(SortMode::Date, filterId) +, _pinned(filterId, 1) { _unreadState.known = true; std::move( diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp index 6b6e1bdd7..5b79fa9b8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp @@ -9,11 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_key.h" #include "dialogs/dialogs_entry.h" +#include "history/history.h" #include "data/data_session.h" namespace Dialogs { -PinnedList::PinnedList(int limit) : _limit(limit) { +PinnedList::PinnedList(FilterId filterId, int limit) +: _filterId(filterId) +, _limit(limit) { Expects(limit > 0); } @@ -41,7 +44,7 @@ int PinnedList::addPinnedGetPosition(const Key &key) { applyLimit(_limit - 1); const auto position = int(_data.size()); _data.push_back(key); - key.entry()->cachePinnedIndex(position + 1); + key.entry()->cachePinnedIndex(_filterId, position + 1); return position; } @@ -54,19 +57,34 @@ void PinnedList::setPinned(const Key &key, bool pinned) { const auto begin = _data.begin(); std::rotate(begin, begin + position, begin + position + 1); for (auto i = 0; i != position + 1; ++i) { - _data[i].entry()->cachePinnedIndex(i + 1); + _data[i].entry()->cachePinnedIndex(_filterId, i + 1); } } } else if (const auto it = ranges::find(_data, key); it != end(_data)) { const auto index = int(it - begin(_data)); _data.erase(it); - key.entry()->cachePinnedIndex(0); + key.entry()->cachePinnedIndex(_filterId, 0); for (auto i = index, count = int(size(_data)); i != count; ++i) { - _data[i].entry()->cachePinnedIndex(i + 1); + _data[i].entry()->cachePinnedIndex(_filterId, i + 1); } } } +void PinnedList::applyFilterPinned( + FilterId filterId, + not_null history, + int index) { + Expects(index > 0); + + history->cachePinnedIndex(filterId, index); + + const auto key = Key{ history }; + if (ranges::find(_data, key) == end(_data)) { + _data.push_back(key); + // #TODO pinned + } +} + void PinnedList::applyLimit(int limit) { Expects(limit >= 0); @@ -83,18 +101,32 @@ void PinnedList::applyList( not_null owner, const QVector &list) { clear(); - for (const auto &peer : ranges::view::reverse(list)) { + for (const auto &peer : list) { peer.match([&](const MTPDdialogPeer &data) { if (const auto peerId = peerFromMTP(data.vpeer())) { - setPinned(owner->history(peerId), true); + addPinned(owner->history(peerId)); } }, [&](const MTPDdialogPeerFolder &data) { - const auto folderId = data.vfolder_id().v; - setPinned(owner->folder(folderId), true); + addPinned(owner->folder(data.vfolder_id().v)); }); } } +void PinnedList::applyList(const std::vector> &list) { + Expects(_filterId != 0); + + clear(); + const auto count = int(list.size()); + _data.reserve(count); + for (auto i = 0; i != count; ++i) { + const auto history = list[i]; + if (history->inChatList()) { + _data.emplace_back(history); + history->cachePinnedIndex(_filterId, i + 1); + } + } +} + void PinnedList::reorder(const Key &key1, const Key &key2) { const auto index1 = ranges::find(_data, key1) - begin(_data); const auto index2 = ranges::find(_data, key2) - begin(_data); @@ -102,8 +134,8 @@ void PinnedList::reorder(const Key &key1, const Key &key2) { Assert(index2 >= 0 && index2 < _data.size()); Assert(index1 != index2); std::swap(_data[index1], _data[index2]); - key1.entry()->cachePinnedIndex(index2 + 1); - key2.entry()->cachePinnedIndex(index1 + 1); + key1.entry()->cachePinnedIndex(_filterId, index2 + 1); + key2.entry()->cachePinnedIndex(_filterId, index1 + 1); } } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h index c7910fc2d..9e064da1a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +class History; + namespace Data { class Session; } // namespace Data @@ -17,7 +19,7 @@ class Key; class PinnedList final { public: - explicit PinnedList(int limit); + PinnedList(FilterId filterId, int limit); void setLimit(int limit); @@ -28,11 +30,17 @@ public: // if (pinned) places on the first place in the list. void setPinned(const Key &key, bool pinned); + void applyFilterPinned( + FilterId filterId, + not_null history, + int index); + void clear(); void applyList( not_null owner, const QVector &list); + void applyList(const std::vector> &list); void reorder(const Key &key1, const Key &key2); const std::vector &order() const { @@ -43,6 +51,7 @@ private: int addPinnedGetPosition(const Key &key); void applyLimit(int limit); + FilterId _filterId = 0; int _limit = 0; std::vector _data; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 00b31be44..f08568d9c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -220,10 +220,8 @@ Row::Row(Key key, int pos) : _id(key), _pos(pos) { } } -uint64 Row::sortKey(SortMode mode) const { - return (mode == SortMode::Complex) - ? _id.entry()->sortKeyInChatList() - : _id.entry()->sortKeyByDate(); +uint64 Row::sortKey(FilterId filterId) const { + return _id.entry()->sortKeyInChatList(filterId); } void Row::validateListEntryCache() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 8fc58f74a..b43b07af2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -90,7 +90,7 @@ public: int pos() const { return _pos; } - uint64 sortKey(SortMode mode) const; + uint64 sortKey(FilterId filterId) const; void validateListEntryCache() const; const Ui::Text::String &listEntryCache() const { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 0d9680c30..b6cf3ed74 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1921,9 +1921,6 @@ void History::setFolderPointer(Data::Folder *folder) { if (_folder == folder) { return; } - if (isPinnedDialog()) { - owner().setChatPinned(this, false); - } auto &filters = owner().chatsFilters(); const auto wasKnown = folderKnown(); const auto wasInList = inChatList(); @@ -1946,6 +1943,7 @@ void History::setFolderPointer(Data::Folder *folder) { for (const auto &filter : filters.list()) { if (filter.contains(this)) { const auto id = filter.id(); + applyFilterPinnedIndex(id, filter); addToChatList(id, filters.chatsList(id)); } } @@ -1959,6 +1957,24 @@ void History::setFolderPointer(Data::Folder *folder) { } } +void History::applyFilterPinnedIndex( + FilterId filterId, + const Data::ChatFilter &filter) { + const auto &pinned = filter.pinned(); + const auto i = ranges::find(pinned, this); + if (i == end(pinned)) { + return; + } + const auto index = (i - begin(pinned)) + 1; + if (index == lookupPinnedIndex(filterId)) { + return; + } + owner().chatsFilters().chatsList(filterId)->pinned()->applyFilterPinned( + filterId, + this, + index); +} + void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { const auto folderId = data.vfolder_id().value_or_empty(); if (!folderKnown()) { @@ -1968,7 +1984,7 @@ void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { clearFolder(); } } - owner().setChatPinned(this, data.is_pinned()); + owner().setChatPinned(this, FilterId(), data.is_pinned()); } TimeId History::adjustedChatListTimeId() const { @@ -2576,7 +2592,7 @@ bool History::useProxyPromotion() const { if (!isProxyPromoted()) { return false; } else if (const auto channel = peer->asChannel()) { - return !isPinnedDialog() && !channel->amIn(); + return !isPinnedDialog(FilterId()) && !channel->amIn(); } return false; } @@ -2595,7 +2611,7 @@ bool History::trackUnreadMessages() const { bool History::shouldBeInChatList() const { if (peer->migrateTo() || !folderKnown()) { return false; - } else if (isPinnedDialog()) { + } else if (isPinnedDialog(FilterId())) { return true; } else if (const auto channel = peer->asChannel()) { if (!channel->amIn()) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 9c321efaf..2e6a06e40 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -31,6 +31,7 @@ namespace Data { struct Draft; class Session; class Folder; +class ChatFilter; } // namespace Data namespace Dialogs { @@ -368,6 +369,10 @@ public: HistoryItem *folderDialogItem = nullptr); void clearFolder(); + void applyFilterPinnedIndex( + FilterId filterId, + const Data::ChatFilter &filter); + // Interface for Data::Histories. void setInboxReadTill(MsgId upTo); std::optional countStillUnreadLocal(MsgId readTillId) const; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 412f90afd..c184251d6 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -226,6 +226,7 @@ void TopBarWidget::showMenu() { Window::FillPeerMenu( _controller, peer, + FilterId(), addAction, Window::PeerMenuSource::History); } else if (const auto folder = _activeChat.folder()) { diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 0e50252db..188ae0261 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -574,6 +574,7 @@ void WrapWidget::showTopBarMenu() { Window::FillPeerMenu( _controller->parentController(), peer, + FilterId(), addAction, Window::PeerMenuSource::Profile); //} else if (const auto feed = key().feed()) { // #feed diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index fa85d2984..c268daa6d 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -64,6 +64,7 @@ public: Filler( not_null controller, not_null peer, + FilterId filterId, const PeerMenuCallback &addAction, PeerMenuSource source); void fill(); @@ -84,6 +85,7 @@ private: not_null _controller; not_null _peer; + FilterId _filterId = 0; const PeerMenuCallback &_addAction; PeerMenuSource _source; @@ -115,7 +117,7 @@ private: }; History *FindWastedPin(not_null data, Data::Folder *folder) { - const auto &order = data->pinnedChatsOrder(folder); + const auto &order = data->pinnedChatsOrder(folder, FilterId()); for (const auto &pinned : order) { if (const auto history = pinned.history()) { if (history->peer->isChat() @@ -134,24 +136,25 @@ void AddChatMembers( AddParticipantsBoxController::Start(navigation, chat); } -bool PinnedLimitReached(Dialogs::Key key) { +bool PinnedLimitReached(Dialogs::Key key, FilterId filterId) { Expects(key.entry()->folderKnown()); const auto entry = key.entry(); const auto owner = &entry->owner(); const auto folder = entry->folder(); - const auto pinnedCount = owner->pinnedChatsCount(folder); - const auto pinnedMax = owner->pinnedChatsLimit(folder); + const auto pinnedCount = owner->pinnedChatsCount(folder, filterId); + const auto pinnedMax = owner->pinnedChatsLimit(folder, filterId); if (pinnedCount < pinnedMax) { return false; } // Some old chat, that was converted, maybe is still pinned. - if (const auto wasted = FindWastedPin(owner, folder)) { - owner->setChatPinned(wasted, false); - owner->setChatPinned(key, true); + const auto wasted = filterId ? nullptr : FindWastedPin(owner, folder); + if (wasted) { + owner->setChatPinned(wasted, FilterId(), false); + owner->setChatPinned(key, FilterId(), true); entry->session().api().savePinnedOrder(folder); } else { - auto errorText = tr::lng_error_pinned_max( + const auto errorText = tr::lng_error_pinned_max( tr::now, lt_count, pinnedMax); @@ -165,12 +168,12 @@ void TogglePinnedDialog(Dialogs::Key key) { return; } const auto owner = &key.entry()->owner(); - const auto isPinned = !key.entry()->isPinnedDialog(); - if (isPinned && PinnedLimitReached(key)) { + const auto isPinned = !key.entry()->isPinnedDialog(0); + if (isPinned && PinnedLimitReached(key, 0)) { return; } - owner->setChatPinned(key, isPinned); + owner->setChatPinned(key, FilterId(), isPinned); const auto flags = isPinned ? MTPmessages_ToggleDialogPin::Flag::f_pinned : MTPmessages_ToggleDialogPin::Flag(0); @@ -194,13 +197,50 @@ void TogglePinnedDialog(Dialogs::Key key) { } } +void TogglePinnedDialog(Dialogs::Key key, FilterId filterId) { + if (!filterId) { + return TogglePinnedDialog(key); + } + const auto owner = &key.entry()->owner(); + const auto isPinned = !key.entry()->isPinnedDialog(filterId); + if (isPinned && PinnedLimitReached(key, filterId)) { + return; + } + + owner->setChatPinned(key, filterId, isPinned); + // #TODO pinned save data and to server + //const auto flags = isPinned + // ? MTPmessages_ToggleDialogPin::Flag::f_pinned + // : MTPmessages_ToggleDialogPin::Flag(0); + //if (const auto history = key.history()) { + // history->session().api().request(MTPmessages_ToggleDialogPin( + // MTP_flags(flags), + // MTP_inputDialogPeer(key.history()->peer->input) + // )).done([=](const MTPBool &result) { + // owner->notifyPinnedDialogsOrderUpdated(); + // }).send(); + //} else if (const auto folder = key.folder()) { + // folder->session().api().request(MTPmessages_ToggleDialogPin( + // MTP_flags(flags), + // MTP_inputDialogPeerFolder(MTP_int(folder->id())) + // )).send(); + //} + if (isPinned) { + if (const auto main = App::main()) { + main->dialogsToUp(); + } + } +} + Filler::Filler( not_null controller, not_null peer, + FilterId filterId, const PeerMenuCallback &addAction, PeerMenuSource source) : _controller(controller) , _peer(peer) +, _filterId(filterId) , _addAction(addAction) , _source(source) { } @@ -242,27 +282,29 @@ bool Filler::showTogglePin() { } void Filler::addTogglePin() { - auto peer = _peer; + const auto filterId = _filterId; + const auto peer = _peer; auto isPinned = false; - if (auto history = peer->owner().historyLoaded(peer)) { - isPinned = history->isPinnedDialog(); + if (const auto history = peer->owner().historyLoaded(peer)) { + isPinned = history->isPinnedDialog(filterId); } - auto pinText = [](bool isPinned) { + const auto pinText = [](bool isPinned) { return isPinned ? tr::lng_context_unpin_from_top(tr::now) : tr::lng_context_pin_to_top(tr::now); }; - auto pinToggle = [=] { - TogglePinnedDialog(peer->owner().history(peer)); + const auto pinToggle = [=] { + TogglePinnedDialog(peer->owner().history(peer), filterId); }; - auto pinAction = _addAction(pinText(isPinned), pinToggle); + const auto pinAction = _addAction(pinText(isPinned), pinToggle); const auto lifetime = Ui::CreateChild(pinAction); Notify::PeerUpdateViewer( peer, Notify::PeerUpdate::Flag::ChatPinnedChanged - ) | rpl::start_with_next([peer, pinAction, pinText] { - auto isPinned = peer->owner().history(peer)->isPinnedDialog(); + ) | rpl::start_with_next([=] { + const auto history = peer->owner().history(peer); + const auto isPinned = history->isPinnedDialog(filterId); pinAction->setText(pinText(isPinned)); }, *lifetime); } @@ -1041,9 +1083,10 @@ Fn DeleteAndLeaveHandler(not_null peer) { void FillPeerMenu( not_null controller, not_null peer, + FilterId filterId, const PeerMenuCallback &callback, PeerMenuSource source) { - Filler filler(controller, peer, callback, source); + Filler filler(controller, peer, filterId, callback, source); filler.fill(); } diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index 2b2a96dd0..cfdeecd8c 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -39,6 +39,7 @@ using PeerMenuCallback = Fn controller, not_null peer, + FilterId filterId, const PeerMenuCallback &addAction, PeerMenuSource source); void FillFolderMenu( From 55900bbd3dd6343b4ff5b52b79ad08bf5aa24d25 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Mar 2020 18:52:01 +0400 Subject: [PATCH 034/115] Save pinned chats in filters to server. --- Telegram/CMakeLists.txt | 2 ++ Telegram/SourceFiles/api/api_chat_filters.cpp | 35 +++++++++++++++++++ Telegram/SourceFiles/api/api_chat_filters.h | 18 ++++++++++ .../SourceFiles/data/data_chat_filters.cpp | 35 ++++++++++++++++--- Telegram/SourceFiles/data/data_chat_filters.h | 7 +++- Telegram/SourceFiles/data/data_session.cpp | 16 +++++---- .../SourceFiles/dialogs/dialogs_entry.cpp | 3 -- .../dialogs/dialogs_inner_widget.cpp | 3 +- .../dialogs/dialogs_pinned_list.cpp | 21 ++--------- .../SourceFiles/dialogs/dialogs_pinned_list.h | 5 --- Telegram/SourceFiles/history/history.cpp | 22 ++---------- Telegram/SourceFiles/history/history.h | 4 --- .../SourceFiles/window/window_peer_menu.cpp | 20 ++--------- 13 files changed, 112 insertions(+), 79 deletions(-) create mode 100644 Telegram/SourceFiles/api/api_chat_filters.cpp create mode 100644 Telegram/SourceFiles/api/api_chat_filters.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b8c24968d..ace72d47d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -134,6 +134,8 @@ nice_target_sources(Telegram ${src_loc} PRIVATE ${style_files} + api/api_chat_filters.cpp + api/api_chat_filters.h api/api_common.h api/api_hash.h api/api_self_destruct.cpp diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp new file mode 100644 index 000000000..68b0dbebe --- /dev/null +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -0,0 +1,35 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "api/api_chat_filters.h" + +#include "data/data_session.h" +#include "data/data_chat_filters.h" +#include "main/main_session.h" +#include "apiwrap.h" + +namespace Api { + +void SaveNewFilterPinned( + not_null session, + FilterId filterId) { + const auto &order = session->data().pinnedChatsOrder( + nullptr, + filterId); + auto &filters = session->data().chatsFilters(); + const auto &filter = filters.applyUpdatedPinned( + filterId, + order); + session->api().request(MTPmessages_UpdateDialogFilter( + MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), + MTP_int(filterId), + filter.tl() + )).send(); + +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_chat_filters.h b/Telegram/SourceFiles/api/api_chat_filters.h new file mode 100644 index 000000000..286ca2945 --- /dev/null +++ b/Telegram/SourceFiles/api/api_chat_filters.h @@ -0,0 +1,18 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +void SaveNewFilterPinned(not_null session, FilterId filterId); + +} // namespace Api diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index d5e1c6aad..048543fea 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -343,9 +343,6 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { const auto feedHistory = [&](not_null history) { const auto now = updated.contains(history); const auto was = filter.contains(history); - if (now) { - history->applyFilterPinnedIndex(id, updated); - } if (now != was) { if (now) { history->addToChatList(id, filterList); @@ -365,7 +362,8 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { if (const auto folder = _owner->folderLoaded(Data::Folder::kId)) { feedList(folder->chatsList()); } - } else if (pinnedChanged) { + } + if (pinnedChanged) { const auto id = filter.id(); const auto filterList = _owner->chatsFilters().chatsList(id); filterList->pinned()->applyList(updated.pinned()); @@ -412,6 +410,35 @@ bool ChatFilters::applyOrder(const QVector &order) { return true; } +const ChatFilter &ChatFilters::applyUpdatedPinned( + FilterId id, + const std::vector &dialogs) { + const auto i = ranges::find(_list, id, &ChatFilter::id); + Assert(i != end(_list)); + + auto always = i->always(); + auto pinned = std::vector>(); + pinned.reserve(dialogs.size()); + for (const auto &row : dialogs) { + if (const auto history = row.history()) { + if (always.contains(history)) { + pinned.push_back(history); + } else if (always.size() < ChatFilter::kPinnedLimit) { + always.insert(history); + pinned.push_back(history); + } + } + } + set(ChatFilter( + id, + i->title(), + i->flags(), + std::move(always), + std::move(pinned), + i->never())); + return *i; +} + const std::vector &ChatFilters::list() const { return _list; } diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index b5a6efbc2..ec7f19fbb 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -13,6 +13,7 @@ class History; namespace Dialogs { class MainList; +class Key; } // namespace Dialogs namespace Data { @@ -34,7 +35,7 @@ public: friend constexpr inline bool is_flag_type(Flag) { return true; }; using Flags = base::flags; - static constexpr int kPinnedLimit = 100; + static constexpr int kPinnedLimit = 10; ChatFilter() = default; ChatFilter( @@ -98,6 +99,10 @@ public: [[nodiscard]] not_null chatsList(FilterId filterId); + const ChatFilter &applyUpdatedPinned( + FilterId id, + const std::vector &dialogs); + private: void load(bool force); bool applyOrder(const QVector &order); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 20e9df135..3720ac5f1 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1570,11 +1570,16 @@ int Session::pinnedChatsCount( int Session::pinnedChatsLimit( Data::Folder *folder, FilterId filterId) const { - return filterId - ? Data::ChatFilter::kPinnedLimit - : folder - ? Global::PinnedDialogsInFolderMax() - : Global::PinnedDialogsCountMax(); + if (!filterId) { + return folder + ? Global::PinnedDialogsInFolderMax() + : Global::PinnedDialogsCountMax(); + } + const auto &list = chatsFilters().list(); + const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); + const auto pinned = (i != end(list)) ? i->pinned().size() : 0; + const auto already = (i != end(list)) ? i->always().size() : 0; + return Data::ChatFilter::kPinnedLimit + pinned - already; } const std::vector &Session::pinnedChatsOrder( @@ -3375,7 +3380,6 @@ auto Session::refreshChatListEntry( const auto filterList = chatsFilters().chatsList(id); auto filterResult = RefreshChatListEntryResult(); if (filter.contains(history)) { - history->applyFilterPinnedIndex(id, filter); filterResult.changed = !entry->inChatList(id); if (filterResult.changed) { entry->addToChatList(id, filterList); diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index ecf99794b..fa0919e67 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -235,9 +235,6 @@ void Entry::removeFromChatList( return; } _chatListLinks.erase(i); - if (isPinnedDialog(filterId)) { - owner().setChatPinned(_key, filterId, false); - } list->removeEntry(_key); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f3bea9083..602793c6b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/multi_select.h" #include "ui/empty_userpic.h" #include "ui/unread_badge.h" +#include "api/api_chat_filters.h" #include "facades.h" #include "styles/style_dialogs.h" #include "styles/style_chat_helpers.h" @@ -1137,7 +1138,7 @@ void InnerWidget::savePinnedOrder() { } } if (_filterId) { - // #TODO pinned reorder data and to server + Api::SaveNewFilterPinned(&session(), _filterId); } else { session().api().savePinnedOrder(_openedFolder); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp index 5b79fa9b8..969bfa64c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp @@ -70,21 +70,6 @@ void PinnedList::setPinned(const Key &key, bool pinned) { } } -void PinnedList::applyFilterPinned( - FilterId filterId, - not_null history, - int index) { - Expects(index > 0); - - history->cachePinnedIndex(filterId, index); - - const auto key = Key{ history }; - if (ranges::find(_data, key) == end(_data)) { - _data.push_back(key); - // #TODO pinned - } -} - void PinnedList::applyLimit(int limit) { Expects(limit >= 0); @@ -120,10 +105,8 @@ void PinnedList::applyList(const std::vector> &list) { _data.reserve(count); for (auto i = 0; i != count; ++i) { const auto history = list[i]; - if (history->inChatList()) { - _data.emplace_back(history); - history->cachePinnedIndex(_filterId, i + 1); - } + _data.emplace_back(history); + history->cachePinnedIndex(_filterId, i + 1); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h index 9e064da1a..cbdb0994b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h @@ -30,11 +30,6 @@ public: // if (pinned) places on the first place in the list. void setPinned(const Key &key, bool pinned); - void applyFilterPinned( - FilterId filterId, - not_null history, - int index); - void clear(); void applyList( diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index b6cf3ed74..a908dfbb6 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1921,6 +1921,9 @@ void History::setFolderPointer(Data::Folder *folder) { if (_folder == folder) { return; } + if (isPinnedDialog(FilterId())) { + owner().setChatPinned(this, FilterId(), false); + } auto &filters = owner().chatsFilters(); const auto wasKnown = folderKnown(); const auto wasInList = inChatList(); @@ -1943,7 +1946,6 @@ void History::setFolderPointer(Data::Folder *folder) { for (const auto &filter : filters.list()) { if (filter.contains(this)) { const auto id = filter.id(); - applyFilterPinnedIndex(id, filter); addToChatList(id, filters.chatsList(id)); } } @@ -1957,24 +1959,6 @@ void History::setFolderPointer(Data::Folder *folder) { } } -void History::applyFilterPinnedIndex( - FilterId filterId, - const Data::ChatFilter &filter) { - const auto &pinned = filter.pinned(); - const auto i = ranges::find(pinned, this); - if (i == end(pinned)) { - return; - } - const auto index = (i - begin(pinned)) + 1; - if (index == lookupPinnedIndex(filterId)) { - return; - } - owner().chatsFilters().chatsList(filterId)->pinned()->applyFilterPinned( - filterId, - this, - index); -} - void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { const auto folderId = data.vfolder_id().value_or_empty(); if (!folderKnown()) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 2e6a06e40..dc05ae971 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -369,10 +369,6 @@ public: HistoryItem *folderDialogItem = nullptr); void clearFolder(); - void applyFilterPinnedIndex( - FilterId filterId, - const Data::ChatFilter &filter); - // Interface for Data::Histories. void setInboxReadTill(MsgId upTo); std::optional countStillUnreadLocal(MsgId readTillId) const; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index c268daa6d..3bf0f97be 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "observer_peer.h" #include "api/api_common.h" +#include "api/api_chat_filters.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_message.h" // GetErrorTextForSending. @@ -45,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_scheduled_messages.h" #include "data/data_histories.h" +#include "data/data_chat_filters.h" #include "dialogs/dialogs_key.h" #include "boxes/peers/edit_peer_info_box.h" #include "facades.h" @@ -208,23 +210,7 @@ void TogglePinnedDialog(Dialogs::Key key, FilterId filterId) { } owner->setChatPinned(key, filterId, isPinned); - // #TODO pinned save data and to server - //const auto flags = isPinned - // ? MTPmessages_ToggleDialogPin::Flag::f_pinned - // : MTPmessages_ToggleDialogPin::Flag(0); - //if (const auto history = key.history()) { - // history->session().api().request(MTPmessages_ToggleDialogPin( - // MTP_flags(flags), - // MTP_inputDialogPeer(key.history()->peer->input) - // )).done([=](const MTPBool &result) { - // owner->notifyPinnedDialogsOrderUpdated(); - // }).send(); - //} else if (const auto folder = key.folder()) { - // folder->session().api().request(MTPmessages_ToggleDialogPin( - // MTP_flags(flags), - // MTP_inputDialogPeerFolder(MTP_int(folder->id())) - // )).send(); - //} + Api::SaveNewFilterPinned(&owner->session(), filterId); if (isPinned) { if (const auto main = App::main()) { main->dialogsToUp(); From e36a66b97110dcb82dcd550b8c4e69f29acaa9a6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Mar 2020 19:00:51 +0400 Subject: [PATCH 035/115] Fix build for macOS. --- Telegram/SourceFiles/platform/mac/mac_touchbar.mm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm b/Telegram/SourceFiles/platform/mac/mac_touchbar.mm index e4b5f70ce..119737cc8 100644 --- a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm +++ b/Telegram/SourceFiles/platform/mac/mac_touchbar.mm @@ -434,7 +434,7 @@ void AppendEmojiPacks(std::vector &to) { if (std::abs(_startPosition - currentPosition) > step) { const auto delta = (currentPosition > _startPosition) ? 1 : -1; const auto newIndex = _tempIndex + delta; - const auto &order = Auth().data().pinnedChatsOrder(nullptr); + const auto &order = Auth().data().pinnedChatsOrder(nullptr, FilterId()); // In case the order has been changed from another device // while the user is dragging the dialog. @@ -444,6 +444,7 @@ void AppendEmojiPacks(std::vector &to) { if (newIndex >= 0 && newIndex < order.size()) { Auth().data().reorderTwoPinnedChats( + FilterId(), order.at(_tempIndex).history(), order.at(newIndex).history()); _tempIndex = newIndex; @@ -1324,7 +1325,7 @@ void AppendEmojiPacks(std::vector &to) { } - (void) updatePinnedButtons { - const auto &order = Auth().data().pinnedChatsOrder(nullptr); + const auto &order = Auth().data().pinnedChatsOrder(nullptr, FilterId()); auto isSelfPeerPinned = false; auto isArchivePinned = false; PinnedDialogButton *selfChatButton; From 3c0ee9fa20501709b9f1f116457a99963c765a55 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 12:13:12 +0400 Subject: [PATCH 036/115] Fix filter chats list edit box title. --- .../SourceFiles/boxes/filters/edit_filter_chats_list.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index 7650c00bf..c425ff364 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -357,6 +357,7 @@ void EditFilterChatsListController::prepareViewHook() { delegate()->peerListSetAboveWidget(prepareTypesList()); delegate()->peerListAddSelectedPeers( _peers | ranges::view::transform(&History::peer)); + updateTitle(); } object_ptr EditFilterChatsListController::prepareTypesList() { @@ -419,7 +420,13 @@ auto EditFilterChatsListController::createRow(not_null history) } void EditFilterChatsListController::updateTitle() { - const auto count = delegate()->peerListSelectedRowsCount(); + auto types = 0; + for (const auto flag : kAllTypes) { + if (_selected & flag) { + ++types; + } + } + const auto count = delegate()->peerListSelectedRowsCount() - types; const auto additional = qsl("%1 / %2").arg(count).arg(kMaxExceptions); delegate()->peerListSetAdditionalTitle(rpl::single(additional)); } From 4881981cf64c7cd36259383fc2f102cfe2d806c7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 13:19:58 +0400 Subject: [PATCH 037/115] Enable / disable side bar based on filters. --- .../boxes/filters/manage_filters_box.cpp | 4 +++- Telegram/SourceFiles/core/application.cpp | 7 ------- Telegram/SourceFiles/core/application.h | 1 - Telegram/SourceFiles/data/data_chat_filters.cpp | 4 ++-- Telegram/SourceFiles/data/data_chat_filters.h | 2 +- .../dialogs/dialogs_inner_widget.cpp | 10 +++++----- Telegram/SourceFiles/facades.cpp | 2 -- Telegram/SourceFiles/facades.h | 1 - .../SourceFiles/settings/settings_codes.cpp | 6 ------ Telegram/SourceFiles/storage/localstorage.cpp | 16 +++++++++++----- .../SourceFiles/window/window_controller.cpp | 2 +- .../window/window_session_controller.cpp | 17 +++++++++++++++++ .../window/window_session_controller.h | 1 + 13 files changed, 41 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp index a6bd98d8e..c12a997dd 100644 --- a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp @@ -504,7 +504,9 @@ void ManageFiltersPrepare::SetupBox( continue; } const auto newId = ids.take(id).value_or(id); - const auto tl = removed ? MTPDialogFilter() : row.filter.tl(); + const auto tl = removed + ? MTPDialogFilter() + : row.filter.tl(newId); const auto request = MTPmessages_UpdateDialogFilter( MTP_flags(removed ? MTPmessages_UpdateDialogFilter::Flag(0) diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 18f3bf4f2..124abd256 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -502,13 +502,6 @@ void Application::switchDebugMode() { } } -void Application::switchWorkMode() { - Global::SetDialogsFiltersEnabled(!Global::DialogsFiltersEnabled()); - Global::SetDialogsFilterId(0); - Local::writeUserSettings(); - App::restart(); -} - void Application::switchTestMode() { if (cTestMode()) { QFile(cWorkingDir() + qsl("tdata/withtestmode")).remove(); diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index dd0b1a805..3bec6b4ae 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -214,7 +214,6 @@ public: void handleAppDeactivated(); void switchDebugMode(); - void switchWorkMode(); void switchTestMode(); void writeInstallBetaVersionsSetting(); diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 048543fea..27efdf7a5 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -92,7 +92,7 @@ ChatFilter ChatFilter::FromTL( }); } -MTPDialogFilter ChatFilter::tl() const { +MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { using TLFlag = MTPDdialogFilter::Flag; const auto flags = TLFlag(0) | ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0)) @@ -124,7 +124,7 @@ MTPDialogFilter ChatFilter::tl() const { } return MTP_dialogFilter( MTP_flags(flags), - MTP_int(_id), + MTP_int(replaceId ? replaceId : _id), MTP_string(_title), MTPstring(), // emoticon MTP_vector(pinned), diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index ec7f19fbb..051afa9da 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -49,7 +49,7 @@ public: [[nodiscard]] static ChatFilter FromTL( const MTPDialogFilter &data, not_null owner); - [[nodiscard]] MTPDialogFilter tl() const; + [[nodiscard]] MTPDialogFilter tl(FilterId replaceId = 0) const; [[nodiscard]] FilterId id() const; [[nodiscard]] QString title() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 602793c6b..caabc4e53 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2546,11 +2546,11 @@ bool InnerWidget::chooseCollapsedRow() { void InnerWidget::switchToFilter(FilterId filterId) { clearSelection(); - if (!Global::DialogsFiltersEnabled() - || !ranges::contains( - session().data().chatsFilters().list(), - filterId, - &Data::ChatFilter::id)) { + const auto found = ranges::contains( + session().data().chatsFilters().list(), + filterId, + &Data::ChatFilter::id); + if (!found) { filterId = 0; } stopReorderPinned(); diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index d4c9211fb..685766d11 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -311,7 +311,6 @@ struct Data { base::Observable AdaptiveChanged; bool DialogsFiltersEnabled = false; - FilterId DialogsFilterId = 0; bool ModerateModeEnabled = false; bool ScreenIsLocked = false; @@ -441,7 +440,6 @@ DefineVar(Global, bool, AdaptiveForWide); DefineRefVar(Global, base::Observable, AdaptiveChanged); DefineVar(Global, bool, DialogsFiltersEnabled); -DefineVar(Global, FilterId, DialogsFilterId); DefineVar(Global, bool, ModerateModeEnabled); DefineVar(Global, bool, ScreenIsLocked); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 1a76f42ca..fd598df9f 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -150,7 +150,6 @@ DeclareVar(bool, AdaptiveForWide); DeclareRefVar(base::Observable, AdaptiveChanged); DeclareVar(bool, DialogsFiltersEnabled); -DeclareVar(FilterId, DialogsFilterId); DeclareVar(bool, ModerateModeEnabled); DeclareVar(bool, ScreenIsLocked); diff --git a/Telegram/SourceFiles/settings/settings_codes.cpp b/Telegram/SourceFiles/settings/settings_codes.cpp index b86179618..3cc2896f1 100644 --- a/Telegram/SourceFiles/settings/settings_codes.cpp +++ b/Telegram/SourceFiles/settings/settings_codes.cpp @@ -69,12 +69,6 @@ auto GenerateCodes() { codes.emplace(qsl("crashplease"), [](::Main::Session *session) { Unexpected("Crashed in Settings!"); }); - codes.emplace(qsl("workmode"), [](::Main::Session *session) { - auto text = Global::DialogsFiltersEnabled() ? qsl("Disable filters?") : qsl("Enable filters?"); - Ui::show(Box(text, [] { - Core::App().switchWorkMode(); - })); - }); codes.emplace(qsl("moderate"), [](::Main::Session *session) { auto text = Global::ModerateModeEnabled() ? qsl("Disable moderate mode?") : qsl("Enable moderate mode?"); Ui::show(Box(text, [] { diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 7fb7c92e1..5d4d1b71b 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -613,7 +613,7 @@ enum { dbiHiddenPinnedMessages = 0x39, dbiRecentEmoji = 0x3a, dbiEmojiVariants = 0x3b, - dbiDialogsFilters = 0x40, + dbiDialogsModeOld = 0x40, dbiModerateMode = 0x41, dbiVideoVolume = 0x42, dbiStickersRecentLimit = 0x43, @@ -644,6 +644,7 @@ enum { dbiCacheSettings = 0x5c, dbiTxtDomainString = 0x5d, dbiApplicationSettings = 0x5e, + dbiDialogsFilters = 0x5f, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -1167,10 +1168,16 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting } } break; - case dbiDialogsFilters: { + case dbiDialogsModeOld: { qint32 enabled, modeInt; stream >> enabled >> modeInt; if (!_checkStreamStatus(stream)) return false; + } break; + + case dbiDialogsFilters: { + qint32 enabled; + stream >> enabled; + if (!_checkStreamStatus(stream)) return false; Global::SetDialogsFiltersEnabled(enabled == 1); } break; @@ -2091,7 +2098,7 @@ void _writeUserSettings() { : QByteArray(); auto callSettings = serializeCallSettings(); - uint32 size = 23 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 24 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); size += sizeof(quint32) + sizeof(qint32); @@ -2104,7 +2111,6 @@ void _writeUserSettings() { size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath()); size += sizeof(quint32) + 3 * sizeof(qint32); size += sizeof(quint32) + 2 * sizeof(qint32); - size += sizeof(quint32) + 2 * sizeof(qint32); size += sizeof(quint32) + sizeof(qint64) + sizeof(qint32); if (!Global::HiddenPinnedMessages().isEmpty()) { size += sizeof(quint32) + sizeof(qint32) + Global::HiddenPinnedMessages().size() * (sizeof(PeerId) + sizeof(MsgId)); @@ -2132,7 +2138,7 @@ void _writeUserSettings() { data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); data.stream << quint32(dbiSongVolume) << qint32(qRound(Global::SongVolume() * 1e6)); data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6)); - data.stream << quint32(dbiDialogsFilters) << qint32(Global::DialogsFiltersEnabled() ? 1 : 0) << static_cast(Global::DialogsFilterId()); + data.stream << quint32(dbiDialogsFilters) << qint32(Global::DialogsFiltersEnabled() ? 1 : 0); data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer()); data.stream << quint32(dbiCacheSettings) << qint64(_cacheTotalSizeLimit) << qint32(_cacheTotalTimeLimit) << qint64(_cacheBigFileTotalSizeLimit) << qint32(_cacheBigFileTotalTimeLimit); diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index b754c9a2f..ea4726060 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -28,7 +28,7 @@ Controller::Controller(not_null account) : _account(account) , _widget(this) { _account->sessionValue( - ) | rpl::start_with_next([=](Main::Session *session) { + ) | rpl::start_with_next([=](Main::Session *session) { _sessionController = session ? std::make_unique(session, this) : nullptr; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index ad4df58b6..8d8d8e8f1 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_channel.h" #include "data/data_chat.h" +#include "data/data_chat_filters.h" #include "passport/passport_form_controller.h" #include "chat_helpers/tabbed_selector.h" #include "core/shortcuts.h" @@ -135,6 +136,13 @@ SessionController::SessionController( folder->updateChatListSortPosition(); closeFolder(); }, lifetime()); + + session->data().chatsFilters().changed( + ) | rpl::start_with_next([=] { + crl::on_main(session, [=] { + refreshFiltersMenu(); + }); + }, session->lifetime()); } not_null<::MainWindow*> SessionController::widget() const { @@ -203,6 +211,15 @@ void SessionController::toggleFiltersMenu(bool enabled) { _window->sideBarChanged(); } +void SessionController::refreshFiltersMenu() { + const auto enabled = !session().data().chatsFilters().list().empty(); + if (enabled != Global::DialogsFiltersEnabled()) { + Global::SetDialogsFiltersEnabled(enabled); + session().saveSettingsDelayed(); + toggleFiltersMenu(enabled); + } +} + bool SessionController::uniqueChatsInSearchResults() const { return session().supportMode() && !session().settings().supportAllSearchResults() diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 8568b7b23..0a8adafa0 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -299,6 +299,7 @@ public: void setActiveChatsFilter(FilterId id); void toggleFiltersMenu(bool enabled); + void refreshFiltersMenu(); rpl::lifetime &lifetime() { return _lifetime; From ffc65f7da4904b28a8bbe261aefcb5ce4f7bc6f7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 14:07:11 +0400 Subject: [PATCH 038/115] Add folders menu to Settings. --- Telegram/Resources/icons/settings_folders.png | Bin 0 -> 360 bytes .../Resources/icons/settings_folders@2x.png | Bin 0 -> 633 bytes .../Resources/icons/settings_folders@3x.png | Bin 0 -> 1017 bytes Telegram/Resources/langs/lang.strings | 2 ++ .../boxes/filters/manage_filters_box.cpp | 5 ++++- .../info/settings/info_settings_widget.cpp | 18 ++++++++++++++---- .../info/settings/info_settings_widget.h | 4 ++++ Telegram/SourceFiles/main/main_app_config.cpp | 10 ++++++++++ Telegram/SourceFiles/main/main_app_config.h | 5 +++++ Telegram/SourceFiles/settings/settings.style | 1 + .../SourceFiles/settings/settings_common.h | 1 + .../SourceFiles/settings/settings_main.cpp | 11 +++++++++++ .../window/window_session_controller.cpp | 2 ++ 13 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 Telegram/Resources/icons/settings_folders.png create mode 100644 Telegram/Resources/icons/settings_folders@2x.png create mode 100644 Telegram/Resources/icons/settings_folders@3x.png diff --git a/Telegram/Resources/icons/settings_folders.png b/Telegram/Resources/icons/settings_folders.png new file mode 100644 index 0000000000000000000000000000000000000000..bb941b3e21cf1d7d4cb27d8090f64a614a22bace GIT binary patch literal 360 zcmV-u0hj)XP)A zuj>6D74;(kU~r=FnJ~D3rvM2d5QZU8N`W!9s-|fmNfJ<1^=&BoTAt@I5v6I$zRwEQ zvQZQT$K7tXhXaOTu&(R)9@8}U1BYSYW*Fnmp&$tOp0;gSS(Xng%QEIT&THT}j@%3( zv>duo@bN_mE&9KI@#i<%bDTc{9<2-b4}o=E^QUdwyNu`gs;X9Ni>&i~ zUnnC*HTgXaZ$b!*qDVNLw=~U9K9k?m@IG4CJ^bT90JsA`bjvlvzPkkg0000U)GWCjHQAn?Kb`eZraf*$}EWF0QZI$V%-TnQ?Q0;;Nl zrfEP3d8u?=hr{6j-EJ4+@%USA7jdd&GD#+r3GvJEcqHX=*>S#r%Xz@jXyjY&&h>gF z>2x|gg0)&LFu6PZem^{dv)POar>ZKEW%=a^VzC(MbUM@+mSvG*u^0})^Z87LD-;T@ zClN)F?DuV}rfGsGijQMSlEAVo*WcisL?QvDQpxl6vMfWp-G)pi^Efvg z4x!O#{Pmh2eS$$$rBb0Xold9lJi#EquKwJ8F%RbY6HKlq>_1b_55SJ2pdbj;F|H6; zg0BZzhYPX}Z!)aI1zCp+@+K20n|-^WJ%c@?oqz6= zESF1a|6{#gyV|-Bx^QmH@C;`3ylq&o*8|<=`yXIN&w^Ov@i??{n9bM5FIFm*BGc(K z1UfXB3A6bYyqBUVWV6{2P1D{cg>z#D%<@WR{%=Fp;exEg1zCp+vJMwy8UXkLzKS|U TR`8Kx00000NkvXXu0mjf4G%8< literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/settings_folders@3x.png b/Telegram/Resources/icons/settings_folders@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9300c2dc6a86f1655d66360db14588a6639796e1 GIT binary patch literal 1017 zcmeAS@N?(olHy`uVBq!ia0vp^At21b1|(&&1r9PWFdy)AaSW-L^LCD{_vZkKw)uOu zh3bf|oh*NMiGp6!;%3)h>?NWlg@JSxBR$r3J0JQ_Z_ha?FuOmNYV5ae=eX~M}= zaAId?&&Vew}?kW!lWyv$>aD zSi4sDeyY*=^XIkX`q{lRjAn)`4O*$@G;`|IsR^8O=g+^+_V?GXpwp*MYfbbB30>%+ zvM6ZflwG@aU6NZKpfN+;)WFH#&u@#PxtZAlQE3SY1OG{V&CSgZIQH(|eYx$;`}ghP zdDYdoH@)WP=fA!*;Q05a_wV0d2?R;iCQ?hcZ#RFz|76`dy;EtMuiUv4^YP}{v%IqM z@`wA5imL#1l$4aXq^GBczCC?W^2d)K7q&^r{oZ5on46n>+S)7Hn{KX=l9c?o{Pb^K z6RF-+HFocJholu32e&#Y=C^**S**I69MPu{s-O@G-Yfe_y)xFz&#_00p z%Lj!{rDkNT5YzMh!9OcPC++={>1%H-4bTX%kXiSmOhss6(#D9ZZ+-|bUcC6qrAv=a z?i220>Fw_i_V@4q_Ic$>&8YbJ?OWH!Uv&DbaffSd+qA=no%46qY>ePp6QK99Bx<() z^wX7>pYDuqcUsu>)`sW9{V_@{j$l`UiT|%Yp>=#w>-qBB+|pd zs_b;oLM%(7t<3s9lh~5hL-2&5rfP7>>0n2I0ZiEm9e%ecMDj@qVp7(HngCSQ*Ba$( z$IHw6b?O<_Fv~Z7yZTz~Y;7~Qt4}`p>hcxFFv~SX^~X0}h|ybq*Us2Dc>AO|2~!Vi zZRc}eeOXLgeCz7VKV~jkx^(F(`9G^dA66CrT|2M$(VclZ>2F@Y4xT@MzF2l`Gw1WC zcgp@)y!#|BF1}^^_UDf2tK%*7pKt4aoOkWsy*xdKzw!^}%(>nuU;bFEyz%$%-wxVO zYsz+4U3J=7QdTyLe-oSFxuZYlST*ixo*Dnh$jr=bWk}bq-Mi1GUfs_dw=<^hT5)=M wyPv;*tm=g6QOy&xE8%`e3I=p0r{G`KsLva-mU(WM17>LkPgg&ebxsLQ0Q<+&>;M1& literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7984173cc..df3608e3e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -344,6 +344,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_send_ctrlenter" = "Send by Ctrl+Enter"; "lng_settings_send_cmdenter" = "Send by Cmd+Enter"; +"lng_settings_section_filters" = "Folders"; + "lng_settings_section_background" = "Chat background"; "lng_settings_bg_use_default" = "Use default color theme"; "lng_settings_bg_from_gallery" = "Choose from gallery"; diff --git a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp index c12a997dd..debf79a40 100644 --- a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp @@ -393,6 +393,8 @@ void ManageFiltersPrepare::SetupBox( crl::guard(button, doneCallback))); }); rows->push_back({ button, filter }); + + wrap->resizeToWidth(content->width()); }; const auto &list = session->data().chatsFilters().list(); for (const auto &filter : list) { @@ -466,8 +468,9 @@ void ManageFiltersPrepare::SetupBox( const auto prepareGoodIdsForNewFilters = [=] { const auto &list = session->data().chatsFilters().list(); - auto localId = 2; + auto localId = 1; const auto chooseNextId = [&] { + ++localId; while (ranges::contains(list, localId, &Data::ChatFilter::id)) { ++localId; } diff --git a/Telegram/SourceFiles/info/settings/info_settings_widget.cpp b/Telegram/SourceFiles/info/settings/info_settings_widget.cpp index 689164a8b..76df4330f 100644 --- a/Telegram/SourceFiles/info/settings/info_settings_widget.cpp +++ b/Telegram/SourceFiles/info/settings/info_settings_widget.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_memento.h" #include "info/info_controller.h" #include "settings/settings_common.h" +#include "boxes/filters/manage_filters_box.h" #include "ui/ui_utility.h" namespace Info { @@ -44,17 +45,26 @@ Widget::Widget( , _self(controller->key().settingsSelf()) , _type(controller->section().settingsType()) , _inner(setInnerWidget(::Settings::CreateSection( - _type, - this, - controller->parentController()))) { + _type, + this, + controller->parentController()))) +, _manageFilters( + std::make_unique( + controller->parentController())) { _inner->sectionShowOther( ) | rpl::start_with_next([=](Type type) { - controller->showSettings(type); + if (type == Type::Folders) { + _manageFilters->showBox(); + } else { + controller->showSettings(type); + } }, _inner->lifetime()); controller->setCanSaveChanges(_inner->sectionCanSaveChanges()); } +Widget::~Widget() = default; + not_null Widget::self() const { return _self; } diff --git a/Telegram/SourceFiles/info/settings/info_settings_widget.h b/Telegram/SourceFiles/info/settings/info_settings_widget.h index f4c241350..30c636d26 100644 --- a/Telegram/SourceFiles/info/settings/info_settings_widget.h +++ b/Telegram/SourceFiles/info/settings/info_settings_widget.h @@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_content_widget.h" #include "info/info_controller.h" +class ManageFiltersPrepare; + namespace Settings { class Section; } // namespace Settings @@ -52,6 +54,7 @@ public: Widget( QWidget *parent, not_null controller); + ~Widget(); not_null self() const; @@ -77,6 +80,7 @@ private: Type _type = Type(); not_null<::Settings::Section*> _inner; + std::unique_ptr _manageFilters; }; diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 08254a15a..8518dec00 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -74,6 +74,16 @@ auto AppConfig::getValue(const QString &key, Extractor &&extractor) const { : MTPJSONValue(MTP_jsonNull())); } +bool AppConfig::getBool(const QString &key, bool fallback) const { + return getValue(key, [&](const MTPJSONValue &value) { + return value.match([&](const MTPDjsonBool &data) { + return mtpIsTrue(data.vvalue()); + }, [&](const auto &data) { + return fallback; + }); + }); +} + double AppConfig::getDouble(const QString &key, double fallback) const { return getValue(key, [&](const MTPJSONValue &value) { return value.match([&](const MTPDjsonNumber &data) { diff --git a/Telegram/SourceFiles/main/main_app_config.h b/Telegram/SourceFiles/main/main_app_config.h index 54afb170e..0ab3625a3 100644 --- a/Telegram/SourceFiles/main/main_app_config.h +++ b/Telegram/SourceFiles/main/main_app_config.h @@ -25,6 +25,8 @@ public: return getString(key, fallback); } else if constexpr (std::is_same_v>) { return getStringArray(key, std::move(fallback)); + } else if constexpr (std::is_same_v) { + return getBool(key, fallback); } } @@ -40,6 +42,9 @@ private: const QString &key, Extractor &&extractor) const; + [[nodiscard]] bool getBool( + const QString &key, + bool fallback) const; [[nodiscard]] double getDouble( const QString &key, double fallback) const; diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 0500f2808..fb45aaaf5 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -52,6 +52,7 @@ settingsDividerLabelPadding: margins(22px, 10px, 22px, 19px); settingsIconInformation: icon {{ "settings_information", menuIconFg }}; settingsIconNotifications: icon {{ "settings_notifications", menuIconFg }}; settingsIconChat: icon {{ "settings_chat", menuIconFg }}; +settingsIconFolders: icon {{ "settings_folders", menuIconFg }}; settingsIconGeneral: icon {{ "settings_advanced", menuIconFg }}; settingsIconPrivacySecurity: icon {{ "settings_privacy_security", menuIconFg }}; settingsIconLanguage: icon {{ "settings_language", menuIconFg }}; diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index ac9f8d88e..c2609c06c 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -37,6 +37,7 @@ enum class Type { PrivacySecurity, Advanced, Chat, + Folders, Calls, }; diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 05a4d384d..f2d848806 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -21,9 +21,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_session.h" #include "data/data_cloud_themes.h" +#include "data/data_chat_filters.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "main/main_session.h" +#include "main/main_account.h" +#include "main/main_app_config.h" #include "apiwrap.h" #include "window/window_session_controller.h" #include "core/file_utilities.h" @@ -101,6 +104,14 @@ void SetupSections( tr::lng_settings_section_chat_settings(), Type::Chat, &st::settingsIconChat); + const auto &appConfig = controller->session().account().appConfig(); + if (!controller->session().data().chatsFilters().list().empty() + || appConfig.get("dialog_filters_enabled", false)) { + addSection( + tr::lng_settings_section_filters(), + Type::Folders, + &st::settingsIconFolders); + } addSection( tr::lng_settings_advanced(), Type::Advanced, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 8d8d8e8f1..135bb95de 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -91,6 +91,8 @@ void SessionNavigation::showPeerInfo( void SessionNavigation::showSettings( Settings::Type type, const SectionShow ¶ms) { + Expects(type != Settings::Type::Folders); + showSection( Info::Memento( Info::Settings::Tag{ _session->user() }, From 4b618aeb6c997f146fc0cd04cf5696e4340e2bb7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 14:25:41 +0400 Subject: [PATCH 039/115] Fix saving filters. --- Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp index debf79a40..a58aeccd6 100644 --- a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp @@ -476,13 +476,13 @@ void ManageFiltersPrepare::SetupBox( } return localId; }; - auto result = base::flat_map(); + auto result = base::flat_map, FilterId>(); for (auto &row : *rows) { const auto id = row.filter.id(); if (row.removed) { continue; } else if (!ranges::contains(list, id, &Data::ChatFilter::id)) { - result.emplace(row.filter.id(), chooseNextId()); + result.emplace(row.button, chooseNextId()); } } return result; @@ -506,7 +506,7 @@ void ManageFiltersPrepare::SetupBox( order.push_back(MTP_int(id)); continue; } - const auto newId = ids.take(id).value_or(id); + const auto newId = ids.take(row.button).value_or(id); const auto tl = removed ? MTPDialogFilter() : row.filter.tl(newId); From 455d1139559b6002ab2b1c8095b6082f359e4153 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 16:14:45 +0400 Subject: [PATCH 040/115] Fix small column layout with filters side bar. --- Telegram/SourceFiles/dialogs/dialogs.style | 5 +++++ .../SourceFiles/dialogs/dialogs_widget.cpp | 21 +++++++++++++++++-- Telegram/SourceFiles/dialogs/dialogs_widget.h | 1 + .../SourceFiles/window/window_controller.cpp | 7 +++++++ .../window/window_session_controller.cpp | 6 +++++- .../window/window_session_controller.h | 3 +++ Telegram/lib_ui | 2 +- 7 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index e42995f19..5afa45572 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -120,6 +120,11 @@ dialogsSearchFrom: IconButton(dialogsCalendar) { icon: icon {{ "dialogs_search_from", dialogsMenuIconFg }}; iconOver: icon {{ "dialogs_search_from", dialogsMenuIconFgOver }}; } +dialogsSearchForNarrowFilters: IconButton(dialogsMenuToggle) { + icon: icon {{ "top_bar_search", menuIconFg }}; + iconOver: icon {{ "top_bar_search", menuIconFgOver }}; + iconPosition: point(4px, 4px); +} dialogsSearchFromPadding: margins(10px, 10px, 10px, 10px); dialogsFilter: FlatInput(defaultFlatInput) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 406075ee2..d7b1e899c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -161,6 +161,7 @@ Widget::Widget( : Window::AbstractSectionWidget(parent, controller) , _searchControls(this) , _mainMenuToggle(_searchControls, st::dialogsMenuToggle) +, _searchForNarrowFilters(_searchControls, st::dialogsSearchForNarrowFilters) , _filter(_searchControls, st::dialogsFilter, tr::lng_dlg_filter()) , _chooseFromUser( _searchControls, @@ -259,8 +260,18 @@ Widget::Widget( Core::App().lockByPasscode(); _lockUnlock->setIconOverride(nullptr); }); - _mainMenuToggle->setVisible(!controller->filtersWidth()); - _mainMenuToggle->setClickedCallback([this] { showMainMenu(); }); + rpl::single( + rpl::empty_value() + ) | rpl::then( + controller->filtersMenuChanged() + ) | rpl::start_with_next([=] { + const auto filtersHidden = !controller->filtersWidth(); + _mainMenuToggle->setVisible(filtersHidden); + _searchForNarrowFilters->setVisible(!filtersHidden); + updateControlsGeometry(); + }, lifetime()); + _mainMenuToggle->setClickedCallback([=] { showMainMenu(); }); + _searchForNarrowFilters->setClickedCallback([=] { Ui::showChatsList(); }); _chooseByDragTimer.setSingleShot(true); connect(&_chooseByDragTimer, SIGNAL(timeout()), this, SLOT(onChooseByDrag())); @@ -1515,6 +1526,12 @@ void Widget::updateControlsGeometry() { _filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height()); auto mainMenuLeft = anim::interpolate(st::dialogsFilterPadding.x(), (smallLayoutWidth - _mainMenuToggle->width()) / 2, smallLayoutRatio); _mainMenuToggle->moveToLeft(mainMenuLeft, st::dialogsFilterPadding.y()); + const auto searchLeft = anim::interpolate( + -_searchForNarrowFilters->width(), + (smallLayoutWidth - _searchForNarrowFilters->width()) / 2, + smallLayoutRatio); + _searchForNarrowFilters->moveToLeft(searchLeft, st::dialogsFilterPadding.y()); + auto right = filterLeft + filterWidth; _lockUnlock->moveToLeft(right + st::dialogsFilterPadding.x(), st::dialogsFilterPadding.y()); _cancelSearch->moveToLeft(right - _cancelSearch->width(), _filter->y()); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 700a5ab96..fc81317e8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -178,6 +178,7 @@ private: object_ptr _searchControls; object_ptr _folderTopBar = { nullptr } ; object_ptr _mainMenuToggle; + object_ptr _searchForNarrowFilters; object_ptr _filter; object_ptr> _chooseFromUser; object_ptr> _jumpToDate; diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index ea4726060..9fa618cf5 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "main/main_account.h" +#include "main/main_session.h" #include "ui/layers/box_content.h" #include "ui/layers/layer_widget.h" #include "ui/toast/toast.h" @@ -32,6 +33,12 @@ Controller::Controller(not_null account) _sessionController = session ? std::make_unique(session, this) : nullptr; + if (_sessionController) { + _sessionController->filtersMenuChanged( + ) | rpl::start_with_next([=] { + sideBarChanged(); + }, session->lifetime()); + } if (_sessionController && Global::DialogsFiltersEnabled()) { _sessionController->toggleFiltersMenu(true); } else { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 135bb95de..bf67f667d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -210,7 +210,7 @@ void SessionController::toggleFiltersMenu(bool enabled) { } else { _filters = nullptr; } - _window->sideBarChanged(); + _filtersMenuChanged.fire({}); } void SessionController::refreshFiltersMenu() { @@ -222,6 +222,10 @@ void SessionController::refreshFiltersMenu() { } } +rpl::producer<> SessionController::filtersMenuChanged() const { + return _filtersMenuChanged.events(); +} + bool SessionController::uniqueChatsInSearchResults() const { return session().supportMode() && !session().settings().supportAllSearchResults() diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 0a8adafa0..38275c58a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -300,6 +300,7 @@ public: void toggleFiltersMenu(bool enabled); void refreshFiltersMenu(); + [[nodiscard]] rpl::producer<> filtersMenuChanged() const; rpl::lifetime &lifetime() { return _lifetime; @@ -354,6 +355,8 @@ private: PeerData *_showEditPeer = nullptr; rpl::variable _openedFolder; + rpl::event_stream<> _filtersMenuChanged; + rpl::lifetime _lifetime; }; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index e4e8faaa7..ed97ff0d4 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit e4e8faaa73d1f6f9b690d7322d069183799ac6c2 +Subproject commit ed97ff0d4fd71dea6ad509b1178bb167f01bc178 From 36b99119956b1171172ffa61bc8bdfaec5c115db Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 17:00:53 +0400 Subject: [PATCH 041/115] Fix crashes in filter chats list editing. --- .../boxes/filters/edit_filter_chats_list.cpp | 19 +++++--------- .../boxes/filters/manage_filters_box.cpp | 14 ++++++++--- Telegram/SourceFiles/boxes/peer_list_box.cpp | 25 +++++++------------ Telegram/SourceFiles/boxes/peer_list_box.h | 10 ++------ 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index c425ff364..6426b44b2 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -44,12 +44,7 @@ public: QString generateName() override; QString generateShortName() override; - void paintEntityUserpicLeft( - Painter &p, - int x, - int y, - int outerWidth, - int size) override; + PaintRoundImageCallback generatePaintUserpicCallback() override; private: [[nodiscard]] Flag flag() const; @@ -144,13 +139,11 @@ QString TypeRow::generateShortName() { return generateName(); } -void TypeRow::paintEntityUserpicLeft( - Painter &p, - int x, - int y, - int outerWidth, - int size) { - PaintFilterChatsTypeIcon(p, flag(), x, y, outerWidth, size); +PaintRoundImageCallback TypeRow::generatePaintUserpicCallback() { + const auto flag = this->flag(); + return [=](Painter &p, int x, int y, int outerWidth, int size) { + PaintFilterChatsTypeIcon(p, flag, x, y, outerWidth, size); + }; } Flag TypeRow::flag() const { diff --git a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp index a58aeccd6..2a60c9873 100644 --- a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp @@ -345,13 +345,12 @@ void ManageFiltersPrepare::SetupBox( AddSubsectionTitle(content, tr::lng_filters_subtitle()); const auto rows = box->lifetime().make_state>(); + const auto rowsCount = box->lifetime().make_state>(); const auto find = [=](not_null button) { const auto i = ranges::find(*rows, button, &FilterRow::button); Assert(i != end(*rows)); return &*i; }; - const auto countNonRemoved = [=] { - }; const auto showLimitReached = [=] { const auto removed = ranges::count_if(*rows, &FilterRow::removed); if (rows->size() < kFiltersLimit + removed) { @@ -393,6 +392,7 @@ void ManageFiltersPrepare::SetupBox( crl::guard(button, doneCallback))); }); rows->push_back({ button, filter }); + *rowsCount = rows->size(); wrap->resizeToWidth(content->width()); }; @@ -462,8 +462,14 @@ void ManageFiltersPrepare::SetupBox( } using namespace rpl::mappers; - emptyAbout->toggleOn(suggested->value() | rpl::map(_1 == 0)); - nonEmptyAbout->toggleOn(suggested->value() | rpl::map(_1 > 0)); + auto showSuggestions = rpl::combine( + suggested->value(), + rowsCount->value() + ) | rpl::map(_1 > 0 && _2 < kFiltersLimit); + emptyAbout->toggleOn(rpl::duplicate( + showSuggestions + ) | rpl::map(!_1)); + nonEmptyAbout->toggleOn(std::move(showSuggestions)); const auto prepareGoodIdsForNewFilters = [=] { const auto &list = session->data().chatsFilters().list(); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 2f86bf5e1..3ab014575 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -484,24 +484,17 @@ QString PeerListRow::generateShortName() { } PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback() { + const auto saved = _isSavedMessagesChat; + const auto peer = this->peer(); return [=](Painter &p, int x, int y, int outerWidth, int size) { - paintEntityUserpicLeft(p, x, y, outerWidth, size); + if (saved) { + Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size); + } else { + peer->paintUserpicLeft(p, x, y, outerWidth, size); + } }; } -void PeerListRow::paintEntityUserpicLeft( - Painter &p, - int x, - int y, - int outerWidth, - int size) { - if (_isSavedMessagesChat) { - Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size); - } else { - peer()->paintUserpicLeft(p, x, y, outerWidth, size); - } -} - void PeerListRow::invalidatePixmapsCache() { if (_checkbox) { _checkbox->invalidateCache(); @@ -571,8 +564,8 @@ void PeerListRow::paintUserpic( paintDisabledCheckUserpic(p, st, x, y, outerWidth); } else if (_checkbox) { _checkbox->paint(p, x, y, outerWidth); - } else { - paintEntityUserpicLeft(p, x, y, outerWidth, st.photoSize); + } else if (const auto callback = generatePaintUserpicCallback()) { + callback(p, x, y, outerWidth, st.photoSize); } } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 005fa563a..706a1d9f6 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -87,7 +87,8 @@ public: [[nodiscard]] virtual QString generateName(); [[nodiscard]] virtual QString generateShortName(); - [[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback(); + [[nodiscard]] virtual auto generatePaintUserpicCallback() + -> PaintRoundImageCallback; void setCustomStatus(const QString &status); void clearCustomStatus(); @@ -207,13 +208,6 @@ protected: explicit PeerListRow(PeerListRowId id); - virtual void paintEntityUserpicLeft( - Painter &p, - int x, - int y, - int outerWidth, - int size); - private: void createCheckbox( const style::RoundImageCheckbox &st, From f0322cd107e17fdb359d01a64c4339418dc5f0d7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 17:39:08 +0400 Subject: [PATCH 042/115] Fix filters unread counters. --- .../SourceFiles/dialogs/dialogs_main_list.cpp | 10 ++-- Telegram/SourceFiles/history/history.cpp | 53 ++++++++++--------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index ee552a265..168dcb7e2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -45,9 +45,11 @@ void MainList::setLoaded(bool loaded) { if (_loaded == loaded) { return; } + const auto recomputer = gsl::finally([&] { + recomputeFullListSize(); + }); const auto notifier = unreadStateChangeNotifier(true); _loaded = loaded; - recomputeFullListSize(); } void MainList::setAllAreMuted(bool allAreMuted) { @@ -71,6 +73,9 @@ const rpl::variable &MainList::fullSize() const { } void MainList::clear() { + const auto recomputer = gsl::finally([&] { + recomputeFullListSize(); + }); const auto notifier = unreadStateChangeNotifier(true); _all.clear(); _unreadState = UnreadState(); @@ -78,7 +83,6 @@ void MainList::clear() { _unreadState.known = true; _cloudUnreadState.known = true; _cloudListSize = 0; - recomputeFullListSize(); } RowsByLetter MainList::addEntry(const Key &key) { @@ -125,7 +129,7 @@ void MainList::unreadEntryChanged( return; } const auto updateCloudUnread = _cloudUnreadState.known && state.known; - const auto notify = loaded() || updateCloudUnread; + const auto notify = !_cloudUnreadState.known || loaded() || state.known; const auto notifier = unreadStateChangeNotifier(notify); if (added) { _unreadState += state; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index a908dfbb6..6da131959 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1781,13 +1781,17 @@ void History::setUnreadCount(int newUnreadCount) { if (_unreadCount == newUnreadCount) { return; } - const auto notifier = unreadStateChangeNotifier(true); - const auto wasForBadge = (unreadCountForBadge() > 0); + const auto refresher = gsl::finally([&] { + if (wasForBadge != (unreadCountForBadge() > 0)) { + owner().chatsFilters().refreshHistory(this); + } + Notify::peerUpdatedDelayed( + peer, + Notify::PeerUpdate::Flag::UnreadViewChanged); + }); + const auto notifier = unreadStateChangeNotifier(true); _unreadCount = newUnreadCount; - if (wasForBadge != (unreadCountForBadge() > 0)) { - owner().chatsFilters().refreshHistory(this); - } if (newUnreadCount == 1) { if (loadedAtBottom()) { @@ -1806,9 +1810,6 @@ void History::setUnreadCount(int newUnreadCount) { } else if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) { calculateFirstUnreadMessage(); } - Notify::peerUpdatedDelayed( - peer, - Notify::PeerUpdate::Flag::UnreadViewChanged); } void History::setUnreadMark(bool unread) { @@ -1819,17 +1820,17 @@ void History::setUnreadMark(bool unread) { return; } const auto noUnreadMessages = !unreadCount(); + const auto refresher = gsl::finally([&] { + if (inChatList() && noUnreadMessages) { + owner().chatsFilters().refreshHistory(this); + updateChatListEntry(); + } + Notify::peerUpdatedDelayed( + peer, + Notify::PeerUpdate::Flag::UnreadViewChanged); + }); const auto notifier = unreadStateChangeNotifier(noUnreadMessages); - _unreadMark = unread; - - if (inChatList() && noUnreadMessages) { - owner().chatsFilters().refreshHistory(this); - updateChatListEntry(); - } - Notify::peerUpdatedDelayed( - peer, - Notify::PeerUpdate::Flag::UnreadViewChanged); } bool History::unreadMark() const { @@ -1844,18 +1845,18 @@ bool History::changeMute(bool newMute) { if (_mute == newMute) { return false; } + const auto refresher = gsl::finally([&] { + if (inChatList()) { + owner().chatsFilters().refreshHistory(this); + updateChatListEntry(); + } + Notify::peerUpdatedDelayed( + peer, + Notify::PeerUpdate::Flag::NotificationsEnabled); + }); const auto notify = (unreadCountForBadge() > 0); const auto notifier = unreadStateChangeNotifier(notify); - _mute = newMute; - - if (inChatList()) { - owner().chatsFilters().refreshHistory(this); - updateChatListEntry(); - } - Notify::peerUpdatedDelayed( - peer, - Notify::PeerUpdate::Flag::NotificationsEnabled); return true; } From ad8b0387f358ba903bf06d8d7f44c05c6551a0fa Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 18:10:22 +0400 Subject: [PATCH 043/115] Improve phrases for loading / empty filters. --- Telegram/Resources/langs/lang.strings | 1 + .../dialogs/dialogs_inner_widget.cpp | 33 ++++++++++--------- .../window/window_session_controller.cpp | 11 +++++++ .../window/window_session_controller.h | 3 +- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index df3608e3e..f17b04e23 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -250,6 +250,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dlg_new_channel_name" = "Channel name"; "lng_no_contacts" = "You have no contacts"; "lng_no_chats" = "Your chats will be here"; +"lng_no_chats_filter" = "No chats currently match this folder."; "lng_contacts_loading" = "Loading..."; "lng_contacts_not_found" = "No contacts found"; "lng_dlg_search_for_messages" = "Search for messages"; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index caabc4e53..8ccc03aac 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -498,11 +498,16 @@ void InnerWidget::paintEvent(QPaintEvent *e) { p.fillRect(dialogsClip, st::dialogsBg); p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); + const auto phrase = _filterId + ? (session().data().chatsList()->loaded() + ? tr::lng_no_chats_filter(tr::now) + : tr::lng_contacts_loading(tr::now)) + : session().data().contactsLoaded().current() + ? tr::lng_no_chats(tr::now) + : tr::lng_contacts_loading(tr::now); p.drawText( QRect(0, 0, fullWidth, st::noContactsHeight - (session().data().contactsLoaded().current() ? st::noContactsFont->height : 0)), - (session().data().contactsLoaded().current() - ? tr::lng_no_chats - : tr::lng_contacts_loading)(tr::now), + phrase, style::al_center); } } else if (_state == WidgetState::Filtered) { @@ -1198,6 +1203,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) { return false; } + const auto list = shownDialogs(); auto yaddWas = _pinnedRows[_draggingIndex].yadd.current(); auto shift = 0; auto now = crl::now(); @@ -1206,7 +1212,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) { shift = -floorclamp(_dragStart.y() - localPosition.y() + (rowHeight / 2), rowHeight, 0, _draggingIndex); for (auto from = _draggingIndex, to = _draggingIndex + shift; from > to; --from) { - shownDialogs()->movePinned(_dragging, -1); + list->movePinned(_dragging, -1); std::swap(_pinnedRows[from], _pinnedRows[from - 1]); _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - rowHeight, 0); _pinnedRows[from].animStartTime = now; @@ -1215,7 +1221,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) { shift = floorclamp(localPosition.y() - _dragStart.y() + (rowHeight / 2), rowHeight, 0, pinnedCount - _draggingIndex - 1); for (auto from = _draggingIndex, to = _draggingIndex + shift; from < to; ++from) { - shownDialogs()->movePinned(_dragging, 1); + list->movePinned(_dragging, 1); std::swap(_pinnedRows[from], _pinnedRows[from + 1]); _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + rowHeight, 0); _pinnedRows[from].animStartTime = now; @@ -2184,21 +2190,19 @@ void InnerWidget::refresh(bool toTop) { if (needCollapsedRowsRefresh()) { return refreshWithCollapsedRows(toTop); } + const auto list = shownDialogs(); + _addContactLnk->setVisible(!_filterId + && (_state == WidgetState::Default) + && list->empty() + && session().data().contactsLoaded().current()); auto h = 0; if (_state == WidgetState::Default) { - if (shownDialogs()->empty()) { + if (list->empty()) { h = st::noContactsHeight; - if (session().data().contactsLoaded().current()) { - if (_addContactLnk->isHidden()) _addContactLnk->show(); - } else { - if (!_addContactLnk->isHidden()) _addContactLnk->hide(); - } } else { - h = dialogsOffset() + shownDialogs()->size() * st::dialogsRowHeight; - if (!_addContactLnk->isHidden()) _addContactLnk->hide(); + h = dialogsOffset() + list->size() * st::dialogsRowHeight; } } else if (_state == WidgetState::Filtered) { - if (!_addContactLnk->isHidden()) _addContactLnk->hide(); if (_waitingForSearch) { h = searchedOffset() + (_searchResults.size() * st::dialogsRowHeight) + ((_searchResults.empty() && !_searchInChat) ? -st::searchedBarHeight : 0); } else { @@ -2556,7 +2560,6 @@ void InnerWidget::switchToFilter(FilterId filterId) { stopReorderPinned(); _filterId = filterId; refreshWithCollapsedRows(true); - _collapsedSelected = 0; } bool InnerWidget::chooseHashtag() { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index bf67f667d..0823a1089 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -141,6 +141,7 @@ SessionController::SessionController( session->data().chatsFilters().changed( ) | rpl::start_with_next([=] { + checkOpenedFilter(); crl::on_main(session, [=] { refreshFiltersMenu(); }); @@ -226,6 +227,16 @@ rpl::producer<> SessionController::filtersMenuChanged() const { return _filtersMenuChanged.events(); } +void SessionController::checkOpenedFilter() { + if (const auto filterId = activeChatsFilterCurrent()) { + const auto &list = session().data().chatsFilters().list(); + const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); + if (i == end(list)) { + setActiveChatsFilter(0); + } + } +} + bool SessionController::uniqueChatsInSearchResults() const { return session().supportMode() && !session().settings().supportAllSearchResults() diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 38275c58a..ef187d9cc 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -299,7 +299,6 @@ public: void setActiveChatsFilter(FilterId id); void toggleFiltersMenu(bool enabled); - void refreshFiltersMenu(); [[nodiscard]] rpl::producer<> filtersMenuChanged() const; rpl::lifetime &lifetime() { @@ -311,6 +310,8 @@ public: private: void init(); void initSupportMode(); + void refreshFiltersMenu(); + void checkOpenedFilter(); int minimalThreeColumnWidth() const; not_null chats() const; From dc49f7e6dc496e47b0295a5a27d6ed5229952fbd Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 18:37:04 +0400 Subject: [PATCH 044/115] Load all archive after the main list for filters. --- Telegram/SourceFiles/apiwrap.cpp | 33 +++++++++++++++---- Telegram/SourceFiles/apiwrap.h | 1 + .../SourceFiles/data/data_chat_filters.cpp | 9 +++++ Telegram/SourceFiles/data/data_chat_filters.h | 2 ++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 2b91cbe5b..847d4a530 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_cloud_themes.h" +#include "data/data_chat_filters.h" #include "data/data_histories.h" #include "dialogs/dialogs_key.h" #include "core/core_cloud_password.h" @@ -248,6 +249,13 @@ ApiWrap::ApiWrap(not_null session) photoUploadReady(data.fullId, data.file); }, _session->lifetime()); + _session->data().chatsFilters().changed( + ) | rpl::filter([=] { + return _session->data().chatsFilters().archiveNeeded(); + }) | rpl::start_with_next([=] { + requestMoreDialogsIfNeeded(); + }, _session->lifetime()); + setupSupportMode(); }); } @@ -856,13 +864,11 @@ void ApiWrap::requestMoreDialogs(Data::Folder *folder) { count); }); - if (!folder) { - if (!_dialogsLoadState || !_dialogsLoadState->listReceived) { - refreshDialogsLoadBlocked(); - } - requestDialogs(folder); - requestContacts(); + if (!folder + && (!_dialogsLoadState || !_dialogsLoadState->listReceived)) { + refreshDialogsLoadBlocked(); } + requestMoreDialogsIfNeeded(); _session->data().chatsListChanged(folder); }).fail([=](const RPCError &error) { dialogsLoadState(folder)->requestId = 0; @@ -888,6 +894,21 @@ void ApiWrap::refreshDialogsLoadBlocked() { && (_dialogsLoadState->offsetDate <= _dialogsLoadTill); } +void ApiWrap::requestMoreDialogsIfNeeded() { + if (_dialogsLoadState && !_dialogsLoadState->listReceived) { + if (_dialogsLoadState->requestId) { + return; + } + requestDialogs(nullptr); + } else if (const auto folder = _session->data().folderLoaded( + Data::Folder::kId)) { + if (_session->data().chatsFilters().archiveNeeded()) { + requestMoreDialogs(folder); + } + } + requestContacts(); +} + void ApiWrap::updateDialogsOffset( Data::Folder *folder, const QVector &dialogs, diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index c48ef2b01..30d364dbd 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -170,6 +170,7 @@ public: void requestDialogs(Data::Folder *folder = nullptr); void requestPinnedDialogs(Data::Folder *folder = nullptr); void requestMoreBlockedByDateDialogs(); + void requestMoreDialogsIfNeeded(); rpl::producer dialogsLoadMayBlockByDate() const; rpl::producer dialogsLoadBlockedByDate() const; diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 27efdf7a5..b5a012c09 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -439,6 +439,15 @@ const ChatFilter &ChatFilters::applyUpdatedPinned( return *i; } +bool ChatFilters::archiveNeeded() const { + for (const auto &filter : _list) { + if (!(filter.flags() & ChatFilter::Flag::NoArchived)) { + return true; + } + } + return false; +} + const std::vector &ChatFilters::list() const { return _list; } diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 051afa9da..9d0e1e028 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -103,6 +103,8 @@ public: FilterId id, const std::vector &dialogs); + [[nodiscard]] bool archiveNeeded() const; + private: void load(bool force); bool applyOrder(const QVector &order); From f643b5f725c26d5ec7f28b67e24525e0a636b32f Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 18 Mar 2020 22:42:01 +0400 Subject: [PATCH 045/115] Closed alpha version 1.9.21.1. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/version.h | 2 +- Telegram/build/version | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index ab2fefff9..34b504faf 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="1.9.21.1" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 577fe7773..d2d7bb6ab 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -33,8 +33,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,9,21,0 - PRODUCTVERSION 1,9,21,0 + FILEVERSION 1,9,21,1 + PRODUCTVERSION 1,9,21,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "1.9.21.0" + VALUE "FileVersion", "1.9.21.1" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "1.9.21.0" + VALUE "ProductVersion", "1.9.21.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 17d878974..7d6c12dfb 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -24,8 +24,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,9,21,0 - PRODUCTVERSION 1,9,21,0 + FILEVERSION 1,9,21,1 + PRODUCTVERSION 1,9,21,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -42,10 +42,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "1.9.21.0" + VALUE "FileVersion", "1.9.21.1" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "1.9.21.0" + VALUE "ProductVersion", "1.9.21.1" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index a2b4c3860..dc9f484bf 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/const_string.h" -#define TDESKTOP_REQUESTED_ALPHA_VERSION (0ULL) +#define TDESKTOP_REQUESTED_ALPHA_VERSION (1009021001ULL) #ifdef TDESKTOP_ALLOW_CLOSED_ALPHA #define TDESKTOP_ALPHA_VERSION TDESKTOP_REQUESTED_ALPHA_VERSION diff --git a/Telegram/build/version b/Telegram/build/version index eaf718e9a..afdd4fda1 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -3,5 +3,5 @@ AppVersionStrMajor 1.9 AppVersionStrSmall 1.9.21 AppVersionStr 1.9.21 BetaChannel 0 -AlphaVersion 0 -AppVersionOriginal 1.9.21 +AlphaVersion 1009021001 +AppVersionOriginal 1.9.21.1 From 4b8a42fafd267b75beac55f83d34e9d282e589d3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Mar 2020 12:06:28 +0400 Subject: [PATCH 046/115] Fix crash in forwarded dice media. --- Telegram/SourceFiles/history/view/media/history_view_dice.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 90200d1f7..f753d434a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -51,6 +51,9 @@ void Dice::draw(Painter &p, const QRect &r, bool selected) { _end->initSize(); } } + if (!_end) { + _drawingEnd = false; + } if (_drawingEnd) { _end->draw(p, r, selected); } else { From a13042ac6a6c4849c2694fd0d5e8b167ea8cabc2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Mar 2020 12:06:43 +0400 Subject: [PATCH 047/115] Improve edit filter phrases. --- Telegram/Resources/langs/lang.strings | 6 +++--- Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp | 2 +- .../SourceFiles/boxes/filters/edit_filter_chats_list.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f17b04e23..986075548 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2274,9 +2274,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_type_groups" = "Groups"; "lng_filters_type_channels" = "Channels"; "lng_filters_type_bots" = "Bots"; -"lng_filters_type_no_archived" = "No Archived"; -"lng_filters_type_no_muted" = "No Muted"; -"lng_filters_type_no_read" = "No Read"; +"lng_filters_type_no_archived" = "Archived"; +"lng_filters_type_no_muted" = "Muted"; +"lng_filters_type_no_read" = "Read"; // Wnd specific diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 558c96b82..e6d6b2f6a 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -44,8 +44,8 @@ constexpr auto kAllTypes = { Flag::Channels, Flag::Bots, Flag::NoMuted, + Flag::NoRead, Flag::NoArchived, - Flag::NoRead }; class FilterChatsPreview final : public Ui::RpWidget { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index 6426b44b2..ea008f4ef 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -29,8 +29,8 @@ constexpr auto kAllTypes = { Flag::Channels, Flag::Bots, Flag::NoMuted, + Flag::NoRead, Flag::NoArchived, - Flag::NoRead }; struct RowSelectionChange { From 1ea42116d27dbaa6cdb86809042915ca8cf79309 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Mar 2020 13:11:41 +0400 Subject: [PATCH 048/115] Fix adding Saved Messages exception to filters. --- Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp | 2 +- Telegram/SourceFiles/data/data_chat_filters.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index e6d6b2f6a..0584c746f 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -276,7 +276,7 @@ void EditExceptions( const auto peers = box->peerListCollectSelectedRows(); auto &&histories = ranges::view::all( peers - ) | ranges::view::transform([=](not_null peer) { + ) | ranges::view::transform([=](not_null peer) { return window->session().data().history(peer); }); auto changed = base::flat_set>{ diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index b5a012c09..0042cb5de 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -59,6 +59,8 @@ ChatFilter ChatFilter::FromTL( const auto channel = owner->channel(data.vchannel_id().v); channel->setAccessHash(data.vaccess_hash().v); return (PeerData*)channel; + }, [&](const MTPDinputPeerSelf &data) { + return (PeerData*)owner->session().user(); }, [&](const auto &data) { return (PeerData*)nullptr; }); From 03da4a5680b5d12d35b499b06cc4952579aa69f3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Mar 2020 13:40:01 +0400 Subject: [PATCH 049/115] Fix pinned chats limit. --- Telegram/SourceFiles/data/data_chat_filters.h | 2 +- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 9d0e1e028..02a749b06 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -35,7 +35,7 @@ public: friend constexpr inline bool is_flag_type(Flag) { return true; }; using Flags = base::flags; - static constexpr int kPinnedLimit = 10; + static constexpr int kPinnedLimit = 100; ChatFilter() = default; ChatFilter( diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ed97ff0d4..1f0a772b1 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ed97ff0d4fd71dea6ad509b1178bb167f01bc178 +Subproject commit 1f0a772b1841af3e8e47b594369cbbf04c760e9f From 9ad5b8ff90c35c28ddcc38f61f2b4c6768bcef3b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Mar 2020 15:06:08 +0400 Subject: [PATCH 050/115] Add special filters icons. --- Telegram/CMakeLists.txt | 5 +- .../icons/{ => filters}/filters_all.png | Bin .../icons/{ => filters}/filters_all@2x.png | Bin .../icons/{ => filters}/filters_all@3x.png | Bin .../icons/filters/filters_all_active.png | Bin 0 -> 923 bytes .../icons/filters/filters_all_active@2x.png | Bin 0 -> 1808 bytes .../icons/filters/filters_all_active@3x.png | Bin 0 -> 2703 bytes .../icons/{ => filters}/filters_bots.png | Bin .../icons/{ => filters}/filters_bots@2x.png | Bin .../icons/{ => filters}/filters_bots@3x.png | Bin .../{ => filters}/filters_bots_active.png | Bin .../{ => filters}/filters_bots_active@2x.png | Bin .../{ => filters}/filters_bots_active@3x.png | Bin .../icons/filters/filters_channels.png | Bin 0 -> 618 bytes .../icons/filters/filters_channels@2x.png | Bin 0 -> 1362 bytes .../icons/filters/filters_channels@3x.png | Bin 0 -> 2133 bytes .../icons/filters/filters_channels_active.png | Bin 0 -> 527 bytes .../filters/filters_channels_active@2x.png | Bin 0 -> 1048 bytes .../filters/filters_channels_active@3x.png | Bin 0 -> 1584 bytes .../icons/{ => filters}/filters_custom.png | Bin .../icons/{ => filters}/filters_custom@2x.png | Bin .../icons/{ => filters}/filters_custom@3x.png | Bin .../{ => filters}/filters_custom_active.png | Bin .../filters_custom_active@2x.png | Bin .../filters_custom_active@3x.png | Bin .../icons/{ => filters}/filters_groups.png | Bin .../icons/{ => filters}/filters_groups@2x.png | Bin .../icons/{ => filters}/filters_groups@3x.png | Bin .../{ => filters}/filters_groups_active.png | Bin .../filters_groups_active@2x.png | Bin .../filters_groups_active@3x.png | Bin .../icons/{ => filters}/filters_private.png | Bin .../{ => filters}/filters_private@2x.png | Bin .../{ => filters}/filters_private@3x.png | Bin .../{ => filters}/filters_private_active.png | Bin .../filters_private_active@2x.png | Bin .../filters_private_active@3x.png | Bin .../icons/{ => filters}/filters_setup.png | Bin .../icons/{ => filters}/filters_setup@2x.png | Bin .../icons/{ => filters}/filters_setup@3x.png | Bin .../icons/{ => filters}/filters_unmuted.png | Bin .../{ => filters}/filters_unmuted@2x.png | Bin .../{ => filters}/filters_unmuted@3x.png | Bin .../icons/filters/filters_unmuted_active.png | Bin 0 -> 510 bytes .../filters/filters_unmuted_active@2x.png | Bin 0 -> 946 bytes .../filters/filters_unmuted_active@3x.png | Bin 0 -> 1444 bytes .../icons/{ => filters}/filters_unread.png | Bin .../icons/{ => filters}/filters_unread@2x.png | Bin .../icons/{ => filters}/filters_unread@3x.png | Bin .../icons/filters/filters_unread_active.png | Bin 0 -> 718 bytes .../filters/filters_unread_active@2x.png | Bin 0 -> 1473 bytes .../filters/filters_unread_active@3x.png | Bin 0 -> 2300 bytes .../Resources/icons/filters/folders_cat.png | Bin 0 -> 1237 bytes .../icons/filters/folders_cat@2x.png | Bin 0 -> 2450 bytes .../icons/filters/folders_cat@3x.png | Bin 0 -> 3804 bytes .../icons/filters/folders_cat_active.png | Bin 0 -> 943 bytes .../icons/filters/folders_cat_active@2x.png | Bin 0 -> 1819 bytes .../icons/filters/folders_cat_active@3x.png | Bin 0 -> 2803 bytes .../Resources/icons/filters/folders_crown.png | Bin 0 -> 1051 bytes .../icons/filters/folders_crown@2x.png | Bin 0 -> 2046 bytes .../icons/filters/folders_crown@3x.png | Bin 0 -> 3190 bytes .../icons/filters/folders_crown_active.png | Bin 0 -> 885 bytes .../icons/filters/folders_crown_active@2x.png | Bin 0 -> 1643 bytes .../icons/filters/folders_crown_active@3x.png | Bin 0 -> 2502 bytes .../icons/filters/folders_favorite.png | Bin 0 -> 923 bytes .../icons/filters/folders_favorite@2x.png | Bin 0 -> 1893 bytes .../icons/filters/folders_favorite@3x.png | Bin 0 -> 2790 bytes .../icons/filters/folders_favorite_active.png | Bin 0 -> 690 bytes .../filters/folders_favorite_active@2x.png | Bin 0 -> 1362 bytes .../filters/folders_favorite_active@3x.png | Bin 0 -> 2033 bytes .../icons/filters/folders_flower.png | Bin 0 -> 1049 bytes .../icons/filters/folders_flower@2x.png | Bin 0 -> 2240 bytes .../icons/filters/folders_flower@3x.png | Bin 0 -> 3269 bytes .../icons/filters/folders_flower_active.png | Bin 0 -> 908 bytes .../filters/folders_flower_active@2x.png | Bin 0 -> 1810 bytes .../filters/folders_flower_active@3x.png | Bin 0 -> 2523 bytes .../Resources/icons/filters/folders_game.png | Bin 0 -> 782 bytes .../icons/filters/folders_game@2x.png | Bin 0 -> 1705 bytes .../icons/filters/folders_game@3x.png | Bin 0 -> 2686 bytes .../icons/filters/folders_game_active.png | Bin 0 -> 620 bytes .../icons/filters/folders_game_active@2x.png | Bin 0 -> 1298 bytes .../icons/filters/folders_game_active@3x.png | Bin 0 -> 1994 bytes .../Resources/icons/filters/folders_home.png | Bin 0 -> 463 bytes .../icons/filters/folders_home@2x.png | Bin 0 -> 866 bytes .../icons/filters/folders_home@3x.png | Bin 0 -> 1238 bytes .../icons/filters/folders_home_active.png | Bin 0 -> 384 bytes .../icons/filters/folders_home_active@2x.png | Bin 0 -> 674 bytes .../icons/filters/folders_home_active@3x.png | Bin 0 -> 1090 bytes .../Resources/icons/filters/folders_love.png | Bin 0 -> 824 bytes .../icons/filters/folders_love@2x.png | Bin 0 -> 1711 bytes .../icons/filters/folders_love@3x.png | Bin 0 -> 2600 bytes .../icons/filters/folders_love_active.png | Bin 0 -> 577 bytes .../icons/filters/folders_love_active@2x.png | Bin 0 -> 1162 bytes .../icons/filters/folders_love_active@3x.png | Bin 0 -> 1799 bytes .../Resources/icons/filters/folders_mask.png | Bin 0 -> 1107 bytes .../icons/filters/folders_mask@2x.png | Bin 0 -> 2221 bytes .../icons/filters/folders_mask@3x.png | Bin 0 -> 3384 bytes .../icons/filters/folders_mask_active.png | Bin 0 -> 849 bytes .../icons/filters/folders_mask_active@2x.png | Bin 0 -> 1591 bytes .../icons/filters/folders_mask_active@3x.png | Bin 0 -> 2507 bytes .../Resources/icons/filters/folders_party.png | Bin 0 -> 505 bytes .../icons/filters/folders_party@2x.png | Bin 0 -> 897 bytes .../icons/filters/folders_party@3x.png | Bin 0 -> 1328 bytes .../icons/filters/folders_party_active.png | Bin 0 -> 521 bytes .../icons/filters/folders_party_active@2x.png | Bin 0 -> 948 bytes .../icons/filters/folders_party_active@3x.png | Bin 0 -> 1450 bytes .../Resources/icons/filters/folders_sport.png | Bin 0 -> 964 bytes .../icons/filters/folders_sport@2x.png | Bin 0 -> 2050 bytes .../icons/filters/folders_sport@3x.png | Bin 0 -> 3259 bytes .../icons/filters/folders_sport_active.png | Bin 0 -> 976 bytes .../icons/filters/folders_sport_active@2x.png | Bin 0 -> 1935 bytes .../icons/filters/folders_sport_active@3x.png | Bin 0 -> 2828 bytes .../Resources/icons/filters/folders_study.png | Bin 0 -> 1025 bytes .../icons/filters/folders_study@2x.png | Bin 0 -> 2107 bytes .../icons/filters/folders_study@3x.png | Bin 0 -> 2990 bytes .../icons/filters/folders_study_active.png | Bin 0 -> 875 bytes .../icons/filters/folders_study_active@2x.png | Bin 0 -> 1689 bytes .../icons/filters/folders_study_active@3x.png | Bin 0 -> 2437 bytes .../Resources/icons/filters/folders_trade.png | Bin 0 -> 606 bytes .../icons/filters/folders_trade@2x.png | Bin 0 -> 1303 bytes .../icons/filters/folders_trade@3x.png | Bin 0 -> 2058 bytes .../icons/filters/folders_travel.png | Bin 0 -> 1143 bytes .../icons/filters/folders_travel@2x.png | Bin 0 -> 2342 bytes .../icons/filters/folders_travel@3x.png | Bin 0 -> 3411 bytes .../icons/filters/folders_travel_active.png | Bin 0 -> 941 bytes .../filters/folders_travel_active@2x.png | Bin 0 -> 1583 bytes .../filters/folders_travel_active@3x.png | Bin 0 -> 2478 bytes .../Resources/icons/filters/folders_work.png | Bin 0 -> 403 bytes .../icons/filters/folders_work@2x.png | Bin 0 -> 704 bytes .../icons/filters/folders_work@3x.png | Bin 0 -> 1191 bytes .../icons/filters/folders_work_active.png | Bin 0 -> 361 bytes .../icons/filters/folders_work_active@2x.png | Bin 0 -> 669 bytes .../icons/filters/folders_work_active@3x.png | Bin 0 -> 1116 bytes .../Resources/icons/filters_all_active.png | Bin 877 -> 0 bytes .../Resources/icons/filters_all_active@2x.png | Bin 1767 -> 0 bytes .../Resources/icons/filters_all_active@3x.png | Bin 2592 -> 0 bytes Telegram/Resources/icons/filters_channels.png | Bin 897 -> 0 bytes .../Resources/icons/filters_channels@2x.png | Bin 1796 -> 0 bytes .../Resources/icons/filters_channels@3x.png | Bin 2645 -> 0 bytes .../icons/filters_channels_active.png | Bin 633 -> 0 bytes .../icons/filters_channels_active@2x.png | Bin 1099 -> 0 bytes .../icons/filters_channels_active@3x.png | Bin 1775 -> 0 bytes .../icons/filters_unmuted_active.png | Bin 470 -> 0 bytes .../icons/filters_unmuted_active@2x.png | Bin 936 -> 0 bytes .../icons/filters_unmuted_active@3x.png | Bin 1482 -> 0 bytes .../Resources/icons/filters_unread_active.png | Bin 677 -> 0 bytes .../icons/filters_unread_active@2x.png | Bin 1402 -> 0 bytes .../icons/filters_unread_active@3x.png | Bin 2108 -> 0 bytes Telegram/SourceFiles/ui/filter_icons.cpp | 50 ++++++++++++++++ Telegram/SourceFiles/ui/filter_icons.h | 52 ++++++++++++++++ Telegram/SourceFiles/ui/filter_icons.style | 54 +++++++++++++++++ Telegram/SourceFiles/window/window.style | 35 ----------- .../window/window_filters_menu.cpp | 56 ++++++------------ Telegram/lib_ui | 2 +- 154 files changed, 180 insertions(+), 74 deletions(-) rename Telegram/Resources/icons/{ => filters}/filters_all.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_all@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_all@3x.png (100%) create mode 100644 Telegram/Resources/icons/filters/filters_all_active.png create mode 100644 Telegram/Resources/icons/filters/filters_all_active@2x.png create mode 100644 Telegram/Resources/icons/filters/filters_all_active@3x.png rename Telegram/Resources/icons/{ => filters}/filters_bots.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_bots@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_bots@3x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_bots_active.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_bots_active@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_bots_active@3x.png (100%) create mode 100644 Telegram/Resources/icons/filters/filters_channels.png create mode 100644 Telegram/Resources/icons/filters/filters_channels@2x.png create mode 100644 Telegram/Resources/icons/filters/filters_channels@3x.png create mode 100644 Telegram/Resources/icons/filters/filters_channels_active.png create mode 100644 Telegram/Resources/icons/filters/filters_channels_active@2x.png create mode 100644 Telegram/Resources/icons/filters/filters_channels_active@3x.png rename Telegram/Resources/icons/{ => filters}/filters_custom.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_custom@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_custom@3x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_custom_active.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_custom_active@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_custom_active@3x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_groups.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_groups@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_groups@3x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_groups_active.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_groups_active@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_groups_active@3x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_private.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_private@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_private@3x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_private_active.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_private_active@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_private_active@3x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_setup.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_setup@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_setup@3x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_unmuted.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_unmuted@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_unmuted@3x.png (100%) create mode 100644 Telegram/Resources/icons/filters/filters_unmuted_active.png create mode 100644 Telegram/Resources/icons/filters/filters_unmuted_active@2x.png create mode 100644 Telegram/Resources/icons/filters/filters_unmuted_active@3x.png rename Telegram/Resources/icons/{ => filters}/filters_unread.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_unread@2x.png (100%) rename Telegram/Resources/icons/{ => filters}/filters_unread@3x.png (100%) create mode 100644 Telegram/Resources/icons/filters/filters_unread_active.png create mode 100644 Telegram/Resources/icons/filters/filters_unread_active@2x.png create mode 100644 Telegram/Resources/icons/filters/filters_unread_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_cat.png create mode 100644 Telegram/Resources/icons/filters/folders_cat@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_cat@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_cat_active.png create mode 100644 Telegram/Resources/icons/filters/folders_cat_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_cat_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_crown.png create mode 100644 Telegram/Resources/icons/filters/folders_crown@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_crown@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_crown_active.png create mode 100644 Telegram/Resources/icons/filters/folders_crown_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_crown_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_favorite.png create mode 100644 Telegram/Resources/icons/filters/folders_favorite@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_favorite@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_favorite_active.png create mode 100644 Telegram/Resources/icons/filters/folders_favorite_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_favorite_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_flower.png create mode 100644 Telegram/Resources/icons/filters/folders_flower@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_flower@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_flower_active.png create mode 100644 Telegram/Resources/icons/filters/folders_flower_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_flower_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_game.png create mode 100644 Telegram/Resources/icons/filters/folders_game@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_game@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_game_active.png create mode 100644 Telegram/Resources/icons/filters/folders_game_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_game_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_home.png create mode 100644 Telegram/Resources/icons/filters/folders_home@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_home@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_home_active.png create mode 100644 Telegram/Resources/icons/filters/folders_home_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_home_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_love.png create mode 100644 Telegram/Resources/icons/filters/folders_love@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_love@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_love_active.png create mode 100644 Telegram/Resources/icons/filters/folders_love_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_love_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_mask.png create mode 100644 Telegram/Resources/icons/filters/folders_mask@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_mask@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_mask_active.png create mode 100644 Telegram/Resources/icons/filters/folders_mask_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_mask_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_party.png create mode 100644 Telegram/Resources/icons/filters/folders_party@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_party@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_party_active.png create mode 100644 Telegram/Resources/icons/filters/folders_party_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_party_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_sport.png create mode 100644 Telegram/Resources/icons/filters/folders_sport@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_sport@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_sport_active.png create mode 100644 Telegram/Resources/icons/filters/folders_sport_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_sport_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_study.png create mode 100644 Telegram/Resources/icons/filters/folders_study@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_study@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_study_active.png create mode 100644 Telegram/Resources/icons/filters/folders_study_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_study_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_trade.png create mode 100644 Telegram/Resources/icons/filters/folders_trade@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_trade@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_travel.png create mode 100644 Telegram/Resources/icons/filters/folders_travel@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_travel@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_travel_active.png create mode 100644 Telegram/Resources/icons/filters/folders_travel_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_travel_active@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_work.png create mode 100644 Telegram/Resources/icons/filters/folders_work@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_work@3x.png create mode 100644 Telegram/Resources/icons/filters/folders_work_active.png create mode 100644 Telegram/Resources/icons/filters/folders_work_active@2x.png create mode 100644 Telegram/Resources/icons/filters/folders_work_active@3x.png delete mode 100644 Telegram/Resources/icons/filters_all_active.png delete mode 100644 Telegram/Resources/icons/filters_all_active@2x.png delete mode 100644 Telegram/Resources/icons/filters_all_active@3x.png delete mode 100644 Telegram/Resources/icons/filters_channels.png delete mode 100644 Telegram/Resources/icons/filters_channels@2x.png delete mode 100644 Telegram/Resources/icons/filters_channels@3x.png delete mode 100644 Telegram/Resources/icons/filters_channels_active.png delete mode 100644 Telegram/Resources/icons/filters_channels_active@2x.png delete mode 100644 Telegram/Resources/icons/filters_channels_active@3x.png delete mode 100644 Telegram/Resources/icons/filters_unmuted_active.png delete mode 100644 Telegram/Resources/icons/filters_unmuted_active@2x.png delete mode 100644 Telegram/Resources/icons/filters_unmuted_active@3x.png delete mode 100644 Telegram/Resources/icons/filters_unread_active.png delete mode 100644 Telegram/Resources/icons/filters_unread_active@2x.png delete mode 100644 Telegram/Resources/icons/filters_unread_active@3x.png create mode 100644 Telegram/SourceFiles/ui/filter_icons.cpp create mode 100644 Telegram/SourceFiles/ui/filter_icons.h create mode 100644 Telegram/SourceFiles/ui/filter_icons.style diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ace72d47d..128852143 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -38,6 +38,7 @@ include(cmake/lib_tgvoip.cmake) set(style_files boxes/boxes.style calls/calls.style + chat_helpers/chat_helpers.style dialogs/dialogs.style export/view/export.style history/history.style @@ -49,7 +50,7 @@ set(style_files passport/passport.style profile/profile.style settings/settings.style - chat_helpers/chat_helpers.style + ui/filter_icons.style window/window.style ) @@ -921,6 +922,8 @@ PRIVATE ui/countryinput.h ui/empty_userpic.cpp ui/empty_userpic.h + ui/filter_icons.cpp + ui/filter_icons.h ui/grouped_layout.cpp ui/grouped_layout.h ui/resize_area.h diff --git a/Telegram/Resources/icons/filters_all.png b/Telegram/Resources/icons/filters/filters_all.png similarity index 100% rename from Telegram/Resources/icons/filters_all.png rename to Telegram/Resources/icons/filters/filters_all.png diff --git a/Telegram/Resources/icons/filters_all@2x.png b/Telegram/Resources/icons/filters/filters_all@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_all@2x.png rename to Telegram/Resources/icons/filters/filters_all@2x.png diff --git a/Telegram/Resources/icons/filters_all@3x.png b/Telegram/Resources/icons/filters/filters_all@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_all@3x.png rename to Telegram/Resources/icons/filters/filters_all@3x.png diff --git a/Telegram/Resources/icons/filters/filters_all_active.png b/Telegram/Resources/icons/filters/filters_all_active.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a0fb691fa4a5dfe1f986fd8363146a96af49ec GIT binary patch literal 923 zcmV;M17!S(P)nvZ?dk3ev0Y6?F;KhdpxeSP7=!U9%RRYkQI%U5tLIt)bwA0HnL$M^TQ z8xM-k>+36acXuZW6&a|nug?|0O<{O=I1wl(P-|=JkCrGF5J!u@uCA_lp)^odRuNr{+S=MmHIIq-4^EdrhayC|#lXmENhMirZ7nu5 zG~oF7xZ4tjfEpVcQ^}xgb#!!?2v8Em!^1-?D=U+Ts9N1+MMXsv*?&{S_U7g$)Ya9& z`uaN1W_LJKKo1WO4uw>8+~40DQx_K(rmm={fQg9-hj|4Q2n37*#oD2mY{&WexiRtd z^khx-^z=YUNr{!Yp@6QhuVHU*&s>^HD@lQW^!WJLSdpb;!RPY2VXJ;d=vpONk2zPgPSXfwSd}L;3;`8&fGB-Uvt^7M8 z^#1-1{r&wwPYFn7FY#Rd1_uX0_DyeZFXZLrsfb)%T`AcSA&J=B+?@K8M1+x%5&QQ9 zg0!(6W}2IutxnLlUNY{?&CNwlVDT2o@NaK#$j=w~IeJ;*?Ch+=+{wuadc9t&7ZIqb zsp-cs0wA7PUR+#6z9s~QP7=<{%!rDu`E}XS(qi>QpK2W;N?>*pH^QC1yu9Gj(h^c? zKyH70eBkBfC2nkN;OOY60%5y+Kvq^(lsyq>Z$*OPz7SAOP7dzx?;A%lk!#wNI5|1# zxPN#l!skmnBO?O`1_sdY_lKKNZGIk${$F(nNCr5}m6er9cMkF+Kbm9F53kG1%W->q zTRCT8cXt<8S66NS&dv_9pYXZCwi76VQlyD7`}kB*5;V(LP*7lXhlYmm=;#O!4i2!b xtgv!c_N=tm6|2a!5*L!zoX79awz53sQ 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 Eg5rw}Z~y=R literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/filters_channels@2x.png b/Telegram/Resources/icons/filters/filters_channels@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..697e07daf19b47a05c6012eb2500f7940618bf6f GIT binary patch literal 1362 zcmV-Y1+DstP)tqJSXi+jicpFy$ij|= zjU*+?MzN5Ug{5Lgc@zs9??>d3$M-+q`!{~)95eHsGjqr9y1#SkcF*^G&-p$+^PMx( z%mg$|1O2C(1%W7waW(u&Kmj63K><>L2m*v!RgeNi5Fpg5f)pTv0HIbDqyP~F2(_vp z1&AO(s8t0iKm-9ottv_TX6^a;8Je4$z2^4&;1m=Tz|71Hyu7@aapA@uA0MHpsK^VX z-vg+$v=mlWR^a2~gM&Z`OG`_U4XCiN5H>b8Y&o2tpTp455ai_KKub#tH}~S=B60z> zwYAxvN{5GsfOBGFW4|B-0s#&X3Lf^^@IA$9Yir^C{ry|AIIy|73H9~$5ET_=n!LE^ zuK~ghaD03mc6WE-{{CK!E9CO(>ME3#m6<|eA&8Dyon+r#1;xa~z~tnl-yZDy}gmBi^*TooomHYbo9K%5fVsin;@Fx!lE9a*h-W19tDaI$W zR+uXX2=^Ru<2*Szfu^P=kWE4egahC;L9gqX*yI4=hRimElboE)CC3luG?fbeR?Hmn@RSjhUU5A4hO@VcJyCkIHpl!x-PVuBL$`)hy#pF!z=&f5P6^UqxU zuJw-)!k$+@Ue_C`M8xwTBN;#b6(EAD0;B*D1PHa~3i4}{CgT1vwKSmX>uWB!+}vDF zjx<3U(DCsx$DpdJijyNzkh%#Q85!Yj;_!_^c6PRu4ILsmpL3!MiHV8i8rH73p4CbA z>0nudSy@?t&t}+$kUBa#ATKWu@Np+Q-)*0@kNvv3Iw`y2dbe@t>*N68{K3IN(Z8*1 z_n^ymA*0k!ZKfxw*M-fpQac zfeJV-E)F_7JK^Z)NL*?u*VNRIc5VFn2dJ{Lk_#G-ta!u6P0$6KKsjA>bTl+JHp2Gy zwrGNrr>Cd6bx!Rw^ZWyZ`v=~?u&t-3=PO`*zs5HFoP`O@alfLX0`!lk;O*`0%iQVd zDYUk>I<{fs*FQk$6u-D&8(w)D8X6!eDG3G!23S?_N@x=(nJn&mxKZPk)r%crt-pX$ zQ&ZvQ=7s}dB}`0Agfg*E+42_<9*y?)b}J@d<9JFSOyTH-gX}*bOuDzX*Ye9Sgi~H# z9u6?fV)z+A2noLwo1LA7{r!D-cz6izIe1!yY2^fANxv7<6aA-}^b^H@ijJPF0z@2f z1xNuR2oP#jK?)E-fKaOnQh*2ogj!XQ0z?oX)T)9MAc6p)Ru!ZG5d;XesvrW;f6XkV U0&ZXV*#H0l07*qoM6N<$g36|XUH||9 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/filters_channels@3x.png b/Telegram/Resources/icons/filters/filters_channels@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3dc154a4548ffe9c434c0638d2a7a5763b68355d GIT binary patch literal 2133 zcmb_edpr~B8>coVQ*MbPDa<9&mOG1FrW121%88A!)5v9v+^uQuxs17NxrQPmImxXu zwz-ttb6JyeDIG&}93q#_&VRpue*c|6-uL-@p7(jbpZEDZ-{^NJvOX z;?l1+R|Gcj-vts8>=c~afWQEJEgdX{g#Mt2?|K7;gh1d+HkPhY!Yimten%Z7OS`-> zf3fbgag`PGr=}O?nxu()%IeQqCZ&2FNK3sW31mrTiUwEA)JHk5Ra75G%1Rx}J9$?n z3YwFhuKiWfZY&SI8p;0Mb27H^R)-OSajO4i;aC2Xj$pW=I(aNI8C^-51>c@cBRT&>UNGX*UDOLzXcZ2*J+Sy4Q zX?bub#%zh+q=A(J3Q>{?n!!=+HsMB zkx@s+mzJK;VVPn{B#XeQW{C!rdT98Kk%AgzMnI)?1DSlF_12d7ag#}#a^(&(?SywH4Yrp{NK)6lf6UlE# z7sTq1O-xu32!y&A_S^$#x6%vL@jQ~?X;^p;3Thl)jY~M|#RSA9%`XgWj2MXf}S>&3llv6jDAQt2?#dZk$P6F!UWf z{@Lh!$@y5rY0ZLHVEG~lW6~k35cxour7$n3)aY`X{;o1|(V(!f``zX$0*`D!kv9!b zzG@2!Y~G8V@~gA|+cZBc7MzONY;Bsm4rsKhJzuRtKaYPLCC928_8+PN;V2&3r3k3@ zGhUd#;JSG_w^DVxG*hg~yl<>IqAyR&{e^rb41;y-0QKFX5Rv~@53Dy4B60)bpdRKB|F-}|+-yj-}4qFmO5cavo3{>#N}P?+!> zz1er@MCj(P24hV|1_n1E0|Nm+Qq6>yKeQ{_+1smPL!zN^&ot=bvemm=#mS0!@Y}oc zA!7d7-O@)+z*CV0TEYb2AS*%Ck~rbuQ_c^a3f}VbY6y%cRM|q_r>VO{Gf-9M#>dA) zz6>fc9!+HF^{syy;_Pm(U!1vT7*uFz5wUm=+SOV>Vy)d<7AO=iS=l614a;dt%7ZDM z`t8faV-HK6^?P|#wU{;k=nIpb4f6*k(M^8QqMZ2?N zZ0@_%$C+FI87rP?t`AUD46`JnGE@LPXM&d~jy;xnIo0wvEl%cI$mw~Ys* z72_NA(d+@{%WY(dQ=z_?VGu8mud94M20$bdYhb}2nZ)=JKSt@9+f;~r1aZvTKss?% zi||_KE@CAhNu6A=2~X?ngYlFdjEhLDD4(1@ChnJ z;|V)zWCw8YA+k2t>~3N0aYx%GsJxB0m#4rcg02W>Fc`(r>kB6Sl&FhA7Zy>>xObNo zpBwXkx=rw%s8~5NB&{J13)}o0%v3z*&OgSYYw3{xvkCb>L$)8VBkqpp>}HaO`~?35 Np-Tuyn|dn@`5(ib{v-eZ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/filters_channels_active.png b/Telegram/Resources/icons/filters/filters_channels_active.png new file mode 100644 index 0000000000000000000000000000000000000000..202ce03684416e22aafa54d6ece333bc8dc8ba8d GIT binary patch literal 527 zcmV+q0`UEbP)0=KB6@C)G$9Q+9(t{Rh6GAZk}v1{_-YdaAq2?hYueA(kYpephUB1azAR3KEev%ml0j86m?EPm#iXkXuV#)4Z<#@4>Y-;<7dvHXFhv`(-3z2-;+8`(}zrU z9cxSjS;9N`Xf$#bGqtT&3)Fpv;_LOQ_sG8{tRx22f$55}=No}^Lm_xX!)9K_K+U<5+ zks|BGfe@4RxMH&^ RJ&XVV002ovPDHLkV1k<5>Y4xm literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/filters_channels_active@2x.png b/Telegram/Resources/icons/filters/filters_channels_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..64306644f28e7897620c4b3faefc9ffa319a06ba GIT binary patch literal 1048 zcmV+z1n2vSP)X%%h>Lc&ok_rrn0U-nsF=r41LI@yY&L9Sa5J1G7K@12XfQUJR7!X1L5pxDH zAcO!S<_!94fad4tMJkmN@pv2woi0`zW1pq~QdVPN(7K=EecTt8jdL{C5B?EG)pq#f2A(-};}QABX(y z?d@*>+Su5DdcE!lo>EZ2Eauta7-HMo+wlGUP2u6H6BJOS)lgmr`}q3$5|fjYft{33 zxZJ* zl`;m0Uw*n2nQqUZ>~w_EP{An_3KYKr>*#(2Hq>gU+cPMe15%k2_Mr!0+n;WaS~i}f z)tqyGe=o+ytfw#OqxYdtsuGa5GC`u-BQ*I{F^+O~cQCbw4?rIh{TgD%fNUM5k{uL4#zTx6;Hc;^Amo>1Knw^WfQUJR{x_h{&rihU zXo-z8==Jq=vY`=dFe5_ZEms04xq$|9(;noeJ;*nG+l{|p8* z*4EaH0&}yW>gML=pja##+3HmGvP-BB7m#v*`VCzLYuUp|*8o>n zS3w2u2TFdCg{7q>*Y?D|!Uv=r+27xH1)^6HopuB)7q4)7dfN37^ZorD)c=Qq*lf2k zUP;S_fsSM{DPCS)guX)VG$u1ajE|3ZRvF!^J~lTu#q8{?czAda`Fvi~YBeJpZpv1K zr9pE0>+5ShVI1XZwMw>S(yxuK@yn_kVQSsp-kz`O)zy`+ykEke{%VJZhmNdi7t}&? zYHF&dzDVGd{zys`Lu4KG*soM7AUEGY4J$Q)4=5by*(BYs;bGfvPv;<8XZz8f0U^{G z5CcL8AY#rS280km#GFA42qA!oIfEDwLI4qS1~DLn03zlLVn7H1M9dk40Qv{h)DYS2 SQ>cOf000099RYR{7 z>bQ((m5oUop-`-C5*Zq8hS72Uhkp1x?|u+EjUS6C$}^M?ywL;1Wkcvo70q0_FVTrl7v@BypH4{Gff^DL zQb(ceSu`;-i@;*Bg2l!B`1tt7(a{jGL=yA&1L-%*9a|5nVF=OD#xJQ^5cgZ4FSe?&(6$n*=)8c zf>dV~l?6=Mj||QB^&yVf*epF*adoz(Si_KuO@2(rDA zNZkW-iSo8~%?vt|&Hm=^?=R)^|BQ|tNH9~rJqT+VwFq1Pj%&FnY-w!F+h=axAQ0fH zROw_eD!r?-^GI`Bn__Ua36%$}IN#G3dg4TcJ+jqe+{?1#-;7A)$^nA^=8y^|_5y>-c{E{ zIRWoWvsE%hBGJQ~O2Y|g1CMvgbPW+&V~xS5XPn}IA_^d%{3ExUSs}@CIDL&A_R3_NLL4pEdI-e*WbqAE>gi)sJ+7BJ%qvt7+sa1f(B!eFGQHchALv`ezk_xQQ6`Ye{vZavYuC_E`C z373$-y3DTC=>bLPz*4$jR^QjT?)2tifTKSyE-nI%Mi2A~S;b&Ry4%r#)m5k2g#~%r zZZ#z~XToIX>cE)A2B@#1yd1|#{9@Mmu~QOT=gWMIE}LW6ATlyFORy9Q1xF;_O(l_> zN`*KY=m|o7-56(CEj{_!fI(jm&y)2+Oi1SnfoG~`FGIDLZzg}&GcZ_=O^9qaNR%1! z#1XDkmc#bkD!hAlmA~jhDnktAN>BFjB-7KioIAZDX^eu8@k!bwL|Iwcq*UXR3tfR~ zo7YO%t_Pp(c#-MrehohU1N8RoTmEKwL(}&{)vQrTW}#PUX=(h`TV3|V zrZ7RVaKE+ntG4#`LSAnc8^15Clu}!pQAj3RQRNyOJ~s0C5vAkq^kz~(#)QnWZ&=ul zBwORLog_Pj&lR^naAM--LQf6tsuFsbb9=>NG1)L<+Zg$~|6~2t2fn9oS=I~%1_oAf zjL+WLG7N1E?}vb!G`>A^GYX$v;&QnzLOW&Y+(rygpdH)IU>vGq_T@KBCKccj}>IOghqN=OFC literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters_custom.png b/Telegram/Resources/icons/filters/filters_custom.png similarity index 100% rename from Telegram/Resources/icons/filters_custom.png rename to Telegram/Resources/icons/filters/filters_custom.png diff --git a/Telegram/Resources/icons/filters_custom@2x.png b/Telegram/Resources/icons/filters/filters_custom@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_custom@2x.png rename to Telegram/Resources/icons/filters/filters_custom@2x.png diff --git a/Telegram/Resources/icons/filters_custom@3x.png b/Telegram/Resources/icons/filters/filters_custom@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_custom@3x.png rename to Telegram/Resources/icons/filters/filters_custom@3x.png diff --git a/Telegram/Resources/icons/filters_custom_active.png b/Telegram/Resources/icons/filters/filters_custom_active.png similarity index 100% rename from Telegram/Resources/icons/filters_custom_active.png rename to Telegram/Resources/icons/filters/filters_custom_active.png diff --git a/Telegram/Resources/icons/filters_custom_active@2x.png b/Telegram/Resources/icons/filters/filters_custom_active@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_custom_active@2x.png rename to Telegram/Resources/icons/filters/filters_custom_active@2x.png diff --git a/Telegram/Resources/icons/filters_custom_active@3x.png b/Telegram/Resources/icons/filters/filters_custom_active@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_custom_active@3x.png rename to Telegram/Resources/icons/filters/filters_custom_active@3x.png diff --git a/Telegram/Resources/icons/filters_groups.png b/Telegram/Resources/icons/filters/filters_groups.png similarity index 100% rename from Telegram/Resources/icons/filters_groups.png rename to Telegram/Resources/icons/filters/filters_groups.png diff --git a/Telegram/Resources/icons/filters_groups@2x.png b/Telegram/Resources/icons/filters/filters_groups@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_groups@2x.png rename to Telegram/Resources/icons/filters/filters_groups@2x.png diff --git a/Telegram/Resources/icons/filters_groups@3x.png b/Telegram/Resources/icons/filters/filters_groups@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_groups@3x.png rename to Telegram/Resources/icons/filters/filters_groups@3x.png diff --git a/Telegram/Resources/icons/filters_groups_active.png b/Telegram/Resources/icons/filters/filters_groups_active.png similarity index 100% rename from Telegram/Resources/icons/filters_groups_active.png rename to Telegram/Resources/icons/filters/filters_groups_active.png diff --git a/Telegram/Resources/icons/filters_groups_active@2x.png b/Telegram/Resources/icons/filters/filters_groups_active@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_groups_active@2x.png rename to Telegram/Resources/icons/filters/filters_groups_active@2x.png diff --git a/Telegram/Resources/icons/filters_groups_active@3x.png b/Telegram/Resources/icons/filters/filters_groups_active@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_groups_active@3x.png rename to Telegram/Resources/icons/filters/filters_groups_active@3x.png diff --git a/Telegram/Resources/icons/filters_private.png b/Telegram/Resources/icons/filters/filters_private.png similarity index 100% rename from Telegram/Resources/icons/filters_private.png rename to Telegram/Resources/icons/filters/filters_private.png diff --git a/Telegram/Resources/icons/filters_private@2x.png b/Telegram/Resources/icons/filters/filters_private@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_private@2x.png rename to Telegram/Resources/icons/filters/filters_private@2x.png diff --git a/Telegram/Resources/icons/filters_private@3x.png b/Telegram/Resources/icons/filters/filters_private@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_private@3x.png rename to Telegram/Resources/icons/filters/filters_private@3x.png diff --git a/Telegram/Resources/icons/filters_private_active.png b/Telegram/Resources/icons/filters/filters_private_active.png similarity index 100% rename from Telegram/Resources/icons/filters_private_active.png rename to Telegram/Resources/icons/filters/filters_private_active.png diff --git a/Telegram/Resources/icons/filters_private_active@2x.png b/Telegram/Resources/icons/filters/filters_private_active@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_private_active@2x.png rename to Telegram/Resources/icons/filters/filters_private_active@2x.png diff --git a/Telegram/Resources/icons/filters_private_active@3x.png b/Telegram/Resources/icons/filters/filters_private_active@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_private_active@3x.png rename to Telegram/Resources/icons/filters/filters_private_active@3x.png diff --git a/Telegram/Resources/icons/filters_setup.png b/Telegram/Resources/icons/filters/filters_setup.png similarity index 100% rename from Telegram/Resources/icons/filters_setup.png rename to Telegram/Resources/icons/filters/filters_setup.png diff --git a/Telegram/Resources/icons/filters_setup@2x.png b/Telegram/Resources/icons/filters/filters_setup@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_setup@2x.png rename to Telegram/Resources/icons/filters/filters_setup@2x.png diff --git a/Telegram/Resources/icons/filters_setup@3x.png b/Telegram/Resources/icons/filters/filters_setup@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_setup@3x.png rename to Telegram/Resources/icons/filters/filters_setup@3x.png diff --git a/Telegram/Resources/icons/filters_unmuted.png b/Telegram/Resources/icons/filters/filters_unmuted.png similarity index 100% rename from Telegram/Resources/icons/filters_unmuted.png rename to Telegram/Resources/icons/filters/filters_unmuted.png diff --git a/Telegram/Resources/icons/filters_unmuted@2x.png b/Telegram/Resources/icons/filters/filters_unmuted@2x.png similarity index 100% rename from Telegram/Resources/icons/filters_unmuted@2x.png rename to Telegram/Resources/icons/filters/filters_unmuted@2x.png diff --git a/Telegram/Resources/icons/filters_unmuted@3x.png b/Telegram/Resources/icons/filters/filters_unmuted@3x.png similarity index 100% rename from Telegram/Resources/icons/filters_unmuted@3x.png rename to Telegram/Resources/icons/filters/filters_unmuted@3x.png diff --git a/Telegram/Resources/icons/filters/filters_unmuted_active.png b/Telegram/Resources/icons/filters/filters_unmuted_active.png new file mode 100644 index 0000000000000000000000000000000000000000..39da6995d39e7b048b3ed622f21567245586307d GIT binary patch literal 510 zcmVYNZPOM$9KI$M=7OY{fzzn43mOLM~K^zPNA@>A=`xvCzF< z&xV+s&~P}^hr_}2eLNmz|JrXELj^IXp;~f6yWOtVoO2{e@*P597@FSr_N4+8nO9s8 z1WicE6k4y>RDUwITCK!{#5c9)c`yog=KH=lQA}t&9*c`sflYE9NJcqXgFRs}6u@z? z6>nmN_)_g4IzpU|B85hy5m$$eH>pA15tGS8pU-FVo{WljzWisSg% zPjW*)!NtRC&UU*cdm$nA*ynk!;0w=gi39-u0+j2zB$S<@+wDdcXcD|$uPmHZh^^2v zfK{mFer!2wLYvJ-!Q8%GQ53O_DF8e9kX*yS2UBNRRy|b4hp~4#LSMa^Fi~S;P)c7e z7qdVa!lvNA9*XL`Cg zhEy=VoqgV`%2DL_@%fWfxE8*9!@H%m>BS+Ts22XDT%pTTA9&sANLbw@=UClX)#wua zHGT0r;S$kT_g>0vd=Y-<+}4>p7nktGT(K-ZZ~1v={Bg;>Ruai~EKc}v{4vg4{!EpT zsl}s_K~SNE;gK3ER%)%a!t~Qe`}+GMmtRg?zkYr9;@fXGO`a^wwy)IS#+oe4ZQHm1 zJbRYceDm$w(eFN1Y`OBSvs2C@z+q{SVe;pTacjf=t1Vc&R@ZL!Sv!XrwvBdq+oPpb zawnHq$fU)s7oVH7fd9+Y%x!V9`|qFcKB}~T|9*4fv)jvFL~Dpx%;C=Woy}h@#MbN> zxBj~B-nIJ#qs)Ak2gj|xTA?7YL3mP?!%UyH{^QA;3)kPVlj{9(@#4hT30Ii3rk}2{ zo4=li(V8)7nwP4z!b;|nva+_`;2ZN#KMipXVt(=ALqKQIJ|R}srI$0OPI$uHRC+qW z;D?o1>iX)va_yy47x|n2Z20tZkC8-LqrbnuIs1jrpEuvMn6lO0);5=IL4?jS9wsph zsa~&kmD<&X1qBOo!xt!RFLC%-5hJZ2x^pRWmy6OZhPQ9udY;#nov?A?l(;2V8?=hN z*|%)ooLp*i>G#SLZw*$p%#5EUIp5?`(qFX+hZ&tGrC$>FI@M(*tK4EGXN( zmX~4Lk9Y6*UN`+}eXd|~xUF0u>_S_=yzYDk#SV@OyBK%v+Ld%H>w7r+oYjY0rayMO zI4xOa^2zUa51i`nbU9QsFEq>PSCqVr(21gt*RNmKKAgEVYTDz|tBNI$*Et+}HQV0K zE~@RzhYtsOLqFbUWC>?eXw)*uHk)nr@PO;ac*pt0hxR1-?Avqyy?&li`~S29#Tl1P zp8r`M66M+KQfCmw{+EG0hNXi+kww5<6F2o;`a`Bq{QJ|#nShy)!PC{xWt~$(699Ac BtWE#` literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/filters_unmuted_active@3x.png b/Telegram/Resources/icons/filters/filters_unmuted_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb402100b432765bfe0833a415d0bf750ef03df GIT binary patch literal 1444 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_cat@2x.png b/Telegram/Resources/icons/filters/folders_cat@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..aa08a4b4c730c97a6585b30ef68930e59af810d5 GIT binary patch literal 2450 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 QX8-^I07*qoM6N<$f*50oVE_OC literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_cat@3x.png b/Telegram/Resources/icons/filters/folders_cat@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ef978ea92cf1d1293a70b428e376269ef7eefdcb GIT binary patch literal 3804 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_cat_active@2x.png b/Telegram/Resources/icons/filters/folders_cat_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..012424adef398224a27e53a5b462704cfd20a876 GIT binary patch literal 1819 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 VPbO`Mf-C?4002ovPDHLkV1nU_?Mwgw literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_crown@2x.png b/Telegram/Resources/icons/filters/folders_crown@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a091297e7e5620865cab85f2816c8bf807c4fd46 GIT binary patch literal 2046 zcmVM25xdy+{P_lFOfq+pOxd05&jjvfGAF0x%gLFO z%xnQ;jQB4r0SFWZ+|O?RK_COj;0hT)89)XE4CKs%GJp&S7|59iWdIovFpx73`WFCd z*|H_uwQCo9{`@(6`t&K=x^*jS+O(;`^y(vHtC8BTL4yYL=+Prm%dK0ts7{?aso5o9 zk{&oPV(HSQDmGqw-n@CK1C)9U>esJd;9Akq0+|vfqz=&g_wNITq@x8gB}_;ipkv35 z1rA9^3uLMSLh9>$_wLS(SOBS4=%eRBKyY8}nj zS#tw1V&~4CtXZ>WPOr9Y+p?7_S86!yNje*KG*N@r&1TG);kl%GF=E6BEiYGMMV~r# zsw$d({`^U;Ter62*Xilfr3-!k{@o`ZH&(A+Z6jYlh{D1`I(qb|Pr;^5n{0#|`ykLi zc<{g{A2${*TxjIif#cG!>HYQVm!#v$l`Djk1hsB%}gY)=#jH9({*V3LnduaCT*n> zD-jdlb)P=tKz@+sHrx?a!Ex&yElHR_3OZ@Rn9XfOfZM^z){QC7PwQ1AF z$fXJ(tIJpOV+vZnd^sU4Fv*-X zYnH_S<;xc-=Yc2v`0+#PGi=x}qg?(bgwC8fBk_q1{?;VS)63Oo4-mY>R4OYgiD$&F za9>@!c5Qm{@Wm3VSBTC`}Pl_Sdhb8Qb$uU@@; zI+5|J1sNB;>8Q;9=zN# zITZES15~3%4JY1+T4&CjNpM}YAn5W(w5W)hG-=`+$f{MVq(0&d9zJ|n63Y+qsa?Bv zQhy|U>T(3r*|TSzz$^;j8)1o8)TOz$2M9v=LAt1u=FFMnc)+=H=cM~?0|67WhQPdc z?_T$|QzN+RmLtw<_=ZyAIe-2<@i(!AbE+*{wn#L$fs7x)ns0dBTTZ=x{raj0X#V{9 zl2XhvfWw{&o4;HI%jo0Bk3Q1k#fyyw&)br5bFJ83-16+;5s{QR5Lc;w1Lef#!p zf=*GM`ou-DDAGe*hXV+Lj~qE7+DEzGp(ExXG2^F$-b`X3$dxeQ5C%GZaiRcur%#`D z&UX;75p;1c*V#CNzgWaXG^FnE0L`5{SJdsC7wMG3>kw3ReZtUelF>_-E=e|)l$1o( zA08kzqsE@h#^^90#Od3(abwSGffC}6?rCYz6&@hC`>R*4q#5enyLZ$XN}LY|dSc=4 z!y@a5bRNW&GoFZX;QyAvg9ra@q+bIzCV-NVH%MDf&x`(J^`fKqE2QQfY3LR z`{A)F7UUt=ibR!{6d$%TViJk?gI#_M`OyG^LUl9Pw{PD#h9tr}ZrnI=-UvBY3~(s& z65~qqG7hQnC+_l!1yDZ5p`-zVBSdz=&Ye3;?u?x`cF}QU#^FV*LCkRrVi96e9GRRq zIzZr657_w!0dWnA%s+noco!R|zI^#|WLYjQv6dLn&8KCiE-%$K*c-O|uK-Fq|8c>L z<%T%*6AKQ!-q3| zG#XW8B)*X&M>4)g^muvx!;<1PtWNgED{&2pGtj2W0>m5HOH4 c4>ADsAKbNVe*`$b6aWAK07*qoM6N<$g0NNPC;$Ke literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_crown@3x.png b/Telegram/Resources/icons/filters/folders_crown@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d60919107abdfd61baed837677c37dd965b582 GIT binary patch literal 3190 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 cK`~n+a07*qoM6N<$f^6y=wg3PC literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_crown_active.png b/Telegram/Resources/icons/filters/folders_crown_active.png new file mode 100644 index 0000000000000000000000000000000000000000..f14e27d9aebc5c194dfc22ac2ce0be0f823e0452 GIT binary patch literal 885 zcmV-*1B(2KP)eiYE#l&A7dPE14)55- zt)ruhXsa$#q0mA_5EVr_2rlBNgHpd*YI|?4uL;SMJWszwgyOwDoY=h`%S0f`Mm`4C`&H-kvHM>dSP*K5h|4mY;SKvwOW*VhNDtE*PE#_#U#3YndM4dwLoGzJ0zP0lSA%0?F z0)xRI(t3kl4Pr1bEvn1r=jR0&PDG8av`Gs$EUQ>heZZRboTgL`{>Mm=49DI-?e3@9FtYtHzQ zkV2-dhO$nkxm_$4vw{%3)`uAK`p9OpoD?%FWFswvUqmCjTrOu_B6#h;jBBXTu}%uC zUO*1O_3h4YZ*MR#FaWGZjV^36L<`T(&b}24Aviuhwgp3aTR@~V9UI9z8BWJVet#TL zGn*6@&HyTOZ*MPB6Eocueq&mh*^3gXhgsvuykmwwK0bi;qi?IDd_M13KMa`jk&!_& zDJHvSMx*-SJ{(gxHa6DEsW{rwvx5b7{5;Iy4@YL2A0HoqMS;=LQEQ`405K3?kc|j30Ae7(AR7^60K`CmK{g`j zEdUf27KZx!`_Z32f6(#qG3xH_MnOSA4A-?I!>5LBKYxFJd~k4pg~#URCie01(akR_ zO!NZ>oM>ul5@KVy|NQy0?f~hYgVNH{M4uHJO;ku_g6;raUtf!MiAEC@QkkGTK;z@% zqFtiVM1`C{(ET|d7#OhR9rHzJXQ%Fd5v;Kr{BZ*4l2zvC=c5lFKA_jvS2Q&>g?|40 zDOraz1Y8CnXLL>tom@F6BqRj&^z@+p{e85xwT0T-+mWB2pBnZx(eduxJJi_Nh?bX^ z(aFgP8W|Zu@87@YwoQH30|EjZ7IKS=iU!0hjh`qeLxMf&_keQh&blY-2JUq;_!+gZj)6+#mkwz00 z5+P({WT3;tL$tQGhIV##P<(v62&c4WnvXP^*$NY&+uK_+uicqOvty<-K#u6^>qEi8 z!6vSVhzQi$+Deh&C((SQ(X1^{!>qcx+7gn&i=3Pssw^?WMK3EW6RO6~&(AnKJe*6O zW+yH#4nICV+UbXewzf7N{q#<_ySwADu`xRXU0q!~%9%Wn==b*a?DRuJLqh{oo(7JY z1|PjIFE2JWe*gZBVJ5-MLpjp{0zo%7H(Xp?jK6>Xj=z5Wiu3aFcocK-R8&;hIBtEj zy1L4xgVH&RJjrpmqoV^44i4hl+FB_u0yaQA4#?s0^z_7}wz9^=?m9I!)zV2hHk8UR zlQc2lw4=1N6h%cvQT10=R#NF&5OoCdr z^77L3dXA2cmOPN<<>e?cGLk7r5-BPvDPi&n8H$RE(E9p1dU$w1D=RDL_w@86CSuc0K7? Yl*GIzK?5kW8f87-ii+!h}(S#QfvO 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 W(U+a?n&Y&`jLzVJF|1nKCF*~VbJrRG literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_favorite.png b/Telegram/Resources/icons/filters/folders_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..400fb9612ecd95d78a43406c8d9edf93066ccfd8 GIT binary patch literal 923 zcmV;M17!S(P)~s~?WBbZsZA85{)ybS z>?ha{sEtx9At-1OMT;Pa=z~aIL_!38ocBHl$Hu1dP8!vs1J^lo?{l8>+%xyi9bH7k zs{I=K_iL0?g!~E8J&`Y|P%@b$w68KO>W16Q%nTa_zZtGdY~x};kjv!^J)F&Ee^$`o z;2^)gzVgGvgEaW)YogWM^{%@R45dvSS-pFzR_-PZ)+2p z?kuy}+}xCz>V8tGlqD8@PHbai!xF1Bv86)TDVSw%Z?9FKdxRP3kv zzyU58xRJw#<6~kq8$LNX5rWIhOCA{+u_Ms-BUV*JxWEZ+8!_nrSP=SsJ|E}vc_F&L zzh||O>k)l8h8V=czy;2FIqM2SU|U-oXEGTfRJ(~67Z-Je)p1~-pPwZb9N^N3!MJ^e z%wy?vT8MF8IeC1*01I1LS#cu7z&H~GamLGVYHG>|w!H!aJ3KtJ#h52-@|_97`GiM? z*52N(O#+O8|IFFu__SBslv-L^NNrtGpBrjI^$i$c5rbH#=2+0=zX#_8b_EY2AVQ6GwJs2+sw^xK|U)hD^N@lC&7{f zAT2E|HaC$g$|R6M=7T5b`Sa%yV(bjcLV~#W?Ay1`=EQN`yLazOASu`$bnV(T2QhX5 z>Q+E63XY)7n>W+9Z{KV_99PsqCrFY4od=yheOlz?^YGv12k1^!f8=TDNYUm5`SOx~q<$2M->|98iCM ze}n|&Wr6OhB53E%opStEzP!9VLIU!tpiMv|rT-PXeED)z{P*wQcLa|zS9B93^bEVgai zCg$emB4u(0ZQi_jBXVrAOyXA`*y+8 ze*y4z@VKh?g@uKJUz3nwy)oIwwz_jH&bU=T9*|KW~v`lLpf5Jg&H(nVAu< zUcHK&OkZ90;K75$4&)x6RGyzWRaI4KeM1MjrUWq{7G~UU(ktSw?C=e|hYlTblzSEU>gqh`(vv4o96cQqIDWFeY%qYd=>d~E zAq9BEfLP+d;vaNgULFk%4LL~SuZXow5r@-w0LHD0B@W-tqDAsQYAYR9mXAWNf>&HQqHbV^zN{J9g|a z<_ET9fv*H-$$l`IAA{6=FJA348UVGj1&`&4*49?XfDQu~cv0uI zd;*)WEem`nf?3nXEI}tuoRBfo!-o%DLD$sO5U)!|>AKxO2fA)Sz_u*#?WUJZnej15 z9W2$cqNu1yT)A>Z@Y1KXk;vd<3N{;|i#FJ6lSQ?is&U9D>*`;9V`HObAFm1)uGG)7 z-2)aE7YTdsNXWYh+QNqY+8sG^BuWjwnLyLYoI#urlh=arkF;^)Myjl=q^D1xMg=3^ z;4~|5=R^WSKGx}1*n&;imNymQ8yF0eWR4)-N|EEXPG{h>m`s|iFflPfb#--~0ig~$ zZUURIt)2h@h80UtM@NTSERHg4$DBWZo_Ie?8w_-y>$ZjCFw6zOuwn^PPldS!aQ*sq zO5}5lHfXC8x6^7#5@zldmz{CxhIc&9Vzrwbdf~1sLqFoFSsj5Cky2y}eO*u}@W6TADBsx&UEY-3tQ-UAuVd zeIH(6qgvr7CypOKE_f?O+`D&AjEszE+n7)|aNvO8Ge2T-a#9Qq4#s^zDi#K#1jTan zuhtp=)%aJ5@WLGuaYie1Zf!1REIq%BuK-6raJ6FAwe1jG}U1b f3JKCMpsD^3kDJp7b}WRW00000NkvXXu0mjfg0Oxn literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_favorite@3x.png b/Telegram/Resources/icons/filters/folders_favorite@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..117bd86c5375d7867f3f9dc1ce5f42b2d8390979 GIT binary patch literal 2790 zcmcIm`9Bl>A9qY_ggGa-ksQleNQRI5XpYDgnK_~{_qp(4NV!6$^POprus97m17cxeIc;TW>c~X&e~arB zGv@lcj57fcV0^`xg@u^Q%|LRnuz*UfOpTplS$}!D4I}I%%5ol)E0}=(fLGy3pZ{JukoLJMx(31N0OMe*zyye+`K6O~; zmN>foSdaEtPiwnKW2jU2v1^Kvuni+JqR`hWB7=oBz^@2uW9IAU;ps@wIP;n)P7En-Os_IAN>m%#a*K52NDE0 zb&n+-9SnvJr;zGUJ+29DvoGSVhM3OnA0Hj+@jj@?kr`VVK&*1|Ni{4D*odopv%EG2 zuDjblD#b~{v9518)1P29H$4^VHt@vFDLC1^BV>FKQ+SsfjvC5h6Qef?OsDa3ze$X9|&8)9g4 z$}_#K_n{W~3(#DFAFWy12_c#KF~6Ob5@~Y=UNzRj0);m`Xqa^D+w9frv-GNa zGm|#GUu#!x>FMTMSX=2dc8K!qjzo@^=S^4=3geM{q1AS zx!SAQUj8{Wc_V1?OWUp2>w|Y)w28KTiAP^H9%t%s5!Z!95!hP~bueB(JULpz&6=}1 zF!N>1x($i2MZ*0ku%M4gmV>_x^Msevy4LJI1L>zLtAjt22A|%?QImZN0UGH*g7qR< zC0kurJz*Dpe7Hw5O3IZo7#TE)IKwMm!>%FQF2j1U{~QOYqAHSHfts#!sO%lG!JiM? zvVT;x+_hn>%A_&x(#QaSTAdaVgnLz4W{{8$FNDYcy%V$X5jM^e{yq)DZ=GsO8&8VUV; zb@4*YTSfKP9-Urfqce$=y)ixz!S+=7RYZOwnFZecX5(Wc zH+fL}=%ZO7MiEVsgs(Cn`Fu#Y5mOr#rA;-qrqpUT3Lc^FX86(TM{`;9_XSS9u2V3D z90Nly7hDb+ep6UuWsW{f4h&ZYFNf8CvzYI z%lMgXLGT73g&;z);3ezpIFi+VG1VR@thEp0KQ^6I@#j`axCe>tTL)F8a>1RqZWO&Z+-I_)jVX+Pb07Zdo$!ylc!o6rE{lI~C>&;D`a*o(I zseLhN?VEWZQH-3OfW3JZWU;r0+^Q)7sW5XF@SFzW|7GVqJBg=oK0zenU>@zZR*?3$ z(8tbCI|8TDrS-y1pK)gnn>^IL^z7H+LWG2YSmAgsp+M12*|9FJEhJP=fuO^VBx3#2G*r^BP#|tAM~#} z7ICM2&dw|6Lr$}WHI#!MMNQW`tAuwr2=_ak35k1W#tM+G+rcm$L2<+3z|nRhgW%TU zuSBFAo^uX$UGKj6UQWQYhLmT@pYn3QqG-;MHnh+)VsRyKpWnPO*$Jj(Hj(vZ-em>Q zyzA0*eN7VCynocK6Ed?-!Fnb*jc=1z0h;xY=+$tt}XcMwY1a@m}(KN!XzbanKu8@rKmt~ z@FXpa$anw%$DI>2V^VUF9b4I6pM6-ICr3O0mS_j&3-sI*Rg1qm({#P-$rxU6@Q+8I z|M(-zDn3Z1k=t~p+@>*)Ss1-qfRZN%42xV-v}Ht;f!HxgUh5yyud_;~Fs*SAIIVK; z+#j4>v3jL1p@O|TALK(<fmN`>eDMF#Xp<~o;}fN z!ges}_W!CkDX#9)@+@v#!Zx}9KXP(rQrYjXUcR=v$SS>HllQF^;X#9ep|hO45_Py# z>d^4lIPKNAjgy|(Meb}Na;!Su;gFJWMOf#3=lr0)(%F?~En&+;ar!w@`q5dL zrM}d!9hl|%Nk^%Z{+yR62{^X1!>R_d7QoyN@Of}gxUyK8AxxY8@lN?4 Dof$(m literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_favorite_active.png b/Telegram/Resources/icons/filters/folders_favorite_active.png new file mode 100644 index 0000000000000000000000000000000000000000..e45aa53a4673f3e34b583ae0abe5aaba956d9d06 GIT binary patch literal 690 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 P00000NkvXXu0mjf6HDXP literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_flower.png b/Telegram/Resources/icons/filters/folders_flower.png new file mode 100644 index 0000000000000000000000000000000000000000..3c452c82a820ed17eb5cfe78a5fdf98c63b642c9 GIT binary patch literal 1049 zcmV+!1m^pRP)$-)Xdr7T!j zD}*9trBD{6l$|9zvaylx@8{0%$!Okr&%E=xw_DPE>YO?AJpcdy{O8P>nU`P27;)OS ziG*+CtHuB`CF$N3gi{|1p)X>l%=jZ1U3o<)98#Ng@ zZ@_^Yuc;&l7#d||Wx7|AlatNC!GYP`-8DHmIc9ZrHHy2owr0}O(#+Y}nW?X@H#Idi z%B`xZGRMcqQFvP-)cwxRj$ZW4%uFcOnIHlKhuGfUj>!9Qp7TfnPeBC|6BFh3_EtBr zbLH`he&XP;(a}K5uWvbzZ74G{Q?Kyq>dGpXl9D1lJw4hOTP6^=1X{MbFBjVoU}rz5 zplx$Hdq!(?jD6y|p{b$auLYck8=9DyKvVAT@1?S`GD7&~=0t;HCJ z8*-j36`h)ziV_W5AvgpYF-&-Cu??{=-rnByWoO$XVS9)=FG@6Qe2dB0hEh{gRg9-3 zAtAwgC2@`6z{L<+76TEvz!&4m$%(@?#@QFf5i^dMX>Dya>+9>9kGE@UYg6va%Zu6D z+cW9u=}IjsDhiZ{aOj})TyMOuyS~1bp`jruFE5wo=4L4@ER5okYiepz3|Q}`_sY5R zSdR8v<~|+Ht6v%$8)bBKRF000yqYpNI2dJ!oHyXWMWeR1R!eAGjJP?wq0`e-B^MMF zxIjJD)z!)2;i1+pFE4c^T=9yE3L!^;0qfQ{qD7$X&HKi7L+%` z{`~wb`}_Oa7!%ZNp9!=Gw0$PL=XOKG!^29RpP%=FBgT`HlZx?nAv7rP;fOG`^S*}}p?pg?J9seaA* zqB9PkT#-Q7a7O#}xnftC&9%LU&u7dSF9qJnfQD=Se~SyECWG#Ag2l>qAo8s2)0akwGK z9UUEVcXy}yPft&>yu2)3U0qUKT>SZ02V-*7zz}fY#xUTo#b*e?+}vE5o14=QrOpn_DA|bGu2hEzW2SayQ{0K zd&(GNoVLtj6ey3W&#H3@WCB@Eg-oDKAd3Q)=Ijj01hOb#Y0l1|OdyK_mgejX`acU) zUS7^BDk|9Cy?fc+yLZ{APoJU((u9vzs?>a`@H;$6U

    CW>+728zP{0&1 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 D_Wc-7 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_flower_active.png b/Telegram/Resources/icons/filters/folders_flower_active.png new file mode 100644 index 0000000000000000000000000000000000000000..598554c474d26642711910b1bad8139b7ef8a488 GIT binary patch literal 908 zcmV;719SX|P)X@q$cE7knM^5xE1lhkB*KGIy*b# zz5Rw5a`kZ5FyX}{5(z343VtQZ6>_MlQPh4+S;PW$48~L*+nfdz=ja#Cg|ws z$lTqK1@>D7&Ckyp+PS6X=H{G4IlcB16BGOf^~U9R3Dx(4HOIuE);hiRe1obOpa1^; z&hq)Zh%GNKv*zYzUtV7H^z=yAI$M`=V`C%mxhyO!c=*M|#lSjna3wCoUew#$OP`;g z+Gee-tzHxOI{WzeAaJ+{a@s>=7Q#Uj;1m0XzQxjWRSrK_ICa})Lp@N(q+11sRit+jCqf~ug z5x+2Te67_kBPXZr?QP!pZ6TrX&Ib5npTl?siHjtT;NBu7R@Xmxd!?(XhH zUiX6>YEUabtl$6_Y;A2Vydd@ah5wPAogHdvY4L~#2M6i-`S~9`h#@C$6c}K^Ar0Iz zubK5Lg!4?bT9q7PetmtVQmI7!{r!S4nM~5b!2#Xg-iiSqF*HF9YJpJ=!n(V=!wUlG z$;pYz<@pZ}50uSj14)rXjm}O_PXlqa)zYsJlGp<#J3c-pex?=;@K)kmhM38QhllGI z1X1`Gwps1YY-NH=)*9yE6#^(8kJI(_wXxaE%nZ%W&Khg*`h&|LMCQzbK+x6IMK?D$ z#)hUOu$`TqvY~CZ3xcRvEJk~Kd#1kH-0tqKz&fmq6zV2O5~flqv2poHQ7ZsJIefNK^|QpBq$`vg8>h9*n>iXJQ(m$hdn4H z$b$h7b=ZSE2+H)(Cr!O+(o_J7)e#eD{ zh4lLMYuVg|?GOtw10qQ;L3{S>q3_?n3n!^+fS8CK07?1?%F4>3(a}-WT&3+leE2}X zpqV6n1YNjrK{`2IhQOecptOiHeo~XPv@|v`F~Rck^3)tDT2D?+vVwvF#tB#S^uM{b zm(@du4yBwNq=X9uEV@J+L41JJg-O|uPAu98+PHC}a$>q1=)|Ippz-l>>HIKuoj!fq zxIJ)-po}_MQNiLAQ_HB&Umv6^V^dKd+tr$W43n$IZ&6#m&W@d(V?b;RV(70Od?d`O%un=t*Q~@y%%icqP3D`^- z8XB@-42+=7n>U*=qi1YY$;ruy1CX1WORcS~^yJAC;)_@`Jv}Y7X(1XQQox1>_~4(P z4WICh81`g=#c4|oF0Q?;kKbZF2o7gwXYJVO*|TTFznOaixOwwtoUPvq*m@-d48UTS z`T6;{UgqU1q8BIV(W6K9_{MMak|RfsBus*@7ZX14i3)6BnN)E?siPlYku9h!cco z3qLY4GD0049d!2WS+d{%d`}|v_V&`hfB#%vL5DVI8;cXZ;1j+P!;c(4e%#_C)}KdN zoFEK>u``(;fVXeob_B@x3>mY~)yj4s2te!TbWabLUQ+FM+sn1eKPS`o(8yhr!bR+~fx=Y+x%R ze0N*&2|`j%)2W{XmX?;Nv$Km{rA&L)9OiofMZjOmfu-LSjdc9wkhrD#ABCnScmo6~UhB^78ViKRfw?ztsss zKrB@8FPqYN4Gatz#DE->WrgpbCWVJWCsOqg1d{N%MdU9AvLv|KA@-?Lr<{^hsnbUg zNUE=|C%ic)Td*#K*n+o@pZ0nQ0zm9ne)Hx{(l|n_y?giiNmizfeu6*}mR_%4zn<`A zRFy%zy?1qW8LFH7W zn*x?wySuxosHiANLM*-w>akOn-kOi>X z?LvY)u24u&NRS5u9_p|Mg#>vp;GqtCkOx8k0eAJRY9_lMM*si-07*qoM6N<$g74gM Aq5uE@ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_flower_active@3x.png b/Telegram/Resources/icons/filters/folders_flower_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb2169eb1b5330a48908888d9a62de0c83c2e1b GIT binary patch literal 2523 zcmV<12_*K3P)I+zee%z~J}grFkoMFhcsAb1f`5D^TZ z9u&cddJzNzDu@bZ@L)s`#Q=ISi(<~9|N7>CyX@Q9c{?*(T|K=_!Mxqq(^K_TecdxX z(>=8nN-0fSDvm^;(h-~~%&G{aggMp}Qo>Tg9E*TsaMK=^66RP09D|$ou#_;zBH$R@ zw1=gHITiuO;HEt+CCsr1I0iTEVJTsbMZht*X%BOZu-cB{EeN+kg9d8cxN+Xrv13Qo zwryLl-piLS)zha>)wy%$yzTqEKPHyemwbQ$H?b2Vre$~YUbx;?_xaw~e zZa2wVzI=HoqVkCZaX8jt(%d4fVZ(;{(4j;5JVgEjEGH2u>+yo71AoIfYL{Dq@toi_36_`VTewn zIz^`~HGlqmHE`fSBOq_yys7r=*`xmc{i|NQc;Vd&+`D&A5z$8Mm8vu`t!vPrL3;P@ z-J0&7iggO(`+)&ifQbT}q|H8RUu?_txYPj{Xy1QrbrzDSEWVbi8fllIY=JsZA=pAtWP z`lOmPX<{shl8Qfn{;ZldZ7OL}iS{rMRW4+KCHT^~apUqOtaa&!6hz#fu70+97!2PdB-$UcGuI%y~LZ`&0694(f6We36cyIAL^M;f$G{J9lpAprJ0Oz?ZTo3=W<=d6F$|^r$Oz z&`_64;DZISU{mz$MI_tvy*nk9Dhh$wX6P?tl19X{BIo#>4-S_ij8xwnf_mm4RS zJ}ZRL#ah*`U!TQ?v17-E))S>}O-g|sKG+h`uFXB@usKS!G3XHQVKh=>ZJ2apthR35 zs<4(ZNU#reP?uA9BF*EIPeHk8u>9z16O5<`Jg!v&27t z{wR6_M$sdxu$`midZL8k%}6YTE+@fKa=hs&C8F0bw8nB1X`e(1d-LW^x$&i5zkV(1 zlPF=BQpZ*<0$;v-`6B9$C}Hr7MpmK@nlxs^7jeRl9Xn<+hN8M{_#!$=L^RWdq@}ef zV?7K*`1bAF`_N&f?2a8feC4hcz=uMselg59y^@OkNk`wleKn1q^okWL3hJh{>6%_7 z;F#&KqT}t`w;DcBBlB(W`Ocj?f%^*`6S2&1G@^oPpgVQyq~Q?c)vH&5)A!~0x)GKk zz-P$i%a=_p(BUPF@;Z5dGk?~sSyPSB+{RFDFZ99JTv@hkS)lK^aftYxk1)KzoiB8s zqUm}T2)tZgnT3z=HCOPOZKFnw0)3B2R7MXUVe8hd%f*}he&NCeO%HrC27RBdzmyK& z;A0`77bblE#w04EhmSC%NeB`eRb>nYe!80WVdu`BL2m1{Yu5()8;3a7d5)U$5iVM^ zNIiV`P*JKN!dX~u+_<5zVZxDSbDc=6vUKTEwQ%9W!VXcXi5C~yTEODPivwF)X2<5u zn`0&9N?Ph7Bo%`%#bn8nC2a8}{-KNSlsIP0m}2<7{b$ac(aoDTkL*ZNl7j&|d zMvaQ>XAw^L2y4}v7q8xf2M_ejnKL!L5)`T>A}OVGf=gfnMqrI1_wU~iQ*ppTcZZQHg*@hMxE z=9cu4BS-X{IdgQEE?so76}=`?$ig--01GeyTO6U4TFEDD%9JT_eDfVc3taU0@#AZB z7zvlTlQhF7Y;(xu$&+iuAE&dhPp@DYi$y6MS#Cal{HTTw9jfS5gfl^)aHT=-*|Vp* zdiAPm)~s2et*|@jDTkMi{l||VVGjvikG&PtbR!wHb z_U+a5=i4jc|LVX?P?QBYWXKRMzdb#)mQl}|H7l#117ofZ=!OkWF_;*WpD+Xi(+k{! zi#VNX*@%NK=;W3wSFW&O%xzdz-^~y8JUb%8*U=YrpJ*!d2v`~gO-LGH2Kpad&nj;JV_wV0tI!;;rK>^8D zF#Kip>eZ&nf_}4v;lfUOU^t)gvGb#J_?W-|uQc$lip>hMP(CCnd_M3TxRfw|OcGs^5|(Is{NPf;{4q&%NlI9v>G6Y03G>G!(IqKiiKfR7E+xz#lSG%K lge96DKe&`Ie@qfx@*g)O=rz21!lVEI002ovPDHLkV1g$1-Pr&D literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_game.png b/Telegram/Resources/icons/filters/folders_game.png new file mode 100644 index 0000000000000000000000000000000000000000..69119b7d9331ae1ffab17b067d5b18629e2587ce GIT binary patch literal 782 zcmV+p1M&QcP)Rc>e+g)WVZ}1#=p+0>-=bpZ|Y|A5rAUQ5Zycfe{N90P# zts~b8T`S~P0XO?eXXxSKLA<}ei}83Yw%e^BqLT)E5rbG@oGSP*8b`TYrp;y}IsBUd zqb#wG<=WZIWL{ogn8kX%rdq8=!C;WQUhgTO>;qrKAQl)9V7idf6S}>l47*^p`v409Y`aZ88!P1Z`=yT% zv)PPSR7GO(9uxMuO1-_kiRE%Bu-ZjoDQvr4Jv-Rm+}z+r===MdJRT3*WpdV8r)IP1 zh?SswoopMSQdpq}7BC^mVfD4_{{kqb{D^VTXf$#Ju|ZDC_t>gyA21=vp^MeFOqEoX zSO)H;-EM1JwRf;=bnR1^Du>0Lw}0#kJO=^+R!^r>hxz&W*`YHj3Wb8eu!_P|IjlD9 z&#sUi;M`}o3SAtbvs;FWR4NsbN~Mk#2mkQ!vx{5H;O{u?^ZA^A3A+EBQmMqYQ>J3E z=)eXh1UYoMwS*><2?s?Yk%ORoKF|7}pC8I(G9(YCcEAQ7NG_K+9=4Zz1Qb zP`zH~puu23p-_l?J|ABppp=&`lI=2q?e*z&ntc&dtyUQ$91hcPIE0Fj!$QKkcM`h4 zzo*a7PY$$BxYOY64L}ZkHk;L6?&K|!ki&rEBm~F1yE|$$8cYoR?sPiz`1oi7G3uY5 zo~YaH@|nR)%8w;u8_1AnXOPQfi1s`5KdD^_xmEvKp=*WQD&S`S1%51=mt<8 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_game@2x.png b/Telegram/Resources/icons/filters/folders_game@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..09fd05fe430c88805bca625f6064e54dd796bccf GIT binary patch literal 1705 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_game_active@3x.png b/Telegram/Resources/icons/filters/folders_game_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5949304685afc274434997f76da29a71c6236985 GIT binary patch literal 1994 zcmchY={ppP7sp5Tora8^WC?eSkR{t>DKbM{%h+PjSVpF9jcnPQ63sNZHFhFP8Ot!2 z5jSRrL6#wj>{~a>5Q?&2_h0zE_`Nvi`#k6KeV+5;d!FZ{xxg=p3dsop002>k%eL2! zHT=I4;5+UFlt+$6^vO?b<%%iYu9rnROW5b*~j{Q_ZGXB zXm|Si`;9LIbL0`*CiT<3-_F%vxYNIu!vf6*lq@_tv4o#Fla(N?!c}6BJpQuKt6KFpsYY46F!pt|70;mA#ku364*CDQBjdxU!UpQ9Qj0A zO3Jo@!wJu=#g9)-PTm+FA1B_7j?RX`V3c5X0p;6ttFm(7Jom(9x#h@}Prq4OSo9I_ zcp6#|!W}sy(GZa^F3cwH?#3MM@7x(Ne>TSpZaW(^&(J5=)DV;v6fSo+djawiy&sij z8*w*M0$Cfuyz=K1h#Mm+PqvM-24x~7IkTxR5f^^8!SXfBFXOQjkfE_4G&EL>RP zc}dOCp{;Ym$z^ zOWtLHp5ES|LaPLnr>7@)B?EVG?9V4va>Wd58~$LCRbEw)zO2ym`<$;N2rvP6!OcLs^>~lEcQirMg?s$ zqntw0%_(aG*Y~!6{w!?cjz$Q^D}{!JnhxS5)SnR!GQ~qqVAUc_p3`Ltiywxqcp(r` z$qEa|(oTYMVTF~ulUAowk>DnYVS-EBk`ZDfHUg|lT*2B7+k4V~25Jsyyh!g4rlJv^ zp4n~0-O$^&^;Nrqz6hqMKgb#@l8xM2b`zZV_#UjM=PaP*8O`u_Ksw5m328^3*NVsc z55gEbGzEN$Y|y~xKo1WRIqh_4$6B?z4u>oVc#8ZqYd zW*hkA%@zNOTfHgw55G#;>mtz*sWycg(0yjLPt)9`lmt8l>$uOKx*!fLhYe`m5J6)w zBo|UJa~0|D^_0k`FM(ceFa@Tg|6?mlOT-H$h|_ANh$o+BHBOBb5i@cfE#I4o#ExkT zAYk36*xm2{ZmR^xwk#)_OEGKPx~y^eh2a}{l?l}J>dgv2Kh7u6e zRFd>Ihlo^_#|%R<(nGppJTY(&139Fs_hmKRAUZa;J`7|Y2X}yR`SwxaXdtcp~b}z z6LB^=t${#cuAeq^sS$4@WEfB=$y zO}8lU52poy8Lki=?eM@rwdt|34|6=?-{~Jzy)+q0oe;nY;=jKh9_;y$^W%v7>$BVm>oZ)=&dvnh=hq5nm%7*1 zBBo13!Q%J6O&KwuD+OZ6r-t;Q!?{+?+Ss3Kl$;zn4Y;tbl~rLHS_(LXJ#S#ZEU>)) zzP)F6a9UlRx71T*Dug*UHa5CC-k2SQ)7?DJO}k{%8zQUQGMk=CBuZ}2bqH(3RHD#R zvn-MC5%@o_(gV9;j*Yh;dw3*@{65kf#v_Yzi{D~(L$G~h`=?0vFCLh@|2H@Si1d2q VbRPPDJATdp2RpcJ^+o^We*w$5#vlLy literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_home.png b/Telegram/Resources/icons/filters/folders_home.png new file mode 100644 index 0000000000000000000000000000000000000000..59ef27c921ee4e2934518d2217735c4c2ba913b3 GIT binary patch literal 463 zcmV;=0WkiFP)wa-Wi@~W zfN}$(%7CRP3fV9Y0CgRR!_YASECAFsAQMbLD0?7rljM=03aAD_@LsJHV)**W+z!YV zhLU+eodaEk@q{`Ca)t4LG6VU-I6~=xLcusd?SaxXwYp7|%(hn_bmTC^?SY!6kuSKa zs!T!7{<^NEHgZ@4m1QY|_=iEjB)s{#4(7;V(A!riNs^^)+hrWb4=sa$NqFN*0?WT9ilUv5R{8?jP0LBYveuJqg$gm>i_dEEPcBW z{cl&u<{98sx*B6up}y~Bo_U@lJj80_0O}gXh=Gv?->;i%ud3L%`SF_Xx=!f38DbB9 zDz-exX?5*&-{M<7t0VhXa-Hr2q^pYRdn%+>LpQ!%h2CCL;~cKqqU8Vp002ovPDHLk FV1f~4(#Ze- literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_home@2x.png b/Telegram/Resources/icons/filters/folders_home@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4ec5954c756fd4e1579d67c0203f6b8ff16fda5d GIT binary patch literal 866 zcmV-o1D*VdP)*eo$#5b=3In9Vi3rI*kPK%u8Zp>#3;cHT3si)YJ-H%7@&}}Y6CsixAQPO( zklX{6!HEdTEzl=8ks!GPvVju;k{ckKIDwJu1G&Hnh-4SYB~BnDdq6&L0wCD|@`>Xa z>2shEI3AHc0}6@b38_3#3^*Q;$^pfM;~2>tC`KHINM=AW<2XXn2Wqui>~gu_ANdWU52(}Wd=*YM@pnk@F_HCa z?Dc?#!(k3+KA)Q;2|9K;vtV2 zkZk;KIscDj}n1 zz})q^PceL5?j}4admr8Fa6TN<|0rS+6cZ2v!65-*5fl>;0>L2xVG$G)5CXv=0bvmo z6A%Kyp*0}<2VbpL!?~QrI2W$vlf>45*6Veyj{E(d{$2#d7HzqU-ULP0W_^%LY`7M& z&-BiyF2;4N!}2-5TTc05oot7B(J_*=n_7 zkH^EC6y$=OklQj2v-WMC1OCZo?K9|4>hMVskm8jkAOcbdD7t9{5s*Sa(M>CefD{6X sZdySEq!3Va(+VOWg@B@)R*(Yd2i}_SIE{R9egFUf07*qoM6N<$g8b%urT_o{ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_home@3x.png b/Telegram/Resources/icons/filters/folders_home@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..52e14773227ce149ca620d09ca6af7aae06908af GIT binary patch literal 1238 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p{OJmUKs7M+SzC{oH>NS%KVMPZ!6K z3dXm0?ESP21=v3DRylD@Sg_z>zrb~eEe9lbFo-{3nIdvwvbBTfVcrD`TKa^zTo{?G zK74j5oV>>T-i^m!%_Y4r+L`U%{P?}N`;(0qR@)Z<3jIo6F@e`{zVX*TZbH zTT}&j#dSoyC8X3MqIDcIDiSUyaXKDm;y!3)Yis%?L+ZeN+eZzpNjv#ZJ6$w5a4U(^ zOoTgPF`y8~VanVesxqt+zh(%5?o;qGb#^U z*udrXnBDoPmPoHSkL+p(6FURVh}MN48$1tdap>C42wCHF;Zl?5aV-X2*&AyTN_(cU zx$k_?W`SHlg`D*#jUHWt6R2Cc+QcDO@Di2 z_wL;qI@80i2-#X$t&0A+_S;RjMO7zC>sEVSm8q$yn2|KEF!APgbI54NqG$+xNR2E?e@PPiz)+d9fflndwWc`m(g!#f$CQZ+35$ zDm%3G`0*OP&DyJ@;^Wt^wcd4kpKqwNwDjWL&HJCdu>bMsF!RHMHHyEQIw}i)tP>5s ze0_O-{T24~_`tj8r#CcxOg$YGc`zdBsrIUbnwy(K!VIQG{Or<_h~K6&RkTMnY~LcT zW7BoLQ(2Qcr>7eJi@X~9G|CKL{&4I_X$0yRw|I!!Sdw+E*n2X^|HDnOLy#UT}k{LM<~kn>y(R>_88FkC^54EBNsCs_9oM@VZ8%OR~~9GwD0 exl|nbIftG!Oe5@r+IR5)0000!lvNA9*MS8k8 zhEy=Vz3JQ6Y_RC;=JwATTi|gDOy$R!R~(5d4Z5q7x$GCzvjztgq^zBue42;2$E4~nJf@g zp%6UBWx^q*n~zi)MEF){`=u-~aHuU*Qu@hW)GEw*Qh!4B91f3v4K)v0+&(cskyclj zRJ|a;qH)34X zlx{LVZF(CxQj zp1NWlX%45KetI8ubKmsSEt(TPZvJ^PWzln)V;fc;m3UEL!Q<>6zVZJ1wONK)+do#U z@mn6etv<~6(euxdp1M&dN{sCS)`n?+t+1K3b<=J8`R7-$amzTGuun=}etBio`s=T% z_NFnH?2K78$NEoqcq*?-z}~iNufF`?qT32kixA4o?9Y1T< zeVMs=AM2v*zgFwtwoGb%()4U=N4@`rq(xdA@8&I!TR*)pHlyxxXNqs+ZNt9Zza4j7 zaO6wBzQFO-i$xqP=X4r5SXdlp$YP0as8bkTi0g_5FLVs+p9@TM44$rjF6*2UngCvx B9QyzO literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_home_active@3x.png b/Telegram/Resources/icons/filters/folders_home_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6d148e1157c74cc1d00dc3929b55c2736c016242 GIT binary patch literal 1090 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 FV1ib|F17#w literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_love@3x.png b/Telegram/Resources/icons/filters/folders_love@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..41b44363e978010537beb3a9939f68c5a54a5cf2 GIT binary patch literal 2600 zcmbtWX*3jU8y?132HBZLS&Pcj*ozklF=aQ#&X^3UA(VZIv2Rht*rSqt8T5LsGZ?Qd z32#P4Cd(j$>_+IF@0{=V_vicZ-1j-pxz2r_`#INjoh!x4!h{zL1p@#8Ubv}|4YOna zb2!UqGC+i~th<_2is(W{BZ3jwWLImK%4Cd3 zUwe}W&{%*wHAn%MDm86wpvWzNb+CH;>Lto``UZkN4H5%uHXan{d;3dt{{^p@d8nFw zupBqsS(wk*E`Ib}C%?tM7qT$FFe1o)TPbBSfsw}|kH;93xM*z6=`6#&K^h}mUT1;1 z32XQ9LL;i|irkb>FeZY>LA1Wc!vZ!8X;gHv?<+#(s-PU(>JzV%6z>uV{;s@_aE=2p znD6b-{|>4v!gOA|c(FF(U<2EqEKnQuYrhtrE%)thz`_81tW=+V^6P-k*jc9UuYaW< z?XA)KQ-tW>x*}dn%g&MFIGXESI|xtD>x45XRklela&rk{SKUGxvh0fyi-Q8ejBU(7 z*t?zFtd4QZJZ0CzcfMcSq>NCR-kFm6v@}tr`I|HSDhqChF%4~rcmW+4p0@rln$~}R zvVwQ6Bjl=BtGu!$i1Ot3X>o&7YbvGIQB5_VP4wXSaO)M(eTEGM5Jjmfx*XwBO2xV( z8G(Z3xPHB`?f6UyUF{rom$(O2yz@YD7*tT%m2{s{KguVmXEtgZtG^ZbeNe#^I^nPE z3%uocN($xdek<=#gRDC&)(M%QRge6Bb0;tK}psA!X#_Xn8u&8}Q2Cr_W^Pt|s5tv=(pR_y1A2k@to`XwpL2~%Pg z<;K_jsW&#;ZQojiDfKiK8ZE@F&-IjY1$>@-YuPpS*0L1hRO9nkIp})eqNyf|2td|3 zY1DOeglrN@bR!7N@4DZhKznUt{#||i`f*9p2~X7Naejl#U(YFZ&RT&Iv+Y3(Oz@dH z7<2de!}zn2ByU7fvWSXoa4}0n>R5|s`O1xX=~}fdJ+kp?h8Y6Zpci%Yvry~d55mWi zzOJ7FyW)E~Zrm{j-c+gT9z10noaj|w|GCp|R@<%0=1n)b-Ki5qjxxGEaj(}9K!@e@U5at9AjhNu&qF4uZfXO zCLY9?f8saLLfZ7C{_$hPFF zj~1nR>%7^*C1sXjCr29MnpjB04lUz=#AJX61k8%euWEbdDxP~wQ(~&#Rp*}D3JG?P z$tcge1L<3Y{y91GPFS8Big5>^Ke%eU2c2Y|0eY#?9CglgS;q6zq}7N)5zr7k|XjU5ljIIn|D z5ihc$U5p&(Vs}T6$Eb&n3q0_2NrhVC_0wBf3P*^>(BYvWpdzviYfR&A{C zp7}S|E}K6eVr+cw0W=*eRxEfQoZ(uuu*KJZk*vn?` zk>mnD-km!$>1Wk)Vgh6`5(J~aktHbgt~D7^w2}NPfs>|MvJg|}ci4QfJIZ?} z4#c-=LX#hX_z|we^bq=VS>I;eARik(KiQ)O9lSAgU zaY5KdSU8)bv%ISTvc8S{TSELu>ec`6pBw%!w~X zSsV8aSo*XZE3qTIKh126Z!NdC}@Dq8%Z2KAV+bK#DE)3ZDWmDP?H z663_ddPCrY-eVKU6a-In{JFv@*PfsUXMC@YdqK&zWdLm9PtPcuxzwY zE3-vQPdgxcH+|7cy@Ppmn8eyHMmJ146R;~u%Q~J`dA6=`ad2>k>CpXhW_Y4@$i~Re zhI+r*_N5abi;7})7#6{TxJ0Tnecvoe$gtbyA;^%K0mjT+{{Krq|070iJn3Hlg+QEZ R|H6!Z0JyP*QT1r;P)hyG`WI-L$pr&Btg&vd`v z>2kTyY&N4_uSY=;y#F-j@L>%xh(!)^q2X$H4=G1Fo#w${z(SaHwpy*Y*=#-r<~oB0 zw4kZ0_w=h_um}McJ06dW;bPDX1teN*?2JosRSY@>Z3C3LL*lus^kVTrTL*+4iNz1Y zX3XdFAO1KT*zkoyK`EsL!U>U2kTN@@Q?!{NO)`W$4+jLOOeVvl(dZ2|9*;Sj z&C255bM4h33OZZ2+kJeh;A1|j#R3!+LeAIT_Hxi~&J!S8x04S2xeomR`A5Ak9W{j| P00000NkvXXu0mjfCS>|i literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_love_active@2x.png b/Telegram/Resources/icons/filters/folders_love_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..55997b62d436eeee88d49257ee6e07dc55fdfa29 GIT binary patch literal 1162 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvI4n|o-U3d z6^w6Z?esEn6gXZT&TC^J;Hd2A)59X~;AjwJX!wDXhsVU!z`#V%#YU-?$uO{n*-@C; z_#=a}E3b&Z&8=Vh-@f;rx)fb|=8Tgm&t~hpv0vBUzw+hF7avE>rJ0k1+8j2XDYX3K z>%b5hqh-fY;p0k;}za&!0V3mX?9vzkh%A_it=%UEQko zcJ|W3!i8mJWmg_NSfC}sC6@Vr^+mDT3*u%8+-8Z=b`QAi9$LE{ENcIL?e^`}{r&x= zOP~Jy8L6+Q_v+TITbuR;wwq-{XEw5Ba0*`D&TDURG$}AYFE7h#@zi4<7T>*lcdne2 z)GuLyzJ=$OI&hmfMl%GwYnU~A_Scs$S93(d_2$k=Kxw zmDO~>lWohEElW1G>NK#dm78#fAvZU-wMTiCZicU@0NX)<=?)=UoR2TOGA#IP+)=#U z>&2Nb>L%7{g8Tw!O^tZ39Jkz3thh1#lCZGwlf_outINbfXKnp8=ZMd5C+oRU*N&ZA z@a@~TS3dGVZx_k(yC_5z9{kLIR$D7?{_-|?dHF86_=rh2Bwa3-PiwX@ExUNG@7v3l zn*N5Hx9rGBXWq4UZ))eM;AHW)IS$XCKdO(JE_frn?#^!%KC+ty^)Awx&Y${}eJV znLTUPm8VaaX659Vv=?oxuc^_o-TPQ|v47#dBR#Rb>@nbCB`igF}a$RxuY| zlDzl0-R5B+!@(DyJ{1L(YwZsY4^KYOc(!1A#HFxV6O;~hvU6+*3kzG6e*V_2s5NH{ z%O0mpIH}g-DPfa5#Ur_oG5K2|Bcq7#_hu)i4>xZ{3Ol^1t#N2@$v16wV48FC>7-2) z)~#P}x;Cbb=i1S*vId5g3l}=d%gOy~EGa2jlDYPz>VgHed&JLZoKQ%usED{IyXp(8 zUG3vF+qQ3SzVX#`lC;CV#v`xZ?_DI}9TE42OX2iP0SBfNP6vW?B~==9SahyVa>U1t ZcVaD{`sDf=W?{UMB47Xj literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_love_active@3x.png b/Telegram/Resources/icons/filters/folders_love_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b1477710a14d54987c2870c3d553fb0aaa00aa5c GIT binary patch literal 1799 zcmbVNX*3&%8ciBoEo1CzFVE6qtPRaeOB6*gZ55S4l17Oj_ERJ+v5r#9R9i^~#WWdf zV#~Exh7?SxH_;9smF+ zd3(8ElVI$>MovbuOCy8FB_I_A^Me5ZZKVo_XlVdIrr+Bgc0CQaUiBKT2Uj14W7N;# zzPG4TRnhRs)|KWpl-)0@QsA*|35jais>i^@)uE?8Wf6}vIkJ1MwnY|ZgBEzA5!Q=n zYUj<{U!wKZIV47IG*BQ8^e5|N7G2P^8BFhK?4g!fYuSGEr3>7)+Bq=T8?;8Y_R)h{=w`Ue*si5YwTznK&Fy zBowxTt(}8v@N1<|8jY5M$K$7l(MV!`BXLSPHA!*eB2y$vxnO4pyE&Dbw9tR&OW{Oqy{iWbRLZf-w*Nn`}1HJTIGQQPKVH=$lU7vcHN{?pNnv4w( zdv+QY;#`E5`ZmaZ-}D;^21=UYSIn=WtgWpZ+uF(zCchspN<(Y?BVNwU&dy$UN#*_Q z@(tz`DneoQyyAstn+pR?#l?yOnHP|O8kfw*=4PvQ1C!a)Z7nT+sJv(dma~BwSE95 zOAU1kNa2nib)$K1g&$QOf+9G!^JVJuK%e-Ltg7nj{9w!PG@d#|aKKJZ(tc917Ja8d ztoC+QSZgdzl&|=1b%v4W`)_)6dV!s6?ill^@j$0`V-1Yyen2KS+&$X&sH?IXG8*x1 z{+r4}UouDS>YzJ2>P2!i(G}blIE_V_l5C|V~t+qgC0`w|wdIAkocvh*1T=N%)n`GpfnwrXEY^bAi zr|rJJFAu$Y=-e;PyhwU_GC!Lx8XTSM1f?L+0 zbMg3A1<;=hP@wFJ#TM^gRuyck(XVENm>Epn!e?YGsMsmNsmt-9f_{YTOUALoWa8lC zhr9Ze%gn`)4fh1yNWj?dS%G#V;QBg75?+ll@EEel##;a)Vh+gZF zlniKNzOPB*{8hy{fuIpQIkjb7*Sh+rmi7}edp{#1V-DgNI&v;R)Jo;^2?HeQ0!lC= zJS0cFG_D(Gu=L5~#{^0m0MuPxt|FAZP+C%w@9<(dj+g~RWM;aYbc8AqP$*86ZF%RP zCuFc;VPVG#I;b6y$ld<=?)srbMIiKmos=;b8RV5WlzmHAEtfC>wh2#1ccb9JhHnKFQ(RGBVgb7->L>IWs-&*m~8>NGG;p z)bIwNuA?IW1BhJ;#fJ8Vg@#V?cs#H#{6_UjaxO;kkvbi8lKMu4K3{Px$@OU|ssA_K d|7$;d0e!-R-|BBjn2|Iaz}v&$z10ns{TCH5S}p(p literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_mask.png b/Telegram/Resources/icons/filters/folders_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..86dc6c336a072464c37e1600720764f2b141ff27 GIT binary patch literal 1107 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 tcuZm@r4UB7?J$P_wd4Qq&4YK_v+ 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 O0Sx~!)vbrQB>o33N=`BW literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_mask_active.png b/Telegram/Resources/icons/filters/folders_mask_active.png new file mode 100644 index 0000000000000000000000000000000000000000..837922a11af5e076a8a1f9dbf29368af5c7bfcb8 GIT binary patch literal 849 zcmV-X1FrmuP)j+Fli962$}sOmO_?7W(t_iTYKogQ>a^yaaUItL?RKGnwkQy*9$#8J@EAO z1f^05lF1~b(`oqp{L~AT#zsa)AQTG0(9jSJ3=F`_%L^2XMcCiphqt#k39IhVw@f@N z7|+kov6($QJmA*W7WVb^i8!8vEe01B7BHXB8>V=Ee#Y(XZS3vs{VSe`i9v_Mf#q`9 zFp+lR@$nI7W@beE`1m+hDis0y{{9xe+Tr7=R0?SUY7P>im6a8BTuc7f*B5SXZsP3h zEWWfWFh4)9RGbz^m8Uf! zSu7UQRh*T+z_4JPo14?csR!5A)})yJ{(kke@Q;p;Bpi_w18fxnsCL5Pu)uKq<>jRs zBKcH(9LHO~-!EZ!CmM}vu|I_9T)4l#=UCBJt5uwsm=J+ZrxTgfVqWWfe0-qC;}Q7b z;bFYGx{_d23|d@+{0|`-*x1-m0=3mc{j;+(JUBSOySqCnxn74Bc6fM*Z2uu04SE)j z#|3GwBx_|tY=h#-$%$TkN5_b;%jGgBL$NFQ&W)2O8*R$;~3rbeeR-ED%wAdHQTDfgp7 zp#VEOJBm-ibZ??N6wQ;dY6MmwaC>{J^jrC9nNTaqI)RZsuANBuuQ@D2W(rygSqhmc bU^f2;w`7W!pbF8E00000NkvXXu0mjf%_x^~ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_mask_active@2x.png b/Telegram/Resources/icons/filters/folders_mask_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9d7918a6ab8bb4bc39ee76028119a49b3e3dfdc3 GIT binary patch literal 1591 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 GmGmDgvBd=d literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_party.png b/Telegram/Resources/icons/filters/folders_party.png new file mode 100644 index 0000000000000000000000000000000000000000..144a8600d7d7c71853dc650d2996972103d18bbe GIT binary patch literal 505 zcmVB!n39ER3>?!o*3?vXVzKwmg6S|BQ)A7zBaj<7*=N8Yd4U-jH@8zC*r4S_ibe z`WxD8Hi3w=GT_oeH8dKHG>*#os$AUEr6Z1GIh{^-dX*$es*oBCSZb(+!}2M!ZU}NEAgcLhO+vz*6I225hwsS=?YS zkmK=a1Xruoix_(Z7_hd^VRkbF@nkZQ%jIH(XS3N;lr=#PjDv|H*4+?7^ZDF}pU-C* zkH==PCdkz^LtQ;X)UDTRW8iQ&2!mj+31=fiolZx#+pRIe2W$5J%n-u;e*bBRbwe`} zJ_z!ue&mi!aem*_CwWw=f3A%c90|N3tzo`Ht#+vFb=HdTwEZ4cUR!NF^T&_OUI|6f vck5Q}p?3nVxsP|q8*=S{hWAJI?>ck`>*S13*ZixR00000NkvXXu0mjflCs@% literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_party@2x.png b/Telegram/Resources/icons/filters/folders_party@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4d9921867d3616067c41ef09b542344bf15b03 GIT binary patch literal 897 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*TX?!S zhEy=Vo#nXB&{5!c`-a4mLWK;4Or2Y&9+6eyQxr^Bxa0VUL&#i!U4Yvx>EtP=+Rycs zljha!k-B_$@%`zmQj*+C!~gy^Hx)Kj7G_~=oqdCw>8AMo)7S5_um}WmF?lc;vPf(X znZS_5VUV{{f$@ky!kr*TrVhmeJC-)F2sk$sdb4sWv@txM##O|-;_^$D_QQ$_O6$!s zn#_Vvw=nj|&2aQs8f19&VamUc6+El1t_sXacKH6=_C$)nF?;9G^v!AIf}3wDPX1W& zMt)|#>$WWqg}nE)UUfeG)W{|>P@1p3GkM=?_MIioY8ln$*Cu~F`Ov0GwA=Mo-gd#> zhfCe>=3Q?)5TC5TUbK=;_rjM8=|3|0IGA32wVHcAJybnbPdv*+YFo>{j^-OD7D|~o zbSsLK?vr&8xRSU1W!dhPRhipw2d)eW5{|m*?sFuJcjv-`N-{?_v2$!>Yi?ZiGrN9k zRG{AU&1y0m1%l0=Y}+gMxft zUU~n$SG-YTm!f!47Wd}T9C?A3X{S?fz5hOO>jSxqSzCp;kH`F#YnM6kl!NclF21D~HsI}j|H5srMhdSpxDcH%jyg^~l_1AxY%-wV|XGMUR$FKeShHa5}vp+AEiZ`^1l} z{yQAB#hAK|e0&-@>0|c%6RZ9mW@bDxCxlU@L4v6#S_@A)nj3my_y4-9rHd+iH{bka zzItA~ZCdodf)BToogV%Y=1D((Vya8>9LsZB9=6Q&wf8yyeh^x0vWmlUg0`Qjisa+7 x%*OGnee?{Uo#DWnJcS&17?0_Sg5C35-uAxJ1c|?U@_>1Y!PC{xWt~$(69A5vf;RvF literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_party@3x.png b/Telegram/Resources/icons/filters/folders_party@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..652338b0789f292f98e71e7f8f7b1def6009685e GIT binary patch literal 1328 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p{OJmUKs7M+SzC{oH>NS%KW=o-U3d z6^w7^?$k!tj}dyOYgo=nTnU%zwb&P*qc@}N& z(7l^CPyQ5&i;Fw->({Rp7qsk^)smjGnUr&IZ`{o+cH83Nj~^D#eRu5N&wu*#>8m_W zI!Dg%X0$%jvwbZhAvAy1EUxFzpIi1HdipeV#>|s-)VLc4_l^9zFKz3vOuqWw*5t*nJug5*1dl8ft@Eb zaqIdb)f*49mhb1~xv=Kj)&Cc);@+wxG^A}^-*#cnmbteazE*n6{N3~+@^=t%VIukv+PHF!ZEKEMoGU{b)H|5vgYN>m(Mmm~&J!|7v*OHUnr6^i z>2+YnOO-905r;&cX_Btkjgja7g8$}mZWB)A8?-KEzJE7szl*~3!!P#i3{sbi`69uu zue{=;P@|b$u+E{6AB>u$^aUl>F1mAGWc!gh7H8{A8klZeym-+)HO1IRs@rbRDF?`udx5DTPwA~qqLkSYPyy7C>` z?RE};O#1P7RPi`Upy6<+G|?@%-|t$bJgH;*Xf(p*atT5LeBX!XdC28*$mjDY7K2Xs}+d#hsqdXN-C}6+eKcP71 zwT}UDW1c^GY%>fWA^4n(xZ0!7n!HMa@9N+o*j>k|3_fy~{uchwbM00000 LNkvXXu0mjf^ts}2 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_party_active@2x.png b/Telegram/Resources/icons/filters/folders_party_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e12213328b3b9c52b32db7787a51b6db94d1cbec GIT binary patch literal 948 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*XM4Ih zhEy=VowYy9%2D8WxTB*^Z$!pJ?u=yZC7U(~iD<_=CxM;Zi}I%XPoFewaol{_I5pXy&$B~Cav0;W~^9shPr&A5* zpKqOF6i~bWe)sq9vDbjvVx|vU{}*;+zV_x0x5Z%-*UE1!vC36#`1I^( zQsUctQUTj>g!UhQTx2wJMeVQUvi!V)^>= z7#b&BzaGAFU!3W|%{P6P1!+!i+FvW$?Yi@B-qr{KX%mZvPq*8x9Te8w%*k5xFv&*l zbcD_~>lIfk7Y0mNef5)4yzOZPq&J^prV zukww_CznX@wJS~Z$oQw2w)*O&V==K7%jG&KPc9 zL_a;DJb~j>M*<_0$M#c#!X7TEE+4?+9BiG7dKh%s?q*EtT-^R_#U+O1qHTv`np*?T z{*-oD_LQSgGLh+PK7*rM!qX1^RF@5mj7zGYcK8GJGcu}ZNb1e@Pi5iicv3qd>dRqY z#!K%S%k3RAuKoYUc}Pja)Igy{kBH=!BKhUT`gr|Pr;k~e9=b0P=-#wYbwAg?4>GEM zKJl{MG(A$~w)muNilocW&R=h5x7hu$yV$SOBr#=wE6*8jn7&=CM;_LKl}R^9hZ;c|8?Z=DO_}48}1IhvVjD zw>1!*kbtlNlr}3XE5q1o%S~uYOUlB+g433MnwC5SQj#$1otnuw+jgg*u+W^4ghHY2 z8DFH)Mxv-xMB!FJL4iQ`OW|W{G&<7T+uL+mAP^MH&W6Xt#%9x+ULCO~spG5seSJmZ z8Wu|#ZD!_iV;ALd+=Kl58Xk|=AX6b$1aI1Hje;J<*Hlzgye-nx)01{};n+-h@pNUy z=fOdbN@|-YPcM$ovn0=FQ<67iIeXUMyvdQy&KixTyj|<)?3A{)o~aCIyP7v=&YxIO zmseuO#l?;E^z`_t-Da_VBNB-(n$15awYIi?o1Z`I^Ggs032tqnw*y=0TrPLWzji9W zy0I~GT=Ou4#K_nS0yXNQmHSuUna?~;q&jeWo>RU5~HKp<*@u> zYZYWKk)Gxy@$>f=TR`MBG&NxsrHzeaCAzDF5V87(kuI!ZR}Aw!_4&)uu`x4hd#G-5 zVxk!g29vC>uL~k;7yb&*naSzgBGr2Z@J?>V>Ld*f4fPHW6G}Cju3WrGmdwluHa}#I zJx&2R`Zy;hHd^|6G^mU}tZKd+3?;ePJ?5?)>DT?rmDPG1Tbuh|p#8KBI#P^Ry zuPBOQ58jk1_d_Eo89n$*KQ*^lC7Tlh0@X!Km)qMo`Cqnal1p?g%VL%+Vc#&4lLc%) z)6N#1JY!+i>!jz*X5sL(aY>CwSI|3o2&G!&6LP+65df;QzspnT!A}DrluRT^C6^9xWmT4H5~{u zzxl8L7_6`I=nNB#XdiH4R%a=^E*1hagJw&G1l*1}S2EMSI@W`XJOGR{tio^!e;S!+ zs4`E$2}N$m@t^T`DBbWwG0L-0D#v<(BkJ*DCz@ zd$M}%mR7S=S~2_SlhgeWcv;)jVFgu;TvS+C`h@Ah%kOX724iv0c6ZM?1W2r49M&zaK%LcK`qY literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_sport.png b/Telegram/Resources/icons/filters/folders_sport.png new file mode 100644 index 0000000000000000000000000000000000000000..b59aa62a3bad5308a1d81d6e70f770982f581916 GIT binary patch literal 964 zcmV;#13UbQP)DAWOdR0|b>UMs9?j0W=d+Y1#-pa~~ zcXoCb6&|Z&+b1f1Vws(tm6w+ni4%PAB_=8UyI(Rg-QC@CdwZ)qo}Qj$VPQe~`}?J< ztIKig?d??^Hhd6bh|PjjJfY#?VR?Oh)db7S%i>RyOHfx=r+PjO4Gm6@555SoU?e1! znGn4LkTNnd@(GkFF{!Dk`D7=?;uDV4*MvxB(-cL99=Er*8y{^)jRLU%I@y2#hUPESu2w56rRB{I9!*bsb)F@2IL`|>@+t&KD{H*3wu&*3dC zE$Q#RzCNc9H_W!UxTrB^H!Q<8eYkc#@C~kQ+n$RusJCfjE0>j(eX!wIUS94Ne{j06 zfraF>?d@RX^bKC7V`F0q%IEp{S?=%eKNNHb+k9|v&=Cd$teg)OercVlBm+u)FhXem+B8Ge=TYI+m|b(?D+vOa(H+c zcNgh2H8wWN)zy{85X%Ai?dJ!CD8UCB$9`~4nNPUJXKQOq2|?z^9%z<`-r3uT#c5Ai z&K6E!3T>XXzy5?ZQmiQ!Iy)s!@WD61gUszj@}%CA`Bif9kAz?Q8thoP)}H;K)|y%GyVl;@*P5BKPJzg zKTjS%ewTKS~`P#ZREQ0LE|S6{z=RYeIIWYK}Hp78lU z8`Xj{aNt0-W5*8l`Sa(Zm}K&!3!Uih0idb@s863hYSX4o>h(ht0km%2I;Gz<4gTx5lG?Rvml`~H zu(tzCmo9CPS-pC-mlqxALZ>8b<3q_Hl>-ot`RLrab87hT;ki!hSAK)&qD6~xWzdOk zAC(MJ>i{vd<;eN__iy@3cXV{*c9dUx_UzdW{hTvrPEZcrj3#W^gfAsdx7Gk+ag=YW zZ{NPDl`B^UJ7}iD)TvYL{-w{;r%#WT#}+ni!l#lJ?&bmF^(x<1{F+6;0i^Nc$J>2N zpLK*C4G=o8DMu2%l@J0n4-hekB!;{h88P<}BSxqjH*QEZX&r4bIt&lCCE=rKZ}Q$8 zKt!=}YO!d>tU1Qf#fulKckkZGK6RIAgU9fP?%0+KH$Ju)aW@ALp}tMLc0+F~(?Pvi zRfi58vc+?s;YG&C=iTu^h5-1QClhEZ4iK9~xwpD~`?eZ0W=znLDO0AXM~@zP0nxj5 z#pNSpkPUPuZ9jbYu%iNBTiD&sEBLiRHM9Q%NF(H$BzclU%!5- z?c2Aj&d$#C;+Z;0$f85)2DGlOE=6$359K3#jse%hQyd`D4k3E-fl_>fM=pFe;4nj7lyId&ZrKo$dN$&w|G@!!9HJLQ-HohuS6 z!HZ0*gzuvBG38U^Mtiqc&Z`A0~qhlO%c#!eMOFlb{s#B*< zsne%VJI(l7IY3vgTycz&sxU?;O`4<*9z3W%efs3^*o9fMW_9x$x!604`0?Y%ZFSD` z%ayVx*PF+j^8M%f@ z-BOSAP5LhRv&w~ryxeMVsHYWYUcPC@RLPU^wH2LS=#U4FJ+jsCZC2$$pzPqWW5=Ae zHuYG# z1LHDb#;Z3gU?jQ4`MM-J%nqH+Yu*Kq1cx$P3;Rc~pYcV813j8B^QByUry0a8Oob|l zjN`Ip%e)}T(~TZax$;uB=|`n^8ziQ4 z+Z_7OQO=${>oeihwZ+E^ zrZc|JUu(W zG9AJ(`5_e@AVtaoniWhM`ItL8&?VEZ*ui$m;4t#z7RXpWrknig1Lq;4Tebk9y_?Tg zZ+*l`zBENi-%BICa3>coTuAg+VwpN$ym*nOWbfaU>KJ9ccQ zu6y_HB|553^zm~hPb&?jE}+_xR3C2}VvwxS^zWEz?Jv5qfh}XVf_u4BOgc!!&9+2- zF4GD$=t5_UA4bK3S}i~ZFgCdCs7X5%`S`&Ai43ynXyIF*SWvq8Dp`XxD7mDQZvUHq zhw2r8a?xwE#==SaHuJx7cWQtF5Z3_J00j^TG}j}j1}K0)pt&AFH9!Fb0?qXZssRch g5NNJPPynER0MLrc!vHUKI{*Lx07*qoM6N<$g6uZ-7ytkO literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_sport@3x.png b/Telegram/Resources/icons/filters/folders_sport@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..422af43e6145489b7db4f648c21821a180982594 GIT binary patch literal 3259 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 L*HouL%Q@zM_0R^Q literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_sport_active.png b/Telegram/Resources/icons/filters/folders_sport_active.png new file mode 100644 index 0000000000000000000000000000000000000000..c3581bc347dae675cf4a4775bdf157c1e41362a8 GIT binary patch literal 976 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 Vnh!dLU`zl2002ovPDHLkV1mggvMT@p literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_sport_active@3x.png b/Telegram/Resources/icons/filters/folders_sport_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f4bdb1f0901bd91c0fa43a3d8ae757be56dd5c27 GIT binary patch literal 2828 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_study.png b/Telegram/Resources/icons/filters/folders_study.png new file mode 100644 index 0000000000000000000000000000000000000000..dbbb4cc2f24c87c0f277241024b9464e04a358b2 GIT binary patch literal 1025 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_study@3x.png b/Telegram/Resources/icons/filters/folders_study@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c76dc5cf269d612bb5c1ff0555af066468d4ff64 GIT binary patch literal 2990 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_study_active.png b/Telegram/Resources/icons/filters/folders_study_active.png new file mode 100644 index 0000000000000000000000000000000000000000..379d3285439b58c2a931ffb99b45b571bf3c14fd GIT binary patch literal 875 zcmV-x1C;!UP)eSO8?;Gis8pFsWn{hD7|{1ffr;h_Y`P&!^-UbGxhRaIq~E1WD)KUPz3X=#y~ zF;H%9ZvI4Ygb$U-;GYN$11^^fBO@b*v&8!@tT+%ZE-o-SIw~v_`!50F1q3!UG=!t0 zBcbdB?DFyw;cyu0anJ(5M%kaPH%3S|AtDzFg|NTB&o-nbLrfM4S65evL?Qx&?ej8z z*Y)N=U0q!QthcupJ3BjU%M|ST`Wh1x6Htq-rtkClgdK9uO$WmB=0H>?2!dc(xx2d? zTU%RFAS$ktlam6(>-A!7Z4H!M=@_sLM?kDh+eX{RMmsw@5s$}letwR_V5QUL$%FN6R zGMS7W4sGqm#s-?3n`Mr_8nXuCeWrhz_4Re>?J6dv+cq#T@Rd`u@$!KfjIRCR;bAd@ z_D&F1U_5g6SVs!6xEw*u_=_}GxI_yg?pu8+lP_r0cqDS<#hQ4^nX za&q!LBRVa|Kss9$5e+|R6|v~h$RChZ0DoPC{sDq-7*tT$XDk2!002ovPDHLkV1iY~ BjOqXY literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_study_active@2x.png b/Telegram/Resources/icons/filters/folders_study_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4ba6d66b929ccc9e4952057f26f1ebd2a252a367 GIT binary patch literal 1689 zcmai#Sya-A0>=L+u4JHKiu=aMxZ7y1DUchcCS`^D+Es63qvZl*f&^wWS7*Xqrv!|# zF|Ubxq6zMml}65JDXC{HHBHhiqQdKapL6En`+l$A!|$B$YIs0RYu-ydO4#1lp*>2N{JTXfIIb2y<1%OXi}V zNILy@3)7@J`xwRa6|A`)o@-%)PC^i#{l#Uc?Wd@l;Dj&){g%43sEm|}#~|iHW8e+W zn|Itk9UW~G+}_9tSfz7%D_cIU)wy-{HDzgoLE~eZI^Y=8)_>P`HHb9#-x5G=K$d#u zkM@v%;u3KFb-7`j5e#Pb*Q-~WNF)+RBK5TL`R4gwWCYUotCCiM;A~GmN@;RFQEqKx zqZt()ok*pc4i8EsCN8e7*tE1;tG&+N6HK_Cp4y%Gfugc9xU)mmR%vPJ!t%o^8U{J? zD7a1;jY6qN2B=W#uA(1|#gA^j9N-89hHoYPXAkJxjJac4T3V#i#Us_VwZ{iBgzLUk zg-|F2*r?hRf?RoPYm3fe=@5y;@Mr0+6_YwI?_GjuXlMjnehLjwNHB{yeR@bP@2&N@ zU64v5>9fkpN|boU;n~?P9o^kSB2nR5*xI|L;bCyfnKP?e!H3y08EIv6^NLt};;_6i z+E6SO|58?_?BL))5%zkrJ33rs(#6Z?j1M+(W@jVEpFB}>b91w2mxBh8t5Xeu(&dMG z4f157Aby~LxwModl}f{ve}tl4U8g6J_ni)g4B6V+{(BTn#*oS6%-r13;@*4s7?TD4 z9A?E`7q(a4+v2ws&svJEYrOf{)E9we<>aNLra}q|3Q|OJ$6xk8HyK~Yw6|pZCyr5B zd7wqnMq{y94lbE+;Y?of#OMe7RB9xquC9*jIA0!ekdY~{fUIo4eln+ej-p}|qR_I_ zIc#ib2p@NGBv8J#lZT3$RT4kmcX12}3yY)b7*K?;c2%kQEio!mw#TxB~$Hzkz3Po2zD`icy_)yV-lMxZ% z?d|Pm??!xsc!0wJjZRLMHZE0!Q?92c7T2DNiVq$@b*(fSulFyM~JjmN0{@%vI~}e>X}e`e!5X9M9!vDck*K6-Ptc+n-#<3Tr}|Lo3M7f$8aK{-uHA z2C?WK|Da>Xx?|HtJ$ybvSKp?mv2lzHELPKh@=w(u>1m|8SNvh|;l?2d&R@DNXl~v& zIkGTLi$}bXOoGaDF%cT)kLt?h^6VF<(99BL2ah56=MOvMVkk_78C|+J@!O61HA>IO z>e`yp9F;JuIxJVlp|Wc2(dbE+nFrURV`9L45s3h-D>NA(jAY$mC=B{|O@YFrzxLFP z?PymdW`1vH*7T+S;>U!vv>>=;Ovyv?zFT(JlP>@SUe=DMn_C_#Jnd4Cq&MeHW#p-3 z$FUfUx~5lCb|j{B;87nu16t%$2Q7|*J3uHGFZw$c7VIkKyF5+`?(zsnQh;0p*EDcG~^*NbG)wFe%TzlXqHN4ElzGr^JY!#{i literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_study_active@3x.png b/Telegram/Resources/icons/filters/folders_study_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..dff4822febef05cb829705cd9b9addc030c59a3e GIT binary patch literal 2437 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_trade.png b/Telegram/Resources/icons/filters/folders_trade.png new file mode 100644 index 0000000000000000000000000000000000000000..62cbae9a8f5713eb6fb057b17305658926616e5f GIT binary patch literal 606 zcmV-k0-^nhP)DF?RFnPw1-$M_BkMr#{<=B6;7uUiGMsEp;oKGYPBNI z!{N{j#<~pU^LbdW*IFW=)9FAU5Fh}#T#oqBLAM_$9*@IxI;9}9*$k4&q%Lr&RMPnj zj>~Ij$nW<M0|eLa|r`RaKcVEO{&gN~KbO0dT>$Ee6AYyq&V-u?(nEsgSj>R9G?| zh}Opn&hlyF4QlqGRuqMMU`6G57D&EPqUyPV%E-+6q$`Of*~9@nR78jz2= z90>Rw>Q!}+fC9t_1qDa}Vh~`cRRk$O3<3R%$*x0B&JUnQRkB{8rGE<$KG>d~9xgzOi;J>w z5;ubC>gr%`Z;uJ+>+1{3%F6x@p#1!Nn46n}@9%FW95h&7UUmZ~aUv)=IT;2A2jS`I zi3fq(%ZJv|++uC5I8 z)zwu)=Mqx;?z0#<}}*Z|7N$bj+jaog;JfL>l+U}$IvQc_a9bXKOX=mNrJ(B0h)*VotP zKrv+8KbDr3prD{Y#+gX8i5{Sah6Xq|I51$)0`4EUL^#4enl_OKR9IN3zw%>r*j<>O zySlmnqe})(MMVYRN2V=Do5%r5O-+U2;bC}veYN8PZ*OlfHZ}(M4#P1~Tbq%Q5uBgzJ2ZYBE0|Ns<{<}cWRWIY= z`sCy!;1Q`C5Ke9BL7nVe)XOii*49=yJw2u8Xp}cMH(i~_@MI@ru%O@qgwq2Q2UJp0 z0_eQch0qY5o5}KKCwdNU#&l>a1}HN#6K-#B>3Qj8TxPi3aM?Kw#$C^wo#=_U9oMG4 z7$AHbrY}64nw^~m+^B5^kgB7{aO#)W&Z#{|y%?ahv^2~64W@KV?HmIM+3-5fYbQHD zU$7y$@n0lTG;#!?ou8j;+uPgx4FxQT;}&c3HN;X56?pAJ_M&$wK%#@t_9Q#$Rb(@# zRYsZDab7#4V+lbh^hy7&?7hxT1d!!&J3qR8)FX&wCyiu6{W+3DF)b@AOPA?C#A{2{ zpIKsFJI>pmg|M#NFv#Kw!{+2X_2`qKnz+6 zkOIUYz)-6QQh*o)7-|(k3J`+;L#-l60b&qfs8s|hKnwy5wTd7Hpns09{pY9HvC#kk N002ovPDHLkV1o5yVp9MB literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_trade@3x.png b/Telegram/Resources/icons/filters/folders_trade@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c854a9497b1c290df50a59ad80d68c2e7df7e182 GIT binary patch literal 2058 zcma)7Yd8}O8{U}HoXzGmMxw7|=3OOeP6-oMIiE*4AIqUhn^6d#H{(q@TC9k_0P*0RVs`*4pACe}?@j zQDJ`14shw?4^W_)of!a7pDn)UD+Bfv6r=@nS5`Zc0hRwxZD!!V`(kz zhOxLOJ6Hsj%fWyOjBl^I4rQj`WY4}A0r_#x+%tB%B~O&uT9r9_{-O+2UISW~7mRSP zG%V7&w$i)I>Yp$&x{jRhFTtRrXMeXf8qH0P*)<*&A4RC9{_Adpb+P`iUe(r9rJZWt`wYW0X zu_vkZ!vg=)pw{fd1z8D6$)xJ9J?W6@CN0-~$j0j&O&fxwv~>5x^Jp=u1ODScC9YH| za`Ht^YTVj4>_<8sQ|Rlh?C9)l15{B`N{Z*r%A)kH+-zWXB$@;V{Q(QW`sf7u*n-sP zg1zE+Ho~NgPQOcLLR@A--%<3^?%a{`JwFHz>@OAr!}?H0^M=v0j4Fr9>CKg?3)~ch z8&a4zH*g3a=j%in9v;45Q6X$h+&L)m+Kt0M(X5N6abnJgqO zN?lM&;X)M@&UkpJ7*M4V#s`1CjN+KTr&ml$6dezR9Xoy;So@7|hkyT>OP4e+`}=qH z6&Vm8PtU29fXs?L@0_T#{8F5sFPyNk1dP-Fcf?h-2M-?5g9Z=a5_^}ObN+B)e=`f1TB^V&nigJPn>jby9A0F9<>tK6dQx8r zMkQ)f*4uN&aJ1e0WX!zG<$wTT4eIg<2L(lBDPQ~*8^`^!{cWCzySsbxXw^fNzhD0T z0BZK_7AEX%)gx3WFB=;nZ{NOM5_E9xfAc0OBO~KAB0tx4tg0s0odH97in`Q!#{yT; zFNnLaxRuE#7w=M=qw3d|*=^~|^)>OpTxCW8-*vrrR0Sv0UPVu9_dT%6?)tY9+7IMR z+?aZqFanhtcihx`UqZq=IXUSE)}PZzAfC?ttP)RtER{(ut@i~O*Y$%cZkja)2g z|CNkm4V+som}&qy8+VA~q1Enx6?Aw;Gk4EE1mD8Tn`1D0H@Kh6)o+{T|AA``b@=(j zqL=uVCVkBuy<(W%R?yYXPs zwUiUR7wMm6Mwx$|Y9RwE3B8K)lSn# z^*&kj>X)lEk=jw^&v!=h_8OFCJNvNN#q;lzgdtu10Rl~fO;Zk+zmGuHEM(?n%6 zK>;_!t<{PYgHl=3b~QTLy=;Lea_qu~PgBU{$JDMxfmlzlHXfp`HeauZC>%jGzZ-!j zQTk?jB)m!9&5qTJ(99ZLNBlquCYrhEA8n|AlTu32&0Dz6q;Bk!mEbeusfB5rF(wk{))vRjC=TLsxv+Bj%Kkj zZWd5=JS`)5;}E_OcsmZquz(IP;TXpRWw<5e^dl8xrQ~2?^>GXk;$+)s0vwhweVQPk zT}=&06sS-l2W^6E9rgQufo#!$!DTV(Bfr9ZF=_yS#)hCSFpup*P<;T4wnP6z@LPIj WYYdZReTvF|?f~prdy86geDXiYn&43Y literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_travel.png b/Telegram/Resources/icons/filters/folders_travel.png new file mode 100644 index 0000000000000000000000000000000000000000..886fcaf538e4abdc53ea7c04a65c8d28d52ddce5 GIT binary patch literal 1143 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_travel@3x.png b/Telegram/Resources/icons/filters/folders_travel@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0897aeee3f28a6ed79aeaab986b01a99faaac066 GIT binary patch literal 3411 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 Cbyo`j literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_travel_active.png b/Telegram/Resources/icons/filters/folders_travel_active.png new file mode 100644 index 0000000000000000000000000000000000000000..7774ca025279d845df089daeb8c0f8d513a82f90 GIT binary patch literal 941 zcmV;e15*5nP)qBW2x2=L$tHE zP!P1R&>x^SVj=nmEJOq`g@sKD3lZb}IPbv|cGua-#tUBx4{T<3=FEAYIdgFhDWyn1 zRb%0*A^8CL11h&7e;F#5p?~EZexkp-yIW07O{s>42DQDtt&Wb4tYLBNf9=sA^!WHl zVsCG6G&wnG)3d{b!(rQCYv1(rG;_SXybwU}pniLMOSQGNeq;TCve_&>JUr0d-5u@j z?h*vLy1M)Vx!S!j&J3X*jYeJmj#V-PWilC#@%;SEvyy-)^Z7ijtgO)J=qNQcHGK+f zgL!>@rHhM;Ph-jgU0q!f28$uxFqF>rr>7@6JUpbixjE|T>7kjK8TON9;+KzcXu~rw zFi<(r{{BA4g1EpqgG(C(yygdkSQ8TyHW>7om#n?LopyG1N?HIAh*IN6#o6wi0QvwJ z5XK)!KnDj0>Ez^ur4bb71mFgrpD)0a2Lc$5KJJd|>uZ*lWwL>N5hfUrWUH~Ukro#h z`5FM3cdwWdwXv}QkxHJ5oH;P=kkivs)<-5xnUZ!a_pbx_-80?{$p&w0YpVpxKSTZf z{j|BcNz2R2dvy1&0?0ND!=fuKngFk%9Mchu2WC=@6Z3I&2PelbQO5mjGbue7+{k4ouaTYHCz#YpaUKC6~y-!2x&61f)DZK6d$8;QWCwsIIP# za=9GuDFhb*DfK6*ODGnL@jk-@#0MF}fxs!XfJmPmNCJ$EjPT55Q$x&e!>+HdJCj(d zkB^UwsI3eJ1hBomy;5*zrK6+6C4esld}TVb1poQ@d6%C|)>8%t>g?>KVzJ0V5J0wW zZf;1Q%1=TI3kz^zll_M;2amYvJvb18VLypsUyO~7nSzr3mxs>I&cu~|0hP;s)n(`%lArKDm|q literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_travel_active@2x.png b/Telegram/Resources/icons/filters/folders_travel_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..059b6b0ffd1bde552b35fa063944999780fde899 GIT binary patch literal 1583 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 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_work@2x.png b/Telegram/Resources/icons/filters/folders_work@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a70a41ac11f4e399c2ca56f89479971a17b4a932 GIT binary patch literal 704 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*m3z84 zhEy=Von_m1$UvlJ?psR+q2&)1O6D}q^1gWS@?oty7c?9pMAlb>+72? zE3v9vxvIRRj^U>2rEeR5377ivP3Tn?oN+hrxp+70Ta9xEcP1`acam|_L9;i>LUY8J z6j~d1-MX{fzA)M$S)TuO!WFl7vMaaMT&XzLIIo#QhfzqusbR@#8I##(@09H>J^py2 zooIP!g3rmDMW-IWl%BPKS1Nn)i@pspdVil5ZM5sNULY978}xEoya2DATz}zY&MS`D ztV?Vh(r(s2oYQc=SlqAa8I8r5 ztyQb%HCMfQyx=T{z(IY2!HX^6>v><4?RE{l)&22Bg%5jdZtPplsLiq^cJmikg|7}R zv02ub)(Bg8{g7g$`sGW;j4a|W&vSQ9`0`zxiEFy_F_ksf%cn0nta^Xn z_l2ylKU@g=(cgD{$DF-i=2(65f4b+*?LQ$_^KS7oDlK90Xy9O+G_9GXLqH+Z5*vqO aLH;C;$LHQx_a6WzNd`|>KbLh*2~7aW)-oCZ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_work@3x.png b/Telegram/Resources/icons/filters/folders_work@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..91d9b99cb22a265cd84dc7db30fb3864a4e3e691 GIT binary patch literal 1191 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p{OJmUKs7M+SzC{oH>NS%KU*PZ!6K z3dXl{4E1vYMOwexIIWxJ3_aA=8Ab)v2Rv!s|8P zedhJlsvB-DpJRH>GcWF0!S%@W+Vc53jqlmY8fTwc=r-wqywLnjKPNUcOpN7XQcFnT zoNzO=l~W>;DWxnlk;!cu!_3&w84VLv9n!)@iIQHrdP8q7ueF`sw$-b(tE;LuEz8_j z`aot5H*bca*`M_*U-d6ty7bwbH)}q0=3Zzqb7qs5d)>Zu+qP?W?%zK?>DLmr+#W}! zwRs6UeiiA!q zufCqfluOiV1Ntn8Yan%aq^PjC0_-Fx%y-MM$~ z+_~}c<;v3CvAg%|`BHDW!Tf3Q@kfu6ii?UK)n1W&ZFfepSifddU&X_KEjY^5eDV7A>yDp)irPD~NnUWefA~~Tg1ET2nX$3(j-=0Cwsv;6UcFis z8z0~9tMoM=uWhb*Acv z8YPc}+<5zT?enkiCkbYl6+L&YmAqIxE-}4~Z{hZ@qR-6Brm^(!tZbkpu@RY6 z4{P!GXH@X|m;9M_&FuQNoVJsyJQ8LSA8&p7wCTF-wA$R_+|y|@1P(PMnoF#I`{qr> z-Q=6s!3ud5pS*OieeCnCXLFfg#(key1zzP zKU8S_ubZqcv!eZC`?PPfeuc;1*sPqqeq}*|ZN{y$y(#9~gm}pswGZs z-9Ob@<*mmp`BNP0FE{o+@Lg`j*;0G?Va950MMg$%4%6VE14fs9FNkeu2{2Y=)iJ)y el1m%m`;7bw8tXLe|2P3l9tKZWKbLh*2~7b1k{*Wu literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_work_active.png b/Telegram/Resources/icons/filters/folders_work_active.png new file mode 100644 index 0000000000000000000000000000000000000000..26fceb1a07094dbdd60bf43fb7fe63e9a3f93139 GIT binary patch literal 361 zcmV-v0ha!WP)dOk72SCF+$pom_q-dP}4Lt3#INzM~8k849tD(;@2 zMg~Q!!p5fSy3?e6-)}MG+#%!wEB=0MJmZ$@9Lmq8#m^O=k-Dxu%TMDt(y}avgB#WZ zC1M_n0|QvV)GWH!;Vkfk*$voCO7eJsSCgGc00g!~NIcY#_?NfjYFpNIrM7LODIy_W zO|&4_pmam)x@Ec4NWn3MG__12Q%F-li;HInuW2c}!WIcl(^QHNY1q+5_qcA)@_3L! zXXZv58ujfjFo31}dU&z97TYfcSUf{YU9qqc(xf$os#E9!lvNA9*g?PF+ zhEy=VoprYNkby|cTw7;G505WSi~+2&7Z`L49~_%2Epnhy?m^FYUZo(1riVAX%^NF& zj-3DT{O4&4z9XR=inkV4I6QcM@cXY{UJeZ%qAVT_9E?h*9hqD>1S0u_6r37P6ir|d zWOA9-%+evCuu0~MnL}u3>&+Z7v)R5Da{Y?OetdC|oZ>2bKZ4ncX>~_m5WX-@WKb`jaH- z6?!|DzIl;xPV4*P^fM>(xxQ)(x267(8A5 KT-G@yGywqm)e@rs literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/filters/folders_work_active@3x.png b/Telegram/Resources/icons/filters/folders_work_active@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4391725c820220fcb0c45656212f06c9762025b GIT binary patch literal 1116 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p{OJmUKs7M+SzC{oH>NS%F+(PZ!6K z3dXl{4E-KEh_uCSmJoaanZ6ajZ5yQ0uM=AFr_QRG_-LmIJ!GKdp0&*QdC+$ zf0{?+dmW)CwTJZoC`ax#`!WCd`_C51Wi$I`hJIZ(t>wUamC{vb%9(_wy$PFO!0GL# z5V=z-QD|C^!=@tBLn=CwjVVvFT0A3+m`mU3H8H(V175n_NHEy&`E&8^{rk87tjP-vt0*YXnUzE9pX?cSb#73H;e z4|N=W5ES&;|GoA>jqLRss)8=0biJ>hpF8c^tH?(cHtTNwy&V@H|Mtt5E8o6-TXXqk z4a7=~t^dD9{<`e?;qRrBh^-LIq=Mea^JuVMOrv)ILFYxf?#`rb!%+0}RN-raJr zU3_KFc{A?Ud&IxTF8+PEE_=bz^}mx17G3-)XliC0^;hzmPT21>U|8uP!s=wp!_7zP z_UA9Yc*4)mZ_V}BH_x5xE8TrJWyYzedScwwRaKjgA9vrqYuBdkZtmT?cWc)dKL~uX zH@Uuk|17`dOT*{(+C97e=6c-kh1-5Scvui|Hf?jS*yoE!e{QvA_J~?zp#SCV+qa8f z{}hZbW^37U;rxbQfeGg3=G*q}mHlz#ob=aEpOW0&-M@YKAW$Z_aPyJPn~hIK1wJ-k zXWW^SsC*{9dHeS56SwC2TzmL1@x~j&oR*(gV%Pd8O!A#!P=7v1Teu@dSnW)F*VR)j zN;g|-+pb=|(fgCrP4f5k{$Kts+qP}nC%2&}ZN`RQPqS3R>T`uUez{-Y;GVSE;jJ-? zO3%ZtrCt(k(>SLYv}sS%N|cCFi#Q~)E@D+nV#dq_&g30QD}~w?rZ%b4TA`HMnR;hu zsJG%1Twp9-HyCS|JB*d?31f-8{`6^6%twB^S3XMOJlwyhMwTy{TJUe5ap%;6J?$4< z*KE0VW&YAFE4D5>)cTY6!KE#;q9Fk1rA(h&g*x6_uQE?*FXpX z`uv(<{54FfL|TOOzDTP=f38AiJ;t)#Znu-g#YHkVH%De>X2`(605KYk0o}T28rf2L!nvca|fwi?YD3{BiH(p;~VRLg6IyyQu@#^KO+O)|G4-Z2s zmD1zE+0W0h5P0%|)?D3M5fHm|1t+1Z(t(Cv0ZAP@kX&88-xmTL+1^z=Zb zQqfG%jKSe>NJ+1*t^hau$jFGA1e3X|g9c0eg>J@g8&0QF!a6xQA=A^-Bp#0wS}7qu zN2tHQU+DN1WnW*P6n=MiC(UDHV`OVHU8O+ks5*ZpAVzRv8 z2+{9MK0ZEV`?t)+Vo}18D}{pDY$o(HbNCz~tWYwUnAjlIV`F23 zo#ue*Dc;`RAQ%inS67!rhMpt{hr?V5-UME+S5zI;i3!m@ z!2!|J0{i>>kk9Ag<>dw5-{0Z>{vM*yDER$;7#tjAN!r`n;o#ta!$5`4=VN|?{5+xE z-CcH{(VhgjIoH?M0h1^J@N+0-T;m_+%E}5{UtbH!pmKhGo{Ll9@PyFV+1UxS#RFcp z3g^1((a}-JWHKBPe4)d`L#EGWvw)vd16NE)!&wWYMvte8HbI!2oP_S~Zf09uT?N`G z0e?wmXJ?sSnafZW==3{=uv7mewH6`0>bENN=PL9M@l7*&OcRJ*00000NkvXXu0mjf DXF`_8 diff --git a/Telegram/Resources/icons/filters_all_active@2x.png b/Telegram/Resources/icons/filters_all_active@2x.png deleted file mode 100644 index 762dc383de750e2e314bcc134d4519894eff7fe9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1767 zcmVKE9AQIFF(uJBI>Bdc= zCJ~hj69QMc5nGffkj8wENDO5BwrHz`}r%mTvdhh@4*PFTT`!6$ad2h}+ zGjrzWnfvCRxq1;oC|siG5b)NK3-Lb!0uUn<1Rw#3L4cta5hMUH2r$$lf&?H20ft&c zkO0IWz)*__5`Y*47-|tg3_!jNyocFheNO2++5n#)kVL2`9h7BuV25?$B!S= z!oou8<>lp2zigbRz11>Hu(*67QI~x)|Ap8~av~ug#EzbZH5D-9DR#rTXkF?z5;^JhG;1Neww{Zt8 z^}+qHiqBKAv9XSR%(1&a_`{Ln;$m4p4Gj&XqN0M}^PVFL7ww*&9*>J2lS8=E1`y^e zEiFw_y?y(Z9zTBEEVr~V@cqAz^%~)g5O=bkTn-;TECYeiuzdgiokE1kbb9mDl5;Jw z?CflE;J^WB@44mV$&)hYo;-OX)%We&M;<(QKyKc=Ngz8Qv*D?50fL(#PhW1eQTjMM zJS_9)+O=y0HkUFPPlXH6p+kpw`f{s{(#O}YU#t9qEdhahl1gq(ux9~bwi1$Wvbp>E z`jYPMZW0z2CX-vMZ~?;9sizk$AJ^B{Rc^<}$CIX}CY4-Iz%}zvpFZie_Za!dj~`XO zY;SL?WGE;rDPd=D`fuQIh?fua0na(STm7yT!7HBudk2UnY#=gK76Pe z`qZgYDw$Fc85yY*Y1Wxx2CXPAGfzx+z$q+EOiWbG;nk~GR+ktm&zw1|R2wx&KTKKTJ*B5G=CiZ!mw^cc~>!NICY z;;9Eufp-N|Z7sl=1J1UAy^LuR=FfkS<)f z;Ox0G`-k*}8H(j!y``mv*%l2RJ$j_s-m5(HxbSHJ^&K>R{@SP*A z2-E;$8qYRBFJHdocFI=6xw$!7US3Wi(PeU0^?CjJb?)x*$Q*x#t-#RI83&Y?m&fgt zQA2!)eD>@aEiEmz3I^{v_wL=}?*8}h-?pCR8v_I<9CUMYlj#_~H-yK+_$CPc^ge(7 zoGvUZ(2b1^X_t#TU3`ZTa>sOVaFE`;dsmu3P*9N8Ur{DYNJyaLPgPnVCuN-@mVEjt{qxr#9rbp$O6=>})ya zm*MPPy6{XZBqW5u&0cy#|MTb11YV?)>Lx$%h`G7BnOwbk)g&^1!iJJS+QwXGefeJi zVogF%kFV^&uNT2nSGY$KJidZ-4S|g%_rE*rX+V15jL5lj(ndPnOSS?Kp%xJ&05J$K)FOfeAO-=3T11cl#2~;>iwI%>`U_U&p-@w@gKPi*002ov JPDHLkV1k+8T~h!6 diff --git a/Telegram/Resources/icons/filters_all_active@3x.png b/Telegram/Resources/icons/filters_all_active@3x.png deleted file mode 100644 index 8a610c42fdacf909f46d6a212b39e589827c3f7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2592 zcmb7G_d6SG7fx)kXN?*~YqnzZB5E~7?OjDwjnv*%UP6snwTU(oN~AR+Myonvw)Sq& z+Ej^AYPD8<`Tm6Whx43Y&ULPHp8LM;`=nV}K-oY-AOHZsW^&uehMN8VA509?H{08B zh??kp^v(4FfQD?AGY@(IfO*QqNZ;-eaN8A%B-TJSxts%Zf|D~0adoFi3sO*)(M2i);BJ1pvoQElm)k#s&%6m( z=}P5T!vBx%ik|Rf|+{d4@grlFU2jtP*`vLSDEdwrm zh*r7ywUUZ|Q6Z!G_&{c7tRj`t?!`_7@yYGt%!>DR7TTZ*&~-@E&ioK%&Q~tD1Xyc} zw>YCr;L8hzB0h~^_4buKKckrL9w3I$S}H3O?oRDfwa#5;1u`Do!DB!NQfxHIZ;C*C zH*oi3jbr`Ztt_Dun!Bn9a{xO_2KqVmCa>_rHjIS1_g>z?h(7aT1Fz!LvjAaF5;m%A;b`)jY_--oQ zTPqhFm%9Hn*x{88&R^$&AYKbTUKy*}w>;8&_Spgp8CpNhSA&mBxtGc)Cvje5usQs_ zvw(d}lmB<+aC7Q%N<#c6a8S ze?HwhF`RDjggS~3mueT|H~R9WwK5kZPFP(WADeDbS6;p(T-mMjD*obc^v|#$->SZI z%2{$ClhNdd=zX2ze*!Cko1^7V*iUX9j*O-Rg4_7jl`PiYtUjve@zDE0Rkxb~8zqIt%58jN)o^UzW;WeK@s6ziovo z%$*d=ysVhJ>K57-e*v*_(+nR(DM=DB?)30>NZi@_RM7o_S%)W_lk$45Tut6X_nYHU z7?Be*WOVGd_bgf+74=Rq3@=dsV?LCsXKhS(aUT~x&DsR zzE>VMOgnNbSm)S_>G`Dh;RYF_F!4$*<#*ogy+#v;!*V(S1I3)KC{DJOf%4SbjY?5fV`KG$r{(xfAF_Ys$QaDW8egB!1IMKk4E+ZMQcZcG?R zX4g_oe@@`D3lv5$y8GK2rNer^HxL~Oiv3Q)HbP^g>r^f$_^@+`K@^Oc=anuH;(IP4 zU`|o3&`REwkiaBPzBp4{kSzRYir~6A)577Zi%4kiFOZ$7YdJX&R`M_t%*WKgTPD>LmYg|f>c4dU z@(MaBu6^-q-ezKjlt)t5QZOXuXzONKXo69*9a%Mc@B8RMDCON5nPYl=tis+aqghwt za5T=&ceP{9Os!NeexBSmNS`B4q7M3F8MUjV^Po3JJo&pURwD@0DB}U)qeD=)G-&=T z>tWAeX)OS!FIaZcQV;l6sQ{8t2>&_jjEn?ePXJxNFV)gDGGEzUd{+I8xCIIQ&V*I= z0^ytZJNpwae>bS?PvQ9z){X#n9-IRv_*+C1>}8mgD}q>GNMnK}(mIQppxL5Ipyk0{ zU21Ny6m<*Ri1Oqv0-BQ4NH$aiF?)DLp%6%lP_F)G*bv*Ux-d zLcJGNn>1N)V2^qD)q#eWV^{L`3U1#KDB(=2b8hm^ycu2<{^)omSQ@>(+lo*rZpE@9 zrRniu8hBmJYTNaD>Jb}u?z&c-O1EY-ND-a<5A`ufzV$d_4(K1O$&Q7W2;Vdp{}G)3 z6=Oxm+mIDBn1iXBh7r^#`487Z@6$dapyQ)yBjS~>B1=Hw@T5<|0*&Q3jrX)94A_Ja ztO9mTzm^=N@kTMQdQLqBv!FbLz?rVvoeHoNcsn?9o9Vw4opZb+Dy<)}G-^^$K7fg_ Kg%QC3miRw^1^E8} diff --git a/Telegram/Resources/icons/filters_channels.png b/Telegram/Resources/icons/filters_channels.png deleted file mode 100644 index 1c0c0601de65e15b020a10ae7d701f8a259e451c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 897 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 Dva}nf diff --git a/Telegram/Resources/icons/filters_channels_active.png b/Telegram/Resources/icons/filters_channels_active.png deleted file mode 100644 index c76a77f8b6aeecaacdb82f3506ad904806426a45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 633 zcmV-<0*3vGP)m)9HjpqXEn15*VQLnayU9OeXnKM59q?Hk;t_ zc);Osa65I}ZZ`x1fjgUMAY{zQXtUI3XZB{KI7X>d!u@_P45wwQR;xUgk$LD#70T!H z+AKvg_|p~)2D!=S^MT9d;^S94QbJy@_f;${-e$9z=BfNrLZ+xDPAVuXYUa}+{|X@; z{5x+cq19^j9Xw4PnL-3;<|Dt9P`lj*_O(!qvd!mn9&zQ9ii*>ilm$X^c~ytgs3q6r8u2Vno9Jx+wJcZ z7v;W2$nW=Kzu(8{bc%&SK?L}gjV>Mn_?RX=hyE2qi;zlrOCd`kl>#dN$L*otma<%4 T|A9lr00000NkvXXu0mjfQ|&4$ diff --git a/Telegram/Resources/icons/filters_channels_active@2x.png b/Telegram/Resources/icons/filters_channels_active@2x.png deleted file mode 100644 index 87b26cd9de100e6b022f523d1768363e70791b11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1099 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvI4nGo-U3d z6^w6Z?e{h|lsLZrSCqJ;td7TAmgQV}d5;u!l$94>(A)Lu!F)vyr%)Gj$Jn`790l4J zbss%C|G}dL&Gr|=D)j^@eyjcoI4 z&izw5(7>T-pupxLpm1gl2^7->`9pE$&r*zD^78Rj_4oJR`}lEVd1d9xHE(x3iQ4X7 zUvHn2pMUuHarP4-I%0{XrJ_;M(Xn4v+dV8PD?9Y*Q<0>PszT-7&6}A&R@@N_s%6Z| z%WIoCQ?jC>LW9%o(1ceL(`VY7ntrWj;N|B(eDr8*%7W5!PmA_5U-+cqR^lt?j>w6L(yIK6jM;;!rI>FEac_WW<(zTGG5xisj*uV1UAKdmqF_xImd zXe#gbtXRNr!jC5fGmj+S6b~|+%^MT?q#?jksBaDj^Y(qG1g9>Mx;lCCcDI;Irp$NbeMX9i8M^adB}|fRZad_1K#pJ9f-P=De_%*4)^C zFW^N##cE4>9_sZRtEo)V4l<$ zI4_ss=8q)#dK-n?jekBiL^y5}Qv4I?xQ_8*SL~IO&&)P{JX$BEtug<+`&soV(}V8% zNiau6MQI%sTmNTQX~uMGd;9QdTLT+=*XQ(H+}^dq;8XwA+qa`H`)??!jByr{-D-Je z?&7$2H}2lmEx+-F$$phGdyD9zNsFQkq$MRa3pqD6eaLP2a)SJn4BrmfL4l;{ltP7w_M`SJdLsF&KyZ7(2oxD_p5=+l`#ysz@oU+8{$J70rHYwF> zo&OQ_VZXZi5?A%9g#~&70#mfCY;8k-{W$t-SxxEj7_+phs$cRBtxk-WUYamZS|E7r z+O@EkC%5g~$@%F1(^rgq558%bO#x=E0QJoIR0x3M4Wn{7bdJlEOB^2g6Ne_{GM6Uha2QSw z#|n{&o#&t9M}g$ zE-wLAe~QAsasrVLZdtzC+uM85#|I*;Y7+>42w2VQkZoAM|E;NsW?^Qw`tYIY$kKuE zzG*zkh>9A#QL2@S4Nfo}I0!IgtiR zPDydc=+!h1i^XC}bhP_9CC`MZsVS7}kvO$Sj~~w$6&3xWVCm8H`K42Ha~WlytS9;W7!`S9xaqIW zq!|rAKfmyu(#e1@(5alM@#Lh1TFw z?og?Uy&o;@?80HTVD#2t=a4nI3F)cDW1u({O*kGW;a^D78MEzSd^sg^^}n z2nt#WxQUR<$;s*L>ztBb{Ki@x>BX$5(1P{>P4l;QTt1x_%hd*zZZbfM7Kk+MRFt=I zO(PoR#T*~^dmG4@3NSDC*cny0ef##$ZuK!PD3E_)a0R*X*6a3`?=hvD?ijlH?3f0~ zgzi_G?Ll3Nx`P`mf-K0P5DK9Apz7bg*JmFa@zVX@I@^Z*{=j&_R<#&dnq7dZZ@2Jb zayT6QHIGsax%}lxW^C}Y=#28H4h}z*|2#$ z-sH=dR0ZO&*NQ|U)=EuHeN&jC)$jyQgp&N8w6@-J#Nkdgw+MHgGWN$=3CYP15TE9V z4VgN6de-H#rN%2zC{%C#n2dNJTP_P}7oIBBHR*A$F3t%HsU34wOIx4j3j~bB#y9TW zJKy@R2D+4JvY(VuBY?*F%Z`^Wsc2MBRl^q9{=YP}x0lE^HkhA3r*vU<*MftC zw{r%-=jXctqa#?-X_jDpwV?s&vJ;8PvjGBul&GjzSdBQhR_Bq}4(zDr&oRCA#yv_( zO8hSs-5LT+{|l4*KVs~vXQrpiU#`Xz;JCX5Naww2SW`#G_Dou3XRg%KSk%*Y@3TyH zk&kx14 zy)|}BV*v>43RFZ>-@OAJMU+GgC(;poBPou<}rbs`47xhRS=Ol`UDFG29wGBlV8Ts>Ifh?RaVjeJWko>E&{vRaJeG(>}o$ zMG=Jp+@pvo?aS64%$zA|hL%!%KCSlQb$^mQ3}0S8oNp|T;=+yDRo M07*qoM6N<$f_FmFNB{r; diff --git a/Telegram/Resources/icons/filters_unmuted_active@2x.png b/Telegram/Resources/icons/filters_unmuted_active@2x.png deleted file mode 100644 index a1ed122a89c9179de210815d6046e8677dba79fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 936 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*$9uXs zhEy=Vops);%2DL_aeLOZFm7?7mYd8|61J>NKe6=EqNV-;PTDUO*Mz_1=g=)loxP|` zaLGlj30|zs=kxtS7F5{YyPtpV%z6uE^Sz(vp0oTsv-teg*DOEl)`b@$r!>+alQ)pMB-{d#=$ zSZ3V+F`to$?wr$&No^RQ?^QR%(@m)D#>pwqznrdNX)l|0oZ*2Ao^Zfk$ z0KMsoGkv0ZL-o0SZ4O@Iyih z9q|i;RvtKWrbpLfkEDsME${Q^&+8;7JofkVOUTMP#ikPHz|PM8B;|jZ!^Vg|Hygyn z#cM^A_b=E`p)SN3=CNk`&%1YHmu9nGvz#W@>n8f^L0*n_L2d0@bIqFm{kwK0b;mci zGyHoP(fIRc<<+dM7C!tpuDD!1bf~GSvQqMedFi3D&ZU<#gAMz(3UjcCEY~-4SQ(P_ z=7>+1wzjs~S>78-%qgE6#h0&-XYBYKZ7;o`z`|z2syXVvgiU8xnmObMMw{t0a6Dl0 zU=U>Ch%~}VbuhGS`lw#y?d_fLIP=l<$L?QR?JS=q$y_*gjLpW@w)SY=ThZD@`D`A~ zj5Ma7cHDk@D_iOf!C&W|e_p0IlX1)D&CGx6>MhdLpSL<4R8cW*c=ztzoC6Y1L-HRR zvz@pRyXNxC1l!;Gw?2Q~e9@xk!nOT64DYo6Ous9=c+)|D`CrHW_N`vM+U?mA&Ch@J z(n>gGrHsouE$1+=&SrFZP|Eh=-@mw*zl<(6CdZnlH=H|nPHo=jmoHx)Nw#E@TK&Un zM`!NQ$AuXtQcrweJ$%SmutxrN+i^cT&BJ|+_ELB5-K%oiwR30V)~%+~3odN8c6cHE z-QeB(_lKW8Ro%01U!Ba?D%o$}zU91il-bRY^SgP%pZtt-U0?hg)f+md3OO)!C^(#$ fgTe~DWM4fV@pkE zUfNs}zUz5u9u;XO@+cX4xVr8ianJXibH4AKU(a{wp6<>HP)#TR08ntjIbJyM`2Q#i zIna_Qzu^M|M`O=p0f3egxxG+n06;p&#S!bB4qRss8{32&>mNdOtZ99-Iope+>BAX+gjRxvu+FR{LA^|U|#aaJJ&{?ITxqc ztjkMV8Jyj?ks%hB?38czqOTCDEOWDT*?=_4zZ&pW9UmOL>1iZUzqbJK#pBnaRg$0A)`D%2k{Us4tGU?WGKo~56#VeWxRk4l zi`R~&g@q5-bh2PM2WufLYigD~6N%`o?+p9IkByC;9vf3Ss-+bb5D;*#9${=8fW-nY zXJ_wQdt3}WL_C3#dAGI}Z);)EC(b_=!|m!?WH1jM$=I7@ZPKBsXfBEt=g+iGcHjL`( z=&-W2wuTzfr>{L}OHtxiPDCu%C0{3Bg=3iS|oC8MYF(IYto%AXIFlPi)p z)kAvEi-`tycG`LenNTS7wy_{2rl#Ja{*$KZ6%-VA%)aYTLqh`wE0s!WsH=0nt4>3V zp>nUHljM>Ji@7tat8s?zM0VUJw3Ek+&dA7E6N~%)n4UqEYYAs(p)h8xUQ=r`lc^R> z-J|yaRQjPjOXOB*dk)Z-K!8C?LvUx$=2C97Wgz>TD=UvO84S@-{=Bkl0+-9>`Lyi7 zWr-vb$?eX=z_=7WdhLt4x;jVSm%qdEgZ0{PEO@{i#&P`Y>aAXL(2|*S0bI+)i;;c#GtTq5%vk-csF|aaoW?ihrr(FcODSZC64{Gjx1(iKQ_{F}g&kA-2Ju zuQTpBX3tl3qj~32vDH=ga;YcF!Szt!Z;lRFIp9U)p$kVq5PBX>4fKGhT#Eq9D=SEw zfZciwa6Ymc2Aa@741mGI(Fi)EluWWyR+bH~ySr%jf%5A1eW!Y#c>ku54X|W;``3&R zGp63tbHl||GcwGwGQs=z?^`Yv{LO7{=Jzf%8(&Jxp@#$KXJ-q{fv;%m>j|c>-reh% zkz%q~gJd$frG;KkPD~u_W2CGBg9wD;ne;~O#>Phf^+?~uJSBY-u}lV5d&yby=FOXc z1jfrPq)`XkX-K$f%8`$5^Ot_s-X77G;G1M(Y8p5wIvTG0cZ0UwF(TiR(7DG~AE+erKbV@C(c76*K+saER1XU102nmhWLV1ob7%D&bCL3QxA`wQD_Dpwt+iOGF%F0SA@wIgrSnUQ8 zkP=j}M=^c}b$H#4Xr-mkK~5nM_EQ@l3Gnz`$#FrZNUTn$Gw0?AtC<$xuBw!yFt(}R zMIr}QWh2_sG7rw2ELJa{oSU2!loS{5tIsYh=qAR*@L{Wo_zLBaskWV+w1Zi{SgiRh z=)EP*8xZjJh-frAK0I8sdLnarDrtza9H?iWX>@IS;EWJ)n@V~*aRmvAnhSpnmo4{2 l56kEimu!R;Isa?%T`?jOT?b<4e>^y6fXi8T$0mCs?O*=Du_*um diff --git a/Telegram/Resources/icons/filters_unread_active.png b/Telegram/Resources/icons/filters_unread_active.png deleted file mode 100644 index 556111486ae50afdc40a8331a7c32322264fa5d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 677 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 AkN^Mx diff --git a/Telegram/SourceFiles/ui/filter_icons.cpp b/Telegram/SourceFiles/ui/filter_icons.cpp new file mode 100644 index 000000000..cbff50b6d --- /dev/null +++ b/Telegram/SourceFiles/ui/filter_icons.cpp @@ -0,0 +1,50 @@ +/* +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 "ui/filter_icons.h" + +#include "styles/style_filter_icons.h" + +namespace Ui { +namespace { + +const auto kIcons = std::vector{ + { &st::filtersAll, &st::filtersAllActive }, + { &st::filtersUnread, &st::filtersAllActive }, + { &st::filtersUnmuted, &st::filtersAllActive }, + { &st::filtersBots, &st::filtersAllActive }, + { &st::filtersChannels, &st::filtersChannelsActive }, + { &st::filtersGroups, &st::filtersGroupsActive }, + { &st::filtersPrivate, &st::filtersPrivateActive }, + { &st::filtersCustom, &st::filtersCustomActive }, + { &st::filtersSetup, &st::filtersSetup }, + { &st::foldersCat, &st::foldersCatActive }, + { &st::foldersCrown, &st::foldersCrownActive }, + { &st::foldersFavorite, &st::foldersFavoriteActive }, + { &st::foldersFlower, &st::foldersFlowerActive }, + { &st::foldersGame, &st::foldersGameActive }, + { &st::foldersHome, &st::foldersHomeActive }, + { &st::foldersLove, &st::foldersLoveActive }, + { &st::foldersMask, &st::foldersMaskActive }, + { &st::foldersParty, &st::foldersPartyActive }, + { &st::foldersSport, &st::foldersSportActive }, + { &st::foldersStudy, &st::foldersStudyActive }, + { &st::foldersTrade, &st::foldersTrade }, + { &st::foldersTravel, &st::foldersTravelActive }, + { &st::foldersWork, &st::foldersWorkActive }, +}; + +} // namespace + +const FilterIcons &LookupFilterIcon(FilterIcon icon) { + Expects(static_cast(icon) >= 0 + && static_cast(icon) < kIcons.size()); + + return kIcons[static_cast(icon)]; +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/filter_icons.h b/Telegram/SourceFiles/ui/filter_icons.h new file mode 100644 index 000000000..cbddff819 --- /dev/null +++ b/Telegram/SourceFiles/ui/filter_icons.h @@ -0,0 +1,52 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace style { +namespace internal { +class Icon; +} // namespace internal +} // namespace style + +namespace Ui { + +enum class FilterIcon : uchar { + All, + Unread, + Unmuted, + Bots, + Channels, + Groups, + Private, + Custom, + Setup, + + Cat, + Crown, + Favorite, + Flower, + Game, + Home, + Love, + Mask, + Party, + Sport, + Study, + Trade, + Travel, + Work, +}; + +struct FilterIcons { + not_null normal; + not_null active; +}; + +[[nodiscard]] const FilterIcons &LookupFilterIcon(FilterIcon icon); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/filter_icons.style b/Telegram/SourceFiles/ui/filter_icons.style new file mode 100644 index 000000000..7eed1b147 --- /dev/null +++ b/Telegram/SourceFiles/ui/filter_icons.style @@ -0,0 +1,54 @@ +/* +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 +*/ +using "ui/colors.palette"; + +filtersAll: icon {{ "filters/filters_all", sideBarIconFg }}; +filtersAllActive: icon {{ "filters/filters_all_active", sideBarIconFgActive }}; +filtersUnread: icon {{ "filters/filters_unread", sideBarIconFg }}; +filtersUnreadActive: icon {{ "filters/filters_unread_active", sideBarIconFgActive }}; +filtersUnmuted: icon {{ "filters/filters_unmuted", sideBarIconFg }}; +filtersUnmutedActive: icon {{ "filters/filters_unmuted_active", sideBarIconFgActive }}; +filtersBots: icon {{ "filters/filters_bots", sideBarIconFg }}; +filtersBotsActive: icon {{ "filters/filters_bots_active", sideBarIconFgActive }}; +filtersChannels: icon {{ "filters/filters_channels", sideBarIconFg }}; +filtersChannelsActive: icon {{ "filters/filters_channels_active", sideBarIconFgActive }}; +filtersGroups: icon {{ "filters/filters_groups", sideBarIconFg }}; +filtersGroupsActive: icon {{ "filters/filters_groups_active", sideBarIconFgActive }}; +filtersPrivate: icon {{ "filters/filters_private", sideBarIconFg }}; +filtersPrivateActive: icon {{ "filters/filters_private_active", sideBarIconFgActive }}; +filtersCustom: icon {{ "filters/filters_custom", sideBarIconFg }}; +filtersCustomActive: icon {{ "filters/filters_custom_active", sideBarIconFgActive }}; +filtersSetup: icon {{ "filters/filters_setup", sideBarIconFg }}; + +foldersCat: icon {{ "filters/folders_cat", sideBarIconFg }}; +foldersCatActive: icon {{ "filters/folders_cat_active", sideBarIconFg }}; +foldersCrown: icon {{ "filters/folders_crown", sideBarIconFg }}; +foldersCrownActive: icon {{ "filters/folders_crown_active", sideBarIconFgActive }}; +foldersFavorite: icon {{ "filters/folders_favorite", sideBarIconFg }}; +foldersFavoriteActive: icon {{ "filters/folders_favorite_active", sideBarIconFgActive }}; +foldersFlower: icon {{ "filters/folders_flower", sideBarIconFg }}; +foldersFlowerActive: icon {{ "filters/folders_flower_active", sideBarIconFgActive }}; +foldersGame: icon {{ "filters/folders_game", sideBarIconFg }}; +foldersGameActive: icon {{ "filters/folders_game_active", sideBarIconFgActive }}; +foldersHome: icon {{ "filters/folders_home", sideBarIconFg }}; +foldersHomeActive: icon {{ "filters/folders_home_active", sideBarIconFgActive }}; +foldersLove: icon {{ "filters/folders_love", sideBarIconFg }}; +foldersLoveActive: icon {{ "filters/folders_love_active", sideBarIconFgActive }}; +foldersMask: icon {{ "filters/folders_mask", sideBarIconFg }}; +foldersMaskActive: icon {{ "filters/folders_mask_active", sideBarIconFgActive }}; +foldersParty: icon {{ "filters/folders_party", sideBarIconFg }}; +foldersPartyActive: icon {{ "filters/folders_party_active", sideBarIconFgActive }}; +foldersSport: icon {{ "filters/folders_sport", sideBarIconFg }}; +foldersSportActive: icon {{ "filters/folders_sport_active", sideBarIconFgActive }}; +foldersStudy: icon {{ "filters/folders_study", sideBarIconFg }}; +foldersStudyActive: icon {{ "filters/folders_study_active", sideBarIconFgActive }}; +foldersTrade: icon {{ "filters/folders_trade", sideBarIconFg }}; +foldersTravel: icon {{ "filters/folders_travel", sideBarIconFg }}; +foldersTravelActive: icon {{ "filters/folders_travel_active", sideBarIconFgActive }}; +foldersWork: icon {{ "filters/folders_work", sideBarIconFg }}; +foldersWorkActive: icon {{ "filters/folders_work_active", sideBarIconFgActive }}; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 2800c0832..bb80c2de6 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -268,41 +268,6 @@ windowFiltersMainMenu: SideBarButton(windowFiltersButton) { iconPosition: point(-1px, -1px); minHeight: 54px; } -windowFiltersAll: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_all", sideBarIconFg }}; - iconActive: icon {{ "filters_all_active", sideBarIconFgActive }}; -} -windowFiltersUnread: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_unread", sideBarIconFg }}; - iconActive: icon {{ "filters_unread_active", sideBarIconFgActive }}; -} -windowFiltersUnmuted: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_unmuted", sideBarIconFg }}; - iconActive: icon {{ "filters_unmuted_active", sideBarIconFgActive }}; -} -windowFiltersBots: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_bots", sideBarIconFg }}; - iconActive: icon {{ "filters_bots_active", sideBarIconFgActive }}; -} -windowFiltersChannels: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_channels", sideBarIconFg }}; - iconActive: icon {{ "filters_channels_active", sideBarIconFgActive }}; -} -windowFiltersGroups: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_groups", sideBarIconFg }}; - iconActive: icon {{ "filters_groups_active", sideBarIconFgActive }}; -} -windowFiltersPrivate: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_private", sideBarIconFg }}; - iconActive: icon {{ "filters_private_active", sideBarIconFgActive }}; -} -windowFiltersCustom: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_custom", sideBarIconFg }}; - iconActive: icon {{ "filters_custom_active", sideBarIconFgActive }}; -} -windowFiltersSetup: SideBarButton(windowFiltersButton) { - icon: icon {{ "filters_setup", sideBarIconFg }}; -} windowFilterSmallItem: PeerListItem(defaultPeerListItem) { height: 44px; photoPosition: point(15px, 5px); diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index d9045a768..a0cbf3edb 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -15,23 +15,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat_filters.h" #include "boxes/filters/manage_filters_box.h" #include "lang/lang_keys.h" +#include "ui/filter_icons.h" #include "styles/style_widgets.h" #include "styles/style_window.h" namespace Window { namespace { -enum class Type { - Unread, - Unmuted, - People, - Groups, - Channels, - Bots, - Custom, -}; +using Icon = Ui::FilterIcon; -[[nodiscard]] Type ComputeType(const Data::ChatFilter &filter) { +[[nodiscard]] Icon ComputeIcon(const Data::ChatFilter &filter) { using Flag = Data::ChatFilter::Flag; const auto all = Flag::Contacts @@ -45,36 +38,23 @@ enum class Type { if (!filter.always().empty() || !filter.never().empty() || !(filter.flags() & all)) { - return Type::Custom; + return Icon::Custom; } else if ((filter.flags() & all) == Flag::Contacts || (filter.flags() & all) == Flag::NonContacts || (filter.flags() & all) == people) { - return Type::People; + return Icon::Private; } else if ((filter.flags() & all) == Flag::Groups) { - return Type::Groups; + return Icon::Groups; } else if ((filter.flags() & all) == Flag::Channels) { - return Type::Channels; + return Icon::Channels; } else if ((filter.flags() & all) == Flag::Bots) { - return Type::Bots; + return Icon::Bots; } else if ((filter.flags() & removed) == Flag::NoRead) { - return Type::Unread; + return Icon::Unread; } else if ((filter.flags() & removed) == Flag::NoMuted) { - return Type::Unmuted; + return Icon::Unmuted; } - return Type::Custom; -} - -[[nodiscard]] const style::SideBarButton &ComputeStyle(Type type) { - switch (type) { - case Type::Unread: return st::windowFiltersUnread; - case Type::Unmuted: return st::windowFiltersUnmuted; - case Type::People: return st::windowFiltersPrivate; - case Type::Groups: return st::windowFiltersGroups; - case Type::Channels: return st::windowFiltersChannels; - case Type::Bots: return st::windowFiltersBots; - case Type::Custom: return st::windowFiltersCustom; - } - Unexpected("Filter type in FiltersMenu::refresh."); + return Icon::Custom; } } // namespace @@ -108,7 +88,7 @@ void FiltersMenu::setup() { if (!fill.isEmpty()) { auto p = QPainter(&_outer); p.setPen(Qt::NoPen); - p.setBrush(st::windowFiltersAll.textBg); + p.setBrush(st::windowFiltersButton.textBg); p.drawRect(fill); } }, _outer.lifetime()); @@ -166,13 +146,15 @@ void FiltersMenu::refresh() { const auto prepare = [&]( FilterId id, const QString &title, - const style::SideBarButton &st, + Icon icon, const QString &badge) { auto button = base::unique_qptr(_container->add( object_ptr( _container, title, - st))); + st::windowFiltersButton))); + const auto &icons = Ui::LookupFilterIcon(icon); + button->setIconOverride(icons.normal, icons.active); if (id > 0) { const auto list = filters->chatsList(id); rpl::single(rpl::empty_value()) | rpl::then( @@ -200,15 +182,15 @@ void FiltersMenu::refresh() { }); now.emplace(id, std::move(button)); }; - prepare(0, tr::lng_filters_all(tr::now), st::windowFiltersAll, {}); + prepare(0, tr::lng_filters_all(tr::now), Icon::All, {}); for (const auto filter : filters->list()) { prepare( filter.id(), filter.title(), - ComputeStyle(ComputeType(filter)), + ComputeIcon(filter), QString()); } - prepare(-1, tr::lng_filters_setup(tr::now), st::windowFiltersSetup, {}); + prepare(-1, tr::lng_filters_setup(tr::now), Icon::Setup, {}); _filters = std::move(now); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 1f0a772b1..ec6744022 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 1f0a772b1841af3e8e47b594369cbbf04c760e9f +Subproject commit ec6744022c1a86f3bead192f9649c10dc424a98b From ce7621fbd9f880e54dfa35291f3d611edcdc0bf7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Mar 2020 16:06:36 +0400 Subject: [PATCH 051/115] Read and write special filter icon emoji. --- .../boxes/filters/edit_filter_box.cpp | 4 + .../SourceFiles/data/data_chat_filters.cpp | 17 +- Telegram/SourceFiles/data/data_chat_filters.h | 4 + Telegram/SourceFiles/ui/filter_icons.cpp | 154 +++++++++++++++--- Telegram/SourceFiles/ui/filter_icons.h | 3 + 5 files changed, 155 insertions(+), 27 deletions(-) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 0584c746f..f5c0a12cf 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -104,6 +104,7 @@ not_null SetupChatsPreview( *data = Data::ChatFilter( data->id(), data->title(), + data->iconEmoji(), (data->flags() & ~flag), data->always(), data->pinned(), @@ -121,6 +122,7 @@ not_null SetupChatsPreview( *data = Data::ChatFilter( data->id(), data->title(), + data->iconEmoji(), data->flags(), std::move(always), std::move(pinned), @@ -295,6 +297,7 @@ void EditExceptions( *data = Data::ChatFilter( data->id(), data->title(), + data->iconEmoji(), (data->flags() & ~options) | rawController->chosenOptions(), include ? std::move(changed) : std::move(removeFrom), std::move(pinned), @@ -415,6 +418,7 @@ void EditFilterBox( const auto result = Data::ChatFilter( data->id(), title, + data->iconEmoji(), data->flags(), data->always(), data->pinned(), diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 0042cb5de..ca9899453 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -23,12 +23,14 @@ namespace Data { ChatFilter::ChatFilter( FilterId id, const QString &title, + const QString &iconEmoji, Flags flags, base::flat_set> always, std::vector> pinned, base::flat_set> never) : _id(id) , _title(title) +, _iconEmoji(iconEmoji) , _always(std::move(always)) , _pinned(std::move(pinned)) , _never(std::move(never)) @@ -87,6 +89,7 @@ ChatFilter ChatFilter::FromTL( return ChatFilter( data.vid().v, qs(data.vtitle()), + qs(data.vemoticon().value_or_empty()), flags, std::move(list), std::move(pinned), @@ -128,7 +131,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { MTP_flags(flags), MTP_int(replaceId ? replaceId : _id), MTP_string(_title), - MTPstring(), // emoticon + MTP_string(_iconEmoji), MTP_vector(pinned), MTP_vector(include), MTP_vector(never)); @@ -142,6 +145,10 @@ QString ChatFilter::title() const { return _title; } +QString ChatFilter::iconEmoji() const { + return _iconEmoji; +} + ChatFilter::Flags ChatFilter::flags() const { return _flags; } @@ -261,7 +268,8 @@ void ChatFilters::load(bool force) { applyRemove(position); changed = true; } - if (changed) { + if (changed || !_loaded) { + _loaded = true; _listChanged.fire({}); } _loadRequestId = 0; @@ -308,7 +316,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) { _list.insert( begin(_list) + position, - ChatFilter(filter.id(), {}, {}, {}, {}, {})); + ChatFilter(filter.id(), {}, {}, {}, {}, {}, {})); applyChange(*(begin(_list) + position), std::move(filter)); } @@ -325,7 +333,7 @@ void ChatFilters::applyRemove(int position) { Expects(position >= 0 && position < _list.size()); const auto i = begin(_list) + position; - applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {})); + applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {})); _list.erase(i); } @@ -434,6 +442,7 @@ const ChatFilter &ChatFilters::applyUpdatedPinned( set(ChatFilter( id, i->title(), + i->iconEmoji(), i->flags(), std::move(always), std::move(pinned), diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 02a749b06..9ad279fa6 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -41,6 +41,7 @@ public: ChatFilter( FilterId id, const QString &title, + const QString &iconEmoji, Flags flags, base::flat_set> always, std::vector> pinned, @@ -53,6 +54,7 @@ public: [[nodiscard]] FilterId id() const; [[nodiscard]] QString title() const; + [[nodiscard]] QString iconEmoji() const; [[nodiscard]] Flags flags() const; [[nodiscard]] const base::flat_set> &always() const; [[nodiscard]] const std::vector> &pinned() const; @@ -63,6 +65,7 @@ public: private: FilterId _id = 0; QString _title; + QString _iconEmoji; base::flat_set> _always; std::vector> _pinned; base::flat_set> _never; @@ -119,6 +122,7 @@ private: rpl::event_stream<> _listChanged; rpl::event_stream> _refreshHistoryRequests; mtpRequestId _loadRequestId = 0; + bool _loaded = false; }; diff --git a/Telegram/SourceFiles/ui/filter_icons.cpp b/Telegram/SourceFiles/ui/filter_icons.cpp index cbff50b6d..eab770ac5 100644 --- a/Telegram/SourceFiles/ui/filter_icons.cpp +++ b/Telegram/SourceFiles/ui/filter_icons.cpp @@ -7,35 +7,128 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/filter_icons.h" +#include "ui/emoji_config.h" #include "styles/style_filter_icons.h" namespace Ui { namespace { const auto kIcons = std::vector{ - { &st::filtersAll, &st::filtersAllActive }, - { &st::filtersUnread, &st::filtersAllActive }, - { &st::filtersUnmuted, &st::filtersAllActive }, - { &st::filtersBots, &st::filtersAllActive }, - { &st::filtersChannels, &st::filtersChannelsActive }, - { &st::filtersGroups, &st::filtersGroupsActive }, - { &st::filtersPrivate, &st::filtersPrivateActive }, - { &st::filtersCustom, &st::filtersCustomActive }, - { &st::filtersSetup, &st::filtersSetup }, - { &st::foldersCat, &st::foldersCatActive }, - { &st::foldersCrown, &st::foldersCrownActive }, - { &st::foldersFavorite, &st::foldersFavoriteActive }, - { &st::foldersFlower, &st::foldersFlowerActive }, - { &st::foldersGame, &st::foldersGameActive }, - { &st::foldersHome, &st::foldersHomeActive }, - { &st::foldersLove, &st::foldersLoveActive }, - { &st::foldersMask, &st::foldersMaskActive }, - { &st::foldersParty, &st::foldersPartyActive }, - { &st::foldersSport, &st::foldersSportActive }, - { &st::foldersStudy, &st::foldersStudyActive }, - { &st::foldersTrade, &st::foldersTrade }, - { &st::foldersTravel, &st::foldersTravelActive }, - { &st::foldersWork, &st::foldersWorkActive }, + { + &st::filtersAll, + &st::filtersAllActive, + "\xF0\x9F\x92\xAC"_cs.utf16() + }, + { + &st::filtersUnread, + &st::filtersUnreadActive, + "\xE2\x9C\x85"_cs.utf16() + }, + { + &st::filtersUnmuted, + &st::filtersUnmutedActive, + "\xF0\x9F\x94\x94"_cs.utf16() + }, + { + &st::filtersBots, + &st::filtersBotsActive, + "\xF0\x9F\xA4\x96"_cs.utf16() + }, + { + &st::filtersChannels, + &st::filtersChannelsActive, + "\xF0\x9F\x93\xA2"_cs.utf16() + }, + { + &st::filtersGroups, + &st::filtersGroupsActive, + "\xF0\x9F\x91\xA5"_cs.utf16() + }, + { + &st::filtersPrivate, + &st::filtersPrivateActive, + "\xF0\x9F\x91\xA4"_cs.utf16() + }, + { + &st::filtersCustom, + &st::filtersCustomActive, + "\xF0\x9F\x93\x81"_cs.utf16() + }, + { + &st::filtersSetup, + &st::filtersSetup, + "\xF0\x9F\x93\x8B"_cs.utf16() + }, + { + &st::foldersCat, + &st::foldersCatActive, + "\xF0\x9F\x90\x88"_cs.utf16() + }, + { + &st::foldersCrown, + &st::foldersCrownActive, + "\xF0\x9F\x91\x91"_cs.utf16() + }, + { + &st::foldersFavorite, + &st::foldersFavoriteActive, + "\xE2\xAD\x90\xEF\xB8\x8F"_cs.utf16() + }, + { + &st::foldersFlower, + &st::foldersFlowerActive, + "\xF0\x9F\x8C\xB9"_cs.utf16() + }, + { + &st::foldersGame, + &st::foldersGameActive, + "\xF0\x9F\x8E\xAE"_cs.utf16() + }, + { + &st::foldersHome, + &st::foldersHomeActive, + "\xF0\x9F\x8F\xA0"_cs.utf16() + }, + { + &st::foldersLove, + &st::foldersLoveActive, + "\xE2\x9D\xA4\xEF\xB8\x8F"_cs.utf16() + }, + { + &st::foldersMask, + &st::foldersMaskActive, + "\xF0\x9F\x8E\xAD"_cs.utf16() + }, + { + &st::foldersParty, + &st::foldersPartyActive, + "\xF0\x9F\x8D\xB8"_cs.utf16() + }, + { + &st::foldersSport, + &st::foldersSportActive, + "\xE2\x9A\xBD\xEF\xB8\x8F"_cs.utf16() + }, + { + &st::foldersStudy, + &st::foldersStudyActive, + "\xF0\x9F\x8E\x93"_cs.utf16() + }, + { + &st::foldersTrade, + &st::foldersTrade, + "\xF0\x9F\x93\x88"_cs.utf16() + }, + { + &st::foldersTravel, + &st::foldersTravelActive, + "\xE2\x9C\x88\xEF\xB8\x8F"_cs.utf16() + }, + { + &st::foldersWork, + &st::foldersWorkActive, + "\xF0\x9F\x92\xBC"_cs.utf16() + }, }; } // namespace @@ -47,4 +140,19 @@ const FilterIcons &LookupFilterIcon(FilterIcon icon) { return kIcons[static_cast(icon)]; } +std::optional LookupFilterIconByEmoji(const QString &emoji) { + static const auto kMap = [] { + auto result = base::flat_map(); + auto index = 0; + for (const auto &entry : kIcons) { + const auto emoji = Ui::Emoji::Find(entry.emoji); + Assert(emoji != nullptr); + result.emplace(emoji, static_cast(index++)); + } + return result; + }(); + const auto i = kMap.find(Ui::Emoji::Find(emoji)); + return (i != end(kMap)) ? std::make_optional(i->second) : std::nullopt; +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/filter_icons.h b/Telegram/SourceFiles/ui/filter_icons.h index cbddff819..d687942cc 100644 --- a/Telegram/SourceFiles/ui/filter_icons.h +++ b/Telegram/SourceFiles/ui/filter_icons.h @@ -45,8 +45,11 @@ enum class FilterIcon : uchar { struct FilterIcons { not_null normal; not_null active; + QString emoji; }; [[nodiscard]] const FilterIcons &LookupFilterIcon(FilterIcon icon); +[[nodiscard]] std::optional LookupFilterIconByEmoji( + const QString &emoji); } // namespace Ui From c4a0bc1fd57832cbbd2359d23c4a9c284cb2ff3f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 19 Mar 2020 18:55:17 +0400 Subject: [PATCH 052/115] Allow selecting custom filter icons. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 1 + .../boxes/filters/edit_filter_box.cpp | 202 ++++++-- .../SourceFiles/data/data_chat_filters.cpp | 5 +- Telegram/SourceFiles/data/data_chat_filters.h | 1 + Telegram/SourceFiles/ui/filter_icon_panel.cpp | 455 ++++++++++++++++++ Telegram/SourceFiles/ui/filter_icon_panel.h | 87 ++++ Telegram/SourceFiles/ui/filter_icons.cpp | 42 +- Telegram/SourceFiles/ui/filter_icons.h | 8 + Telegram/SourceFiles/window/window.style | 9 + .../window/window_filters_menu.cpp | 35 +- 11 files changed, 766 insertions(+), 81 deletions(-) create mode 100644 Telegram/SourceFiles/ui/filter_icon_panel.cpp create mode 100644 Telegram/SourceFiles/ui/filter_icon_panel.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 128852143..674627575 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -924,6 +924,8 @@ PRIVATE ui/empty_userpic.h ui/filter_icons.cpp ui/filter_icons.h + ui/filter_icon_panel.cpp + ui/filter_icon_panel.h ui/grouped_layout.cpp ui/grouped_layout.h ui/resize_area.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 986075548..942341bf2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2277,6 +2277,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_type_no_archived" = "Archived"; "lng_filters_type_no_muted" = "Muted"; "lng_filters_type_no_read" = "Read"; +"lng_filters_icon_header" = "Choose icon"; // Wnd specific diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index f5c0a12cf..25d486e2d 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -12,10 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" +#include "ui/effects/panel_animation.h" +#include "ui/filter_icons.h" +#include "ui/filter_icon_panel.h" #include "data/data_chat_filters.h" #include "data/data_peer.h" #include "data/data_session.h" #include "settings/settings_common.h" +#include "base/event_filter.h" #include "lang/lang_keys.h" #include "history/history.h" #include "main/main_session.h" @@ -91,39 +95,42 @@ private: not_null SetupChatsPreview( not_null content, - not_null data, + not_null*> data, Flags flags, ExceptionPeersGetter peers) { + const auto rules = data->current(); const auto preview = content->add(object_ptr( content, - data->flags() & flags, - (data->*peers)())); + rules.flags() & flags, + (rules.*peers)())); preview->flagRemoved( ) | rpl::start_with_next([=](Flag flag) { + const auto rules = data->current(); *data = Data::ChatFilter( - data->id(), - data->title(), - data->iconEmoji(), - (data->flags() & ~flag), - data->always(), - data->pinned(), - data->never()); + rules.id(), + rules.title(), + rules.iconEmoji(), + (rules.flags() & ~flag), + rules.always(), + rules.pinned(), + rules.never()); }, preview->lifetime()); preview->peerRemoved( ) | rpl::start_with_next([=](not_null history) { - auto always = data->always(); - auto pinned = data->pinned(); - auto never = data->never(); + const auto rules = data->current(); + auto always = rules.always(); + auto pinned = rules.pinned(); + auto never = rules.never(); always.remove(history); pinned.erase(ranges::remove(pinned, history), end(pinned)); never.remove(history); *data = Data::ChatFilter( - data->id(), - data->title(), - data->iconEmoji(), - data->flags(), + rules.id(), + rules.title(), + rules.iconEmoji(), + rules.flags(), std::move(always), std::move(pinned), std::move(never)); @@ -261,21 +268,23 @@ void EditExceptions( not_null window, not_null context, Flags options, - not_null data, + not_null*> data, Fn refresh) { const auto include = (options & Flag::Contacts) != Flags(0); + const auto rules = data->current(); auto controller = std::make_unique( window, (include ? tr::lng_filters_include_title() : tr::lng_filters_exclude_title()), options, - data->flags() & options, - include ? data->always() : data->never()); + rules.flags() & options, + include ? rules.always() : rules.never()); const auto rawController = controller.get(); auto initBox = [=](not_null box) { box->addButton(tr::lng_settings_save(), crl::guard(context, [=] { const auto peers = box->peerListCollectSelectedRows(); + const auto rules = data->current(); auto &&histories = ranges::view::all( peers ) | ranges::view::transform([=](not_null peer) { @@ -285,20 +294,22 @@ void EditExceptions( histories.begin(), histories.end() }; - auto removeFrom = include ? data->never() : data->always(); + auto removeFrom = include ? rules.never() : rules.always(); for (const auto &history : changed) { removeFrom.remove(history); } - auto pinned = data->pinned(); - pinned.erase(ranges::remove_if(pinned, [&](not_null history) { + auto pinned = rules.pinned(); + pinned.erase(ranges::remove_if(pinned, [&]( + not_null history) { const auto contains = changed.contains(history); return include ? !contains : contains; }), end(pinned)); *data = Data::ChatFilter( - data->id(), - data->title(), - data->iconEmoji(), - (data->flags() & ~options) | rawController->chosenOptions(), + rules.id(), + rules.title(), + rules.iconEmoji(), + ((rules.flags() & ~options) + | rawController->chosenOptions()), include ? std::move(changed) : std::move(removeFrom), std::move(pinned), include ? std::move(removeFrom) : std::move(changed)); @@ -314,6 +325,94 @@ void EditExceptions( Ui::LayerOption::KeepOther); } +[[nodiscard]] void CreateIconSelector( + not_null outer, + not_null box, + not_null parent, + not_null input, + not_null*> data) { + const auto rules = data->current(); + const auto toggle = Ui::CreateChild(parent.get()); + toggle->resize(st::windowFilterIconToggleSize); + + const auto type = toggle->lifetime().make_state(); + data->value( + ) | rpl::map([=](const Data::ChatFilter &filter) { + return Ui::ComputeFilterIcon(filter); + }) | rpl::start_with_next([=](Ui::FilterIcon icon) { + *type = icon; + toggle->update(); + }, toggle->lifetime()); + + input->geometryValue( + ) | rpl::start_with_next([=](QRect geometry) { + const auto left = geometry.x() + geometry.width() - toggle->width(); + const auto position = st::windowFilterIconTogglePosition; + toggle->move( + left - position.x(), + geometry.y() + position.y()); + }, toggle->lifetime()); + + toggle->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(toggle); + const auto icons = Ui::LookupFilterIcon(*type); + icons.normal->paintInCenter(p, toggle->rect(), st::emojiIconFg->c); + }, toggle->lifetime()); + + const auto panel = toggle->lifetime().make_state( + outer); + toggle->installEventFilter(panel); + toggle->addClickHandler([=] { + panel->toggleAnimated(); + }); + panel->chosen( + ) | rpl::filter([=](Ui::FilterIcon icon) { + return icon != Ui::ComputeFilterIcon(data->current()); + }) | rpl::start_with_next([=](Ui::FilterIcon icon) { + panel->hideAnimated(); + const auto rules = data->current(); + *data = Data::ChatFilter( + rules.id(), + rules.title(), + Ui::LookupFilterIcon(icon).emoji, + rules.flags(), + rules.always(), + rules.pinned(), + rules.never()); + }, panel->lifetime()); + + const auto updatePanelGeometry = [=] { + const auto global = toggle->mapToGlobal({ + toggle->width(), + toggle->height() + }); + const auto local = outer->mapFromGlobal(global); + const auto position = st::windwoFilterIconPanelPosition; + const auto padding = panel->innerPadding(); + panel->move( + local.x() - panel->width() + position.x() + padding.right(), + local.y() + position.y() - padding.top()); + }; + + const auto filterForGeometry = [=](not_null event) { + const auto type = event->type(); + if (type == QEvent::Move || type == QEvent::Resize) { + // updatePanelGeometry uses not only container geometry, but + // also container children geometries that will be updated later. + crl::on_main(panel, [=] { updatePanelGeometry(); }); + } + return base::EventFilterResult::Continue; + }; + + const auto installFilterForGeometry = [&](not_null target) { + panel->lifetime().make_state>( + base::install_event_filter(target, filterForGeometry)); + }; + installFilterForGeometry(outer); + installFilterForGeometry(box); +} + } // namespace void EditFilterBox( @@ -323,18 +422,28 @@ void EditFilterBox( Fn doneCallback) { const auto creating = filter.title().isEmpty(); box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit()); + box->setCloseByOutsideClick(false); + + using State = rpl::variable; + const auto data = box->lifetime().make_state(filter); const auto content = box->verticalLayout(); const auto name = content->add( object_ptr( box, - st::defaultInputField, + st::windowFilterNameInput, tr::lng_filters_new_name(), - filter.title()), + data->current().title()), st::markdownLinkFieldPadding); name->setMaxLength(kMaxFilterTitleLength); - const auto data = box->lifetime().make_state(filter); + const auto outer = box->getDelegate()->outerContainer(); + CreateIconSelector( + outer, + box, + content, + name, + data); constexpr auto kTypes = Flag::Contacts | Flag::NonContacts @@ -391,8 +500,12 @@ void EditFilterBox( st::settingsDividerLabelPadding); const auto refreshPreviews = [=] { - include->updateData(data->flags() & kTypes, data->always()); - exclude->updateData(data->flags() & kExcludeTypes, data->never()); + include->updateData( + data->current().flags() & kTypes, + data->current().always()); + exclude->updateData( + data->current().flags() & kExcludeTypes, + data->current().never()); }; includeAdd->setClickedCallback([=] { EditExceptions(window, box, kTypes, data, refreshPreviews); @@ -403,26 +516,27 @@ void EditFilterBox( const auto save = [=] { const auto title = name->getLastText().trimmed(); + const auto rules = data->current(); + const auto result = Data::ChatFilter( + rules.id(), + title, + rules.iconEmoji(), + rules.flags(), + rules.always(), + rules.pinned(), + rules.never()); if (title.isEmpty()) { name->showError(); return; - } else if (!(data->flags() & kTypes) && data->always().empty()) { + } else if (!(rules.flags() & kTypes) && rules.always().empty()) { window->window().showToast(tr::lng_filters_empty(tr::now)); return; - } else if ((data->flags() == (kTypes | Flag::NoArchived)) - && data->always().empty() - && data->never().empty()) { + } else if ((rules.flags() == (kTypes | Flag::NoArchived)) + && rules.always().empty() + && rules.never().empty()) { window->window().showToast(tr::lng_filters_default(tr::now)); return; } - const auto result = Data::ChatFilter( - data->id(), - title, - data->iconEmoji(), - data->flags(), - data->always(), - data->pinned(), - data->never()); box->closeBox(); doneCallback(result); diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index ca9899453..e4f28f2c6 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -128,7 +128,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { never.push_back(history->peer->input); } return MTP_dialogFilter( - MTP_flags(flags), + MTP_flags(flags | TLFlag::f_emoticon), MTP_int(replaceId ? replaceId : _id), MTP_string(_title), MTP_string(_iconEmoji), @@ -344,7 +344,8 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { const auto pinnedChanged = (filter.pinned() != updated.pinned()); if (!rulesChanged && !pinnedChanged - && filter.title() == updated.title()) { + && filter.title() == updated.title() + && filter.iconEmoji() == updated.iconEmoji()) { return false; } if (rulesChanged) { diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 9ad279fa6..8e4cd95cd 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -75,6 +75,7 @@ private: inline bool operator==(const ChatFilter &a, const ChatFilter &b) { return (a.title() == b.title()) + && (a.iconEmoji() == b.iconEmoji()) && (a.flags() == b.flags()) && (a.always() == b.always()) && (a.never() == b.never()); diff --git a/Telegram/SourceFiles/ui/filter_icon_panel.cpp b/Telegram/SourceFiles/ui/filter_icon_panel.cpp new file mode 100644 index 000000000..0748d2e93 --- /dev/null +++ b/Telegram/SourceFiles/ui/filter_icon_panel.cpp @@ -0,0 +1,455 @@ +/* +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 "ui/filter_icon_panel.h" + +#include "ui/widgets/shadow.h" +#include "ui/image/image_prepare.h" +#include "ui/effects/panel_animation.h" +#include "ui/ui_utility.h" +#include "ui/filter_icons.h" +#include "lang/lang_keys.h" +#include "core/application.h" +#include "app.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_window.h" + +namespace Ui { +namespace { + +constexpr auto kHideTimeoutMs = crl::time(300); +constexpr auto kDelayedHideTimeoutMs = 3 * crl::time(1000); +constexpr auto kIconsPerRow = 6; + +constexpr auto kIcons = std::array{ + FilterIcon::Cat, + FilterIcon::Crown, + FilterIcon::Favorite, + FilterIcon::Flower, + FilterIcon::Game, + FilterIcon::Home, + FilterIcon::Love, + FilterIcon::Mask, + FilterIcon::Party, + FilterIcon::Sport, + FilterIcon::Study, + FilterIcon::Trade, + FilterIcon::Travel, + FilterIcon::Work, + + FilterIcon::All, + FilterIcon::Unread, + FilterIcon::Unmuted, + FilterIcon::Bots, + FilterIcon::Channels, + FilterIcon::Groups, + FilterIcon::Private, + FilterIcon::Custom, + FilterIcon::Setup, +}; + +} // namespace + +FilterIconPanel::FilterIconPanel(QWidget *parent) +: RpWidget(parent) +, _inner(Ui::CreateChild(this)) { + setup(); +} + +FilterIconPanel::~FilterIconPanel() { + hideFast(); +} + +rpl::producer FilterIconPanel::chosen() const { + return _chosen.events(); +} + +void FilterIconPanel::setup() { + setupInner(); + resize(_inner->rect().marginsAdded(innerPadding()).size()); + _inner->move(innerRect().topLeft()); + + _hideTimer.setCallback([=] { hideByTimerOrLeave(); }); + + macWindowDeactivateEvents( + ) | rpl::filter([=] { + return !isHidden(); + }) | rpl::start_with_next([=] { + hideAnimated(); + }, lifetime()); + + setAttribute(Qt::WA_OpaquePaintEvent, false); + + hideChildren(); + hide(); +} + +void FilterIconPanel::setupInner() { + const auto count = kIcons.size(); + const auto rows = (count / kIconsPerRow) + + ((count % kIconsPerRow) ? 1 : 0); + const auto single = st::windowFilterIconSingle; + const auto size = QSize( + single.width() * kIconsPerRow, + single.height() * rows); + const auto full = QRect(QPoint(), size).marginsAdded( + st::windowFilterIconPadding).size(); + _inner->resize(full); + + _inner->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + auto p = Painter(_inner); + App::roundRect( + p, + _inner->rect(), + st::emojiPanBg, + ImageRoundRadius::Small); + p.setFont(st::emojiPanHeaderFont); + p.setPen(st::emojiPanHeaderFg); + p.drawTextLeft( + st::windowFilterIconHeaderPosition.x(), + st::windowFilterIconHeaderPosition.y(), + _inner->width(), + tr::lng_filters_icon_header(tr::now)); + + const auto selected = (_pressed >= 0) ? _pressed : _selected; + for (auto i = 0; i != kIcons.size(); ++i) { + const auto rect = countRect(i); + if (!rect.intersects(clip)) { + continue; + } + if (i == selected) { + App::roundRect( + p, + rect, + st::emojiPanHover, + StickerHoverCorners); + } + const auto icon = LookupFilterIcon(kIcons[i]).normal; + icon->paintInCenter(p, rect, st::emojiIconFg->c); + } + }, _inner->lifetime()); + + _inner->setMouseTracking(true); + _inner->events( + ) | rpl::start_with_next([=](not_null e) { + switch (e->type()) { + case QEvent::Leave: setSelected(-1); break; + case QEvent::MouseMove: + mouseMove(static_cast(e.get())->pos()); + break; + case QEvent::MouseButtonPress: + mousePress(static_cast(e.get())->button()); + break; + case QEvent::MouseButtonRelease: + mouseRelease(static_cast(e.get())->button()); + break; + } + }, _inner->lifetime()); +} + +void FilterIconPanel::setSelected(int selected) { + if (_selected == selected) { + return; + } + const auto was = (_selected >= 0); + updateRect(_selected); + _selected = selected; + updateRect(_selected); + const auto now = (_selected >= 0); + if (was != now) { + _inner->setCursor(now ? style::cur_pointer : style::cur_default); + } +} + +void FilterIconPanel::setPressed(int pressed) { + if (_pressed == pressed) { + return; + } + updateRect(_pressed); + _pressed = pressed; + updateRect(_pressed); +} + +QRect FilterIconPanel::countRect(int index) const { + Expects(index >= 0); + + const auto row = index / kIconsPerRow; + const auto column = index % kIconsPerRow; + const auto single = st::windowFilterIconSingle; + const auto rect = QRect( + QPoint(column * single.width(), row * single.height()), + single); + const auto padding = st::windowFilterIconPadding; + return rect.translated(padding.left(), padding.top()); +} + +void FilterIconPanel::updateRect(int index) { + if (index < 0) { + return; + } + _inner->update(countRect(index)); +} + +void FilterIconPanel::mouseMove(QPoint position) { + const auto padding = st::windowFilterIconPadding; + if (!_inner->rect().marginsRemoved(padding).contains(position)) { + setSelected(-1); + } else { + const auto point = position - QPoint(padding.left(), padding.top()); + const auto column = point.x() / st::windowFilterIconSingle.width(); + const auto row = point.y() / st::windowFilterIconSingle.height(); + const auto index = row * kIconsPerRow + column; + setSelected(index < kIcons.size() ? index : -1); + } +} + +void FilterIconPanel::mousePress(Qt::MouseButton button) { + if (button != Qt::LeftButton) { + return; + } + setPressed(_selected); +} + +void FilterIconPanel::mouseRelease(Qt::MouseButton button) { + if (button != Qt::LeftButton) { + return; + } + const auto pressed = _pressed; + setPressed(-1); + if (pressed == _selected && pressed >= 0) { + Assert(pressed < kIcons.size()); + _chosen.fire_copy(kIcons[pressed]); + } +} + +void FilterIconPanel::paintEvent(QPaintEvent *e) { + Painter p(this); + + // This call can finish _a_show animation and destroy _showAnimation. + const auto opacityAnimating = _a_opacity.animating(); + + const auto showAnimating = _a_show.animating(); + if (_showAnimation && !showAnimating) { + _showAnimation.reset(); + if (!opacityAnimating) { + showChildren(); + } + } + + if (showAnimating) { + Assert(_showAnimation != nullptr); + if (auto opacity = _a_opacity.value(_hiding ? 0. : 1.)) { + _showAnimation->paintFrame( + p, + 0, + 0, + width(), + _a_show.value(1.), + opacity); + } + } else if (opacityAnimating) { + p.setOpacity(_a_opacity.value(_hiding ? 0. : 1.)); + p.drawPixmap(0, 0, _cache); + } else if (_hiding || isHidden()) { + hideFinished(); + } else { + if (!_cache.isNull()) _cache = QPixmap(); + Ui::Shadow::paint( + p, + innerRect(), + width(), + st::emojiPanAnimation.shadow); + } +} + +void FilterIconPanel::enterEventHook(QEvent *e) { + Core::App().registerLeaveSubscription(this); + showAnimated(); +} + +void FilterIconPanel::leaveEventHook(QEvent *e) { + Core::App().unregisterLeaveSubscription(this); + if (_a_show.animating() || _a_opacity.animating()) { + hideAnimated(); + } else { + _hideTimer.callOnce(kHideTimeoutMs); + } + return TWidget::leaveEventHook(e); +} + +void FilterIconPanel::otherEnter() { + showAnimated(); +} + +void FilterIconPanel::otherLeave() { + if (_a_opacity.animating()) { + hideByTimerOrLeave(); + } else { + _hideTimer.callOnce(0); + } +} + +void FilterIconPanel::hideFast() { + if (isHidden()) return; + + _hideTimer.cancel(); + _hiding = false; + _a_opacity.stop(); + hideFinished(); +} + +void FilterIconPanel::opacityAnimationCallback() { + update(); + if (!_a_opacity.animating()) { + if (_hiding) { + _hiding = false; + hideFinished(); + } else if (!_a_show.animating()) { + showChildren(); + } + } +} + +void FilterIconPanel::hideByTimerOrLeave() { + if (isHidden()) { + return; + } + + hideAnimated(); +} + +void FilterIconPanel::prepareCacheFor(bool hiding) { + if (_a_opacity.animating()) { + return; + } + + auto showAnimation = base::take(_a_show); + auto showAnimationData = base::take(_showAnimation); + _hiding = false; + showChildren(); + + _cache = Ui::GrabWidget(this); + + _a_show = base::take(showAnimation); + _showAnimation = base::take(showAnimationData); + _hiding = hiding; + if (_a_show.animating()) { + hideChildren(); + } +} + +void FilterIconPanel::startOpacityAnimation(bool hiding) { + prepareCacheFor(hiding); + hideChildren(); + _a_opacity.start( + [=] { opacityAnimationCallback(); }, + _hiding ? 1. : 0., + _hiding ? 0. : 1., + st::emojiPanDuration); +} + +void FilterIconPanel::startShowAnimation() { + if (!_a_show.animating()) { + auto image = grabForAnimation(); + + _showAnimation = std::make_unique(st::emojiPanAnimation, Ui::PanelAnimation::Origin::TopRight); + auto inner = rect().marginsRemoved(st::emojiPanMargins); + _showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor())); + _showAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small)); + _showAnimation->start(); + } + hideChildren(); + _a_show.start([this] { update(); }, 0., 1., st::emojiPanShowDuration); +} + +QImage FilterIconPanel::grabForAnimation() { + auto cache = base::take(_cache); + auto opacityAnimation = base::take(_a_opacity); + auto showAnimationData = base::take(_showAnimation); + auto showAnimation = base::take(_a_show); + + showChildren(); + Ui::SendPendingMoveResizeEvents(this); + + auto result = QImage( + size() * cIntRetinaFactor(), + QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + if (_inner) { + QPainter p(&result); + Ui::RenderWidget(p, _inner, _inner->pos()); + } + + _a_show = base::take(showAnimation); + _showAnimation = base::take(showAnimationData); + _a_opacity = base::take(opacityAnimation); + _cache = base::take(_cache); + + return result; +} + +void FilterIconPanel::hideAnimated() { + if (isHidden() || _hiding) { + return; + } + + _hideTimer.cancel(); + startOpacityAnimation(true); +} + +void FilterIconPanel::toggleAnimated() { + if (isHidden() || _hiding || _hideAfterSlide) { + showAnimated(); + } else { + hideAnimated(); + } +} + +void FilterIconPanel::hideFinished() { + hide(); + _a_show.stop(); + _showAnimation.reset(); + _cache = QPixmap(); + _hiding = false; +} + +void FilterIconPanel::showAnimated() { + _hideTimer.cancel(); + _hideAfterSlide = false; + showStarted(); +} + +void FilterIconPanel::showStarted() { + if (isHidden()) { + raise(); + show(); + startShowAnimation(); + } else if (_hiding) { + startOpacityAnimation(false); + } +} + +bool FilterIconPanel::eventFilter(QObject *obj, QEvent *e) { + if (e->type() == QEvent::Enter) { + otherEnter(); + } else if (e->type() == QEvent::Leave) { + otherLeave(); + } + return false; +} + +style::margins FilterIconPanel::innerPadding() const { + return st::emojiPanMargins; +} + +QRect FilterIconPanel::innerRect() const { + return rect().marginsRemoved(innerPadding()); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/filter_icon_panel.h b/Telegram/SourceFiles/ui/filter_icon_panel.h new file mode 100644 index 000000000..1606166a8 --- /dev/null +++ b/Telegram/SourceFiles/ui/filter_icon_panel.h @@ -0,0 +1,87 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/timer.h" +#include "ui/rp_widget.h" +#include "ui/effects/animations.h" + +namespace Ui { + +enum class FilterIcon : uchar; +class PanelAnimation; + +class FilterIconPanel final : public Ui::RpWidget { +public: + FilterIconPanel(QWidget *parent); + ~FilterIconPanel(); + + void hideFast(); + [[nodiscard]] bool hiding() const { + return _hiding || _hideTimer.isActive(); + } + + [[nodiscard]] style::margins innerPadding() const; + + void showAnimated(); + void hideAnimated(); + void toggleAnimated(); + + [[nodiscard]] rpl::producer chosen() const; + +private: + void enterEventHook(QEvent *e) override; + void leaveEventHook(QEvent *e) override; + void otherEnter(); + void otherLeave(); + + void paintEvent(QPaintEvent *e) override; + bool eventFilter(QObject *obj, QEvent *e) override; + + void setup(); + void setupInner(); + void hideByTimerOrLeave(); + + // Rounded rect which has shadow around it. + [[nodiscard]] QRect innerRect() const; + + [[nodiscard]] QImage grabForAnimation(); + void startShowAnimation(); + void startOpacityAnimation(bool hiding); + void prepareCacheFor(bool hiding); + + void opacityAnimationCallback(); + + void hideFinished(); + void showStarted(); + void setSelected(int selected); + void setPressed(int pressed); + [[nodiscard]] QRect countRect(int index) const; + void updateRect(int index); + void mouseMove(QPoint position); + void mousePress(Qt::MouseButton button); + void mouseRelease(Qt::MouseButton button); + + const not_null _inner; + rpl::event_stream _chosen; + + int _selected = -1; + int _pressed = -1; + + std::unique_ptr _showAnimation; + Ui::Animations::Simple _a_show; + + bool _hiding = false; + bool _hideAfterSlide = false; + QPixmap _cache; + Ui::Animations::Simple _a_opacity; + base::Timer _hideTimer; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/filter_icons.cpp b/Telegram/SourceFiles/ui/filter_icons.cpp index eab770ac5..1e049f965 100644 --- a/Telegram/SourceFiles/ui/filter_icons.cpp +++ b/Telegram/SourceFiles/ui/filter_icons.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/filter_icons.h" #include "ui/emoji_config.h" +#include "data/data_chat_filters.h" #include "styles/style_filter_icons.h" namespace Ui { @@ -62,7 +63,7 @@ const auto kIcons = std::vector{ { &st::foldersCat, &st::foldersCatActive, - "\xF0\x9F\x90\x88"_cs.utf16() + "\xF0\x9F\x90\xB1"_cs.utf16() }, { &st::foldersCrown, @@ -155,4 +156,43 @@ std::optional LookupFilterIconByEmoji(const QString &emoji) { return (i != end(kMap)) ? std::make_optional(i->second) : std::nullopt; } +FilterIcon ComputeDefaultFilterIcon(const Data::ChatFilter &filter) { + using Icon = FilterIcon; + using Flag = Data::ChatFilter::Flag; + + const auto all = Flag::Contacts + | Flag::NonContacts + | Flag::Groups + | Flag::Channels + | Flag::Bots; + const auto removed = Flag::NoRead | Flag::NoMuted; + const auto people = Flag::Contacts | Flag::NonContacts; + const auto allNoArchive = all | Flag::NoArchived; + if (!filter.always().empty() + || !filter.never().empty() + || !(filter.flags() & all)) { + return Icon::Custom; + } else if ((filter.flags() & all) == Flag::Contacts + || (filter.flags() & all) == Flag::NonContacts + || (filter.flags() & all) == people) { + return Icon::Private; + } else if ((filter.flags() & all) == Flag::Groups) { + return Icon::Groups; + } else if ((filter.flags() & all) == Flag::Channels) { + return Icon::Channels; + } else if ((filter.flags() & all) == Flag::Bots) { + return Icon::Bots; + } else if ((filter.flags() & removed) == Flag::NoRead) { + return Icon::Unread; + } else if ((filter.flags() & removed) == Flag::NoMuted) { + return Icon::Unmuted; + } + return Icon::Custom; +} + +FilterIcon ComputeFilterIcon(const Data::ChatFilter &filter) { + return LookupFilterIconByEmoji(filter.iconEmoji()).value_or( + ComputeDefaultFilterIcon(filter)); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/filter_icons.h b/Telegram/SourceFiles/ui/filter_icons.h index d687942cc..fadde3f29 100644 --- a/Telegram/SourceFiles/ui/filter_icons.h +++ b/Telegram/SourceFiles/ui/filter_icons.h @@ -13,6 +13,10 @@ class Icon; } // namespace internal } // namespace style +namespace Data { +class ChatFilter; +} // namespace Data + namespace Ui { enum class FilterIcon : uchar { @@ -52,4 +56,8 @@ struct FilterIcons { [[nodiscard]] std::optional LookupFilterIconByEmoji( const QString &emoji); +[[nodiscard]] FilterIcon ComputeDefaultFilterIcon( + const Data::ChatFilter &filter); +[[nodiscard]] FilterIcon ComputeFilterIcon(const Data::ChatFilter &filter); + } // namespace Ui diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index bb80c2de6..75b0e2fc1 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -284,6 +284,15 @@ windowFilterSmallList: PeerList(defaultPeerList) { windowFilterSmallRemove: IconButton(notifyClose) { } windowFilterSmallRemoveRight: 10px; +windowFilterNameInput: InputField(defaultInputField) { + textMargins: margins(0px, 26px, 36px, 4px); +} +windowFilterIconToggleSize: size(36px, 36px); +windowFilterIconTogglePosition: point(-4px, 12px); +windwoFilterIconPanelPosition: point(-2px, -1px); +windowFilterIconSingle: size(44px, 42px); +windowFilterIconPadding: margins(10px, 36px, 10px, 8px); +windowFilterIconHeaderPosition: point(18px, 14px); windowFilterTypeContacts: icon {{ "filters_type_contacts", historyPeerUserpicFg }}; windowFilterTypeNonContacts: icon {{ "filters_type_noncontacts", historyPeerUserpicFg }}; windowFilterTypeGroups: icon {{ "filters_type_groups", historyPeerUserpicFg }}; diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index a0cbf3edb..4c77580af 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -24,39 +24,6 @@ namespace { using Icon = Ui::FilterIcon; -[[nodiscard]] Icon ComputeIcon(const Data::ChatFilter &filter) { - using Flag = Data::ChatFilter::Flag; - - const auto all = Flag::Contacts - | Flag::NonContacts - | Flag::Groups - | Flag::Channels - | Flag::Bots; - const auto removed = Flag::NoRead | Flag::NoMuted; - const auto people = Flag::Contacts | Flag::NonContacts; - const auto allNoArchive = all | Flag::NoArchived; - if (!filter.always().empty() - || !filter.never().empty() - || !(filter.flags() & all)) { - return Icon::Custom; - } else if ((filter.flags() & all) == Flag::Contacts - || (filter.flags() & all) == Flag::NonContacts - || (filter.flags() & all) == people) { - return Icon::Private; - } else if ((filter.flags() & all) == Flag::Groups) { - return Icon::Groups; - } else if ((filter.flags() & all) == Flag::Channels) { - return Icon::Channels; - } else if ((filter.flags() & all) == Flag::Bots) { - return Icon::Bots; - } else if ((filter.flags() & removed) == Flag::NoRead) { - return Icon::Unread; - } else if ((filter.flags() & removed) == Flag::NoMuted) { - return Icon::Unmuted; - } - return Icon::Custom; -} - } // namespace FiltersMenu::FiltersMenu( @@ -187,7 +154,7 @@ void FiltersMenu::refresh() { prepare( filter.id(), filter.title(), - ComputeIcon(filter), + Ui::ComputeFilterIcon(filter), QString()); } prepare(-1, tr::lng_filters_setup(tr::now), Icon::Setup, {}); From b3f8e276614d44b2d647ccad2e475e9f614f5bdd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 00:20:35 +0400 Subject: [PATCH 053/115] Improve error phrase about pin limit. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/window/window_peer_menu.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 942341bf2..b47060848 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2278,6 +2278,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_type_no_muted" = "Muted"; "lng_filters_type_no_read" = "Read"; "lng_filters_icon_header" = "Choose icon"; +"lng_filters_error_pinned_max" = "Sorry, you can't pin any more chats to the top."; // Wnd specific diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 3bf0f97be..12464881c 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -156,10 +156,12 @@ bool PinnedLimitReached(Dialogs::Key key, FilterId filterId) { owner->setChatPinned(key, FilterId(), true); entry->session().api().savePinnedOrder(folder); } else { - const auto errorText = tr::lng_error_pinned_max( - tr::now, - lt_count, - pinnedMax); + const auto errorText = filterId + ? tr::lng_filters_error_pinned_max(tr::now) + : tr::lng_error_pinned_max( + tr::now, + lt_count, + pinnedMax); Ui::show(Box(errorText)); } return true; From ba6373a0aebf639befcae0d0cfc3e6472143629e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 00:41:21 +0400 Subject: [PATCH 054/115] Fix filter icon panel wrong hiding. --- Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp | 1 + Telegram/SourceFiles/ui/filter_icon_panel.cpp | 4 ++-- Telegram/SourceFiles/ui/filter_icon_panel.h | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp index b5b24688c..8fba46c57 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp @@ -280,6 +280,7 @@ void TabbedPanel::hideByTimerOrLeave() { void TabbedPanel::prepareCacheFor(bool hiding) { if (_a_opacity.animating()) { + _hiding = hiding; return; } diff --git a/Telegram/SourceFiles/ui/filter_icon_panel.cpp b/Telegram/SourceFiles/ui/filter_icon_panel.cpp index 0748d2e93..9bb41c3a5 100644 --- a/Telegram/SourceFiles/ui/filter_icon_panel.cpp +++ b/Telegram/SourceFiles/ui/filter_icon_panel.cpp @@ -325,6 +325,7 @@ void FilterIconPanel::hideByTimerOrLeave() { void FilterIconPanel::prepareCacheFor(bool hiding) { if (_a_opacity.animating()) { + _hiding = hiding; return; } @@ -404,7 +405,7 @@ void FilterIconPanel::hideAnimated() { } void FilterIconPanel::toggleAnimated() { - if (isHidden() || _hiding || _hideAfterSlide) { + if (isHidden() || _hiding) { showAnimated(); } else { hideAnimated(); @@ -421,7 +422,6 @@ void FilterIconPanel::hideFinished() { void FilterIconPanel::showAnimated() { _hideTimer.cancel(); - _hideAfterSlide = false; showStarted(); } diff --git a/Telegram/SourceFiles/ui/filter_icon_panel.h b/Telegram/SourceFiles/ui/filter_icon_panel.h index 1606166a8..44b4f6c1a 100644 --- a/Telegram/SourceFiles/ui/filter_icon_panel.h +++ b/Telegram/SourceFiles/ui/filter_icon_panel.h @@ -77,7 +77,6 @@ private: Ui::Animations::Simple _a_show; bool _hiding = false; - bool _hideAfterSlide = false; QPixmap _cache; Ui::Animations::Simple _a_opacity; base::Timer _hideTimer; From 18805a5ef8780284aae6f6a4a0f396d904189d02 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 15:39:33 +0400 Subject: [PATCH 055/115] Allow reordering filters in side bar. --- Telegram/SourceFiles/api/api_chat_filters.cpp | 16 ++ Telegram/SourceFiles/api/api_chat_filters.h | 8 +- .../window/window_filters_menu.cpp | 198 ++++++++++++------ .../SourceFiles/window/window_filters_menu.h | 24 +++ Telegram/lib_base | 2 +- Telegram/lib_ui | 2 +- 6 files changed, 179 insertions(+), 71 deletions(-) diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index 68b0dbebe..8af6a2fa5 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -32,4 +32,20 @@ void SaveNewFilterPinned( } +void SaveNewOrder( + not_null session, + const std::vector &order) { + auto &filters = session->data().chatsFilters(); + auto ids = QVector(); + ids.reserve(order.size()); + for (const auto id : order) { + ids.push_back(MTP_int(id)); + } + const auto wrapped = MTP_vector(ids); + filters.apply(MTP_updateDialogFilterOrder(wrapped)); + session->api().request(MTPmessages_UpdateDialogFiltersOrder( + wrapped + )).send(); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_chat_filters.h b/Telegram/SourceFiles/api/api_chat_filters.h index 286ca2945..893002586 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.h +++ b/Telegram/SourceFiles/api/api_chat_filters.h @@ -13,6 +13,12 @@ class Session; namespace Api { -void SaveNewFilterPinned(not_null session, FilterId filterId); +void SaveNewFilterPinned( + not_null session, + FilterId filterId); + +void SaveNewOrder( + not_null session, + const std::vector &order); } // namespace Api diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 4c77580af..795169e52 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -16,21 +16,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/filters/manage_filters_box.h" #include "lang/lang_keys.h" #include "ui/filter_icons.h" +#include "ui/wrap/vertical_layout_reorder.h" +#include "api/api_chat_filters.h" #include "styles/style_widgets.h" #include "styles/style_window.h" namespace Window { -namespace { - -using Icon = Ui::FilterIcon; - -} // namespace FiltersMenu::FiltersMenu( not_null parent, not_null session) : _session(session) , _parent(parent) +, _manage(std::make_unique(_session)) , _outer(_parent) , _menu(&_outer, QString(), st::windowFiltersMainMenu) , _scroll(&_outer) @@ -40,24 +38,17 @@ FiltersMenu::FiltersMenu( setup(); } +FiltersMenu::~FiltersMenu() = default; + void FiltersMenu::setup() { _outer.setAttribute(Qt::WA_OpaquePaintEvent); _outer.show(); _outer.paintRequest( ) | rpl::start_with_next([=](QRect clip) { - const auto bottom = _scroll.y() + _container->height(); - const auto height = _outer.height() - bottom; - if (height <= 0) { - return; - } - const auto fill = clip.intersected( - QRect(0, bottom, _outer.width(), height)); - if (!fill.isEmpty()) { - auto p = QPainter(&_outer); - p.setPen(Qt::NoPen); - p.setBrush(st::windowFiltersButton.textBg); - p.drawRect(fill); - } + auto p = QPainter(&_outer); + p.setPen(Qt::NoPen); + p.setBrush(st::windowFiltersButton.textBg); + p.drawRect(clip); }, _outer.lifetime()); _parent->heightValue( @@ -89,11 +80,15 @@ void FiltersMenu::setup() { const auto i = _filters.find(_activeFilterId); if (i != end(_filters)) { i->second->setActive(false); + } else if (!_activeFilterId) { + _all->setActive(false); } _activeFilterId = id; const auto j = _filters.find(_activeFilterId); if (j != end(_filters)) { j->second->setActive(true); + } else if (!_activeFilterId) { + _all->setActive(true); } }, _outer.lifetime()); @@ -107,58 +102,125 @@ void FiltersMenu::refresh() { if (filters->list().empty()) { return; } - const auto manage = _outer.lifetime().make_state( - _session); - auto now = base::flat_map>(); - const auto prepare = [&]( - FilterId id, - const QString &title, - Icon icon, - const QString &badge) { - auto button = base::unique_qptr(_container->add( - object_ptr( - _container, - title, - st::windowFiltersButton))); - const auto &icons = Ui::LookupFilterIcon(icon); - button->setIconOverride(icons.normal, icons.active); - if (id > 0) { - const auto list = filters->chatsList(id); - rpl::single(rpl::empty_value()) | rpl::then( - list->unreadStateChanges( - ) | rpl::map([] { return rpl::empty_value(); }) - ) | rpl::start_with_next([=, raw = button.get()] { - const auto &state = list->unreadState(); - const auto count = (state.chats + state.marks); - const auto muted = (state.chatsMuted + state.marksMuted); - const auto string = !count - ? QString() - : (count > 99) - ? "..." - : QString::number(count); - raw->setBadge(string, count == muted); - }, button->lifetime()); - } - button->setActive(_session->activeChatsFilterCurrent() == id); - button->setClickedCallback([=] { - if (id >= 0) { - _session->setActiveChatsFilter(id); - } else { - manage->showBox(); - } - }); - now.emplace(id, std::move(button)); - }; - prepare(0, tr::lng_filters_all(tr::now), Icon::All, {}); - for (const auto filter : filters->list()) { - prepare( - filter.id(), - filter.title(), - Ui::ComputeFilterIcon(filter), - QString()); + + if (!_list) { + setupList(); + } + _reorder->cancel(); + auto now = base::flat_map>(); + for (const auto filter : filters->list()) { + now.emplace( + filter.id(), + prepareButton( + _list, + filter.id(), + filter.title(), + Ui::ComputeFilterIcon(filter))); } - prepare(-1, tr::lng_filters_setup(tr::now), Icon::Setup, {}); _filters = std::move(now); + _reorder->start(); + + _container->resizeToWidth(_outer.width()); +} + +void FiltersMenu::setupList() { + _all = prepareButton( + _container, + 0, + tr::lng_filters_all(tr::now), + Ui::FilterIcon::All); + _list = _container->add(object_ptr(_container)); + _setup = prepareButton( + _container, + -1, + tr::lng_filters_setup(tr::now), + Ui::FilterIcon::Setup); + _reorder = std::make_unique(_list); + + _reorder->updates( + ) | rpl::start_with_next([=](Ui::VerticalLayoutReorder::Single data) { + using State = Ui::VerticalLayoutReorder::State; + if (data.state == State::Started) { + ++_reordering; + } else { + Ui::PostponeCall(&_outer, [=] { + --_reordering; + }); + if (data.state == State::Applied) { + applyReorder(data.widget, data.oldPosition, data.newPosition); + } + } + }, _outer.lifetime()); +} + +base::unique_qptr FiltersMenu::prepareButton( + not_null container, + FilterId id, + const QString &title, + Ui::FilterIcon icon) { + auto button = base::unique_qptr(container->add( + object_ptr( + container, + title, + st::windowFiltersButton))); + const auto raw = button.get(); + const auto &icons = Ui::LookupFilterIcon(icon); + raw->setIconOverride(icons.normal, icons.active); + if (id > 0) { + const auto filters = &_session->session().data().chatsFilters(); + const auto list = filters->chatsList(id); + rpl::single(rpl::empty_value()) | rpl::then( + list->unreadStateChanges( + ) | rpl::map([] { return rpl::empty_value(); }) + ) | rpl::start_with_next([=] { + const auto &state = list->unreadState(); + const auto count = (state.chats + state.marks); + const auto muted = (state.chatsMuted + state.marksMuted); + const auto string = !count + ? QString() + : (count > 99) + ? "..." + : QString::number(count); + raw->setBadge(string, count == muted); + }, raw->lifetime()); + } + raw->setActive(_session->activeChatsFilterCurrent() == id); + raw->setClickedCallback([=] { + if (_reordering) { + return; + } else if (id >= 0) { + _session->setActiveChatsFilter(id); + } else { + _manage->showBox(); + } + }); + return button; +} + +void FiltersMenu::applyReorder( + not_null widget, + int oldPosition, + int newPosition) { + if (newPosition == oldPosition) { + return; + } + + const auto filters = &_session->session().data().chatsFilters(); + const auto &list = filters->list(); + Assert(oldPosition >= 0 && oldPosition < list.size()); + Assert(newPosition >= 0 && newPosition < list.size()); + const auto id = list[oldPosition].id(); + const auto i = _filters.find(id); + Assert(i != end(_filters)); + Assert(i->second == widget); + + auto order = ranges::view::all( + list + ) | ranges::view::transform( + &Data::ChatFilter::id + ) | ranges::to_vector; + base::reorder(order, oldPosition, newPosition); + Api::SaveNewOrder(&_session->session(), order); } } // namespace Window diff --git a/Telegram/SourceFiles/window/window_filters_menu.h b/Telegram/SourceFiles/window/window_filters_menu.h index 985fc9af2..9ebbaec5b 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.h +++ b/Telegram/SourceFiles/window/window_filters_menu.h @@ -11,6 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/wrap/vertical_layout.h" +class ManageFiltersPrepare; + +namespace Ui { +class VerticalLayoutReorder; +enum class FilterIcon : uchar; +} // namespace Ui + namespace Window { class SessionController; @@ -20,19 +27,36 @@ public: FiltersMenu( not_null parent, not_null session); + ~FiltersMenu(); private: void setup(); void refresh(); + void setupList(); + void applyReorder( + not_null widget, + int oldPosition, + int newPosition); + [[nodiscard]] base::unique_qptr prepareButton( + not_null container, + FilterId id, + const QString &title, + Ui::FilterIcon icon); const not_null _session; const not_null _parent; + std::unique_ptr _manage; Ui::RpWidget _outer; Ui::SideBarButton _menu; Ui::ScrollArea _scroll; not_null _container; + Ui::VerticalLayout *_list = nullptr; + std::unique_ptr _reorder; + base::unique_qptr _all; + base::unique_qptr _setup; base::flat_map> _filters; FilterId _activeFilterId = 0; + int _reordering = 0; }; diff --git a/Telegram/lib_base b/Telegram/lib_base index 62d4145ba..7de25679d 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 62d4145ba04d8b6205fe0413318bc5a0f19f9410 +Subproject commit 7de25679dd68f3fb7d2faee77ff5311f4d9587d6 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index ec6744022..b051948e6 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit ec6744022c1a86f3bead192f9649c10dc424a98b +Subproject commit b051948e6947b97394df9f0f8b24e8bf407e596c From b8684af53734565c10403817b31406d8d335cd03 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 16:48:52 +0400 Subject: [PATCH 056/115] Improve filters reordering. --- Telegram/SourceFiles/api/api_chat_filters.cpp | 16 -------------- Telegram/SourceFiles/api/api_chat_filters.h | 4 ---- .../boxes/filters/manage_filters_box.cpp | 15 +++++-------- .../SourceFiles/data/data_chat_filters.cpp | 22 +++++++++++++++++++ Telegram/SourceFiles/data/data_chat_filters.h | 5 +++++ .../window/window_filters_menu.cpp | 7 ++++-- .../SourceFiles/window/window_filters_menu.h | 1 + Telegram/lib_ui | 2 +- 8 files changed, 40 insertions(+), 32 deletions(-) diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index 8af6a2fa5..68b0dbebe 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -32,20 +32,4 @@ void SaveNewFilterPinned( } -void SaveNewOrder( - not_null session, - const std::vector &order) { - auto &filters = session->data().chatsFilters(); - auto ids = QVector(); - ids.reserve(order.size()); - for (const auto id : order) { - ids.push_back(MTP_int(id)); - } - const auto wrapped = MTP_vector(ids); - filters.apply(MTP_updateDialogFilterOrder(wrapped)); - session->api().request(MTPmessages_UpdateDialogFiltersOrder( - wrapped - )).send(); -} - } // namespace Api diff --git a/Telegram/SourceFiles/api/api_chat_filters.h b/Telegram/SourceFiles/api/api_chat_filters.h index 893002586..59c29f72b 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.h +++ b/Telegram/SourceFiles/api/api_chat_filters.h @@ -17,8 +17,4 @@ void SaveNewFilterPinned( not_null session, FilterId filterId); -void SaveNewOrder( - not_null session, - const std::vector &order); - } // namespace Api diff --git a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp index 2a60c9873..102976472 100644 --- a/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/manage_filters_box.cpp @@ -501,7 +501,8 @@ void ManageFiltersPrepare::SetupBox( auto addRequests = Requests(), removeRequests = Requests(); auto &realFilters = session->data().chatsFilters(); const auto &list = realFilters.list(); - auto order = QVector(); + auto order = std::vector(); + order.reserve(rows->size()); for (const auto &row : *rows) { const auto id = row.filter.id(); const auto removed = row.removed; @@ -509,7 +510,7 @@ void ManageFiltersPrepare::SetupBox( if (removed && i == end(list)) { continue; } else if (!removed && i != end(list) && *i == row.filter) { - order.push_back(MTP_int(id)); + order.push_back(id); continue; } const auto newId = ids.take(row.button).value_or(id); @@ -526,7 +527,7 @@ void ManageFiltersPrepare::SetupBox( removeRequests.push_back(request); } else { addRequests.push_back(request); - order.push_back(MTP_int(newId)); + order.push_back(newId); } realFilters.apply(MTP_updateDialogFilter( MTP_flags(removed @@ -542,12 +543,8 @@ void ManageFiltersPrepare::SetupBox( std::move(request) ).afterRequest(previousId).send(); } - if (!order.isEmpty() && !addRequests.empty()) { - realFilters.apply( - MTP_updateDialogFilterOrder(MTP_vector(order))); - session->api().request(MTPmessages_UpdateDialogFiltersOrder( - MTP_vector(order) - )).afterRequest(previousId).send(); + if (!order.empty() && !addRequests.empty()) { + realFilters.saveOrder(order, previousId); } box->closeBox(); }; diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index e4f28f2c6..f14a5e0a7 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -451,6 +451,28 @@ const ChatFilter &ChatFilters::applyUpdatedPinned( return *i; } +void ChatFilters::saveOrder( + const std::vector &order, + mtpRequestId after) { + if (after) { + _saveOrderAfterId = after; + } + const auto api = &_owner->session().api(); + api->request(_saveOrderRequestId).cancel(); + + auto ids = QVector(); + ids.reserve(order.size()); + for (const auto id : order) { + ids.push_back(MTP_int(id)); + } + const auto wrapped = MTP_vector(ids); + + apply(MTP_updateDialogFilterOrder(wrapped)); + _saveOrderRequestId = api->request(MTPmessages_UpdateDialogFiltersOrder( + wrapped + )).afterRequest(_saveOrderAfterId).send(); +} + bool ChatFilters::archiveNeeded() const { for (const auto &filter : _list) { if (!(filter.flags() & ChatFilter::Flag::NoArchived)) { diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 8e4cd95cd..c6e15d7f1 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -106,6 +106,9 @@ public: const ChatFilter &applyUpdatedPinned( FilterId id, const std::vector &dialogs); + void saveOrder( + const std::vector &order, + mtpRequestId after = 0); [[nodiscard]] bool archiveNeeded() const; @@ -123,6 +126,8 @@ private: rpl::event_stream<> _listChanged; rpl::event_stream> _refreshHistoryRequests; mtpRequestId _loadRequestId = 0; + mtpRequestId _saveOrderRequestId = 0; + mtpRequestId _saveOrderAfterId = 0; bool _loaded = false; }; diff --git a/Telegram/SourceFiles/window/window_filters_menu.cpp b/Telegram/SourceFiles/window/window_filters_menu.cpp index 795169e52..e035c1b4a 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.cpp +++ b/Telegram/SourceFiles/window/window_filters_menu.cpp @@ -99,7 +99,7 @@ void FiltersMenu::setup() { void FiltersMenu::refresh() { const auto filters = &_session->session().data().chatsFilters(); - if (filters->list().empty()) { + if (filters->list().empty() || _ignoreRefresh) { return; } @@ -220,7 +220,10 @@ void FiltersMenu::applyReorder( &Data::ChatFilter::id ) | ranges::to_vector; base::reorder(order, oldPosition, newPosition); - Api::SaveNewOrder(&_session->session(), order); + + _ignoreRefresh = true; + filters->saveOrder(order); + _ignoreRefresh = false; } } // namespace Window diff --git a/Telegram/SourceFiles/window/window_filters_menu.h b/Telegram/SourceFiles/window/window_filters_menu.h index 9ebbaec5b..eceb23ff0 100644 --- a/Telegram/SourceFiles/window/window_filters_menu.h +++ b/Telegram/SourceFiles/window/window_filters_menu.h @@ -57,6 +57,7 @@ private: base::flat_map> _filters; FilterId _activeFilterId = 0; int _reordering = 0; + bool _ignoreRefresh = false; }; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index b051948e6..eaab3f9a8 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit b051948e6947b97394df9f0f8b24e8bf407e596c +Subproject commit eaab3f9a8a5a1bc429449a9a40c0798f2203947b From ab16e8e0830a47af0488e46f213e32ba2abc5805 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 17:12:13 +0400 Subject: [PATCH 057/115] Improve archive <-> filter navigation. --- .../dialogs/dialogs_inner_widget.cpp | 20 +++++++++++-------- .../window/window_session_controller.cpp | 4 ++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 8ccc03aac..f8de2ed16 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -382,7 +382,6 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) { //const auto lastMousePosition = _lastMousePosition; clearSelection(); _openedFolder = folder; - _filterId = _openedFolder ? 0 : _controller->activeChatsFilterCurrent(); refreshWithCollapsedRows(true); // This doesn't work, because we clear selection in leaveEvent on hide. //if (mouseSelection && lastMousePosition) { @@ -2258,9 +2257,7 @@ void InnerWidget::searchInChat(Key key, UserData *from) { _searchInChat = key; _searchFromUser = from; if (_searchInChat) { - if (_openedFolder) { - changeOpenedFolder(nullptr); - } + _controller->closeFolder(); onHashtagFilterUpdate(QStringRef()); _cancelSearchInChat->show(); refreshSearchInChatLabel(); @@ -2549,7 +2546,6 @@ bool InnerWidget::chooseCollapsedRow() { } void InnerWidget::switchToFilter(FilterId filterId) { - clearSelection(); const auto found = ranges::contains( session().data().chatsFilters().list(), filterId, @@ -2557,9 +2553,17 @@ void InnerWidget::switchToFilter(FilterId filterId) { if (!found) { filterId = 0; } - stopReorderPinned(); - _filterId = filterId; - refreshWithCollapsedRows(true); + if (_filterId == filterId) { + return; + } + if (_openedFolder) { + _filterId = filterId; + } else { + clearSelection(); + stopReorderPinned(); + _filterId = filterId; + refreshWithCollapsedRows(true); + } } bool InnerWidget::chooseHashtag() { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 0823a1089..d2e3a3bcf 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -244,6 +244,7 @@ bool SessionController::uniqueChatsInSearchResults() const { } void SessionController::openFolder(not_null folder) { + setActiveChatsFilter(0); _openedFolder = folder.get(); } @@ -775,6 +776,9 @@ FilterId SessionController::activeChatsFilterCurrent() const { void SessionController::setActiveChatsFilter(FilterId id) { _activeChatsFilter = id; + if (id) { + closeFolder(); + } } SessionController::~SessionController() = default; From 1de9352f3a99c88cda7dfcd1d4dc863256d1a14b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 17:14:49 +0400 Subject: [PATCH 058/115] Don't close filter by escape. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index d7b1e899c..0ddb7fc4b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1592,8 +1592,6 @@ void Widget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { if (_openedFolder) { controller()->closeFolder(); - } else if (_inner->filterId()) { - controller()->setActiveChatsFilter(0); } else { e->ignore(); } From b42ba1a7a32e1fd8c0d336616083e346123a20c1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 17:23:52 +0400 Subject: [PATCH 059/115] Fix switch-to-filter in single column mode. --- Telegram/SourceFiles/window/window_session_controller.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index d2e3a3bcf..f3a4e8c58 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -779,6 +779,9 @@ void SessionController::setActiveChatsFilter(FilterId id) { if (id) { closeFolder(); } + if (Adaptive::OneColumn()) { + Ui::showChatsList(); + } } SessionController::~SessionController() = default; From 2dbaee4fe1b775827106da290c1c53eab2746495 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 17:49:43 +0400 Subject: [PATCH 060/115] Filter only chats from the list in exceptions. --- Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index ea008f4ef..5d4d0ee98 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -409,7 +409,7 @@ object_ptr EditFilterChatsListController::prepareTypesList() { auto EditFilterChatsListController::createRow(not_null history) -> std::unique_ptr { - return std::make_unique(history); + return history->inChatList() ? std::make_unique(history) : nullptr; } void EditFilterChatsListController::updateTitle() { From b1606821c0c688379c5bff222a9f39df489ef990 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 18:20:57 +0400 Subject: [PATCH 061/115] Suggest filter name by content type. --- Telegram/Resources/langs/lang.strings | 3 + .../boxes/filters/edit_filter_box.cpp | 82 +++++++++++++++++-- .../boxes/filters/edit_filter_chats_list.cpp | 7 ++ 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b47060848..40e0ad990 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2265,6 +2265,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_exclude_muted" = "Muted"; "lng_filters_exclude_read" = "Read"; "lng_filters_exclude_archived" = "Archived"; +"lng_filters_name_people" = "People"; +"lng_filters_name_unread" = "Unread"; +"lng_filters_name_unmuted" = "Unmuted"; "lng_filters_add" = "Done"; "lng_filters_limit" = "Sorry, you have reached folders limit."; "lng_filters_empty" = "Please choose at least one chat for this folder."; diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 25d486e2d..b95333613 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -93,9 +93,16 @@ private: }; +struct NameEditing { + not_null field; + bool custom = false; + bool settingDefault = false; +}; + not_null SetupChatsPreview( not_null content, not_null*> data, + Fn updateDefaultTitle, Flags flags, ExceptionPeersGetter peers) { const auto rules = data->current(); @@ -107,7 +114,7 @@ not_null SetupChatsPreview( preview->flagRemoved( ) | rpl::start_with_next([=](Flag flag) { const auto rules = data->current(); - *data = Data::ChatFilter( + auto computed = Data::ChatFilter( rules.id(), rules.title(), rules.iconEmoji(), @@ -115,6 +122,8 @@ not_null SetupChatsPreview( rules.always(), rules.pinned(), rules.never()); + updateDefaultTitle(computed); + *data = std::move(computed); }, preview->lifetime()); preview->peerRemoved( @@ -126,7 +135,7 @@ not_null SetupChatsPreview( always.remove(history); pinned.erase(ranges::remove(pinned, history), end(pinned)); never.remove(history); - *data = Data::ChatFilter( + auto computed = Data::ChatFilter( rules.id(), rules.title(), rules.iconEmoji(), @@ -134,6 +143,8 @@ not_null SetupChatsPreview( std::move(always), std::move(pinned), std::move(never)); + updateDefaultTitle(computed); + *data = std::move(computed); }, preview->lifetime()); return preview; @@ -269,6 +280,7 @@ void EditExceptions( not_null context, Flags options, not_null*> data, + Fn updateDefaultTitle, Fn refresh) { const auto include = (options & Flag::Contacts) != Flags(0); const auto rules = data->current(); @@ -304,7 +316,7 @@ void EditExceptions( const auto contains = changed.contains(history); return include ? !contains : contains; }), end(pinned)); - *data = Data::ChatFilter( + auto computed = Data::ChatFilter( rules.id(), rules.title(), rules.iconEmoji(), @@ -313,6 +325,8 @@ void EditExceptions( include ? std::move(changed) : std::move(removeFrom), std::move(pinned), include ? std::move(removeFrom) : std::move(changed)); + updateDefaultTitle(computed); + *data = computed; refresh(); box->closeBox(); })); @@ -413,6 +427,28 @@ void EditExceptions( installFilterForGeometry(box); } +[[nodiscard]] QString DefaultTitle(const Data::ChatFilter &filter) { + using Icon = Ui::FilterIcon; + const auto icon = Ui::ComputeDefaultFilterIcon(filter); + switch (icon) { + case Icon::Private: + return (filter.flags() & Data::ChatFilter::Flag::NonContacts) + ? tr::lng_filters_name_people(tr::now) + : tr::lng_filters_include_contacts(tr::now); + case Icon::Groups: + return tr::lng_filters_include_groups(tr::now); + case Icon::Channels: + return tr::lng_filters_include_channels(tr::now); + case Icon::Bots: + return tr::lng_filters_include_bots(tr::now); + case Icon::Unread: + return tr::lng_filters_name_unread(tr::now); + case Icon::Unmuted: + return tr::lng_filters_name_unmuted(tr::now); + } + return QString(); +} + } // namespace void EditFilterBox( @@ -433,10 +469,30 @@ void EditFilterBox( box, st::windowFilterNameInput, tr::lng_filters_new_name(), - data->current().title()), + filter.title()), st::markdownLinkFieldPadding); name->setMaxLength(kMaxFilterTitleLength); + const auto nameEditing = box->lifetime().make_state( + NameEditing{ name }); + nameEditing->custom = !creating; + QObject::connect(name, &Ui::InputField::changed, [=] { + if (!nameEditing->settingDefault) { + nameEditing->custom = true; + } + }); + const auto updateDefaultTitle = [=](const Data::ChatFilter &filter) { + if (nameEditing->custom) { + return; + } + const auto title = DefaultTitle(filter); + if (nameEditing->field->getLastText() != title) { + nameEditing->settingDefault = true; + nameEditing->field->setText(title); + nameEditing->settingDefault = false; + } + }; + const auto outer = box->getDelegate()->outerContainer(); CreateIconSelector( outer, @@ -466,6 +522,7 @@ void EditFilterBox( const auto include = SetupChatsPreview( content, data, + updateDefaultTitle, kTypes, &Data::ChatFilter::always); @@ -483,6 +540,7 @@ void EditFilterBox( const auto exclude = SetupChatsPreview( content, data, + updateDefaultTitle, kExcludeTypes, &Data::ChatFilter::never); @@ -508,10 +566,22 @@ void EditFilterBox( data->current().never()); }; includeAdd->setClickedCallback([=] { - EditExceptions(window, box, kTypes, data, refreshPreviews); + EditExceptions( + window, + box, + kTypes, + data, + updateDefaultTitle, + refreshPreviews); }); excludeAdd->setClickedCallback([=] { - EditExceptions(window, box, kExcludeTypes, data, refreshPreviews); + EditExceptions( + window, + box, + kExcludeTypes, + data, + updateDefaultTitle, + refreshPreviews); }); const auto save = [=] { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index 5d4d0ee98..e0a64e834 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/vertical_layout.h" #include "base/object_ptr.h" #include "styles/style_window.h" +#include "styles/style_boxes.h" namespace { @@ -359,6 +360,9 @@ object_ptr EditFilterChatsListController::prepareTypesList() { container->add(CreateSectionSubtitle( container, tr::lng_filters_edit_types())); + container->add(object_ptr( + container, + st::membersMarginTop)); const auto delegate = container->lifetime().make_state(); const auto controller = container->lifetime().make_state( &session(), @@ -381,6 +385,9 @@ object_ptr EditFilterChatsListController::prepareTypesList() { } } } + container->add(object_ptr( + container, + st::membersMarginBottom)); container->add(CreateSectionSubtitle( container, tr::lng_filters_edit_chats())); From 48f67d27f158f658f6b3b993e62a0a096111bcf6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 18:24:37 +0400 Subject: [PATCH 062/115] Fix active icon states. --- Telegram/SourceFiles/ui/filter_icons.cpp | 4 ++-- Telegram/SourceFiles/ui/filter_icons.style | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/ui/filter_icons.cpp b/Telegram/SourceFiles/ui/filter_icons.cpp index 1e049f965..d575e6fa7 100644 --- a/Telegram/SourceFiles/ui/filter_icons.cpp +++ b/Telegram/SourceFiles/ui/filter_icons.cpp @@ -57,7 +57,7 @@ const auto kIcons = std::vector{ }, { &st::filtersSetup, - &st::filtersSetup, + &st::filtersSetupActive, "\xF0\x9F\x93\x8B"_cs.utf16() }, { @@ -117,7 +117,7 @@ const auto kIcons = std::vector{ }, { &st::foldersTrade, - &st::foldersTrade, + &st::foldersTradeActive, "\xF0\x9F\x93\x88"_cs.utf16() }, { diff --git a/Telegram/SourceFiles/ui/filter_icons.style b/Telegram/SourceFiles/ui/filter_icons.style index 7eed1b147..a93d243ea 100644 --- a/Telegram/SourceFiles/ui/filter_icons.style +++ b/Telegram/SourceFiles/ui/filter_icons.style @@ -24,6 +24,7 @@ filtersPrivateActive: icon {{ "filters/filters_private_active", sideBarIconFgAct filtersCustom: icon {{ "filters/filters_custom", sideBarIconFg }}; filtersCustomActive: icon {{ "filters/filters_custom_active", sideBarIconFgActive }}; filtersSetup: icon {{ "filters/filters_setup", sideBarIconFg }}; +filtersSetupActive: icon {{ "filters/filters_setup", sideBarIconFgActive }}; foldersCat: icon {{ "filters/folders_cat", sideBarIconFg }}; foldersCatActive: icon {{ "filters/folders_cat_active", sideBarIconFg }}; @@ -48,6 +49,7 @@ foldersSportActive: icon {{ "filters/folders_sport_active", sideBarIconFgActive foldersStudy: icon {{ "filters/folders_study", sideBarIconFg }}; foldersStudyActive: icon {{ "filters/folders_study_active", sideBarIconFgActive }}; foldersTrade: icon {{ "filters/folders_trade", sideBarIconFg }}; +foldersTradeActive: icon {{ "filters/folders_trade", sideBarIconFgActive }}; foldersTravel: icon {{ "filters/folders_travel", sideBarIconFg }}; foldersTravelActive: icon {{ "filters/folders_travel_active", sideBarIconFgActive }}; foldersWork: icon {{ "filters/folders_work", sideBarIconFg }}; From 0063edb14fd1ca65b9d27064ac0b379ff392ff7e Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 18:56:18 +0400 Subject: [PATCH 063/115] Support saved messages in filters edit. --- .../boxes/filters/edit_filter_box.cpp | 42 +++++++++----- .../boxes/filters/edit_filter_chats_list.cpp | 57 ++++++++++++++++++- .../SourceFiles/boxes/peer_list_controllers.h | 17 +++--- 3 files changed, 92 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index b95333613..f288f9289 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -234,19 +234,35 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) { top += st.height; } for (const auto &[history, button] : _removePeer) { - history->peer->paintUserpicLeft( - p, - iconLeft, - top + iconTop, - width(), - st.photoSize); - p.setPen(st::contactsNameFg); - history->peer->nameText().drawLeftElided( - p, - nameLeft, - top + nameTop, - button->x() - nameLeft, - width()); + const auto savedMessages = history->peer->isSelf(); + if (savedMessages) { + Ui::EmptyUserpic::PaintSavedMessages( + p, + iconLeft, + top + iconTop, + width(), + st.photoSize); + p.setPen(st::contactsNameFg); + p.drawTextLeft( + nameLeft, + top + nameTop, + width(), + tr::lng_saved_messages(tr::now)); + } else { + history->peer->paintUserpicLeft( + p, + iconLeft, + top + iconTop, + width(), + st.photoSize); + p.setPen(st::contactsNameFg); + history->peer->nameText().drawLeftElided( + p, + nameLeft, + top + nameTop, + button->x() - nameLeft, + width()); + } top += st.height; } } diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp index e0a64e834..a3a1b015e 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -52,6 +52,16 @@ private: }; +class ExceptionRow final : public ChatsListBoxController::Row { +public: + explicit ExceptionRow(not_null history); + + QString generateName() override; + QString generateShortName() override; + PaintRoundImageCallback generatePaintUserpicCallback() override; + +}; + class TypeDelegate final : public PeerListContentDelegate { public: void peerListSetTitle(rpl::producer title) override; @@ -151,6 +161,34 @@ Flag TypeRow::flag() const { return static_cast(id() & 0xFF); } +ExceptionRow::ExceptionRow(not_null history) : Row(history) { + if (peer()->isSelf()) { + setCustomStatus(tr::lng_saved_forward_here(tr::now)); + } +} + +QString ExceptionRow::generateName() { + return peer()->isSelf() + ? tr::lng_saved_messages(tr::now) + : Row::generateName(); +} + +QString ExceptionRow::generateShortName() { + return generateName(); +} + +PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback() { + const auto peer = this->peer(); + const auto saved = peer->isSelf(); + return [=](Painter &p, int x, int y, int outerWidth, int size) { + if (saved) { + Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size); + } else { + peer->paintUserpicLeft(p, x, y, outerWidth, size); + } + }; +} + void TypeDelegate::peerListSetTitle(rpl::producer title) { } @@ -349,8 +387,19 @@ bool EditFilterChatsListController::handleDeselectForeignRow( void EditFilterChatsListController::prepareViewHook() { delegate()->peerListSetTitle(std::move(_title)); delegate()->peerListSetAboveWidget(prepareTypesList()); - delegate()->peerListAddSelectedPeers( - _peers | ranges::view::transform(&History::peer)); + + const auto count = int(_peers.size()); + const auto rows = std::make_unique[]>(count); + auto i = 0; + for (const auto history : _peers) { + rows[i++].emplace(history); + } + auto pointers = std::vector(); + pointers.reserve(count); + for (auto i = 0; i != count; ++i) { + pointers.push_back(&*rows[i]); + } + delegate()->peerListAddSelectedRows(pointers); updateTitle(); } @@ -416,7 +465,9 @@ object_ptr EditFilterChatsListController::prepareTypesList() { auto EditFilterChatsListController::createRow(not_null history) -> std::unique_ptr { - return history->inChatList() ? std::make_unique(history) : nullptr; + return history->inChatList() + ? std::make_unique(history) + : nullptr; } void EditFilterChatsListController::updateTitle() { diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index a1a52e701..b6ca666f9 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -87,14 +87,6 @@ private: class ChatsListBoxController : public PeerListController { public: - ChatsListBoxController(not_null navigation); - ChatsListBoxController( - std::unique_ptr searchController); - - void prepare() override final; - std::unique_ptr createSearchRow(not_null peer) override final; - -protected: class Row : public PeerListRow { public: Row(not_null history); @@ -107,6 +99,15 @@ protected: not_null _history; }; + + ChatsListBoxController(not_null navigation); + ChatsListBoxController( + std::unique_ptr searchController); + + void prepare() override final; + std::unique_ptr createSearchRow(not_null peer) override final; + +protected: virtual std::unique_ptr createRow(not_null history) = 0; virtual void prepareViewHook() = 0; virtual void updateRowHook(not_null row) { From 1be6d968e0ca7154bafd9caad20a8487324b7256 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 19:02:16 +0400 Subject: [PATCH 064/115] Allow emoji suggestions in filter titles. --- Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index f288f9289..59a23fa55 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/filters/edit_filter_box.h" #include "boxes/filters/edit_filter_chats_list.h" +#include "chat_helpers/emoji_suggestions_widget.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" @@ -488,6 +489,13 @@ void EditFilterBox( filter.title()), st::markdownLinkFieldPadding); name->setMaxLength(kMaxFilterTitleLength); + name->setInstantReplaces(Ui::InstantReplaces::Default()); + name->setInstantReplacesEnabled( + window->session().settings().replaceEmojiValue()); + Ui::Emoji::SuggestionsController::Init( + box->getDelegate()->outerContainer(), + name, + &window->session()); const auto nameEditing = box->lifetime().make_state( NameEditing{ name }); From 3ac33e4c1c42680990f6ddd725a3fbefb695a119 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 19:19:55 +0400 Subject: [PATCH 065/115] Leave unread chats in filter until closed. --- Telegram/SourceFiles/data/data_chat_filters.cpp | 3 ++- Telegram/SourceFiles/history/history.cpp | 17 +++++++++++++++++ Telegram/SourceFiles/history/history.h | 3 +++ Telegram/SourceFiles/history/history_widget.cpp | 1 + .../window/window_session_controller.cpp | 6 ++++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index f14a5e0a7..1058f68ec 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -199,7 +199,8 @@ bool ChatFilter::contains(not_null history) const { && (!(_flags & Flag::NoRead) || history->unreadCount() || history->unreadMark() - || history->hasUnreadMentions()) + || history->hasUnreadMentions() + || history->fakeUnreadWhileOpened()) && (!(_flags & Flag::NoArchived) || (history->folderKnown() && !history->folder()))) || _always.contains(history); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 6da131959..1197822b9 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1837,6 +1837,23 @@ bool History::unreadMark() const { return _unreadMark; } +void History::setFakeUnreadWhileOpened(bool enabled) { + if (_fakeUnreadWhileOpened == enabled + || (enabled + && (!inChatList() + || (!unreadCount() + && !unreadMark() + && !hasUnreadMentions())))) { + return; + } + _fakeUnreadWhileOpened = enabled; + owner().chatsFilters().refreshHistory(this); +} + +[[nodiscard]] bool History::fakeUnreadWhileOpened() const { + return _fakeUnreadWhileOpened; +} + bool History::mute() const { return _mute; } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index dc05ae971..c41087a92 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -202,6 +202,8 @@ public: void setUnreadCount(int newUnreadCount); void setUnreadMark(bool unread); [[nodiscard]] bool unreadMark() const; + void setFakeUnreadWhileOpened(bool enabled); + [[nodiscard]] bool fakeUnreadWhileOpened() const; [[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0. [[nodiscard]] bool mute() const; bool changeMute(bool newMute); @@ -540,6 +542,7 @@ private: std::optional _chatListMessage; bool _unreadMark = false; + bool _fakeUnreadWhileOpened = false; // A pointer to the block that is currently being built. // We hold this pointer so we can destroy it while building diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 8cbf6e522..1905627b8 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1816,6 +1816,7 @@ void HistoryWidget::showHistory( && (!_history->loadedAtTop() || !_migrated->loadedAtBottom())) { _migrated->clear(History::ClearType::Unload); } + _history->setFakeUnreadWhileOpened(true); _topBar->setActiveChat( _history, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index f3a4e8c58..58743b6c3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -257,7 +257,13 @@ const rpl::variable &SessionController::openedFolder() const { } void SessionController::setActiveChatEntry(Dialogs::RowDescriptor row) { + if (const auto history = _activeChatEntry.current().key.history()) { + history->setFakeUnreadWhileOpened(false); + } _activeChatEntry = row; + if (const auto history = row.key.history()) { + history->setFakeUnreadWhileOpened(true); + } if (session().supportMode()) { pushToChatEntryHistory(row); } From a9ff9ac50166729484254900f173cb5729a5698a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 19:38:11 +0400 Subject: [PATCH 066/115] Fix one active icon. --- Telegram/SourceFiles/ui/filter_icons.style | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/ui/filter_icons.style b/Telegram/SourceFiles/ui/filter_icons.style index a93d243ea..288bd1b36 100644 --- a/Telegram/SourceFiles/ui/filter_icons.style +++ b/Telegram/SourceFiles/ui/filter_icons.style @@ -27,7 +27,7 @@ filtersSetup: icon {{ "filters/filters_setup", sideBarIconFg }}; filtersSetupActive: icon {{ "filters/filters_setup", sideBarIconFgActive }}; foldersCat: icon {{ "filters/folders_cat", sideBarIconFg }}; -foldersCatActive: icon {{ "filters/folders_cat_active", sideBarIconFg }}; +foldersCatActive: icon {{ "filters/folders_cat_active", sideBarIconFgActive }}; foldersCrown: icon {{ "filters/folders_crown", sideBarIconFg }}; foldersCrownActive: icon {{ "filters/folders_crown_active", sideBarIconFgActive }}; foldersFavorite: icon {{ "filters/folders_favorite", sideBarIconFg }}; From 71fc7a1b4ec0b09c4a840d3d571589f86d35a18c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Mar 2020 22:43:51 +0400 Subject: [PATCH 067/115] Closed alpha version 1.9.21.2. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp | 2 +- Telegram/SourceFiles/core/version.h | 2 +- Telegram/build/version | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 34b504faf..89960e50c 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="1.9.21.2" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index d2d7bb6ab..ec33eb195 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -33,8 +33,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,9,21,1 - PRODUCTVERSION 1,9,21,1 + FILEVERSION 1,9,21,2 + PRODUCTVERSION 1,9,21,2 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "1.9.21.1" + VALUE "FileVersion", "1.9.21.2" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "1.9.21.1" + VALUE "ProductVersion", "1.9.21.2" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 7d6c12dfb..0519c535e 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -24,8 +24,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,9,21,1 - PRODUCTVERSION 1,9,21,1 + FILEVERSION 1,9,21,2 + PRODUCTVERSION 1,9,21,2 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -42,10 +42,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "1.9.21.1" + VALUE "FileVersion", "1.9.21.2" VALUE "LegalCopyright", "Copyright (C) 2014-2020" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "1.9.21.1" + VALUE "ProductVersion", "1.9.21.2" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 59a23fa55..543fa98d0 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -356,7 +356,7 @@ void EditExceptions( Ui::LayerOption::KeepOther); } -[[nodiscard]] void CreateIconSelector( +void CreateIconSelector( not_null outer, not_null box, not_null parent, diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index dc9f484bf..765c54133 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/const_string.h" -#define TDESKTOP_REQUESTED_ALPHA_VERSION (1009021001ULL) +#define TDESKTOP_REQUESTED_ALPHA_VERSION (1009021002ULL) #ifdef TDESKTOP_ALLOW_CLOSED_ALPHA #define TDESKTOP_ALPHA_VERSION TDESKTOP_REQUESTED_ALPHA_VERSION diff --git a/Telegram/build/version b/Telegram/build/version index afdd4fda1..0e1e7c32b 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -3,5 +3,5 @@ AppVersionStrMajor 1.9 AppVersionStrSmall 1.9.21 AppVersionStr 1.9.21 BetaChannel 0 -AlphaVersion 1009021001 -AppVersionOriginal 1.9.21.1 +AlphaVersion 1009021002 +AppVersionOriginal 1.9.21.2 From bba511409ffc19fcc3de95c822ce981cbb7d2347 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Mar 2020 12:46:19 +0400 Subject: [PATCH 068/115] Fix layout of cloud themes in a narrow window. --- Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp b/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp index c63be6ba5..f827cab3f 100644 --- a/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp +++ b/Telegram/SourceFiles/window/themes/window_themes_cloud_list.cpp @@ -312,6 +312,7 @@ object_ptr CloudList::takeWidget() { rpl::producer CloudList::empty() const { using namespace rpl::mappers; + return _count.value() | rpl::map(_1 == 0); } @@ -720,6 +721,7 @@ int CloudList::resizeGetHeight(int newWidth) { auto rowHeight = 0; for (const auto &element : _elements) { const auto button = element.button.get(); + button->resizeToWidth(single); button->moveToLeft(int(std::round(x)), y); accumulate_max(rowHeight, button->height()); x += single + skip; From 8cdf8d5edc5ba8636eb156fb5c55895975f22f45 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Mar 2020 13:38:50 +0400 Subject: [PATCH 069/115] Fix legacy group admin status in members dropdown. Fixes #6347. --- Telegram/SourceFiles/profile/profile_block_group_members.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index 3b6c1608a..b859f1b46 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -290,7 +290,8 @@ void GroupMembersWidget::setItemFlags( using AdminState = Item::AdminState; const auto user = getMember(item)->user(); const auto isCreator = (peerFromUser(chat->creator) == item->peer->id); - const auto isAdmin = chat->hasAdminRights(); + const auto isAdmin = (item->peer->isSelf() && chat->hasAdminRights()) + || chat->admins.contains(user); const auto adminState = isCreator ? AdminState::Creator : isAdmin From aa88ee6d880f875c6d0fd38f335ee655ea3f51df Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Mar 2020 14:08:54 +0400 Subject: [PATCH 070/115] Reset chosen filter on Escape. --- Telegram/SourceFiles/dialogs/dialogs_widget.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 0ddb7fc4b..e6054dc3d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -686,7 +686,11 @@ void Widget::escape() { controller()->closeFolder(); } else if (!onCancelSearch() || (!_searchInChat && !App::main()->selectingPeer())) { - emit cancelled(); + if (controller()->activeChatEntryCurrent().key) { + emit cancelled(); + } else if (controller()->activeChatsFilterCurrent()) { + controller()->setActiveChatsFilter(FilterId(0)); + } } } From 6022fa790e515d975308a4cf2043e39c6656e50d Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Mar 2020 14:09:27 +0400 Subject: [PATCH 071/115] Fix fakeUnreadWhileOpened flag reset. --- .../SourceFiles/window/window_session_controller.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 58743b6c3..b1f207821 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -257,12 +257,14 @@ const rpl::variable &SessionController::openedFolder() const { } void SessionController::setActiveChatEntry(Dialogs::RowDescriptor row) { - if (const auto history = _activeChatEntry.current().key.history()) { - history->setFakeUnreadWhileOpened(false); + const auto was = _activeChatEntry.current().key.history(); + const auto now = row.key.history(); + if (was && was != now) { + was->setFakeUnreadWhileOpened(false); } _activeChatEntry = row; - if (const auto history = row.key.history()) { - history->setFakeUnreadWhileOpened(true); + if (now) { + now->setFakeUnreadWhileOpened(true); } if (session().supportMode()) { pushToChatEntryHistory(row); From d5b8fc703e300495fb2f318612a873f3c87b6874 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Mar 2020 15:10:09 +0400 Subject: [PATCH 072/115] Fix Ctrl+[1-5] jump to pinned in folders. --- Telegram/SourceFiles/apiwrap.cpp | 2 +- Telegram/SourceFiles/data/data_session.cpp | 4 +--- Telegram/SourceFiles/data/data_session.h | 2 +- .../SourceFiles/dialogs/dialogs_inner_widget.cpp | 13 ++++++++++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 847d4a530..591f9f582 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1003,7 +1003,7 @@ void ApiWrap::requestPinnedDialogs(Data::Folder *folder) { result.match([&](const MTPDmessages_peerDialogs &data) { _session->data().processUsers(data.vusers()); _session->data().processChats(data.vchats()); - _session->data().clearPinnedChats(folder, FilterId()); + _session->data().clearPinnedChats(folder); _session->data().applyDialogs( folder, data.vmessages().v, diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 3720ac5f1..23613af95 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1557,8 +1557,6 @@ void Session::applyDialog( int Session::pinnedChatsCount( Data::Folder *folder, FilterId filterId) const { - Expects(!folder || !filterId); - if (!filterId) { return pinnedChatsOrder(folder, filterId).size(); } @@ -1591,7 +1589,7 @@ const std::vector &Session::pinnedChatsOrder( return list->pinned()->order(); } -void Session::clearPinnedChats(Data::Folder *folder, FilterId filterId) { +void Session::clearPinnedChats(Data::Folder *folder) { chatsList(folder)->pinned()->clear(); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index fecc1fc87..4b1eb9025 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -371,7 +371,7 @@ public: const Dialogs::Key &key, FilterId filterId, bool pinned); - void clearPinnedChats(Data::Folder *folder, FilterId filterId); + void clearPinnedChats(Data::Folder *folder); void applyPinnedChats( Data::Folder *folder, const QVector &list); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f8de2ed16..66de48b43 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2999,11 +2999,18 @@ void InnerWidget::setupShortcuts() { Command::ChatPinned4, Command::ChatPinned5, }; - auto &&pinned = ranges::view::zip(kPinned, ranges::view::ints(0, ranges::unreachable)); + auto &&pinned = ranges::view::zip( + kPinned, + ranges::view::ints(0, ranges::unreachable)); for (const auto [command, index] : pinned) { request->check(command) && request->handle([=, index = index] { - const auto list = session().data().chatsList()->indexed(); - const auto count = Dialogs::PinnedDialogsCount(_filterId, list); + const auto list = (_filterId + ? session().data().chatsFilters().chatsList(_filterId) + : session().data().chatsList() + )->indexed(); + const auto count = Dialogs::PinnedDialogsCount( + _filterId, + list); if (index >= count) { return false; } From 2c0b852dadfff98857114c097da8270de630c68d Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Mar 2020 15:27:07 +0400 Subject: [PATCH 073/115] Limit image size the app tries to read. This will prevent some OOM crashes. --- Telegram/SourceFiles/app.cpp | 38 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index dfc4bcedc..fb7bd3f78 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -56,23 +56,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #endif // OS_MAC_OLD namespace { - App::LaunchState _launchState = App::Launched; - HistoryView::Element *hoveredItem = nullptr, - *pressedItem = nullptr, - *hoveredLinkItem = nullptr, - *pressedLinkItem = nullptr, - *mousedItem = nullptr; +constexpr auto kImageAreaLimit = 6'016 * 3'384; - struct CornersPixmaps { - QPixmap p[4]; - }; - QVector corners; - using CornersMap = QMap; - CornersMap cornersMap; - QImage cornersMaskLarge[4], cornersMaskSmall[4]; +App::LaunchState _launchState = App::Launched; - int32 serviceImageCacheSize = 0; +HistoryView::Element *hoveredItem = nullptr, + *pressedItem = nullptr, + *hoveredLinkItem = nullptr, + *pressedLinkItem = nullptr, + *mousedItem = nullptr; + +struct CornersPixmaps { + QPixmap p[4]; +}; +QVector corners; +using CornersMap = QMap; +CornersMap cornersMap; +QImage cornersMaskLarge[4], cornersMaskSmall[4]; + +int32 serviceImageCacheSize = 0; } // namespace @@ -331,6 +334,13 @@ namespace App { reader.setAutoTransform(true); #endif // OS_MAC_OLD if (animated) *animated = reader.supportsAnimation() && reader.imageCount() > 1; + if (!reader.canRead()) { + return QImage(); + } + const auto imageSize = reader.size(); + if (imageSize.width() * imageSize.height() > kImageAreaLimit) { + return QImage(); + } QByteArray fmt = reader.format(); if (!fmt.isEmpty()) *format = fmt; if (!reader.read(&result)) { From 01c79f917e66312aa78fb1842e0511bb356faa11 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Mar 2020 16:59:46 +0400 Subject: [PATCH 074/115] Add limits on video frame size. Any video that starts streaming is limited to 4K. Any in-chat streaming is limited to full hd. Any GIF panel animation is limited to 720p. --- .../history/view/media/history_view_gif.cpp | 18 ++++++++++--- .../inline_bot_layout_internal.cpp | 25 ++++++++++++++++--- .../media/clip/media_clip_ffmpeg.cpp | 4 +++ .../streaming/media_streaming_video_track.cpp | 4 +++ 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index db3296bc1..3c73ff621 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -40,6 +40,7 @@ namespace { constexpr auto kMaxGifForwardedBarLines = 4; constexpr auto kUseNonBlurredThreshold = 240; +constexpr auto kMaxInlineArea = 1920 * 1080; int gifMaxStatusWidth(DocumentData *document) { auto result = st::normalFont->width(formatDownloadText(document->size, document->size)); @@ -47,6 +48,11 @@ int gifMaxStatusWidth(DocumentData *document) { return result; } +[[nodiscard]] bool CanPlayInline(not_null document) { + const auto dimensions = document->dimensions; + return dimensions.width() * dimensions.height() <= kMaxInlineArea; +} + } // namespace struct Gif::Streamed { @@ -261,7 +267,7 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms const auto selected = (selection == FullSelection); const auto autoPaused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any); const auto cornerDownload = downloadInCorner(); - const auto canBePlayed = _data->canBePlayed(); + const auto canBePlayed = _data->canBePlayed() && CanPlayInline(_data); const auto autoplay = autoplayEnabled() && canBePlayed; const auto activeRoundPlaying = activeRoundStreamed(); const auto startPlay = autoplay @@ -865,7 +871,7 @@ void Gif::drawGrouped( const auto autoPaused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any); const auto fullFeatured = fullFeaturedGrouped(sides); const auto cornerDownload = fullFeatured && downloadInCorner(); - const auto canBePlayed = _data->canBePlayed(); + const auto canBePlayed = _data->canBePlayed() && CanPlayInline(_data);; const auto autoplay = fullFeatured && autoplayEnabled() && canBePlayed; const auto startPlay = autoplay && !_streamed; if (startPlay) { @@ -1404,7 +1410,13 @@ void Gif::repaintStreamedContent() { } void Gif::streamingReady(::Media::Streaming::Information &&info) { - history()->owner().requestViewResize(_parent); + if (info.video.size.width() * info.video.size.height() + > kMaxInlineArea) { + _data->dimensions = info.video.size; + stopAnimation(); + } else { + history()->owner().requestViewResize(_parent); + } } void Gif::stopAnimation() { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 06dd1b0c8..34bc88d35 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -35,6 +35,13 @@ namespace internal { using TextState = HistoryView::TextState; +constexpr auto kMaxInlineArea = 1280 * 720; + +[[nodiscard]] bool CanPlayInline(not_null document) { + const auto dimensions = document->dimensions; + return dimensions.width() * dimensions.height() <= kMaxInlineArea; +} + FileBase::FileBase(not_null context, not_null result) : ItemBase(context, result) { } @@ -141,7 +148,10 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons document->automaticLoad(fileOrigin(), nullptr); bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading(); - if (loaded && !_gif && !_gif.isBad()) { + if (loaded + && !_gif + && !_gif.isBad() + && CanPlayInline(document)) { auto that = const_cast(this); that->_gif = Media::Clip::MakeReader(document, FullMsgId(), [that](Media::Clip::Notification notification) { that->clipCallback(notification); @@ -371,9 +381,16 @@ void Gif::clipCallback(Media::Clip::Notification notification) { _gif.setBad(); getShownDocument()->unload(); } else if (_gif->ready() && !_gif->started()) { - auto height = st::inlineMediaHeight; - auto frame = countFrameSize(); - _gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, RectPart::None); + if (_gif->width() * _gif->height() > kMaxInlineArea) { + getShownDocument()->dimensions = QSize( + _gif->width(), + _gif->height()); + unloadAnimation(); + } else { + auto height = st::inlineMediaHeight; + auto frame = countFrameSize(); + _gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, RectPart::None); + } } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) { unloadAnimation(); } diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp index e8fda438a..b6bd69426 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp @@ -17,6 +17,7 @@ namespace internal { namespace { constexpr auto kSkipInvalidDataPackets = 10; +constexpr auto kMaxInlineArea = 1280 * 720; // See https://github.com/telegramdesktop/tdesktop/issues/7225 constexpr auto kAlignImageBy = 64; @@ -59,6 +60,9 @@ ReaderImplementation::ReadResult FFMpegReaderImplementation::readNextFrame() { do { int res = avcodec_receive_frame(_codecContext, _frame.get()); if (res >= 0) { + if (_frame->width * _frame->height > kMaxInlineArea) { + return ReadResult::Error; + } processReadFrame(); return ReadResult::Success; } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp index b2cc6ff00..28d066e1e 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp @@ -14,6 +14,7 @@ namespace Media { namespace Streaming { namespace { +constexpr auto kMaxFrameArea = 3840 * 2160; // usual 4K constexpr auto kDisplaySkipped = crl::time(-1); constexpr auto kFinishedPosition = std::numeric_limits::max(); static_assert(kDisplaySkipped != kTimeUnknown); @@ -511,6 +512,9 @@ bool VideoTrackObject::tryReadFirstFrame(FFmpeg::Packet &&packet) { } bool VideoTrackObject::processFirstFrame() { + if (_stream.frame->width * _stream.frame->height >= kMaxFrameArea) { + return false; + } auto frame = ConvertFrame( _stream, _stream.frame.get(), From a7906f14ed5dbb3b5c17b1b3d3170c739356e848 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 23 Mar 2020 17:09:05 +0400 Subject: [PATCH 075/115] Update submodules. --- Telegram/lib_spellcheck | 2 +- Telegram/lib_ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index 25f44d999..6f9dbaa7b 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit 25f44d99915101e7a832cb38bd9b2cee47508c94 +Subproject commit 6f9dbaa7b29ead994a1f0738e82023af7892d106 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index eaab3f9a8..f497eac80 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit eaab3f9a8a5a1bc429449a9a40c0798f2203947b +Subproject commit f497eac804c596005a2ae41d0b28c55453b65db0 From 568325f201fbef3b3d7cfd599193406e5079297e Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Mar 2020 11:26:08 +0400 Subject: [PATCH 076/115] Show folders in settings async. --- .../SourceFiles/settings/settings_common.cpp | 28 +++++++++------ .../SourceFiles/settings/settings_common.h | 6 ++++ .../SourceFiles/settings/settings_main.cpp | 35 +++++++++++++++---- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index 6acf33b3d..6f7476fc2 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -80,21 +80,19 @@ void AddDividerText( st::settingsDividerLabelPadding)); } -not_null AddButton( - not_null container, +object_ptr